Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eacc0e86c9 | ||
|
|
ac912cb354 | ||
|
|
e4cee2a06d | ||
|
|
006b405ad5 | ||
|
|
8539d1bf5b | ||
|
|
3473875d9a | ||
|
|
f077e5038e | ||
|
|
e4eb7e54f7 |
@@ -0,0 +1,6 @@
|
||||
# Keycloak receives HTTP only from the trusted reverse proxy.
|
||||
KC_HTTP_ENABLED=true
|
||||
KC_PROXY_HEADERS=xforwarded
|
||||
KC_HOSTNAME=https://auth.example.test
|
||||
KC_HOSTNAME_STRICT=true
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
server {
|
||||
listen 8080;
|
||||
server_name auth.example.test;
|
||||
|
||||
location / {
|
||||
proxy_pass http://keycloak:8080;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Port 443;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
auth.example.test {
|
||||
tls /etc/tls/tls.crt /etc/tls/tls.key
|
||||
|
||||
reverse_proxy keycloak:8080 {
|
||||
header_up Host {host}
|
||||
header_up X-Forwarded-Host {host}
|
||||
header_up X-Forwarded-Port 443
|
||||
header_up X-Forwarded-Proto https
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
events {}
|
||||
|
||||
http {
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name auth.example.test;
|
||||
|
||||
ssl_certificate /etc/tls/tls.crt;
|
||||
ssl_certificate_key /etc/tls/tls.key;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
|
||||
location / {
|
||||
proxy_pass http://keycloak:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Port 443;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
tunnel: 00000000-0000-0000-0000-000000000000
|
||||
credentials-file: /etc/cloudflared/00000000-0000-0000-0000-000000000000.json
|
||||
|
||||
ingress:
|
||||
- hostname: auth.example.test
|
||||
service: http://reverse-proxy:8080
|
||||
- service: http_status:404
|
||||
@@ -0,0 +1,24 @@
|
||||
# Google redirect URI policy
|
||||
|
||||
Google에 등록하는 redirect URI는 애플리케이션 SPA callback이 아니라 Keycloak
|
||||
broker endpoint다.
|
||||
|
||||
```text
|
||||
https://auth.example.test/realms/keycloak-patterns/broker/google/endpoint
|
||||
```
|
||||
|
||||
규칙:
|
||||
|
||||
- production URI는 HTTPS와 고정된 public Keycloak origin을 사용한다.
|
||||
- wildcard, path prefix, 임시 tunnel hostname을 production OAuth client에
|
||||
등록하지 않는다.
|
||||
- 개발·스테이징·운영은 Google OAuth client를 분리한다.
|
||||
- reverse proxy가 있더라도 Google이 보는 URI와 Keycloak이 생성하는 URI가
|
||||
byte-for-byte 같아야 한다.
|
||||
- `configure-google-idp.sh`가 출력하는 URI를 Google Console의 Authorized
|
||||
redirect URI와 대조한다.
|
||||
|
||||
```sh
|
||||
PUBLIC_KEYCLOAK_URL=https://auth.example.test \
|
||||
./scripts/verify-google-redirect-uri-policy.sh
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# HTTPS termination: nginx or Caddy
|
||||
|
||||
두 예제 모두 public `443`에서 TLS를 종료하고 private Docker network의
|
||||
`keycloak:8080`으로 전달한다. Keycloak 쪽 설정은
|
||||
`deploy/reverse-proxy/keycloak.env.example`의 hostname/proxy contract를
|
||||
같이 사용한다.
|
||||
|
||||
- nginx: 인증서 배포·갱신을 운영자가 담당할 때 적합하다.
|
||||
- Caddy: ACME를 통한 인증서 수명주기를 proxy가 담당하게 할 때 간단하다.
|
||||
- 둘을 동시에 production entry point로 띄우지 않는다.
|
||||
- 인증서와 private key는 repository 또는 image에 포함하지 않는다.
|
||||
- HTTP challenge/redirect 및 방화벽의 80/443 허용은 배포 환경에서 별도로
|
||||
결정한다.
|
||||
|
||||
검증 스크립트는 임시 자체 서명 인증서를 만들고 두 vendor image에서 설정을
|
||||
각각 validate한 뒤 임시 파일을 제거한다.
|
||||
|
||||
```sh
|
||||
./scripts/verify-https-termination-config.sh
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
# Public HTTPS domain for broker callbacks
|
||||
|
||||
Google brokering을 반복 테스트할 때는 Cloudflare **named tunnel + 관리
|
||||
도메인**을 기본 profile로 사용한다. `trycloudflare.com` quick tunnel과
|
||||
임의 ngrok URL은 일회성 데모용이며 고정 callback으로 간주하지 않는다.
|
||||
|
||||
설정 순서:
|
||||
|
||||
1. `cloudflared tunnel login`
|
||||
2. `cloudflared tunnel create keycloak-patterns`
|
||||
3. 예제 config의 tunnel UUID와 credentials path를 실제 값으로 교체
|
||||
4. `cloudflared tunnel route dns keycloak-patterns auth.example.test`
|
||||
5. `cloudflared tunnel run keycloak-patterns`
|
||||
6. Keycloak `KC_HOSTNAME`과 Google redirect URI를 같은 public host로 설정
|
||||
|
||||
컨테이너 안의 `127.0.0.1`은 cloudflared 컨테이너 자신이므로 origin에는
|
||||
`reverse-proxy:8080` 같은 Compose service DNS를 사용한다. 마지막 catch-all
|
||||
ingress는 알 수 없는 hostname을 404로 끝낸다.
|
||||
|
||||
실 tunnel 생성과 DNS 변경에는 사용자 소유 계정·도메인이 필요하므로 자동
|
||||
검증은 ingress 파일의 구조까지만 수행한다.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Reverse proxy headers
|
||||
|
||||
TLS를 reverse proxy에서 종료하면 Keycloak은 브라우저가 사용한 외부 origin을
|
||||
정확히 알아야 한다. 배포 예제는 다음 계약을 함께 적용한다.
|
||||
|
||||
- nginx는 `Host`, `X-Forwarded-Host`, `X-Forwarded-Port`,
|
||||
`X-Forwarded-Proto`, `X-Forwarded-For`를 덮어쓴다.
|
||||
- Keycloak은 `KC_PROXY_HEADERS=xforwarded`로 그 헤더 형식을 명시한다.
|
||||
- `KC_HOSTNAME`은 외부 HTTPS URL로 고정하고 strict hostname 검증을 켠다.
|
||||
- Keycloak의 8080 포트는 public으로 publish하지 않고 proxy network에서만
|
||||
접근시킨다. 신뢰되지 않은 클라이언트가 forwarded header를 직접 넣을 수
|
||||
있으면 안 된다.
|
||||
|
||||
`scripts/verify-reverse-proxy-headers.sh`는 양쪽 설정의 짝과 nginx 구문을
|
||||
검증한다.
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
realm="${KEYCLOAK_REALM:-keycloak-patterns}"
|
||||
public_keycloak_url="${PUBLIC_KEYCLOAK_URL:-https://auth.example.test}"
|
||||
expected="$public_keycloak_url/realms/$realm/broker/google/endpoint"
|
||||
|
||||
case "$public_keycloak_url" in
|
||||
https://*) ;;
|
||||
*)
|
||||
echo "PUBLIC_KEYCLOAK_URL must use https outside the local mock environment" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$public_keycloak_url" in
|
||||
*\** | */)
|
||||
echo "PUBLIC_KEYCLOAK_URL must be an exact origin without wildcard/trailing slash" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
test "$expected" = "https://auth.example.test/realms/keycloak-patterns/broker/google/endpoint"
|
||||
echo "Google redirect URI policy verified: $expected"
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
test_dir="$(mktemp -d)"
|
||||
cleanup() {
|
||||
rm -rf "$test_dir"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
openssl req -x509 -newkey rsa:2048 -nodes -days 1 \
|
||||
-subj "/CN=auth.example.test" \
|
||||
-keyout "$test_dir/tls.key" \
|
||||
-out "$test_dir/tls.crt" >/dev/null 2>&1
|
||||
|
||||
docker run --rm \
|
||||
--add-host keycloak:127.0.0.1 \
|
||||
-v "$PWD/deploy/tls/nginx.conf:/etc/nginx/nginx.conf:ro" \
|
||||
-v "$test_dir:/etc/tls:ro" \
|
||||
nginx:1.29-alpine nginx -t
|
||||
|
||||
docker run --rm \
|
||||
--add-host keycloak:127.0.0.1 \
|
||||
-v "$PWD/deploy/tls/Caddyfile:/etc/caddy/Caddyfile:ro" \
|
||||
-v "$test_dir:/etc/tls:ro" \
|
||||
caddy:2.10.2-alpine caddy validate --config /etc/caddy/Caddyfile
|
||||
|
||||
echo "nginx and Caddy HTTPS termination configurations verified"
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
config=deploy/tunnel/cloudflared-config.yml
|
||||
grep -q '^tunnel: [0-9a-f-]*$' "$config"
|
||||
grep -q '^ - hostname: auth.example.test$' "$config"
|
||||
grep -q '^ service: http://reverse-proxy:8080$' "$config"
|
||||
grep -q '^ - service: http_status:404$' "$config"
|
||||
|
||||
docker run --rm \
|
||||
-v "$PWD/$config:/etc/cloudflared/config.yml:ro" \
|
||||
cloudflare/cloudflared:2025.6.1 \
|
||||
tunnel --config /etc/cloudflared/config.yml ingress validate
|
||||
|
||||
echo "Cloudflare named-tunnel ingress configuration verified"
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
config=deploy/reverse-proxy/nginx-keycloak.conf
|
||||
env_file=deploy/reverse-proxy/keycloak.env.example
|
||||
|
||||
grep -q 'proxy_set_header X-Forwarded-Host' "$config"
|
||||
grep -q 'proxy_set_header X-Forwarded-Port 443' "$config"
|
||||
grep -q 'proxy_set_header X-Forwarded-Proto https' "$config"
|
||||
grep -q '^KC_PROXY_HEADERS=xforwarded$' "$env_file"
|
||||
grep -q '^KC_HOSTNAME=https://' "$env_file"
|
||||
|
||||
docker run --rm \
|
||||
--add-host keycloak:127.0.0.1 \
|
||||
-v "$PWD/$config:/etc/nginx/conf.d/default.conf:ro" \
|
||||
nginx:1.29-alpine nginx -t
|
||||
|
||||
echo "Reverse-proxy header and Keycloak hostname contracts verified"
|
||||
Reference in New Issue
Block a user