Files
document-haness/docs/keycloak-session-store/tech-log-studio/trust-handed-over-at-the-edge/reference/reference-clear-the-header-before-you-trust-it.md
T
DongHyeonkaandClaude Opus 5 2109f726fe feat(pipeline): keycloak-session-store 25편·virtualization 59편을 S3→S5→S6 으로 돌린다
기록 84편을 계약 에이전트로 다시 썼다. 기존 71편(kss 25 · virt 46)과, 계약에만
있고 안 쓰여 있던 새 글감 13편이다. 원장 84개를 열어 단계마다 스킬 영수증과 관문
종료 코드를 적었고 verify-pipeline-run.py 가 error 0 으로 닫는다.

SSOT 결함 둘을 고쳤다.

- kss 의 `약 58일` 이 반입 중 `약 59일` 로 바뀌어 있었다. 원 증거 파일이
  「남은 일수: 88일 … 실제 갱신까지 약 58일」로 산수를 직접 적는다. D-4a 쪽
  `약 59일` 은 강제 갱신 뒤(`VALID: 89 days`)라 맞는 값이라 그대로 뒀다.
- virt §198 의 `11.6GB` 는 §178 의 원 측정 `Mem: 11648`(MiB)과 어긋나는데
  원 가이드의 표기 그대로라 고치지 않고 쓰이는 자리에 대조를 적었다.

기록의 수치 오류 셋을 고쳤다 — CASE 요약의 「게스트 셋에 8240MB」(5120+3120 은
둘이다), k3s 편이 같은 것을 여섯·일곱·여덟로 세던 것, no-docker 편의 「셋을 더
든다」(§281 의 표는 네 행이고 디스크 행이 빠져 있었다).

계약을 셋 고쳤다.

- kss 의 sourceRepository 리비전이 cdac9b8 이었는데 그 커밋에는 docs/guides/**
  28개가 아예 없다. 9465582b 로 바꾸고, 반입한 바이트가 어느 커밋과도 같지 않다는
  것을 측정값과 함께 적었다 — 반입은 커밋이 아니라 그 시점의 작업 트리에서 떠 온
  것이다(kss 297/306 · virt 12/14 가 작업 트리와 같고, 200 커밋을 거슬러 전수
  대조했을 때 가장 가까운 커밋도 28개가 어긋났다).
- virt 계약이 「2026-09-11 재배분」이라고 적는데 SSOT 는 재배분 날짜를 적지 않고
  재배분 뒤 값은 이미 2026-09-10 측정에 찍혀 있다.
- kss 후보 대장이 지나친 절 아홉에 처분을 적었다(warn 9 → 0). 새 글감은 0건이고
  넷은 앵커가 h3 슬러그의 접두가 아니라 중간 토막이라 검사기가 못 본 것이었다.

style_profile.mjs 의 결함 둘을 고쳤다 — frontmatter 가 문장으로 세어져
(실측 398자짜리 「문장」 하나) 평균 길이를 기준 안으로 밀어 올리고 있었고,
engPerSent 의 분자는 목록을 포함한 글에서, 분모는 목록을 걷어낸 글에서 세고
있었다(Question 기록에서 11.94 → 3.86).

verify-pipeline.py 전 항목 PASS · error 0 · unittest 334건 OK.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 11:01:55 +09:00

5.6 KiB

kind, slug, title, topic, topicName, project, status, sourceRevision, source, evidence
kind slug title topic topicName project status sourceRevision source evidence
REFERENCE clear-the-header-before-you-trust-it 믿기 전에 그 헤더를 먼저 지운다 trust-handed-over-at-the-edge 위조 신원 헤더와 로그아웃 전파 keycloak-session-store 게시 전 cdac9b8178391311d8eca1ebc6cac15bb62d79af
final/document.md#선택이-코드와-흐름에-반영되는-방식-b4
final/document.md#얻은-것-잃은-것-적용하지-않을-때-열린-질문-네-개에-대한-답
../../../final/evidence/raw/b4-edge-authorization__01-header-handling.txt
../../../final/evidence/raw/followup__03-b4-role-propagation.txt

믿기 전에 그 헤더를 먼저 지운다

엣지가 넣어 준 신원 헤더는 업스트림으로 넘기기 전에 그 프록시에서 직접 지운다. nginx 는 proxy_set_header 로 자기가 설정한 이름만 덮어써서, 밖에서 붙인 같은 이름의 헤더가 permitAll 경로의 앱까지 그대로 도착했다. 지우기의 효과는 이 실험대가 재지 않았다.

관계

  • nginx 는 자기가 설정하지 않은 헤더를 덮어쓰지 않았다 이 기준이 나온 실험이다. 위조 헤더가 어디까지 도착했고 어디서 401 이 됐는지가 거기 있다.
  • 쿠키에 세션을 담으면 지울 대상을 잃는다 — TTL 로 되찾은 고아 세션 엣지가 로그인 때 만든 세션을 다루는 쪽이다. IdP 에서 바꾼 값이 늦게 온 것도 그 세션을 지워 재인증시키기 전까지였다.

목적

엣지가 인증을 대신하고 그 결과를 헤더로 업스트림에 넘기는 구성에서, 업스트림이 받은 헤더의 출처를 구별하지 못한 채 인가에 쓰는 것을 막는다. 위조 경로와 정상 경로가 같은 헤더 이름을 쓰므로, 지우는 단계가 없으면 업스트림에는 같은 값으로 보인다.

프록시가 동명 헤더를 알아서 덮어쓸 것이라는 전제부터 틀렸다. 이 실험대는 nginx 한 겹에서 그 전제가 깨지는 것을 봤고, 나머지 프록시는 확인하지 못했다.

규칙

1. 엣지가 넣는 신원 헤더는 업스트림으로 넘기기 전에 지운다

프록시 설정에서 그 헤더 이름을 빈 값으로 덮어쓰는 줄을 먼저 둔다. 원본 가이드가 적은 형태는 proxy_set_header X-Auth-Request-Roles "" 다.

이 실험대는 그 수정을 적용한 적이 없다. 적용한 뒤 위조가 막히는지도 재지 않았고, 원본 가이드가 「아래는 미검증이며, 적용하려면 랩 호스트에서 사람이 직접 친다」로 못박고 있다. 검증된 완화책이 아니라 아직 확인하지 않은 처방으로 읽는다.

2. 그 프록시가 무엇을 덮어쓰는지 설정에서 읽고, 밖에서 한 번 보내 본다

랩 호스트의 nginx 설정에 있던 것은 Host · X-Forwarded-Host · X-Forwarded-Proto · X-Forwarded-Port · X-Forwarded-For · X-Real-IP 여섯 줄이고 X-Auth-Request-* 는 없었다. 설정에 있는 이름은 덮어쓰고, 없는 이름은 클라이언트가 보낸 값이 그대로 업스트림까지 간다.

설정을 읽는 것만으로는 끝나지 않는다. 밖에서 같은 이름을 붙여 보내 업스트림에 무엇이 도착하는지 보고, 그 전에 아무것도 안 붙인 요청을 한 번 찍어 둔다. 그 대조가 없으면 원래 있던 값과 내가 넣은 값이 구별되지 않는다.

3. 헤더가 도착한 것과 인가가 뚫린 것을 같은 사건으로 세지 않는다

아무 인증 없이 보낸 신원 헤더 셋은 검증 없이 그대로 도착했다. 같은 헤더로 토큰을 요구하는 경로를 찔렀을 때는 401 이 왔다.

/api/echo : HTTP 200 (permitAll) /api/me : HTTP 401 /api/protected : HTTP 401

헤더가 도착했다는 사실만으로 인가가 뚫렸다고 세지 않는다. 위험은 헤더만 읽어 인가하는 앱이 그 뒤에 있을 때 생긴다.

4. IdP 에서 클레임을 바꿔도 이미 로그인한 요청에는 옛 값이 간다

IdP 에서 값을 바꾼 뒤 12회 · 약 6.4초 동안 옛 값이 갔고, Redis 세션을 지워 재인증시킨 뒤에야 새 값이 왔다. 요청 횟수로는 반영되지 않는다. 바뀐 값이 곧바로 적용돼야 하면 세션을 지워 재인증시키는 경로를 미리 정해 둔다.

적용 조건

  • 엣지가 인증을 대신하고 그 결과를 헤더로 업스트림에 넘기는 구성
  • 확인한 프록시 : host nginx 한 겹
  • 같은 체인의 k3s Traefik : 헤더 처리 미측정
  • 다른 프록시 : 이 결과를 옮기기 전에 그 프록시에서 같은 확인을 다시 한다

예외

  • 업스트림이 헤더가 아니라 서명된 토큰을 검증하면 이 지우기가 필요 없다. 토큰을 요구하는 두 경로는 같은 위조 헤더에 401 로 답했다.
  • 위조를 잰 경로는 permitAll 인 echo 앱이라 oauth2-proxy 를 거치지 않았다. nginx 와 oauth2-proxy 를 다 지난 요청에 무엇이 도착하는지는 이 측정에 없다.
  • 쿠키가 만료되면 옛 클레임이 씻기는지는 재지 않았다. 만료를 기다려 본 적이 없고, --cookie-refresh 가 준다는 최대 지연도 설정의 정의일 뿐 이 실험대에서 잰 값이 아니다.

예시

  • 밖에서 아무 인증 없이 붙인 x-auth-request-user · x-auth-request-email · x-auth-request-roles 가 그대로 앱에 도착했다.
  • 같은 이름의 헤더를 두 개 보내면 덮어쓰지도 합치지도 않고 admin 과 editor 가 둘 다 도착했다.
  • IdP 에서 이메일 클레임을 바꾼 뒤 12회를 더 보냈는데 전부 옛 값이었고, 세션을 지운 뒤 보낸 3회는 새 값이었다.