Files
vpd-permission-poc/docs/design/547-vm-https-hardening/README.md

126 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 설계서: 외부 VM HTTPS 및 직접 포트 차단 (#547)
> **상태**: Approved
> **작성**: [AI] Architect · **최종수정**: 2026-06-28
> **추적성** — Redmine: #547 · 관련 ADR: 없음
> · 구현 파일: `src/main/resources/application.yml`, `SecurityConfig.java`, `scripts/deploy-backoffice-vm.sh`, `scripts/configure-backoffice-https-vm.sh`, `deploy/caddy/Caddyfile.template`
> · 테스트: `TransportSecurityTest.java`, `scripts/test-backoffice-https-config.sh`
## 1. 목적 (Why)
외부 VM에서 관리자 자격증명과 세션 쿠키가 평문 HTTP로 전송되고 애플리케이션 포트 `8082`가 직접 노출되는 경로를 제거한다.
## 2. 범위 (Scope)
- **포함**: 공개 DNS 이름 또는 통제 가능한 public IPv4 기반 HTTPS, HTTP→HTTPS 전환, Caddy TLS termination과 인증서 자동 갱신, 애플리케이션 루프백 바인딩, forwarded header 처리, Secure/HttpOnly/SameSite 세션 쿠키, HSTS, 배포·검증·롤백 절차.
- **제외**: OCI NSG와 VM firewalld의 실제 변경, DNS 레코드 생성, Caddy 패키지 설치, 기본 `admin/admin` 제거(#548), CSP/CDN 공급망 강화(#549).
## 3. 인수조건 (Acceptance Criteria)
- [ ] 외부 HTTP 요청은 HTTPS로 전환되고 로그인 폼은 HTTPS에서만 제출된다.
- [ ] 애플리케이션은 VM의 `127.0.0.1:8082`에만 바인딩되어 외부 직접 접속이 실패한다.
- [ ] HTTPS 로그인 응답의 `JSESSIONID``Secure`, `HttpOnly`, `SameSite=Lax`가 있다.
- [ ] HTTPS 응답에 1년 HSTS가 있고, 전달된 scheme/host가 redirect 생성에 반영된다.
- [ ] 공개 프록시는 클라이언트가 보낸 `X-Forwarded-*`를 신뢰하지 않고 직접 다시 설정한다.
- [ ] 인증서 갱신·기동·헬스체크·롤백 절차가 자동화 스크립트와 런북에 있다.
## 4. 컨텍스트 & 제약
- Caddy automatic HTTPS는 외부 80/443 접근과 지속 가능한 인증서 저장소를 전제로 인증서를 발급·갱신하고 HTTP를 HTTPS로 전환한다.
- Lets Encrypt public IPv4 인증서는 2026년부터 정식 지원되며 `shortlived` ACME profile과 약 6일 수명을 사용한다. Caddy가 profile과 갱신을 지속적으로 관리한다.
- FQDN이 있으면 일반 공개 인증서를 우선하고, DNS가 준비되지 않은 단일 VM은 통제 중인 고정 public IPv4 인증서를 사용할 수 있다. Caddy 로컬 CA 인증서는 운영에 사용하지 않는다.
- 애플리케이션의 forwarded header 처리는 연결 원본 CIDR을 자체 판별하지 않는다. 따라서 `127.0.0.1` 바인딩을 보안 경계로 삼아 같은 VM의 Caddy만 접근시킨다.
- Caddy는 인터넷에 직접 연결되는 첫 프록시다. CDN/로드밸런서를 추가할 때만 명시적인 `trusted_proxies` CIDR 검토가 필요하다.
- 방화벽/NSG 변경은 서비스 영향과 잠금 위험이 있어 스크립트가 자동 수행하지 않는다.
## 5. 아키텍처 개요
```text
Internet client
├─ HTTP :80 ───────> Caddy ── 308 HTTPS redirect
└─ HTTPS :443 ─TLS─> Caddy ── X-Forwarded-* 재생성
└─ HTTP 127.0.0.1:8082
Spring Boot
├─ require-https
├─ HSTS
└─ Secure/HttpOnly/SameSite cookie
Internet client ── HTTP :8082 ──X (loopback bind + firewalld/NSG deny)
```
- `deploy/caddy/Caddyfile.template`: TLS/redirect/HSTS/reverse proxy의 선언적 설정.
- `scripts/configure-backoffice-https-vm.sh`: 입력 검증, 원격 설정 검증·백업·적용·확인.
- `scripts/deploy-backoffice-vm.sh`: 앱 배포 시 운영 보안 환경값을 강제하고 루프백 헬스체크.
- Spring 설정: proxy가 전달한 HTTPS scheme을 인식하고 직접 HTTP 요청을 거부한다.
- 순수 경계 검증(호스트명, 이메일, 포트, 템플릿 렌더링)은 로컬 dry-run 테스트로 검증하고, SSH/systemd/ACME는 명시적 운영 단계에서 수행한다.
## 6. 설정 모델
| 설정 | 운영값 | 검증 |
|---|---|---|
| `BACKOFFICE_BIND_ADDRESS` | `127.0.0.1` | HTTPS 배포 스크립트가 고정 |
| `BACKOFFICE_FORWARD_HEADERS_STRATEGY` | `framework` | 배포 환경 override |
| `BACKOFFICE_REQUIRE_HTTPS` | `true` | 배포 환경 override |
| `BACKOFFICE_SESSION_COOKIE_SECURE` | `true` | 배포 환경 override |
| public host | 소유한 FQDN 또는 고정 public IPv4 | FQDN/IP 문법과 expected address 일치 검증 |
| TLS email | ACME 운영 이메일 | 공백/개행/비정상 형식 거부 |
| app port | `8082` | 165535 숫자 |
로컬 개발은 기존 HTTP 흐름을 보존하기 위해 HTTPS 강제와 Secure cookie의 기본값을 `false`로 둔다. VM 배포 산출물에서만 안전한 운영값으로 덮어쓴다.
## 7. 함수/스크립트 명세
| 함수/스크립트 | 책임 | 입력 | 출력 | 실패 |
|---|---|---|---|---|
| `securityFilterChain` | 인증, HTTPS channel, HSTS 구성 | 보안 설정 | servlet filter chain | 구성 오류 시 기동 실패 |
| `deploy-backoffice-vm.sh` | jar/env/wallet 배포와 내부 헬스체크 | SSH 대상, 포트 | 루프백 앱 | SSH/build/start 실패 |
| `configure-backoffice-https-vm.sh` | Caddy 설정 적용·검증 | FQDN, TLS email, SSH 대상 | HTTPS endpoint | DNS/Caddy/sudo/TLS 검증 실패 |
| `test-backoffice-https-config.sh` | 입력 거부와 Caddy 템플릿 검증 | 로컬 저장소 | PASS/FAIL | assertion 실패 |
## 8. 적용 흐름
1. 소유한 FQDN의 A/AAAA 레코드를 VM에 연결하거나, 고정 public IPv4를 endpoint로 확정한다.
2. 개별 승인으로 NSG/firewalld에서 80/443을 열고 8082 ingress를 제거한다.
3. VM에 공식 Caddy 패키지와 systemd service가 준비됐는지 확인한다.
4. 앱을 재배포해 `127.0.0.1:8082`, forwarded headers, HTTPS 강제, Secure cookie를 활성화한다.
5. Caddy 설정을 문법 검증한 후 기존 설정을 timestamp backup하고 원자적으로 설치한다.
6. HTTP redirect, TLS 신뢰, HSTS, cookie attributes, `:8082` 차단을 외부에서 검증한다.
## 9. 엣지케이스 & 에러 처리
- FQDN DNS가 아직 VM을 가리키지 않으면 Caddy 설정을 적용하지 않는다.
- IPv4 endpoint는 expected address와 같아야 하며 `shortlived` profile 없는 설정을 허용하지 않는다.
- wildcard hostname은 이 단일 VM 스크립트에서 거부한다.
- Caddy 설정 검증 또는 reload가 실패하면 직전 Caddyfile을 복원하고 reload한다.
- 외부 HTTPS 검증이 실패해도 앱의 루프백 프로세스는 유지하며 로그와 복구 명령을 출력한다.
- Caddy 장애 시 `8082`를 공개하는 방식으로 우회하지 않는다. 직전 Caddyfile 복원 또는 앱/프록시 동시 롤백만 허용한다.
## 10. 테스트 계획
- Maven 통합 테스트:
- 직접 HTTP 요청이 HTTPS로 redirect되는지 확인.
- `X-Forwarded-Proto=https`, `X-Forwarded-Host`를 적용한 요청이 정상 처리되고 HSTS가 있는지 확인.
- 운영 환경변수가 Secure/HttpOnly/SameSite cookie 설정에 결합되는지 확인.
- 셸 테스트:
- FQDN/email/port 입력 검증.
- 렌더링된 Caddyfile에 public host, HSTS, 루프백 upstream이 있는지 확인.
- 배포 dry-run이 루프백/HTTPS 운영 override를 표시하는지 확인.
- 운영 검증:
- `curl -I http://<fqdn>/login`
- `curl -I https://<fqdn>/login`
- HTTPS GET의 `Set-Cookie`
- 외부 `curl http://<fqdn>:8082/login` 실패
- `systemctl is-active caddy`와 Caddy journal의 갱신 오류 확인.
## 11. 리스크 & 대안 검토
- Caddy를 선택한 이유는 HTTP redirect, ACME 발급/갱신, reverse proxy를 하나의 짧은 선언으로 운영할 수 있기 때문이다.
- Nginx+Certbot은 가능하지만 인증서 갱신 hook과 설정 검증 경로가 분리되어 이 단일 VM PoC의 운영 표면이 더 넓다.
- Spring Boot 직접 TLS는 애플리케이션 재기동과 인증서 교체가 결합되고 80→443 처리 및 권한 분리가 불리해 제외한다.
## 12. 미해결 질문 (배포 전 필수 입력)
- 사용할 공개 FQDN 또는 `130.162.134.59` 고정 IP와 ACME 알림 이메일.
- DNS 변경 완료 여부와 80/443 NSG/firewalld 변경 승인.