Files
llm-wiki/raw/branch-notes/feature-keycloak-reverse-proxy-headers.md

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
DEC-KEYCLOAK-PATTERNS-OVERVIEW-AUTH-TAXONOMY-001@1
1 feature-keycloak-reverse-proxy-headers feature-keycloak-patterns
keycloak-patterns
branch
keycloak-patterns
p3b
reverse-proxy
keycloak-hostname
nginx
caddy
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가 받는 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_HEADERSKC_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:

범위

포함 범위

  • 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개+)

관련 sub-branch

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 :8080HTTP forward 하는 것이 전제.

  • UNSUPPORTED_IMPL_DECISION: KC_HOSTNAME=https://kc.example.comhttps:// prefix 강제 여부KC-HOST-C4backchannel-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-nginxKC_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-mismatchKC_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 D6KC_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_HOSTNAMEkc.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 함정" 섹션으로 인용 후보.