--- title: branch / feature-keycloak-edge-forwardauth-no-google (P1A Edge Forward Auth, no Google) source_type: branch-note status: raw id: BR-KEYCLOAK-CHILD-12F5B5DA kind: branch-child project: keycloak-patterns-overview work_item: WI-KEYCLOAK-PATTERNS-OVERVIEW-020 inherits: [DEC-KEYCLOAK-PATTERNS-OVERVIEW-AUTH-TAXONOMY-001@1] refines: [] overrides: [] depends_on: [] contract_packet: 1 branch: feature-keycloak-edge-forwardauth-no-google parent_branch: feature-keycloak-patterns related_projects: [keycloak-patterns] tags: [branch, keycloak-patterns, auth, oauth2, oidc] created: 2026-05-25 target_merge: status_label: in-progress contract_packet_sha256: e8d7d4e1b0766c23adb5879a98225689106a683f5882dd49a3b4b24b5fc18f08 --- # branch: feature-keycloak-edge-forwardauth-no-google (P1A Edge Forward Auth, no Google) > Layer: `raw/branch-notes/` — Keycloak 패턴 P1A 한정 sub-branch. **Ingress(nginx `auth_request` 또는 Traefik `forwardAuth`)가 외부 auth service(oauth2-proxy baseline)에 인증을 위임**하고 백엔드는 인증 코드를 갖지 않는 패턴. Google federation 없음(=P1B는 별도 sub-branch). > 본 sub-branch는 **문서까지만**(=`documented-only`). 실제 ingress/Traefik 환경 구축은 root branch의 P3A 한정. > `status_label`: `in-progress` ## 부모 (필수) - **Parent branch (root)**: [[raw/branch-notes/feature-keycloak-patterns]] - **Parent project**: [[raw/project-notes/keycloak-patterns-overview]] - **Sibling sub-branches**: - [[raw/branch-notes/feature-keycloak-edge-forwardauth-google-federation]] — P1B (Edge + Google) - [[raw/branch-notes/feature-keycloak-internal-spa-direct-no-google]] — P2A (Cluster-internal, no Google) - [[raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation]] — P2B (Cluster-internal + Google) - [[raw/branch-notes/feature-keycloak-single-ec2-no-google]] — P3A (Single EC2, no Google) - [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]] — P3B (Single EC2 + Google) ## 브랜치 계약 패킷 - **생성 시 프로젝트 개정**: `1` - **패킷 스키마**: `contract_packet: 1` - **완료 조건**: project governance hub가 AP1~AP4 taxonomy와 child progress index를 유지한다 ### 상속한 프로젝트 결정 | Decision Ref | Project Summary | Branch Application | Source | |---|---|---|---| | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-AUTH-TAXONOMY-001@1` | canonical 분류축은 AP1~AP4 인증 통합 아키텍처와 cross-cutting 변형이다 | no-Google Edge ForwardAuth를 AP4 비교 자료로 분류한다 | [[raw/project-notes/keycloak-patterns-overview]] | ### 브랜치 지역 결정 | Decision ID | Decision | Relation | Supporting Claims | Status | |---|---|---|---|---| | D1 | ingress 축과 auth-service 축을 분리한다 | `local` | [[raw/project-notes/keycloak-patterns-overview]] | `proposed` | | D2 | upstream identity header naming을 고정한다 | `local` | [[raw/project-notes/keycloak-patterns-overview]] | `proposed` | | D3 | backend authentication을 edge ForwardAuth에 위임한다 | `local` | [[raw/project-notes/keycloak-patterns-overview]] | `proposed` | | D4 | ingress-only traffic으로 header spoofing을 방어한다 | `local` | [[raw/project-notes/keycloak-patterns-overview]] | `proposed` | ### 선언한 예외 | Override ID | Overrides | Reason | Approval | Status | |---|---|---|---|---| 없음. ## 목표 Edge forward-auth 패턴이 무엇이고, 왜 이 배치를 택하는지를 컴포넌트 다이어그램 + 토큰 교환 sequence + 신뢰 경계 수준으로 정리. 백엔드 코드에서 인증 로직을 제거하고 **edge proxy 단일 지점에서 zero-trust ingress** 를 강제하는 흐름을 면접에서 설명할 수 있어야 함. 핵심 질문 두 개에 답할 수 있어야 한다: 1. 왜 백엔드에 JWT validator를 두지 않고 edge proxy에 인증을 위임하는가? → 다중 서비스에 일관 인증 + 인증 코드 0줄. 2. edge proxy를 신뢰하는 대신 잃는 것은? → 백엔드는 헤더만 보고 사용자를 식별하므로, ingress 우회 경로가 있으면 헤더 spoofing 위험. - 이슈: - PR: ## 범위 ### 포함 범위 - P1A 컴포넌트 다이어그램 (텍스트 + Mermaid) - 토큰 교환 sequence 7단계 - Ingress 선택(nginx vs Traefik)과 auth service 선택(oauth2-proxy vs 호환 OIDC agent)의 2축 비교 - nginx `auth_request` 방식과 Traefik `forwardAuth` 방식의 차이 - 신뢰 경계 정의 (ingress → proxy까지) - 장단점 / 운영 비용 / 보안 surface - 외부 공식 문서 raw 보존 (oauth2-proxy, Traefik, nginx) ### 제외 범위 - Google IdP brokering (=P1B sub-branch에서 다룸) - 실제 K8s / docker-compose 환경 구축 (root branch P3A 한정) - BFF 패턴 (별도 결합 패턴, sub-branch에서 언급만) - mTLS / FAPI / DPoP 등 고급 보안 옵션 - oauth2-proxy 비-Keycloak provider (GitHub, Google direct 등) ## 근거 (필수, 최소 1개+) > 본 sub-branch의 P1A Edge ForwardAuth 패턴 채택 근거. 상세 대안 비교는 §외부 근거 / 대안 조사 참조. | Source | 정당화하는 결정 | |---|---| | [[raw/official-docs/oauth2-proxy-overview-config-official]] | oauth2-proxy 공식 — 채택 컴포넌트 근거 (load-bearing: `OAUTH2PROXY-C2`/`C3`; `C1` 은 `needs-confirmation`, 결정 미인용) | | [[raw/official-docs/oauth2-proxy-keycloak-oidc-provider-official]] | oauth2-proxy ↔ Keycloak OIDC 연동 — `provider=keycloak-oidc` 채택 근거 | | [[raw/official-docs/oauth2-proxy-nginx-integration-official]] | nginx auth_request 통합 — Ingress-Nginx 결합 근거 | | [[raw/official-docs/nginx-auth-request-module-official]] | nginx ngx_http_auth_request_module — subrequest 동작 근거 | | [[raw/official-docs/traefik-forwardauth-middleware-official]] | Traefik ForwardAuth middleware — K8s 환경 대안 비교 근거 | ## 컴포넌트 다이어그램 ### 텍스트 ``` Browser → Ingress (Nginx / Traefik) │ ├── (1) ForwardAuth subrequest → oauth2-proxy / compatible auth service │ │ │ └── (2) OIDC handshake → Keycloak │ │ │ ← (3) 세션 쿠키 + X-Auth-Request-* 헤더 ←┘ │ ↓ (4) 인증 통과 시 backend로 forward (헤더만 신뢰) Backend (인증 코드 0줄, 헤더 trust만) ``` ### Mermaid ```mermaid flowchart LR B[Browser] -->|HTTPS| I[Ingress: Nginx or Traefik] I -. auth_request or forwardAuth .-> P[oauth2-proxy / auth service] P -. OIDC .-> K[Keycloak] P -- 202 + X-Auth-Request-* --> I I -->|trusted headers| BE[Backend API] ``` ## 토큰 교환 sequence > "Browser, Ingress, oauth2-proxy, Keycloak, Backend" 5개 액터 기준. 1. **Browser → Ingress**: unauthenticated request `GET /api/orders` (쿠키 없음). 2. **Ingress → oauth2-proxy `/oauth2/auth`**: nginx의 `auth_request` 디렉티브 또는 Traefik의 `forwardAuth` 미들웨어가 subrequest 전송. 이 endpoint는 **요청을 프록시하지 않고** 202(Accepted) 또는 401(Unauthorized)만 반환. 3. **oauth2-proxy → Keycloak `/protocol/openid-connect/auth`**: 쿠키 없으므로 401 → Ingress가 error_page로 받아 named location `@oauth2_signin`으로 302 redirect 발급. 사용자 브라우저가 Keycloak 로그인 페이지로 이동. 4. **Browser → Keycloak 로그인 UI → 사용자 인증 → callback**: Authorization Code Flow + PKCE. Keycloak이 oauth2-proxy의 callback URL (`/oauth2/callback`)로 `code` 파라미터와 함께 redirect. 5. **oauth2-proxy → Keycloak `/protocol/openid-connect/token`**: `code` + `client_secret` → `access_token` + `id_token` + (옵션) `refresh_token` 교환. oauth2-proxy는 confidential client. 6. **oauth2-proxy → 세션 쿠키 발급**: JavaScript가 raw token을 읽지 못하는 HttpOnly 세션 쿠키(`_oauth2_proxy`)를 발급한다. cookie-backed store면 encrypted cookie가 token material을 보유할 수 있고, Redis/server-side store면 cookie는 opaque session identifier만 보유한다. 이후 `auth_request` subrequest 통과 시 `X-Auth-Request-User`, `X-Auth-Request-Email`, `X-Auth-Request-Groups` 헤더를 Ingress에 응답한다(P1A baseline은 access-token upstream 전달 미사용). 7. **Ingress → Backend**: Ingress가 응답 헤더에서 `auth_request_set` 으로 변수 추출 → `proxy_set_header X-User $user; X-Email $email;` 형식으로 backend에 헤더 주입. **백엔드는 JWT 검증을 하지 않고 헤더만 신뢰**. ## 장점 / 단점 ### 장점 - **백엔드 인증 코드 0줄**: Resource Server 보일러플레이트(spring-security-oauth2-resource-server, JWT decoder, JWKS cache 등) 불필요. - **다중 서비스 일관 인증**: 같은 ingress 뒤의 모든 backend에 동일한 인증 정책 적용. 마이크로서비스 환경에서 인증 코드 분산을 방지. - **raw token의 JavaScript 노출 없음**: HttpOnly cookie라 SPA script가 access/refresh token bytes를 직접 읽지 못한다. 다만 cookie-backed session이면 브라우저가 encrypted token-bearing cookie를 보유하므로 server-side custody와 동일하다고 표현하지 않는다. - **운영 일원화**: 인증 정책 변경(allowed-role, allowed-group 등) 시 oauth2-proxy config만 수정. ### 단점 - **헤더 spoofing risk**: 백엔드가 ingress-only traffic을 강제하지 못하면(예: backend가 직접 NodePort 노출), 공격자가 `X-Auth-Request-User: admin` 헤더를 위조해 우회 가능. - **proxy SPOF**: oauth2-proxy 다운 시 모든 backend 접근 불가. HA 구성 필수. - **세션 저장 방식별 위험**: cookie-backed store가 access token까지 encrypted cookie에 담으면 nginx의 기본 4kb 헤더 한도를 넘어 split-cookie 처리가 필요하다. Redis/server-side store면 cookie는 opaque ID지만 외부 state store 운영 책임이 생긴다. - **백엔드가 토큰 claim 직접 접근 불가**: scope / custom claim 기반 fine-grained 권한 체크가 필요하면 추가로 `X-Auth-Request-Access-Token` 헤더로 토큰 자체를 전달하거나, 결국 backend에서도 JWT 파싱해야 함. ## 신뢰 경계 ``` [ Public Internet ] ←→ [ Ingress + Forward-Auth Proxy ] ←→ [ Backend ] untrusted ← 인증 경계 (boundary) trusted-by-header ``` - **인증 boundary**: ingress → proxy 까지. 이 구간에서 사용자 식별 확정. - **백엔드 전제**: ingress 외 경로로는 도달 불가. 구체적 강제 수단: - K8s: `NetworkPolicy` 로 ingress namespace에서만 backend pod 접근 허용. - VM: backend listen address를 loopback / private subnet으로 한정. Security Group으로 ingress IP만 허용. - **이 전제가 깨지면** 패턴 전체가 깨짐 → 외부 공격자가 backend에 직접 `X-Auth-Request-User: anyuser` 헤더로 요청 가능. ## 제외 범위 (재확인) - Google federation은 **P1B 별도 sub-branch** ([[raw/branch-notes/feature-keycloak-edge-forwardauth-google-federation]]). 본 sub-branch는 Keycloak 자체 user store만 사용. ## TODO 각 항목 옆에 증거 등급 표기: `actually-implemented` | `locally-verified` | `prod-verified` | `documented-only` | `planned` | `needs-confirmation` - [x] P1A 컴포넌트 다이어그램 (텍스트 + Mermaid) — 등급: `documented-only` - [x] 토큰 교환 sequence 7단계 — 등급: `documented-only` - [x] Ingress(nginx/Traefik)와 auth service(oauth2-proxy/호환 agent) 선택축 분리 (D1) — 등급: `documented-only` - [x] 신뢰 경계 정의 + header spoofing 방어(D4)를 자식 branch 로 위임 — 등급: `documented-only` - [ ] 실 구현(oauth2-proxy config + nginx `auth_request` block) — root branch **P3A** 한정 — 등급: `planned` - [ ] §Claims To Verify 5개 주장 실측 (nginx build flag / 4kb cookie / 헤더 전파 / ingress-only / Traefik 비-2XX) — 등급: `planned` (일부 `needs-confirmation`) ## 진행 중 메모 - **P1A 의 본질 한 줄**: edge proxy 가 인증을 종결하고 backend 는 헤더만 신뢰 → "backend 인증 코드 0줄" 이 최대 이점이자 동시에 최대 약점(header spoofing). 이 한 문장이 §장점·§단점·D3·D4 를 관통한다. - **subrequest mode endpoint 구분**: oauth2-proxy 의 `/oauth2/auth` 는 요청을 프록시하지 않고 2xx/401 만 반환하는 subrequest 전용 endpoint(`O2PN-C2`)로, 정상 reverse-proxy mode(`/oauth2/start`,`/oauth2/callback`)와 경로가 다르다. nginx `auth_request` 는 이 endpoint 만 부른다 — 두 mode 를 혼동하면 302 루프가 난다. - **4kb cookie 함정**: cookie-backed store가 access token을 encrypted cookie에 실으면 nginx 기본 헤더 한도(4kb)를 넘어 split cookie가 되고, nginx가 첫 `Set-Cookie`만 복사하는 문제(`O2PN-C6`)가 있다. Redis/server-side store에서는 이 크기 위험 대신 state-store 운영 위험을 검증한다. - 본 sub-branch 는 `documented-only`. `wiki/projects/` 승급은 root 의 6-패턴 비교 매트릭스 시점에 일괄 처리(개별 승급 없음). ## 결정 사항 (decisions) - **D1** 2026-05-25: 선택을 두 축으로 분리한다. - **Ingress 축**: Ingress-Nginx면 `auth_request`, Traefik이면 `forwardAuth` middleware를 사용한다. - **Auth service 축**: oauth2-proxy를 baseline OIDC agent로 두며, 다른 호환 auth service를 쓰려면 동일한 allow/deny·header contract를 검증한다. - Traefik `forwardAuth`는 OIDC session provider 자체가 아니라 외부 auth service를 호출하는 middleware다. 따라서 `Traefik + oauth2-proxy`는 정상 조합이며 상호 배타적 대안이 아니다. - **D2** 2026-05-25: 헤더 이름은 nginx 측 `X-Auth-Request-User` 가 사실상 표준 (oauth2-proxy 응답 헤더). Traefik의 `X-Forwarded-User`는 oauth2-proxy 측 옵션 `--pass-user-headers`가 추가 발급하는 헤더로, 본 문서에서는 nginx 계열 명명 우선. - **D3** 2026-05-25: 백엔드 인증 코드를 제거하고 edge proxy 의 ForwardAuth subrequest 로 인증 위임 (Edge ForwardAuth 패턴 채택). - **D4** 2026-05-25: header spoofing 방어를 위해 ingress-only traffic 강제 (K8s NetworkPolicy 또는 VPC SG) — backend 가 ingress 외 경로로 도달 불가해야 함. ## 결정-근거 매핑 > 본 sub-branch 의 P1A Edge ForwardAuth 패턴 채택 결정과 raw source claim 매핑. claim 형식 `raw//.md#`. | Decision ID | Decision | Supporting Claims | Evidence Strength | Open Risk | |---|---|---|---|---| | D1 | Ingress 축(nginx `auth_request` / Traefik `forwardAuth`)과 auth service 축(oauth2-proxy / compatible OIDC agent)을 분리. baseline은 두 ingress 모두 oauth2-proxy 호출 | `raw/official-docs/oauth2-proxy-overview-config-official.md#OAUTH2PROXY-C2`, `raw/official-docs/oauth2-proxy-overview-config-official.md#OAUTH2PROXY-C3`, `raw/official-docs/oauth2-proxy-keycloak-oidc-provider-official.md#O2PK-C1`, `raw/official-docs/oauth2-proxy-keycloak-oidc-provider-official.md#O2PK-C2`, `raw/official-docs/oauth2-proxy-nginx-integration-official.md#O2PN-C1`, `raw/official-docs/traefik-forwardauth-middleware-official.md#TFA-C1`, `raw/official-docs/traefik-forwardauth-middleware-official.md#TFA-C3`, `raw/official-docs/traefik-forwardauth-middleware-official.md#TFA-C4` | `official-vendor-doc` (3 vendors: oauth2-proxy + Keycloak + Traefik) | ingress별 비-2XX 처리 차이와 auth service 대체 호환성은 실측 필요 | | D2 | 헤더 명명은 nginx 계열 `X-Auth-Request-User` (oauth2-proxy 응답 헤더) 우선 | `raw/official-docs/oauth2-proxy-overview-config-official.md#OAUTH2PROXY-C3`, `raw/official-docs/oauth2-proxy-overview-config-official.md#OAUTH2PROXY-C4`, `raw/official-docs/oauth2-proxy-nginx-integration-official.md#O2PN-C3` | `official-vendor-doc` | `X-Auth-Request-*` (subrequest mode response headers) vs `X-Forwarded-*` (`--pass-user-headers` upstream forwarding) 의 정확한 default 활성화 여부는 OAUTH2PROXY-C4 의 "Does not prove" 에 명시 — 별도 config 확인 필요 | | D3 | 백엔드 인증 코드 제거 + edge proxy 의 ForwardAuth subrequest 로 인증 위임 (Edge ForwardAuth 패턴 채택) | `raw/official-docs/nginx-auth-request-module-official.md#NGAR-C1`, `raw/official-docs/nginx-auth-request-module-official.md#NGAR-C2`, `raw/official-docs/nginx-auth-request-module-official.md#NGAR-C4`, `raw/official-docs/nginx-auth-request-module-official.md#NGAR-C5`, `raw/official-docs/oauth2-proxy-nginx-integration-official.md#O2PN-C2`, `raw/official-docs/oauth2-proxy-nginx-integration-official.md#O2PN-C5`, `raw/official-docs/traefik-forwardauth-middleware-official.md#TFA-C1` | `official-standard (nginx) + official-vendor-doc (oauth2-proxy, Traefik)` | "다중 백엔드에 일관 인증" / "백엔드 코드 0줄" 의 운영상 이점은 본 raw claim 들이 직접 enumerate 하지 않음 — 운영 관행 추론 | | D4 | header spoofing 방어 — ingress-only traffic 강제 (NetworkPolicy / VPC SG) | UNSUPPORTED_DECISION (오current Sources 표 에는 NetworkPolicy / VPC SG enforcement 의 공식 raw 가 없음 — sub-sub-branch `feature-keycloak-header-spoofing-defense` 에서 별도 raw 보존 필요) | `internal-reasoning` (보안 일반 원칙) | NetworkPolicy default deny / VPC SG 정확한 구성 패턴이 본 sub-branch 의 Sources 표에 부재. 구현 단계 진입 전 K8s NetworkPolicy 공식 doc + AWS SG 공식 doc 을 raw 로 보존 필요 | ## 구현 가이드 > 본 sub-branch 는 `documented-only` — 산출물은 코드가 아니라 패턴 문서다. 아래는 root branch **P3A** 에서 실제 구현 시 이 branch 의 결정(D1~D4)이 강제하는 config 앵커의 사전 명세. 모든 항목 등급 `planned`(코드 미존재 — `src/` grep 으로 확정 안 됨). > **`NO_GROUND_TRUTH`**: 본 branch 는 `keycloak-patterns` 학습 프로젝트 소속으로 ca-tmpl skeleton 범위 밖 인프라 설정이다 — error-codes/env-keys/headers registry 등 ca-tmpl 계약 SSOT 대조 대상이 아니며, 근거는 vendor 공식 doc(oauth2-proxy / nginx / Traefik)이다. ### 1. oauth2-proxy config (K8s + Ingress-Nginx 경로) > **Trace**: D1(nginx ingress + oauth2-proxy baseline) + D2(헤더 명명 nginx 계열). Supporting: `OAUTH2PROXY-C2`, `O2PK-C1`, `O2PK-C2`, `O2PN-C3`. > > - **UNSUPPORTED_IMPL_DECISION**: 없음 — 아래 파라미터·값은 vendor doc 이 verbatim 명시. | 설정 | 값 | 근거 claim | |---|---|---| | `--provider` | `keycloak-oidc` | `O2PK-C1` | | `--client-id` / `--client-secret` / `--oidc-issuer-url` | confidential client 3종 필수 파라미터 | `O2PK-C1` | | issuer URL 패턴 | Keycloak 17+ `https:///realms/` (17 미만 legacy `/auth/realms/...`) | `O2PK-C2` | | `--set-xauthrequest` | 활성 — `X-Auth-Request-User`/`-Email` 응답 헤더 발급 (subrequest mode) | `O2PN-C3` | ### 2. nginx `auth_request` location block > **Trace**: D3(edge ForwardAuth 로 인증 위임). Supporting: `NGAR-C1`, `NGAR-C2`, `NGAR-C4`, `NGAR-C5`, `O2PN-C2`, `O2PN-C5`. > > - **UNSUPPORTED_IMPL_DECISION**: named location 이름(`@oauth2_signin`)은 관례적 명명 — 임의 선택 가능(vendor 예시 관용값). 강제되는 것은 이름이 아니라 "401 → 302 redirect" contract(`O2PN-C5`)뿐. trade-off: 관용값을 벗어나면 남의 예시 config 를 그대로 못 붙임. | 디렉티브 | 역할 | 근거 claim | |---|---|---| | `auth_request /oauth2/auth;` | 보호 location 에서 subrequest 발사 (URI = oauth2-proxy subrequest endpoint) | `NGAR-C4`, `O2PN-C2` | | subrequest 응답 contract | 2xx=allow, 401/403=deny | `NGAR-C2`, `O2PN-C2` | | `auth_request_set $user $upstream_http_x_auth_request_user;` (+ `$email`) | subrequest 응답 헤더 → main request 변수 | `NGAR-C5`, `O2PN-C3` | | `proxy_set_header X-User $user;` (+ `X-Email $email;`) | backend 로 사용자 식별 헤더 주입 | `O2PN-C3` | | `error_page 401 = @oauth2_signin;` → `return 302 /oauth2/sign_in?rd=...` | 미인증 브라우저 302 redirect | `O2PN-C5` | | nginx build | `--with-http_auth_request_module` 필수 (기본 빌드 미포함) | `NGAR-C1` · 검증 → §Claims 1 (`NGAR-C7`) | ### 3. Traefik `forwardAuth` ingress variant (auth service는 별도) > **Trace**: D1(Traefik ingress 축). `forwardAuth.address`는 oauth2-proxy 또는 호환 auth service를 가리킨다. Supporting: `TFA-C1`, `TFA-C3`, `TFA-C4`. > > - **UNSUPPORTED_IMPL_DECISION**: nginx(401/403 만 deny)와 Traefik(모든 non-2XX 를 302 포함 client 에 그대로 전달)의 비-2XX 처리 contract 차이는 vendor doc 이 명시(`TFA-C1`)하나, 두 경로가 동일 로그인 UX 를 내는지는 미실측 → §Claims 5. trade-off: 두 경로를 "동등"으로 문서화하려면 이 실측이 선행. | 미들웨어 옵션 | 역할 | 근거 claim | |---|---|---| | `forwardAuth.address` | oauth2-proxy `/oauth2/auth` 로 위임, 2XX=allow + 원본 요청 진행 | `TFA-C1` | | `authResponseHeaders` | 인증 서버 응답 헤더(`X-Auth-Request-*`)를 forwarded request 로 복사 (충돌 헤더 대체) | `TFA-C3` | | `authRequestHeaders` | 인증 서버로 전달할 request 헤더 필터 (비우면 전부 전달 — sensitive 헤더 노출 주의) | `TFA-C4` | ### 4. ingress-only 강제 (header spoofing 방어) — 자식 branch 로 위임 > **Trace**: D4(`UNSUPPORTED_DECISION`). > > - **R3 OUT_OF_BRANCH_SCOPE**: NetworkPolicy default-deny / VPC SG 의 구체 구성은 P1A 결정 범위 밖 — 자식 sub-sub-branch [[raw/branch-notes/feature-keycloak-header-spoofing-defense]] 가 owner. 본 branch 는 "ingress 외 경로로 backend 도달 불가를 강제해야 한다"는 원칙만 명시하고 enforcement detail 은 재진술하지 않는다(포인터만). ## 엣지·실패·의존 > R4(깊이 게이트) 캡처. 정상 경로(§토큰 교환 sequence) 외 구현 중 부딪힐 실패·엣지 + 다른 계약 의존. - **실패·엣지 경로**: - **proxy SPOF**: oauth2-proxy 다운 → 같은 ingress 뒤 모든 backend 접근 불가(§단점). 기대 동작: HA(replica ≥2) + readiness probe. 미구성 시 단일 장애점. - **4kb cookie 초과 / split cookie**: access_token 을 cookie 에 실으면 nginx 헤더 한도 초과 → nginx 가 첫 `Set-Cookie` 만 복사(`O2PN-C6`). 기대 동작: cookie 분할 처리 또는 access_token 을 cookie 에 싣지 않음. → §Claims 2. - **미인증 XHR/API 요청**: `O2PN-C5` 의 401→302 redirect 는 브라우저 전제. API client 는 302 를 따라가지 못함. 기대 동작: `Accept: application/json` 요청엔 401 유지(별도 처리 — `O2PN-C5` "Does not prove" 참조). - **nginx build 에 auth_request 모듈 부재**: 기본 빌드 미포함(`NGAR-C7`) → `auth_request` directive 무효화. 기대 동작: 기동 시 config 오류로 조기 실패. → §Claims 1. - **nginx vs Traefik 비-2XX contract 차이**: oauth2-proxy 가 5xx 반환 시 nginx(401/403 만 deny, 그 외 error)와 Traefik(모든 non-2XX 를 client 에 전달)의 최종 응답이 갈림(`TFA-C1`). → §Claims 5. - **다른 계약 의존** (대상 브랜치 + 그 Decision ID 병기): - [[raw/branch-notes/feature-keycloak-header-spoofing-defense]] `D1`(백엔드 ingress-뒤 격리 = 1차 방어) + `D3`(K8s NetworkPolicy default-deny) / `D4`(EC2 Security Group + private-subnet listen) 에 의존 — 이 계약이 없으면 본 branch `D3`(헤더 trust)의 전제가 깨져 외부에서 `X-Auth-Request-User` 위조가 가능해지고 P1A 패턴 전체가 무력화된다. 본 branch `D4` 는 원칙만 선언, enforcement detail 은 이 자식 owner. - [[raw/branch-notes/feature-keycloak-oauth2-proxy-oidc-flow]] `D2`(oauth2-proxy `provider=keycloak-oidc` + `--client-id/-secret/-oidc-issuer-url` + Keycloak 17+ issuer URL 패턴)가 §sequence step 3~6 handshake 의 owner. 이 계약이 바뀌면 본 branch §구현 가이드 1 의 config 앵커(D1/D2)가 영향받음. - [[raw/branch-notes/feature-keycloak-nginx-auth-request-integration]] `D2`(subrequest 2xx/401/403 contract) + `D3`(`auth_request_set`→`proxy_set_header` 2-step 헤더 전파) + `D5`(4kb cookie split 대응)가 본 branch §구현 가이드 2(nginx `auth_request` block)의 owner. - **P1B 변형** [[raw/branch-notes/feature-keycloak-edge-forwardauth-google-federation]] — Google federation을 추가해도 [[raw/branch-notes/feature-keycloak-edge-forwardauth-no-google]] D3의 header-only backend trust를 그대로 consume한다. ## 검증해야 할 주장 > 본 sub-branch 는 `documented-only` 단계. 구현 진입 시 검증해야 할 주장 enumerate. | Claim | Why uncertain | How to verify | Status | |---|---|---|---| | nginx 빌드에 `--with-http_auth_request_module` 가 활성화되어 있음 | `NGAR-C7` 명시 — 기본 빌드에 포함되지 않음 | `nginx -V 2>&1 \| grep -o with-http_auth_request_module` | `planned` (구현 진입 시) | | oauth2-proxy 가 큰 access_token (Keycloak refresh token 포함) 을 cookie 4kb 한도 내에서 처리 또는 split | `O2PN-C6` 명시 — 분할 cookie 시 nginx 가 첫 `Set-Cookie` 만 복사 | docker-compose 환경에서 큰 토큰 발급 후 브라우저 cookie 확인 + nginx access_log 의 Set-Cookie 헤더 검사 | `planned` | | `--set-xauthrequest` 활성화 시 nginx `auth_request_set` 이 backend 까지 `X-User` / `X-Email` 헤더 전파 | `O2PN-C3` 가 contract 명시했으나 실제 nginx config 의 `proxy_set_header` 작성 필요 | backend 에 echo endpoint 추가 후 curl 로 헤더 확인 | `planned` | | backend 가 ingress-only traffic 만 받음 (header spoofing 우회 차단) | D4 의 UNSUPPORTED_DECISION 와 동일 — 정책 enforcement 가 실제로 강제되는지 별도 검증 필요 | K8s: NetworkPolicy default-deny 적용 후 다른 namespace 에서 curl 시도 → 차단 확인. VM: backend listen address 가 loopback / private subnet 인지 `ss -tln` 확인 | `needs-confirmation` | | Traefik `forwardAuth` 의 비-2XX 응답 처리가 nginx `auth_request` 와 호환 가능 | `TFA-C1` 가 "비 2XX 응답은 그대로 client 에 반환" 명시 — nginx 의 "401/403 만 deny, 그 외 error" 와 contract 차이 | 두 환경에서 oauth2-proxy 가 5xx 반환 시 client 가 받는 응답 비교 (curl -v) | `planned` | ## 마주친 문제 - 아직 없음(문서 단계). ## 묶음 (자식 sub-sub-branches) - [[raw/official-docs/nginx-auth-request-module-official]] - [[raw/official-docs/oauth2-proxy-endpoints-official]] - [[raw/official-docs/oauth2-proxy-keycloak-oidc-provider-official]] - [[raw/official-docs/oauth2-proxy-nginx-integration-official]] - [[raw/official-docs/oauth2-proxy-overview-config-official]] - [[raw/official-docs/traefik-forwardauth-middleware-official]] - [[raw/branch-notes/feature-keycloak-oauth2-proxy-oidc-flow]] — oauth2-proxy 구성과 OIDC 흐름 - [[raw/branch-notes/feature-keycloak-nginx-auth-request-integration]] — nginx auth_request 통합 (4kb cookie 함정) - [[raw/branch-notes/feature-keycloak-header-spoofing-defense]] — 헤더 spoofing 방어 (NetworkPolicy / SG / mTLS) - [[raw/branch-notes/feature-keycloak-traefik-forwardauth-alternative]] — Traefik ForwardAuth 대안 비교 > Sources / 근거 자료는 본 문서 하단 "외부 근거 / 대안 조사" 섹션 참조. Errors / Interview prep / Lectures 는 현재 없음 (Phase 3 P3A 실 구현 또는 외부 산출물 단계에 누적 예정). ## 관련 일일 노트 ## 관련 sub-branch - [[raw/branch-notes/feature-keycloak-patterns]] (root) - [[raw/branch-notes/feature-keycloak-edge-forwardauth-google-federation]] — P1B Edge + Google federation (본 패턴의 federation 변형) ## 외부 근거 / 대안 조사 (2026-05-25 — P1A Edge Forward Auth) 본 sub-branch의 **Edge ForwardAuth 패턴** 채택에 대한 외부 source 조사. 대안은 동일 목적(브라우저 인증 + 백엔드 신뢰)을 다른 방식으로 달성하는 패턴들과 비교. - **채택 결정 (nginx `auth_request` 또는 Traefik `forwardAuth` ingress + oauth2-proxy baseline + Keycloak)**: - [[raw/official-docs/oauth2-proxy-overview-config-official]] — oauth2-proxy 공식 (Reverse proxy + auth provider integration) - [[raw/official-docs/oauth2-proxy-keycloak-oidc-provider-official]] — oauth2-proxy ↔ Keycloak OIDC 연동 - [[raw/official-docs/oauth2-proxy-nginx-integration-official]] — nginx auth_request 통합 - [[raw/official-docs/nginx-auth-request-module-official]] — nginx ngx_http_auth_request_module (2xx=allow / 401|403=deny contract) - [[raw/official-docs/traefik-forwardauth-middleware-official]] — Traefik ForwardAuth middleware (K8s 환경 대안) - **검토한 대안**: - **대안 1: SPA Direct OIDC + Resource Server (P2A)** — 클라이언트가 직접 Keycloak 호출, 백엔드는 JWT validator. 장: 백엔드 stateless / 단: SPA에 token 노출. 비교 sub-branch: [[raw/branch-notes/feature-keycloak-internal-spa-direct-no-google]]. - **대안 2: BFF (Backend-for-Frontend)** — 백엔드 session cookie + 백엔드가 token holder. 장: XSS surface 축소 / 단: 백엔드 stateful. (Curity / Philippe De Ryck 권고). - **대안 3: API Gateway 인증 (Kong, AWS API Gateway + Cognito)** — vendor lock-in + cloud 종속. - **대안 4: Service Mesh (Istio AuthorizationPolicy + JWT filter)** — K8s mesh 인프라 전제. - **대안 5: 백엔드 직접 인증 (Spring Security `oauth2Login`)** — 백엔드가 redirect/callback 처리. 단일 서비스에는 단순하나 다중 서비스 시 중복. - **비교 핵심**: Edge ForwardAuth는 **다중 백엔드 서비스가 동일 인증을 공유**할 때 가장 단순. 백엔드 코드 0줄 인증. 단, header spoofing 방어 (ingress-only traffic 강제 — K8s NetworkPolicy 또는 VPC SG) 필수. SPA Direct는 mobile/IoT까지 같은 token으로 쓸 때 유리. BFF는 XSS 민감 환경(예: 금융). Service Mesh는 이미 mesh 도입된 환경. ## 완료 후 정리 > 본 sub-branch는 **문서까지만**. wiki 추출은 root branch의 비교 매트릭스 시점에 일괄 처리. - PR 링크: - 리뷰 메모: - 머지 결과 / 배포 환경: 해당 없음 (문서 단계) - **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출): - `actually-implemented` 항목: 없음 - `locally-verified` 항목: 없음 - `prod-verified` 항목: 없음 - **추출하지 않을 항목** (planned / documented-only / abandoned): - 본 sub-branch 전체가 `documented-only` 등급. 추후 root branch의 6 패턴 비교 매트릭스에 인용되는 형태로만 활용. `wiki/projects/` 직접 승급 없음.