Files
document-haness/.playwright-mcp/page-2026-08-26T11-39-54-704Z.yml

474 lines
36 KiB
YAML

- generic [ref=f13e3]:
- link "본문으로 건너뛰기" [ref=f13e4] [cursor=pointer]:
- /url: "#main-content"
- banner [ref=f13e5]:
- generic [ref=f13e6]:
- link "TechLog Studio" [ref=f13e7] [cursor=pointer]:
- /url: /studio
- text: TechLog
- generic [ref=f13e8]: Studio
- navigation "Studio 주 탐색" [ref=f13e10]:
- link "작업본" [ref=f13e11] [cursor=pointer]:
- /url: /studio/documents
- link "게시 기록" [ref=f13e12] [cursor=pointer]:
- /url: /studio/publications
- link "새 문서" [ref=f13e13] [cursor=pointer]:
- /url: /studio/documents/new
- link "주제·프로젝트" [ref=f13e14] [cursor=pointer]:
- /url: /studio/taxonomy
- link "릴리즈" [ref=f13e15] [cursor=pointer]:
- /url: /studio/releases
- link "공개 사이트 보기" [ref=f13e16] [cursor=pointer]:
- /url: /
- button "로그아웃" [ref=f13e17]
- main [ref=f13e18]:
- generic [ref=f13e19]:
- generic [ref=f13e20]:
- region [ref=f13e21]:
- generic [ref=f13e22]:
- paragraph [ref=f13e23]: REFERENCE · VERSION 11
- heading "문서 편집" [level=1] [ref=f13e24]
- paragraph [ref=f13e25]: Forward-Auth에서 Identity Header를 신뢰하기 위한 조건
- region [ref=f13e26]:
- generic [ref=f13e27]:
- paragraph [ref=f13e28]: DOCUMENT
- heading "기본 정보" [level=2] [ref=f13e29]
- generic [ref=f13e30]:
- generic [ref=f13e31]:
- generic [ref=f13e32]: 제목
- textbox "제목" [ref=f13e33]: Forward-Auth에서 Identity Header를 신뢰하기 위한 조건
- generic [ref=f13e34]:
- generic [ref=f13e35]: slug
- textbox "slug" [ref=f13e36]:
- /placeholder: 비우면 제목에서 만듭니다 (영문 소문자·숫자·하이픈)
- text: forward-auth-identity-header-trust
- generic [ref=f13e37]:
- generic [ref=f13e38]: 요약
- textbox "요약" [ref=f13e39]: upstream이 사용자를 판단하는 근거가 헤더 하나뿐인 구조에서, 그 헤더를 믿을 수 있게 만드는 조건을 모았다. 외부 경로 차단, 동명 헤더 덮어쓰기, internal credential 검증이 서로 다른 곳에 함께 있어야 한다.
- generic [ref=f13e40]:
- generic [ref=f13e41]: Topic
- combobox "Topic" [ref=f13e42]:
- option "선택하지 않음"
- option "OAuth/OIDC 인증 경계" [selected]
- generic [ref=f13e43]:
- generic [ref=f13e44]: Project
- combobox "Project" [ref=f13e45]:
- option "미지정"
- option "Backend Clean Architecture"
- option "KeyCloak Patterns" [selected]
- option "Liner N + 1문제"
- group "관계" [ref=f13e46]:
- generic [ref=f13e48]:
- generic [ref=f13e49]:
- generic [ref=f13e50]: 관계 1 대상
- combobox "관계 1 대상" [ref=f13e51]:
- option "대상 선택"
- option "인증 구조를 보안 성숙도 단계로 취급하지 않는다"
- option "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가"
- option "Authorization Code Flow의 Endpoint와 Credential 이동 기준"
- option "BFF 인증 구조 설계 기준"
- option "BFF가 OAuth Token을 관리하는 조건"
- option "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가"
- option "Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정"
- option "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가" [disabled]
- option "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유" [selected]
- option "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건"
- option "외부 IdP Federation을 별도의 인증 구조로 세지 않는다"
- option "외부 IdP Federation과 Application 인증 경계"
- option "OAuth/OIDC 인증 패턴 선택 기준"
- option "OAuth Token과 Application Session을 구분하는 기준" [disabled]
- option "Public Client와 Confidential Client 구분 기준"
- option "Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출"
- option "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가"
- option "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계"
- generic [ref=f13e52]:
- generic [ref=f13e53]: 관계 1 이유
- textbox "관계 1 이유" [ref=f13e54]: 이 기준의 다섯 조건을 실제 설정에서 확인한 기록이다.
- generic [ref=f13e55]:
- button "위로" [disabled] [ref=f13e56]
- button "아래로" [ref=f13e57]
- button "삭제" [ref=f13e58]
- generic [ref=f13e59]:
- generic [ref=f13e60]:
- generic [ref=f13e61]: 관계 2 대상
- combobox "관계 2 대상" [ref=f13e62]:
- option "대상 선택"
- option "인증 구조를 보안 성숙도 단계로 취급하지 않는다"
- option "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가"
- option "Authorization Code Flow의 Endpoint와 Credential 이동 기준"
- option "BFF 인증 구조 설계 기준"
- option "BFF가 OAuth Token을 관리하는 조건"
- option "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가"
- option "Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정"
- option "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가" [selected]
- option "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유" [disabled]
- option "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건"
- option "외부 IdP Federation을 별도의 인증 구조로 세지 않는다"
- option "외부 IdP Federation과 Application 인증 경계"
- option "OAuth/OIDC 인증 패턴 선택 기준"
- option "OAuth Token과 Application Session을 구분하는 기준" [disabled]
- option "Public Client와 Confidential Client 구분 기준"
- option "Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출"
- option "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가"
- option "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계"
- generic [ref=f13e63]:
- generic [ref=f13e64]: 관계 2 이유
- textbox "관계 2 이유" [ref=f13e65]: 헤더를 어디까지 늘릴지가 이 기준의 미결 항목이다.
- generic [ref=f13e66]:
- button "위로" [ref=f13e67]
- button "아래로" [ref=f13e68]
- button "삭제" [ref=f13e69]
- generic [ref=f13e70]:
- generic [ref=f13e71]:
- generic [ref=f13e72]: 관계 3 대상
- combobox "관계 3 대상" [ref=f13e73]:
- option "대상 선택"
- option "인증 구조를 보안 성숙도 단계로 취급하지 않는다"
- option "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가"
- option "Authorization Code Flow의 Endpoint와 Credential 이동 기준"
- option "BFF 인증 구조 설계 기준"
- option "BFF가 OAuth Token을 관리하는 조건"
- option "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가"
- option "Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정"
- option "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가" [disabled]
- option "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유" [disabled]
- option "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건"
- option "외부 IdP Federation을 별도의 인증 구조로 세지 않는다"
- option "외부 IdP Federation과 Application 인증 경계"
- option "OAuth/OIDC 인증 패턴 선택 기준"
- option "OAuth Token과 Application Session을 구분하는 기준" [selected]
- option "Public Client와 Confidential Client 구분 기준"
- option "Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출"
- option "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가"
- option "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계"
- generic [ref=f13e74]:
- generic [ref=f13e75]: 관계 3 이유
- textbox "관계 3 이유" [ref=f13e76]: identity 헤더를 JWT나 session과 같은 이름으로 부르지 않는다.
- generic [ref=f13e77]:
- button "위로" [ref=f13e78]
- button "아래로" [disabled] [ref=f13e79]
- button "삭제" [ref=f13e80]
- button "관계 추가" [ref=f13e81]
- region [ref=f13e82]:
- generic [ref=f13e83]:
- paragraph [ref=f13e84]: REFERENCE
- heading "재사용할 기준" [level=2] [ref=f13e85]
- generic [ref=f13e86]:
- generic [ref=f13e87]: 목적
- textbox "목적" [ref=f13e88]: 외부 요청이 edge를 지나 인증되고 upstream으로 가는 구조에서, upstream이 사용자를 판단하는 근거는 헤더 하나다. 같은 이름의 헤더를 인증을 마친 edge가 만들 수도 있고 공격자가 요청에 직접 적어 보낼 수도 있다. upstream이 받는 요청에서 이 둘은 구분되지 않는다. identity header를 upstream에서 사용하려면 먼저 그 헤더가 edge를 통해 생성됐음을 보장하는 경로와 검증 방법을 정한다.
- group "규칙" [ref=f13e89]:
- generic [ref=f13e91]:
- generic [ref=f13e92]:
- generic [ref=f13e93]: 규칙 1 제목
- textbox "규칙 1 제목" [ref=f13e94]: 외부에서 upstream과 auth proxy에 직접 닿지 못하게 한다
- generic [ref=f13e95]:
- generic [ref=f13e96]: 규칙 1 본문
- textbox "규칙 1 본문" [ref=f13e97]: edge만 공개하고 나머지는 내부 network에 두면서 host port로 노출하지 않는다. 이걸 안 하면 공격자가 edge를 건너뛰고 upstream을 직접 부른다. 그때는 헤더를 아무리 검사해도 공격자가 그 헤더를 마음대로 쓸 수 있어서 의미가 없다.
- generic [ref=f13e98]:
- button "위로" [disabled] [ref=f13e99]
- button "아래로" [ref=f13e100]
- button "삭제" [ref=f13e101]
- generic [ref=f13e102]:
- generic [ref=f13e103]:
- generic [ref=f13e104]: 규칙 2 제목
- textbox "규칙 2 제목" [ref=f13e105]: client가 보낸 동명 헤더를 항상 덮어쓴다
- generic [ref=f13e106]:
- generic [ref=f13e107]: 규칙 2 본문
- textbox "규칙 2 본문" [ref=f13e108]: merge가 아니라 덮어쓰기로 채우고, 인증 결과에서 복사한 값만 upstream으로 보낸다. merge로 두면 client가 보낸 값이 앞이나 뒤에 함께 붙고, 어느 쪽을 읽을지는 upstream 구현에 달려 있다. trusted proxy 범위도 같이 좁힌다. 넓게 잡으면 같은 network 안의 다른 workload가 edge인 척할 수 있고, forwarded 계열 헤더를 믿는 설정에서는 그 범위가 곧 신뢰 경계다.
- generic [ref=f13e109]:
- button "위로" [ref=f13e110]
- button "아래로" [ref=f13e111]
- button "삭제" [ref=f13e112]
- generic [ref=f13e113]:
- generic [ref=f13e114]:
- generic [ref=f13e115]: 규칙 3 제목
- textbox "규칙 3 제목" [ref=f13e116]: auth endpoint는 subrequest 전용으로 둔다
- generic [ref=f13e117]:
- generic [ref=f13e118]: 규칙 3 본문
- textbox "규칙 3 본문" [ref=f13e119]: "이 endpoint는 외부 client가 쓰라고 만든 것이 아니다. proxy가 만드는 subrequest만 들어가게 하고 외부 호출에는 응답하지 않게 둔다. Nginx라면 `internal` location이 그 역할을 한다."
- generic [ref=f13e120]:
- button "위로" [ref=f13e121]
- button "아래로" [ref=f13e122]
- button "삭제" [ref=f13e123]
- generic [ref=f13e124]:
- generic [ref=f13e125]:
- generic [ref=f13e126]: 규칙 4 제목
- textbox "규칙 4 제목" [ref=f13e127]: upstream이 헤더 존재만 보지 않는다
- generic [ref=f13e128]:
- generic [ref=f13e129]: 규칙 4 본문
- textbox "규칙 4 본문" [ref=f13e130]: 배포 시 주입한 internal credential과 요청 값을 비교한다. 비교 구현은 입력값의 일치 길이에 따라 실행 시간이 크게 달라지지 않는 방식을 사용한다. internal credential 검증을 controller마다 반복하면 새 endpoint에서 누락될 수 있다. 운영에서는 filter, interceptor, security chain 등 공통 처리 경로에 적용한다.
- generic [ref=f13e131]:
- button "위로" [ref=f13e132]
- button "아래로" [ref=f13e133]
- button "삭제" [ref=f13e134]
- generic [ref=f13e135]:
- generic [ref=f13e136]:
- generic [ref=f13e137]: 규칙 5 제목
- textbox "규칙 5 제목" [ref=f13e138]: Network 격리와 헤더 검증을 모두 적용한다
- generic [ref=f13e139]:
- generic [ref=f13e140]: 규칙 5 본문
- textbox "규칙 5 본문" [ref=f13e141]: 격리는 밖에서 들어오는 직접 접근을 막고 헤더 검증은 안에서 만들어진 위조를 막는다. 막는 대상이 달라서 하나로 다른 하나를 대체했다고 쓸 수 없다.
- generic [ref=f13e142]:
- button "위로" [ref=f13e143]
- button "아래로" [ref=f13e144]
- button "삭제" [ref=f13e145]
- generic [ref=f13e146]:
- generic [ref=f13e147]:
- generic [ref=f13e148]: 규칙 6 제목
- textbox "규칙 6 제목" [ref=f13e149]: 전달할 헤더를 allowlist로 고정한다
- generic [ref=f13e150]:
- generic [ref=f13e151]: 규칙 6 본문
- textbox "규칙 6 본문" [ref=f13e152]: 복사할 응답 헤더 목록을 정해 두고 그 밖은 버린다. 늘릴 때마다 claim 출처와 다중 값 구분자, escaping, 최대 크기, upstream 검증 계약을 다시 정해야 한다. user와 email만 전달하는 구조는 누가 왔는지만 말하고 무엇을 해도 되는지는 말하지 않는다. role이 바뀌었을 때 proxy session과 downstream 인가가 언제 따라가는지도 따로 정한다.
- generic [ref=f13e153]:
- button "위로" [ref=f13e154]
- button "아래로" [ref=f13e155]
- button "삭제" [ref=f13e156]
- generic [ref=f13e157]:
- generic [ref=f13e158]:
- generic [ref=f13e159]: 규칙 7 제목
- textbox "규칙 7 제목" [ref=f13e160]: 검사 지점은 요청 실패가 아니라 응답의 사용자다
- generic [ref=f13e161]:
- generic [ref=f13e162]: 규칙 7 본문
- textbox "규칙 7 본문" [ref=f13e163]: 위조 헤더를 얹은 정상 session 요청은 정상 session이니 200이 되는 것이 맞다. 확인할 값은 그 응답의 사용자가 위조 값인지 실제 인증된 사용자인지다. 요청이 실패하는지만 보면 덮어쓰기가 동작하는지 알 수 없다.
- generic [ref=f13e164]:
- button "위로" [ref=f13e165]
- button "아래로" [ref=f13e166]
- button "삭제" [ref=f13e167]
- generic [ref=f13e168]:
- generic [ref=f13e169]:
- generic [ref=f13e170]: 규칙 8 제목
- textbox "규칙 8 제목" [ref=f13e171]: 지금 확인한 것과 운영에서 더 필요한 것을 나눠 적는다
- generic [ref=f13e172]:
- generic [ref=f13e173]: 규칙 8 본문
- textbox "규칙 8 본문" [ref=f13e174]: 이 기준에서 실제 fixture로 확인한 것은 외부 경로 차단, 헤더 덮어쓰기, auth endpoint 내부 전용 지정, upstream의 internal credential 확인이다. 운영에서는 여기에 더 필요하다. 공유 secret을 secret manager에서 주입하고 교체 절차를 두는 것, network policy로 경로를 강제하는 것, 그리고 더 강하게 묶으려면 mTLS나 workload identity를 쓰는 것이다. 두 묶음을 같은 문단에 섞어 적지 않는다.
- generic [ref=f13e175]:
- button "위로" [ref=f13e176]
- button "아래로" [disabled] [ref=f13e177]
- button "삭제" [ref=f13e178]
- button "규칙 추가" [ref=f13e179]
- group "적용 조건" [ref=f13e180]:
- generic [ref=f13e182]:
- generic [ref=f13e183]:
- generic [ref=f13e184]: 적용 조건 1
- textbox "적용 조건 1" [ref=f13e185]: upstream에 OAuth client나 JWT 검증 코드를 넣기 어려울 때
- generic [ref=f13e186]:
- button "위로" [disabled] [ref=f13e187]
- button "아래로" [ref=f13e188]
- button "삭제" [ref=f13e189]
- generic [ref=f13e190]:
- generic [ref=f13e191]:
- generic [ref=f13e192]: 적용 조건 2
- textbox "적용 조건 2" [ref=f13e193]: 여러 legacy service 앞에 같은 로그인 정책을 둘 때
- generic [ref=f13e194]:
- button "위로" [ref=f13e195]
- button "아래로" [ref=f13e196]
- button "삭제" [ref=f13e197]
- generic [ref=f13e198]:
- generic [ref=f13e199]:
- generic [ref=f13e200]: 적용 조건 3
- textbox "적용 조건 3" [ref=f13e201]: edge에서 정책을 강제할 수 있을 때
- generic [ref=f13e202]:
- button "위로" [ref=f13e203]
- button "아래로" [ref=f13e204]
- button "삭제" [ref=f13e205]
- generic [ref=f13e206]:
- generic [ref=f13e207]:
- generic [ref=f13e208]: 적용 조건 4
- textbox "적용 조건 4" [ref=f13e209]: 이미 forward-auth를 쓰고 있는 구조를 점검할 때
- generic [ref=f13e210]:
- button "위로" [ref=f13e211]
- button "아래로" [disabled] [ref=f13e212]
- button "삭제" [ref=f13e213]
- button "적용 조건 추가" [ref=f13e214]
- group "예외" [ref=f13e215]:
- generic [ref=f13e217]:
- generic [ref=f13e218]:
- generic [ref=f13e219]: 예외 1
- textbox "예외 1" [ref=f13e220]: backend 직접 경로나 헤더 덮어쓰기를 닫을 수 없는 환경이면 이 구조를 쓰지 않는다.
- generic [ref=f13e221]:
- button "위로" [disabled] [ref=f13e222]
- button "아래로" [ref=f13e223]
- button "삭제" [ref=f13e224]
- generic [ref=f13e225]:
- generic [ref=f13e226]:
- generic [ref=f13e227]: 예외 2
- textbox "예외 2" [ref=f13e228]: 애플리케이션이 사용자별 API 조합과 세밀한 인가를 직접 맡아야 하면 BFF 구조가 더 자연스럽다.
- generic [ref=f13e229]:
- button "위로" [ref=f13e230]
- button "아래로" [ref=f13e231]
- button "삭제" [ref=f13e232]
- generic [ref=f13e233]:
- generic [ref=f13e234]:
- generic [ref=f13e235]: 예외 3
- textbox "예외 3" [ref=f13e236]: 임의 경로와 body, streaming을 그대로 넘기는 범용 reverse proxy가 필요하면 URI rewrite와 timeout, 응답 헤더 처리를 따로 설계해야 한다.
- generic [ref=f13e237]:
- button "위로" [ref=f13e238]
- button "아래로" [disabled] [ref=f13e239]
- button "삭제" [ref=f13e240]
- button "예외 추가" [ref=f13e241]
- group "예시" [ref=f13e242]:
- generic [ref=f13e244]:
- generic [ref=f13e245]:
- generic [ref=f13e246]: 예시 1
- textbox "예시 1" [ref=f13e247]: 외부에는 edge만 공개하고 app과 auth proxy의 port는 host에 publish하지 않는다
- generic [ref=f13e248]:
- button "위로" [disabled] [ref=f13e249]
- button "아래로" [ref=f13e250]
- button "삭제" [ref=f13e251]
- generic [ref=f13e252]:
- generic [ref=f13e253]:
- generic [ref=f13e254]: 예시 2
- textbox "예시 2" [ref=f13e255]: 정상 session에 위조 헤더를 얹은 요청은 200을 받지만 응답의 사용자는 실제 사용자다
- generic [ref=f13e256]:
- button "위로" [ref=f13e257]
- button "아래로" [ref=f13e258]
- button "삭제" [ref=f13e259]
- generic [ref=f13e260]:
- generic [ref=f13e261]:
- generic [ref=f13e262]: 예시 3
- textbox "예시 3" [ref=f13e263]: 외부에서 auth endpoint를 직접 부르면 404가 된다
- generic [ref=f13e264]:
- button "위로" [ref=f13e265]
- button "아래로" [ref=f13e266]
- button "삭제" [ref=f13e267]
- generic [ref=f13e268]:
- generic [ref=f13e269]:
- generic [ref=f13e270]: 예시 4
- textbox "예시 4" [ref=f13e271]: upstream은 user 헤더와 internal token을 함께 확인하고 하나라도 어긋나면 401을 돌려준다
- generic [ref=f13e272]:
- button "위로" [ref=f13e273]
- button "아래로" [ref=f13e274]
- button "삭제" [ref=f13e275]
- generic [ref=f13e276]:
- generic [ref=f13e277]:
- generic [ref=f13e278]: 예시 5
- textbox "예시 5" [ref=f13e279]: 내부 검사가 controller 하나에만 있으면 새 endpoint에는 보호가 따라오지 않는다
- generic [ref=f13e280]:
- button "위로" [ref=f13e281]
- button "아래로" [disabled] [ref=f13e282]
- button "삭제" [ref=f13e283]
- button "예시 추가" [ref=f13e284]
- generic [ref=f13e285]:
- generic [ref=f13e286]: 마지막 검증일
- textbox "마지막 검증일" [ref=f13e287]
- region [ref=f13e288]:
- generic [ref=f13e289]:
- paragraph [ref=f13e290]: LIVE
- heading "즉시 미리보기" [level=2] [ref=f13e291]
- generic [ref=f13e294]:
- generic [ref=f13e295]:
- navigation "문서 경로" [ref=f13e296]:
- link "Reference" [ref=f13e297] [cursor=pointer]:
- /url: /explore/references
- generic [ref=f13e298]: /
- generic [ref=f13e299]: OAuth/OIDC 인증 경계
- generic [ref=f13e300]: /
- link "KeyCloak Patterns" [ref=f13e301] [cursor=pointer]:
- /url: /projects/keycloak-patterns
- heading "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건" [level=1] [ref=f13e302]
- paragraph [ref=f13e303]: upstream이 사용자를 판단하는 근거가 헤더 하나뿐인 구조에서, 그 헤더를 믿을 수 있게 만드는 조건을 모았다. 외부 경로 차단, 동명 헤더 덮어쓰기, internal credential 검증이 서로 다른 곳에 함께 있어야 한다.
- generic [ref=f13e304]:
- generic [ref=f13e305]:
- term [ref=f13e306]: 유형
- definition [ref=f13e307]: Reference
- generic [ref=f13e308]:
- term [ref=f13e309]: 프로젝트
- definition [ref=f13e310]: KeyCloak Patterns
- generic [ref=f13e311]:
- term [ref=f13e312]: 게시
- definition [ref=f13e313]: 게시 전
- region [ref=f13e314]:
- paragraph [ref=f13e315]: Purpose
- heading "이 기준을 쓰는 이유" [level=2] [ref=f13e316]
- paragraph [ref=f13e317]: 외부 요청이 edge를 지나 인증되고 upstream으로 가는 구조에서, upstream이 사용자를 판단하는 근거는 헤더 하나다.같은 이름의 헤더를 인증을 마친 edge가 만들 수도 있고 공격자가 요청에 직접 적어 보낼 수도 있다. upstream이 받는 요청에서 이 둘은 구분되지 않는다.identity header를 upstream에서 사용하려면 먼저 그 헤더가 edge를 통해 생성됐음을 보장하는 경로와 검증 방법을 정한다.
- article [ref=f13e318]:
- region [ref=f13e319]:
- heading "판단 기준" [level=2] [ref=f13e320]
- list [ref=f13e321]:
- listitem [ref=f13e322]:
- generic [ref=f13e323]: "01"
- generic [ref=f13e324]:
- heading "외부에서 upstream과 auth proxy에 직접 닿지 못하게 한다" [level=3] [ref=f13e325]
- paragraph [ref=f13e326]: edge만 공개하고 나머지는 내부 network에 두면서 host port로 노출하지 않는다. 이걸 안 하면 공격자가 edge를 건너뛰고 upstream을 직접 부른다. 그때는 헤더를 아무리 검사해도 공격자가 그 헤더를 마음대로 쓸 수 있어서 의미가 없다.
- listitem [ref=f13e327]:
- generic [ref=f13e328]: "02"
- generic [ref=f13e329]:
- heading "client가 보낸 동명 헤더를 항상 덮어쓴다" [level=3] [ref=f13e330]
- paragraph [ref=f13e331]: merge가 아니라 덮어쓰기로 채우고, 인증 결과에서 복사한 값만 upstream으로 보낸다. merge로 두면 client가 보낸 값이 앞이나 뒤에 함께 붙고, 어느 쪽을 읽을지는 upstream 구현에 달려 있다. trusted proxy 범위도 같이 좁힌다. 넓게 잡으면 같은 network 안의 다른 workload가 edge인 척할 수 있고, forwarded 계열 헤더를 믿는 설정에서는 그 범위가 곧 신뢰 경계다.
- listitem [ref=f13e332]:
- generic [ref=f13e333]: "03"
- generic [ref=f13e334]:
- heading "auth endpoint는 subrequest 전용으로 둔다" [level=3] [ref=f13e335]
- paragraph [ref=f13e336]: "이 endpoint는 외부 client가 쓰라고 만든 것이 아니다. proxy가 만드는 subrequest만 들어가게 하고 외부 호출에는 응답하지 않게 둔다. Nginx라면 `internal` location이 그 역할을 한다."
- listitem [ref=f13e337]:
- generic [ref=f13e338]: "04"
- generic [ref=f13e339]:
- heading "upstream이 헤더 존재만 보지 않는다" [level=3] [ref=f13e340]
- paragraph [ref=f13e341]: 배포 시 주입한 internal credential과 요청 값을 비교한다. 비교 구현은 입력값의 일치 길이에 따라 실행 시간이 크게 달라지지 않는 방식을 사용한다. internal credential 검증을 controller마다 반복하면 새 endpoint에서 누락될 수 있다. 운영에서는 filter, interceptor, security chain 등 공통 처리 경로에 적용한다.
- listitem [ref=f13e342]:
- generic [ref=f13e343]: "05"
- generic [ref=f13e344]:
- heading "Network 격리와 헤더 검증을 모두 적용한다" [level=3] [ref=f13e345]
- paragraph [ref=f13e346]: 격리는 밖에서 들어오는 직접 접근을 막고 헤더 검증은 안에서 만들어진 위조를 막는다. 막는 대상이 달라서 하나로 다른 하나를 대체했다고 쓸 수 없다.
- listitem [ref=f13e347]:
- generic [ref=f13e348]: "06"
- generic [ref=f13e349]:
- heading "전달할 헤더를 allowlist로 고정한다" [level=3] [ref=f13e350]
- paragraph [ref=f13e351]: 복사할 응답 헤더 목록을 정해 두고 그 밖은 버린다. 늘릴 때마다 claim 출처와 다중 값 구분자, escaping, 최대 크기, upstream 검증 계약을 다시 정해야 한다. user와 email만 전달하는 구조는 누가 왔는지만 말하고 무엇을 해도 되는지는 말하지 않는다. role이 바뀌었을 때 proxy session과 downstream 인가가 언제 따라가는지도 따로 정한다.
- listitem [ref=f13e352]:
- generic [ref=f13e353]: "07"
- generic [ref=f13e354]:
- heading "검사 지점은 요청 실패가 아니라 응답의 사용자다" [level=3] [ref=f13e355]
- paragraph [ref=f13e356]: 위조 헤더를 얹은 정상 session 요청은 정상 session이니 200이 되는 것이 맞다. 확인할 값은 그 응답의 사용자가 위조 값인지 실제 인증된 사용자인지다. 요청이 실패하는지만 보면 덮어쓰기가 동작하는지 알 수 없다.
- listitem [ref=f13e357]:
- generic [ref=f13e358]: "08"
- generic [ref=f13e359]:
- heading "지금 확인한 것과 운영에서 더 필요한 것을 나눠 적는다" [level=3] [ref=f13e360]
- paragraph [ref=f13e361]: 이 기준에서 실제 fixture로 확인한 것은 외부 경로 차단, 헤더 덮어쓰기, auth endpoint 내부 전용 지정, upstream의 internal credential 확인이다. 운영에서는 여기에 더 필요하다. 공유 secret을 secret manager에서 주입하고 교체 절차를 두는 것, network policy로 경로를 강제하는 것, 그리고 더 강하게 묶으려면 mTLS나 workload identity를 쓰는 것이다. 두 묶음을 같은 문단에 섞어 적지 않는다.
- region [ref=f13e362]:
- heading "적용할 때" [level=2] [ref=f13e363]
- list [ref=f13e364]:
- listitem [ref=f13e365]: upstream에 OAuth client나 JWT 검증 코드를 넣기 어려울 때
- listitem [ref=f13e366]: 여러 legacy service 앞에 같은 로그인 정책을 둘 때
- listitem [ref=f13e367]: edge에서 정책을 강제할 수 있을 때
- listitem [ref=f13e368]: 이미 forward-auth를 쓰고 있는 구조를 점검할 때
- region [ref=f13e369]:
- heading "예외와 주의" [level=2] [ref=f13e370]
- list [ref=f13e371]:
- listitem [ref=f13e372]: backend 직접 경로나 헤더 덮어쓰기를 닫을 수 없는 환경이면 이 구조를 쓰지 않는다.
- listitem [ref=f13e373]: 애플리케이션이 사용자별 API 조합과 세밀한 인가를 직접 맡아야 하면 BFF 구조가 더 자연스럽다.
- listitem [ref=f13e374]: 임의 경로와 body, streaming을 그대로 넘기는 범용 reverse proxy가 필요하면 URI rewrite와 timeout, 응답 헤더 처리를 따로 설계해야 한다.
- region [ref=f13e375]:
- heading "예시" [level=2] [ref=f13e376]
- list [ref=f13e377]:
- listitem [ref=f13e378]: 외부에는 edge만 공개하고 app과 auth proxy의 port는 host에 publish하지 않는다
- listitem [ref=f13e379]: 정상 session에 위조 헤더를 얹은 요청은 200을 받지만 응답의 사용자는 실제 사용자다
- listitem [ref=f13e380]: 외부에서 auth endpoint를 직접 부르면 404가 된다
- listitem [ref=f13e381]: upstream은 user 헤더와 internal token을 함께 확인하고 하나라도 어긋나면 401을 돌려준다
- listitem [ref=f13e382]: 내부 검사가 controller 하나에만 있으면 새 endpoint에는 보호가 따라오지 않는다
- paragraph [ref=f13e383]: 마지막 검증
- region [ref=f13e384]:
- paragraph [ref=f13e385]: Relations
- heading "이 기록과 연결된 맥락" [level=2] [ref=f13e386]
- list [ref=f13e387]:
- listitem [ref=f13e388]:
- link "이 기준의 다섯 조건을 실제 설정에서 확인한 기록이다. Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유" [ref=f13e389] [cursor=pointer]:
- /url: /cases/identity-header-trust
- generic [ref=f13e390]: 이 기준의 다섯 조건을 실제 설정에서 확인한 기록이다.
- strong [ref=f13e391]: Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유
- generic [ref=f13e392]:
- complementary [ref=f13e393]:
- heading "작업 상태" [level=2] [ref=f13e394]
- status "편집 상태" [ref=f13e395]: 저장됨
- generic [ref=f13e396]:
- generic [ref=f13e397]:
- term [ref=f13e398]: 저장 버전
- definition [ref=f13e399]: "11"
- generic [ref=f13e400]:
- term [ref=f13e401]: 종류
- definition [ref=f13e402]: REFERENCE
- paragraph [ref=f13e403]: 불완전한 초안도 저장할 수 있습니다. Ctrl+S 로도 저장합니다. 게시를 누르면 채워야 할 칸을 그 자리에 표시합니다.
- generic [ref=f13e404]:
- button "저장" [disabled] [ref=f13e405]
- button "게시" [ref=f13e406]
- paragraph [ref=f13e407]: 버전 11으로 저장했습니다.