Complete production-readiness roadmap: security blockers, standalone binaries, hardening, quality & ops - #2
Merged
Conversation
- policy: ** glob now requires a path-segment boundary so /api/** no longer matches /api-internal or /apiv2 (matcher regression tests added) - policy: max_age fails closed — tokens without auth_time are denied by policies that require auth freshness - stepup: guard re-evaluates policy against the saved path before replaying a step-up cookie, drives StateMachine Complete/Fail, and only replays flows that reach StateCompleted (bypass regression test) - dpop: real RFC 9449 key binding — cnf.jkt (RFC 7638 thumbprint) verified against the proof JWK after introspection, ath is mandatory, jti replay cache reuses token.Cache, optional server nonce (RFC 9449 §8) - revocation: jti → token-hash secondary index so JTI revocation evicts the real cache entry; revoke-all/session eviction is per-subject and per-session instead of flushing the shared cache - introspection: map aud/jti/cnf into CommonClaims, send token_type_hint - cache: clamp TTL to remaining token lifetime, never cache expired tokens Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016G5EUFGBr3WGXbLV971QQZ
- cmd/iam-service: full gateway wiring from env vars (IAM_ADDR, IAM_REALM, IAM_POLICY_FILE, IAM_UPSTREAM_URL, IAM_OIDC_*, IAM_LOG_FORMAT plus admin, webhook, cookie, and DPoP options); auto-starts the in-process LocalAS with demo tokens when IAM_OIDC_DISCOVERY_URL is unset; echo mode when no upstream is configured; graceful shutdown on SIGINT/SIGTERM - cmd/iam-cli: policy-check (dry-run via simulator, non-zero exit on deny), token (signed test JWTs via tokenfactory), introspect (RFC 7662 client) - tenant: add StaticResolver for explicit single-tenant defaults - tests/integration: binary boot smoke test (build, /health, RFC 9470 challenge, SIGTERM graceful exit) - Makefile: make service / make cli now run the new binaries Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016G5EUFGBr3WGXbLV971QQZ
…iring, fail-closed tenants - jwt: JWTValidator now enforces WithValidMethods (asymmetric algs only by default) and optional WithIssuer/WithAudience via ExpectedIssuer / ExpectedAudience config - gateway: assert token issuer matches the resolved tenant's provider issuer after introspection (cross-tenant token reuse blocked) - gateway: strip client-sent X-Tenant-ID and X-Iam-* headers before proxying; re-inject X-Iam-Subject/Tenant/Acr/Scopes from verified claims - gateway: FAPI 2.0 profile enforcement wired via GuardConfig.FAPI; CommonClaims implements fapi.TokenClaims (HasDPoP via cnf.jkt, HasPARRequestURI, GetAuthAge, GetNonce); IAM_ENABLE_FAPI env in service - tenant: resolution now fails closed — no implicit "default" fallback; single-tenant deployments opt in with StaticResolver; HeaderResolver documented as trusted-edge-only - stepup: BeginChallenge generates the CSRF StateID into the signed cookie - guard: pass authorization_details through to policy evaluation Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016G5EUFGBr3WGXbLV971QQZ
…overage ≥80% - gateway: per-client-IP token-bucket rate limiter (RateLimitRPS/Burst in GuardConfig, IAM_RATE_LIMIT_RPS env), 429 + Retry-After when exceeded - token: coverage 56% → 81% — introspector round-trip (incl. aud string/array and cnf.jkt mapping, token_type_hint assertion), CachedIntrospector hit/ revoke/expired-clamp, RedisCache round-trip with fake client, revocation index JSON round-trip, claims helpers - token: cache-hit benchmark (~510ns/op, 2 allocs) - goredis: real-Redis integration test, opt-in via REDIS_ADDR - ci: GitHub Actions workflow — build, vet, race tests with Redis service container, coverage artifact, golangci-lint - roadmap: mark all four milestones complete - gofmt across the repo Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016G5EUFGBr3WGXbLV971QQZ
Security re-review flagged that the cross-tenant issuer binding silently skipped when the RFC 7662 response carried no iss (the field is OPTIONAL per §2.2), letting a token from a shared/foreign AS be accepted under the wrong tenant. When the provider declares an issuer, an introspection response without iss is now rejected; regression test added with a stub provider returning active claims without iss. Also compare the admin bearer token in constant time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016G5EUFGBr3WGXbLV971QQZ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements all four milestones from
ROADMAP.md, taking the gateway from "feature-complete library" to production-ready. Full suite passes withgo test ./... -race; a follow-up IAM security review of the branch confirmed all previously known Critical/High findings are fixed (and its one new High finding is fixed in the last commit).M1 — Security blockers (
3fca194)StateMachine.Complete()/Fail(), and only replays flows that reachStateCompleted. Regression test proves a bronze token can no longer reach a silver-protected resource via the cookie.cnf.jktmapped from introspection and verified against the proof JWK's RFC 7638 thumbprint (RFC test vector covered);athis mandatory;jtireplay cache reusestoken.Cache; optional server-issued nonce (DPoP-Nonce+error="use_dpop_nonce").jti → token-hashsecondary index so JTI revocation evicts the real cache entry; revoke-all/session revocation is per-subject/per-session instead ofFlush()on the shared cache.**glob requires a path-segment boundary (/api/**no longer matches/api-internal);max_agefails closed for tokens withoutauth_time.M2 — Standalone binaries (
38edd51)cmd/iam-service: full env-driven wiring; auto-starts in-process LocalAS with demo tokens whenIAM_OIDC_DISCOVERY_URLis unset; echo mode without upstream; graceful shutdown.cmd/iam-cli:policy-check(non-zero exit on deny),token,introspect./health, RFC 9470 challenge, SIGTERM).M3 — Hardening & RFC gaps (
c45ffaa)WithValidMethods(asymmetric-only by default) plus optional issuer/audience checks.X-Tenant-ID/X-Iam-*stripped; verifiedX-Iam-Subject/Tenant/Acr/Scopesre-injected.StaticResolver.GuardConfig.FAPI,IAM_ENABLE_FAPI); cache TTL clamped to remaining token lifetime; CSRFStateIDgenerated into the signed step-up cookie.M4 — Quality & ops (
941deb4)Retry-After).pkg/core/tokencoverage 56% → 81%; cache-hit benchmark (~510ns/op).REDIS_ADDR), run in CI via a Redis service container.Security re-review follow-up (
01a879b)iss(the field is optional per §2.2, but skipping the check allowed cross-tenant token reuse against ASes that omit it). Regression test added.Testing
go test ./... -racegreen across all packages (incl. Redis integration against a realredis-server).issfail-closed, rate limiter, binary boot smoke test.🤖 Generated with Claude Code
https://claude.ai/code/session_016G5EUFGBr3WGXbLV971QQZ
Generated by Claude Code