본문으로 건너뛰기

Admin UI Playwright 스크린샷 자동 캡처 절차

운영자 사이클 #1346 에서 사용한 admin UI Playwright 자동 캡처 절차를 표준화. 신규 admin 페이지 추가 시 동일 절차로 docs 스크린샷 갱신.

캡처 대상 (현 기준 7 페이지)

페이지인증캡처 파일
/loginX00-login-page.png
/admin/ml-pluginsO04-admin-ml-plugins-authenticated.png
/admin/driftO05-admin-drift-authenticated.png
/admin/model-grantsO06-admin-model-grants-authenticated.png
/admin/ml-pluginsredirect01-admin-ml-plugins-auth-redirect.png
/admin/driftredirect02-admin-drift-auth-redirect.png
/admin/model-grantsredirect03-admin-model-grants-auth-redirect.png

저장 경로: docs-site/static/img/m3-cycle/

절차

1. 임시 admin 비밀번호 발급

Keycloak admin 사용자 (admin@genon.ai) 의 비밀번호를 임시 reset:

# Vault 의 gend-realm-sync client_secret 으로 master realm 토큰 획득
CLIENT_SECRET=$(kubectl --context aks-genos-prod -n gend exec vault-0 -- \
env VAULT_SKIP_VERIFY=true VAULT_TOKEN="$VAULT_ROOT" \
vault kv get -format=json secret/gend/gend-realm-sync \
| jq -r '.data.data.client-secret')

TOKEN=$(curl -sS "https://gend.genon.ai/auth/realms/master/protocol/openid-connect/token" \
-d "client_id=gend-realm-sync" -d "client_secret=$CLIENT_SECRET" \
-d "grant_type=client_credentials" | jq -r .access_token)

USER_ID=$(curl -sS "https://gend.genon.ai/auth/admin/realms/gend/users?username=admin" \
-H "Authorization: Bearer $TOKEN" | jq -r '.[0].id')

TEMP_PW=$(openssl rand -base64 24 | tr -d '/=+' | head -c 28)Aa1!
curl -sS -X PUT "https://gend.genon.ai/auth/admin/realms/gend/users/$USER_ID/reset-password" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d "{\"type\":\"password\",\"value\":\"$TEMP_PW\",\"temporary\":false}"
echo "TEMP PW: $TEMP_PW"

2. Playwright 로그인 + 캡처

# scripts/capture_admin_ui.py (follow-up)
from playwright.sync_api import sync_playwright

def capture():
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
# Login
page.goto("https://gend.genon.ai/login")
page.fill('input[aria-label="이메일 또는 사용자명"]', "admin")
page.fill('input[aria-label="비밀번호"]', TEMP_PW)
page.click('button:has-text("로그인")')
page.wait_for_url("https://gend.genon.ai/")
# Capture admin pages
for path, fname in [
("/admin/ml-plugins", "04-admin-ml-plugins-authenticated.png"),
("/admin/drift", "05-admin-drift-authenticated.png"),
("/admin/model-grants", "06-admin-model-grants-authenticated.png"),
]:
page.goto(f"https://gend.genon.ai{path}")
page.wait_for_load_state("networkidle")
page.screenshot(path=f"docs-site/static/img/m3-cycle/{fname}", full_page=True)
browser.close()

3. 비밀번호 보안 회전

캡처 직후 임시 비밀번호를 새로운 안전한 랜덤 값으로 회전 + Vault 저장:

NEW_PW=$(openssl rand -base64 24 | tr -d '/=+' | head -c 28)Aa1!
curl -sS -X PUT "https://gend.genon.ai/auth/admin/realms/gend/users/$USER_ID/reset-password" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d "{\"type\":\"password\",\"value\":\"$NEW_PW\",\"temporary\":false}"

# Vault 에 저장
kubectl --context aks-genos-prod -n gend exec vault-0 -- \
env VAULT_SKIP_VERIFY=true VAULT_TOKEN="$VAULT_ROOT" \
vault kv put secret/gend/keycloak-admin-user-rotated \
username=admin password="$NEW_PW" \
rotated_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
rotated_by="screenshot-capture-cycle"

자동화 follow-up

본 절차를 scripts/capture_admin_ui.sh (또는 GitHub Actions workflow) 로 자동화 → admin UI 변경 시 docs 스크린샷 자동 갱신.

트러블슈팅

증상원인해결
Master realm token 401 (invalid_client_credentials)비밀번호 문제가 아니다 — master admin-cli 가 confidential 이라(#900) client_secret 없는 password grant 는 사용자 자격증명을 평가하기도 전에 거부된다--client admin-cli --secret <gend-api-keycloak-admin/GEND_KEYCLOAK_ADMIN_CLIENT_SECRET> 를 함께 전달, 또는 ② gend-realm-sync client_credentials grant 사용
Playwright login 실패UI 셀렉터 변경aria-label 또는 data-testid 기준 안정화
임시 비밀번호 회전 누락캡처 종료 후 cleanup 미실행캡처 스크립트 try/finally 패턴
스크린샷 빈 화면networkidle 대기 부족wait_for_selector 로 명시적 대기

:::warning 401 을 "비밀번호가 상했다"로 오진하지 말 것 Master realm token 401 을 보고 시크릿을 회전하면 안 된다. KEYCLOAK_ADMIN / KEYCLOAK_ADMIN_PASSWORD빈 DB 최초 부팅 때만 읽히므로, Secret 을 새 값으로 덮어써도 Keycloak DB 안의 실제 비밀번호는 바뀌지 않는다 — 없던 불일치를 진짜로 만들어낸다.

판별법: Keycloak 파드 로그의 LOGIN_ERROR 이벤트에서

  • error="invalid_client_credentials" + userId="null"클라이언트 인증 단계 거부. 사용자 자격증명은 평가조차 되지 않았다 = 비밀번호와 무관. --secret 을 붙여야 한다.
  • error="invalid_user_credentials" (userId 가 채워짐) → 그때가 진짜 비밀번호 문제다.

(prod 실측 2026-07-31: Secret 은 생성 후 write 0회, admin credential 1행, 평문↔argon2id 해시 일치. client_secret 을 추가하니 HTTP 200. "비밀번호 stale" 진단은 반증됐다.) :::

관련