한 줄 요약: WAF가 뭘 얼마나 막고 있는지 한눈에 보는 Grafana 대시보드를 만들어 AMG에 올리고, 콘솔로 손대던 Secrets Manager 주입을 Terraform으로 코드화한 날.
WAF를 ALB 앞에 세워 두긴 했는데, 정작 "지금 뭘 얼마나 막고 있나"를 볼 화면이 없었다. 그래서 WAF 트래픽을 보는 Grafana 대시보드를 만들었다. 데이터는 CloudWatch의 AWS/WAFV2 지표를 끌어다 쓰고, 화면은 AMG(Amazon Managed Grafana)에 띄웠다.
행 그룹으로 나눠 패널 17개를 깔았다. 전체 허용/차단/카운트 요청 추이, 룰별(rate-limit-auth·rate-limit-global·관리형 3종) 차단량, 차단 상위 IP 같은 것들이다. 노린 건 "rate-limit이 우리 트래픽의 과속을 잡는 층"과 "관리형 룰이 AWS 평판으로 잡는 층"을 따로 볼 수 있게 한 거다.
만들면서 두 가지를 배웠다. 먼저 WAF 지표의 차원. 룰별로 끊어 보려면 dimension이 룰 이름이 아니라 VisibilityConfig에 설정한 MetricName이라, 거기에 안 맞추면 그래프가 빈다. 그리고 AMG 배포 방식. AMG 워크스페이스가 SSO 전용이라 로컬 API 키를 못 만든다. 할 수 없이 워크스페이스 서비스계정으로 단명 토큰을 발급받아(토큰은 출력하지 않고 변수로만) HTTP API로 푸시했고, 작업이 끝나자 서비스계정을 지웠다. 푸시 전에는 라이브 대시보드를 GET으로 백업해 두고 덮어썼다. 각 패널엔 설명 툴팁(description)도 넣어 라이브에 반영했다(version 4).
dev·prod 시크릿 주입은 콘솔에서 손으로 붙여 둔 상태였다. 콘솔 수동본은 역할을 다시 만들거나 환경을 재구축하면 그대로 날아가니, Terraform modules/ecs가 소유하도록 코드화했다(커밋 97bdbd5).
원칙은 하나다. 시크릿 값은 Terraform에 두지 않는다. TF로 시크릿을 만들면 값이 state(S3)에 평문으로 남기 때문이다. 그래서 data "aws_secretsmanager_secret"으로 farmily/{env}/app의 ARN만 가져오고, 값은 Secrets Manager에만 둔다. 키 목록을 for 문으로 돌려 secrets[](valueFrom)를 만들고, 민감 키는 app_environment에서 걸러 평문 env로 새지 않게 막았다. 실행역할 정책은 count로 게이트를 걸어, 허용 ARN이 없으면 정책 자체가 안 만들어진다. 라이브 task def는 ignore_changes라 CI/CD가 굴리는 배포는 건드리지 않는다. 덕분에 새 환경을 만들거나 replace해도 평문으로 되돌아가지 않는다.
VisibilityConfig의 MetricName으로 맞춰야 한다. 안 맞으면 그래프가 빈다.AWS/WAFV2)가 저장소이고 AMG는 그걸 비추는 것이다.data로 ARN만 참조하고 값은 Secrets Manager에만 둔다.count로 게이트하면 허용 ARN이 없을 때 정책이 아예 안 생겨, 모듈을 다른 환경에 재사용해도 안전하다.data 참조로만 가는 정석을 코드에 박아 뒀다.