EC2 인스턴스가 사라졌다 — CloudTrail Event History로 삭제 주체 추적하기

어느 날 오전, 모니터링 알림이 울리고 확인해보니 운영 중이던 EC2 인스턴스가 없다. 콘솔에서 찾을 수 없고, Auto Scaling 그룹도 아니다. 누군가 TerminateInstances API를 직접 호출한 것이다. CloudTrail Event History는 이 상황에서 가장 먼저 열어야 할 도구다.

TL;DR — 핵심 요약

단계목적핵심 확인 항목
1. Event History 필터TerminateInstances 이벤트 검색이벤트 이름, 리소스 ID
2. 이벤트 상세 확인호출 주체 식별userIdentity, sourceIPAddress
3. CLI로 정밀 조회인스턴스 ID 기준 필터링lookup-events 파라미터
4. 원인 분석역할/사용자 권한 검토assumedRole, sessionContext

CloudTrail Event History가 동작하는 방식

CloudTrail은 AWS 계정 내 API 호출을 기록하는 감사 로그 서비스다. 리전별로 활성화되며, 별도 Trail을 구성하지 않아도 Event History는 기본적으로 각 리전에서 최근 90일간의 관리 이벤트(Management Events)를 자동으로 보관한다. EC2 인스턴스 종료는 TerminateInstances API 호출로 기록되며, 이 이벤트에는 호출 주체, 시간, 소스 IP, 대상 리소스 정보가 포함된다.

중요한 점은 Event History가 기록하는 것은 관리 이벤트(Management Events)뿐이라는 것이다. S3 객체 읽기 같은 데이터 이벤트는 별도 Trail 설정이 필요하다. EC2 종료는 관리 이벤트에 해당하므로 기본 Event History에서 조회 가능하다.

sequenceDiagram participant U as IAM 사용자/역할 participant EC2 as EC2 API participant CT as CloudTrail participant EH as Event History U->>EC2: TerminateInstances 호출 EC2-->>U: 응답 반환 EC2->>CT: API 호출 이벤트 전달 CT->>EH: 이벤트 저장 (약 15분 내) Note over EH: 90일간 보관 U->>EH: lookup-events 조회 EH-->>U: userIdentity, sourceIP, 리소스 정보 반환
  1. API 호출 발생: IAM 사용자 또는 역할이 TerminateInstances를 호출한다.
  2. CloudTrail 기록: 해당 리전의 CloudTrail이 이벤트를 캡처하고 Event History에 저장한다.
  3. 조회 가능 기간: 이벤트 발생 후 약 15분 내에 Event History에서 조회 가능해지며, 90일간 보관된다.
  4. userIdentity 구조: 이벤트 레코드의 userIdentity 필드가 호출 주체를 식별한다. IAM 사용자, 역할(AssumedRole), 루트 계정, AWS 서비스 등 타입이 구분된다.

CloudTrail Event History에서 TerminateInstances 이벤트 찾기

콘솔에서 인스턴스가 사라진 것을 확인했다면, 먼저 해당 인스턴스가 있던 리전을 확인해야 한다. CloudTrail Event History는 리전별로 분리되어 있다. 서울 리전(ap-northeast-2)에서 실행 중이던 인스턴스라면, 콘솔 우상단 리전을 서울로 맞추고 CloudTrail을 열어야 한다.

콘솔에서 조회하는 방법

  1. AWS 콘솔 → CloudTrail → Event History 메뉴로 이동한다.
  2. 필터를 '이벤트 이름(Event name)'으로 설정하고 TerminateInstances를 입력한다.
  3. 시간 범위를 인스턴스가 사라진 시점 전후로 좁힌다. 정확한 시간을 모른다면 최근 1-3일로 설정한다.
  4. 이벤트 목록에서 해당 항목을 클릭하면 JSON 형태의 이벤트 레코드 전체를 볼 수 있다.

이벤트를 찾았다면 userIdentity 블록을 먼저 확인한다. 여기서 누가 호출했는지 바로 드러난다.

CLI로 정밀하게 조회하기

콘솔 필터만으로는 특정 인스턴스 ID를 기준으로 좁히기 어렵다. CLI의 lookup-events 명령은 리소스 ID 기준 필터를 지원하므로, 인스턴스 ID를 알고 있다면 훨씬 정확하게 찾을 수 있다.

aws cloudtrail lookup-events \
  --region ap-northeast-2 \
  --lookup-attributes AttributeKey=EventName,AttributeValue=TerminateInstances \
  --start-time 2024-01-15T00:00:00Z \
  --end-time 2024-01-16T23:59:59Z \
  --query 'Events[*].{Time:EventTime,User:Username,Event:CloudTrailEvent}' \
  --output json

인스턴스 ID(i-0abcd1234efgh5678)를 알고 있다면 리소스 기준으로 직접 필터링할 수 있다.

aws cloudtrail lookup-events \
  --region ap-northeast-2 \
  --lookup-attributes AttributeKey=ResourceName,AttributeValue=i-0abcd1234efgh5678 \
  --query 'Events[*].{Time:EventTime,User:Username,EventName:EventName}' \
  --output table

이 명령은 해당 인스턴스 ID와 관련된 모든 이벤트를 반환한다. TerminateInstances 외에도 StopInstances, StartInstances 등 해당 리소스에 대한 전체 작업 이력을 볼 수 있다.

이벤트 레코드 해석 — userIdentity 읽는 법

이벤트를 찾았다면 실제 레코드를 읽어야 한다. 핵심은 userIdentity 블록이다. 타입에 따라 해석 방법이 달라진다.

IAM 사용자가 직접 호출한 경우

{
  "userIdentity": {
    "type": "IAMUser",
    "principalId": "AIDAEXAMPLEID",
    "arn": "arn:aws:iam::123456789012:user/john.doe",
    "accountId": "123456789012",
    "userName": "john.doe"
  },
  "eventTime": "2024-01-15T09:23:41Z",
  "eventName": "TerminateInstances",
  "sourceIPAddress": "203.0.113.45",
  "requestParameters": {
    "instancesSet": {
      "items": [
        { "instanceId": "i-0abcd1234efgh5678" }
      ]
    }
  }
}

userName 필드가 명확하게 나온다. sourceIPAddress도 함께 확인한다. 사무실 IP 대역이 아닌 외부 IP라면 자격증명 유출을 의심해야 한다.

IAM 역할(Role)을 통해 호출한 경우

{
  "userIdentity": {
    "type": "AssumedRole",
    "principalId": "AROAEXAMPLEID:session-name",
    "arn": "arn:aws:sts::123456789012:assumed-role/DevOpsRole/session-name",
    "accountId": "123456789012",
    "sessionContext": {
      "sessionIssuer": {
        "type": "Role",
        "principalId": "AROAEXAMPLEID",
        "arn": "arn:aws:iam::123456789012:role/DevOpsRole",
        "accountId": "123456789012",
        "userName": "DevOpsRole"
      },
      "webIdFederationData": {},
      "attributes": {
        "creationDate": "2024-01-15T08:00:00Z",
        "mfaAuthenticated": "false"
      }
    }
  }
}

역할을 Assume한 경우 typeAssumedRole로 표시된다. arn 필드의 세션 이름 부분(session-name)이 중요하다. EC2 인스턴스 프로파일이라면 인스턴스 ID가, AWS SSO라면 사용자 이메일이 세션 이름으로 찍히는 경우가 많다. sessionContext.sessionIssuer.arn이 실제 역할 ARN이다.

역할 기반 호출에서 '누가'를 찾는 것은 두 단계다. 먼저 어떤 역할인지 확인하고, 그 역할을 누가 Assume했는지를 추적해야 한다. 세션 이름이 단서가 되는 경우가 많다.

실제 사례 — 잘못 짚은 원인과 실제 원인

서비스 배포 직후 인스턴스가 사라졌다는 제보가 들어왔다. 처음에는 Auto Scaling 정책이 스케일 인을 과도하게 실행한 것으로 의심했다. Auto Scaling 활동 로그를 뒤졌지만 해당 인스턴스에 대한 기록이 없었다.

CloudTrail에서 TerminateInstances를 검색하자 이벤트가 바로 나왔다. userIdentity.typeAssumedRole이었고, 세션 이름이 GitHubActions였다. CI/CD 파이프라인에서 배포 스크립트가 구 버전 인스턴스를 정리하는 로직을 포함하고 있었는데, 인스턴스 ID 필터 조건에 버그가 있어서 잘못된 인스턴스를 종료한 것이었다.

Auto Scaling을 의심하는 동안 실제 원인은 CI/CD 파이프라인의 IAM 역할이었다. CloudTrail 이벤트의 세션 이름이 없었다면 역할만 보고 '배포 역할이 왜 인스턴스를 지워?'에서 막혔을 것이다.

삭제 주체 확인 후 — 후속 조치 흐름

graph TD A["CloudTrail에서
TerminateInstances 이벤트 확인"] --> B{"userIdentity.type"} B --> |"IAMUser"| C["userName 필드로
사용자 특정"] B --> |"AssumedRole"| D["sessionContext.sessionIssuer.arn
으로 역할 확인"] B --> |"Root"| E["루트 계정 사용 — 즉시 조사"] C --> F["sourceIPAddress 확인
비정상 IP 여부 판단"] D --> G["세션 이름으로
실제 호출 주체 추적"] F --> H["접근 키 비활성화
또는 콘솔 세션 종료"] G --> I["역할 신뢰 정책 및
권한 정책 검토"] H --> J["ec2:TerminateInstances
권한 최소화"] I --> J J --> K["DisableApiTermination
중요 인스턴스에 적용"]
  1. IAM 사용자 직접 호출: 해당 사용자의 접근 키 또는 콘솔 세션을 즉시 비활성화하고, 의도적 행위인지 확인한다.
  2. 역할(AssumedRole) 호출: 세션 이름으로 호출 주체(사람 또는 서비스)를 특정하고, 역할의 신뢰 정책과 권한 정책을 검토한다.
  3. 권한 최소화: 해당 역할 또는 사용자에게 ec2:TerminateInstances가 실제로 필요한지 검토한다. 특정 인스턴스 또는 태그 조건으로 제한할 수 있다면 적용한다.
  4. 재발 방지: 중요 인스턴스에 DisableApiTermination 속성을 활성화하거나, SCP로 특정 역할 외 종료를 차단하는 방안을 검토한다.

인스턴스 종료 보호 설정 확인 및 활성화

# 현재 종료 보호 상태 확인
aws ec2 describe-instance-attribute \
  --region ap-northeast-2 \
  --instance-id i-0abcd1234efgh5678 \
  --attribute disableApiTermination \
  --query 'DisableApiTermination.Value'
# 종료 보호 활성화
aws ec2 modify-instance-attribute \
  --region ap-northeast-2 \
  --instance-id i-0abcd1234efgh5678 \
  --disable-api-termination

CloudTrail 조회에 필요한 최소 IAM 권한

Event History 조회는 읽기 전용 권한이지만, 명시적으로 허용해야 한다. 보안 담당자나 SRE가 사고 조사를 위해 사용하는 최소 권한 정책 예시다.

🔽 CloudTrail 조회용 최소 IAM 정책 (클릭하여 펼치기)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "CloudTrailReadOnly",
      "Effect": "Allow",
      "Action": [
        "cloudtrail:LookupEvents",
        "cloudtrail:GetEventSelectors",
        "cloudtrail:DescribeTrails",
        "cloudtrail:GetTrailStatus"
      ],
      "Resource": "*"
    }
  ]
}

cloudtrail:LookupEvents는 리소스 수준 제한을 지원하지 않아 "Resource": "*"가 필요하다. AWS Service Authorization Reference에서 확인할 수 있다.

90일이 지난 경우 — S3 Trail 로그 조회

Event History는 90일이 한계다. 그 이전 이벤트를 추적해야 한다면 S3에 저장된 Trail 로그를 직접 조회해야 한다. Trail이 구성되어 있지 않았다면 90일 이전 이벤트는 복구할 수 없다. 이것이 중요 계정에서 Trail을 S3에 장기 보관하도록 구성해야 하는 이유다.

S3 Trail 로그가 있다면 Amazon Athena로 쿼리하는 것이 가장 효율적이다. CloudTrail 콘솔에서 Athena 테이블 생성을 직접 지원하며, 생성 후 표준 SQL로 조회할 수 있다.

-- Athena에서 TerminateInstances 이벤트 조회 예시
SELECT
  eventtime,
  useridentity.arn,
  useridentity.type,
  sourceipaddress,
  requestparameters
FROM cloudtrail_logs
WHERE
  eventsource = 'ec2.amazonaws.com'
  AND eventname = 'TerminateInstances'
  AND eventtime >= '2024-01-01T00:00:00Z'
ORDER BY eventtime DESC;

마무리 및 다음 단계 — CloudTrail로 EC2 삭제 추적

EC2 인스턴스 삭제 주체를 찾는 과정은 단순하다. CloudTrail Event History에서 TerminateInstances를 검색하고, 이벤트 레코드의 userIdentity 블록을 읽으면 된다. 핵심은 typeAssumedRole인 경우 세션 이름까지 확인해야 실제 호출 주체를 특정할 수 있다는 점이다.

재발 방지를 위해 중요 인스턴스에 종료 보호를 설정하고, 90일 이후에도 추적이 가능하도록 Trail을 S3에 구성해두는 것을 권장한다. 사고는 항상 90일이 지난 뒤에 발견되기도 한다.

핵심 용어 정리

용어설명
Event History별도 Trail 구성 없이 각 리전에서 자동으로 제공되는 최근 90일간의 관리 이벤트 조회 기능
userIdentityCloudTrail 이벤트 레코드에서 API를 호출한 주체 정보를 담는 필드. type, arn, sessionContext 등을 포함한다
AssumedRoleIAM 역할을 Assume하여 API를 호출한 경우의 userIdentity 타입. 세션 이름으로 실제 호출 주체를 추적할 수 있다
Management EventAWS 리소스에 대한 제어 플레인 작업(생성, 수정, 삭제 등). EC2 종료가 여기에 해당하며 Event History에 기본 기록된다
DisableApiTerminationEC2 인스턴스 속성으로, 활성화 시 API를 통한 인스턴스 종료를 차단한다

Related Posts

댓글

이 블로그의 인기 게시물

EC2 SSH 연결 타임아웃 완전 해결 가이드: Security Group 인바운드 규칙부터 라우팅까지

EC2 SSH 연결 시간 초과: 확인해야 할 보안 그룹(Security Group) 규칙

IAM User vs IAM Role 차이점 완전 정리 — EC2에서 S3 접근 시 무엇을 써야 하는가