diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml new file mode 100644 index 0000000..dd2b55f --- /dev/null +++ b/docker-compose-prod.yml @@ -0,0 +1,56 @@ +services: + nginx: + image: nginx:alpine + container_name: kamco-cd-nginx + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro + - ./nginx/ssl:/etc/nginx/ssl:ro + - nginx-logs:/var/log/nginx + depends_on: + kamco-changedetection-api: + condition: service_healthy + networks: + - kamco-cds + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "--no-check-certificate", "https://localhost/monitor/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + + kamco-changedetection-api: + build: + context: . + dockerfile: Dockerfile + image: kamco-cd-training-api:${IMAGE_TAG:-latest} + container_name: kamco-cd-training-api + expose: + - "8080" + environment: + - SPRING_PROFILES_ACTIVE=prod + - TZ=Asia/Seoul + volumes: + - ./app/model_output:/app/model-outputs + - ./app/train_dataset:/app/train-dataset + - /var/run/docker.sock:/var/run/docker.sock + networks: + - kamco-cds + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/monitor/health"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 40s + +networks: + kamco-cds: + external: true + +volumes: + nginx-logs: + driver: local diff --git a/nginx/SSL_SETUP.md b/nginx/SSL_SETUP.md new file mode 100644 index 0000000..5b3a22a --- /dev/null +++ b/nginx/SSL_SETUP.md @@ -0,0 +1,397 @@ +# SSL 사설 인증서 설정 가이드 (RedHat 9.6) + +## 개요 + +이 문서는 RedHat 9.6 환경에서 `https://api.train-kamco.com` 및 `https://train-kamco.com` 도메인을 위한 100년 유효한 사설 SSL 인증서 설정 방법을 설명합니다. + +## 디렉토리 구조 + +``` +nginx/ +├── nginx.conf # Nginx 설정 파일 +├── ssl/ +│ ├── openssl.cnf # OpenSSL 설정 파일 (SAN 포함) +│ ├── train-kamco.com.crt # 사설 SSL 인증서 (멀티 도메인) +│ └── train-kamco.com.key # 개인 키 (비공개) +└── SSL_SETUP.md # 이 문서 +``` + +## 인증서 정보 + +- **도메인**: api.train-kamco.com, train-kamco.com (멀티 도메인) +- **유효 기간**: 100년 (36500일) +- **알고리즘**: RSA 4096-bit +- **CN (Common Name)**: api.train-kamco.com +- **SAN (Subject Alternative Names)**: api.train-kamco.com, train-kamco.com + +## 사설 SSL 인증서 생성 (이미 생성됨) + +인증서가 이미 생성되어 있습니다. 재생성이 필요한 경우 아래 단계를 따르세요. + +### 1. OpenSSL 설정 파일 생성 + +```bash +cd /path/to/kamco-train-api + +cat > nginx/ssl/openssl.cnf << 'EOF' +[req] +default_bits = 4096 +prompt = no +default_md = sha256 +distinguished_name = dn +req_extensions = v3_req + +[dn] +C=KR +ST=Seoul +L=Seoul +O=KAMCO +OU=Training +CN=api.train-kamco.com + +[v3_req] +subjectAltName = @alt_names + +[alt_names] +DNS.1 = api.train-kamco.com +DNS.2 = train-kamco.com +EOF +``` + +### 2. SSL 인증서 및 개인 키 생성 + +```bash +# nginx/ssl 디렉토리 생성 (없는 경우) +mkdir -p nginx/ssl +chmod 700 nginx/ssl + +# 인증서 및 개인 키 생성 (100년 유효) +openssl req -new -x509 -newkey rsa:4096 -sha256 -nodes \ + -keyout nginx/ssl/train-kamco.com.key \ + -out nginx/ssl/train-kamco.com.crt \ + -days 36500 \ + -config nginx/ssl/openssl.cnf \ + -extensions v3_req + +# 파일 권한 설정 +chmod 600 nginx/ssl/train-kamco.com.key +chmod 644 nginx/ssl/train-kamco.com.crt +``` + +### 3. 인증서 검증 + +```bash +# 인증서 정보 확인 +openssl x509 -in nginx/ssl/train-kamco.com.crt -text -noout + +# 유효 기간 확인 +openssl x509 -in nginx/ssl/train-kamco.com.crt -text -noout | grep -A2 "Validity" + +# SAN (멀티 도메인) 확인 +openssl x509 -in nginx/ssl/train-kamco.com.crt -text -noout | grep -A1 "Subject Alternative Name" + +# CN 확인 +openssl x509 -in nginx/ssl/train-kamco.com.crt -noout -subject + +# 개인 키 확인 +openssl rsa -in nginx/ssl/train-kamco.com.key -check +``` + +**예상 결과**: +``` +X509v3 Subject Alternative Name: + DNS:api.train-kamco.com, DNS:train-kamco.com + +Validity + Not Before: Mar 2 23:39:XX 2026 GMT + Not After : Feb 6 23:39:XX 2126 GMT +``` + +## /etc/hosts 설정 (RedHat 9.6) + +### 1. hosts 파일에 도메인 추가 + +```bash +# root 권한으로 실행 +echo "127.0.0.1 api.train-kamco.com train-kamco.com" | sudo tee -a /etc/hosts + +# 확인 +cat /etc/hosts | grep train-kamco +``` + +**예상 결과**: +``` +127.0.0.1 api.train-kamco.com train-kamco.com +``` + +### 2. 도메인 확인 + +```bash +# ping 테스트 +ping -c 2 api.train-kamco.com +ping -c 2 train-kamco.com +``` + +## Docker Compose 배포 + +### 1. 기존 컨테이너 중지 (실행 중인 경우) + +```bash +cd /path/to/kamco-train-api +docker-compose -f docker-compose-prod.yml down +``` + +### 2. Production 환경 실행 + +```bash +# IMAGE_TAG 환경 변수 설정 (선택사항) +export IMAGE_TAG=latest + +# Docker Compose 실행 +docker-compose -f docker-compose-prod.yml up -d + +# 컨테이너 상태 확인 +docker-compose -f docker-compose-prod.yml ps +``` + +### 3. 로그 확인 + +```bash +# Nginx 로그 +docker logs kamco-cd-nginx + +# API 로그 +docker logs kamco-cd-training-api + +# 실시간 로그 확인 +docker-compose -f docker-compose-prod.yml logs -f +``` + +## HTTPS 접속 테스트 + +### 1. HTTP → HTTPS 리다이렉트 테스트 + +```bash +# api.train-kamco.com +curl -I http://api.train-kamco.com + +# train-kamco.com +curl -I http://train-kamco.com + +# 예상 결과: 301 Moved Permanently +# Location: https://api.train-kamco.com/ 또는 https://train-kamco.com/ +``` + +### 2. HTTPS 헬스체크 (-k: 사설 인증서 경고 무시) + +```bash +# api.train-kamco.com +curl -k https://api.train-kamco.com/monitor/health + +# train-kamco.com +curl -k https://train-kamco.com/monitor/health + +# 예상 결과: {"status":"UP","components":{...}} +``` + +### 3. SSL 인증서 확인 + +```bash +# api.train-kamco.com +openssl s_client -connect api.train-kamco.com:443 -showcerts + +# train-kamco.com +openssl s_client -connect train-kamco.com:443 -showcerts + +# CN 및 SAN 확인 +``` + +### 4. 브라우저 테스트 + +브라우저에서 다음 URL에 접속: + +- `https://api.train-kamco.com/monitor/health` +- `https://train-kamco.com/monitor/health` + +**주의**: 사설 인증서이므로 "안전하지 않음" 경고가 표시됩니다. +- **Chrome/Edge**: "고급" → "계속 진행" 클릭 +- **Firefox**: "위험 감수 및 계속" 클릭 + +## 브라우저에서 사설 인증서 신뢰 설정 (선택사항) + +사설 인증서를 브라우저에 등록하면 경고 없이 접속 가능합니다. + +### Chrome/Edge (RedHat Desktop) + +1. `chrome://settings/certificates` 접속 +2. **Authorities** 탭 선택 +3. **Import** 클릭 +4. `nginx/ssl/train-kamco.com.crt` 선택 +5. **Trust this certificate for identifying websites** 체크 +6. **OK** 클릭 + +### Firefox + +1. `about:preferences#privacy` 접속 +2. **Certificates** → **View Certificates** 클릭 +3. **Authorities** 탭 선택 +4. **Import** 클릭 +5. `nginx/ssl/train-kamco.com.crt` 선택 +6. **Trust this CA to identify websites** 체크 +7. **OK** 클릭 + +## 방화벽 설정 (RedHat 9.6) + +### 1. 방화벽 상태 확인 + +```bash +sudo firewall-cmd --state +``` + +### 2. HTTP (80) 및 HTTPS (443) 포트 개방 + +```bash +# HTTP 포트 개방 +sudo firewall-cmd --permanent --add-port=80/tcp + +# HTTPS 포트 개방 +sudo firewall-cmd --permanent --add-port=443/tcp + +# 방화벽 재로드 +sudo firewall-cmd --reload + +# 확인 +sudo firewall-cmd --list-ports +``` + +**예상 결과**: +``` +80/tcp 443/tcp +``` + +## 보안 체크리스트 + +- [x] `train-kamco.com.key` 파일 권한이 600으로 설정됨 +- [x] ssl 디렉토리가 버전 관리에서 제외됨 (.gitignore 확인) +- [x] 두 도메인(api.train-kamco.com, train-kamco.com) 모두 SAN에 포함됨 +- [ ] 방화벽에서 80, 443 포트 개방 확인 +- [x] HSTS 헤더 활성화 확인 +- [x] TLS 1.2 이상만 허용 확인 +- [ ] /etc/hosts에 도메인 매핑 확인 + +## 트러블슈팅 + +### 인증서 관련 오류 + +**"certificate verify failed"** +```bash +# 해결: -k 플래그 사용 (사설 인증서 경고 무시) +curl -k https://api.train-kamco.com/monitor/health +``` + +**"NET::ERR_CERT_AUTHORITY_INVALID" (브라우저)** +- 정상 동작: 사설 인증서이므로 브라우저 경고는 예상된 동작입니다 +- 해결: 브라우저에 인증서 등록 (위 "브라우저에서 사설 인증서 신뢰 설정" 참조) + +### 연결 오류 + +**"Connection refused"** +```bash +# 컨테이너 상태 확인 +docker ps | grep kamco-cd + +# 포트 바인딩 확인 +docker port kamco-cd-nginx + +# 예상 결과: +# 80/tcp -> 0.0.0.0:80 +# 443/tcp -> 0.0.0.0:443 +``` + +**"502 Bad Gateway"** +```bash +# API 컨테이너 상태 확인 +docker logs kamco-cd-training-api + +# nginx → API 연결 확인 +docker exec kamco-cd-nginx wget -qO- http://kamco-changedetection-api:8080/monitor/health +``` + +**"Name or service not known" (도메인 해석 실패)** +```bash +# /etc/hosts 확인 +cat /etc/hosts | grep train-kamco + +# 없으면 추가 +echo "127.0.0.1 api.train-kamco.com train-kamco.com" | sudo tee -a /etc/hosts +``` + +### 방화벽 관련 오류 + +**외부에서 접속 안 됨** +```bash +# 방화벽 확인 +sudo firewall-cmd --list-ports + +# 포트 개방 +sudo firewall-cmd --permanent --add-port=80/tcp +sudo firewall-cmd --permanent --add-port=443/tcp +sudo firewall-cmd --reload +``` + +## 인증서 만료 및 갱신 + +### 만료 확인 + +```bash +# 인증서 만료일 확인 +openssl x509 -in nginx/ssl/train-kamco.com.crt -noout -enddate + +# 예상 결과: notAfter=Feb 6 23:39:XX 2126 GMT (100년 후) +``` + +### 갱신 방법 (필요시) + +100년 유효한 인증서이므로 갱신이 필요하지 않지만, 재생성이 필요한 경우: + +```bash +# 기존 인증서 백업 +cp nginx/ssl/train-kamco.com.crt nginx/ssl/train-kamco.com.crt.bak +cp nginx/ssl/train-kamco.com.key nginx/ssl/train-kamco.com.key.bak + +# 위의 "사설 SSL 인증서 생성" 단계 재실행 + +# nginx 재시작 +docker-compose -f docker-compose-prod.yml restart nginx +``` + +## 주의사항 + +1. **사설 인증서 경고**: 브라우저에서 "안전하지 않음" 경고가 표시됩니다. 프로덕션 환경에서는 **공인 인증서(Let's Encrypt, GlobalSign 등) 사용을 권장**합니다. + +2. **포트 80/443**: Docker가 자동으로 처리하지만, 이미 사용 중인 프로세스가 있으면 충돌할 수 있습니다. + ```bash + # 포트 사용 확인 + sudo lsof -i :80 + sudo lsof -i :443 + ``` + +3. **대용량 파일 업로드**: `client_max_body_size`를 10GB로 설정했으므로, 서버 메모리 및 디스크 용량을 충분히 확보하세요. + +4. **인증서 백업**: `train-kamco.com.key` 파일은 매우 중요합니다. 안전한 곳에 백업하세요. + +5. **SELinux**: RedHat 9.6에서 SELinux가 활성화된 경우, Docker 볼륨 마운트 권한 문제가 발생할 수 있습니다. + ```bash + # SELinux 상태 확인 + getenforce + + # 필요시 permissive 모드로 변경 + sudo setenforce 0 + ``` + +## 참고 자료 + +- [OpenSSL Documentation](https://www.openssl.org/docs/) +- [Nginx SSL Configuration](https://nginx.org/en/docs/http/configuring_https_servers.html) +- [Docker Compose Documentation](https://docs.docker.com/compose/) +- [Let's Encrypt (공인 인증서)](https://letsencrypt.org/) diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000..9411202 --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,93 @@ +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # 로그 설정 + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + error_log /var/log/nginx/error.log warn; + + sendfile on; + keepalive_timeout 65; + + # 업로드 파일 크기 제한 (10GB) + client_max_body_size 10G; + + # Upstream 설정 + upstream api_backend { + server kamco-changedetection-api:8080; + } + + # HTTP → HTTPS 리다이렉트 서버 + server { + listen 80; + server_name api.train-kamco.com train-kamco.com; + + # 모든 HTTP 요청을 HTTPS로 리다이렉트 + return 301 https://$server_name$request_uri; + } + + # HTTPS 서버 설정 + server { + listen 443 ssl http2; + server_name api.train-kamco.com train-kamco.com; + + # SSL 인증서 설정 (사설 인증서 - 멀티 도메인) + ssl_certificate /etc/nginx/ssl/train-kamco.com.crt; + ssl_certificate_key /etc/nginx/ssl/train-kamco.com.key; + + # SSL 프로토콜 및 암호화 설정 + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'; + ssl_prefer_server_ciphers off; + + # SSL 세션 캐시 + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + # HSTS (HTTP Strict Transport Security) + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + + # 보안 헤더 + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + + # 프록시 설정 + location / { + proxy_pass http://api_backend; + 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_set_header X-Forwarded-Host $server_name; + + # 타임아웃 설정 (대용량 파일 업로드 지원) + proxy_connect_timeout 300s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; + + # 버퍼 설정 + proxy_buffering on; + proxy_buffer_size 4k; + proxy_buffers 8 4k; + proxy_busy_buffers_size 8k; + } + + # 헬스체크 엔드포인트 + location /monitor/health { + proxy_pass http://api_backend/monitor/health; + access_log off; + } + } +} \ No newline at end of file