AWS Cloud School 13기 113일

Forever 김·2026년 6월 15일

AWS Cloud School

목록 보기
103/116

2026-06-15

Today

한 줄 요약: prod ALB에 AWS WAF(L7 방어)를 세우고, 그 과정에서 드러난 콘솔 드리프트를 전수 조사해 Terraform으로 흡수했으며, prod 평문 시크릿을 Secrets Manager로 무중단 이전했다. 인프라 보안 3건(방화벽·IaC 정합·시크릿)을 하루에 닫은 날.

  • 🛡️ AWS WAF prod ALB 적용 — rate-limit + AWS managed 룰 5개, modules/waf 신설, apply·검증
  • 🔍 콘솔 드리프트 전수조사 + terraform import — agentcore SG·S3 templates 흡수, relay(bastion) 정리, 최종 plan "No changes"
  • 📦 commit → PR #25 → 머지 (코드↔배포 동기화) + 블로그 2편(보안 2편·Terraform 13편) + 문서/타임라인/칸반 갱신
  • 🔐 prod Secrets Manager 마이그레이션 — 평문 시크릿 9개 → farmily/prod/app, task def secrets[]화, 무중단 배포 성공(prod-app:16)

Notes

1. AWS WAF — "왜 지금"에서 시작하기

prod ALB 로그에 ThinkPHP/PHP RCE 스캐너 probe(/index.php?s=..., /xmlrpc.php)가 꾸준히 들어오고 있었다. 우리 백엔드는 Spring(Java)이라 404로 무해하지만, ① 요청이 ECS까지 도달해 컴퓨트를 쓰고 ② 정찰 신호라 brute-force·flood로 번질 수 있다. ALB 앞단에 차단 계층이 0이라는 게 진짜 문제였다.

설계의 첫 단추는 위협을 계층으로 나누는 것이었다.

  • L3/L4(volumetric DDoS)Shield Standard가 자동·무료로 이미 막고 있다. 만들 리소스 없음. (aws shield get-subscription-state = INACTIVE = Advanced 미가입 = Standard로 동작 확인)
  • L7(스캐너·brute-force·flood·SQLi)AWS WAF가 우리가 세울 층.

Shield Advanced는 월 $3,000인데, 그게 주는 L7 방어는 WAF rate-limit으로 직접 커버되고 비용 보호도 우리 규모엔 효용이 없어 Standard 유지. 이 트레이드오프 자체가 면접 단골 질문이다.

룰 설계는 임의로 하지 않고 AWS Security Blog "The three most important AWS WAF rate-based rules"(Shield Response Team)를 근거로 잡았다. SRT가 꼽은 3대 룰 = ① Blanket(전역) ② URI-specific(로그인) ③ IP reputation. 우리 모듈이 정확히 이 셋을 포함한다(+ KnownBadInputs·Common 보너스). Blanket 한도 2,000/5분도 AWS 예시값 그대로.

p5  rate-limit-auth      /api/v1/auth/* 5분 100회 (scope_down)  Block
p10 rate-limit-global    전역 IP 5분 2000회                      Block
p20 AmazonIpReputation   악성/스캐너 IP                          Block
p30 KnownBadInputs       RCE/exploit 페이로드                    Block
p40 CommonRuleSet        SQLi/XSS                                Count(관측)→Block 승격

CommonRuleSet만 Count로 시작한 게 핵심 설계다 — managed 룰은 정상 요청을 공격으로 오인(false positive)할 위험이 있어, 며칠 관측 후 오탐이 없으면 common_rule_action="none"(변수 토글)으로 차단 승격한다. AWS도 "로그로 임계값 정하고 위험한 룰은 Count 카나리 후 Block"을 권장한다.

apply에서 진짜 함정을 만났다. web ACL description에 괄호 ()를 넣었더니 ValidationException — WAFv2 description은 정규식으로 허용 문자가 제한(괄호 불가)된다. 괄호를 빼니 통과. log group 이름도 반드시 aws-waf-logs- 프리픽스여야 로깅이 붙는다.

검증은 aws wafv2 get-web-acl-for-resource로 ALB ARN에 prod-alb-waf(룰 5개)가 연결됐는지 확인했다.

2. 드리프트 — WAF를 올리기 전에 잡아야 했던 것

WAF를 apply하려다, 팀이 AgentCore 준비하며 콘솔로 만든 SG·S3가 Terraform 밖에 있다는 걸 알았다. 이게 위험한 이유는 우리 SG가 inline ingress 방식이기 때문이다 — TF가 그 SG의 규칙 전체를 소유하므로, 콘솔로 추가한 규칙은 다음 apply가 삭제한다. 무관한 작업(WAF) 한 번에 엉뚱한 규칙이 날아갈 수 있었다.

추측 대신 terraform plan을 권위 도구로 전수 조사했다(prod·dev). 내가 추가한 코드 변경을 "예상"으로 분리하니, 진짜 드리프트는 둘뿐이었다.

  • prod: bastion(farmily-relay-sg)이 RDS(5432)·Redis(6379) SG에 콘솔로 부착돼 있었다. 이 bastion은 launch-wizard로 만든 EC2인데 SSH·5432가 0.0.0.0/0(전 세계) 오픈 — 별도 보안 위험. (IaC 정리가 보안 점검으로 이어진 사례)
  • dev: 스케줄러 Lambda의 source_code_hash 불일치. 알고 보니 드리프트가 아니라 6/11 커밋했지만 배포 안 된 코드였다(archive_file은 해시가 내용 결정적이라 git으로 추적 가능).

콘솔 리소스는 코드를 실제와 1:1로 쓴 뒤 terraform import로 흡수했다. 여기서 또 함정 — SG의 name은 변경 불가(ForceNew) 속성이라, 모듈 컨벤션(prod-)을 따르려 이름을 바꾸면 import 후 plan이 "destroy+create"로 재생성을 시도한다. 그래서 콘솔 생성명 farmily-agentcore-sg를 그대로 코드에 박아 깨끗하게 import했다. S3 버킷 import는 객체(파일)를 건드리지 않아 템플릿 HTML은 그대로 보존됐다.

import 중 팀원(민석)이 prod state 락을 잡고 있어 한 번 막혔다. Lock Info에 누가·언제 잡았는지 나오는데, active 락은 절대 force-unlock하지 않았다(state 손상 위험). 해제 후 fresh plan으로 재검증하고 재개. 최종 terraform plan = "No changes" = 드리프트 0 달성. 모든 변경은 commit → PR #25 → 머지로 기록.

3. prod Secrets Manager — 무중단 이전과 두 번의 함정

dev는 6/12에 끝냈고 오늘 prod 차례. 가장 중요한 원칙부터 — 시크릿 값은 Terraform에 두면 안 된다. TF로 시크릿을 만들면 값이 state(S3)에 평문 저장돼 문제를 키운다. 그래서 값은 콘솔(IaC 밖), 구조·IAM만 코드. 이건 속도 타협이 아니라 보안상 올바른 선택이다.

prod extra_environment를 키별로 분류해 시크릿 9개(DB_PASSWORD·JWT·KAKAO×2·KMA·PORTONE×3·FCM)를 가렸다. 비밀 아닌 것(KAKAO_CLIENT_ID·PORTONE_IMP_CODE·*_TEST_MODE 등)은 env에 남겼다. task def 새 리비전에서 9개를 environmentsecrets[](valueFrom ARN)로 옮겼다.

valueFrom 문법: arn:...:secret:farmily/prod/app-<suffix>:KEY:: — 키 뒤 콜론 2개(version 자리 비움), 키 대소문자 정확.

배포에서 dev와 똑같은 함정이 재현됐다.

ResourceInitializationError: retrieved secret from Secrets Manager
did not contain json key DB_PASSWORD

시크릿에 DB_PASSWORD 키가 빠져 있었다. 하지만 prod는 한순간도 안 끊겼다 — 구 task(prod-app:15)가 그대로 서빙했다. Circuit Breaker + minHealthy 100%가 "새 task가 헬스 통과해야 구 task가 빠진다"를 보장하기 때문. 키를 채우고 --force-new-deployment로 재시도하니 running 1 / failed 0으로 정상 기동, 롤아웃 COMPLETED. prod-app:16로 전환 완료.
ㅁㅁ


Learned

  • Shield Standard vs Advanced — Standard는 무료·자동·L3/L4, Advanced는 $3,000/월·L7·DRT·비용보호. "우리는 WAF rate-limit으로 L7 커버하니 Standard 유지"가 트레이드오프 답. get-subscription-state=INACTIVE로 Standard 동작 확인.
  • "IP 평판을 우리가 어떻게 아나?" — 우리는 모른다. AWSManagedRulesAmazonIpReputationListAWS가 자사 위협 인텔로 상시 갱신하는 관리형 룰이고 우리는 이름만 참조($0). rate-limit(우리 트래픽 행동) vs IP reputation(AWS의 전역 평판) — 신호가 다른 두 층.
  • inline ingress 드리프트 함정 — inline ingress {}는 TF가 규칙 전체를 소유 → 콘솔 추가분은 apply가 삭제. terraform plan이 최고의 드리프트 탐지기(단, TF가 아는 리소스만).
  • SG name은 변경 불가(ForceNew) — import할 땐 콘솔 생성명을 그대로 코드화해야 재생성을 피한다.
  • 시크릿을 Terraform에 두면 안 되는 이유 — 값이 state(S3)에 평문 저장된다. 값은 콘솔/시크릿스토어, 코드엔 구조·ARN·IAM만.
  • 시크릿은 이미 tfvars·state에 흘러들어 있다 — 마이그레이션의 목적은 추가가 아니라 task def 평문 노출면을 줄이는 것. 옮긴 뒤 tfvars extra_environment에서 빼야 state에서도 빠진다(DB_PASSWORD는 RDS가 필요로 해 예외 → RDS-managed password가 후속 해법).
  • 자동 교체(rotation)는 단일 자격증명용 — 9개 혼합 JSON 시크릿엔 부적합(외부 서비스 키는 AWS가 못 바꿈). DB_PASSWORD를 RDS-managed로 가면 RDS가 교체까지 관리 = "교체를 제대로 쓰는 자리".
  • FCM 같은 다필드 JSON — 키 1개(FCM_SERVICE_ACCOUNT_JSON)의 값에 통째로. private_key\n은 literal 유지.
  • Circuit Breaker = 위험한 변경의 안전망 — 시크릿 전환이 실패해도 구 task 생존으로 무중단. "새 버전이 헬스 통과해야 구 버전이 빠진다".
  • archive_file 해시는 내용 결정적 — Lambda source_code_hash 불일치 = 진짜 코드 변경(phantom 아님) → git으로 미배포 커밋임을 규명.

Prompt(회고)

  • 잘한 것: "WAF apply 전에 드리프트부터"라는 순서 판단. 만약 드리프트를 모르고 apply했다면 relay의 RDS·Redis 접근이 조용히 삭제됐을 것. terraform plan을 진단 도구로 먼저 돌린 게 사고를 막았다.
  • 반복된 함정: prod 시크릿 배포가 dev와 똑같이 DB_PASSWORD 키 누락으로 실패. dev에서 한 번 겪고도 prod에서 재현 — 체크리스트("배포 전 9개 키 전부 확인")를 절차로 박아야 한다. 단, Circuit Breaker 덕에 무중단이었던 게 설계의 승리.
  • 드러난 빚: JWT placeholder·webhook placeholder·dev-master-enabled가 prod에 살아 있었다. 시크릿 마이그레이션이 우연히 이걸 드러냈다 — 평문을 한 곳에 모으니 비로소 "이게 placeholder였네"가 보였다. 정리(IaC·시크릿)가 곧 보안 감사가 된다.
  • 이제 ECS에서의 Test는 거의 마무리가 되어간다. 이제 AI만 우리랑 붙여서 잘되는지 파악 후 EKS로 modernaztion을 하면 된다. 이제 수료까지 한달도 안남았다. 열심히 해야겠다. 후회없이...
profile
나를 한줄로

0개의 댓글