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