forked from baron/baron-sso
feature/i18n 코드 통합 & 인프라 검증로직 추가
This commit is contained in:
@@ -110,6 +110,14 @@ HYDRA_ADMIN_URL=http://hydra:4445
|
||||
# Oathkeeper가 /oidc 경로를 Hydra Public API로 라우팅합니다.
|
||||
HYDRA_PUBLIC_URL=${OATHKEEPER_PUBLIC_URL}/oidc
|
||||
|
||||
# OIDC 클라이언트 callback (콤마 구분)
|
||||
ADMINFRONT_CALLBACK_URLS=http://localhost:5173/auth/callback
|
||||
DEVFRONT_CALLBACK_URLS=http://localhost:5174/callback
|
||||
|
||||
# Kratos allowed_return_urls 확장 목록 (콤마 구분, 선택)
|
||||
# 기본값은 KRATOS_UI_URL, USERFRONT_URL, 각 callback URL을 자동 포함합니다.
|
||||
KRATOS_ALLOWED_RETURN_URLS_EXTRA=
|
||||
|
||||
# Oathkeeper JWKS (내부 통신용)
|
||||
JWKS_URL=http://oathkeeper:4456/.well-known/jwks.json
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,6 +6,7 @@
|
||||
.vscode/
|
||||
.codex
|
||||
.codex/
|
||||
.generated/
|
||||
*.swp
|
||||
*.log
|
||||
*.out
|
||||
|
||||
37
Makefile
37
Makefile
@@ -10,29 +10,50 @@ endif
|
||||
COMPOSE_INFRA := compose.infra.yaml
|
||||
COMPOSE_ORY := compose.ory.yaml
|
||||
COMPOSE_APP := docker-compose.yaml
|
||||
AUTH_CONFIG_ENV := .generated/auth-config.env
|
||||
|
||||
COMPOSE_ENV_FILES :=
|
||||
ifneq (,$(wildcard ./.env))
|
||||
COMPOSE_ENV_FILES += --env-file .env
|
||||
endif
|
||||
COMPOSE_ENV_FILES += --env-file $(AUTH_CONFIG_ENV)
|
||||
|
||||
# --- 인증 설정 빌드/검증 ---
|
||||
build-auth-config:
|
||||
@echo "Building auth config..."
|
||||
@mkdir -p .generated
|
||||
@bash scripts/auth_config.sh build
|
||||
|
||||
validate-auth-config: build-auth-config
|
||||
@echo "Validating auth config..."
|
||||
@bash scripts/auth_config.sh validate
|
||||
|
||||
verify-auth-config: validate-auth-config
|
||||
@echo "Verifying auth config wiring..."
|
||||
@bash scripts/auth_config.sh verify
|
||||
|
||||
# --- 기본 실행 ---
|
||||
# 주의: --remove-orphan 사용 금지 (다른 스택이 orphan으로 판단되어 종료될 수 있음)
|
||||
up-all:
|
||||
up-all: validate-auth-config
|
||||
@echo "Starting ALL stacks (infra + ory + app)..."
|
||||
docker compose -f $(COMPOSE_INFRA) -f $(COMPOSE_ORY) -f $(COMPOSE_APP) up -d
|
||||
docker compose $(COMPOSE_ENV_FILES) -f $(COMPOSE_INFRA) -f $(COMPOSE_ORY) -f $(COMPOSE_APP) up -d
|
||||
|
||||
# --- 개별 스택 실행 ---
|
||||
up-infra:
|
||||
@echo "Starting Infra stack (postgres/clickhouse/redis)..."
|
||||
docker compose -f $(COMPOSE_INFRA) up -d
|
||||
|
||||
up-ory:
|
||||
up-ory: validate-auth-config
|
||||
@echo "Starting Ory stack (kratos/hydra/keto/oathkeeper)..."
|
||||
docker compose -f $(COMPOSE_ORY) up -d
|
||||
docker compose $(COMPOSE_ENV_FILES) -f $(COMPOSE_ORY) up -d
|
||||
|
||||
up-app:
|
||||
up-app: validate-auth-config
|
||||
@echo "Starting App stack (backend/userfront/adminfront/devfront)..."
|
||||
docker compose -f $(COMPOSE_APP) up -d
|
||||
docker compose $(COMPOSE_ENV_FILES) -f $(COMPOSE_APP) up -d
|
||||
|
||||
up-backend:
|
||||
up-backend: validate-auth-config
|
||||
@echo "Starting Backend only..."
|
||||
docker compose -f $(COMPOSE_APP) up -d backend
|
||||
docker compose $(COMPOSE_ENV_FILES) -f $(COMPOSE_APP) up -d backend
|
||||
|
||||
up-dev: up-infra up-ory
|
||||
@echo "Dev stack is up (infra + ory)."
|
||||
|
||||
31
README.md
31
README.md
@@ -178,15 +178,40 @@ docker network create public_net #서비스용
|
||||
#### 2. 인프라 및 Ory Stack 실행
|
||||
데이터베이스와 Ory 서비스(Kratos, Hydra, Keto 등)를 실행합니다.
|
||||
```bash
|
||||
docker compose -f compose.infra.yaml -f compose.ory.yaml up -d
|
||||
# 권장: Make 실행 (인증 설정 검증 포함)
|
||||
make up-dev
|
||||
```
|
||||
|
||||
#### 3. 애플리케이션 실행
|
||||
userfront와 backend 서비스를 실행합니다.
|
||||
```bash
|
||||
docker compose -f docker-compose.yaml up -d
|
||||
make up-app
|
||||
```
|
||||
(또는 전체 스택 한번에 실행: `make up-all`)
|
||||
|
||||
### Make 기반 인증 설정 검증 (권장)
|
||||
`up-*` 타깃은 실행 전 인증 리다이렉트 설정을 자동 검증합니다.
|
||||
|
||||
```bash
|
||||
# 1) 인증 설정 생성
|
||||
make build-auth-config
|
||||
|
||||
# 2) 정적 검증 (callback / allowed_return_urls / 게이트웨이 매핑)
|
||||
make validate-auth-config
|
||||
|
||||
# 3) 배선 + (가능 시) 런타임 Hydra client 검증
|
||||
make verify-auth-config
|
||||
```
|
||||
|
||||
- 생성 파일: `.generated/auth-config.env` (compose 실행 시 자동 주입)
|
||||
- 게이트웨이 경유 환경은 URL 문자열 완전일치 대신 매핑 유효성(`direct_match` / `mapped_match`) 기준으로 검증합니다.
|
||||
- 관련 정책 문서: `docs/oidc_redirect_mapping_validation_policy.md`
|
||||
|
||||
직접 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 -f compose.infra.yaml -f compose.ory.yaml -f docker-compose.yaml up -d`)
|
||||
|
||||
- **gateway (UserFront 프록시)**: http://localhost:5000 접속
|
||||
- **backend**: http://localhost:3000 (API)
|
||||
|
||||
@@ -29,7 +29,7 @@ services:
|
||||
- KRATOS_SERVE_PUBLIC_BASE_URL=${KRATOS_BROWSER_URL:-http://localhost:4433}
|
||||
- KRATOS_SERVE_ADMIN_BASE_URL=${KRATOS_ADMIN_URL:-http://kratos:4434}
|
||||
- KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL:-http://localhost:5000}
|
||||
- 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:-["http://localhost:5000","http://localhost:5000/"]}
|
||||
- KRATOS_SELFSERVICE_FLOWS_ERROR_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/error
|
||||
- KRATOS_SELFSERVICE_FLOWS_SETTINGS_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/error?error=settings_disabled
|
||||
- KRATOS_SELFSERVICE_FLOWS_RECOVERY_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/recovery
|
||||
@@ -55,7 +55,7 @@ services:
|
||||
- KRATOS_SERVE_PUBLIC_BASE_URL=${KRATOS_BROWSER_URL:-http://localhost:4433}
|
||||
- KRATOS_SERVE_ADMIN_BASE_URL=${KRATOS_ADMIN_URL:-http://kratos:4434}
|
||||
- KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL:-http://localhost:5000}
|
||||
- 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:-["http://localhost:5000","http://localhost:5000/"]}
|
||||
- KRATOS_SELFSERVICE_FLOWS_ERROR_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/error
|
||||
- KRATOS_SELFSERVICE_FLOWS_SETTINGS_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/error?error=settings_disabled
|
||||
- KRATOS_SELFSERVICE_FLOWS_RECOVERY_UI_URL=${KRATOS_UI_URL:-http://localhost:5000}/recovery
|
||||
@@ -203,6 +203,8 @@ services:
|
||||
- HYDRA_ADMIN_URL=http://hydra:4445
|
||||
- OATHKEEPER_INTROSPECT_CLIENT_ID=${OATHKEEPER_INTROSPECT_CLIENT_ID:-oathkeeper-introspect}
|
||||
- OATHKEEPER_INTROSPECT_CLIENT_SECRET=${OATHKEEPER_INTROSPECT_CLIENT_SECRET:-oathkeeper-secret}
|
||||
- ADMINFRONT_CALLBACK_URLS=${ADMINFRONT_CALLBACK_URLS:-http://localhost:5173/auth/callback}
|
||||
- DEVFRONT_CALLBACK_URLS=${DEVFRONT_CALLBACK_URLS:-http://localhost:5174/callback}
|
||||
command: |
|
||||
hydra clients create \
|
||||
--endpoint http://hydra:4445 \
|
||||
@@ -211,7 +213,7 @@ services:
|
||||
--grant-types authorization_code,refresh_token \
|
||||
--response-types code \
|
||||
--scope openid,offline_access,profile,email \
|
||||
--callbacks http://localhost:5000/callback;
|
||||
--callbacks "$ADMINFRONT_CALLBACK_URLS";
|
||||
|
||||
hydra clients create \
|
||||
--endpoint http://hydra:4445 \
|
||||
@@ -221,7 +223,7 @@ services:
|
||||
--scope openid,offline_access,profile,email \
|
||||
--token-endpoint-auth-method none \
|
||||
--response-types code \
|
||||
--callbacks http://localhost:5174/callback;
|
||||
--callbacks "$DEVFRONT_CALLBACK_URLS";
|
||||
|
||||
hydra clients create \
|
||||
--endpoint http://hydra:4445 \
|
||||
|
||||
67
docs/oidc_redirect_mapping_validation_policy.md
Normal file
67
docs/oidc_redirect_mapping_validation_policy.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# OIDC Redirect 매핑 검증 정책
|
||||
|
||||
## 목적
|
||||
- OIDC 로그인/리다이렉트 검증 시, URL 문자열의 기계적 동일성만으로 정상/비정상을 판단하지 않습니다.
|
||||
- Gateway(Oathkeeper/Nginx) 경유 구조에서 발생하는 Public URL과 Internal URL의 의도된 차이를 정책적으로 허용하되, 매핑의 유효성은 엄격히 검증합니다.
|
||||
|
||||
## 적용 범위
|
||||
- UserFront, AdminFront, DevFront의 로그인/콜백 경로
|
||||
- Ory Stack(Hydra/Kratos/Oathkeeper) 설정
|
||||
- `compose.ory.yaml`, `gateway/nginx.conf`, `docker/ory/oathkeeper/rules*.json`
|
||||
- `Makefile` 기반 사전 검증/스모크 검증 단계
|
||||
|
||||
## 핵심 원칙
|
||||
1. URL 직접 일치(`direct_match`)와 게이트웨이 매핑 일치(`mapped_match`)를 구분합니다.
|
||||
2. Public URL과 Internal URL이 다르더라도, `Public -> Gateway/Oathkeeper -> Internal` 경로가 검증되면 정상으로 간주합니다.
|
||||
3. 매핑 체인이 없거나 규칙이 누락된 경우는 실패(`unmapped_fail`)로 간주합니다.
|
||||
|
||||
## 용어 정의
|
||||
- Public URL: 브라우저에서 접근하는 URL. 예: `https://sso-test.hmac.kr/oidc/oauth2/auth`
|
||||
- Internal URL: 컨테이너 내부 통신 URL. 예: `http://hydra:4444/oauth2/auth`
|
||||
- Mapping Chain: Public 요청이 Gateway/Oathkeeper 규칙을 통해 Internal URL로 전달되는 경로
|
||||
|
||||
## 판정 규칙
|
||||
1. `direct_match`
|
||||
- 프론트가 생성한 `redirect_uri`/`authority`와 실제 등록/설정 URL이 동일 레이어에서 직접 일치
|
||||
- 예: 로컬 개발에서 모두 `http://localhost:*` 기준
|
||||
|
||||
2. `mapped_match`
|
||||
- Public URL과 Internal URL이 다르지만, 아래가 모두 성립
|
||||
- Gateway 라우팅 규칙 존재 (예: `/oidc` rewrite)
|
||||
- Oathkeeper `match`와 `upstream` 규칙 존재 (예: `strip_path_prefix=/oidc`)
|
||||
- 최종 업스트림이 기대 서비스(Hydra/Kratos)로 연결
|
||||
|
||||
3. `unmapped_fail`
|
||||
- Public/Internal 불일치가 있는데 매핑 규칙이 없거나 누락
|
||||
- callback/return URL이 등록되지 않았거나 경로 규약 불일치
|
||||
- 환경 변수는 존재하나 실제 compose/rules 반영이 누락
|
||||
|
||||
## 검증 항목
|
||||
1. 정적 검증 (`make validate-auth-config`)
|
||||
- `USERFRONT_URL`, `OATHKEEPER_PUBLIC_URL`, `HYDRA_PUBLIC_URL`, `KRATOS_BROWSER_URL` 정합성
|
||||
- `ADMINFRONT_CALLBACK_URLS`, `DEVFRONT_CALLBACK_URLS` URL 유효성/중복/경로 규약
|
||||
- Gateway `/oidc`, `/auth` 라우팅 규칙 존재 여부
|
||||
- Oathkeeper `rules*.json`의 Hydra/Kratos 매핑 규칙 존재 여부
|
||||
|
||||
2. 런타임 검증 (`make verify-oidc-config`)
|
||||
- OIDC Discovery endpoint 조회 가능 여부
|
||||
- Hydra 등록 client(`adminfront`, `devfront`)의 `redirect_uris` 확인
|
||||
- 필요 시 Gateway 경유 endpoint probe로 매핑 체인 확인
|
||||
|
||||
## 경로 규약
|
||||
- DevFront callback: `/callback`
|
||||
- AdminFront callback: `/auth/callback`
|
||||
- UserFront OIDC 진입점: `/oidc/*` (Gateway 경유)
|
||||
|
||||
## 운영 지침
|
||||
1. 환경별 URL은 동일할 필요가 없고, 매핑 체인이 검증 가능해야 합니다.
|
||||
2. `localhost` 하드코딩은 로컬 전용 예외로만 허용하며, 스테이징/운영은 env 기반으로 주입합니다.
|
||||
3. 신규 도메인/경로 추가 시, 프론트 설정과 Ory/Gateway 규칙을 반드시 동시에 변경하고 검증 결과를 이슈/PR에 첨부합니다.
|
||||
|
||||
## 관련 이슈
|
||||
- #262
|
||||
- #269
|
||||
- #271
|
||||
- #272
|
||||
- #274
|
||||
- #276
|
||||
@@ -10,6 +10,9 @@
|
||||
|
||||
## 2) 실행 방법
|
||||
```bash
|
||||
# 인증 리다이렉트 설정 생성/검증
|
||||
make validate-auth-config
|
||||
|
||||
# 인프라 + Ory Stack
|
||||
docker compose -f compose.infra.yaml -f compose.ory.yaml up -d
|
||||
|
||||
@@ -17,6 +20,13 @@ docker compose -f compose.infra.yaml -f compose.ory.yaml up -d
|
||||
docker compose -f docker-compose.yaml up -d
|
||||
```
|
||||
|
||||
Make 기반 실행을 사용할 경우:
|
||||
```bash
|
||||
make up-ory
|
||||
make up-app
|
||||
```
|
||||
`up-*` 타깃은 내부적으로 `validate-auth-config`를 선행 수행하여 callback/allowed_return_urls 정합성을 먼저 검증합니다.
|
||||
|
||||
## 3) 내부 통신 vs 브라우저 접근용 URL 분리
|
||||
Ory 구성은 **컨테이너 내부 통신 URL**과 **브라우저 접근 URL**을 분리해야 합니다.
|
||||
|
||||
|
||||
366
scripts/auth_config.sh
Executable file
366
scripts/auth_config.sh
Executable file
@@ -0,0 +1,366 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
OUTPUT_DIR="$ROOT_DIR/.generated"
|
||||
OUTPUT_FILE="$OUTPUT_DIR/auth-config.env"
|
||||
MODE="${1:-build}"
|
||||
|
||||
if [[ -f "$ROOT_DIR/.env" ]]; then
|
||||
# shellcheck disable=SC1091
|
||||
set -a
|
||||
source "$ROOT_DIR/.env"
|
||||
set +a
|
||||
fi
|
||||
|
||||
USERFRONT_URL="${USERFRONT_URL:-http://localhost:5000}"
|
||||
OATHKEEPER_PUBLIC_URL="${OATHKEEPER_PUBLIC_URL:-$USERFRONT_URL}"
|
||||
HYDRA_PUBLIC_URL="${HYDRA_PUBLIC_URL:-${OATHKEEPER_PUBLIC_URL%/}/oidc}"
|
||||
KRATOS_UI_URL="${KRATOS_UI_URL:-http://localhost:5000}"
|
||||
ADMINFRONT_CALLBACK_URLS="${ADMINFRONT_CALLBACK_URLS:-http://localhost:5173/auth/callback}"
|
||||
DEVFRONT_CALLBACK_URLS="${DEVFRONT_CALLBACK_URLS:-http://localhost:5174/callback}"
|
||||
KRATOS_ALLOWED_RETURN_URLS_EXTRA="${KRATOS_ALLOWED_RETURN_URLS_EXTRA:-}"
|
||||
|
||||
declare -a WARNINGS=()
|
||||
|
||||
fail() {
|
||||
echo "[auth-config] ERROR: $1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
warn() {
|
||||
WARNINGS+=("$1")
|
||||
}
|
||||
|
||||
trim() {
|
||||
local value="$1"
|
||||
value="${value#"${value%%[![:space:]]*}"}"
|
||||
value="${value%"${value##*[![:space:]]}"}"
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
csv_to_lines() {
|
||||
local csv="$1"
|
||||
printf '%s\n' "$csv" | tr ',' '\n' | while IFS= read -r raw; do
|
||||
local item
|
||||
item="$(trim "$raw")"
|
||||
if [[ -n "$item" ]]; then
|
||||
printf '%s\n' "$item"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
is_http_url() {
|
||||
local url="$1"
|
||||
[[ "$url" =~ ^https?://[^[:space:]]+$ ]]
|
||||
}
|
||||
|
||||
canonicalize_url() {
|
||||
local url="$1"
|
||||
if [[ "$url" =~ ^(https?://[^/]+)(/.*)?$ ]]; then
|
||||
local origin="${BASH_REMATCH[1]}"
|
||||
local path="${BASH_REMATCH[2]:-}"
|
||||
if [[ -z "$path" || "$path" == "/" ]]; then
|
||||
printf '%s' "$origin"
|
||||
return
|
||||
fi
|
||||
path="${path%/}"
|
||||
if [[ -z "$path" ]]; then
|
||||
path="/"
|
||||
fi
|
||||
printf '%s%s' "$origin" "$path"
|
||||
return
|
||||
fi
|
||||
printf '%s' "$url"
|
||||
}
|
||||
|
||||
is_origin_like_url() {
|
||||
local url="$1"
|
||||
[[ "$url" =~ ^https?://[^/]+/?$ ]]
|
||||
}
|
||||
|
||||
url_path() {
|
||||
local url="$1"
|
||||
if [[ "$url" =~ ^https?://[^/]+(/.*)?$ ]]; then
|
||||
local path="${BASH_REMATCH[1]:-/}"
|
||||
printf '%s' "$path"
|
||||
return
|
||||
fi
|
||||
printf ''
|
||||
}
|
||||
|
||||
json_escape() {
|
||||
local value="$1"
|
||||
value="${value//\\/\\\\}"
|
||||
value="${value//\"/\\\"}"
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
join_csv() {
|
||||
local -n arr_ref=$1
|
||||
local out=""
|
||||
local first=1
|
||||
for item in "${arr_ref[@]}"; do
|
||||
if [[ $first -eq 1 ]]; then
|
||||
out="$item"
|
||||
first=0
|
||||
else
|
||||
out="$out,$item"
|
||||
fi
|
||||
done
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
to_json_array() {
|
||||
local -n arr_ref=$1
|
||||
local json="["
|
||||
local first=1
|
||||
for item in "${arr_ref[@]}"; do
|
||||
local escaped
|
||||
escaped="$(json_escape "$item")"
|
||||
if [[ $first -eq 1 ]]; then
|
||||
json="$json\"$escaped\""
|
||||
first=0
|
||||
else
|
||||
json="$json,\"$escaped\""
|
||||
fi
|
||||
done
|
||||
json="$json]"
|
||||
printf '%s' "$json"
|
||||
}
|
||||
|
||||
collect_values() {
|
||||
declare -ga ADMIN_CALLBACKS=()
|
||||
declare -ga DEV_CALLBACKS=()
|
||||
declare -ga EXTRA_ALLOWED_RETURNS=()
|
||||
|
||||
while IFS= read -r item; do
|
||||
ADMIN_CALLBACKS+=("$item")
|
||||
done < <(csv_to_lines "$ADMINFRONT_CALLBACK_URLS")
|
||||
|
||||
while IFS= read -r item; do
|
||||
DEV_CALLBACKS+=("$item")
|
||||
done < <(csv_to_lines "$DEVFRONT_CALLBACK_URLS")
|
||||
|
||||
while IFS= read -r item; do
|
||||
EXTRA_ALLOWED_RETURNS+=("$item")
|
||||
done < <(csv_to_lines "$KRATOS_ALLOWED_RETURN_URLS_EXTRA")
|
||||
}
|
||||
|
||||
validate_urls() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
if ! is_http_url "$value"; then
|
||||
fail "$key must be a valid http/https URL: $value"
|
||||
fi
|
||||
}
|
||||
|
||||
validate_callback_group() {
|
||||
local group_name="$1"
|
||||
local expected_path="$2"
|
||||
shift 2
|
||||
local urls=("$@")
|
||||
|
||||
if [[ ${#urls[@]} -eq 0 ]]; then
|
||||
fail "$group_name is empty"
|
||||
fi
|
||||
|
||||
local matched_expected=0
|
||||
local has_path=0
|
||||
for url in "${urls[@]}"; do
|
||||
validate_urls "$group_name entry" "$url"
|
||||
local path
|
||||
path="$(url_path "$url")"
|
||||
if [[ -n "$path" && "$path" != "/" ]]; then
|
||||
has_path=1
|
||||
fi
|
||||
if [[ "$path" == "$expected_path" ]]; then
|
||||
matched_expected=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $has_path -eq 0 ]]; then
|
||||
fail "$group_name must include path segment (callback path required)"
|
||||
fi
|
||||
|
||||
if [[ $matched_expected -eq 0 ]]; then
|
||||
warn "$group_name does not include recommended path: $expected_path"
|
||||
fi
|
||||
}
|
||||
|
||||
validate_gateway_mapping() {
|
||||
validate_urls "HYDRA_PUBLIC_URL" "$HYDRA_PUBLIC_URL"
|
||||
validate_urls "USERFRONT_URL" "$USERFRONT_URL"
|
||||
validate_urls "KRATOS_UI_URL" "$KRATOS_UI_URL"
|
||||
|
||||
local mode=""
|
||||
if [[ "$HYDRA_PUBLIC_URL" =~ ^https?://hydra(:[0-9]+)?(/|$) ]]; then
|
||||
mode="direct_match"
|
||||
else
|
||||
mode="mapped_match"
|
||||
if ! grep -Eq 'location /oidc' "$ROOT_DIR/gateway/nginx.conf"; then
|
||||
mode="unmapped_fail"
|
||||
fi
|
||||
if ! grep -Eq 'rewrite \^/oidc/\(\.\*\)\$ /\$1 break;' "$ROOT_DIR/gateway/nginx.conf"; then
|
||||
mode="unmapped_fail"
|
||||
fi
|
||||
if ! grep -Eq '"url": "<\.\*>://<\.\*>/oidc/oauth2/<\.\*>"' "$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"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$mode" == "unmapped_fail" ]]; then
|
||||
fail "Public/Internal Hydra URL mismatch exists but gateway mapping rules are incomplete"
|
||||
fi
|
||||
|
||||
OIDC_HYDRA_URL_MATCH_MODE="$mode"
|
||||
}
|
||||
|
||||
build_allowed_return_urls() {
|
||||
declare -ga KRATOS_ALLOWED_RETURN_URLS=()
|
||||
declare -gA _seen_allowed=()
|
||||
|
||||
add_allowed_url() {
|
||||
local candidate="$1"
|
||||
candidate="$(trim "$candidate")"
|
||||
[[ -z "$candidate" ]] && return
|
||||
validate_urls "allowed_return_url" "$candidate"
|
||||
candidate="$(canonicalize_url "$candidate")"
|
||||
if [[ -z "${_seen_allowed[$candidate]:-}" ]]; then
|
||||
KRATOS_ALLOWED_RETURN_URLS+=("$candidate")
|
||||
_seen_allowed["$candidate"]=1
|
||||
fi
|
||||
}
|
||||
|
||||
add_allowed_with_slash_variant() {
|
||||
local candidate="$1"
|
||||
add_allowed_url "$candidate"
|
||||
local normalized
|
||||
normalized="$(canonicalize_url "$candidate")"
|
||||
if is_origin_like_url "$candidate"; then
|
||||
add_allowed_url "${normalized}/"
|
||||
fi
|
||||
}
|
||||
|
||||
add_allowed_with_slash_variant "$KRATOS_UI_URL"
|
||||
add_allowed_with_slash_variant "$USERFRONT_URL"
|
||||
|
||||
for url in "${ADMIN_CALLBACKS[@]}"; do
|
||||
add_allowed_url "$url"
|
||||
done
|
||||
for url in "${DEV_CALLBACKS[@]}"; do
|
||||
add_allowed_url "$url"
|
||||
done
|
||||
for url in "${EXTRA_ALLOWED_RETURNS[@]}"; do
|
||||
add_allowed_url "$url"
|
||||
done
|
||||
|
||||
if [[ ${#KRATOS_ALLOWED_RETURN_URLS[@]} -eq 0 ]]; then
|
||||
fail "KRATOS allowed return URL list is empty"
|
||||
fi
|
||||
}
|
||||
|
||||
write_output() {
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
local admin_csv dev_csv returns_json
|
||||
admin_csv="$(join_csv ADMIN_CALLBACKS)"
|
||||
dev_csv="$(join_csv DEV_CALLBACKS)"
|
||||
returns_json="$(to_json_array KRATOS_ALLOWED_RETURN_URLS)"
|
||||
|
||||
cat >"$OUTPUT_FILE" <<EOF
|
||||
# Generated by scripts/auth_config.sh
|
||||
# Do not edit manually.
|
||||
ADMINFRONT_CALLBACK_URLS=$admin_csv
|
||||
DEVFRONT_CALLBACK_URLS=$dev_csv
|
||||
KRATOS_ALLOWED_RETURN_URLS_JSON=$returns_json
|
||||
OIDC_HYDRA_URL_MATCH_MODE=$OIDC_HYDRA_URL_MATCH_MODE
|
||||
EOF
|
||||
}
|
||||
|
||||
validate_compose_wiring() {
|
||||
grep -Eq 'KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS=\$\{KRATOS_ALLOWED_RETURN_URLS_JSON' "$ROOT_DIR/compose.ory.yaml" \
|
||||
|| fail "compose.ory.yaml is not wired to KRATOS_ALLOWED_RETURN_URLS_JSON"
|
||||
grep -Eq 'ADMINFRONT_CALLBACK_URLS=\$\{ADMINFRONT_CALLBACK_URLS' "$ROOT_DIR/compose.ory.yaml" \
|
||||
|| fail "compose.ory.yaml is not wired to ADMINFRONT_CALLBACK_URLS"
|
||||
grep -Eq 'DEVFRONT_CALLBACK_URLS=\$\{DEVFRONT_CALLBACK_URLS' "$ROOT_DIR/compose.ory.yaml" \
|
||||
|| fail "compose.ory.yaml is not wired to DEVFRONT_CALLBACK_URLS"
|
||||
}
|
||||
|
||||
verify_runtime_hydra_clients() {
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
warn "docker command is unavailable; runtime hydra verification skipped"
|
||||
return
|
||||
fi
|
||||
|
||||
if ! docker ps --format '{{.Names}}' 2>/dev/null | grep -q '^ory_hydra$'; then
|
||||
warn "ory_hydra is not running; runtime hydra verification skipped"
|
||||
return
|
||||
fi
|
||||
|
||||
local admin_info dev_info
|
||||
if ! admin_info="$(docker exec ory_hydra hydra clients get --endpoint http://hydra:4445 adminfront 2>/dev/null)"; then
|
||||
fail "failed to read hydra client 'adminfront' from running container"
|
||||
fi
|
||||
if ! dev_info="$(docker exec ory_hydra hydra clients get --endpoint http://hydra:4445 devfront 2>/dev/null)"; then
|
||||
fail "failed to read hydra client 'devfront' from running container"
|
||||
fi
|
||||
|
||||
for callback in "${ADMIN_CALLBACKS[@]}"; do
|
||||
if ! grep -Fq "$callback" <<<"$admin_info"; then
|
||||
fail "adminfront hydra client does not include callback: $callback"
|
||||
fi
|
||||
done
|
||||
for callback in "${DEV_CALLBACKS[@]}"; do
|
||||
if ! grep -Fq "$callback" <<<"$dev_info"; then
|
||||
fail "devfront hydra client does not include callback: $callback"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
run_validation() {
|
||||
collect_values
|
||||
validate_callback_group "ADMINFRONT_CALLBACK_URLS" "/auth/callback" "${ADMIN_CALLBACKS[@]}"
|
||||
validate_callback_group "DEVFRONT_CALLBACK_URLS" "/callback" "${DEV_CALLBACKS[@]}"
|
||||
validate_gateway_mapping
|
||||
build_allowed_return_urls
|
||||
}
|
||||
|
||||
print_summary() {
|
||||
echo "[auth-config] mode: $MODE"
|
||||
echo "[auth-config] hydra_url_match_mode: $OIDC_HYDRA_URL_MATCH_MODE"
|
||||
echo "[auth-config] admin_callbacks: $(join_csv ADMIN_CALLBACKS)"
|
||||
echo "[auth-config] dev_callbacks: $(join_csv DEV_CALLBACKS)"
|
||||
echo "[auth-config] kratos_allowed_return_urls_count: ${#KRATOS_ALLOWED_RETURN_URLS[@]}"
|
||||
|
||||
if [[ ${#WARNINGS[@]} -gt 0 ]]; then
|
||||
for message in "${WARNINGS[@]}"; do
|
||||
echo "[auth-config] WARN: $message"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
case "$MODE" in
|
||||
build)
|
||||
run_validation
|
||||
write_output
|
||||
print_summary
|
||||
echo "[auth-config] wrote: $OUTPUT_FILE"
|
||||
;;
|
||||
validate)
|
||||
run_validation
|
||||
print_summary
|
||||
;;
|
||||
verify)
|
||||
run_validation
|
||||
validate_compose_wiring
|
||||
verify_runtime_hydra_clients
|
||||
print_summary
|
||||
echo "[auth-config] compose wiring verified"
|
||||
;;
|
||||
*)
|
||||
fail "Unsupported mode: $MODE (supported: build|validate|verify)"
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user