CloudTrail イベントを CloudWatch Logs メトリクスフィルターで監視している時に特定の IAM ロールによる操作は検知対象外としたい
コンバンハ、千葉(幸)です。
今回想定しているのは以下の構成です。
- CloudTrail ログを CloudWatch Logs に出力している
- CloudWatch Logs ロググループのメトリクスフィルターで特定のイベントを検知するようにしている
例えば以下のように IAM ポリシーの変更・アタッチ・デタッチ・削除などのイベントを検知させ、該当イベントが起こった場合に CloudWatch アラームを発報するような使い方です。
ここで、検知対象のアクションを定常的にを行う(例えば定期的に IAM ポリシーのメンテナンスをする)役割の IAM ロールがあった場合、都度通知が行われるのがノイズになることもあるでしょう。
特定の IAM ロールによる操作は上記の検知の対象外としたい、というのが今回のテーマです。
先にまとめ
もともとのフィルターパターンが以下だったとして……
{($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventName=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolicy)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=DeletePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersion)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.eventName=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGroupPolicy)||($.eventName=DetachGroupPolicy)}
cm-policymaintainer
という IAM ロールによる操作を対象外としたい場合は、こう書く。
{($.userIdentity.sessionContext.sessionIssuer.userName!=cm-policymaintainer)&&(($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventName=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolicy)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=DeletePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersion)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.eventName=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGroupPolicy)||($.eventName=DetachGroupPolicy))}
もともとのフィルターパターンを{A}
とすると、以下の書き方をする、ということです。
{($.userIdentity.sessionContext.sessionIssuer.userName!=cm-policymaintainer)&&(A)}
今回やりたいこと
今回やりたいことについてもう少し補足します。
CloudTrail のイベント監視を仕掛けているアカウントに IAM ロールがあり、別アカウントの IAM ロールがそれを引き受けて作業するケースを想定しています。
ここでは以下の通りの名称であるとします。
- 引き受けられる IAM ロール:
cm-policymaintainer
- 引き受ける IAM ロール:
cm-account-manager
最終的に使用された IAM ロールがcm-policymaintainer
であればメトリクスフィルターに引っかからないようにしたいです。
ここで、やろうと思えば引き受け元のエンティティまで含めてメトリクスフィルターを設定できます。最終的に使用される IAM ロールがcm-policymaintainer
であっても、それを引き受けるのが IAM ロールhoge
や IAM ユーザーであった場合には検知対象のままとする、ということです。(※厳密に言うと完全にできる、とは言えないかもしれません。後述します。)
今回は IAM ロールcm-policymaintainer
を引き受けられるエンティティが信頼ポリシーによって適切に制御されていることを前提に、最終的に使用されるロールのみを基準としての除外とします。
IAM ロールを引き受けたセッションによるイベント
上記の構成で操作を行った場合の CloudTrail イベント例が以下です。
ここではcm-policymaintainer
が自身のインラインポリシーを変更しています。
{ "eventVersion": "1.08", "userIdentity": { "type": "AssumedRole", "principalId": "AROAXXXXXXXXXXXXXXXX:cm-account-manager", "arn": "arn:aws:sts::000000000000:assumed-role/cm-policymaintainer/cm-account-manager", "accountId": "000000000000", "accessKeyId": "ASIAXXXXXXXXXXXXXXXX", "sessionContext": { "sessionIssuer": { "type": "Role", "principalId": "AROAXXXXXXXXXXXXXXXX", "arn": "arn:aws:iam::000000000000:role/cm-policymaintainer", "accountId": "000000000000", "userName": "cm-policymaintainer" }, "webIdFederationData": {}, "attributes": { "creationDate": "2022-05-13T17:24:45Z", "mfaAuthenticated": "false" } } }, "eventTime": "2022-05-13T17:24:49Z", "eventSource": "iam.amazonaws.com", "eventName": "PutRolePolicy", "awsRegion": "us-east-1", "sourceIPAddress": "18.xx.xx.xx", "userAgent": "Boto3/1.20.52 Python/3.9.11 Linux/4.14.255-273-220.498.amzn2.aarch64 exec-env/AWS_Lambda_python3.9 Botocore/1.23.52", "requestParameters": { "roleName": "cm-policymaintainer", "policyName": "CMMaintenancePolicy", "policyDocument": "略" }, "responseElements": null, "requestID": "0ebfbdbf-xxxx-xxxx-xxxx-c374fe528477", "eventID": "1812dde7-xxxx-xxxx-xxxx-2f7853657bee", "readOnly": false, "eventType": "AwsApiCall", "managementEvent": true, "recipientAccountId": "000000000000", "eventCategory": "Management", "tlsDetails": { "tlsVersion": "TLSv1.2", "cipherSuite": "ECDHE-RSA-AES128-GCM-SHA256", "clientProvidedHostHeader": "iam.amazonaws.com" } }
15 行目の部分をメトリクスフィルターのフィルターパターンに使用しています。15行目がcm-policymaintainer
でない、かつ A である、という定義になります。
{($.userIdentity.sessionContext.sessionIssuer.userName!=cm-policymaintainer)&&(A)}
冒頭で例に挙げたA
の中では、26行目のeventName
部を OR 条件で繋いでいます。
($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventName=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolicy)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=DeletePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersion)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.eventName=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGroupPolicy)||($.eventName=DetachGroupPolicy)
IAM ロールを引き受けたエンティティまで指定したい場合は?
「誰がIAMロールを引き受けたか」まで限定したい場合は、以下 5行目を用いるとよいでしょう。
.....略 "userIdentity": { "type": "AssumedRole", "principalId": "AROAXXXXXXXXXXXXXXXX:cm-account-manager", #AROAから始まるIDは11行目のものと同一 "arn": "arn:aws:sts::000000000000:assumed-role/cm-policymaintainer/cm-account-manager", "accountId": "000000000000", "accessKeyId": "ASIAXXXXXXXXXXXXXXXX", #AssumeRoleした際に払い出された一時的なアクセスキー "sessionContext": { "sessionIssuer": { "type": "Role", "principalId": "AROAXXXXXXXXXXXXXXXX", #最終的に使用されるロールの一意の識別子 "arn": "arn:aws:iam::000000000000:role/cm-policymaintainer", "accountId": "000000000000", "userName": "cm-policymaintainer" }, ....略
5行目はロールを引き受けたセッションの ARN を表し、以下の構成になっています。
arn:aws:sts::<アカウント番号>:assumed-role/<ロール名>/<セッション名>
最後のセッション名にあたる部分として、今回の例では引き受け元の IAM ロールcm-account-manager
の名称が設定されています。IAM ユーザーが引き受け元となる場合にはユーザー名がセッション名として設定されます。
ただし、セッション名は一部を除きカスタマー側でコントロールできる部分です。セッション名がcm-account-manager
であるからといって、引き受け元の IAM ユーザー/IAMロールの名称がcm-account-manager
であるとは限りません。
そのため、この方式が成立するためには IAM ロールの信頼ポリシーで引き受け可能なエンティティが適切に制限されていることが前提になります。
4行目の principalId は使えないの?
セッション名でしか指定できないことが問題なら、引き受け元のエンティティそのものを指定できる部分はないの?というのが気になるところです。結果から言うとありません。
4行目は以下記載となっており、初見では引き受け元の IAM ロールを指しているのかと捉えました。
"principalId": "AROAXXXXXXXXXXXXXXXX:cm-account-manager"
AROAから始まる ID は IAM ロールの 一意の識別子 を表すため、名称から見ても引き受け元の IAM ロールのことを指していると考えたのです。
実際には、以下の構成になっていました。
"principalId": "<引き受けられるIAMロールの識別子>:<セッション名>"
ここでも引き受け元の情報はセッション名しか得られないため、同じ問題に直面します。残念。
フィルターパターンをテストする
ここまで挙げてきたフィルターパターンが意図通りに機能するかをテストします。
テストは CloudWatch Logs ロググループからメトリクスフィルターの作成/変更を試みる際の画面から実行できます。
イベントメッセージに以下の形式のイベントをそのまま入力できれば楽なのですが、改行を挟むと別のログイベントとしてみなされるため意図通りに機能しません。
{ "eventVersion": "1.08", "userIdentity": { "type": "AssumedRole", "principalId": "AROAXXXXXXXXXXXXXXXX:cm-account-manager", "arn": "arn:aws:sts::000000000000:assumed-role/cm-policymaintainer/cm-account-manager", "accountId": "000000000000", "accessKeyId": "ASIAXXXXXXXXXXXXXXXX", "sessionContext": { "sessionIssuer": { "type": "Role", "principalId": "AROAXXXXXXXXXXXXXXXX", "arn": "arn:aws:iam::000000000000:role/cm-policymaintainer", "accountId": "000000000000", "userName": "cm-policymaintainer" }, "webIdFederationData": {}, "attributes": { "creationDate": "2022-05-13T17:24:45Z", "mfaAuthenticated": "false" } } }, "eventTime": "2022-05-13T17:24:49Z", "eventSource": "iam.amazonaws.com", "eventName": "PutRolePolicy", "awsRegion": "us-east-1", "sourceIPAddress": "18.xx.xx.xx", "userAgent": "Boto3/1.20.52 Python/3.9.11 Linux/4.14.255-273-220.498.amzn2.aarch64 exec-env/AWS_Lambda_python3.9 Botocore/1.23.52", "requestParameters": { "roleName": "cm-policymaintainer", "policyName": "CMMaintenancePolicy", "policyDocument": "略" }, "responseElements": null, "requestID": "0ebfbdbf-xxxx-xxxx-xxxx-c374fe528477", "eventID": "1812dde7-xxxx-xxxx-xxxx-2f7853657bee", "readOnly": false, "eventType": "AwsApiCall", "managementEvent": true, "recipientAccountId": "000000000000", "eventCategory": "Management", "tlsDetails": { "tlsVersion": "TLSv1.2", "cipherSuite": "ECDHE-RSA-AES128-GCM-SHA256", "clientProvidedHostHeader": "iam.amazonaws.com" } }
jq -c
で 1行のエントリに変換します。わたしは Mac を使用しているので、イベントをクリップボードにコピーした状態で以下を実行しました。
$ pbpaste | jq -c | pbcopy
1 行にしたログイベントを入力してから「パターンをテスト」を実行し、結果に表示されればフィルターパターンに合致するということになります。
変更前のフィルターパターンの場合
まずは以下のフィルターパターンです。
{($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventName=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolicy)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=DeletePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersion)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.eventName=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGroupPolicy)||($.eventName=DetachGroupPolicy)}
先ほどのサンプルイベントでテストすると、フィルターパターンの一致が確認できます。
{"eventVersion":"1.08","userIdentity":{"type":"AssumedRole","principalId":"AROAXXXXXXXXXXXXXXXX:cm-account-manager","arn":"arn:aws:sts::000000000000:assumed-role/cm-policymaintainer/cm-account-manager","accountId":"000000000000","accessKeyId":"ASIAXXXXXXXXXXXXXXXX","sessionContext":{"sessionIssuer":{"type":"Role","principalId":"AROAXXXXXXXXXXXXXXXX","arn":"arn:aws:iam::000000000000:role/cm-policymaintainer","accountId":"000000000000","userName":"cm-policymaintainer"},"webIdFederationData":{},"attributes":{"creationDate":"2022-05-13T17:24:45Z","mfaAuthenticated":"false"}}},"eventTime":"2022-05-13T17:24:49Z","eventSource":"iam.amazonaws.com","eventName":"PutRolePolicy","awsRegion":"us-east-1","sourceIPAddress":"18.xx.xx.xx","userAgent":"Boto3/1.20.52 Python/3.9.11 Linux/4.14.255-273-220.498.amzn2.aarch64 exec-env/AWS_Lambda_python3.9 Botocore/1.23.52","requestParameters":{"roleName":"cm-policymaintainer","policyName":"CMMaintenancePolicy","policyDocument":"略"},"responseElements":null,"requestID":"0ebfbdbf-xxxx-xxxx-xxxx-c374fe528477","eventID":"1812dde7-xxxx-xxxx-xxxx-2f7853657bee","readOnly":false,"eventType":"AwsApiCall","managementEvent":true,"recipientAccountId":"000000000000","eventCategory":"Management","tlsDetails":{"tlsVersion":"TLSv1.2","cipherSuite":"ECDHE-RSA-AES128-GCM-SHA256","clientProvidedHostHeader":"iam.amazonaws.com"}}
eventName
をダミーのものに変更したイベントでテストすると、意図通り一致しません。
{"eventVersion":"1.08","userIdentity":{"type":"AssumedRole","principalId":"AROAXXXXXXXXXXXXXXXX:cm-account-manager","arn":"arn:aws:sts::000000000000:assumed-role/cm-policymaintainer/cm-account-manager","accountId":"000000000000","accessKeyId":"ASIAXXXXXXXXXXXXXXXX","sessionContext":{"sessionIssuer":{"type":"Role","principalId":"AROAXXXXXXXXXXXXXXXX","arn":"arn:aws:iam::000000000000:role/cm-policymaintainer","accountId":"000000000000","userName":"cm-policymaintainer"},"webIdFederationData":{},"attributes":{"creationDate":"2022-05-13T17:24:45Z","mfaAuthenticated":"false"}}},"eventTime":"2022-05-13T17:24:49Z","eventSource":"iam.amazonaws.com","eventName":"dummyEvent","awsRegion":"us-east-1","sourceIPAddress":"18.xx.xx.xx","userAgent":"Boto3/1.20.52 Python/3.9.11 Linux/4.14.255-273-220.498.amzn2.aarch64 exec-env/AWS_Lambda_python3.9 Botocore/1.23.52","requestParameters":{"roleName":"cm-policymaintainer","policyName":"CMMaintenancePolicy","policyDocument":"略"},"responseElements":null,"requestID":"0ebfbdbf-xxxx-xxxx-xxxx-c374fe528477","eventID":"1812dde7-xxxx-xxxx-xxxx-2f7853657bee","readOnly":false,"eventType":"AwsApiCall","managementEvent":true,"recipientAccountId":"000000000000","eventCategory":"Management","tlsDetails":{"tlsVersion":"TLSv1.2","cipherSuite":"ECDHE-RSA-AES128-GCM-SHA256","clientProvidedHostHeader":"iam.amazonaws.com"}}
変更後のフィルターパターンの場合
テストするフィルターパターンを以下に変更します。
{($.userIdentity.sessionContext.sessionIssuer.userName!=cm-policymaintainer)&&(($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventName=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolicy)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=DeletePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersion)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.eventName=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGroupPolicy)||($.eventName=DetachGroupPolicy))}
先ほどはじめにテストしたものと同じイベントでテストすると、意図通り一致していません。これで特定のロールによる操作は除外できそうです。
{"eventVersion":"1.08","userIdentity":{"type":"AssumedRole","principalId":"AROAXXXXXXXXXXXXXXXX:cm-account-manager","arn":"arn:aws:sts::000000000000:assumed-role/cm-policymaintainer/cm-account-manager","accountId":"000000000000","accessKeyId":"ASIAXXXXXXXXXXXXXXXX","sessionContext":{"sessionIssuer":{"type":"Role","principalId":"AROAXXXXXXXXXXXXXXXX","arn":"arn:aws:iam::000000000000:role/cm-policymaintainer","accountId":"000000000000","userName":"cm-policymaintainer"},"webIdFederationData":{},"attributes":{"creationDate":"2022-05-13T17:24:45Z","mfaAuthenticated":"false"}}},"eventTime":"2022-05-13T17:24:49Z","eventSource":"iam.amazonaws.com","eventName":"PutRolePolicy","awsRegion":"us-east-1","sourceIPAddress":"18.xx.xx.xx","userAgent":"Boto3/1.20.52 Python/3.9.11 Linux/4.14.255-273-220.498.amzn2.aarch64 exec-env/AWS_Lambda_python3.9 Botocore/1.23.52","requestParameters":{"roleName":"cm-policymaintainer","policyName":"CMMaintenancePolicy","policyDocument":"略"},"responseElements":null,"requestID":"0ebfbdbf-xxxx-xxxx-xxxx-c374fe528477","eventID":"1812dde7-xxxx-xxxx-xxxx-2f7853657bee","readOnly":false,"eventType":"AwsApiCall","managementEvent":true,"recipientAccountId":"000000000000","eventCategory":"Management","tlsDetails":{"tlsVersion":"TLSv1.2","cipherSuite":"ECDHE-RSA-AES128-GCM-SHA256","clientProvidedHostHeader":"iam.amazonaws.com"}}
ログイベントのuserIdentity.sessionContext.sessionIssuer.userName
を ダミーのものに変更すると、きちんと一致しました。指定したロール以外による操作の場合は引き続き検知されます。
{"eventVersion":"1.08","userIdentity":{"type":"AssumedRole","principalId":"AROAXXXXXXXXXXXXXXXX:cm-account-manager","arn":"arn:aws:sts::000000000000:assumed-role/cm-policymaintainer/cm-account-manager","accountId":"000000000000","accessKeyId":"ASIAXXXXXXXXXXXXXXXX","sessionContext":{"sessionIssuer":{"type":"Role","principalId":"AROAXXXXXXXXXXXXXXXX","arn":"arn:aws:iam::000000000000:role/cm-policymaintainer","accountId":"000000000000","userName":"cm-dummuyRole"},"webIdFederationData":{},"attributes":{"creationDate":"2022-05-13T17:24:45Z","mfaAuthenticated":"false"}}},"eventTime":"2022-05-13T17:24:49Z","eventSource":"iam.amazonaws.com","eventName":"PutRolePolicy","awsRegion":"us-east-1","sourceIPAddress":"18.xx.xx.xx","userAgent":"Boto3/1.20.52 Python/3.9.11 Linux/4.14.255-273-220.498.amzn2.aarch64 exec-env/AWS_Lambda_python3.9 Botocore/1.23.52","requestParameters":{"roleName":"cm-policymaintainer","policyName":"CMMaintenancePolicy","policyDocument":"略"},"responseElements":null,"requestID":"0ebfbdbf-xxxx-xxxx-xxxx-c374fe528477","eventID":"1812dde7-xxxx-xxxx-xxxx-2f7853657bee","readOnly":false,"eventType":"AwsApiCall","managementEvent":true,"recipientAccountId":"000000000000","eventCategory":"Management","tlsDetails":{"tlsVersion":"TLSv1.2","cipherSuite":"ECDHE-RSA-AES128-GCM-SHA256","clientProvidedHostHeader":"iam.amazonaws.com"}}
ちなみにプリンシパルが IAM ユーザーの場合は
今回は IAM ロールによる作業を想定してフィルターパターンを検討しましたが、IAM ユーザーを用いて作業した場合、CloudTrail イベントでは以下のように記録されます。
{ "eventVersion": "1.08", "userIdentity": { "type": "IAMUser", "principalId": "AIDAXXXXXXXXXXXXXXXXX", "arn": "arn:aws:iam::000000000000:user/chiba-sample", "accountId": "000000000000", "accessKeyId": "AKIAQXXXXXXXXXXXXXXX", "userName": "chiba-sample" }, 以下略
若干userIdentity
の構成が変わっています。
ロールを除外する場合のフィルターパターンは以下の形式でしたが、
{($.userIdentity.sessionContext.sessionIssuer.userName!=ロール名)&&(A)}
ユーザーで同じことをする場合は以下の形式になります。
{($.userIdentity.userName!=ユーザー名)&&(A)}
終わりに
メトリクスフィルターのフィルターパターンで特定の IAM ロールによる操作を除外するケースを考えてみました。フィルターパターンの構文と CloudTral イベントにおけるuserIdentity
の内容について理解が進みました。
今回はロールの名称をフィルターパターンに用いましたが、userAgent
やsourceIPAddress
など他の項目を用いて除外する、という使い方もできそうです。……と締めようと考えましたが、最近このあたりの仕様変更がありました。マネジメントコンソールから操作した場合はAWS Internal
という値に置き換えられるようなのでご注意ください。
同じような構成をとっている方の参考になれば幸いです。
以上、 チバユキ (@batchicchi) がお送りしました。