This workshop provides an example of how to deploy a Keycloak server on Kubernetes and use it to secure the following applications
- Quarkus with OIDC extension
- SpringBoot with spring-security
- React frontend that communicates with both backend applications
It provides two ways on how to follow instructions:
All steps are automated. This can be used to quickly setup the environment without any manual steps.
- kubectl (Installation guide here
- Minikube (Installation guide here)
- jq (Installation guide here)
- Taskfile (Installation guide here)
- OpenSSL for creating certificates
All steps are done manually and are described in this guide.
It is recommended to use Minikube for manual deployment as well as it is easier to configure all configurations like Ingress and realm configuration because we assume the app hosts are in form of app-name.${namespace}.${minikube-ip}.nip.io
.
It should be possible to use the steps with any Kubernetes cluster, however, you need to replace hosts manually everywhere.
Each place that needs the change should be mentioned in the guide.
- kubectl (Installation guide here
- jq (Installation guide here)
- OpenSSL for creating certificates
Below you can find the steps of the workshop that contain both manual and automated steps.
This workshop requires Kubernetes environment that supports Ingress.
To start Minikube and enable Ingress, execute the following command:
[Automated]
task prepare-minikube
[Manual]
minikube start
minikube addons enable ingress
minikube addons enable ingress-dns
This step deploys Keycloak operator, Postgres database, Keycloak server and a Keycloak realm names riviera-dev-realm
with all necessary resources.
task keycloak-deploy-all
-
Create a namespace for Keycloak
kubectl create namespace keycloak-namespace
-
Deploy Keycloak operator - this deploys Keycloak operator version 25.0.1
kubectl -n keycloak-namespace apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/refs/tags/25.0.1/kubernetes/keycloaks.k8s.keycloak.org-v1.yml kubectl -n keycloak-namespace apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/refs/tags/25.0.1/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml kubectl -n keycloak-namespace apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/refs/tags/25.0.1/kubernetes/kubernetes.yml
-
Deploy Postgres database - this deploys PostgreSQL database that will be used as Keycloak storage. Also, create Kubernetes Secret with the database credentials.
kubectl -n keycloak-namespace apply -f keycloak/k8s-resources/postgres.yaml kubectl -n keycloak-namespace create secret generic keycloak-db-secret --from-literal=username=testuser --from-literal=password=testpassword
-
We will configure TLS for Keycloak and therefore, we need to create a Kubernetes Secret with a certificate and a key.
- Create a self-signed certificate using OpenSSL, for this step it is necessary to replace
KEYCLOAK_URL
andKEYCLOAK_IP
with actual values. For Minikube the values are:KEYCLOAK_URL
=keycloak.keycloak-namespace.$(minikube ip).nip.io
andKEYCLOAK_IP
=$(minikube ip)
.export KEYCLOAK_IP=$(minikube ip) export KEYCLOAK_URL=keycloak.keycloak-namespace.${KEYCLOAK_IP}.nip.io openssl req -subj "/CN=${KEYCLOAK_URL}/O=Test Keycloak./C=US" -addext "subjectAltName = IP:${KEYCLOAK_IP}, DNS:${KEYCLOAK_URL}" -newkey rsa:2048 -nodes -keyout keycloak_key.pem -x509 -days 365 -out keycloak_certificate.pem
- Create a Kubernetes Secret with the certificate and key. Note that
keycloak-tls-secret
is the name of the secret that will be used in the Keycloak deployment later.kubectl -n keycloak-namespace create secret tls keycloak-tls-secret --cert=keycloak_certificate.pem --key=keycloak_key.pem
- Later in the workshop, we need to configure applications to trust this certificate, therefore we will prepare JKS truststore named
i-trust-keycloak.jks
. NoteKEYCLOAK_URL
is the same as in the previous step.openssl x509 -outform der -in keycloak_certificate.pem -out keycloak_certificate.der keytool -import -alias ${KEYCLOAK_URL} -keystore i-trust-keycloak.jks -file keycloak_certificate.der -storepass password -noprompt
- Create a self-signed certificate using OpenSSL, for this step it is necessary to replace
-
Deploy Keycloak server to Kubernetes. Before deploying, the file
keycloak/k8s-resources/keycloak.yaml
needs to contain correct Keycloak host.- For Minikube, replace hosts using
envsubst
command using the following.NAMESPACE=keycloak-namespace MINIKUBE_IP=$(minikube ip) envsubst < keycloak/k8s-resources/keycloak.yaml | kubectl -n keycloak-namespace apply -f -
- For other Kubernetes clusters, replace host manually in the file
keycloak/k8s-resources/keycloak.yaml
on line with commentREPLACE WITH KEYCLOAK_URL
.kubectl -n keycloak-namespace apply -f keycloak/k8s-resources/keycloak.yaml
- For Minikube, replace hosts using
-
Wait until Keycloak is ready. You can use the following command that will do the waiting for you.
kubectl -n keycloak-namespace wait --for=condition=Ready --timeout=180s keycloaks.k8s.keycloak.org/keycloak-riviera-dev
-
This workshop assumes there is no TLS termination on the proxy configured. At the moment, there seems to be a bug in Minikube and therefore we need to use a workaround to make Ingress advertise the correct certificate by the following command. Make sure you replace
KEYCLOAK_URL
the same way as in the previous steps.- For Minikube execute the following command.
NAMESPACE=keycloak-namespace MINIKUBE_IP=$(minikube ip) envsubst < keycloak/k8s-resources/keycloak-ingress-tls-patch.yaml > ingress-patch-replaced.yaml kubectl -n keycloak-namespace patch ingress keycloak-riviera-dev-ingress --patch-file ingress-patch-replaced.yaml
- For other Kubernetes clusters replace
KEYCLOAK_URL
manually in the filekeycloak/k8s-resources/keycloak-ingress-tls-patch.yaml
and execute the following command.kubectl -n keycloak-namespace patch ingress keycloak-riviera-dev-ingress --patch-file keycloak/k8s-resources/keycloak-ingress-tls-patch.yaml
- For Minikube execute the following command.
-
Create Keycloak realm with all necessary resources. This step creates:
- Realm
riviera-dev-realm
- Clients for all application we will use in this workshop
- Roles
admin
anduser
- Users
admin
(with rolesuser
andadmin
) anduser
(with roleuser
) with passwordsadmin
anduser
.
Keycloak needs to be aware of locations of each application it is securing, therefore the file
keycloak/k8s-resources/keycloak-realm-import.yaml
needs to be updated with the actual URLs.- For Minikube execute the following command.
NAMESPACE=keycloak-namespace MINIKUBE_IP=$(minikube ip) envsubst '${NAMESPACE},${MINIKUBE_IP}' < keycloak/k8s-resources/keycloak-realm-import.yaml | kubectl -n keycloak-namespace apply -f -
- For the other Kubernetes clusters update URLs manually. Note this step can be done later in the Keycloak admin console. Look for the comment
# REPLACE WITH [APPLICATION]_URL
and replace the URLs with actual values. Note the comment sometimes contains information to append/*
to the URL. Then execute the following command:kubectl -n keycloak-namespace apply -f keycloak/k8s-resources/keycloak-realm-import.yaml
- Realm
Obtain the Keycloak URL and credentials by executing the following command (for [Automated] deployments this is printed automatically):
echo "Access Keycloak on https://$(kubectl -n keycloak-namespace get ingress/keycloak-riviera-dev-ingress -o jsonpath='{.spec.rules[0].host}')"
echo "Username:" $(kubectl -n keycloak-namespace get secrets keycloak-riviera-dev-initial-admin -o=jsonpath='{.data.username}' | base64 -d)
echo "Password:" $(kubectl -n keycloak-namespace get secrets keycloak-riviera-dev-initial-admin -o=jsonpath='{.data.password}' | base64 -d)
Access Keycloak on the printed URL and login with the printed credentials. You should be able to access Keycloak admin console and riviera-dev-realm
should be available if created.
Obtain token for both users by executing the following commands:
export KEYCLOAK_URL=$(kubectl -n keycloak-namespace get ingress/keycloak-riviera-dev-ingress -o jsonpath='{.spec.rules[0].host}')
export USER_TOKEN=$(curl -s --insecure -X POST https://$KEYCLOAK_URL/realms/riviera-dev-realm/protocol/openid-connect/token \
-u 'quarkus-oidc-extension:1LZ65XcapsfnwEOLsByUW7KKv05mGsZF' \
-H "content-type: application/x-www-form-urlencoded" \
-d "username=user&password=user&grant_type=password" | jq --raw-output '.access_token')
export ADMIN_TOKEN=$(curl -s --insecure -X POST https://$KEYCLOAK_URL/realms/riviera-dev-realm/protocol/openid-connect/token \
-u 'quarkus-oidc-extension:1LZ65XcapsfnwEOLsByUW7KKv05mGsZF' \
-H "content-type: application/x-www-form-urlencoded" \
-d "username=admin&password=admin&grant_type=password" | jq --raw-output '.access_token')
This step deploys a Quarkus with OIDC extension that is secured by Keycloak.
This step requires additional prerequisites:
- Java 17
task quarkus-deploy-all
- Change directory to quarkus-oid-extension.
cd quarkus-oidc-extension
- If you are not using Minikube, it is necessary to do some configuration changes in
application.properties
file.- configure Quarkus Kubernetes extension to use correct image where you plan to push the Docker image that will be built in the following step.
- Configure Ingress host here to match the correct host.
- Configure quarkus.oidc.auth-server-url to point to
$KEYCLOAK_URL/realms/riviera-dev-realm
. - Configure
quarkus.http.cors.origins
to match the host of the javascript-react application.
- Build the Quarkus source code application. Note the parameters
minikube.ip
andnamespace
are usable only for Minikube and ../mvnw install -DskipTests -Dminikube.ip=$(minikube ip) -Dnamespace=keycloak-namespace
- Build the Docker image and push to a registry so it is available for the Kubernetes cluster.
For Minikube execute the following:
eval $(minikube docker-env) && docker build . -t quarkus-oidc-extension:1.0.0-SNAPSHOT -f src/main/docker/Dockerfile.jvm
- This application also needs to trust Keycloak certificate and therefore we need to configure truststore. The application expects the truststore to be present in a Kubernetes Secret with name
i-trust-keycloak-secret
. Create it using the following command.kubectl -n keycloak-namespace create secret generic i-trust-keycloak-secret --from-file=../i-trust-keycloak.jks
- Deploy the Quarkus application to Kubernetes. This step creates a Kubernetes Deployment, Service and Ingress for the Quarkus application. Note the Kubernetes resources are automatically created by the Quarkus Kubernetes extension in step 3.
kubectl -n keycloak-namespace apply -f target/kubernetes/kubernetes.yml
- Change directory back to the root of the project.
cd ..
Quarkus application contains 2 endpoint /user
and /admin
. Both are secured by Keycloak and require a valid token to access.
/user
is accessible by users with role user
and /admin
is accessible by users with role admin
.
You can change the endpoint and the used token in the following command to test the access.
curl -i "http://$(kubectl -n keycloak-namespace get ingress/quarkus-oidc-extension -o jsonpath='{.spec.rules[0].host}')/user" --header "Authorization: Bearer $USER_TOKEN"
Expected output is:
HTTP/1.1 200 OK
Date: Tue, 02 Jul 2024 18:12:32 GMT
Content-Type: text/plain;charset=UTF-8
Content-Length: 49
Connection: keep-alive
Cache-Control: no-cache
Hello from Quarkus user endpoint: User The First!
This step deploys a SpringBoot application with spring-security that is secured by Keycloak.
This step requires additional prerequisites:
- Java 17
task spring-security-deploy
- If you are not using Minikube, it is necessary to do some configuration changes.
- Change directory to spring-security.
cd spring-security
- Build the SpringBoot application. Note this step also builds the Docker image.
- For Minikube execute the following command.
eval $(minikube docker-env) && ./mvnw spring-boot:build-image -DskipTests -Dminikube.ip=$(minikube ip) -Dnamespace=keycloak-namespace
- For other Kubernetes clusters, adjust the configuration based on the step 1., execute the following command and push the built image to the registry that is accessible by the Kubernetes cluster.
./mvnw spring-boot:build-image -DskipTests
- For Minikube execute the following command.
- This application also needs to trust Keycloak certificate and therefore we need to configure truststore. Skip this step if you already done that for Quarkus applicaiton. The application expects the truststore to be present in a Kubernetes Secret with name
i-trust-keycloak-secret
. Create it using the following command.kubectl -n keycloak-namespace create secret generic i-trust-keycloak-secret --from-file=../i-trust-keycloak.jks
- Create the Kubernetes resources for the SpringBoot application. This step creates a Kubernetes Deployment, Service.
- For Minikube execute the following command. Note, thanks to
CHECKSUM
you can rebuild the image, execute the following command again and the pod will be automatically restarted.NAMESPACE=keycloak-namespace MINIKUBE_IP=$(minikube ip) CHECKSUM=$(sha256sum target/spring-security-1.0.0-SNAPSHOT.jar | awk '{ print $1 }') \ envsubst < k8s-resources/spring-security.yaml \ | kubectl -n keycloak-namespace apply -f -
- For other Kubernetes clusters replace all occurrences of
# Replace with ...
comment ink8s-resources/spring-security.yaml
and execute the following command.kubectl -n keycloak-namespace apply -f k8s-resources/spring-security.yaml
- For Minikube execute the following command. Note, thanks to
- Change directory back to the root of the project.
cd ..
Spring-security application contains 2 endpoints /
and /protected/premium
. Both are secured by Keycloak and require a valid token to access.
/
is accessible by users with role user
and /protected/premium
is accessible by users with role admin
.
You can change the endpoint and the used token in the following command to test the access.
curl -i -v "http://$(kubectl -n keycloak-namespace get ingress/spring-security -o jsonpath='{.spec.rules[0].host}')/" --header "Authorization: Bearer $USER_TOKEN"
Expected output is:
HTTP/1.1 200
Date: Wed, 03 Jul 2024 11:31:27 GMT
Content-Type: text/plain;charset=UTF-8
Content-Length: 52
Connection: keep-alive
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Hello from SpringBoot user endpoint: User The First!%
This step deploys a React frontend application that communicates with both backend applications.
task javascript-react-deploy
- Change directory to javascript-react.
cd javascript-react
- Build Docker image containing the React application.
- For Minikube.
eval $(minikube docker-env) && docker build -t javascript-react:1.0.0-SNAPSHOT .
- For other Kubernetes clusters, build the image and push it to the registry that is accessible by the Kubernetes cluster.
docker build -t javascript-react:1.0.0-SNAPSHOT .
- For Minikube.
- Create the Kubernetes resources for the React application. This step creates a Kubernetes Deployment, Service and Ingress.
- For Minikube execute the following command. Note, thanks to
CHECKSUM
you can rebuild the image, execute the following command again and the pod will be automatically restarted.NAMESPACE=keyclok-namespace MINIKUBE_IP=$(minikube ip) \ CHECKSUM=$(find src -type f -exec sha256sum {} \; | sha256sum | awk '{ print $1 }') \ envsubst < k8s-resources/javascript-react.yaml | kubectl -n kubernetes-namespace apply -f -
- For other Kubernetes clusters replace all occurrences of
# Replace with ...
comment ink8s-resources/javascript-react.yaml
and execute the following command.kubectl -n keycloak-namespace apply -f k8s-resources/javascript-react.yaml
- For Minikube execute the following command. Note, thanks to
- Change directory back to the root of the project.
cd ..
React application is accessible on the URL printed by the following command.
echo "Access React application on http://$(kubectl -n keycloak-namespace get ingress/javascript-react -o jsonpath='{.spec.rules[0].host}')"
After accessing the app you should be redirected to Keycloak login page. After successful login (with users user
or admin
), you should be redirected back to the React application where you can see responses from Quarkus and SpringBoot applications.
This step deploys a new namespace monitoring
that contains Prometheus and Grafana with some predefined dashboards.
This step requires additional prerequisites:
- Helm (installation guide here)
task grafana-deploy
- Create a namespace for monitoring.
kubectl create namespace monitoring
- Add the Prometheus Helm repository.
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm repo update
- Install Prometheus and Grafana using Helm. This step will configure Grafana to be secured by Keycloak. Note this steps also created a
PodMonitor
resource for Keycloak in the namespacekeycloak-namespace
.- For Minikube:
export KEYCLOAK_URL=https://keycloak.keycloak-namespace.$(minikube ip).nip.io helm upgrade --install prometheus prometheus-community/kube-prometheus-stack --version 39.11.0 -f grafana/monitoring.yaml \ --set grafana."grafana\.ini".server.root_url=https://grafana.$(minikube ip).nip.io \ --set grafana."grafana\.ini"."auth\.generic_oauth".auth_url=${KEYCLOAK_URL}/realms/riviera-dev-realm/protocol/openid-connect/auth \ --set grafana."grafana\.ini"."auth\.generic_oauth".token_url=${KEYCLOAK_URL}/realms/riviera-dev-realm/protocol/openid-connect/token \ --set grafana."grafana\.ini"."auth\.generic_oauth".api_url=${KEYCLOAK_URL}/realms/riviera-dev-realm/protocol/openid-connect/userinfo \ --set prometheus.prometheusSpec.retention=168h helm upgrade --install monitoring --set namespace=keycloak-namespace --set hostname=$(minikube ip).nip.io grafana/monitoring
- For other Kubernetes clusters replace URLs mentioned above with the actual URLs for Grafana and Keycloak.
- For Minikube:
Access Grafana on the URL printed by the following command.
echo "Access Grafana on https://$(kubectl -n monitoring get ingress/grafana-ingress -o jsonpath='{.spec.rules[0].host}')"
When Grafana login form appears, there is a possibility to login with Keycloak (Sign in with Keycloak-OAuth
). You can use users user
or admin
to login. Notice the difference between access rights between these two users.
You can then navigate to dashboard Riviera Dev Workshop Dashboard
to see how many Keycloak sessions are currently present in memory.