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

8.3 KiB
Raw Blame History

설계서: 외부 VM HTTPS 및 직접 포트 차단 (#547)

상태: Approved 작성: [AI] Architect · 최종수정: 2026-06-28 추적성 — Redmine: #547 · 관련 ADR: 없음 · 구현 파일: vpd-backoffice/src/main/resources/application.yml, SecurityConfig.java, scripts/deploy-backoffice-vm.sh, scripts/configure-backoffice-https-vm.sh, deploy/vpd-backoffice/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 로그인 응답의 JSESSIONIDSecure, 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. 아키텍처 개요

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/vpd-backoffice/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 변경 승인.