Skip to content

[bp/1.39] oauth2: add assertion_audience and source key_id from the token secret (#46699) - #47445

Open
larsore wants to merge 1 commit into
envoyproxy:release/v1.39from
kartverket:backport-46699-to-v1.39
Open

larsore wants to merge 1 commit into
envoyproxy:release/v1.39from
kartverket:backport-46699-to-v1.39

Conversation

@larsore

@larsore larsore commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Backport of #46699.

Nominated via backport/review on the original PR; opening this so the stable maintainers can evaluate it directly.

This adds API surface to a stable branch, which I recognise is the main argument against it. The case for considering it anyway: private_key_jwt shipped in 1.39.0 but cannot be used at all with providers that require the authorization server's issuer identifier as the assertion audience (mandated by FAPI 2.0 §5.3.3.1) or a kid header to select among multiple registered keys. ID-Porten, the national identity provider for Norwegian citizens, requires both. Both additions are opt-in and inert unless configured, so no existing 1.39 configuration changes behaviour.

Not a clean pick: 1.39 predates the exception-free FilterConfig migration (#46323) and the ASSERT_OK test migration, so the tests were adapted to the branch's existing constructor and assertion style. Non-test changes apply unmodified. //test/extensions/filters/http/oauth2/... and //test/common/secret/... pass against the 1.39 base.

…sertion (envoyproxy#46699)

Commit Message: oauth2: add assertion_audience and source key_id from
the token secret

Adds `assertion_audience` to `PrivateKeyJwtConfig`, setting the `aud`
claim of the JWT client assertion. RFC 7523 Section 3 requires `aud` to
identify the authorization server and permits the token endpoint URL;
RFC 9126 Section 2.1 recommends the authorization server's issuer
identifier and FAPI 2.0 Section 5.3.3.1 requires it. When unset, the
`aud` claim remains the configured `token_endpoint` URI.

Also allows `token_secret` to be supplied as a multi-entry generic
secret with a `private_key` entry holding the PEM-encoded key and an
optional `key_id` entry. When the `key_id` entry is present, its value
is set as the `kid` header parameter of the client assertion per RFC
7515 Section 4.1.4, which providers use to select the correct
verification key when a client has more than one key registered.
Single-value secrets continue to hold the key directly and emit no `kid`
header, so existing configurations are unaffected.

Supporting this required exposing the named entries of a multi-entry
`GenericSecret` through `ThreadLocalGenericSecretProvider`, which
previously only surfaced the single-value form. That change is additive:
the existing accessor is unchanged and existing consumers are
unaffected.

Additional Description: Private key JWT client authentication was
requested in envoyproxy#40127 and implemented in envoyproxy#44005. When testing that support
against ID-Porten (Norway's public sector IDP for norwegian citizens),
token requests were rejected for two reasons: the provider expects its
issuer identifier as the assertion audience rather than the token
endpoint URL, and it requires a `kid` header to select the verification
key among the several keys registered for the client. Neither value is
configurable today. The `aud` claim is derived from `token_endpoint` and
no `kid` header is emitted at all.

The two values are sourced differently because they change on different
terms. The assertion audience identifies the configured authorization
server, so it belongs in filter configuration alongside the endpoints it
refers to. The key ID identifies the signing key, so it belongs in the
secret that carries that key: when the key rotates, the new key ID
arrives with it, and the two can never be observed out of step.

This PR was developed with AI assistance. I have reviewed and fully
understand all of the code being submitted.

Risk Level: Low (small optional feature; new fields are opt-in and
defaults preserve existing behavior)

Testing: Unit tests.
* `test/common/secret/secret_provider_impl_test.cc` (new): named-entry
lookup for single-value and multi-entry secrets, absent entries, empty
secrets, and the unreadable-entry error path.
* `filter_test`: `SDSSecretReader` resolving the key and key ID from a
multi-entry secret, a single-value secret yielding no key ID, and a
missing client secret provider; the `kid` header present when the secret
carries a key ID and absent when it does not; audience defaulting to the
token endpoint URI, an explicitly configured audience, and decoding the
generated assertion to assert the resulting `aud` claim.
* `client_assertion_test`: `kid` present when supplied, omitted when
empty, and JSON-escaped so a value from a secret store cannot break out
of the JWT header.
* `config_test`: loading a configuration that sets `assertion_audience`.

Docs Changes: API documentation inline with the proto in
`api/envoy/extensions/filters/http/oauth2/v3/oauth.proto`. The new
`assertion_audience` field, and the `token_secret` field documenting
both accepted secret forms and the `private_key` / `key_id` entry names.
No changes to docs/root were needed.

Release Notes: Added
`changelogs/current/new_features/oauth2__assertion-audience-and-key-id.rst`.

Platform Specific Features: N/A

Runtime guard: N/A. Both additions are opt-in and preserve existing
behavior for configurations that do not use them.

Fixes #Issue: N/A — follow-up to envoyproxy#40127 / envoyproxy#44005, which are already
closed.

API
Considerations(https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):
* Feature enablement: both additions are opt-in. Unset
`assertion_audience` preserves the current `aud` value (the
`token_endpoint` URI), and a single-value `token_secret` is read exactly
as today with no `kid` header emitted. No existing user is affected
without a config change.
* WKT wrapping: `assertion_audience` is a `string` where empty
unambiguously means "unset"; there is no plausible future default that
would require a wrapper type.
* Validation: no protoc-gen-validate rules are added. `aud` may be any
string per RFC 7519, so constraining it in the API would risk rejecting
legitimate provider configurations. The `key_id` entry is data inside a
secret rather than a config field and is not validated at config load.
* Existing field semantics: `token_secret` now accepts the multi-entry
`GenericSecret` form in addition to the single value form.
`GenericSecret` already defines the multi-entry form for distributing
associated secrets together, and the entry names are documented on the
field.

cc @kanurag94

---------

Signed-off-by: Lars Sørensen <larssorensen98@hotmail.com>
(cherry picked from commit e06565d)

Signed-off-by: Lars Sørensen <54811127+larsore@users.noreply.github.com>
@repokitteh-read-only

Copy link
Copy Markdown

CC @envoyproxy/api-shepherds: Your approval is needed for changes made to (api/envoy/|docs/root/api-docs/).
envoyproxy/api-shepherds assignee is @wbpcode
CC @envoyproxy/api-watchers: FYI only for changes made to (api/envoy/|docs/root/api-docs/).

🐱

Caused by: #47445 was opened by larsore.

see: more, trace.

@larsore

larsore commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

/retest

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants