1
0
forked from baron/baron-sso

ory스택 버전업 및 하드코딩URL 제거

This commit is contained in:
2026-05-07 10:27:31 +09:00
parent 13dee9ae9b
commit 45a14163bf
25 changed files with 1583 additions and 779 deletions

View File

@@ -76,20 +76,20 @@ HYDRA_DB=ory_hydra
KETO_DB=ory_keto KETO_DB=ory_keto
# Ory Kratos Configuration # Ory Kratos Configuration
KRATOS_VERSION=v25.4.0-distroless KRATOS_VERSION=v26.2.0-distroless
# KRATOS_PUBLIC_PORT=4433 # Internal only # KRATOS_PUBLIC_PORT=4433 # Internal only
# KRATOS_ADMINFRONT_PORT=4434 # Internal only # KRATOS_ADMINFRONT_PORT=4434 # Internal only
KRATOS_UI_NODE_VERSION=v25.4.0 KRATOS_UI_NODE_VERSION=v26.2.0
# KRATOS_UI_PORT=4455 # Internal only # KRATOS_UI_PORT=4455 # Internal only
# Ory Hydra Configuration # Ory Hydra Configuration
HYDRA_VERSION=v25.4.0-distroless HYDRA_VERSION=v26.2.0-distroless
# HYDRA_PUBLIC_PORT=4441 # Internal only # HYDRA_PUBLIC_PORT=4441 # Internal only
# HYDRA_ADMINFRONT_PORT=4445 # Internal only # HYDRA_ADMINFRONT_PORT=4445 # Internal only
# Ory Keto Configuration # Ory Keto Configuration
KETO_VERSION=v25.4.0-distroless KETO_VERSION=v26.2.0-distroless
# KETO_READ_PORT=4466 # Internal only # KETO_READ_PORT=4466 # Internal only
# KETO_WRITE_PORT=4467 # Internal only # KETO_WRITE_PORT=4467 # Internal only
KETO_READ_URL=http://keto:4466 KETO_READ_URL=http://keto:4466
@@ -109,16 +109,21 @@ KRATOS_UI_URL=http://localhost:5000
HYDRA_ADMIN_URL=http://hydra:4445 HYDRA_ADMIN_URL=http://hydra:4445
# Oathkeeper가 /oidc 경로를 Hydra Public API로 라우팅합니다. # Oathkeeper가 /oidc 경로를 Hydra Public API로 라우팅합니다.
HYDRA_PUBLIC_URL=${OATHKEEPER_PUBLIC_URL}/oidc HYDRA_PUBLIC_URL=${OATHKEEPER_PUBLIC_URL}/oidc
# 선택: Hydra 화면 핸드오프 URL을 USERFRONT_URL 기준 기본값과 다르게 둘 때만 설정합니다.
# HYDRA_LOGIN_URL=https://sso.hmac.kr/login
# HYDRA_CONSENT_URL=https://sso.hmac.kr/consent
# HYDRA_ERROR_URL=https://sso.hmac.kr/error
# Kratos allowed_return_urls 확장 목록 (콤마 구분, 선택) # Kratos allowed_return_urls 확장 목록 (콤마 구분, 선택)
# 기본값은 KRATOS_UI_URL, USERFRONT_URL, 각 callback URL을 자동 포함합니다. # 기본값은 KRATOS_UI_URL, USERFRONT_URL, 각 callback URL을 자동 포함합니다.
KRATOS_ALLOWED_RETURN_URLS_EXTRA=[] KRATOS_ALLOWED_RETURN_URLS_EXTRA=[]
KRATOS_ALLOWED_RETURN_URLS_JSON=["http://localhost:5000","http://localhost:5000/","https://sso.hmac.kr","https://sso.hmac.kr/","https://sso.hmac.kr/ko","https://sso.hmac.kr/ko/","https://sso.hmac.kr/en","https://sso.hmac.kr/en/","https://sso.hmac.kr/auth/callback","https://sso.hmac.kr/ko/auth/callback","https://sso.hmac.kr/en/auth/callback","http://localhost:5173/auth/callback","http://localhost:5174/auth/callback","http://localhost:5175/auth/callback","https://sso.hmac.kr/orgfront/auth/callback"]
# Oathkeeper JWKS (내부 통신용) # Oathkeeper JWKS (내부 통신용)
JWKS_URL=http://oathkeeper:4456/.well-known/jwks.json JWKS_URL=http://oathkeeper:4456/.well-known/jwks.json
# Oathkeeper 실행 사용자/프로브 설정 # Oathkeeper 실행 사용자/프로브 설정
OATHKEEPER_VERSION=v25.4.0 OATHKEEPER_VERSION=v26.2.0
OATHKEEPER_UID=1001 OATHKEEPER_UID=1001
OATHKEEPER_GID=1001 OATHKEEPER_GID=1001
OATHKEEPER_HEALTH_URL=http://oathkeeper:4456/health/ready OATHKEEPER_HEALTH_URL=http://oathkeeper:4456/health/ready
@@ -140,5 +145,5 @@ VITE_OIDC_CLIENT_ID=devfront
VITE_OIDC_AUTHORITY=https://sso.hmac.kr/oidc VITE_OIDC_AUTHORITY=https://sso.hmac.kr/oidc
DEVFRONT_URL=http://localhost:5174 DEVFRONT_URL=http://localhost:5174
DEVFRONT_CALLBACK_URLS=http://localhost:5174/auth/callback,https://sso.hmac.kr/devfront/auth/callback DEVFRONT_CALLBACK_URLS=http://localhost:5174/auth/callback,https://sso.hmac.kr/devfront/auth/callback
ORGFRONT_CALLBACK_URLS=http://localhost:5175/auth/callback,http://172.16.10.176:5175/auth/callback,https://baron-orgchart.hmac.kr/auth/callback ORGFRONT_CALLBACK_URLS=http://localhost:5175/auth/callback,https://sso.hmac.kr/orgfront/auth/callback
VITE_ORGCHART_URL= VITE_ORGCHART_URL=

View File

@@ -124,11 +124,13 @@ jobs:
CSRF_COOKIE_NAME=${{ vars.CSRF_COOKIE_NAME }} CSRF_COOKIE_NAME=${{ vars.CSRF_COOKIE_NAME }}
CSRF_COOKIE_SECRET=${{ secrets.STG_CSRF_COOKIE_SECRET }} CSRF_COOKIE_SECRET=${{ secrets.STG_CSRF_COOKIE_SECRET }}
# Frontend OIDC configs for Staging # Frontend/Ory URL configs for Staging
VITE_OIDC_AUTHORITY=https://sso.hmac.kr/oidc VITE_OIDC_AUTHORITY=${{ vars.VITE_OIDC_AUTHORITY }}
ADMINFRONT_CALLBACK_URLS=http://localhost:5173/auth/callback,https://sso.hmac.kr/auth/callback,http://172.16.10.176:5173/auth/callback,https://sadmin.hmac.kr/auth/callback ADMINFRONT_CALLBACK_URLS=${{ vars.ADMINFRONT_CALLBACK_URLS }}
DEVFRONT_CALLBACK_URLS=http://localhost:5174/auth/callback,https://sso.hmac.kr/devfront/auth/callback,http://172.16.10.176:5174/auth/callback,https://sdev.hmac.kr/auth/callback DEVFRONT_CALLBACK_URLS=${{ vars.DEVFRONT_CALLBACK_URLS }}
ORGFRONT_CALLBACK_URLS=http://localhost:5175/auth/callback,http://172.16.10.176:5175/auth/callback,https://baron-orgchart.hmac.kr/auth/callback ORGFRONT_CALLBACK_URLS=${{ vars.ORGFRONT_CALLBACK_URLS }}
KRATOS_ALLOWED_RETURN_URLS_JSON=${{ vars.KRATOS_ALLOWED_RETURN_URLS_JSON }}
KRATOS_ALLOWED_RETURN_URLS_EXTRA=${{ vars.KRATOS_ALLOWED_RETURN_URLS_EXTRA }}
# OATHKEEPER_INTROSPECT_CLIENT_ID=${{ vars.OATHKEEPER_INTROSPECT_CLIENT_ID }} # OATHKEEPER_INTROSPECT_CLIENT_ID=${{ vars.OATHKEEPER_INTROSPECT_CLIENT_ID }}
# OATHKEEPER_INTROSPECT_CLIENT_SECRET=${{ secrets.STG_OATHKEEPER_INTROSPECT_CLIENT_SECRET }} # OATHKEEPER_INTROSPECT_CLIENT_SECRET=${{ secrets.STG_OATHKEEPER_INTROSPECT_CLIENT_SECRET }}
EOF EOF

View File

@@ -29,7 +29,7 @@ ifneq (,$(wildcard ./.env))
COMPOSE_DROP_ENV_ARGS += --env-file .env COMPOSE_DROP_ENV_ARGS += --env-file .env
endif endif
.PHONY: build-auth-config validate-auth-config verify-auth-config up-all up-infra up-ory up-app up-backend ensure-networks ensure-infra ensure-ory up-dev up-front-dev dev down drop down-app down-backend down-infra down-ory check-infra ps logs-infra logs-ory logs-app .PHONY: build-auth-config validate-auth-config verify-auth-config up up-all up-infra up-ory up-app up-backend ensure-networks ensure-infra ensure-ory up-dev up-front-dev dev down drop down-app down-backend down-infra down-ory check-infra ps logs-infra logs-ory logs-app
# --- 인증 설정 빌드/검증 --- # --- 인증 설정 빌드/검증 ---
build-auth-config: build-auth-config:
@@ -47,6 +47,8 @@ verify-auth-config: validate-auth-config
# --- 기본 실행 --- # --- 기본 실행 ---
# 주의: --remove-orphan 사용 금지 (다른 스택이 orphan으로 판단되어 종료될 수 있음) # 주의: --remove-orphan 사용 금지 (다른 스택이 orphan으로 판단되어 종료될 수 있음)
up: up-all
up-all: ensure-networks validate-auth-config up-all: ensure-networks validate-auth-config
@echo "Starting ALL stacks (infra + ory + app)..." @echo "Starting ALL stacks (infra + ory + app)..."
docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_INFRA) -f $(COMPOSE_ORY) -f $(COMPOSE_APP) up -d docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_INFRA) -f $(COMPOSE_ORY) -f $(COMPOSE_APP) up -d
@@ -61,7 +63,7 @@ up-ory: ensure-networks validate-auth-config
docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_ORY) up -d docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_ORY) up -d
up-app: ensure-networks validate-auth-config up-app: ensure-networks validate-auth-config
@echo "Starting App stack (backend/userfront/adminfront/devfront)..." @echo "Starting App stack (backend/userfront/adminfront/devfront/orgfront)..."
docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_APP) up -d docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_APP) up -d
up-backend: ensure-networks validate-auth-config up-backend: ensure-networks validate-auth-config

View File

@@ -395,11 +395,13 @@ USERFRONT_URL=https://sso.example.com
- `KRATOS_BROWSER_URL`: 보통 `${OATHKEEPER_PUBLIC_URL}/auth` - `KRATOS_BROWSER_URL`: 보통 `${OATHKEEPER_PUBLIC_URL}/auth`
- `KRATOS_UI_URL`: UserFront UI URL (로컬 예: `http://localhost:5000`) - `KRATOS_UI_URL`: UserFront UI URL (로컬 예: `http://localhost:5000`)
- `ADMINFRONT_CALLBACK_URLS`: 콤마 구분 콜백 목록 (예: `http://localhost:5173/auth/callback`) - `ADMINFRONT_CALLBACK_URLS`: 콤마 구분 콜백 목록 (예: `http://localhost:5173/auth/callback`)
- `DEVFRONT_CALLBACK_URLS`: 콤마 구분 콜백 목록 (예: `http://localhost:5174/callback`) - `DEVFRONT_CALLBACK_URLS`: 콤마 구분 콜백 목록 (예: `http://localhost:5174/auth/callback`)
- 주의: callback URL 끝에 `/`가 붙으면 `make validate-auth-config`에서 실패 처리됩니다. - 주의: callback URL 끝에 `/`가 붙으면 `make validate-auth-config`에서 실패 처리됩니다.
- `KRATOS_ALLOWED_RETURN_URLS_EXTRA`: 추가 허용 return URL (선택) - `KRATOS_ALLOWED_RETURN_URLS_EXTRA`: 추가 허용 return URL (선택)
- 빈값: `[]` - 빈값: `[]`
- 다중값: `["https://a.example.com/callback","https://b.example.com/callback"]` 또는 `https://a.example.com/callback,https://b.example.com/callback` - 다중값: `["https://a.example.com/callback","https://b.example.com/callback"]` 또는 `https://a.example.com/callback,https://b.example.com/callback`
- `KRATOS_ALLOWED_RETURN_URLS_JSON`: stage/prod에서 권장하는 전체 허용 return URL 목록
- 공개 도메인, `/ko`, `/en`, `/auth/callback`, `/ko/auth/callback`, `/en/auth/callback`, 각 front callback을 포함해야 합니다.
- `CLIENT_LOG_DEBUG`: 클라이언트 로그 디버그 모드 강제 (기본: 비운영 `true`, 운영 `false`) - `CLIENT_LOG_DEBUG`: 클라이언트 로그 디버그 모드 강제 (기본: 비운영 `true`, 운영 `false`)
- 운영(`APP_ENV=production|prod`)에서 `true|1|on|yes` 설정 시 `INFO/DEBUG` 클라이언트 로그 수집 허용 - 운영(`APP_ENV=production|prod`)에서 `true|1|on|yes` 설정 시 `INFO/DEBUG` 클라이언트 로그 수집 허용
- 미설정(기본) 시 운영에서는 `WARN/ERROR`만 수집 - 미설정(기본) 시 운영에서는 `WARN/ERROR`만 수집

View File

@@ -23,20 +23,20 @@ services:
# --- Kratos --- # --- Kratos ---
kratos-migrate: kratos-migrate:
image: oryd/kratos:${KRATOS_VERSION:-v25.4.0} image: oryd/kratos:${KRATOS_VERSION:-v26.2.0}
environment: environment:
- DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KRATOS_DB:-ory_kratos}?sslmode=disable&max_conns=20 - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KRATOS_DB:-ory_kratos}?sslmode=disable&max_conns=20
- KRATOS_SERVE_PUBLIC_BASE_URL=${KRATOS_BROWSER_URL:-http://localhost:4433} - KRATOS_SERVE_PUBLIC_BASE_URL=${KRATOS_BROWSER_URL}
- KRATOS_SERVE_ADMIN_BASE_URL=${KRATOS_ADMIN_URL:-http://kratos:4434} - KRATOS_SERVE_ADMIN_BASE_URL=${KRATOS_ADMIN_URL}
- KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL:-http://localhost:5000} - KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL}
- KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS=${KRATOS_ALLOWED_RETURN_URLS_JSON:-["http://localhost:5000","http://localhost:5000/"]} - KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS=${KRATOS_ALLOWED_RETURN_URLS_JSON:-["${KRATOS_UI_URL}","${KRATOS_UI_URL}/"]}
- KRATOS_SELFSERVICE_FLOWS_ERROR_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/error - KRATOS_SELFSERVICE_FLOWS_ERROR_UI_URL=${KRATOS_UI_URL}/error
- KRATOS_SELFSERVICE_FLOWS_SETTINGS_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/error?error=settings_disabled - KRATOS_SELFSERVICE_FLOWS_SETTINGS_UI_URL=${KRATOS_UI_URL}/error?error=settings_disabled
- KRATOS_SELFSERVICE_FLOWS_RECOVERY_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/recovery - KRATOS_SELFSERVICE_FLOWS_RECOVERY_UI_URL=${KRATOS_UI_URL}/recovery
- KRATOS_SELFSERVICE_FLOWS_VERIFICATION_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/verification - KRATOS_SELFSERVICE_FLOWS_VERIFICATION_UI_URL=${KRATOS_UI_URL}/verification
- KRATOS_SELFSERVICE_FLOWS_LOGIN_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/login - KRATOS_SELFSERVICE_FLOWS_LOGIN_UI_URL=${KRATOS_UI_URL}/login
- KRATOS_SELFSERVICE_FLOWS_REGISTRATION_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/registration - KRATOS_SELFSERVICE_FLOWS_REGISTRATION_UI_URL=${KRATOS_UI_URL}/registration
- KRATOS_SELFSERVICE_FLOWS_LOGOUT_AFTER_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL:-http://localhost:5000}/login - KRATOS_SELFSERVICE_FLOWS_LOGOUT_AFTER_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL}/login
volumes: volumes:
- ./docker/ory/kratos:/etc/config/kratos - ./docker/ory/kratos:/etc/config/kratos
command: migrate sql up -e -c /etc/config/kratos/kratos.yml --yes command: migrate sql up -e -c /etc/config/kratos/kratos.yml --yes
@@ -47,22 +47,22 @@ services:
- ory-net - ory-net
kratos: kratos:
image: oryd/kratos:${KRATOS_VERSION:-v25.4.0} image: oryd/kratos:${KRATOS_VERSION:-v26.2.0}
container_name: ory_kratos container_name: ory_kratos
environment: environment:
- DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KRATOS_DB:-ory_kratos}?sslmode=disable&max_conns=20 - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KRATOS_DB:-ory_kratos}?sslmode=disable&max_conns=20
- COOKIE_SECRET=${COOKIE_SECRET:-localcookie123} - COOKIE_SECRET=${COOKIE_SECRET:-localcookie123}
- KRATOS_SERVE_PUBLIC_BASE_URL=${KRATOS_BROWSER_URL:-http://localhost:4433} - KRATOS_SERVE_PUBLIC_BASE_URL=${KRATOS_BROWSER_URL}
- KRATOS_SERVE_ADMIN_BASE_URL=${KRATOS_ADMIN_URL:-http://kratos:4434} - KRATOS_SERVE_ADMIN_BASE_URL=${KRATOS_ADMIN_URL}
- KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL:-http://localhost:5000} - KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL}
- KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS=${KRATOS_ALLOWED_RETURN_URLS_JSON:-["http://localhost:5000","http://localhost:5000/"]} - KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS=${KRATOS_ALLOWED_RETURN_URLS_JSON:-["${KRATOS_UI_URL}","${KRATOS_UI_URL}/"]}
- KRATOS_SELFSERVICE_FLOWS_ERROR_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/error - KRATOS_SELFSERVICE_FLOWS_ERROR_UI_URL=${KRATOS_UI_URL}/error
- KRATOS_SELFSERVICE_FLOWS_SETTINGS_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/error?error=settings_disabled - KRATOS_SELFSERVICE_FLOWS_SETTINGS_UI_URL=${KRATOS_UI_URL}/error?error=settings_disabled
- KRATOS_SELFSERVICE_FLOWS_RECOVERY_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/recovery - KRATOS_SELFSERVICE_FLOWS_RECOVERY_UI_URL=${KRATOS_UI_URL}/recovery
- KRATOS_SELFSERVICE_FLOWS_VERIFICATION_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/verification - KRATOS_SELFSERVICE_FLOWS_VERIFICATION_UI_URL=${KRATOS_UI_URL}/verification
- KRATOS_SELFSERVICE_FLOWS_LOGIN_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/login - KRATOS_SELFSERVICE_FLOWS_LOGIN_UI_URL=${KRATOS_UI_URL}/login
- KRATOS_SELFSERVICE_FLOWS_REGISTRATION_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/registration - KRATOS_SELFSERVICE_FLOWS_REGISTRATION_UI_URL=${KRATOS_UI_URL}/registration
- KRATOS_SELFSERVICE_FLOWS_LOGOUT_AFTER_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL:-http://localhost:5000}/login - KRATOS_SELFSERVICE_FLOWS_LOGOUT_AFTER_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL}/login
volumes: volumes:
- ./docker/ory/kratos:/etc/config/kratos - ./docker/ory/kratos:/etc/config/kratos
command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier
@@ -75,7 +75,7 @@ services:
# --- Hydra --- # --- Hydra ---
hydra-migrate: hydra-migrate:
image: oryd/hydra:${HYDRA_VERSION:-v25.4.0} image: oryd/hydra:${HYDRA_VERSION:-v26.2.0}
environment: environment:
- DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${HYDRA_DB:-ory_hydra}?sslmode=disable&max_conns=20 - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${HYDRA_DB:-ory_hydra}?sslmode=disable&max_conns=20
command: migrate sql up -e --yes command: migrate sql up -e --yes
@@ -86,14 +86,14 @@ services:
- ory-net - ory-net
hydra: hydra:
image: oryd/hydra:${HYDRA_VERSION:-v25.4.0} image: oryd/hydra:${HYDRA_VERSION:-v26.2.0}
container_name: ory_hydra container_name: ory_hydra
environment: environment:
- DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${HYDRA_DB:-ory_hydra}?sslmode=disable&max_conns=20 - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${HYDRA_DB:-ory_hydra}?sslmode=disable&max_conns=20
- URLS_SELF_ISSUER=${USERFRONT_URL:-http://localhost:5000}/oidc - URLS_SELF_ISSUER=${HYDRA_PUBLIC_URL}
- URLS_LOGIN=${USERFRONT_URL:-http://localhost:5000}/login - URLS_LOGIN=${HYDRA_LOGIN_URL:-${USERFRONT_URL}/login}
- URLS_CONSENT=${USERFRONT_URL:-http://localhost:5000}/consent - URLS_CONSENT=${HYDRA_CONSENT_URL:-${USERFRONT_URL}/consent}
- URLS_ERROR=${USERFRONT_URL:-http://localhost:5000}/error - URLS_ERROR=${HYDRA_ERROR_URL:-${USERFRONT_URL}/error}
- SECRETS_SYSTEM=${ORY_POSTGRES_PASSWORD} - SECRETS_SYSTEM=${ORY_POSTGRES_PASSWORD}
volumes: volumes:
- ./docker/ory/hydra:/etc/config/hydra - ./docker/ory/hydra:/etc/config/hydra
@@ -107,7 +107,7 @@ services:
# --- Keto --- # --- Keto ---
keto-migrate: keto-migrate:
image: oryd/keto:${KETO_VERSION:-v25.4.0} image: oryd/keto:${KETO_VERSION:-v26.2.0}
environment: environment:
- DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20 - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20
volumes: volumes:
@@ -120,7 +120,7 @@ services:
- ory-net - ory-net
keto: keto:
image: oryd/keto:${KETO_VERSION:-v25.4.0} image: oryd/keto:${KETO_VERSION:-v26.2.0}
container_name: ory_keto container_name: ory_keto
environment: environment:
- DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20 - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20
@@ -136,14 +136,19 @@ services:
# --- Oathkeeper --- # --- Oathkeeper ---
oathkeeper_logs_init: oathkeeper_logs_init:
image: alpine:latest image: alpine:latest
command: ["sh", "-c", "mkdir -p /var/log/oathkeeper && chown -R ${OATHKEEPER_UID:-1001}:${OATHKEEPER_GID:-1001} /var/log/oathkeeper"] command:
[
"sh",
"-c",
"mkdir -p /var/log/oathkeeper && chown -R ${OATHKEEPER_UID:-1001}:${OATHKEEPER_GID:-1001} /var/log/oathkeeper",
]
volumes: volumes:
- oathkeeper_logs:/var/log/oathkeeper - oathkeeper_logs:/var/log/oathkeeper
networks: networks:
- ory-net - ory-net
oathkeeper: oathkeeper:
image: oryd/oathkeeper:${OATHKEEPER_VERSION:-v25.4.0} image: oryd/oathkeeper:${OATHKEEPER_VERSION:-v26.2.0}
container_name: ory_oathkeeper container_name: ory_oathkeeper
user: "${OATHKEEPER_UID:-1001}:${OATHKEEPER_GID:-1001}" user: "${OATHKEEPER_UID:-1001}:${OATHKEEPER_GID:-1001}"
ports: ports:
@@ -220,56 +225,56 @@ services:
- /bin/sh - /bin/sh
- -ec - -ec
- | - |
apk add --no-cache curl tar apk add --no-cache curl tar
HYDRA_CLI_VERSION="$${HYDRA_VERSION:-v26.2.0}" HYDRA_CLI_VERSION="$${HYDRA_VERSION:-v26.2.0}"
HYDRA_CLI_VERSION="$${HYDRA_CLI_VERSION%-distroless}" HYDRA_CLI_VERSION="$${HYDRA_CLI_VERSION%-distroless}"
HYDRA_CLI_ARCHIVE_VERSION="$${HYDRA_CLI_VERSION#v}" HYDRA_CLI_ARCHIVE_VERSION="$${HYDRA_CLI_VERSION#v}"
curl -fsSLo /tmp/hydra.tar.gz "https://github.com/ory/hydra/releases/download/$${HYDRA_CLI_VERSION}/hydra_$${HYDRA_CLI_ARCHIVE_VERSION}-linux_64bit.tar.gz" curl -fsSLo /tmp/hydra.tar.gz "https://github.com/ory/hydra/releases/download/$${HYDRA_CLI_VERSION}/hydra_$${HYDRA_CLI_ARCHIVE_VERSION}-linux_64bit.tar.gz"
tar -xzf /tmp/hydra.tar.gz -C /usr/local/bin hydra tar -xzf /tmp/hydra.tar.gz -C /usr/local/bin hydra
rm /tmp/hydra.tar.gz rm /tmp/hydra.tar.gz
hydra delete oauth2-client --endpoint http://hydra:4445 adminfront >/dev/null 2>&1 || true hydra delete oauth2-client --endpoint "$${HYDRA_ADMIN_URL}" adminfront >/dev/null 2>&1 || true
hydra delete oauth2-client --endpoint http://hydra:4445 devfront >/dev/null 2>&1 || true hydra delete oauth2-client --endpoint "$${HYDRA_ADMIN_URL}" devfront >/dev/null 2>&1 || true
hydra delete oauth2-client --endpoint http://hydra:4445 orgfront >/dev/null 2>&1 || true hydra delete oauth2-client --endpoint "$${HYDRA_ADMIN_URL}" orgfront >/dev/null 2>&1 || true
hydra delete oauth2-client --endpoint http://hydra:4445 ${OATHKEEPER_INTROSPECT_CLIENT_ID:-oathkeeper-introspect} >/dev/null 2>&1 || true hydra delete oauth2-client --endpoint "$${HYDRA_ADMIN_URL}" "$${OATHKEEPER_INTROSPECT_CLIENT_ID:-oathkeeper-introspect}" >/dev/null 2>&1 || true
hydra create oauth2-client \ hydra create oauth2-client \
--endpoint http://hydra:4445 \ --endpoint "$${HYDRA_ADMIN_URL}" \
--id adminfront \ --id adminfront \
--name "AdminFront" \ --name "AdminFront" \
--grant-type authorization_code,refresh_token \
--response-type code \
--scope openid,offline_access,profile,email \
--token-endpoint-auth-method none \
--redirect-uri ${ADMINFRONT_CALLBACK_URLS}
hydra create oauth2-client \
--endpoint "$${HYDRA_ADMIN_URL}" \
--id devfront \
--name "DevFront" \
--grant-type authorization_code,refresh_token \ --grant-type authorization_code,refresh_token \
--response-type code \ --response-type code \
--scope openid,offline_access,profile,email \ --scope openid,offline_access,profile,email \
--token-endpoint-auth-method none \ --token-endpoint-auth-method none \
--redirect-uri ${ADMINFRONT_CALLBACK_URLS:-http://localhost:5173/auth/callback} --redirect-uri ${DEVFRONT_CALLBACK_URLS}
hydra create oauth2-client \ hydra create oauth2-client \
--endpoint http://hydra:4445 \ --endpoint "$${HYDRA_ADMIN_URL}" \
--id devfront \ --id orgfront \
--name "DevFront" \ --name "OrgFront" \
--grant-type authorization_code,refresh_token \ --grant-type authorization_code,refresh_token \
--response-type code \ --response-type code \
--scope openid,offline_access,profile,email \ --scope openid,offline_access,profile,email \
--token-endpoint-auth-method none \ --token-endpoint-auth-method none \
--redirect-uri ${DEVFRONT_CALLBACK_URLS:-http://localhost:5174/auth/callback} --redirect-uri ${ORGFRONT_CALLBACK_URLS}
hydra create oauth2-client \ hydra create oauth2-client \
--endpoint http://hydra:4445 \ --endpoint "$${HYDRA_ADMIN_URL}" \
--id orgfront \ --id "$${OATHKEEPER_INTROSPECT_CLIENT_ID:-oathkeeper-introspect}" \
--name "OrgFront" \ --secret "$${OATHKEEPER_INTROSPECT_CLIENT_SECRET:-oathkeeper-secret}" \
--grant-type authorization_code,refresh_token \ --grant-type client_credentials \
--response-type code \ --response-type token \
--scope openid,offline_access,profile,email \ --scope openid,offline_access,profile,email
--token-endpoint-auth-method none \
--redirect-uri ${ORGFRONT_CALLBACK_URLS:-http://localhost:5175/auth/callback,https://baron-orgchart.hmac.kr/auth/callback}
hydra create oauth2-client \
--endpoint http://hydra:4445 \
--id ${OATHKEEPER_INTROSPECT_CLIENT_ID:-oathkeeper-introspect} \
--secret ${OATHKEEPER_INTROSPECT_CLIENT_SECRET:-oathkeeper-secret} \
--grant-type client_credentials \
--response-type token \
--scope openid,offline_access,profile,email
depends_on: depends_on:
ory_stack_check: ory_stack_check:
condition: service_completed_successfully condition: service_completed_successfully

View File

@@ -18,11 +18,15 @@ echo "🚀 Creating instance: ${INSTANCE_NAME} (Port Prefix: ${PORT_PREFIX}xxx)"
# 1. 폴더 구조 생성 # 1. 폴더 구조 생성
mkdir -p "${TARGET_DIR}/gateway" mkdir -p "${TARGET_DIR}/gateway"
mkdir -p "${TARGET_DIR}/ory/init-db"
mkdir -p "${TARGET_DIR}/ory/kratos" mkdir -p "${TARGET_DIR}/ory/kratos"
mkdir -p "${TARGET_DIR}/ory/hydra"
mkdir -p "${TARGET_DIR}/ory/keto"
mkdir -p "${TARGET_DIR}/ory/oathkeeper" mkdir -p "${TARGET_DIR}/ory/oathkeeper"
mkdir -p "${TARGET_DIR}/userfront" mkdir -p "${TARGET_DIR}/userfront"
mkdir -p "${TARGET_DIR}/adminfront" mkdir -p "${TARGET_DIR}/adminfront"
mkdir -p "${TARGET_DIR}/devfront" mkdir -p "${TARGET_DIR}/devfront"
mkdir -p "${TARGET_DIR}/orgfront"
# 2. .env 생성 및 변수 로드 # 2. .env 생성 및 변수 로드
sed "s/{{INSTANCE_NAME}}/${INSTANCE_NAME}/g; s/{{PORT_PREFIX}}/${PORT_PREFIX}/g" \ sed "s/{{INSTANCE_NAME}}/${INSTANCE_NAME}/g; s/{{PORT_PREFIX}}/${PORT_PREFIX}/g" \
@@ -34,6 +38,7 @@ USERFRONT_PORT="${PORT_PREFIX}500"
DOMAIN_SUFFIX=$(grep "DOMAIN_SUFFIX=" "${TARGET_DIR}/.env" | cut -d'=' -f2 | tr -d '\r') DOMAIN_SUFFIX=$(grep "DOMAIN_SUFFIX=" "${TARGET_DIR}/.env" | cut -d'=' -f2 | tr -d '\r')
ADMINFRONT_DOMAIN="${INSTANCE_NAME}-admin.${DOMAIN_SUFFIX}" ADMINFRONT_DOMAIN="${INSTANCE_NAME}-admin.${DOMAIN_SUFFIX}"
DEVFRONT_DOMAIN="${INSTANCE_NAME}-dev.${DOMAIN_SUFFIX}" DEVFRONT_DOMAIN="${INSTANCE_NAME}-dev.${DOMAIN_SUFFIX}"
ORGFRONT_DOMAIN="${INSTANCE_NAME}-org.${DOMAIN_SUFFIX}"
# 3. Docker Compose & Config 복사 및 치환 # 3. Docker Compose & Config 복사 및 치환
cp "${BASE_DIR}/templates/docker-compose.yaml" "${TARGET_DIR}/" cp "${BASE_DIR}/templates/docker-compose.yaml" "${TARGET_DIR}/"
@@ -55,15 +60,22 @@ sed "s/{{ADMINFRONT_DOMAIN}}/${ADMINFRONT_DOMAIN}/g; s/{{BACKEND_PORT}}/${BACKEN
"${BASE_DIR}/templates/adminfront/vite.config.ts" > "${TARGET_DIR}/adminfront/vite.config.ts" "${BASE_DIR}/templates/adminfront/vite.config.ts" > "${TARGET_DIR}/adminfront/vite.config.ts"
sed "s/{{DEVFRONT_DOMAIN}}/${DEVFRONT_DOMAIN}/g; s/{{BACKEND_PORT}}/${BACKEND_PORT}/g" \ sed "s/{{DEVFRONT_DOMAIN}}/${DEVFRONT_DOMAIN}/g; s/{{BACKEND_PORT}}/${BACKEND_PORT}/g" \
"${BASE_DIR}/templates/devfront/vite.config.ts" > "${TARGET_DIR}/devfront/vite.config.ts" "${BASE_DIR}/templates/devfront/vite.config.ts" > "${TARGET_DIR}/devfront/vite.config.ts"
sed "s/{{ORGFRONT_DOMAIN}}/${ORGFRONT_DOMAIN}/g; s/{{BACKEND_PORT}}/${BACKEND_PORT}/g" \
"${BASE_DIR}/templates/orgfront/vite.config.ts" > "${TARGET_DIR}/orgfront/vite.config.ts"
# 4. 프론트엔드 auth.ts 주입 (하드코딩된 포트 해결) # 4. 프론트엔드 auth.ts 주입 (하드코딩된 포트 해결)
sed "s/{{USERFRONT_PORT}}/${USERFRONT_PORT}/g; s/{{CLIENT_ID}}/adminfront/g" \ sed "s/{{USERFRONT_PORT}}/${USERFRONT_PORT}/g; s/{{CLIENT_ID}}/adminfront/g" \
"${BASE_DIR}/templates/auth.template.ts" > "${TARGET_DIR}/adminfront/auth.ts" "${BASE_DIR}/templates/auth.template.ts" > "${TARGET_DIR}/adminfront/auth.ts"
sed "s/{{USERFRONT_PORT}}/${USERFRONT_PORT}/g; s/{{CLIENT_ID}}/devfront/g" \ sed "s/{{USERFRONT_PORT}}/${USERFRONT_PORT}/g; s/{{CLIENT_ID}}/devfront/g" \
"${BASE_DIR}/templates/auth.template.ts" > "${TARGET_DIR}/devfront/auth.ts" "${BASE_DIR}/templates/auth.template.ts" > "${TARGET_DIR}/devfront/auth.ts"
sed "s/{{USERFRONT_PORT}}/${USERFRONT_PORT}/g" \
"${BASE_DIR}/templates/orgfront/auth.ts" > "${TARGET_DIR}/orgfront/auth.ts"
# 5. Ory 정적 설정 복사 # 5. Ory 정적 설정 복사
if [ -d "${BASE_DIR}/../docker/ory/init-db" ]; then cp -n "${BASE_DIR}/../docker/ory/init-db/"* "${TARGET_DIR}/ory/init-db/" 2>/dev/null || true; fi
if [ -d "${BASE_DIR}/../docker/ory/kratos" ]; then cp -n "${BASE_DIR}/../docker/ory/kratos/"* "${TARGET_DIR}/ory/kratos/" 2>/dev/null || true; fi if [ -d "${BASE_DIR}/../docker/ory/kratos" ]; then cp -n "${BASE_DIR}/../docker/ory/kratos/"* "${TARGET_DIR}/ory/kratos/" 2>/dev/null || true; fi
if [ -d "${BASE_DIR}/../docker/ory/hydra" ]; then cp -n "${BASE_DIR}/../docker/ory/hydra/"* "${TARGET_DIR}/ory/hydra/" 2>/dev/null || true; fi
if [ -d "${BASE_DIR}/../docker/ory/keto" ]; then cp -n "${BASE_DIR}/../docker/ory/keto/"* "${TARGET_DIR}/ory/keto/" 2>/dev/null || true; fi
if [ -d "${BASE_DIR}/../docker/ory/oathkeeper" ]; then cp -n "${BASE_DIR}/../docker/ory/oathkeeper/"* "${TARGET_DIR}/ory/oathkeeper/" 2>/dev/null || true; fi if [ -d "${BASE_DIR}/../docker/ory/oathkeeper" ]; then cp -n "${BASE_DIR}/../docker/ory/oathkeeper/"* "${TARGET_DIR}/ory/oathkeeper/" 2>/dev/null || true; fi
# 6. 마무리 # 6. 마무리

View File

@@ -17,6 +17,7 @@ BACKEND_PORT=${P}000
USERFRONT_PORT=${P}500 USERFRONT_PORT=${P}500
ADMINFRONT_PORT=${P}173 ADMINFRONT_PORT=${P}173
DEVFRONT_PORT=${P}174 DEVFRONT_PORT=${P}174
ORGFRONT_PORT=${P}175
OATHKEEPER_PROXY_PORT=${P}467 OATHKEEPER_PROXY_PORT=${P}467
# === [3] 도메인 설정 (별도 도메인 구조) === # === [3] 도메인 설정 (별도 도메인 구조) ===
@@ -25,23 +26,44 @@ DOMAIN_SUFFIX=hmac.kr
USERFRONT_URL=https://{{INSTANCE_NAME}}-sso.${DOMAIN_SUFFIX} USERFRONT_URL=https://{{INSTANCE_NAME}}-sso.${DOMAIN_SUFFIX}
ADMINFRONT_URL=https://{{INSTANCE_NAME}}-admin.${DOMAIN_SUFFIX} ADMINFRONT_URL=https://{{INSTANCE_NAME}}-admin.${DOMAIN_SUFFIX}
DEVFRONT_URL=https://{{INSTANCE_NAME}}-dev.${DOMAIN_SUFFIX} DEVFRONT_URL=https://{{INSTANCE_NAME}}-dev.${DOMAIN_SUFFIX}
ORGFRONT_URL=https://{{INSTANCE_NAME}}-org.${DOMAIN_SUFFIX}
# OIDC/Auth URL # OIDC/Auth URL
VITE_OIDC_AUTHORITY=${USERFRONT_URL}/oidc VITE_OIDC_AUTHORITY=${USERFRONT_URL}/oidc
ADMINFRONT_CALLBACK_URLS=${ADMINFRONT_URL}/auth/callback ADMINFRONT_CALLBACK_URLS=${ADMINFRONT_URL}/auth/callback
DEVFRONT_CALLBACK_URLS=${DEVFRONT_URL}/auth/callback DEVFRONT_CALLBACK_URLS=${DEVFRONT_URL}/auth/callback
ORGFRONT_CALLBACK_URLS=${ORGFRONT_URL}/auth/callback
# Ory URL # Ory URL
KRATOS_UI_URL=${USERFRONT_URL}/auth KRATOS_UI_URL=${USERFRONT_URL}/auth
KRATOS_BROWSER_URL=${USERFRONT_URL}/auth KRATOS_BROWSER_URL=${USERFRONT_URL}/auth
KRATOS_ADMIN_URL=http://kratos:4434
HYDRA_PUBLIC_URL=${USERFRONT_URL}/oidc HYDRA_PUBLIC_URL=${USERFRONT_URL}/oidc
HYDRA_ADMIN_URL=http://hydra:4445
OATHKEEPER_PUBLIC_URL=${USERFRONT_URL} OATHKEEPER_PUBLIC_URL=${USERFRONT_URL}
KETO_READ_URL=http://keto:4466
KETO_WRITE_URL=http://keto:4467
# Ory versions
KRATOS_VERSION=v26.2.0
HYDRA_VERSION=v26.2.0
KETO_VERSION=v26.2.0
OATHKEEPER_VERSION=v26.2.0
ORY_POSTGRES_TAG=17-alpine
# === [4] IDP 및 DB Config === # === [4] IDP 및 DB Config ===
IDP_PROVIDER=ory IDP_PROVIDER=ory
DB_PASSWORD=password DB_PASSWORD=password
ORY_POSTGRES_USER=ory ORY_POSTGRES_USER=ory
ORY_POSTGRES_PASSWORD=generated_secret_here ORY_POSTGRES_PASSWORD=generated_secret_here
ORY_POSTGRES_DB=ory
KRATOS_DB=ory_kratos
HYDRA_DB=ory_hydra
KETO_DB=ory_keto
OATHKEEPER_UID=1001
OATHKEEPER_GID=1001
OATHKEEPER_INTROSPECT_CLIENT_ID=oathkeeper-introspect
OATHKEEPER_INTROSPECT_CLIENT_SECRET=oathkeeper-secret
CLICKHOUSE_PASSWORD=password CLICKHOUSE_PASSWORD=password
REDIS_ADDR=redis:6379 REDIS_ADDR=redis:6379

View File

@@ -10,7 +10,7 @@ services:
ports: ports:
- "${DB_PORT}:5432" - "${DB_PORT}:5432"
volumes: volumes:
- db_data_${INSTANCE_NAME}:/var/lib/postgresql/data - db_data:/var/lib/postgresql/data
networks: [app_net] networks: [app_net]
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"] test: ["CMD-SHELL", "pg_isready -U postgres"]
@@ -33,42 +33,228 @@ services:
- "${CLICKHOUSE_PORT_HTTP}:8123" - "${CLICKHOUSE_PORT_HTTP}:8123"
- "${CLICKHOUSE_PORT_NATIVE}:9000" - "${CLICKHOUSE_PORT_NATIVE}:9000"
volumes: volumes:
- clickhouse_data_${INSTANCE_NAME}:/var/lib/clickhouse - clickhouse_data:/var/lib/clickhouse
networks: [app_net] networks: [app_net]
# --- Ory Stack --- # --- Ory Stack ---
postgres_ory: postgres_ory:
image: postgres:17-alpine image: postgres:${ORY_POSTGRES_TAG:-17-alpine}
container_name: ${COMPOSE_PROJECT_NAME}_ory_db container_name: ${COMPOSE_PROJECT_NAME}_ory_db
environment: environment:
- POSTGRES_USER=${ORY_POSTGRES_USER} - POSTGRES_USER=${ORY_POSTGRES_USER:-ory}
- POSTGRES_PASSWORD=${ORY_POSTGRES_PASSWORD} - POSTGRES_PASSWORD=${ORY_POSTGRES_PASSWORD}
- POSTGRES_DB=${ORY_POSTGRES_DB:-ory}
volumes: volumes:
- ory_db_data_${INSTANCE_NAME}:/var/lib/postgresql/data - ory_db_data:/var/lib/postgresql/data
- ./ory/init-db:/docker-entrypoint-initdb.d:ro
networks: [app_net] networks: [app_net]
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${ORY_POSTGRES_USER}"] test: ["CMD-SHELL", "pg_isready -U ${ORY_POSTGRES_USER:-ory} -d ${KRATOS_DB:-ory_kratos}"]
interval: 5s interval: 5s
kratos: kratos-migrate:
image: oryd/kratos:v25.4.0 image: oryd/kratos:${KRATOS_VERSION:-v26.2.0}
container_name: ${COMPOSE_PROJECT_NAME}_kratos
env_file: .env env_file: .env
environment:
- DSN=postgres://${ORY_POSTGRES_USER:-ory}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KRATOS_DB:-ory_kratos}?sslmode=disable&max_conns=20
- KRATOS_SERVE_PUBLIC_BASE_URL=${KRATOS_BROWSER_URL}
- KRATOS_SERVE_ADMIN_BASE_URL=${KRATOS_ADMIN_URL:-http://kratos:4434}
- KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL}
- KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS=${KRATOS_ALLOWED_RETURN_URLS_JSON:-["${KRATOS_UI_URL}","${KRATOS_UI_URL}/","${USERFRONT_URL}","${USERFRONT_URL}/","${USERFRONT_URL}/ko","${USERFRONT_URL}/ko/","${USERFRONT_URL}/en","${USERFRONT_URL}/en/","${USERFRONT_URL}/auth/callback","${USERFRONT_URL}/ko/auth/callback","${USERFRONT_URL}/en/auth/callback","${ADMINFRONT_URL}/auth/callback","${DEVFRONT_URL}/auth/callback","${ORGFRONT_URL}/auth/callback"]}
- KRATOS_SELFSERVICE_FLOWS_ERROR_UI_URL=${KRATOS_UI_URL}/error
- KRATOS_SELFSERVICE_FLOWS_SETTINGS_UI_URL=${KRATOS_UI_URL}/error?error=settings_disabled
- KRATOS_SELFSERVICE_FLOWS_RECOVERY_UI_URL=${KRATOS_UI_URL}/recovery
- KRATOS_SELFSERVICE_FLOWS_VERIFICATION_UI_URL=${KRATOS_UI_URL}/verification
- KRATOS_SELFSERVICE_FLOWS_LOGIN_UI_URL=${KRATOS_UI_URL}/login
- KRATOS_SELFSERVICE_FLOWS_REGISTRATION_UI_URL=${KRATOS_UI_URL}/registration
- KRATOS_SELFSERVICE_FLOWS_LOGOUT_AFTER_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL}/login
volumes: volumes:
- ./ory/kratos:/etc/config/kratos:ro - ./ory/kratos:/etc/config/kratos:ro
command: serve -c /etc/config/kratos/kratos.yml --dev command: migrate sql up -e -c /etc/config/kratos/kratos.yml --yes
networks: [app_net] networks: [app_net]
depends_on: depends_on:
postgres_ory: { condition: service_healthy } postgres_ory: { condition: service_healthy }
kratos:
image: oryd/kratos:${KRATOS_VERSION:-v26.2.0}
container_name: ${COMPOSE_PROJECT_NAME}_kratos
env_file: .env
environment:
- DSN=postgres://${ORY_POSTGRES_USER:-ory}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KRATOS_DB:-ory_kratos}?sslmode=disable&max_conns=20
- COOKIE_SECRET=${COOKIE_SECRET}
- KRATOS_SERVE_PUBLIC_BASE_URL=${KRATOS_BROWSER_URL}
- KRATOS_SERVE_ADMIN_BASE_URL=${KRATOS_ADMIN_URL:-http://kratos:4434}
- KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL}
- KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS=${KRATOS_ALLOWED_RETURN_URLS_JSON:-["${KRATOS_UI_URL}","${KRATOS_UI_URL}/","${USERFRONT_URL}","${USERFRONT_URL}/","${USERFRONT_URL}/ko","${USERFRONT_URL}/ko/","${USERFRONT_URL}/en","${USERFRONT_URL}/en/","${USERFRONT_URL}/auth/callback","${USERFRONT_URL}/ko/auth/callback","${USERFRONT_URL}/en/auth/callback","${ADMINFRONT_URL}/auth/callback","${DEVFRONT_URL}/auth/callback","${ORGFRONT_URL}/auth/callback"]}
- KRATOS_SELFSERVICE_FLOWS_ERROR_UI_URL=${KRATOS_UI_URL}/error
- KRATOS_SELFSERVICE_FLOWS_SETTINGS_UI_URL=${KRATOS_UI_URL}/error?error=settings_disabled
- KRATOS_SELFSERVICE_FLOWS_RECOVERY_UI_URL=${KRATOS_UI_URL}/recovery
- KRATOS_SELFSERVICE_FLOWS_VERIFICATION_UI_URL=${KRATOS_UI_URL}/verification
- KRATOS_SELFSERVICE_FLOWS_LOGIN_UI_URL=${KRATOS_UI_URL}/login
- KRATOS_SELFSERVICE_FLOWS_REGISTRATION_UI_URL=${KRATOS_UI_URL}/registration
- KRATOS_SELFSERVICE_FLOWS_LOGOUT_AFTER_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL}/login
volumes:
- ./ory/kratos:/etc/config/kratos:ro
command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier
networks: [app_net]
depends_on:
kratos-migrate: { condition: service_completed_successfully }
hydra-migrate:
image: oryd/hydra:${HYDRA_VERSION:-v26.2.0}
env_file: .env
environment:
- DSN=postgres://${ORY_POSTGRES_USER:-ory}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${HYDRA_DB:-ory_hydra}?sslmode=disable&max_conns=20
command: migrate sql up -e --yes
networks: [app_net]
depends_on:
postgres_ory: { condition: service_healthy }
hydra:
image: oryd/hydra:${HYDRA_VERSION:-v26.2.0}
container_name: ${COMPOSE_PROJECT_NAME}_hydra
env_file: .env
environment:
- DSN=postgres://${ORY_POSTGRES_USER:-ory}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${HYDRA_DB:-ory_hydra}?sslmode=disable&max_conns=20
- URLS_SELF_ISSUER=${HYDRA_PUBLIC_URL}
- URLS_LOGIN=${HYDRA_LOGIN_URL:-${USERFRONT_URL}/login}
- URLS_CONSENT=${HYDRA_CONSENT_URL:-${USERFRONT_URL}/consent}
- URLS_ERROR=${HYDRA_ERROR_URL:-${USERFRONT_URL}/error}
- SECRETS_SYSTEM=${ORY_POSTGRES_PASSWORD}
volumes:
- ./ory/hydra:/etc/config/hydra:ro
command: serve -c /etc/config/hydra/hydra.yml all --dev
networks: [app_net]
depends_on:
hydra-migrate: { condition: service_completed_successfully }
keto-migrate:
image: oryd/keto:${KETO_VERSION:-v26.2.0}
env_file: .env
environment:
- DSN=postgres://${ORY_POSTGRES_USER:-ory}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20
volumes:
- ./ory/keto:/etc/config/keto:ro
command: ["migrate", "up", "-c", "/etc/config/keto/keto.yml", "--yes"]
networks: [app_net]
depends_on:
postgres_ory: { condition: service_healthy }
keto:
image: oryd/keto:${KETO_VERSION:-v26.2.0}
container_name: ${COMPOSE_PROJECT_NAME}_keto
env_file: .env
environment:
- DSN=postgres://${ORY_POSTGRES_USER:-ory}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20
volumes:
- ./ory/keto:/etc/config/keto:ro
command: serve -c /etc/config/keto/keto.yml
networks: [app_net]
depends_on:
keto-migrate: { condition: service_completed_successfully }
oathkeeper_logs_init:
image: alpine:latest
command: ["sh", "-c", "mkdir -p /var/log/oathkeeper && chown -R ${OATHKEEPER_UID:-1001}:${OATHKEEPER_GID:-1001} /var/log/oathkeeper"]
volumes:
- oathkeeper_logs:/var/log/oathkeeper
networks: [app_net]
oathkeeper: oathkeeper:
image: oryd/oathkeeper:v25.4.0 image: oryd/oathkeeper:${OATHKEEPER_VERSION:-v26.2.0}
container_name: ${COMPOSE_PROJECT_NAME}_oathkeeper container_name: ${COMPOSE_PROJECT_NAME}_oathkeeper
env_file: .env env_file: .env
user: "${OATHKEEPER_UID:-1001}:${OATHKEEPER_GID:-1001}"
ports: ports:
- "${OATHKEEPER_PROXY_PORT}:4455" - "${OATHKEEPER_PROXY_PORT}:4455"
environment:
- APP_ENV=${APP_ENV:-production}
- LOG_LEVEL=debug
- OATHKEEPER_INTROSPECT_CLIENT_ID=${OATHKEEPER_INTROSPECT_CLIENT_ID:-oathkeeper-introspect}
- OATHKEEPER_INTROSPECT_CLIENT_SECRET=${OATHKEEPER_INTROSPECT_CLIENT_SECRET:-oathkeeper-secret}
volumes: volumes:
- ./ory/oathkeeper:/etc/config/oathkeeper:ro - ./ory/oathkeeper:/etc/config/oathkeeper:ro
- oathkeeper_logs:/var/log/oathkeeper
entrypoint: ["/etc/config/oathkeeper/entrypoint.sh"]
networks: [app_net]
depends_on:
oathkeeper_logs_init: { condition: service_completed_successfully }
kratos: { condition: service_started }
hydra: { condition: service_started }
ory_stack_check:
image: alpine:latest
container_name: ${COMPOSE_PROJECT_NAME}_ory_stack_check
command: >
/bin/sh -c "
apk add --no-cache curl;
echo 'Wait for Ory services...';
until curl -s http://kratos:4433/health/ready; do sleep 1; done;
until curl -s http://hydra:4444/health/ready; do sleep 1; done;
until curl -s http://keto:4466/health/ready; do sleep 1; done;
echo 'Ory stack is ready.';"
depends_on:
- kratos
- hydra
- keto
networks: [app_net]
init-rp:
image: alpine:latest
env_file: .env
command:
- /bin/sh
- -ec
- |
apk add --no-cache curl tar
HYDRA_CLI_VERSION="$${HYDRA_VERSION:-v26.2.0}"
HYDRA_CLI_VERSION="$${HYDRA_CLI_VERSION%-distroless}"
HYDRA_CLI_ARCHIVE_VERSION="$${HYDRA_CLI_VERSION#v}"
curl -fsSLo /tmp/hydra.tar.gz "https://github.com/ory/hydra/releases/download/$${HYDRA_CLI_VERSION}/hydra_$${HYDRA_CLI_ARCHIVE_VERSION}-linux_64bit.tar.gz"
tar -xzf /tmp/hydra.tar.gz -C /usr/local/bin hydra
rm /tmp/hydra.tar.gz
upsert_client() {
ID=$$1
shift
if hydra get oauth2-client --endpoint "$${HYDRA_ADMIN_URL:-http://hydra:4445}" "$$ID" >/dev/null 2>&1; then
hydra update oauth2-client --endpoint "$${HYDRA_ADMIN_URL:-http://hydra:4445}" "$$ID" "$$@"
else
hydra create oauth2-client --endpoint "$${HYDRA_ADMIN_URL:-http://hydra:4445}" --id "$$ID" "$$@"
fi
}
upsert_client "adminfront" \
--name "AdminFront" \
--grant-type authorization_code,refresh_token \
--response-type code \
--scope openid,offline_access,profile,email \
--token-endpoint-auth-method none \
--redirect-uri "$${ADMINFRONT_CALLBACK_URLS:-$${ADMINFRONT_URL}/auth/callback}"
upsert_client "devfront" \
--name "DevFront" \
--grant-type authorization_code,refresh_token \
--response-type code \
--scope openid,offline_access,profile,email \
--token-endpoint-auth-method none \
--redirect-uri "$${DEVFRONT_CALLBACK_URLS:-$${DEVFRONT_URL}/auth/callback}"
upsert_client "orgfront" \
--name "OrgFront" \
--grant-type authorization_code,refresh_token \
--response-type code \
--scope openid,offline_access,profile,email \
--token-endpoint-auth-method none \
--redirect-uri "$${ORGFRONT_CALLBACK_URLS:-$${ORGFRONT_URL}/auth/callback}"
upsert_client "$${OATHKEEPER_INTROSPECT_CLIENT_ID:-oathkeeper-introspect}" \
--secret "$${OATHKEEPER_INTROSPECT_CLIENT_SECRET:-oathkeeper-secret}" \
--grant-type client_credentials \
--response-type token \
--scope openid,offline_access,profile,email
depends_on:
ory_stack_check: { condition: service_completed_successfully }
networks: [app_net] networks: [app_net]
# --- Application Services --- # --- Application Services ---
@@ -78,6 +264,14 @@ services:
env_file: .env env_file: .env
environment: environment:
- PORT=${BACKEND_PORT} - PORT=${BACKEND_PORT}
- APP_ENV=${APP_ENV:-production}
- IDP_PROVIDER=${IDP_PROVIDER:-ory}
- USERFRONT_URL=${USERFRONT_URL}
- KRATOS_ADMIN_URL=${KRATOS_ADMIN_URL:-http://kratos:4434}
- HYDRA_ADMIN_URL=${HYDRA_ADMIN_URL:-http://hydra:4445}
- HYDRA_PUBLIC_URL=${HYDRA_PUBLIC_URL}
- KETO_READ_URL=${KETO_READ_URL:-http://keto:4466}
- KETO_WRITE_URL=${KETO_WRITE_URL:-http://keto:4467}
- DB_HOST=postgres - DB_HOST=postgres
- REDIS_ADDR=redis:6379 - REDIS_ADDR=redis:6379
- CLICKHOUSE_HOST=clickhouse - CLICKHOUSE_HOST=clickhouse
@@ -90,6 +284,7 @@ services:
depends_on: depends_on:
postgres: { condition: service_healthy } postgres: { condition: service_healthy }
redis: { condition: service_started } redis: { condition: service_started }
oathkeeper: { condition: service_started }
gateway: gateway:
image: nginx:alpine image: nginx:alpine
@@ -147,6 +342,11 @@ networks:
name: ${COMPOSE_PROJECT_NAME}_net name: ${COMPOSE_PROJECT_NAME}_net
volumes: volumes:
db_data_${INSTANCE_NAME}: db_data:
ory_db_data_${INSTANCE_NAME}: name: db_data_${INSTANCE_NAME}
clickhouse_data_${INSTANCE_NAME}: ory_db_data:
name: ory_db_data_${INSTANCE_NAME}
clickhouse_data:
name: clickhouse_data_${INSTANCE_NAME}
oathkeeper_logs:
name: oathkeeper_logs_${INSTANCE_NAME}

View File

@@ -29,7 +29,6 @@ http {
} }
location /oidc { location /oidc {
rewrite ^/oidc/(.*)$ /$1 break;
proxy_pass http://oathkeeper_srv; proxy_pass http://oathkeeper_srv;
proxy_set_header Host $host; proxy_set_header Host $host;
} }

View File

@@ -1,16 +1,20 @@
version: v25.4.0 version: v26.2.0
dsn: ${DSN} dsn: ${DSN}
serve: serve:
public: public:
base_url: http://localhost:4433/ base_url: ${KRATOS_BROWSER_URL}
cors: cors:
enabled: true enabled: true
allowed_origins: allowed_origins:
- http://backend:{{BACKEND_PORT}} - http://backend:{{BACKEND_PORT}}
- ${USERFRONT_URL}
- ${ADMINFRONT_URL}
- ${DEVFRONT_URL}
- ${ORGFRONT_URL}
admin: admin:
base_url: http://localhost:4434/ base_url: ${KRATOS_ADMIN_URL}
session: session:
cookie: cookie:
@@ -19,30 +23,22 @@ session:
path: / path: /
selfservice: selfservice:
default_browser_return_url: http://localhost:{{USERFRONT_PORT}}/ default_browser_return_url: ${KRATOS_UI_URL}
allowed_return_urls: allowed_return_urls:
- http://backend:{{BACKEND_PORT}} - ${KRATOS_UI_URL}
- http://backend:{{BACKEND_PORT}}/ - ${KRATOS_UI_URL}/
- http://localhost:{{USERFRONT_PORT}} - ${USERFRONT_URL}
- https://app.brsw.kr - ${USERFRONT_URL}/
- https://app.brsw.kr/ - ${USERFRONT_URL}/ko
- https://sss.hmac.kr - ${USERFRONT_URL}/ko/
- https://sss.hmac.kr/ - ${USERFRONT_URL}/en
- https://sso.hmac.kr - ${USERFRONT_URL}/en/
- https://sso.hmac.kr/ - ${USERFRONT_URL}/auth/callback
- https://ssologin.hmac.kr - ${USERFRONT_URL}/ko/auth/callback
- https://ssologin.hmac.kr/ - ${USERFRONT_URL}/en/auth/callback
- https://sso-test.hmac.kr - ${ADMINFRONT_URL}/auth/callback
- https://sso-test.hmac.kr/ - ${DEVFRONT_URL}/auth/callback
- https://ssob.hmac.kr - ${ORGFRONT_URL}/auth/callback
- https://ssob.hmac.kr/
- https://ssob.hmac.kr/ko
- https://ssob.hmac.kr/ko/
- https://ssob.hmac.kr/en
- https://ssob.hmac.kr/en/
- https://ssob.hmac.kr/auth/callback
- https://ssob.hmac.kr/ko/auth/callback
- https://ssob.hmac.kr/en/auth/callback
methods: methods:
password: password:
@@ -55,24 +51,24 @@ selfservice:
flows: flows:
error: error:
ui_url: http://localhost:{{USERFRONT_PORT}}/error ui_url: ${KRATOS_UI_URL}/error
settings: settings:
ui_url: http://localhost:{{USERFRONT_PORT}}/error?error=settings_disabled ui_url: ${KRATOS_UI_URL}/error?error=settings_disabled
privileged_session_max_age: 15m privileged_session_max_age: 15m
recovery: recovery:
ui_url: http://localhost:{{USERFRONT_PORT}}/recovery ui_url: ${KRATOS_UI_URL}/recovery
use: code use: code
verification: verification:
ui_url: http://localhost:{{USERFRONT_PORT}}/verification ui_url: ${KRATOS_UI_URL}/verification
use: code use: code
logout: logout:
after: after:
default_browser_return_url: http://localhost:{{USERFRONT_PORT}}/login default_browser_return_url: ${KRATOS_UI_URL}/login
login: login:
ui_url: http://localhost:{{USERFRONT_PORT}}/login ui_url: ${KRATOS_UI_URL}/login
lifespan: 10m lifespan: 10m
registration: registration:
ui_url: http://localhost:{{USERFRONT_PORT}}/registration ui_url: ${KRATOS_UI_URL}/registration
lifespan: 10m lifespan: 10m
log: log:

View File

@@ -1,9 +1,52 @@
[ [
{ {
"id": "backend-api-rule", "id": "public-health",
"description": "공개 헬스체크",
"match": { "match": {
"url": "<.*>://<.*>/api/v1/<.*>", "url": "<.*>://<[^/]+>/health",
"methods": ["GET", "POST", "PUT", "DELETE", "PATCH"] "methods": ["GET"]
},
"upstream": {
"url": "http://backend:{{BACKEND_PORT}}"
},
"authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" },
"mutators": [{ "handler": "noop" }]
},
{
"id": "public-preflight",
"description": "CORS preflight",
"match": {
"url": "<.*>://<[^/]+>/api/v1/<.*>",
"methods": ["OPTIONS"]
},
"upstream": {
"url": "http://backend:{{BACKEND_PORT}}"
},
"authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" },
"mutators": [{ "handler": "noop" }]
},
{
"id": "public-auth",
"description": "인증/회원가입 등 공개 엔드포인트",
"match": {
"url": "<.*>://<[^/]+>/api/v1/auth/<.*>",
"methods": ["GET", "POST", "OPTIONS"]
},
"upstream": {
"url": "http://backend:{{BACKEND_PORT}}"
},
"authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" },
"mutators": [{ "handler": "noop" }]
},
{
"id": "backend-command",
"description": "Command 요청은 Backend로 전달 (Audit 강제)",
"match": {
"url": "<.*>://<[^/]+>/api/v1/<.*>",
"methods": ["POST", "PUT", "PATCH", "DELETE"]
}, },
"upstream": { "upstream": {
"url": "http://backend:{{BACKEND_PORT}}" "url": "http://backend:{{BACKEND_PORT}}"
@@ -11,5 +54,106 @@
"authenticators": [{ "handler": "cookie_session" }], "authenticators": [{ "handler": "cookie_session" }],
"authorizer": { "handler": "remote_json" }, "authorizer": { "handler": "remote_json" },
"mutators": [{ "handler": "noop" }] "mutators": [{ "handler": "noop" }]
},
{
"id": "backend-query",
"description": "Backend Query (admin/dev 포함)",
"match": {
"url": "<.*>://<[^/]+>/api/v1/<.*>",
"methods": ["GET"]
},
"upstream": {
"url": "http://backend:{{BACKEND_PORT}}"
},
"authenticators": [{ "handler": "cookie_session" }],
"authorizer": { "handler": "remote_json" },
"mutators": [{ "handler": "noop" }]
},
{
"id": "hydra-well-known",
"description": "Hydra OIDC Discovery & JWKS",
"match": {
"url": "<.*>://<[^/]+>/.well-known/<.*>",
"methods": ["GET", "OPTIONS"]
},
"upstream": {
"url": "http://hydra:4444"
},
"authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" },
"mutators": [{ "handler": "noop" }]
},
{
"id": "hydra-well-known-oidc",
"description": "Hydra OIDC Discovery & JWKS (with /oidc prefix)",
"match": {
"url": "<.*>://<[^/]+>/oidc/.well-known/<.*>",
"methods": ["GET", "OPTIONS"]
},
"upstream": {
"url": "http://hydra:4444",
"strip_path": "/oidc"
},
"authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" },
"mutators": [{ "handler": "noop" }]
},
{
"id": "hydra-oauth2",
"description": "Hydra OAuth2 Endpoints",
"match": {
"url": "<.*>://<[^/]+>/oauth2/<.*>",
"methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]
},
"upstream": {
"url": "http://hydra:4444"
},
"authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" },
"mutators": [{ "handler": "noop" }]
},
{
"id": "hydra-oauth2-oidc",
"description": "Hydra OAuth2 Endpoints (with /oidc prefix)",
"match": {
"url": "<.*>://<[^/]+>/oidc/oauth2/<.*>",
"methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]
},
"upstream": {
"url": "http://hydra:4444",
"strip_path": "/oidc"
},
"authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" },
"mutators": [{ "handler": "noop" }]
},
{
"id": "hydra-userinfo",
"description": "Hydra Userinfo",
"match": {
"url": "<.*>://<[^/]+>/userinfo",
"methods": ["GET", "POST", "OPTIONS"]
},
"upstream": {
"url": "http://hydra:4444"
},
"authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" },
"mutators": [{ "handler": "noop" }]
},
{
"id": "hydra-userinfo-oidc",
"description": "Hydra Userinfo (with /oidc prefix)",
"match": {
"url": "<.*>://<[^/]+>/oidc/userinfo",
"methods": ["GET", "POST", "OPTIONS"]
},
"upstream": {
"url": "http://hydra:4444",
"strip_path": "/oidc"
},
"authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" },
"mutators": [{ "handler": "noop" }]
} }
] ]

View File

@@ -22,13 +22,13 @@ services:
retries: 5 retries: 5
kratos-migrate: kratos-migrate:
image: oryd/kratos:${KRATOS_VERSION:-v25.4.0} image: oryd/kratos:${KRATOS_VERSION:-v26.2.0}
environment: environment:
- DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KRATOS_DB:-ory_kratos}?sslmode=disable&max_conns=20 - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KRATOS_DB:-ory_kratos}?sslmode=disable&max_conns=20
- KRATOS_SERVE_PUBLIC_BASE_URL="${KRATOS_BROWSER_URL:-http://localhost:4433}" - KRATOS_SERVE_PUBLIC_BASE_URL=${KRATOS_BROWSER_URL}
- KRATOS_SERVE_ADMIN_BASE_URL="${KRATOS_ADMIN_URL:-http://kratos:4434}" - KRATOS_SERVE_ADMIN_BASE_URL=${KRATOS_ADMIN_URL}
- KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL="${KRATOS_UI_URL:-http://localhost:5000}" - KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL}
- KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS='["${KRATOS_UI_URL:-http://localhost:5000}","${USERFRONT_URL:-http://localhost:5000}"]' - KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS=${KRATOS_ALLOWED_RETURN_URLS_JSON:-["${KRATOS_UI_URL}","${USERFRONT_URL}"]}
volumes: volumes:
- ./docker/ory/kratos:/etc/config/kratos - ./docker/ory/kratos:/etc/config/kratos
command: migrate sql up -e -c /etc/config/kratos/kratos.yml --yes command: migrate sql up -e -c /etc/config/kratos/kratos.yml --yes
@@ -39,15 +39,15 @@ services:
- ory-net - ory-net
kratos: kratos:
image: oryd/kratos:${KRATOS_VERSION:-v25.4.0} image: oryd/kratos:${KRATOS_VERSION:-v26.2.0}
container_name: ory_kratos container_name: ory_kratos
environment: environment:
- DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KRATOS_DB:-ory_kratos}?sslmode=disable&max_conns=20 - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KRATOS_DB:-ory_kratos}?sslmode=disable&max_conns=20
- COOKIE_SECRET="${COOKIE_SECRET:-localcookie123}" - COOKIE_SECRET=${COOKIE_SECRET:-localcookie123}
- KRATOS_SERVE_PUBLIC_BASE_URL="${KRATOS_BROWSER_URL:-http://localhost:4433}" - KRATOS_SERVE_PUBLIC_BASE_URL=${KRATOS_BROWSER_URL}
- KRATOS_SERVE_ADMIN_BASE_URL="${KRATOS_ADMIN_URL:-http://kratos:4434}" - KRATOS_SERVE_ADMIN_BASE_URL=${KRATOS_ADMIN_URL}
- KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL="${KRATOS_UI_URL:-http://localhost:5000}" - KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL}
- KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS='["${KRATOS_UI_URL:-http://localhost:5000}","${USERFRONT_URL:-http://localhost:5000}"]' - KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS=${KRATOS_ALLOWED_RETURN_URLS_JSON:-["${KRATOS_UI_URL}","${USERFRONT_URL}"]}
volumes: volumes:
- ./docker/ory/kratos:/etc/config/kratos - ./docker/ory/kratos:/etc/config/kratos
command: serve -c /etc/config/kratos/kratos.yml command: serve -c /etc/config/kratos/kratos.yml
@@ -59,7 +59,7 @@ services:
- kratosnet - kratosnet
hydra-migrate: hydra-migrate:
image: oryd/hydra:${HYDRA_VERSION:-v25.4.0} image: oryd/hydra:${HYDRA_VERSION:-v26.2.0}
environment: environment:
- DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${HYDRA_DB:-ory_hydra}?sslmode=disable&max_conns=20 - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${HYDRA_DB:-ory_hydra}?sslmode=disable&max_conns=20
command: migrate sql up -e --yes command: migrate sql up -e --yes
@@ -70,13 +70,14 @@ services:
- ory-net - ory-net
hydra: hydra:
image: oryd/hydra:${HYDRA_VERSION:-v25.4.0} image: oryd/hydra:${HYDRA_VERSION:-v26.2.0}
container_name: ory_hydra container_name: ory_hydra
environment: environment:
- DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${HYDRA_DB:-ory_hydra}?sslmode=disable&max_conns=20 - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${HYDRA_DB:-ory_hydra}?sslmode=disable&max_conns=20
- URLS_SELF_ISSUER=${USERFRONT_URL:-http://localhost:5000}/oidc - URLS_SELF_ISSUER=${HYDRA_PUBLIC_URL}
- URLS_LOGIN=${USERFRONT_URL:-http://localhost:5000}/login - URLS_LOGIN=${HYDRA_LOGIN_URL:-${USERFRONT_URL}/login}
- URLS_CONSENT=${USERFRONT_URL:-http://localhost:5000}/consent - URLS_CONSENT=${HYDRA_CONSENT_URL:-${USERFRONT_URL}/consent}
- URLS_ERROR=${HYDRA_ERROR_URL:-${USERFRONT_URL}/error}
- SECRETS_SYSTEM=${ORY_POSTGRES_PASSWORD} - SECRETS_SYSTEM=${ORY_POSTGRES_PASSWORD}
volumes: volumes:
- ./docker/ory/hydra:/etc/config/hydra - ./docker/ory/hydra:/etc/config/hydra
@@ -89,7 +90,7 @@ services:
- hydranet - hydranet
keto-migrate: keto-migrate:
image: oryd/keto:${KETO_VERSION:-v25.4.0} image: oryd/keto:${KETO_VERSION:-v26.2.0}
environment: environment:
- DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20 - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20
volumes: volumes:
@@ -102,7 +103,7 @@ services:
- ory-net - ory-net
keto: keto:
image: oryd/keto:${KETO_VERSION:-v25.4.0} image: oryd/keto:${KETO_VERSION:-v26.2.0}
container_name: ory_keto container_name: ory_keto
environment: environment:
- DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20 - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20
@@ -116,21 +117,24 @@ services:
- ory-net - ory-net
oathkeeper: oathkeeper:
image: oryd/oathkeeper:${OATHKEEPER_VERSION:-v0.40.6} image: oryd/oathkeeper:${OATHKEEPER_VERSION:-v26.2.0}
container_name: oathkeeper container_name: oathkeeper
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
kratos: kratos:
condition: service_started condition: service_started
environment: environment:
- APP_ENV=${APP_ENV:-development}
- LOG_LEVEL=debug - LOG_LEVEL=debug
command: serve proxy --config /etc/config/oathkeeper/oathkeeper.yml - OATHKEEPER_INTROSPECT_CLIENT_ID=${OATHKEEPER_INTROSPECT_CLIENT_ID:-oathkeeper-introspect}
- OATHKEEPER_INTROSPECT_CLIENT_SECRET=${OATHKEEPER_INTROSPECT_CLIENT_SECRET:-oathkeeper-secret}
volumes: volumes:
- ./docker/ory/oathkeeper:/etc/config/oathkeeper - ./docker/ory/oathkeeper:/etc/config/oathkeeper
- oathkeeper_logs:/var/log/oathkeeper - oathkeeper_logs:/var/log/oathkeeper
entrypoint: ["/etc/config/oathkeeper/entrypoint.sh"]
networks: networks:
- ory-net - ory-net
- baron_net - baron_net
- public_net - public_net
ports: ports:
- "4455:4455" - "4455:4455"
@@ -168,47 +172,47 @@ services:
- /bin/sh - /bin/sh
- -ec - -ec
- | - |
apk add --no-cache curl tar apk add --no-cache curl tar
HYDRA_CLI_VERSION="$${HYDRA_VERSION:-v26.2.0}" HYDRA_CLI_VERSION="$${HYDRA_VERSION:-v26.2.0}"
HYDRA_CLI_VERSION="$${HYDRA_CLI_VERSION%-distroless}" HYDRA_CLI_VERSION="$${HYDRA_CLI_VERSION%-distroless}"
HYDRA_CLI_ARCHIVE_VERSION="$${HYDRA_CLI_VERSION#v}" HYDRA_CLI_ARCHIVE_VERSION="$${HYDRA_CLI_VERSION#v}"
curl -fsSLo /tmp/hydra.tar.gz "https://github.com/ory/hydra/releases/download/$${HYDRA_CLI_VERSION}/hydra_$${HYDRA_CLI_ARCHIVE_VERSION}-linux_64bit.tar.gz" curl -fsSLo /tmp/hydra.tar.gz "https://github.com/ory/hydra/releases/download/$${HYDRA_CLI_VERSION}/hydra_$${HYDRA_CLI_ARCHIVE_VERSION}-linux_64bit.tar.gz"
tar -xzf /tmp/hydra.tar.gz -C /usr/local/bin hydra tar -xzf /tmp/hydra.tar.gz -C /usr/local/bin hydra
rm /tmp/hydra.tar.gz rm /tmp/hydra.tar.gz
echo "Creating/Updating OAuth2 Clients..." echo "Creating/Updating OAuth2 Clients..."
hydra create oauth2-client \
--endpoint http://hydra:4445 \
--id adminfront \
--name "AdminFront" \
--grant-type authorization_code,refresh_token \
--response-type code \
--scope openid,offline_access,profile,email \
--token-endpoint-auth-method none \
--redirect-uri ${ADMINFRONT_CALLBACK_URLS:-http://localhost:5173/auth/callback,http://172.16.10.176:5173/auth/callback}
hydra create oauth2-client \ hydra create oauth2-client \
--endpoint http://hydra:4445 \ --endpoint "$${HYDRA_ADMIN_URL}" \
--id devfront \ --id adminfront \
--name "DevFront" \ --name "AdminFront" \
--grant-type authorization_code,refresh_token \ --grant-type authorization_code,refresh_token \
--response-type code \ --response-type code \
--scope openid,offline_access,profile,email \ --scope openid,offline_access,profile,email \
--token-endpoint-auth-method none \ --token-endpoint-auth-method none \
--redirect-uri ${DEVFRONT_CALLBACK_URLS:-http://localhost:5174/auth/callback,http://172.16.10.176:5174/auth/callback} --redirect-uri "$${ADMINFRONT_CALLBACK_URLS}"
hydra create oauth2-client \ hydra create oauth2-client \
--endpoint http://hydra:4445 \ --endpoint "$${HYDRA_ADMIN_URL}" \
--id orgfront \ --id devfront \
--name "OrgFront" \ --name "DevFront" \
--grant-type authorization_code,refresh_token \ --grant-type authorization_code,refresh_token \
--response-type code \ --response-type code \
--scope openid,offline_access,profile,email \ --scope openid,offline_access,profile,email \
--token-endpoint-auth-method none \ --token-endpoint-auth-method none \
--redirect-uri ${ORGFRONT_CALLBACK_URLS:-http://localhost:5175/auth/callback,http://172.16.10.176:5175/auth/callback,https://baron-orgchart.hmac.kr/auth/callback} --redirect-uri "$${DEVFRONT_CALLBACK_URLS}"
echo "All RP clients initialized successfully." hydra create oauth2-client \
--endpoint "$${HYDRA_ADMIN_URL}" \
--id orgfront \
--name "OrgFront" \
--grant-type authorization_code,refresh_token \
--response-type code \
--scope openid,offline_access,profile,email \
--token-endpoint-auth-method none \
--redirect-uri "$${ORGFRONT_CALLBACK_URLS}"
echo "All RP clients initialized successfully."
depends_on: depends_on:
ory_stack_check: ory_stack_check:
condition: service_completed_successfully condition: service_completed_successfully

View File

@@ -4,23 +4,18 @@ dsn: ${DSN}
serve: serve:
public: public:
base_url: http://localhost:4433/ base_url: ${KRATOS_BROWSER_URL}
cors: cors:
enabled: true enabled: true
allowed_origins: allowed_origins:
- http://localhost:5173 - ${USERFRONT_URL}
- http://localhost:5174 - ${ADMINFRONT_URL}
- http://localhost:5175 - ${DEVFRONT_URL}
- http://localhost:5000 - ${ORGFRONT_URL}
- http://backend:3000 - http://backend:3000
- http://baron_backend:3000 - http://baron_backend:3000
- https://ssologin.hmac.kr
- https://sso-test.hmac.kr
- https://app.brsw.kr
- https://sss.hmac.kr
- https://sso.hmac.kr
admin: admin:
base_url: http://localhost:4434/ base_url: ${KRATOS_ADMIN_URL}
session: session:
cookie: cookie:
@@ -29,21 +24,22 @@ session:
path: / path: /
selfservice: selfservice:
default_browser_return_url: http://localhost:5000/ default_browser_return_url: ${KRATOS_UI_URL}
allowed_return_urls: allowed_return_urls:
- http://baron_backend:3000 - ${KRATOS_UI_URL}
- http://baron_backend:3000/ - ${KRATOS_UI_URL}/
- http://localhost:5000 - ${USERFRONT_URL}
- https://app.brsw.kr - ${USERFRONT_URL}/
- https://app.brsw.kr/ - ${USERFRONT_URL}/ko
- https://sss.hmac.kr - ${USERFRONT_URL}/ko/
- https://sss.hmac.kr/ - ${USERFRONT_URL}/en
- https://sso.hmac.kr - ${USERFRONT_URL}/en/
- https://sso.hmac.kr/ - ${USERFRONT_URL}/auth/callback
- https://ssologin.hmac.kr - ${USERFRONT_URL}/ko/auth/callback
- https://ssologin.hmac.kr/ - ${USERFRONT_URL}/en/auth/callback
- https://sso-test.hmac.kr - ${ADMINFRONT_URL}/auth/callback
- https://sso-test.hmac.kr/ - ${DEVFRONT_URL}/auth/callback
- ${ORGFRONT_URL}/auth/callback
methods: methods:
password: password:
@@ -56,24 +52,24 @@ selfservice:
flows: flows:
error: error:
ui_url: http://localhost:5000/error ui_url: ${KRATOS_UI_URL}/error
settings: settings:
ui_url: http://localhost:5000/error?error=settings_disabled ui_url: ${KRATOS_UI_URL}/error?error=settings_disabled
privileged_session_max_age: 15m privileged_session_max_age: 15m
recovery: recovery:
ui_url: http://localhost:5000/recovery ui_url: ${KRATOS_UI_URL}/recovery
use: code use: code
verification: verification:
ui_url: http://localhost:5000/verification ui_url: ${KRATOS_UI_URL}/verification
use: code use: code
logout: logout:
after: after:
default_browser_return_url: http://localhost:5000/login default_browser_return_url: ${KRATOS_UI_URL}/login
login: login:
ui_url: http://localhost:5000/login ui_url: ${KRATOS_UI_URL}/login
lifespan: 10m lifespan: 10m
registration: registration:
ui_url: http://localhost:5000/registration ui_url: ${KRATOS_UI_URL}/registration
lifespan: 10m lifespan: 10m
log: log:

View File

@@ -3,7 +3,7 @@
"id": "public-health", "id": "public-health",
"description": "공개 헬스체크", "description": "공개 헬스체크",
"match": { "match": {
"url": "<.*>://<.*>/health", "url": "<.*>://<[^/]+>/health",
"methods": ["GET"] "methods": ["GET"]
}, },
"upstream": { "upstream": {
@@ -17,7 +17,7 @@
"id": "public-preflight", "id": "public-preflight",
"description": "CORS preflight", "description": "CORS preflight",
"match": { "match": {
"url": "<.*>://<.*>/api/v1/<.*>", "url": "<.*>://<[^/]+>/api/v1/<.*>",
"methods": ["OPTIONS"] "methods": ["OPTIONS"]
}, },
"upstream": { "upstream": {
@@ -31,7 +31,7 @@
"id": "public-auth", "id": "public-auth",
"description": "인증/회원가입 등 공개 엔드포인트", "description": "인증/회원가입 등 공개 엔드포인트",
"match": { "match": {
"url": "<.*>://<.*>/api/v1/auth/<.*>", "url": "<.*>://<[^/]+>/api/v1/auth/<.*>",
"methods": ["GET", "POST", "OPTIONS"] "methods": ["GET", "POST", "OPTIONS"]
}, },
"upstream": { "upstream": {
@@ -45,7 +45,7 @@
"id": "backend-command", "id": "backend-command",
"description": "Command 요청은 Backend로 전달 (Audit 강제)", "description": "Command 요청은 Backend로 전달 (Audit 강제)",
"match": { "match": {
"url": "<.*>://<.*>/api/v1/<.*>", "url": "<.*>://<[^/]+>/api/v1/<.*>",
"methods": ["POST", "PUT", "PATCH", "DELETE"] "methods": ["POST", "PUT", "PATCH", "DELETE"]
}, },
"upstream": { "upstream": {
@@ -59,7 +59,7 @@
"id": "backend-query", "id": "backend-query",
"description": "Backend Query (admin/dev 포함)", "description": "Backend Query (admin/dev 포함)",
"match": { "match": {
"url": "<.*>://<.*>/api/v1/<.*>", "url": "<.*>://<[^/]+>/api/v1/<.*>",
"methods": ["GET"] "methods": ["GET"]
}, },
"upstream": { "upstream": {
@@ -73,7 +73,7 @@
"id": "hydra-well-known", "id": "hydra-well-known",
"description": "Hydra OIDC Discovery & JWKS", "description": "Hydra OIDC Discovery & JWKS",
"match": { "match": {
"url": "<.*>://<.*>/.well-known/<.*>", "url": "<.*>://<[^/]+>/.well-known/<.*>",
"methods": ["GET", "OPTIONS"] "methods": ["GET", "OPTIONS"]
}, },
"upstream": { "upstream": {
@@ -87,12 +87,12 @@
"id": "hydra-well-known-oidc", "id": "hydra-well-known-oidc",
"description": "Hydra OIDC Discovery & JWKS (with /oidc prefix)", "description": "Hydra OIDC Discovery & JWKS (with /oidc prefix)",
"match": { "match": {
"url": "<.*>://<.*>/oidc/.well-known/<.*>", "url": "<.*>://<[^/]+>/oidc/.well-known/<.*>",
"methods": ["GET", "OPTIONS"] "methods": ["GET", "OPTIONS"]
}, },
"upstream": { "upstream": {
"url": "http://hydra:4444", "url": "http://hydra:4444",
"strip_path_prefix": "/oidc" "strip_path": "/oidc"
}, },
"authenticators": [{ "handler": "noop" }], "authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" }, "authorizer": { "handler": "allow" },
@@ -102,7 +102,7 @@
"id": "hydra-oauth2", "id": "hydra-oauth2",
"description": "Hydra OAuth2 Endpoints", "description": "Hydra OAuth2 Endpoints",
"match": { "match": {
"url": "<.*>://<.*>/oauth2/<.*>", "url": "<.*>://<[^/]+>/oauth2/<.*>",
"methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"] "methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]
}, },
"upstream": { "upstream": {
@@ -116,12 +116,12 @@
"id": "hydra-oauth2-oidc", "id": "hydra-oauth2-oidc",
"description": "Hydra OAuth2 Endpoints (with /oidc prefix)", "description": "Hydra OAuth2 Endpoints (with /oidc prefix)",
"match": { "match": {
"url": "<.*>://<.*>/oidc/oauth2/<.*>", "url": "<.*>://<[^/]+>/oidc/oauth2/<.*>",
"methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"] "methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]
}, },
"upstream": { "upstream": {
"url": "http://hydra:4444", "url": "http://hydra:4444",
"strip_path_prefix": "/oidc" "strip_path": "/oidc"
}, },
"authenticators": [{ "handler": "noop" }], "authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" }, "authorizer": { "handler": "allow" },
@@ -131,7 +131,7 @@
"id": "hydra-userinfo", "id": "hydra-userinfo",
"description": "Hydra Userinfo", "description": "Hydra Userinfo",
"match": { "match": {
"url": "<.*>://<.*>/userinfo", "url": "<.*>://<[^/]+>/userinfo",
"methods": ["GET", "POST", "OPTIONS"] "methods": ["GET", "POST", "OPTIONS"]
}, },
"upstream": { "upstream": {
@@ -145,12 +145,12 @@
"id": "hydra-userinfo-oidc", "id": "hydra-userinfo-oidc",
"description": "Hydra Userinfo (with /oidc prefix)", "description": "Hydra Userinfo (with /oidc prefix)",
"match": { "match": {
"url": "<.*>://<.*>/oidc/userinfo", "url": "<.*>://<[^/]+>/oidc/userinfo",
"methods": ["GET", "POST", "OPTIONS"] "methods": ["GET", "POST", "OPTIONS"]
}, },
"upstream": { "upstream": {
"url": "http://hydra:4444", "url": "http://hydra:4444",
"strip_path_prefix": "/oidc" "strip_path": "/oidc"
}, },
"authenticators": [{ "handler": "noop" }], "authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" }, "authorizer": { "handler": "allow" },

View File

@@ -3,7 +3,7 @@
"id": "public-health", "id": "public-health",
"description": "공개 헬스체크", "description": "공개 헬스체크",
"match": { "match": {
"url": "<.*>://<.*>/health", "url": "<.*>://<[^/]+>/health",
"methods": ["GET"] "methods": ["GET"]
}, },
"upstream": { "upstream": {
@@ -17,7 +17,7 @@
"id": "public-preflight", "id": "public-preflight",
"description": "CORS preflight", "description": "CORS preflight",
"match": { "match": {
"url": "<.*>://<.*>/api/v1/<.*>", "url": "<.*>://<[^/]+>/api/v1/<.*>",
"methods": ["OPTIONS"] "methods": ["OPTIONS"]
}, },
"upstream": { "upstream": {
@@ -31,7 +31,7 @@
"id": "public-auth", "id": "public-auth",
"description": "인증/회원가입 등 공개 엔드포인트", "description": "인증/회원가입 등 공개 엔드포인트",
"match": { "match": {
"url": "<.*>://<.*>/api/v1/auth/<.*>", "url": "<.*>://<[^/]+>/api/v1/auth/<.*>",
"methods": ["GET", "POST", "OPTIONS"] "methods": ["GET", "POST", "OPTIONS"]
}, },
"upstream": { "upstream": {
@@ -45,7 +45,7 @@
"id": "backend-command", "id": "backend-command",
"description": "Command 요청은 Backend로 전달 (Audit 강제)", "description": "Command 요청은 Backend로 전달 (Audit 강제)",
"match": { "match": {
"url": "<.*>://<.*>/api/v1/<.*>", "url": "<.*>://<[^/]+>/api/v1/<.*>",
"methods": ["POST", "PUT", "PATCH", "DELETE"] "methods": ["POST", "PUT", "PATCH", "DELETE"]
}, },
"upstream": { "upstream": {
@@ -59,7 +59,7 @@
"id": "backend-query", "id": "backend-query",
"description": "Backend Query (admin/dev 포함)", "description": "Backend Query (admin/dev 포함)",
"match": { "match": {
"url": "<.*>://<.*>/api/v1/<.*>", "url": "<.*>://<[^/]+>/api/v1/<.*>",
"methods": ["GET"] "methods": ["GET"]
}, },
"upstream": { "upstream": {
@@ -73,7 +73,7 @@
"id": "hydra-well-known", "id": "hydra-well-known",
"description": "Hydra OIDC Discovery & JWKS", "description": "Hydra OIDC Discovery & JWKS",
"match": { "match": {
"url": "<.*>://<.*>/.well-known/<.*>", "url": "<.*>://<[^/]+>/.well-known/<.*>",
"methods": ["GET", "OPTIONS"] "methods": ["GET", "OPTIONS"]
}, },
"upstream": { "upstream": {
@@ -87,12 +87,12 @@
"id": "hydra-well-known-oidc", "id": "hydra-well-known-oidc",
"description": "Hydra OIDC Discovery & JWKS (with /oidc prefix)", "description": "Hydra OIDC Discovery & JWKS (with /oidc prefix)",
"match": { "match": {
"url": "<.*>://<.*>/oidc/.well-known/<.*>", "url": "<.*>://<[^/]+>/oidc/.well-known/<.*>",
"methods": ["GET", "OPTIONS"] "methods": ["GET", "OPTIONS"]
}, },
"upstream": { "upstream": {
"url": "http://hydra:4444", "url": "http://hydra:4444",
"strip_path_prefix": "/oidc" "strip_path": "/oidc"
}, },
"authenticators": [{ "handler": "noop" }], "authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" }, "authorizer": { "handler": "allow" },
@@ -102,7 +102,7 @@
"id": "hydra-oauth2", "id": "hydra-oauth2",
"description": "Hydra OAuth2 Endpoints", "description": "Hydra OAuth2 Endpoints",
"match": { "match": {
"url": "<.*>://<.*>/oauth2/<.*>", "url": "<.*>://<[^/]+>/oauth2/<.*>",
"methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"] "methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]
}, },
"upstream": { "upstream": {
@@ -116,12 +116,12 @@
"id": "hydra-oauth2-oidc", "id": "hydra-oauth2-oidc",
"description": "Hydra OAuth2 Endpoints (with /oidc prefix)", "description": "Hydra OAuth2 Endpoints (with /oidc prefix)",
"match": { "match": {
"url": "<.*>://<.*>/oidc/oauth2/<.*>", "url": "<.*>://<[^/]+>/oidc/oauth2/<.*>",
"methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"] "methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]
}, },
"upstream": { "upstream": {
"url": "http://hydra:4444", "url": "http://hydra:4444",
"strip_path_prefix": "/oidc" "strip_path": "/oidc"
}, },
"authenticators": [{ "handler": "noop" }], "authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" }, "authorizer": { "handler": "allow" },
@@ -131,7 +131,7 @@
"id": "hydra-userinfo", "id": "hydra-userinfo",
"description": "Hydra Userinfo", "description": "Hydra Userinfo",
"match": { "match": {
"url": "<.*>://<.*>/userinfo", "url": "<.*>://<[^/]+>/userinfo",
"methods": ["GET", "POST", "OPTIONS"] "methods": ["GET", "POST", "OPTIONS"]
}, },
"upstream": { "upstream": {
@@ -145,12 +145,12 @@
"id": "hydra-userinfo-oidc", "id": "hydra-userinfo-oidc",
"description": "Hydra Userinfo (with /oidc prefix)", "description": "Hydra Userinfo (with /oidc prefix)",
"match": { "match": {
"url": "<.*>://<.*>/oidc/userinfo", "url": "<.*>://<[^/]+>/oidc/userinfo",
"methods": ["GET", "POST", "OPTIONS"] "methods": ["GET", "POST", "OPTIONS"]
}, },
"upstream": { "upstream": {
"url": "http://hydra:4444", "url": "http://hydra:4444",
"strip_path_prefix": "/oidc" "strip_path": "/oidc"
}, },
"authenticators": [{ "handler": "noop" }], "authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" }, "authorizer": { "handler": "allow" },

View File

@@ -1,9 +1,9 @@
[ [
{ {
"id": "public-health", "id": "public-health",
"description": "공개 헬스체크 (PROD 도메인)", "description": "공개 헬스체크 (PROD)",
"match": { "match": {
"url": "https://app.brsw.kr/health", "url": "<.*>://<[^/]+>/health",
"methods": ["GET"] "methods": ["GET"]
}, },
"upstream": { "upstream": {
@@ -15,9 +15,9 @@
}, },
{ {
"id": "public-preflight", "id": "public-preflight",
"description": "CORS preflight (PROD 도메인)", "description": "CORS preflight (PROD)",
"match": { "match": {
"url": "https://app.brsw.kr/api/v1/<.*>", "url": "<.*>://<[^/]+>/api/v1/<.*>",
"methods": ["OPTIONS"] "methods": ["OPTIONS"]
}, },
"upstream": { "upstream": {
@@ -29,9 +29,9 @@
}, },
{ {
"id": "public-auth", "id": "public-auth",
"description": "인증/회원가입 등 공개 엔드포인트 (PROD 도메인)", "description": "인증/회원가입 등 공개 엔드포인트 (PROD)",
"match": { "match": {
"url": "https://app.brsw.kr/api/v1/auth/<.*>", "url": "<.*>://<[^/]+>/api/v1/auth/<.*>",
"methods": ["GET", "POST", "OPTIONS"] "methods": ["GET", "POST", "OPTIONS"]
}, },
"upstream": { "upstream": {
@@ -45,7 +45,7 @@
"id": "backend-command", "id": "backend-command",
"description": "Command 요청은 Backend로 전달 (Audit 강제)", "description": "Command 요청은 Backend로 전달 (Audit 강제)",
"match": { "match": {
"url": "https://app.brsw.kr/api/v1/<.*>", "url": "<.*>://<[^/]+>/api/v1/<.*>",
"methods": ["POST", "PUT", "PATCH", "DELETE"] "methods": ["POST", "PUT", "PATCH", "DELETE"]
}, },
"upstream": { "upstream": {
@@ -59,7 +59,7 @@
"id": "backend-query", "id": "backend-query",
"description": "Backend Query (admin/dev 포함)", "description": "Backend Query (admin/dev 포함)",
"match": { "match": {
"url": "https://app.brsw.kr/api/v1/<.*>", "url": "<.*>://<[^/]+>/api/v1/<.*>",
"methods": ["GET"] "methods": ["GET"]
}, },
"upstream": { "upstream": {
@@ -68,5 +68,92 @@
"authenticators": [{ "handler": "cookie_session" }], "authenticators": [{ "handler": "cookie_session" }],
"authorizer": { "handler": "remote_json" }, "authorizer": { "handler": "remote_json" },
"mutators": [{ "handler": "noop" }] "mutators": [{ "handler": "noop" }]
},
{
"id": "hydra-well-known",
"description": "Hydra OIDC Discovery & JWKS (PROD)",
"match": {
"url": "<.*>://<[^/]+>/.well-known/<.*>",
"methods": ["GET", "OPTIONS"]
},
"upstream": {
"url": "http://hydra:4444"
},
"authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" },
"mutators": [{ "handler": "noop" }]
},
{
"id": "hydra-well-known-oidc",
"description": "Hydra OIDC Discovery & JWKS with /oidc prefix (PROD)",
"match": {
"url": "<.*>://<[^/]+>/oidc/.well-known/<.*>",
"methods": ["GET", "OPTIONS"]
},
"upstream": {
"url": "http://hydra:4444",
"strip_path": "/oidc"
},
"authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" },
"mutators": [{ "handler": "noop" }]
},
{
"id": "hydra-oauth2",
"description": "Hydra OAuth2 Endpoints (PROD)",
"match": {
"url": "<.*>://<[^/]+>/oauth2/<.*>",
"methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]
},
"upstream": {
"url": "http://hydra:4444"
},
"authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" },
"mutators": [{ "handler": "noop" }]
},
{
"id": "hydra-oauth2-oidc",
"description": "Hydra OAuth2 Endpoints with /oidc prefix (PROD 도메인)",
"match": {
"url": "<.*>://<[^/]+>/oidc/oauth2/<.*>",
"methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]
},
"upstream": {
"url": "http://hydra:4444",
"strip_path": "/oidc"
},
"authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" },
"mutators": [{ "handler": "noop" }]
},
{
"id": "hydra-userinfo",
"description": "Hydra Userinfo (PROD)",
"match": {
"url": "<.*>://<[^/]+>/userinfo",
"methods": ["GET", "POST", "OPTIONS"]
},
"upstream": {
"url": "http://hydra:4444"
},
"authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" },
"mutators": [{ "handler": "noop" }]
},
{
"id": "hydra-userinfo-oidc",
"description": "Hydra Userinfo with /oidc prefix (PROD 도메인)",
"match": {
"url": "<.*>://<[^/]+>/oidc/userinfo",
"methods": ["GET", "POST", "OPTIONS"]
},
"upstream": {
"url": "http://hydra:4444",
"strip_path": "/oidc"
},
"authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" },
"mutators": [{ "handler": "noop" }]
} }
] ]

View File

@@ -3,7 +3,7 @@
"id": "public-health", "id": "public-health",
"description": "공개 헬스체크", "description": "공개 헬스체크",
"match": { "match": {
"url": "<.*>://<.*>/health", "url": "<.*>://<[^/]+>/health",
"methods": ["GET"] "methods": ["GET"]
}, },
"upstream": { "upstream": {
@@ -17,7 +17,7 @@
"id": "public-preflight", "id": "public-preflight",
"description": "CORS preflight", "description": "CORS preflight",
"match": { "match": {
"url": "<.*>://<.*>/api/v1/<.*>", "url": "<.*>://<[^/]+>/api/v1/<.*>",
"methods": ["OPTIONS"] "methods": ["OPTIONS"]
}, },
"upstream": { "upstream": {
@@ -31,7 +31,7 @@
"id": "public-auth", "id": "public-auth",
"description": "인증/회원가입 등 공개 엔드포인트", "description": "인증/회원가입 등 공개 엔드포인트",
"match": { "match": {
"url": "<.*>://<.*>/api/v1/auth/<.*>", "url": "<.*>://<[^/]+>/api/v1/auth/<.*>",
"methods": ["GET", "POST", "OPTIONS"] "methods": ["GET", "POST", "OPTIONS"]
}, },
"upstream": { "upstream": {
@@ -45,7 +45,7 @@
"id": "backend-command", "id": "backend-command",
"description": "Command 요청은 Backend로 전달 (Audit 강제)", "description": "Command 요청은 Backend로 전달 (Audit 강제)",
"match": { "match": {
"url": "<.*>://<.*>/api/v1/<.*>", "url": "<.*>://<[^/]+>/api/v1/<.*>",
"methods": ["POST", "PUT", "PATCH", "DELETE"] "methods": ["POST", "PUT", "PATCH", "DELETE"]
}, },
"upstream": { "upstream": {
@@ -59,7 +59,7 @@
"id": "backend-query", "id": "backend-query",
"description": "Backend Query (admin/dev 포함)", "description": "Backend Query (admin/dev 포함)",
"match": { "match": {
"url": "<.*>://<.*>/api/v1/<.*>", "url": "<.*>://<[^/]+>/api/v1/<.*>",
"methods": ["GET"] "methods": ["GET"]
}, },
"upstream": { "upstream": {
@@ -73,7 +73,7 @@
"id": "hydra-well-known", "id": "hydra-well-known",
"description": "Hydra OIDC Discovery & JWKS", "description": "Hydra OIDC Discovery & JWKS",
"match": { "match": {
"url": "<.*>://<.*>/.well-known/<.*>", "url": "<.*>://<[^/]+>/.well-known/<.*>",
"methods": ["GET", "OPTIONS"] "methods": ["GET", "OPTIONS"]
}, },
"upstream": { "upstream": {
@@ -87,12 +87,12 @@
"id": "hydra-well-known-oidc", "id": "hydra-well-known-oidc",
"description": "Hydra OIDC Discovery & JWKS (with /oidc prefix)", "description": "Hydra OIDC Discovery & JWKS (with /oidc prefix)",
"match": { "match": {
"url": "<.*>://<.*>/oidc/.well-known/<.*>", "url": "<.*>://<[^/]+>/oidc/.well-known/<.*>",
"methods": ["GET", "OPTIONS"] "methods": ["GET", "OPTIONS"]
}, },
"upstream": { "upstream": {
"url": "http://hydra:4444", "url": "http://hydra:4444",
"strip_path_prefix": "/oidc" "strip_path": "/oidc"
}, },
"authenticators": [{ "handler": "noop" }], "authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" }, "authorizer": { "handler": "allow" },
@@ -102,7 +102,7 @@
"id": "hydra-oauth2", "id": "hydra-oauth2",
"description": "Hydra OAuth2 Endpoints", "description": "Hydra OAuth2 Endpoints",
"match": { "match": {
"url": "<.*>://<.*>/oauth2/<.*>", "url": "<.*>://<[^/]+>/oauth2/<.*>",
"methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"] "methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]
}, },
"upstream": { "upstream": {
@@ -116,12 +116,12 @@
"id": "hydra-oauth2-oidc", "id": "hydra-oauth2-oidc",
"description": "Hydra OAuth2 Endpoints (with /oidc prefix)", "description": "Hydra OAuth2 Endpoints (with /oidc prefix)",
"match": { "match": {
"url": "<.*>://<.*>/oidc/oauth2/<.*>", "url": "<.*>://<[^/]+>/oidc/oauth2/<.*>",
"methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"] "methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]
}, },
"upstream": { "upstream": {
"url": "http://hydra:4444", "url": "http://hydra:4444",
"strip_path_prefix": "/oidc" "strip_path": "/oidc"
}, },
"authenticators": [{ "handler": "noop" }], "authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" }, "authorizer": { "handler": "allow" },
@@ -131,7 +131,7 @@
"id": "hydra-userinfo", "id": "hydra-userinfo",
"description": "Hydra Userinfo", "description": "Hydra Userinfo",
"match": { "match": {
"url": "<.*>://<.*>/userinfo", "url": "<.*>://<[^/]+>/userinfo",
"methods": ["GET", "POST", "OPTIONS"] "methods": ["GET", "POST", "OPTIONS"]
}, },
"upstream": { "upstream": {
@@ -145,12 +145,12 @@
"id": "hydra-userinfo-oidc", "id": "hydra-userinfo-oidc",
"description": "Hydra Userinfo (with /oidc prefix)", "description": "Hydra Userinfo (with /oidc prefix)",
"match": { "match": {
"url": "<.*>://<.*>/oidc/userinfo", "url": "<.*>://<[^/]+>/oidc/userinfo",
"methods": ["GET", "POST", "OPTIONS"] "methods": ["GET", "POST", "OPTIONS"]
}, },
"upstream": { "upstream": {
"url": "http://hydra:4444", "url": "http://hydra:4444",
"strip_path_prefix": "/oidc" "strip_path": "/oidc"
}, },
"authenticators": [{ "handler": "noop" }], "authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" }, "authorizer": { "handler": "allow" },

View File

@@ -1,501 +1,521 @@
services: services:
postgres: postgres:
image: postgres:17-alpine image: postgres:17-alpine
container_name: baron_postgres container_name: baron_postgres
environment: environment:
POSTGRES_USER: "${DB_USER:-baron}" POSTGRES_USER: "${DB_USER:-baron}"
POSTGRES_PASSWORD: "${DB_PASSWORD:-password}" POSTGRES_PASSWORD: "${DB_PASSWORD:-password}"
POSTGRES_DB: "${DB_NAME:-baron_sso}" POSTGRES_DB: "${DB_NAME:-baron_sso}"
ports: ports:
- "${DB_PORT:-5432}:5432" - "${DB_PORT:-5432}:5432"
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
- ./docker/init-metadata:/docker-entrypoint-initdb.d - ./docker/init-metadata:/docker-entrypoint-initdb.d
networks: networks:
- baron_net - baron_net
healthcheck: healthcheck:
test: test:
[ [
"CMD-SHELL", "CMD-SHELL",
"pg_isready -U ${DB_USER:-baron} -d ${DB_NAME:-baron_sso}", "pg_isready -U ${DB_USER:-baron} -d ${DB_NAME:-baron_sso}",
] ]
interval: 5s interval: 5s
timeout: 5s timeout: 5s
retries: 5 retries: 5
restart: always restart: always
clickhouse: clickhouse:
image: clickhouse/clickhouse-server:latest image: clickhouse/clickhouse-server:latest
container_name: baron_clickhouse container_name: baron_clickhouse
restart: always restart: always
volumes: volumes:
- clickhouse_data:/var/lib/clickhouse - clickhouse_data:/var/lib/clickhouse
environment: environment:
CLICKHOUSE_USER: "${CLICKHOUSE_USER:-baron}" CLICKHOUSE_USER: "${CLICKHOUSE_USER:-baron}"
CLICKHOUSE_PASSWORD: "${CLICKHOUSE_PASSWORD:-password}" CLICKHOUSE_PASSWORD: "${CLICKHOUSE_PASSWORD:-password}"
networks: networks:
- baron_net - baron_net
healthcheck: healthcheck:
test: ["CMD", "clickhouse-client", "--query", "SELECT 1"] test: ["CMD", "clickhouse-client", "--query", "SELECT 1"]
interval: 5s interval: 5s
timeout: 5s timeout: 5s
retries: 5 retries: 5
redis: redis:
image: redis:7-alpine image: redis:7-alpine
container_name: baron_redis container_name: baron_redis
restart: always restart: always
command: redis-server --port 6389 command: redis-server --port 6389
ports: ports:
- "6389:6389" - "6389:6389"
volumes: volumes:
- redis_data:/data - redis_data:/data
networks: networks:
- baron_net - baron_net
healthcheck: healthcheck:
test: ["CMD", "redis-cli", "-p", "6389", "ping"] test: ["CMD", "redis-cli", "-p", "6389", "ping"]
interval: 5s interval: 5s
timeout: 5s timeout: 5s
retries: 5 retries: 5
gateway: gateway:
image: nginx:alpine image: nginx:alpine
container_name: baron_gateway container_name: baron_gateway
restart: always restart: always
ports: ports:
- "${USERFRONT_PORT:-5000}:5000" - "${USERFRONT_PORT:-5000}:5000"
volumes: volumes:
- ./gateway/nginx.conf:/etc/nginx/conf.d/default.conf:ro - ./gateway/nginx.conf:/etc/nginx/conf.d/default.conf:ro
networks: networks:
- baron_net - baron_net
- public_net - public_net
healthcheck: healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:5000/"] test: ["CMD", "wget", "-qO-", "http://127.0.0.1:5000/"]
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 3 retries: 3
start_period: 10s start_period: 10s
postgres_ory: postgres_ory:
image: postgres:${ORY_POSTGRES_TAG:-17-alpine} image: postgres:${ORY_POSTGRES_TAG:-17-alpine}
container_name: ory_postgres container_name: ory_postgres
environment: environment:
- POSTGRES_USER=${ORY_POSTGRES_USER:-ory} - POSTGRES_USER=${ORY_POSTGRES_USER:-ory}
- POSTGRES_PASSWORD=${ORY_POSTGRES_PASSWORD:-secret} - POSTGRES_PASSWORD=${ORY_POSTGRES_PASSWORD:-secret}
- POSTGRES_DB=${ORY_POSTGRES_DB:-ory} - POSTGRES_DB=${ORY_POSTGRES_DB:-ory}
volumes: volumes:
- ./docker/ory/init-db:/docker-entrypoint-initdb.d - ./docker/ory/init-db:/docker-entrypoint-initdb.d
- ory_postgres_data:/var/lib/postgresql/data - ory_postgres_data:/var/lib/postgresql/data
networks: networks:
- ory-net - ory-net
healthcheck: healthcheck:
test: test:
[ [
"CMD-SHELL", "CMD-SHELL",
"pg_isready -U ${ORY_POSTGRES_USER:-ory} -d ${KRATOS_DB:-ory_kratos}", "pg_isready -U ${ORY_POSTGRES_USER:-ory} -d ${KRATOS_DB:-ory_kratos}",
] ]
interval: 5s interval: 5s
timeout: 5s timeout: 5s
retries: 5 retries: 5
kratos-migrate: kratos-migrate:
image: oryd/kratos:${KRATOS_VERSION:-v25.4.0} image: oryd/kratos:${KRATOS_VERSION:-v26.2.0}
environment: environment:
- DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KRATOS_DB:-ory_kratos}?sslmode=disable&max_conns=20 - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KRATOS_DB:-ory_kratos}?sslmode=disable&max_conns=20
- KRATOS_SERVE_PUBLIC_BASE_URL=${KRATOS_BROWSER_URL:-http://localhost:4433} - KRATOS_SERVE_PUBLIC_BASE_URL=${KRATOS_BROWSER_URL}
- KRATOS_SERVE_ADMIN_BASE_URL=${KRATOS_ADMIN_URL:-http://kratos:4434} - KRATOS_SERVE_ADMIN_BASE_URL=${KRATOS_ADMIN_URL}
- KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL:-http://localhost:5000} - KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL}
- KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS=["${KRATOS_UI_URL:-http://localhost:5000}","${USERFRONT_URL:-http://localhost:5000}"] - KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS=${KRATOS_ALLOWED_RETURN_URLS_JSON:-["${KRATOS_UI_URL}","${KRATOS_UI_URL}/","${USERFRONT_URL}","${USERFRONT_URL}/","${USERFRONT_URL}/ko","${USERFRONT_URL}/ko/","${USERFRONT_URL}/en","${USERFRONT_URL}/en/","${USERFRONT_URL}/auth/callback","${USERFRONT_URL}/ko/auth/callback","${USERFRONT_URL}/en/auth/callback","${ADMINFRONT_URL}/auth/callback","${DEVFRONT_URL}/auth/callback","${ORGFRONT_URL}/auth/callback"]}
- KRATOS_SELFSERVICE_FLOWS_ERROR_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/error - KRATOS_SELFSERVICE_FLOWS_ERROR_UI_URL=${KRATOS_UI_URL}/error
- KRATOS_SELFSERVICE_FLOWS_SETTINGS_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/error?error=settings_disabled - KRATOS_SELFSERVICE_FLOWS_SETTINGS_UI_URL=${KRATOS_UI_URL}/error?error=settings_disabled
- KRATOS_SELFSERVICE_FLOWS_RECOVERY_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/recovery - KRATOS_SELFSERVICE_FLOWS_RECOVERY_UI_URL=${KRATOS_UI_URL}/recovery
- KRATOS_SELFSERVICE_FLOWS_VERIFICATION_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/verification - KRATOS_SELFSERVICE_FLOWS_VERIFICATION_UI_URL=${KRATOS_UI_URL}/verification
- KRATOS_SELFSERVICE_FLOWS_LOGIN_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/login - KRATOS_SELFSERVICE_FLOWS_LOGIN_UI_URL=${KRATOS_UI_URL}/login
- KRATOS_SELFSERVICE_FLOWS_REGISTRATION_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/registration - KRATOS_SELFSERVICE_FLOWS_REGISTRATION_UI_URL=${KRATOS_UI_URL}/registration
- KRATOS_SELFSERVICE_FLOWS_LOGOUT_AFTER_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL:-http://localhost:5000}/login - KRATOS_SELFSERVICE_FLOWS_LOGOUT_AFTER_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL}/login
volumes: volumes:
- ./docker/ory/kratos:/etc/config/kratos - ./docker/ory/kratos:/etc/config/kratos
command: migrate sql up -e -c /etc/config/kratos/kratos.yml --yes command: migrate sql up -e -c /etc/config/kratos/kratos.yml --yes
depends_on: depends_on:
postgres_ory: postgres_ory:
condition: service_healthy condition: service_healthy
networks: networks:
- ory-net - ory-net
kratos: kratos:
image: oryd/kratos:${KRATOS_VERSION:-v25.4.0} image: oryd/kratos:${KRATOS_VERSION:-v26.2.0}
container_name: ory_kratos container_name: ory_kratos
environment: environment:
- DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KRATOS_DB:-ory_kratos}?sslmode=disable&max_conns=20 - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KRATOS_DB:-ory_kratos}?sslmode=disable&max_conns=20
- COOKIE_SECRET=${COOKIE_SECRET:-localcookie123} - COOKIE_SECRET=${COOKIE_SECRET:-localcookie123}
- KRATOS_SERVE_PUBLIC_BASE_URL=${KRATOS_BROWSER_URL:-http://localhost:4433} - KRATOS_SERVE_PUBLIC_BASE_URL=${KRATOS_BROWSER_URL}
- KRATOS_SERVE_ADMIN_BASE_URL=${KRATOS_ADMIN_URL:-http://kratos:4434} - KRATOS_SERVE_ADMIN_BASE_URL=${KRATOS_ADMIN_URL}
- KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL:-http://localhost:5000} - KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL}
- KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS=["${KRATOS_UI_URL:-http://localhost:5000}","${USERFRONT_URL:-http://localhost:5000}"] - KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS=${KRATOS_ALLOWED_RETURN_URLS_JSON:-["${KRATOS_UI_URL}","${KRATOS_UI_URL}/","${USERFRONT_URL}","${USERFRONT_URL}/","${USERFRONT_URL}/ko","${USERFRONT_URL}/ko/","${USERFRONT_URL}/en","${USERFRONT_URL}/en/","${USERFRONT_URL}/auth/callback","${USERFRONT_URL}/ko/auth/callback","${USERFRONT_URL}/en/auth/callback","${ADMINFRONT_URL}/auth/callback","${DEVFRONT_URL}/auth/callback","${ORGFRONT_URL}/auth/callback"]}
- KRATOS_SELFSERVICE_FLOWS_ERROR_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/error - KRATOS_SELFSERVICE_FLOWS_ERROR_UI_URL=${KRATOS_UI_URL}/error
- KRATOS_SELFSERVICE_FLOWS_SETTINGS_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/error?error=settings_disabled - KRATOS_SELFSERVICE_FLOWS_SETTINGS_UI_URL=${KRATOS_UI_URL}/error?error=settings_disabled
- KRATOS_SELFSERVICE_FLOWS_RECOVERY_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/recovery - KRATOS_SELFSERVICE_FLOWS_RECOVERY_UI_URL=${KRATOS_UI_URL}/recovery
- KRATOS_SELFSERVICE_FLOWS_VERIFICATION_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/verification - KRATOS_SELFSERVICE_FLOWS_VERIFICATION_UI_URL=${KRATOS_UI_URL}/verification
- KRATOS_SELFSERVICE_FLOWS_LOGIN_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/login - KRATOS_SELFSERVICE_FLOWS_LOGIN_UI_URL=${KRATOS_UI_URL}/login
- KRATOS_SELFSERVICE_FLOWS_REGISTRATION_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/registration - KRATOS_SELFSERVICE_FLOWS_REGISTRATION_UI_URL=${KRATOS_UI_URL}/registration
- KRATOS_SELFSERVICE_FLOWS_LOGOUT_AFTER_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL:-http://localhost:5000}/login - KRATOS_SELFSERVICE_FLOWS_LOGOUT_AFTER_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL}/login
volumes: volumes:
- ./docker/ory/kratos:/etc/config/kratos - ./docker/ory/kratos:/etc/config/kratos
command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier
depends_on: depends_on:
kratos-migrate: kratos-migrate:
condition: service_completed_successfully condition: service_completed_successfully
networks: networks:
- ory-net - ory-net
- kratosnet - kratosnet
hydra-migrate: hydra-migrate:
image: oryd/hydra:${HYDRA_VERSION:-v25.4.0} image: oryd/hydra:${HYDRA_VERSION:-v26.2.0}
environment: environment:
- DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${HYDRA_DB:-ory_hydra}?sslmode=disable&max_conns=20 - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${HYDRA_DB:-ory_hydra}?sslmode=disable&max_conns=20
command: migrate sql up -e --yes command: migrate sql up -e --yes
depends_on: depends_on:
postgres_ory: postgres_ory:
condition: service_healthy condition: service_healthy
networks: networks:
- ory-net - ory-net
hydra: hydra:
image: oryd/hydra:${HYDRA_VERSION:-v25.4.0} image: oryd/hydra:${HYDRA_VERSION:-v26.2.0}
container_name: ory_hydra container_name: ory_hydra
environment: environment:
- DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${HYDRA_DB:-ory_hydra}?sslmode=disable&max_conns=20 - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${HYDRA_DB:-ory_hydra}?sslmode=disable&max_conns=20
- URLS_SELF_ISSUER=${USERFRONT_URL:-http://localhost:5000}/oidc - URLS_SELF_ISSUER=${HYDRA_PUBLIC_URL}
- URLS_LOGIN=${USERFRONT_URL:-http://localhost:5000}/login - URLS_LOGIN=${HYDRA_LOGIN_URL:-${USERFRONT_URL}/login}
- URLS_CONSENT=${USERFRONT_URL:-http://localhost:5000}/consent - URLS_CONSENT=${HYDRA_CONSENT_URL:-${USERFRONT_URL}/consent}
- SECRETS_SYSTEM=${ORY_POSTGRES_PASSWORD} - URLS_ERROR=${HYDRA_ERROR_URL:-${USERFRONT_URL}/error}
volumes: - SECRETS_SYSTEM=${ORY_POSTGRES_PASSWORD}
- ./docker/ory/hydra:/etc/config/hydra volumes:
command: serve -c /etc/config/hydra/hydra.yml all --dev - ./docker/ory/hydra:/etc/config/hydra
depends_on: command: serve -c /etc/config/hydra/hydra.yml all --dev
hydra-migrate: depends_on:
condition: service_completed_successfully hydra-migrate:
networks: condition: service_completed_successfully
- ory-net networks:
- hydranet - ory-net
- hydranet
keto-migrate: keto-migrate:
image: oryd/keto:${KETO_VERSION:-v25.4.0} image: oryd/keto:${KETO_VERSION:-v26.2.0}
environment: environment:
- DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20 - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20
volumes: volumes:
- ./docker/ory/keto:/etc/config/keto - ./docker/ory/keto:/etc/config/keto
command: ["migrate", "up", "-c", "/etc/config/keto/keto.yml", "--yes"] command: ["migrate", "up", "-c", "/etc/config/keto/keto.yml", "--yes"]
depends_on: depends_on:
postgres_ory: postgres_ory:
condition: service_healthy condition: service_healthy
networks: networks:
- ory-net - ory-net
keto: keto:
image: oryd/keto:${KETO_VERSION:-v25.4.0} image: oryd/keto:${KETO_VERSION:-v26.2.0}
container_name: ory_keto container_name: ory_keto
environment: environment:
- DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20 - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20
volumes: volumes:
- ./docker/ory/keto:/etc/config/keto - ./docker/ory/keto:/etc/config/keto
command: serve -c /etc/config/keto/keto.yml command: serve -c /etc/config/keto/keto.yml
depends_on: depends_on:
keto-migrate: keto-migrate:
condition: service_completed_successfully condition: service_completed_successfully
networks: networks:
- ory-net - ory-net
oathkeeper: oathkeeper_logs_init:
image: oryd/oathkeeper:${OATHKEEPER_VERSION:-v0.40.6} image: alpine:latest
container_name: oathkeeper command:
restart: unless-stopped [
depends_on: "sh",
kratos: "-c",
condition: service_started "mkdir -p /var/log/oathkeeper && chown -R ${OATHKEEPER_UID:-1001}:${OATHKEEPER_GID:-1001} /var/log/oathkeeper",
environment: ]
- LOG_LEVEL=debug volumes:
command: serve proxy --config /etc/config/oathkeeper/oathkeeper.yml - oathkeeper_logs:/var/log/oathkeeper
volumes: networks:
- ./docker/ory/oathkeeper:/etc/config/oathkeeper - ory-net
- oathkeeper_logs:/var/log/oathkeeper
networks:
- ory-net
- baron_net
- public_net
ports:
- "4455:4455"
- "4456:4456"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:4456/health/ready"]
interval: 5s
timeout: 5s
retries: 5
ory_clickhouse: oathkeeper:
image: clickhouse/clickhouse-server:latest image: oryd/oathkeeper:${OATHKEEPER_VERSION:-v0.40.6}
container_name: ory_clickhouse container_name: oathkeeper
environment: restart: unless-stopped
- CLICKHOUSE_USER=${ORY_CLICKHOUSE_USER:-ory} depends_on:
- CLICKHOUSE_PASSWORD=${ORY_CLICKHOUSE_PASSWORD:-orypass} oathkeeper_logs_init:
volumes: condition: service_completed_successfully
- ory_clickhouse_data:/var/lib/clickhouse kratos:
- ./docker/ory/clickhouse:/docker-entrypoint-initdb.d condition: service_started
networks: user: "${OATHKEEPER_UID:-1001}:${OATHKEEPER_GID:-1001}"
- ory-net environment:
- APP_ENV=${APP_ENV:-stage}
- LOG_LEVEL=debug
- OATHKEEPER_INTROSPECT_CLIENT_ID=${OATHKEEPER_INTROSPECT_CLIENT_ID:-oathkeeper-introspect}
- OATHKEEPER_INTROSPECT_CLIENT_SECRET=${OATHKEEPER_INTROSPECT_CLIENT_SECRET:-oathkeeper-secret}
volumes:
- ./docker/ory/oathkeeper:/etc/config/oathkeeper
- oathkeeper_logs:/var/log/oathkeeper
entrypoint: ["/etc/config/oathkeeper/entrypoint.sh"]
networks:
- ory-net
- baron_net
- public_net
ports:
- "4455:4455"
- "4456:4456"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:4456/health/ready"]
interval: 5s
timeout: 5s
retries: 5
ory_stack_check: ory_clickhouse:
image: alpine:latest image: clickhouse/clickhouse-server:latest
container_name: ory_stack_check container_name: ory_clickhouse
command: > environment:
/bin/sh -c " - CLICKHOUSE_USER=${ORY_CLICKHOUSE_USER:-ory}
apk add --no-cache curl; - CLICKHOUSE_PASSWORD=${ORY_CLICKHOUSE_PASSWORD:-orypass}
echo 'Wait for services...'; volumes:
until curl -s http://kratos:4433/health/ready; do sleep 1; done; - ory_clickhouse_data:/var/lib/clickhouse
until curl -s http://hydra:4444/health/ready; do sleep 1; done; - ./docker/ory/clickhouse:/docker-entrypoint-initdb.d
until curl -s http://keto:4466/health/ready; do sleep 1; done; networks:
echo 'Ory Stack is fully operational!';" - ory-net
depends_on:
- kratos
- hydra
- keto
networks:
- ory-net
init-rp: ory_stack_check:
image: alpine:latest image: alpine:latest
env_file: container_name: ory_stack_check
- .env command: >
command: /bin/sh -c "
- /bin/sh apk add --no-cache curl;
- -ec echo 'Wait for services...';
- | until curl -s http://kratos:4433/health/ready; do sleep 1; done;
apk add --no-cache curl tar until curl -s http://hydra:4444/health/ready; do sleep 1; done;
HYDRA_CLI_VERSION="$${HYDRA_VERSION:-v26.2.0}" until curl -s http://keto:4466/health/ready; do sleep 1; done;
HYDRA_CLI_VERSION="$${HYDRA_CLI_VERSION%-distroless}" echo 'Ory Stack is fully operational!';"
HYDRA_CLI_ARCHIVE_VERSION="$${HYDRA_CLI_VERSION#v}" depends_on:
curl -fsSLo /tmp/hydra.tar.gz "https://github.com/ory/hydra/releases/download/$${HYDRA_CLI_VERSION}/hydra_$${HYDRA_CLI_ARCHIVE_VERSION}-linux_64bit.tar.gz" - kratos
tar -xzf /tmp/hydra.tar.gz -C /usr/local/bin hydra - hydra
rm /tmp/hydra.tar.gz - keto
networks:
- ory-net
# Function to create or update OAuth2 client (Idempotency) init-rp:
upsert_client() { image: alpine:latest
ID=$$1 env_file:
shift - .env
if hydra get oauth2-client --endpoint http://hydra:4445 "$$ID" >/dev/null 2>&1; then command:
echo "Updating existing client: $$ID" - /bin/sh
hydra update oauth2-client --endpoint http://hydra:4445 "$$ID" "$$@" - -ec
else - |
echo "Creating new client: $$ID" apk add --no-cache curl tar
hydra create oauth2-client --endpoint http://hydra:4445 --id "$$ID" "$$@" HYDRA_CLI_VERSION="$${HYDRA_VERSION:-v26.2.0}"
fi HYDRA_CLI_VERSION="$${HYDRA_CLI_VERSION%-distroless}"
} HYDRA_CLI_ARCHIVE_VERSION="$${HYDRA_CLI_VERSION#v}"
curl -fsSLo /tmp/hydra.tar.gz "https://github.com/ory/hydra/releases/download/$${HYDRA_CLI_VERSION}/hydra_$${HYDRA_CLI_ARCHIVE_VERSION}-linux_64bit.tar.gz"
tar -xzf /tmp/hydra.tar.gz -C /usr/local/bin hydra
rm /tmp/hydra.tar.gz
upsert_client "adminfront" \ # Function to create or update OAuth2 client (Idempotency)
--name "AdminFront" \ upsert_client() {
--grant-type authorization_code,refresh_token \ ID=$$1
--response-type code \ shift
--scope openid,offline_access,profile,email \ if hydra get oauth2-client --endpoint "$${HYDRA_ADMIN_URL}" "$$ID" >/dev/null 2>&1; then
--token-endpoint-auth-method none \ echo "Updating existing client: $$ID"
--redirect-uri "$${ADMINFRONT_CALLBACK_URLS:-http://localhost:5173/auth/callback}" hydra update oauth2-client --endpoint "$${HYDRA_ADMIN_URL}" "$$ID" "$$@"
else
echo "Creating new client: $$ID"
hydra create oauth2-client --endpoint "$${HYDRA_ADMIN_URL}" --id "$$ID" "$$@"
fi
}
upsert_client "devfront" \ upsert_client "adminfront" \
--name "DevFront" \ --name "AdminFront" \
--grant-type authorization_code,refresh_token \ --grant-type authorization_code,refresh_token \
--response-type code \ --response-type code \
--scope openid,offline_access,profile,email \ --scope openid,offline_access,profile,email \
--token-endpoint-auth-method none \ --token-endpoint-auth-method none \
--redirect-uri "$${DEVFRONT_CALLBACK_URLS:-http://localhost:5174/auth/callback}" --redirect-uri "$${ADMINFRONT_CALLBACK_URLS:-$${ADMINFRONT_URL}/auth/callback}"
upsert_client "orgfront" \ upsert_client "devfront" \
--name "OrgFront" \ --name "DevFront" \
--grant-type authorization_code,refresh_token \ --grant-type authorization_code,refresh_token \
--response-type code \ --response-type code \
--scope openid,offline_access,profile,email \ --scope openid,offline_access,profile,email \
--token-endpoint-auth-method none \ --token-endpoint-auth-method none \
--redirect-uri "$${ORGFRONT_CALLBACK_URLS:-http://localhost:5175/auth/callback,http://172.16.10.176:5175/auth/callback, https://baron-orgchart.hmac.kr/auth/callback}" --redirect-uri "$${DEVFRONT_CALLBACK_URLS:-$${DEVFRONT_URL}/auth/callback}"
upsert_client "$${OATHKEEPER_INTROSPECT_CLIENT_ID:-oathkeeper-introspect}" \ upsert_client "orgfront" \
--secret "$${OATHKEEPER_INTROSPECT_CLIENT_SECRET:-oathkeeper-secret}" \ --name "OrgFront" \
--grant-type client_credentials \ --grant-type authorization_code,refresh_token \
--response-type token \ --response-type code \
--scope openid,offline_access,profile,email --scope openid,offline_access,profile,email \
depends_on: --token-endpoint-auth-method none \
ory_stack_check: --redirect-uri "$${ORGFRONT_CALLBACK_URLS:-$${ORGFRONT_URL}/auth/callback}"
condition: service_completed_successfully
networks:
- hydranet
backend: upsert_client "$${OATHKEEPER_INTROSPECT_CLIENT_ID:-oathkeeper-introspect}" \
build: --secret "$${OATHKEEPER_INTROSPECT_CLIENT_SECRET:-oathkeeper-secret}" \
context: ./backend --grant-type client_credentials \
dockerfile: Dockerfile --response-type token \
container_name: baron_backend --scope openid,offline_access,profile,email
env_file: depends_on:
- .env ory_stack_check:
environment: condition: service_completed_successfully
- APP_ENV=${APP_ENV:-development} networks:
- GO_ENV=${APP_ENV:-development} - hydranet
- COOKIE_SECRET=${COOKIE_SECRET}
- JWT_SECRET=${JWT_SECRET}
- NAVER_CLOUD_ACCESS_KEY=${NAVER_CLOUD_ACCESS_KEY}
- NAVER_CLOUD_SECRET_KEY=${NAVER_CLOUD_SECRET_KEY}
- NAVER_CLOUD_SERVICE_ID=${NAVER_CLOUD_SERVICE_ID}
- NAVER_SENDER_PHONE_NUMBER=${NAVER_SENDER_PHONE_NUMBER}
- USERFRONT_URL=${USERFRONT_URL}
- REDIS_ADDR=${REDIS_ADDR}
- IDP_PROVIDER=${IDP_PROVIDER:-ory}
- KRATOS_ADMIN_URL=${KRATOS_ADMIN_URL:-http://kratos:4434}
- HYDRA_ADMIN_URL=${HYDRA_ADMIN_URL:-http://hydra:4445}
- HYDRA_PUBLIC_URL=${HYDRA_PUBLIC_URL:-http://hydra:4444}
- DB_HOST=postgres
- CLICKHOUSE_HOST=clickhouse
- CLICKHOUSE_PORT=${CLICKHOUSE_PORT_NATIVE:-9000}
- CLICKHOUSE_USER=${CLICKHOUSE_USER:-baron}
- CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD:-password}
- SEED_TENANT_CSV_PATH=/app/seed-tenant.csv
depends_on:
clickhouse:
condition: service_healthy
redis:
condition: service_healthy
oathkeeper:
condition: service_healthy
kratos:
condition: service_started
hydra:
condition: service_started
keto:
condition: service_started
infra_check:
condition: service_started
networks:
- baron_net
- ory-net
volumes:
- ./backend:/app
- ./adminfront/seed-tenant.csv:/app/seed-tenant.csv:ro
command: ["go", "run", "./cmd/server"]
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 10s
adminfront: backend:
build: build:
context: ./adminfront context: ./backend
dockerfile: Dockerfile dockerfile: Dockerfile
container_name: baron_adminfront container_name: baron_backend
env_file: env_file:
- .env - .env
environment: environment:
- APP_ENV=${APP_ENV:-development} - APP_ENV=${APP_ENV:-development}
- API_PROXY_TARGET=http://baron_backend:3000 - GO_ENV=${APP_ENV:-development}
ports: - COOKIE_SECRET=${COOKIE_SECRET}
- "${ADMINFRONT_PORT:-5173}:5173" - JWT_SECRET=${JWT_SECRET}
volumes: - NAVER_CLOUD_ACCESS_KEY=${NAVER_CLOUD_ACCESS_KEY}
- ./adminfront:/app - NAVER_CLOUD_SECRET_KEY=${NAVER_CLOUD_SECRET_KEY}
- /app/node_modules - NAVER_CLOUD_SERVICE_ID=${NAVER_CLOUD_SERVICE_ID}
networks: - NAVER_SENDER_PHONE_NUMBER=${NAVER_SENDER_PHONE_NUMBER}
- baron_net - USERFRONT_URL=${USERFRONT_URL}
- REDIS_ADDR=${REDIS_ADDR}
- IDP_PROVIDER=${IDP_PROVIDER:-ory}
- KRATOS_ADMIN_URL=${KRATOS_ADMIN_URL:-http://kratos:4434}
- HYDRA_ADMIN_URL=${HYDRA_ADMIN_URL:-http://hydra:4445}
- HYDRA_PUBLIC_URL=${HYDRA_PUBLIC_URL}
- DB_HOST=postgres
- CLICKHOUSE_HOST=clickhouse
- CLICKHOUSE_PORT=${CLICKHOUSE_PORT_NATIVE:-9000}
- CLICKHOUSE_USER=${CLICKHOUSE_USER:-baron}
- CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD:-password}
- SEED_TENANT_CSV_PATH=/app/seed-tenant.csv
depends_on:
clickhouse:
condition: service_healthy
redis:
condition: service_healthy
oathkeeper:
condition: service_healthy
kratos:
condition: service_started
hydra:
condition: service_started
keto:
condition: service_started
infra_check:
condition: service_started
networks:
- baron_net
- ory-net
volumes:
- ./backend:/app
- ./adminfront/seed-tenant.csv:/app/seed-tenant.csv:ro
command: ["go", "run", "./cmd/server"]
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 10s
devfront: adminfront:
build: build:
context: ./devfront context: ./adminfront
dockerfile: Dockerfile dockerfile: Dockerfile
container_name: baron_devfront container_name: baron_adminfront
env_file: env_file:
- .env - .env
environment: environment:
- APP_ENV=${APP_ENV:-development} - APP_ENV=${APP_ENV:-development}
- API_PROXY_TARGET=http://baron_backend:3000 - API_PROXY_TARGET=http://baron_backend:3000
ports: ports:
- "${DEVFRONT_PORT:-5174}:5173" - "${ADMINFRONT_PORT:-5173}:5173"
volumes: volumes:
- ./devfront:/app - ./adminfront:/app
- /app/node_modules - /app/node_modules
networks: networks:
- baron_net - baron_net
orgfront: devfront:
build: build:
context: ./orgfront context: ./devfront
dockerfile: Dockerfile dockerfile: Dockerfile
container_name: baron_orgfront container_name: baron_devfront
env_file: env_file:
- .env - .env
environment: environment:
- APP_ENV=${APP_ENV:-development} - APP_ENV=${APP_ENV:-development}
- API_PROXY_TARGET=http://baron_backend:3000 - API_PROXY_TARGET=http://baron_backend:3000
- USERFRONT_URL=${USERFRONT_URL} ports:
ports: - "${DEVFRONT_PORT:-5174}:5173"
- "${ORGFRONT_PORT:-5175}:5175" volumes:
volumes: - ./devfront:/app
- ./orgfront:/app - /app/node_modules
- /app/node_modules networks:
networks: - baron_net
- baron_net
userfront: orgfront:
build: build:
context: . context: ./orgfront
dockerfile: userfront/Dockerfile dockerfile: Dockerfile
container_name: baron_userfront container_name: baron_orgfront
env_file: env_file:
- .env - .env
environment: environment:
- BACKEND_URL=${BACKEND_URL:-} - APP_ENV=${APP_ENV:-development}
- USERFRONT_URL=${USERFRONT_URL} - API_PROXY_TARGET=http://baron_backend:3000
- APP_ENV=${APP_ENV} - USERFRONT_URL=${USERFRONT_URL}
networks: ports:
- baron_net - "${ORGFRONT_PORT:-5175}:5175"
- ory-net volumes:
depends_on: - ./orgfront:/app
backend: - /app/node_modules
condition: service_healthy networks:
command: > - baron_net
/bin/sh -c "mkdir -p /usr/share/nginx/html/assets &&
echo \"BACKEND_URL=$${BACKEND_URL}\" >> /usr/share/nginx/html/assets/.env &&
echo \"USERFRONT_URL=$${USERFRONT_URL}\" >> /usr/share/nginx/html/assets/.env &&
echo \"APP_ENV=$${APP_ENV}\" >> /usr/share/nginx/html/assets/.env &&
cp /usr/share/nginx/html/assets/.env /usr/share/nginx/html/.env &&
nginx -g 'daemon off;'"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:5000/"]
interval: 10s
timeout: 5s
retries: 3
start_period: 10s
infra_check: userfront:
image: alpine build:
command: ["echo", "Infrastructure assumed running"] context: .
networks: dockerfile: userfront/Dockerfile
- baron_net container_name: baron_userfront
env_file:
- .env
environment:
- BACKEND_URL=${BACKEND_URL:-}
- USERFRONT_URL=${USERFRONT_URL}
- APP_ENV=${APP_ENV}
networks:
- baron_net
- ory-net
depends_on:
backend:
condition: service_healthy
command: >
/bin/sh -c "mkdir -p /usr/share/nginx/html/assets &&
echo \"BACKEND_URL=$${BACKEND_URL}\" >> /usr/share/nginx/html/assets/.env &&
echo \"USERFRONT_URL=$${USERFRONT_URL}\" >> /usr/share/nginx/html/assets/.env &&
echo \"APP_ENV=$${APP_ENV}\" >> /usr/share/nginx/html/assets/.env &&
cp /usr/share/nginx/html/assets/.env /usr/share/nginx/html/.env &&
nginx -g 'daemon off;'"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:5000/"]
interval: 10s
timeout: 5s
retries: 3
start_period: 10s
infra_check:
image: alpine
command: ["echo", "Infrastructure assumed running"]
networks:
- baron_net
volumes: volumes:
postgres_data: postgres_data:
clickhouse_data: clickhouse_data:
redis_data: redis_data:
ory_postgres_data: ory_postgres_data:
ory_clickhouse_data: ory_clickhouse_data:
oathkeeper_logs: oathkeeper_logs:
networks: networks:
baron_net: baron_net:
external: true external: true
name: baron_net name: baron_net
public_net: public_net:
external: true external: true
name: public_net name: public_net
ory-net: ory-net:
external: true external: true
name: ory-net name: ory-net
hydranet: hydranet:
external: true external: true
name: hydranet name: hydranet
kratosnet: kratosnet:
external: true external: true
name: kratosnet name: kratosnet

View File

@@ -23,7 +23,7 @@ sequenceDiagram
UF->>HY: 로그인 승인 요청 UF->>HY: 로그인 승인 요청
HY->>User: 권한 동의(Consent) 화면 표시 HY->>User: 권한 동의(Consent) 화면 표시
User->>HY: '허용' 클릭 User->>HY: '허용' 클릭
HY-->>DF: 인증 코드와 함께 리다이렉트 (/callback?code=...) HY-->>DF: 인증 코드와 함께 리다이렉트 (/auth/callback?code=...)
DF->>HY: 토큰 교환 요청 (Code -> ID/Access Token) DF->>HY: 토큰 교환 요청 (Code -> ID/Access Token)
HY-->>DF: 토큰 발급 HY-->>DF: 토큰 발급
Note over DF: [FIX] 백엔드 /api/me 호출 대신<br/>ID Token에서 프로필 정보 직접 추출 Note over DF: [FIX] 백엔드 /api/me 호출 대신<br/>ID Token에서 프로필 정보 직접 추출
@@ -46,7 +46,7 @@ sequenceDiagram
* 사용자가 '허용'을 누르면 Hydra는 `devfront`가 신뢰할 수 있는 앱임을 기록합니다. * 사용자가 '허용'을 누르면 Hydra는 `devfront`가 신뢰할 수 있는 앱임을 기록합니다.
4. **인증 코드 전달 및 토큰 교환 (Callback)**: 4. **인증 코드 전달 및 토큰 교환 (Callback)**:
* Hydra는 사용자를 `devfront`의 콜백 페이지(`http://localhost:5174/callback?code=...`)로 보냅니다. * Hydra는 사용자를 `devfront`의 콜백 페이지(`http://localhost:5174/auth/callback?code=...`)로 보냅니다.
* `devfront`는 이 코드를 Hydra의 토큰 엔드포인트로 보내 **ID Token**과 **Access Token**을 발급받습니다. * `devfront`는 이 코드를 Hydra의 토큰 엔드포인트로 보내 **ID Token**과 **Access Token**을 발급받습니다.
5. **사용자 정보 로드 (Profile Recovery)**: 5. **사용자 정보 로드 (Profile Recovery)**:
@@ -71,9 +71,9 @@ hydra clients create
--response-types code --response-types code
--scope openid,offline_access,profile,email --scope openid,offline_access,profile,email
--token-endpoint-auth-method none \ # Public Client (PKCE 사용) --token-endpoint-auth-method none \ # Public Client (PKCE 사용)
--callbacks http://localhost:5174/callback; --callbacks http://localhost:5174/auth/callback;
``` ```
이 설정으로 인해 `devfront`라는 ID의 클라이언트가 미리 존재하게 되며, `localhost:5174`로의 리다이렉션이 안전하게 허용됩니다. 이 설정으로 인해 `devfront`라는 ID의 클라이언트가 미리 존재하게 되며, `localhost:5174/auth/callback`로의 리다이렉션이 안전하게 허용됩니다.
--- ---

View File

@@ -7,7 +7,8 @@
## 적용 범위 ## 적용 범위
- UserFront, AdminFront, DevFront의 로그인/콜백 경로 - UserFront, AdminFront, DevFront의 로그인/콜백 경로
- Ory Stack(Hydra/Kratos/Oathkeeper) 설정 - Ory Stack(Hydra/Kratos/Oathkeeper) 설정
- `compose.ory.yaml`, `gateway/nginx.conf`, `docker/ory/oathkeeper/rules*.json` - `compose.ory.yaml`, `docker/compose.ory.yaml`, `docker/staging_pull_compose.template.yaml`
- `gateway/nginx.conf`, `deploy/templates/gateway/nginx.conf`, `docker/ory/oathkeeper/rules*.json`
- `Makefile` 기반 사전 검증/스모크 검증 단계 - `Makefile` 기반 사전 검증/스모크 검증 단계
## 핵심 원칙 ## 핵심 원칙
@@ -27,8 +28,8 @@
2. `mapped_match` 2. `mapped_match`
- Public URL과 Internal URL이 다르지만, 아래가 모두 성립 - Public URL과 Internal URL이 다르지만, 아래가 모두 성립
- Gateway 라우팅 규칙 존재 (예: `/oidc` rewrite) - Gateway 라우팅 규칙 존재: `/oidc` prefix를 제거하지 않고 Oathkeeper로 전달
- Oathkeeper `match``upstream` 규칙 존재 (예: `strip_path_prefix=/oidc`) - Oathkeeper `match``upstream` 규칙 존재: `/oidc/*` rule이 `strip_path=/oidc`로 Hydra에 전달
- 최종 업스트림이 기대 서비스(Hydra/Kratos)로 연결 - 최종 업스트림이 기대 서비스(Hydra/Kratos)로 연결
3. `unmapped_fail` 3. `unmapped_fail`
@@ -42,6 +43,8 @@
- `ADMINFRONT_CALLBACK_URLS`, `DEVFRONT_CALLBACK_URLS` URL 유효성/중복/경로 규약 - `ADMINFRONT_CALLBACK_URLS`, `DEVFRONT_CALLBACK_URLS` URL 유효성/중복/경로 규약
- Gateway `/oidc`, `/auth` 라우팅 규칙 존재 여부 - Gateway `/oidc`, `/auth` 라우팅 규칙 존재 여부
- Oathkeeper `rules*.json`의 Hydra/Kratos 매핑 규칙 존재 여부 - Oathkeeper `rules*.json`의 Hydra/Kratos 매핑 규칙 존재 여부
- staging pull/deploy template의 Oathkeeper entrypoint 사용 여부
- `KRATOS_ALLOWED_RETURN_URLS_JSON`에 공개 도메인, locale path, callback/return path가 포함되는지 여부
2. 런타임 검증 (`make verify-oidc-config`) 2. 런타임 검증 (`make verify-oidc-config`)
- OIDC Discovery endpoint 조회 가능 여부 - OIDC Discovery endpoint 조회 가능 여부
@@ -49,9 +52,38 @@
- 필요 시 Gateway 경유 endpoint probe로 매핑 체인 확인 - 필요 시 Gateway 경유 endpoint probe로 매핑 체인 확인
## 경로 규약 ## 경로 규약
- DevFront callback: `/callback` - DevFront callback: `/auth/callback`
- AdminFront callback: `/auth/callback` - AdminFront callback: `/auth/callback`
- OrgFront callback: `/auth/callback`
- UserFront OIDC 진입점: `/oidc/*` (Gateway 경유) - UserFront OIDC 진입점: `/oidc/*` (Gateway 경유)
- locale return path: `/ko`, `/en`, `/ko/auth/callback`, `/en/auth/callback`
## `/oidc` 책임 경계
- Gateway는 `/oidc` prefix를 보존합니다.
- Oathkeeper는 `/oidc/.well-known/*`, `/oidc/oauth2/*`, `/oidc/userinfo` rule에서 `strip_path=/oidc`를 적용합니다.
- Hydra는 prefix가 제거된 내부 경로(`/.well-known/*`, `/oauth2/*`, `/userinfo`)를 받습니다.
- 따라서 gateway template이나 staging pull compose에서 `rewrite ^/oidc`가 다시 들어가면 dev/stage/prod 간 책임 경계가 달라지므로 실패로 간주합니다.
## Oathkeeper rules 선택 정책
- Oathkeeper는 직접 `command: serve proxy ...`로 시작하지 않고 `/etc/config/oathkeeper/entrypoint.sh`를 통해 시작합니다.
- entrypoint는 `APP_ENV`에 따라 다음 파일을 선택하고 `/tmp/oathkeeper/rules.active.json`으로 복사합니다.
- `stage|staging`: `rules.stage.json`
- `production|prod`: `rules.prod.json`
- 그 외: `rules.json`
- `oathkeeper.yml``file:///tmp/oathkeeper/rules.active.json`만 읽습니다.
## Kratos allowed return URL 정책
- stage/prod에서는 `KRATOS_ALLOWED_RETURN_URLS_JSON`을 명시하는 것을 우선합니다.
- 최소 포함 대상:
- `KRATOS_UI_URL`, `KRATOS_UI_URL/`
- `USERFRONT_URL`, `USERFRONT_URL/`
- `USERFRONT_URL/ko`, `USERFRONT_URL/ko/`
- `USERFRONT_URL/en`, `USERFRONT_URL/en/`
- `USERFRONT_URL/auth/callback`
- `USERFRONT_URL/ko/auth/callback`
- `USERFRONT_URL/en/auth/callback`
- `ADMINFRONT_CALLBACK_URLS`, `DEVFRONT_CALLBACK_URLS`, `ORGFRONT_CALLBACK_URLS`
- private IP, legacy domain, comma-space가 포함된 URI 항목은 stage/prod 기본값으로 두지 않습니다.
## 운영 지침 ## 운영 지침
1. 환경별 URL은 동일할 필요가 없고, 매핑 체인이 검증 가능해야 합니다. 1. 환경별 URL은 동일할 필요가 없고, 매핑 체인이 검증 가능해야 합니다.
@@ -65,3 +97,4 @@
- #272 - #272
- #274 - #274
- #276 - #276
- #710

View File

@@ -33,12 +33,14 @@ Ory 구성은 **컨테이너 내부 통신 URL**과 **브라우저 접근 URL**
### 내부 통신용 URL(컨테이너 네트워크) ### 내부 통신용 URL(컨테이너 네트워크)
- `KRATOS_PUBLIC_URL=http://kratos:4433` - `KRATOS_PUBLIC_URL=http://kratos:4433`
- `KRATOS_ADMIN_URL=http://kratos:4434` - `KRATOS_ADMIN_URL=http://kratos:4434`
- `HYDRA_PUBLIC_URL=http://hydra:4444`
- `HYDRA_ADMIN_URL=http://hydra:4445` - `HYDRA_ADMIN_URL=http://hydra:4445`
- Hydra public upstream은 Oathkeeper rule 내부에서 `http://hydra:4444`로 전달합니다.
### 브라우저 접근용 URL(외부 도메인/프록시) ### 브라우저 접근용 URL(외부 도메인/프록시)
- `KRATOS_BROWSER_URL` : Kratos Public의 외부 URL - `KRATOS_BROWSER_URL` : Kratos Public의 외부 URL. 보통 `${OATHKEEPER_PUBLIC_URL}/auth`
- `KRATOS_UI_URL` : UserFront의 외부 URL (Kratos UI 역할) - `KRATOS_UI_URL` : UserFront의 외부 URL (Kratos UI 역할)
- `HYDRA_PUBLIC_URL` : Hydra issuer/OIDC discovery의 외부 URL. 보통 `${OATHKEEPER_PUBLIC_URL}/oidc`
- `VITE_OIDC_AUTHORITY` : 프론트엔드 OIDC authority. `HYDRA_PUBLIC_URL`과 같아야 합니다.
예시(로컬): 예시(로컬):
```env ```env
@@ -48,8 +50,11 @@ KRATOS_UI_URL=http://localhost:5000
예시(리버스 프록시/도메인): 예시(리버스 프록시/도메인):
```env ```env
KRATOS_BROWSER_URL=https://sso.example.com OATHKEEPER_PUBLIC_URL=https://sso.example.com
KRATOS_BROWSER_URL=https://sso.example.com/auth
KRATOS_UI_URL=https://sso.example.com KRATOS_UI_URL=https://sso.example.com
HYDRA_PUBLIC_URL=https://sso.example.com/oidc
VITE_OIDC_AUTHORITY=https://sso.example.com/oidc
``` ```
### 포트 노출 정책 ### 포트 노출 정책
@@ -64,6 +69,7 @@ Kratos는 self-service UI URL을 설정값으로 사용합니다. **UserFront의
- `KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL` - `KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL`
- `KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS` - `KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS`
- `KRATOS_SELFSERVICE_FLOWS_*_UI_URL` - `KRATOS_SELFSERVICE_FLOWS_*_UI_URL`
- `KRATOS_ALLOWED_RETURN_URLS_JSON`
compose에서 기본적으로 다음과 같이 오버라이드합니다: compose에서 기본적으로 다음과 같이 오버라이드합니다:
- `KRATOS_SELFSERVICE_FLOWS_LOGIN_UI_URL=${KRATOS_UI_URL}/login` - `KRATOS_SELFSERVICE_FLOWS_LOGIN_UI_URL=${KRATOS_UI_URL}/login`
@@ -72,18 +78,44 @@ compose에서 기본적으로 다음과 같이 오버라이드합니다:
- `KRATOS_SELFSERVICE_FLOWS_RECOVERY_UI_URL=${KRATOS_UI_URL}/recovery` - `KRATOS_SELFSERVICE_FLOWS_RECOVERY_UI_URL=${KRATOS_UI_URL}/recovery`
- `KRATOS_SELFSERVICE_FLOWS_VERIFICATION_UI_URL=${KRATOS_UI_URL}/verification` - `KRATOS_SELFSERVICE_FLOWS_VERIFICATION_UI_URL=${KRATOS_UI_URL}/verification`
## 5) 트러블슈팅 stage/prod에서는 `KRATOS_ALLOWED_RETURN_URLS_JSON`에 공개 도메인과 callback/locale 경로를 명시합니다.
### 5.1 로그인 클릭 시 동작 없음
필수 후보:
- `${KRATOS_UI_URL}`, `${KRATOS_UI_URL}/`
- `${USERFRONT_URL}`, `${USERFRONT_URL}/`
- `${USERFRONT_URL}/ko`, `${USERFRONT_URL}/ko/`
- `${USERFRONT_URL}/en`, `${USERFRONT_URL}/en/`
- `${USERFRONT_URL}/auth/callback`
- `${USERFRONT_URL}/ko/auth/callback`
- `${USERFRONT_URL}/en/auth/callback`
- `${ADMINFRONT_CALLBACK_URLS}`, `${DEVFRONT_CALLBACK_URLS}`, `${ORGFRONT_CALLBACK_URLS}`
## 5) `/oidc` Gateway/Oathkeeper 책임 경계
Gateway는 `/oidc` prefix를 rewrite하지 않습니다. `/oidc/*` 요청은 prefix를 보존한 채 Oathkeeper로 전달하고, Oathkeeper rule이 `strip_path=/oidc`로 Hydra 내부 upstream(`http://hydra:4444`)에 전달합니다.
이 정책은 `gateway/nginx.conf`, `deploy/templates/gateway/nginx.conf`, `docker/ory/oathkeeper/rules*.json`, `docker/staging_pull_compose.template.yaml`에서 동일해야 합니다.
## 6) Oathkeeper active rules
Oathkeeper는 `/etc/config/oathkeeper/entrypoint.sh`를 통해 시작해야 합니다. entrypoint는 `APP_ENV`에 따라 env별 rules 파일을 고르고 `/tmp/oathkeeper/rules.active.json`을 생성합니다.
- `APP_ENV=stage|staging`: `rules.stage.json`
- `APP_ENV=production|prod`: `rules.prod.json`
- 그 외: `rules.json`
`docker/ory/oathkeeper/oathkeeper.yml``file:///tmp/oathkeeper/rules.active.json`을 읽습니다. compose나 배포 템플릿이 entrypoint를 우회해 `oathkeeper serve proxy`를 직접 실행하면 active rules 생성이 누락될 수 있습니다.
## 7) 트러블슈팅
### 7.1 로그인 클릭 시 동작 없음
- 원인: Kratos 기동 실패(설정 파싱 실패 등) 또는 브라우저용 URL이 내부 도메인(`kratos:...`)으로 설정됨 - 원인: Kratos 기동 실패(설정 파싱 실패 등) 또는 브라우저용 URL이 내부 도메인(`kratos:...`)으로 설정됨
- 확인: - 확인:
- `docker logs ory_kratos`에서 config 오류 여부 확인 - `docker logs ory_kratos`에서 config 오류 여부 확인
- 브라우저 네트워크 탭에서 `/self-service/login/browser` 응답 확인(302 Location 헤더) - 브라우저 네트워크 탭에서 `/self-service/login/browser` 응답 확인(302 Location 헤더)
### 5.2 kratos.yml에 ${...} 환경 변수 치환 실패 ### 7.2 kratos.yml에 ${...} 환경 변수 치환 실패
- Kratos 설정 파일은 `${ENV}` 치환을 지원하지 않음 - Kratos 설정 파일은 `${ENV}` 치환을 지원하지 않음
- 해결: compose 환경 변수로 `KRATOS_SELFSERVICE_*`, `KRATOS_SERVE_*` 오버라이드 사용 - 해결: compose 환경 변수로 `KRATOS_SELFSERVICE_*`, `KRATOS_SERVE_*` 오버라이드 사용
## 6) 네트워크 접근 테스트 ## 8) 네트워크 접근 테스트
아래 스크립트는 **ory-net에서 Admin 포트 접근 가능** / **baron_net(Frontend 영역)에서 접근 불가**를 검증합니다. 아래 스크립트는 **ory-net에서 Admin 포트 접근 가능** / **baron_net(Frontend 영역)에서 접근 불가**를 검증합니다.
```bash ```bash
@@ -101,7 +133,7 @@ docker run --rm --network baron_net curlimages/curl:8.10.1 -fsS http://hydra:444
docker run --rm --network baron_net curlimages/curl:8.10.1 -fsS http://kratos:4434/health/ready docker run --rm --network baron_net curlimages/curl:8.10.1 -fsS http://kratos:4434/health/ready
``` ```
## 7) 참고 파일 ## 9) 참고 파일
- `compose.ory.yaml` - `compose.ory.yaml`
- `docker/ory/kratos/kratos.yml` - `docker/ory/kratos/kratos.yml`
- `.env.sample` - `.env.sample`

View File

@@ -61,7 +61,6 @@ server {
# Hydra Public API # Hydra Public API
location /oidc { location /oidc {
rewrite ^/oidc/(.*)$ /$1 break;
proxy_pass $oathkeeper_upstream; proxy_pass $oathkeeper_upstream;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;

View File

@@ -16,11 +16,12 @@ fi
USERFRONT_URL="${USERFRONT_URL:-http://localhost:5000}" USERFRONT_URL="${USERFRONT_URL:-http://localhost:5000}"
OATHKEEPER_PUBLIC_URL="${OATHKEEPER_PUBLIC_URL:-$USERFRONT_URL}" OATHKEEPER_PUBLIC_URL="${OATHKEEPER_PUBLIC_URL:-$USERFRONT_URL}"
HYDRA_PUBLIC_URL="${HYDRA_PUBLIC_URL:-${OATHKEEPER_PUBLIC_URL%/}/oidc}" HYDRA_PUBLIC_URL="${HYDRA_PUBLIC_URL:-${OATHKEEPER_PUBLIC_URL%/}/oidc}"
HYDRA_ADMIN_URL="${HYDRA_ADMIN_URL:-http://hydra:4445}"
KRATOS_UI_URL="${KRATOS_UI_URL:-http://localhost:5000}" KRATOS_UI_URL="${KRATOS_UI_URL:-http://localhost:5000}"
ADMINFRONT_URL="${ADMINFRONT_URL:-https://sadmin.hmac.kr}" ADMINFRONT_URL="${ADMINFRONT_URL:-https://sadmin.hmac.kr}"
DEVFRONT_URL="${DEVFRONT_URL:-https://sdev.hmac.kr}" DEVFRONT_URL="${DEVFRONT_URL:-https://sdev.hmac.kr}"
ADMINFRONT_CALLBACK_URLS="${ADMINFRONT_CALLBACK_URLS:-http://172.16.10.176:5173/auth/callback}" ADMINFRONT_CALLBACK_URLS="${ADMINFRONT_CALLBACK_URLS:-${ADMINFRONT_URL%/}/auth/callback}"
DEVFRONT_CALLBACK_URLS="${DEVFRONT_CALLBACK_URLS:-http://172.16.10.176:5174/auth/callback}" DEVFRONT_CALLBACK_URLS="${DEVFRONT_CALLBACK_URLS:-${DEVFRONT_URL%/}/auth/callback}"
KRATOS_ALLOWED_RETURN_URLS_EXTRA="${KRATOS_ALLOWED_RETURN_URLS_EXTRA:-}" KRATOS_ALLOWED_RETURN_URLS_EXTRA="${KRATOS_ALLOWED_RETURN_URLS_EXTRA:-}"
declare -a WARNINGS=() declare -a WARNINGS=()
@@ -258,13 +259,10 @@ validate_gateway_mapping() {
if ! grep -Eq 'location /oidc' "$ROOT_DIR/gateway/nginx.conf"; then if ! grep -Eq 'location /oidc' "$ROOT_DIR/gateway/nginx.conf"; then
mode="unmapped_fail" mode="unmapped_fail"
fi fi
if ! grep -Eq 'rewrite \^/oidc/\(\.\*\)\$ /\$1 break;' "$ROOT_DIR/gateway/nginx.conf"; then if ! grep -Eq '"url": "<\.\*>://<(\.\*|\[\^/\]\+)>/oidc/oauth2/<\.\*>"' "$ROOT_DIR/docker/ory/oathkeeper/rules.json"; then
mode="unmapped_fail" mode="unmapped_fail"
fi fi
if ! grep -Eq '"url": "<\.\*>://<\.\*>/oidc/oauth2/<\.\*>"' "$ROOT_DIR/docker/ory/oathkeeper/rules.json"; then if ! grep -Eq '"strip_path(_prefix)?": "/oidc"' "$ROOT_DIR/docker/ory/oathkeeper/rules.json"; then
mode="unmapped_fail"
fi
if ! grep -Eq '"strip_path_prefix": "/oidc"' "$ROOT_DIR/docker/ory/oathkeeper/rules.json"; then
mode="unmapped_fail" mode="unmapped_fail"
fi fi
fi fi
@@ -358,10 +356,10 @@ verify_runtime_hydra_clients() {
fi fi
local admin_info dev_info local admin_info dev_info
if ! admin_info="$(docker exec ory_hydra hydra get oauth2-client --endpoint http://hydra:4445 adminfront 2>/dev/null)"; then if ! admin_info="$(docker exec ory_hydra hydra get oauth2-client --endpoint "$HYDRA_ADMIN_URL" adminfront 2>/dev/null)"; then
fail "failed to read hydra client 'adminfront' from running container" fail "failed to read hydra client 'adminfront' from running container"
fi fi
if ! dev_info="$(docker exec ory_hydra hydra get oauth2-client --endpoint http://hydra:4445 devfront 2>/dev/null)"; then if ! dev_info="$(docker exec ory_hydra hydra get oauth2-client --endpoint "$HYDRA_ADMIN_URL" devfront 2>/dev/null)"; then
fail "failed to read hydra client 'devfront' from running container" fail "failed to read hydra client 'devfront' from running container"
fi fi
@@ -382,6 +380,7 @@ run_validation() {
validate_dotenv_line_safety "BACKEND_URL" validate_dotenv_line_safety "BACKEND_URL"
validate_dotenv_line_safety "OATHKEEPER_PUBLIC_URL" validate_dotenv_line_safety "OATHKEEPER_PUBLIC_URL"
validate_dotenv_line_safety "HYDRA_PUBLIC_URL" validate_dotenv_line_safety "HYDRA_PUBLIC_URL"
validate_dotenv_line_safety "HYDRA_ADMIN_URL"
validate_dotenv_line_safety "KRATOS_BROWSER_URL" validate_dotenv_line_safety "KRATOS_BROWSER_URL"
validate_dotenv_line_safety "KRATOS_UI_URL" validate_dotenv_line_safety "KRATOS_UI_URL"
validate_dotenv_line_safety "ADMINFRONT_URL" validate_dotenv_line_safety "ADMINFRONT_URL"

View File

@@ -3,6 +3,19 @@ set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
dry_run_default_dev="$(
make --dry-run --always-make -C "$repo_root" dev 2>&1
)"
default_app_up_line="$(
grep -E "docker compose .* -f docker-compose.yaml up .*backend.*adminfront.*devfront.*orgfront.*userfront" <<<"$dry_run_default_dev" | tail -1
)"
if [[ -z "$default_app_up_line" ]]; then
echo "make dev must include orgfront in the default development app services." >&2
exit 1
fi
dry_run_dev="$( dry_run_dev="$(
make --dry-run --always-make -C "$repo_root" dev DEV_SERVICES="backend adminfront" 2>&1 make --dry-run --always-make -C "$repo_root" dev DEV_SERVICES="backend adminfront" 2>&1
)" )"
@@ -45,10 +58,32 @@ if ! grep -q "Ensuring Ory stack" <<<"$dry_run_up_dev"; then
exit 1 exit 1
fi fi
dry_run_up_app="$(
make --dry-run --always-make -C "$repo_root" up-app 2>&1
)"
if ! grep -q "Starting App stack (backend/userfront/adminfront/devfront/orgfront)" <<<"$dry_run_up_app"; then
echo "make up-app must announce orgfront as part of the app stack." >&2
exit 1
fi
dry_run_up_all="$( dry_run_up_all="$(
make --dry-run --always-make -C "$repo_root" up-all 2>&1 make --dry-run --always-make -C "$repo_root" up-all 2>&1
)" )"
if ! dry_run_up="$(
make --dry-run --always-make -C "$repo_root" up 2>&1
)"; then
echo "make up must be available as the default full-stack startup target." >&2
echo "$dry_run_up" >&2
exit 1
fi
if ! grep -q "Starting ALL stacks (infra + ory + app)" <<<"$dry_run_up"; then
echo "make up must delegate to the full-stack startup flow." >&2
exit 1
fi
if ! grep -q "Ensuring Docker networks" <<<"$dry_run_up_all"; then if ! grep -q "Ensuring Docker networks" <<<"$dry_run_up_all"; then
echo "make up-all must ensure external Docker networks before compose up." >&2 echo "make up-all must ensure external Docker networks before compose up." >&2
exit 1 exit 1

View File

@@ -10,6 +10,26 @@ docker_config="$(
docker compose --env-file "$repo_root/.env" -f "$repo_root/docker/compose.ory.yaml" config docker compose --env-file "$repo_root/.env" -f "$repo_root/docker/compose.ory.yaml" config
)" )"
override_env="$(mktemp)"
cp "$repo_root/.env" "$override_env"
cat >> "$override_env" <<'EOF'
USERFRONT_URL=https://compose-policy.example.test/sso
HYDRA_PUBLIC_URL=https://compose-policy.example.test/sso/oidc
KRATOS_UI_URL=https://compose-policy.example.test/ui
KRATOS_BROWSER_URL=https://compose-policy.example.test/auth
ADMINFRONT_CALLBACK_URLS=https://compose-policy.example.test/admin/callback
DEVFRONT_CALLBACK_URLS=https://compose-policy.example.test/dev/callback
ORGFRONT_CALLBACK_URLS=https://compose-policy.example.test/org/callback
EOF
trap 'rm -f "$override_env"' EXIT
override_config="$(
docker compose --env-file "$override_env" -f "$repo_root/compose.ory.yaml" config
)"
override_docker_config="$(
docker compose --env-file "$override_env" -f "$repo_root/docker/compose.ory.yaml" config
)"
for service in kratos hydra keto oathkeeper; do for service in kratos hydra keto oathkeeper; do
version_key="$(tr '[:lower:]' '[:upper:]' <<<"$service")_VERSION" version_key="$(tr '[:lower:]' '[:upper:]' <<<"$service")_VERSION"
expected_version="$(grep -E "^${version_key}=" "$repo_root/.env" | cut -d= -f2-)" expected_version="$(grep -E "^${version_key}=" "$repo_root/.env" | cut -d= -f2-)"
@@ -28,6 +48,40 @@ if grep -q "oryd/hydra:v25.4.0" <<<"$root_config"; then
exit 1 exit 1
fi fi
for compose_file in "$repo_root/compose.ory.yaml" "$repo_root/docker/compose.ory.yaml"; do
if grep -Eq 'redirect-uri .*:-.*https?://' "$compose_file"; then
echo "ERROR: $compose_file must not hard-code external redirect URI fallbacks; use .env variables." >&2
exit 1
fi
if grep -Eq 'KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS=.*https?://localhost' "$compose_file"; then
echo "ERROR: $compose_file must not hard-code Kratos allowed return URL fallbacks; use .env variables." >&2
exit 1
fi
if awk 'in_block && /^ [A-Za-z0-9_-]+:/ { exit } /^ init-rp:/ { in_block=1 } in_block { print }' "$compose_file" | grep -q -- '--endpoint http://hydra:4445'; then
echo "ERROR: $compose_file init-rp must use HYDRA_ADMIN_URL instead of hard-coded Hydra admin endpoint." >&2
exit 1
fi
if awk 'in_block && /^ [A-Za-z0-9_-]+:/ { exit } /^[[:space:]]+oathkeeper:/ { in_block=1 } in_block { print }' "$compose_file" | grep -q "command: serve proxy --config /etc/config/oathkeeper/oathkeeper.yml"; then
echo "ERROR: $compose_file Oathkeeper must use entrypoint.sh instead of bypassing rules.active.json generation." >&2
exit 1
fi
done
for expected_url in \
"https://compose-policy.example.test/sso/oidc" \
"https://compose-policy.example.test/sso/login" \
"https://compose-policy.example.test/sso/consent" \
"https://compose-policy.example.test/sso/error" \
"https://compose-policy.example.test/admin/callback" \
"https://compose-policy.example.test/dev/callback" \
"https://compose-policy.example.test/org/callback"
do
if ! grep -q "$expected_url" <<<"$override_config$override_docker_config"; then
echo "ERROR: Ory compose config must render env override URL: $expected_url" >&2
exit 1
fi
done
root_init_rp="$( root_init_rp="$(
awk 'in_block && /^ [A-Za-z0-9_-]+:/ { exit } /^ init-rp:/ { in_block=1 } in_block { print }' "$repo_root/compose.ory.yaml" awk 'in_block && /^ [A-Za-z0-9_-]+:/ { exit } /^ init-rp:/ { in_block=1 } in_block { print }' "$repo_root/compose.ory.yaml"
)" )"
@@ -53,3 +107,159 @@ if grep -q "releases/download/v25.4.0" "$repo_root/docker/staging_pull_compose.t
echo "ERROR: staging pull compose must not download a hard-coded Hydra v25.4.0 CLI." >&2 echo "ERROR: staging pull compose must not download a hard-coded Hydra v25.4.0 CLI." >&2
exit 1 exit 1
fi fi
staging_pull_template="$repo_root/docker/staging_pull_compose.template.yaml"
if ! grep -q 'entrypoint: \["/etc/config/oathkeeper/entrypoint.sh"\]' "$staging_pull_template"; then
echo "ERROR: staging pull Oathkeeper must use the env-aware entrypoint." >&2
exit 1
fi
if grep -q "command: serve proxy --config /etc/config/oathkeeper/oathkeeper.yml" "$staging_pull_template"; then
echo "ERROR: staging pull Oathkeeper must not bypass entrypoint.sh with a direct command." >&2
exit 1
fi
if ! grep -q "URLS_SELF_ISSUER=\${HYDRA_PUBLIC_URL}" "$staging_pull_template"; then
echo "ERROR: staging pull Hydra issuer must use HYDRA_PUBLIC_URL." >&2
exit 1
fi
if grep -Eq '(KRATOS_(SERVE|SELFSERVICE|UI|BROWSER|PUBLIC|ADMIN).*:-http://localhost|URLS_.*:-http://localhost)' "$staging_pull_template"; then
echo "ERROR: staging pull Ory browser URLs must not fall back to localhost." >&2
exit 1
fi
if ! grep -q 'KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS=${KRATOS_ALLOWED_RETURN_URLS_JSON' "$staging_pull_template"; then
echo "ERROR: staging pull Kratos allowed_return_urls must be driven by KRATOS_ALLOWED_RETURN_URLS_JSON." >&2
exit 1
fi
for return_path in '/ko' '/en' '/auth/callback' '/ko/auth/callback' '/en/auth/callback'; do
if ! grep -q "$return_path" "$staging_pull_template" "$repo_root/deploy/templates/.env.template" "$repo_root/.gitea/workflows/staging_code_pull.yml"; then
echo "ERROR: staging/prod allowed_return_urls must include locale/callback path: $return_path" >&2
exit 1
fi
done
if grep -Eq 'ORGFRONT_CALLBACK_URLS=.*(172\.16\.10\.176|baron-orgchart\.hmac\.kr|, https?://)' "$staging_pull_template" "$repo_root/.gitea/workflows/staging_code_pull.yml"; then
echo "ERROR: staging pull OrgFront callbacks must not keep private IP, legacy orgchart domain, or comma-space URI entries." >&2
exit 1
fi
if grep -q "rewrite \\^/oidc" "$repo_root/gateway/nginx.conf"; then
echo "ERROR: gateway must preserve the /oidc prefix and let Oathkeeper strip it." >&2
exit 1
fi
for rules_file in \
"$repo_root/docker/ory/oathkeeper/rules.json" \
"$repo_root/docker/ory/oathkeeper/rules.stage.json" \
"$repo_root/docker/ory/oathkeeper/rules.prod.json"
do
for rule_id in hydra-well-known hydra-well-known-oidc hydra-oauth2 hydra-oauth2-oidc hydra-userinfo hydra-userinfo-oidc; do
if ! grep -q "\"id\": \"$rule_id\"" "$rules_file"; then
echo "ERROR: Oathkeeper rules must expose Hydra public route in $rules_file: $rule_id" >&2
exit 1
fi
done
for prefixed_rule in hydra-well-known-oidc hydra-oauth2-oidc hydra-userinfo-oidc; do
if ! awk -v id="\"id\": \"$prefixed_rule\"" '
$0 ~ id { in_rule = 1 }
in_rule && /strip_path/ && /\/oidc/ { found = 1 }
in_rule && /^ }[,]?$/ { in_rule = 0 }
END { exit found ? 0 : 1 }
' "$rules_file"; then
echo "ERROR: prefixed Oathkeeper route must strip /oidc in $rules_file: $prefixed_rule" >&2
exit 1
fi
done
done
for wildcard_rules_file in \
"$repo_root/docker/ory/oathkeeper/rules.json" \
"$repo_root/docker/ory/oathkeeper/rules.stage.json"
do
if grep -q "<\\.\\*>://<\\.\\*>/" "$wildcard_rules_file"; then
echo "ERROR: wildcard Oathkeeper host must not swallow path segments in $wildcard_rules_file." >&2
exit 1
fi
done
deploy_template="$repo_root/deploy/templates/docker-compose.yaml"
deploy_env_template="$repo_root/deploy/templates/.env.template"
deploy_gateway_template="$repo_root/deploy/templates/gateway/nginx.conf"
deploy_kratos_template="$repo_root/deploy/templates/ory/kratos/kratos.yml"
deploy_oathkeeper_rules_template="$repo_root/deploy/templates/ory/oathkeeper/rules.json"
for required_template in \
"$repo_root/deploy/templates/orgfront/vite.config.ts" \
"$repo_root/deploy/templates/orgfront/auth.ts" \
"$repo_root/docker/ory/init-db/01_create_dbs.sh" \
"$repo_root/docker/ory/hydra/hydra.yml" \
"$repo_root/docker/ory/keto/keto.yml" \
"$repo_root/docker/ory/oathkeeper/entrypoint.sh" \
"$repo_root/docker/ory/oathkeeper/oathkeeper.yml"
do
if [[ ! -f "$required_template" ]]; then
echo "ERROR: deploy instance generation requires missing source file: $required_template" >&2
exit 1
fi
done
if grep -Eq "oryd/(kratos|hydra|keto|oathkeeper):v25\\.4\\.0" "$deploy_template"; then
echo "ERROR: deploy template Ory stack must not hard-code v25.4.0 images." >&2
exit 1
fi
for prod_sensitive_file in \
"$repo_root/docker/ory/oathkeeper/rules.prod.json" \
"$repo_root/docker/ory/kratos/kratos.yml" \
"$repo_root/deploy/templates/ory/kratos/kratos.yml"
do
if grep -q "app\\.brsw\\.kr" "$prod_sensitive_file"; then
echo "ERROR: Ory production-sensitive config must not hard-code app.brsw.kr: $prod_sensitive_file" >&2
exit 1
fi
done
for service in kratos-migrate kratos hydra-migrate hydra keto-migrate keto oathkeeper_logs_init oathkeeper; do
if ! grep -q "^ $service:" "$deploy_template"; then
echo "ERROR: deploy template Ory stack must include service: $service" >&2
exit 1
fi
done
for version_key in KRATOS_VERSION HYDRA_VERSION KETO_VERSION OATHKEEPER_VERSION; do
if ! grep -q "^$version_key=v26\\.2\\.0$" "$deploy_env_template"; then
echo "ERROR: deploy env template must define $version_key=v26.2.0." >&2
exit 1
fi
done
if ! grep -q 'entrypoint: \["/etc/config/oathkeeper/entrypoint.sh"\]' "$deploy_template"; then
echo "ERROR: deploy template Oathkeeper must use the env-aware entrypoint." >&2
exit 1
fi
if grep -q "rewrite \\^/oidc" "$deploy_gateway_template"; then
echo "ERROR: deploy template gateway must preserve the /oidc prefix." >&2
exit 1
fi
if ! grep -q '^version: v26.2.0$' "$deploy_kratos_template"; then
echo "ERROR: deploy Kratos template config version must match v26.2.0." >&2
exit 1
fi
for rule_id in hydra-well-known hydra-well-known-oidc hydra-oauth2 hydra-oauth2-oidc hydra-userinfo hydra-userinfo-oidc; do
if ! grep -q "\"id\": \"$rule_id\"" "$deploy_oathkeeper_rules_template"; then
echo "ERROR: deploy Oathkeeper rules must expose Hydra public route: $rule_id" >&2
exit 1
fi
done
if ! grep -q '"strip_path": "/oidc"' "$deploy_oathkeeper_rules_template"; then
echo "ERROR: deploy Oathkeeper prefixed routes must strip /oidc with strip_path." >&2
exit 1
fi