Normalize canonical inbound grants - #6473
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## spiffe-integration-split3-2 #6473 +/- ##
===============================================================
+ Coverage 78.09% 78.12% +0.03%
===============================================================
Files 768 769 +1
Lines 74722 74776 +54
===============================================================
+ Hits 58351 58420 +69
+ Misses 16366 16351 -15
Partials 5 5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
a93bd9b to
047b301
Compare
JAORMX
left a comment
There was a problem hiding this comment.
I reviewed this against #6467 and #6200. CI is green, but I found two blocking model/correctness issues:
-
SPIFFEClientsis nested underinbound_grants.token_exchange(pkg/authserver/inbound_grants.go:19-24). That makes SPIFFE client authentication subordinate to RFC 8693: disabling or omitting token exchange removes the association, and every SPIFFE-authenticated client is necessarily token-exchange-enabled. #6200 calls forspiffeClientAuthunderinboundGrantswith grants and token-exchange permission configured per association. Authentication method and grant authorization need to remain independent in the canonical schema. -
SPIFFE validation reports paths under
inbound_grants.spiffe_client_auth[...], but this PR's actual serialized path isinbound_grants.token_exchange.spiffe_clients[...](pkg/authserver/spiffe_trust.go, reached fromRunConfig.Validate). Operators therefore get errors naming a field that does not exist. Please make the validation paths match the final schema.
The normalization direction is useful, but it currently projects the canonical wire types back into the legacy combined TrustedIssuer runtime representation. That leaves parallel policy shapes and several field-by-field copy/conflict-detection sites that must evolve in lockstep. I would prefer one grant-oriented normalized runtime model consumed by validation/provider construction, with legacy and canonical wire inputs translated into it once.
Standards notes: the commit is missing the required Signed-off-by trailer (CONTRIBUTING.md:91), and the PR is well beyond the repository's 400-line guideline. The signoff must be fixed; please also consider whether the compatibility normalization can be split from the SPIFFE wiring to reduce review risk.
8abeeaa to
108bc9c
Compare
047b301 to
7c5e172
Compare
JAORMX
left a comment
There was a problem hiding this comment.
I re-reviewed the rebased head (7c5e172). The rebase fixes the two model issues from my previous review: spiffe_client_auth remains a sibling of the grant families, validation paths now match the schema, and the commit has its DCO trailer.
One new enablement inconsistency blocks approval:
- Canonical
inbound_grantscan disable token exchange, and this PR correctly removes the Fosite handler and discovery advertisement viaTokenExchangeEnabled. However,/oauth/registerstill callsValidateDCRRequestwithout that capability (pkg/authserver/server/handlers/dcr.go:243-257). It can therefore return201 Createdfor a token-exchange-only client even though the server has no token-exchange handler. This is especially direct forprivate_key_jwt, whose token-only path explicitly accepts a token-exchange registration. Please threadTokenExchangeEnabledinto DCR validation (or rejectallow_private_key_jwt_registrationwhen canonical configuration disables token exchange) and add a regression test proving a disabled grant cannot be registered.
The architecture doc also still places SPIFFE clients at inbound_grants.token_exchange.spiffe_clients; the implemented path is the sibling inbound_grants.spiffe_client_auth. Please update those references and make the documented legacy/canonical exclusion scope match the family-wide behavior in NormalizeInboundGrants.
CI currently has two failures. Both look unrelated to this authserver diff: one VirtualMCP Redis/session E2E timeout interrupted the remaining specs, and one MCPOIDCConfig integration test timed out amid resource-version conflicts and a missing test field index. They still need a clean rerun before approval.
|
Pushed a commit that resolves both blocking issues — thanks for catching these.
On the normalization-direction note (projecting canonical types back into the legacy Signed-off-by trailer fixed. CI is green on the amended commit. |
108bc9c to
e497cae
Compare
7c5e172 to
a1a21ea
Compare
JAORMX
left a comment
There was a problem hiding this comment.
I re-reviewed the current rebased head (a1a21ea). The earlier SPIFFE placement and validation-path fixes remain good, but the latest blocker is still present: validateDCRRequest does not receive TokenExchangeEnabled, so /oauth/register can create a token-exchange-only client after canonical configuration has removed the token-exchange handler and discovery advertisement (pkg/authserver/server/handlers/dcr.go:243-257). Please gate DCR validation on the effective capability and add the disabled-grant regression test.
The architecture documentation still names inbound_grants.token_exchange.spiffe_clients at docs/arch/17-token-exchange-delegation.md:127, while the implemented path is inbound_grants.spiffe_client_auth. Its “mutually exclusive per issuer” wording around line 703 also does not match the family-wide conflict implemented by NormalizeInboundGrants; please align one side.
The amended commit has its DCO trailer. CI is still running on this head.
e497cae to
3413cc2
Compare
The SPIFFE client-auth epic needs a place to configure SPIFFE association policy without inventing a parallel trust/grant path next to the existing delegate-client and trusted-issuer configuration. As more inbound grant families (RFC 8693 token exchange, RFC 7523 JWT-bearer, SPIFFE) accumulate, they need one canonical surface to configure and reason about instead of three independent ones, without breaking deployments that already rely on the legacy fields. Add pkg/authserver/inbound_grants.go with NormalizeInboundGrants, which reconciles a new canonical RunConfig.InboundGrants surface (per-family token_exchange/jwt_bearer sub-configs whose issuer_policies reference a trusted_issuers entry by name) against the legacy top-level delegate_clients and the RFC 8693/7523 fields embedded directly on trusted_issuers. Legacy and canonical configuration for the same grant family are mutually exclusive and rejected at validation time; the two families are otherwise independent, and omitting inbound_grants entirely preserves released behavior. Thread the normalized result through RunConfig.Validate, the embedded-auth-server runner, and buildProvider/discovery, adding a DisableTokenExchange capability so RFC 8693 registration and discovery advertisement can be turned off together and can't drift out of sync. Add TrustedIssuer.Name so canonical issuer_policies can reference an issuer without duplicating its fields. SPIFFE client authentication (InboundGrants.SPIFFEClientAuth, defined in the previous commit) is deliberately kept a sibling of TokenExchange and JWTBearer here, not nested under either: SPIFFE authenticates a client, it does not by itself grant it anything, so making it subordinate to RFC 8693 enablement would mean disabling token exchange silently drops every SPIFFE association, and every SPIFFE-authenticated client would be implicitly token-exchange-capable. It is validated and wired directly from RunConfig.InboundGrants in RunConfig.Validate/embeddedauthserver.go, independent of this file's legacy/canonical projection, so authentication method and grant-family enablement stay separately configurable. Update docs/arch/17-token-exchange-delegation.md for the new inbound_grants shape and the now-conditional token-exchange discovery advertisement, and add a runner-level test proving the canonical delegate-client, SPIFFE-client, and jwt_bearer paths reach a running server (the existing tests only covered normalization in isolation). SPIFFE client-auth associations always require the token-exchange grant (the only grant type they may declare), independent of the legacy/canonical token-exchange projection above: NormalizeInboundGrants now sets Capabilities.TokenExchange true whenever InboundGrants.SPIFFEClientAuth is non-empty, so a SPIFFE-only configuration cannot leave it false and silently disable the RFC 8693 grant handler server-wide -- which would reject every SPIFFE client's own token requests before authentication is even checked. Guarded by a regression test in this package (not just the runner-level test above) since the equivalent fix was previously lost during a rebase when its only coverage lived one package away. DCR (RFC 7591 /oauth/register) now rejects a registration whose effective grant types include token-exchange when it is disabled server-wide, instead of accepting the client and only failing later, confusingly, at /oauth/token. The check runs on the post-defaulting grant types validateGrantTypes already computes (a private_key_jwt client with an empty grant_types is implicitly token-exchange-only), so it catches both the explicit and implicit cases the same way scope validation already gates DCR on ScopesSupported. Corrected two stale doc references caught in review: the SPIFFE client-policy field path (inbound_grants.spiffe_client_auth, not nested under token_exchange) and the JWT-bearer legacy/canonical conflict wording (family-wide across all issuers, not per-issuer). Refs #6200 Signed-off-by: Jakub Hrozek <jakub@stacklok.com>
a1a21ea to
f1fc2d8
Compare
|
Pushed a commit addressing both findings. DCR/token-exchange capability gating. Docs drift. Fixed the stale Also worth flagging: rebasing onto #6467's fix (the hard-reject on non-empty CI is green. |
Summary
The SPIFFE client-auth epic needs a place to configure SPIFFE association policy without inventing a parallel trust/grant path next to the existing delegate-client and trusted-issuer configuration. As more inbound grant families (RFC 8693 token exchange, RFC 7523 JWT-bearer, SPIFFE) accumulate, they need one canonical surface to configure and reason about instead of three independent ones, without breaking deployments that already rely on the legacy fields.
Stacked on #6467.
pkg/authserver/inbound_grants.gowithNormalizeInboundGrants, which reconciles a new canonicalRunConfig.InboundGrantssurface (per-familytoken_exchange/jwt_bearersub-configs whoseissuer_policiesreference atrusted_issuersentry by name) against the legacy top-leveldelegate_clientsand the RFC 8693/7523 fields embedded directly ontrusted_issuers.inbound_grantsentirely preserves released behavior.RunConfig.Validate, the embedded-auth-server runner, andbuildProvider/discovery — this is also wherespiffe_trust_domains(added in Define SPIFFE trust configuration #6467) finally gets validated and wired intoConfig.SPIFFETrust, closing the gap flagged in that PR's review.DisableTokenExchangecapability so RFC 8693 registration and discovery advertisement turn off together and can't drift out of sync.TrustedIssuer.Nameso canonicalissuer_policiescan reference an issuer without duplicating its fields.docs/arch/17-token-exchange-delegation.mdfor the newinbound_grantsshape and the now-conditional token-exchange discovery advertisement.jwt_bearerpaths reach a running server (the existing tests only covered normalization in isolation).Fixes #
Type of change
Test plan
task test)task test-e2e)task lint-fix)pkg/authserver/inbound_grants_test.gocovers normalization: legacy/canonical mutual exclusion per grant family, issuer-policy resolution by name (unknown/duplicate refs), and capability derivation.pkg/authserver/runner/embeddedauthserver_test.goadds coverage proving the canonical delegate-client, SPIFFE-client, andjwt_bearerpaths actually reach a running embedded auth server, not just the normalization step in isolation.API Compatibility
v1beta1API, OR theapi-break-allowedlabel is applied and the migration guidance is described above.Special notes for reviewers
This closes the "
spiffe_trust_domainsis a silent no-op" gap flagged in review on #6467 —RunConfig.Validate()now callsValidateSPIFFETrustviaNormalizeInboundGrants, andConfig.SPIFFETrustis built and threaded into the embedded auth server inembeddedauthserver.go. Live SVID/bundle verification is still out of scope here; this is config normalization and wiring only.