mirror of
https://github.com/hanzoai/gateway.git
synced 2026-10-30 15:04:44 +00:00
Every hk- in the docs, the widget refusal messages and the fixture tokens named a shape that authenticates nothing. Cloud accepts pk- and sk- and nothing else, IAM mints sk-, and no generator in the fleet still produces hk- — so a reader who copied one of these examples got a 401 and no way to see why. The predicate keeps it. authz.IsAPIKey still matches hk-, and should: those keys are still in circulation and have to reach the backend that refuses them rather than be parsed here as a JWT. TestIsAPIKey pins that row, and the two comments annotating the predicate now mark the shape retired rather than list it alongside the current ones. Where the hk- row sat in the forged-header and widget-key tables, pk- takes its place. sk- was already covered in both, and pk- — the shape that ships in a browser bundle — is the one neither table had ever exercised. Verified: make test, 489 pass / 0 fail, including all four param_forwarding fixtures whose bearer changed. Co-authored-by: Hanzo Dev <[email protected]>
6.9 KiB
6.9 KiB
gateway — horizontal-scale audit
Branch: feat/edge-scale-cleanup. Coordinates with feat/hip-0110-gateway-edge-process (parallel agent, WIP — see "Blocker" below).
Checklist
| Item | Status | Evidence / fix |
|---|---|---|
| No persistent state. Connection state only. | PASS | auth_middleware.go keeps a per-process JWKS cache (newJWKSCache, 5min TTL) and a per-process billing checker; both are recent-fetch memoizations, not authoritative state. No on-disk database; no session store. JWKS cache misses fall back to JWKS fetch — replicas converge independently. |
| No in-memory session cache (JWT is the session) | PASS | No session store. The JWKS cache holds signing keys, not user sessions — its eviction has no effect on which user is logged in. Verified by absence of any session/cookie storage primitive in *.go. |
| No per-replica DB connection | PASS | No database/sql import in the gateway repo. The billing checker is HTTP-only against commerce. ZAP is binary-RPC, also stateless on the gateway side. |
| Graceful shutdown drains in-flight requests | PASS | After HIP-0110 the canonical entrypoint is the zip-based cmd/gateway/main.go, which wraps the public + health listeners in signal.NotifyContext(SIGINT, SIGTERM) and drains them via app.ShutdownWithContext(drainCtx) under a deadline. The drain deadline is now operator-configurable via GATEWAY_SHUTDOWN_TIMEOUT (any time.ParseDuration string; defaults to 30s; falls back to the default on empty / malformed / non-positive values). Helper covered by cmd/gateway/main_test.go::TestShutdownGraceFromEnv. The legacy path (cmd/gateway/main_legacy.go, //go:build legacy) remains untouched per the original audit note. |
| Routes config loadable from KMS (no plaintext routes secret on disk in prod) | PASS | router_engine.go::loadRoutesFromEnv now resolves GATEWAY_ROUTES_KMS_PATH through a KMSResolver interface defined in router_kms.go. Default resolver is a noop; the HTTP resolver activates automatically when GATEWAY_KMS_ENDPOINT + IAM_CLIENT_ID/SECRET (or the GATEWAY_KMS_* overrides) are set, speaking the same Universal-Auth + /api/v3/secrets/raw/<path> contract as hanzoai/base/plugins/platform/kms.go. File-based loader path (GATEWAY_ROUTES_FILE → ./routes.yaml) is unchanged and remains the fallback. Covered by router_engine_kms_test.go (resolver injection, error surfacing, malformed-YAML guard, file fallback, HTTP round-trip stub). |
| All identity headers stripped from incoming external request before middleware runs | PASS | stripIdentityHeaders (auth_middleware.go:764) deletes X-User-Id, X-Org-Id, X-Roles, X-User-Permissions, X-User-Email, X-Phone-Number, X-User-IsAdmin, plus legacy X-User-Role, X-Tenant-Id/ID, X-Org, X-IAM-, X-HANZO- prefixes. Called unconditionally at the top of the middleware on EVERY path including bypass paths (auth_middleware.go:526, 545). Strip-list ⊇ write-list contract enforced by TestStripList_CoversAllWrittenHeaders and TestStripIdentityHeaders_AllVariants. |
| Auth middleware writes fresh identity headers from JWT and ONLY from JWT | PASS | After stripIdentityHeaders, only the JWT validation path (or the configured allowlists for opaque API keys like sk-, pk-, fw_, hz_) sets the headers. The X-User-Permissions bit-field derivation is documented in LLM.md and lives in one place (auth_middleware.go around line 797). |
| Forwards via ZAP to cloud. JSON only at client↔gateway boundary. | PASS | ZAP-HTTP backend transport (zap_backend.go) wraps RoundTripper and routes any backend whose host:port matches INGRESS_ZAP_BACKENDS over github.com/zap-proto/http. JSON sits at the client edge (the legacy engine parses incoming JSON, encodes downstream); ZAP carries the inter-service hop. |
| Reverse-push channel from base via ZAP is wired | DEFER | Out of scope for this branch. The ZAP listener (zap_listener.go) exists for incoming binary calls. Reverse-push (base → gateway → client SSE) is the HIP-0110 deliverable; left to the parallel agent. |
| Memory < 64 MiB idle, < 200 MiB at 10k in-flight conns | UNVERIFIED → see docs/MEMORY_PROFILE.md | Gateway repo currently does not compile on this branch (vendor/go.mod mismatch from commit 56397a4 — replace directives dropped, vendor not refreshed). The audit ships the pprof harness + the procedure; the measurement runs after the build is unbroken. |
| No cgo. Static-linkable binary. | PASS | grep -rn "^import \"C\"|cgo" *.go cmd/ returns empty. Dockerfile builds CGO_ENABLED=0-able alpine binary. |
| One replica = same behavior as N replicas | PASS | Stateless apart from per-process JWKS + billing caches (each a memoization with TTL; cache misses converge). No leader election. No cross-replica coordination. ZAP RPC outbound is per-process. The breaker primitive (new in hanzoai/zip/middleware/breaker.go) is also per-process by design. |
Memory profile
See docs/MEMORY_PROFILE.md for the harness. The harness is committed; the measured numbers update once the gateway build is restored.
Blocker
feat/hip-0110-gateway-edge-process is the parallel agent's branch — it is restructuring the gateway from a legacy-engine-resident process into a thin edge binary. Two changes intersect this audit:
cmd/gateway/main.go→cmd/gateway/main_legacy.gorename in the HIP-0110 branch.- New
middleware/auth.gopackage withgateway.SetGatewayWritten/gateway.AssertGatewayWrittenmarkers — these symbols are NOT yet defined in the gateway package, so the HIP-0110 branch does not build today.
Recommendation: let HIP-0110 land first. The breaker primitive in zip is the only piece of this audit's source-level scope that gateway will need to consume; it ships independently in hanzoai/zip and will be importable when HIP-0110 wires it into the gateway's ZAP client.
Things deliberately NOT done in this branch
- The strip-list and write-list audits — these are already covered by
auth_middleware_security_test.go::TestStripList_CoversAllWrittenHeadersand::TestStripIdentityHeaders_AllVariants. Re-asserting them here would be duplication, not coverage. - Breaker integration in the ZAP client — gateway does not yet have an importable
hanzoai/zip(build broken). Once HIP-0110 lands and the build is green, integration is a one-line wrap around the*zaphttp.Transportper-backend. - Adding
GracefulShutdownwrap aroundcmd.Execute— upstream cobra-helper ownership; touching it from this branch collides with HIP-0110 work.
Update — post HIP-0110
- Graceful shutdown row moved PARTIAL → PASS now that the zip-based
cmd/gateway/main.goowns lifecycle andGATEWAY_SHUTDOWN_TIMEOUTis configurable. - KMS routes loader row added (PASS) — the
// TODO: KMS integrationinrouter_engine.go::loadRoutesFromEnvis replaced by theKMSResolverinterface inrouter_kms.gowith HTTP impl and stubbed tests. No behavior change whenGATEWAY_ROUTES_KMS_PATHis unset.