V1.2 tier g#57
Merged
Merged
Conversation
Introduce a pluggable secrets resolver that lets operators store sensitive config values (HMAC keys, cache passwords, OAuth client secrets) in external backends and reference them via URI syntax: secret: vault://secret/data/lwauth/hmac-keys#svc-a Components: - pkg/secrets: core Resolver with TTL cache, Ref parser, Backend interface, and scheme-based registry - pkg/secrets/vault: HashiCorp Vault KV v2 adapter supporting static token, Kubernetes auth, and AppRole auth - internal/config: integration into Compile() to resolve secretRefs in identifier, authorizer, and mutator configs at startup - docs/operations/vault-local-setup.md: local kind + Vault runbook
All 17 module factories (7 identity, 6 authz, 4 mutator) now reject
unrecognized config keys at startup with ErrConfig, preventing silent
misconfiguration that could degrade security posture.
Root cause: the RBAC authorizer silently ignored 'defaultAllow: true'
(a non-existent field), leaving an empty allow-set that denied every
request with 403. The same silent-ignore pattern existed in all other
module factories.
Changes:
- pkg/module: add CheckUnknownKeys() shared helper
- pkg/authz/{rbac,cel,opa,composite,openfga,spicedb}: add key validation
- pkg/identity/{apikey,hmac,jwt,oauth2,introspection,mtls,dpop}: add key validation
- pkg/mutator/{headers,jwtissue}: add key validation
- All packages: add TestXxx_RejectsUnknownConfigKey tests
- pkg/identity/apikey: accept 'header' as alias for 'headerName'
…books - DPoP: add pinned key verification (cnf claim JWK thumbprint matching) - DPoP: rotatable identifier with key rotation lifecycle tracking - DPoP: comprehensive unit tests for pinned key flows - Introspection: expose token_type in identity attributes - Server: fix header forwarding in authorize endpoint - Design: add G19 STATE-PERSIST-1 (persistent storage) to Tier G - Deploy: add Kind cluster E2E config and test scripts - Docs: operational runbooks for DPoP, API key, HMAC, and mTLS revocation
…ngletons Factory & Abstract Factory: - pkg/module: generic Registry[T] replacing duplicated map+mutex code - pkg/module: DecodeConfig() helper for struct-tag-driven config parsing - pkg/module: DecoratedRegistry[T] with composable decorator chains - pkg/module: unit tests for registry, decode, and decorator - internal/config: PipelineBuilder with fluent API and swappable registries - Refactored apikey, mtls, cel factories to use DecodeConfig Adapter Pattern: - pkg/authz/spicedb: PermissionChecker interface decoupling from SDK - pkg/authz/openfga: uses shared HTTP client pool via connpool - pkg/connpool: reusable gRPC connection pool (same pattern as plugin/grpc) Singleton Pattern: - pkg/connpool: process-wide Valkey client pool (eliminates connection churn on config reload, shares across cache + revocation subsystems) - pkg/connpool: shared HTTP client pool keyed by base URL - pkg/observability: unified Facade singleton (metrics + audit + tracing) - Refactored cache/valkey and revocation to use connpool.GetValkey()
Decorator Pattern: - pkg/module/timeout.go: WithIdentifierTimeout, WithAuthorizerTimeout, WithMutatorTimeout — per-module deadline enforcement decorators - pkg/module/tracing.go: WithIdentifierTracing, WithAuthorizerTracing, WithMutatorTracing — composable OTel span decorators; also WithIdentifierMetrics, WithAuthorizerMetrics for per-call latency - pkg/module/shadow.go: WithShadowAuthorizer — runs shadow policy alongside prod and reports disagreements via callback - pkg/module/breaker.go: WithIdentifierBreaker, WithAuthorizerBreaker, WithMutatorBreaker — wraps modules with upstream.Guard circuit breaker Worker Pool Pattern: - pkg/revocation/parallel.go: ParallelChecker with bounded errgroup for concurrent revocation key checks (eliminates sequential round-trips) - internal/pipeline/engine.go: identifyAllMust() rewrites AllMust mode to fan out all identifiers concurrently via errgroup with config-order claim merge (first-writer-wins preserved) - internal/pipeline/engine.go: checkRevocation() now uses ParallelChecker - pkg/keyrotation/reaper.go: background worker goroutine that prunes retired keys on a ticker and emits state transition callbacks Tests: - pkg/module/timeout_test.go: timeout decorator unit tests - pkg/revocation/parallel_test.go: parallel checker unit tests - pkg/keyrotation/reaper_test.go: reaper prune + transition tests (channel-based sync to avoid timing flakes under load)
- README.md: updated repo layout with connpool, decorator refs, parallel checker, reaper, and observability facade - docs/ARCHITECTURE.md: rewritten plugin registry section with generic Registry[T] and full decorator table; updated concurrency model for AllMust errgroup fan-out and ParallelChecker; added connection pool singleton section; updated pipeline contract with identifier modes - pkg/module/README.md: new — documents interfaces, generic registry, DecoratedRegistry, all decorator types (timeout, tracing, metrics, breaker, shadow), and DecodeConfig helper - pkg/revocation/README.md: added ParallelChecker docs and usage example - pkg/keyrotation/README.md: added background Reaper section with config table and usage example - pkg/connpool/README.md: new — documents Valkey, HTTP, gRPC pools with usage examples and testing guidance
- federation/server: fix send-on-closed-channel race in Subscribe() Capture latest snapshot before releasing subscribersMu; send initial snapshot while lock is held so concurrent Unsubscribe cannot close the channel between registration and the initial send (CH-01) - plugin/supervisor: fix killTimer data race in runOnce() Add killTimerMu sync.Mutex to synchronize reads/writes of killTimer between termination goroutines and the cmd.Wait() path; keeps single-invocation guarantee via existing sync.Once (CH-02) - observability/audit: make AsyncSink.Close() idempotent Wrap close(a.ch) in sync.Once to prevent close-of-closed-channel panic on repeated shutdown calls (CH-03) - cache/decision: pass context through singleflight fn signature Propagate context.WithoutCancel to prevent first-caller cancellation from failing all coalesced waiters; release distSF lock on error paths - cache/distsf: final L2 check before returning ErrDistSFLost Prevents spurious lost-wake races when winner writes between last poll and deadline expiry - pipeline/engine: add Engine.Close(), fix canary header lookup Close() stops the rate-limiter reaper on engine swap; shouldCanary now uses strings.ToLower for case-insensitive header matching - server: Swap() closes old engine after 30s grace period Prevents rate-limiter goroutine leak on hot-reload - server/http: dynamic dispatch closure for module HTTP mounts Ensures hot-reload picks up new module instances without rebuilding mux - connpool: length-prefix hash fields to prevent null-byte collisions Fixes key confusion across Valkey, gRPC, and HTTP pool configs; HTTP pool key now includes Timeout field - identity/introspection: singleflight context isolation + claims copy Use context.WithoutCancel inside sf.Do; deep-copy claims map before returning to prevent concurrent mutation across waiters - identity/oauth2: singleflight refresh coalescing (RC-01) Add refreshSF singleflight.Group to coalesce concurrent refresh-token exchanges, preventing IdP reuse-detection revocation - identity/oauth2: enforce RFC-correct HTTP methods on flow endpoints /oauth2/start GET-only, /oauth2/callback GET-only, /oauth2/refresh POST-only, /oauth2/userinfo GET+POST, /oauth2/device/start POST-only - module/decorator: synchronize decorator slice reads/writes Add sync.RWMutex to DecoratedRegistry to prevent data race between AddDecorator and concurrent Build calls - module/decode_config: reject unknown fields in nested structs Switch to json.NewDecoder + DisallowUnknownFields to catch typos that top-level field check cannot see - plugin/wasm: nil-guard Identity before field access Prevent nil-dereference panic in Authorizer and Mutator when called with a nil Identity (unauthenticated path) - session/memory: return deep copy from Load() Prevents concurrent Claims map mutation across callers sharing the same session pointer
G2 — ADMIN-RBAC-1: Validating admission webhook for AuthConfig mutations - api/crd/v1alpha1: add PolicyBinding CRD type (User/Group/ServiceAccount subjects, create/update/delete/* verbs, optional resourceNames scoping) - internal/webhook: admission webhook handler, TLS server (:9443), PolicyResolver interface with InformerResolver (production) and StaticResolver (testing) - Fail-open semantics: no bindings → allow; resolver error → allow - Helm: ValidatingWebhookConfiguration, Service, cert-manager Certificate, PolicyBinding CRD definition, all gated by .Values.webhook.enabled - 11 unit tests covering subject matching, verb/resource restrictions, fail-open, method guard, wildcard verbs G3 — DATA-RES-1: PII redaction and data-residency audit routing - internal/config: add AuditSpec with RedactionSpec (per-field hash/drop) and DataResidencySpec (region tag) to AuthConfig - pkg/observability/audit: RedactingSink applies HMAC-SHA-256 hashing or value dropping per field; shallow-copies events to preserve originals - pkg/observability/audit: RegionRoutingSink routes events to region-specific sinks by tenant→region mapping with fallback - pkg/observability/audit: TenantAwareSink dynamically applies per-tenant redaction rules as AuthConfigs reconcile (RWMutex-safe) - 18 new tests: drop/hash/deterministic hashing/key isolation/concurrent access/region routing/fallback/cross-cutting integration docs/DESIGN.md: mark G1, G2, G3 as done
Add lwauthctl compliance --config FILE --framework NAME that inspects an AuthConfig YAML offline and emits a JSON compliance evidence report. Supported frameworks: SOC 2 Type II, ISO 27001:2022, PCI DSS 4.0, HIPAA Security Rule (45 CFR 164), FedRAMP Rev 5 (NIST 800-53). Each framework maps 10 controls to automated checks that inspect: - Identifier configuration (authentication coverage) - Authorizer configuration (least privilege, no defaultAllow) - Rate limiting (boundary protection) - mTLS identifiers (encryption in transit) - Credential revocation (real-time compromise response) - Audit configuration and PII redaction (GDPR, monitoring) - Policy versioning (change management evidence) - External secret backends (no plaintext credentials) - Key rotation settings (authenticator lifecycle) - Data residency (regional audit retention) Report includes per-control pass/fail/warn status with human-readable evidence strings, summary counts, and exits non-zero on any failure for CI integration. 11 unit tests: fully-configured pass, empty-config failures, all 5 frameworks, JSON roundtrip, defaultAllow detection, PII redaction, revocation checks. docs/DESIGN.md: mark G4 as done
G5 — ID-MFA-1: Adds OIDC assurance claims as first-class policy inputs
and a step-up challenge response path for insufficient authentication.
pkg/module: extend Identity with ACR (string) and AMR ([]string) fields
Populated from standard OIDC claims; available to all authorizers.
pkg/module: extend Decision with StepUp (*StepUpChallenge)
Carries RequiredACR, RequiredAMR, and MaxAge for challenge responses.
Pipeline and all three server doors (HTTP, gRPC, native) already
forward ResponseHeaders on deny — no pipeline changes needed.
pkg/identity/jwt: auto-extract acr/amr from JWT claims
extractACR reads string claim, extractAMR handles []string, []any,
and single string formats per RFC 8176.
pkg/authz/assurance: new 'assurance' authorizer module
Rule-based matching: each rule has optional match predicates
(HTTP methods, path globs with /** support) and a requirement
(required ACR values, required AMR methods, max auth age).
First matching rule wins. On mismatch, returns 401 with:
- StepUp challenge struct for programmatic consumers
- WWW-Authenticate header per RFC 6750 §3 with acr_values
and max_age parameters for IdP-driven step-up flows
No-match = allow (no assurance constraint).
pkg/builtins: register 'assurance' authorizer type
15 unit tests: ACR match/mismatch, AMR subset checking, method/path
predicates, combined predicates, multi-rule first-match, nil identity,
WWW-Authenticate header format, multiple acceptable ACR values, MaxAge
propagation, factory parsing, config validation.
docs/DESIGN.md: mark G5 as done
TestSign_Identifier_RequireMode_RejectsTamperedResponse and TestSign_Identifier_RequireMode_RejectsUnknownKid used a 200ms timeout that was insufficient under full-suite CPU contention. The bufconn gRPC server needs more headroom when all packages run in parallel. Bump both to 2s — still fast, no longer flaky.
POST /v1/admin/explain — admin-gated endpoint that dry-runs the pipeline against a synthetic request and returns a structured JSON trace showing: - Which identifier(s) matched or didn't (with per-stage latency) - Revocation check result (if configured) - Authorizer verdict (allow/deny with reason) - Mutator execution (applied/error) - Resolved identity (subject, source, ACR, AMR) - Policy version evaluated - Total wall-clock latency Protected by the new 'explain' admin RBAC verb. New files: - internal/pipeline/explain.go — Engine.Explain() method - internal/admin/explain.go — HTTP handler + request/response types - internal/pipeline/explain_test.go — 6 unit tests - internal/admin/explain_test.go — 6 integration tests (12 total)
G5 (STEP-UP-MFA): - Enforce maxAge at runtime with auth_time extraction and age check - Reject future auth_time values (negative age bypass) - Strict parser: reject unknown rule/match/require keys - Require at least one constraint in require blocks (acr/amr/maxAge) G6 (EXPLAIN-ENDPOINT): - Validate explain endpoint is admin-gated, body-bounded, no side effects G2 (ADMIN-RBAC-1): - Webhook fail-closed on resolver error, missing bindings, unknown ops - Deny malformed requests with missing resource name G3 (DATA-RES-1): - Serialize engine swap and audit policy apply under ConfigApplyMu - Add config_apply_lock.go for cycle-free shared mutex - Race-condition regression test for concurrent config apply G4 (COMP-REPORT-1): - Gate audit/privacy controls behind --runtime-verified flag - Reject unknown redaction fields and actions with fail status All tests pass including -race detector.
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.
No description provided.