OIDC Authentication in RKE2 Kubernetes with Keycloak
In order to avoid certificate- and token-based authentication in your Kubernetes cluster, you can use an OpenID Connect (OIDC) identity provider to authenticate users. When running kubectl
, we want to automatically open a browser window to sign in and then retrieve a short-lived token to authenticate our further requests against the Kubernetes API Server.
This guide will show you how to use the open-source OIDC-compatible identity provider Keycloak to achieve exactly that. I run all Kubernetes clusters with RKE2, so this guide will focus on RKE2, however the majority of steps are absolutely identical for other ways of deploying Kubernetes.
Keycloak
I assume that you already have a Keycloak instance up and running somewhere. This instance needs to be reachable from your Kubernetes cluster as well as from the clients you want to authenticate. Usually you can operate Keycloak publicly, as long as you adhere to some basic production security best practices such as running it behind a reverse proxy and restricting access to the admin console (which you need to enter for the next step).
Client
First of all, you need to configure a so-called Client in Keycloak. This might sound like creating an end-user, but a Client in Keycloak is simply some kind of application that wants to authenticate users against Keycloak. In our case, this is a Kubernetes cluster.
- Open your Keycloak admin console, select the Clients tab on the left and click on Create.
- Choose OpenID Connect as client type.
- Enter a Client ID of your choice. This is the identifier of the Kubernetes cluster in Keycloak. If you’re authenticating multiple clusters against the same Keycloak instance, you should prefix the client IDs and use a unique name for each cluster (e.g.
rke2-cluster-1
,rke2-cluster-2
etc.). - On the second page (Capability config), enable Client authentication and make sure that the Standard flow as well as Direct access grants are activated (which should be the case by default).
- On the third page (Login settings), we need to configure the Valid redirect URIs. This is the address where the end-user is allowed to be redirected to after a successful authentication, depending on what was requested during the authentication process. You can enter
http://*
as wildcard to allow all addresses, as the authentication is happening in the end-user’s browser locally anyway. - No other settings need to be changed, so you can complete the form and click on Save.
Groups
Later, when authenticated users are making requests to the Kubernetes API Server, you somehow assign certain RBAC permissions to them. I’d suggest using groups to manage these permissions, instead of assigning permissions to users directly. You can create as many groups as you want and need. Simply give them a meaningful name.
- Open your Keycloak admin console, select the Groups tab on the left and click on Create.
- Enter a Name for the group and click on Create.
Client Scope
We now need to create a so-called Client Scope for the group membership information. This is important so that Keycloak adds a list of groups, the authenticated user is a member of, to the token payload it issues. This is then used by the Kubernetes API Server to grant the user the corresponding RBAC permissions. Without that client scope, Kubernetes knows who the user is, but not which groups they belong to and subsequently not which group permissions apply. If you decided to assign permissions to users instead of groups, you can skip this step.
- Open your Keycloak admin console, select the Client scopes tab on the left and click on Create client scope.
- Use
groups
as Name and hit Save. - After the client scope has been created, two more tabs will appear: Mappers and Scope.
- Open the Mappers tab and click on Add mapper (by configuration).
- Search for Group Membership and select it.
- Again, set
groups
as Name and Token Claim Name. - Make sure that you disable the Full group path option and click Save.
In order to glue the client scope to our client, we need to assign it.
- Go back to the Clients tab on the left and select your Kubernetes cluster client.
- Open the Client scopes tab and click Add client scope.
- Choose your freshly created
groups
client-scope and click Add (Default). - Save the changes to the client.
Now, whenever the end-user authenticates against the Kubernetes Keycloak client, the group membership(s) will be added to the issued token, so Kubernetes can evaluate it and grant the user the corresponding RBAC permissions.
User
Finally, you can create a user that you can authenticate with. You can do so in the Users tab on the left of your Keycloak admin console. Click on Create and enter the user’s credentials. Don’t forget to join the user into the group(s) you created before.
Kubernetes (RKE2)
Now that we have the Keycloak instance set up and configured for Kubernetes to use, we need to configure RKE2 to use it. To do so, you need to edit your RKE2 configuration file on all your RKE2 server nodes:
Issuer URL
Make sure to replace the keycloak.example.com
placeholder with your actual Keycloak domain. The /realms/master
path is the default path for the master
realm in Keycloak. If you named your realm differently, or created a new one specifically for your Kubernetes cluster, make sure to adjust the path accordingly. You can verify that your OIDC issuer URL is correct by navigating to the URL in your browser and appending /.well-known/openid-configuration
to it:
This will give you the OpenID Connect configuration file, your Kubernetes API server will attempt to fetch upon startup. If you see the configuration in your terminal, you know that your issuer URL has been set correctly.
Client ID
Also, make sure you replace <your-client-id>
with the actual client ID you chose in Keycloak when setting up the client.
CA File
Last but not least, you can see a --oidc-ca-file
parameter in the configuration. This is the file containing the CA that issued the TLS certificate for your Keycloak instance. Kubernetes uses this to verify that it’s talking to the correct Keycloak instance.
If you’re unsure what the CA is (e.g. because you’re using Let’s Encrypt and didn’t think about that, yet), you can retrieve it with a bit of openssl
magic in your terminal:
Your CA certificate should be the last in the chain. You can copy it and store it in a file, e.g. under /etc/rancher/rke2/oidc-ca.crt
. You don’t need to bother about adding the file or the containing directory as extra volume mount to the Kubernetes API server. RKE2 automatically detects the file and adds the corresponding mount. Nice!
Username Claim
When the JWT token is issued by Keycloak, it will contain a field called preferred_username
which contains your Keycloak username.
Alternatively, you can use the sub
field as the username. This is the subject identifier and is unique for each Keycloak user. However, in case of Keycloak, this will be a UUID. Not so handy for humans, if you want to directly assign permissions to a user or want to see at a glance which user did what in the audit log.
However, don’t use the username
field, because it will contain the full name of the user, including spaces and other special characters.
This username will then be prefixed with the --oidc-username-prefix
which I recommend setting to e.g. oidc:
(as in the example configuration above). That will cause the Kubernetes API server to prepend oidc:
to the username in Kubernetes which allows you to easily distinguish between OIDC-authenticated users and “regular” users. This is especially useful for RBAC configuration and for audit logging.
Groups Claim
Same as with the username claim, the groups
field will contain an array of group names the user is a member of in Keycloak. Thanks to the --oidc-groups-prefix
parameter, the oidc:
prefix will be prepended to each group name as well. As with the usernames, this is neat for distinguishing OIDC-authenticated groups from other groups, such as Kubernetes-native system groups.
If not prefixed, the groups are directly recognized by Kubernetes. This can be useful as well, but for an extra layer of security and transparency, I recommend prefixing it and then mapping it back in Kubernetes using ClusterRoleBindings
or RoleBindings
.
Restart RKE2 Server
After you’ve updated the configuration, you need to restart all affected RKE2 server nodes. On each node run the following command:
Please make extra-sure that you wait between restarting each node until the node gets back into a Ready
state, otherwise you’ll risk losing your quorum. That would be a really bad day for you, trust me.
You can check the node status by running kubectl get nodes
.
Also, don’t worry about your existing authentication. You’re probably using a token or a certificate to authenticate and this won’t change. It will still work exactly as before in addition to the OIDC authentication.
kubectl
Now that you have your Kubernetes API server configured to use Keycloak for OIDC authentication, you’re ready to try it out for the first time. In order to get a smooth authentication experience, you should install the great kubelogin plugin. This plugin will automatically open your browser and point it to the Keycloak instance to sign in and take care of token retrieval and renewal.
Once you have installed the plugin, it’s time to adjust your kubectl
client configuration to use it. Open your kubeconfig
file (usually located at ~/.kube/config
) and amend your user configuration (or create a new one) with the following parameters:
As with the RKE2 configuration, you need to replace the Issuer URL, Client ID and this time the Client Secret as well. Latter of which can be found in your Keycloak admin console under the Credentials tab of your client. Also, don’t forget to assign the user to a context, so your new configuration is actually used.
Then you’re ready to go and authenticate with kubectl
for the first time. Try it out by running
Your browser will be opened, you’ll see the Keycloak login screen and after a successful login you should see … an error. I know, I know, but it’s worth it in the end, no worries. Your user is now authenticated, but that doesn’t mean that you have any permissions to do anything whatsoever on your Kubernetes cluster. RBAC is saving the day again!
RBAC
The final step is now to assign certain Kubernetes permissions to your user - or better: to the group(s) you created in Keycloak. To test things out, you can simply create a new ClusterRole
that allows your group to list nodes and then bind it to your group using a ClusterRoleBinding
.
ClusterRole
Save this to a file, e.g. clusterrole.yaml
and apply it using kubectl apply -f clusterrole.yaml
. If you’re unfamiliar with RBAC in Kubernetes, this will create a role without any namespace scope, granting the permission to list all nodes across the whole cluster.
However, the cluster still doesn’t know who the role we just created is assigned to.
ClusterRoleBinding
This is the final piece of glue that ties everything together. The ClusterRoleBinding
will bind the oidc-test
cluster role to the group(s) you created in Keycloak.
Take a look at the highlighted line where we specified the group name. We’re using the oidc:
prefix here, as we also did in the RKE2 configuration. This is mandatory, otherwise Kubernetes will not recognize the group binding.
Save this to a file, e.g. clusterrolebinding.yaml
and apply it using kubectl apply -f clusterrolebinding.yaml
.
Final Test
Now that everything is set up, you can test everything again.
You should now see the nodes in the output. If you don’t, check the logs of the Kubernetes API server for any errors and double-check your configuration.
🎉 It works!
Final Words
For my security-focused audience, I’d like to point out a neat feature of this authentication mechanism. The JWT token that is issued by Keycloak is only valid for 60 seconds. It probably took you a bit longer to create the RBAC-related manifests in the last step. However, kubectl
didn’t prompt you to authenticate again, even though the token had already expired. This is because kubelogin
automatically retrieved a new token for you behind the scenes.
This token is statically signed and the Kubernetes API server trusts the token issuer (Keycloak). It does not revalidate the token by contacting Keycloak each time it receives a request. This makes the authentication mechanism lightning fast. However, what if we now suspend a Keycloak user? Doesn’t that mean the suspension is useless?
Nope. As the token is only valid for 60 seconds before it needs to be renewed, the worst case is that the user still has access to the Kubernetes API for another 60 seconds. After that, the token will expire. Then, during renewal, Keycloak will notice the suspension and the token won’t be issued.
If you’re interested in more details or need help setting up OIDC authentication in your Kubernetes cluster, feel free to reach out to me for consulting services and assistance.