refactor: 문서 개선 중

This commit is contained in:
donghyeon-ka
2026-09-21 14:30:55 +09:00
parent c93cdea150
commit 805a18f486
1497 changed files with 525837 additions and 59152 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,23 +1,23 @@
{
"version": "1.1",
"id": "b4-header-trust-boundary",
"title": "지우지 않으면 통과한다",
"question": "Edge 가 넣어주는 인가 헤더를 앱이 믿어도 되는가",
"title": "헤더 도착과 인가 우회는 다르다",
"question": "같은 위조 헤더가 도착하는 것과 인가를 우회하는 것은 같은가",
"type": "architecture",
"direction": "TB",
"direction": "LR",
"audience": [
"Forward-Auth 구조를 운영하는 엔지니어"
],
"summary": "nginx 는 자기가 설정하지 않은 동명 헤더를 덮어쓰지 않는다. 먼저 지워야 한다.",
"alt": "밖에서 들어온 위조 헤더가 프록시를 그대로 통과해 앱에 닿는 구성. 프록시가 그 이름을 설정할 때만 덮어쓴다.",
"long_description": "nginx 는 자기가 proxy_set_header 로 설정한 헤더만 덮어쓴다. 설정하지 않은 이름은 클라이언트가 보낸 값이 그대로 지나간다. 그래서 X-Auth-Request-Roles 같은 헤더를 앱이 믿으려면 프록시가 그 이름을 먼저 빈 값으로 지워야 한다. 그리고 IdP 에서 클레임을 바꿔도 반영되지 않는다. 12회 요청 6초 동안 옛 값이 갔고 세션을 지워 재인증한 뒤에야 새 값이 왔다. 세션은 로그인 시점의 스냅샷이다.",
"summary": "동명 위조 헤더는 permitAll echo 까지 도착했지만 JWT 보호 경로는 401 이었다. 헤더 삭제는 이 실험대에서 적용하지 않은 미검증 처방이다.",
"alt": "외부 위조 헤더가 nginx 를 지나 permitAll echo 에서는 200으로 도착하지만 JWT 보호 경로에서는 401로 막히는 분기.",
"long_description": "위조 헤더는 app1.hyeonworks.com/api 의 permitAll echo 앱까지 그대로 도착했다. 같은 헤더로 JWT 를 요구하는 /api/me 와 /api/protected 를 호출하면 401이었다. 따라서 헤더 도착과 인가 우회는 다른 사건이다. proxy_set_header 로 먼저 지우는 것은 문서에 적힌 처방이지만 이 실험대에서는 적용하지 않아 미검증이다.",
"source_context": {
"document": "docs/keycloak-session-store/final/document.md",
"document_sha256": "1d44cba1905544d92f1d26ae36a8deb64a3db3914d6b488fd30d6ae7f8cfbabe",
"document_sha256": "28aef96a2bbb94fbb10ade26a71238fee62a5a4d9fa6e7749ae98cfd0a65e560",
"anchor": {
"kind": "heading",
"value": "B-4 · Edge 인가의 범위 (Q4)",
"line": 401
"line": 821
}
},
"composition": {
@@ -36,14 +36,14 @@
"kind": "actor",
"role": "source",
"emphasis": "warning",
"description": "밖에서 들어온 요청이 앱이 믿는 헤더 이름을 그대로 쓴다.",
"description": "앱이 신뢰할 수 있는 이름과 같은 헤더를 보낸다.",
"details": [
"X-Auth-Request-Roles"
],
"evidence": [
{
"start_line": 393,
"end_line": 400
"start_line": 823,
"end_line": 830
}
],
"assumption": false
@@ -54,50 +54,50 @@
"kind": "gateway",
"role": "control",
"emphasis": "warning",
"description": "설정하지 않은 이름은 덮어쓰지 않는다.",
"description": "설정하지 않은 동명 헤더를 덮어쓰지 않는다.",
"details": [
"proxy_set_header ... \"\""
"동명 헤더 미삭제"
],
"evidence": [
{
"start_line": 393,
"end_line": 400
"start_line": 829,
"end_line": 830
}
],
"assumption": false
},
{
"id": "proxy",
"label": "oauth2-proxy",
"kind": "gateway",
"role": "control",
"id": "echo",
"label": "permitAll echo",
"kind": "service",
"role": "target",
"emphasis": "normal",
"description": "인증 결과를 헤더로 넣는다.",
"description": "위조 헤더가 도착하는 것을 관측한 경로다.",
"details": [
"--set-xauthrequest"
"/api/echo · HTTP 200"
],
"evidence": [
{
"start_line": 393,
"end_line": 400
"start_line": 825,
"end_line": 840
}
],
"assumption": false
},
{
"id": "app",
"label": "",
"id": "protected",
"label": "JWT 보호 경로",
"kind": "service",
"role": "target",
"emphasis": "primary",
"description": "헤더를 믿고 인가한다.",
"description": "같은 위조 헤더만으로는 통과하지 못했다.",
"details": [
"로그인 시점 스냅샷"
"/api/me · /api/protected · 401"
],
"evidence": [
{
"start_line": 401,
"end_line": 408
"start_line": 832,
"end_line": 840
}
],
"assumption": false
@@ -112,8 +112,22 @@
"kind": "request",
"evidence": [
{
"start_line": 393,
"end_line": 400
"start_line": 823,
"end_line": 830
}
],
"assumption": false
},
{
"id": "n-e",
"from": "nginx",
"to": "echo",
"label": "그대로 전달",
"kind": "request",
"evidence": [
{
"start_line": 825,
"end_line": 840
}
],
"assumption": false
@@ -121,27 +135,13 @@
{
"id": "n-p",
"from": "nginx",
"to": "proxy",
"label": "미삭제 시 통과",
"to": "protected",
"label": "같은 헤더",
"kind": "request",
"evidence": [
{
"start_line": 393,
"end_line": 400
}
],
"assumption": false
},
{
"id": "p-a",
"from": "proxy",
"to": "app",
"label": "인가 헤더",
"kind": "request",
"evidence": [
{
"start_line": 393,
"end_line": 408
"start_line": 832,
"end_line": 840
}
],
"assumption": false
@@ -149,6 +149,6 @@
],
"legend": [],
"metadata": {
"rationale": "위조 경로와 정상 경로가 같은 헤더 이름을 쓴다는 것을 그렸다. 지우는 단계가 없으면 둘을 구별할 수 없다."
"rationale": "위조 헤더의 도착과 인가 우회를 한 경로로 합치지 않고, 같은 입력이 permitAll 과 JWT 보호 경로에서 갈리는 것을 그렸다. 헤더 삭제 처방은 미검증이므로 노드로 확정하지 않았다."
}
}
}