CLI SSO 운영 (Keycloak gend-cli client)
GenD CLI 의 사람용 SSO 흐름 — gend auth login — 은 Keycloak realm 의
gend-cli public client 에 의존한다. 본 문서는 운영자가 본 client 를
환경별로 (Kind dev / AKS prod) 동기화하고 검증하는 방법을 설명한다.
사용자용 빠른 시작: gend-cli 빠른 시작
머신용 자격증명: M2M 서비스 계정 인증
1. Client 의 책무
gend-cli 는 RFC 8252 (Authorization Code + Loopback + PKCE) 와
RFC 8628 (Device Authorization Grant) 둘 다 지원하는 public client 다.
| 속성 | 값 | 이유 |
|---|---|---|
publicClient | true | CLI 는 client_secret 안전 보관 불가 (RFC 8252) |
standardFlowEnabled | true | Loopback Authorization Code |
directAccessGrantsEnabled | false | password grant 금지 (보안) |
serviceAccountsEnabled | false | M2M 은 별도 gend-service / /admin/service-clients |
pkce.code.challenge.method | S256 | public client 필수 보안 |
oauth2.device.authorization.grant.enabled | true | Device Flow |
oauth2.device.polling.interval | 5 | 폴링 빈도 (초) |
redirectUris | http://127.0.0.1:*, http://localhost:*, urn:ietf:wg:oauth:2.0:oob | RFC 8252 권장 |
protocolMappers | gend-api audience + groups | gend-ui 와 동일 권한 모델 |
정의는 infra/keycloak/base/realm-export.json 에 단일 진실 공급원으로
존재한다. 환경별 redirectUris 차이는 overlay (infra/keycloak/overlays/<env>/)
에서 patch.
2. 동기화 절차
GenD Keycloak realm 은 ArgoCD ApplicationSet 미포함 — 수동 명령으로만 prod 에 반영된다.
Kind (dev / staging)
make keycloak-realm-sync KCTX=kind-gend-local
realm-sync-job Pod 가 realm-export.json 을 entity 별로 split 한 뒤
kcadm 으로 idempotent CRUD 한다. 신규 gend-cli client 는 첫 실행 시
CREATE, 이후 변경은 UPDATE.
AKS prod (정비창 필수)
# 사전 점검
kubectl --context aks-genos-prod -n gend get pods -l app=keycloak
kubectl --context aks-genos-prod -n gend get configmap keycloak-realm-export -o yaml | grep gend-cli
# 실행 (정비창 권장 — Keycloak 재시작 없음, sync Job 만 실행)
make keycloak-realm-sync KCTX=aks-genos-prod
# 검증
kubectl --context aks-genos-prod -n gend logs job/keycloak-realm-sync | grep -E "CREATE|UPDATE.*gend-cli"
⚠️
realm-sync-job자체는 무중단 (Keycloak 데이터만 변경) 이지만, 잘못된 realm 정의가 들어가면 모든 사람 사용자가 즉시 로그인 차단된다. 정비창 + 즉시 roll-back 절차 (이전 realm-export.json revert + 재실행) 를 사전 합의한 상태에서 진행한다.
3. 검증
3.1 Discovery endpoint
curl -s https://gend.genon.ai/auth/realms/gend/.well-known/openid-configuration \
| jq '{issuer, device_authorization_endpoint, token_endpoint}'
AKS prod 검증 결과 (Epic #1309 정비창, 2026-05-28):
{
"issuer": "https://gend.genon.ai/auth/realms/gend",
"device_authorization_endpoint": "https://gend.genon.ai/auth/realms/gend/protocol/openid-connect/auth/device",
"token_endpoint": "https://gend.genon.ai/auth/realms/gend/protocol/openid-connect/token"
}
device_authorization_endpoint 가 null 이 나오면 oauth2.device.authorization.grant.enabled 가 누락된 것.
3.2 Device endpoint 호출
Keycloak public client (gend-cli) 는 oauth2.pkce.code.challenge.method=S256
강제이므로 device endpoint 도 PKCE 동반 필요:
VERIFIER=$(openssl rand -base64 32 | tr -d '=' | tr '+/' '-_')
CHALLENGE=$(echo -n "$VERIFIER" | openssl dgst -sha256 -binary | base64 | tr -d '=' | tr '+/' '-_')
curl -s -X POST https://gend.genon.ai/auth/realms/gend/protocol/openid-connect/auth/device \
-d 'client_id=gend-cli' \
-d 'scope=openid profile email' \
-d "code_challenge=$CHALLENGE" \
-d 'code_challenge_method=S256' \
| jq '{verification_uri, user_code, expires_in, interval}'
AKS prod 검증 결과 (정비창):
{
"verification_uri": "https://gend.genon.ai/auth/realms/gend/device",
"user_code": "OPFU-HPMA",
"expires_in": 600,
"interval": 5
}
PKCE 미동반 시 {"error":"invalid_request","error_description":"Missing parameter: code_challenge_method"}.
관련 follow-up: #1361 — M4 initiate_device_flow 가 PKCE 미전송 → 본 검증에서 발견됨, CLI fix 필요.
3.3 회귀 가드 (CI 자동)
scripts/lint_gend_cli_keycloak_client.py 가 realm-export.json 의 핵심
invariant (publicClient, PKCE S256, Device Flow enabled, redirectUris loopback,
audience mapper) 를 검증한다. 위반 시 PR CI 가 차단한다.
수동 실행:
python3 scripts/lint_gend_cli_keycloak_client.py
python3 -m pytest scripts/test_lint_gend_cli_keycloak_client.py -v
3.4 실제 로그인 라운드트립
gend auth login # 브라우저 자동 진입
gend auth status # 세션 요약
gend catalog catalogs # API 호출 (자동 갱신 검증)
gend auth logout # 서버 + 로컬 cleanup
3.5 realm-sync Job 로그 — AKS prod 정비창 결과
make keycloak-realm-sync KCTX=aks-genos-prod 실행 후 Job 로그에서
CREATE client gend-cli + 2 mappers 확인:
$ kubectl --context aks-genos-prod -n gend logs job/keycloak-realm-sync \
| grep -E "CREATE|UPDATE.*gend-cli"
UPDATE client gend-ui (...)
...
CREATE client gend-cli
CREATE mapper gend-cli/gend-api-audience
CREATE mapper gend-cli/group-membership
UPDATE client trino (...)
...
Epic #1309 정비창 실행 (2026-05-28) 기록. 기존 client 들은 모두 UPDATE
(secret 보존), gend-cli 만 신규 CREATE.
3.6 UI 모달 격하 스크린샷

AKS prod 에서 실측. Epic #1309 M6 의 격하 시그널이 모두 노출됨:
- 타이틀 —
API 토큰 (데모/디버깅 전용) - 설명 — 일상 CLI 는
gend auth login(Loopback / Device Flow) 권장, CI/자동화는/admin/service-clients(M2M) - amber notice — "즉석 curl / 토큰 클레임 디버깅 용도로만 권장" +
gend auth login(사람 SSO) //admin/service-clients(M2M) 두 alternative - 토큰 (마스킹) + 만료 시각 + CLI 사용 예시 (
export GEND_API_URL=...)
캡처 자동화 (운영자 재현): ui/.local/capture-pr1352.mjs (본 worktree 의
gitignored 경로) 가 §3.7 의 M2M token 을 oidc.user:... localStorage
형식으로 주입한 뒤 Playwright 로 사용자 메뉴 → "API 토큰" click + dialog
크롭 캡처. 운영자 본인 SSO 비밀번호 없이 K8s Secret 만으로 진행 가능.
3.7 M2M token 자동 발급 (운영자 진단용)
ApiTokenDialog 스크린샷 / MCP 회귀 진단 등 audience=gend-api 토큰이 필요한
경우 gend-realm-sync (master service account) → gend-service (gend realm
M2M) 체인으로 자동 발급한다:
SECRET=$(kubectl --context aks-genos-prod -n gend get secret gend-realm-sync \
-o jsonpath='{.data.client-secret}' | base64 -d)
# 1) master realm service account → admin-scope token
MASTER_TOK=$(curl -s -X POST \
https://gend.genon.ai/auth/realms/master/protocol/openid-connect/token \
-d 'grant_type=client_credentials' \
-d 'client_id=gend-realm-sync' \
-d "client_secret=$SECRET" | jq -r .access_token)
# 2) admin API 로 gend-service client secret 조회
SVC_ID=$(curl -s -H "Authorization: Bearer $MASTER_TOK" \
"https://gend.genon.ai/auth/admin/realms/gend/clients?clientId=gend-service" \
| jq -r '.[0].id')
SVC_SECRET=$(curl -s -H "Authorization: Bearer $MASTER_TOK" \
"https://gend.genon.ai/auth/admin/realms/gend/clients/$SVC_ID/client-secret" \
| jq -r .value)
# 3) gend realm client_credentials grant → audience=gend-api token
M2M_TOK=$(curl -s -X POST \
https://gend.genon.ai/auth/realms/gend/protocol/openid-connect/token \
-d 'grant_type=client_credentials' \
-d 'client_id=gend-service' \
-d "client_secret=$SVC_SECRET" | jq -r .access_token)
발급된 token claim 예 (Epic #1309 정비창 검증):
{
"aud": ["gend-api", "account"],
"azp": "gend-service",
"iss": "https://gend.genon.ai/auth/realms/gend",
"scope": "email profile"
}
3.8 MCP 회귀 검증 (사람 SSO 흐름 무영향 확인)
Epic #1309 의 realm 변경이 MCP 핸들러 (POST /mcp) 회귀를 일으키지 않는지
검증. MCP endpoint 는 Ingress 가 /api 만 라우팅하므로 직접 port-forward.
kubectl --context aks-genos-prod -n gend port-forward svc/gend-api 18000:8000 &
M2M_TOK=... # §3.7 참조
curl -s -X POST http://localhost:18000/mcp \
-H "Authorization: Bearer $M2M_TOK" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}' \
| jq '.result.tools | length'
Epic #1309 정비창 검증 결과: 27 tools 노출 (list_catalogs / list_schemas
/ execute_query / list_intel_sources 등). audience=gend-api 토큰만 있으면
azp 가 gend-service 든 gend-cli 든 동일 핸들러 통과 — 사람 SSO 흐름
(azp=gend-cli) 에도 회귀 없음.
4. Roll-back
문제 발생 시 이전 realm-export.json 으로 revert + 재실행:
git revert <bad-commit>
git push origin main
# main 머지 후
make keycloak-realm-sync KCTX=aks-genos-prod
realm-sync-job 이 entity 별 idempotent 라 revert 도 안전. 단,
삭제는 명시 처리 필요 — gend-cli client 를 완전히 제거하려면
admin console 또는 kcadm delete clients/<id> 를 수동 호출.
5. 알려진 함정
| 함정 | 대응 |
|---|---|
redirectUris 의 http://127.0.0.1:* wildcard 가 Keycloak 17 미만에서 거부 | infra/keycloak/base/deployment.yaml 의 이미지 태그 확인 (현재 26.2) |
| AKS prod 의 issuer URL 이 CLI authority 와 다름 (Mixed Content 등) | discovery.py 의 metadata override 지원, feedback_keycloak_oidc 메모 참조 |
gend auth login 실패 후 stale 키체인 entry 잔존 | gend auth logout 또는 keyring del gend-cli default 로 정리 |
| Loopback 임의 포트가 사내 방화벽에 막힘 | --device 명시 또는 GEND_HEADLESS=1 설정 |
refresh_token rotation 시 멀티 터미널 race | TokenStore 의 fcntl 파일 잠금 — ~/.gend/token.lock |