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.go — TrustedIssuerRunConfig +
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
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_x509andspiffe_jwtare twofeatures 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
SPIFFE ID, the trust domain, and the authentication method (
spiffe_x509orspiffe_jwt).extension of the
trustedIssuers/inboundGrantssurface rather than aparallel one. Concretely: a trust-domain declaration (name, trust domain,
bundle source) as a sibling of
trustedIssuers, plus aspiffeClientAuthentry under
inboundGrants(trust-domain ref, enabled methods, clientassociations).
enabled by default.
/*patterns with registeredOAuth clients.
per association.
registration defaults.
Reuse and prior art
The
xaa-spike-1branch built this surface once already, for inbound trust andID-JAG. Extend it; do not add a third way to declare external trust.
pkg/authserver/trust_config.go—TrustedIssuerRunConfig+InboundGrantsRunConfig, validated into anIssuerTrustRegistrybyBuildIssuerTrustRegistry. The governing rule is that trust registration andgrant 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.
validateIDJAGGrantsenforces that no two trusted issuers may resolvesubjects onto the same upstream identity space. That is the existing
precedent for "a workload cannot claim a
client_idassociated with anotherSPIFFE ID" — model the association check on it rather than inventing a new
shape.
Two patterns from the
spiffee-authserverbranch must not be lifted:upstream/spiffe.go(ProviderTypeSPIFFE) andupstream/oidc_trust.go(
ProviderTypeOIDCTrust). Thexaa-spike-1branch first modelled externaltrust as a
jwks-trustupstream-provider type and then deliberately deletedit in favour of a first-class config field. A SPIFFE trust domain is a trust
declaration, not an identity provider.
nilpolicy 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
for the same association.
is enabled.
client_idassociated with a different SPIFFE ID.configuration error, not a silent no-op.
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.RegisterClientdocumentsErrAlreadyExistsbut both backendssilently overwrite, so a re-registering workload replaces an existing client's
configuration instead of failing.
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
pkg/authserver/trust_config.go(unpushed,xaa-spike-1). If this issuestarts first, land the shared trust-config in a form the ID-JAG work can
adopt, not a SPIFFE-only copy.
Related