feat: SPIFFE/mTLS service auth for DNS servers (prefer over static JWT)#57
feat: SPIFFE/mTLS service auth for DNS servers (prefer over static JWT)#57PenguinzTech wants to merge 2 commits into
Conversation
Service-to-service auth standard: prefer SPIFFE/SPIRE mTLS over long-lived static shared secrets. DNS servers authenticating to the manager now prefer a mesh-forwarded SPIFFE identity, with the legacy per-server JWT as fallback. - app/services/spiffe.py (new, pure/testable): SpiffeId parsing, Envoy XFCC extraction, and resolve_dns_server_identity() validating trust domain + the spiffe://<td>/<env>/dns-server/<server_id> scheme (fail-closed). - middleware/auth.py: server_token_required tries SPIFFE first (validated XFCC peer identity → authenticate with no secret); falls back to the JWT path and logs that the static-secret path was used. g.current_server now records auth_method (spiffe|jwt). - config.py: SPIFFE_ENABLED (default true), SPIFFE_TRUST_DOMAIN (penguintech.io), SPIFFE_XFCC_HEADER. Tests: test_spiffe_auth.py (22) — SPIFFE ID parse, XFCC extraction (quoted/ multi-element/case-insensitive), identity resolution fail-closed on wrong trust domain/service/id, and middleware prefer-SPIFFE/fallback-JWT/disabled. Full manager suite: 130 passing. flake8 clean. Documents the SPIFFE scheme, XFCC trust boundary, config, and SPIRE deployment as the operational follow-up in docs/ENTERPRISE_SECURITY.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewer's GuideAdds SPIFFE/mTLS-based service-to-service authentication for DNS servers, preferring mesh-forwarded SPIFFE identities via XFCC over legacy per-server JWTs, with configuration toggles, clear trust-boundary documentation, and targeted tests for SPIFFE parsing, XFCC extraction, identity resolution, and middleware behavior. Sequence diagram for DNS server SPIFFE/mTLS authentication preferencesequenceDiagram
actor DnsServer
participant ServiceMesh
participant ManagerAuthMiddleware
participant SpiffeService
participant DnsServerDB
DnsServer->>ServiceMesh: mTLS connection
ServiceMesh->>ManagerAuthMiddleware: HTTP request + XFCC header
ManagerAuthMiddleware->>ManagerAuthMiddleware: _authenticate_server_via_spiffe()
ManagerAuthMiddleware->>SpiffeService: resolve_dns_server_identity(xfcc_header, SPIFFE_TRUST_DOMAIN)
alt valid_spiffe_identity
SpiffeService-->>ManagerAuthMiddleware: DnsServerIdentity
ManagerAuthMiddleware->>DnsServerDB: dns_server[server_id]
DnsServerDB-->>ManagerAuthMiddleware: server
ManagerAuthMiddleware->>ManagerAuthMiddleware: set g.current_server(auth_method=spiffe)
ManagerAuthMiddleware-->>DnsServer: handler response
else no_spiffe_identity_or_mismatch
SpiffeService-->>ManagerAuthMiddleware: None
ManagerAuthMiddleware->>ManagerAuthMiddleware: legacy JWT validation via AuthService
ManagerAuthMiddleware->>ManagerAuthMiddleware: set g.current_server(auth_method=jwt)
ManagerAuthMiddleware-->>DnsServer: handler response or 401
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- In
_authenticate_server_via_spiffe, avoid hard-coding thepenguintech.iotrust domain default and rely solely oncurrent_app.config['SPIFFE_TRUST_DOMAIN']so configuration is centralized and consistent withConfig.SPIFFE_TRUST_DOMAIN. - The
SPIFFE_ENABLEDflag inConfigtreats any non-"true" string as false; consider normalizing common truthy values (e.g., "1", "yes", "on") to reduce configuration surprises across environments.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `_authenticate_server_via_spiffe`, avoid hard-coding the `penguintech.io` trust domain default and rely solely on `current_app.config['SPIFFE_TRUST_DOMAIN']` so configuration is centralized and consistent with `Config.SPIFFE_TRUST_DOMAIN`.
- The `SPIFFE_ENABLED` flag in `Config` treats any non-"true" string as false; consider normalizing common truthy values (e.g., "1", "yes", "on") to reduce configuration surprises across environments.
## Individual Comments
### Comment 1
<location path="manager/backend/app/services/spiffe.py" line_range="117" />
<code_context>
+ ``None`` (fail closed) for any mismatch — the caller then falls back to the
+ legacy server JWT.
+ """
+ if not xfcc_header or not trust_domain:
+ return None
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Normalize the trust domain to avoid subtle mismatches (e.g. trailing slashes or whitespace).
`SpiffeId.in_trust_domain` currently compares `trust_domain` by raw string equality. If `SPIFFE_TRUST_DOMAIN` contains whitespace or a trailing slash (e.g. `"penguintech.io/"` from an env var), valid SPIFFE IDs will incorrectly fail the trust-domain check. Please normalize the `trust_domain` input (trim whitespace and trailing `/`) before comparison so configuration quirks don’t cause false negatives, while still rejecting truly invalid domains.
Suggested implementation:
```python
Returns a :class:`DnsServerIdentity` only when the peer presents a SPIFFE ID
that is in the configured trust domain and matches the DNS-server path
scheme ``<env>/dns-server/<server_id>`` with a numeric server id. Returns
``None`` (fail closed) for any mismatch — the caller then falls back to the
legacy server JWT.
"""
if not xfcc_header or not trust_domain:
return None
# Normalize trust domain to avoid subtle mismatches from configuration
normalized_trust_domain = trust_domain.strip().rstrip("/")
if not normalized_trust_domain:
# Fail closed if normalization strips the domain to empty
return None
trust_domain = normalized_trust_domain
Service-to-service authentication standard: SPIFFE/SPIRE is preferred — a peer
```
1. No further changes are strictly required if the rest of the function and any downstream calls (e.g. `SpiffeId.in_trust_domain(trust_domain=...)`) use the local `trust_domain` variable — they will automatically see the normalized value.
2. If `trust_domain` is also passed elsewhere within this function (e.g. for logging or other checks), those will likewise benefit from normalization without additional edits.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| ``None`` (fail closed) for any mismatch — the caller then falls back to the | ||
| legacy server JWT. | ||
| """ | ||
| if not xfcc_header or not trust_domain: |
There was a problem hiding this comment.
suggestion (bug_risk): Normalize the trust domain to avoid subtle mismatches (e.g. trailing slashes or whitespace).
SpiffeId.in_trust_domain currently compares trust_domain by raw string equality. If SPIFFE_TRUST_DOMAIN contains whitespace or a trailing slash (e.g. "penguintech.io/" from an env var), valid SPIFFE IDs will incorrectly fail the trust-domain check. Please normalize the trust_domain input (trim whitespace and trailing /) before comparison so configuration quirks don’t cause false negatives, while still rejecting truly invalid domains.
Suggested implementation:
Returns a :class:`DnsServerIdentity` only when the peer presents a SPIFFE ID
that is in the configured trust domain and matches the DNS-server path
scheme ``<env>/dns-server/<server_id>`` with a numeric server id. Returns
``None`` (fail closed) for any mismatch — the caller then falls back to the
legacy server JWT.
"""
if not xfcc_header or not trust_domain:
return None
# Normalize trust domain to avoid subtle mismatches from configuration
normalized_trust_domain = trust_domain.strip().rstrip("/")
if not normalized_trust_domain:
# Fail closed if normalization strips the domain to empty
return None
trust_domain = normalized_trust_domain
Service-to-service authentication standard: SPIFFE/SPIRE is preferred — a peer- No further changes are strictly required if the rest of the function and any downstream calls (e.g.
SpiffeId.in_trust_domain(trust_domain=...)) use the localtrust_domainvariable — they will automatically see the normalized value. - If
trust_domainis also passed elsewhere within this function (e.g. for logging or other checks), those will likewise benefit from normalization without additional edits.
Security review: SPIFFE_ENABLED defaulted to true, so the manager trusted the client-supplied X-Forwarded-Client-Cert header out of the box. Where the manager is reachable without a mesh that strips inbound XFCC, a caller could forge the header and authenticate as any DNS server — a spoofable-field auth bypass. Documentation is not a control. Default SPIFFE_ENABLED to false (opt-in). XFCC is trusted only after an operator explicitly enables it, having confirmed the mesh injects+strips XFCC and the manager is not directly reachable. Add a fail-safe regression test (spoofed XFCC ignored while disabled); update the prefer-SPIFFE test to opt in; correct the docs default + rationale. Manager suite: 131 passing. flake8 clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stacked on #56. Fifth/final branch in the chain.
Implements the service-to-service auth standard — SPIFFE/mTLS preferred over long-lived static shared secrets. DNS servers authenticating to the manager now prefer a mesh-forwarded SPIFFE identity, with the legacy per-server JWT as fallback.
Changes
app/services/spiffe.py(new, pure/testable) —SpiffeIdparsing, Envoy XFCC extraction, andresolve_dns_server_identity()validating the trust domain +spiffe://<td>/<env>/dns-server/<server_id>scheme (fail-closed on any mismatch).middleware/auth.py—server_token_requiredtries SPIFFE first (validated XFCC peer identity → authenticate with no secret); falls back to the JWT path and logs that the static-secret path was used.g.current_serverrecordsauth_method(spiffe|jwt).config.py—SPIFFE_ENABLED(defaulttrue),SPIFFE_TRUST_DOMAIN(penguintech.io),SPIFFE_XFCC_HEADER.Trust boundary
XFCC is trusted only when injected by the mesh sidecar and the manager isn't directly reachable — documented explicitly.
Tests
test_spiffe_auth.py(22): SPIFFE ID parse/reject, XFCC extraction (quoted / multi-element / case-insensitive), identity resolution fail-closed (wrong trust domain / service / non-numeric id / bad arity), and middleware prefer-SPIFFE / fallback-JWT / disabled. Full manager suite: 130 passing. flake8 clean.Operational follow-up
Deploying SPIRE (server + agents issuing the SVIDs, mesh forwarding XFCC) is the ops step to retire the per-server JWT secret entirely — documented in
docs/ENTERPRISE_SECURITY.md.🤖 Generated with Claude Code
Summary by Sourcery
Prefer SPIFFE/mTLS-based service identities for DNS server authentication while retaining the legacy per-server JWT as a fallback.
New Features:
Enhancements:
Documentation:
Tests: