CloudWatch Logs メトリクスフィルターで複数の AND 条件と OR 条件と不等価演算子を組み合わせて除外を行う

メトリクスフィルターのフィルターパターンを検討する際に不等価演算子(!=)と OR の組み合わせでつまづきました

コンバンハ、千葉(幸)です。

先日、以下のエントリを書きました。

大まかな内容は以下の通りです。

  • CloudTrail イベントを CloudWatch Logs に出力している
  • 複数のイベント名を対象にメトリクスフィルターでフィルターパターンを設定している
  • 特定の IAM ロールの操作によるイベントの場合はパターン一致しないようにフィルターパターンを書きたい

元のフィルターパターンを{A}とした場合、以下のようにフィルターパターンを書くことで実現できました。

{($.userIdentity.sessionContext.sessionIssuer.userName!=ロール名)&&(A)}

今回はもう少し除外パターンを複雑にしたケースを考えてみます。

今回やりたいこと

サンプルの CloudTrail イベントが以下だったとします。

{
    "eventVersion": "1.08",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "AROAXXXXXXXXXXXXXXXX:cm-account-manager",
        "arn": "arn:aws:sts::000000000000:assumed-role/cm-hoge/cm-account-manager",
        "accountId": "000000000000",
        "accessKeyId": "ASIAXXXXXXXXXXXXXXXX",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "AROAXXXXXXXXXXXXXXXX",
                "arn": "arn:aws:iam::000000000000:role/cm-hoge",
                "accountId": "000000000000",
                "userName": "cm-hoge"
            },
            "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-hoge",
        "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 ロールが以下のいずれかである……①
    • cm-hoge
    • cm-fuga
    • cm-piyo
  • UserAgent が Boto3 から始まるものである……②
  • イベントがポリシー変更に関するものである……③

日本語にすると難しいですね。もっと洗練された書き方をしたかったですが、わたしの文章力ではこれが限界でした。

上記の条件を、以下を組み合わせて実現します。

  • AND(&&
  • 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)

イベント名を OR でつないだフィルターパターンは以下ドキュメントでいくつか例示されています。

(上記を単体でフィルターパターンとして使用する場合には、{}で囲ってあげる必要があります。)

それを踏まえて①と②の除外条件を取り込んだ内容が以下です。

{((($.userIdentity.sessionContext.sessionIssuer.userName!=cm-hoge)&&($.userIdentity.sessionContext.sessionIssuer.userName!=cm-fuga)&&($.userIdentity.sessionContext.sessionIssuer.userName!=cm-piyo))||($.userAgent!=Boto3*))&&(③)}

1行だと見づらいので、改行とインデントを加えてみたものが以下です。(実際に設定する際には改行が含まれてはいけません。)

{
    (
        (
            ($.userIdentity.sessionContext.sessionIssuer.userName!=cm-hoge)&&
            ($.userIdentity.sessionContext.sessionIssuer.userName!=cm-fuga)&&
            ($.userIdentity.sessionContext.sessionIssuer.userName!=cm-piyo)
        )||
        ($.userAgent!=Boto3*)
    )&&
    (③)
}

つまずきポイント

勝手にわたしがつまずいたところを記しておきます。

当初、①に相当する以下の箇所を OR でつなぐ書き方をしていました。

        (
            ($.userIdentity.sessionContext.sessionIssuer.userName!=cm-hoge)&&
            ($.userIdentity.sessionContext.sessionIssuer.userName!=cm-fuga)&&
            ($.userIdentity.sessionContext.sessionIssuer.userName!=cm-piyo)
        )

こういう書き方をしていた、ということです。

        (
            ($.userIdentity.sessionContext.sessionIssuer.userName!=cm-hoge)||
            ($.userIdentity.sessionContext.sessionIssuer.userName!=cm-fuga)||
            ($.userIdentity.sessionContext.sessionIssuer.userName!=cm-piyo)
        )

OR でつなぐパターンだと、意図した挙動になりません。この箇所に関して言えば、使用される IAM ロールが何であってもパターンに一致します。

例えば本来は除外したいcm-hogeであっても、!=cm-fuga!=cm-piyoにマッチするからです。

③でイベント名を羅列するのと同じノリで①でも OR でつないでしまったのですが、不等価演算子の場合は注意が必要、ということを学びました。

フィルターパターンをテストしてみる

先述のフィルターパターンが意図した通りの挙動を示すかパターンをテストします。

{((($.userIdentity.sessionContext.sessionIssuer.userName!=cm-hoge)&&($.userIdentity.sessionContext.sessionIssuer.userName!=cm-fuga)&&($.userIdentity.sessionContext.sessionIssuer.userName!=cm-piyo))||($.userAgent!=Boto3*))&&(($.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 ロールが以下のいずれかである……①
    • cm-hoge
    • cm-fuga
    • cm-piyo
  • UserAgent が Boto3 から始まるものである……②
  • イベントがポリシー変更に関するものである……③

冒頭で示したサンプルイベントをベースに、一部の値を dummy に変えて以下のパターンを確認します。

# ①userName ②userAgent ③eventName 期待する結果
1 満たす 満たす 満たす 一致しない
2 満たす 満たす 満たさない 一致しない
3 満たす 満たさない 満たす 一致する
4 満たす 満たさない 満たさない 一致しない
5 満たさない 満たす 満たす 一致する
6 満たさない 満たす 満たさない 一致しない
7 満たさない 満たさない 満たす 一致する
8 満たさない 満たさない 満たさない 一致しない

以下の8行のログイベントを利用し、マネジメントコンソールのメトリクスフィルター編集画面でテストを行います。

{"eventVersion":"1.08","userIdentity":{"type":"AssumedRole","principalId":"AROAXXXXXXXXXXXXXXXX:cm-account-manager","arn":"arn:aws:sts::000000000000:assumed-role/cm-hoge/cm-account-manager","accountId":"000000000000","accessKeyId":"ASIAXXXXXXXXXXXXXXXX","sessionContext":{"sessionIssuer":{"type":"Role","principalId":"AROAXXXXXXXXXXXXXXXX","arn":"arn:aws:iam::000000000000:role/cm-hoge","accountId":"000000000000","userName":"cm-hoge"},"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-hoge","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"}}
{"eventVersion":"1.08","userIdentity":{"type":"AssumedRole","principalId":"AROAXXXXXXXXXXXXXXXX:cm-account-manager","arn":"arn:aws:sts::000000000000:assumed-role/cm-hoge/cm-account-manager","accountId":"000000000000","accessKeyId":"ASIAXXXXXXXXXXXXXXXX","sessionContext":{"sessionIssuer":{"type":"Role","principalId":"AROAXXXXXXXXXXXXXXXX","arn":"arn:aws:iam::000000000000:role/cm-hoge","accountId":"000000000000","userName":"cm-hoge"},"webIdFederationData":{},"attributes":{"creationDate":"2022-05-13T17:24:45Z","mfaAuthenticated":"false"}}},"eventTime":"2022-05-13T17:24:49Z","eventSource":"iam.amazonaws.com","eventName":"dummy","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-hoge","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"}}
{"eventVersion":"1.08","userIdentity":{"type":"AssumedRole","principalId":"AROAXXXXXXXXXXXXXXXX:cm-account-manager","arn":"arn:aws:sts::000000000000:assumed-role/cm-hoge/cm-account-manager","accountId":"000000000000","accessKeyId":"ASIAXXXXXXXXXXXXXXXX","sessionContext":{"sessionIssuer":{"type":"Role","principalId":"AROAXXXXXXXXXXXXXXXX","arn":"arn:aws:iam::000000000000:role/cm-hoge","accountId":"000000000000","userName":"cm-hoge"},"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":"dummuy","requestParameters":{"roleName":"cm-hoge","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"}}
{"eventVersion":"1.08","userIdentity":{"type":"AssumedRole","principalId":"AROAXXXXXXXXXXXXXXXX:cm-account-manager","arn":"arn:aws:sts::000000000000:assumed-role/cm-hoge/cm-account-manager","accountId":"000000000000","accessKeyId":"ASIAXXXXXXXXXXXXXXXX","sessionContext":{"sessionIssuer":{"type":"Role","principalId":"AROAXXXXXXXXXXXXXXXX","arn":"arn:aws:iam::000000000000:role/cm-hoge","accountId":"000000000000","userName":"cm-hoge"},"webIdFederationData":{},"attributes":{"creationDate":"2022-05-13T17:24:45Z","mfaAuthenticated":"false"}}},"eventTime":"2022-05-13T17:24:49Z","eventSource":"iam.amazonaws.com","eventName":"dummuy","awsRegion":"us-east-1","sourceIPAddress":"18.xx.xx.xx","userAgent":"dummuy":"cm-hoge","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"}}
{"eventVersion":"1.08","userIdentity":{"type":"AssumedRole","principalId":"AROAXXXXXXXXXXXXXXXX:cm-account-manager","arn":"arn:aws:sts::000000000000:assumed-role/cm-hoge/cm-account-manager","accountId":"000000000000","accessKeyId":"ASIAXXXXXXXXXXXXXXXX","sessionContext":{"sessionIssuer":{"type":"Role","principalId":"AROAXXXXXXXXXXXXXXXX","arn":"arn:aws:iam::000000000000:role/cm-hoge","accountId":"000000000000","userName":"dummuy"},"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-hoge","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"}}
{"eventVersion":"1.08","userIdentity":{"type":"AssumedRole","principalId":"AROAXXXXXXXXXXXXXXXX:cm-account-manager","arn":"arn:aws:sts::000000000000:assumed-role/cm-hoge/cm-account-manager","accountId":"000000000000","accessKeyId":"ASIAXXXXXXXXXXXXXXXX","sessionContext":{"sessionIssuer":{"type":"Role","principalId":"AROAXXXXXXXXXXXXXXXX","arn":"arn:aws:iam::000000000000:role/cm-hoge","accountId":"000000000000","userName":"dummuy"},"webIdFederationData":{},"attributes":{"creationDate":"2022-05-13T17:24:45Z","mfaAuthenticated":"false"}}},"eventTime":"2022-05-13T17:24:49Z","eventSource":"iam.amazonaws.com","eventName":"dummuy","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-hoge","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"}}
{"eventVersion":"1.08","userIdentity":{"type":"AssumedRole","principalId":"AROAXXXXXXXXXXXXXXXX:cm-account-manager","arn":"arn:aws:sts::000000000000:assumed-role/cm-hoge/cm-account-manager","accountId":"000000000000","accessKeyId":"ASIAXXXXXXXXXXXXXXXX","sessionContext":{"sessionIssuer":{"type":"Role","principalId":"AROAXXXXXXXXXXXXXXXX","arn":"arn:aws:iam::000000000000:role/cm-hoge","accountId":"000000000000","userName":"dummuy"},"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":"dummuy","requestParameters":{"roleName":"cm-hoge","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"}}
{"eventVersion":"1.08","userIdentity":{"type":"AssumedRole","principalId":"AROAXXXXXXXXXXXXXXXX:cm-account-manager","arn":"arn:aws:sts::000000000000:assumed-role/cm-hoge/cm-account-manager","accountId":"000000000000","accessKeyId":"ASIAXXXXXXXXXXXXXXXX","sessionContext":{"sessionIssuer":{"type":"Role","principalId":"AROAXXXXXXXXXXXXXXXX","arn":"arn:aws:iam::000000000000:role/cm-hoge","accountId":"000000000000","userName":"dummuy"},"webIdFederationData":{},"attributes":{"creationDate":"2022-05-13T17:24:45Z","mfaAuthenticated":"false"}}},"eventTime":"2022-05-13T17:24:49Z","eventSource":"iam.amazonaws.com","eventName":"dummy","awsRegion":"us-east-1","sourceIPAddress":"18.xx.xx.xx","userAgent":"dummuy","requestParameters":{"roleName":"cm-hoge","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_filterpattern_test

ちなみにフィルターパターンの文字数上限は 1024

今回使用したフィルターパターンは 729 文字でした。もっと条件を複雑にしたい、となった場合はどの程度まで長くできるでしょうか。

十分に長い文字列をフィルターパターンとして指定しテストすると、以下のエラーが発生しました。

CloudWatch_Management_Console_filterpattern_too_long

サブスクリプションフィルターのテスト中にエラーが発生しました。

1 validation error detected: Value '略' at 'filterPattern' failed to satisfy constraint: Member must have length less than or equal to 1024

正確にはサブスクリプションフィルターではなくメトリクスフィルターなのでコンソールのバグかと思います。ただ、サブスクリプションフィルターも同じく文字数制限は 1024 です。

1024 文字以下にせよ、とのことです。この上限については以下ページから確認できました。

filterPattern

A filter pattern for extracting metric data out of ingested log events.
Type: String
Length Constraints: Minimum length of 0. Maximum length of 1024.
Required: Yes

この値はクォータのページからは確認できませんでした。パラメータの上限は API リファレンスから確認できる(こともある)、と覚えておくと役立つかもしれません。

終わりに

CloudWatch Logs メトリクスフィルターで複数の AND 、OR、不等価演算子を含むフィルターパターンを書いてみました。終わってみればなんてことなかったですが、答えに辿り着くまでにだいぶ回り道をしてしまったのでブログに残しておきます。

似たケースで利用を考えている方の参考になれば幸いです。

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