AWS CloudTrailに大量のNotFound系エラーが出てるんですけど!?
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"
}
関連記事 | Related Posts
We are hiring!
【データエンジニア】分析G/名古屋・大阪
分析グループについてKINTOにおいて開発系部門発足時から設置されているチームであり、それほど経営としても注力しているポジションです。決まっていること、分かっていることの方が少ないぐらいですので、常に「なぜ」を考えながら、未知を楽しめるメンバーが集まっております。
【クラウドエンジニア】Cloud Infrastructure G/東京・大阪
KINTO Tech BlogWantedlyストーリーCloud InfrastructureグループについてAWSを主としたクラウドインフラの設計、構築、運用を主に担当しています。