Skip to content

Support routing based on TLS client certificate presence/signer for mixed-auth endpoints on shared port#1769

Open
akrambek wants to merge 5 commits into
aklivity:developfrom
akrambek:feature/1697
Open

Support routing based on TLS client certificate presence/signer for mixed-auth endpoints on shared port#1769
akrambek wants to merge 5 commits into
aklivity:developfrom
akrambek:feature/1697

Conversation

@akrambek
Copy link
Copy Markdown
Contributor

Fixes #1697

Comment on lines +121 to +140
bindings:
net0:
type: tls
kind: server
vault: server
options:
keys:
- localhost
trust:
- clientca
mutual: requested
routes:
- when:
- cert:
signer:
cn: serverca
exit: app0
- when:
- cert: false
exit: app1
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.

The TLS server has a localhost Server Certificate signed by the Server CA, and it also trusts Client Certificates signed by the Client CA. This Client CA has an alias clientca in the server vault.

This syntax and example is quite confusing because the route seems to be saying it matches any Client Certificate with a signer that has Common Name serverca, which seems to convey a Server CA.

I think we need to maintain the abstraction based on alias instead of unpacking the Common Name of the signer, so that we don't force config changes when the content of the vault changes such that the alias maps to a new CA with a different Common Name.

We can use mutual: none instead of the cert false to indicate no client certificate.

When mutual:none, then trust cannot be specified on the route when condition.
When trust is specified on the route when condition, then mutual:required is implied.
When condition does not support mutual:requested as a value to match on, that doesn't specify either outcome.

Suggested change
bindings:
net0:
type: tls
kind: server
vault: server
options:
keys:
- localhost
trust:
- clientca
mutual: requested
routes:
- when:
- cert:
signer:
cn: serverca
exit: app0
- when:
- cert: false
exit: app1
bindings:
net0:
type: tls
kind: server
vault: server
options:
keys:
- localhost
trust:
- clientca
mutual: requested
routes:
- when:
- trust:
- clientca
mutual: required # implied when trust is present, can be omitted
exit: app1
- when:
- mutual: none # implies `trust` must not be present
exit: app0

@akrambek akrambek requested a review from jfallows May 19, 2026 16:40
Comment on lines +83 to +102

/**
* Resolves the named trusted certificate entries from the vault by alias.
* <p>
* Used by callers that need to identify which configured trust alias signed a peer
* certificate (e.g. for routing decisions), without exposing the underlying certificate
* contents through configuration. Implementations should return only the aliases that
* resolve to a trusted certificate entry in the vault; aliases that do not resolve are
* omitted from the returned map.
* </p>
*
* @param certRefs list of vault entry names identifying the trusted certificates to resolve
* @return a map from resolved alias to the corresponding X.509 certificate, or {@code null}
* if no aliases could be resolved
*/
default Map<String, X509Certificate> resolveTrust(
List<String> certRefs)
{
return null;
}
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.

I'd like us to try to solve this without changing the VaultHandler abstraction if possible.
See comments below.

Comment on lines +1686 to +1706

boolean clientCertPresent = false;
List<String> clientCertTrustAliases = null;
try
{
Certificate[] peerCerts = tlsSession.getPeerCertificates();
if (peerCerts.length > 0 && binding != null)
{
X509Certificate leaf = (X509Certificate) peerCerts[0];
clientCertPresent = true;
clientCertTrustAliases = binding.resolveTrustAliases(leaf.getIssuerX500Principal());
}
}
catch (SSLPeerUnverifiedException ex)
{
// no client cert presented under mutual: requested
}

final TlsRouteConfig route = binding != null
? binding.resolve(authorization, tlsHostname, tlsProtocol, port, clientCertPresent, clientCertTrustAliases)
: null;
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.

The current VaultHandler abstract maps from aliases to TrustManagerFactory which we use to configure the SSLEngine.

Once that handshake completes successfully, we then want to know if we trust the client certificate to follow a specific route.

I think we can do that by creating a different TrustManagerFactory instance (via the VaultHandler) per route, for just the trusted aliases on that route. Then we can assess if the client certificate is trusted by using the TrustManager[] for that route, iterating over it to find the X509TrustManager instances, and checking if the client is trusted, if so follow the route.
For the mutual:none case, we just need to check that the client did not present a peer certificate chain.

So in terms of APIs, we can probably just pass the client certificate chain to binding.resolve(...) and let it figure out which routes are valid using the TrustManagerFactory for the trusted routes (mutual:required), and verifying no client certificate chain for the mutual:none routes.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support routing based on TLS client certificate presence/signer for mixed-auth endpoints on shared port

2 participants