Skip to content

feat: SPIFFE/mTLS service auth for DNS servers (prefer over static JWT)#57

Open
PenguinzTech wants to merge 2 commits into
feature/scope-bundle-authzfrom
feature/spiffe-service-auth
Open

feat: SPIFFE/mTLS service auth for DNS servers (prefer over static JWT)#57
PenguinzTech wants to merge 2 commits into
feature/scope-bundle-authzfrom
feature/spiffe-service-auth

Conversation

@PenguinzTech

@PenguinzTech PenguinzTech commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

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) — SpiffeId parsing, Envoy XFCC extraction, and resolve_dns_server_identity() validating the trust domain + spiffe://<td>/<env>/dns-server/<server_id> scheme (fail-closed on any mismatch).
  • middleware/auth.pyserver_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 records auth_method (spiffe|jwt).
  • config.pySPIFFE_ENABLED (default true), 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:

  • Introduce a SPIFFE-based DNS server identity resolver that parses SPIFFE IDs and extracts them from Envoy XFCC headers.
  • Add configuration options to enable SPIFFE authentication, set the SPIFFE trust domain, and configure the XFCC header name.

Enhancements:

  • Update DNS server authentication middleware to first authenticate via SPIFFE identity and record the auth method, falling back to the existing JWT-based path when needed.
  • Log authentication via SPIFFE or legacy JWT to improve observability of server auth flows.

Documentation:

  • Document the SPIFFE/mTLS service-to-service authentication model, configuration, trust boundary, and SPIRE operational follow-up in enterprise security docs, including an operator checklist update.

Tests:

  • Add dedicated tests for SPIFFE ID parsing, XFCC header extraction, DNS server identity resolution, and middleware behavior when preferring SPIFFE over JWT and when SPIFFE is disabled.

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>
@sourcery-ai

sourcery-ai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds 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 preference

sequenceDiagram
    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
Loading

File-Level Changes

Change Details Files
Introduce a pure SPIFFE helper module to parse SPIFFE IDs, extract them from Envoy XFCC headers, and resolve DNS server identities in a fail-closed manner.
  • Define SpiffeId dataclass with parsing, trust-domain checks, and stringification for spiffe:// URIs.
  • Implement XFCC header parsing to collect URI=spiffe://... values across comma- and semicolon-delimited elements, handling quoting and key case-insensitivity.
  • Implement resolve_dns_server_identity to validate trust domain and DNS-server path scheme /dns-server/<numeric_id>, returning a DnsServerIdentity or None on any mismatch.
manager/backend/app/services/spiffe.py
Update DNS server authentication middleware to prefer SPIFFE/mTLS identities, fall back to legacy JWT, and record the auth method on the request context.
  • Add logging and a helper _authenticate_server_via_spiffe that uses the SPIFFE module, configuration, and DB to authenticate a server by SPIFFE identity.
  • Modify server_token_required to first attempt SPIFFE-based auth, short-circuit on success, or fall through to the existing JWT validation path when SPIFFE is unavailable or invalid.
  • Extend g.current_server to include an auth_method flag (spiffe or jwt) and log when the legacy static-secret JWT path is used.
manager/backend/app/middleware/auth.py
Add configuration knobs for SPIFFE-based authentication and document the trust boundary and operational rollout steps.
  • Introduce SPIFFE_ENABLED, SPIFFE_TRUST_DOMAIN, and SPIFFE_XFCC_HEADER config values sourced from environment variables with sensible defaults.
  • Document the SPIFFE/mTLS service-to-service auth flow, identity scheme, trust boundary around XFCC, and SPIRE deployment follow-up in the enterprise security docs.
  • Update the operator checklist to include SPIFFE trust domain and mesh/XFCC forwarding requirements.
manager/backend/app/config.py
docs/ENTERPRISE_SECURITY.md
Add unit and middleware tests to validate SPIFFE parsing, XFCC extraction, identity resolution, and SPIFFE-preferred auth behavior.
  • Test SpiffeId parsing, malformed input rejection, and trust-domain membership checks.
  • Test XFCC parsing for single, quoted, multi-element, and non-SPIFFE URI cases.
  • Test DNS server identity resolution for valid identities, various mismatch scenarios, multiple URIs, and empty inputs.
  • Test middleware behavior for SPIFFE-preferred auth, JWT fallback when no SPIFFE identity is present, and SPIFFE-disabled configuration skipping SPIFFE auth.
manager/backend/tests/test_spiffe_auth.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • 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.
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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
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:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 mismatchthe 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.

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>
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