228 lines
10 KiB
Markdown
228 lines
10 KiB
Markdown
# 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 권장) <!-- gitleaks:allow -->
|
|
- `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
|