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 ロールがそれを引き受けて作業するケースを想定しています。

MetricFilter crossaccount

ここでは以下の通りの名称であるとします。

  • 引き受けられる 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 ロググループからメトリクスフィルターの作成/変更を試みる際の画面から実行できます。

CloudWatch_Management_Console_filter_pattern

イベントメッセージに以下の形式のイベントをそのまま入力できれば楽なのですが、改行を挟むと別のログイベントとしてみなされるため意図通りに機能しません。

サンプルイベント

{
    "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 行にしたログイベントを入力してから「パターンをテスト」を実行し、結果に表示されればフィルターパターンに合致するということになります。

CloudWatch_Management_Console_filter_pattern-2964054

変更前のフィルターパターンの場合

まずは以下のフィルターパターンです。

{($.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"}}

Fileterpattern_test

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"}}

CloudWatch_Management_Console_not_match_filter

変更後のフィルターパターンの場合

テストするフィルターパターンを以下に変更します。

{($.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"}}

CloudWatch_Management_Console_not_match_filter

ログイベントの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"}}

Fileterpattern_test

ちなみにプリンシパルが 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の内容について理解が進みました。

今回はロールの名称をフィルターパターンに用いましたが、userAgentsourceIPAddressなど他の項目を用いて除外する、という使い方もできそうです。……と締めようと考えましたが、最近このあたりの仕様変更がありました。マネジメントコンソールから操作した場合はAWS Internalという値に置き換えられるようなのでご注意ください。

同じような構成をとっている方の参考になれば幸いです。

以上、 チバユキ (@batchicchi) がお送りしました。

参考