AWS CloudTrailに大量のNotFoundエラーイベントが出てるんですけど!? こんにちは。(今更)酒癖50を観てもお酒を嫌いになれなかったKINTO テクノロジーズCCoEチーム所属の栗原です。以前に同じチームの多田から KINTOテクノロジーズにおけるCCoEの活動内容 を紹介しましたが、クラウド環境をセキュアに保てるよう日々活動しています。AWSアカウントの健全性を確認するためAWS CloudTrailのログを分析していたところ、大量のNotFound系のエラーが定期的に発生していたことに気がつきました。地味な話になりますが、AWSを利用しているユーザーであれば同じ事象に遭遇しているはずなのにググってもヒットしなかったので調査内容をブログにしてみました。 結論 結論から言いますと、 AWS CloudTrailの分析時には、AWS Configレコーダーのサービスリンクロール経由のNot Found系エラーは除外して分析するべき。 になります。AWS Configの挙動上、どうしても発生してしまうエラーイベントが存在するので、適切にフィルタリングして分析ノイズを減らすことが可能です。 調査内容 KINTO テクノロジーズでは、 AWS マルチアカウント管理を実現するベストプラクティス に則り、AWS Control TowerでLanding Zoneを管理するマルチアカウント構成をとっています。そのため AWS Configで構成情報を、AWS CloudTrailで監査ログを管理しています。 AWSアカウントの健全性を確認するためAWS CloudTrailのログを分析していたところ、NotFound系のエラーイベントが大量かつ定期的に発生していることがわかりました。 とあるAWSアカウントの1ヶ月程度のCloudTrailログのAWS Athenaでの分析結果がこちらです。このアカウントは発行して最低限のセキュリティ設定を施したのみで、ワークロードは構築していないアカウントとなります。 -- errorCodeの上位を分析 WITH filterd AS ( SELECT * FROM cloudtrail_logs WHERE errorCode IS NOT NULL ) SELECT errorCode, count(errorcode) as eventCount, count(errorCode) * 100 / (select count(*) from filterd) as errorRate FROM filterd GROUP BY errorCode eventCount errorRate ResourceNotFoundException 1,515 18 ReplicationConfigurationNotFoundError 1,112 13 ObjectLockConfigurationNotFoundError 958 11 NoSuchWebsiteConfiguration 954 11 NoSuchCORSConfiguration 952 11 InvalidRequestException 627 7 Client.RequestLimitExceeded 609 7 -- 特定のerroCodeの発生頻度を確認 SELECT date(from_iso8601_timestamp(eventtime)) as "date" count(*) as count FROM cloudtrail_logs WHERE errorcode = 'ResourceNotFoundException' GROUP BY date(from_iso8601_timestamp(eventtime)) ORDER BY "date" ASC LIMIT 5 date count 2023-10-19 52 2023-10-20 80 2023-10-21 80 2023-10-22 80 2023-10-23 80 いくつかのerrorCodeをピックアップして、AWS CloudTrailのレコードを眺めると(実際のAWS CloudTrailログは記事の最後に記載します。)、アクセス元であるuserIdentityのarnフィールドに記録されているのは全て arn:aws:sts::${AWS_ACCOUNT_ID}:assumed-role/AWSServiceRoleForConfig/${SESSION_NAME} となっていました。これはAWS Configにアタッチされる サービスリンクロール です。対象リソースは存在するのにNotFoundになる理由がわからなかったのですが、 eventName の箇所を確認すると、リソース本体の構成情報を取得するAPIではなく、それぞれの従属するリソースの情報を取得するAPIであることがわかりました。 リソース errorCode 呼ばれていたAPI(eventName) Lambda ResourceNotFoundException GetPolicy20150331v2 S3 ReplicationConfigurationNotFoundError GetBucketReplication S3 NoSuchCORSConfiguration GetBucketCors ワークロードに影響があるエラーではないですが、通常の監視やトラブルシューティングのノイズになるため解消していきたいところですが、そのためには"関連リソースになにかしらの設定をする"(例えばLambdaのリソースベースポリシーに、自身のアカウントからのみInvokeFunctionのActionを許可する)といった、本質的ではない対応をする必要があります。 結果として、我々CCoEチームではAWS CloudTrailの分析時にAWS Configのサービスリンクロールからのアクセスは除外する。という対応する結論にいたりました。AWS Athenaで分析するのであれば以下の様なクエリを実行するイメージです。 SELECT * FROM cloudtrail_logs WHERE userIdentity.arn not like '%AWSServiceRoleForConfig%' 少しだけDeep Dive 本調査の過程でわかったAWS Configの構成情報の記録の挙動を少しDeep Diveします。公式ドキュメントにも明文化されていないが、本調査でわかったことが2点あります。 従属(補足)リソース(勝手に命名しました。)の記録の挙動 従属(補足)リソースの記録頻度 従属(補足)リソースの記録の挙動 AWS Configはリソース本体の構成情報を記録するだけでなく、関連リソース(relationship)も合わせて記録してくれる挙動があります。これらには、 「直接的な」関係 、 「関節的な」関係 と名前がつけられています。 AWS Config は、設定フィールドからほとんどのリソースタイプの関係を導き出します。これを「直接的な」関係と呼びます。直接的な関係は、リソース (A) と別のリソース (B) との間の一方向関係 (A→B) であり、通常、リソース (A) の Describe API レスポンスから取得されます。以前は、AWS Config が当初サポートしていた一部のリソースタイプについて、他のリソースの設定から関係もキャプチャし、双方向 (B→A) の「間接的な」関係を作成していました。例えば、Amazon EC2 インスタンスとそのセキュリティグループの関係は直接的です。セキュリティグループは Amazon EC2 インスタンスの Describe API レスポンスに含まれるためです。一方、セキュリティグループと Amazon EC2 インスタンスの関係は間接的です。セキュリティグループを記述しても、関連付けられているインスタンスに関する情報は返されないためです。その結果、リソース設定の変更が検出されると、AWS Configはそのリソースの CI を作成するだけでなく、間接的な関係を持つリソースを含む関連リソースの CI も生成します。例えば、Amazon EC2AWS Config インスタンスの変更を検出すると、そのインスタンスの CI と、そのインスタンスに関連付けられているセキュリティグループの CI が作成されます。 -- https://docs.aws.amazon.com/ja_jp/config/latest/developerguide/faq.html#faq-1 従属(補足)リソース と勝手に命名していますが、関連リソースとはまた別に、リソース本体の設定であるように見えるものの、取得APIも分かれているようなリソースがあります。Lambdaのケースでいうと、Lambda自体は GetFunction で取得できるリソースですが、 リソースベースポリシー はまた別のリソースで、 GetPolicy で取得できるリソースです。CI(Configuration Item)をみてみると、従属(補足)リソースであるリソースベースポリシーは以下の様に、 supplementaryConfiguration フィールドに記録されます。 { "version": "1.3", "accountId": "<$AWS_ACCOUNT_ID>", "configurationItemCaptureTime": "2023-12-15T09:52:19.238Z", "configurationItemStatus": "OK", "configurationStateId": "************", "configurationItemMD5Hash": "", "arn": "arn:aws:lambda:ap-northeast-1:<$AWS_ACCOUNT_ID>:function:check-config-behavior", "resourceType": "AWS::Lambda::Function", "resourceId": "check-config-behavior", "resourceName": "check-config-behavior", "awsRegion": "ap-northeast-1", "availabilityZone": "Not Applicable", "tags": { "Purpose": "investigate" }, "relatedEvents": [], # 関連リソース "relationships": [ { "resourceType": "AWS::IAM::Role", "resourceName": "check-config-behavior-role-nkmqq3sh", "relationshipName": "Is associated with " } ], ... 中略 # 従属(補足)リソース "supplementaryConfiguration": { "Policy": "{\"Version\":\"2012-10-17\",\"Id\":\"default\",\"Statement\":[{\"Sid\":\"test-poilcy\",\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::<$AWS_ACCOUNT_ID>:root\"},\"Action\":\"lambda:InvokeFunction\",\"Resource\":\"arn:aws:lambda:ap-northeast-1:<$AWS_ACCOUNT_ID>:function:check-config-behavior\"}]}", "Tags": { "Purpose": "investigate" } } } 従属(補足)リソースの記録頻度 AWS ConfigのCIの記録頻度は、 RecordingMode の設定に従いますが、従属(補足)リソースについてはその限りではないようです。NotFound系だった場合リトライしている可能性もありそうですが、12時間や24時間に1回記録を試みているような動作になっていました。これも従属(補足)リソースの種類によって規則性があるわけではないようです。なかなかにブラックボックスな挙動ですがこの様な調査結果となりました。 まとめ 以上、AWS CloudTrailに出力されている謎のNotFound系エラーイベントの正体と、対策について紹介しました。今後詳細を調査予定ですが、Macieのサービスリンクロールからも同じ様なエラーイベントが発生していることが確認できています。AWS CloudTrailの分析は退屈な作業ではありますが、AWSサービスの挙動を深く理解できる機会にもなるので、積極的に実施していきましょう!AWSを使い倒したいエンジニアの方、小出恵介さんってやっぱいい俳優だよね!という方、 プラットフォームG で絶賛採用募集中です! 最後にそれぞれのAWS CloudTrailエラーイベントを記載して終わりにします。ご拝読ありがとうございました。 Lambda: ResourceNotFoundException { "eventVersion": "1.08", "userIdentity": { "type": "AssumedRole", "principalId": "************:LambdaDescribeHandlerSession", "arn": "arn:aws:sts::<$AWS_ACCOUNT_ID>:assumed-role/AWSServiceRoleForConfig/LambdaDescribeHandlerSession", "accountId": "<$AWS_ACCOUNT_ID>", "accessKeyId": "*********", "sessionContext": { "sessionIssuer": { "type": "Role", "principalId": "*********", "arn": "arn:aws:iam::<$AWS_ACCOUNT_ID>:role/aws-service-role/config.amazonaws.com/AWSServiceRoleForConfig", "accountId": "<$AWS_ACCOUNT_ID>", "userName": "AWSServiceRoleForConfig" }, "webIdFederationData": {}, "attributes": { "creationDate": "2023-12-03T09:09:17Z", "mfaAuthenticated": "false" } }, "invokedBy": "config.amazonaws.com" }, "eventTime": "2023-12-03T09:09:19Z", "eventSource": "lambda.amazonaws.com", "eventName": "GetPolicy20150331v2", "awsRegion": "ap-northeast-1", "sourceIPAddress": "config.amazonaws.com", "userAgent": "config.amazonaws.com", "errorCode": "ResourceNotFoundException", "errorMessage": "The resource you requested does not exist.", "requestParameters": { "functionName": "**************" }, "responseElements": null, "requestID": "******************", "eventID": "******************", "readOnly": true, "eventType": "AwsApiCall", "managementEvent": true, "recipientAccountId": "<$AWS_ACCOUNT_ID>", "eventCategory": "Management" } S3: ReplicationConfigurationNotFoundError { "eventVersion": "1.09", "userIdentity": { "type": "AssumedRole", "principalId": "**********:AWSConfig-Describe", "arn": "arn:aws:sts::<$AWS_ACCOUNT_ID>:assumed-role/AWSServiceRoleForConfig/AWSConfig-Describe", "accountId": "<$AWS_ACCOUNT_ID>", "accessKeyId": "*************", "sessionContext": { "sessionIssuer": { "type": "Role", "principalId": "*************", "arn": "arn:aws:iam::<$AWS_ACCOUNT_ID>:role/aws-service-role/config.amazonaws.com/AWSServiceRoleForConfig", "accountId": "<$AWS_ACCOUNT_ID>", "userName": "AWSServiceRoleForConfig" }, "attributes": { "creationDate": "2023-12-03T13:09:16Z", "mfaAuthenticated": "false" } }, "invokedBy": "config.amazonaws.com" }, "eventTime": "2023-12-03T13:09:55Z", "eventSource": "s3.amazonaws.com", "eventName": "GetBucketReplication", "awsRegion": "ap-northeast-1", "sourceIPAddress": "config.amazonaws.com", "userAgent": "config.amazonaws.com", "errorCode": "ReplicationConfigurationNotFoundError", "errorMessage": "The replication configuration was not found", "requestParameters": { "replication": "", "bucketName": "*********", "Host": "*************" }, "responseElements": null, "additionalEventData": { "SignatureVersion": "SigV4", "CipherSuite": "ECDHE-RSA-AES128-GCM-SHA256", "bytesTransferredIn": 0, "AuthenticationMethod": "AuthHeader", "x-amz-id-2": "**************", "bytesTransferredOut": 338 }, "requestID": "**********", "eventID": "*************", "readOnly": true, "resources": [ { "accountId": "<$AWS_ACCOUNT_ID>", "type": "AWS::S3::Bucket", "ARN": "arn:aws:s3:::***********" } ], "eventType": "AwsApiCall", "managementEvent": true, "recipientAccountId": "<$AWS_ACCOUNT_ID>", "vpcEndpointId": "vpce-***********", "eventCategory": "Management" } S3: NoSuchCORSConfiguration { "eventVersion": "1.09", "userIdentity": { "type": "AssumedRole", "principalId": "***********:AWSConfig-Describe", "arn": "arn:aws:sts::<$AWS_ACCOUNT_ID>:assumed-role/AWSServiceRoleForConfig/AWSConfig-Describe", "accountId": "<$AWS_ACCOUNT_ID>", "accessKeyId": "***************", "sessionContext": { "sessionIssuer": { "type": "Role", "principalId": "*************", "arn": "arn:aws:iam::<$AWS_ACCOUNT_ID>:role/aws-service-role/config.amazonaws.com/AWSServiceRoleForConfig", "accountId": "<$AWS_ACCOUNT_ID>", "userName": "AWSServiceRoleForConfig" }, "attributes": { "creationDate": "2023-12-03T13:09:16Z", "mfaAuthenticated": "false" } }, "invokedBy": "config.amazonaws.com" }, "eventTime": "2023-12-03T13:09:55Z", "eventSource": "s3.amazonaws.com", "eventName": "GetBucketCors", "awsRegion": "ap-northeast-1", "sourceIPAddress": "config.amazonaws.com", "userAgent": "config.amazonaws.com", "errorCode": "NoSuchCORSConfiguration", "errorMessage": "The CORS configuration does not exist", "requestParameters": { "bucketName": "********", "Host": "*************************8", "cors": "" }, "responseElements": null, "additionalEventData": { "SignatureVersion": "SigV4", "CipherSuite": "ECDHE-RSA-AES128-GCM-SHA256", "bytesTransferredIn": 0, "AuthenticationMethod": "AuthHeader", "x-amz-id-2": "*********************", "bytesTransferredOut": 339 }, "requestID": "***********", "eventID": "*****************", "readOnly": true, "resources": [ { "accountId": "<$AWS_ACCOUNT_ID>", "type": "AWS::S3::Bucket", "ARN": "arn:aws:s3:::*************" } ], "eventType": "AwsApiCall", "managementEvent": true, "recipientAccountId": "<$AWS_ACCOUNT_ID>", "vpcEndpointId": "vpce-********", "eventCategory": "Management" }