S3→CloudTrail→EventBridge→Step Functionsの構成からCloudTrailを削除した
S3バケットの特定のパス以下にファイルが作成されたことをトリガに、Step Functionsステートマシーンを実行する構成を作成していました。間にEventBridgeを挟んでS3のPutObjectイベントをキャプチャするのですが、EventBridgeは少し前までは、S3のPutObjectイベントなどのオブジェクトレベルのイベントをキャプチャすることができませんでした。代わりにCloudTrail証跡でオブジェクトレベルのAPI操作のログを記録し、そのログをEventBridgeでキャプチャする必要がありました。 S3 → CloudTrail証跡 → EventBridge Rule → Step Functionsステートマシーンということですね。
ですが昨年末のアップデートで、CloudTrail証跡なしでEventBridgeで直接S3のオブジェクトレベルイベントを拾えるようになりました。
参画中のプロジェクトでこの構成にリファクタリングしました。その内容をレポートします。
S3でEventBridgeの通知を有効にする
デフォルトではS3バケットはオブジェクトレベルイベントの通知をEventBridgeに送信しません。有効化する必要があります。
コンソール
バケットのプロパティページのイベント通知欄に項目があります。「編集」ボタンから進んでオンにしてください。
Terraform
Terraformでやる場合は以下のようにaws_s3_bucket_notification
リソースでeventbridge
argumentをtrueにするだけです。
※ Terraform AWS providerは最近 Version 4がリリースされS3バケット周りの設定方法が大幅に変わりましたが、本設定に関してはVersion3でも4でも同じです。
resource "aws_s3_bucket_notification" "artifact" { bucket = aws_s3_bucket.artifact.id eventbridge = true }
CloudFormation
CloudFormationの場合はAWS::S3::Bucket
のプロパティのNotificationConfiguration.EventBridgeConfiguration.EventBridgeEnabled
をtrueにします。
Resources: S3Bucket: Type: 'AWS::S3::Bucket' Properties: NotificationConfiguration: EventBridgeConfiguration: EventBridgeEnabled: true
CDK
CDKだとまだL2 Construct(High Level Construct)でこの設定をすることはできないようです。L1 Construct(Low Level Construct)を使いましょう。
EventBridge Ruleのイベントパターンを変更する
続いて、EventBridge Ruleのイベントパターンを変更します。まず今回の変更でキャプチャすべきイベントJSONオブジェクトがどの様に変わるのか確認します。
CloudTrail経由のイベントのJSONオブジェクト例(before)
{ "version": "0", "id": "4cffe52e-8d6c-3adc-505d-462f4dae2c19", "detail-type": "AWS API Call via CloudTrail", "source": "aws.s3", "account": "123456789012", "time": "2022-02-10T04:06:49Z", "region": "us-east-1", "resources": [], "detail": { "eventVersion": "1.08", "userIdentity": { "type": "AssumedRole", "principalId": "AROA3JJ2V3S3UAYQIBV6P:kazue@example.com", "arn": "arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_AdministratorAccess_64d4465b3b83b8ff/kazue@example.com", "accountId": "123456789012", "accessKeyId": "ASIA3JJ2V3S3UNG6LTKN", "sessionContext": { "sessionIssuer": { "type": "Role", "principalId": "AROA3JJ2V3S3UAYQIBV6K", "arn": "arn:aws:iam::123456789012:role/aws-reserved/sso.amazonaws.com/AWSReservedSSO_AdministratorAccess_64d4465b3b83b8ff", "accountId": "123456789012", "userName": "AWSReservedSSO_AdministratorAccess_64d4465b3b83b8ff" }, "attributes": { "creationDate": "2022-02-10T02:59:21Z", "mfaAuthenticated": "false" } } }, "eventTime": "2022-02-10T04:06:49Z", "eventSource": "s3.amazonaws.com", "eventName": "PutObject", "awsRegion": "us-east-1", "sourceIPAddress": "203.0.113.1", "userAgent": "[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36]", "requestParameters": { "X-Amz-Date": "20220210T040647Z", "bucketName": "kazue-test-bucket", "X-Amz-Algorithm": "AWS4-HMAC-SHA256", "x-amz-acl": "private", "X-Amz-SignedHeaders": "content-md5;content-type;host;x-amz-acl;x-amz-storage-class", "Host": "kazue-test-bucket.s3.us-east-1.amazonaws.com", "X-Amz-Expires": "300", "key": "testdir/hoge.zip", "x-amz-storage-class": "STANDARD" }, "responseElements": { "x-amz-server-side-encryption": "AES256", "x-amz-version-id": "RhTPG0P2Vdy5_deBzhj6cmHcEIQqEG91" }, "additionalEventData": { "SignatureVersion": "SigV4", "CipherSuite": "ECDHE-RSA-AES128-GCM-SHA256", "bytesTransferredIn": 42369, "SSEApplied": "Default_SSE_S3", "AuthenticationMethod": "QueryString", "x-amz-id-2": "0gw8HnoBBrp6qsDpZ0ZsnyVCfLe6B4e8ZML7ysADQZkC8q0F47vn2N28gFnncFDjKipYzjSqxIk=", "bytesTransferredOut": 0 }, "requestID": "ZZTDH1K6TPJAN610", "eventID": "16515f34-d8ae-4fcd-9500-6b28049cb14d", "readOnly": false, "resources": [ { "type": "AWS::S3::Object", "ARN": "arn:aws:s3:::kazue-test-bucket/testdir/hoge.zip" }, { "accountId": "123456789012", "type": "AWS::S3::Bucket", "ARN": "arn:aws:s3:::kazue-test-bucket" } ], "eventType": "AwsApiCall", "managementEvent": false, "recipientAccountId": "123456789012", "eventCategory": "Data" } }
S3イベントのJSONオブジェクト例(after)
{ "version": "0", "id": "0db5f183-ecf2-ecd7-281c-229291d6b510", "detail-type": "Object Created", "source": "aws.s3", "account": "12345678901", "time": "2022-02-14T08:43:15Z", "region": "us-east-1", "resources": [ "arn:aws:s3:::kazue-test-bucket" ], "detail": { "version": "0", "bucket": { "name": "kazue-test-bucket" }, "object": { "key": "testdir/hoge.zip", "size": 0, "etag": "d41d8cd98f00b204e9800998ecf8427e", "version-id": "Ol_eYI0xGjttxhTecgMhNkqC.7HRw036", "sequencer": "00620A1623001910FB" }, "request-id": "A7CFWHJZN7H45821", "requester": "123456789012", "source-ip-address": "147.192.166.89", "reason": "PutObject" } }
これを踏まえてイベントパターンを設定します。今回は例として、kazue-test-bucket
という名前のS3バケットのtestdir
ディレクトリ以下にファイルが作成されたことをキャプチャできるルールを作成しています。
イベントパターン before
{ "detail-type": ["AWS API Call via CloudTrail"], "source": ["aws.s3"], "detail": { "eventSource": ["s3.amazonaws.com"], "eventName": ["PutObject"], "requestParameters": { "bucketName": ["kazue-test-bucket"], "key": [{ "prefix": "testdir/" }] } } }
イベントパターン after
{ "detail-type": ["Object Created"], "source": ["aws.s3"], "detail": { "bucket": { "name": ["kazue-test-bucket"] }, "object": { "key": [{ "prefix": "testdir/" }] } } }
diff
{ - "detail-type": ["AWS API Call via CloudTrail"], + "detail-type": ["Object Created"], "source": ["aws.s3"], "detail": { - "eventSource": ["s3.amazonaws.com"], - "eventName": ["PutObject"], - "requestParameters": { - "bucketName": ["kazue-test-bucket"], + "bucket": { + "name": ["kazue-test-bucket"] + }, + "object": { "key": [{ "prefix": "testdir/" }] } }
Step Functionsステートマシーンの定義変更
上記EventBridge RuleのターゲットとしてStep Functionsステートマシーンを設定すると、作成されたS3オブジェクトに関する情報をステートマシーン内でContext オブジェクトのプロパティとして使うことができます。イベントオブジェクトが変わったので当然このContextを参照している部分も変更が必要です。
今回のケースですとオブジェクトのキー値を参照している箇所と、フルパス(=バケット名+キー)値を参照している箇所があったので、それぞれ以下のように変更しました。
キー
"Parameters": { - "objectKey.$": "$$.Execution.Input.detail.requestParameters.key" + "objectKey.$": "$$.Execution.Input.detail.object.key" }
フルパス
"Parameters": { - "fileLocation.$": "States.Format('{}/{}', $$.Execution.Input.detail.requestParameters.bucketName, $$.Execution.Input.detail.requestParameters.key)" + "fileLocation.$": "States.Format('{}/{}', $$.Execution.Input.detail.bucket.name, $$.Execution.Input.detail.object.key)" }
CloudTrail証跡を削除する
あとは不要になったCloudTrail証跡を削除するだけです。証跡を保管しているS3バケットは(削除の仕方によりますが)まずバケット内を空にしないと削除できませんのでご注意ください。