The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
292 lines
19 KiB
Markdown
292 lines
19 KiB
Markdown
---
|
|
id: a0e1cc05-92b3-4dac-bce1-513ab8cd862b
|
|
kind: CASE
|
|
slug: identity-header-trust
|
|
title: Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유
|
|
topic: oauth-oidc-auth-boundary
|
|
topicName: OAuth/OIDC 인증 경계
|
|
project: KeyCloak Patterns
|
|
status: 게시 중
|
|
version: 39
|
|
verifiedOn: 2026-08-25
|
|
studio: "https://hyeonworks.com/studio/documents/a0e1cc05-92b3-4dac-bce1-513ab8cd862b/edit"
|
|
public: "https://hyeonworks.com/cases/identity-header-trust"
|
|
assets:
|
|
- key: ap4-edge-trust-1cff2399
|
|
file: ../../../final/assets/tech-log-studio/ap4-edge-trust.svg
|
|
---
|
|
|
|
# Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유
|
|
|
|
X-Auth-Request-User는 인증을 마친 edge가 만들 수도 있고 공격자가 직접 적어 보낼 수도 있다.
|
|
upstream이 받는 요청에서는 두 경우의 모양이 같다.
|
|
그래서 header overwrite, backend direct path 차단, internal credential 검증을 서로 독립된 세 곳에 둔다.
|
|
|
|
## 관계
|
|
|
|
- **Forward-Auth에서 Identity Header를 신뢰하기 위한 조건**
|
|
이 기준의 다섯 조건이 실제로 어떻게 구성되는지 코드와 설정으로 확인한 자리다.
|
|
- **OAuth Token과 Application Session을 구분하는 기준**
|
|
proxy session cookie와 identity 헤더를 JWT와 구분해야 하는 실례다.
|
|
- **OAuth/OIDC 인증 패턴 선택 기준**
|
|
OAuth를 모르는 upstream 앞의 공통 관문을 얻고 network·헤더 신뢰 계약을 내주는 경우다.
|
|
- **Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가**
|
|
edge가 user와 email만 전달한다는 사실이 이 질문의 출발점이다.
|
|
|
|
## 문제
|
|
|
|
앞단 proxy가 로그인을 맡으면 upstream은 OAuth를 몰라도 된다.
|
|
|
|
대신 upstream은 X-Auth-Request-User 하나로 사용자를 판단하게 된다.
|
|
이 헤더는 인증을 마친 edge가 만들 수도 있고 공격자가 요청에 직접 적어 보낼 수도 있다.
|
|
upstream이 받는 요청에서는 둘이 구분되지 않는다는 점이 문제가 된다.
|
|
|
|
backend port가 외부에 열려 있거나 Nginx가 브라우저의 헤더를 그대로 넘기면
|
|
공격자가 인증된 사용자처럼 보낼 수 있다.
|
|
|
|
그래서 이 구조의 문제는 upstream이 X-Auth-Request-User의 출처를 구분할 수 없다는 점이다.
|
|
|
|
## 결론
|
|
|
|
헤더를 믿으려면 서로 독립된 세 곳에서 막아야 한다.
|
|
|
|
host port 닫힘 : 외부에서 upstream·proxy로 바로 가는 경로를 막는다
|
|
Nginx header 덮어쓰기 : client가 보낸 동명 헤더를 merge하지 않고 덮어쓴다
|
|
upstream internal token : edge를 거치지 않은 내부 요청을 막는다
|
|
|
|
network isolation만으로는 내부 workload나 잘못된 proxy 헤더가 신뢰되는 문제를 막지 못한다.
|
|
controller의 공유 token만으로는 외부 직접 접근이 어려워지는 network 속성을 대신할 수 없다.
|
|
|
|
## 검증 환경
|
|
|
|
Keycloak 26.7.0, oauth2-proxy 7.15.2
|
|
|
|
client : edge-proxy
|
|
confidential, PKCE S256 : o
|
|
|
|
외부 공개
|
|
Nginx : 8088
|
|
app 8081, oauth2-proxy 4180 : Compose network에 expose만, host publish x
|
|
|
|
Nginx
|
|
auth_request /oauth2/auth
|
|
location = /oauth2/auth : internal
|
|
auth_request_set으로 user, email, Set-Cookie 복사
|
|
client 제공 동명 헤더 : 덮어쓰기
|
|
trusted proxy : 단일 IP
|
|
|
|
upstream
|
|
EdgeIdentityController.currentUser(HttpServletRequest)
|
|
X-Internal-Auth-Token 비교 : MessageDigest.isEqual
|
|
SecurityConfig의 /edge/** : permitAll
|
|
|
|
AP4_SESSION
|
|
HttpOnly : true
|
|
SameSite : Lax
|
|
Secure : false in local HTTP fixture
|
|
expire : 1 hour in proxy configuration
|
|
session-cookie-minimal : true
|
|
|
|
server-side session store : x
|
|
automatic discovery : x
|
|
login, token, JWKS, userinfo URL을 각각 관리.
|
|
|
|
HTTP : o
|
|
|
|
## 재현 조건
|
|
|
|
1. cookie 없이 GET /를 부르면 /oauth2/start로 302가 되는지 확인.
|
|
|
|
2. cookie 없이 GET /api/edge를 부르면 Location 없는 401이 되는지 확인.
|
|
|
|
3. authorization request에 client_id=edge-proxy와 code_challenge_method=S256이 있는지 확인.
|
|
|
|
4. 로그인 뒤 cookie가 AP4_SESSION이며 HttpOnly와 SameSite=Lax인지 확인.
|
|
브라우저 요청 목록에 Keycloak token endpoint가 없어야 함.
|
|
Web Storage가 비어 있고 document.cookie로 session cookie를 읽을 수 없어야 함.
|
|
|
|
5. 정상 session에 다음 헤더를 얹어 GET /api/edge를 보냄.
|
|
X-Auth-Request-User : spoofed-admin
|
|
X-Auth-Request-Email : spoofed-admin@example.test
|
|
X-Internal-Auth-Token : attacker-controlled-token
|
|
|
|
응답은 200이고 user는 spoofed-admin이 아니라 실제 authenticated user여야 함.
|
|
|
|
6. 외부에서 GET /oauth2/auth를 부르면 404인지 확인.
|
|
|
|
7. host의 4180과 8081에 접근할 수 없는지 확인.
|
|
|
|
8. 내부에서 /edge/me를 부를 때 user 헤더만 있거나 internal token이 없거나 틀리면 401이고,
|
|
둘 다 맞으면 200인지 확인.
|
|
|
|
## 본문
|
|
|
|
<!-- body:start -->
|
|
이 기록은 앞단 프록시가 로그인을 맡는 구성에서 upstream이 받은 사용자 헤더를 어디까지 믿을 수 있는지 확인한 것이다. forward-auth는 실제 요청을 upstream으로 넘기기 전에 별도의 인증 엔드포인트에 허용 여부를 묻는 방식이고, Nginx에서는 `auth_request` 디렉티브가 그 질문을 subrequest로 만든다. 이 두 낱말만 알면 따라올 수 있고, 나머지 용어는 쓰는 자리에서 푼다. 먼저 위조 요청의 모양부터 보고, 그것을 막는 세 곳을 하나씩 따라간 뒤, 지금 확인한 범위와 확인하지 않은 범위를 나눠 적는다.
|
|
|
|
## 같은 이름의 헤더가 두 곳에서 만들어진다
|
|
|
|
:::evidence key="ap4-edge-trust-1cff2399" alt="왼쪽 외부 영역의 브라우저에 AP4_SESSION과 점선으로 표시된 클라이언트 제공 헤더가 있다. 가운데 Nginx는 8088만 공개하고 헤더 덮어쓰기를 맡는다. 오른쪽 점선 영역은 호스트 포트가 닫혀 있고 oauth2-proxy와 Spring upstream이 들어 있다. Nginx가 oauth2-proxy에 auth_request를 보내 사용자와 이메일을 받고, Nginx가 만든 헤더와 내부 토큰으로 upstream 요청을 만든다." caption="" zoom="true"
|
|
:::
|
|
|
|
앞단 프록시가 로그인을 맡으면 upstream은 OAuth를 몰라도 된다. 로그인과 세션 검증은 앞단에 세운 oauth2-proxy가 맡는데, 이렇게 로그인을 대신 받는 관문을 edge라고 부른다. upstream은 요청에 붙어 온 `X-Auth-Request-User` 하나로 사용자를 판단한다. 이 헤더는 oauth2-proxy가 확인한 로그인 사용자의 이름을 담아 edge가 upstream 요청에 붙이는 값이다.
|
|
|
|
문제는 같은 이름의 헤더를 브라우저도 직접 적어 보낼 수 있다는 점이다. upstream이 받는 요청에서 두 값은 이름도 형식도 같고, 어느 쪽이 붙였는지 적힌 자리가 없다. 그래서 이 구조의 문제는 upstream이 `X-Auth-Request-User`의 출처를 구분할 수 없다는 점이다. 백엔드 포트가 외부에 열려 있거나 Nginx가 브라우저의 헤더를 그대로 넘기면 공격자가 인증된 사용자처럼 보낼 수 있다.
|
|
|
|
### 위조 헤더를 얹은 요청이 200이면 뚫린 걸까?
|
|
|
|
로그인을 마친 브라우저가 정상 요청에 헤더 3개를 얹었다고 해 보자.
|
|
|
|
```http label="공격자가 보낸 요청"
|
|
GET http://localhost:8088/api/edge
|
|
Cookie: AP4_SESSION=<opaque-session>
|
|
X-Auth-Request-User: spoofed-admin
|
|
X-Auth-Request-Email: spoofed-admin@example.test
|
|
X-Internal-Auth-Token: attacker-controlled-token
|
|
```
|
|
|
|
세션 자체는 유효하므로 이 요청이 200으로 처리되는 것은 정상이고, 그래서 이 테스트의 판정 기준은 상태 코드가 아니다. 응답 코드만 보면 위조가 통했는지 알 수 없어서, 확인할 값은 응답의 `user`가 `spoofed-admin`으로 바뀌지 않고 실제로 인증된 사용자로 남았는지다.
|
|
|
|
## 세 곳에서 나눠 막는다
|
|
|
|
헤더를 믿으려면 서로 독립된 세 곳에서 막아야 한다. 포트를 닫아 두었으면 헤더 검사는 없어도 되지 않을까? 세 곳은 각각 다른 구간을 맡고 있어서 어느 하나도 나머지 둘의 자리를 대신하지 못한다.
|
|
|
|
### 밖에서 들어올 수 있는 길을 8088 하나로 줄인다
|
|
|
|
밖으로 연 포트는 Nginx의 8088 하나다. `app`의 8081과 oauth2-proxy의 4180은 Compose 네트워크에 `expose`만 하고 호스트 `ports`로는 내보내지 않아서, 포트 2개에는 밖에서 직접 붙을 수 없다.
|
|
|
|
인증 엔드포인트도 같은 이유로 닫아 두는데, `location = /oauth2/auth`가 `internal`이라 Nginx가 만든 subrequest만 들어갈 수 있고, 외부에서 같은 경로를 부르면 404가 된다. `internal` 지정이 없으면 이 엔드포인트가 밖에서 부를 수 있는 인증 우회 지점이 된다.
|
|
|
|
이 경계가 막는 것은 edge를 건너뛰고 upstream이나 프록시로 바로 가는 경로여서, 내부 workload가 보낸 요청이나 Nginx가 잘못 넘긴 헤더는 여기서 걸리지 않는다.
|
|
|
|
### 클라이언트가 보낸 헤더를 덮어써서 지운다
|
|
|
|
Nginx는 upstream을 부르기 전에 인증 결과를 먼저 묻는다. `auth_request`는 원래 요청을 처리하기 전에 지정한 경로로 subrequest를 보내고 그 응답 코드로 요청을 계속할지 정하는 디렉티브다.
|
|
|
|
```nginx label="upstream을 부르기 전에 먼저 물어본다"
|
|
auth_request /oauth2/auth;
|
|
```
|
|
|
|
oauth2-proxy가 세션을 유효하다고 판단하면 결과를 응답 헤더로 돌려준다. Nginx는 `auth_request_set`으로 그 값을 지역 변수 3개에 복사한다.
|
|
|
|
```text label="auth_request_set — 값의 출처가 여기서 고정된다"
|
|
$auth_user ← oauth2-proxy X-Auth-Request-User
|
|
$auth_email ← oauth2-proxy X-Auth-Request-Email
|
|
$auth_cookie ← oauth2-proxy Set-Cookie
|
|
```
|
|
|
|
그다음 원래 요청을 그대로 넘기지 않는다. 외부 `/api/edge`는 내부 `/edge/me`로 다시 매핑되고, 헤더 3개는 클라이언트가 보낸 값과 **merge하지 않고 덮어쓰기**로 채워진다.
|
|
|
|
```http label="upstream이 실제로 받는 요청"
|
|
GET http://app:8081/edge/me
|
|
X-Auth-Request-User: <oauth2-proxy-authenticated-user>
|
|
X-Auth-Request-Email: <oauth2-proxy-authenticated-email>
|
|
X-Internal-Auth-Token: <nginx-environment-secret>
|
|
```
|
|
|
|
그래서 클라이언트가 무엇을 보냈든 upstream 입력은 oauth2-proxy가 확인한 값이 된다. 신뢰할 프록시 범위도 IP 1개로 좁혀 두었는데, 이 범위를 넓게 잡으면 같은 내부 네트워크에 있는 다른 서비스가 신뢰받는 프록시처럼 요청을 보낼 수 있기 때문이다.
|
|
|
|
### upstream이 내부 토큰까지 확인한다
|
|
|
|
`EdgeIdentityController.currentUser(HttpServletRequest)`가 `/edge/me`를 받는데, 여기서 확인하는 값은 2개다. `X-Auth-Request-User`를 읽어 비어 있는지 보고, `X-Internal-Auth-Token`을 읽어 배포할 때 설정해 둔 내부 토큰(internal token)과 비교한다.
|
|
|
|
비교에는 일반 문자열 비교 대신 `MessageDigest.isEqual`을 썼는데, 두 바이트 배열이 앞에서 몇 바이트까지 같은지에 따라 실행 시간이 크게 달라지지 않는 비교다. 일반 비교를 쓰면 값이 어디까지 맞았는지가 응답 시간으로 새어 나갈 수 있다.
|
|
|
|
두 조건이 모두 맞을 때만 허용 목록에 있는 필드 4개를 응답에 넣는다.
|
|
|
|
```json label="정상 응답 — 필드 4개"
|
|
{
|
|
"pattern": "AP4-edge-forward-auth",
|
|
"user": "regular-user",
|
|
"email": "regular-user@example.test",
|
|
"identityHeader": "X-Auth-Request-User"
|
|
}
|
|
```
|
|
|
|
하나라도 다르면 401이 된다.
|
|
|
|
```json label="사용자 헤더가 없거나 내부 토큰이 틀릴 때"
|
|
{
|
|
"error": "trusted edge authentication is required"
|
|
}
|
|
```
|
|
|
|
:::danger
|
|
|
|
현재 `SecurityConfig`는 `/edge/**`를 `permitAll`로 두고 `/edge/me` 컨트롤러가 직접 내부 토큰을 확인한다. 새 edge 엔드포인트를 추가하면서 같은 메서드를 부르지 않으면 그 엔드포인트는 보호되지 않는다.
|
|
|
|
:::
|
|
|
|
검사가 컨트롤러 하나에만 들어 있어서 운영으로 넘어갈 때는 필터나 인터셉터, security chain처럼 대상 엔드포인트 전체에 걸리는 공통 경계로 옮겨야 한다. 이 경계가 막는 것은 edge를 거치지 않은 내부 요청이고, 공유 토큰만으로는 외부에서 upstream으로 바로 가는 경로가 어려워지는 네트워크 속성을 대신할 수 없다.
|
|
|
|
### 경로에 따라 다른 코드가 돌아온다
|
|
|
|
같은 미인증 요청이라도 경로에 따라 결과가 다르다. 쿠키 없이 `/`를 부르면 `/oauth2/start`로 302가 되고, 같은 상태에서 `/api/edge`를 부르면 `Location` 없는 401이 된다. 화면을 여는 요청과 프로그램이 부르는 요청은 원하는 실패 모양이 다르기 때문이다. 사람은 로그인 화면으로 가야 하고, 프로그램은 리다이렉트를 따라가는 대신 401을 받아야 한다.
|
|
|
|
**리다이렉트 없는 JSON 401은 정확히 `/api/edge` 경로에만 구성돼 있다.** 다른 경로는 로그인 리다이렉트 규칙을 따른다.
|
|
|
|
| 외부 입력 | 인증 상태 | 결과 |
|
|
|---|---|---|
|
|
| `GET /` | 미인증 | `/oauth2/start` 302 |
|
|
| `GET /api/edge` | 미인증 | 리다이렉트 없는 401 |
|
|
| `GET /oauth2/auth` | 무관 | 404 |
|
|
| `GET /` + 위조 헤더 | 정상 세션 | 실제 사용자 200 |
|
|
| `/edge/me` + 사용자 헤더만 | 내부 토큰 없음 | 401 |
|
|
| `/edge/me` + 틀린 토큰 | 토큰 불일치 | 401 |
|
|
|
|
위의 네 줄은 밖에서 들어온 요청이고, 아래 두 줄은 edge를 거치지 않고 내부에서 `/edge/me`로 바로 들어온 요청이다.
|
|
|
|
## 이 fixture가 보장하는 범위
|
|
|
|
### 브라우저에 남는 것은 opaque 쿠키 하나다
|
|
|
|
로그인이 끝나면 브라우저에는 `AP4_SESSION` 쿠키 하나가 남고, 서버 쪽 세션 저장소는 따로 두지 않았다. `session-cookie-minimal=true`를 쓰면 쿠키에는 access·refresh·ID 토큰 대신 edge에 필요한 최소 정보만 남는다. 여기서 opaque는 브라우저가 값을 해석하지 않고 다음 요청에 그대로 돌려준다는 뜻이다.
|
|
|
|
```text label="AP4_SESSION 쿠키 설정"
|
|
name = AP4_SESSION
|
|
HttpOnly = true
|
|
SameSite = Lax
|
|
Secure = false in local HTTP fixture
|
|
expire = 1 hour in proxy configuration
|
|
```
|
|
|
|
`HttpOnly`가 붙어 있어 JavaScript로 읽을 수 없고, 유효 기간은 프록시 설정에서 1시간이다. `Secure`가 `false`인 것은 local HTTP fixture 기준이라 그렇고, HTTPS로 올리면 `Secure = true`로 바꿔야 한다. 레플리카를 늘린다면 같은 쿠키를 검증할 시크릿을 어떻게 배포하고 교체할지도 정해야 하는데, 지금 fixture에는 정해 둔 것이 없다.
|
|
|
|
코드 교환은 브라우저가 아니라 oauth2-proxy가 컨테이너 안에서 하기 때문에, automatic discovery를 끄고 같은 realm을 가리키는 주소 4개를 각각 관리한다.
|
|
|
|
```text label="같은 realm을 가리키는 주소 4개"
|
|
issuer expected value = http://localhost:8080/realms/keycloak-patterns
|
|
login URL = http://localhost:8080/.../auth
|
|
redeem/token URL = http://keycloak:8080/.../token
|
|
JWKS/userinfo URL = http://keycloak:8080/...
|
|
```
|
|
|
|
issuer는 요청을 보내기 위한 주소가 아니라 Keycloak이 발급한 토큰의 `iss` claim이 기대한 값과 같은지 검증하는 기준값이다. 브라우저는 docker 내부 호스트명인 `keycloak:8080`에 접근할 수 없어서 로그인에는 `localhost:8080`을 쓰고, 컨테이너 안에서는 자기 `localhost:8080`이 Keycloak이 아니므로 토큰과 JWKS(JSON Web Key Set) 요청에는 `keycloak:8080`을 쓴다.
|
|
|
|
### upstream이 믿는 입력이 JWT 1개에서 3개로 바뀐다
|
|
|
|
앞의 세 구조에서는 Resource Server가 서명된 JWT를 받아 서명과 issuer, audience를 직접 확인한다. `/edge/me`는 JWT를 입력으로 받지 않는다. 대신 요청이 edge를 거쳐 들어왔다는 네트워크 위치와 `X-Internal-Auth-Token`, edge가 넘긴 사용자와 이메일을 믿는다. 믿는 입력이 JWT 1개에서 3개로 늘어난 셈이라, 백엔드 직접 경로나 사용자 제공 헤더 중 하나만 열려도 다른 사용자처럼 요청을 보낼 수 있게 된다.
|
|
|
|
지금 edge 응답은 사용자와 이메일만 전달하고 role, groups, tenant, 인증 방식, 토큰 만료는 전달하지 않는다. 패턴이 금지하는 것은 아니지만, 헤더를 하나 늘릴 때 아래 6개를 함께 정해야 한다.
|
|
|
|
- claim 출처 : oauth2-proxy나 별도 인증 서비스가 어느 값을 읽는가
|
|
- 허용 목록 : Nginx가 어느 응답 헤더만 복사하는가
|
|
- 덮어쓰기 : 클라이언트가 보낸 동명 헤더를 항상 지우거나 덮어쓰는가
|
|
- 직렬화 : 다중 값, 구분자, escaping, 최대 크기는 무엇인가
|
|
- upstream 검증 : 헤더 존재만 볼지 값과 service identity까지 볼지
|
|
- 갱신 : role이 바뀌면 프록시 세션과 downstream 인가가 언제 따라가는가
|
|
|
|
### 커밋된 테스트가 확인하도록 정의한 16개
|
|
|
|
이 기록에서 확인했다고 적은 것은 마지막 실행 성적표가 아니라, 커밋된 자동 테스트가 확인하도록 정의한 계약이다. 항목은 16개이고 그중 10개를 확인했다.
|
|
|
|
쿠키 없는 `/`는 302를 받고 쿠키 없는 `/api/edge`는 401을 받는다. authorization request에는 `edge-proxy` 클라이언트와 PKCE(Proof Key for Code Exchange) S256 challenge가 들어 있어야 한다. 로그인 뒤 쿠키는 `AP4_SESSION`이고 `HttpOnly`와 `SameSite=Lax`가 붙어 있어야 하며, 브라우저 요청 목록에 Keycloak 토큰 엔드포인트가 없고 Web Storage가 비어 있고 `document.cookie`로 세션 쿠키를 읽을 수 없어야 한다. 위조 헤더를 얹은 요청은 실제 사용자로 200을 받고, 외부에서 부른 `/oauth2/auth`는 404, 호스트의 4180과 8081은 접근 불가여야 한다. 사용자 헤더가 없거나 내부 토큰이 없거나 틀리면 401이다.
|
|
|
|
나머지 6개는 이 계약 밖이다. role 전달, 새 엔드포인트에 검사를 공통으로 거는 것, 상태를 바꾸는 요청의 CSRF(Cross-Site Request Forgery), 세션 갱신, 레플리카 사이의 시크릿 공유, 내부 시크릿 교체는 확인하지 않았다.
|
|
|
|
지금 설정은 `/api/edge`와 `/`를 모두 `/edge/me`로 바꾸기 때문에 `/orders/123` 같은 임의 경로를 보존하는 범용 리버스 프록시가 아니고, 그래서 path와 method, body, streaming, websocket, 큰 헤더 동작은 입증하지 못했다.
|
|
|
|
지금까지 upstream이 `X-Auth-Request-User`의 출처를 구분할 수 없다는 문제와, 그것을 호스트 포트·헤더 덮어쓰기·내부 토큰 세 곳으로 나눠 막은 구성을 살펴봤다. 위조 헤더를 얹은 요청이 200을 받으면서도 응답의 `user`는 실제 사용자로 남는다는 것까지가 지금 확인한 범위이고, role 전달이나 시크릿 교체처럼 운영에서 먼저 정해야 할 6개는 그 밖에 있다.
|
|
|
|
<!-- body:end -->
|