← 목록

SES CloudTrail 데이터 이벤트 활성화 및 감사 쿼리 구성

발송은 성공하는데 감사 로그에는 아무것도 없던 이유. 관리 이벤트와 데이터 이벤트의 차이, 그리고 감사 범위를 켜면서 드러난 설정 버그

2026-04-15AWSSESCloudTrailSecurity

들어가며

IAM User가 보낸 SES 메일 발송 이력을 확인해야 할 일이 생겼다. 감사 환경은 이미 만들어 뒀으니(CloudTrail + Athena 감사 로그 분석 환경 구축) 쿼리만 돌리면 될 줄 알았다.

결과가 비어 있었다. 기간을 넓혀도, 조건을 풀어도 한 건도 나오지 않았다.

이럴 때 가능성은 둘이다. 정말 발송이 없었거나, 기록되지 않았거나. 확인하려고 CLI로 직접 메일을 보내봤다.

aws ses send-email ... → MessageId 정상 리턴 (발송 성공)
CloudTrail 조회         → 여전히 없음

발송은 됐는데 기록이 없다. 로그가 없다는 것과 기록되지 않는다는 건 다른 문제라는 걸 여기서 알았다.


근본 원인: 관리 이벤트와 데이터 이벤트

SES의 SendEmail, SendRawEmail 같은 발송 API는 CloudTrail의 관리 이벤트(Management Event)가 아니라 **데이터 이벤트(Data Event)**로 분류된다.

구분예시기본 기록
관리 이벤트CreateConfigurationSet, VerifyDomainIdentity자동으로 기록됨
데이터 이벤트SendEmail, SendRawEmail명시적으로 켜야 기록됨

둘을 가르는 기준은 대상이다. 관리 이벤트는 리소스를 만들고 바꾸는 행위이고, 데이터 이벤트는 그 리소스를 사용하는 행위다. 같은 구분이 다른 서비스에도 그대로 적용된다.

서비스관리 이벤트 (자동)데이터 이벤트 (수동)
S3버킷 생성, 정책 변경객체 GetObject, PutObject
SESIdentity 인증, 설정 변경메일 발송
Lambda함수 생성, 설정 변경함수 호출

데이터 이벤트가 기본 비활성인 이유는 양 때문이다. 사용 행위는 관리 행위보다 수백 배 많이 발생하고, 전부 기록하면 저장과 조회 비용이 커진다. 그래서 AWS는 "필요한 리소스만 골라서 켜라"는 쪽을 기본값으로 뒀다.

문제는 이 기본값을 모르면 감사 체계가 돌아가고 있다고 착각한다는 점이다. Trail은 켜져 있고, 로그는 쌓이고, 쿼리도 동작한다. 다만 우리가 찾는 행위만 처음부터 기록되지 않았을 뿐이다.

참고로 SES 발송 이벤트를 CloudTrail로 남기는 기능 자체가 2025년 4월에 추가됐다. 그전에는 추적이 불가능했다.


SES 데이터 이벤트 활성화

CloudTrail 콘솔에서는 SES 데이터 이벤트 유형이 아직 드롭다운에 없어서 CLI로 설정했다. 기존 basic event_selector를 advanced_event_selector로 전환하면서 SES 데이터 이벤트를 추가하는 방식이다.

aws cloudtrail put-event-selectors \
  --region ap-northeast-2 \
  --trail-name [trail-name] \
  --advanced-event-selectors file://scripts/ses-cloudtrail-selectors.json

scripts/ses-cloudtrail-selectors.json 내용이다.

[
  {
    "Name": "Management events",
    "FieldSelectors": [
      { "Field": "eventCategory", "Equals": ["Management"] }
    ]
  },
  {
    "Name": "S3 data events for target buckets",
    "FieldSelectors": [
      { "Field": "eventCategory", "Equals": ["Data"] },
      { "Field": "resources.type", "Equals": ["AWS::S3::Object"] },
      { "Field": "resources.ARN", "StartsWith": ["arn:aws:s3:::[bucket-prod]/", "arn:aws:s3:::[bucket-dev]/"] }
    ]
  },
  {
    "Name": "SES data events",
    "FieldSelectors": [
      { "Field": "eventCategory", "Equals": ["Data"] },
      { "Field": "resources.type", "Equals": ["AWS::SES::EmailIdentity"] }
    ]
  }
]

기존 S3 데이터 이벤트 설정을 같은 파일에 다시 적어둔 게 중요하다. advanced_event_selector는 기존 설정을 대체하기 때문에, 빠뜨린 항목은 그 순간부터 기록이 끊긴다.

주의사항

마지막 항목은 감사 범위의 구멍이라서 따로 적어뒀다. API로 보낸 메일은 남고 SMTP로 보낸 메일은 남지 않는다면, "발송 이력이 없다"는 조회 결과가 발송이 없었다는 뜻이 아니게 된다. 감사 체계에서는 무엇을 볼 수 있는지만큼 무엇을 볼 수 없는지를 적어두는 게 중요하다. 나중에 그 구멍을 모르는 사람이 조회 결과만 보고 결론을 내리면 잘못된 판단이 된다.

비용

데이터 이벤트의 비용 부담은 발송량에 비례한다. 우리 규모에서는 걱정할 수준이 아니었고, 그렇다면 켜지 않을 이유가 없었다.


활성화 후 확인 결과

활성화 이후 CloudTrail에 기록된 SES 발송 주체다.

주체유형eventName용도
[iam-user-dev]IAM UserSendEmailCLI 테스트, 앱 dev
[email-forward-lambda-role]Lambda RoleSendRawEmail메일 수신 후 포워딩
[report-lambda-role]Lambda RoleSendEmailConfig 보고서 발송
[health-check-lambda-role]Lambda RoleSendEmailECS 헬스체크 알림
[admin-user]IAM UserSendRawEmail온프레미스 서버 발송

감사 로그가 알려준 뜻밖의 사실

목록을 보다가 예상과 다른 게 나왔다. dev 앱의 SES 호출이 IAM User가 아니라 ECS Task Role로 실행되고 있었다.

원인을 따라가 보니 설정 이름 불일치였다.

SesConfig 클래스:   @Value("${aws.ses.secret-key:}")
application.yml:    ses.secret-key 로 정의

→ 이름이 안 맞아 secretKey가 빈 문자열
→ DefaultCredentialsProvider로 fallback
→ ECS Task Role의 권한으로 발송

기본값이 있는 @Value라서 애플리케이션은 아무 오류 없이 떴고, 메일도 정상적으로 나갔다. 동작에는 문제가 없고 권한 주체만 달랐기 때문에, 감사 로그를 보기 전에는 알 방법이 없었다. 개발팀에 property 이름 수정을 안내했다.

이 발견이 이 작업에서 가장 값졌다. 감사 로그를 켜는 목적은 사고 대응인데, 실제로는 평상시의 설정 오류를 먼저 잡아줬다. 누가 무엇을 하고 있는지 보이기 시작하면, 의도와 다른 부분이 드러난다.


Athena 저장 쿼리 현황

S3 파일 접근 이력

쿼리명환경대상조회 기간
s3-dev-presign-activity개발IAM User최근 7일
s3-prod-presign-activity운영IAM User최근 7일
s3-prod-taskrole-activity운영ECS Task Role최근 7일

SES 이메일 발송 이력

쿼리명환경대상조회 기간
ses-all-email-activity통합전체 (ALL)최근 7일
ses-dev-email-activity개발IAM User최근 30일
ses-prod-email-activity운영IAM User최근 7일
ses-prod-taskrole-email-activity운영ECS Task Role최근 7일

Task Role용 쿼리를 따로 만든 건 앞의 발견 때문이다. 사람이 쓰는 자격증명만 보고 있으면, 애플리케이션이 하는 일은 감사 범위 밖에 남는다.

조회 가능 시점

이벤트 유형조회 가능 시점
S3 객체 접근 (데이터 이벤트)2026-04-08 이후
SES 이메일 발송 (데이터 이벤트)2026-04-14 이후

이 표가 이 글에서 가장 중요할지도 모른다. 데이터 이벤트는 켠 시점 이후만 쌓인다. 그전 기간은 지금 아무리 조회해도 나오지 않는다. 감사 설정이 사후 도구가 아니라 사전 준비인 이유다.


Terraform 반영 상태

항목상태비고
Athena ses-all-email-activity 쿼리Terraform 적용 완료신규 추가
Athena ses-dev-email-activity 쿼리Terraform 코드 수정됨Access Key OR 조건 추가, 30일로 확장
CloudTrail SES 데이터 이벤트CLI로 적용, Terraform 미반영basic에서 advanced event selector로 전환 필요

cloudtrail.tf의 event_selector를 advanced_event_selector로 마이그레이션하면 코드와 실제 상태가 일치한다. 다만 기존 S3 데이터 이벤트 설정도 함께 옮겨야 해서 주의가 필요하다.

이 어긋난 구간을 표에 남겨둔 이유가 있다. 코드와 실제 상태가 다른 상태는 그 자체로 위험하다. 누군가 terraform apply를 돌리면 CLI로 켜둔 SES 데이터 이벤트가 조용히 사라질 수 있다. 다음 사람이 diff를 보고 당황하지 않도록, 지금 무엇이 코드 밖에 있는지 적어두는 것까지가 작업의 일부다.


배운 점

로그가 없다는 것과 기록되지 않는다는 것은 다르다. 발송은 성공하는데 CloudTrail에는 아무것도 없어서 한참 찾았는데, 원인은 SES 발송이 데이터 이벤트였기 때문이다. 감사 체계를 만들 때 먼저 확인해야 하는 건 쿼리가 아니라, 무엇이 기본으로 기록되고 무엇을 따로 켜야 하는지였다.

감사 설정은 사후가 아니라 사전 작업이다. 데이터 이벤트는 켠 시점 이후만 쌓인다. 사건이 난 다음에 켜면 그 사건은 영영 확인할 수 없다. 비용이 크지 않다면 필요해지기 전에 켜두는 쪽이 맞다는 결론에 도달했다.

감사 로그는 사고보다 평상시의 오류를 먼저 보여줬다. 설정 이름 불일치로 엉뚱한 권한 주체가 쓰이고 있던 걸 이 작업 중에 발견했다. 동작에는 문제가 없어서 아무도 몰랐던 종류다. 관측이 생기면 의도와 실제의 차이가 드러난다는 걸, NLB 포트별 트래픽 확인에서도 비슷하게 겪었다.

볼 수 없는 것을 적어두는 것도 감사 설계다. SMTP 발송이 기록되지 않는다는 제한을 적어두지 않으면, 나중에 조회 결과만 보고 잘못된 결론을 내리게 된다. 코드와 실제 상태가 어긋난 구간도 같은 이유로 남겨뒀다.


이어 읽기