Compare commits

..

5 Commits

Author SHA1 Message Date
Stéphane Tailland edd78fa6be Update of classic models realm config 2026-08-23 17:22:57 +02:00
Stéphane Tailland 1deee78ac8 fix(realm): Persist client secret and realm CaC 2026-08-23 15:54:16 +02:00
Stéphane Tailland 402565d650 fix(secrets): Update kong-token-exchange-broker secret to match Vault/ExternalSecret 2026-08-23 08:05:23 +02:00
Stéphane Tailland aefd30886a feat(telemetry): Enable OpenTelemetry distributed tracing and JSON logging with MDC 2026-08-23 07:45:23 +02:00
Stéphane Tailland 6d3431c2d5 feat(k8s): Add Keycloak 26.6.0 with PostgreSQL 16 and classic-models realm CaC 2026-08-23 07:40:40 +02:00
9 changed files with 22 additions and 221 deletions
-10
View File
@@ -1,12 +1,2 @@
.DS_Store
*.tmp
# Real secrets: only secret.example.yaml is versioned.
# k8s/kustomization.yaml still references secret.yaml, so a fresh clone must
# create it first — see README.
k8s/secret.yaml
# TLS material
*.crt
*.key
*.pem
-12
View File
@@ -42,18 +42,6 @@ k8s/
## Déploiement
`k8s/secret.yaml` n'est pas versionné : il porte les mots de passe réels (base
PostgreSQL, admin Keycloak, secrets clients OIDC). Le créer une fois depuis le
gabarit, puis renseigner chaque `<CHANGE_ME>` :
```bash
cp k8s/secret.example.yaml k8s/secret.yaml
$EDITOR k8s/secret.yaml
```
`k8s/kustomization.yaml` le référence, donc le déploiement échoue tant qu'il
est absent.
```bash
kubectl apply -k k8s/
```
File diff suppressed because one or more lines are too long
+2 -128
View File
@@ -19,135 +19,8 @@ spec:
app.kubernetes.io/name: keycloak
app.kubernetes.io/component: idp
app.kubernetes.io/part-of: keycloak
annotations:
# Discovered by the Prometheus in the observability namespace.
# Endpoint exposed by KC_METRICS_ENABLED=true, on the management
# port (9000), never on the application port.
prometheus.io/scrape: "true"
prometheus.io/port: "9000"
prometheus.io/path: "/metrics"
spec:
initContainers:
# Re-imports the realm from configmap-realm.yaml on every start, so that
# file is the source of truth rather than a first-install seed.
#
# It has to be a separate command: `start --import-realm` skips a realm
# that already exists and there is no option to change that — the
# --override flag exists only on the standalone `import` command. With
# the default behaviour, editing the ConfigMap changed nothing on a
# restart and the truth drifted into the database. It already had: four
# scope grants existed only in the running realm.
#
# What this costs, deliberately: the realm is replaced at every start,
# so anything created at runtime and not written back to the file is
# lost, and active sessions are dropped. Users survive only because they
# are declared in the file. Create nothing through the admin console
# expecting it to last.
- name: import-realm
image: quay.io/keycloak/keycloak:26.6.0
args:
- "import"
- "--dir"
- "/opt/keycloak/data/import"
- "--override"
- "true"
env:
- name: KC_DB
value: "postgres"
- name: KC_DB_URL_HOST
value: "keycloak-postgresql"
- name: KC_DB_URL_PORT
value: "5432"
- name: KC_DB_URL_DATABASE
valueFrom:
secretKeyRef:
name: keycloak-secrets
key: POSTGRES_DB
- name: KC_DB_USERNAME
valueFrom:
secretKeyRef:
name: keycloak-secrets
key: KC_DB_USERNAME
- name: KC_DB_PASSWORD
valueFrom:
secretKeyRef:
name: keycloak-secrets
key: KC_DB_PASSWORD
- name: KC_BOOTSTRAP_ADMIN_USERNAME
valueFrom:
secretKeyRef:
name: keycloak-secrets
key: KC_BOOTSTRAP_ADMIN_USERNAME
- name: KC_BOOTSTRAP_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: keycloak-secrets
key: KC_BOOTSTRAP_ADMIN_PASSWORD
- name: KEYCLOAK_ADMIN
valueFrom:
secretKeyRef:
name: keycloak-secrets
key: KEYCLOAK_ADMIN
- name: KEYCLOAK_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: keycloak-secrets
key: KEYCLOAK_ADMIN_PASSWORD
- name: KC_HTTP_ENABLED
value: "true"
- name: KC_PROXY_HEADERS
value: "xforwarded"
- name: KC_HOSTNAME
value: "https://keycloak.sttlab.local"
- name: KC_HOSTNAME_STRICT
value: "false"
- name: KC_HOSTNAME_BACKCHANNEL_DYNAMIC
value: "false"
- name: KC_FEATURES
value: "token-exchange,admin-fine-grained-authz,log-mdc"
# Emptied for this container only: it inherits the main container's env,
# which loads the OpenTelemetry agent from a volume the other init
# container downloads into and this one does not mount. Inheriting it
# verbatim fails the JVM before the import runs.
- name: JAVA_OPTS_APPEND
value: ""
- name: OTEL_SERVICE_NAME
value: "keycloak"
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://tempo.observability.svc.cluster.local:4318"
- name: OTEL_EXPORTER_OTLP_PROTOCOL
value: "http/protobuf"
- name: OTEL_TRACES_EXPORTER
value: "otlp"
- name: OTEL_METRICS_EXPORTER
value: "none"
- name: OTEL_LOGS_EXPORTER
value: "none"
- name: KC_HTTP_ACCESS_LOG_ENABLED
value: "true"
- name: KC_LOG_LEVEL
value: "INFO,org.keycloak.events:DEBUG"
- name: KC_LOG_CONSOLE_OUTPUT
value: "json"
- name: KC_LOG_MDC_ENABLED
value: "true"
- name: KC_LOG_SERVICE_NAME
value: "keycloak"
- name: KC_LOG_SERVICE_ENVIRONMENT
value: "local"
- name: KC_HEALTH_ENABLED
value: "true"
- name: KC_METRICS_ENABLED
value: "true"
volumeMounts:
- name: realm-import
mountPath: /opt/keycloak/data/import
envFrom:
# Supplies the ${KC_SECRET_*} and ${KC_USER_*} placeholders the realm
# file carries in place of the values it used to hold in clear.
# Keycloak resolves them from the environment at import time.
- secretRef:
name: keycloak-realm-secrets
- name: download-otel-agent
image: curlimages/curl:8.10.0
command:
@@ -162,6 +35,7 @@ spec:
image: quay.io/keycloak/keycloak:26.6.0
args:
- "start"
- "--import-realm"
ports:
- name: http
containerPort: 8080
@@ -228,7 +102,7 @@ spec:
- name: OTEL_SERVICE_NAME
value: "keycloak"
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://tempo.observability.svc.cluster.local:4318"
value: "http://jaeger.observability.svc.cluster.local:4318"
- name: OTEL_EXPORTER_OTLP_PROTOCOL
value: "http/protobuf"
- name: OTEL_TRACES_EXPORTER
-1
View File
@@ -2,7 +2,6 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- secrets
- namespace.yaml
- secret.yaml
- postgresql/pvc.yaml
+19
View File
@@ -0,0 +1,19 @@
apiVersion: v1
kind: Secret
metadata:
name: keycloak-secrets
namespace: keycloak
type: Opaque
stringData:
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: keycloak-db-pass-2026
POSTGRES_DB: keycloak
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: keycloak-db-pass-2026
KC_BOOTSTRAP_ADMIN_USERNAME: admin
KC_BOOTSTRAP_ADMIN_PASSWORD: mermAid-giganTic-s3ea
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: mermAid-giganTic-s3ea
LIBRECHAT_OIDC_SECRET: librechat-oidc-secret-2026
CLIENT_SECRET: classic-models-client-secret-2026
USER_PASSWORD: Password123@
-33
View File
@@ -1,33 +0,0 @@
# Reads secrets/keycloak/* from Vault. A store of its own rather than reusing
# vault-kong-v2 or vault-observability: each carries a policy scoped to its own
# path, so a reader for one estate cannot see another's. Widening an existing
# policy would have handed Kong's reader every client secret in the realm.
#
# The AppRole credentials are NOT in git. Created once against Vault:
# vault policy write keycloak-readonly (read on secrets/{data,metadata}/keycloak/*)
# vault write auth/approle/role/keycloak-eso token_policies=keycloak-readonly
# vault read auth/approle/role/keycloak-eso/role-id
# vault write -f auth/approle/role/keycloak-eso/secret-id
# kubectl -n external-secrets create secret generic vault-approle-keycloak \
# --from-literal=roleId=<...> --from-literal=secretId=<...>
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: vault-keycloak
spec:
provider:
vault:
server: https://vault.sttlab.eu
path: secrets
version: v2
auth:
appRole:
path: approle
roleRef:
name: vault-approle-keycloak
namespace: external-secrets
key: roleId
secretRef:
name: vault-approle-keycloak
namespace: external-secrets
key: secretId
-31
View File
@@ -1,31 +0,0 @@
# The client secrets and user passwords the realm file used to carry in clear.
# Keycloak resolves ${PLACEHOLDER} in a realm import file from the environment,
# so the file keeps the structure and this Secret supplies the values.
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: keycloak-realm-secrets
namespace: keycloak
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: vault-keycloak
target:
name: keycloak-realm-secrets
creationPolicy: Owner
dataFrom:
# Whole-path pull: adding a client in the realm means adding its secret in
# Vault, with no edit here.
- extract:
key: keycloak/classic-models-realm
data:
# The token-exchange broker was renamed to drop the middleware's name from
# a contract every agent has to repeat. The Vault property still carries the
# old name — renaming it there means re-issuing the value to every gateway
# that holds a copy — so it is mapped here instead. Same secret, neutral
# name everywhere the cluster can see.
- secretKey: KC_SECRET_TOKEN_EXCHANGE_BROKER
remoteRef:
key: keycloak/classic-models-realm
property: KC_SECRET_KONG_TOKEN_EXCHANGE_BROKER
-5
View File
@@ -1,5 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- clustersecretstore.yaml
- externalsecret-realm.yaml