Files
keycloak-pattern/docs/ap4-edge-forward-auth.md
T

71 lines
3.6 KiB
Markdown

# AP4 · oauth2-proxy Edge Forward Auth
## 첫 단계: oauth2-proxy 자체 OIDC 흐름
`feature/keycloak-oauth2-proxy-oidc-flow`에서는 oauth2-proxy를
`http://localhost:4180`에 직접 노출해 구성 요소를 분리해서 확인합니다.
1. `/edge/me` 미인증 요청이 Keycloak로 redirect됩니다.
2. oauth2-proxy는 confidential `edge-proxy` client와 PKCE S256을 사용합니다.
3. callback에서 code/token 교환과 ID/access token 검증은 서버끼리
수행합니다.
4. 브라우저에는 HttpOnly `AP4_SESSION` cookie만 남습니다.
5. oauth2-proxy가 backend 요청에 `X-Forwarded-User`를 붙여 200을 받습니다.
Keycloak이 발급하는 issuer는 브라우저 기준
`http://localhost:8080/realms/keycloak-patterns`입니다. 컨테이너 내부의
`localhost`는 oauth2-proxy 자신이므로 discovery endpoint에 도달할 수
없습니다. 그래서 이 로컬 Compose 구성은 issuer 검증값은 외부 URL로
유지하되, login URL은 브라우저용 외부 주소, token/JWKS/userinfo는
`http://keycloak:8080` 내부 주소로 각각 명시합니다.
HTTP 로컬 시연이라 `cookie-secure=false`를 사용합니다. 운영 HTTPS에서는
반드시 secure cookie로 되돌려야 합니다.
## 다음 단계의 보안 전제
이 첫 feature의 backend는 전달된 사용자 헤더를 신뢰하며 8081도
loopback에 publish되어 있습니다. 따라서 로컬에서 직접
`X-Forwarded-User: spoofed-admin`을 보내면 우회가 재현됩니다. 이후
Nginx `auth_request` 통합을 거쳐 최종 feature에서 backend no-publish와
내부 shared-secret 검증을 함께 적용합니다.
## 두 번째 단계: Nginx `auth_request`
`feature/keycloak-nginx-auth-request-integration`부터 외부 진입점은
`http://localhost:8088` Nginx 하나입니다. oauth2-proxy의 4180 포트는
Compose 네트워크에만 expose됩니다.
- Nginx의 정확 일치 `location = /oauth2/auth``internal`이라 외부에서
직접 호출할 수 없습니다.
- 인증 서브리퀘스트에는 본문을 보내지 않고 `Content-Length`
비웁니다.
- 일반 브라우저 요청의 401은 `/oauth2/start` 302로 변환합니다.
- API 요청 `/api/edge`는 redirect하지 않고 JSON 401을 반환합니다.
- 인증 성공 시 oauth2-proxy의 `X-Auth-Request-User`와 email만 backend로
전달합니다.
Nginx 컨테이너 IP를 전용 Compose subnet에서 고정하고 oauth2-proxy의
trusted proxy를 그 단일 IP로 제한합니다. 다만 이 단계에서는 backend
8081이 로컬 호스트에 열려 있어 신뢰 헤더를 직접 위조할 수 있습니다.
그 재현 조건은 마지막 feature에서 제거합니다.
## 마지막 단계: 신뢰 경계와 헤더 스푸핑 방어
`feature/keycloak-header-spoofing-defense`에서는 신뢰 경계를 실제
네트워크와 application 양쪽에서 강제합니다.
1. backend 8081과 oauth2-proxy 4180은 host에 publish하지 않습니다.
브라우저가 접근 가능한 application 포트는 Nginx 8088뿐입니다.
2. Nginx는 client가 보낸 `X-Auth-Request-User`, email, 내부 토큰을
그대로 전달하지 않고 oauth2-proxy 결과와 server-side 토큰으로
항상 덮어씁니다.
3. backend는 `X-Auth-Request-User``X-Internal-Auth-Token`이 모두
유효할 때만 edge identity를 받아들이며 token은 constant-time으로
비교합니다.
shared token은 방어 심층화 수단입니다. 운영에서는 Secret Manager나
orchestrator secret으로 주입하고 주기적으로 교체해야 합니다. 서비스
간 mTLS 또는 service mesh identity를 사용할 수 있다면 단순 shared
token보다 강한 workload identity로 대체하는 편이 좋습니다.