20th of February, 2022:
I have published a version of this article adapted for Keycloak 17: Keycloak 17.0.0 with TLS in Docker compose behind Envoy proxy.
The 24 hours of Nürburgring race was just red flagged for the remainder of the night due to the fog. That’s a perfect opportunity to add TLS to my Keycloak Docker Compose setup described previously here[1].
There are multiple ways of setting up TLS for Keycloak, one of them being the native Java JKS key store / trust store gymnastics.
Well, that’s certainly a way to go. If you prefer that path, feel free to do so, details are here[2] but that’s a lot of work. I like my life simple so I choose to use a proxy to terminate TLS instead.
Let’s have a look at the original compose.yml file, it’s short:
|
|
To enable the proxy with TLS support, let’s modify the yaml file to this:
|
|
There are four differences in the new file:
- there is a new envoy service
- the keycloak service additionally depends on the envoy service
- the keycloak service no longer exposes the 28080 port on the host
- there is a new environment variable defined for the keycloak service:
PROXY_ADDRESS_FORWARDING: "true"
§envoy configuration
Looking closely at the envoy service, we can spot the host ./etc/envoy to container /etc/envoy volume bind. The proxy command references the /etc/envoy/envoy-keycloak.yaml configuration file. The file must have the yaml extension, yml is not going to work. The content is:
|
|
The directory structure looks like this:
.
├── compose.yml
└── etc
└── envoy
└── envoy-keycloak.yaml
You might ask what is this config file doing so let’s look at it from top to bottom.
- First, we define a listener bound to 0.0.0.0 on port 443 - standard HTTPS stuff.
- Next, we create a filter chain matching the idp.gruchalski.com domain name - this is the TLS SNI matching. The TLS SNI implies that our service, here Keycloak, will be accessed over HTTPS only and hostname advertised during the TLS handshake is used to find the upstream (cluster) target to forward the traffic to. As you can probably already imagine, I will be accessing Keycloak via https://idp.gruchalski.com.
- The connections matching the filtered domain will be forwarded to the proxy-domain1 cluster via the http_filter.
- The cluster forwards the requests to the load balancer endpoints, in this, we have one at keycloak:8080. This is the name of the container on Docker network used for this setup.
The part I’ve glossed over is the transport_socket.common_tls_context.tls_certificates. It points at the TLS certificate and key used for the filter for the domain.
Okay, a couple of caveats:
- we don’t have the certificate yet
- how do we access Keycloak using the domain name when it is running in local compose
§the domain name
Easy, modify the /etc/hosts file by adding:
127.0.0.1 idp.gruchalski.com
§certificates
This isn’t a rocket science either. In your case, you probably already have a domain name you want to use instead of idp.gruchalski.com so replace all occurences with your own domain in configs above and commands below.
Because Keycloak is used in the browser, we want real TLS certificates from one of the public trusted certificate authorities. Let’s Encrypt is for sure an awesome choice. We can get the LE certficites in multiple ways but at the core, we either need the control over the DNS for the dns-01 challenge or we need a http/https server reachable via the domain names for which the certificates should be issued. More about LE challenge types[3].
Long story short, as I am requesting the certifciates for the local compose setup, the http-01 and tls-alpn-01 challenges are not an option because Let’s Encrypt will not be able to call back to a server running on my local machine.
The dns-01 challenge is the way to go but it requires having an administrative control over the DNS server so the required TXT records can be created to complete the LE challenge. I have that, I use AWS Route 53 as my DNS of choice.
A couple of days ago, I have written about the LEGO client which I used for obtaining the certificates[4]. Here, I’d use the following command:
|
|
As a result, my file structure now looks like this:
.
├── compose.yml
└── etc
└── envoy
├── accounts
│ └── acme-v02.api.letsencrypt.org
│ └── radek@gruchalski.com
│ ├── account.json
│ └── keys
│ └── radek@gruchalski.com.key
├── certificates
│ ├── idp.gruchalski.com.crt
│ ├── idp.gruchalski.com.issuer.crt
│ ├── idp.gruchalski.com.json
│ └── idp.gruchalski.com.key
└── envoy-keycloak.yaml
The configuration is now complete.
After starting the setup with docker compose -f compose.yml up
, I can access my Keycloak by entering https://idp.gruchalski.com in the browser address bar. The TLS request is terminated at Envoy and Envoy finds the cluster based on the hostname advertised during the TLS handshake. The request is then forwarded to Keycloak on port 8080.
Installtion finalization is exactly the same as in the previous article.