Skip to content

V1.2 tier g#57

Merged
mikeappsec merged 13 commits into
mainfrom
v1.2-tier-g
May 5, 2026
Merged

V1.2 tier g#57
mikeappsec merged 13 commits into
mainfrom
v1.2-tier-g

Conversation

@mikeappsec

Copy link
Copy Markdown
Owner

No description provided.

mikeappsec added 13 commits May 4, 2026 14:37
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.
@mikeappsec
mikeappsec merged commit f455392 into main May 5, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant