8000 Update HaProxySslClientCertificateLookup to load Base64 encoded DER Chain by ryanemerson · Pull Request #49181 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content

Update HaProxySslClientCertificateLookup to load Base64 encoded DER Chain - #49181

Merged
ahus1 merged 8 commits into
keycloak:mainfrom
ryanemerson:49180/haproxy_base64_der_certs
May 26, 2026
Merged

ahus1 merged 8 commits into
keycloak:mainfrom
ryanemerson:49180/haproxy_base64_der_certs

Conversation

@ryanemerson
Copy link
Copy Markdown
Contributor

headers

Closes #49180

ryanemerson added a commit to ryanemerson/keycloak-quickstarts that referenced this pull request May 20, 2026
@ryanemerson
ryanemerson commented May 20, 2026 •
Copy link
Copy Markdown
Contributor Author

@ahus1 @pruivo as agreed on the call earlier, these are the changes to make HaProxySslClientCertificateLookup work well with a simple HAProxy config of:

    # Forward verified client certificate and chain to Keycloak
    http-request set-header Client-Cert :%[ssl_c_der,base64]: if { ssl_c_used } { ssl_c_verify 0 }
    http-request set-header Client-Cert-Chain :%[ssl_c_chain_der,base64]: if { ssl_c_used } { ssl_c_verify 0 }

I have pushed a commit to keycloak/keycloak-quickstarts#762 which has the required changes to easily test this.

ryanemerson added a commit to ryanemerson/keycloak-quickstarts that referenced this pull request May 20, 2026
Depends on the changes in keycloak/keycloak#49181

Signed-off-by: Ryan Emerson <remerson@ibm.com>
ryanemerson added a commit to ryanemerson/keycloak-quickstarts that referenced this pull request May 21, 2026
Depends on the changes in keycloak/keycloak#49181

Signed-off-by: Ryan Emerson <remerson@ibm.com>
ryanemerson added a commit to ryanemerson/keycloak-quickstarts that referenced this pull request May 21, 2026
Depends on the changes in keycloak/keycloak#49181

Signed-off-by: Ryan Emerson <remerson@ibm.com>
@ryanemerson
Copy link
Copy Markdown
Contributor Author

Upon further investigation we can update the provider in a backwards compatible way. The provider makes use of PemUtils.decodeCertificate(pem) which handles base64 certificates. So the following config will work when we have a single intermediate cert.

HAProxy:

http-request set-header Client-Cert %[ssl_c_der,base64] if { ssl_c_used } { ssl_c_verify 0 }
http-request set-header Client-Cert-Chain_0 %[ssl_c_chain_der,base64] if { ssl_c_used } { ssl_c_verify 0 }

Keycloak:

KC_SPI_X509CERT_LOOKUP__PROVIDER: haproxy
KC_SPI_X509CERT_LOOKUP__HAPROXY__SSL_CLIENT_CERT: Client-Cert
KC_SPI_X509CERT_LOOKUP__HAPROXY__SSL_CERT_CHAIN_PREFIX: Client-Cert-Chain

We can keep above handling when SSL_CERT_CHAIN_PREFIX is configured, however we should update HaProxySslClientCertificateLookup so that when SSL_CERT_CHAIN is provided, we read CERTIFICATE_CHAIN_LENGTH certs from the single base64 encoded chain provided by HAProxy.

HAProxy:

http-request set-header Client-Cert %[ssl_c_der,base64] if { ssl_c_used } { ssl_c_verify 0 }
http-request set-header Client-Cert-Chain %[ssl_c_chain_der,base64] if { ssl_c_used } { ssl_c_verify 0 }

Keycloak:

KC_SPI_X509CERT_LOOKUP__PROVIDER: haproxy
KC_SPI_X509CERT_LOOKUP__HAPROXY__SSL_CLIENT_CERT: Client-Cert
KC_SPI_X509CERT_LOOKUP__HAPROXY__SSL_CERT_CHAIN: Client-Cert-Chain

@ryanemerson ryanemerson changed the title Update HaProxySslClientCertificateLookup to load Base64 encoded DER Update HaProxySslClientCertificateLookup to load Base64 encoded DER Chain May 22, 2026
@ryanemerson
ryanemerson marked this pull request as ready for review May 22, 2026 16:24
@ryanemerson
ryanemerson requested review from a team as code owners May 22, 2026 16:24
@rmartinc rmartinc left a comment
Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Thanks @ryanemerson for the PR! But if I understanding this correctly the chain in HA proxy contains the leaf certificate too (it's the full chain presented by the client in the mTLS negitation), so the older header with just the leaf is not needed. In this regard is different to rfc 9440. Check if I'm not mistaken here.

I would try to get real headers from HA proxy for the tests and create new files with them instead of reusing the RFC 9440 ones. Just to be sure we are testing what HA proxy is really sending.

I'm also adding @shawkins for the review as he is more experienced with the certificate lookup implementations than me. 😄

@rmartinc
rmartinc requested a review from shawkins May 25, 2026 08:57
@ryanemerson
ryanemerson commented May 25, 2026 •
Copy link
Copy Markdown
Contributor Author

Thanks for the review @rmartinc

But if I understanding this correctly the chain in HA proxy contains the leaf certificate too

That's not the case, it does not contain the leaf certificate, only the intermediaries.

HAProxy relies upon the OpenSSL SSL_get_peer_cert_chain method:

SSL_get_peer_cert_chain() returns a pointer to STACK_OF(X509) certificates forming the certificate chain sent by the peer. If called on the client side, the stack also contains the peer's certificate; if called on the server side, the peer's certificate must be obtained separately using SSL_get_peer_certificate(3). If the peer did not present a certificate, NULL is returned.

https://docs.openssl.org/master/man3/SSL_get_peer_cert_chain/

I would try to get real headers from HA proxy for the tests and create new files with them instead of reusing the RFC 9440 ones. Just to be sure we are testing what HA proxy is really sending.

Yes that's a good point, updated to use a Base64 cert and cert chain consisting of two intermediary certs generated by HAProxy. I have also updated the test to assert the expected cert Subject DN for each index in the chain.

@ahus1 ahus1 left a comment •
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hello @ryanemerson - looks good overall. Some nitpicks below.

What I now wonder: Is haproxy rfc9440 compliant or not? I remember you referenced and issue in one of our meetings. If it is not, and if we're waiting for an issue to be implemented, it would be good to reference it somewhere in the docs or the code.

(Reading your comment to Ricardo, this is partly answered there, which is "no, it is not compliant?")

Comment thread docs/guides/server/reverseproxy.adoc Resolved
* provide a built-in mechanism to define a header per intermediate cert in the chain . Example HAProxy config:
* <pre>
* http-request set-header Client-Cert %[ssl_c_der,base64]
* http-request set-header Client-Cert-Chain_0 %[ssl_c_chain_der,base64]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm confused: In this line, %[ssl_c_chain_der,base64] produces a single certificate, while below it produces the whole chain?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It only works if a single intermediate cert exists in the chain. I can make that limitation clearer in the JavaDoc, as well as stating that this is now deprecated.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

JavaDocs updated, hopefully this makes the limitation of using the old prefix approach clearer.

Comment on lines +463 to +473
Example HAProxy configuration:

[source]
----
http-request set-header Client-Cert %[ssl_c_der,base64] if { ssl_c_used } { ssl_c_verify 0 }
http-request set-header Client-Cert-Chain %[ssl_c_chain_der,base64] if { ssl_c_used } { ssl_c_verify 0 }
----

Corresponding {project_name} configuration:

<@kc.start parameters="--spi-x509cert-lookup--provider=haproxy --spi-x509cert-lookup--haproxy--ssl-client-cert=Client-Cert --spi-x509cert-lookup--haproxy--ssl-cert-chain=Client-Cert-Chain --spi-x509cert-lookup--haproxy--certificate-chain-length=2"/>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd say this should better be described in the haproxy-reencrypt.adoc file. At the same time, having it here aigns it with the other proxies.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I can add it to haproxy-reencrypt.adoc as an optional configuration as well. I think it makes sense to keep the changes in reverseproxy to be consistent with the other providers docs, as we don't plan to add a dedicated blueprint for each proxy.

To simplify the documentation and user steps, I could make it so that the haproxy provider has default header values for both the cert and cert-chain e.g. "Client-Cert" and "Client-Cert-Chain", that way there's two less KC CLI options that the user needs to specify. If a user configures ssl-cert-chain-prefix then we use that for backwards compatibility reasons and log a warning. WDYT @ahus1 @pruivo ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm fine with the suggestion.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I like the idea.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Some second thoughts: We must not change the default behavior, as people might not filter those headers in their proxy. With that, they would be vulnerable (or at least someone can pass in a header). They might today not have the parameter specified as they don't care about the intermediate certificates.

So we would need to keep that change until KC 27 (as it is a breaking change).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch. I'll leave the defaults as is then 👍

@ahus1 ahus1 self-assigned this May 25, 2026
@pruivo pruivo left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Some minor nitpicks :)

Comment thread docs/guides/server/reverseproxy.adoc Resolved
headers

Closes keycloak#49180

Signed-off-by: Ryan Emerson <remerson@ibm.com>
Signed-off-by: Ryan Emerson <remerson@ibm.com>
…rt for ssl-cert-chain

Signed-off-by: Ryan Emerson <remerson@ibm.com>
Signed-off-by: Ryan Emerson <remerson@ibm.com>
…d cert files and assert Subject DNs

Signed-off-by: Ryan Emerson <remerson@ibm.com>
- Deprecate use of sslCertChainPrefix
- Add note in upgrading guide
- Clarify javadoc
- Update HAProxy blueprint to include ssl-cert-chain usage
- Add test cases for invalid HTTP header values

Signed-off-by: Ryan Emerson <remerson@ibm.com>
@ryanemerson
ryanemerson force-pushed the 49180/haproxy_base64_der_certs branch from f6df9c1 to dc6b218 Compare May 26, 2026 10:20
@ryanemerson
Copy link
Copy Markdown
Contributor Author

Is haproxy rfc9440 compliant or not? I remember you referenced and issue in one of our meetings. If it is not, and if we're waiting for an issue to be implemented, it would be good to reference it somewhere in the docs or the code.

Not. I have updated the JavaDocs of HaProxySslClientCertificateLookup to detail the current limitations of HAProxy and provided an issue to the tracker issue.

Thanks for the reviews @ahus1 @pruivo. I believe I have addressed all comments, but I would like your input on #49181 (comment)

@pruivo pruivo left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A couple of code suggestions :)

Signed-off-by: Alexander Schwartz <alexander.schwartz@gmx.net>
Signed-off-by: Ryan Emerson <remerson@ibm.com>
ryanemerson added a commit to ryanemerson/keycloak-quickstarts that referenced this pull request May 26, 2026
Depends on the changes in keycloak/keycloak#49181

Signed-off-by: Ryan Emerson <remerson@ibm.com>
@ahus1
ahus1 dismissed rmartinc’s stale review May 26, 2026 20:07

was addressed

@ahus1
ahus1 enabled auto-merge (squash) May 26, 2026 20:07
@ahus1 ahus1 left a comment
DD8F
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approving based on @pruivo's review

@ahus1
ahus1 merged commit 5253082 into keycloak:main May 26, 2026
137 of 148 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unable to easily provide full certificate chain with HaProxySslClientCertificateLookup

5 participants

0