feat(infra): HTTPS(TLS) 스캐폴딩 + 프록시 헤더 처리
- Spring prod: server.forward-headers-strategy=framework — nginx의 X-Forwarded-Proto를 신뢰해 프록시 뒤에서도 request.isSecure()=true → Secure 쿠키·https 리다이렉트 정상화. - frontend/nginx-tls.conf: 443 TLS 종료 + 80→443 리다이렉트(+ /api 프록시). - infra/docker-compose.tls.yml: base와 함께 쓰는 HTTPS 오버레이(443 매핑·인증서 볼륨· ACS_PUBLIC_BASE_URL/ACS_COOKIE_SECURE). infra/certs/.gitignore로 인증서·키 커밋 제외. - README: HTTPS 적용 절차(인증서 준비→.env→compose 오버레이) 갱신. 검증: forward-headers=framework + cookie.secure=true로 기동 후 X-Forwarded-Proto=https 유무 대조 — 헤더 있으면 XSRF-TOKEN·JSESSIONID 모두 Secure, 없으면 XSRF Secure 미부여(프록시 프로토콜 연동 확인). 자체서명 인증서 생성 확인. 전체 컨테이너 TLS e2e는 Docker 데몬 기동 시 별도 검증 필요. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
25
README.md
25
README.md
@@ -80,13 +80,26 @@ docker compose logs -f app # 기동/마이그레이션 로그
|
|||||||
1. 서버에서 메시지 API 도달 확인: `nc -vz 210.104.132.59 8000`
|
1. 서버에서 메시지 API 도달 확인: `nc -vz 210.104.132.59 8000`
|
||||||
2. `ACS_SMS_PROVIDER=hanbank`, `ACS_PUBLIC_BASE_URL`=외부 접속 URL 로 설정 후 재기동
|
2. `ACS_SMS_PROVIDER=hanbank`, `ACS_PUBLIC_BASE_URL`=외부 접속 URL 로 설정 후 재기동
|
||||||
|
|
||||||
### HTTPS / 카메라 스캔 (선택, 접속 방식에 따라 필요)
|
### HTTPS 적용 (TLS 종료는 nginx가 담당)
|
||||||
- 출입콘솔의 **웹캠 QR 스캔**은 secure context(HTTPS 또는 localhost)에서만 동작한다.
|
브라우저 → **nginx(web, TLS 종료)** → 내부 http → Spring(app) 구조. 앱은 `server.forward-headers-strategy=framework`
|
||||||
서버를 `http://내부IP`로 접속하면 카메라가 차단된다.
|
(prod에 반영됨)로 nginx의 `X-Forwarded-Proto`를 신뢰해 Secure 쿠키·https 리다이렉트를 처리한다.
|
||||||
|
|
||||||
|
TLS 오버레이가 준비돼 있다 — 인증서만 넣으면 된다:
|
||||||
|
1. **인증서 준비** — `infra/certs/{fullchain.pem,privkey.pem}` (사내 CA 발급 / 공인 CA / 테스트용 자체서명).
|
||||||
|
자체서명 예: `openssl req -x509 -newkey rsa:2048 -nodes -days 825 -keyout infra/certs/privkey.pem -out infra/certs/fullchain.pem -subj "/CN=<host>"`
|
||||||
|
(`infra/certs/*.pem`은 `.gitignore`로 커밋 제외.)
|
||||||
|
2. **`.env`**: `ACS_PUBLIC_BASE_URL=https://<도메인>`, `ACS_COOKIE_SECURE=true`.
|
||||||
|
3. **기동** (base + TLS 오버레이):
|
||||||
|
```bash
|
||||||
|
cd infra
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.tls.yml up -d --build
|
||||||
|
```
|
||||||
|
→ nginx가 `443` TLS 종료 + `80→443` 리다이렉트. `frontend/nginx-tls.conf` 사용.
|
||||||
|
|
||||||
|
`ACS_COOKIE_SECURE=true`는 위처럼 **실제 HTTPS로 서비스될 때만** 켠다(평문 http에서 켜면 쿠키 미전송 → 로그인 불가).
|
||||||
|
|
||||||
|
- 출입콘솔의 **웹캠 QR 스캔**은 secure context(HTTPS 또는 localhost)에서만 동작 → HTTPS면 `http://내부IP`에서도 카메라 사용 가능.
|
||||||
- 방문자 공개 링크(`/pass/{token}`)도 HTTPS 도메인이면 휴대폰에서 안전하게 열린다.
|
- 방문자 공개 링크(`/pass/{token}`)도 HTTPS 도메인이면 휴대폰에서 안전하게 열린다.
|
||||||
- 적용하려면: 사내 도메인/인증서 확보 → `frontend/nginx.conf`에 443 TLS server 블록 추가,
|
|
||||||
`docker-compose.yml` web 서비스에 `"443:443"` 매핑 + 인증서 볼륨 마운트,
|
|
||||||
`ACS_PUBLIC_BASE_URL`을 `https://...`로 설정. (도메인/인증서 확정 후 진행)
|
|
||||||
|
|
||||||
### 사내망 빌드 참고
|
### 사내망 빌드 참고
|
||||||
`docker compose build`는 컨테이너 안에서 npm/maven 의존성을 받는다. dev 서버는 공개망 직접
|
`docker compose build`는 컨테이너 안에서 npm/maven 의존성을 받는다. dev 서버는 공개망 직접
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
spring.application.name=acs
|
spring.application.name=acs
|
||||||
server.port=8080
|
server.port=8080
|
||||||
|
|
||||||
|
# ===== Reverse proxy (nginx terminates TLS) =====
|
||||||
|
# Honor X-Forwarded-Proto/For from the nginx front so request.isSecure() is true behind
|
||||||
|
# TLS termination — makes the CSRF XSRF-TOKEN cookie Secure and redirects use https.
|
||||||
|
server.forward-headers-strategy=framework
|
||||||
|
|
||||||
# ===== Session cookie hardening =====
|
# ===== Session cookie hardening =====
|
||||||
# SameSite=Lax complements the CSRF token defense.
|
# SameSite=Lax complements the CSRF token defense.
|
||||||
# Secure=true means the cookie is only sent over HTTPS — enable it (ACS_COOKIE_SECURE=true)
|
# Secure=true means the cookie is only sent over HTTPS — enable it (ACS_COOKIE_SECURE=true)
|
||||||
|
|||||||
43
frontend/nginx-tls.conf
Normal file
43
frontend/nginx-tls.conf
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# HTTPS variant of nginx.conf — used by the docker-compose.tls.yml overlay.
|
||||||
|
# nginx terminates TLS and reverse-proxies /api to the backend over the internal network.
|
||||||
|
# Mount a certificate at /etc/nginx/certs/{fullchain.pem,privkey.pem}.
|
||||||
|
|
||||||
|
# Redirect all plain HTTP to HTTPS.
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
http2 on;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
ssl_certificate /etc/nginx/certs/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/certs/privkey.pem;
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# SPA client-side routing: fall back to index.html
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Proxy API calls to the backend service (session cookie preserved).
|
||||||
|
# X-Forwarded-Proto=https lets the app (server.forward-headers-strategy=framework)
|
||||||
|
# know the original request was secure → Secure cookies + https redirects.
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://app:8080;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_cookie_path / /;
|
||||||
|
}
|
||||||
|
}
|
||||||
5
infra/certs/.gitignore
vendored
Normal file
5
infra/certs/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# TLS certificates/keys are environment-specific secrets — never commit them.
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
*.crt
|
||||||
|
!.gitignore
|
||||||
21
infra/docker-compose.tls.yml
Normal file
21
infra/docker-compose.tls.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# HTTPS overlay. Use together with the base compose file:
|
||||||
|
# docker compose -f docker-compose.yml -f docker-compose.tls.yml up -d --build
|
||||||
|
#
|
||||||
|
# Requires a TLS certificate under ./certs/{fullchain.pem,privkey.pem}.
|
||||||
|
# For a quick internal/test cert (self-signed):
|
||||||
|
# openssl req -x509 -newkey rsa:2048 -nodes -days 825 \
|
||||||
|
# -keyout certs/privkey.pem -out certs/fullchain.pem -subj "/CN=<your-host>"
|
||||||
|
# For production, drop in the cert issued for your internal domain (corporate CA / public CA).
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
ports:
|
||||||
|
- "${WEB_TLS_PORT:-443}:443" # base file already maps WEB_PORT:80 (used for the redirect)
|
||||||
|
volumes:
|
||||||
|
- ./certs:/etc/nginx/certs:ro
|
||||||
|
- ../frontend/nginx-tls.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
|
|
||||||
|
app:
|
||||||
|
environment:
|
||||||
|
# The public link in the pass email/SMS + the secure-cookie switch.
|
||||||
|
ACS_PUBLIC_BASE_URL: ${ACS_PUBLIC_BASE_URL:-https://localhost}
|
||||||
|
ACS_COOKIE_SECURE: "true"
|
||||||
Reference in New Issue
Block a user