Files
llm-wiki/raw/branch-notes/feature-keycloak-public-domain-tunneling.md

265 lines
24 KiB
Markdown

---
title: branch / feature-keycloak-public-domain-tunneling (P3B 학습 환경 public 도메인 확보 — ngrok / Cloudflare Tunnel)
source_type: branch-note
status: raw
id: BR-KEYCLOAK-CHILD-89A2896F
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-public-domain-tunneling
parent_branch: feature-keycloak-patterns
related_projects: [keycloak-patterns]
tags: [branch, keycloak-patterns, p3b, public-domain, ngrok, cloudflare-tunnel, https]
created: 2026-05-25
target_merge:
status_label: in-progress
contract_packet_sha256: 5605aa356ecefd27cabba9dff6699e3058ee39235cbb0dd012b97176fb46a417
---
# branch: feature-keycloak-public-domain-tunneling (P3B 학습 환경 public 도메인 확보 — ngrok
> Layer: `raw/branch-notes/` — [[raw/branch-notes/feature-keycloak-patterns]] governance Work Item의 child. P3B 의미 계약은 [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]]를 참조한다. **Google OAuth redirect_uri 정책(HTTPS + localhost 외 IP 불가)** 때문에 학습 환경에서 어떻게 public URL을 확보할지 비교.
> 본 sub-sub-branch는 **문서까지만** — 실 ngrok 구동 / Cloudflare Tunnel 설치 / EC2 도메인 매핑은 진행하지 않음. 등급 `documented-only` (P3B 전체 등급에 종속).
> `status_label`: `in-progress`
<!-- section-id: branch-parent -->
## 부모 (필수)
[[raw/branch-notes/feature-keycloak-patterns]]
<!-- GENERATED: branch-contract:start -->
<!-- section-id: branch-contract-packet -->
## 브랜치 계약 패킷
- **생성 시 프로젝트 개정**: `1`
- **패킷 스키마**: `contract_packet: 1`
- **완료 조건**: project governance hub가 AP1~AP4 taxonomy와 child progress index를 유지한다
<!-- section-id: inherited-project-decisions -->
### 상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
| `DEC-KEYCLOAK-PATTERNS-OVERVIEW-AUTH-TAXONOMY-001@1` | canonical 분류축은 AP1~AP4 인증 통합 아키텍처와 cross-cutting 변형이다 | single-EC2 Google federation 변형의 public-domain tunnel 경계에 적용한다 | [[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 -->
## 목표
P3A에서는 `localhost:8080`으로 모든 통신이 끝났지만 P3B는 **Google이 Keycloak callback URI로 redirect** 해야 한다. Google OAuth 2.0 client는 **redirect_uri를 HTTPS 도메인으로 제한** (localhost는 dev 한정 예외, raw IP 금지). 따라서 학습 환경에서도 public 접근 가능한 HTTPS URL을 어떻게 확보할지 결정해야 한다.
면접에서 답해야 할 질문:
1. 학습 환경에서 왜 EC2 public IP만으론 부족한가? → Google이 IP 주소 redirect_uri 거부, HTTPS + 도메인 강제.
2. 부모가 선택한 public URL 전략을 어떻게 실행하나? → [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]] D3가 provider 우선순위를 소유하고, 본 문서는 named tunnel·managed custom domain과 임시 random URL의 운영 차이만 구체화한다.
3. 학습 → 운영 전환 시 무엇이 바뀌나? → tunnel 제거하고 EC2 public IP + Route53 A 레코드 + ACM cert로 대체.
- 이슈:
- PR:
<!-- section-id: branch-scope -->
## 범위
### 포함 범위
- ngrok / Cloudflare Tunnel / EC2 + Route53 도메인 3개 옵션의 trade-off
- 비교표: cost / static URL / TLS 자동 / 운영 비용 / inbound port 노출
- Google redirect_uri 정책과 각 옵션의 적합도
- 부모 [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]] D3의 채택 결정을 소비해 Cloudflare named tunnel·managed custom domain, ngrok 임시 URL, EC2 도메인 옵션의 실행 메커니즘을 정리
### 제외 범위
- 실 ngrok account 생성, Cloudflare account 연동, cloudflared 데몬 설치
- 자체 도메인 구매 / Route53 hosted zone 생성
- 운영용 ACM cert / ALB 구성 (P3B 운영 시나리오는 부모 sub-branch의 "대안 3"로만 언급)
- ngrok / Cloudflare Tunnel의 enterprise 기능 (custom domain on free plan 제외, IP allowlist 등)
## 근거 (필수, 최소 1개+)
- [[raw/official-docs/ngrok-http-tunnel-official]] — ngrok HTTP tunnel 공식
- [[raw/official-docs/cloudflare-tunnel-routing-official]] — Cloudflare Tunnel routing 공식
- [[raw/official-docs/google-oauth2-redirect-uri-validation-official]] — Google redirect_uri 정책 (HTTPS + localhost 외 IP 불가)
## TODO
각 항목 옆에 증거 등급 표기.
- [ ] **ngrok 무료 plan 동작 확인**`ngrok http 80``https://<random>.ngrok-free.app` 임시 URL 발급, 세션 종료 시 URL 변경 — 등급: `planned`
- [ ] **Cloudflare Tunnel 동작 확인**`cloudflared tunnel create <name>` + `cloudflared tunnel route dns <name> kc.example.com` → named tunnel과 Cloudflare가 관리하는 custom hostname 연결. `trycloudflare.com` quick tunnel의 random URL은 고정 callback으로 사용하지 않음 — 등급: `planned`
- [ ] **EC2 public IP + Route53 도메인 옵션 정리** — Route53 hosted zone + A 레코드 + ACM cert + ALB (또는 EC2 직결 + nginx + Let's Encrypt) — 등급: `planned`
- [ ] **비교표 작성** — cost / static URL / TLS 자동 / inbound port 노출 / 운영 비용 / Google Console redirect_uri exact match 적합도 — 등급: `planned`
- [ ] **부모 결정 수용 확인** — [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]] D3의 Cloudflare named tunnel + managed custom domain 기본 경로와 random URL dev-only fallback을 본 실행 절차에 반영 — 등급: `planned`
## 진행 중 메모
- ngrok free plan은 2026-05 기준 1 세션당 random subdomain. `https://<8자>.ngrok-free.app` 형식. 세션 끊기면 다음 세션은 다른 subdomain.
- Cloudflare Tunnel의 `trycloudflare.com` quick tunnel은 무료지만 URL이 random (ngrok와 유사). 정적 도메인 원하면 Cloudflare account + 자체 도메인 (Cloudflare DNS로 위임) + named tunnel 필요.
- EC2 public IP는 인스턴스 stop/start 시 변경 (Elastic IP 할당하면 고정). 도메인 매핑 안 하면 Google이 redirect_uri로 IP 거부.
- 학습 환경 핵심: **Google Console에 등록한 redirect_uri와 실제 Keycloak issuer URL이 글자 단위로 일치**해야 함 (Google exact match 정책). URL 변경 시마다 Console 업데이트 필요.
### 비교표 초안
| 항목 | ngrok free | Cloudflare Tunnel (named) | EC2 + Route53 + ACM |
|------|-----------|---------------------------|---------------------|
| cost | 무료 | 무료 (Cloudflare account 필요) | Route53 hosted zone $0.50/월 + ACM 무료 + EC2 비용 |
| static URL | ❌ (세션마다 변경) | ✅ (영구) | ✅ |
| TLS 자동 | ✅ (ngrok edge) | ✅ (Cloudflare edge) | ACM + ALB (자동) 또는 Let's Encrypt (cron) |
| inbound port 노출 | 불필요 (egress only) | 불필요 (egress only) | 필요 (443 open) |
| 운영 비용 | 매 세션 Console 갱신 | 도메인 1회 설정 후 무 | DNS / cert / SG 관리 |
| Google redirect_uri 적합도 | 낮음 (URL 변경 burden) | 높음 (정적) | 높음 (정적) |
## 결정 사항 (decisions)
- **2026-05-25 (Historical / superseded selection wording)**: 본 문서가 Cloudflare 1순위·ngrok 2순위를 직접 결정한다고 적었으나, provider 우선순위의 owner는 부모 [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]] D3이다. 본 문서는 선택 결과의 운영 메커니즘만 소유한다.
- **2026-07-18 (Reference-Only)**: 부모 D3가 Cloudflare 경로를 선택하면 **named tunnel + Cloudflare 관리 custom hostname**을 stable Google callback으로 사용한다. `trycloudflare.com` quick tunnel과 ngrok random hostname은 dev-only이며 URL이 바뀌면 Google Console 값을 함께 갱신한다.
- **2026-05-25**: 운영 환경 옵션 **EC2 + Route53 + ACM + ALB**. 본 sub-sub-branch에서는 비교 대상으로만 기재, 실 구성은 P3B 전체가 `documented-only`이므로 진행 안 함.
- **2026-05-25**: 본 sub-sub-branch 전체 등급 `documented-only`. P3B 부모 결정(문서까지만)에 종속. 실 tunnel 구동 / 도메인 매핑은 P3A 완료 후 선택적 확장 시점에 재검토.
## 결정-근거 매핑
> 각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시. `Decision ID` 는 본 branch-note 안에서 안정적으로 유지.
> `선택 조건` 열(R2): "이 조건일 때 이 결정, 다른 조건이면 어떤 대안". 분기 없으면 `N/A`.
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|---|
| D1 | **Cloudflare 운영 profile(부모 D3 소비)** — stable Google callback이 필요하면 named tunnel을 Cloudflare가 관리하는 custom hostname(예: `kc.example.com`)에 연결한다. quick tunnel random URL은 이 profile에 포함하지 않는다. | 부모 [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]] D3가 Cloudflare를 선택하고, 관리 도메인을 확보할 수 있을 때. provider 선택 자체는 본 문서가 재정의하지 않는다. | `raw/official-docs/cloudflare-tunnel-routing-official.md#CLOUDFLARE-TUNNEL-C1` (cloudflared outbound), `raw/official-docs/cloudflare-tunnel-routing-official.md#CLOUDFLARE-TUNNEL-C2` (firewall inbound 차단 권장), `raw/official-docs/cloudflare-tunnel-routing-official.md#CLOUDFLARE-TUNNEL-C3` (tunnel `<UUID>.cfargotunnel.com` subdomain 자동 부여), `raw/official-docs/cloudflare-tunnel-routing-official.md#CLOUDFLARE-TUNNEL-C4` (사용자 hostname CNAME → cfargotunnel.com), `raw/official-docs/google-oauth2-redirect-uri-validation-official.md#GOOGLE-REDIR-C2` (Google redirect URI raw IP 금지 → 도메인 필요) | `official-vendor-doc + official-vendor-doc` | managed custom hostname의 Google 등록과 실제 callback 성공은 P3B 실측 필요. `<UUID>.cfargotunnel.com`이나 `trycloudflare.com` URL을 stable callback으로 간주하지 않는다. |
| D2 | **ngrok 임시 운영 profile(부모 D3 fallback 소비)** — random URL은 dev-only이며 Google Console redirect_uri 갱신을 동반한다. | 부모 D3가 1회성 데모 fallback을 선택한 경우. 반복 사용·stable callback이면 부모 D3의 Cloudflare named tunnel profile로 돌아간다. | `raw/official-docs/ngrok-http-tunnel-official.md#NGROK-C1` (`ngrok http <port>` 가 random HTTPS hostname 생성), `raw/official-docs/ngrok-http-tunnel-official.md#NGROK-C3` (random hostname 은 기존 Domain object 와 매칭 안 됨), `raw/official-docs/ngrok-http-tunnel-official.md#NGROK-C4` (고정 URL에는 별도 Domain record + DNS CNAME 필요), `raw/official-docs/google-oauth2-redirect-uri-validation-official.md#GOOGLE-REDIR-C3` (exact match) | `official-vendor-doc + official-vendor-doc` | free plan에서 재시작마다 hostname이 바뀌는지는 `NGROK-C1` 인용에 직접 명시되지 않아 별도 확인 필요 |
| D3 | 운영 환경 옵션 **EC2 + Route53 + ACM + ALB** — 비교 대상으로만 기재 | 운영(production) 환경이거나 tunnel 의존을 제거해야 할 때. 학습 환경이면 → **D1/D2**. **본 branch 범위 밖**(§범위 Out of scope: 운영 ACM/ALB 구성 제외 + 부모 note 의 "대안 3"과 동일 tree 관행: AWS 경로 = 명명된 비교 대안, 전용 raw 미첨부) — 비교 축으로만 존재 | UNSUPPORTED_DECISION / OUT_OF_BRANCH_SCOPE (AWS Route53 / ACM / ALB 공식 raw 미수집 — 본 branch 의 Sources 인용 범위 밖이자 운영 구성 결정은 별도 branch 영역) | `UNSUPPORTED_DECISION` | 실 채택 시점에 AWS 공식 raw 인용 보강 필요 (예: ACM cert 자동 갱신 정책) |
| D4 | 본 sub-sub-branch 전체 등급 `documented-only` (P3B 부모 결정 종속, 실 tunnel 구동 보류) | P3A 완료 전 학습·문서 단계인 동안 적용. P3A 완료 후 선택적 확장 시점이면 → 실 tunnel 구동 / 도메인 매핑 재검토 (N/A — project scope 결정) | UNSUPPORTED_DECISION (project scope 결정 — 부모 branch P3B 의 `documented-only` 정책에 종속, 외부 raw 인용 불필요) | `UNSUPPORTED_DECISION` | scope 결정 자체는 외부 raw 가 root 가 아님 |
## 구현 가이드
> 본 branch 는 `documented-only` — 실 코드/구동 없음. 따라서 본 §는 "다음 P3B 확장 작업자가 되묻지 않고 각 옵션을 셋업할 수 있는 수준"의 사전 명세 (등급은 전부 `planned`/`documented-only`). 각 sub-section 은 본 branch 의 `Decision ID` + `Supporting Claim ID` 에서 도출된 것만 기재한다.
>
> **범위 경계**: Keycloak `KC_HOSTNAME` / `KC_PROXY_HEADERS` / relative-path / TLS 종단 config 는 본 branch 결정 영역 밖(sibling `feature-keycloak-reverse-proxy-headers` / `feature-keycloak-https-termination-caddy-nginx` 소유) → 여기 재진술하지 않고 §엣지·실패·의존 에 의존 링크로만 둔다 (R3 OUT_OF_BRANCH_SCOPE).
### 1. Cloudflare Tunnel (D1) — 부모 D3 선택을 실행하는 named tunnel 셋업 절차
> **Trace**: D1 + `CLOUDFLARE-TUNNEL-C1`(outbound-only) / `C2`(inbound 차단 권장) / `C3`(`<UUID>.cfargotunnel.com` 자동 부여) / `C4`(사용자 hostname CNAME → cfargotunnel.com) / `C5`(`cloudflared tunnel route dns`, running 아니면 트래픽 없음).
>
> - **UNSUPPORTED_IMPL_DECISION**: (1) tunnel/도메인 명명(`kc.example.com`, `<name>`)은 예시값 — 사용자가 소유·위임한 Cloudflare 관리 도메인에 종속하며 raw 근거 없음(trade-off: 구체 도메인은 실 확장 시점 확정). (2) step 4 ingress `config.yml` 문법(`ingress:` 블록 / `service:` 매핑)은 `CLOUDFLARE-TUNNEL-C1`(outbound-only)이 증명하지 않는 미근거 detail(trade-off: ingress 규칙 공식 페이지 미수집 — 실 셋업 시 Cloudflare Tunnel `config.yml` 문서 참조). documented-only 이므로 둘 다 실 확장 시점 확정.
| 단계 | 명령 / 설정 | 근거 | 결과 |
|---|---|---|---|
| 1. 전제·인증 | Cloudflare account + Cloudflare DNS 로 위임한 도메인 1개 → `cloudflared tunnel login` (브라우저 인증 → `cert.pem`) | (계정 전제 — raw 밖) | named tunnel + 사용자 CNAME 가능 조건 |
| 2. tunnel 생성 | `cloudflared tunnel create <name>` | `CLOUDFLARE-TUNNEL-C3` | tunnel UUID + `<UUID>.cfargotunnel.com` 자동 부여 |
| 3. DNS 라우팅 | `cloudflared tunnel route dns <UUID-or-NAME> kc.example.com` | `CLOUDFLARE-TUNNEL-C4`, `C5` | 사용자 hostname → cfargotunnel.com CNAME 생성 (단 tunnel running 전엔 트래픽 없음) |
| 4. ingress | `config.yml``ingress:` 블록에 `kc.example.com``service: http://localhost:8080`(Keycloak) 매핑 | `CLOUDFLARE-TUNNEL-C1`(outbound-only) + **UNSUPPORTED_IMPL_DECISION**(ingress 문법 미근거) | origin→Cloudflare outbound, ingress 규칙으로 Keycloak 라우팅 |
| 5. 구동 | `cloudflared tunnel run <name>` | `CLOUDFLARE-TUNNEL-C1`, `C2` | EC2 SG inbound 0 개로 public HTTPS 노출, TLS 는 Cloudflare edge 종단 |
### 2. ngrok (D2) — quick tunnel 셋업 절차
> **Trace**: D2 + `NGROK-C1`(`ngrok http <port>` random HTTPS hostname) / `C3`(random hostname = Domain object 미매칭) / `C4`(bring-your-own-domain 절차) + `GOOGLE-REDIR-C3`(exact match → URL 변경 시 재등록).
>
> - **UNSUPPORTED_IMPL_DECISION**: "재시작마다 hostname 변경" 은 `NGROK-C1` 인용 범위 밖(관행) → §검증해야 할 주장으로 이관해 별도 확인(trade-off: free plan 정책 페이지 미수집 상태에서 단정 금지).
| 단계 | 명령 / 설정 | 근거 | 결과 |
|---|---|---|---|
| 0. 전제 | 계정 가입 후 `ngrok config add-authtoken <token>` (authtoken 등록) | (계정 전제 — raw 밖) | ngrok agent 인증 완료 |
| 1. 임시 URL | `ngrok http 8080` | `NGROK-C1`, `C2`(scheme https default) | `https://<random>.ngrok.app` 발급 |
| 2. URL 변동성 | (재시작) | `NGROK-C3` | random hostname → reserved Domain 미매칭 → 세션마다 URL 변경 가능 → Google Console redirect_uri 재등록(`GOOGLE-REDIR-C3`) |
| 3. 고정 URL(선택) | Domain record 생성 + DNS CNAME + matching hostname 으로 endpoint 생성 | `NGROK-C4` | 고정 URL 확보(단 free plan 가부는 미확인 — §검증) |
### 3. Google Cloud Console redirect URI 등록 제약 (D1·D2 공통)
> **Trace**: `GOOGLE-REDIR-C2`(host = raw IP 금지, localhost 예외) + `GOOGLE-REDIR-C3`(등록값과 byte-level exact match, 불일치 시 `redirect_uri_mismatch`).
>
> - **OUT_OF_BRANCH_SCOPE**: 등록할 broker endpoint URL 의 정확한 형식(`/realms/{realm}/broker/google/endpoint` + `KC_HTTP_RELATIVE_PATH` 결합)은 sibling `feature-keycloak-reverse-proxy-headers` / `feature-keycloak-google-redirect-uri-policy` 소유 → 링크만, 재진술 금지.
- 등록 host 는 **도메인 필수**(raw IP 금지, `GOOGLE-REDIR-C2`) → D1/D2 의 public URL 이 이 제약을 만족시키는 이유.
- 등록값은 실제 요청 redirect_uri 와 **정확히 일치**(`GOOGLE-REDIR-C3`) → D2(ngrok random URL) 의 갱신 burden 이 여기서 발생.
- stable callback은 `<UUID>.cfargotunnel.com` 또는 `trycloudflare.com` 주소가 아니라 Cloudflare가 관리하는 custom hostname을 사용한다. 그 hostname의 Google 등록 성공은 §검증해야 할 주장으로 남긴다.
## 엣지·실패·의존
> 정상 경로(public URL 확보 → Google redirect 통과) 외에 실 확장 시 부딪힐 실패/엣지와 다른 branch 계약 의존을 미리 열거.
- **실패·엣지 경로**:
- **ngrok URL drift** — free plan 재시작 시 hostname 변경 가능 → 등록 redirect_uri 와 불일치 → `redirect_uri_mismatch`(`GOOGLE-REDIR-C3`). 기대 동작: dev-only로 제한하고 URL 변경 시 Console 갱신, stable callback은 부모 D3가 고른 D1 profile 사용.
- **Cloudflare 라우팅 ≠ 가용** — `cloudflared` 미실행 시 CNAME 은 있어도 트래픽 안 흐름(`CLOUDFLARE-TUNNEL-C5`). 기대: `cloudflared tunnel run` 데몬 상시 실행(systemd 등).
- **CNAME 전파 지연** — `cloudflared tunnel route dns` 직후 DNS 전파 지연(수초~수분)으로 등록 URL 이 일시 미해석 → Google redirect 일시 실패 가능. 기대: `dig <host>` 로 전파 확인 후 Google 등록/로그인 시도.
- **cfargotunnel 도메인 정책 미검증** — Google 이 `<UUID>.cfargotunnel.com` generic subdomain 을 거부할 가능성(`GOOGLE-REDIR-C2` 는 raw IP 만 금지, generic subdomain 은 미보증). 기대: 거부 시 사용자 소유 도메인 CNAME 으로 우회(`CLOUDFLARE-TUNNEL-C4`). → §검증.
- **outbound 443 차단 환경** — 방화벽이 outbound 를 막으면 cloudflared 미동작(`CLOUDFLARE-TUNNEL-C1` "Does not prove" 단서). 기대: egress 443 허용 확인.
- **quick tunnel 혼동** — `trycloudflare.com` quick tunnel 은 random URL(ngrok 유사). 정적 도메인이 목표면 named tunnel + 계정 도메인 필요(진행 중 메모).
- **다른 계약 의존**:
- [[raw/branch-notes/feature-keycloak-reverse-proxy-headers]] `D7`(`KC_HOSTNAME=https://kc.example.com`) + `D1`(`KC_PROXY_HEADERS=xforwarded`) + `D6`(`KC_HTTP_ENABLED=true` + `KC_PROXY_TRUSTED_ADDRESSES`) — 본 branch 가 고른 public host 를 그 branch 가 Keycloak issuer 로 주입. 그 계약(hostname 형식 / `D3` relative path `/keycloak`)이 바뀌면 본 branch 의 redirect URI 등록값도 영향.
- [[raw/branch-notes/feature-keycloak-google-redirect-uri-policy]] `D1`(broker endpoint URL 을 Authorized redirect URIs 에 등록) + `D8`(ngrok 운영 burden → Cloudflare 정적 도메인 채택 정당화) — 본 branch D2(ngrok URL 변경 burden)가 그 branch 의 갱신 운영(`D8`)과 결합.
- [[raw/branch-notes/feature-keycloak-https-termination-caddy-nginx]] `D1`(Cloudflare edge TLS 종단) — 본 branch D1(Cloudflare Tunnel)과 짝: edge 종단이므로 EC2 내부는 HTTP forward. 본 branch D3(EC2 직결)로 가면 그 branch `D2`(Caddy) / `D3`(nginx) + Let's Encrypt 가 TLS 담당.
- 부모 [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]] D3가 provider 우선순위와 채택 조건의 owner다. 본 sub-sub-branch D1/D2는 선택된 provider의 운영 profile만 제공하는 Reference-Only 문서다.
## 검증해야 할 주장
> 공식 문서나 사례는 근거지만, 내 프로젝트에서의 동작을 자동으로 보장하지 않는다.
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
| Cloudflare named tunnel에 연결한 managed custom hostname(예: `kc.example.com`)이 Google Cloud Console authorized redirect URI 등록과 실제 callback에 통과 | `GOOGLE-REDIR-C2`는 raw IP 금지만 보증하고, Cloudflare DNS·TLS·tunnel 조합의 종단 동작을 직접 보증하지 않음 | P3B에서 managed custom hostname을 등록하고 실제 broker login을 수행해 exact match와 callback 성공 확인 | `needs-confirmation` |
| ngrok free plan 에서 재시작 시마다 hostname 이 변경 | `NGROK-C1` 은 random hostname 만 보증, 재시작 시 변경 정책은 본 raw 인용에 직접 없음 | ngrok pricing/free plan 페이지를 `raw/official-docs/` 로 등록 → free plan 의 reserved domain 정책 verbatim 확보 | `planned` |
| Cloudflare Tunnel `trycloudflare.com` quick tunnel 이 무료 + URL random | 본 branch 의 진행 중 메모만 — `cloudflare-tunnel-routing-official` 인용에 직접 없음 | Cloudflare quick tunnel 공식 페이지 발췌 후 `raw/official-docs/` 등록 | `planned` |
| Keycloak `KC_HOSTNAME=<tunnel-url>` 설정 시 issuer `iss` 가 정확히 `https://<tunnel-url>/realms/{realm}` 형식으로 발급 | Keycloak hostname 동작은 별도 raw (`keycloak-hostname-configuration`) 필요 | P3B 시연 시 token 발급 후 jwt.io 로 `iss` 디코딩 → backend `issuer-uri` 와 byte-level 비교 | `needs-confirmation` |
## 마주친 문제
- 아직 없음 (문서 단계).
## 관련 sub-branch
- [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]] (부모, P3B Single EC2 + Google federation)
- [[raw/branch-notes/feature-keycloak-reverse-proxy-headers]] — Keycloak reverse proxy 설정 (KC_PROXY_HEADERS + KC_HOSTNAME)
- [[raw/branch-notes/feature-keycloak-google-redirect-uri-policy]] — Google OAuth client 등록 + ngrok URL 변경 시 갱신
- [[raw/branch-notes/feature-keycloak-https-termination-caddy-nginx]] — HTTPS termination (Caddy vs nginx + Let's Encrypt)
## 묶음
<!-- GENERATED: sources:start -->
- [[raw/official-docs/google-oauth2-redirect-uri-validation-official]]
- [[raw/official-docs/ngrok-http-tunnel-official]]
<!-- GENERATED: sources:end -->
> 본 branch 는 leaf — 자식 자료 없음. errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.
### 오류 기록
- (없음)
### 면접 준비
- (없음)
## 관련 일일 노트
## 완료 후 정리
> 본 sub-sub-branch는 **문서까지만**. 실 tunnel 구동 / 도메인 매핑 / Google Console 등록 흐름은 P3A 완료 후 선택적 확장.
- PR 링크:
- 리뷰 메모:
- 머지 결과 / 배포 환경: 해당 없음 (문서 단계, P3B 전체 `documented-only`)
- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출):
- `actually-implemented` 항목: 없음
- `locally-verified` 항목: 없음
- `prod-verified` 항목: 없음
- **추출하지 않을 항목** (planned / documented-only / abandoned):
- 본 sub-sub-branch 전체가 `documented-only`. 추후 부모 P3B의 6 패턴 비교 매트릭스 내 "public 도메인 확보 비교표"로만 인용.