Repository navigation
feat(dashboard): internal cluster SA-token auth lane for the read API (ADR-0043) - #354
Merged
Merged
Conversation
…(ADR-0043) Add a mesh-internal auth lane to the dashboard read surface, ALONGSIDE the edge Cloudflare Access path (ADR-0030): an in-cluster caller authorized by its Kubernetes ServiceAccount-token JWT (cluster issuer, RS256, a required `aud`, and a `sub` allowlist) can read `/api/*.json` without an edge Access JWT it cannot mint in-cluster — the lane the cluster repo's `fixer` needs. Pure addition, read-only, fail-closed: - reuses the ADR-0030 `Verifier` (alg pinned to RS256 — the alg-confusion close; iss/aud/exp/nbf/sub all required) pointed at the cluster JWKS via a new `ClusterJwksFetcher` that trusts the cluster CA and presents protector's own projected SA token, read fresh on every fetch, to the authenticated `openid/v1/jwks` discovery endpoint; - one gate, two lanes tried in order: a verified + allowlisted SA token is admitted read-only at the floor `Redacted` tier; a verified-but-non-allowlisted one is a hard 401 (never a 302, never a fall-through); an unverifiable one falls through to the UNCHANGED edge path (the internal lane never fails open); - fails closed everywhere: an empty allowlist, an unreadable cluster CA, or an unreachable cluster JWKS all admit no internal caller, and the edge path and every mutating/admission path are untouched. Config via `PROTECTOR_DASHBOARD_INTERNAL_*` (aud enables the lane; allowed subs; issuer/jwks/token/ca default to the in-pod projected-token locations). The lane rides on the Oidc-mode Enforcer, so it is active only when the edge OIDC issuer is configured. New unit + integration tests mirror watcher's internal-lane suite (allowlisted admit; valid-non-allowlisted 401; unverifiable fall-through; wrong aud/iss/alg/ expiry reject; JWKS-unreachable fail-closed) and prove the edge Bearer path and no-token 401 still hold with the lane configured. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013cYVqzH7Xfwea7fAozdQK7
thejefflarson
force-pushed
the
dashboard-internal-sa-auth
branch
from
November 8, 2026 05:19
bf642c5 to
3b1a1cd
Compare
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.
What
Give protector's read-only dashboard JSON API a mesh-internal auth lane so an in-cluster caller (the cluster repo's
fixer) authorized by its Kubernetes ServiceAccount-token JWT can read/api/*.jsonwithout a Cloudflare Access JWT it cannot mint in-cluster.This is pure addition on top of ADR-0030's edge OIDC path, read-only, and fail-closed. The edge Access path and every mutating/admission/engine path are untouched. New ADR:
docs/adr/0043. Mirrors the pattern watcher shipped for its/api(watcher PR #253 / its ADR-0022).How
Verifier(algorithm pinned to RS256 — the alg-confusion close;iss/aud/exp/nbf/suball required) pointed at the cluster issuer/JWKS via a newjwks::ClusterJwksFetcherthat trusts the cluster CA and presents protector's own projected SA token, read fresh on every fetch, to the authenticated…/openid/v1/jwksdiscovery endpoint. The same bounded-body / single-flight / rate-limitedJwksStoreprotects this fetch.enforce): when the lane is configured and anAuthorization: Bearertoken is present it is tried first —sub→ admitted read-only, at the floorRedactedtier (a cluster SA token carries notierclaim), subjected to the same minimum-tier gate as the edge path;Cf-Access-Jwt-Assertion, so the edge path then rejects it).Enforcer, so it is active only when the edge OIDC issuer is configured.Config (env, all
PROTECTOR_DASHBOARD_INTERNAL_*)…_AUD…_ALLOWED_SUBSsuballowlist.…_ISSUERhttps://kubernetes.default.svc.cluster.localiss.…_JWKS_URL<issuer>/openid/v1/jwks…_TOKEN_PATH/var/run/secrets/kubernetes.io/serviceaccount/token…_CA_PATH/var/run/secrets/kubernetes.io/serviceaccount/ca.crtTests
New unit tests (
auth::internal::tests) + integration tests (enforce_tests) mirror watcher's internal-lane suite: allowlisted admit; valid-but-non-allowlisted 401; unverifiable fall-through; wrongaud/iss/alg/expiry reject; JWKS-unreachable fail-closed; and the edge Bearer path + no-token 401 still hold with the lane configured.cargo fmt --checkclean, fullcargo nextest rungreen (1273 passed).Cluster-side follow-ups (separate repo)
The cluster repo's protector chart sets
PROTECTOR_DASHBOARD_INTERNAL_AUD=protectorand the allowlist tosystem:serviceaccount:fixer:fixer(companion PR).fixer's chart must then mount a projectedserviceAccountTokenwithaudience: protectorand present it as the bearer toprotector-dashboard:8080.🤖 Generated with Claude Code
https://claude.ai/code/session_013cYVqzH7Xfwea7fAozdQK7