diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..b4f0438 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,39 @@ +{ + "mcpServers": { + "context7": { + "command": "npx", + "args": ["-y", "@upstash/context7-mcp@latest"] + }, + "cloudflare": { + "command": "npx", + "args": ["mcp-remote", "https://docs.mcp.cloudflare.com/sse"] + }, + "aws-api-mcp-server": { + "command": "uvx", + "args": ["awslabs.aws-api-mcp-server@latest"], + "env": { + "AWS_REGION": "ap-northeast-2" + } + }, + "aws-knowledge-mcp-server": { + "command": "uvx", + "args": [ + "mcp-proxy", + "--transport", + "streamablehttp", + "https://knowledge-mcp.global.api.aws" + ] + }, + "aws-serverless-mcp-server": { + "command": "uvx", + "args": [ + "awslabs.aws-serverless-mcp-server@latest", + "--allow-write", + "--allow-sensitive-data-access" + ], + "env": { + "AWS_REGION": "ap-northeast-2" + } + } + } +} diff --git a/CROWDSEC-WAF-INTEGRATION.md b/CROWDSEC-WAF-INTEGRATION.md new file mode 100644 index 0000000..483c460 --- /dev/null +++ b/CROWDSEC-WAF-INTEGRATION.md @@ -0,0 +1,309 @@ +# CrowdSec - AWS WAF 실시간 통합 가이드 + +## 📋 개요 + +이 문서는 CrowdSec와 AWS WAF 간의 실시간 IP 차단 통합 시스템 구현 가이드입니다. CrowdSec가 탐지한 악성 IP를 AWS Lambda를 통해 자동으로 WAF IP Set에 추가/제거하여 실시간 보안을 제공합니다. + +## 🏗️ 아키텍처 + +``` +CrowdSec Container → Webhook Notification → API Gateway → Lambda Function → AWS WAF IP Set → CloudFront Distribution +``` + +### 주요 구성 요소 + +1. **CrowdSec Container** (Incus) + - 보안 이벤트 감지 및 분석 + - Nginx Proxy Manager 로그 모니터링 + - 웹훅 알림 전송 + +2. **AWS API Gateway** + - 웹훅 엔드포인트 제공 + - Lambda 함수 트리거 + +3. **AWS Lambda Function** + - CrowdSec Alert 데이터 처리 + - WAF IP Set 업데이트 수행 + +4. **AWS WAF v2** + - IP Set 기반 차단 규칙 + - CloudFront 배포 보호 + +## 🚀 구현 단계 + +### 1. 인프라 배포 + +```bash +# OpenTofu 초기화 +tofu init + +# 인프라 배포 +tofu apply -auto-approve +``` + +**배포되는 리소스:** +- Lambda 함수 및 IAM 역할 +- API Gateway 웹훅 엔드포인트 +- WAF v2 IP Set 및 Web ACL +- CloudWatch 로그 그룹 + +### 2. CrowdSec 컨테이너 설정 + +#### 컨테이너 생성 및 설정 +```bash +# 새 컨테이너 생성 +incus launch ubuntu:24.04 crowdsec + +# CrowdSec 설치 +incus exec crowdsec -- apt update +incus exec crowdsec -- curl -s https://install.crowdsec.net | sh + +# Nginx Proxy Manager 컬렉션 설치 +incus exec crowdsec -- cscli collections install crowdsecurity/nginx-proxy-manager +incus exec crowdsec -- systemctl reload crowdsec +``` + +#### 웹훅 알림 설정 +```bash +# 알림 설정 파일 생성 +incus exec crowdsec -- tee /etc/crowdsec/notifications/aws-waf.yaml << 'EOF' +type: http +name: aws-waf +log_level: info +url: https://8zdmpjfnhh.execute-api.us-east-1.amazonaws.com/dev/webhook +method: POST +headers: + Content-Type: application/json + User-Agent: CrowdSec/1.7.0 +timeout: 10s +format: | + {{ .|toJson }} +EOF + +# 프로필 설정 +incus exec crowdsec -- tee /etc/crowdsec/profiles.yaml << 'EOF' +name: aws_waf_profile +filters: + - Alert.Remediation == true && Alert.GetScenario() startsWith "crowdsecurity/" +notifications: + - aws-waf +on_success: break +EOF + +# CrowdSec 재시작 +incus exec crowdsec -- systemctl restart crowdsec +``` + +### 3. Lambda 함수 코드 + +**파일 위치:** `/Users/kaffa/Projects/was-cf/lambda-crowdsec-waf.py` + +주요 기능: +- CrowdSec Alert JSON 구조 처리 +- IP 주소 추출 및 검증 +- WAF IP Set 업데이트 (추가/제거) +- 에러 처리 및 로깅 + +핵심 처리 로직: +```python +# CrowdSec Alert 구조 처리 (toJson 형식) +if isinstance(webhook_data, list): + # Alert 배열인 경우 + for alert in webhook_data: + if 'decisions' in alert and alert['decisions']: + for decision in alert['decisions']: + # Source IP 추출 + source_ip = alert.get('source', {}).get('ip', '') + # Decision 정보 포함 + decision['source_ip'] = source_ip + decision['alert_uuid'] = alert.get('uuid', '') + decisions.append(decision) +``` + +## 📊 WAF 규칙 우선순위 + +AWS WAF Web ACL 규칙 순서 (낮은 번호가 높은 우선순위): + +1. **Priority 1: BlockedIPsRule** ← CrowdSec IP 차단 (최고 우선순위) - `aws-cf-dev-blocked-ips` IP Set 사용 +2. **Priority 2: RateLimitRule** ← 일반 레이트 제한 (10,000 req/5min) - 전체 IP 대상 +3. **Priority 3: AWSManagedRulesCommonRuleSet** ← AWS 관리형 공통 규칙 +4. **Priority 4: AWSManagedRulesKnownBadInputsRuleSet** ← AWS 관리형 악성 입력 규칙 + +**⚠️ 참고:** 이전에 있던 `suspicious_ips` IP Set과 `SuspiciousIPsRateLimit` 규칙은 현재 Lambda 기반 실시간 통합으로 대체되어 제거되었습니다. + +## 🧪 테스트 및 검증 + +### 1. 웹훅 엔드포인트 테스트 +```bash +curl -X POST https://8zdmpjfnhh.execute-api.us-east-1.amazonaws.com/dev/webhook \ + -H "Content-Type: application/json" \ + -H "User-Agent: CrowdSec/1.7.0" \ + -d '[{ + "uuid": "test-uuid-123", + "machine_id": "test-machine", + "created_at": "2025-09-09T02:50:00Z", + "scenario": "test/security-scan", + "source": {"ip": "192.168.1.100"}, + "decisions": [{ + "value": "192.168.1.100", + "type": "ban", + "action": "add", + "scope": "ip" + }] + }]' +``` + +**예상 응답:** +```json +{ + "success": true, + "message": "WAF IP Set updated successfully", + "ips_added": ["192.168.1.100"], + "ips_removed": [], + "total_ips": 1 +} +``` + +### 2. WAF IP Set 확인 +```bash +# IP Set 내용 조회 +aws wafv2 get-ip-set \ + --scope CLOUDFRONT \ + --id c43ff364-f3e2-43c7-8462-8fae20599d8d \ + --name aws-cf-dev-blocked-ips \ + --region us-east-1 \ + --query 'IPSet.Addresses' +``` + +### 3. CrowdSec 알림 테스트 +```bash +# CrowdSec 알림 테스트 +incus exec crowdsec -- cscli notifications test aws-waf +``` + +## 📈 설치된 보안 컬렉션 + +### Nginx Proxy Manager 컬렉션 +- **컬렉션:** `crowdsecurity/nginx-proxy-manager` +- **파서:** `crowdsecurity/nginx-proxy-manager-logs` +- **HTTP 로그:** `crowdsecurity/http-logs` + +### 주요 보안 시나리오 (40+ 개) +- **Web 공격:** XSS, SQLi, Path Traversal 프로빙 +- **CVE 취약점:** Log4j, Spring4Shell, Apache 등 +- **관리자 인터페이스:** 무차별 대입 공격 감지 +- **백도어 시도:** 악성 스크립트 업로드 감지 +- **WordPress:** 스캔 및 무차별 대입 공격 +- **네트워크:** 오픈 프록시, 크롤링 감지 + +## 🔧 리소스 정보 + +### AWS 리소스 +```bash +# CloudFront Distribution ID +E1XR8P4ENGP8RU + +# WAF Web ACL ID +d21d84c1-edb9-40af-9cdd-27f42f09c499 + +# WAF IP Set ID +c43ff364-f3e2-43c7-8462-8fae20599d8d + +# Lambda Function +aws-cf-dev-crowdsec-waf-updater + +# Webhook URL +https://8zdmpjfnhh.execute-api.us-east-1.amazonaws.com/dev/webhook +``` + +### Terraform 출력값 +```bash +tofu output +``` + +## 🔍 모니터링 및 로그 + +### Lambda 함수 로그 +```bash +# CloudWatch 로그 확인 +aws logs tail /aws/lambda/aws-cf-dev-crowdsec-waf-updater --follow +``` + +### CrowdSec 로그 +```bash +# CrowdSec 서비스 로그 +incus exec crowdsec -- journalctl -u crowdsec -f + +# 차단 결정 목록 +incus exec crowdsec -- cscli decisions list +``` + +### WAF 메트릭 +- CloudWatch에서 WAF 차단 메트릭 확인 +- `BlockedIPsRule` 메트릭 모니터링 + +## 🛠️ 문제 해결 + +### 일반적인 문제 + +1. **웹훅 연결 실패** + - 네트워크 연결 확인 + - API Gateway URL 유효성 검사 + - CrowdSec 알림 설정 재검토 + +2. **Lambda 함수 오류** + - CloudWatch 로그 확인 + - IAM 권한 검증 + - WAF IP Set 접근 권한 확인 + +3. **IP가 차단되지 않음** + - WAF 규칙 우선순위 확인 + - CloudFront 배포와 WAF 연결 상태 + - IP Set 업데이트 상태 확인 + +### 디버깅 명령어 +```bash +# CrowdSec 상태 확인 +incus exec crowdsec -- cscli metrics + +# 알림 플러그인 상태 +incus exec crowdsec -- cscli notifications list + +# Lambda 함수 테스트 +aws lambda invoke --function-name aws-cf-dev-crowdsec-waf-updater response.json + +# WAF 규칙 확인 +aws wafv2 get-web-acl --scope CLOUDFRONT --id d21d84c1-edb9-40af-9cdd-27f42f09c499 --name aws-cf-dev-waf --region us-east-1 +``` + +## 🚀 확장 가능성 + +1. **다중 환경 지원** + - 개발/스테이징/프로덕션 환경별 WAF 연결 + - 환경별 IP Set 분리 + +2. **고급 알림** + - Slack/Discord 통합 + - 이메일 알림 추가 + - 대시보드 연동 + +3. **IP 화이트리스트** + - 신뢰할 수 있는 IP 자동 제외 + - 지역별 IP 필터링 + +4. **자동 해제** + - 시간 기반 자동 차단 해제 + - 위험도 기반 차단 기간 조정 + +## 📝 참고 자료 + +- [CrowdSec Documentation](https://docs.crowdsec.net/) +- [AWS WAF Developer Guide](https://docs.aws.amazon.com/waf/) +- [Lambda Developer Guide](https://docs.aws.amazon.com/lambda/) +- [CrowdSec Hub](https://hub.crowdsec.net/) + +--- + +**구현 완료일:** 2025-09-09 +**마지막 업데이트:** 2025-09-09 +**작성자:** Claude Code SuperClaude Framework \ No newline at end of file diff --git a/main.tf b/main.tf index 228c261..6b62be0 100644 --- a/main.tf +++ b/main.tf @@ -35,8 +35,8 @@ resource "aws_cloudfront_distribution" "main" { comment = "CloudFront distribution for ${var.project_name} - ${var.environment}" default_root_object = "index.html" - # Aliases (custom domain names) - Disabled for default certificate - # aliases = var.cloudfront_aliases + # Aliases (custom domain names) - Enable when ACM certificate is available + aliases = var.create_acm_certificate ? var.cloudfront_aliases : null # Default cache behavior default_cache_behavior { @@ -74,9 +74,12 @@ resource "aws_cloudfront_distribution" "main" { } } - # SSL/TLS certificate - Use CloudFront default certificate (temporary) + # SSL/TLS certificate - Use ACM certificate when available viewer_certificate { - cloudfront_default_certificate = true + acm_certificate_arn = var.create_acm_certificate ? aws_acm_certificate.main[0].arn : null + ssl_support_method = var.create_acm_certificate ? "sni-only" : null + minimum_protocol_version = var.create_acm_certificate ? "TLSv1.2_2021" : null + cloudfront_default_certificate = var.create_acm_certificate ? false : true } # Custom error responses diff --git a/outputs.tf b/outputs.tf index fb9dd8c..8212f7b 100644 --- a/outputs.tf +++ b/outputs.tf @@ -67,6 +67,17 @@ output "waf_web_acl_id" { value = var.enable_waf ? aws_wafv2_web_acl.cloudfront[0].id : null } +output "waf_blocked_ips_set_arn" { + description = "WAF Blocked IPs IP Set ARN" + value = var.enable_waf ? aws_wafv2_ip_set.blocked_ips[0].arn : null +} + +output "waf_blocked_ips_set_id" { + description = "WAF Blocked IPs IP Set ID" + value = var.enable_waf ? aws_wafv2_ip_set.blocked_ips[0].id : null +} + + # Origin Information output "origin_domain" { description = "Origin domain name" @@ -110,4 +121,16 @@ output "domain_validation_records" { value = dvo.resource_record_value } ] -} \ No newline at end of file +} + +# CrowdSec Integration Information +output "crowdsec_sync_command" { + description = "Command to synchronize CrowdSec with WAF" + value = "incus exec crowdsec -- /usr/local/bin/crowdsec-waf-sync sync" +} + +output "waf_ip_set_id" { + description = "WAF IP Set ID for CrowdSec integration" + value = var.enable_waf ? aws_wafv2_ip_set.blocked_ips[0].id : null +} + diff --git a/security.tf b/security.tf index 0a3bba0..d792ba4 100644 --- a/security.tf +++ b/security.tf @@ -83,6 +83,24 @@ resource "aws_security_group" "web" { } } +# IP Set for blocked IPs +resource "aws_wafv2_ip_set" "blocked_ips" { + provider = aws.us_east_1 # CloudFront WAF must be in us-east-1 + count = var.enable_waf ? 1 : 0 + name = "${var.project_name}-${var.environment}-blocked-ips" + description = "IP addresses to be blocked" + scope = "CLOUDFRONT" + ip_address_version = "IPV4" + + # Start with empty set - IPs can be added via AWS CLI or Console + addresses = [] + + tags = { + Name = "${var.project_name}-${var.environment}-blocked-ips" + } +} + + # WAF Web ACL for CloudFront (optional) resource "aws_wafv2_web_acl" "cloudfront" { provider = aws.us_east_1 # CloudFront WAF must be in us-east-1 @@ -94,10 +112,32 @@ resource "aws_wafv2_web_acl" "cloudfront" { allow {} } - # Rate limiting rule + # Block IPs in blocked list + rule { + name = "BlockedIPsRule" + priority = 1 + + action { + block {} + } + + statement { + ip_set_reference_statement { + arn = aws_wafv2_ip_set.blocked_ips[0].arn + } + } + + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "BlockedIPsRule" + sampled_requests_enabled = true + } + } + + # Rate limiting rule (original setting restored) rule { name = "RateLimitRule" - priority = 1 + priority = 2 action { block {} @@ -120,7 +160,7 @@ resource "aws_wafv2_web_acl" "cloudfront" { # AWS Managed Rules - Core Rule Set rule { name = "AWSManagedRulesCommonRuleSet" - priority = 2 + priority = 3 override_action { none {} @@ -143,7 +183,7 @@ resource "aws_wafv2_web_acl" "cloudfront" { # AWS Managed Rules - Known Bad Inputs rule { name = "AWSManagedRulesKnownBadInputsRuleSet" - priority = 3 + priority = 4 override_action { none {} diff --git a/sync-crowdsec-to-waf.sh b/sync-crowdsec-to-waf.sh new file mode 100755 index 0000000..051cba5 --- /dev/null +++ b/sync-crowdsec-to-waf.sh @@ -0,0 +1,90 @@ +#!/bin/bash + +# CrowdSec에서 차단된 IP를 AWS WAF BlockedIPsRule에 동기화하는 스크립트 + +# 설정 +WAF_IP_SET_ID="c43ff364-f3e2-43c7-8462-8fae20599d8d" +WAF_IP_SET_NAME="aws-cf-dev-blocked-ips" +REGION="us-east-1" +SCOPE="CLOUDFRONT" + +# CrowdSec에서 현재 차단된 IP 목록 가져오기 +get_crowdsec_banned_ips() { + # CrowdSec CLI를 통해 차단된 IP 목록 가져오기 + if command -v cscli &> /dev/null; then + # 로컬 CrowdSec에서 차단 결정 가져오기 + cscli decisions list -o json | jq -r '.[] | select(.type=="ban") | .value' | sort -u + else + # Incus 컨테이너 내부의 CrowdSec에 접근 + incus exec crowdsec -- cscli decisions list -o json | jq -r '.[] | select(.type=="ban") | .value' | sort -u + fi +} + +# AWS WAF IP Set의 현재 IP 목록 가져오기 +get_waf_current_ips() { + aws wafv2 get-ip-set \ + --scope $SCOPE \ + --id $WAF_IP_SET_ID \ + --name $WAF_IP_SET_NAME \ + --region $REGION \ + --query 'IPSet.Addresses[]' \ + --output text | tr '\t' '\n' | sed 's|/32||g' | sort -u +} + +# WAF IP Set 업데이트 +update_waf_ip_set() { + local ip_list="$1" + + # 현재 lock token 가져오기 + LOCK_TOKEN=$(aws wafv2 get-ip-set \ + --scope $SCOPE \ + --id $WAF_IP_SET_ID \ + --name $WAF_IP_SET_NAME \ + --region $REGION \ + --query 'LockToken' \ + --output text) + + # IP 주소를 CIDR 형식으로 변환 (단일 IP는 /32 추가) + local cidr_list="" + if [ -n "$ip_list" ]; then + cidr_list=$(echo "$ip_list" | grep -v '^$' | sed 's|$|/32|g' | paste -sd, -) + fi + + echo "Updating WAF IP Set with IPs: $cidr_list" + + # WAF IP Set 업데이트 + aws wafv2 update-ip-set \ + --scope $SCOPE \ + --id $WAF_IP_SET_ID \ + --name $WAF_IP_SET_NAME \ + --addresses $cidr_list \ + --lock-token $LOCK_TOKEN \ + --region $REGION +} + +# 메인 동기화 로직 +sync_ips() { + echo "$(date): Starting CrowdSec to WAF IP sync..." + + # CrowdSec에서 차단된 IP 가져오기 + crowdsec_ips=$(get_crowdsec_banned_ips) + echo "CrowdSec banned IPs: $(echo "$crowdsec_ips" | wc -l) IPs" + + # 현재 WAF IP Set의 IP 가져오기 + waf_ips=$(get_waf_current_ips) + echo "Current WAF IPs: $(echo "$waf_ips" | wc -l) IPs" + + # IP 목록 비교 + if [ "$crowdsec_ips" != "$waf_ips" ]; then + echo "IP lists differ, updating WAF..." + update_waf_ip_set "$crowdsec_ips" + echo "WAF IP Set updated successfully!" + else + echo "IP lists are already in sync" + fi + + echo "$(date): Sync completed" +} + +# 실행 +sync_ips \ No newline at end of file diff --git a/variables.tf b/variables.tf index ed38111..379a3dc 100644 --- a/variables.tf +++ b/variables.tf @@ -156,4 +156,5 @@ variable "cloudfront_logs_prefix" { description = "Prefix for CloudFront logs in S3" type = string default = "cloudfront-logs/" -} \ No newline at end of file +} +