# Keycloak 기준 ## 목적 이 문서는 Kubernetes 환경에서 Keycloak 26+ (Quarkus distribution)을 1000+ 서비스의 ID 브로커로 운영하기 위한 기준을 고정한다. - 빌드/실행 두 단계(`kc.sh build` → `kc.sh start --optimized`)를 전제한다 - Hostname v2, proxy-headers, management port 9000, Infinispan 캐시를 명시한다 - 단일 Deployment 수제 배포 대신 **Keycloak Operator**를 1차 권장 경로로 둔다 - DB / 캐시 / probe / Ingress / RealmImport 를 YAML이 아닌 "설계 결정"으로 먼저 고정한다 - auth-server(도메인 위임)와 Keycloak(IdP)의 ownership 경계를 분리한다 ## 공식 의미 (Keycloak 26+ 기준) - 운영 실행 방식은 **두 단계**다. `kc.sh build`가 Quarkus augmentation을 수행해 optimized 이미지를 만들고, `kc.sh start --optimized`가 그 이미지를 기동한다. 빌드 시 configuration은 런타임에 변경 불가능하다. - **`--proxy` 옵션은 v24에서 deprecated, v26에서 제거되었다.** 대체는 `--proxy-headers=xforwarded` 또는 `--proxy-headers=forwarded`다. - **Hostname v2**가 기본값이며 `--hostname`은 full URL을 받는다. v24+ 이후 `hostname-url`, `hostname-path`, `hostname-port`는 제거되었다. admin 전용 주소는 `--hostname-admin`으로 지정한다. - `--hostname-strict`의 production 기본값은 `true`다. `--hostname-backchannel-dynamic`은 기본 `false`다. - HTTPS 종료를 Ingress/LB가 하면 Keycloak은 `KC_HTTP_ENABLED=true`로 HTTP를 수신한다. - DB는 `KC_DB=postgres`, `KC_DB_URL`은 **JDBC URL**(`jdbc:postgresql://host:5432/db`) 형식이다. - **Management interface는 기본 포트 `9000`**에서 제공되고, `/health`, `/health/started`, `/health/ready`, `/health/live`, `/metrics`를 호스팅한다. Pod probe와 Prometheus scrape는 모두 9000 대상이다. - Production cache type 기본은 `ispn`(Infinispan distributed). **cache-stack 기본값이 `kubernetes`(DNS_PING)에서 v25부터 `jdbc-ping`으로 바뀌었다.** Operator가 관리하는 StatefulSet은 Raft-less 클러스터링을 jdbc-ping으로 수행한다. - Operator가 생성하는 워크로드는 **StatefulSet**이다 (pod ordering이 Infinispan discovery와 맞물린다). 사용자 수제 YAML에서도 Operator 경로가 1차 권장이다. - `KeycloakRealmImport` CR은 Keycloak server가 준비된 후 realm JSON을 server side로 import하는 1회성 Job을 생성한다. ## 기본 규칙 ### 1. 운영 실행은 `start --optimized` 두 단계 빌드 단계에서 feature/db/health/metrics를 굽고, 실행 단계에서 runtime config만 주입한다. 기본: - Dockerfile에서 `RUN /opt/keycloak/bin/kc.sh build`로 optimized 이미지 생성 - 컨테이너 CMD는 `kc.sh start --optimized` - runtime-only config: hostname, DB URL/credential, log level 기본 금지: - `start-dev` 운영 사용 - `start` 단독 실행(build 없이 매 기동마다 augmentation) ### 2. `--proxy-headers` 사용, `--proxy` 금지 Keycloak 26에서 `--proxy`는 제거되었다. 기본: - HTTPS 종료 proxy 뒤: `KC_PROXY_HEADERS=xforwarded` (nginx, Traefik, ingress-nginx 등) - RFC 7239 지원 proxy: `KC_PROXY_HEADERS=forwarded` - proxy가 Host / X-Forwarded-* 를 **덮어쓰도록** 고정 기본 금지: - `KC_PROXY=edge|reencrypt|passthrough` 등 legacy 옵션 ### 3. Hostname v2: full URL로 고정 기본: - `KC_HOSTNAME=https://auth.example.com` (full URL) - Admin Console 분리: `KC_HOSTNAME_ADMIN=https://admin-auth.example.com` - `KC_HOSTNAME_STRICT=true` (production 기본 유지) - `KC_HOSTNAME_BACKCHANNEL_DYNAMIC=false` (기본값; 다중 cluster federation일 때만 true 검토) 기본 금지: - 제거된 옵션 사용: `KC_HOSTNAME_URL`, `KC_HOSTNAME_PATH`, `KC_HOSTNAME_PORT` - hostname 없이 요청 헤더에서 해석되도록 방치 ### 4. HTTPS는 Ingress/LB에서 종료, Pod는 HTTP Pod 내부에서 TLS 재암호화가 필요 없으면 Pod는 HTTP로 수신한다. 기본: - `KC_HTTP_ENABLED=true`, `KC_HTTP_PORT=8080` - Ingress가 TLS 종료 + proxy-header 주입 - passthrough TLS가 필요한 보안 요구가 있을 때만 `KC_HTTPS_*` 경로 채택 ### 5. DB는 외부 PostgreSQL + JDBC URL 기본: - `KC_DB=postgres` - `KC_DB_URL=jdbc:postgresql://keycloak-db-rw:5432/keycloak` (CloudNativePG `-rw` RW endpoint 권장) - `KC_DB_USERNAME`, `KC_DB_PASSWORD` → Secret `secretKeyRef` - Keycloak schema와 auth-server schema는 **다른 DB 또는 다른 database**로 분리 기본 금지: - 내장 H2 (`dev-file`, `dev-mem`) 운영 - root/superuser credential 사용 - Keycloak DB에 auth-server migration 수행 ### 6. Management port 9000은 외부 비공개 기본: - Pod containerPort 9000 (`KC_HTTP_MANAGEMENT_PORT=9000`) - Service에 9000 expose하되 Ingress 대상 제외 - probe는 9000 대상: `/health/started`, `/health/ready`, `/health/live` - Prometheus scrape는 내부 scraper가 9000/`/metrics`에 직접 접근 ### 7. Probe timing은 Keycloak 기동 특성에 맞춘다 Keycloak은 JVM + Quarkus + Infinispan + DB migration으로 cold start가 30~120초다. 기본: - `startupProbe`: `/health/started`, `periodSeconds: 5`, `failureThreshold: 60` → 최대 5분 유예 - `readinessProbe`: `/health/ready`, `periodSeconds: 10`, `failureThreshold: 3` - `livenessProbe`: `/health/live`, `periodSeconds: 30`, `failureThreshold: 3`, `initialDelaySeconds: 60` ### 8. Cache: Infinispan + 버전별 stack 기본값 인지 v25+ 기본 stack은 **`jdbc-ping`**이다. DB를 discovery 매체로 쓰므로 headless service / ServiceAccount RBAC가 필요 없다. 기본: - Operator 관리 클러스터: `KC_CACHE=ispn`, `KC_CACHE_STACK=jdbc-ping` (명시) - 수제 StatefulSet에서 headless service 경유 discovery를 쓰려면 `KC_CACHE_STACK=kubernetes` (DNS_PING) 선택 - local mode 운영 금지 (`KC_CACHE=local`은 single replica 테스트 전용) ### 9. Operator 경로를 1차 권장으로 1000+ 서비스 규모에서 realm import, CR 기반 롤아웃, cache stack 자동 설정, StatefulSet 관리를 Operator가 담당한다. 기본: - `Keycloak` CR + `KeycloakRealmImport` CR 조합 - OLM(OperatorHub) 또는 공식 manifest 설치 - 수제 StatefulSet 유지보수는 Operator 기능이 부족할 때만 허용 ### 10. 공개 경로 최소화 Ingress에 허용하는 기본 경로: - `/realms/` — OIDC / SAML endpoint - `/resources/` — Keycloak theme / JS - `/.well-known/` — OIDC discovery, JWKS - `/js/` — Keycloak adapter JS (필요 시) 기본 금지: - `/admin/` 외부 공개 (별도 admin host 경유) - `/metrics`, `/health*` 외부 공개 - `/` 전체 wildcard 공개 ### 11. Admin Console은 별도 host로 분리 Admin 접근은 일반 SSO host와 다른 경로로 둔다. 기본: - `KC_HOSTNAME_ADMIN=https://admin-auth.example.com` - Admin host는 사내 IP 화이트리스트 / VPN / OIDC forward-auth로 추가 보호 - production에서 `/admin/` 을 SSO 공용 host에 노출 금지 ### 12. Realm은 `KeycloakRealmImport` CR로 선언적 관리 기본: - realm JSON은 Git에 보관 - `KeycloakRealmImport` CR이 Job을 생성해 server-side import - secret이 들어가는 identity provider client secret은 Vault에서 주입 기본 금지: - Admin REST / kcadm.sh를 CI/CD pipeline이 직접 호출해 상태 변경 - realm export 파일을 Pod 내부 파일로 배포 ### 13. High Availability: replicas ≥ 2 + PDB + topologySpread Operator는 `instances` 필드로 replica를 제어한다. 기본: - `instances: 3` (odd quorum 아님 — cache replication 안정성) - `PodDisruptionBudget minAvailable: 2` - `topologySpreadConstraints`로 node/zone 분산 ### 14. Sticky session은 성능 최적화 옵션 Infinispan이 session을 복제하므로 필수는 아니지만, login flow 중간 redirect 지연을 줄인다. 기본: - Ingress controller에서 `AUTH_SESSION_ID` cookie affinity - Service `sessionAffinity: ClientIP`는 2차 선택지 ### 15. Security context: Restricted PSS 준수 기본: - `runAsNonRoot: true`, `runAsUser: 1000` - `readOnlyRootFilesystem: true` (Keycloak은 `/opt/keycloak/data` 만 writable 요구; emptyDir 마운트) - `allowPrivilegeEscalation: false`, `capabilities.drop: [ALL]` - `seccompProfile: RuntimeDefault` ### 16. Resource 요청은 JVM 특성 반영 기본 단일 replica: - requests: `cpu: 500m`, `memory: 1Gi` - limits: `cpu: 2`, `memory: 2Gi` - JVM: `JAVA_OPTS_APPEND=-XX:MaxRAMPercentage=70 -XX:InitialRAMPercentage=50` login throughput 요구가 높으면 replica 수평 확장 우선 (JVM heap 수직 확장 2차). ### 17. Observability 기본: - `KC_METRICS_ENABLED=true`, `KC_HEALTH_ENABLED=true` - `ServiceMonitor` 또는 `PodMonitor`로 9000/`/metrics` scrape - event metric (login failure, token issuance)은 필요한 것만 활성화 (high cardinality 방지) ### 18. DB credential / admin credential은 Vault 경유 기본: - `KC_DB_PASSWORD`: VSO `VaultDynamicSecret`(postgres dynamic role) 또는 `VaultStaticSecret` → K8s Secret 동기화 - Bootstrap admin (`KEYCLOAK_ADMIN`, `KEYCLOAK_ADMIN_PASSWORD`): 최초 기동 후 제거, 실 운영 admin은 realm-managed 기본 금지: - Secret을 Git에 평문 저장 - 환경변수 default value로 credential 하드코딩 ### 19. 현재 스택 기본 권장안 - 배포: Keycloak Operator + `Keycloak` CR + `KeycloakRealmImport` CR - 워크로드: StatefulSet (Operator 생성) - Service: ClusterIP (9000, 8080) - Ingress: SSO host + Admin host 분리 - DB: CloudNativePG PostgreSQL cluster + Vault dynamic secret - Cache: `ispn` + `jdbc-ping` - Probe: 9000 management port - Replicas: 3 + PDB + topologySpread ## 프로젝트 기준 요약 - Keycloak 26+ Quarkus distribution, `start --optimized` 두 단계 - `--proxy-headers` 사용, `--proxy` 금지 - Hostname v2 full URL, admin host 분리, strict=true 유지 - DB: 외부 PostgreSQL, JDBC URL, Vault credential - Management port 9000 내부 전용, probe / metrics 대상 - Infinispan `ispn` + `jdbc-ping` (v25+) - Operator 경로 1차 권장 (CR로 realm import 포함) - Admin Console 별도 host, 공개 경로는 `/realms/`, `/resources/`, `/.well-known/` - Replicas ≥ 2 + PDB + topologySpread + Restricted PSS