Support routing based on TLS client certificate presence/signer for mixed-auth endpoints on shared port#1769
Support routing based on TLS client certificate presence/signer for mixed-auth endpoints on shared port#1769akrambek wants to merge 5 commits into
Conversation
| 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 |
There was a problem hiding this comment.
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.
| 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 |
|
|
||
| /** | ||
| * 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; | ||
| } |
There was a problem hiding this comment.
I'd like us to try to solve this without changing the VaultHandler abstraction if possible.
See comments below.
|
|
||
| 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; |
There was a problem hiding this comment.
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.
Fixes #1697