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