- Add idempotent migrations for `is_public`, `bypass_paths`, and `allowed_cidrs` in `server/db.ts`. - Update `AppRecord` and `getAppByHost` in `server/auth-session.ts` to cache bypass rules in Valkey. - Implement native Deno, fast-path prefix (`isPathBypassed`) and CIDR matchers (`isIpAllowed`). - Update `GET /api/forward-auth` to evaluate dynamic rules and properly return 302/403 for unregistered domains. - Create `ui/components/UnregisteredAppPage.tsx` SSR view for browser fallbacks. - Update `AdminAppsPage.tsx` to handle the new ingress settings visually and post to `/api/admin/apps`. - Add `POST /api/guests/sandbox` to generate ephemeral Valkey guest sessions. - Update `POST /api/register/verify` to detect `upgrade_session` and promote guests to full users in-flight. - Add `docs/TIER1_INGRESS_SPEC.md`. - Ensure tests run cleanly and add comprehensive unit test cases for the bypass matrix. Co-authored-by: mrteye <1945243+mrteye@users.noreply.github.com>
146 lines
7.1 KiB
Markdown
146 lines
7.1 KiB
Markdown
# TASK METADATA
|
|
|
|
- **Target Files:** `server/db.ts`, `server/auth-session.ts`, `server/main.ts`,
|
|
`server/main.test.ts`, `ui/components/AdminAppsPage.tsx`, `ui/mod.ts`,
|
|
`infra/setup.ts`, `docs/TIER1_INGRESS_SPEC.md`
|
|
- **Core Objective:** Implement Tier 1 Universal Global Edge Ingress Protection
|
|
on Traefik with dynamic Valkey-cached bypass matrix, self-exemption, CIDR
|
|
allowlisting, unregistered app SSR fallback, and ephemeral guest sandboxes.
|
|
- **Dependencies:** PostgreSQL `apps` schema, Valkey 8 cache,
|
|
`docs/FORWARDAUTH_REDIRECT_SPEC.md`.
|
|
- **Additional Important Notes:** Must guarantee $<30\mu s$ edge lookups via
|
|
Valkey L1/L2 caching and prevent infinite redirect deadlocks on `auth-api` and
|
|
ACME challenge routers.
|
|
|
|
---
|
|
|
|
### 1. TASK METADATA
|
|
|
|
(Defined in the header block above)
|
|
|
|
### 2. Architectural Considerations & Risks
|
|
|
|
- **Risks & Vulnerabilities:**
|
|
- **Recursive Deadlocks (Self-Exemption):** Applying ForwardAuth globally at
|
|
`entryPoints.websecure.http.middlewares` creates an infinite loop if
|
|
`auth-api` intercepts its own login/verification endpoints. `auth.atyg.org`
|
|
and `/.well-known/acme-challenge/*` must be explicitly exempt.
|
|
- **Edge Latency Regressions:** Querying PostgreSQL on every incoming
|
|
ForwardAuth request for dynamic bypass rules would bottleneck cluster
|
|
throughput. All `is_public`, `bypass_paths`, and `allowed_cidrs` fields must
|
|
be cached inside the Valkey `auth:app_by_host:<host>` key.
|
|
- **ReDoS in Path Matching:** Evaluating wildcard bypasses using unconstrained
|
|
regular expressions could allow malicious paths to trigger catastrophic
|
|
backtracking. Path matching must use fast, deterministic prefix and glob
|
|
matching.
|
|
- **Open Redirects on Unregistered Apps:** When routing unregistered domains
|
|
to SSR error views, destination parameters must be strictly validated
|
|
against approved hosts to prevent phishing vectors.
|
|
|
|
- **Alternatives & Architecture Decisions:**
|
|
- **Valkey App Caching over Traefik HTTP Dynamic Provider:** Evaluating
|
|
dynamic bypass paths inside `auth-api` via Valkey caches eliminates Traefik
|
|
config file churn, provider polling overhead, and proxy reload downtime.
|
|
- **Defense-in-Depth Universal Bypasses:**
|
|
- _Traefik Level:_ `/.well-known/acme-challenge/*` and global `/healthz`
|
|
bypass ForwardAuth entirely at the edge router level to avoid certificate
|
|
renewal failures during container restarts.
|
|
- _Auth-API Level:_ Application-specific paths (`/api/public/*`,
|
|
`raw/branch/main/sdk/*`, `/webhooks/*`) and CIDR subnets are evaluated in
|
|
`auth-api` with zero network overhead.
|
|
- **Dual-Response Protocol Compliance:** Unregistered domains receive an HTTP
|
|
`302 Redirect` to `https://auth.atyg.org/errors/unregistered?host=...` for
|
|
browsers (`Accept: text/html`) and a clean HTTP `403 Forbidden`
|
|
(`{"error": "Application not registered"}`) for APIs/cURL.
|
|
- **Ephemeral Guest Sandboxes (Use Case 11):** Ephemeral sessions are held in
|
|
Valkey with `account_status: "guest"` and promoted in-flight to full users
|
|
upon passkey enrollment without losing UUID or state.
|
|
|
|
### 3. Proposed Implementation
|
|
|
|
#### Phase 1: Database Schema & Valkey Caching Upgrades
|
|
|
|
1. **Database Migration (`server/db.ts`):**
|
|
- Add idempotent migrations inside `initDb()`:
|
|
```sql
|
|
ALTER TABLE apps ADD COLUMN IF NOT EXISTS is_public BOOLEAN DEFAULT FALSE;
|
|
ALTER TABLE apps ADD COLUMN IF NOT EXISTS bypass_paths TEXT[] DEFAULT '{}';
|
|
ALTER TABLE apps ADD COLUMN IF NOT EXISTS allowed_cidrs TEXT[] DEFAULT '{}';
|
|
```
|
|
2. **Valkey App Cache Hydration (`server/auth-session.ts`):**
|
|
- Update `getAppByHost(host)` to select
|
|
`id, name, domain, is_public, bypass_paths, allowed_cidrs`.
|
|
- Cache the complete application metadata in `auth:app_by_host:<host>` with
|
|
1-hour TTL.
|
|
|
|
#### Phase 2: Dynamic Bypass Evaluator & ForwardAuth Hardening
|
|
|
|
1. **Path & CIDR Matcher Helper (`server/auth-session.ts`):**
|
|
- Implement
|
|
`isPathBypassed(requestPath: string, bypassPaths: string[]): boolean` using
|
|
deterministic prefix matching (`path.startsWith(...)` and `/*` wildcard
|
|
support).
|
|
- Implement `isIpAllowed(clientIp: string, allowedCidrs: string[]): boolean`
|
|
for subnet validation.
|
|
2. **Update `GET /api/forward-auth` (`server/main.ts`):**
|
|
- **Step 1: Unregistered App Check:** If `appRecord` is null:
|
|
- If `Accept: text/html`: Return `302 Redirect` to
|
|
`https://${loginDomain}/errors/unregistered?host=${encodeURIComponent(host)}`.
|
|
- Else: Return `403 Forbidden` (`{"error": "Application not registered"}`).
|
|
- **Step 2: Dynamic Bypass Check:**
|
|
- If `appRecord.is_public === true`, or
|
|
`isPathBypassed(uri, appRecord.bypass_paths)`, or
|
|
`isIpAllowed(clientIp, appRecord.allowed_cidrs)`:
|
|
- Immediately return `200 OK` (bypassing session checks).
|
|
- **Step 3: RFC 9421 & Session Checks:** Proceed with existing HTTP
|
|
Signatures and WebAuthn session validation.
|
|
|
|
#### Phase 3: Unregistered Application SSR Error Page & Admin UI
|
|
|
|
1. **Create SSR Error View (`ui/components/UnregisteredAppPage.tsx`):**
|
|
- Pure Hono SSR JSX page explaining the domain is not registered in the
|
|
Auth-Yes catalog, with a direct 1-click button for Global Admins:
|
|
_"Register Application in IAM Console"_.
|
|
2. **Update Route in `ui/mod.ts`:**
|
|
- Mount `GET /errors/unregistered` rendering `UnregisteredAppPage`.
|
|
3. **Management Console Updates (`ui/components/AdminAppsPage.tsx` &
|
|
`server/main.ts`):**
|
|
- Expose `is_public`, `bypass_paths`, and `allowed_cidrs` in
|
|
`POST /api/admin/apps` and `PUT /api/admin/apps/:id`.
|
|
- Add toggle switches and input fields to the Admin Web UI.
|
|
|
|
#### Phase 4: Ephemeral Guest Sandboxes (Use Case 11)
|
|
|
|
1. **Guest Endpoint (`server/main.ts`):**
|
|
- `POST /api/guests/sandbox`: Generates a guest `sessionId`, stores
|
|
`{ uuid: guestUuid, username: "guest-<id>", account_status: "guest" }` in
|
|
Valkey with 2-hour TTL, and returns the session cookie.
|
|
2. **In-Flight Passkey Promotion (`server/main.ts`):**
|
|
- Update `/api/register/verify` to detect `upgrade_session` parameter,
|
|
creating the PostgreSQL user with the existing `guestUuid` and changing
|
|
status to `active`.
|
|
|
|
#### Phase 5: Documentation & Traefik Compose Guides
|
|
|
|
1. **Author `docs/TIER1_INGRESS_SPEC.md`:**
|
|
- Document static/dynamic Traefik configurations for
|
|
`entryPoints.websecure.http.middlewares=auth-forward@docker` with
|
|
router-level self-exemption overrides for `auth.atyg.org`.
|
|
|
|
### 4. Testing & Verification Matrix
|
|
|
|
- [ ] **Unit Tests (`server/main.test.ts`):**
|
|
- Verify `GET /api/forward-auth` returns `200 OK` when
|
|
`appRecord.is_public === true`.
|
|
- Verify `GET /api/forward-auth` returns `200 OK` for paths matching
|
|
`bypass_paths` (e.g. `/api/public/ping`).
|
|
- Verify `GET /api/forward-auth` returns `302 Redirect` to
|
|
`/errors/unregistered` for unauthenticated browser requests to unregistered
|
|
domains.
|
|
- Verify `GET /api/forward-auth` returns `403 Forbidden` for API requests to
|
|
unregistered domains.
|
|
- Verify guest session generation and in-flight passkey promotion.
|
|
- [ ] **Quality Gates:**
|
|
- `deno fmt`, `deno task lint`, `deno task check`, `deno task test` (100%
|
|
pass).
|