一般的な方法
前記事 の最後で言及したのですが、一般的な方法は、
【米国東部(バージニア北部)リージョンの EventBridge ルール】
↓
【米国東部(バージニア北部)リージョンの SNSトピック】
の2つのサービスの連携があって、そのうち、【SNSトピック】の代わりに【Lambda関数】にすれば、連携できます。
手順は、AWSドキュメント - AWS アカウントに IAM の変更が加えられたときに通知を受け取るにはどうすればよいですか? に記載がありますので、このリンク先に従って進めていき、SNSのところだけLambda関数にすれば実装できます。
別リージョンのLambda関数にイベントを渡す方法
上記の例では、Lambda関数はIAMのイベントを拾えるリージョンと同じ、米国東部(バージニア北部)リージョンに作成することになります。
ここでは、「東京リージョンのLambda関数にイベントを渡したいんだ!」 という人がいた場合に(自分のことですが)、どのように実装するかを書きます。
サービス連携
【米国東部(バージニア北部)リージョンの EventBridge ルール】
↓
【東京リージョンの Event Bridge イベントバス】
↓
【東京リージョンの EventBridge ルール】
↓
【東京リージョンの Lambda関数】
このようなサービス連携にすれば実現できます。
ステップ1 :東京リージョンのLambda関数の作成
マネージメントコンソールに接続し、東京リージョンのLambdaコンソールを開きます。
作成の選択肢:一から作成
関数名:simple-event-log
ランタイム:Python 3.x
その他デフォルト値
Helloのサンプルを消して、超シンプルな3行のpythonの関数に書き換えます。
def lambda_handler(event, content):
print(event)
return {}
デプロイボタンで保存します。
テストボタンで、デフォルトのテストデータ { "key1" : "value1" }
等のテスト作成します。
続けてテスト実行します。
CloudWatchのロググループにアクセスします。
/aws/lambda/simple-event-log
ロググループで指定のデータ { 'key1' : 'value1' }
がそのまま記録されていることを確認できればOKです。
ステップ2 :東京リージョンの EventBridge イベントバスの作成
東京リージョンのままで、EventBridge イベントバスを作成します。
- 名前:iam-event-bus
- その他の設定はデフォルト
作成されたら、ARN名 arn:aws:events:ap-northeast-1:XXXXXXXXXXXX:event-bus/iam-event-bus をテキストエディタなどにコピーしておきます。
ステップ3 :東京リージョンの EventBridge ルール作成
東京リージョンのままで、EventBridgeルールを作成します。
- 名前:iam-event-to-lambda
- イベントバス:iam-event-bus
- ルールタイプ:イベントパターンを持つルール
- イベントソース:AWSイベントまたはEventBridgeパートナーイベント
- 作成のメソッド:カスタムパターン(JSONエディタ)
イベントパターン
{
"source" : ["aws:iam"]
}
- ターゲット1
- ターゲットタイプ:AWSのサービス
- ターゲットを選択:Lambda関数
- 機能:simple-event-log
- その他はデフォルト値
ルールを作成します。
IAMイベントを全通しするルールになっています。
ステップ4 :バージニア北部リージョンの EventBridge ルール作成
**バージニア北部リージョンの** EventBridgeルールを作成します。
右上が「バージニア北部」となっていることを確認します(しつこい)。
- 名前:iam-event-to-ap-northeast-1
- イベントバス:default (ここはdefaultでよい)
- ルールタイプ:イベントパターンを持つルール
- イベントソース:AWSイベントまたはEventBridgeパートナーイベント
- 作成のメソッド:カスタムパターン(JSONエディタ)
イベントパターン
{
"source" : ["aws:iam"],
"detail" : {
"eventSource" : ["iam.amazonaws.com"],
"eventName" : [
{ "prefix" : "Add" },
{ "prefix" : "Attach" },
{ "prefix" : "Change" },
{ "prefix" : "Create" },
{ "prefix" : "Detach" },
{ "prefix" : "Put" },
{ "prefix" : "Remove" },
{ "prefix" : "Set" },
{ "prefix" : "Update" }
]
}
}
- ターゲット1
- ターゲットタイプ:EventBridgeイベントバス
- ターゲットタイプ(イベントバス):別のアカウントまたはリージョンのイベントバス
- ターゲットとしてのイベントバス:(先ほどテキストエディタにコピーしたARN名)
- 実行ロール:新しいロールを作成 (ロール名を変えたい場合は任意で)
ルールを作成します。
IAMの変更のAPIだけを捕まえる、ということで、APIのプレフィックスを使ってイベントを捕まえるのが良いのではと思い、このイベントパターンにしています。
その他、Generate、Tag、Untag、Resync、Reset 等も変更のAPIに当たりそうなので、必要に応じて追加してもいいでしょう。
動作確認
何らかのIAMに関する変更を加えてみましょう。
IAMユーザーグループの作成、IAMユーザーへIAMポリシーのアタッチ、IAMユーザーの削除、等、なんでもよいです。
権限が無くてもよいです。権限のエラーで失敗したとしても、エラーイベントが発動されます。
**東京リージョンの** CloudWatchのロググループにアクセスします。
/aws/lambda/simple-event-log
ロググループを再読み込みします。
IAMのイベントが届いていることを確認します。
{
"version": "0",
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"detail-type": "AWS API Call via CloudTrail",
"source": "aws.iam",
"region": "us-east-1",
"resources": []
"detail": {
"userIdentity" : {
"arn" : "arn:aws:iam:xxxxxxxxxxxx:user/UserNameHere",
"userName" : "UserNameHere"
},
"eventTime" : "2023-02-07T08:10:12Z",
"eventName" : "DeleteUser",
"requestParameters" : {
"userName" : "hogehoge1"
},
"sourceIPAddress" : "12.34.56.78",
}
}
実際にはもっと項目が多いですが、重要な項目だけをピックアップしました。
ここから、12.34.56.78
のIPアドレスで接続した UserNameHere
さんが 2023/02/07 08:10:12
のUTC日時に DeleteUser
APIを実行して hogehoge1
ユーザーを削除した ということが分かります。
Lambda関数に他の入力データも渡したい場合
Lambda関数によっては、元々入力データを使っている場合もあると思います。
その場合、元の入力データとEventBridgeの入力データと、両方のデータが必要になってきます。
いくつか方法があると思いますが、ひとつに Lambda の前に Step Functions をはさむ 方法があります。
元データを {"foo" : "bar"}
とすると、
入力データ(ペイロード)として、
{
"foo" : "bar",
"orginalEvent.$" : "$"
}
と指定すると、"originalEvent"
に元のEventデータが格納され、そこに元も合わせて Lambdaに渡すことができます。
(.$
が付いているのは、誤記ではなく、この表記です)
"originalEvent" の箇所は違う名称でもいいです。
注意事項
今回のEventBridgeは、IAMの変更系イベントをそのまま全スルーしているため、無限ループを作らないように注意が必要です。
具体的には、Lambda関数の中では 絶対に IAMの変更APIを呼んではいけません。それがまたIAMのイベント発生になり、Lambda関数呼び出しになり・・・と無限ループになってしまって、高額な請求につながります。
Lambda関数に送って何がしたいのか
例えば・・・
- 社内ルールで監査ログ保管期間が決まっているなどで、S3などに長期保存する
- 着任者や離任者と、IAMユーザーの整合性をチェックする
「IAM変更したのは誰だっ!」と吠えられた時の犯人探しがすぐできるように備える
など、活用できますね。