diff --git a/.gitea/workflows/staging_code_pull.yml b/.gitea/workflows/staging_code_pull.yml index 8e5f2f8a..4e1a6233 100644 --- a/.gitea/workflows/staging_code_pull.yml +++ b/.gitea/workflows/staging_code_pull.yml @@ -163,8 +163,9 @@ jobs: done - # [중요] 설정 파일 권한 문제 해결 (Ory 이미지는 root가 아닌 사용자로 실행됨) - chmod -R 777 docker/ory || true + # Ory 컨테이너가 직접 읽는 설정은 env 기반으로 완성한 뒤 mount합니다. + bash scripts/render_ory_config.sh + chmod -R 777 config/.generated/ory || true cp docker/staging_pull_compose.template.yaml staging_pull_compose.yaml diff --git a/.gitignore b/.gitignore index 1a5c7519..a4f12e27 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ .codex/ .serena/ .generated/ +config/.generated/ *.swp *.log *.out diff --git a/Makefile b/Makefile index f4bd4fa0..df61eac0 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ endif COMPOSE_INFRA := compose.infra.yaml COMPOSE_ORY := compose.ory.yaml COMPOSE_APP := docker-compose.yaml -AUTH_CONFIG_ENV := .generated/auth-config.env +AUTH_CONFIG_ENV := config/.generated/auth-config.env DEV_SERVICES ?= backend adminfront devfront orgfront userfront DEV_NETWORKS := baron_net ory-net hydranet kratosnet public_net INFRA_CONTAINERS := baron_postgres baron_clickhouse baron_redis baron_gateway @@ -29,12 +29,12 @@ ifneq (,$(wildcard ./.env)) COMPOSE_DROP_ENV_ARGS += --env-file .env endif -.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 +.PHONY: build-auth-config validate-auth-config verify-auth-config render-ory-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: @echo "Building auth config..." - @mkdir -p .generated + @mkdir -p config/.generated @bash scripts/auth_config.sh build validate-auth-config: build-auth-config @@ -45,30 +45,34 @@ verify-auth-config: validate-auth-config @echo "Verifying auth config wiring..." @bash scripts/auth_config.sh verify +render-ory-config: validate-auth-config + @echo "Rendering Ory config..." + @bash scripts/render_ory_config.sh + # --- 기본 실행 --- # 주의: --remove-orphan 사용 금지 (다른 스택이 orphan으로 판단되어 종료될 수 있음) up: up-all -up-all: ensure-networks validate-auth-config +up-all: ensure-networks render-ory-config @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 --build -d # --- 개별 스택 실행 --- up-infra: ensure-networks @echo "Starting Infra stack (postgres/clickhouse/redis)..." docker compose -f $(COMPOSE_INFRA) up -d -up-ory: ensure-networks validate-auth-config +up-ory: ensure-networks render-ory-config @echo "Starting Ory stack (kratos/hydra/keto/oathkeeper)..." docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_ORY) up -d -up-app: ensure-networks validate-auth-config +up-app: ensure-networks render-ory-config @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 --build -d -up-backend: ensure-networks validate-auth-config +up-backend: ensure-networks render-ory-config @echo "Starting Backend only..." - docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_APP) up -d backend + docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_APP) up --build -d backend ensure-networks: @echo "Ensuring Docker networks..." @@ -97,7 +101,7 @@ ensure-infra: ensure-networks echo "Infra stack is already running."; \ fi -ensure-ory: ensure-networks validate-auth-config +ensure-ory: ensure-networks render-ory-config @echo "Ensuring Ory stack..." @missing=0; \ for container in $(ORY_CONTAINERS); do \ @@ -121,7 +125,7 @@ up-front-dev: up-infra up-ory up-backend dev: up-dev @echo "Starting development app containers in foreground attach mode..." - docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_APP) up $(DEV_SERVICES) + docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_APP) up --build $(DEV_SERVICES) # --- 종료 (Down) --- down: diff --git a/README.md b/README.md index dd0385bb..a9559c83 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ flowchart ``` ### 1. Backend (Go Fiber) -- **Language**: Go 1.25+ +- **Language**: Go 1.26.2+ - **Framework**: Fiber v2.25+ - **Database**: - **ClickHouse**: 감사 로그 (고성능 데이터 수집) @@ -436,7 +436,7 @@ USERFRONT_URL=https://sso.example.com ```bash make validate-auth-config ``` -위 검증은 callback/allowed_return_urls/게이트웨이 매핑 규칙을 점검하고 `.generated/auth-config.env`를 생성합니다. +위 검증은 callback/allowed_return_urls/게이트웨이 매핑 규칙을 점검하고 `config/.generated/auth-config.env`를 생성합니다. ### 전체 스택 실행 (Running the Stack) @@ -478,7 +478,7 @@ make validate-auth-config make verify-auth-config ``` -- 생성 파일: `.generated/auth-config.env` (compose 실행 시 자동 주입) +- 생성 파일: `config/.generated/auth-config.env` (compose 실행 시 자동 주입) - 게이트웨이 경유 환경은 URL 문자열 완전일치 대신 매핑 유효성(`direct_match` / `mapped_match`) 기준으로 검증합니다. - 관련 정책 문서: `docs/oidc_redirect_mapping_validation_policy.md` @@ -493,8 +493,8 @@ make up-app 직접 Compose를 사용하려면 다음처럼 env 파일을 함께 주입하세요. ```bash -docker compose --env-file .env --env-file .generated/auth-config.env -f compose.infra.yaml -f compose.ory.yaml up -d -docker compose --env-file .env --env-file .generated/auth-config.env -f docker-compose.yaml up -d +docker compose --env-file .env --env-file config/.generated/auth-config.env -f compose.infra.yaml -f compose.ory.yaml up -d +docker compose --env-file .env --env-file config/.generated/auth-config.env -f docker-compose.yaml up -d ``` - **gateway (UserFront 프록시)**: http://localhost:5000 접속 diff --git a/README_en.md b/README_en.md index 5ba1130b..79fba211 100644 --- a/README_en.md +++ b/README_en.md @@ -14,7 +14,7 @@ It leverages **Descope** for secure, passwordless authentication (Enchanted Link - Descope SDK Integration (Enchanted Link, Magic Link) ### 2. Backend (Go Fiber) -- **Language**: Go 1.25+ +- **Language**: Go 1.26.2+ - **Framework**: Fiber v2.25+ - **Database**: - **ClickHouse**: Audit Logs (High performance ingestion) diff --git a/backend/Dockerfile b/backend/Dockerfile index 3c2fbf7e..e72ea159 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.25-alpine +FROM golang:1.26.2-alpine WORKDIR /app diff --git a/backend/go.mod b/backend/go.mod index fe7a265b..cb9b8736 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -1,6 +1,6 @@ module baron-sso-backend -go 1.25.4 +go 1.26.2 require ( github.com/ClickHouse/clickhouse-go/v2 v2.42.0 diff --git a/compose.ory.yaml b/compose.ory.yaml index 99213589..2672ddde 100644 --- a/compose.ory.yaml +++ b/compose.ory.yaml @@ -38,7 +38,7 @@ services: - KRATOS_SELFSERVICE_FLOWS_REGISTRATION_UI_URL=${KRATOS_UI_URL}/registration - KRATOS_SELFSERVICE_FLOWS_LOGOUT_AFTER_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL}/login volumes: - - ./docker/ory/kratos:/etc/config/kratos + - ./config/.generated/ory/kratos:/etc/config/kratos command: migrate sql up -e -c /etc/config/kratos/kratos.yml --yes depends_on: postgres_ory: @@ -64,7 +64,7 @@ services: - KRATOS_SELFSERVICE_FLOWS_REGISTRATION_UI_URL=${KRATOS_UI_URL}/registration - KRATOS_SELFSERVICE_FLOWS_LOGOUT_AFTER_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL}/login volumes: - - ./docker/ory/kratos:/etc/config/kratos + - ./config/.generated/ory/kratos:/etc/config/kratos command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier depends_on: kratos-migrate: @@ -96,7 +96,7 @@ services: - URLS_ERROR=${HYDRA_ERROR_URL:-${USERFRONT_URL}/error} - SECRETS_SYSTEM=${ORY_POSTGRES_PASSWORD} volumes: - - ./docker/ory/hydra:/etc/config/hydra + - ./config/.generated/ory/hydra:/etc/config/hydra command: serve -c /etc/config/hydra/hydra.yml all --dev depends_on: hydra-migrate: @@ -111,7 +111,7 @@ services: environment: - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20 volumes: - - ./docker/ory/keto:/etc/config/keto + - ./config/.generated/ory/keto:/etc/config/keto command: ["migrate", "up", "-c", "/etc/config/keto/keto.yml", "--yes"] depends_on: postgres_ory: @@ -125,7 +125,7 @@ services: environment: - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20 volumes: - - ./docker/ory/keto:/etc/config/keto + - ./config/.generated/ory/keto:/etc/config/keto command: serve -c /etc/config/keto/keto.yml depends_on: keto-migrate: @@ -159,7 +159,7 @@ services: - 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 + - ./config/.generated/ory/oathkeeper:/etc/config/oathkeeper - oathkeeper_logs:/var/log/oathkeeper entrypoint: ["/etc/config/oathkeeper/entrypoint.sh"] depends_on: @@ -205,9 +205,27 @@ services: /bin/sh -c " apk add --no-cache curl; echo 'Wait for 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; + check_ready() { + name=\"$$1\"; + url=\"$$2\"; + max=\"$${ORY_STACK_CHECK_MAX_ATTEMPTS:-60}\"; + i=1; + while [ \"$$i\" -le \"$$max\" ]; do + if curl --connect-timeout 2 --max-time 3 -fsS \"$$url\" >/dev/null; then + echo \"Ory service ready: $$name\"; + return 0; + fi; + echo \"Waiting for Ory service: $$name ($$i/$$max)\"; + i=$$((i + 1)); + sleep 1; + done; + echo \"ERROR: Ory service not ready: $$name after $$max attempts ($$url)\" >&2; + echo \"ERROR: Check service logs: docker logs ory_$$name\" >&2; + return 1; + }; + check_ready kratos http://kratos:4433/health/ready || exit 1; + check_ready hydra http://hydra:4444/health/ready || exit 1; + check_ready keto http://keto:4466/health/ready || exit 1; echo 'Ory Stack is fully operational!';" depends_on: - kratos diff --git a/deploy/create-instance.sh b/deploy/create-instance.sh index ecbe46e8..e386e774 100644 --- a/deploy/create-instance.sh +++ b/deploy/create-instance.sh @@ -18,11 +18,12 @@ echo "🚀 Creating instance: ${INSTANCE_NAME} (Port Prefix: ${PORT_PREFIX}xxx)" # 1. 폴더 구조 생성 mkdir -p "${TARGET_DIR}/gateway" +mkdir -p "${TARGET_DIR}/config/.generated" mkdir -p "${TARGET_DIR}/ory/init-db" -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/templates/kratos" +mkdir -p "${TARGET_DIR}/ory/templates/hydra" +mkdir -p "${TARGET_DIR}/ory/templates/keto" +mkdir -p "${TARGET_DIR}/ory/templates/oathkeeper" mkdir -p "${TARGET_DIR}/userfront" mkdir -p "${TARGET_DIR}/adminfront" mkdir -p "${TARGET_DIR}/devfront" @@ -47,13 +48,15 @@ cp "${BASE_DIR}/templates/docker-compose.yaml" "${TARGET_DIR}/" sed "s/{{BACKEND_PORT}}/${BACKEND_PORT}/g" "${BASE_DIR}/templates/gateway/nginx.conf" > "${TARGET_DIR}/gateway/nginx.conf" sed "s/{{BACKEND_PORT}}/${BACKEND_PORT}/g" "${BASE_DIR}/templates/userfront/nginx.conf" > "${TARGET_DIR}/userfront/nginx.conf" -# Oathkeeper Rules -sed "s/{{BACKEND_PORT}}/${BACKEND_PORT}/g" "${BASE_DIR}/templates/ory/oathkeeper/rules.json" > "${TARGET_DIR}/ory/oathkeeper/rules.json" -cp "${TARGET_DIR}/ory/oathkeeper/rules.json" "${TARGET_DIR}/ory/oathkeeper/rules.active.json" +# Oathkeeper Rules template +sed "s/{{BACKEND_PORT}}/${BACKEND_PORT}/g" "${BASE_DIR}/templates/ory/oathkeeper/rules.json" > "${TARGET_DIR}/ory/templates/oathkeeper/rules.json" +cp "${TARGET_DIR}/ory/templates/oathkeeper/rules.json" "${TARGET_DIR}/ory/templates/oathkeeper/rules.stage.json" +cp "${TARGET_DIR}/ory/templates/oathkeeper/rules.json" "${TARGET_DIR}/ory/templates/oathkeeper/rules.prod.json" +cp "${TARGET_DIR}/ory/templates/oathkeeper/rules.json" "${TARGET_DIR}/ory/templates/oathkeeper/rules.active.json" -# Kratos Config +# Kratos Config template sed "s/{{BACKEND_PORT}}/${BACKEND_PORT}/g; s/{{USERFRONT_PORT}}/${USERFRONT_PORT}/g" \ - "${BASE_DIR}/templates/ory/kratos/kratos.yml" > "${TARGET_DIR}/ory/kratos/kratos.yml" + "${BASE_DIR}/templates/ory/kratos/kratos.yml.template" > "${TARGET_DIR}/ory/templates/kratos/kratos.yml.template" # Vite Configs sed "s/{{ADMINFRONT_DOMAIN}}/${ADMINFRONT_DOMAIN}/g; s/{{BACKEND_PORT}}/${BACKEND_PORT}/g" \ @@ -71,12 +74,18 @@ sed "s/{{USERFRONT_PORT}}/${USERFRONT_PORT}/g; s/{{CLIENT_ID}}/devfront/g" \ sed "s/{{USERFRONT_PORT}}/${USERFRONT_PORT}/g" \ "${BASE_DIR}/templates/orgfront/auth.ts" > "${TARGET_DIR}/orgfront/auth.ts" -# 5. Ory 정적 설정 복사 +# 5. Ory template 복사 및 완성 config 렌더링 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/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/kratos" ]; then cp -n "${BASE_DIR}/../docker/ory/kratos/"* "${TARGET_DIR}/ory/templates/kratos/" 2>/dev/null || true; fi +if [ -d "${BASE_DIR}/../docker/ory/kratos/courier-templates" ]; then cp -a "${BASE_DIR}/../docker/ory/kratos/courier-templates" "${TARGET_DIR}/ory/templates/kratos/" 2>/dev/null || true; fi +if [ -d "${BASE_DIR}/../docker/ory/hydra" ]; then cp -n "${BASE_DIR}/../docker/ory/hydra/"* "${TARGET_DIR}/ory/templates/hydra/" 2>/dev/null || true; fi +if [ -d "${BASE_DIR}/../docker/ory/keto" ]; then cp -n "${BASE_DIR}/../docker/ory/keto/"* "${TARGET_DIR}/ory/templates/keto/" 2>/dev/null || true; fi +if [ -d "${BASE_DIR}/../docker/ory/oathkeeper" ]; then cp -n "${BASE_DIR}/../docker/ory/oathkeeper/"* "${TARGET_DIR}/ory/templates/oathkeeper/" 2>/dev/null || true; fi + +ORY_CONFIG_ENV_FILES="${TARGET_DIR}/.env" \ +ORY_CONFIG_TEMPLATE_ROOT="${TARGET_DIR}/ory/templates" \ +ORY_CONFIG_OUTPUT_DIR="${TARGET_DIR}/config/.generated/ory" \ + bash "${BASE_DIR}/../scripts/render_ory_config.sh" # 6. 마무리 chmod +x "${TARGET_DIR}/.env" diff --git a/deploy/templates/docker-compose.yaml b/deploy/templates/docker-compose.yaml index 5544039f..4c0e230d 100644 --- a/deploy/templates/docker-compose.yaml +++ b/deploy/templates/docker-compose.yaml @@ -69,7 +69,7 @@ services: - 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 + - ./config/.generated/ory/kratos:/etc/config/kratos:ro command: migrate sql up -e -c /etc/config/kratos/kratos.yml --yes networks: [app_net] depends_on: @@ -94,7 +94,7 @@ services: - 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 + - ./config/.generated/ory/kratos:/etc/config/kratos:ro command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier networks: [app_net] depends_on: @@ -122,7 +122,7 @@ services: - URLS_ERROR=${HYDRA_ERROR_URL:-${USERFRONT_URL}/error} - SECRETS_SYSTEM=${ORY_POSTGRES_PASSWORD} volumes: - - ./ory/hydra:/etc/config/hydra:ro + - ./config/.generated/ory/hydra:/etc/config/hydra:ro command: serve -c /etc/config/hydra/hydra.yml all --dev networks: [app_net] depends_on: @@ -134,7 +134,7 @@ services: 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 + - ./config/.generated/ory/keto:/etc/config/keto:ro command: ["migrate", "up", "-c", "/etc/config/keto/keto.yml", "--yes"] networks: [app_net] depends_on: @@ -147,7 +147,7 @@ services: 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 + - ./config/.generated/ory/keto:/etc/config/keto:ro command: serve -c /etc/config/keto/keto.yml networks: [app_net] depends_on: @@ -173,7 +173,7 @@ services: - OATHKEEPER_INTROSPECT_CLIENT_ID=${OATHKEEPER_INTROSPECT_CLIENT_ID:-oathkeeper-introspect} - OATHKEEPER_INTROSPECT_CLIENT_SECRET=${OATHKEEPER_INTROSPECT_CLIENT_SECRET:-oathkeeper-secret} volumes: - - ./ory/oathkeeper:/etc/config/oathkeeper:ro + - ./config/.generated/ory/oathkeeper:/etc/config/oathkeeper:ro - oathkeeper_logs:/var/log/oathkeeper entrypoint: ["/etc/config/oathkeeper/entrypoint.sh"] networks: [app_net] @@ -189,9 +189,27 @@ services: /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; + check_ready() { + name=\"$$1\"; + url=\"$$2\"; + max=\"$${ORY_STACK_CHECK_MAX_ATTEMPTS:-60}\"; + i=1; + while [ \"$$i\" -le \"$$max\" ]; do + if curl --connect-timeout 2 --max-time 3 -fsS \"$$url\" >/dev/null; then + echo \"Ory service ready: $$name\"; + return 0; + fi; + echo \"Waiting for Ory service: $$name ($$i/$$max)\"; + i=$$((i + 1)); + sleep 1; + done; + echo \"ERROR: Ory service not ready: $$name after $$max attempts ($$url)\" >&2; + echo \"ERROR: Check service logs: docker logs $${COMPOSE_PROJECT_NAME}_$$name\" >&2; + return 1; + }; + check_ready kratos http://kratos:4433/health/ready || exit 1; + check_ready hydra http://hydra:4444/health/ready || exit 1; + check_ready keto http://keto:4466/health/ready || exit 1; echo 'Ory stack is ready.';" depends_on: - kratos diff --git a/deploy/templates/ory/kratos/kratos.yml b/deploy/templates/ory/kratos/kratos.yml.template similarity index 58% rename from deploy/templates/ory/kratos/kratos.yml rename to deploy/templates/ory/kratos/kratos.yml.template index 45fa5952..de5b981b 100644 --- a/deploy/templates/ory/kratos/kratos.yml +++ b/deploy/templates/ory/kratos/kratos.yml.template @@ -1,20 +1,17 @@ version: v26.2.0 -dsn: ${DSN} +dsn: ${KRATOS_DSN} serve: public: - base_url: ${KRATOS_BROWSER_URL} + base_url: http://localhost:4433/ cors: enabled: true allowed_origins: - http://backend:{{BACKEND_PORT}} - - ${USERFRONT_URL} - - ${ADMINFRONT_URL} - - ${DEVFRONT_URL} - - ${ORGFRONT_URL} + - http://localhost:{{USERFRONT_PORT}} admin: - base_url: ${KRATOS_ADMIN_URL} + base_url: http://localhost:4434/ session: cookie: @@ -23,22 +20,17 @@ session: path: / selfservice: - default_browser_return_url: ${KRATOS_UI_URL} + default_browser_return_url: http://localhost:{{USERFRONT_PORT}}/ allowed_return_urls: - - ${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 + - http://localhost:{{USERFRONT_PORT}} + - http://localhost:{{USERFRONT_PORT}}/ + - http://localhost:{{USERFRONT_PORT}}/ko + - http://localhost:{{USERFRONT_PORT}}/ko/ + - http://localhost:{{USERFRONT_PORT}}/en + - http://localhost:{{USERFRONT_PORT}}/en/ + - http://localhost:{{USERFRONT_PORT}}/auth/callback + - http://localhost:{{USERFRONT_PORT}}/ko/auth/callback + - http://localhost:{{USERFRONT_PORT}}/en/auth/callback methods: password: @@ -51,24 +43,24 @@ selfservice: flows: error: - ui_url: ${KRATOS_UI_URL}/error + ui_url: http://localhost:{{USERFRONT_PORT}}/error settings: - ui_url: ${KRATOS_UI_URL}/error?error=settings_disabled + ui_url: http://localhost:{{USERFRONT_PORT}}/error?error=settings_disabled privileged_session_max_age: 15m recovery: - ui_url: ${KRATOS_UI_URL}/recovery + ui_url: http://localhost:{{USERFRONT_PORT}}/recovery use: code verification: - ui_url: ${KRATOS_UI_URL}/verification + ui_url: http://localhost:{{USERFRONT_PORT}}/verification use: code logout: after: - default_browser_return_url: ${KRATOS_UI_URL}/login + default_browser_return_url: http://localhost:{{USERFRONT_PORT}}/login login: - ui_url: ${KRATOS_UI_URL}/login + ui_url: http://localhost:{{USERFRONT_PORT}}/login lifespan: 10m registration: - ui_url: ${KRATOS_UI_URL}/registration + ui_url: http://localhost:{{USERFRONT_PORT}}/registration lifespan: 10m log: diff --git a/docker/compose.ory.yaml b/docker/compose.ory.yaml index 02a455e9..2494ed35 100644 --- a/docker/compose.ory.yaml +++ b/docker/compose.ory.yaml @@ -30,7 +30,7 @@ services: - KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL} - KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS=${KRATOS_ALLOWED_RETURN_URLS_JSON:-["${KRATOS_UI_URL}","${USERFRONT_URL}"]} volumes: - - ./docker/ory/kratos:/etc/config/kratos + - ../config/.generated/ory/kratos:/etc/config/kratos command: migrate sql up -e -c /etc/config/kratos/kratos.yml --yes depends_on: postgres_ory: @@ -49,7 +49,7 @@ services: - KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL} - KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS=${KRATOS_ALLOWED_RETURN_URLS_JSON:-["${KRATOS_UI_URL}","${USERFRONT_URL}"]} volumes: - - ./docker/ory/kratos:/etc/config/kratos + - ../config/.generated/ory/kratos:/etc/config/kratos command: serve -c /etc/config/kratos/kratos.yml depends_on: kratos-migrate: @@ -80,7 +80,7 @@ services: - URLS_ERROR=${HYDRA_ERROR_URL:-${USERFRONT_URL}/error} - SECRETS_SYSTEM=${ORY_POSTGRES_PASSWORD} volumes: - - ./docker/ory/hydra:/etc/config/hydra + - ../config/.generated/ory/hydra:/etc/config/hydra command: serve -c /etc/config/hydra/hydra.yml all --dev depends_on: hydra-migrate: @@ -94,7 +94,7 @@ services: environment: - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20 volumes: - - ./docker/ory/keto:/etc/config/keto + - ../config/.generated/ory/keto:/etc/config/keto command: ["migrate", "up", "-c", "/etc/config/keto/keto.yml", "--yes"] depends_on: postgres_ory: @@ -108,7 +108,7 @@ services: environment: - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20 volumes: - - ./docker/ory/keto:/etc/config/keto + - ../config/.generated/ory/keto:/etc/config/keto command: serve -c /etc/config/keto/keto.yml depends_on: keto-migrate: @@ -129,7 +129,7 @@ services: - 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 + - ../config/.generated/ory/oathkeeper:/etc/config/oathkeeper - oathkeeper_logs:/var/log/oathkeeper entrypoint: ["/etc/config/oathkeeper/entrypoint.sh"] networks: @@ -152,9 +152,27 @@ services: /bin/sh -c " apk add --no-cache curl; echo 'Wait for 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; + check_ready() { + name=\"$$1\"; + url=\"$$2\"; + max=\"$${ORY_STACK_CHECK_MAX_ATTEMPTS:-60}\"; + i=1; + while [ \"$$i\" -le \"$$max\" ]; do + if curl --connect-timeout 2 --max-time 3 -fsS \"$$url\" >/dev/null; then + echo \"Ory service ready: $$name\"; + return 0; + fi; + echo \"Waiting for Ory service: $$name ($$i/$$max)\"; + i=$$((i + 1)); + sleep 1; + done; + echo \"ERROR: Ory service not ready: $$name after $$max attempts ($$url)\" >&2; + echo \"ERROR: Check service logs: docker logs ory_$$name\" >&2; + return 1; + }; + check_ready kratos http://kratos:4433/health/ready || exit 1; + check_ready hydra http://hydra:4444/health/ready || exit 1; + check_ready keto http://keto:4466/health/ready || exit 1; echo 'Ory Stack is fully operational!';" depends_on: - kratos diff --git a/docker/ory/hydra/hydra.yml b/docker/ory/hydra/hydra.yml.template similarity index 97% rename from docker/ory/hydra/hydra.yml rename to docker/ory/hydra/hydra.yml.template index 7bf79b10..811eec27 100644 --- a/docker/ory/hydra/hydra.yml +++ b/docker/ory/hydra/hydra.yml.template @@ -1,4 +1,4 @@ -dsn: ${DSN} +dsn: ${HYDRA_DSN} serve: cookies: @@ -77,7 +77,7 @@ urls: secrets: system: - - ${SECRETS_SYSTEM} + - ${HYDRA_SYSTEM_SECRET} webfinger: oidc_discovery: diff --git a/docker/ory/keto/keto.yml b/docker/ory/keto/keto.yml.template similarity index 91% rename from docker/ory/keto/keto.yml rename to docker/ory/keto/keto.yml.template index 3ec2be81..44fdb408 100644 --- a/docker/ory/keto/keto.yml +++ b/docker/ory/keto/keto.yml.template @@ -1,5 +1,5 @@ version: v0.11.0 -dsn: ${DSN} +dsn: ${KETO_DSN} serve: read: host: 0.0.0.0 diff --git a/docker/ory/kratos/kratos.yml b/docker/ory/kratos/kratos.yml.template similarity index 58% rename from docker/ory/kratos/kratos.yml rename to docker/ory/kratos/kratos.yml.template index d1bd22cd..ad713e9a 100644 --- a/docker/ory/kratos/kratos.yml +++ b/docker/ory/kratos/kratos.yml.template @@ -1,21 +1,21 @@ version: v26.2.0 -dsn: ${DSN} +dsn: ${KRATOS_DSN} serve: public: - base_url: ${KRATOS_BROWSER_URL} + base_url: http://localhost:4433/ cors: enabled: true allowed_origins: - - ${USERFRONT_URL} - - ${ADMINFRONT_URL} - - ${DEVFRONT_URL} - - ${ORGFRONT_URL} + - http://localhost:5000 + - http://localhost:5173 + - http://localhost:5174 + - http://localhost:5175 - http://backend:3000 - http://baron_backend:3000 admin: - base_url: ${KRATOS_ADMIN_URL} + base_url: http://localhost:4434/ session: cookie: @@ -24,22 +24,20 @@ session: path: / selfservice: - default_browser_return_url: ${KRATOS_UI_URL} + default_browser_return_url: http://localhost:5000/ allowed_return_urls: - - ${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 + - http://localhost:5000 + - http://localhost:5000/ + - http://localhost:5000/ko + - http://localhost:5000/ko/ + - http://localhost:5000/en + - http://localhost:5000/en/ + - http://localhost:5000/auth/callback + - http://localhost:5000/ko/auth/callback + - http://localhost:5000/en/auth/callback + - http://localhost:5173/auth/callback + - http://localhost:5174/auth/callback + - http://localhost:5175/auth/callback methods: password: @@ -52,24 +50,24 @@ selfservice: flows: error: - ui_url: ${KRATOS_UI_URL}/error + ui_url: http://localhost:5000/error settings: - ui_url: ${KRATOS_UI_URL}/error?error=settings_disabled + ui_url: http://localhost:5000/error?error=settings_disabled privileged_session_max_age: 15m recovery: - ui_url: ${KRATOS_UI_URL}/recovery + ui_url: http://localhost:5000/recovery use: code verification: - ui_url: ${KRATOS_UI_URL}/verification + ui_url: http://localhost:5000/verification use: code logout: after: - default_browser_return_url: ${KRATOS_UI_URL}/login + default_browser_return_url: http://localhost:5000/login login: - ui_url: ${KRATOS_UI_URL}/login + ui_url: http://localhost:5000/login lifespan: 10m registration: - ui_url: ${KRATOS_UI_URL}/registration + ui_url: http://localhost:5000/registration lifespan: 10m log: diff --git a/docker/ory/oathkeeper/oathkeeper.yml b/docker/ory/oathkeeper/oathkeeper.yml.template similarity index 100% rename from docker/ory/oathkeeper/oathkeeper.yml rename to docker/ory/oathkeeper/oathkeeper.yml.template diff --git a/docker/staging_pull_compose.template.yaml b/docker/staging_pull_compose.template.yaml index d4a91f9c..2ed6b07c 100644 --- a/docker/staging_pull_compose.template.yaml +++ b/docker/staging_pull_compose.template.yaml @@ -114,7 +114,7 @@ services: - KRATOS_SELFSERVICE_FLOWS_REGISTRATION_UI_URL=${KRATOS_UI_URL}/registration - KRATOS_SELFSERVICE_FLOWS_LOGOUT_AFTER_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL}/login volumes: - - ./docker/ory/kratos:/etc/config/kratos + - ./config/.generated/ory/kratos:/etc/config/kratos command: migrate sql up -e -c /etc/config/kratos/kratos.yml --yes depends_on: postgres_ory: @@ -140,7 +140,7 @@ services: - KRATOS_SELFSERVICE_FLOWS_REGISTRATION_UI_URL=${KRATOS_UI_URL}/registration - KRATOS_SELFSERVICE_FLOWS_LOGOUT_AFTER_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL}/login volumes: - - ./docker/ory/kratos:/etc/config/kratos + - ./config/.generated/ory/kratos:/etc/config/kratos command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier depends_on: kratos-migrate: @@ -171,7 +171,7 @@ services: - URLS_ERROR=${HYDRA_ERROR_URL:-${USERFRONT_URL}/error} - SECRETS_SYSTEM=${ORY_POSTGRES_PASSWORD} volumes: - - ./docker/ory/hydra:/etc/config/hydra + - ./config/.generated/ory/hydra:/etc/config/hydra command: serve -c /etc/config/hydra/hydra.yml all --dev depends_on: hydra-migrate: @@ -185,7 +185,7 @@ services: environment: - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20 volumes: - - ./docker/ory/keto:/etc/config/keto + - ./config/.generated/ory/keto:/etc/config/keto command: ["migrate", "up", "-c", "/etc/config/keto/keto.yml", "--yes"] depends_on: postgres_ory: @@ -199,7 +199,7 @@ services: environment: - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20 volumes: - - ./docker/ory/keto:/etc/config/keto + - ./config/.generated/ory/keto:/etc/config/keto command: serve -c /etc/config/keto/keto.yml depends_on: keto-migrate: @@ -236,7 +236,7 @@ services: - 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 + - ./config/.generated/ory/oathkeeper:/etc/config/oathkeeper - oathkeeper_logs:/var/log/oathkeeper entrypoint: ["/etc/config/oathkeeper/entrypoint.sh"] networks: @@ -271,9 +271,27 @@ services: /bin/sh -c " apk add --no-cache curl; echo 'Wait for 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; + check_ready() { + name=\"$$1\"; + url=\"$$2\"; + max=\"$${ORY_STACK_CHECK_MAX_ATTEMPTS:-60}\"; + i=1; + while [ \"$$i\" -le \"$$max\" ]; do + if curl --connect-timeout 2 --max-time 3 -fsS \"$$url\" >/dev/null; then + echo \"Ory service ready: $$name\"; + return 0; + fi; + echo \"Waiting for Ory service: $$name ($$i/$$max)\"; + i=$$((i + 1)); + sleep 1; + done; + echo \"ERROR: Ory service not ready: $$name after $$max attempts ($$url)\" >&2; + echo \"ERROR: Check service logs: docker logs ory_$$name\" >&2; + return 1; + }; + check_ready kratos http://kratos:4433/health/ready || exit 1; + check_ready hydra http://hydra:4444/health/ready || exit 1; + check_ready keto http://keto:4466/health/ready || exit 1; echo 'Ory Stack is fully operational!';" depends_on: - kratos diff --git a/docs/TEST_GUIDE.md b/docs/TEST_GUIDE.md index c18e57d0..f9af9c0e 100644 --- a/docs/TEST_GUIDE.md +++ b/docs/TEST_GUIDE.md @@ -5,7 +5,7 @@ ## 1. 준비 사항 테스트를 실행하기 위해 다음 도구들이 설치되어 있어야 합니다. - **Docker & Docker Compose** (백엔드 인프라 의존성용) -- **Go 1.25+** +- **Go 1.26.2+** - **Flutter SDK** - **Node.js 24 LTS+** diff --git a/docs/개발완료보고서.md b/docs/개발완료보고서.md index 5ec115a7..09087f4d 100644 --- a/docs/개발완료보고서.md +++ b/docs/개발완료보고서.md @@ -88,7 +88,7 @@ flowchart ``` ### 1. Backend (Go Fiber) -- **Language**: Go 1.25+ +- **Language**: Go 1.26.2+ - **Framework**: Fiber v2.25+ - **Database**: - **ClickHouse**: 감사 로그 (고성능 데이터 수집) diff --git a/scripts/auth_config.sh b/scripts/auth_config.sh index 544638ac..dfe3f60e 100755 --- a/scripts/auth_config.sh +++ b/scripts/auth_config.sh @@ -2,7 +2,7 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -OUTPUT_DIR="$ROOT_DIR/.generated" +OUTPUT_DIR="$ROOT_DIR/config/.generated" OUTPUT_FILE="$OUTPUT_DIR/auth-config.env" MODE="${1:-build}" diff --git a/scripts/render_ory_config.sh b/scripts/render_ory_config.sh new file mode 100755 index 00000000..d2487101 --- /dev/null +++ b/scripts/render_ory_config.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +OUTPUT_DIR="${ORY_CONFIG_OUTPUT_DIR:-$ROOT_DIR/config/.generated/ory}" +TEMPLATE_ROOT="${ORY_CONFIG_TEMPLATE_ROOT:-$ROOT_DIR/docker/ory}" + +load_env_file() { + local env_file="$1" + if [[ -f "$env_file" ]]; then + set -a + # shellcheck disable=SC1090 + source "$env_file" + set +a + fi +} + +fail() { + echo "[ory-config] ERROR: $1" >&2 + exit 1 +} + +render_template() { + local src="$1" + local dst="$2" + mkdir -p "$(dirname "$dst")" + perl -pe ' + s/\$\{([A-Za-z_][A-Za-z0-9_]*)(:-([^}]*))?\}/ + exists $ENV{$1} ? $ENV{$1} : defined $3 ? $3 : die "missing env var: $1\n" + /gex + ' "$src" > "$dst" +} + +copy_if_exists() { + local src="$1" + local dst="$2" + if [[ -e "$src" ]]; then + mkdir -p "$(dirname "$dst")" + cp -a "$src" "$dst" + fi +} + +if [[ -n "${ORY_CONFIG_ENV_FILES:-}" ]]; then + IFS=':' read -r -a env_files <<<"$ORY_CONFIG_ENV_FILES" + for env_file in "${env_files[@]}"; do + load_env_file "$env_file" + done +else + load_env_file "$ROOT_DIR/.env" + load_env_file "$ROOT_DIR/config/.generated/auth-config.env" +fi + +ORY_POSTGRES_USER="${ORY_POSTGRES_USER:-ory}" +ORY_POSTGRES_PASSWORD="${ORY_POSTGRES_PASSWORD:-secret}" +KRATOS_DB="${KRATOS_DB:-ory_kratos}" +HYDRA_DB="${HYDRA_DB:-ory_hydra}" +KETO_DB="${KETO_DB:-ory_keto}" +KRATOS_DSN="${KRATOS_DSN:-postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KRATOS_DB}?sslmode=disable&max_conns=20}" +HYDRA_DSN="${HYDRA_DSN:-postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${HYDRA_DB}?sslmode=disable&max_conns=20}" +KETO_DSN="${KETO_DSN:-postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB}?sslmode=disable&max_conns=20}" +HYDRA_SYSTEM_SECRET="${HYDRA_SYSTEM_SECRET:-${SECRETS_SYSTEM:-${ORY_POSTGRES_PASSWORD}}}" +OATHKEEPER_INTROSPECT_CLIENT_ID="${OATHKEEPER_INTROSPECT_CLIENT_ID:-oathkeeper-introspect}" +OATHKEEPER_INTROSPECT_CLIENT_SECRET="${OATHKEEPER_INTROSPECT_CLIENT_SECRET:-oathkeeper-secret}" + +export KRATOS_DSN HYDRA_DSN KETO_DSN HYDRA_SYSTEM_SECRET +export OATHKEEPER_INTROSPECT_CLIENT_ID OATHKEEPER_INTROSPECT_CLIENT_SECRET + +rm -rf "$OUTPUT_DIR" +mkdir -p "$OUTPUT_DIR" + +render_template "$TEMPLATE_ROOT/kratos/kratos.yml.template" "$OUTPUT_DIR/kratos/kratos.yml" +copy_if_exists "$TEMPLATE_ROOT/kratos/identity.schema.json" "$OUTPUT_DIR/kratos/identity.schema.json" +copy_if_exists "$TEMPLATE_ROOT/kratos/courier-http.jsonnet" "$OUTPUT_DIR/kratos/courier-http.jsonnet" +if [[ -d "$TEMPLATE_ROOT/kratos/courier-templates" ]]; then + mkdir -p "$OUTPUT_DIR/kratos" + cp -a "$TEMPLATE_ROOT/kratos/courier-templates" "$OUTPUT_DIR/kratos/courier-templates" +fi + +render_template "$TEMPLATE_ROOT/hydra/hydra.yml.template" "$OUTPUT_DIR/hydra/hydra.yml" + +render_template "$TEMPLATE_ROOT/keto/keto.yml.template" "$OUTPUT_DIR/keto/keto.yml" +copy_if_exists "$TEMPLATE_ROOT/keto/namespaces.ts" "$OUTPUT_DIR/keto/namespaces.ts" +copy_if_exists "$TEMPLATE_ROOT/keto/namespaces.yml" "$OUTPUT_DIR/keto/namespaces.yml" + +render_template "$TEMPLATE_ROOT/oathkeeper/oathkeeper.yml.template" "$OUTPUT_DIR/oathkeeper/oathkeeper.yml" +copy_if_exists "$TEMPLATE_ROOT/oathkeeper/entrypoint.sh" "$OUTPUT_DIR/oathkeeper/entrypoint.sh" +chmod +x "$OUTPUT_DIR/oathkeeper/entrypoint.sh" +for rules_file in "$TEMPLATE_ROOT"/oathkeeper/rules*.json; do + [[ -e "$rules_file" ]] || continue + copy_if_exists "$rules_file" "$OUTPUT_DIR/oathkeeper/$(basename "$rules_file")" +done + +if find "$OUTPUT_DIR" -type f \( -name '*.yml' -o -name '*.yaml' -o -name '*.json' -o -name '*.toml' \) -print0 | xargs -0 grep -n '\${' >/tmp/ory-render-unresolved.$$ 2>/dev/null; then + cat /tmp/ory-render-unresolved.$$ >&2 + rm -f /tmp/ory-render-unresolved.$$ + fail "rendered Ory config contains unresolved placeholders" +fi +rm -f /tmp/ory-render-unresolved.$$ + +echo "[ory-config] wrote: $OUTPUT_DIR" diff --git a/test/backend_go_version_policy_test.sh b/test/backend_go_version_policy_test.sh new file mode 100644 index 00000000..64072168 --- /dev/null +++ b/test/backend_go_version_policy_test.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +TARGET_GO_VERSION="1.26.2" + +GO_MOD="$ROOT_DIR/backend/go.mod" +BACKEND_DOCKERFILE="$ROOT_DIR/backend/Dockerfile" +LOCAL_COMPOSE="$ROOT_DIR/docker-compose.yaml" +STAGING_COMPOSE="$ROOT_DIR/docker/docker-compose.staging.template.yaml" +PULL_COMPOSE="$ROOT_DIR/docker/staging_pull_compose.template.yaml" +DEPLOY_TEMPLATE="$ROOT_DIR/deploy/templates/docker-compose.yaml" +README="$ROOT_DIR/README.md" +README_EN="$ROOT_DIR/README_en.md" +TEST_GUIDE="$ROOT_DIR/docs/TEST_GUIDE.md" +COMPLETION_REPORT="$ROOT_DIR/docs/개발완료보고서.md" + +for file in \ + "$GO_MOD" \ + "$BACKEND_DOCKERFILE" \ + "$LOCAL_COMPOSE" \ + "$STAGING_COMPOSE" \ + "$PULL_COMPOSE" \ + "$DEPLOY_TEMPLATE" \ + "$README" \ + "$README_EN" \ + "$TEST_GUIDE" \ + "$COMPLETION_REPORT" +do + if [[ ! -f "$file" ]]; then + echo "ERROR: expected file not found: $file" >&2 + exit 1 + fi +done + +if ! grep -Eq "^go ${TARGET_GO_VERSION}$" "$GO_MOD"; then + echo "ERROR: backend go.mod must use go ${TARGET_GO_VERSION}." >&2 + exit 1 +fi + +if ! grep -Eq "^FROM golang:${TARGET_GO_VERSION}-alpine$" "$BACKEND_DOCKERFILE"; then + echo "ERROR: backend Dockerfile must use golang:${TARGET_GO_VERSION}-alpine." >&2 + exit 1 +fi + +for file in "$LOCAL_COMPOSE" "$PULL_COMPOSE"; do + if ! grep -Fq "context: ./backend" "$file" && ! grep -Fq "context: ../../backend" "$file"; then + echo "ERROR: backend compose build context is missing in $file." >&2 + exit 1 + fi +done + +for file in "$STAGING_COMPOSE" "$DEPLOY_TEMPLATE"; do + if ! grep -Eq "^[[:space:]]+backend:$" "$file"; then + echo "ERROR: backend service is missing in $file." >&2 + exit 1 + fi +done + +legacy_refs="$( + grep -R -nE "golang:1\\.25|^go 1\\.25" \ + "$ROOT_DIR/backend" \ + "$ROOT_DIR/docker-compose.yaml" \ + "$ROOT_DIR/docker" \ + "$ROOT_DIR/deploy/templates" \ + "$README" \ + "$README_EN" \ + "$TEST_GUIDE" \ + "$COMPLETION_REPORT" || true +)" + +if [[ -n "$legacy_refs" ]]; then + echo "ERROR: legacy backend Go version references remain." >&2 + echo "$legacy_refs" >&2 + exit 1 +fi + +echo "OK: backend Go base version policy is ${TARGET_GO_VERSION}" diff --git a/test/make_dev_targets_test.sh b/test/make_dev_targets_test.sh index 367574ef..4d9453f8 100644 --- a/test/make_dev_targets_test.sh +++ b/test/make_dev_targets_test.sh @@ -30,6 +30,11 @@ if ! grep -q "Ensuring Ory stack" <<<"$dry_run_dev"; then exit 1 fi +if ! grep -q "Rendering Ory config" <<<"$dry_run_dev"; then + echo "make dev must render Ory config before starting services." >&2 + exit 1 +fi + app_up_line="$( grep -E "docker compose .* -f docker-compose.yaml up .*backend.*adminfront" <<<"$dry_run_dev" | tail -1 )" @@ -44,6 +49,11 @@ if grep -q -- " -d" <<<"$app_up_line"; then exit 1 fi +if ! grep -q -- " --build" <<<"$app_up_line"; then + echo "make dev must rebuild app service images before starting development containers." >&2 + exit 1 +fi + dry_run_up_dev="$( make --dry-run --always-make -C "$repo_root" up-dev 2>&1 )" @@ -67,6 +77,20 @@ if ! grep -q "Starting App stack (backend/userfront/adminfront/devfront/orgfront exit 1 fi +if ! grep -q "Rendering Ory config" <<<"$dry_run_up_app"; then + echo "make up-app must render Ory config before starting services." >&2 + exit 1 +fi + +up_app_line="$( + grep -E "docker compose .* -f docker-compose.yaml up .*backend.*adminfront.*devfront.*orgfront.*userfront|docker compose .* -f docker-compose.yaml up " <<<"$dry_run_up_app" | tail -1 +)" + +if ! grep -q -- " --build" <<<"$up_app_line"; then + echo "make up-app must rebuild app service images before starting containers." >&2 + exit 1 +fi + dry_run_up_all="$( make --dry-run --always-make -C "$repo_root" up-all 2>&1 )" @@ -84,6 +108,16 @@ if ! grep -q "Starting ALL stacks (infra + ory + app)" <<<"$dry_run_up"; then exit 1 fi +if ! grep -q "config/.generated/auth-config.env" <<<"$dry_run_up"; then + echo "make up must use generated env from config/.generated." >&2 + exit 1 +fi + +if ! grep -q "Rendering Ory config" <<<"$dry_run_up"; then + echo "make up must render Ory config before compose up." >&2 + exit 1 +fi + if ! grep -q "Ensuring Docker networks" <<<"$dry_run_up_all"; then echo "make up-all must ensure external Docker networks before compose up." >&2 exit 1 diff --git a/test/ory_log_pipeline_policy_test.sh b/test/ory_log_pipeline_policy_test.sh index 18c2b5ed..01f30881 100755 --- a/test/ory_log_pipeline_policy_test.sh +++ b/test/ory_log_pipeline_policy_test.sh @@ -3,6 +3,8 @@ set -euo pipefail repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +"$repo_root/scripts/render_ory_config.sh" >/dev/null + docker run --rm \ -e ORY_CLICKHOUSE_USER=ory \ -e ORY_CLICKHOUSE_PASSWORD=orypass \ @@ -14,12 +16,12 @@ if grep -q '/etc/config/oathkeeper/rules.active.json' "$repo_root/docker/ory/oat exit 1 fi -if ! grep -q 'file:///tmp/oathkeeper/rules.active.json' "$repo_root/docker/ory/oathkeeper/oathkeeper.yml"; then +if ! grep -q 'file:///tmp/oathkeeper/rules.active.json' "$repo_root/config/.generated/ory/oathkeeper/oathkeeper.yml"; then echo "ERROR: Oathkeeper config must load active rules from writable runtime storage." >&2 exit 1 fi -if ! grep -q '^version: v26.2.0$' "$repo_root/docker/ory/kratos/kratos.yml"; then +if ! grep -q '^version: v26.2.0$' "$repo_root/config/.generated/ory/kratos/kratos.yml"; then echo "ERROR: Kratos config version must match the v26.2.0 runtime." >&2 exit 1 fi diff --git a/test/ory_v26_compose_policy_test.sh b/test/ory_v26_compose_policy_test.sh index df6be834..ddf467ca 100644 --- a/test/ory_v26_compose_policy_test.sh +++ b/test/ory_v26_compose_policy_test.sh @@ -67,6 +67,30 @@ for compose_file in "$repo_root/compose.ory.yaml" "$repo_root/docker/compose.ory fi done +for stack_check_file in \ + "$repo_root/compose.ory.yaml" \ + "$repo_root/docker/compose.ory.yaml" \ + "$repo_root/docker/staging_pull_compose.template.yaml" \ + "$repo_root/deploy/templates/docker-compose.yaml" +do + if grep -q 'until curl -s http://' "$stack_check_file"; then + echo "ERROR: Ory stack check must not wait forever; use bounded readiness checks in $stack_check_file." >&2 + exit 1 + fi + if ! grep -q 'ORY_STACK_CHECK_MAX_ATTEMPTS' "$stack_check_file"; then + echo "ERROR: Ory stack check must expose ORY_STACK_CHECK_MAX_ATTEMPTS in $stack_check_file." >&2 + exit 1 + fi + if ! grep -q 'ERROR: Ory service not ready' "$stack_check_file"; then + echo "ERROR: Ory stack check must report the failed service name in $stack_check_file." >&2 + exit 1 + fi + if ! grep -q 'check_ready kratos .* || exit 1' "$stack_check_file"; then + echo "ERROR: Ory stack check must raise a non-zero exit when Kratos is not ready in $stack_check_file." >&2 + exit 1 + fi +done + for expected_url in \ "https://compose-policy.example.test/sso/oidc" \ "https://compose-policy.example.test/sso/login" \ @@ -189,17 +213,17 @@ 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_kratos_template="$repo_root/deploy/templates/ory/kratos/kratos.yml.template" 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/hydra/hydra.yml.template" \ + "$repo_root/docker/ory/keto/keto.yml.template" \ "$repo_root/docker/ory/oathkeeper/entrypoint.sh" \ - "$repo_root/docker/ory/oathkeeper/oathkeeper.yml" + "$repo_root/docker/ory/oathkeeper/oathkeeper.yml.template" do if [[ ! -f "$required_template" ]]; then echo "ERROR: deploy instance generation requires missing source file: $required_template" >&2 @@ -214,8 +238,8 @@ 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" + "$repo_root/docker/ory/kratos/kratos.yml.template" \ + "$repo_root/deploy/templates/ory/kratos/kratos.yml.template" 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 @@ -223,6 +247,51 @@ do fi done +for compose_file in "$repo_root/compose.ory.yaml" "$repo_root/docker/compose.ory.yaml" "$repo_root/docker/staging_pull_compose.template.yaml"; do + if grep -Eq './docker/ory/(kratos|hydra|keto|oathkeeper):/etc/config/' "$compose_file"; then + echo "ERROR: Ory compose must mount rendered config/.generated/ory config, not source templates: $compose_file" >&2 + exit 1 + fi +done + +if grep -Eq '\./ory/(kratos|hydra|keto|oathkeeper):/etc/config/' "$deploy_template"; then + echo "ERROR: deploy template must mount rendered config/.generated/ory config, not source templates." >&2 + exit 1 +fi + +if grep -q 'ory/generated' "$deploy_template" "$repo_root/deploy/create-instance.sh"; then + echo "ERROR: deploy template must use config/.generated/ory, not ory/generated." >&2 + exit 1 +fi + +if ! grep -q '^render-ory-config:' "$repo_root/Makefile"; then + echo "ERROR: Makefile must render Ory config before starting Ory services." >&2 + exit 1 +fi + +if ! grep -q 'scripts/render_ory_config.sh' "$repo_root/.gitea/workflows/staging_code_pull.yml"; then + echo "ERROR: staging code pull must render Ory config before docker compose up." >&2 + exit 1 +fi + +"$repo_root/scripts/render_ory_config.sh" >/dev/null + +for generated_config in \ + "$repo_root/config/.generated/ory/kratos/kratos.yml" \ + "$repo_root/config/.generated/ory/hydra/hydra.yml" \ + "$repo_root/config/.generated/ory/keto/keto.yml" \ + "$repo_root/config/.generated/ory/oathkeeper/oathkeeper.yml" +do + if [[ ! -f "$generated_config" ]]; then + echo "ERROR: Ory rendered config is missing: $generated_config" >&2 + exit 1 + fi + if grep -q '\${' "$generated_config"; then + echo "ERROR: Ory rendered config must not contain placeholders: $generated_config" >&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