Repository navigation
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds RFC 8705 mutual-TLS client authentication support for Cloud Foundry app instance identity by introducing a dedicated /oauth/mtls/token endpoint, validating instance certificates against per-client CA configuration, and enriching issued JWTs with CF identity claims derived from certificate subject fields. It also updates OIDC discovery to advertise mTLS endpoint aliases and tls_client_auth as a supported token endpoint authentication method.
Changes:
- Introduces
/oauth/mtls/tokenwith a dedicated Spring Security filter chain and request-to-certificate mapping viaClientCertificateMapper. - Adds TLS client certificate validation (
TlsClientAuthentication) and a token enhancer (MtlsClaimsEnhancer) to emitcnf.x5t#S256plus configured subject-derived claims. - Extends client auth method support across model/constants and OIDC discovery metadata (
tls_client_auth,mtls_endpoint_aliases).
Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| server/src/test/java/org/cloudfoundry/identity/uaa/oauth/tls/TlsClientAuthenticationTest.java | Adds unit coverage for null inputs and malformed CA handling in TLS cert validation. |
| server/src/test/java/org/cloudfoundry/identity/uaa/oauth/tls/MtlsClaimsEnhancerTest.java | Verifies OU/CN claim extraction and cnf.x5t#S256 behavior for mTLS tokens. |
| server/src/test/java/org/cloudfoundry/identity/uaa/oauth/tls/ClientCertificateMapperFilterTest.java | Confirms servlet filter registration for mapping XFCC to request X509Certificate attribute on /oauth/mtls/*. |
| server/src/test/java/org/cloudfoundry/identity/uaa/oauth/provider/client/ClientCredentialsTokenGranterTests.java | Ensures tls_client_auth is allowed for client_credentials. |
| server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaClientAuthenticationProviderTest.java | Updates provider wiring to include TlsClientAuthentication. |
| server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ClientDetailsAuthenticationProviderTests.java | Adds tests for mtls path detection and TLS config deserialization behavior. |
| server/src/test/java/org/cloudfoundry/identity/uaa/account/OpenIdConnectEndpointsTest.java | Validates discovery document includes mtls_endpoint_aliases.token_endpoint. |
| server/src/main/java/org/cloudfoundry/identity/uaa/web/FilterChainOrder.java | Adds a new security chain order slot (OAUTH_11) for the mTLS token endpoint chain. |
| server/src/main/java/org/cloudfoundry/identity/uaa/SpringServletXmlFiltersConfiguration.java | Registers ClientCertificateMapper filter for /oauth/mtls/*. |
| server/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenEndpoint.java | Expands token endpoint mapping to include /oauth/mtls/token. |
| server/src/main/java/org/cloudfoundry/identity/uaa/oauth/tls/TlsClientAuthentication.java | Adds per-client CA-based PKIX validation and request certificate extraction helper. |
| server/src/main/java/org/cloudfoundry/identity/uaa/oauth/tls/MtlsClaimsEnhancer.java | Implements JWT enrichment from cert subject + cnf.x5t#S256 for the mTLS flow. |
| server/src/main/java/org/cloudfoundry/identity/uaa/oauth/provider/client/ClientCredentialsTokenGranter.java | Allows tls_client_auth as a valid auth method for client credentials. |
| server/src/main/java/org/cloudfoundry/identity/uaa/oauth/beans/OauthEndpointSecurityConfiguration.java | Adds dedicated security filter chain for /oauth/mtls/token (stateless + CSRF disabled). |
| server/src/main/java/org/cloudfoundry/identity/uaa/oauth/beans/OauthEndpointBeanConfiguration.java | Wires TlsClientAuthentication into ClientDetailsAuthenticationProvider bean construction. |
| server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ClientDetailsAuthenticationProvider.java | Detects mTLS path, validates certs, and parses per-client TLS configuration from additional info. |
| server/src/main/java/org/cloudfoundry/identity/uaa/account/OpenIdConnectEndpoints.java | Populates mtls_endpoint_aliases in OIDC discovery. |
| server/build.gradle.kts | Adds dependency on the Gorouter client certificate mapper (Jakarta). |
| model/src/test/resources/org/cloudfoundry/identity/uaa/account/OpenIdConfiguration.json | Updates fixture to include tls_client_auth in supported auth methods. |
| model/src/test/java/org/cloudfoundry/identity/uaa/constants/ClientAuthenticationTest.java | Adds tests for tls_client_auth support, secret requirements, and validity rules. |
| model/src/test/java/org/cloudfoundry/identity/uaa/client/UaaClientDetailsTest.java | Adds JSON round-trip test for TLS client auth config and adjusts hashCode assertion. |
| model/src/test/java/org/cloudfoundry/identity/uaa/client/TlsClientAuthConfigurationTest.java | Adds unit tests for TLS auth config JSON round-tripping and equality semantics. |
| model/src/test/java/org/cloudfoundry/identity/uaa/account/OpenIdConfigurationTests.java | Updates supported auth methods expectations and adds tests for mTLS aliases field. |
| model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/TokenConstants.java | Exposes CLIENT_AUTH_TLS_CLIENT_AUTH constant. |
| model/src/main/java/org/cloudfoundry/identity/uaa/constants/ClientAuthentication.java | Adds TLS_CLIENT_AUTH constant and updates supported/valid method logic and calculation. |
| model/src/main/java/org/cloudfoundry/identity/uaa/client/UaaClientDetails.java | Introduces tlsClientAuthConfiguration field and includes it in equals/hashCode. |
| model/src/main/java/org/cloudfoundry/identity/uaa/client/TlsClientAuthConfiguration.java | Adds model for trusted CA PEM + claim mapping configuration. |
| model/src/main/java/org/cloudfoundry/identity/uaa/account/OpenIdConfiguration.java | Adds mtls_endpoint_aliases and includes tls_client_auth in supported methods. |
rkoster
marked this pull request as ready for review
August 18, 2026 06:43
Add TlsClientAuthConfiguration field serialized as tls-client-auth-ca in client JSON, following the clientJwtConfig pattern. Includes getter/setter, copy constructor support, equals/hashCode. Fix fragile isPositive() hash code assertion to isNotZero().
…tailsAuthenticationProvider
…dpoint The BOSH ERB template emits 'mtls.endpoint' (from the nested mtls.endpoint YAML block) but the @value annotation was reading 'uaa.mtls_endpoint_path', a key never emitted by the template. Align the annotation to the actual Spring property so operator-configured paths are honoured.
Member
|
there is a 2nd #4076 |
fhanik
added a commit
to fhanik/uaa
that referenced
this pull request
Sep 24, 2026
Contributor
|
Superseded by #4076 |
fhanik
added a commit
to fhanik/uaa
that referenced
this pull request
Sep 24, 2026
Closed
fhanik
added a commit
to fhanik/uaa
that referenced
this pull request
Sep 24, 2026
fhanik
added a commit
to fhanik/uaa
that referenced
this pull request
Sep 25, 2026
fhanik
added a commit
to fhanik/uaa
that referenced
this pull request
Sep 25, 2026
fhanik
added a commit
to fhanik/uaa
that referenced
this pull request
Sep 25, 2026
fhanik
added a commit
to fhanik/uaa
that referenced
this pull request
Sep 29, 2026
…round rkoster pushed 14 follow-up commits to cloudfoundry#3972 (471ad57..a80e1c9) addressing the cloudfoundry#4075 review. 471ad57 is the last commit squashed into this branch's base, so none of those 14 are here -- both branches fixed most of the same findings independently. Comparing them surfaced three gaps on our side. These tests fail today and are made to pass in the following commit. G2 CA rotation is silently broken. A platform CA has to be rotatable without an outage, which means publishing a bundle holding the outgoing and incoming CA together. Registration already accepts one -- PemCertificateParser.parseCertificateChain reads every entry -- but TlsClientAuthentication has its own private parser that takes only the first PEM object and builds a single TrustAnchor. So the rotation appears to configure successfully and then half the workloads fail. Observed: a certificate from the second CA in the bundle is refused with "certificate chain validation failed". G1 pins the first-CA case, which already works, so the pair localises the defect to the anchor set. G3 The mTLS endpoint serves GET. /oauth/mtls/token shares a @RequestMapping with /oauth/token and inherits its GET support, so a token request can be made with the parameters in the query string, where access logs and every proxy in front of UAA record them. /oauth/token keeps GET for backwards compatibility; this endpoint is new and has no such debt. Expects 405 with an Allow: POST header. G4 guards the blast radius -- both paths are mapped by the same controller method, so restricting one must not restrict the other. E7 The buildpack certificate mapper is registered even when mTLS is disabled. It exists only to turn Gorouter's X-Forwarded-Client-Cert header into the servlet X509Certificate attribute for this one endpoint, so with the feature off it is a third-party filter in every request's path for no purpose. It is also built reflectively from a package-private class and throws IllegalStateException on failure, which couples UAA's ability to start to that jar being present even for operators who will never enable mTLS. Not covered by a test, and fixed in the following commit: revocation behaviour (setRevocationEnabled(false) -- no CRL, no OCSP) is not mentioned anywhere under docs/.
fhanik
added a commit
to fhanik/uaa
that referenced
this pull request
Sep 29, 2026
…mapper Makes the three tests from the previous commit pass, and documents revocation. Ported in intent from rkoster's cloudfoundry#3972 follow-ups (ede683e, aa075f5, cb5fbd8, 1cc3ed4); the implementations here are our own, since the two branches have diverged. CA rotation (G1, G2) TlsClientAuthentication had a private single-certificate PEM parser and built a one-element TrustAnchor set, while registration validated the value with PemCertificateParser, which reads every certificate. A rotation bundle therefore registered successfully and then only the first CA was trusted. Both call sites -- the client CA and the trusted-proxy CA -- now use the same shared parser, and every certificate in the value becomes an anchor, so an operator can publish outgoing+incoming CAs for the overlap window. The private parser is deleted, which removes the asymmetry rather than papering over it. C3's expected message moves with it: the shared parser says "Not a PEM-encoded X.509 certificate" where the private one said "No PEM object found in tls-client-auth-ca"; same status, same error code, same distinction between a bad CA and a certificate that simply did not validate. POST-only (G3, G4) Enforced in MtlsEndpointAvailabilityFilter rather than in UaaTokenEndpoint. "Method not allowed" is a property of the resource, not of the caller, so deciding it ahead of Spring Security means an unauthenticated GET is answered 405 -- with an Allow header, per RFC 9110 -- instead of 401, and PUT/DELETE are covered by the same check. UaaTokenEndpoint is left untouched, so /oauth/token keeps the GET support it has for backwards compatibility. The disabled check still runs first: a 404 endpoint must not disclose which methods it would have accepted. Certificate mapper gating (E7) clientCertificateMapperFilter now takes uaa.mtls-enabled and returns a disabled registration when the feature is off. Beyond not placing a third-party filter in every request's path for no reason, this stops a deployment that will never use mTLS from depending on the buildpack jar merely to boot -- the class is package-private and loaded reflectively, and its absence throws. Existing callers in the server tests pass true; a new case pins the disabled branch. Revocation documentation docs/UAA-Client-Authentication.md gains an "Operating tls_client_auth" section recording that PKIX validation runs with setRevocationEnabled(false) -- no CRL, no OCSP -- what that means for a stolen certificate, and that removing tls-client-auth-ca is the way to cut a client off. The same section documents the rotation procedure the fix above enables. No new markdownlint findings (21 before, 21 after; all pre-existing). Verified: 50 mTLS MockMvc tests and 101 server-module tls/token tests pass.
Contributor
|
superseded by #4076 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements RFC 8705 mutual-TLS client
authentication for Cloud Foundry app instance identity, enabling workload identity
federation with AWS, GCP, Azure, and any OIDC-aware service.
CF app instances already receive a short-lived X.509 certificate from the Diego
Cell (
instance.crt/instance.key). This change lets an app exchange that certfor a UAA JWT containing verified
app_guid,space_guid,org_guid, andcf_instance_guidclaims — without secrets or user credentials.How it works
Changes (this PR — 18 commits)
Model layer:
ClientAuthentication: addtls_client_authconstantTokenConstants: addCLIENT_AUTH_TLS_CLIENT_AUTHTlsClientAuthConfiguration: per-client CA PEM + claim-mapping modelUaaClientDetails: addtlsClientAuthConfigurationfieldOpenIdConfiguration: addmtls_endpoint_aliasesto OIDC discoveryServer layer:
ClientDetailsAuthenticationProvider:isTlsClientAuth(),validateTlsClientAuth(),getTlsClientAuthConfiguration()— handles in-memory, Map (Jackson), and flat String PEM (BOSH) config formsTlsClientAuthentication: PKIX cert chain validation against per-client CAClientCertificateMapperregistration:SpringServletXmlFiltersConfigurationregisters thejava-buildpack-client-certificate-mapper-jakartafilter for/oauth/mtls/*to materialiseX-Forwarded-Client-Certas ajakarta.servlet.request.X509CertificateattributeClientCredentialsTokenGranter: allowtls_client_authalongsideclient_secretMtlsClaimsEnhancer:UaaTokenEnhancerthat reads cert subject OU fields and maps them to JWT claims per per-client configuration; handles DB-loaded clients (readsadditionalInformationdirectly) and Diego multi-valued RDNsFilterChainOrder.OAUTH_11+mtlsTokenEndpointSecurity: dedicated security filter chain for/oauth/mtls/tokenwith CSRF disabledUaaTokenEndpoint: add/oauth/mtls/tokento@RequestMappingmtls_endpoint_aliasesDeployment notes
Requires the Gorouter to be configured with
forwarded_client_cert: sanitize_setso it validates the TLS session cert and injects it as
X-Forwarded-Client-Cert.The UAA client for an app must be configured with:
tls-client-auth-trusted-proxy-caswitches this client to the Gorouter/XFCC-forwarding-onlytopology: UAA then requires the
X-Forwarded-Client-Certheader to actually be present and itsimmediate TLS peer to have presented a certificate signed by that CA during the handshake --
preventing a direct caller (bypassing the Gorouter) from replaying a harvested certificate it
doesn't hold the private key for, or a direct connection from being silently accepted instead.
For a client that connects to UAA directly (e.g. permitted by Application Security Group
configuration, bypassing the Gorouter), omit this property entirely -- configuring it at all
makes the client reject direct connections. See
docs/UAA-Client-Authentication.mdfor both cases; two separate UAA clients are needed to support both patterns for the same
workload.
Proof of concept
End-to-end verified on a real CF deployment: a Go app pushes its Diego instance cert
to
POST /oauth/mtls/token, and the returned JWT contains:{ "app_guid": "b0bff1c2-a258-4060-981d-601f22e6bcf8", "space_guid": "02700fa7-8db7-4598-b015-9a5fc73d4656", "org_guid": "8deb6c47-8460-4501-8a86-246b774d97e4", "cf_instance_guid": "86bf36e4-af79-4d7a-6484-0d89", "client_auth_method": "tls_client_auth", "cnf": { "x5t#S256": "rk4P4d0DXNJDpZeOotKRUzmoaomqSqPQn8OzyKQhMuw" } }All GUIDs verified against
cf app,cf org, andcf space --guid.Related
cloudfoundry/uaa-release