#!/usr/bin/env bash set -euo pipefail repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" root_config="$( docker compose --env-file "$repo_root/.env" -f "$repo_root/compose.ory.yaml" config )" docker_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 version_key="$(tr '[:lower:]' '[:upper:]' <<<"$service")_VERSION" expected_version="$(grep -E "^${version_key}=" "$repo_root/.env" | cut -d= -f2-)" if [[ -z "$expected_version" ]]; then echo "ERROR: $version_key must be set in .env" >&2 exit 1 fi if ! grep -q "image: oryd/${service}:${expected_version}" <<<"$root_config"; then echo "ERROR: compose.ory.yaml must render oryd/${service}:${expected_version}" >&2 exit 1 fi done if grep -q "oryd/hydra:v25.4.0" <<<"$root_config"; then echo "ERROR: compose.ory.yaml must not hard-code init-rp to hydra v25.4.0." >&2 exit 1 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 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" \ "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="$( awk 'in_block && /^ [A-Za-z0-9_-]+:/ { exit } /^ init-rp:/ { in_block=1 } in_block { print }' "$repo_root/compose.ory.yaml" )" docker_init_rp="$( awk 'in_block && /^ [A-Za-z0-9_-]+:/ { exit } /^ init-rp:/ { in_block=1 } in_block { print }' "$repo_root/docker/compose.ory.yaml" )" if grep -q "image: oryd/hydra" <<<"$root_init_rp$docker_init_rp"; then echo "ERROR: init-rp must not use the Hydra service image because distroless tags do not provide /bin/sh." >&2 exit 1 fi if ! grep -q "migrate sql up" "$repo_root/compose.ory.yaml"; then echo "ERROR: compose.ory.yaml Kratos migration must use migrate sql up." >&2 exit 1 fi if ! grep -q "keto-migrate:" <<<"$docker_config"; then echo "ERROR: docker/compose.ory.yaml must include keto-migrate for clean Ory installs." >&2 exit 1 fi if grep -q "releases/download/v25.4.0" "$repo_root/docker/staging_pull_compose.template.yaml"; then echo "ERROR: staging pull compose must not download a hard-coded Hydra v25.4.0 CLI." >&2 exit 1 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.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.template" \ "$repo_root/docker/ory/keto/keto.yml.template" \ "$repo_root/docker/ory/oathkeeper/entrypoint.sh" \ "$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 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.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 exit 1 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 ! awk '/^ensure-ory:/ { in_target=1 } in_target && /^[^[:space:]].*:/ && $0 !~ /^ensure-ory:/ { exit } in_target { print }' "$repo_root/Makefile" | grep -q 'restart kratos'; then echo "ERROR: make up-dev must restart Kratos when Ory is already running so rendered dev config is applied." >&2 exit 1 fi if ! awk '/^up-all:/ { in_target=1 } in_target && /^[^[:space:]].*:/ && $0 !~ /^up-all:/ { exit } in_target { print }' "$repo_root/Makefile" | grep -q 'restart kratos'; then echo "ERROR: make up must restart Kratos after rendering Ory config." >&2 exit 1 fi if ! awk '/^up-ory:/ { in_target=1 } in_target && /^[^[:space:]].*:/ && $0 !~ /^up-ory:/ { exit } in_target { print }' "$repo_root/Makefile" | grep -q 'restart kratos'; then echo "ERROR: make up-ory must restart Kratos after rendering Ory config." >&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 if ! grep -q 'up -d --force-recreate kratos hydra keto oathkeeper' "$repo_root/.gitea/workflows/staging_code_pull.yml"; then echo "ERROR: staging code pull must restart Ory services after rendering static config." >&2 exit 1 fi if grep -Eq '^[[:space:]]*rm -rf "?\$OUTPUT_DIR"?[[:space:]]*$' "$repo_root/scripts/render_ory_config.sh"; then echo "ERROR: Ory renderer must preserve config/.generated/ory service directories so live bind mounts stay valid." >&2 exit 1 fi "$repo_root/scripts/render_ory_config.sh" >/dev/null local_rendered_kratos="$repo_root/config/.generated/ory/kratos/kratos.yml" if ! awk '/session:/ { in_session=1 } in_session && /domain:/ { print; exit }' "$local_rendered_kratos" | grep -q 'domain: localhost'; then echo "ERROR: rendered local Kratos config must use localhost as session.cookie.domain for dev runs." >&2 exit 1 fi stage_render_dir="$(mktemp -d)" stage_render_env="$(mktemp)" cat > "$stage_render_env" <<'EOF' USERFRONT_URL=https://sso.hmac.kr ADMINFRONT_URL=https://sadmin.hmac.kr DEVFRONT_URL=https://sdev.hmac.kr ORGFRONT_URL=https://sorg.hmac.kr KRATOS_UI_URL=https://sso.hmac.kr KRATOS_BROWSER_URL=https://sso.hmac.kr/auth KRATOS_ADMIN_URL=http://kratos:4434 ORY_POSTGRES_PASSWORD=policy-test KRATOS_ALLOWED_RETURN_URLS_JSON= KRATOS_ALLOWED_RETURN_URLS_EXTRA= EOF ORY_CONFIG_ENV_FILES="$stage_render_env" ORY_CONFIG_OUTPUT_DIR="$stage_render_dir/ory" "$repo_root/scripts/render_ory_config.sh" >/dev/null stage_rendered_kratos="$stage_render_dir/ory/kratos/kratos.yml" if ! awk '/allowed_return_urls:/ { in_block=1; next } in_block && /^[[:space:]]+methods:/ { exit } in_block { print }' "$stage_rendered_kratos" | grep -q 'https://sso.hmac.kr'; then echo "ERROR: rendered stage Kratos config must include the public userfront URL in allowed_return_urls." >&2 exit 1 fi if awk '/allowed_return_urls:/ { in_block=1; next } in_block && /^[[:space:]]+methods:/ { exit } in_block { print }' "$stage_rendered_kratos" | grep -q 'http://localhost:5000'; then echo "ERROR: rendered stage Kratos allowed_return_urls must not fall back to localhost." >&2 exit 1 fi if ! awk '/session:/ { in_session=1 } in_session && /domain:/ { print; exit }' "$stage_rendered_kratos" | grep -q 'domain: hmac.kr'; then echo "ERROR: rendered stage Kratos config must derive hmac.kr as session.cookie.domain." >&2 exit 1 fi rm -rf "$stage_render_dir" "$stage_render_env" 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 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