Files
platform-core/services/keycloak/README.md
T

4.7 KiB

Keycloak 서비스

이 Kustomization은 keycloak namespace에 Keycloak 26.7.0 인스턴스 하나를 선언한다. 이 인스턴스는 독립적인 OIDC Provider이며 인증 proxy나 Traefik ForwardAuth middleware가 아니다.

외부 요청 경로는 다음과 같다.

Client -> Host Nginx (TLS) -> Traefik web/HTTP -> keycloak-service:8080

상태(2026-07-23): Host Nginx의 Keycloak 공개 전환과 Gitea OIDC·브랜딩 rollout을 완료했다. Nginx 후보 SHA-256은 5c5cd74b4992f537fd27c50cf2209573a80a9904e0b19b58c3154717be6ff4a5, 백업은 /etc/nginx/sites-available/learn-services.before-keycloak-20260723160519이며 discovery issuer와 Gitea OAuth source·정책·redirect·브랜딩 자동 검증을 통과했다. 실제 realm 사용자의 브라우저 login/callback/logout은 별도 수용 시험으로 남아 있다.

Keycloak이 생성하는 Ingress는 비활성화한다. 저장소에서 관리하는 Ingress는 Traefik의 내부 HTTP entrypoint를 통해 id.learn.hyeonworks.com만 노출한다. 관리 포트 9000에는 Ingress나 NodePort가 없으며, NetworkPolicy의 출발지는 namespace 범위 Operator로 제한한다. TLS는 Host Nginx에서만 종료하므로 Keycloak은 Traefik이 선택한 HTTP 경로의 xforwarded header만 수락한다.

필수 Secret 계약

credential Secret 매니페스트나 값은 이 저장소에 보관하지 않는다. Keycloak 리소스를 생성하기 전에 동일하게 생성한 비밀번호를 사용하는 keycloak-db-credentials를 다음 두 namespace에 생성한다.

  • platform-data: CloudNativePG의 DatabaseRole에서 사용
  • keycloak: Keycloak 서버에서 사용

각 Secret의 유형은 kubernetes.io/basic-auth, username은 정확히 keycloak이어야 하며 usernamepassword 키가 있어야 한다. platform-data 사본에는 cnpg.io/reload: "true" label도 있어야 한다.

인스턴스를 처음 조정할 때 Operator가 keycloak namespace에 keycloak-initial-admin을 생성한다. 이 생성된 Secret은 소스 관리하지 않는다. 서비스를 운영 준비 완료 상태로 판단하기 전에 bootstrap credential을 교체하고 MFA를 활성화한다.

의존성과 렌더링 순서

  1. keycloak, platform-data, cnpg-system namespace가 존재한다.
  2. CloudNativePG와 platform-postgres가 Ready 상태다.
  3. keycloak-db-credentials의 두 사본이 모두 존재한다.
  4. Keycloak Operator가 설치되어 Ready 상태다.
  5. 이 서비스 Kustomization을 적용한다.
  6. Ready 상태와 내부 Traefik routing을 확인한 후에만 Host Nginx에 id.learn.hyeonworks.com을 설정한다.

Helm 없이 렌더링한다.

kubectl kustomize services/keycloak

배포 후 다음을 검증한다.

kubectl -n keycloak wait --for=condition=Ready \
  keycloak/keycloak --timeout=15m
kubectl -n keycloak get keycloak,pod,service,ingress,networkpolicy
curl --fail --silent --show-error \
  https://id.learn.hyeonworks.com/realms/hyeonworks/.well-known/openid-configuration \
  | jq --exit-status \
      --arg issuer 'https://id.learn.hyeonworks.com/realms/hyeonworks' \
      '.issuer == $issuer'

마지막 검사는 HTTP 성공 여부만 보지 않고 JSON의 issuer가 공개 issuer와 정확히 같은지 확인한다. 따라서 전환 전 Host Nginx의 정적 hold 응답이 HTTP 200을 반환하더라도 성공으로 오인하지 않는다.

TCP 9000을 대상으로 하는 Ingress, NodePort 또는 LoadBalancer가 없는지도 별도로 확인한다.

수동 bootstrap 진입점

Keycloak-only 적용은 AIStor와 분리한다. 저장소 루트에서 다음 순서를 사용하며 Secret payload는 명령 인자나 출력에 넣지 않는다.

kubectl apply --filename=infrastructure/namespaces/phase2/keycloak.yaml
bash scripts/bootstrap/create-keycloak-secrets.sh --generate --execute
bash scripts/bootstrap/apply-keycloak.sh --execute
bash scripts/bootstrap/configure-keycloak-gitea-oidc.sh --execute

각 스크립트는 context와 적용 범위를 다시 확인한다. 마지막 스크립트는 hyeonworks realm, confidential Gitea client와 Gitea OIDC Secret을 구성한다.

public discovery 전환은 클러스터 적용과 분리된 root 작업이며 실제 전환을 완료했다.

sudo bash scripts/bootstrap/apply-host-nginx-keycloak.sh --execute

상세 명령·출력·실패 경계는 중앙 OIDC 실행 원장에 기록한다.

공식 참고 문서