Skip to content

Define the SPIFFE client identity, association, and configuration model #6200

Description

@jhrozek

Description

Establish the shared foundation both SPIFFE authentication methods sit on: what
an authenticated SPIFFE principal is, how a SPIFFE ID is associated with an
OAuth client, and how an operator declares all of it.

This is the piece that decides whether spiffe_x509 and spiffe_jwt are two
features or one. Everything downstream — certificate validation, assertion
validation, grant issuance, Cedar inputs — consumes the normalized principal
defined here, so if the two credential paths produce different principals the
"equivalent authorization outcome" guarantee is unenforceable rather than
merely untested.

Scope

  • Define a normalized authenticated principal carrying the OAuth client ID, the
    SPIFFE ID, the trust domain, and the authentication method (spiffe_x509 or
    spiffe_jwt).
  • Add one authoritative runtime and operator configuration model, as an
    extension of the trustedIssuers / inboundGrants surface rather than a
    parallel one. Concretely: a trust-domain declaration (name, trust domain,
    bundle source) as a sibling of trustedIssuers, plus a spiffeClientAuth
    entry under inboundGrants (trust-domain ref, enabled methods, client
    associations).
  • Allow X.509-SVID, JWT-SVID, or both to be enabled explicitly. Nothing is
    enabled by default.
  • Associate exact SPIFFE IDs or segment-safe /* patterns with registered
    OAuth clients.
  • Configure grants, scopes, resources, audiences and token-exchange permission
    per association.
  • Make client association durable and restart-safe.
  • Remove ambiguous SPIFFE upstream-provider behaviour and allow-all
    registration defaults.

Reuse and prior art

The xaa-spike-1 branch built this surface once already, for inbound trust and
ID-JAG. Extend it; do not add a third way to declare external trust.

  • pkg/authserver/trust_config.goTrustedIssuerRunConfig +
    InboundGrantsRunConfig, validated into an IssuerTrustRegistry by
    BuildIssuerTrustRegistry. The governing rule is that trust registration and
    grant enablement are separate declarations and neither may silently broaden
    the other: an issuer trusted but enabled for no grant is rejected, not
    ignored. That is exactly this issue's fail-closed requirement.
  • validateIDJAGGrants enforces that no two trusted issuers may resolve
    subjects onto the same upstream identity space. That is the existing
    precedent for "a workload cannot claim a client_id associated with another
    SPIFFE ID"
    — model the association check on it rather than inventing a new
    shape.

Two patterns from the spiffee-authserver branch must not be lifted:

  • upstream/spiffe.go (ProviderTypeSPIFFE) and upstream/oidc_trust.go
    (ProviderTypeOIDCTrust). The xaa-spike-1 branch first modelled external
    trust as a jwks-trust upstream-provider type and then deliberately deleted
    it in favour of a first-class config field. A SPIFFE trust domain is a trust
    declaration, not an identity provider.
  • The branch's two known policy weaknesses: a nil policy meaning allow-all,
    and auto-registered clients receiving every supported scope and every allowed
    audience. Per-identity narrowing is required here, not deferred.

Acceptance criteria

  • X.509-SVID and JWT-SVID authentication produce the same normalized principal
    for the same association.
  • Configuration does not advertise or accept an authentication method unless it
    is enabled.
  • Exact and wildcard SPIFFE ID matching respects path-segment boundaries.
  • A public client cannot become confidential by presenting an SVID.
  • A workload cannot claim a client_id associated with a different SPIFFE ID.
  • A missing association policy fails closed.
  • A trust domain declared but enabled for no authentication method is a
    configuration error, not a silent no-op.
  • Client association and authorization policy survive authorization-server
    restarts.

Blocker on the restart-safety criterion

The last criterion is not reachable with the storage layer as it stands.
Two defects catalogued in #6082 are directly in the way:

  • ClientRegistry.RegisterClient documents ErrAlreadyExists but both backends
    silently overwrite, so a re-registering workload replaces an existing client's
    configuration instead of failing.
  • In-memory storage loses confidential clients on restart, so any configuration
    naming them goes stale every boot.

Either fix those as part of this issue, or state the Redis-only constraint
explicitly and drop the criterion. Do not leave it implied.

Dependencies

Related

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions