Compare commits
19 Commits
edd78fa6be
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 62d0b371f6 | |||
| dc1e7de204 | |||
| 9f2bd159d8 | |||
| 5048a154c5 | |||
| 04d0d65863 | |||
| 7e7e9d741b | |||
| eed8c3da3b | |||
| 6c89f6124d | |||
| f9900a5fb9 | |||
| a97399c2bf | |||
| 1b613768e7 | |||
| c1eaead5d2 | |||
| 80a2a27ca9 | |||
| 01d2082f3b | |||
| aac85b2f7a | |||
| cf456f10a1 | |||
| d5242f68ca | |||
| b9287761bf | |||
| 08200a7df0 |
+12
@@ -0,0 +1,12 @@
|
|||||||
|
.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
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# Infra Keycloak (Kubernetes / CaC)
|
||||||
|
|
||||||
|
Déploiement de **Keycloak 26.6.0** et **PostgreSQL 16** en Configuration-as-Code (CaC) sous Kubernetes dans le namespace `keycloak`.
|
||||||
|
|
||||||
|
## Structure du Répertoire
|
||||||
|
|
||||||
|
```text
|
||||||
|
k8s/
|
||||||
|
├── kustomization.yaml # Manifeste Kustomize principal
|
||||||
|
├── namespace.yaml # Namespace keycloak
|
||||||
|
├── secret.yaml # Secrets (Admin, DB, OIDC secrets)
|
||||||
|
├── secret.example.yaml # Gabarit des secrets
|
||||||
|
├── postgresql/
|
||||||
|
│ ├── pvc.yaml # Stockage persistant PostgreSQL (2Gi)
|
||||||
|
│ ├── statefulset.yaml # Base de données PostgreSQL 16
|
||||||
|
│ └── service.yaml # Service PostgreSQL (port 5432)
|
||||||
|
├── keycloak/
|
||||||
|
│ ├── configmap-realm.yaml # Realm classic-models (Configuration-as-Code)
|
||||||
|
│ ├── deployment.yaml # Déploiement Keycloak 26.6.0 (--import-realm)
|
||||||
|
│ └── service.yaml # Service Keycloak (ports 8080, 9000)
|
||||||
|
└── ingress/
|
||||||
|
├── tls.yaml # Secret TLS wildcard (*.sttlab.local)
|
||||||
|
└── ingress.yaml # Ingress NGINX (keycloak.sttlab.local)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fonctionnalités Clés
|
||||||
|
|
||||||
|
- **Version Keycloak** : `quay.io/keycloak/keycloak:26.6.0` (Runtime Quarkus).
|
||||||
|
- **Import Automatique du Realm (CaC)** : Le realm `classic-models` est chargé au démarrage via l'option `--import-realm` depuis le `ConfigMap` monté dans `/opt/keycloak/data/import/`.
|
||||||
|
- **Token Exchange RFC 8693** : Activé via `KC_FEATURES: "token-exchange,admin-fine-grained-authz"`.
|
||||||
|
- **Clients Configurés** :
|
||||||
|
- `librechat` : Client OIDC pour le login et le flux OAuth MCP.
|
||||||
|
- `mcp-catalog` : Audience du serveur MCP.
|
||||||
|
- `kong-token-exchange-broker` : Échangeur de jetons RFC 8693 de Kong Gateway.
|
||||||
|
- `catalog-api` & `classic-models-api` : Audiences des services backend.
|
||||||
|
- **Utilisateurs & Rôles** :
|
||||||
|
- `admin-user` (`admin`)
|
||||||
|
- `pm-user` (`product_manager`)
|
||||||
|
- `ro-user` (`read_only`)
|
||||||
|
- `customer-user` (`customer`)
|
||||||
|
- `support-user` (`support`)
|
||||||
|
|
||||||
|
## 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/
|
||||||
|
```
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: keycloak
|
||||||
|
namespace: keycloak
|
||||||
|
annotations:
|
||||||
|
nginx.ingress.kubernetes.io/proxy-buffer-size: "128k"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-buffers-number: "4"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-busy-buffers-size: "256k"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-body-size: "20m"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- keycloak.sttlab.local
|
||||||
|
secretName: keycloak-tls
|
||||||
|
rules:
|
||||||
|
- host: keycloak.sttlab.local
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: keycloak
|
||||||
|
port:
|
||||||
|
number: 8080
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURQRENDQWlTZ0F3SUJBZ0lVZC91dERmK2YweVFSM3YzcVRqWnF4K3d2VFpNd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0dURVhNQlVHQTFVRUF3d09LaTV6ZEhSc1lXSXViRzlqWVd3d0hoY05NalV4TVRJME1UVXpPRFExV2hjTgpNall4TVRJME1UVXpPRFExV2pBWk1SY3dGUVlEVlFRRERBNHFMbk4wZEd4aFlpNXNiMk5oYkRDQ0FTSXdEUVlKCktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQUxqMHNyeXE4cVh0a1NoVC9CKy9FalZKTk4vVExISW4KUUpZYzZ4dVVIL2Y3cTNkWWRMbTRpSDBYbkZnclNRTFVvNGlxZC9NY3plZjVpV0NZd1Q1RkZhN1AxNy9UY0xWZgpnc28wajQxcU5MQmpHeERRR1E2WjFWQ3hpNm9hbjhvdzk4cnVtalZqMEUwUWdzNUNYL3JOK2djRW1aL1RaRldVCnF1ZFZvenRwZWlWcUowZHhDSXErS011dHpkMUJVN0JYeDRWUk85WSt0MWlkVWM3b1k3QlBtUzJxUEFON1R0WFAKajFLaEgyQmdMc21GNUdSaFlITlM1NzhlQ2x0Y21TMnNiUTNjN0d3T1Y5NmxwZ0Y5VGl4RXZGWGVONHhrNDNFNgoxQ09VRDdDUmlXQVowWEk4Rk1scFpHZU5aZVY0YmJWL01xVmlnMXliV05PejlNREtZajNlb1hVQ0F3RUFBYU44Ck1Ib3dIUVlEVlIwT0JCWUVGRGJ6WVdPUHI0dnhhK1ZPTnM3eEhiY054NzBZTUI4R0ExVWRJd1FZTUJhQUZEYnoKWVdPUHI0dnhhK1ZPTnM3eEhiY054NzBZTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3SndZRFZSMFJCQ0F3SG9JTwpLaTV6ZEhSc1lXSXViRzlqWVd5Q0RITjBkR3hoWWk1c2IyTmhiREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBCnRVNHA1L09OWG03eDRZRHpTZUw4TmRUaFZMZ3VpRnVyMGpjVHh2dlBVUzRORk5zblFKazRPWHhCZ2p5MVl3cEwKM2k1Ni80bUNFZDlPQ1FGL0hsMGVGd0daQnpOS2xNb1RBMDhBQy9sQkR2UHJuVmp1VXUwcWUvYi8yS09raEVTYQp4OHJRTnAwbzhwY2owZjI5bnUwSmZScVlHa3p5MExoMnFnWGhQdTJpQyt0MlBmZGxlUkFreTF0dDY4d29nZEZMClR2SERzSmJWby9paHNQcE9ObFN5SGZ4RzZiYi8wTWt6NjRQSDQ2VFR0OUd2SmZsRUtNcUR3aXZORlVBMVA1Uy8KVTRJYzhRYXBueGM3aDIrV2JSam1Zcm51eWdDMndNQkU2ckw5T0YzZ3dDcjFma2dHR1JyNWxWUlVxMlgySFpWNQo1anFWaXY2ZXVycThPNjFOM3ZXR0NRPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
|
||||||
|
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktZd2dnU2lBZ0VBQW9JQkFRQzQ5TEs4cXZLbDdaRW8KVS93ZnZ4STFTVFRmMHl4eUowQ1dIT3NibEIvMys2dDNXSFM1dUloOUY1eFlLMGtDMUtPSXFuZnpITTNuK1lsZwptTUUrUlJXdXo5ZS8wM0MxWDRMS05JK05halN3WXhzUTBCa09tZFZRc1l1cUdwL0tNUGZLN3BvMVk5Qk5FSUxPClFsLzZ6Zm9IQkptZjAyUlZsS3JuVmFNN2FYb2xhaWRIY1FpS3ZpakxyYzNkUVZPd1Y4ZUZVVHZXUHJkWW5WSE8KNkdPd1Q1a3RxandEZTA3Vno0OVNvUjlnWUM3SmhlUmtZV0J6VXVlL0hncGJYSmt0ckcwTjNPeHNEbGZlcGFZQgpmVTRzUkx4VjNqZU1aT054T3RRamxBK3drWWxnR2RGeVBCVEphV1JualdYbGVHMjFmektsWW9OY20xalRzL1RBCnltSTkzcUYxQWdNQkFBRUNnZ0VBSjBEZnNrVUJySStIOEN3cTVUM3pDbFlZSDZ5ODBSbGRqcjVpYzM3emJmaisKd2h5RzRXZTFwLzdiMHFhVkgwR3IxM1IrY3AxWllmTkc0YlpweE9KNm9KaEI3TmRlSTFBNi9Oazg0a0Y1SHpwWgpZeThYTWs4Wnc3OTZxY1p1eFVXeFFXTjBYUUpyRWdBQUV2QnBXRnhneFI2SDBERFlLWVdjNCszNzc3OEtDbWhwCmVpUWdoMWhpaEZ5OWF6QnVJN0VsTm02QzQ4VktrU1dwQUMyZHNNN2FvVkJjd1RHcjVjWjh6SVBWT2dTWitUVXMKelBINkV6b0VqRUQzajNFemRESENKMCtVdVNBTDh0L3F4ZGNEZjREdkdFeWR4S3J4OWpxSXJnalNab1UvOC94WApMUy9rRnk0cE9URnFsSDc2UUU4TytrbDRDTHZDQTdKTzNCN1p3dzM2d1FLQmdRRHJVaHdmNkQ5b3lsT3hPL3U1Cm5ndjl2YW04SGs0QlBRRjRHYi8zTU9DdzRGWnhIclFQRWg4Q1BLVXBndnhDRUM3UVNHOTJybVJjYUZXcDlqaCsKTWQyOFc5NHBlWW9wRnlkc0ZFa3F6bjFTb05qNkFFM3JhbTgwZ1JWempMZUFQNUxiRENXUVZtTmpnTTRySjJVZwpzVVhSZk5Kd3RIcEZwUkE0dmM4MUcvQk84UUtCZ1FESk5ZNGpGTmxoYUF5UytraHVJcm9PY0l2KzBoZElZcStpCktjeTJYbnBHSEd5b0s4S0pIckp2c1Via3NnTVlKVTJlT0g3cWlkNEo4Y1BDbmUwSjIvL0FvZytleEkvNnJOZzMKYll4UDNHWlp1aHNNclFEMmZ1WkxhM2pFZGJtaUxuM1A0cFNiZVJ4cDd5VitadlBlZ1dsZ0dsMHgwRE9Uc3NpNApDKzUxMnBRQ3hRS0JnRWhzYXU5L0FSVDFRRERKN3owL1lSMWs0L3Bic1ZJOFBsbUNRMHlHcElDTGFzOUJ6ZFRkCmxPR1JYSnVjRHBzZWMrbnlnU3VibkpPOElMRkFjRDZXaGxIVVRGbWphRndoSGliclQyRjNUNmJBamtiVEZTZ2gKQjd5M0JhMGNYOVYrdTJuaklTUjlFZldyek1nSFJkNTc4S3I2cWFHNlNlOXBUOGJHMUNEYVNtbGhBb0dBVlNqLwpvY1YvUkFUZjNZMWsvb2hnS3lCamdGUzNXZ0c2eUczY29RdFh3VGppbkVMNU1VUDJ6ZU5BV0NjbnZCNTl2WXUzCjI5akNTMzh5RmJyMXlvaDh0TEZpM3hYZXdvQkpleHc0bU14MTNXQXhkcW42a0ZtaGF5WnZsT2NteklmVTVCbkkKUUEyQWd5MmV0dU1vTUhLaGdQVm5vRis0bExqRXozRHRXcUJLcU9rQ2dZQlN6SHo3N1RkR0pRUVVMajJRVUFDOQozSXlaaXJmL0k4YnhPSnZrQnA3dktWODcxdHJmQXBaMDQwWDhIb1ZwZUErdjVZM3N4WkNKbUhwclhMNXZ1T1NaCnJoaHVvbTZuNHhUMi9qa2YybnR5c0UveFZGQUVrS2hVNEZISXNPR091V21YSWlSSzFLa2hJQXFhNzVBeVhESU4KdEVycWpTaGtFNHArdkQ0T0QzSmNqZz09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: keycloak-tls
|
||||||
|
namespace: keycloak
|
||||||
|
type: kubernetes.io/tls
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,288 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: keycloak
|
||||||
|
namespace: keycloak
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: keycloak
|
||||||
|
app.kubernetes.io/component: idp
|
||||||
|
app.kubernetes.io/part-of: keycloak
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: keycloak
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: keycloak
|
||||||
|
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:
|
||||||
|
- "sh"
|
||||||
|
- "-c"
|
||||||
|
- "curl -sSL -o /otel/opentelemetry-javaagent.jar https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v2.31.0/opentelemetry-javaagent.jar"
|
||||||
|
volumeMounts:
|
||||||
|
- name: otel-agent
|
||||||
|
mountPath: /otel
|
||||||
|
containers:
|
||||||
|
- name: keycloak
|
||||||
|
image: quay.io/keycloak/keycloak:26.6.0
|
||||||
|
args:
|
||||||
|
- "start"
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8080
|
||||||
|
- name: https
|
||||||
|
containerPort: 8443
|
||||||
|
- name: management
|
||||||
|
containerPort: 9000
|
||||||
|
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"
|
||||||
|
- name: JAVA_OPTS_APPEND
|
||||||
|
value: "-javaagent:/otel/opentelemetry-javaagent.jar"
|
||||||
|
- 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"
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health/ready
|
||||||
|
port: 9000
|
||||||
|
initialDelaySeconds: 15
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 5
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health/live
|
||||||
|
port: 9000
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 15
|
||||||
|
failureThreshold: 5
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 1024Mi
|
||||||
|
limits:
|
||||||
|
cpu: 2000m
|
||||||
|
memory: 2048Mi
|
||||||
|
volumeMounts:
|
||||||
|
- name: realm-import
|
||||||
|
mountPath: /opt/keycloak/data/import
|
||||||
|
readOnly: true
|
||||||
|
- name: otel-agent
|
||||||
|
mountPath: /otel
|
||||||
|
volumes:
|
||||||
|
- name: realm-import
|
||||||
|
configMap:
|
||||||
|
name: keycloak-realm-classic-models
|
||||||
|
- name: otel-agent
|
||||||
|
emptyDir: {}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: keycloak
|
||||||
|
namespace: keycloak
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: keycloak
|
||||||
|
app.kubernetes.io/component: idp
|
||||||
|
app.kubernetes.io/part-of: keycloak
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 8080
|
||||||
|
targetPort: 8080
|
||||||
|
- name: management
|
||||||
|
port: 9000
|
||||||
|
targetPort: 9000
|
||||||
|
selector:
|
||||||
|
app: keycloak
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
- namespace.yaml
|
||||||
|
- secret.yaml
|
||||||
|
- postgresql/pvc.yaml
|
||||||
|
- postgresql/statefulset.yaml
|
||||||
|
- postgresql/service.yaml
|
||||||
|
- keycloak/configmap-realm.yaml
|
||||||
|
- keycloak/deployment.yaml
|
||||||
|
- keycloak/service.yaml
|
||||||
|
- ingress/tls.yaml
|
||||||
|
- ingress/ingress.yaml
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: keycloak
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: postgresql-data
|
||||||
|
namespace: keycloak
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 2Gi
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: keycloak-postgresql
|
||||||
|
namespace: keycloak
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: postgresql
|
||||||
|
app.kubernetes.io/component: database
|
||||||
|
app.kubernetes.io/part-of: keycloak
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 5432
|
||||||
|
targetPort: 5432
|
||||||
|
name: postgresql
|
||||||
|
selector:
|
||||||
|
app: keycloak-postgresql
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: keycloak-postgresql
|
||||||
|
namespace: keycloak
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: postgresql
|
||||||
|
app.kubernetes.io/component: database
|
||||||
|
app.kubernetes.io/part-of: keycloak
|
||||||
|
spec:
|
||||||
|
serviceName: keycloak-postgresql
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: keycloak-postgresql
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: keycloak-postgresql
|
||||||
|
app.kubernetes.io/name: postgresql
|
||||||
|
app.kubernetes.io/component: database
|
||||||
|
app.kubernetes.io/part-of: keycloak
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: postgresql
|
||||||
|
image: postgres:16-alpine
|
||||||
|
ports:
|
||||||
|
- containerPort: 5432
|
||||||
|
name: postgresql
|
||||||
|
env:
|
||||||
|
- name: POSTGRES_DB
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: keycloak-secrets
|
||||||
|
key: POSTGRES_DB
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: keycloak-secrets
|
||||||
|
key: POSTGRES_USER
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: keycloak-secrets
|
||||||
|
key: POSTGRES_PASSWORD
|
||||||
|
- name: PGDATA
|
||||||
|
value: /var/lib/postgresql/data/pgdata
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /var/lib/postgresql/data
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: postgresql-data
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: keycloak-secrets
|
||||||
|
namespace: keycloak
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
POSTGRES_USER: keycloak
|
||||||
|
POSTGRES_PASSWORD: <CHANGE_ME>
|
||||||
|
POSTGRES_DB: keycloak
|
||||||
|
KC_DB_USERNAME: keycloak
|
||||||
|
KC_DB_PASSWORD: <CHANGE_ME>
|
||||||
|
KC_BOOTSTRAP_ADMIN_USERNAME: admin
|
||||||
|
KC_BOOTSTRAP_ADMIN_PASSWORD: <CHANGE_ME>
|
||||||
|
KEYCLOAK_ADMIN: admin
|
||||||
|
KEYCLOAK_ADMIN_PASSWORD: <CHANGE_ME>
|
||||||
|
LIBRECHAT_OIDC_SECRET: <CHANGE_ME>
|
||||||
|
CLIENT_SECRET: <CHANGE_ME>
|
||||||
|
USER_PASSWORD: <CHANGE_ME>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- clustersecretstore.yaml
|
||||||
|
- externalsecret-realm.yaml
|
||||||
Reference in New Issue
Block a user