1
0
forked from baron/baron-sso
Files
baron-sso/docs/traefik-production-rp-bootstrap-design.md
2026-06-18 11:02:48 +09:00

191 lines
11 KiB
Markdown

# 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": "<from env>",
"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 <client_id>`로 존재 여부 확인
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에 맞추는 것이다.