From ddcb1c08e67fb76af446bc81e90959ddb2ed9bf8 Mon Sep 17 00:00:00 2001 From: DongHyeonka Date: Thu, 3 Sep 2026 16:20:38 +0900 Subject: [PATCH] docs: record the session store lab roadmap and progress Co-Authored-By: Claude Opus 5 --- README.md | 5 + deploy/lab/README.md | 9 +- docs/session-store-lab-roadmap.md | 195 ++++++++++++++++++++++++++++++ 3 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 docs/session-store-lab-roadmap.md diff --git a/README.md b/README.md index 29e6033..ca6a58e 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,11 @@ Keycloak을 중심으로 네 가지 브라우저 인증 통합 패턴을 같은 - AP3: Backend-for-Frontend (BFF) - AP4: Edge forward-auth +세션 저장소·refresh token 경쟁·장애 복구는 네 패턴을 가로지르는 별도 축으로 +`develop-keycloak-session-store` 브랜치에서 진행합니다. 계획과 진행 상황은 +[`docs/session-store-lab-roadmap.md`](docs/session-store-lab-roadmap.md)에 +있습니다. + 현재 `develop`의 공통 baseline은 Keycloak, PostgreSQL, Spring Boot API, nginx를 Docker Compose로 실행하는 토대입니다. 패턴별 구현은 이 baseline 위에서 별도 브랜치로 진행합니다. diff --git a/deploy/lab/README.md b/deploy/lab/README.md index 1ae1a99..31026e8 100644 --- a/deploy/lab/README.md +++ b/deploy/lab/README.md @@ -3,8 +3,13 @@ 세션 저장소·refresh token 경쟁·장애 복구를 검증하는 2노드 k3s 실험대. 네 인증 패턴(AP1~AP4)을 가로지르는 공통층이므로 별도 축으로 관리한다. -개념 설명은 [`docs/session-lab-concepts.md`](../../docs/session-lab-concepts.md)에 -누적한다. 이 문서는 절차만 담는다. +이 문서는 **절차**만 담는다. + +| 문서 | 내용 | +|---|---| +| [`docs/session-store-lab-roadmap.md`](../../docs/session-store-lab-roadmap.md) | 이 축의 계획과 진행 상황 | +| [`docs/session-lab-concepts.md`](../../docs/session-lab-concepts.md) | 등장 개념 전체 | +| [`docs/two-hop-proxy-header-contract.md`](../../docs/two-hop-proxy-header-contract.md) | 첫 실험 결과 | ## 토폴로지 diff --git a/docs/session-store-lab-roadmap.md b/docs/session-store-lab-roadmap.md new file mode 100644 index 0000000..414ec6d --- /dev/null +++ b/docs/session-store-lab-roadmap.md @@ -0,0 +1,195 @@ +# 세션 저장소 실험 축 — 계획과 진행 + +`develop-keycloak-session-store` 브랜치가 담당하는 작업의 전체 지도. +**무엇이 끝났고 무엇이 남았는지**를 여기서 추적한다. + +## 왜 별도 축인가 + +네 인증 패턴(AP1~AP4)은 **브라우저와 토큰의 관계**를 비교한다. 이 축은 +그것과 직교하는 질문을 다룬다 — **세션과 토큰이 서버 쪽 어디에 저장되고, +그 저장소가 죽으면 무슨 일이 벌어지는가.** + +초기 검토에서 전제 하나가 교정됐다. **Keycloak은 Redis를 세션 저장소로 +지원하지 않는다.** 그래서 이 축은 두 계층으로 갈린다. + +| 계층 | 저장소 | 해당 패턴 | +|---|---|---| +| **A. Keycloak 자체** | 임베디드 Infinispan + PostgreSQL | 네 패턴 공통 | +| **B. 애플리케이션 세션** | **Redis** | AP2 / AP3 / AP4 | + +A층은 네 패턴과 무관하게 공통이고, B층은 서버 세션을 갖는 세 패턴에만 +존재한다. 그래서 이 축을 AP1~AP4 어디에도 넣지 않고 별도로 둔다. + +## 기존 브랜치 레지스트리에 넣지 않는 이유 + +`docs/keycloak-branch-manifest.tsv`와 `scripts/audit-keycloak-branches.sh`는 +**정확히 39개** 브랜치를 강제하고, 각 브랜치가 외부 노트 파일과 1:1로 +대응하는지 검사한다. + +```sh +if [ "$expected_count" -ne 39 ]; then + echo "manifest must contain exactly 39 Keycloak branches" >&2 +``` + +이 축의 브랜치를 manifest에 추가하면 그 감사가 깨진다. 원래 39개는 +**완결된 인벤토리**이므로 건드리지 않고, 이 축은 이 문서로 추적한다. + +## 진행 상황 + +``` +✅ 환경 구축 +✅ 2홉 프록시 헤더 계약 +────────────────────────────────────────────── + Keycloak 멀티노드 클러스터 형성 + persistent vs volatile 세션 + refresh token 동시 갱신 경쟁 + Redis 애플리케이션 세션 + SSO + 장애 주입과 복구 +``` + +### ✅ 완료 — 환경 구축 + +2노드 k3s 실험대. 상세는 [`deploy/lab/README.md`](../deploy/lab/README.md), +개념은 [`session-lab-concepts.md`](session-lab-concepts.md). + +``` +브라우저 ─https─▶ 호스트 nginx(TLS 종료) ─▶ Traefik ─▶ Pod + kc-lab-1 / kc-lab-2 +``` + +**왜 Docker Compose가 아닌가** — 한 커널에서 "노드 죽이기"는 프로세스 +죽이기일 뿐이다. 노드 간 방화벽·비대칭 파티션·진짜 노드 상실은 **독립된 +커널 두 개**가 있어야 성립한다. + +### ✅ 완료 — 2홉 프록시 헤더 계약 + +[`two-hop-proxy-header-contract.md`](two-hop-proxy-header-contract.md) · +증거 [`evidence/two-hop-proxy-headers/`](evidence/two-hop-proxy-headers/) + +**확인한 것** — `docs/reverse-proxy-headers.md`의 1홉 계약이 2홉에서 +성립하지 않는다. 원인이 둘이었고 스위치가 셋이었다. + +| 스위치 | 하는 일 | +|---|---| +| nginx `proxy_set_header` | 헤더를 **만든다** | +| Traefik `forwardedHeaders.trustedIPs` | 받은 헤더를 **전달할지 버릴지** | +| 앱 `forward-headers-strategy` / `KC_PROXY_HEADERS` | 도착한 헤더를 **읽을지** | + +여기에 **프록시 우회 차단**(NetworkPolicy)까지 더해야 신뢰 경계가 닫힌다. +"헤더를 믿는다"와 "앞에 반드시 프록시가 있다"는 한 쌍이다. + +**다음 작업에 미치는 영향** — Keycloak을 올릴 때 +`KC_PROXY_HEADERS=xforwarded`와 `KC_HOSTNAME=https://auth.hyeonworks.com`을 +근거를 갖고 넣을 수 있고, 로그인이 깨져도 프록시 원인은 배제하고 볼 수 있다. + +--- + +### 다음 — Keycloak 멀티노드 클러스터 형성 + +브랜치: `feature/keycloak-multinode-cluster-jdbc-ping` + +**확인할 것** + +- Keycloak 2개 파드가 **서로를 발견해 하나의 클러스터를 이루는가** +- k3s 기본 디스커버리는 `jdbc-ping` — PostgreSQL의 `JGROUPS_PING` 테이블로 + 서로를 찾는다. 멀티캐스트가 필요 없다 +- **TCP 7800이 막히면 어떻게 되는가** — 디스커버리는 DB로 하지만 실제 클러스터 + 통신은 7800이다. 막으면 "DB에는 서로 등록되는데 클러스터가 안 붙는" 증상이 + 나온다. 단일 노드에서는 재현 불가능한 고장이며, 이 실험대를 2노드로 만든 + 이유 중 하나다 + +**주의** — Traefik이 replica 1이라 그 파드가 있는 노드를 죽이면 진입점 자체가 +사라진다. 노드 상실 실험 전에 Traefik을 2개로 늘릴지 결정해야 한다. + +**설정 근거** — 헤더 계약에서 확정한 값을 그대로 쓴다. + +``` +KC_HOSTNAME=https://auth.hyeonworks.com +KC_HOSTNAME_STRICT=true +KC_PROXY_HEADERS=xforwarded +KC_HTTP_ENABLED=true +``` + +### persistent vs volatile 세션 + +브랜치: `feature/keycloak-persistent-vs-volatile-sessions` + +**확인할 것** + +- Keycloak 26 기본값은 `persistent-user-sessions` — 세션이 **DB가 진실의 + 원천**이다 +- `--features-disabled=persistent-user-sessions`로 volatile 전환 시 비교 +- **PostgreSQL을 죽이면** 각각 어떻게 되는가 +- **노드 하나를 죽이면** 세션이 살아남는가 +- **롤링 배포 시** 로그아웃되는가 ← 운영에서 가장 자주 겪는 시나리오 + +이것이 "세션을 DB에 둘 때 vs 안 둘 때"의 Keycloak 버전이다. + +### refresh token 동시 갱신 경쟁 + +브랜치: `feature/keycloak-refresh-token-concurrency` + +**확인할 것** + +- realm은 이미 revoke refresh token + max reuse 0 + ([`refresh-token-rotation.md`](refresh-token-rotation.md)) +- **같은 RT로 두 노드에 동시 refresh**하면 몇 개가 성공하는가 +- 재사용 탐지가 **세션 전체를 무효화하는가** +- Infinispan `DIST_SYNC` 복제 지연이 경쟁 윈도우에 미치는 영향 + +단일 노드에서는 관찰할 수 없는 항목이다. + +### Redis 애플리케이션 세션 + SSO + +브랜치: `feature/keycloak-redis-app-session-store` + +**확인할 것** + +- B층 도입 — BFF(Spring Session Data Redis), oauth2-proxy(`--session-store-type=redis`) +- **Redis가 죽으면** 앱 세션은 전멸하지만 **Keycloak SSO 세션은 살아 있다** + → 재로그인이 무프롬프트로 끝난다. "SSO를 추가하면 뭐가 달라지나"의 답 +- Redis `appendonly` 유무에 따른 복구 차이 +- **백채널 로그아웃** — Keycloak에서 로그아웃했을 때 Redis의 앱 세션도 죽이려면 + `sid → session` 역인덱스가 필요하다. Redis 스키마 설계의 실질적 지점 +- 앱 두 개(`app1`, `app2`)로 SSO 체감 + +### 장애 주입과 복구 + +브랜치: `feature/keycloak-failure-injection-recovery` + +**확인할 것** + +| 주입 | 방법 | +|---|---| +| 노드 상실 | `virsh destroy` — 프로세스 kill 이 아닌 진짜 상실 | +| 비대칭 파티션 | 한쪽 게스트의 인바운드만 nftables 로 차단 | +| JGroups 7800 차단 | NetworkPolicy — 운영에서 쓸 방식 그대로 | +| DB 상실 | PostgreSQL 파드 정지 | +| Redis 상실 | Redis 파드 정지 | +| 지연 주입 | 게스트 안에서 `tc netem` — 커널이 분리돼 있어 안전 | + +**복구 절차**를 각각 기록한다. 이 실험대가 재현할 수 없는 것(실제 AZ 간 RTT, +ALB 고유 동작, 규모)은 스코프에서 제외한다. + +## 스코프에서 제외한 것 + +| 항목 | 이유 | +|---|---| +| 성능·처리량 측정 | 단일 물리 머신의 숫자는 운영에 대해 아무것도 말해주지 않는다 | +| 실제 AZ 간 지연 | 한 박스 안이라 재현 불가. `tc netem` 으로 근사만 | +| ALB 고유 동작 | 자체 스티키 쿠키·60초 idle timeout 은 실물 ALB 가 있어야 한다 | +| PostgreSQL HA | 스코프 폭발. "죽으면 어떻게 되나"까지가 현실적 선 | +| 멀티 사이트 / cross-site Infinispan | 로컬에서 "사이트"가 가짜라 배우는 것이 적다 | + +**이 실험대가 검증하는 것은 계약(정합성)이지 성능이 아니다.** + +## 관련 문서 + +| 문서 | 내용 | +|---|---| +| [`session-lab-concepts.md`](session-lab-concepts.md) | 등장 개념 전체 (가상화·네트워크·k3s·TLS·패키지) | +| [`two-hop-proxy-header-contract.md`](two-hop-proxy-header-contract.md) | 첫 실험의 측정·진단·수정 | +| [`deploy/lab/README.md`](../deploy/lab/README.md) | 실험대 구축·복구 절차 | +| [`four-pattern-tradeoff-matrix.md`](four-pattern-tradeoff-matrix.md) | AP1~AP4 비교. "server session" 행이 B층 대상 | +| [`refresh-token-rotation.md`](refresh-token-rotation.md) | 회전 계약 (1홉·단일 노드 가정) | +| [`reverse-proxy-headers.md`](reverse-proxy-headers.md) | 1홉 헤더 계약 원본 |