Add example for using SPIRE for mTLS with Keycloak (#248)
* Add example for using Spire for mTLS with Keycloak Signed-off-by: Moritz Schmitz von Hülst <[email protected]> * Minor improvement to the README.md Signed-off-by: Moritz Schmitz von Hülst <[email protected]> * But I still need to learn GitHub Markdown format Signed-off-by: Moritz Schmitz von Hülst <[email protected]> * Make it more obvious that it works without a (correct) password Signed-off-by: Moritz Schmitz von Hülst <[email protected]> * Add warning for Kubernetes 1.29+ feature Signed-off-by: Moritz Schmitz von Hülst <[email protected]> * Move ghostunnel into an initContainer with restartPolicy=Always Signed-off-by: Moritz Schmitz von Hülst <[email protected]> * Apply suggestions from code review Co-authored-by: kfox1111 <[email protected]> Signed-off-by: Moritz Schmitz von Hülst <[email protected]> * Move java-spiffe-helper-properties into extraDeploy of the Keycloak chart and pin node version to it has a matching rancher/kubectl image Signed-off-by: Moritz Schmitz von Hülst <[email protected]> --------- Signed-off-by: Moritz Schmitz von Hülst <[email protected]> Co-authored-by: kfox1111 <[email protected]>
This commit is contained in:
co-authored by
kfox1111
parent
cc8ec897bf
commit
43a72a22ff
@@ -0,0 +1,67 @@
|
||||
# keycloak-config-cli using spire
|
||||
|
||||
> [!WARNING]
|
||||
> This example uses
|
||||
> the [`SidecarContainers`](https://kubernetes.io/docs/concepts/workloads/pods/sidecar-containers/#enabling-sidecar-containers)
|
||||
> feature. This is only enabled by default in Kubernetes 1.29+.
|
||||
|
||||
This example shows how to leverage SPIRE in establishing an mTLS connection
|
||||
between [Keycloak](https://www.keycloak.org/) and [keycloak-config-cli](https://github.com/adorsys/keycloak-config-cli),
|
||||
a tool to configure Keycloak.
|
||||
|
||||
## Setup
|
||||
|
||||
1. Create a local cluster for testing
|
||||
|
||||
```shell
|
||||
kind create cluster --image kindest/node:v1.29.0
|
||||
```
|
||||
|
||||
2. Install CRDs
|
||||
|
||||
```shell
|
||||
helm upgrade --install -n spire-server spire-crds ../../charts/spire-crds --create-namespace
|
||||
```
|
||||
|
||||
3. Install `spire-server`
|
||||
|
||||
```shell
|
||||
helm upgrade --install -n spire-server spire ../../charts/spire --create-namespace -f spire-values.yaml
|
||||
```
|
||||
|
||||
4. Install `keycloak` (this also configures Keycloak for client certificate authentication)
|
||||
|
||||
```shell
|
||||
helm upgrade --install keycloak oci://registry-1.docker.io/bitnamicharts/keycloak -f keycloak-values.yaml
|
||||
```
|
||||
|
||||
5. Install `keycloak-config-cli`
|
||||
|
||||
```shell
|
||||
kubectl apply -f keycloak-config-cli.yaml
|
||||
```
|
||||
|
||||
6. Verify the realm config at the bottom of [keycloak-config-cli.yaml](./keycloak-config-cli.yaml) has been created!
|
||||
7. Cleanup
|
||||
|
||||
```shell
|
||||
kind delete cluster
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
### java-spiffe-helper as Keycloak initContainer
|
||||
|
||||
This example uses [java-spiffe-helper](https://github.com/spiffe/java-spiffe/tree/main/java-spiffe-helper) as an
|
||||
initContainer for Keycloak. It fetches the certificates from the `spire-agent` and conveniently provides them to
|
||||
Keycloak in `pkcs12` format.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Keycloak does not rotate the certificates like SPIRE does. If you want to run the `keycloak-config-cli`
|
||||
> job again, you need to make sure Keycloak is also restarted/provided with non-expired certificates.
|
||||
|
||||
### Common name as username
|
||||
|
||||
This example is configured to read the username from the common name (`CN`) from the client certificate. Keycloak has
|
||||
some options there, this looked like the easiest one. SPIRE joins the values from `dnsNameTemplates` in the
|
||||
common name section of the certificate, so make sure you can somehow extract the username from it.
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: keycloak-config-cli
|
||||
labels:
|
||||
app: keycloak-config-cli
|
||||
spec:
|
||||
backoffLimit: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: keycloak-config-cli
|
||||
app.kubernetes.io/instance: keycloak-config-cli # This needs to match the podSelector for the SpiffeID
|
||||
spec:
|
||||
initContainers:
|
||||
- name: ghostunnel
|
||||
image: ghostunnel/ghostunnel:v1.7.3
|
||||
imagePullPolicy: IfNotPresent
|
||||
restartPolicy: Always
|
||||
args:
|
||||
- client
|
||||
- --use-workload-api-addr
|
||||
- unix:///run/spire/agent-sockets/spire-agent.sock # The filename depends on what the spire-agent uses
|
||||
- --listen
|
||||
- localhost:8080 # Listen on local http
|
||||
- --target
|
||||
- keycloak:8443 # Tunnel via https
|
||||
- --status
|
||||
- http://0.0.0.0:6060
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: listen
|
||||
protocol: TCP
|
||||
- containerPort: 6060
|
||||
name: readiness
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /_status
|
||||
port: readiness
|
||||
volumeMounts:
|
||||
- name: spire-sockets
|
||||
mountPath: /run/spire/agent-sockets
|
||||
readOnly: true
|
||||
containers:
|
||||
- name: keycloak-config-cli
|
||||
image: adorsys/keycloak-config-cli:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: KEYCLOAK_URL
|
||||
value: "http://127.0.0.1:8080"
|
||||
- name: KEYCLOAK_USER
|
||||
value: "keycloak-config-cli"
|
||||
- name: KEYCLOAK_PASSWORD
|
||||
value: "doesn't matter, since we are authenticated via the client certificate"
|
||||
- name: KEYCLOAK_CLIENTID
|
||||
value: "keycloak-config-cli" # This is the client created on bootstrapping Keycloak via the keycloak-config-cli sidecar
|
||||
volumeMounts:
|
||||
- name: realm
|
||||
mountPath: /config
|
||||
restartPolicy: OnFailure
|
||||
volumes:
|
||||
- name: realm
|
||||
configMap:
|
||||
name: keycloak-config-cli
|
||||
- name: spire-sockets
|
||||
hostPath:
|
||||
path: /run/spire/agent-sockets # This needs to match the path mounted by the spire-agent
|
||||
type: DirectoryOrCreate
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: keycloak-config-cli
|
||||
labels:
|
||||
app: keycloak-config-cli
|
||||
data:
|
||||
keycloak-config-cli.json: |
|
||||
{
|
||||
"id": "keycloak-config-cli",
|
||||
"realm": "keycloak-config-cli",
|
||||
"enabled": true
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
extraDeploy:
|
||||
- apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: java-spiffe-helper-properties
|
||||
data:
|
||||
java-spiffe-helper.properties: |
|
||||
keyStorePath=/certs/keystore.p12
|
||||
keyStorePass=password
|
||||
keyPass=password
|
||||
trustStorePath=/certs/truststore.p12
|
||||
trustStorePass=password
|
||||
keyStoreType=pkcs12
|
||||
keyAlias=spiffe
|
||||
spiffeSocketPath=unix:/run/spire/agent-sockets/spire-agent.sock
|
||||
service:
|
||||
extraPorts:
|
||||
- name: https
|
||||
port: 8443
|
||||
targetPort: 8443
|
||||
extraEnvVars:
|
||||
- name: KC_HTTPS_CLIENT_AUTH
|
||||
value: "request"
|
||||
- name: KC_HTTPS_KEY_STORE_FILE
|
||||
value: "/certs/keystore.p12"
|
||||
- name: KC_HTTPS_KEY_STORE_PASSWORD
|
||||
value: "password"
|
||||
- name: KC_HTTPS_KEY_STORE_TYPE
|
||||
value: "pkcs12"
|
||||
- name: KC_HTTPS_TRUST_STORE_FILE
|
||||
value: "/certs/truststore.p12"
|
||||
- name: KC_HTTPS_TRUST_STORE_PASSWORD
|
||||
value: "password"
|
||||
- name: KC_HTTPS_TRUST_STORE_TYPE
|
||||
value: "pkcs12"
|
||||
initContainers:
|
||||
- name: java-spiffe-helper
|
||||
image: ghcr.io/spiffe/java-spiffe-helper:0.8.5
|
||||
imagePullPolicy: IfNotPresent
|
||||
restartPolicy: Always
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- ls
|
||||
- /certs/truststore.p12
|
||||
volumeMounts:
|
||||
- name: java-spiffe-helper-properties
|
||||
mountPath: /app/java-spiffe-helper.properties
|
||||
subPath: java-spiffe-helper.properties
|
||||
- name: spire-sockets
|
||||
mountPath: /run/spire/agent-sockets
|
||||
readOnly: true
|
||||
- name: certs
|
||||
mountPath: /certs
|
||||
extraVolumeMounts:
|
||||
- name: certs
|
||||
mountPath: /certs
|
||||
extraVolumes:
|
||||
- name: java-spiffe-helper-properties
|
||||
configMap:
|
||||
name: java-spiffe-helper-properties
|
||||
- name: spire-sockets
|
||||
hostPath:
|
||||
path: /run/spire/agent-sockets
|
||||
type: DirectoryOrCreate
|
||||
- name: certs
|
||||
emptyDir: {}
|
||||
auth:
|
||||
adminPassword: "password"
|
||||
keycloakConfigCli:
|
||||
enabled: true
|
||||
configuration:
|
||||
master.json: |
|
||||
{
|
||||
"id": "master",
|
||||
"realm": "master",
|
||||
"enabled": true,
|
||||
"users": [
|
||||
{
|
||||
"username": "keycloak-config-cli",
|
||||
"enabled": true,
|
||||
"realmRoles": [
|
||||
"admin"
|
||||
],
|
||||
"credentials": [
|
||||
{
|
||||
"type": "password",
|
||||
"value": "it-really-doesn't-matter-what-you-put-here"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"authenticationFlows": [
|
||||
{
|
||||
"alias": "direct grant x509",
|
||||
"providerId": "basic-flow",
|
||||
"topLevel": true,
|
||||
"builtIn": false,
|
||||
"authenticationExecutions": [
|
||||
{
|
||||
"authenticatorConfig": "username",
|
||||
"authenticator": "direct-grant-auth-x509-username",
|
||||
"requirement": "REQUIRED",
|
||||
"priority": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"authenticatorConfig": [
|
||||
{
|
||||
"alias": "username",
|
||||
"config": {
|
||||
"x509-cert-auth.regular-expression": "CN=(keycloak-config-cli)",
|
||||
"x509-cert-auth.mapper-selection": "Username or Email",
|
||||
"x509-cert-auth.mapping-source-selection": "Match SubjectDN using regular expression"
|
||||
}
|
||||
}
|
||||
],
|
||||
"clients": [
|
||||
{
|
||||
"clientId": "keycloak-config-cli",
|
||||
"name": "keycloak-config-cli",
|
||||
"enabled": true,
|
||||
"standardFlowEnabled": false,
|
||||
"directAccessGrantsEnabled": true,
|
||||
"publicClient": true,
|
||||
"authenticationFlowBindingOverrides": {
|
||||
"direct_grant": "direct grant x509"
|
||||
},
|
||||
"fullScopeAllowed": true,
|
||||
"nodeReRegistrationTimeout": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
spire-server:
|
||||
controllerManager:
|
||||
identities:
|
||||
clusterSPIFFEIDs:
|
||||
default:
|
||||
enabled: false
|
||||
keycloak:
|
||||
spiffeIDTemplate: spiffe://{{ .TrustDomain }}/ns/{{ .PodMeta.Namespace }}/sa/{{ .PodSpec.ServiceAccountName }}
|
||||
namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: default
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/instance: keycloak
|
||||
dnsNameTemplates:
|
||||
- keycloak
|
||||
keycloak-config-cli:
|
||||
spiffeIDTemplate: spiffe://{{ .TrustDomain }}/ns/{{ .PodMeta.Namespace }}/sa/{{ .PodSpec.ServiceAccountName }}
|
||||
namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: default
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/instance: keycloak-config-cli
|
||||
dnsNameTemplates:
|
||||
- keycloak-config-cli # This is the common name used for the certificate. In this case, the username
|
||||
Reference in New Issue
Block a user