{ "version": "1.1", "id": "b4-header-trust-boundary", "title": "지우지 않으면 통과한다", "question": "Edge 가 넣어주는 인가 헤더를 앱이 믿어도 되는가", "type": "architecture", "direction": "TB", "audience": [ "Forward-Auth 구조를 운영하는 엔지니어" ], "summary": "nginx 는 자기가 설정하지 않은 동명 헤더를 덮어쓰지 않는다. 먼저 지워야 한다.", "alt": "밖에서 들어온 위조 헤더가 프록시를 그대로 통과해 앱에 닿는 구성. 프록시가 그 이름을 설정할 때만 덮어쓴다.", "long_description": "nginx 는 자기가 proxy_set_header 로 설정한 헤더만 덮어쓴다. 설정하지 않은 이름은 클라이언트가 보낸 값이 그대로 지나간다. 그래서 X-Auth-Request-Roles 같은 헤더를 앱이 믿으려면 프록시가 그 이름을 먼저 빈 값으로 지워야 한다. 그리고 IdP 에서 클레임을 바꿔도 반영되지 않는다. 12회 요청 6초 동안 옛 값이 갔고 세션을 지워 재인증한 뒤에야 새 값이 왔다. 세션은 로그인 시점의 스냅샷이다.", "source_context": { "document": "docs/keycloak-session-store/final/document.md", "document_sha256": "1d44cba1905544d92f1d26ae36a8deb64a3db3914d6b488fd30d6ae7f8cfbabe", "anchor": { "kind": "heading", "value": "B-4 · Edge 인가의 범위 (Q4)", "line": 401 } }, "composition": { "profile": "component-flow", "diagram_only": true, "reference_ids": [ "payment-event-flow" ], "rationale": "헤더가 어디서 설정되고 어디서 신뢰되는가가 지배적 질문이다. 신뢰 경계이므로 component-flow 를 골랐다." }, "groups": [], "nodes": [ { "id": "attacker", "label": "밖에서 보낸 위조 헤더", "kind": "actor", "role": "source", "emphasis": "warning", "description": "앱이 믿는 이름을 그대로 쓴다.", "details": [ "X-Auth-Request-Roles" ], "evidence": [ { "start_line": 393, "end_line": 400 } ], "assumption": false }, { "id": "nginx", "label": "nginx", "kind": "gateway", "role": "control", "emphasis": "warning", "description": "설정하지 않은 이름은 덮어쓰지 않는다.", "details": [ "proxy_set_header ... \"\"" ], "evidence": [ { "start_line": 393, "end_line": 400 } ], "assumption": false }, { "id": "proxy", "label": "oauth2-proxy", "kind": "gateway", "role": "control", "emphasis": "normal", "description": "인증 결과를 헤더로 넣는다.", "details": [ "--set-xauthrequest" ], "evidence": [ { "start_line": 393, "end_line": 400 } ], "assumption": false }, { "id": "app", "label": "앱", "kind": "service", "role": "target", "emphasis": "primary", "description": "헤더를 믿고 인가한다.", "details": [ "로그인 시점 스냅샷" ], "evidence": [ { "start_line": 401, "end_line": 408 } ], "assumption": false } ], "edges": [ { "id": "a-n", "from": "attacker", "to": "nginx", "label": "위조 헤더", "kind": "request", "evidence": [ { "start_line": 393, "end_line": 400 } ], "assumption": false }, { "id": "n-p", "from": "nginx", "to": "proxy", "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 } ], "assumption": false } ], "legend": [], "metadata": { "rationale": "위조 경로와 정상 경로가 같은 헤더 이름을 쓴다는 것을 그렸다. 지우는 단계가 없으면 둘을 구별할 수 없다." } }