Terraform으로 인프라를 구성하면서 WAF도 적용해보려 했지만,
AWS의 WAF는 시작부터 월 만원 이상이 나올 수 있다.
Nginx로 자체 구현하는 건 설정이 귀찮고 관리가 번거로워서,
이번에는 Cloudflare를 사용해 보기로 했다.
Cloudflare는 무료 플랜에서도 WAF, SSL, CDN, 캐싱, Rate Limiting 등
대부분의 웹 보안에 필요한 기능을 제공하며,
무엇보다 ALB 앞단에서 프록시로 작동하기 때문에 AWS 리소스를 보완하기에 적합하다.
[사용자 브라우저]
↓ HTTPS
[Cloudflare (WAF + Proxy + SSL)]
↓ HTTPS
[AWS ALB (ACM 인증서)]
↓
[EC2, ECS, 서비스 백엔드]
기존에는 Route53 + ALB + ACM으로 HTTPS 구성만 해놓은 상태였고,
여기에 Cloudflare를 얹어서 보안과 관리 편의성을 강화하는 방식으로 진행했다.
https://cloudflare.com 가입 (구글 계정 가능)
도메인 입력 후 Free 플랜으로 시작하고
다음과 같은 네임서버(NS) 2개를 제공받는다.
lia.ns.cloudflare.com
marek.ns.cloudflare.com
AWS Route 53 콘솔 → 도메인 등록 탭에서
기존 4개의 네임서버를 모두 삭제하고, 위 두 개만 등록한다.
전파에는 최대 24시간이 걸릴 수 있지만, 보통 10분 내에 Active 상태로 전환된다.
Cloudflare → DNS 탭 → + Add Record
| 항목 | 값 |
|---|---|
| Type | CNAME |
| Name | @ (루트 도메인) |
| Target | ALB의 DNS 주소 (예: app-alb-1234.ap-northeast-2.elb.amazonaws.com) |
| Proxy | ☁️ Proxied (주황색 구름) |
A 레코드는 IP 주소가 필요하므로 ALB 같은 DNS 주소는 CNAME으로 등록해야 한다.
Cloudflare → SSL/TLS 탭 → Settings
Full(Strict) 모드로 설정한다.
이 모드는 Cloudflare ↔ ALB 구간도 HTTPS로 통신하며, ACM 인증서를 검증한다.
ACM 인증서를 발급받고 ALB에 연결한 상태라면, 문제 없이 적용된다.
Cloudflare에서 다양한 보안 기능을 무료로 제공한다.
| 항목 | 추천 값 |
|---|---|
| WAF Ruleset | OWASP 기본 활성화 |
| Rate Limiting | /login, /admin 등에 적용 |
| Bot Fight Mode | ON |
| Browser Integrity Check | ON |
| Challenge Passage | 24시간 |
| Always Use HTTPS | ON |
| Minimum TLS Version | 1.2 |
캐싱을 설정하면 서버가 일시적으로 다운돼도 정적 리소스를 사용자에게 제공할 수 있다.
Challenge Passage는 사용자의 브라우저 인증 상태를 유지하는 시간이다.
기본은 30분이지만 일반 서비스에서는 24시간 정도면 괜찮지 않을까...?
Terraform으로 ALB를 삭제했다가 다시 생성하면
ALB의 DNS 이름이 바뀐다.
이 경우 Cloudflare는 이전 레코드를 계속 바라보기 때문에 다음과 같은 에러가 발생할 수 있다:
→ 이때는 Cloudflare DNS 레코드에 새 ALB DNS 이름을 수동으로 갱신하면, 수 분 내에 정상화된다.
이걸 매번 수동으로 바꾸는 건 귀찮다.
Terraform에서 Cloudflare까지 함께 관리하면 이런 반복 작업 없이 자동화가 가능하다.
Cloudflare는 API 토큰과 Zone ID만 있으면 Terraform으로 DNS 레코드를 만들고 관리할 수 있다.
ALB를 다시 만들어도 DNS가 자동 반영되므로 훨씬 안정적이다.
Cloudflare 대시보드 → Overview 탭 → Zone ID 복사
# API Token과 Zone_id는 .tfvars 파일에 넣자.
# CloudFlare Provider 설정
provider "cloudflare" {
api_token = var.cloudflare_api_token
}
# API Token 변수
variable "cloudflare_api_token" {
type = string
}
# Zone_id 변수
variable "cloudflare_zone_id" {
description = "CloudFlare Zone ID"
type = string
}
# CloudFlare DNS 레코드 설정
resource "cloudflare_record" "alb" {
zone_id = var.cloudflare_zone_id
name = "@"
type = "CNAME"
content = aws_lb.app.dns_name # AWS쪽에서 생성된 ALB의 DNS이름
proxied = true
}
기존 레코드가 이미 있다면 삭제 후 apply 해도 되고,
terraform import로 기존 레코드를 state에 연결해도 된다. (삭제 후 apply 추천)