Files
document-haness/docs/keycloak-session-store/source/deploy/lab
DongHyeonkaandClaude Opus 5 b2963105a8 docs(keycloak-session-store): import the session-storage lab as a new project
The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.

Follows the import procedure in README.md.

  source/     the originating repository verbatim — 78 documents, 28 SVGs,
              8 manifests, plus .source-revision recording the commit
  final/      the SSOT
    document.md   729 lines written from the 29 experiment documents, not
                  concatenated: what was predicted, what was measured, and
                  where the measurement itself was wrong
    evidence/raw    125 outputs, flattened to <experiment>__<file> because
                    the originals collided (01-baseline.txt appeared three
                    times) and the audit only globs the top level
    evidence/meta   one per raw file; command and exitCode are null and the
                    README says why rather than inventing them
    evidence/browser  22 captures
    assets/       three diagrams through techviz
    .techviz/     their VizSpecs

A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.

Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.

verify-pipeline.py passes. audit-records.py reports no issues.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 22:51:59 +09:00
..

Session store lab

세션 저장소·refresh token 경쟁·장애 복구를 검증하는 2노드 k3s 실험대. 네 인증 패턴(AP1~AP4)을 가로지르는 공통층이므로 별도 축으로 관리한다.

이 문서는 절차만 담는다.

문서 내용
docs/session-store-lab-roadmap.md 이 축의 계획과 진행 상황
docs/session-lab-concepts.md 등장 개념 전체
docs/session-lab-operations.md 관측 도구 · 자주 쓰는 명령 · 훈련
docs/two-hop-proxy-header-contract.md 첫 실험 결과

토폴로지

  브라우저 / SSH  (tailnet)
        │  https://{auth,app1,app2}.hyeonworks.com → 100.83.212.4
        ▼
  lab host  ── nginx :443  TLS 종료 · X-Forwarded-* 주입
        │      nginx :80   301 → https
        │
        │  virbr0  192.168.122.0/24 (libvirt NAT)
        ├──▶ kc-lab-1  .11  k3s server  Traefik :80
        └──▶ kc-lab-2  .12  k3s agent   Traefik :80
                            └──▶ Pod

nginx → Traefik 2홉이 운영 구조와 같다는 점이 이 배치의 핵심이다. L7 프록시가 두 겹인 이유는 역할이 다르기 때문이다 — nginx는 바깥세상과의 접점(TLS·인증서·헤더)을, Traefik은 클러스터 내부의 동적 라우팅을 맡는다.

구성 요소

경로 역할
cloud-init/kc-lab.yaml.example 게스트 부트스트랩 템플릿
host/nginx-keycloak-lab.conf lab host의 sites-available/keycloak-lab
k8s/echo.yaml 2홉 헤더 계약 측정용 워크로드
scripts/rebuild-seed.sh cloud-init 시드 ISO 재생성 + 풀 업로드
scripts/build-and-import.sh 이미지 빌드 → 각 노드 containerd 반입
scripts/measure-proxy-headers.sh 헤더 계약 실측
scripts/verify-lab.sh 인프라 상태 점검

상태 점검

./deploy/lab/scripts/verify-lab.sh      # lab host 에서

404가 성공 신호다. TLS가 종료되고 Traefik까지 도달했으나 매칭되는 Ingress 규칙이 없다는 뜻이다. 502나 연결 거부면 체인이 끊긴 것이다.

첫 실험 — 2홉 헤더 계약

docs/reverse-proxy-headers.md의 계약은 nginx 1홉을 가정하고 쓰였다. 실제 배치는 2홉이므로, nginx가 세팅한 X-Forwarded-*를 Traefik이 그대로 넘기는지 덮어쓰는지 측정해서 확인한다.

이 결론이 뒤의 모든 실험에 깔린다. Keycloak의 iss 클레임, redirect URL, 쿠키 도메인 검증이 전부 이 헤더에 의존하기 때문이다.

# 워크스테이션: 이미지 빌드 후 두 노드에 반입
./deploy/lab/scripts/build-and-import.sh

# lab host: 배포
kubectl apply -f deploy/lab/k8s/echo.yaml
kubectl -n header-lab rollout status deployment/echo

# 어디서든: 실측
./deploy/lab/scripts/measure-proxy-headers.sh

관측 대상은 넷이다.

  1. X-Forwarded-For — Traefik이 덧붙이는가 덮어쓰는가
  2. X-Forwarded-Proto / -Host / -Port — 그대로 전달되는가
  3. 위조 내성 — 클라이언트가 직접 넣은 X-Forwarded-*가 앱까지 도달하는가
  4. scheme / secure / requestUrl — Keycloak이 URL을 만들 때 쓰는 값

3번이 신뢰 경계의 핵심이다. 이 헤더들은 누구나 위조할 수 있는 평범한 HTTP 헤더이므로, 신뢰 경계에 선 프록시가 반드시 덮어써야 한다.

이미지 배포 경로

k3s는 containerd를 쓰고 이 실험대에는 레지스트리가 없다.

워크스테이션  docker build → docker save
      │ ssh (lab host 경유)
      ▼
게스트        sudo k3s ctr images import
매니페스트    imagePullPolicy: Never

두 노드 모두에 반입해야 한다. 스케줄러가 어느 노드에 배치할지 모른다. Keycloak·PostgreSQL·Redis는 공식 이미지를 그대로 당겨오므로 이 경로가 필요한 것은 자체 빌드 이미지뿐이다.

lab host에 Docker를 설치하지 않는다. k3s의 containerd와 이미지 저장소가 갈려서 docker build한 이미지를 k3s가 보지 못하게 된다.

게스트 재생성

파괴적 실험 후 초기화하는 경로다.

virsh destroy kc-lab-1
virsh undefine kc-lab-1                       # --remove-all-storage 는 시드 ISO 까지 지운다
virsh vol-delete --pool default kc-lab-1.qcow2

./deploy/lab/scripts/rebuild-seed.sh 1        # user-data 를 고쳤을 때만

virt-install --name kc-lab-1 --memory 3584 --vcpus 2 \
  --disk size=20,backing_store=/var/lib/libvirt/images/base.qcow2 \
  --disk vol=default/seed-kc-lab-1.iso,device=disk,bus=virtio,readonly=on \
  --network network=default,mac=52:54:00:aa:bb:11 \
  --import --os-variant debian12 --noautoconsole

시드는 virtio 디스크로 붙인다. virt-install --cloud-init은 시드를 SATA CD-ROM으로 붙이는데, Debian genericcloud 이미지는 크기를 줄이려고 물리 하드웨어 드라이버를 제외해서 AHCI 장치를 보지 못한다. 그러면 cloud-init이 데이터소스를 찾지 못하고 아무 오류도 남기지 않은 채 종료한다. 증상은 hostname이 localhost로 남고 SSH가 Permission denied (publickey)로 거부되는 것뿐이다.

게스트에 들어갈 수 없을 때는 화면을 직접 뜬다.

virsh screenshot kc-lab-1 /tmp/kc1.ppm    # 확장자와 무관하게 PNG 로 저장된다

localhost login:이면 cloud-init 미실행, kc-lab-1 login:이면 실행된 것이다.