# Traefik Production RP Bootstrap Design ## Context 프로덕션 배포환경에서는 Baron SSO 앞단에 Traefik이 reverse proxy로 먼저 떠 있고, Traefik dashboard와 보호 대상 라우트도 Baron SSO 인증을 사용해야 한다. 현재 `config/traefik-compose.yml`은 Traefik과 `traefik-forward-auth`를 사전 구동하는 형태지만 다음 보완이 필요하다. - `CLIENT_ID`, `CLIENT_SECRET`, `SECRET`가 파일에 하드코딩되어 있다. - OIDC endpoint가 Baron/Ory Hydra가 아니라 Keycloak style path를 가리킨다. - `traefik-public` external network가 선언만 되어 있고 서비스에 연결되어 있지 않다. - production boot flow에서 Traefik forward-auth RP가 Hydra에 자동 등록되지 않는다. 관련 이슈: #1221 ## Policy Alignment - OAuth2/OIDC client SoT는 Ory Hydra다. - client secret 원문은 Git에 커밋하지 않고 `.env`, 배포 host의 secret file, Gitea Actions secret, 또는 운영 secret store에서 주입한다. - Kratos는 identity SoT, Keto는 authorization relation SoT로 유지한다. - Traefik과 forward-auth가 신뢰할 수 있는 last-hop proxy가 되며, 애플리케이션은 임의 외부 요청의 identity header를 신뢰하지 않는다. - Wiki가 사용 중이므로 실제 Wiki 업데이트 전 검토본은 `docs/` 문서로 둔다. ## Target Architecture ```mermaid flowchart TD User[Browser] --> Traefik[Traefik edge proxy] Traefik -->|ForwardAuth| TFA[traefik-forward-auth] TFA -->|OIDC authorize/token/userinfo| Hydra[Ory Hydra public endpoint] Hydra --> UserFront[Baron UserFront login/consent] UserFront --> Backend[Baron Backend] Backend --> Kratos[Ory Kratos] Backend --> Keto[Ory Keto] Traefik --> Dashboard[api@internal dashboard] Traefik --> BaronRoute[Baron app routes] ``` `traefik-forward-auth`는 Hydra confidential client로 등록한다. SPA용 `adminfront`, `devfront`, `orgfront`와 달리 server-side middleware이므로 `client_secret` 기반 client로 다룬다. ## Configuration Contract 운영 환경 변수는 다음 이름을 기준으로 둔다. | Key | Required | Example | Note | |---|---:|---|---| | `TRAEFIK_DASHBOARD_HOST` | yes | `traefik.brsw.kr` | Traefik dashboard host | | `TRAEFIK_FORWARD_AUTH_HOST` | yes | `auth.brsw.kr` | forward-auth callback host | | `TRAEFIK_FORWARD_AUTH_CLIENT_ID` | yes | `traefik-forward-auth` | Hydra client id | | `TRAEFIK_FORWARD_AUTH_CLIENT_SECRET` | yes in production | secret value | Git에 저장하지 않는다 | | `TRAEFIK_FORWARD_AUTH_COOKIE_SECRET` | yes in production | 32+ byte random | forward-auth cookie signing secret | | `TRAEFIK_FORWARD_AUTH_CALLBACK_URLS` | yes | `https://auth.brsw.kr/_oauth` | comma-separated | | `HYDRA_PUBLIC_URL` | yes | `https://app.brsw.kr/oidc` | Baron/Ory public issuer base | `config/traefik-compose.yml`은 위 값을 직접 박지 않고 `${...}` placeholder만 사용한다. 여기서 금지하는 것은 "운영 secret 원문을 코드/문서/compose 파일에 커밋하는 것"이다. 배포 이후에는 발급된 client id/secret/cookie secret을 운영 설정 또는 저장소 secret에 고정해 재사용해야 한다. ## Compose Design `config/traefik-compose.yml`의 방향은 다음과 같다. - `traefik`와 `forward-auth` 모두 `traefik-public` external network에 붙인다. - `forward-auth` service에는 `traefik.http.services.forward-auth.loadbalancer.server.port=4181` label을 명시한다. - dashboard router에는 `auth-forward@docker` middleware를 적용한다. - forward-auth provider endpoint는 `HYDRA_PUBLIC_URL` 기반 `generic-oauth` 설정으로 산출한다. Traefik이 Baron/Ory보다 먼저 떠야 하는 bootstrap 순서에서는 OIDC discovery가 시작 시점에 실패할 수 있으므로 명시 endpoint 방식을 사용한다. - auth: `${HYDRA_PUBLIC_URL}/oauth2/auth` - token: `${HYDRA_PUBLIC_URL}/oauth2/token` - userinfo: `${HYDRA_PUBLIC_URL}/userinfo` - `INSECURE_COOKIE=false`를 production 기본값으로 둔다. - `CLIENT_SECRET`과 cookie `SECRET`는 운영 secret 주입만 허용한다. Baron app compose 쪽에서도 production-facing 진입 서비스는 `traefik-public`에 붙어야 한다. 기존 정책상 외부 진입은 gateway/Oathkeeper 계층으로 수렴하는 것이 맞으므로, Traefik이 직접 backend admin endpoint로 붙는 구조는 피한다. ## Hydra Client Bootstrap Design 현재 `compose.ory.yaml`의 `init-rp`는 Hydra 준비 후 기본 RP를 등록한다. Traefik forward-auth RP도 같은 부팅 경로에 넣되, production mode에서만 활성화한다. 권장 조건: ```sh APP_ENV in production|prod TRAEFIK_FORWARD_AUTH_ENABLED=true ``` 등록 payload: ```json { "client_id": "traefik-forward-auth", "client_name": "Traefik Forward Auth", "client_secret": "", "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "scope": "openid offline_access profile email", "redirect_uris": ["https://auth.brsw.kr/_oauth"], "token_endpoint_auth_method": "client_secret_basic", "metadata": { "managed_by": "baron-sso-boot", "system_client": true, "purpose": "traefik-forward-auth", "status": "active" } } ``` 구현은 delete/create보다 idempotent upsert가 안전하다. 최초 부팅 시에는 값이 없으면 생성값을 발급할 수 있지만, RP가 한번 등록된 뒤에는 같은 `client_id`, `client_secret`, callback URI를 운영 설정으로 고정해야 한다. 재배포마다 secret이 바뀌면 forward-auth cookie/session과 Hydra client 인증이 깨질 수 있으므로 rotation은 별도 운영 작업으로만 수행한다. 1. `hydra get oauth2-client `로 존재 여부 확인 2. 없으면 env/secret store 값을 사용해 `hydra create oauth2-client` 3. 없고 초기 생성 모드가 명시되어 있으면 random secret을 생성해 운영자가 저장할 수 있게 출력 또는 secret file에 기록 4. 있으면 `hydra update oauth2-client`로 redirect URI, scope, metadata 같은 비밀값이 아닌 설정을 동기화 5. client secret은 기본적으로 덮어쓰지 않고, rotation flag가 있을 때만 갱신 6. secret mismatch나 필수 redirect URI 누락 시 실패 기본 RP 등록 shell block이 길어지고 있으므로, 구현 단계에서는 `scripts/register_hydra_clients.sh` 같은 별도 boot script로 분리하는 편이 유지보수에 유리하다. ## Generated Auth Config `scripts/auth_config.sh`는 다음을 확장한다. - `TRAEFIK_FORWARD_AUTH_CALLBACK_URLS` parsing - production mode에서 Traefik client id/secret/cookie secret 필수 검증 - generated `config/.generated/auth-config.env`에 Traefik callback CSV 포함 - `KRATOS_ALLOWED_RETURN_URLS_JSON`에 forward-auth callback URL 포함 - `verify` mode에서 runtime Hydra client에 Traefik callback이 들어 있는지 확인 운영 secret 보관 위치는 두 가지를 허용한다. - 배포 host 기준: `.env` 또는 Docker secret/secret file로 주입 - 저장소 기준: Gitea Actions secret에 저장하고 배포 workflow에서 `.env` 또는 secret file로 렌더링 두 경우 모두 Git tracked 파일에는 실제 secret 값을 남기지 않는다. 이렇게 하면 Ory 렌더링과 Hydra RP 등록이 같은 auth config contract를 공유한다. ## Test Plan 구현 전에 RED test를 먼저 추가한다. 1. `test/traefik_forward_auth_config_policy_test.sh` - `config/traefik-compose.yml`에 literal `CLIENT_SECRET=` 또는 `SECRET=` 값이 있으면 실패 - `traefik`와 `forward-auth`가 `traefik-public` network에 붙지 않으면 실패 - `PROVIDER_GENERIC_*_URL`이 Keycloak path를 사용하면 실패 - dashboard router에 `auth-forward` middleware가 없으면 실패 2. `test/auth_config_traefik_rp_policy_test.sh` - `APP_ENV=production`에서 Traefik client secret이 없으면 `scripts/auth_config.sh validate` 실패 - callback URL이 `/_oauth` 형태가 아니거나 http URL이면 실패 - generated env에 `TRAEFIK_FORWARD_AUTH_CALLBACK_URLS`가 없으면 실패 3. `test/compose_ory_traefik_rp_bootstrap_policy_test.sh` - `init-rp` 또는 분리된 boot script가 Traefik RP를 등록하지 않으면 실패 - boot flow가 delete/create only 방식이면 실패하고 upsert 방식을 요구 4. live verification - `hydra get oauth2-client --endpoint "$HYDRA_ADMIN_URL" "$TRAEFIK_FORWARD_AUTH_CLIENT_ID"` - dashboard 접근 시 Hydra authorize redirect 발생 - callback URL이 registered redirect URI와 정확히 일치 - `docker network inspect traefik-public`에서 `traefik`, `forward-auth`, production entry service 연결 확인 ## Rollout Sequence 1. `.env.sample`에 Traefik forward-auth 변수를 placeholder로 추가한다. 2. RED policy tests를 추가하고 실패를 확인한다. 3. `config/traefik-compose.yml`을 env-driven Baron/Ory 설정으로 교체한다. 4. `scripts/auth_config.sh`와 Hydra RP bootstrap script를 확장한다. 5. `compose.ory.yaml`의 `init-rp`가 production에서 Traefik RP upsert를 실행하도록 연결한다. 6. Baron SSO production deployment template의 public-facing services에 Traefik labels와 `traefik-public` external network를 연결한다. 7. `make validate-auth-config`, 관련 shell policy tests, compose config 검증을 통과시킨다. 8. 운영 환경에서 live verification을 수행한다. ## Baron SSO Deployment Labels `deploy/templates/docker-compose.yaml`에서 외부 진입 서비스는 다음 Traefik router를 갖는다. | Service | Host variable | Internal port | Purpose | |---|---|---:|---| | `gateway` | `PUBLIC_HOST` | 80 | SSO main app, `/api`, `/auth`, `/oidc` | | `adminfront` | `ADMINFRONT_HOST` | 5173 | Admin console | | `devfront` | `DEVFRONT_HOST` | 5173 | Developer/RP console | | `orgfront` | `ORGFRONT_HOST` | 5175 | Organization console | 각 public-facing service는 내부 `app_net`과 external `traefik_public`에 동시에 연결한다. `traefik_public`의 실제 Docker network name은 `.env`의 `TRAEFIK_PUBLIC_NETWORK`로 관리하며 기본값은 `traefik-public`이다. `deploy/create-instance.sh`는 `TARGET_DIR=/home/user/prod.baron-sso`처럼 레포 밖 고정 경로에 생성할 수 있고, 이 경우에도 compose build context가 깨지지 않도록 `.env`의 `SOURCE_ROOT`를 실행 중인 레포 루트 절대 경로로 채운다. ## Open Decisions - `TRAEFIK_FORWARD_AUTH_HOST`를 `auth.brsw.kr`로 분리할지, `app.brsw.kr/_oauth` 같은 동일 host callback으로 둘지 결정이 필요하다. - `External RP Ory IAM Foundation` 마일스톤은 현재 Due Date가 비어 있다. 구현 착수 전에 목표 Due Date를 정하는 것이 좋다. - `traefik-forward-auth`를 계속 사용할지, Baron Backend/Oathkeeper에 first-party forward-auth endpoint를 만들지는 별도 장기 개선안으로 남긴다. 단기 목표는 현재 compose 구조를 안전하게 Baron/Ory에 맞추는 것이다.