Keycloak on Kubernetes high availability cluster (with ldap as user federation) using codecentrics Helm Charts - kubernetes

We wanted to set up a high available Keycloak cluster on Kubernetes (with ldap as a user federation). We decided to use codecentrics helm charts since we were trying them out for a single Keycloak instance setup and that worked well. For the cluster we ran into a few issues while trying to set up everything correctly and didn't find the best sources in the wide internet. Therefore I decided to write a short summary what our main issues where and how we got through them.
Solutions to our problems where described on this website (amongst others), but things are described kind of very briefly and felt partly incomplete.
Issues we faced where:
Choosing the correct jgroups.discoveryProtocol
Adding the correct discoveryProperties
Parts that need to be overridden in your own values.yaml
Bonus issues (we already faced with the single instance setup):
Setting up an truststore to connect ldap as a user federation via ladps
Adding a custom theme for keycloak
I will try and update this if things change due to codecentrics updating their helm charts.
Thanks to codecentrics for providing the helm charts by the way!

Disclaimer:
This is the way we set it up - I hope this is helpful, but I do not take responsibility for configuration errors and resulting security flaws. Also we went through many different sources on the internet, I am sorry that I can't give credits to all of them, but it has been a few days since than an I can't get them together anymore...
CODECENTRIC CHART VERSION < 9.0.0
The main issues:
1. Choosing the correct jgroups.discoveryProtocol:
I will not explain things here but for us the correct protocol to use was org.jgroups.protocols.JDBC_PING. Find out more about the protocols (and general cluster setup) here.
discoveryProtocol: org.jgroups.protocols.JDBC_PING
With JDBC_PING jgroups will manage instance discovery. Therefore and for caching user sessions the database provided for keycloak will be enhanced with extra tables, e.g. JGROUPSPING.
2. Setting up the discoveryProperties:
This needs to be set to
discoveryProperties: >
"datasource_jndi_name=java:jboss/datasources/KeycloakDS"
to avoid an error like:
java.lang.IllegalStateException: java.lang.IllegalArgumentException:
Either the 4 configuration properties starting with 'connection_' or
the datasource_jndi_name must be set
3. Other parts that need to be set (as mostly described in the readme of codecentrics github and in the comments of the values.yaml in github as well):
setting the clusterDomain according to your cluster
setting the number of replicas greater than 1 to enable clustering
setting the service.type: We went with ClusterIP but it also can work with other setups like LoadBalancer depending on your setup
optional but recommended: Setting either maxUnavailable or minAvailable to always have sufficient pods available according to your needs.
setting up our Ingress (which looks pretty much standard):
ingress:
enabled: true
path: /
annotations: {
kubernetes.io/ingress.class: nginx
}
hosts:
- your.host.org
Bonus issues:
1. The truststore:
To have Keycloak communicate with ldap via ldaps we had to set up a truststore with the certificate of our ldap in it:
Receive the certificate from ldap and save it somewhere:
openssl s_client -connect your.ldap.domain.org < /dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > /path/to/ldap.cert
Create a new keystore:
keytool -genkey -alias replserver \
-keyalg RSA -keystore /path/to/keystore.jks \
-dname "CN=AddCommonName, OU=AddOrganizationalUnit, O=AddOrganisation, L=AddLocality, S=AddStateOrProvinceName, C=AddCountryName" \
-storepass use_the_same_password \
-keypass use_the_same_password \
-deststoretype pkcs12
Add the downloaded certificate to the keystore:
keytool -import -alias ldaps -file /path/to/ldap.cert -storetype JKS -keystore path/to/keystore.jks
Type in the required password: use_the_same_password.
Trust the certificate by typing 'yes'.
Provide the keystore in a configmap:
kubectl create configmap cert-keystore --from-file=path/to/keystore.jks
Enhance your values.yaml for the truststore:
Add and mount the config map:
extraVolumes: |
- name: cert-keystore
configMap:
name: cert-keystore
extraVolumeMounts: |
- name: cert-keystore
mountPath: "/keystore/"
readOnly: true
Tell java tu use it:
javaToolOptions: >-
-[maybe some other settings of yours]
-Djavax.net.ssl.trustStore=/keystore/keystore.jks
-Djavax.net.ssl.trustStorePassword=<<keystore_password>>
Since we didn't want to upload the keystore password to git we added a step to our pipeline where it gets sed into the values.yaml, replacing the <<keystore_password>>.
2. Adding a custom theme:
Mainly we are providing a docker container with our custom theme in it:
extraInitContainers: |
- name: theme-provider
image: docker_repo_url/themeContainer:version
imagePullPolicy: IfNotPresent
command:
- sh
args:
- -c
- |
echo "Copying theme..."
cp -R /custom-theme/* /theme
volumeMounts:
- name: theme
mountPath: /theme
Add and mount the theme:
extraVolumes: |
- name: theme
emptyDir: {}
extraVolumeMounts: |
- name: theme
mountPath: /opt/jboss/keycloak/themes/custom-theme
You now should be able to choose the custom theme in the Keycloak admin UI via Realm Settings -> Themes.
CODECENTRIC CHART VERSION 9.0.0 to 9.3.2 (and maybe higher)
1. Clustering
We are still going with JDBC_PING since we had problems with DNS_PING as described in the Codecentric Repo readme:
extraEnv: |
## KEYCLOAK CONFIG
- name: PROXY_ADDRESS_FORWARDING
value: "true"
### CLUSTERING
- name: JGROUPS_DISCOVERY_PROTOCOL
value: org.jgroups.protocols.JDBC_PING
- name: JGROUPS_DISCOVERY_PROPERTIES
value: 'datasource_jndi_name=java:jboss/datasources/KeycloakDS'
- name: CACHE_OWNERS_COUNT
value: "2"
- name: CACHE_OWNERS_AUTH_SESSIONS_COUNT
value: "2"
With the service set up as ClusterIP:
service:
annotations: {}
labels: {}
type: ClusterIP
loadBalancerIP: ""
httpPort: 80
httpNodePort: null
httpsPort: 8443
httpsNodePort: null
httpManagementPort: 9990
httpManagementNodePort: null
extraPorts: []
2. 502 Error Ingress Problem
We encountered a 502 error with Codecentrics chart 9.x.x for which fixing took a while to figure out. A solution for this is also described here, where we took our inspiration but for us the following ingress setup was enough:
ingress:
enabled: true
servicePort: http
# Ingress annotations
annotations: {
kubernetes.io/ingress.class: nginx,
nginx.ingress.kubernetes.io/proxy-buffer-size: 128k,
}
CODECENTRIC CHART VERSION 9.5.0 (and maybe higher)
Updating to 9.5.0 needs to be tested. Especially if desired to go with KUBE_PING and maybe even Autoscaling.
I will update after testing if something changed significantly.

Related

Kubernetes - Create custom secret holding SSL certificates

I have a problem. In my kubernetes cluster I am running a GitLab image for my own project. This image requires a .crt and .key as certificates for HTTPS usage. I have setup an Ingress resource with a letsencrypt-issuer, which successfully obtains the certificates. But to use those they need to be named as my.dns.com.crt and my.dns.com.key. So I manually ran the following 3 commands:
kubectl get secret project-gitlab-tls -n project-utility \
-o jsonpath='{.data.tls\.crt}' | base64 --decode > /mnt/data/project/gitlab/certs/tls.crt
kubectl get secret project-gitlab-tls -n project-utility \
-o jsonpath='{.data.tls\.key}' | base64 --decode > /mnt/data/project/gitlab/certs/tls.key
kubectl create secret generic gitlab-registry-certs \
--from-file=gitlab.project.com.crt=/mnt/data/project/gitlab/certs/tls.crt \
--from-file=gitlab.project.com.key=/mnt/data/project/gitlab/certs/tls.key \
--namespace project-utility
The first 2 commands print the decoded crt/key content in a file, so that the third command can use those files to create a custom mapping to the specific DNS names. Then in the GitLab deployment I mount this gitlab-registry-certs like this:
volumeMounts:
- mountPath: /etc/gitlab/ssl
name: registry-certs
volumes:
- name: registry-certs
secret:
secretName: gitlab-registry-certs
This all works, but I want this process to be automated, because I am using ArgoCD as deployment tool. I thought about a job, but a job runs a ubuntu version which is not allowed to make changes to the cluster, so I need to call a bash script on the external host. How can I achieve this, because I can only find things about jobs which run an image and not how to execute host commands. If there is a way easier method to use the certificates that I am not seeing please let me know, because I kinda feel weird about this way of using the certificates, but GitLab requires the naming convention of <DNS>.crt and <DNS>.key, so thats why I am doing the remapping.
So the question is how to automate this remapping process so that on cluster generation a job will be executed after obtaining the certificates but before the deployment gets created?
Why are you bothering with this complicated process of creating a new secret? Just rename them in your volumeMounts section by using a subPath:
containers:
- ...
volumeMounts:
- name: registry-certs
mountPath: /etc/gitlab/ssl/my.dns.com.crt
subPath: tls.crt
- name: registry-certs
mountPath: /etc/gitlab/ssl/my.dns.com.key
subPath: tls.key
volumes:
- name: registry-certs
secret:
secretName: project-gitlab-tls
More info in the documentation.

How to use custom themes on Keycloak Operator (v13.0.0)?

I was installing Keycloak using Operator (version 13.0.0). The updated code has theme related stuff github repository and supports custom theme integration quite well. All we need an URL where the custom theme is located. I tried it and worked flawlessly.
However, what if we have themes in some local directory, not on some public URL. How do we suppose to upload the theme in the Keycloak then?
I've tried using the File URL and file paths as well but didn't work for me.
The Keycloak.yaml
apiVersion: keycloak.org/v1alpha1
kind: Keycloak
metadata:
name: keycloak-test
labels:
app: keycloak-test
spec:
instances: 1
extensions:
- https://SOME-PUBLIC-URL/keycloak-themes.jar
externalAccess:
enabled: False
podDisruptionBudget:
enabled: True
We can add custom keycloak themes in keycloak operator (v13.0.0) using the below steps:
Create a jar file for your custom theme using step shown here Deploying Keycloak Themes
Create a kubernetes configmap of the jar using the following command
kubectl create cm customtheme --from-file customtheme.jar
To use above configmap update Keycloak.yaml and add the following code block
keycloakDeploymentSpec:
experimental:
volumes:
defaultMode: 0777
items:
- name: customtheme
mountPath: /opt/jboss/keycloak/standalone/deployments/custom-themes
subPath: customtheme.jar
configMaps:
- customtheme
Note: Make sure the size of theme is less than 1MB.
You can create a .tar file (e.g., custom_theme.tar) with the custom themes to be used in Keycloak, and then mount a volume to the folder where the Keycloak themes are stored (i.e., /opt/jboss/keycloak/themes/my_custom_theme), and copy the .tar file with the custom themes from a local folder into the Keycloak container.
You can find complete example of this approach here.

How to create keycloak with operator and external database

I follow this but it is not working.
I created custom secret:
apiVersion: v1
kind: Secret
metadata:
name: keycloak-db-secret
data:
POSTGRES_DATABASE: ...
POSTGRES_EXTERNAL_ADDRESS: ...
POSTGRES_EXTERNAL_PORT: ...
POSTGRES_HOST: ...
POSTGRES_USERNAME: ...
POSTGRES_PASSWORD: ...
and keycloak with external db:
apiVersion: keycloak.org/v1alpha1
kind: Keycloak
metadata:
labels:
app: keycloak
name: keycloak
spec:
externalDatabase:
enabled: true
instances: 1
but when I check log, keycloak can not connect to db. It is still using default vaule: keycloak-postgresql.keycloak not value defined in my custom secret ? Why it is not using my value from secrets ?
UPDATE
when I check keycloak pod which was created by operator I can see:
env:
- name: DB_VENDOR
value: POSTGRES
- name: DB_SCHEMA
value: public
- name: DB_ADDR
value: keycloak-postgresql.keycloak
- name: DB_PORT
value: '5432'
- name: DB_DATABASE
value: keycloak
- name: DB_USER
valueFrom:
secretKeyRef:
name: keycloak-db-secret
key: POSTGRES_USERNAME
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: keycloak-db-secret
key: POSTGRES_PASSWORD
so now I know why I can not connect to db. It use different DB_ADDR. How I can use address: my-app.postgres (db in another namespace).
I dont know why POSTGRES_HOST in secret not working and pod still using default service name
To connect with service in another namespace you can use.
<servicename>.<namespace>.svc.cluster.local
suppose your Postgres deployment and service running in test namespace it will go like
postgres.test.svc.cluster.local
this is what i am using : https://github.com/harsh4870/Keycloack-postgres-kubernetes-deployment/blob/main/keycload-deployment.yaml
i have also attached the Postgres file you can use it however in my case i have setup both in the same namespace keycloak and Postgres so working like charm.
I'm using Azure PostgreSQL for that, and it works correctly. In pod configuration, it also uses keycloak-postgresql.keycloak as DB_ADDR, but this is pointing to my internal service created by operator based on keycloak-db-secret.
keycloak-postgresql.keycloak this is the another service created by Keycloak Operator, which is used to connect to Postgresql's service.
You can check its endpoint.
$ kubectl get endpoints keycloak-postgresql -n keycloak
NAME ENDPOINTS AGE
keycloak-postgresql {postgresql's service ip}:5432 4m31s
However, the reason why it fails is due to the selector of this service:
selector:
app: keycloak
component: database
So if your DB Pod has the different Label, the selector will not work.
I reported this issue to the community. If they reply me, I will try to fix this bug by submitting a patch.
I was having this same issue, and then after looking at #JiyeYu 's answer, I have searched the project's issue backlog, and I've found some related issues that are still open (at the moment of this reply).
Particularly this one: https://issues.redhat.com/browse/KEYCLOAK-18602
After reading this, and its comments, I did the following:
Don't use IPs on POSTGRES_EXTERNAL_ADDRESS. If your PostGres is hosted within K8s via a StatefulSet, use the full <servicename>.<namespace>.svc.cluster.local (like #Harsh Manvar 's answer)
Remove the POSTGRES_HOST setting from the secret (don't just set it to the default, delete it). Apparently, it is not only being ignored, but also breaking the keycloak pod initialization process somehow.
After I applied these changes the issue was solved for me.
I also had similar problem, it turned out since I was using SSLMODE: "verify-full", keycloak expected correct hostname of my external db.
Since somehow Keycloak translates internally the real external db address into "keycloak-postgresql.keycloak", it expected something like "keycloak-postgresql.my-keycloak-namespace"
The log went something like this:
SEVERE [org.postgresql.ssl.PGjdbcHostnameVerifier] (ServerService Thread Pool -- 57) Server name validation failed: certificate for host keycloak-postgresql.my-keycloak-namespace dNSName entries subjectAltName, but none of them match. Assuming server name validation failed
After I added the host keycloak-postgresql.my-keycloak-namespace on the db certificate, it worked like advertised.

Helm secrets plugin unable to decrypt secrets.yaml encrypted before with GPG key

I have got a problem with decryption of my secrets.yaml file. The process freez like on pic. below:
helm secrets dec
Based on the example from official documentation: https://github.com/futuresimple/helm-secrets
1) I have my gpg key fingerprint added in the .sops.yaml
2) I make custom secrets.yaml file to encrypt:
replicaCount:
image:
repository: git/repo
tag: v1
pullPolicy: always
service:
type: nodeport
port: 3456
targetPort: 4665
ingress:
enabled: true
Then I successfully encrypted this file with my key:
helm secrets enc
File is properly encrypted but unfortunetly I am not able to decrypt it back.
The command is suspended indefinitely as on the pic
Im not sure that helps me to solve this problem, but I made new GPG key WITHOUT PASS PHRASE. Propably SOPS was able to encrypt file, but can't obtain it to decryption. Hope it helps someone !

Mount Kubernetes secret as a file with rw permission

I am trying to create a file with in a POD from kubernetes sceret, but i am facing one issue like, i am not able to change permission of my deployed files.
I am getting below error,
chmod: changing permissions of '/root/.ssh/id_rsa': Read-only file system
I have already apply defaultmode & mode for the same but still it is not working.
volumes:
- name: gitsecret
secret:
secretName: git-keys
VolumeMounts:
- mountPath: "/root/.ssh"
name: gitsecret
readOnly: false
thank you
As you stated, your version of Kubernetes is 1.10 and documentation for it is available here
You can have a look at the github link #RyanDawson provided, there you will be able to find that this RO flag for configMap and secrets was intentional. It can be disabled using feature gate ReadOnlyAPIDataVolumes.
You can follow this guide on how to Disabling Features Using Feature Gates.
As a workaround, you can try this approach:
containers:
- name: apache
image: apache:2.4
lifecycle:
postStart:
exec:
command: ["chown", "www-data:www-data", "/var/www/html/app/etc/env.php"]
You can find explanation inside Kubernetes docs Attach Handlers to Container Lifecycle Events
There has been some back and forth over this but presumably you are on a k8s version where configmap and secret are read-only no matter how you set the flag - the issue is https://github.com/kubernetes/kubernetes/issues/62099 I think you'll need to follow the advice on there and create an emptyDir volume to copy the relevant files into.