37 KiB
title, source_type, status, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, branch, parent_branch, related_projects, tags, created, target_merge, status_label, contract_packet_sha256
| title | source_type | status | id | kind | project | work_item | inherits | refines | overrides | depends_on | contract_packet | branch | parent_branch | related_projects | tags | created | target_merge | status_label | contract_packet_sha256 | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| branch / feature-keycloak-reverse-proxy-headers (P3B Keycloak reverse proxy 설정 — KC_PROXY_HEADERS + KC_HOSTNAME) | branch-note | raw | BR-KEYCLOAK-CHILD-2D084935 | branch-child | keycloak-patterns-overview | WI-KEYCLOAK-PATTERNS-OVERVIEW-020 |
|
1 | feature-keycloak-reverse-proxy-headers | feature-keycloak-patterns |
|
|
2026-05-25 | in-progress | 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
부모 (필수)
raw/branch-notes/feature-keycloak-patterns
브랜치 계약 패킷
- 생성 시 프로젝트 개정:
1 - 패킷 스키마:
contract_packet: 1 - 완료 조건: project governance hub가 AP1~AP4 taxonomy와 child progress index를 유지한다
상속한 프로젝트 결정
| 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 |
브랜치 지역 결정
기존 branch-local 결정은 아래
## Decision Evidence Map의 D-row가 소유하며 이 packet에서 복제하지 않는다.
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|
선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|
없음.
목표
Keycloak이 reverse proxy 뒤에 있을 때 디폴트로는 Host 헤더와 X-Forwarded-* 헤더를 신뢰하지 않는다. 그 결과 issuer URL / authorization endpoint / token endpoint 등이 internal hostname(http://keycloak:8080)으로 발급되어 다음 함정이 발생한다:
- SPA가 받는
issclaim이 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을 강제 하는 것.
면접에서 답해야 할 질문:
KC_PROXY_HEADERS와KC_HOSTNAME의 차이는? → 전자는 proxy가 보낸 헤더를 신뢰할지(어떤 헤더 포맷인지), 후자는 issuer URL 강제 override.KC_HOSTNAME_STRICT는 왜 필요한가? → 클라이언트가 보낸 Host 헤더로 issuer가 결정되는 디폴트 동작을 막아 issuer URL을 고정.- nginx vs Caddy 선택 기준은? → Caddy는 reverse_proxy directive가 X-Forwarded-* 자동 설정, nginx는 명시 필요.
- 이슈:
- PR:
범위
포함 범위
- 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-* 헤더 신뢰) vsforwarded(RFC 7239 Forwarded 헤더 신뢰) 차이 — 등급:plannedKC_HOSTNAME=<public-domain>설정 — Keycloak이 발급하는 issuer / authorization / token URL을 이 값으로 고정 — 등급:plannedKC_HOSTNAME_STRICT=true— 클라이언트 Host 헤더 무시,KC_HOSTNAME값 강제 사용 — 등급:plannedKC_HTTP_RELATIVE_PATH=/keycloak— path-prefix 라우팅 시 (nginx가/keycloak/*→ Keycloak 8080) — 등급:plannedKC_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=xforwardedKC_HOSTNAME=https://kc.example.comKC_HOSTNAME_STRICT=trueKC_HTTP_RELATIVE_PATH=/keycloakKC_HTTP_ENABLED=trueKC_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/-Prefix5종 파싱),#KC-RP-C1(forwarded=RFC 7239 — 비교 baseline). proxy 측 헤더 주입은 §진행 중 메모의 nginx/Caddy config 초안이 실체.
- UNSUPPORTED_IMPL_DECISION: (a) Caddy
reverse_proxy가 자동으로 emit 하는 X-Forwarded- 헤더 셋*이 Keycloakxforwarded파싱 기대치(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=proxyX-Forwarded-Prefix주입, B=Keycloakhttp-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로 교정했다. nginxlocation /keycloak/ { proxy_pass http://127.0.0.1:8080; }와 마찬가지로/keycloakprefix를 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 하는 헤더가 Keycloakxforwarded기대 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_HOSTNAMEscheme 누락 → 일부 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-C6does-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 dockeycloak-reverseproxy-official.mdParent 표가 지정). 본 branch D6 은 이 값을 재진술 → §Audit A1. 본 branch 의KC_PROXY_HEADERS=xforwarded(D1) 은 헤더 파싱만 켜므로(KC-RP-C2does-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 의 전제.
- raw/branch-notes/feature-keycloak-header-spoofing-defense D6 — ⚠️ RESTATED_FOREIGN_DECISION.
검증해야 할 주장
공식 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 강제 디폴트).
묶음
본 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 함정" 섹션으로 인용 후보.
- 본 sub-sub-branch 전체가