特定のタグが設定されたAMIを作成した際にEventBridgeからSNSへ発行する

2021.08.31

いわさです。

EC2のAMIを取得する際にタグ付けをすることがあると思います。
特定のタグが付与されたAMIが作成された時のイベントをフックして処理を行いたいケースが出ましてEventBridgeでどうにか出来ないかなと検討をしていました。

EventBridgeには「サービスごとの事前定義パターン」というものが用意されていて、ある程度のものであればそれで済んだり済まなかったりします。

しかし、私が今回検出したかった、AMI作成イベントについては事前定義パターンとして用意されていないようでした。

しかし、EventBridgeはCloudTrailのイベントレコードをトリガーとすることも出来るので、CloudTrailに発生するレコードであれば全てトリガーにすることが出来ます。
また、CloudTrailであればタグやリソースなど様々な情報を保持しているため、イベント発生の条件に活用することも出来そうです。

今回はCloudTrailイベントレコードを使って、特定タグのAMIが作成された時にイベント通知をさせてみます。

EC2のイメージ作成から

まずは、CloudTrailでイベント履歴からイベントレコードを探します。
AMI作成はCreateImageイベントが対象となります。

手動でAMIを作成した時のCreateImageイベントレコード

{
    "eventVersion": "1.08",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "111111111111:cm-iwasa.takahito",
        "arn": "arn:aws:sts::111111111111:assumed-role/cm-iwasa.takahito/cm-iwasa.takahito",
        "accountId": "111111111111",
        "accessKeyId": "111111111111",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "111111111111",
                "arn": "arn:aws:iam::111111111111:role/cm-iwasa.takahito",
                "accountId": "111111111111",
                "userName": "cm-iwasa.takahito"
            },
            "webIdFederationData": {},
            "attributes": {
                "creationDate": "2021-08-30T12:12:11Z",
                "mfaAuthenticated": "true"
            }
        }
    },
    "eventTime": "2021-08-30T12:35:09Z",
    "eventSource": "ec2.amazonaws.com",
    "eventName": "CreateImage",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "111.111.111.111",
    "userAgent": "EC2ConsoleFrontend, aws-internal/3 aws-sdk-java/1.11.1030 Linux/5.4.134-73.228.amzn2int.x86_64 OpenJDK_64-Bit_Server_VM/25.292-b10 java/1.8.0_292 vendor/Oracle_Corporation cfg/retry-mode/legacy",
    "requestParameters": {
        "instanceId": "i-077b9dc382195c889",
        "name": "hogehoge",
        "description": "hoge",
        "noReboot": false,
        "blockDeviceMapping": {
            "items": [
                {
                    "deviceName": "/dev/xvda",
                    "ebs": {
                        "volumeSize": 8,
                        "deleteOnTermination": true
                    }
                }
            ]
        },
        "tagSpecificationSet": {
            "items": [
                {
                    "resourceType": "image",
                    "tags": [
                        {
                            "key": "deprecation",
                            "value": "hoge"
                        }
                    ]
                },
                {
                    "resourceType": "snapshot",
                    "tags": [
                        {
                            "key": "deprecation",
                            "value": "hoge"
                        }
                    ]
                }
            ]
        }
    },
    "responseElements": {
        "requestId": "99bf5f7f-6207-4f50-8dde-045223f42859",
        "imageId": "ami-07db53af262aff8c1"
    },
    "requestID": "99bf5f7f-6207-4f50-8dde-045223f42859",
    "eventID": "17cb917d-84d3-4cdd-9f48-131fdc0989f6",
    "readOnly": false,
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "111111111111",
    "eventCategory": "Management"
}

イベントパターンのdetail配下からがCloudTrailイベントログになります。
上記ハイライトした部分をパターンに当てはめると以下のようなイベントパターンになります。

イベントパターン

{
  "source": ["aws.ec2"],
  "detail-type": ["AWS API Call via CloudTrail"],
  "detail": {
    "eventSource": ["ec2.amazonaws.com"],
    "eventName": ["CreateImage"],
    "errorCode": [{
      "exists": false
    }],
    "requestParameters": {
      "tagSpecificationSet": {
        "items": {
          "resourceType": ["image"],
          "tags": {
            "key": ["deprecation"],
            "value": ["hoge"]
          }
        }
      }
    }
  }
}

タグ情報にはAMIとスナップショットそれぞれの情報が含まれているので、リソースタイプでフィルタリングしています。
また、CreateImageコール時にエラーが発生した場合でもCloudTrailイベントレコードは発生するので、errorCodeフィールドを含むイベントは無視するようにしています。

今回はイベント通知先にSNSトピックを指定します。
通知内容はAMI IDのみを出力するようにします。

一致したイベントの一部で以下のようにAMI IDが含まれるプロパティを指定しました。

$.detail.responseElements.imageId

ではAMIを作成してみましょう。

通知されました!

注意点としては、CreateImageイベントはPending状態で通知が飛んできます。
ですので、もしLambdaなどで後続処理を行いたい場合は、後続処理の内容によってはAMI作成ステータスがAvailableになるまで待機する必要があるかもしれません。

opswitchから

弊社より提供しているopswitchでもEC2のバックアップとしてAMIを取得することが出来ます。
また、AMIの作成時に任意のタグを付与することも出来ます。

opswtichとても使いやすくてオススメです。
無料ですので是非みなさん使ってみてください。

さて、前述のとおりopswitchでEC2のバックアップ取得が出来ますが、先程作成したEC2のイメージ作成と同じイベントを使うことは出来ません。

CloudTrailのイベントレコードを見てみましょう。

opswitch EC2バックアップ取得時のCreateImageイベントレコード

{
    "eventVersion": "1.08",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "111111111111:189ffcd7-45fb-42ad-8405-4d980025bbfd",
        "arn": "arn:aws:sts::111111111111:assumed-role/opswitch-stack-IAMRole-111111111111/189ffcd7-45fb-42ad-8405-111111111111",
        "accountId": "111111111111",
        "accessKeyId": "111111111111",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "111111111111",
                "arn": "arn:aws:iam::111111111111:role/opswitch-stack-IAMRole-111111111111",
                "accountId": "111111111111",
                "userName": "opswitch-stack-IAMRole-111111111111"
            },
            "webIdFederationData": {},
            "attributes": {
                "creationDate": "2021-08-30T11:04:50Z",
                "mfaAuthenticated": "false"
            }
        }
    },
    "eventTime": "2021-08-30T11:04:51Z",
    "eventSource": "ec2.amazonaws.com",
    "eventName": "CreateImage",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "111.111.111.111",
    "userAgent": "Boto3/1.12.35 Python/3.5.5 Linux/4.14.181-140.257.amzn2.x86_64 exec-env/AWS_ECS_EC2 Botocore/1.15.35 Resource",
    "requestParameters": {
        "instanceId": "i-077b9dc382195c889",
        "name": "i-077b9dc382195c889_2021-08-30_2004",
        "noReboot": true
    },
    "responseElements": {
        "requestId": "2c44897f-20cb-4b10-a1f7-7353109405f1",
        "imageId": "ami-02c4da7fce6a06413"
    },
    "requestID": "2c44897f-20cb-4b10-a1f7-7353109405f1",
    "eventID": "cd1c99f2-6d66-42de-842e-21387662bb85",
    "readOnly": false,
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "111111111111",
    "eventCategory": "Management"
}

タグに関する情報が無いんですね。

どうやらopswitchでは時間差(AMIの作成ステータス確認後?)でタグを別途付与しているようです。

よく見ると、CreateImageのあとにCreateTagsイベントレコードが発行されています。
それも、SnapShot用とAMI用で2回タグ作成イベントが発行されています。

CreateTagsイベントはSnapShotとAMIで構造の違いは無さそうですが、リソースIDの接頭語で判別することが出来そうです。

opswitch EC2バックアップ取得時のCreateTagsイベントレコード

{
    "eventVersion": "1.08",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "111111111111:189ffcd7-45fb-42ad-8405-4d980025bbfd",
        "arn": "arn:aws:sts::111111111111:assumed-role/opswitch-stack-IAMRole-111111111111/189ffcd7-45fb-42ad-8405-111111111111",
        "accountId": "111111111111",
        "accessKeyId": "111111111111",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "111111111111",
                "arn": "arn:aws:iam::111111111111:role/opswitch-stack-IAMRole-111111111111",
                "accountId": "111111111111",
                "userName": "opswitch-stack-IAMRole-111111111111"
            },
            "webIdFederationData": {},
            "attributes": {
                "creationDate": "2021-08-30T11:04:50Z",
                "mfaAuthenticated": "false"
            }
        }
    },
    "eventTime": "2021-08-30T11:06:31Z",
    "eventSource": "ec2.amazonaws.com",
    "eventName": "CreateTags",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "111.111.111.111",
    "userAgent": "Boto3/1.12.35 Python/3.5.5 Linux/4.14.181-140.257.amzn2.x86_64 exec-env/AWS_ECS_EC2 Botocore/1.15.35 Resource",
    "requestParameters": {
        "resourcesSet": {
            "items": [
                {
                    "resourceId": "ami-02c4da7fce6a06413"
                }
            ]
        },
        "tagSet": {
            "items": [
                {
                    "key": "cm-Task",
                    "value": "iwasa ami backup"
                },
                {
                    "key": "creation_info",
                    "value": "dfc86e0f-adb8-4711-b8d7-22ccd6504a04_2021-08-30_20-00-21-254_0_i-077b9dc382195c889"
                },
                {
                    "key": "deprecation",
                    "value": "hoge"
                }
            ]
        }
    },
    "responseElements": {
        "requestId": "6cf6471f-0df6-46f9-8951-bbacf6f2bb0b",
        "_return": true
    },
    "requestID": "6cf6471f-0df6-46f9-8951-bbacf6f2bb0b",
    "eventID": "1ee9ef95-5643-4318-81ff-17be6603ce6b",
    "readOnly": false,
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "111111111111",
    "eventCategory": "Management"
}

ハイライトした部分を取得して、リソースIDは部分一致でフィルタリングしてやるのが良さそうです。

部分一致にはワイルドカードは使えなくて、プレフィックスフィルタリングの機能を使うことが出来ます。
特殊なフィルターを使いたい場合は以下が参考になります。

最終的にはこうなりました。

イベントパターン

{
  "source": ["aws.ec2"],
  "detail-type": ["AWS API Call via CloudTrail"],
  "detail": {
    "eventSource": ["ec2.amazonaws.com"],
    "eventName": ["CreateTags"],
    "requestParameters": {
      "resourcesSet": {
        "items": {
          "resourceId": [{
            "prefix": "ami-"
          }]
        }
      },
      "tagSet": {
        "items": {
          "key": ["deprecation"],
          "value": ["hoge"]
        }
      }
    }
  }
}

opswitchでEC2バックアップを作成してみましょう。

今回もSNSでメール通知しました。
送信コンテンツは先程と同様にオブジェクトからAMIのIDを取得しましたが、取得先は違います。

$.detail.requestParameters.resourcesSet.items[0].resourceId

ゼロインデックスを見ると予期せぬ挙動をしそうな感覚になって気持ち悪いですよね。
通知自体は成功しました。

最後に

実は私が把握してないだけでAMI作成ならこっちで拾えるよ、なんてあるかもしれませんが、CloudTrailのイベントレコードからイベントパターンが生成出来るようになると、EventBridgeの活用の幅が一気に広がった気がします。

AMIの日次取得を検知した先については、また別のタイミングで活用の記事を作成したいと思います。