merge: shared broker and deployment contracts
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
# Federated account key: `sub`, not email
|
||||
|
||||
외부 IdP의 email은 표시·연락 속성이지 계정 식별자나 자동 연결 증명이 아니다.
|
||||
Keycloak의 federated identity는 provider alias와 provider user ID(`sub`)를
|
||||
로컬 사용자에 연결한다.
|
||||
|
||||
정책:
|
||||
|
||||
- 신규 identity의 email이 기존 로컬 계정과 충돌하면 기존 계정의 인증을 다시
|
||||
요구하는 기본 First Broker Login flow를 사용한다.
|
||||
- `Automatically Set Existing User`를 production flow에 넣지 않는다.
|
||||
- upstream email 변경은 같은 `sub`의 계정 귀속을 바꾸지 않는다.
|
||||
- 마지막 로그인 수단을 unlink하는 UI에서는 먼저 다른 인증 수단을 등록하도록
|
||||
안내한다.
|
||||
|
||||
`verify-account-linking-sub-vs-email.sh`는 mock IdP 사용자의 email을 실제로
|
||||
변경하고 다시 로그인한다. 로컬 사용자 ID가 유지되고 federated `userId`가
|
||||
upstream `sub`와 같은지 확인한 후 원래 email을 복구한다.
|
||||
@@ -0,0 +1,37 @@
|
||||
# Four Keycloak integration patterns
|
||||
|
||||
| 축 | AP1 SPA direct | AP2 token mediator | AP3 BFF | AP4 edge auth |
|
||||
|---|---|---|---|---|
|
||||
| OAuth client | public | confidential | confidential | confidential proxy |
|
||||
| browser 보유물 | access/refresh token | 짧은 handoff code 또는 app token | HttpOnly session cookie | proxy session cookie |
|
||||
| OAuth code 교환 | browser + PKCE | mediator backend | BFF | oauth2-proxy |
|
||||
| API bearer 검증 | Spring resource server | mediator/downstream API | BFF 내부 또는 downstream | edge가 인증 후 trusted header |
|
||||
| server session | 없음 | handoff 상태만 짧게 | 필수 | proxy cookie/session |
|
||||
| XSS token 탈취면 | 가장 큼 | 축소 | browser token 제거 | browser token 제거 |
|
||||
| CSRF 주의 | token endpoint/refresh 설계 | app cookie 사용 시 | 필수 방어 | proxy cookie 사용 시 |
|
||||
| 수평 확장 상태 | 단순 | handoff store 공유 가능 | session store 필요 | proxy 설정에 따름 |
|
||||
| 주 학습 포인트 | PKCE/JWT/RS | token 경계·one-time handoff | oauth2Login/session/CSRF | auth_request/header trust |
|
||||
|
||||
## 선택 기준
|
||||
|
||||
- 브라우저에서 OAuth와 token 수명주기를 직접 학습하려면 AP1.
|
||||
- 브라우저에 upstream token을 주지 않되 API 호출은 bearer 중심으로 유지하려면
|
||||
AP2.
|
||||
- token을 browser에서 완전히 제거하고 애플리케이션 단위 인가·세션을
|
||||
중앙화하려면 AP3.
|
||||
- 기존 upstream을 수정하기 어렵고 경계에서 일괄 인증하려면 AP4.
|
||||
|
||||
Google federation은 다섯 번째 인증 패턴이 아니다. 네 패턴 모두 최종적으로
|
||||
Keycloak token/session을 소비하며, Google은 Keycloak 앞의 upstream IdP
|
||||
hop으로 추가된다.
|
||||
|
||||
## 이 repository의 실행 증거
|
||||
|
||||
- AP1: PKCE SPA, issuer/audience, token storage, refresh/logout 검증
|
||||
- AP2: confidential client와 one-time access handoff 검증
|
||||
- AP3: `oauth2Login` session과 CSRF/SameSite 검증
|
||||
- AP4: oauth2-proxy, nginx `auth_request`, spoofed header 제거 검증
|
||||
- 공통: local mock Google brokering, First Broker Login, claim/role mapping 검증
|
||||
|
||||
각 근거 브랜치와 병합 여부는 `keycloak-branch-manifest.tsv` 및
|
||||
`audit-keycloak-branches.sh`로 추적한다.
|
||||
@@ -0,0 +1,18 @@
|
||||
# Google claim-to-role mapping
|
||||
|
||||
`hd=example.test`인 upstream OIDC identity에는 Keycloak realm role
|
||||
`employee-role`을 부여한다. 매핑 키는 email이 아니라 Google subject이며,
|
||||
role 조건에 쓰는 `hd` claim은 mock provider와 실제 Google provider에서 같은
|
||||
계약을 사용한다.
|
||||
|
||||
Realm import는 `oidc-role-idp-mapper`를 선언한다. 실제 Google 설정 스크립트도
|
||||
같은 mapper를 upsert한다. 따라서 재실행해도 mapper가 중복되지 않는다.
|
||||
|
||||
검증:
|
||||
|
||||
```sh
|
||||
./scripts/verify-google-claim-to-role.sh
|
||||
```
|
||||
|
||||
검증기는 mock Google 로그인, Authorization Code + PKCE 교환, 최종 Keycloak
|
||||
access token의 `realm_access.roles`를 차례로 확인한다.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Google redirect URI policy
|
||||
|
||||
Google에 등록하는 redirect URI는 애플리케이션 SPA callback이 아니라 Keycloak
|
||||
broker endpoint다.
|
||||
|
||||
```text
|
||||
https://auth.example.test/realms/keycloak-patterns/broker/google/endpoint
|
||||
```
|
||||
|
||||
규칙:
|
||||
|
||||
- production URI는 HTTPS와 고정된 public Keycloak origin을 사용한다.
|
||||
- wildcard, path prefix, 임시 tunnel hostname을 production OAuth client에
|
||||
등록하지 않는다.
|
||||
- 개발·스테이징·운영은 Google OAuth client를 분리한다.
|
||||
- reverse proxy가 있더라도 Google이 보는 URI와 Keycloak이 생성하는 URI가
|
||||
byte-for-byte 같아야 한다.
|
||||
- `configure-google-idp.sh`가 출력하는 URI를 Google Console의 Authorized
|
||||
redirect URI와 대조한다.
|
||||
|
||||
```sh
|
||||
PUBLIC_KEYCLOAK_URL=https://auth.example.test \
|
||||
./scripts/verify-google-redirect-uri-policy.sh
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# HTTPS termination: nginx or Caddy
|
||||
|
||||
두 예제 모두 public `443`에서 TLS를 종료하고 private Docker network의
|
||||
`keycloak:8080`으로 전달한다. Keycloak 쪽 설정은
|
||||
`deploy/reverse-proxy/keycloak.env.example`의 hostname/proxy contract를
|
||||
같이 사용한다.
|
||||
|
||||
- nginx: 인증서 배포·갱신을 운영자가 담당할 때 적합하다.
|
||||
- Caddy: ACME를 통한 인증서 수명주기를 proxy가 담당하게 할 때 간단하다.
|
||||
- 둘을 동시에 production entry point로 띄우지 않는다.
|
||||
- 인증서와 private key는 repository 또는 image에 포함하지 않는다.
|
||||
- HTTP challenge/redirect 및 방화벽의 80/443 허용은 배포 환경에서 별도로
|
||||
결정한다.
|
||||
|
||||
검증 스크립트는 임시 자체 서명 인증서를 만들고 두 vendor image에서 설정을
|
||||
각각 validate한 뒤 임시 파일을 제거한다.
|
||||
|
||||
```sh
|
||||
./scripts/verify-https-termination-config.sh
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
# Public HTTPS domain for broker callbacks
|
||||
|
||||
Google brokering을 반복 테스트할 때는 Cloudflare **named tunnel + 관리
|
||||
도메인**을 기본 profile로 사용한다. `trycloudflare.com` quick tunnel과
|
||||
임의 ngrok URL은 일회성 데모용이며 고정 callback으로 간주하지 않는다.
|
||||
|
||||
설정 순서:
|
||||
|
||||
1. `cloudflared tunnel login`
|
||||
2. `cloudflared tunnel create keycloak-patterns`
|
||||
3. 예제 config의 tunnel UUID와 credentials path를 실제 값으로 교체
|
||||
4. `cloudflared tunnel route dns keycloak-patterns auth.example.test`
|
||||
5. `cloudflared tunnel run keycloak-patterns`
|
||||
6. Keycloak `KC_HOSTNAME`과 Google redirect URI를 같은 public host로 설정
|
||||
|
||||
컨테이너 안의 `127.0.0.1`은 cloudflared 컨테이너 자신이므로 origin에는
|
||||
`reverse-proxy:8080` 같은 Compose service DNS를 사용한다. 마지막 catch-all
|
||||
ingress는 알 수 없는 hostname을 404로 끝낸다.
|
||||
|
||||
실 tunnel 생성과 DNS 변경에는 사용자 소유 계정·도메인이 필요하므로 자동
|
||||
검증은 ingress 파일의 구조까지만 수행한다.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Reverse proxy headers
|
||||
|
||||
TLS를 reverse proxy에서 종료하면 Keycloak은 브라우저가 사용한 외부 origin을
|
||||
정확히 알아야 한다. 배포 예제는 다음 계약을 함께 적용한다.
|
||||
|
||||
- nginx는 `Host`, `X-Forwarded-Host`, `X-Forwarded-Port`,
|
||||
`X-Forwarded-Proto`, `X-Forwarded-For`를 덮어쓴다.
|
||||
- Keycloak은 `KC_PROXY_HEADERS=xforwarded`로 그 헤더 형식을 명시한다.
|
||||
- `KC_HOSTNAME`은 외부 HTTPS URL로 고정하고 strict hostname 검증을 켠다.
|
||||
- Keycloak의 8080 포트는 public으로 publish하지 않고 proxy network에서만
|
||||
접근시킨다. 신뢰되지 않은 클라이언트가 forwarded header를 직접 넣을 수
|
||||
있으면 안 된다.
|
||||
|
||||
`scripts/verify-reverse-proxy-headers.sh`는 양쪽 설정의 짝과 nginx 구문을
|
||||
검증한다.
|
||||
Reference in New Issue
Block a user