0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CloudTrail の eventType を実際のログで理解する ── ログインと VPC 作成を例に

0
Last updated at Posted at 2026-06-20

CloudTrail の eventType を実際のログで理解する ── ログインと VPC 作成を例に

AWS CloudTrail には eventType というフィールドがあり、記録されたイベントの種類を分類する。
本記事では マネジメントコンソールにログインして VPC を作成する という一連の操作を実際に行い、取得したログをもとに eventType の違いを解説する。

eventType の種類

以下は公式ドキュメントの説明をそのまま引用したもの。

eventType 説明(公式ドキュメントより)
AwsApiCall An API was called.
AwsServiceEvent The service generated an event related to your trail. For example, this can occur when another account made a call with a resource that you own.
AwsConsoleAction An action was taken in the console that was not an API call.
AwsConsoleSignIn A user in your account (root, IAM, federated, SAML, or SwitchRole) signed in to the AWS Management Console.
AwsVpceEvents CloudTrail network activity events enable VPC endpoint owners to record AWS API calls made using their VPC endpoints from a private VPC to the AWS service. To record network activity events, the VPC endpoint owner must enable network activity events for the event source.

出典: CloudTrail record contents


検証環境

  • IAM ユーザー: example-user
  • 操作内容:
    1. マネジメントコンソールにログイン(2026-06-20T12:55:16Z
    2. ap-northeast-1 で VPC(名前: hogehoge、CIDR: 10.0.0.0/24)を作成(2026-06-20T12:55:39Z

1. AwsConsoleSignIn ── コンソールへのログイン

コンソールにサインインすると eventType: AwsConsoleSignIn のイベントが記録される。

{
  "eventVersion": "1.11",
  "userIdentity": {
    "type": "IAMUser",
    "principalId": "AIDAXXXXXXXXXXXXXXXXX",
    "arn": "arn:aws:iam::123456789012:user/example-user",
    "accountId": "123456789012",
    "userName": "example-user"
  },
  "eventTime": "2026-06-20T12:55:16Z",
  "eventSource": "signin.amazonaws.com",
  "eventName": "ConsoleLogin",
  "awsRegion": "ap-southeast-2",
  "sourceIPAddress": "xxx.xxx.xxx.xxx",
  "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36",
  "requestParameters": null,
  "responseElements": {
    "ConsoleLogin": "Success"
  },
  "additionalEventData": {
    "LoginTo": "https://console.aws.amazon.com/console/home?...",
    "MobileVersion": "No",
    "MFAUsed": "No"
  },
  "eventID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "readOnly": false,
  "eventType": "AwsConsoleSignIn",
  "managementEvent": true,
  "recipientAccountId": "123456789012",
  "eventCategory": "Management",
  "tlsDetails": {
    "tlsVersion": "TLSv1.3",
    "cipherSuite": "TLS_AES_128_GCM_SHA256",
    "clientProvidedHostHeader": "ap-southeast-2.signin.aws.amazon.com"
  }
}

ポイント

フィールド 意味
eventType AwsConsoleSignIn サインインイベント専用の分類
eventSource signin.amazonaws.com サインインサービスからの記録
eventName ConsoleLogin イベント名(固定)
responseElements.ConsoleLogin Success ログイン成否(失敗時は Failure
additionalEventData.MFAUsed No MFA 未使用(有効なら Yes
additionalEventData.LoginTo URL ログイン後のリダイレクト先
requestParameters null ログインにリクエストパラメータなし

ログアウトは記録されない

コンソールからログアウトしても CloudTrail にイベントは記録されない。ログアウトはブラウザ側でセッション Cookie を破棄するだけで、AWS のサーバーへの API 呼び出しが発生しないため。

なぜ awsRegion が ap-southeast-2 なのか?

ConsoleLogin イベントの awsRegion はブラウザが接続した signin エンドポイントのリージョン が記録される。これはコンソールが最終的に操作するリージョンとは無関係。

今回の操作では ap-southeast-2.signin.aws.amazon.com に接続してログインし、その後 ap-northeast-1 で VPC を作成している。lookup-events コマンドのデフォルトリージョン(ap-northeast-1)では ConsoleLogin イベントが見つからないため、全リージョンを確認する必要がある。

# すべてのリージョンを確認する必要がある
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=ConsoleLogin \
  --start-time "2026-06-20T00:00:00Z" \
  --region ap-southeast-2

2. AwsApiCall ── VPC 作成(書き込み系)

コンソールの「VPC を作成」ボタンを押すと、内部的に EC2 API(CreateVpc)が呼び出される。コンソール操作であっても API 呼び出しは AwsApiCall に分類される。

{
  "eventVersion": "1.11",
  "userIdentity": {
    "type": "IAMUser",
    "principalId": "AIDAXXXXXXXXXXXXXXXXX",
    "arn": "arn:aws:iam::123456789012:user/example-user",
    "accountId": "123456789012",
    "accessKeyId": "ASIAXXXXXXXXXXXXXXXXX",
    "userName": "example-user",
    "sessionContext": {
      "attributes": {
        "creationDate": "2026-06-20T12:55:16Z",
        "mfaAuthenticated": "false"
      }
    }
  },
  "eventTime": "2026-06-20T12:55:39Z",
  "eventSource": "ec2.amazonaws.com",
  "eventName": "CreateVpc",
  "awsRegion": "ap-northeast-1",
  "sourceIPAddress": "xxx.xxx.xxx.xxx",
  "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36",
  "requestParameters": {
    "cidrBlock": "10.0.0.0/24",
    "instanceTenancy": "default",
    "amazonProvidedIpv6CidrBlock": false,
    "tagSpecificationSet": {
      "items": [
        {
          "resourceType": "vpc",
          "tags": [
            { "key": "Name", "value": "hogehoge" }
          ]
        }
      ]
    }
  },
  "responseElements": {
    "requestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "vpc": {
      "vpcId": "vpc-xxxxxxxxxxxxxxxxx",
      "state": "pending",
      "ownerId": "123456789012",
      "cidrBlock": "10.0.0.0/24",
      "cidrBlockAssociationSet": {
        "items": [
          {
            "cidrBlock": "10.0.0.0/24",
            "associationId": "vpc-cidr-assoc-xxxxxxxxxxxxxxxxx",
            "cidrBlockState": { "state": "associated" }
          }
        ]
      },
      "dhcpOptionsId": "dopt-xxxxxxxxxxxxxxxxx",
      "instanceTenancy": "default",
      "tagSet": {
        "items": [{ "key": "Name", "value": "hogehoge" }]
      },
      "isDefault": false
    }
  },
  "requestID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "eventID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "readOnly": false,
  "eventType": "AwsApiCall",
  "managementEvent": true,
  "recipientAccountId": "123456789012",
  "eventCategory": "Management",
  "tlsDetails": {
    "tlsVersion": "TLSv1.3",
    "cipherSuite": "TLS_AES_128_GCM_SHA256",
    "clientProvidedHostHeader": "ec2.ap-northeast-1.amazonaws.com"
  },
  "sessionCredentialFromConsole": "true"
}

ポイント

フィールド 意味
eventType AwsApiCall API 呼び出しイベント
eventSource ec2.amazonaws.com EC2 サービスが処理
eventName CreateVpc 呼び出された API 名
readOnly false 書き込み系 API(変更を伴う)
requestParameters CIDR・タグなど リクエスト内容が記録される
responseElements VPC ID・状態など 作成されたリソース情報が記録される
sessionCredentialFromConsole "true" コンソール経由であることを示すフラグ
sessionContext.creationDate 2026-06-20T12:55:16Z ログインセッションの作成時刻

ログインイベントとの紐付け

CreateVpc イベントの sessionContext.creationDate2026-06-20T12:55:16Z)が、ConsoleLogin イベントの eventTime2026-06-20T12:55:16Z)と 完全一致 している。これにより、同一セッションで行われた操作であることが確認できる。

12:55:16Z  ConsoleLogin (AwsConsoleSignIn) ─── セッション開始
    ↕ sessionContext.creationDate で紐付け可能
12:55:39Z  CreateVpc (AwsApiCall)          ─── 同セッションで操作

3. AwsApiCall ── 画面描画による読み取り系 API(readOnly)

コンソールで VPC ダッシュボードを開いたり、作成後に画面が更新されたりすると、大量の読み取り系 API が自動的に呼び出される。

{
  "eventVersion": "1.11",
  "userIdentity": {
    "type": "IAMUser",
    "userName": "example-user",
    "sessionContext": {
      "attributes": {
        "creationDate": "2026-06-20T12:55:16Z",
        "mfaAuthenticated": "false"
      }
    }
  },
  "eventTime": "2026-06-20T12:55:59Z",
  "eventSource": "ec2.amazonaws.com",
  "eventName": "DescribeVpcs",
  "awsRegion": "ap-northeast-1",
  "sourceIPAddress": "xxx.xxx.xxx.xxx",
  "userAgent": "Mozilla/5.0 ...",
  "requestParameters": {
    "maxResults": 1000,
    "vpcSet": {},
    "filterSet": {}
  },
  "responseElements": null,
  "readOnly": true,
  "eventType": "AwsApiCall",
  "managementEvent": true,
  "eventCategory": "Management",
  "sessionCredentialFromConsole": "true"
}

ポイント

フィールド 意味
readOnly true 読み取り専用 API
responseElements null 読み取り系は null(結果は返さない)
eventType AwsApiCall 書き込み系と同じ分類

4. AwsApiCall ── VPC フィルタ操作(readOnly)

VPC 一覧画面でフィルタボックスに VPC ID を入力して絞り込むと、コンソールはブラウザ内でクライアントサイド処理するのではなく、特定の VPC ID を指定して DescribeVpcs を再度呼び出す。これも AwsApiCall(readOnly)として記録される。

初期表示とフィルタ後で requestParameters が変化している点に注目:

# 初期表示(全件取得)
"requestParameters": {
  "maxResults": 1000,
  "vpcSet": {},
  "filterSet": {}
}

# フィルタ後(特定 VPC ID を指定)
"requestParameters": {
  "vpcSet": {
    "items": [{ "vpcId": "vpc-xxxxxxxxxxxxxxxxx" }]
  },
  "filterSet": {}
}

フィルタ後の完全なイベント:

{
  "eventVersion": "1.11",
  "userIdentity": {
    "type": "IAMUser",
    "principalId": "AIDAXXXXXXXXXXXXXXXXX",
    "arn": "arn:aws:iam::123456789012:user/example-user",
    "accountId": "123456789012",
    "accessKeyId": "ASIAXXXXXXXXXXXXXXXXX",
    "userName": "example-user",
    "sessionContext": {
      "attributes": {
        "creationDate": "2026-06-20T13:12:16Z",
        "mfaAuthenticated": "false"
      }
    }
  },
  "eventTime": "2026-06-20T13:12:28Z",
  "eventSource": "ec2.amazonaws.com",
  "eventName": "DescribeVpcs",
  "awsRegion": "ap-northeast-1",
  "sourceIPAddress": "xxx.xxx.xxx.xxx",
  "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36",
  "requestParameters": {
    "vpcSet": {
      "items": [
        { "vpcId": "vpc-xxxxxxxxxxxxxxxxx" }
      ]
    },
    "filterSet": {}
  },
  "responseElements": null,
  "requestID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "eventID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "readOnly": true,
  "eventType": "AwsApiCall",
  "managementEvent": true,
  "recipientAccountId": "123456789012",
  "eventCategory": "Management",
  "tlsDetails": {
    "tlsVersion": "TLSv1.3",
    "cipherSuite": "TLS_AES_128_GCM_SHA256",
    "clientProvidedHostHeader": "ec2.ap-northeast-1.amazonaws.com"
  },
  "sessionCredentialFromConsole": "true"
}

ポイント

フィールド 意味
eventType AwsApiCall フィルタ操作も API 呼び出しとして記録
readOnly true 読み取りのみ
requestParameters.vpcSet.items VPC ID のリスト フィルタで指定した VPC ID が記録される
responseElements null 読み取り系のため null

5. AwsConsoleAction ── 今回の操作では記録なし

AwsConsoleAction は「コンソール上で行われたが API 呼び出しではない操作」に分類される。今回のログイン〜VPC 作成・フィルタ操作を通じて AwsConsoleAction は 1 件も記録されなかった

上記のとおり、VPC のフィルタ操作のようにコンソール上の UI 操作に見えるものも、実際には API 呼び出し(AwsApiCall)として記録される。現状、具体的にどのような操作が AwsConsoleAction に該当するのかは不明。


6. ログイン〜VPC 作成で記録されたイベントの全体像

ログインして VPC 作成画面を操作した際、以下のイベントが時系列で記録されていた。

[ap-southeast-2]
12:55:16Z  ConsoleLogin        (AwsConsoleSignIn) ← ★ ログイン

[ap-northeast-1]
12:55:20Z  ListApplications           (AwsApiCall) ← コンソールホーム描画
12:55:20Z  DescribeRegions            (AwsApiCall)
12:55:21Z  ListNotificationHubs       (AwsApiCall)
12:55:24Z  DescribeVpcs               (AwsApiCall) ← VPCダッシュボード描画開始
12:55:24Z  DescribeSubnets            (AwsApiCall)   ↓ 以下、ダッシュボード描画に伴う
12:55:24Z  DescribeRouteTables        (AwsApiCall)     Describe系APIが並列実行
12:55:24Z  DescribeInternetGateways   (AwsApiCall)
12:55:24Z  DescribeSecurityGroups     (AwsApiCall)
12:55:24Z  DescribeNatGateways        (AwsApiCall)
12:55:24Z  DescribeNetworkAcls        (AwsApiCall)
12:55:24Z  DescribeAddresses          (AwsApiCall)
12:55:24Z  DescribeInstances          (AwsApiCall)
12:55:24Z  ... (多数)
12:55:26Z  DescribeAvailabilityZones  (AwsApiCall) ← VPC作成フォーム表示
12:55:26Z  DescribeIpamPools          (AwsApiCall)
12:55:26Z  DescribeIpv6Pools          (AwsApiCall)
12:55:39Z  CreateVpc                  (AwsApiCall) ← ★ VPC 作成(readOnly: false)
12:55:40Z  DescribeVpcs               (AwsApiCall) ← 作成後の画面更新
12:55:40Z  DescribeRouteTables        (AwsApiCall)
12:55:40Z  DescribeNatGateways        (AwsApiCall)
12:55:40Z  ... (多数)
12:55:57Z  DescribeVpcEncryptionControls (AwsApiCall)
12:55:59Z  DescribeVpcs               (AwsApiCall)

コンソールで 1 回 VPC を作成しただけで、裏側では数十件の AwsApiCall が発生している。


まとめ

操作 eventType readOnly 記録リージョン 主なフィールド
コンソールログイン AwsConsoleSignIn - signin エンドポイントのリージョン responseElements.ConsoleLogin, additionalEventData.MFAUsed
VPC 作成 AwsApiCall false 操作したリージョン requestParameters, responseElements(VPC ID など)
画面描画(Describe 系) AwsApiCall true 操作したリージョン responseElements: null
VPC フィルタ操作 AwsApiCall true 操作したリージョン requestParameters.vpcSet.items(VPC ID)
コンソール固有操作 AwsConsoleAction - - 今回の操作では記録なし(詳細不明)

コンソール操作か CLI/SDK 操作かの判別方法

eventType だけではコンソール操作か CLI/SDK 操作かを区別できない。以下のフィールドを合わせて確認する。

判別材料 コンソール操作 CLI/SDK 操作
sessionCredentialFromConsole "true" 存在しない
userAgent ブラウザの UA(Mozilla/5.0... aws-cli/... / aws-sdk-...

参考


付録: eventType 別に同一 S3 バケットへ振り分ける CDK 実装

AwsConsoleSignIn とそれ以外のイベントを同一 S3 バケットの別フォルダに振り分ける仕組みを AWS CDK(TypeScript)で実装する。

アーキテクチャ

マルチリージョン CloudTrail Trail(全リージョンのイベントを ap-northeast-1 に集約)
  → CloudWatch Logs(ap-northeast-1)
    ├── サブスクリプションフィルタ①: { $.eventType = "AwsConsoleSignIn" }
    │     → Firehose①(console-signin-filter)
    │         → S3: <bucket>/AWSConsoleSignIn/yyyy/MM/dd/
    └── サブスクリプションフィルタ②: { $.eventType != "AwsConsoleSignIn" }
          → Firehose②(console-others-filter)
              → S3: <bucket>/Others/yyyy/MM/dd/

CWL は 1 ロググループあたりサブスクリプションフィルタを最大 2 本設定できる。この上限をちょうど使い切る構成になる。

なぜ EventBridge ではなく Trail + CloudWatch Logs を使うのか

EventBridge には source: aws.signin / detail-type: AWS Console Sign In via CloudTrail という条件でサインインイベントをキャッチするルールを作成できる。しかし "via CloudTrail" という名称が示す通り、このイベントは CloudTrail の Trail が有効になっていないと EventBridge に流れない。Trail なしの状態で EventBridge ルールを作成しても MatchedEvents が常にゼロになる。

さらに EventBridge 方式には構造的な問題がある。ConsoleLogin イベントは signin エンドポイントのリージョンに記録されるため、どのリージョンに記録されるかは環境依存であり、理論上はサインインエンドポイントが存在するすべてのリージョンに EventBridge ルールをデプロイする必要がある。

マルチリージョン Trail 方式ならこれらの問題を両方解決できる。

観点 EventBridge 方式 Trail + CWL 方式(本実装)
Trail の要否 実は必要 必要(明示的に管理)
デプロイリージョン 全 signin リージョン分 ap-northeast-1 のみ
遅延 数秒〜数分 数分(CWL 配信遅延)
リアルタイム検知 可能 不要な用途に最適

プロジェクト構成

signin-filter-cdk/
├── bin/
│   └── app.ts                 # CDK アプリエントリーポイント(1リージョンのみ)
├── lib/
│   └── signin-filter-stack.ts # スタック定義(Trail + CWL + Firehose×2 + S3)
├── cdk.json
├── package.json
└── tsconfig.json

bin/app.ts ── エントリーポイント

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { SigninFilterStack } from '../lib/signin-filter-stack';

const app = new cdk.App();

// ap-northeast-1 の 1 リージョンのみ。マルチリージョン Trail が全リージョンを網羅する
new SigninFilterStack(app, 'SigninFilterStack', {
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region: 'ap-northeast-1',
  },
});

lib/signin-filter-stack.ts ── スタック定義

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as logs from 'aws-cdk-lib/aws-logs';
import * as cloudtrail from 'aws-cdk-lib/aws-cloudtrail';
import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose';

export class SigninFilterStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // 1. S3 バケット: CloudTrail 生ログ(Trail が全管理イベントを書き込む)
    const trailBucket = new s3.Bucket(this, 'TrailBucket', {
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      encryption: s3.BucketEncryption.S3_MANAGED,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
    });

    // 2. CloudWatch Logs グループ: Trail → CWL のストリーミング転送先
    const trailLogGroup = new logs.LogGroup(this, 'TrailLogGroup', {
      logGroupName: '/aws/cloudtrail/management-events',
      retention: logs.RetentionDays.ONE_MONTH,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    // 3. マルチリージョン Trail
    // isMultiRegionTrail: true で全リージョンのイベントを ap-northeast-1 に集約する
    new cloudtrail.Trail(this, 'ManagementTrail', {
      trailName: 'management-events',
      bucket: trailBucket,
      sendToCloudWatchLogs: true,
      cloudWatchLogGroup: trailLogGroup,
      isMultiRegionTrail: true,
      includeGlobalServiceEvents: true,
      enableFileValidation: true,
      managementEvents: cloudtrail.ReadWriteType.ALL,
    });

    // 4. S3 バケット: eventType 別フォルダで振り分けるバケット(Firehose の出力先)
    const signinBucket = new s3.Bucket(this, 'SigninLogsBucket', {
      bucketName: `signinfilterstack-${this.account}`,
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      encryption: s3.BucketEncryption.S3_MANAGED,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
    });

    // 5. IAM ロール: 2本の Firehose が同じロール・同じバケットを共有する
    const firehoseRole = new iam.Role(this, 'FirehoseRole', {
      assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'),
      inlinePolicies: {
        S3Access: new iam.PolicyDocument({
          statements: [
            new iam.PolicyStatement({
              actions: [
                's3:AbortMultipartUpload',
                's3:GetBucketLocation',
                's3:GetObject',
                's3:ListBucket',
                's3:ListBucketMultipartUploads',
                's3:PutObject',
              ],
              resources: [signinBucket.bucketArn, `${signinBucket.bucketArn}/*`],
            }),
          ],
        }),
      },
    });

    // 6a. Firehose①: AwsConsoleSignIn → AWSConsoleSignIn/ プレフィックス
    const signinStream = new firehose.CfnDeliveryStream(this, 'DeliveryStream', {
      deliveryStreamName: 'console-signin-filter',
      deliveryStreamType: 'DirectPut',
      s3DestinationConfiguration: {
        bucketArn: signinBucket.bucketArn,
        roleArn: firehoseRole.roleArn,
        prefix: 'AWSConsoleSignIn/!{timestamp:yyyy}/!{timestamp:MM}/!{timestamp:dd}/',
        errorOutputPrefix: 'errors/signin/!{firehose:error-output-type}/!{timestamp:yyyy}/!{timestamp:MM}/!{timestamp:dd}/',
        bufferingHints: { intervalInSeconds: 60, sizeInMBs: 1 },
        compressionFormat: 'GZIP',
      },
    });

    // 6b. Firehose②: AwsConsoleSignIn 以外 → Others/ プレフィックス
    const othersStream = new firehose.CfnDeliveryStream(this, 'OthersDeliveryStream', {
      deliveryStreamName: 'console-others-filter',
      deliveryStreamType: 'DirectPut',
      s3DestinationConfiguration: {
        bucketArn: signinBucket.bucketArn,
        roleArn: firehoseRole.roleArn,
        prefix: 'Others/!{timestamp:yyyy}/!{timestamp:MM}/!{timestamp:dd}/',
        errorOutputPrefix: 'errors/others/!{firehose:error-output-type}/!{timestamp:yyyy}/!{timestamp:MM}/!{timestamp:dd}/',
        bufferingHints: { intervalInSeconds: 60, sizeInMBs: 1 },
        compressionFormat: 'GZIP',
      },
    });

    // 7. IAM ロール: CWL が両 Firehose にデータを送るための権限
    const cwlToFirehoseRole = new iam.Role(this, 'CWLToFirehoseRole', {
      assumedBy: new iam.ServicePrincipal(`logs.${this.region}.amazonaws.com`),
      inlinePolicies: {
        FirehoseAccess: new iam.PolicyDocument({
          statements: [
            new iam.PolicyStatement({
              actions: ['firehose:PutRecord', 'firehose:PutRecordBatch'],
              resources: [signinStream.attrArn, othersStream.attrArn],
            }),
          ],
        }),
      },
    });

    // 8a. サブスクリプションフィルタ①: AwsConsoleSignIn のみ → Firehose①
    // ConsoleLogin / SwitchRole / RenewRole / ExitRole を一括キャプチャする
    new logs.CfnSubscriptionFilter(this, 'ConsoleLoginFilter', {
      logGroupName: trailLogGroup.logGroupName,
      filterName: 'ConsoleLoginOnly',
      filterPattern: '{ $.eventType = "AwsConsoleSignIn" }',
      destinationArn: signinStream.attrArn,
      roleArn: cwlToFirehoseRole.roleArn,
    });

    // 8b. サブスクリプションフィルタ②: AwsConsoleSignIn 以外 → Firehose②
    // CWL は 1 ロググループあたり最大 2 本のサブスクリプションフィルタを設定できる
    new logs.CfnSubscriptionFilter(this, 'OthersFilter', {
      logGroupName: trailLogGroup.logGroupName,
      filterName: 'OthersOnly',
      filterPattern: '{ $.eventType != "AwsConsoleSignIn" }',
      destinationArn: othersStream.attrArn,
      roleArn: cwlToFirehoseRole.roleArn,
    });

    new cdk.CfnOutput(this, 'TrailBucketName', {
      value: trailBucket.bucketName,
      description: 'CloudTrail 全管理イベント S3 バケット',
    });
    new cdk.CfnOutput(this, 'SigninBucketName', {
      value: signinBucket.bucketName,
      description: 'eventType 別振り分け済みログ S3 バケット',
    });
  }
}

デプロイ手順

cd signin-filter-cdk
npm install
npx cdk bootstrap --profile default  # 初回のみ
npx cdk deploy --profile default

動作確認

コンソールへログインすると、数分後に S3 バケットへ GZIP 圧縮ファイルが保存される。

s3://<bucket>/
├── AWSConsoleSignIn/2026/06/30/
│   └── console-signin-filter-1-2026-06-30-04-12-17-xxxx.gz
└── Others/2026/06/30/
    └── console-others-filter-1-2026-06-30-04-11-26-xxxx.gz

S3 ファイルの構造と注意点

二重 GZIP 形式

S3 に保存されるファイルは 二重 GZIP になっている。

  • 1層目: Firehose が付与する GZIP(compressionFormat: 'GZIP' の設定による)
  • 2層目: CWL がサブスクリプションフィルタ経由で送信する際に付与する GZIP

コマンドラインで展開する場合は 2 段階必要になる。

# 1層目を展開(-k で元ファイルを保持)
gzip -d -k console-signin-filter-1-xxxx.gz

# 展開後もバイナリ(gzip)なので、.gz として再展開
mv console-signin-filter-1-xxxx console-signin-filter-1-xxxx.layer2.gz
gzip -d console-signin-filter-1-xxxx.layer2.gz
# → テキスト JSON が得られる

CWL ラッパー形式(DATA_MESSAGE / CONTROL_MESSAGE)

展開後の JSON は CloudTrail イベントそのものではなく、CWL ラッパーに包まれている。messageType フィールドで 2 種類に分かれる。

messageType 内容 発生タイミング
DATA_MESSAGE 実際の CloudTrail イベントを含む通常データ ログが発生するたびに常時届く
CONTROL_MESSAGE ヘルスチェック(CloudTrail イベントなし) サブスクリプションフィルタの作成・更新時のみ

DATA_MESSAGE の構造:

{
  "messageType": "DATA_MESSAGE",
  "owner": "123456789012",
  "logGroup": "/aws/cloudtrail/management-events",
  "logStream": "123456789012_CloudTrail_ap-northeast-1_4",
  "subscriptionFilters": ["ConsoleLoginOnly"],
  "logEvents": [
    {
      "id": "...",
      "timestamp": 1782792736989,
      "message": "{\"eventVersion\":\"1.11\",\"eventType\":\"AwsConsoleSignIn\",...}"
    }
  ]
}

CONTROL_MESSAGE の構造(CloudTrail イベントは含まない):

{
  "messageType": "CONTROL_MESSAGE",
  "owner": "CloudwatchLogs",
  "logGroup": "",
  "logEvents": [
    { "message": "CWL CONTROL MESSAGE: Checking health of destination Firehose." }
  ]
}

⚠️ CONTROL_MESSAGE はフィルタをバイパスする

{ $.eventType = "AwsConsoleSignIn" } のサブスクリプションフィルタを設定していても、CONTROL_MESSAGE はフィルタパターンを無視して全宛先に強制転送される。これは AWS の仕様であり、回避できない。

  • AWSConsoleSignIn/Others/両フォルダに同時に 1 件ずつ届く
  • 発生するのはサブスクリプションフィルタの作成・更新時のみなので、通常運用中は発生しない
  • messageType == "CONTROL_MESSAGE" で識別できるため、集計処理では除外できる

Firehose が複数レコードを区切り文字なしで連結する

Firehose は 60 秒(または 1MB)のバッファ中に受け取った複数の DATA_MESSAGE を、改行・区切り文字なしで 1 ファイルに連結して S3 に書き出す。

# ファイル内部のイメージ(実際は改行なし)
{"messageType":"DATA_MESSAGE","logEvents":[...]}{"messageType":"DATA_MESSAGE","logEvents":[...]}...

そのため json.loads() で一括パースするとエラーになる。JSONDecoder.raw_decode() を使って 1 オブジェクトずつ切り出す必要がある。

1 つの DATA_MESSAGE に複数の CloudTrail イベントが含まれることがある

CWL は Firehose へ転送するタイミングをバッチにまとめる。60 秒以内に複数の CloudTrail イベントが発生した場合、1 つの DATA_MESSAGElogEvents 配列に複数イベントがまとめて入ることがある。

例: 10 秒差で 2 回ログインした場合

{
  "messageType": "DATA_MESSAGE",
  "logEvents": [
    { "message": "{...\"eventTime\":\"2026-06-30T04:32:34Z\"...}" },
    { "message": "{...\"eventTime\":\"2026-06-30T04:32:44Z\"...}" }
  ]
}

解析スクリプト

上記の構造を踏まえた解析スクリプト:

import gzip, io, json
from collections import Counter

def parse_firehose_file(path: str) -> list[dict]:
    """Firehose が出力した二重 GZIP ファイルから CloudTrail イベントを抽出する"""
    # 二重 GZIP を展開
    with gzip.open(path, 'rb') as f:
        layer1 = f.read()
    with gzip.GzipFile(fileobj=io.BytesIO(layer1)) as gz:
        raw = gz.read().decode('utf-8')

    # Firehose 連結(区切り文字なし)を 1 オブジェクトずつ切り出す
    decoder = json.JSONDecoder()
    events = []
    pos = 0
    while True:
        chunk = raw[pos:].lstrip()
        if not chunk:
            break
        obj, end = decoder.raw_decode(chunk)
        pos += end

        # CONTROL_MESSAGE はヘルスチェックのため除外する
        if obj.get('messageType') != 'DATA_MESSAGE':
            continue

        for log_event in obj['logEvents']:
            events.append(json.loads(log_event['message']))

    return events


events = parse_firehose_file('console-signin-filter-1-xxxx.gz')

# eventType の集計
counter = Counter(e['eventType'] for e in events)
print(counter)
# → Counter({'AwsConsoleSignIn': 2})

# イベント一覧
for e in events:
    print(e['eventType'], e['eventName'], e['eventTime'],
          e.get('userIdentity', {}).get('userName'))
# → AwsConsoleSignIn ConsoleLogin 2026-06-30T04:32:34Z hijiri
# → AwsConsoleSignIn ConsoleLogin 2026-06-30T04:32:44Z hijiri
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?