342 lines
37 KiB
Markdown
342 lines
37 KiB
Markdown
---
|
|
title: branch / feature-keycloak-reverse-proxy-headers (P3B Keycloak reverse proxy 설정 — KC_PROXY_HEADERS + KC_HOSTNAME)
|
|
source_type: branch-note
|
|
status: raw
|
|
id: BR-KEYCLOAK-CHILD-2D084935
|
|
kind: branch-child
|
|
project: keycloak-patterns-overview
|
|
work_item: WI-KEYCLOAK-PATTERNS-OVERVIEW-020
|
|
inherits: [DEC-KEYCLOAK-PATTERNS-OVERVIEW-AUTH-TAXONOMY-001@1]
|
|
refines: []
|
|
overrides: []
|
|
depends_on: []
|
|
contract_packet: 1
|
|
branch: feature-keycloak-reverse-proxy-headers
|
|
parent_branch: feature-keycloak-patterns
|
|
related_projects: [keycloak-patterns]
|
|
tags: [branch, keycloak-patterns, p3b, reverse-proxy, keycloak-hostname, nginx, caddy]
|
|
created: 2026-05-25
|
|
target_merge:
|
|
status_label: in-progress
|
|
contract_packet_sha256: 87115ea4c7f2ee6851a0dd97401058806a1273783e4b25ece55613fbb67c8f9c
|
|
---
|
|
|
|
# branch: feature-keycloak-reverse-proxy-headers (P3B Keycloak reverse proxy 설정 — KC_PROXY_HEADERS + KC_HOSTNAME)
|
|
|
|
> Layer: `raw/branch-notes/` — [[raw/branch-notes/feature-keycloak-patterns]] governance Work Item의 child. P3B 의미 계약은 [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]]를 참조한다. **Cloudflare Tunnel / nginx / Caddy 뒤에 Keycloak이 위치할 때 redirect URL이 internal hostname(`keycloak:8080`)으로 떨어지는 함정** 해결.
|
|
> 본 sub-sub-branch는 **문서까지만** — 실 Keycloak 구동 / nginx config 적용은 진행하지 않음. 등급 `documented-only`.
|
|
> `status_label`: `in-progress`
|
|
|
|
<!-- section-id: branch-parent -->
|
|
## 부모 (필수)
|
|
|
|
[[raw/branch-notes/feature-keycloak-patterns]]
|
|
|
|
<!-- GENERATED: branch-contract:start -->
|
|
<!-- section-id: branch-contract-packet -->
|
|
## 브랜치 계약 패킷
|
|
|
|
- **생성 시 프로젝트 개정**: `1`
|
|
- **패킷 스키마**: `contract_packet: 1`
|
|
- **완료 조건**: project governance hub가 AP1~AP4 taxonomy와 child progress index를 유지한다
|
|
|
|
<!-- section-id: inherited-project-decisions -->
|
|
### 상속한 프로젝트 결정
|
|
|
|
| Decision Ref | Project Summary | Branch Application | Source |
|
|
|---|---|---|---|
|
|
| `DEC-KEYCLOAK-PATTERNS-OVERVIEW-AUTH-TAXONOMY-001@1` | canonical 분류축은 AP1~AP4 인증 통합 아키텍처와 cross-cutting 변형이다 | single-EC2 Google federation 변형의 reverse-proxy header 경계에 적용한다 | [[raw/project-notes/keycloak-patterns-overview]] |
|
|
|
|
<!-- section-id: branch-local-decisions -->
|
|
### 브랜치 지역 결정
|
|
|
|
> 기존 branch-local 결정은 아래 `## Decision Evidence Map`의 D-row가 소유하며 이 packet에서 복제하지 않는다.
|
|
|
|
| Decision ID | Decision | Relation | Supporting Claims | Status |
|
|
|---|---|---|---|---|
|
|
|
|
<!-- section-id: declared-overrides -->
|
|
### 선언한 예외
|
|
|
|
| Override ID | Overrides | Reason | Approval | Status |
|
|
|---|---|---|---|---|
|
|
|
|
없음.
|
|
<!-- GENERATED: branch-contract:end -->
|
|
|
|
<!-- section-id: branch-goal -->
|
|
## 목표
|
|
|
|
Keycloak이 reverse proxy 뒤에 있을 때 디폴트로는 `Host` 헤더와 `X-Forwarded-*` 헤더를 신뢰하지 않는다. 그 결과 issuer URL / authorization endpoint / token endpoint 등이 internal hostname(`http://keycloak:8080`)으로 발급되어 다음 함정이 발생한다:
|
|
- SPA가 받는 `iss` claim이 public URL이 아닌 internal URL → JWT 검증 실패
|
|
- Google이 redirect 받을 callback URL이 internal → Google이 도달 불가
|
|
- discovery document(`/.well-known/openid-configuration`)의 모든 endpoint가 internal URL
|
|
|
|
해결은 Keycloak에 **proxy 환경임을 명시** + **canonical public hostname을 강제** 하는 것.
|
|
|
|
면접에서 답해야 할 질문:
|
|
1. `KC_PROXY_HEADERS`와 `KC_HOSTNAME`의 차이는? → 전자는 proxy가 보낸 헤더를 신뢰할지(어떤 헤더 포맷인지), 후자는 issuer URL 강제 override.
|
|
2. `KC_HOSTNAME_STRICT`는 왜 필요한가? → 클라이언트가 보낸 Host 헤더로 issuer가 결정되는 디폴트 동작을 막아 issuer URL을 고정.
|
|
3. nginx vs Caddy 선택 기준은? → Caddy는 reverse_proxy directive가 X-Forwarded-* 자동 설정, nginx는 명시 필요.
|
|
|
|
- 이슈:
|
|
- PR:
|
|
|
|
<!-- section-id: branch-scope -->
|
|
## 범위
|
|
|
|
### 포함 범위
|
|
|
|
- Keycloak 환경변수: `KC_PROXY_HEADERS`, `KC_HOSTNAME`, `KC_HOSTNAME_STRICT`, `KC_HTTP_RELATIVE_PATH`, `KC_HTTP_ENABLED`, `KC_PROXY_TRUSTED_ADDRESSES`
|
|
- nginx config 예시 (X-Forwarded-For / X-Forwarded-Proto / Host 명시)
|
|
- Caddy config 예시 (reverse_proxy directive — X-Forwarded-* 자동)
|
|
- path-prefix 라우팅 (`/keycloak/*`) 시 `KC_HTTP_RELATIVE_PATH` 설정
|
|
- Cloudflare Tunnel origin이 HTTP일 때 X-Forwarded-Proto: https 주입 흐름
|
|
|
|
### 제외 범위
|
|
|
|
- 실 Keycloak realm / client / IdP 등록 (별도 sub-sub-branch `-6-3`)
|
|
- HTTPS termination 자체 (별도 sub-sub-branch `-6-4`)
|
|
- Apache HTTP Server 또는 HAProxy reverse proxy 옵션
|
|
- Keycloak admin console 보안 분리 (`KC_HOSTNAME_ADMIN`)
|
|
|
|
## 근거 (필수, 최소 1개+)
|
|
|
|
- [[raw/official-docs/keycloak-reverseproxy-official]] — Keycloak behind reverse proxy 공식
|
|
- [[raw/official-docs/keycloak-hostname-configuration]] — Keycloak hostname configuration 공식
|
|
|
|
## 관련 sub-branch
|
|
|
|
- [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]] (부모, P3B Single EC2 + Google federation)
|
|
- [[raw/branch-notes/feature-keycloak-public-domain-tunneling]] — 학습 환경 public 도메인 확보 (ngrok / Cloudflare Tunnel)
|
|
- [[raw/branch-notes/feature-keycloak-google-redirect-uri-policy]] — Google OAuth client 등록 + ngrok URL 변경 시 갱신
|
|
- [[raw/branch-notes/feature-keycloak-https-termination-caddy-nginx]] — HTTPS termination (Caddy vs nginx + Let's Encrypt)
|
|
|
|
## TODO
|
|
|
|
각 항목 옆에 증거 등급 표기.
|
|
|
|
- [ ] **`KC_PROXY_HEADERS` 모드 정리** — `xforwarded` (X-Forwarded-* 헤더 신뢰) vs `forwarded` (RFC 7239 Forwarded 헤더 신뢰) 차이 — 등급: `planned`
|
|
- [ ] **`KC_HOSTNAME=<public-domain>` 설정** — Keycloak이 발급하는 issuer / authorization / token URL을 이 값으로 고정 — 등급: `planned`
|
|
- [ ] **`KC_HOSTNAME_STRICT=true`** — 클라이언트 Host 헤더 무시, `KC_HOSTNAME` 값 강제 사용 — 등급: `planned`
|
|
- [ ] **`KC_HTTP_RELATIVE_PATH=/keycloak`** — path-prefix 라우팅 시 (nginx가 `/keycloak/*` → Keycloak 8080) — 등급: `planned`
|
|
- [ ] **`KC_HTTP_ENABLED=true` + `KC_PROXY_TRUSTED_ADDRESSES=127.0.0.1`** — reverse proxy가 HTTPS 종단 후 Keycloak에 HTTP forward, proxy header spoofing 방지 — 등급: `planned`
|
|
- [ ] **nginx config 예시 작성** — `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; X-Forwarded-Proto $scheme; Host $host;` — 등급: `planned`
|
|
- [ ] **Caddy config 예시 작성** — `reverse_proxy localhost:8080` (X-Forwarded-* 자동 설정 동작 검증) — 등급: `planned`
|
|
- [ ] **Cloudflare Tunnel + Keycloak 조합 시 헤더 흐름** — Cloudflare edge에서 X-Forwarded-Proto: https 자동 주입, origin은 HTTP로 받음 — 등급: `planned`
|
|
|
|
## 진행 중 메모
|
|
|
|
- Keycloak 25.x 기준 `--proxy <mode>` 옵션은 deprecated → `KC_PROXY_HEADERS=xforwarded|forwarded` 사용.
|
|
- `KC_HOSTNAME_STRICT_BACKCHANNEL` 옵션은 server-to-server 호출 시 internal hostname 사용 허용 여부. 단일 EC2 + Cloudflare Tunnel 조합에서는 `false` 유지 (모두 public hostname 통일).
|
|
- `KC_PROXY_TRUSTED_ADDRESSES`는 25.x에서 추가된 옵션. proxy header를 보낸 source IP를 화이트리스트화 → header spoofing 방지. 단일 EC2 nginx 시나리오는 `127.0.0.1`.
|
|
- Cloudflare Tunnel origin이 `http://localhost:8080`이면 edge에서 받은 HTTPS 정보는 `X-Forwarded-Proto: https` 헤더로 전달 → `KC_PROXY_HEADERS=xforwarded` 필요.
|
|
|
|
### nginx config 예시 초안
|
|
|
|
```
|
|
server {
|
|
listen 443 ssl;
|
|
server_name kc.example.com;
|
|
|
|
location /keycloak/ {
|
|
proxy_pass http://127.0.0.1:8080;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_set_header X-Forwarded-Host $host;
|
|
}
|
|
}
|
|
```
|
|
|
|
대응 Keycloak 환경변수:
|
|
- `KC_PROXY_HEADERS=xforwarded`
|
|
- `KC_HOSTNAME=https://kc.example.com`
|
|
- `KC_HOSTNAME_STRICT=true`
|
|
- `KC_HTTP_RELATIVE_PATH=/keycloak`
|
|
- `KC_HTTP_ENABLED=true`
|
|
- `KC_PROXY_TRUSTED_ADDRESSES=127.0.0.1`
|
|
|
|
### Caddy config 예시 초안
|
|
|
|
```
|
|
kc.example.com {
|
|
handle /keycloak/* {
|
|
reverse_proxy localhost:8080
|
|
}
|
|
}
|
|
```
|
|
|
|
method B(`KC_HTTP_RELATIVE_PATH=/keycloak`)에서는 `handle`이 `/keycloak` prefix를 보존하도록 구성한다. `handle_path`는 prefix를 strip하므로 이 실행 예시에 사용하지 않는다. Caddy가 자동 생성하는 X-Forwarded-* 헤더의 정확한 집합은 별도 실측 대상이다.
|
|
|
|
## 결정 사항 (decisions)
|
|
|
|
- **2026-05-25**: P3B는 `KC_PROXY_HEADERS=xforwarded` 채택. 이유: nginx / Caddy / Cloudflare Tunnel 모두 X-Forwarded-* 헤더가 디폴트 (RFC 7239 Forwarded 헤더는 덜 보편적).
|
|
- **2026-05-25**: `KC_HOSTNAME_STRICT=true` 강제. 이유: 클라이언트 Host 헤더에 의존하면 multi-host 시나리오에서 issuer URL이 갈리고, Google brokering callback URL exact match 정책과 충돌.
|
|
- **2026-05-25**: path-prefix 라우팅(`/keycloak/*`) 채택. 이유: 단일 EC2에 SPA(`/`) + API(`/api/*`) + Keycloak(`/keycloak/*`)을 한 도메인에 묶기 위함. 부모 P3B 다이어그램과 일치.
|
|
- **2026-05-25**: 학습 단계는 **Caddy 우선** (1줄 config + Let's Encrypt 자동). nginx는 운영 환경 비교 대상으로만 기재. 사유 상세는 sub-sub-branch `-6-4`에서 추가 논의.
|
|
- **2026-05-25**: 본 sub-sub-branch 전체 등급 `documented-only`. 실 Keycloak 구동 / 환경변수 적용 / nginx 또는 Caddy 동작 검증은 P3A 완료 후 선택적 확장 시점에 재검토.
|
|
|
|
## 결정-근거 매핑
|
|
|
|
> 각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시. Keycloak vendor doc 으로 직접 뒷받침되는 결정 (D1, D2, D3, D6) 과 운영 환경 비교 / scoping 결정 (D4, D5) 을 분리.
|
|
> `선택 조건` 열(R2)은 "이 조건일 때 이 결정, 다른 조건이면 어떤 대안" — 분기 없으면 `N/A`. (2026-07-18 `/branch-spec`: 기존 D1~D7 의 Decision / Supporting Claims / Evidence Strength / Open Risk 셀은 verbatim 보존하고 `선택 조건` 열만 신규 추가. D2·D4·D6·D7 은 다른 owner 브랜치에 위임되는 관심사를 선택 조건 셀에 명시 — 상세는 §Audit & Findings.)
|
|
|
|
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|
|
|---|---|---|---|---|---|
|
|
| D1 | P3B 는 `KC_PROXY_HEADERS=xforwarded` 채택 (nginx / Caddy / Cloudflare Tunnel 모두 X-Forwarded-* 가 디폴트, RFC 7239 Forwarded 는 덜 보편적) | proxy 가 `X-Forwarded-*` 를 emit 할 때 (nginx / Caddy / Cloudflare Tunnel). **대안 `forwarded`**: proxy 가 RFC 7239 표준 `Forwarded` 헤더를 emit 할 때 (`KC-RP-C1` — 덜 보편적). **미설정은 선택지 아님**: reverse proxy 없이 직결일 때만 유효한데 P3B 는 항상 proxy 뒤 | `raw/official-docs/keycloak-reverseproxy-official.md#KC-RP-C2` (xforwarded 가 X-Forwarded-For/Proto/Host/Port/Prefix 파싱), `raw/official-docs/keycloak-reverseproxy-official.md#KC-RP-C1` (forwarded 가 RFC 7239 파싱 — 비교 baseline) | `official-vendor-doc` (Keycloak 공식이 두 옵션 모두 명시) | 헤더 파싱 활성화만으로 spoofing 방어 안 됨 (`KC-RP-C2` does-not-prove) — `KC_PROXY_TRUSTED_ADDRESSES` 별도 필수 |
|
|
| D2 | `KC_HOSTNAME_STRICT=true` 강제 (클라이언트 Host 헤더 무시, issuer URL 고정) | production / multi-host 시나리오 항상 (`KC-HOST-C5` 기본 true). **대안 (strict 완화)**: reverse proxy 가 Host header 를 overwrite 하는 경우만 예외 (`KC-HOST-C5` 예외 절). ⚠️ **`KC_HOSTNAME` 값 결정의 owner 는 [[raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch]]** — 본 branch 는 proxy-headers 와 hostname-strict 의 *상호작용*만 소유 | `raw/official-docs/keycloak-hostname-configuration.md#KC-HOST-C2` (hostname 옵션 의무화 + dynamic URL resolution 차단), `raw/official-docs/keycloak-hostname-configuration.md#KC-HOST-C3` (fraudulent issuer 방지 보안 목적), `raw/official-docs/keycloak-hostname-configuration.md#KC-HOST-C5` (`hostname-strict` 기본 true, production 항상 true 권장) | `official-vendor-doc` (Keycloak hostname-v2 공식이 strict 강제를 보안 목적으로 명시) | reverse proxy 가 Host header 를 overwrite 하는 경우의 예외 처리 (`KC-HOST-C5` 의 예외 절 — "unless your reverse proxy overwrites the Host header") 가 nginx/Caddy 각각의 default 동작과 일치하는지 별도 검증 |
|
|
| D3 | path-prefix 라우팅 (`/keycloak/*`) 채택 + `KC_HTTP_RELATIVE_PATH=/keycloak` 설정 — SPA(`/`) + API(`/api/*`) + Keycloak(`/keycloak/*`) 한 도메인 묶기 | 한 도메인에 SPA + API + Keycloak 를 subpath 로 묶을 때 **method B (Keycloak `http-relative-path`)**. **대안 method A**: proxy 가 `X-Forwarded-Prefix` 헤더 주입 (`KC-RP-C6` — Keycloak 은 context path 무변경). **subpath 불요**: Keycloak 전용 서브도메인(`kc.example.com/`)이면 relative-path 자체가 불필요 | `raw/official-docs/keycloak-reverseproxy-official.md#KC-RP-C6` (subpath 노출 방법 — proxy 의 `X-Forwarded-Prefix` 또는 Keycloak `http-relative-path` 둘 중 선택) | `official-vendor-doc` (subpath 노출 공식 옵션 2종 명시) | 두 방법 (A: proxy prefix 주입 vs B: Keycloak relative path) 의 정확한 trade-off (admin console URL, OIDC discovery 경로 영향) 본 인용 부분만 (`KC-RP-C6` does-not-prove) — admin console URL 변경 함정 별도 검증 |
|
|
| D4 | 학습 단계는 Caddy 우선 (1줄 config + Let's Encrypt 자동), nginx 는 운영 환경 비교 대상 | 학습 단계 (config 단순성 우선). **대안 nginx**: 운영 / 기존 nginx 스택 재사용 시. ⚠️ **HTTPS termination + Caddy vs nginx 선택의 owner 는 [[raw/branch-notes/feature-keycloak-https-termination-caddy-nginx]]** — 본 branch 는 그 선택에 delegate (proxy 가 emit 하는 헤더 계약만 소유) | UNSUPPORTED_DECISION (Caddy `reverse_proxy` directive 의 X-Forwarded-* 자동 설정 동작에 대한 공식 vendor doc raw 보존 부재 — 자체 메모) | UNSUPPORTED_DECISION | Caddy 가 emit 하는 X-Forwarded-* 헤더 셋이 Keycloak `xforwarded` 파싱 기대치 (5종 헤더) 와 정확히 일치하는지 미검증 |
|
|
| D5 | 본 sub-sub 전체 등급 `documented-only` (실 Keycloak 구동 / 환경변수 적용 / nginx 또는 Caddy 동작 검증은 P3A 완료 후) | N/A (organizational scoping — 분기 없음). P3A 완료 후 선택적 확장 시점에 실 구동 등급으로 재검토 | UNSUPPORTED_DECISION (학습 단계 scoping — 외부 vendor 인용 불요) | N/A (organizational decision) | 환경변수 조합의 실제 동작 (특히 `KC_HTTP_ENABLED=true` 누락 시 부팅 실패) 이 문서상의 가정과 어긋날 수 있음 |
|
|
| D6 | `KC_HTTP_ENABLED=true` + `KC_PROXY_TRUSTED_ADDRESSES=127.0.0.1` 채택 (reverse proxy HTTPS 종단 후 Keycloak 에 HTTP forward + proxy header spoofing 방지) | TLS edge termination(proxy 가 HTTPS 종단) 시 `KC_HTTP_ENABLED=true` 필수 (`KC-RP-C4`). **대안 (http-enabled 불요)**: TLS passthrough 모드. ⚠️ **`KC_PROXY_TRUSTED_ADDRESSES`(proxy-header spoofing 방어)의 owner 는 [[raw/branch-notes/feature-keycloak-header-spoofing-defense]] D6** — source doc `keycloak-reverseproxy-official.md` Parent 표가 그 branch 를 근거 소유자로 지정. 본 branch 는 `KC_HTTP_ENABLED`(TLS-edge 결과)만 소유, trusted-addresses 는 §Audit A1 로 위임 | `raw/official-docs/keycloak-reverseproxy-official.md#KC-RP-C4` (TLS edge termination 시 `http-enabled` 필수), `raw/official-docs/keycloak-reverseproxy-official.md#KC-RP-C5` (`--proxy-trusted-addresses=192.168.0.32,127.0.0.0/8` 예시), `raw/official-docs/keycloak-reverseproxy-official.md#KC-RP-C3` (header spoofing 공식 경고) | `official-vendor-doc` (Keycloak 공식이 3개 항목 모두 verbatim 명시) | TLS passthrough 모드 (단일 EC2 에서 향후 변경 가능성) 에서는 `http-enabled` 불필요 — 본 인용 범위 밖 (`KC-RP-C4` does-not-prove) |
|
|
| D7 | `KC_HOSTNAME=https://kc.example.com` (full URL with `https://` prefix) — scheme 없으면 일부 endpoint 가 http 로 발급 | `hostname-backchannel-dynamic=true` 시 full URL 필수 (`KC-HOST-C4`). **backchannel-dynamic=false(단일 EC2)** 에서 `https://` prefix 강제 여부는 **미검증** → §구현 가이드 `UNSUPPORTED_IMPL_DECISION`. ⚠️ hostname 값 owner 는 [[raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch]] | `raw/official-docs/keycloak-hostname-configuration.md#KC-HOST-C4` (`hostname-backchannel-dynamic=true` 시 hostname 옵션은 full URL 로 지정해야 함) | `official-vendor-doc` (조건부 — `hostname-backchannel-dynamic=true` 시) | full URL 의 정확한 schema/port 처리 (`https://` 의무 여부) 본 인용 범위 밖 (`KC-HOST-C4` does-not-prove). `hostname-backchannel-dynamic=false` 인 단일 EC2 에서도 `https://` prefix 가 강제되는지 미검증 |
|
|
|
|
## 구현 가이드
|
|
|
|
> 본 sub-sub 는 `documented-only`(D5) — 여기서 "구현"은 각 환경변수·proxy 헤더 설정의 **구성 레시피**(다음 구현자가 되묻지 않고 config 를 작성할 수준)를 뜻한다. 본 branch 의 in-scope 결정(D1 proxy-header 파싱 모드 · D3 subpath 노출 · D6 의 `KC_HTTP_ENABLED` · D7 hostname full-URL)에서 도출되는 detail 만 적고, 각 cell 을 Decision ID + Supporting Claim ID 로 trace 한다.
|
|
> **OUT_OF_BRANCH_SCOPE 정제(R3, CLAUDE.md §15.5)**: (1) HTTPS termination 자체(Caddy vs nginx 선택, cert 발급/갱신)는 [[raw/branch-notes/feature-keycloak-https-termination-caddy-nginx]] 소유 — 본 § 은 proxy 가 **emit 하는 헤더 계약**만 다루고 TLS 설정 라인은 남기지 않는다. (2) `KC_PROXY_TRUSTED_ADDRESSES`(spoofing 방어)는 [[raw/branch-notes/feature-keycloak-header-spoofing-defense]] D6 소유(§Audit A1) — 본 § 은 `KC_HTTP_ENABLED` 만. (3) `KC_HOSTNAME` **값** 결정과 `iss` 검증은 [[raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch]] 소유 — 본 § 은 proxy-headers 와 hostname 의 *상호작용*만.
|
|
|
|
### 0. 환경변수 ↔ 결정 ↔ 소유 매핑 (요약)
|
|
|
|
> In-scope #1(환경변수 정리)의 종결 표. 각 KC_* 키가 어느 결정에서 나오고, 본 branch 소유인지 위임인지 한눈에.
|
|
|
|
| 환경변수 | 값 (P3B) | Decision | 근거 Claim | 소유 |
|
|
|---|---|---|---|---|
|
|
| `KC_PROXY_HEADERS` | `xforwarded` | D1 | `KC-RP-C2` | **본 branch (owner)** |
|
|
| `KC_HTTP_RELATIVE_PATH` | `/keycloak` | D3 | `KC-RP-C6` (method B) | **본 branch (owner)** |
|
|
| `KC_HTTP_ENABLED` | `true` | D6 | `KC-RP-C4` | **본 branch (owner)** |
|
|
| `KC_HOSTNAME` | `https://kc.example.com` | D7 | `KC-HOST-C4` (조건부) | **값 = iss-claim-hostname-mismatch**, 본 branch 는 scheme/proxy 상호작용만 |
|
|
| `KC_HOSTNAME_STRICT` | `true` | D2 | `KC-HOST-C5` | **값 = iss-claim-hostname-mismatch**, 본 branch 는 proxy 예외절 검증 |
|
|
| `KC_PROXY_TRUSTED_ADDRESSES` | `127.0.0.1` | D6 | `KC-RP-C5` | **header-spoofing-defense D6 (위임, §Audit A1)** |
|
|
|
|
### 1. `KC_PROXY_HEADERS=xforwarded` — 신뢰할 헤더 5종 계약 (D1)
|
|
|
|
> **Trace**: D1 / `keycloak-reverseproxy-official#KC-RP-C2`(xforwarded 가 `X-Forwarded-For`/`-Proto`/`-Host`/`-Port`/`-Prefix` 5종 파싱), `#KC-RP-C1`(forwarded=RFC 7239 — 비교 baseline). proxy 측 헤더 주입은 §진행 중 메모의 nginx/Caddy config 초안이 실체.
|
|
>
|
|
> - **UNSUPPORTED_IMPL_DECISION**: (a) **Caddy `reverse_proxy` 가 자동으로 emit 하는 X-Forwarded-* 헤더 셋**이 Keycloak `xforwarded` 파싱 기대치(5종)와 정확히 일치하는지 — Caddy 공식 vendor doc raw 미보존(D4 UNSUPPORTED). trade-off: nginx 는 `proxy_set_header` 로 5종을 **명시**하므로 결정론적이나, Caddy 는 "자동" 이 5종 전체를 포함한다는 근거가 본 repo 에 없음 → §Claims To Verify 로 실측 위임. (b) `X-Forwarded-Port` / `X-Forwarded-Prefix` 를 nginx config 초안이 **누락** — 5종 중 3종(For/Proto/Host)만 명시. Port/Prefix 누락 시 Keycloak 이 기본 port/무-prefix 로 추정하는지 미검증.
|
|
|
|
| 헤더 | nginx (명시 필요) | Caddy (자동 주장) | Keycloak 소비처 |
|
|
|---|---|---|---|
|
|
| `X-Forwarded-For` | `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;` | 자동 | client IP (access log, trusted-addresses 판정) |
|
|
| `X-Forwarded-Proto` | `proxy_set_header X-Forwarded-Proto $scheme;` | 자동 | issuer scheme (https 강제의 핵심) |
|
|
| `X-Forwarded-Host` | `proxy_set_header X-Forwarded-Host $host;` | 자동 | issuer host (`KC_HOSTNAME_STRICT=true` 면 KC_HOSTNAME 이 우선) |
|
|
| `X-Forwarded-Port` | ⚠️ nginx 초안 누락 — `proxy_set_header X-Forwarded-Port $server_port;` 추가 권장 | 자동 | issuer port |
|
|
| `X-Forwarded-Prefix` | method A 채택 시만 (D3 은 method B 라 불요) | method A 채택 시만 | subpath (D3 은 relative-path 로 대체) |
|
|
|
|
### 2. Subpath 노출 — method B(`KC_HTTP_RELATIVE_PATH`) 채택 (D3)
|
|
|
|
> **Trace**: D3 / `keycloak-reverseproxy-official#KC-RP-C6`(subpath 노출 2방법: A=proxy `X-Forwarded-Prefix` 주입, B=Keycloak `http-relative-path`). 본 branch 는 **B** 채택 — SPA(`/`)+API(`/api/*`)+KC(`/keycloak/*`) 를 한 도메인에 묶는 부모 P3B 다이어그램과 정합.
|
|
>
|
|
> - **UNSUPPORTED_IMPL_DECISION**: method A vs B 의 정확한 trade-off(admin console URL 변경, OIDC discovery 경로 영향)는 `KC-RP-C6` 이 "두 방법 존재" 만 증명하고 detail 은 does-not-prove. **B 채택 근거는 사용자 trade-off**: relative-path 는 Keycloak 이 스스로 모든 endpoint 를 `/keycloak/*` 로 발급하므로 proxy 가 prefix 를 매 요청 rewrite 할 필요가 없어 단순 — 단, admin console 도 `/keycloak/admin` 으로 이동하는 부작용(아래 표)을 감수.
|
|
|
|
| 항목 | method B (채택) | method A (대안) |
|
|
|---|---|---|
|
|
| Keycloak 설정 | `KC_HTTP_RELATIVE_PATH=/keycloak` | 무변경 (context path `/`) |
|
|
| proxy 설정 | `location /keycloak/ { proxy_pass http://127.0.0.1:8080; }` (path 그대로 전달) | `X-Forwarded-Prefix: /keycloak` 주입 + `xforwarded` |
|
|
| admin console URL | `/keycloak/admin` 으로 **이동** (함정 — §엣지) | `/admin` 유지 |
|
|
| OIDC discovery | `/keycloak/realms/{realm}/.well-known/openid-configuration` | 동일(prefix 는 forwarded) |
|
|
| broker endpoint (Google) | `https://kc.example.com/keycloak/realms/{realm}/broker/google/endpoint` | 동일 — Google Console 등록 URL owner=[[raw/branch-notes/feature-keycloak-google-redirect-uri-policy]] |
|
|
|
|
> ⚠️ **method B ↔ proxy 라우팅 정합 함정 (depth 감사 2026-07-18 F1, 초안 수정 완료)**: 과거 초안의 `handle_path /keycloak/*`는 prefix를 strip해 method B와 충돌할 수 있으므로 위 copyable 예시를 `handle /keycloak/*` + `reverse_proxy`로 교정했다. nginx `location /keycloak/ { proxy_pass http://127.0.0.1:8080; }`와 마찬가지로 `/keycloak` prefix를 origin까지 보존하는 것이 이 문서의 deploy invariant다. 실제 Caddy route와 discovery 200 여부는 vendor raw 미보존 때문에 §Claims To Verify에서 확인한다.
|
|
|
|
### 3. `KC_HTTP_ENABLED=true` + hostname full-URL 상호작용 (D6 부분 · D7)
|
|
|
|
> **Trace**: D6 / `keycloak-reverseproxy-official#KC-RP-C4`(TLS edge termination 시 `http-enabled` 필수). D7 / `keycloak-hostname-configuration#KC-HOST-C4`(backchannel-dynamic=true 시 full URL 요구). proxy 가 HTTPS 를 종단하고 Keycloak `:8080` 에 **HTTP** forward 하는 것이 전제.
|
|
>
|
|
> - **UNSUPPORTED_IMPL_DECISION**: `KC_HOSTNAME=https://kc.example.com` 의 **`https://` prefix 강제 여부** — `KC-HOST-C4` 는 `backchannel-dynamic=true` 조건에서만 full URL 을 요구한다. 단일 EC2 는 `backchannel-dynamic=false` 이므로 hostname-only(`kc.example.com`)로 충분한지 vs scheme 을 붙여야 일부 endpoint 가 http 로 새지 않는지 **미확정**. trade-off: 사용자 메모는 "scheme 없으면 일부 endpoint 가 http 로 발급되는 사례 보고" 라 항상 `https://` 를 붙이는 보수적 선택 — vendor 직접 근거 없음(§Claims To Verify).
|
|
|
|
| 항목 | 명세 | 근거 |
|
|
|---|---|---|
|
|
| `KC_HTTP_ENABLED` | `true` — proxy 가 HTTPS 종단 후 Keycloak 은 HTTP 로 수신 | `KC-RP-C4` (edge termination 시 필수) |
|
|
| `KC_HOSTNAME` scheme | `https://` prefix 포함 (보수적 — issuer/discovery 를 https 로 고정) | `KC-HOST-C4` (조건부) + UNSUPPORTED_IMPL |
|
|
| `X-Forwarded-Proto` 와의 관계 | proxy 가 `X-Forwarded-Proto: https` 주입 → Keycloak 이 http 수신에도 issuer 를 https 로 발급 | `KC-RP-C2` (proto 파싱) |
|
|
| TLS 종단 위치 | proxy(nginx/Caddy/Cloudflare edge) — **본 branch 미소유**, [[raw/branch-notes/feature-keycloak-https-termination-caddy-nginx]] 참조 | R3 위임 |
|
|
|
|
## 엣지·실패·의존
|
|
|
|
> R4(깊이 게이트) 캡처용. 정상 경로 외에 *구현 중 부딪힐* 실패/엣지/다른 계약 의존을 미리 열거. 본 branch 는 `documented-only` 라 대부분 "실 적용 시 예상 함정" 이나, 실패 지점을 미리 명명해 둔다.
|
|
|
|
- **실패·엣지 경로**:
|
|
- **`KC_HTTP_ENABLED=true` 누락 → 부팅 실패** (D6): proxy 가 HTTPS 를 edge termination 하고 Keycloak 에 HTTP forward 하는데 `http-enabled` 가 꺼져 있으면 production mode 는 HTTPS 를 강제해 부팅이 실패(`KC-RP-C4` 가 "필수" 명시). 단 "부팅 실패" 자체의 정확한 동작은 does-not-prove → §Claims To Verify.
|
|
- **`KC_HTTP_RELATIVE_PATH` 변경 → admin console URL 동반 이동** (D3): `/keycloak` 설정 시 admin console 이 `/admin` → `/keycloak/admin` 으로 이동. 기존 북마크/자동화 스크립트가 `/admin` 을 하드코딩하면 404. 기대 동작: 모든 관리 접근을 `/keycloak/admin` 으로 통일.
|
|
- **Caddy X-Forwarded-* 헤더 셋 불일치** (D1/D4): Caddy `reverse_proxy` 가 자동 emit 하는 헤더가 Keycloak `xforwarded` 기대 5종과 다르면(예: `X-Forwarded-Port` 누락) issuer port 가 틀어질 수 있음. Caddy vendor doc 미보존이라 실측 전엔 확정 불가(D4 UNSUPPORTED).
|
|
- **nginx 초안의 `X-Forwarded-Port`/`X-Forwarded-Prefix` 누락** (D1): §진행 중 메모의 nginx config 는 For/Proto/Host 3종만 명시 — 5종 중 2종 누락. Keycloak 이 기본값으로 추정하는지, issuer port 가 틀어지는지 미검증.
|
|
- **`KC_HOSTNAME` scheme 누락 → 일부 endpoint http 발급** (D7): `kc.example.com`(scheme 없음)으로 설정 시 일부 endpoint 가 http 로 발급되는 사례 보고(사용자 메모) → 항상 `https://` prefix. vendor 직접 근거 없음.
|
|
- **subpath + OIDC discovery 경로 변화** (D3): `/keycloak/` subpath 하에서 discovery 의 모든 endpoint URL 이 `/keycloak/realms/.../` prefix 를 가져야 함. 하나라도 prefix 없이 발급되면 SPA/RS 가 endpoint 를 못 찾음. `KC-RP-C6` does-not-prove "OIDC discovery 경로 영향".
|
|
|
|
- **다른 계약 의존** (대상 브랜치 + Decision ID 병기):
|
|
- [[raw/branch-notes/feature-keycloak-header-spoofing-defense]] **D6** — ⚠️ **RESTATED_FOREIGN_DECISION**. `KC_PROXY_TRUSTED_ADDRESSES=127.0.0.1`(proxy-header spoofing 방어)의 owner 는 그 branch(source doc `keycloak-reverseproxy-official.md` Parent 표가 지정). 본 branch D6 은 이 값을 재진술 → §Audit A1. 본 branch 의 `KC_PROXY_HEADERS=xforwarded`(D1) 은 헤더 **파싱만** 켜므로(`KC-RP-C2` does-not-prove spoofing 방어) trusted-addresses 없이는 spoofing 에 취약 — 두 계약이 **짝**으로만 안전.
|
|
- [[raw/branch-notes/feature-keycloak-https-termination-caddy-nginx]] — `KC_HTTP_ENABLED=true`(D6)의 전제인 **TLS edge termination** 이 그 branch 소유. Caddy vs nginx 선택(D4)도 그 branch 가 owner — 본 branch 는 delegate. 그 branch 가 TLS passthrough 로 바꾸면 본 branch D6 의 `http-enabled` 전제가 무너짐.
|
|
- [[raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch]] — `KC_HOSTNAME` **값** 과 `iss` 검증의 owner. 본 branch D2/D7 은 그 값에 proxy-headers 가 어떻게 상호작용하는지(strict=true 하에서 issuer 결정 우선순위)만 다룬다. 그 branch 가 hostname 값을 바꾸면 broker endpoint URL(아래) 도 연동 변경.
|
|
- [[raw/branch-notes/feature-keycloak-google-redirect-uri-policy]] — D3 의 `/keycloak/` subpath 가 broker endpoint URL(`https://kc.example.com/keycloak/realms/{realm}/broker/google/endpoint`)을 결정 → Google Console authorized redirect URI 에 **subpath 포함** 필수. subpath 를 빼고 등록하면 Google federation redirect 실패. redirect URI 등록 정책은 그 branch 소유.
|
|
- [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]] **(부모)** — 본 sub-sub 는 그 배포의 **proxy-header 계약**(nginx/Caddy → Keycloak 8080, KC_HOSTNAME/relative-path)을 채우는 역할. 부모 다이어그램의 단일 도메인 subpath 배치가 D3 의 전제.
|
|
|
|
## 검증해야 할 주장
|
|
|
|
> 공식 vendor doc 이 옵션의 존재와 형식을 증명해도 단일 EC2 + Cloudflare Tunnel 조합에서의 실제 동작은 별개. 다음은 P3A 또는 실 Keycloak 구동 시 실측 필요.
|
|
|
|
| Claim | Why uncertain | How to verify | Status |
|
|
|---|---|---|---|
|
|
| `KC_HOSTNAME=https://kc.example.com` 의 scheme prefix 가 단일 EC2 (`hostname-backchannel-dynamic=false`) 에서도 강제 필요한지 | `KC-HOST-C4` 의 조건절 ("If set to true, hostname option needs to be specified as a full URL") 만 명시 — false 시의 형식 강제 미확인 | scheme 없이 `KC_HOSTNAME=kc.example.com` 으로 부팅 시도 후 issuer URL 의 scheme 확인 | `needs-confirmation` |
|
|
| `KC_HTTP_RELATIVE_PATH=/keycloak` 변경 후 admin console URL 이 `/keycloak/admin` 으로 변경되는 동작 | `KC-RP-C6` does-not-prove "admin console URL 변경 함정" | `/admin` vs `/keycloak/admin` 양쪽 접근 후 응답 확인 | `planned` |
|
|
| Cloudflare Tunnel origin 이 HTTP 인데 `KC_HTTP_ENABLED=true` 누락 시 Keycloak 부팅 실패 (production mode 는 HTTPS 강제 디폴트) | 본 사용자 메모의 추론 — vendor 공식이 부팅 실패 자체를 명시했는지 verbatim 미확인 | `KC_HTTP_ENABLED` 미설정 + edge HTTP 환경에서 부팅 시도 후 에러 메시지 캡처 | `needs-confirmation` |
|
|
| `KC_PROXY_TRUSTED_ADDRESSES=127.0.0.1` 로 충분한지 (loopback 만 신뢰 ⇒ 같은 호스트 nginx 만 헤더 신뢰) | `KC-RP-C5` does-not-prove "화이트리스트 외 IP 의 정확한 동작" | 외부 IP 에서 `X-Forwarded-For` 위조 요청 후 Keycloak access log 의 client IP 확인 | `needs-confirmation` |
|
|
| Caddy `reverse_proxy localhost:8080` 가 자동 설정하는 X-Forwarded-* 헤더 셋이 Keycloak `xforwarded` 파싱 기대치 (5종) 와 일치 | `D4` UNSUPPORTED — Caddy 공식 vendor doc raw 보존 부재 | Caddy 뒤에 echo 서버 띄워 X-Forwarded-* 헤더 명세 확인 후 Keycloak 파싱 동작과 비교 | `planned` |
|
|
| (과거 초안 회귀 방지) 폐기된 `handle_path /keycloak/*`와 현행 `handle`+`reverse_proxy`가 method B에서 실제로 다른 결과를 내는지 | Caddy 공식 vendor doc raw 미보존(D4 UNSUPPORTED). copyable 설정은 이미 prefix 보존형으로 교정했지만 runtime 검증은 아직 없음 | 현행 `handle` config로 `/keycloak/realms/{realm}/.well-known/openid-configuration` 200과 endpoint prefix를 확인. 비교 실험이 필요할 때만 폐기된 `handle_path`를 별도 negative case로 실행 | `needs-confirmation` |
|
|
| `/keycloak/` subpath + `KC_HTTP_RELATIVE_PATH` 조합에서 OIDC discovery (`.well-known/openid-configuration`) endpoint 경로 변화 | `KC-RP-C6` does-not-prove "OIDC discovery 경로 영향" | discovery endpoint 호출 후 모든 endpoint URL prefix 검증 (`/keycloak/realms/.../auth` 등) | `needs-confirmation` |
|
|
| Google OAuth client redirect URI 가 `https://kc.example.com/keycloak/realms/{realm}/broker/google/endpoint` 와 exact match 일 때만 동작 | 본 사용자 메모의 추론 — Google 공식 vendor doc raw 보존 부재 | `/keycloak/` 없는 URL 로 Google client 등록 후 federation 시도 → redirect 실패 확인 | `planned` |
|
|
| `KC_HOSTNAME_STRICT_BACKCHANNEL=false` 유지가 단일 EC2 + Cloudflare Tunnel 조합에서 server-to-server 호출에 문제 없는지 | 본 사용자 메모의 추론 — vendor 인용 부재 | Keycloak 가 IdP discovery / token 발급 시 internal vs public hostname 사용 여부 wireshark 로 추적 | `planned` |
|
|
|
|
## Audit & Findings (2026-07-18 `/branch-spec` 정합 감사)
|
|
|
|
> 본 § 는 채움 중 발견한 **결정으로 흡수되지 않은 정합 문제·위임 권고**만 보존 (CLAUDE.md §15.5 R3 — 이관/위임 history 는 별도 § 에). 자동 rewrite 안 함 — 타 branch 결정 영역은 *정합 권고만*.
|
|
|
|
| ID | 유형 | 발견 | 조치 |
|
|
|---|---|---|---|
|
|
| **A1** | `RESTATED_FOREIGN_DECISION` (**미해소 — `/sync` owner 확정 권고**) | 본 branch **D6** 이 `KC_PROXY_TRUSTED_ADDRESSES=127.0.0.1` 를 결정하는데, 같은 값을 [[raw/branch-notes/feature-keycloak-header-spoofing-defense]] **D6** 도 결정한다("Keycloak reverse proxy 환경에서 `KC_PROXY_TRUSTED_ADDRESSES` 화이트리스트로 proxy header 송신 IP 제한 — 단일 EC2 = `127.0.0.1`"). source doc `keycloak-reverseproxy-official.md` 의 Parent 표(L28)가 **header-spoofing-defense 를 "`KC_PROXY_TRUSTED_ADDRESSES` 화이트리스트 도입 근거"의 소유 branch 로 지정** → spoofing 방어 관심사의 owner 는 그 branch. 본 branch 는 proxy-header **파싱 모드**(D1)의 owner 이지 spoofing 방어의 owner 가 아님 | **자동 rewrite 안 함**(D6 은 사용자 작성 결정). **2026-07-18 조치**: D6 의 신규 `선택 조건` 셀 + §구현 가이드 §0/§3(R3) + §엣지·실패·의존에 "trusted-addresses = header-spoofing-defense D6 위임, 본 branch 는 `KC_HTTP_ENABLED` 만 소유" 를 명시해 *노트가 spoofing owner 를 자처하는 상태*를 제거. **잔여 사용자 결정**: `/sync` 로 "proxy trusted-addresses" owner 를 header-spoofing-defense 로 확정하고, 본 branch D6 을 그 결정의 *reference-only 소비*(값 재진술 제거)로 격하할지 판단 |
|
|
| **A2** | `BACKREF_INTEGRITY` (해소됨 — 이번 세션 hook 알림 대응) | 이번 세션의 Decision Evidence Map 수정(선택 조건 열 추가)에 대해 consistency hook 이 본 노트 D1·D6 을 참조하는 문서 2건을 비차단 알림: [[raw/branch-notes/feature-keycloak-header-spoofing-defense]] L206 → D6, [[raw/branch-notes/feature-keycloak-https-termination-caddy-nginx]] L159·L232 → D1 | **전파 불요 확인**: D1·D6 의 `Decision`/`Supporting Claims`/`Evidence Strength`/`Open Risk` 셀은 **verbatim 보존**하고 신규 `선택 조건` 열만 추가 — 참조된 의미(D1=`xforwarded` 채택, D6=`KC_HTTP_ENABLED`/trusted-addresses)는 불변. 두 citing 문서의 요약은 낡지 않음 → 갱신 없음 |
|
|
| **A3** | `OUT_OF_BRANCH_SCOPE` 정제 (조치 완료) | §진행 중 메모의 nginx/Caddy config 초안이 TLS termination(`listen 443 ssl`, cert)까지 포함 — HTTPS termination 은 [[raw/branch-notes/feature-keycloak-https-termination-caddy-nginx]] 소유(D4 도 그 branch 에 delegate) | §진행 중 메모의 초안은 **사용자 작성이라 보존**. §구현 가이드(R3 정제)는 TLS 라인을 남기지 않고 proxy 가 **emit 하는 헤더 계약**만 명세. Caddy vs nginx 선택은 그 branch 로 위임 명시 |
|
|
| **A4** | `IMPL_UNDERSPECIFIED` (depth 감사 F1 — **해소**) | 과거 Caddy 초안 `handle_path /keycloak/*`(prefix strip)과 D3 method B(`KC_HTTP_RELATIVE_PATH`, prefix 보존 기대)의 충돌 가능성을 발견 | copyable Caddy snippet을 `handle /keycloak/*` + `reverse_proxy`로 수정해 prefix 보존 invariant와 일치시켰다. 폐기된 `handle_path`는 회귀 방지 역사/negative test에서만 언급하며 runtime 확인은 §Claims To Verify에 유지 |
|
|
|
|
## 마주친 문제
|
|
|
|
- 아직 없음 (문서 단계). 실 적용 시 예상되는 함정:
|
|
- `KC_HOSTNAME`을 `kc.example.com`(scheme 없음)으로 적으면 일부 endpoint가 http로 발급되는 사례 보고 있음 → 항상 `https://` prefix 포함.
|
|
- `KC_HTTP_RELATIVE_PATH` 변경 후 admin console URL도 함께 변경 → `/keycloak/admin`이 됨에 유의.
|
|
- Cloudflare Tunnel origin이 HTTP인데 `KC_HTTP_ENABLED=true` 누락 시 Keycloak 부팅 실패 (production mode는 HTTPS 강제 디폴트).
|
|
|
|
## 묶음
|
|
|
|
<!-- GENERATED: sources:start -->
|
|
- [[raw/official-docs/keycloak-hostname-configuration]]
|
|
- [[raw/official-docs/keycloak-reverseproxy-official]]
|
|
<!-- GENERATED: sources:end -->
|
|
|
|
> 본 branch 는 leaf — 자식 자료 없음. errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.
|
|
|
|
### 오류 기록
|
|
|
|
- (없음)
|
|
|
|
### 면접 준비
|
|
|
|
- (없음)
|
|
|
|
## 관련 일일 노트
|
|
|
|
|
|
## 완료 후 정리
|
|
|
|
> 본 sub-sub-branch는 **문서까지만**. 실 Keycloak / nginx / Caddy 구동은 P3A 완료 후 선택적 확장.
|
|
|
|
- PR 링크:
|
|
- 리뷰 메모:
|
|
- 머지 결과 / 배포 환경: 해당 없음 (문서 단계, P3B 전체 `documented-only`)
|
|
- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출):
|
|
- `actually-implemented` 항목: 없음
|
|
- `locally-verified` 항목: 없음
|
|
- `prod-verified` 항목: 없음
|
|
- **추출하지 않을 항목** (planned / documented-only / abandoned):
|
|
- 본 sub-sub-branch 전체가 `documented-only`. 추후 `wiki/concepts/keycloak-deployment-patterns.md` 합성 시 "Keycloak behind reverse proxy 함정" 섹션으로 인용 후보.
|