The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
107 lines
5.2 KiB
Markdown
107 lines
5.2 KiB
Markdown
---
|
|
kind: CASE
|
|
slug: trust-policy-lives-in-nginx-not-in-the-code
|
|
title: forwarded 헤더 신뢰 판정이 Nginx에만 있고 Java 정책 421 LOC은 배선되지 않았다
|
|
topic: duplicate-mechanisms
|
|
project: clean-architecture-backend-template
|
|
status: 게시 전
|
|
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
|
rootTreeNode: case:trust-policy-lives-in-nginx-not-in-the-code
|
|
evidenceCapturedOn: 2026-09-01
|
|
assets:
|
|
- key: trust-policy-lives-in-nginx-not-in-the-code
|
|
file: ../../../final/evidence/rendered/trust-policy-lives-in-nginx-not-in-the-code.svg
|
|
evidence:
|
|
- ../../../final/evidence/raw/trust-policy-lives-in-nginx-not-in-the-code.txt
|
|
source:
|
|
- 원본 분석 절은 final/document.md#3-1 · analysis/14 §32.2 이다.
|
|
---
|
|
|
|
# forwarded 헤더 신뢰 판정이 Nginx에만 있고 Java 정책 421 LOC은 배선되지 않았다
|
|
|
|
웹 리프의 프록시 패키지는 421 줄로 신뢰 프록시 정책과 헤더 정화기와 정규화 타입을 갖는다. 실제 신뢰 판정은 Nginx 설정이 하고, 그 설정은 들어온 forwarded 헤더를 원격 주소로 교체한다.
|
|
|
|
## 관계
|
|
|
|
- **중복 장치를 찾으면 어느 쪽이 조립됐는지 먼저 확인한다**
|
|
이 사례가 그 규칙의 인프라 판이다.
|
|
- **요청 식별자를 클라이언트가 고를 수 없다는 정책이 다른 필터에서 뒤집힌다**
|
|
같은 리프에서 같은 계열의 사례다.
|
|
|
|
## 문제
|
|
|
|
웹 리프의 프록시 패키지는 네 파일로 421 줄이다.
|
|
|
|
TrustedProxyPolicy 161 줄
|
|
NormalizedForwardedHeaders 158 줄
|
|
ForwardedHeaderSanitizer 72 줄
|
|
UntrustedForwardedHeaderException 30 줄
|
|
|
|
이 코드가 하는 일은 어떤 프록시를 신뢰할지 정하고 forwarded 헤더를 정규화하는 것이다.
|
|
|
|
문제는 이것이 실제 판정 경로인가다.
|
|
|
|
## 결론
|
|
|
|
Nginx 설정이 그 판정을 대신한다.
|
|
|
|
프록시 헤더 설정 파일의 주석이 자기 지위를 명시한다. 이것이 권위 있는 forwarded 헤더이며 모든 location 에서 include 된다는 것이다.
|
|
|
|
설정 내용은 교체다.
|
|
|
|
Host 를 host 변수로 설정
|
|
X-Real-IP 를 remote_addr 로 설정
|
|
X-Forwarded-For 를 remote_addr 로 설정
|
|
X-Forwarded-Host 를 이 배포의 공개 이름으로 설정
|
|
|
|
주석이 모든 줄이 SET 이고 ADD 가 아니라고 못 박는다. 클라이언트가 보낸 X-Forwarded-For 는 remote_addr 로 교체되고 X-Forwarded-Host 는 이 배포의 공개 이름으로 교체된다.
|
|
|
|
즉 애플리케이션에 도달하는 시점에 그 헤더들은 이미 신뢰할 수 있는 값이다. Java 정책이 판정할 것이 남아 있지 않다.
|
|
|
|
Java 쪽 참조 수도 그것과 맞는다.
|
|
|
|
ForwardedHeaderSanitizer : main 참조 0
|
|
TrustedProxyPolicy : main 참조 1, test 참조 2
|
|
UntrustedForwardedHeaderException : main 참조 1, test 참조 1
|
|
NormalizedForwardedHeaders : main 참조 2
|
|
|
|
같은 설정 파일의 주석이 왜 include 방식인지도 적는다. Nginx 의 배열 지시어 상속 규칙이 병합이 아니라 교체이기 때문이다. location 안의 proxy_set_header 하나가 server 수준에서 상속된 모든 proxy_set_header 를 버린다. 보안 헤더를 server 수준에 두고 location 마다 하나씩 추가하는 설정은 보안 헤더를 하나도 보내지 않으며, 유일한 증상은 애플리케이션이 조용히 클라이언트를 다시 신뢰하는 것이다.
|
|
|
|
그 주석이 이 사례의 위험을 정확히 서술한다. 신뢰 판정이 인프라에 있으면 인프라 설정 실수가 애플리케이션의 신뢰 정책을 조용히 되돌린다. 그리고 그때 되돌아갈 Java 정책은 배선되어 있지 않다.
|
|
|
|
## 검증 환경
|
|
|
|
Nginx 설정 : 웹 리프의 nginxProxyTest 소스셋 아래 proxy_headers.conf
|
|
확인 방식 : 파일 LOC 계수와 타입별 참조 계수, 설정 파일 대조
|
|
소스 수정 : x
|
|
|
|
## 재현 조건
|
|
|
|
1. 웹 리프의 proxy 패키지 파일과 줄 수를 센다. 네 파일 421 줄이다.
|
|
2. 각 타입의 main 참조와 test 참조를 센다.
|
|
3. Nginx 프록시 헤더 설정을 읽는다. 모든 지시어가 SET 이다.
|
|
4. 그 설정의 주석에서 include 방식을 택한 이유를 읽는다.
|
|
|
|
## 본문
|
|
|
|
<!-- body:start -->
|
|
|
|
forwarded 헤더를 어디까지 믿을지 판정하는 Java 정책이 421 LOC 작성돼 있고 배선되지 않는다. 실제 판정은 Nginx 설정이 한다.
|
|
|
|
## 판정을 실제로 하는 곳
|
|
|
|
:::evidence key="trust-policy-lives-in-nginx-not-in-the-code" alt="분석 문서 final/document.md 에서 이 기록의 근거 절을 그대로 잘라낸 18줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="final/document.md 발췌 — 18줄" zoom="true"
|
|
:::
|
|
|
|
## 리뷰가 닿지 않는 자리로 정책이 옮겨졌다
|
|
|
|
두 곳이 어긋나면 코드 리뷰가 잡을 수 없고, Java 쪽을 고쳐도 동작이 바뀌지 않는다.
|
|
|
|
## 확인하지 못한 것
|
|
|
|
이 Nginx 설정이 실제 배포에서 쓰이는 설정과 같은지 확인하지 않았다. 확인한 파일은 웹 리프의 테스트 소스셋 아래에 있다. 별도로 인프라 디렉터리에 파일서버용 Nginx 설정이 있고 그것은 다른 파일이다.
|
|
|
|
Java 정책이 어떤 경로에서 호출되는지 그 한 건씩의 참조를 추적하지 않았다.
|
|
|
|
<!-- body:end -->
|