From a831792c5c7afc7074025a4a03ed8bc12e147f20 Mon Sep 17 00:00:00 2001 From: DongHyeonka Date: Thu, 3 Sep 2026 14:18:08 +0900 Subject: [PATCH] docs: map deploy/ assets and record why unapplied configs are kept Co-Authored-By: Claude Opus 5 --- docs/session-lab-concepts.md | 154 +++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) diff --git a/docs/session-lab-concepts.md b/docs/session-lab-concepts.md index d4da975..2f568b4 100644 --- a/docs/session-lab-concepts.md +++ b/docs/session-lab-concepts.md @@ -2672,6 +2672,160 @@ pacman-key --list-keys | head --- +## 9층. `deploy/` — 무엇이 살아 있고 무엇이 참조인가 + +저장소에 있으나 지금 적용되지 않는 설정이 여럿이다. 죽은 코드가 아니라 +**의도적으로 남겨둔 참조 자산**이며, 그 구분을 여기 기록한다. + +### 전체 지도 + +| 경로 | 상태 | 대상 배포 형태 | 검증 | +|---|---|---|---| +| `lab/host/nginx-keycloak-lab.conf` | **적용 중** | 2노드 k3s 실험대 | `lab/scripts/verify-lab.sh` | +| `lab/cloud-init/kc-lab.yaml.example` | **적용 중**(템플릿) | 실험대 게스트 | 게스트 부팅 | +| `lab/k8s/echo.yaml` | **적용 중** | 실험대 | `kubectl apply` | +| `reverse-proxy/nginx-keycloak.conf` | 참조 | 단일 호스트 Compose | `scripts/verify-reverse-proxy-headers.sh` | +| `reverse-proxy/keycloak.env.example` | 참조 | 위와 한 쌍 | 동일 | +| `tls/nginx.conf` | 참조 | 단일 호스트, 운영자가 인증서 관리 | `scripts/verify-https-termination-config.sh` | +| `tls/Caddyfile` | 참조 | 단일 호스트, ACME 자동화 | 동일 | +| `tunnel/cloudflared-config.yml` | **미채택** | 공개 도메인 터널 | `scripts/verify-public-tunnel-config.sh` | + +**세 가지 상태** + +- **적용 중** — 지금 실험대에서 실제로 도는 설정 +- **참조** — 다른 배포 형태의 예제. 실행되지는 않지만 **문법·계약 검증은 받는다** +- **미채택** — 조건이 맞지 않아 고르지 않은 경로. 근거를 남기려고 보존한다 + +### 왜 적용하지 않는 것을 남겨두는가 + +**1. 이 저장소의 목적이 비교다.** 네 인증 패턴을 같은 인프라에서 비교하는 +학습 프로젝트이므로, **배포 형태도 선택지를 나란히 두고 트레이드오프를 +기록하는 것 자체가 산출물**이다. 하나만 남기면 "왜 이걸 골랐는가"의 근거가 +사라지고, 조건이 바뀌었을 때 재검토할 자료가 없어진다. + +**2. 죽은 코드가 아니라 테스트되는 코드다.** 각 파일에 대응하는 +`scripts/verify-*.sh`가 붙어 있다. + +``` +scripts/verify-reverse-proxy-headers.sh → deploy/reverse-proxy/ 두 파일의 계약 짝 +scripts/verify-https-termination-config.sh → deploy/tls/ 두 파일을 실제 이미지로 validate +scripts/verify-public-tunnel-config.sh → deploy/tunnel/ ingress 구조 +``` + +특히 두 번째는 임시 자체서명 인증서를 만들어 **nginx와 Caddy 두 벤더 이미지에서 +각각 설정을 검증하고** 임시 파일을 지운다. 실행되지 않을 뿐 **깨지면 드러난다.** + +**3. 배포 형태가 바뀌면 되살아난다.** 지금은 2노드 k3s지만 단일 호스트로 +옮기면 `reverse-proxy/`가 곧바로 쓰인다. 그래서 **실험대 전용 설정은 +`lab/` 아래로 분리**해 일반 배포 설정과 섞이지 않게 두었다. + +### `reverse-proxy/` — 1홉 계약의 원본 + +**`keycloak.env.example`** — Keycloak 쪽이 지켜야 할 네 줄이다. + +| 설정 | 의미 | 없거나 틀리면 | +|---|---|---| +| `KC_HTTP_ENABLED=true` | 프록시가 TLS를 끊었으므로 Keycloak은 평문 HTTP를 받는다 | 기동 거부 | +| `KC_PROXY_HEADERS=xforwarded` | **`X-Forwarded-*`를 신뢰하겠다는 명시적 옵트인** | 헤더를 통째로 무시한다 | +| `KC_HOSTNAME=https://auth.example.test` | 외부에서 보이는 주소를 고정 | 내부 주소가 `iss`에 박힌다 | +| `KC_HOSTNAME_STRICT=true` | Host 헤더를 믿지 않고 위 값만 쓴다 | Host 조작으로 흐름을 돌릴 여지 | + +**두 번째 줄이 이 실험대의 핵심 개념과 직결된다.** `/api/echo`에서 확인한 +Spring의 `forward-headers-strategy`와 **정확히 같은 성격의 스위치**다. +프레임워크는 기본적으로 forwarded 헤더를 믿지 않으며, 신뢰는 명시적으로 +켜야 한다. 켜지 않으면 프록시가 아무리 올바른 헤더를 넣어도 무시된다. + +**`nginx-keycloak.conf`** — 프록시 쪽 짝이다. **이것이 1홉을 가정한 원본**이며, +[`docs/reverse-proxy-headers.md`](reverse-proxy-headers.md)가 문서화한 계약이다. + +실험대의 `lab/host/nginx-keycloak-lab.conf`와 세 곳이 다르다. + +| | `reverse-proxy/` (원본) | `lab/host/` (실험대) | +|---|---|---| +| upstream | `keycloak:8080` 단일 | 노드 2개 (`.11`, `.12`) | +| TLS | 없음 (앞단이 따로 종료) | 여기서 종료 (Let's Encrypt) | +| `X-Forwarded-For` | `$proxy_add_x_forwarded_for` (덧붙이기) | `$remote_addr` (**덮어쓰기**) | + +세 번째 줄이 신뢰 경계의 차이다. 덧붙이면 클라이언트가 위조한 값이 +사슬 앞부분에 남고, 덮어쓰면 사라진다. **이 차이를 실측으로 확정하는 것이 +첫 실험의 목적이다.** + +### `tls/` — 같은 일을 하는 두 구현 + +`nginx.conf`와 `Caddyfile`은 **동일한 결과**를 만든다. 공개 443에서 TLS를 +종료하고 사설 네트워크의 `keycloak:8080`으로 평문 전달한다. + +``` + nginx Caddy + ssl_certificate …crt tls /etc/tls/tls.crt /etc/tls/tls.key + ssl_certificate_key …key + proxy_set_header Host $host header_up Host {host} + proxy_set_header X-Forwarded-* header_up X-Forwarded-* +``` + +**차이는 인증서 수명주기를 누가 관리하는가 하나뿐이다.** + +| | nginx | Caddy | +|---|---|---| +| 발급·갱신 | **운영자**가 담당 (certbot 등) | **프록시가 ACME로 자동** | +| 설정 분량 | 많다 | 적다 | +| 통제력 | 세밀 | 자동화에 위임 | + +**이 실험대는 nginx + certbot을 골랐다.** DNS-01 와일드카드가 필요했고, +인증서 발급 시점과 방식을 직접 통제해야 했기 때문이다. + +**둘을 동시에 진입점으로 띄우지 않는다.** 같은 443을 두 프로세스가 잡을 수 +없다. 예제가 둘인 것은 선택지를 보여주기 위해서다. + +### `tunnel/` — 채택하지 않은 이유를 남긴 자산 + +`cloudflared-config.yml`은 Cloudflare named tunnel 설정이다. +**아웃바운드 연결만 쓰므로 포트포워딩 없이 공개 HTTPS 이름을 얻는다.** +공유기를 건드릴 수 없는 환경에서 매력적인 선택지다. + +```yaml +ingress: + - hostname: auth.example.test + service: http://reverse-proxy:8080 # ← 127.0.0.1 이 아니다 + - service: http_status:404 # ← catch-all +``` + +- `service:`에 `127.0.0.1`을 쓰면 **cloudflared 컨테이너 자신**을 가리킨다. + Compose 서비스 DNS 이름을 써야 한다 +- 마지막 catch-all은 알 수 없는 hostname을 404로 끝낸다. 없으면 오류가 난다 + +**그런데 이 실험대는 채택하지 않았다.** 이유가 실험의 성격과 맞물린다. + +``` + 터널 사용 : 브라우저 → Cloudflare 엣지 → nginx → Traefik → Pod (3홉) + 현재 구성 : 브라우저 → nginx → Traefik → Pod (2홉) +``` + +**Cloudflare 엣지가 TLS를 끊고 다시 맺으면서 홉이 하나 늘고**, +`CF-Connecting-IP` 같은 자체 헤더가 섞인다. 이 실험대가 측정하려는 것이 +정확히 **`nginx → Traefik` 2홉의 forwarded 헤더 계약**이므로, +앞에 한 겹이 더 붙으면 **측정이 오염된다.** + +그래서 tailnet 직결을 택했다. 조건이 바뀌어(예: 다른 회선으로 이전) 공개 +접근이 필요해지면 이 파일이 그대로 쓰인다. + +### `.example` 접미사 관례 + +`keycloak.env.example`, `kc-lab.yaml.example`처럼 **비밀이 들어갈 자리가 있는 +파일은 `.example`로 커밋하고 실파일은 무시한다.** 저장소가 `.env.example`에 +쓰는 것과 같은 규칙이다. + +``` +.env.example → .env (gitignore) +deploy/lab/cloud-init/kc-lab.yaml.example → kc-lab-1.yaml, kc-lab-2.yaml (gitignore) +``` + +`kc-lab.yaml.example`이 감추는 것은 `plain_text_passwd`(콘솔 비상용 비밀번호)와 +SSH 공개키 두 줄이다. 공개키 자체는 비밀이 아니지만, **저장소가 공개이므로 +호스트 신원 정보를 불필요하게 노출하지 않는다.** + +--- + ## 아직 기록하지 않은 개념 실험 설계 단계에서 아래 항목을 이 문서에 추가한다.