The authorized client repository is AuthenticatedPrincipalOAuth2AuthorizedClientRepository, keyed by principal with no session id in it, which is the mechanism behind the sharing problem Q1 and Q3 describe. Sharing a store does not fix a lookup key. Five problems on the way in: only build output was committed under bff/, a duplicate YAML key broke the image build and was invisible until the full log was captured, env placeholders without defaults broke the tests, actuator was behind the login redirect so a 200 was the login page, and the 117KB beans response failed through the proxy. Deploying two replicas made the login itself fail before any experiment started, because the authorization request lives in per-instance memory and the callback lands elsewhere. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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
관측 대상은 넷이다.
X-Forwarded-For— Traefik이 덧붙이는가 덮어쓰는가X-Forwarded-Proto/-Host/-Port— 그대로 전달되는가- 위조 내성 — 클라이언트가 직접 넣은
X-Forwarded-*가 앱까지 도달하는가 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:이면 실행된 것이다.