Skip to content

feat(spiffe): JWT-SVID signing endpoint (RFC UAA-SPIFFE-001) - #3968

Draft
rkoster wants to merge 9 commits into
cloudfoundry:developfrom
rkoster:spiffe-signer
Draft

rkoster wants to merge 9 commits into
cloudfoundry:developfrom
rkoster:spiffe-signer

Conversation

@rkoster

@rkoster rkoster commented Jun 29, 2026

Copy link
Copy Markdown

Summary

Adds a JWT-SVID signing endpoint to UAA so it can act as a SPIFFE identity
server for Cloud Foundry workloads (offline-OIDC federation).

POST /jwt-svid/sign authenticates a SPIFFE Agent (client credentials), verifies
the caller's CF instance-identity certificate against the configured CA,
parses org/space/app GUIDs from the cert OUs, checks a proof-of-possession
signature for freshness, and mints an RS256 JWT-SVID signed with UAA's active key
and OIDC issuer (so it verifies offline against /token_keys).

Key changes (all under org.cloudfoundry.identity.uaa.spiffe)

  • JwtSvidController, JwtSvidRequest / JwtSvidResponse, JwtSvidSigner
  • InstanceIdentityVerifier, CertificateOuParser, ProofOfPossessionVerifier
  • SpiffeId, CfInstanceIdentity, SpiffeProperties, SpiffeConfiguration,
    SpiffeSecurityConfiguration
  • Full unit-test coverage for each component (9 focused commits).

Context

Part of RFC UAA-SPIFFE-001UAA as a SPIFFE Identity Server for Cloud Foundry.

Cross-repo dependencies

  • cloudfoundry/uaa-release spiffe-signer — exposes uaa.spiffe.* (trust
    domain, instance-identity CA) and the signer client.
  • Consumed by the SPIFFE Agent in cloudfoundry/diego-release spiffe-agent.

Status

Draft / POC. End-to-end verified on bosh-lite: a CF app received a valid JWT-SVID
with cf org/space/app/process_type claims.

rkoster added 9 commits June 26, 2026 19:26
Reject process_type not matching [A-Za-z0-9_-]{1,63} and audience that is blank, over 512 chars, or contains control characters (400). process_type is concatenated into the SPIFFE ID path and every proof-of-possession message field must be newline-free, so this closes SPIFFE-ID injection and PoP message ambiguity. Adds characterization tests pinning existing fail-closed behaviour: malformed PEM, future-dated and malformed-base64 PoP, and a not-yet-valid instance cert.
Comment on lines +49 to +59
SecurityFilterChain chain = http
.securityMatcher("/jwt-svid/**")
.authorizeHttpRequests(auth -> {
auth.requestMatchers("/**").hasAuthority("uaa.resource");
auth.anyRequest().denyAll();
})
.authenticationManager(clientAuthenticationManager)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterAt(clientAuthenticationFilter.getFilter(), BasicAuthenticationFilter.class)
.anonymous(AnonymousConfigurer::disable)
.csrf(CsrfConfigurer::disable)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved proof-of-possession, signing, configuration, validation, and documentation issues block approval.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity · 3 Medium severity · 2 Low severity

Open (6)
What changed in this PR

Adds an optional UAA SPIFFE JWT-SVID signing endpoint for authenticated Cloud Foundry workloads, including certificate validation, proof-of-possession checks, and JWT issuance.

Changes:

  • Adds protected /jwt-svid/sign request and response handling.
  • Adds SPIFFE configuration, security, identity parsing, and certificate verification.
  • Adds proof-of-possession validation and JWT-SVID signing.
  • Adds focused unit tests and certificate fixtures.
File Description
server/​src/​test/​java/​org/​cloudfoundry/​identity/​uaa/​spiffe/​SpiffeTestCerts.java Provides test certificates and keys.
server/​src/​test/​java/​org/​cloudfoundry/​identity/​uaa/​spiffe/​SpiffePropertiesTests.java Tests configuration defaults.
server/​src/​test/​java/​org/​cloudfoundry/​identity/​uaa/​spiffe/​SpiffeIdTests.java Tests SPIFFE ID formatting.
server/​src/​test/​java/​org/​cloudfoundry/​identity/​uaa/​spiffe/​SpiffeConfigurationTests.java Tests CA parsing.
server/​src/​test/​java/​org/​cloudfoundry/​identity/​uaa/​spiffe/​ProofOfPossessionVerifierTests.java Tests proof-of-possession validation.
server/​src/​test/​java/​org/​cloudfoundry/​identity/​uaa/​spiffe/​JwtSvidSignerTests.java Tests JWT-SVID signing.
server/​src/​test/​java/​org/​cloudfoundry/​identity/​uaa/​spiffe/​JwtSvidControllerTests.java Tests endpoint behavior and validation.
server/​src/​test/​java/​org/​cloudfoundry/​identity/​uaa/​spiffe/​InstanceIdentityVerifierTests.java Tests certificate trust and validity.
server/​src/​test/​java/​org/​cloudfoundry/​identity/​uaa/​spiffe/​CertificateOuParserTests.java Tests certificate OU extraction.
server/​src/​main/​java/​org/​cloudfoundry/​identity/​uaa/​spiffe/​SpiffeSecurityConfiguration.java Protects the signing endpoint.
server/​src/​main/​java/​org/​cloudfoundry/​identity/​uaa/​spiffe/​SpiffeProperties.java Defines SPIFFE configuration.
server/​src/​main/​java/​org/​cloudfoundry/​identity/​uaa/​spiffe/​SpiffeId.java Formats workload SPIFFE IDs.
server/​src/​main/​java/​org/​cloudfoundry/​identity/​uaa/​spiffe/​SpiffeConfiguration.java Parses the configured CA.
server/​src/​main/​java/​org/​cloudfoundry/​identity/​uaa/​spiffe/​ProofOfPossessionVerifier.java Verifies proof-of-possession signatures.
server/​src/​main/​java/​org/​cloudfoundry/​identity/​uaa/​spiffe/​JwtSvidSigner.java Creates signed JWT-SVIDs.
server/​src/​main/​java/​org/​cloudfoundry/​identity/​uaa/​spiffe/​JwtSvidResponse.java Defines signing responses.
server/​src/​main/​java/​org/​cloudfoundry/​identity/​uaa/​spiffe/​JwtSvidRequest.java Defines signing requests.
server/​src/​main/​java/​org/​cloudfoundry/​identity/​uaa/​spiffe/​JwtSvidController.java Implements the signing endpoint.
server/​src/​main/​java/​org/​cloudfoundry/​identity/​uaa/​spiffe/​InstanceIdentityVerifier.java Validates instance certificates.
server/​src/​main/​java/​org/​cloudfoundry/​identity/​uaa/​spiffe/​CfInstanceIdentity.java Stores Cloud Foundry identity attributes.
server/​src/​main/​java/​org/​cloudfoundry/​identity/​uaa/​spiffe/​CertificateOuParser.java Extracts identity values from certificate OUs.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +30 to +32
if (Math.abs(Instant.now().getEpochSecond() - timestamp) > properties.popFreshnessSeconds()) {
return false;
}
Comment on lines +77 to +90
private static void validateRequest(JwtSvidRequest request) {
String processType = request.processType();
if (processType == null || !PROCESS_TYPE_PATTERN.matcher(processType).matches()) {
throw new BadSvidRequestException("process_type must match [A-Za-z0-9_-]{1,63}");
}
String audience = request.audience();
if (audience == null || audience.isBlank()
|| audience.length() > MAX_AUDIENCE_LENGTH
|| containsControlCharacter(audience)) {
throw new BadSvidRequestException(
"audience must be non-blank, at most " + MAX_AUDIENCE_LENGTH
+ " characters, and free of control characters");
}
}
Comment on lines +63 to +64
KeyInfo activeKey = keyInfoService.getActiveKey();
String svid = JwtHelper.encode(claims, activeKey).getEncoded();
Comment on lines +9 to +10
@ConfigurationProperties(prefix = "uaa.spiffe")
public record SpiffeProperties(
this.properties = properties;
}

@PostMapping(value = "/jwt-svid/sign", consumes = "application/json", produces = "application/json")
Comment on lines +50 to +54
.securityMatcher("/jwt-svid/**")
.authorizeHttpRequests(auth -> {
auth.requestMatchers("/**").hasAuthority("uaa.resource");
auth.anyRequest().denyAll();
})
@strehle

strehle commented Sep 22, 2026

Copy link
Copy Markdown
Member

@rkoster @beyhan which RFC references this feature ? SPIFFE not equal to mTLS, but for us not clear in which context UAA should do what

@strehle strehle added the clarification needed The issue is not accepted but we need clarification label Sep 22, 2026

This branch has not been deployed

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

Labels

clarification needed The issue is not accepted but we need clarification

Projects

Development

Successfully merging this pull request may close these issues.

4 participants