Today there are many ways to secure applications. With the rise of Kubernetes you might search for a self hosted open source solution for Identity Management. One of the most popular and powerful candidates is Keycloak. Lets explore how these both work together.
For my sample scenario I am going to use minikube to setup a local Kubernetes cluster to work with. My goal is to protect an application running in this cluster without the need of adding any code to it. Additionally it should be a scaling solution that can be easily added to any other application running inside the cluster. As sample application to protect, I chose httpbin.
At the very first I need a running keycloak instance to authenticate to. Even though the keycloak-operator seems to be in a pretty early stage and might not work perfectly yet, I decided to give it a try. Lets clone the git repo and checkout the latest release.
In order to use the custom keycloak resource definitions coming from the operator, they need to be applied to the cluster. There is a Makefile target that can be used for this. Along with CRDs it also deploys some roles, bindings, service accounts etc. When these basic cluster preparations are done, the keycloak-operator can be deployed into the newly created keycloak namespace.
Since I do not want to lose my configured keycloak data every time I restart my minikube kubernetes
cluster, I want to setup keycloak with persistence. Before proceeding with the installation of keycloak
it is necessary to setup a
PersistentVolume, that can be used for the underlying postgres database.
Finally keycloak can be installed by using the
keycloaks.keycloak.org CRD. It is going to deploy a postgresql database
using our persistence volume, that then can get picked up by keycloak.
Keycloak is running and ready to be used. To setup an easy accessible address for the keycloak instance the minikube ingress addon together with a hostname entry can be used. While I am at adding hostnames, I already add another one that I need for resolving my sample httpbin application later on.
Of course the corresponding Kubernetes ingress resource needs to be created as well. This resource could also be created by the
keycloak operator by passing
externalAccess.enabled: True to the keycloak spec, but it did not work for me due to some missing annotation
for telling nginx to use https for the upstream service. So I created an ingress resource by myself.
If everything is set up correctly, the keycloak instance should be accessible now.
Now, after keycloak was successfully installed to the cluster, it is time to prepare the instance for
the client application. The easiest way might be to use the adminstration console UI from keycloak.
It can be accessed via browser at
http://keycloak.local/auth/admin/. The necessary admin credentials
have been initially created as kubernetes secrets by the keycloak operator while installing.
Taking the credentials to login to the adminstration console UI, the client configuration can be started. Following steps have to be done:
http://keycloak.local/authfor this realm.
openid-connectand access type
http://httpbin.local/oauth/callbackto valid Redirect URIs.
I called my realm
test and my client
httpbin. By setting the client access-type to
confidential a client-secret has
been generated by keycloak. It is needed for the upcoming gatekeeper configuration and can be found at the credentials tab of
the client configuration.
By having a running keycloak instance configured with proper client settings and a test user, everything is in place to protect the sample httbin application. As mentioned in the introduction I would like to secure the application without adding code. Here comes Keycloak Gatekeeper into play, which is an OIDC proxy service, handling authentication for an upstream application. The idea of gatekeeper is to have it as close as possible to the upstream application, thats why I am going to deploy it as sidecar to the httpbin application. But before proceeding with the deployment, the client-secret (from keycloak client configuration) needs to be accessible in the cluster.
Having the secret in place, finally the sample application along with gatekeeper can be deployed.
To make the OIDC discovery of gatekeeper work with the internal Keycloak Kubernetes service URL, it is necessary to pass it as argument
--openid-provider-proxy parameter. I found a related issue here.
To verify the protection of my httpbin application, I am going to use a couple of curl commands. First of all, what happens when I try to access my httpbin application?
Cool, I get redirected to the keycloak login page. Lets use the credentials of the test-user I created earlier.
To use the received access token for further commands, I am storing it inside a shell variable by using the command-line
jq for grabbing it from the JSON response.
Et Voilà! After getting an access token for my test-user, I can finally pass through gatekeeper to my httpbin application, showing that I am authenticated. Goal reached! :) For upstream services it might be necessary to get some user information as well. These can be obtained from custom headers forwarded by gatekeeper.
For having a better and more insightful reading experience I used curl here. Of course it is easier to verify the protection by navigate to
http://httpbin.local in browser and go through the flow there.
I think keycloak is a wonderful piece of software for handling user identity. It might be not the easiest to setup with Kubernetes yet, but I am confident that especially the keycloak-operator will improve quick and constantly. Using gatekeeper deployed as sidecar to the application you want to protect by providing a little configuration seems to me like a very scalable and consistent solution. At the time of microservices you want to focus as much as possible on business logic. This solution could save you from implementing same authentication layer over and over again. Additionally it can be applied to protect third-party applications, that do not support any auth flows by themselves, like dashboards etc.
Happy authenticating and authorizing!