요약: aws에서 누가, 언제, 무엇을 했는지 알고 싶다면 CloudTrail 로그를 보기
지난 글에서는 누구에게 무엇을 허용할 것인지를 다뤘는데, 이번 글은
"실제로 누가 무엇을 했는가"
에 대한 내용이다
| Presigned URL | CloudTrail | |
|---|---|---|
| 질문 | 누구에게 무엇을 허용할까 | 누가 실제로 무엇을 했을까 |
| 성격 | 사전 통제 | 사후 탐지 |
| 시점 | 사건 전 | 사건 후 |
권한을 아무리 잘 설계해도 누가 언제 그걸 바꿔버렸는지 추적할 수 없으면 의미가 없고,
반대로 로그만 잔뜩 쌓아둬도 의미가 없으니 막을 수단이 있어야한다
CloudTrail이란
aws 작업을 감사하는 서비스로, api 호출과 사용자 활동을 자동으로 기록해준다

기록되는 주요 사건의 마지막 두 개를 눈여겨봐야하는데
IAM 권한, 버킷 정책 등 설계한 통제 장치들이 누군가에 의해 바뀌었는지를 감시하는 게 CloudTrail이다
모든 AWS 작업은 API 호출이다
이게 CloudTrail을 이해하는 핵심인 것 같다
콘솔에서 버튼 클릭
cli 명령어 입력 ──▶ StopInstances api 호출 ──▶ CloudTrail json 기록
terraform apply
콘솔의 예쁜 버튼 아래엔 결국 api가 있을 뿐이니, 어떤 도구를 쓰든 eventName은 똑같이 기록된다
CloudTrail 하나로 도구와 무관하게 모든 활동을 잡아낼 수 있는 이유!
실습
이번 실습은 직접 사건을 만들고, 그 흔적을 읽는 순서로 진행했다

1. IAM 계정 확인
루트 계정으로 실습해도 되긴 하지만, 감사 로그를 다루는 실습에서 루트를 쓰는 건 좀 아이러니하니까 lab-user를 따로 만들자
AdministratorAccess가 붙어 있는지 확인하고, 없다면 직접 정책 연결로 붙여주면 된다
cf. 실무에서는
AdministratorAccess를 이렇게 막 주면 안 된다로그를 볼 사람과 로그를 끌 수 있는 사람은 반드시 분리되어야 하기 때문



2. EC2 인스턴스 생성
CloudTrail-Lab이라는 이름으로 t3.micro (혹은 t2.micro) 하나만 띄운다. Amazon Linux 2023에 키 페어 없음, 스토리지는 기본 8GiB로 두면 됨


3. CloudTrail 추적 생성
Security-Lab이라는 이름으로 빠른 추적 생성을 누르면 끝난다

로그가 저장될 s3 버킷은 자동으로 만들어지고, 이벤트를 로깅하는 것 자체에는 비용이 들지 않지만 로그를 저장하는 s3 버킷에는 요금이 발생한다는 안내가 뜬다
4. 인스턴스 중지
ec2로 돌아가서 방금 만든 인스턴스를 중지시키고,

다시 CloudTrail의 이벤트 기록으로 이동해 StopInstances를 검색해보자

방금 내가 한 행동(StopInstances)이 로그로 남아 있다!
json 코드 분석하기
이벤트를 클릭하면 아래 같은 json이 나온다


해당 이벤트 하나에 육하원칙이 거의 다 들어 있었다
| 질문 | 필드 |
|---|---|
| 누가 | userIdentity.type / arn / userName |
| 언제 | eventTime |
| 어디서 | sourceIPAddress / awsRegion |
| 무엇을 | eventName / eventSource |
| 무엇에 대해 | resources[].ARN |
| 어떻게 | userAgent / sessionCredentialFromConsole |
| 결과는 | responseElements.currentState |
| 위험한가 | readOnly |
readOnly를 보자!
"readOnly": false
읽기 전용 작업인지 알려주는 필드로,
| api | readOnly | 의미 |
|---|---|---|
| DescribeInstances | true | 조회만 |
| GetBucketPolicy | true | 조회만 |
| StopInstances | false | 상태 변경 |
| DeleteBucket | false | 리소스 변경 |
수만 건의 로그에서 "진짜 문제가 될 만한 것"만 걸러내는 첫 번째 필터라서, 보안 관제에서 제일 먼저 거는 조건이라고 한다
다만 readOnly: true인 이벤트를 아예 안 봐도 되는 건 아니다. DescribeInstances나 ListBuckets 같은 조회 api가 비정상적으로 폭주하는 건 정찰의 전형적인 신호이기도 하다
requestID와 eventID의 차이
비슷해 보이는데 역할이 다르다
requestID: aws가 해당 api 요청에 부여한 id로, aws에 문의할 때 쓴다eventID: CloudTrail 이벤트 자체의 고유 id로, 로그 추적하고 분석할 때 쓴다
previousState → currentState
"currentState": { "code": 64, "name": "stopping" },
"previousState": { "code": 16, "name": "running" }
상태 변화의 전후를 통째로 담고 있어서, 로그만 보고도 "실행 중이던 인스턴스가 중지되었다"는 걸 알 수 있다
mfaAuthenticated
"mfaAuthenticated": "false"
실제 운영 계정이었다면 이 false가 경보 대상이다
많은 조직이 CloudTrail 로그에서 mfaAuthenticated: false인 콘솔 로그인을 알람 조건으로 걸어둔다고 한다
실패한 API 호출이 더 많은 걸 말해준다
CloudTrail은 성공한 호출만 기록하는 게 아니라 실패한 api 호출도 기록한다
권한 없이 s3 버킷을 삭제하려고 시도하면 AccessDenied와 함께 이벤트가 남는데, 사실 이게 CloudTrail의 진짜 가치인 것 같다
탈취한 키로 접속
→ GetCallerIdentity (성공) "나는 누구지?"
→ ListUsers (AccessDenied)
→ ListRoles (AccessDenied)
→ ListBuckets (성공) ← 여기서 뚫림
→ GetObject × 수백 건 (성공) ← 데이터 유출
성공 직전의 실패 더미가 침해의 가장 선명한 지문이 되는 셈이다!
반대로 실패가 없어서 위험한 경우도 있다
AdministratorAccess를 가진 계정이 탈취되면 아무것도 실패하지 않아서 로그만으론 정상 관리자와 구분이 안 된다최소 권한 원칙이 곧 탐지 가능성이기도 한 것
GuardDuty가 하는 일이 정확히 이건데, Recon:IAMUser/MaliciousIPCaller 같은 탐지 항목이 전부 CloudTrail의 실패 이벤트를 먹고 동작한다
로그를 실제로 쓰는 방법
실습에서는 콘솔의 이벤트 기록까지만 봤지만, 콘솔은 90일치 관리 이벤트만 보여주기 때문에 실무에서는 단계가 올라간다
| 단계 | 도구 | 용도 |
|---|---|---|
| 1 | CloudTrail 콘솔 이벤트 기록 | 최근 90일, 단건 조회 |
| 2 | s3 + Athena | 장기 로그를 sql로 검색 |
| 3 | CloudWatch Logs + Metric Filter + Alarm | 특정 이벤트 발생 시 즉시 알림 |
| 4 | EventBridge + Lambda | 위험 이벤트 자동 차단·롤백 |
| 5 | GuardDuty / Security Hub | ml 기반 이상 탐지 |
특히 3번이 가성비가 좋다고 한다. DeleteTrail, StopLogging, DeleteBucket, Root 계정 로그인 같은 이벤트가 발생하면 sns로 즉시 알림을 받는 것인데, 이 몇 줄짜리 설정이 실무 보안의 기본이라고 한다
-- Athena로 Root 계정의 위험 작업만 뽑아보기
SELECT eventtime, useridentity.arn, eventname, errorcode
FROM cloudtrail_logs
WHERE useridentity.type = 'Root'
AND readonly = false
실습 화면 상단에 "CloudTrail Lake가 2026년 5월 31일부터 신규 고객에게 제공되지 않는다"는 배너가 떠 있었는데,
유사 기능은 CloudWatch를 참고하라고 하니, 앞으로는 CloudWatch Logs 쪽 파이프라인에 익숙해지는 게 유리해 보인다
관리 이벤트 vs 데이터 이벤트
CloudTrail 기본 설정으로는 s3 파일을 누가 다운로드했는지가 기록되지 않는다
- 관리 이벤트 :
PutBucketPolicy,CreateBucket처럼 리소스를 관리하는 작업으로 기본 활성화되어 있다 - 데이터 이벤트 :
GetObject,PutObject처럼 데이터를 다루는 작업인데 별도로 켜야 하고 유료다
(지난 글의 presigned url과 연결하면, 데이터 이벤트를 켜야 발급된 url로 누가 언제 파일을 받아갔는지 추적할 수 있다)
로그 버킷이 공격당하면 어떡할까
로그를 지울 수 있는 사람은 흔적도 지울 수 있다는 게 감사 시스템의 근본 딜레마다
실습에서도 마지막에 추적을 삭제하고 s3 버킷을 비우고 지웠는데, 공격자가 계정 권한을 얻으면 똑같이 할 수 있는 것 아닌가?
실무에서는 여러 레이어로 막는다
1. 계정 분리 (제일 중요)
[운영 계정 A] ─┐
[운영 계정 B] ─┼──▶ [로그 아카이브 전용 계정] ← 로그 버킷은 여기에만
[운영 계정 C] ─┘ (운영팀은 접근 권한 없음)
운영 계정이 통째로 털려도 로그는 다른 계정에 있어서 손댈 수 없다
2. s3 Object Lock
지정 기간 동안 Root 계정조차 삭제/수정 불가하도록 만든다. worm(Write Once Read Many) 스토리지가 되는 것
3. Log File Validation
CloudTrail이 1시간마다 로그 파일의 sha-256 해시를 모아 다이제스트 파일을 만들고 aws 비공개 키로 서명한다
로그가 조작되면 해시가 안 맞고 파일이 사라지면 다이제스트에 구멍이 나기 때문에, 삭제를 막진 못해도 삭제된 사실은 증명된다
4. scp로 봉쇄
Organizations 서비스 제어 정책으로 cloudtrail:StopLogging, cloudtrail:DeleteTrail을 조직 전체에서 deny
5. 데드맨 스위치
CloudWatch Alarm
Metric: CloudTrail 이벤트 수
조건: 15분간 이벤트 0건
TreatMissingData: breaching ← 데이터가 없으면 경보
정상이라면 최소한 몇 건은 흐르기 때문에, 침묵 자체가 신호가 된다
그리고 애초에 CloudTrail을 끄는 행위인 StopLogging, DeleteTrail도 CloudTrail에 기록되기 때문에 완벽한 은폐는 어려운 구조다
왜 RDS가 아니라 S3일까?
CloudTrail 추적을 만들면 s3 버킷이 자동으로 생기는데, 왜 하필 s3일까?
로그의 성격이 관계형 데이터베이스와 안 맞기 때문이다
| 특성 | CloudTrail 로그 | rds가 잘하는 일 |
|---|---|---|
| 쓰기 | 한 번 쓰고 안 고침 | 잦은 update/delete |
| 읽기 | 사고 났을 때 가끔, 대량 스캔 | 수시로, 소량 정밀 조회 |
| 스키마 | 서비스마다 구조가 다름 | 고정 스키마 |
| 볼륨 | 큰 조직은 하루 수억 건 | gb~tb |
| 보관 | 규정상 수년 | 보통 짧음 |
| - 비용 : s3 Standard는 gb당 월 $0.023인데 rds 스토리지는 그 5~10배에 인스턴스 시간당 요금이 상시 발생해서, 1년에 몇 번 볼 로그를 위해 db를 24시간 켜둘 이유가 없음 | ||
- 스키마 부재 : ec2는 instancesSet, s3는 bucketName, iam은 또 다른 구조라서 수백 개 서비스의 파라미터를 하나의 테이블로 정규화하는 건 불가능함 |
||
| - 불변성 : s3는 Object Lock으로 worm 보장이 되는데 rds는 db 관리자가 update 한 방이면 조작 가능해서 감사 로그에는 치명적임 | ||
| - 수명 주기 : 90일 후 Glacier로, 7년 후 삭제. s3 Lifecycle 규칙 몇 줄이면 끝남 |
검색은 Athena가 담당하고, s3에 있는 json을 그 자리에서 sql로 조회하고 스캔한 데이터 양만큼만 과금하는 방식
"저장은 싸게, 조회는 필요할 때만"
헷갈리는 이웃 서비스들
| 서비스 | 기록 대상 | 한 줄 요약 |
|---|---|---|
| CloudTrail | api 호출 | 누가 무엇을 했나 |
| CloudWatch Logs | 애플리케이션·시스템 로그 | 앱이 무슨 말을 했나 |
| CloudWatch Metrics | 성능 지표 | 얼마나 바쁜가 |
| vpc Flow Logs | 네트워크 트래픽 | 어떤 패킷이 오갔나 |
| Config | 리소스 구성 변화 | 지금 어떤 모습인가 |
saa 시험에서도 이 구분이 단골로 나오는데, "감사", "누가(who)", "규정 준수", "api 호출" 키워드가 보이면 CloudTrail이고 "구성 규칙 위반"이면 Config, "거부된 트래픽"이면 vpc Flow Logs다
cf. 그럼 EC2 안에서 rm -rf 한 것도 남을까?
안 남는다!
CloudTrail은 AWS API 경계에서 일어난 일만 보기 때문에, 인스턴스 내부에서 무슨 명령을 실행했는지, 파일 내용이 무엇이었는지는 기록하지 않는다
(이건 CloudWatch Agent나 auditd 같은 별도의 os 감사 도구의 영역)
리소스 정리
- EC2 인스턴스 종료(삭제)
- CloudTrail 추적 삭제

- S3 버킷 비우기 → 버킷 삭제

- IAM 사용자 삭제

마치며
aws 아키텍처 아이콘은 서비스 카테고리별로 색이 정해져 있다
| 카테고리 | 색 | 예시 |
|---|---|---|
| Compute | 주황 | ec2, Lambda |
| Storage | 초록 | s3, ebs |
| Database | 파랑 | rds, DynamoDB |
| Networking | 보라 | vpc, CloudFront |
| Security, Identity | 빨강 | iam, GuardDuty |
| Management & Governance | 핑크 | CloudTrail, CloudWatch, CloudFormation |
지난 글의 Presigned URL 자료와 비교해보면 재밌다. S3는 초록, Lambda는 주황이었으니까
여기서 흥미로운 건 CloudTrail이 보안 서비스처럼 느껴지는데도 색은 빨강(Security)이 아니라 핑크(Management & Governance)라는 점이다
사실, CloudTrail 자체는 아무것도 막지 않고 그저 기록할 뿐이며, 그 기록을 보안에 쓸지 비용 추적에 쓸지 규정 준수 증빙에 쓸지는 전부 쓰는 사람의 몫이기 때문에 핑크색이 맞는 거 같다
지난 글에서 Presigned URL이 막는 쪽이었다면 CloudTrail은 보는 쪽인데, 색깔 하나가 서비스의 성격을 정확히 말해주고 있었다는 게 신기했다!
'aws' 카테고리의 다른 글
| [AWS] AWS Student Community Day 회고 (0) | 2026.07.10 |
|---|---|
| [AWS] S3 Presigned URL: 비공개 파일 안전하게 공유하기 (0) | 2026.07.10 |
| [AWS] DMS, Route 53, ACM (0) | 2026.07.10 |