Files
llm-wiki/raw/branch-notes/feature-keycloak-header-spoofing-defense.md

312 lines
36 KiB
Markdown

---
title: branch / feature-keycloak-header-spoofing-defense (P1A — 헤더 spoofing 방어, Network 경계 강제)
source_type: branch-note
status: raw
id: BR-KEYCLOAK-PATTERNS-OVERVIEW-014
kind: project-work-item
project: keycloak-patterns-overview
work_item: WI-KEYCLOAK-PATTERNS-OVERVIEW-014
inherits: [DEC-KEYCLOAK-PATTERNS-OVERVIEW-EDGE-FORWARDAUTH-001@1, DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1]
refines: []
overrides: []
depends_on: [WI-KEYCLOAK-PATTERNS-OVERVIEW-012]
contract_packet: 1
branch: feature-keycloak-header-spoofing-defense
parent_branch:
related_projects: [keycloak-patterns]
tags: [branch, keycloak-patterns, p1a, network-policy, security-group, header-spoofing, zero-trust]
created: 2026-05-25
target_merge:
status_label: in-progress
contract_packet_sha256: 2bdbf4639b4e61ad38f9f32504f877dd87ad84f8f07523fd0c874bc316c1ae79
---
# branch: feature-keycloak-header-spoofing-defense (P1A — 헤더 spoofing 방어, Network 경계 강제)
> Layer: `raw/branch-notes/` — [[raw/project-notes/keycloak-patterns-overview]]의 `WI-KEYCLOAK-PATTERNS-OVERVIEW-014` 직접 branch.
> P1A 패턴이 깨지는 **유일하고도 가장 흔한 경로** — backend가 ingress 우회 경로로 도달 가능할 때 — 의 방어 메커니즘을 정리. K8s `NetworkPolicy`, EC2 Security Group, mTLS, shared-secret 헤더 검증 4가지를 비교.
> 본 sub-sub-branch는 **문서까지만** (`documented-only`).
> `status_label`: `in-progress`
<!-- section-id: branch-parent -->
## 부모 (필수)
[[raw/project-notes/keycloak-patterns-overview]]
<!-- GENERATED: branch-contract:start -->
<!-- section-id: branch-contract-packet -->
## 브랜치 계약 패킷
- **생성 시 프로젝트 개정**: `1`
- **패킷 스키마**: `contract_packet: 1`
- **완료 조건**: forwarded-user spoofing 우회를 재현하고 network isolation 후 차단한다
<!-- section-id: inherited-project-decisions -->
### 상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
| `DEC-KEYCLOAK-PATTERNS-OVERVIEW-EDGE-FORWARDAUTH-001@1` | AP4는 oauth2-proxy ForwardAuth를 사용한다 | forwarded identity header의 신뢰 경계와 network isolation에 적용한다 | [[raw/project-notes/keycloak-patterns-overview]] |
| `DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1` | done-bar는 E2E success와 signature security failure 재현·해결 evidence다 | spoofing 우회 재현과 차단 evidence를 완료 조건으로 사용한다 | [[raw/project-notes/keycloak-patterns-overview]] |
<!-- section-id: branch-local-decisions -->
### 브랜치 지역 결정
> 기존 branch-local 결정은 아래 `## Decision Evidence Map`의 D-row가 소유하며 이 packet에서 복제하지 않는다.
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|---|---|---|---|
<!-- section-id: declared-overrides -->
### 선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|---|---|---|---|
없음.
<!-- GENERATED: branch-contract:end -->
<!-- section-id: branch-goal -->
## 목표
P1A의 단점 섹션(부모 sub-branch §장점/단점)에서 가장 먼저 등장하는 문제는 **헤더 spoofing**이다. backend는 `X-Auth-Request-User: alice` 헤더를 oauth2-proxy가 붙였다고 **믿고만** 동작하므로, ingress를 우회해 backend에 직접 접근할 수 있는 경로가 하나라도 있으면 패턴 전체가 무너진다.
본 sub-sub는 이 단일 위협에 대해:
1. **K8s 환경**: `NetworkPolicy`로 ingress namespace의 pod만 backend pod에 in-bound 허용.
2. **EC2/VM 환경**: Security Group inbound를 ALB/ingress SG만 허용. backend가 0.0.0.0에 listen하지 않게.
3. **mTLS 옵션**: ingress ↔ backend 간 mutual TLS로 헤더 발신자를 cryptographic하게 검증.
4. **헤더 검증 추가**: oauth2-proxy ↔ backend가 공유하는 shared secret을 별도 헤더(`X-Internal-Auth-Token`)에 실어 backend가 검증.
이 네 가지 trade-off와 각각의 운영 비용을 정리.
핵심 질문:
1. K8s `NetworkPolicy`는 default-deny + ingress namespace allow 두 단계로 작성해야 한다. 왜?
2. EC2 Security Group만으로 충분한가? VPC 내부 다른 인스턴스의 위협은?
3. mTLS는 왜 ForwardAuth 패턴에서 자주 생략되는가? (운영 복잡도 vs 위협 모델)
4. shared-secret 헤더는 어디에 저장하고 어떻게 회전하는가?
- 이슈:
- PR:
<!-- section-id: branch-scope -->
## 범위
### 포함 범위
- 4가지 헤더 spoofing 방어 메커니즘의 **비교·근거 문서화** (`documented-only`): K8s NetworkPolicy 2단계(D3), EC2 Security Group + loopback bind 2계층(D4), mTLS deferral 조건(D2), shared-secret 헤더(D5).
- 각 메커니즘의 공식 vendor doc 근거 + 명시적 실패 모드 정리 (§Decision Evidence Map, §구현 가이드, §엣지·실패·의존).
- 면접 답변 후보: "P1A/AP4 패턴에서 가장 중요한 운영 결정 = 백엔드를 ingress 뒤에 네트워크로 격리하는 강제 메커니즘"(D1).
### 제외 범위
> 의도적으로 제외. 면접에서 "이건 범위에 없었습니다"라고 답할 근거.
- **실 구현 / E2E 시연** — 본 note 는 `documented-only`. 실 방어 구성·시연은 P3A 실 구현 단계(프로젝트 SSOT §5).
- **mTLS 실 구성** (D2) — 프로젝트 SSOT §5 에서 project-level out-of-scope. 본 note 는 "왜 defer 하는가"의 조건만 문서화.
- **K8s 클러스터 실 구축** — 프로젝트는 single-EC2 실 구현(SSOT §F5), cluster-internal 은 §2.2 cross-cutting 문서만. NetworkPolicy 는 원칙 문서화만.
- **Detection 계층** (VPC Flow Logs / GuardDuty 등 사후 탐지) — prevention 이 아니므로 본 결정 축 밖.
- **IAM 최소권한** (SG 수정 권한 scoping) — SG 방어를 우회할 수 있는 control-plane 위협이나 네트워크 결정과 독립된 별도 관심사.
## 근거 (필수, 최소 1개+)
> 부모 sub-branch에서 인용한 자료 + 본 sub-sub에서 추가 검토 후보.
- [[raw/official-docs/oauth2-proxy-nginx-integration-official]] — ingress 우회 위험을 명시한 oauth2-proxy 공식 가이드 (D1 근거, 부모 인용 재참조)
- [[raw/official-docs/keycloak-reverseproxy-official]] — Keycloak reverse proxy 환경의 header spoofing 공식 경고(KC-RP-C3) + `KC_PROXY_TRUSTED_ADDRESSES` 예시(KC-RP-C5) (D1·D6 근거)
- [[raw/official-docs/traefik-forwardauth-middleware-official]] — Traefik ForwardAuth `authResponseHeaders` replace 동작(TFA-C3) + `trustForwardHeader` deprecated 경고(TFA-C6) (D1·D7 근거)
- [[raw/official-docs/aws-security-group-referencing-official]] — AWS 공식: SG-source rule 은 그 SG 소속 인스턴스만 대상·private IP 통신(AWS-SG-REF-C1), same-VPC/peering/TGW 범위 조건(AWS-SG-REF-C2), multi-SG aggregation=union(AWS-SG-REF-C3) (D4 SG-reference 동작·범위 근거)
- [[raw/official-docs/aws-alb-target-security-group-restriction-official]] — AWS 공식: target(EC2 instance) 의 security group 을 load balancer 의 security group 만 허용하도록 제한하라는 권고 (D4 SG 제한 부분의 근거)
- [[raw/official-docs/docker-port-publishing-loopback-bind-official]] — Docker Engine 공식: host IP 미지정 시 기본적으로 모든 host 주소(`0.0.0.0`/`[::]`)에 publish 하는 것이 "insecure by default", publish flag 에 `127.0.0.1`/`::1` 을 포함하면 Docker host 로만 접근 범위가 좁혀짐 (D4 listen-address 제한 부분의 근거)
- [[raw/official-docs/k8s-network-policy-official]] — Kubernetes NetworkPolicy 공식: pod 기본 non-isolated → NetworkPolicy 가 selecting 시 isolated (KNP-C1), policy additive/union 의미론 (KNP-C2), CNI 미구현 시 no effect — silent no-op 위험 (KNP-C3). D3(K8s NetworkPolicy default-deny + ingress namespace allow 2단계 작성) 근거로 2026-07-16 raw 보존 완료
- [[raw/official-docs/aws-cloudfront-origin-shared-secret-header-official]] — AWS CloudFront→ALB shared-secret custom header 공식 mitigation: 헤더를 secure credential 로 취급 (CF-ALB-SECRET-C2), secret 유출 시 전면 우회되는 명시적 실패 모드 (CF-ALB-SECRET-C3), network-layer(AWS-managed prefix list) 병행 권고 (CF-ALB-SECRET-C4), make-before-break 회전 절차 (CF-ALB-SECRET-C5). D5(shared-secret 헤더 = defense-in-depth 2차, network 격리가 1차) 근거로 2026-07-16 raw 보존 완료
- [[raw/official-docs/istio-mtls-cert-rotation-official]] — Istio 공식: mTLS 채택 시 key management 시스템이 cert 생성·배포·rotation 을 자동화해야 함(ISTIO-MTLS-C1/C2/C3), mTLS handshake 의 secure naming check 가 발신자를 암호학적으로 인증(ISTIO-MTLS-C4/C5). D2(학습 프로젝트 한정 mTLS out-of-scope)의 "운영 비용 = cert lifecycle" 기술적 전제 근거로 2026-07-16 raw 보존 완료
- (검토 후보) Calico NetworkPolicy 공식 — 본 sub-sub 진행 시 raw 추가 여부 결정
## TODO
각 항목 옆에 증거 등급 표기.
- [ ] K8s `NetworkPolicy` 예제 작성 (default-deny ingress + ingress namespace allow + DNS egress 허용) — 등급: `planned` — 근거: `[[raw/official-docs/k8s-network-policy-official]]#KNP-C1`(selecting 시 isolated), `#KNP-C2`(additive/union — 두 리소스로 나눠 써도 안전)
- [ ] `NetworkPolicy`의 CNI 의존성 정리 (Calico / Cilium 등이 지원해야 동작. flannel default는 enforce 안 함) — 등급: `planned` — 근거: `[[raw/official-docs/k8s-network-policy-official]]#KNP-C3`(CNI 미구현 시 no effect, 어떤 CNI 가 구현하는지는 does-not-prove)
- [ ] EC2 Security Group inbound 예제: backend SG는 ALB SG만 허용. SSH/관리 포트는 별도 bastion SG — 등급: `planned`
- [ ] backend listen address를 `0.0.0.0:8080`이 아닌 `127.0.0.1:8080` + sidecar proxy 또는 private subnet 한정 — 등급: `planned`
- [ ] mTLS 옵션 비교: ingress ↔ backend 간 TLS client cert 검증. cert 발급/회전 비용 정리 — 등급: `planned`
- [ ] mTLS 미사용 사유 정리 (학습 프로젝트 한정, 운영 복잡도 > 위협 모델) — 등급: `planned`
- [ ] shared-secret 헤더 검증 패턴: `X-Internal-Auth-Token: <hmac>` + backend middleware 검증. 회전 정책 — 등급: `planned`
- [x] 4가지 방어책 비교표 (운영 비용 / 보안 강도 / 도입 시점) — §구현 가이드 §0 에 작성 완료 (2026-07-16) — 등급: `documented-only`
- [ ] 면접 답변 후보 정리: "P1A 패턴에서 가장 중요한 운영 결정은?" → "백엔드가 ingress 외 경로로 도달되지 않도록 네트워크 격리를 강제하는 것" — 등급: `planned`
- [x] K8s NetworkPolicy 공식 raw 보존 검토 ([[raw/official-docs/k8s-network-policy-official]] — 2026-07-16 완료, KNP-C1/C2/C3 추출) — 등급: `actually-implemented` (raw 보존 자체)
## 진행 중 메모
> 작업하며 떠오른 메모.
- ForwardAuth 패턴의 위협 모델 핵심: **backend는 헤더만 보는 trust-on-message**. 메시지 발신자 검증이 네트워크 레이어에 위임된다.
- K8s NetworkPolicy는 CNI 미지원이면 manifest만 있고 enforce가 안 되는 silent failure 위험 있음. `kubectl get networkpolicy`만으로는 enforcement 여부 알 수 없음.
- shared-secret 헤더는 spoofing 방어로는 약함 (헤더 자체가 leak되면 끝). 네트워크 격리가 1차, shared-secret은 defense-in-depth 2차 정도로 정리.
## 결정 사항 (decisions)
- **2026-05-25 (decision candidate)**: **P1A 패턴의 가장 중요한 운영 결정 = 백엔드를 ingress 뒤에 격리하는 강제 메커니즘**. 본 sub-sub의 결론은 면접/포트폴리오 답변에서 P1A를 설명할 때의 핵심 메시지로 채택.
- **2026-05-25**: 학습 프로젝트 한정으로 mTLS는 out of scope. 운영 환경 가정 시 검토 항목으로만 표기.
## 결정-근거 매핑
> 각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시. 본 sub-sub 의 4가지 방어책 중 Traefik `trustForwardHeader` deprecated 경고, Keycloak `KC_PROXY_TRUSTED_ADDRESSES`, EC2 Security Group(D4), K8s NetworkPolicy(D3, 2026-07-16 [[raw/official-docs/k8s-network-policy-official]] 보존 후), shared-secret 헤더(D5, 2026-07-16 [[raw/official-docs/aws-cloudfront-origin-shared-secret-header-official]] 보존 후 — CloudFront→ALB 구조적 동형 패턴 인용) 는 vendor doc 으로 직접 뒷받침. mTLS(D2) 만 여전히 UNSUPPORTED_DECISION.
| Decision ID | Decision | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|
| D1 | P1A 패턴의 가장 중요한 운영 결정 = 백엔드를 ingress 뒤에 격리하는 강제 메커니즘 (네트워크 경계가 1차 방어, 헤더 검증은 trust-on-message) | `raw/official-docs/traefik-forwardauth-middleware-official.md#TFA-C3` (`authResponseHeaders` 의 replace 동작이 client spoof 를 강제로 deny 하지 않음 — does-not-prove), `raw/official-docs/oauth2-proxy-nginx-integration-official.md#O2PN-C3` (X-User 헤더 신뢰가 안전하다는 뜻 아님 — does-not-prove), `raw/official-docs/keycloak-reverseproxy-official.md#KC-RP-C3` (proxy header spoofing 공식 경고) | `official-vendor-doc` (3개 공식 문서가 모두 "헤더 검증만으로는 부족" 을 명시) | 면접 답변으로 채택했으나, 학습 프로젝트 자체는 단일 EC2 단독 운영이라 실제 네트워크 격리 시연 부재 |
| D2 | 학습 프로젝트 한정으로 mTLS 는 out of scope (운영 환경 가정 시 검토 항목) | `raw/official-docs/istio-mtls-cert-rotation-official.md#ISTIO-MTLS-C1` (mTLS 채택 시 cert 생성·배포·rotation 을 자동화하는 key management 시스템이 필요 — 운영 비용의 실체가 "cert lifecycle 관리"), `#ISTIO-MTLS-C2`(rotation 이 자동·주기적으로 발생해야 함 — 수동 관리가 아니라 자동화 인프라 자체가 전제), `#ISTIO-MTLS-C4`(mTLS 는 secure naming check 로 발신자를 암호학적으로 인증 — mTLS 의 강점 자체는 공식 근거 확보) + **UNSUPPORTED_DECISION 잔존**: "이미 mesh 가 없으면 그 비용이 정당화되지 않는다"는 결론 자체는 Istio 문서가 직접 말하지 않음 — 이 프로젝트가 단일 EC2 라는 전제와 결합한 사용자 trade-off 판단 | `official-vendor-doc`(mTLS 운영 비용의 기술적 전제) + `UNSUPPORTED_DECISION`(mesh 부재 시 defer 하는 결론 자체) | 학습 단계 deferral 이 운영 단계에서 누락될 위험. mesh 신규 도입 비용과 mTLS 를 mesh 없이 수동 구성하는 비용의 정량 비교는 여전히 미실측 |
| D3 | K8s 환경: `NetworkPolicy` default-deny + ingress namespace allow 2단계 작성 | `raw/official-docs/k8s-network-policy-official.md#KNP-C1` (pod 는 기본 non-isolated, selecting 하는 NetworkPolicy 가 있어야 isolated 시작 — default-deny 가 먼저 필요한 이유), `#KNP-C2` (policy 는 additive/union 의미론 — default-deny 와 ingress-namespace-allow 를 별도 두 리소스로 나눠 작성해도 안전하게 합쳐짐), `#KNP-C3` (CNI 가 NetworkPolicy 를 구현하지 않으면 리소스 생성이 no effect — silent no-op 위험, does-not-prove: 어떤 CNI 가 구현하는지 목록) | `official-standard` (Kubernetes 공식 concepts 문서, 3개 claim 모두 원문 verbatim) | NetworkPolicy 가 CNI 미지원 환경 (flannel default) 에서 silent failure → spoofing 방어 실패 (KNP-C3 로 공식 근거 확보됐으나, 실제 클러스터의 CNI 가 NetworkPolicy 를 구현하는지는 실측 필요 — Claims To Verify 참조) |
| D4 | EC2/VM 환경: Security Group inbound 를 ALB/ingress SG 만 허용 + backend 가 `0.0.0.0` 가 아닌 `127.0.0.1:8080` listen 또는 private subnet 한정 | `raw/official-docs/aws-security-group-referencing-official.md#AWS-SG-REF-C1` (SG-source rule 은 그 SG 에 연결된 인스턴스만 대상, private IP 로 통신), `#AWS-SG-REF-C2` (SG-reference 는 same-VPC/peering/(inbound 한정)transit-gateway 범위에서만 동작 — CIDR-source 와 달리 범위 제약), `#AWS-SG-REF-C3` (multi-SG aggregation=union — does-not-prove: leftover broad CIDR allow rule 이 함께 aggregate 되면 SG-narrow rule 이 무력화될 수 있다는 문장은 원문에 없고 논리적 추론), `raw/official-docs/docker-port-publishing-loopback-bind-official.md#DOCKER-PORT-PUB-C1` (host IP 미지정 시 Docker daemon 이 기본적으로 `0.0.0.0`/`[::]` 전체에 publish), `#DOCKER-PORT-PUB-C3` (이 기본 동작이 "insecure by default" — 공식 경고), `#DOCKER-PORT-PUB-C4` (publish flag 에 `127.0.0.1`/`::1` 을 포함하면 오직 Docker host 만 접근 가능해짐 — listen address 를 `127.0.0.1` 로 제한하는 부분의 공식 근거) | `official-vendor-doc` (SG-reference 의 scope·동작과 backend listen address 를 `127.0.0.1` 로 제한하는 부분 모두 이제 공식 근거 확보. 단 SG 절반의 "ALB SG 만 허용" 표현은 이 branch 의 실제 토폴로지가 ALB 없는 단일 EC2 라는 점에서 `aws-alb-target-security-group-restriction-official.md` 자신이 "적용될 실제 ALB 가 없다"고 명시 — 원칙만 차용) | VPC 내부 다른 인스턴스의 lateral movement 는 SG-reference 로 이론상 차단되나, 같은 인스턴스에 연결된 **다른** SG 에 broad CIDR allow rule 이 남아있으면 aggregation(union) 으로 인해 무력화될 수 있음 — 실 환경에서 leftover rule 부재 여부 실측 필요. loopback bind(`127.0.0.1:8080`) 절반도 실제 `docker-compose.yml` 적용 후 host 외부에서 curl 실패·host 내부에서 curl 성공 실측 필요 (Claims To Verify 항목 참조) |
| D5 | shared-secret 헤더 (`X-Internal-Auth-Token: <hmac>`) 는 defense-in-depth 2차 — 1차 방어는 네트워크 격리 | `raw/official-docs/aws-cloudfront-origin-shared-secret-header-official.md#CF-ALB-SECRET-C2` (헤더 이름/값을 secure credential 로 취급), `#CF-ALB-SECRET-C3` (헤더 secret 유출 = 전면 우회되는 명시적 실패 모드), `#CF-ALB-SECRET-C4` (network-layer prefix list 병행 권고 — header 검증 단독 불충분을 AWS 스스로 인정), `#CF-ALB-SECRET-C5` (make-before-break 회전 절차) | `official-vendor-doc` (CloudFront→ALB 맥락의 구조적 동형 패턴 — 직접 Keycloak/oauth2-proxy 문서는 아니므로 구조 유사성 인용) | 헤더 자체가 leak 되면 우회 가능 (C3 이 명시). 회전 절차의 정확한 "주기"(수치)는 인용 범위 밖 — 본 프로젝트의 실제 회전 주기는 별도 결정 필요 |
| D6 | Keycloak reverse proxy 환경에서 `KC_PROXY_TRUSTED_ADDRESSES` 화이트리스트로 proxy header 송신 IP 제한 (단일 EC2 = `127.0.0.1`) | `raw/official-docs/keycloak-reverseproxy-official.md#KC-RP-C5` (`--proxy-trusted-addresses=192.168.0.32,127.0.0.0/8` 예시), `raw/official-docs/keycloak-reverseproxy-official.md#KC-RP-C3` (header spoofing 공식 경고) | `official-vendor-doc` | 화이트리스트 외 IP 가 proxy 헤더 emit 시의 정확한 동작 (drop/ignore/log) 은 인용 범위 밖 (`KC-RP-C5` does-not-prove) |
| D7 | Traefik 사용 시 `trustForwardHeader=true` 회피 (deprecated marker — `X-Forwarded-*` 무조건 신뢰 위험) | `raw/official-docs/traefik-forwardauth-middleware-official.md#TFA-C6` | `official-vendor-doc` | 대체 옵션의 정확한 신규 이름은 본 인용에 없음 — 별도 deprecated 경고 페이지 raw 보존 필요 |
## 구현 가이드
> 본 sub-sub 는 `documented-only` — 여기서 "구현"은 각 방어 메커니즘의 **구성 레시피**(다음 구현자가 되묻지 않고 config 를 작성할 수준)를 뜻한다. 4가지 메커니즘 각각을 결정(D2~D5) + 근거 Claim ID 로 trace. 근거가 *원칙*만 주고 *detail*(정확한 라벨/알고리즘/저장소)을 주지 않는 cell 은 `UNSUPPORTED_IMPL_DECISION` + trade-off 한 줄(CLAUDE.md §15.5 R2).
>
> **OUT_OF_BRANCH_SCOPE 정제(R3)**: mTLS 실 cert 파이프라인(D2)은 프로젝트 SSOT §5 에서 project-level out-of-scope 이므로 "왜 defer 인가"의 조건만 남기고 실제 명세는 남기지 않는다. IAM 최소권한·detection(Flow Logs/GuardDuty)도 별도 관심사로 §엣지·실패·의존 으로 이관.
### 0. 4가지 방어책 종합 비교 (선택 요약)
> In-scope #1(비교·근거 문서화)의 종결 표. 개별 선택 조건이 §1~§4 에 산재하므로 여기서 한눈에 통합.
| 방어책 | 계층 | 운영 비용 | 보안 강도 | 도입 시점/조건 | 근거 |
|---|---|---|---|---|---|
| **D3** K8s NetworkPolicy | L3/L4 네트워크 (1차) | 낮음 (선언 YAML, CNI 의존) | 높음 — 우회 경로 원천 차단, 단 CNI 미구현 시 silent no-op | K8s 환경일 때 | KNP-C1/C2/C3 |
| **D4** EC2 SG + loopback | ENI 경계 + host 경계 (1차) | 낮음 (SG rule + compose 1줄) | 높음 — 단 leftover CIDR rule union 위험 | EC2/VM 환경일 때 (단일 EC2 = loopback 우선) | AWS-SG-REF-C1/C2, DOCKER-PORT-PUB-C4 |
| **D5** shared-secret 헤더 | app 계층 (2차) | 중간 (secret 저장+회전) | 약함 — leak 시 전면 우회 | 상시 2차 defense-in-depth (단독 1차 금지) | CF-ALB-SECRET-C3/C4/C5 |
| **D2** mTLS | 전송 계층 (암호학적) | 높음 (cert lifecycle 자동화 인프라) | 가장 넓음 — 경계 *내부* 위협도 방어 | mesh 운영 중 / 멀티테넌트 / 규제 시만 (그 외 defer) | ISTIO-MTLS-C1/C2/C4 |
**핵심 선택 규칙**: ① 환경으로 1차 방어 결정 (K8s→D3, EC2→D4) → ② D5 를 상시 2차로 병행 → ③ D2 는 조건(mesh/멀티테넌트/규제) 충족 시에만 (그 전엔 network 격리로 충분).
### 1. K8s NetworkPolicy 2단계 구성 (D3)
> **Trace**: D3 / `k8s-network-policy-official#KNP-C1`(pod 기본 non-isolated → selecting NetworkPolicy 가 있어야 isolated), `#KNP-C2`(additive/union — default-deny 와 allow 를 별도 리소스로 나눠도 안전하게 합쳐짐), `#KNP-C3`(CNI 미구현 시 no effect).
>
> - **UNSUPPORTED_IMPL_DECISION**: (a) DNS egress 허용 레시피(port 53 → kube-dns)는 kubernetes.io 공식 문서에 verbatim 부재(community recipe 만 존재, 조사에서 확인) — trade-off: DNS egress 를 빼면 pod 이름 해석이 깨져 정상 트래픽도 실패하므로 실용상 필요하나 공식 근거 미확보, 실 구성 시 사용 CNI vendor 문서로 확인. (b) `namespaceSelector` 라벨 값은 실 클러스터의 namespace 라벨링 규약에 의존 — 임의 결정.
| 단계 | manifest 골자 | 근거 |
|---|---|---|
| 1. default-deny ingress | `podSelector: {}` + `policyTypes: [Ingress]` (ingress 규칙 없음) → backend namespace 의 모든 pod 를 isolated 로 전환 | KNP-C1 |
| 2. ingress-namespace allow | `podSelector: <backend>` + `ingress: [{from: [{namespaceSelector: <ingress ns 라벨>}]}]` → proxy namespace 만 허용 | KNP-C2 (1·2 를 별도 리소스로 나눠도 union) |
| 3. enforcement 확인 | 실사용 CNI 가 NetworkPolicy 를 구현하는지 확인 — `kubectl get networkpolicy` 로는 불충분(§Claims To Verify 실측) | KNP-C3 (does-not-prove: 어떤 CNI 가 구현하는지) |
주의(조사 확인): `from` 배열의 한 원소 안에 `namespaceSelector`+`podSelector` 를 같이 넣으면 AND(교집합), 별도 원소면 OR — "ingress namespace 의 아무 pod 든 허용"은 `namespaceSelector` **단독 원소**여야 함.
### 2. EC2 Security Group + loopback bind — 서로 다른 2계층 (D4)
> **Trace**: D4 / `aws-security-group-referencing-official#AWS-SG-REF-C1`(SG-source rule = 그 SG 소속 인스턴스만, private IP), `#AWS-SG-REF-C2`(same-VPC/peering/TGW 범위), `aws-alb-target-security-group-restriction-official#ALB-SG-C1`(target SG source = LB SG 권고), `docker-port-publishing-loopback-bind-official#DOCKER-PORT-PUB-C4`(publish flag 에 `127.0.0.1` 포함 시 Docker host 만 접근).
>
> - **UNSUPPORTED_IMPL_DECISION**: 이 프로젝트의 실제 토폴로지는 **ALB 없는 단일 EC2**(SSOT §F5) — "backend SG 를 ALB SG 만 허용"은 멀티 인스턴스 확장 시의 원칙 차용이고, 현 배포의 실적용 메커니즘은 **loopback bind / no-publish** 다. SG-ref 와 loopback 은 대체가 아니라 서로 다른 계층(SG=ENI 경계, loopback=host 경계)이라 병행. trade-off: 단일 EC2 에서 SG-ref 는 시연할 별도 proxy 인스턴스가 없어 문서 근거로만 남김.
| 계층 | 실적용(단일 EC2) | 멀티 인스턴스 확장 시 | 근거 |
|---|---|---|---|
| host 경계 | docker-compose 에서 backend 포트를 `127.0.0.1:8080:8080` bind 또는 `ports:` 생략(`expose:` 만) | 동일 유지 | DOCKER-PORT-PUB-C4, C3(insecure by default) |
| ENI 경계 | (해당 없음 — proxy·backend 동일 host) | backend SG inbound source = proxy/ALB SG-reference | AWS-SG-REF-C1, ALB-SG-C1 |
### 3. shared-secret 헤더 검증 — defense-in-depth 2차 (D5)
> **Trace**: D5 / `aws-cloudfront-origin-shared-secret-header-official#CF-ALB-SECRET-C2`(헤더를 secure credential 로 취급), `#CF-ALB-SECRET-C3`(secret 유출 = 전면 우회), `#CF-ALB-SECRET-C4`(network-layer 병행 권고), `#CF-ALB-SECRET-C5`(make-before-break 회전).
>
> - **UNSUPPORTED_IMPL_DECISION**: (a) 근거는 AWS CloudFront→ALB 의 **구조적 동형** 패턴이며 oauth2-proxy/nginx→backend 스택의 vendor 직접 근거가 아님(유추 적용 — 과장 금지). (b) HMAC 알고리즘·secret 저장소(env var vs secret manager)·Spring backend 미들웨어 검증 코드·app-code 무중단 회전 구현은 CloudFront 문서(infra-rule 계층만)의 범위 밖 — 임의 결정. trade-off: 학습 단계는 env var + 단일 시크릿으로 충분하나, 유출 시 무력화되므로 network 격리(D3/D4) 없이 단독 1차 사용 금지.
| 항목 | 명세 | 근거 |
|---|---|---|
| 헤더 | proxy 가 `X-Internal-Auth-Token` 주입, backend 미들웨어가 검증 | CF-ALB-SECRET-C2 (구조적 동형) |
| 저장 | secure credential 로 취급 — 평문 config 반입 금지 | CF-ALB-SECRET-C2 |
| 회전 | make-before-break: 신규 헤더 추가 → backend 양쪽 허용 → 구 헤더 송신 중단 → 구 헤더 허용 제거 | CF-ALB-SECRET-C5 |
| 위치 | 반드시 network 격리(D3/D4) 하위의 2차 — 단독 1차 금지 | CF-ALB-SECRET-C3, C4 |
### 4. mTLS — deferral 조건만 (D2, 실 cert 파이프라인은 OUT_OF_BRANCH_SCOPE)
> **Trace**: D2 / `istio-mtls-cert-rotation-official#ISTIO-MTLS-C1`(cert 생성·배포·rotation 자동화 필요 = 운영 비용의 실체), `#ISTIO-MTLS-C2`(주기적 자동 회전). 프로젝트 SSOT §5(mTLS project-level out-of-scope) + §F5(single-EC2).
>
> - **deferral 조건(언제 재검토)**: 이미 service mesh 운영 중 / 멀티테넌트 클러스터(backend namespace 를 타 팀 공유) / 규제(FAPI·PCI) 요구 → mTLS 채택. 그 외(단일 테넌트·단일 EC2·학습)는 network 격리로 충분.
> - **실 cert 파이프라인 명세는 본 § 에 남기지 않음**(OUT_OF_BRANCH_SCOPE — 운영 전환 시 별도 branch). "mesh 부재 시 defer 결론 자체"는 여전히 UNSUPPORTED_DECISION(Decision Evidence Map D2 참조).
## 엣지·실패·의존
> R4(깊이 게이트) 캡처용. 정상 경로 외에 *구현 중 부딪힐* 실패/엣지/다른 계약 의존을 미리 열거.
- **실패·엣지 경로**:
- **NetworkPolicy silent no-op** (D3): 실사용 CNI 가 NetworkPolicy 를 구현하지 않으면(flannel default) manifest 는 API 에 저장되나 아무것도 차단하지 않음 — `kubectl get networkpolicy` 로는 탐지 불가(KNP-C3). 기대 동작: 적용 후 직접 backend pod IP 로 curl 해 차단 여부 실측(§Claims To Verify).
- **SG rule aggregation(union)** (D4): backend 인스턴스에 연결된 **다른** SG 에 broad CIDR allow rule 이 남아있으면 SG-ref 의 narrow rule 과 union 되어 무력화(AWS-SG-REF-C3 does-not-prove — 논리적 추론). 기대 동작: 신규 SG-ref rule 추가 전 기존 rule 전수 감사.
- **loopback bind 회귀** (D4): docker-compose 의 `127.0.0.1:8080:8080` 을 실수로 `8080:8080` 으로 되돌리면 즉시 전체 외부 노출(DOCKER-PORT-PUB-C3 "insecure by default"). SG 계층이 최후 방어선.
- **shared-secret leak = 전면 우회** (D5): 헤더/시크릿이 유출되면 방어가 완전 무력화(CF-ALB-SECRET-C3). network 격리(1차) 없이 단독 사용 금지가 그래서 강제.
- **mTLS deferral 누락** (D2): 프로젝트가 멀티테넌트/prod 로 전환될 때 deferral 재검토가 누락되면 network 격리 단일 계층에만 의존하게 됨.
- **다른 계약 의존** (대상 브랜치 + Decision ID 병기):
- 부모 [[raw/branch-notes/feature-keycloak-edge-forwardauth-no-google]] `D2`(헤더 명명 = nginx 계열 `X-Auth-Request-User` 우선) — 본 branch 의 4 방어가 지키는 "신뢰 헤더"의 이름 owner. 부모 D2 가 헤더 이름을 바꾸면 본 branch 방어 대상 입력이 바뀜.
- 부모 [[raw/branch-notes/feature-keycloak-edge-forwardauth-no-google]] `D4`(header spoofing 방어 = ingress-only 강제, NetworkPolicy/VPC SG) — **역방향 계약**: 부모 D4 는 현재 UNSUPPORTED_DECISION 으로 enforcement detail 을 본 sub-sub 에 명시 위임("별도 raw 보존 필요", 부모 `:184`). 본 branch 의 D3/D4 + 이번 세션 보존한 `k8s-network-policy-official`·`aws-security-group-referencing-official` raw 가 그 위임을 이행 — 부모 D4 는 향후 이 근거로 갱신 가능(갱신은 부모 owner 결정, 본 branch 는 근거만 제공).
- 형제 [[raw/branch-notes/feature-keycloak-oauth2-proxy-oidc-flow]] / [[raw/branch-notes/feature-keycloak-nginx-auth-request-integration]] — 실제로 헤더를 주입하는 proxy 구성. 이들이 정한 헤더 이름(`X-Auth-Request-User/Email/Groups`)이 본 branch 방어의 입력.
- **D5 신규 헤더 주입 선행 의존**: 본 branch D5 가 도입하는 `X-Internal-Auth-Token` 은 형제 [[raw/branch-notes/feature-keycloak-nginx-auth-request-integration]] 의 현재 전달 헤더 목록(`X-Auth-Request-*` only, 형제 `:70`)에 **부재**. D5 실 구현 시 형제 proxy 구성이 이 헤더를 주입하도록 *확장이 선행*돼야 함(형제 branch 의 향후 Decision 이 owner — 현재는 미존재 계약).
- 본 branch 의 D6(`KC_PROXY_TRUSTED_ADDRESSES`) — Keycloak reverse-proxy 헤더 계약([[raw/branch-notes/feature-keycloak-reverse-proxy-headers]] 계열)에 의존. proxy 신뢰 주소 정책이 바뀌면 D6 도 영향.
- **범위 밖(별도 관심사, 본 branch 미소유)**: IAM 최소권한(SG 수정 권한 scoping) = SG 방어(D4)를 우회할 수 있는 control-plane 위협이나 네트워크 결정과 독립 / Detection 계층(VPC Flow Logs·GuardDuty) = prevention 아님. 둘 다 본 branch 결정 범위 밖으로 명시.
## 검증해야 할 주장
> 4가지 방어책의 실효성과 운영 비용은 vendor doc 만으로 검증 불가. 다음은 실 환경 시연 시 실측 필요.
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
| K8s `NetworkPolicy` 가 CNI 미지원 (flannel default) 환경에서 silent failure 하는지 — `kubectl get networkpolicy` 만으로 enforcement 여부 확인 불가 | `D3` 는 이제 `raw/official-docs/k8s-network-policy-official.md#KNP-C3` 로 "CNI 미구현 시 no effect" 원칙 자체는 공식 근거 확보(2026-07-16). 단 이 학습 프로젝트가 실제 사용하는 CNI 가 NetworkPolicy 를 구현하는지, `kubectl get networkpolicy` 만으로 enforcement 여부를 판별 가능한지는 KNP-C3 의 인용 범위 밖(does-not-prove) — 여전히 실측 필요 | flannel(또는 실사용 CNI) 환경에서 NetworkPolicy 적용 후 ingress 우회 시도 (직접 backend pod IP curl) 로 enforce 여부 확인 | `needs-confirmation` |
| EC2 Security Group inbound 가 ALB/proxy SG-reference 만 허용해도 VPC 내부 다른 인스턴스의 lateral movement 차단 가능한지 | `D4` 는 이제 `aws-security-group-referencing-official#AWS-SG-REF-C1/C2` 로 "SG-source = SG 소속 인스턴스만·same-VPC 범위" 동작은 공식 근거 확보(2026-07-16). 단 실 배포 backend 인스턴스에 leftover broad CIDR allow rule 이 없는지(union 무력화 여부, AWS-SG-REF-C3 does-not-prove)는 실측 필요 | bastion 인스턴스에서 backend `:8080` 직접 curl 시도 후 차단 여부 확인 + 기존 SG rule 전수 감사 | `planned` |
| shared-secret 헤더 (`X-Internal-Auth-Token: hmac`) 가 backend middleware 에서 실제로 우회 차단을 하는지 | `D5` 는 이제 `raw/official-docs/aws-cloudfront-origin-shared-secret-header-official.md#CF-ALB-SECRET-C1~C5` 로 CloudFront→ALB 맥락의 패턴·실패 모드·회전 절차는 공식 근거 확보(2026-07-16). 단 본 프로젝트의 oauth2-proxy/nginx→backend 조합에서 동일 mitigation 이 실제로 동작하는지, 헤더 leak 시 무력화 위험도가 정량적으로 어느 정도인지는 CloudFront 인용 범위 밖(구조적 유사성만 인용 — does-not-prove) — 여전히 실측 필요 | shared-secret 없는 직접 요청과 위조 요청을 backend 에 보내 응답 차이 확인 | `planned` |
| Keycloak `KC_PROXY_TRUSTED_ADDRESSES=127.0.0.1` 가 단일 EC2 환경에서 spoofing 차단에 충분한지 | `KC-RP-C5` does-not-prove "화이트리스트 외 IP 의 정확한 동작" | 외부에서 직접 Keycloak `:8080``X-Forwarded-For: 8.8.8.8` 위조 요청 후 로그에 위조 IP 가 기록되는지 확인 | `needs-confirmation` |
| nginx + oauth2-proxy 의 `X-Auth-Request-User` 헤더가 backend 도달 전에 ingress 외부에서 주입된 동일 이름 헤더로 위조 가능한지 | `O2PN-C3` does-not-prove "backend 가 X-User 헤더를 신뢰해도 안전" | 외부 client 가 `X-Auth-Request-User: admin` 헤더를 포함한 요청을 ingress 와 backend 양쪽에 보내 처리 결과 비교 | `needs-confirmation` |
| Traefik `trustForwardHeader=true` deprecated 의 대체 옵션 (정확한 신규 이름과 동작) | `TFA-C6` does-not-prove "대체 옵션명" | Traefik 공식 deprecated 경고 페이지 추가 raw 보존 후 신규 옵션 시연 | `planned` |
| K8s NetworkPolicy 공식 vendor doc raw 보존 ([[raw/official-docs/k8s-network-policy-official]]) | (해결됨 — 2026-07-16 raw 보존 완료, KNP-C1/C2/C3 추출로 D3 UNSUPPORTED_DECISION 해소) | kubernetes.io 공식 NetworkPolicy 페이지를 `raw-source-template` 으로 보존 후 Claim ID 추출 | `done` |
| mTLS ingress↔backend 의 cert 발급/회전 운영 비용이 위협 모델 대비 정당한지 | `D2` UNSUPPORTED — vendor 인용 부재 + 학습 단계 deferral | cert-manager 또는 SPIFFE/SPIRE 등 cert 자동화 도구 비교 + 회전 주기별 운영 비용 측정 | `planned` |
## 마주친 문제
- 아직 없음(문서 단계).
## 묶음
<!-- GENERATED: sources:start -->
- [[raw/official-docs/aws-alb-target-security-group-restriction-official]]
- [[raw/official-docs/aws-cloudfront-origin-shared-secret-header-official]]
- [[raw/official-docs/aws-security-group-referencing-official]]
- [[raw/official-docs/docker-port-publishing-loopback-bind-official]]
- [[raw/official-docs/istio-mtls-cert-rotation-official]]
- [[raw/official-docs/k8s-network-policy-official]]
- [[raw/official-docs/keycloak-reverseproxy-official]]
- [[raw/official-docs/nginx-auth-request-module-official]]
- [[raw/official-docs/traefik-forwardauth-middleware-official]]
<!-- GENERATED: sources:end -->
<!-- GENERATED: branches:start -->
- [[raw/branch-notes/feature-keycloak-traefik-forwardauth-alternative]]
<!-- GENERATED: branches:end -->
> 본 sub-sub-branch 는 leaf — 자식 branch 없음. Phase 3 P3A 실 구현 또는 외부 산출물 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화. (근거 raw 자료 8건은 §Sources / 근거 에 단일 관리 — 여기 중복 나열하지 않음.)
### 오류 기록 (이 sub-sub-branch 작업 중 발생)
- (없음 — 현재 documented-only 단계)
### 면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- (없음 — Phase 3 실 구현 단계에 누적)
## 관련 일일 노트
## 완료 후 정리
> 본 sub-sub-branch는 **문서까지만**. wiki 추출은 root branch의 비교 매트릭스 시점에 일괄 처리.
- PR 링크:
- 리뷰 메모:
- 머지 결과 / 배포 환경: 해당 없음 (문서 단계)
- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출):
- `actually-implemented` 항목: 없음
- `locally-verified` 항목: 없음
- `prod-verified` 항목: 없음
- **추출하지 않을 항목** (planned / documented-only / abandoned):
- 본 sub-sub-branch 전체가 `documented-only` 등급. P1A 부모 sub-branch와 함께 추후 비교 매트릭스 / `wiki/concepts/keycloak-deployment-patterns.md`에 인용되는 형태로만 활용. `wiki/projects/` 직접 승급 없음.