docs(keycloak-session-store): import the session-storage lab as a new project
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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
43bccd08a8
commit
b2963105a8
+34
@@ -0,0 +1,34 @@
|
||||
수집 시각: 2026-09-03 15:01:30 KST
|
||||
대상: https://app1.hyeonworks.com/api/echo
|
||||
|
||||
=== [1] 호스트 nginx 가 주입하는 헤더 ===
|
||||
3: server 192.168.122.11:80;
|
||||
4: server 192.168.122.12:80;
|
||||
8: listen 80 default_server;
|
||||
14: listen 443 ssl default_server;
|
||||
26: proxy_set_header Host $host;
|
||||
27: proxy_set_header X-Forwarded-Host $host;
|
||||
28: proxy_set_header X-Forwarded-Proto http;
|
||||
29: proxy_set_header X-Forwarded-Port 80;
|
||||
30: proxy_set_header X-Forwarded-For $remote_addr;
|
||||
31: proxy_set_header X-Real-IP $remote_addr;
|
||||
|
||||
=== [2] Traefik entryPoint 인자 (forwardedHeaders 부재 확인) ===
|
||||
["--entryPoints.metrics.address=:9100/tcp"
|
||||
"--entryPoints.traefik.address=:8080/tcp"
|
||||
"--entryPoints.web.address=:8000/tcp"
|
||||
"--entryPoints.websecure.address=:8443/tcp"
|
||||
"--metrics.prometheus.entrypoint=metrics"
|
||||
"--entryPoints.websecure.http.tls=true"
|
||||
→ forwardedHeaders.trustedIPs 인자가 없음 = 기본값(신뢰 안 함)
|
||||
|
||||
=== [3] Traefik 파드 수와 위치 ===
|
||||
traefik-59b7647586-ftwf8 10.42.0.8 kc-lab-1
|
||||
|
||||
=== [4] traefik Service externalTrafficPolicy ===
|
||||
Cluster
|
||||
→ Cluster = svclb 가 SNAT 하여 클라이언트 IP 소실
|
||||
|
||||
=== [5] 앱 파드의 스위치 상태 ===
|
||||
SERVER_PORT=8081
|
||||
SERVER_FORWARD_HEADERS_STRATEGY=none
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
수집 시각: 2026-09-03 15:02:27 KST
|
||||
|
||||
=== [A] 정상 경로 — 브라우저와 같은 요청 ===
|
||||
명령: curl -s https://app1.hyeonworks.com/api/echo
|
||||
x-forwarded-proto http
|
||||
x-forwarded-port 80
|
||||
x-forwarded-for 10.42.0.1
|
||||
x-forwarded-host app1.hyeonworks.com
|
||||
x-real-ip 10.42.0.1
|
||||
x-forwarded-server traefik-59b7647586-ftwf8
|
||||
--- 앱이 해석한 값
|
||||
scheme http
|
||||
secure False
|
||||
serverName app1.hyeonworks.com
|
||||
serverPort 80
|
||||
remoteAddr 10.42.0.8
|
||||
localAddr 10.42.1.3
|
||||
requestUrl http://app1.hyeonworks.com/api/echo
|
||||
|
||||
=== [B] 대조 실험 1 — nginx 우회, 헤더 없이 Traefik 직접 ===
|
||||
명령: curl http://192.168.122.11/api/echo -H 'Host: app1.hyeonworks.com' (test-server 에서)
|
||||
x-forwarded-proto http
|
||||
x-forwarded-port 80
|
||||
x-forwarded-for 10.42.0.1
|
||||
x-forwarded-host app1.hyeonworks.com
|
||||
x-real-ip 10.42.0.1
|
||||
x-forwarded-server traefik-59b7647586-ftwf8
|
||||
--- 앱이 해석한 값
|
||||
scheme http
|
||||
secure False
|
||||
serverName app1.hyeonworks.com
|
||||
serverPort 80
|
||||
remoteAddr 10.42.0.8
|
||||
localAddr 10.42.0.9
|
||||
requestUrl http://app1.hyeonworks.com/api/echo
|
||||
|
||||
=== [C] 대조 실험 2 — nginx 우회, 올바른 헤더를 명시해서 ===
|
||||
명령: 위와 동일 + -H 'X-Forwarded-Proto: https' -H 'X-Forwarded-Port: 443' -H 'X-Forwarded-For: 203.0.113.7'
|
||||
x-forwarded-proto http
|
||||
x-forwarded-port 80
|
||||
x-forwarded-for 10.42.0.1
|
||||
x-forwarded-host app1.hyeonworks.com
|
||||
x-real-ip 10.42.0.1
|
||||
x-forwarded-server traefik-59b7647586-ftwf8
|
||||
--- 앱이 해석한 값
|
||||
scheme http
|
||||
secure False
|
||||
serverName app1.hyeonworks.com
|
||||
serverPort 80
|
||||
remoteAddr 10.42.0.8
|
||||
localAddr 10.42.1.3
|
||||
requestUrl http://app1.hyeonworks.com/api/echo
|
||||
|
||||
★ [C] 에서 https/443/203.0.113.7 을 명시했음에도 http/80/10.42.0.1 이 도달했다.
|
||||
→ Traefik 이 들어온 X-Forwarded-* 를 신뢰하지 않고 재작성한다는 독립적 증거.
|
||||
|
||||
=== [D] 위조 테스트 — 클라이언트가 직접 헤더 주입 ===
|
||||
명령: curl https://app1.hyeonworks.com/api/echo -H 'X-Forwarded-Host: evil.example.com' -H 'X-Forwarded-For: 1.2.3.4'
|
||||
x-forwarded-proto http
|
||||
x-forwarded-port 80
|
||||
x-forwarded-for 10.42.1.0
|
||||
x-forwarded-host app1.hyeonworks.com
|
||||
x-real-ip 10.42.1.0
|
||||
x-forwarded-server traefik-59b7647586-ftwf8
|
||||
--- 앱이 해석한 값
|
||||
scheme http
|
||||
secure False
|
||||
serverName app1.hyeonworks.com
|
||||
serverPort 80
|
||||
remoteAddr 10.42.0.8
|
||||
localAddr 10.42.0.9
|
||||
requestUrl http://app1.hyeonworks.com/api/echo
|
||||
|
||||
★ evil.example.com 과 1.2.3.4 가 도달하지 않았다 = 신뢰 경계는 작동.
|
||||
|
||||
=== [E] 파드 분배 8회 ===
|
||||
pod 10.42.1.3 | traefik traefik-59b7647586-ftwf8
|
||||
pod 10.42.0.9 | traefik traefik-59b7647586-ftwf8
|
||||
pod 10.42.1.3 | traefik traefik-59b7647586-ftwf8
|
||||
pod 10.42.0.9 | traefik traefik-59b7647586-ftwf8
|
||||
pod 10.42.1.3 | traefik traefik-59b7647586-ftwf8
|
||||
pod 10.42.0.9 | traefik traefik-59b7647586-ftwf8
|
||||
pod 10.42.1.3 | traefik traefik-59b7647586-ftwf8
|
||||
pod 10.42.0.9 | traefik traefik-59b7647586-ftwf8
|
||||
|
||||
=== [F] HTTP → HTTPS 리다이렉트 ===
|
||||
status=301 location=https://app1.hyeonworks.com/api/echo
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
수집 시각: 2026-09-03 15:32:04 KST
|
||||
단계: A(nginx) + B(Traefik) + C(앱) 모두 적용 후
|
||||
|
||||
=== [1] nginx 가 보내는 값 ===
|
||||
28: proxy_set_header X-Forwarded-Proto https;
|
||||
29: proxy_set_header X-Forwarded-Port 443;
|
||||
30: proxy_set_header X-Forwarded-For $remote_addr;
|
||||
31: proxy_set_header X-Real-IP $remote_addr;
|
||||
|
||||
=== [2] Traefik entryPoint 인자 ===
|
||||
"--entryPoints.web.forwardedHeaders.trustedIPs=10.42.0.0/16
|
||||
"--entryPoints.websecure.forwardedHeaders.trustedIPs=10.42.0.0/16
|
||||
|
||||
=== [3] 앱 스위치 ===
|
||||
SERVER_FORWARD_HEADERS_STRATEGY=native
|
||||
|
||||
=== [4] 최종 측정 ===
|
||||
x-forwarded-proto https
|
||||
x-forwarded-port 443
|
||||
x-forwarded-host app1.hyeonworks.com
|
||||
x-real-ip 100.123.124.30
|
||||
x-forwarded-server traefik-697889c85-g7xpp
|
||||
--- 앱이 해석한 값
|
||||
scheme https
|
||||
secure True
|
||||
serverName app1.hyeonworks.com
|
||||
serverPort 443
|
||||
remoteAddr 100.123.124.30
|
||||
localAddr 10.42.0.10
|
||||
requestUrl https://app1.hyeonworks.com/api/echo
|
||||
|
||||
=== [5] 위조 테스트 — 클라이언트가 http/evil/1.2.3.4 를 주입 ===
|
||||
x-forwarded-proto https
|
||||
x-forwarded-port 443
|
||||
x-forwarded-host app1.hyeonworks.com
|
||||
x-real-ip 100.123.124.30
|
||||
x-forwarded-server traefik-697889c85-g7xpp
|
||||
--- 앱이 해석한 값
|
||||
scheme https
|
||||
secure True
|
||||
serverName app1.hyeonworks.com
|
||||
serverPort 443
|
||||
remoteAddr 100.123.124.30
|
||||
localAddr 10.42.1.6
|
||||
requestUrl https://app1.hyeonworks.com/api/echo
|
||||
|
||||
★ 주입값이 하나도 반영되지 않았다. nginx 의 $remote_addr 덮어쓰기가 방어한다.
|
||||
|
||||
=== [6] 파드 분배 6회 ===
|
||||
pod 10.42.0.10 | remoteAddr 100.123.124.30 | scheme https
|
||||
pod 10.42.1.6 | remoteAddr 100.123.124.30 | scheme https
|
||||
pod 10.42.0.10 | remoteAddr 100.123.124.30 | scheme https
|
||||
pod 10.42.1.6 | remoteAddr 100.123.124.30 | scheme https
|
||||
pod 10.42.0.10 | remoteAddr 100.123.124.30 | scheme https
|
||||
pod 10.42.1.6 | remoteAddr 100.123.124.30 | scheme https
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
수집 시각: 2026-09-03 16:16:48 KST
|
||||
주제: 프록시 우회 경로 차단 (NetworkPolicy)
|
||||
|
||||
=== [1] 차단 전 — 클러스터 안에서 앱에 직접 요청 ===
|
||||
명령: kubectl run ... -- curl http://echo:8081/api/echo \
|
||||
-H 'X-Forwarded-Proto: https' -H 'X-Forwarded-Host: evil.example.com' -H 'X-Forwarded-For: 1.2.3.4'
|
||||
|
||||
scheme https
|
||||
secure True
|
||||
serverName evil.example.com ← 위조 성공
|
||||
remoteAddr 1.2.3.4 ← 위조 성공
|
||||
requestUrl https://evil.example.com/api/echo
|
||||
|
||||
★ Traefik 을 거치지 않으면 헤더 위조가 그대로 통한다.
|
||||
trustedIPs 와 internalProxies 가 둘 다 '대역'을 믿기 때문.
|
||||
|
||||
=== [2] 적용한 것 ===
|
||||
deploy/lab/k8s/traefik-forwarded-headers.yaml — 192.168.122.0/24 제거
|
||||
"--entryPoints.web.forwardedHeaders.trustedIPs=10.42.0.0/16"
|
||||
"--entryPoints.websecure.forwardedHeaders.trustedIPs=10.42.0.0/16"
|
||||
|
||||
deploy/lab/k8s/echo-network-policy.yaml — Traefik 파드에서만 8081 허용
|
||||
[{"from":[{"namespaceSelector":{"matchLabels":{"kubernetes.io/metadata.name":"kube-system"}},"podSelector":{"matchLabels":{"app.kubernetes.io/name":"traefik"}}}],"ports":[{"port":8081,"protocol":"TCP"}]},{"from":[{"ipBlock":{"cidr":"10.42.0.1/32"}},{"ipBlock":{"cidr":"10.42.1.1/32"}}],"ports":[{"port":8081,"protocol":"TCP"}]}]
|
||||
|
||||
=== [3] 차단 후 — 정상 경로 (계속 동작해야 함) ===
|
||||
x-forwarded-proto https
|
||||
x-forwarded-port 443
|
||||
x-forwarded-host app1.hyeonworks.com
|
||||
x-real-ip 100.123.124.30
|
||||
x-forwarded-server traefik-5d6fcf895-wpfhr
|
||||
--- 앱이 해석한 값
|
||||
scheme https
|
||||
secure True
|
||||
serverName app1.hyeonworks.com
|
||||
serverPort 443
|
||||
remoteAddr 100.123.124.30
|
||||
localAddr 10.42.0.14
|
||||
requestUrl https://app1.hyeonworks.com/api/echo
|
||||
|
||||
=== [4] 차단 후 — 우회 시도 ===
|
||||
HTTP 000 / curl exit 7
|
||||
HTTP 000 / curl exit 7
|
||||
|
||||
★ curl exit 7 = Failed to connect. 연결 자체가 성립하지 않는다.
|
||||
|
||||
=== [5] 파드 건강 상태 (probe 가 차단되지 않았는지) ===
|
||||
echo-54dbd94986-8jmdb 1/1 Running restarts=0
|
||||
echo-54dbd94986-lfltk 1/1 Running restarts=0
|
||||
@@ -0,0 +1,199 @@
|
||||
# 증거 — 2홉 프록시 헤더 계약 (수정 전 상태)
|
||||
|
||||
`docs/two-hop-proxy-header-contract.md`의 진단을 뒷받침하는 원자료.
|
||||
**모두 수정 전 상태에서 수집**했으며, 수정 후 재수집하여 대조한다.
|
||||
|
||||
수집 시각: 2026-09-03 15:01~15:03 KST
|
||||
|
||||
| 파일 | 내용 |
|
||||
|---|---|
|
||||
| `01-environment.txt` | 수정 전 세 계층의 설정 스냅샷 |
|
||||
| `02-measurements.txt` | 수정 전 측정 · 대조 실험 · 위조 테스트 · 분배 |
|
||||
| `stage-a-nginx-fixed.png` | A 단계 브라우저 화면 |
|
||||
| `stage-b-traefik-trusts.png` | B 단계 브라우저 화면 |
|
||||
| `stage-c-resolved.png` | C 단계 브라우저 화면 |
|
||||
| `04-after-fix.txt` | 수정 후 측정 · 위조 테스트 · 분배 |
|
||||
|
||||
---
|
||||
|
||||
## 확인된 문제는 둘이다
|
||||
|
||||
최초 진단은 "Traefik이 덮어쓴다" 하나였으나, 증거 수집 과정에서
|
||||
**독립된 원인이 두 개**임이 드러났다.
|
||||
|
||||
### 문제 1 — nginx가 애초에 틀린 값을 보낸다
|
||||
|
||||
`01-environment.txt`
|
||||
|
||||
```
|
||||
26: proxy_set_header Host $host;
|
||||
27: proxy_set_header X-Forwarded-Host $host;
|
||||
28: proxy_set_header X-Forwarded-Proto http; ← https 여야 한다
|
||||
29: proxy_set_header X-Forwarded-Port 80; ← 443 이어야 한다
|
||||
30: proxy_set_header X-Forwarded-For $remote_addr;
|
||||
31: proxy_set_header X-Real-IP $remote_addr;
|
||||
```
|
||||
|
||||
`listen 443 ssl` 서버 블록 안인데 `X-Forwarded-Proto`가 `http`다.
|
||||
TLS를 종료하는 서버가 "원래 요청은 평문이었다"고 알리고 있다.
|
||||
|
||||
HTTP 전용으로 먼저 세운 뒤 TLS를 얹는 과정에서 **이 두 줄을 함께 바꾸지
|
||||
않아 남은 값**이다. 설정 자체는 문법 오류가 없으므로 `nginx -t`도 통과하고,
|
||||
**아무 경고 없이 잘못된 값이 전파된다.**
|
||||
|
||||
### 문제 2 — Traefik이 올바른 값이 와도 덮어쓴다
|
||||
|
||||
`02-measurements.txt`의 **대조 실험 [C]** 가 이를 독립적으로 증명한다.
|
||||
nginx를 우회해 Traefik에 직접 요청하면서 올바른 헤더를 명시했다.
|
||||
|
||||
```
|
||||
보낸 것 : X-Forwarded-Proto: https
|
||||
X-Forwarded-Port: 443
|
||||
X-Forwarded-For: 203.0.113.7
|
||||
|
||||
도달한 것: x-forwarded-proto http
|
||||
x-forwarded-port 80
|
||||
x-forwarded-for 10.42.0.1
|
||||
```
|
||||
|
||||
**세 값 모두 재작성됐다.** Traefik entryPoint에
|
||||
`forwardedHeaders.trustedIPs`가 설정되지 않아 들어온 헤더를 신뢰하지 않는다.
|
||||
|
||||
`01-environment.txt`의 Traefik 인자 목록에 `forwardedHeaders` 관련 항목이
|
||||
하나도 없는 것이 그 근거다.
|
||||
|
||||
**문제 1만 고쳐서는 해결되지 않는다.** 두 원인이 직렬로 걸려 있다.
|
||||
|
||||
---
|
||||
|
||||
## 브라우저 증거
|
||||
|
||||
스크린샷은 모두 **브라우저가 `/api/echo` 응답을 렌더링한 실제 화면**이다.
|
||||
앱이 정렬된 JSON을 내보내도록 `spring.jackson.serialization.indent-output`을
|
||||
켜두었으므로 브라우저의 JSON 뷰어 설정과 무관하게 동일하게 읽힌다.
|
||||
|
||||
세 장은 **같은 요청을 세 가지 설정 상태에서** 찍은 것이다.
|
||||
|
||||
| 파일 | 켜진 스위치 | 화면에서 확인할 것 |
|
||||
|---|---|---|
|
||||
| `stage-a-nginx-fixed.png` | nginx 만 | `x-forwarded-proto: http` — Traefik 이 덮어씀 |
|
||||
| `stage-b-traefik-trusts.png` | nginx + Traefik | **헤더는 `https`인데 `scheme: http`** |
|
||||
| `stage-c-resolved.png` | 셋 다 | `scheme: https`, `secure: true` |
|
||||
|
||||
**`stage-b`가 가장 중요한 한 장이다.** `x-forwarded-proto: https`가 앱에
|
||||
도착해 있는데도 `scheme: http`, `secure: false`, `requestUrl: http://...`다.
|
||||
**헤더가 도착하는 것과 앱이 그것을 읽는 것은 다른 문제**임을 한 화면이
|
||||
보여준다.
|
||||
|
||||
## 정상으로 확인된 것
|
||||
|
||||
증거 수집에서 **문제가 아니라고 확인된 항목**도 함께 남긴다.
|
||||
|
||||
| 항목 | 결과 |
|
||||
|---|---|
|
||||
| TLS 종료 | 정상. 실인증서, `isSecureContext=true` |
|
||||
| `X-Forwarded-Host` | 유지됨 — Traefik이 이것만은 덮어쓰지 않는다 |
|
||||
| 위조 차단 | 클라이언트가 넣은 `evil.example.com`, `1.2.3.4`가 앱에 도달하지 않음 |
|
||||
| 파드 분배 | 8회 요청이 두 파드에 정확히 번갈아 도달 |
|
||||
| HTTP 리다이렉트 | `301 → https://app1.hyeonworks.com/api/echo` |
|
||||
|
||||
**위조가 차단되는 것은 nginx가 막아서가 아니라 Traefik이 전부 덮어쓰기
|
||||
때문**이다. 문제 2를 고치면 이 방어가 nginx의 `$remote_addr` 덮어쓰기로
|
||||
옮겨간다. 수정 후 재측정에서 **위조가 여전히 막히는지 반드시 확인**해야 한다.
|
||||
|
||||
---
|
||||
|
||||
## 재수집 방법
|
||||
|
||||
```bash
|
||||
# 터미널 증거
|
||||
./deploy/lab/scripts/measure-proxy-headers.sh
|
||||
|
||||
# 개별 확인
|
||||
curl -s https://app1.hyeonworks.com/api/echo | python3 -m json.tool
|
||||
|
||||
# 대조 실험 (test-server 에서, nginx 우회)
|
||||
curl -s http://192.168.122.11/api/echo \
|
||||
-H 'Host: app1.hyeonworks.com' \
|
||||
-H 'X-Forwarded-Proto: https' -H 'X-Forwarded-Port: 443' \
|
||||
-H 'X-Forwarded-For: 203.0.113.7' | python3 -m json.tool
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 수정 후 (2026-09-03 15:32 KST)
|
||||
|
||||
세 스위치를 순서대로 켜며 각 단계를 측정했다. 상세 절차는
|
||||
`docs/two-hop-proxy-header-contract.md` 9~11절.
|
||||
|
||||
| 파일 | 단계 |
|
||||
|---|---|
|
||||
| `stage-a-nginx-fixed.png` | A — nginx 만 고침 |
|
||||
| `stage-b-traefik-trusts.png` | B — Traefik `trustedIPs` 추가 |
|
||||
| `stage-c-resolved.png` | C — 앱 `strategy=native` |
|
||||
| `04-after-fix.txt` | 최종 측정 · 위조 테스트 · 분배 |
|
||||
|
||||
스크린샷은 브라우저가 `/api/echo` 응답을 렌더링한 **실제 화면**이다.
|
||||
|
||||
### 단계별 결과
|
||||
|
||||
| 항목 | 최초 | A | B | C |
|
||||
|---|---|---|---|---|
|
||||
| `x-forwarded-proto` | `http` | **`http`** | `https` | `https` |
|
||||
| `x-real-ip` | `10.42.1.0` | `10.42.1.0` | `100.123.124.30` | `100.123.124.30` |
|
||||
| `scheme` (앱 해석) | `http` | `http` | **`http`** | **`https`** |
|
||||
| `requestUrl` | `http://…` | `http://…` | `http://…` | **`https://…`** |
|
||||
|
||||
**A 이후 아무 변화가 없는 것**이 Traefik 덮어쓰기의 증거이고,
|
||||
**B 이후 헤더는 살아났으나 앱 해석은 그대로인 것**이 2번과 3번 스위치가
|
||||
다른 일을 한다는 증거다.
|
||||
|
||||
### 위조 차단 재확인
|
||||
|
||||
`04-after-fix.txt` [5]. 클라이언트가 `X-Forwarded-Proto: http`,
|
||||
`X-Forwarded-Host: evil.example.com`, `X-Forwarded-For: 1.2.3.4`를 주입했으나
|
||||
**하나도 반영되지 않았다.**
|
||||
|
||||
**방어 주체가 바뀌었다.** 수정 전에는 Traefik이 전부 덮어써서 막았고,
|
||||
수정 후에는 nginx의 `$remote_addr`가 막는다. 그래서 nginx에서
|
||||
`$proxy_add_x_forwarded_for`(덧붙이기)로 바꾸면 안 된다.
|
||||
|
||||
### 겪은 함정
|
||||
|
||||
`kubectl rollout status`가 완료를 알려도 **helm-controller의 Job이 차트를
|
||||
업그레이드하는 동안 구 Traefik 파드가 함께 살아 있다.** 이 시점에 측정하면
|
||||
옛 파드가 응답해 "고쳤는데 안 바뀌었다"고 오해하게 된다. `x-forwarded-server`
|
||||
값의 파드 이름으로 어느 파드가 응답했는지 확인해야 한다.
|
||||
|
||||
---
|
||||
|
||||
## 프록시 우회 차단 (2026-09-03 16:16 KST)
|
||||
|
||||
`05-networkpolicy.txt`
|
||||
|
||||
헤더 신뢰를 켠 뒤 남아 있던 구멍을 실증하고 막았다.
|
||||
|
||||
**차단 전** — 클러스터 안에서 Traefik을 우회해 앱에 직접 요청하면
|
||||
`serverName: evil.example.com`, `remoteAddr: 1.2.3.4`로 **위조가 성립했다.**
|
||||
|
||||
**적용한 것**
|
||||
|
||||
| 파일 | 변경 |
|
||||
|---|---|
|
||||
| `traefik-forwarded-headers.yaml` | `192.168.122.0/24` 제거 (SNAT 때문에 도달 불가한 대역) |
|
||||
| `echo-network-policy.yaml` | Traefik 파드에서만 8081 허용 (라벨 기준) |
|
||||
|
||||
**차단 후**
|
||||
|
||||
```
|
||||
정상 경로 scheme=https, remoteAddr=100.123.124.30 동작
|
||||
우회 시도 HTTP 000 / curl exit 7 연결 거부
|
||||
파드 상태 1/1 Running, restarts=0 probe 정상
|
||||
```
|
||||
|
||||
`exit 7`은 curl의 "Failed to connect"다. HTTP 403이 아니라
|
||||
**TCP 연결 자체가 성립하지 않았다**는 뜻이다.
|
||||
|
||||
`restarts=0`이 중요하다. NetworkPolicy에서 kubelet probe 경로를 빠뜨리면
|
||||
probe가 실패해 파드가 재시작 루프에 빠진다. 노드의 cni0 주소
|
||||
(`10.42.0.1`, `10.42.1.1`)를 `/32`로 허용해 이를 피했다.
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 91 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 93 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 90 KiB |
Reference in New Issue
Block a user