diff --git a/aws-ses-setup.md b/aws-ses-setup.md index 5da7b7a..e79bf30 100644 --- a/aws-ses-setup.md +++ b/aws-ses-setup.md @@ -36,6 +36,20 @@ AWS SES(Simple Email Service)를 사용하여 서버에서 메일을 발송하 --- +## 이메일 인증 (SPF, DKIM, DMARC) + +> DMARC 준수를 위해 SPF와 DKIM 모두 설정 필요. 두 가지 중 하나라도 통과하면 DMARC 통과. + +### 인증 방식 비교 + +| 방식 | 역할 | 검증 대상 | +|------|------|----------| +| **SPF** | 발신 서버 허용 목록 | MAIL FROM 도메인 | +| **DKIM** | 메일 서명 검증 | 메일 헤더 서명 | +| **DMARC** | SPF/DKIM 정책 통합 | From 도메인 정렬 | + +--- + ## DNS 설정 (Cloudflare) ### anvil.it.com @@ -45,6 +59,16 @@ AWS SES(Simple Email Service)를 사용하여 서버에서 메일을 발송하 |------|------|---------| | TXT | `_amazonses.anvil.it.com` | `0cuw9v32N+aeFiNlTh2Poxglgzf3BlmFRjVOjeLEdy4=` | +**TXT 레코드 (SPF)** ⭐ 신규 +| Type | Name | Content | +|------|------|---------| +| TXT | `anvil.it.com` | `v=spf1 include:amazonses.com ~all` | + +**TXT 레코드 (DMARC)** ⭐ 신규 +| Type | Name | Content | +|------|------|---------| +| TXT | `_dmarc.anvil.it.com` | `v=DMARC1; p=quarantine; rua=mailto:dmarc@anvil.it.com; pct=100` | + **CNAME 레코드 (DKIM)** | Type | Name | Target | |------|------|--------| @@ -59,6 +83,16 @@ AWS SES(Simple Email Service)를 사용하여 서버에서 메일을 발송하 |------|------|---------| | TXT | `_amazonses.ironclad.it.com` | `C+RMHyLd/U2H5WSCu8L2avRn8NmuwEll0xxYjTyvoEY=` | +**TXT 레코드 (SPF)** ⭐ 신규 +| Type | Name | Content | +|------|------|---------| +| TXT | `ironclad.it.com` | `v=spf1 include:amazonses.com ~all` | + +**TXT 레코드 (DMARC)** ⭐ 신규 +| Type | Name | Content | +|------|------|---------| +| TXT | `_dmarc.ironclad.it.com` | `v=DMARC1; p=quarantine; rua=mailto:dmarc@ironclad.it.com; pct=100` | + **CNAME 레코드 (DKIM)** | Type | Name | Target | |------|------|--------| @@ -66,6 +100,14 @@ AWS SES(Simple Email Service)를 사용하여 서버에서 메일을 발송하 | CNAME | `qm7d7qgkdikpcbrbgo7bmgqfmuulrbah._domainkey` | `qm7d7qgkdikpcbrbgo7bmgqfmuulrbah.dkim.amazonses.com` | | CNAME | `tu6oey5tktoqub753cxpayhhwrlzcskk._domainkey` | `tu6oey5tktoqub753cxpayhhwrlzcskk.dkim.amazonses.com` | +### DMARC 정책 옵션 + +| 정책 | 설명 | 권장 단계 | +|------|------|----------| +| `p=none` | 모니터링만 (조치 없음) | 초기 테스트 | +| `p=quarantine` | 스팸함으로 이동 | 중간 단계 | +| `p=reject` | 수신 거부 | 최종 단계 | + --- ## IAM 권한 설정 @@ -255,10 +297,72 @@ aws ses get-identity-dkim-attributes --identities newdomain.com --- +## 반송/불만 처리 (SNS 연동) + +> 반송(Bounce)과 불만(Complaint) 처리는 SES 평판 유지에 필수 + +### SNS 토픽 생성 + +```bash +# 반송 알림용 토픽 +aws sns create-topic --name ses-bounces --region ap-northeast-2 + +# 불만 알림용 토픽 +aws sns create-topic --name ses-complaints --region ap-northeast-2 +``` + +### SES 알림 설정 + +```bash +# 반송 알림 연결 +aws ses set-identity-notification-topic \ + --identity anvil.it.com \ + --notification-type Bounce \ + --sns-topic arn:aws:sns:ap-northeast-2:ACCOUNT_ID:ses-bounces + +# 불만 알림 연결 +aws ses set-identity-notification-topic \ + --identity anvil.it.com \ + --notification-type Complaint \ + --sns-topic arn:aws:sns:ap-northeast-2:ACCOUNT_ID:ses-complaints +``` + +### 반송 유형 + +| 유형 | 설명 | 조치 | +|------|------|------| +| **Hard Bounce** | 영구적 실패 (주소 없음) | 즉시 목록에서 제거 | +| **Soft Bounce** | 일시적 실패 (용량 초과) | 재시도 후 제거 | +| **Complaint** | 스팸 신고 | 즉시 목록에서 제거 | + +### 권장 임계값 + +- **반송률**: 5% 미만 유지 (10% 초과 시 계정 정지 위험) +- **불만률**: 0.1% 미만 유지 + +--- + +## 인증 검증 명령어 + +```bash +# SPF 레코드 확인 +dig TXT anvil.it.com +short + +# DMARC 레코드 확인 +dig TXT _dmarc.anvil.it.com +short + +# DKIM 레코드 확인 +dig CNAME dgcehnldehfmfgpvrrbc6drwasiibhnp._domainkey.anvil.it.com +short +``` + +--- + ## 참고 사항 - **프로덕션 모드**: 수신자 제한 없음 -- **DKIM**: 메일 신뢰도 향상 (스팸 방지) +- **DKIM**: 메일 서명으로 위변조 방지 +- **SPF**: 허용된 발신 서버 목록 정의 +- **DMARC**: SPF/DKIM 정책 통합 및 리포트 수신 - **발송 한도**: 필요시 AWS에 증가 요청 가능 - **비용**: $0.10 / 1,000통 diff --git a/gitea-setup.md b/gitea-setup.md index 09e5e4b..9a8a2c0 100644 --- a/gitea-setup.md +++ b/gitea-setup.md @@ -82,7 +82,12 @@ GITEA_ROOT_URL=https://your-domain.com GITEA_HTTP_PORT=3000 GITEA_SSH_PORT=2222 -# Security (automatically generated) +# Security - 파일 기반 Secret 권장 (환경변수 노출 방지) +# docker-compose에서 secrets 사용 시: +# GITEA__security__SECRET_KEY__FILE=/run/secrets/gitea_secret_key +# GITEA__security__INTERNAL_TOKEN__FILE=/run/secrets/gitea_internal_token + +# 환경변수 방식 (개발용) GITEA_SECRET_KEY=your_secret_key GITEA_INTERNAL_TOKEN=your_internal_token @@ -95,6 +100,75 @@ GITEA_ADMIN_EMAIL=admin@your-domain.com GITEA_ADMIN_PASSWORD=secure_password ``` +### 보안 강화 설정 (app.ini) + +프로덕션 환경에서 아래 설정을 `app.ini` 또는 환경변수로 추가: + +```ini +[security] +; 비밀번호 정책 강화 +MIN_PASSWORD_LENGTH = 10 +PASSWORD_COMPLEXITY = lower,upper,digit +PASSWORD_HASH_ALGO = argon2 + +; Git Hooks 비활성화 (보안 강화) +DISABLE_GIT_HOOKS = true + +; 리버스 프록시 신뢰 설정 +REVERSE_PROXY_TRUSTED_PROXIES = 127.0.0.0/8,::1/128 + +; Secret 파일 기반 관리 (권장) +SECRET_KEY_URI = file:/etc/gitea/secret_key +INTERNAL_TOKEN_URI = file:/etc/gitea/internal_token + +[service] +; 회원가입 비활성화 (필요시) +DISABLE_REGISTRATION = true +; 로그인 필수 +REQUIRE_SIGNIN_VIEW = true +; 이메일 비공개 기본값 +DEFAULT_KEEP_EMAIL_PRIVATE = true + +[repository.signing] +; 커밋 서명 설정 +SIGNING_KEY = default +INITIAL_COMMIT = always +``` + +### Docker Secrets 사용 (프로덕션 권장) + +```yaml +# docker-compose.yml +services: + gitea: + image: gitea/gitea:latest-rootless + environment: + - GITEA__security__SECRET_KEY__FILE=/run/secrets/gitea_secret_key + - GITEA__security__INTERNAL_TOKEN__FILE=/run/secrets/gitea_internal_token + - GITEA__database__PASSWD__FILE=/run/secrets/db_password + secrets: + - gitea_secret_key + - gitea_internal_token + - db_password + +secrets: + gitea_secret_key: + file: ./secrets/secret_key + gitea_internal_token: + file: ./secrets/internal_token + db_password: + file: ./secrets/db_password +``` + +```bash +# Secret 파일 생성 +mkdir -p secrets +openssl rand -base64 32 > secrets/secret_key +openssl rand -base64 32 > secrets/internal_token +openssl rand -base64 24 > secrets/db_password +chmod 600 secrets/* +``` + ### Advanced Configuration For advanced settings, modify: diff --git a/n8n-setup-guide.md b/n8n-setup-guide.md index e7f131a..c42b215 100644 --- a/n8n-setup-guide.md +++ b/n8n-setup-guide.md @@ -20,14 +20,16 @@ graph TD ### 1. n8n 설치 및 실행 ```bash -# Docker Compose로 실행 (권장) -docker run -it --rm --name n8n -p 5678:5678 n8nio/n8n +# Docker Compose로 실행 (권장) - 공식 이미지 사용 +docker run -it --rm --name n8n -p 5678:5678 docker.n8n.io/n8nio/n8n # 또는 npm으로 설치 npm install n8n -g n8n start ``` +> **주의**: `n8nio/n8n` 대신 공식 레지스트리 `docker.n8n.io/n8nio/n8n` 사용 권장 + ### 2. Credentials 설정 n8n에서 다음 Credentials를 생성해야 합니다: @@ -223,39 +225,93 @@ curl -X POST "http://localhost:5678/webhook/cf-tunnel" \ ## 🚀 프로덕션 배포 -### 1. Docker Compose 설정 +### 1. Docker Compose 설정 (보안 강화) ```yaml version: '3.8' services: n8n: - image: n8nio/n8n + image: docker.n8n.io/n8nio/n8n ports: - - "5678:5678" + - "127.0.0.1:5678:5678" environment: + # 기본 설정 - N8N_HOST=0.0.0.0 - N8N_PORT=5678 - N8N_PROTOCOL=https + - NODE_ENV=production + # 보안 설정 (필수) + - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY} # openssl rand -hex 32 로 생성 + - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true + # 인증 설정 - N8N_BASIC_AUTH_ACTIVE=true - N8N_BASIC_AUTH_USER=admin - - N8N_BASIC_AUTH_PASSWORD=secure-password + - N8N_BASIC_AUTH_PASSWORD=${N8N_ADMIN_PASSWORD} + # 보안 강화 옵션 + - N8N_PUBLIC_API_DISABLED=true # Public API 비활성화 + - N8N_RUNNERS_ENABLED=true # Task Runner 활성화 + - N8N_PROXY_HOPS=1 # 리버스 프록시 사용 시 + # Webhook 설정 + - WEBHOOK_URL=https://n8n.yourdomain.com/ + # 타임존 + - GENERIC_TIMEZONE=Asia/Seoul + - TZ=Asia/Seoul volumes: - n8n_data:/home/node/.n8n + - ./local-files:/files restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:5678/healthz"] + interval: 30s + timeout: 10s + retries: 3 volumes: n8n_data: ``` -### 2. 리버스 프록시 설정 +### 환경변수 파일 (.env) +```bash +# 암호화 키 생성 (최초 1회) +openssl rand -hex 32 + +# .env 파일 예시 +N8N_ENCRYPTION_KEY=your_generated_key_here +N8N_ADMIN_PASSWORD=secure_password_here +``` + +> **중요**: `N8N_ENCRYPTION_KEY`는 DB에 저장되는 credential을 암호화합니다. 분실 시 모든 credential 재설정 필요. + +### 2. 리버스 프록시 설정 (보안 헤더 포함) ```nginx server { listen 80; server_name n8n.yourdomain.com; - + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name n8n.yourdomain.com; + + ssl_certificate /etc/letsencrypt/live/n8n.yourdomain.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/n8n.yourdomain.com/privkey.pem; + + # 보안 헤더 + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + location / { - proxy_pass http://localhost:5678; + proxy_pass http://127.0.0.1:5678; + 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 Connection ""; + proxy_buffering off; + proxy_read_timeout 300s; } } ```