CT.LAMBDA.PV.2
コントロールの説明
Lambda 関数のリソースベースポリシーで、自アカウント外のプリンシパルへのアクセス許可を禁止します。
本コントロールは SCP により、Lambda 関数に対する lambda:AddPermission API で外部プリンシパルを設定する操作をブロックします。パブリック(Principal: *)だけでなく、特定の外部アカウント ID を指定したクロスアカウントの add-permission もブロックされる。
Security Hub CSPM の [Lambda.1] は「Lambda 関数のリソースベースポリシーがパブリックアクセスを禁止しているか」をチェック(検出)しますが、本コントロールを有効にすることで、そもそもパブリックアクセスの設定自体を API レベルでブロックします。
検証環境
本記事のコマンドは、--profile 指定がない場合は Workload アカウントで実行する。事前に export AWS_PROFILE=Workload でデフォルトを設定しておくと便利。
検証の流れ
flowchart LR
A[1. 事前準備] --> B[2. Principal:* 設定成功]
B --> C[3. Security Hub Lambda.1<br>FAILED を確認]
C --> D[4. CT.LAMBDA.PV.2<br>有効化]
D --> E[5. 既存設定<br>維持を確認]
D --> F[6. Security Hub Lambda.1<br>FAILED のまま確認]
D --> G[7. 新規 Principal:*<br>設定拒否を確認]
E & F & G --> H[8. CloudTrail<br>で確認]
H --> I[9. CT.LAMBDA.PV.2<br>無効化]
I --> J[10. Principal:*<br>設定成功を確認]
結果
- コントロールの有効化前、Lambda 関数のリソースベースポリシーに
Principal: *を設定できることを確認できた。 - Security Hub [Lambda.1] で「FAILED」として検出されることを確認できた。
- コントロールの有効化後、既存のリソースベースポリシー(
Principal: *)は維持されることを確認できた。SCP は API コールをブロックするだけで既存設定には遡及しないため、Security Hub [Lambda.1] も FAILED のまま変化しなかった。 - 有効化中に別の関数で
lambda:AddPermission(Principal: *)を試みると、explicit deny in a service control policyで拒否されることを確認できた。 - 有効化中にクロスアカウント(
--principal <組織外アカウント ID>)のadd-permissionも同様に拒否されることを確認できた。パブリックだけでなくクロスアカウントの設定もブロックされる。 - 拒否されたイベントが CloudTrail に記録されること、および管理アカウントの Organization Trail(CloudWatch Logs)からも確認できることを確認できた。
- コントロールの無効化後、再び
Principal: *の設定が可能になることを確認できた。
1. 事前準備
検証用の IAM 実行ロールと Lambda 関数を作成する。
aws iam create-role \
--role-name ct-lambda-pv2-test-role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}' \
--query 'Role.Arn'"arn:aws:iam::<Workload のアカウント ID>:role/ct-lambda-pv2-test-role"Lambda 関数を 2 つ作成する(有効化前テスト用と有効化後テスト用)。
echo 'def handler(event, context): return "ok"' > lambda_function.py
zip function.zip lambda_function.pyaws lambda create-function \
--function-name ct-lambda-pv2-test-1 \
--runtime python3.12 \
--role arn:aws:iam::<Workload のアカウント ID>:role/ct-lambda-pv2-test-role \
--handler lambda_function.handler \
--zip-file fileb://function.zip \
--query '{FunctionName:FunctionName,FunctionArn:FunctionArn}'{
"FunctionName": "ct-lambda-pv2-test-1",
"FunctionArn": "arn:aws:lambda:ap-northeast-1:<Workload のアカウント ID>:function:ct-lambda-pv2-test-1"
}aws lambda create-function \
--function-name ct-lambda-pv2-test-2 \
--runtime python3.12 \
--role arn:aws:iam::<Workload のアカウント ID>:role/ct-lambda-pv2-test-role \
--handler lambda_function.handler \
--zip-file fileb://function.zip \
--query '{FunctionName:FunctionName,FunctionArn:FunctionArn}'{
"FunctionName": "ct-lambda-pv2-test-2",
"FunctionArn": "arn:aws:lambda:ap-northeast-1:<Workload のアカウント ID>:function:ct-lambda-pv2-test-2"
}2. コントロール有効化前の確認
リソースベースポリシーに Principal: * を追加する。
aws lambda add-permission \
--function-name ct-lambda-pv2-test-1 \
--statement-id public-access \
--action lambda:InvokeFunction \
--principal '*'{
"Statement": "{\"Sid\":\"public-access\",\"Effect\":\"Allow\",\"Principal\":\"*\",\"Action\":\"lambda:InvokeFunction\",\"Resource\":\"arn:aws:lambda:ap-northeast-1:<Workload のアカウント ID>:function:ct-lambda-pv2-test-1\"}"
}ポリシーを確認する。
aws lambda get-policy --function-name ct-lambda-pv2-test-1 \
--output json | jq '.Policy | fromjson'{
"Version": "2012-10-17",
"Id": "default",
"Statement": [
{
"Sid": "public-access",
"Effect": "Allow",
"Principal": "*",
"Action": "lambda:InvokeFunction",
"Resource": "arn:aws:lambda:ap-northeast-1:<Workload のアカウント ID>:function:ct-lambda-pv2-test-1"
}
]
}3. Security Hub CSPM での検出確認(Lambda.1 FAILED)
Config ルールを手動トリガーして検出を早める。Lambda.1 に対応する Config ルール名は securityhub-lambda-function-public-access-prohibited-<ハッシュ> の形式で、環境ごとにハッシュ部分が異なる。
aws configservice start-config-rules-evaluation \
--config-rule-names securityhub-lambda-function-public-access-prohibited-<ハッシュ>Security Hub で検出を確認する。
aws securityhub get-findings \
--filters '{
"ComplianceSecurityControlId": [{"Value": "Lambda.1", "Comparison": "EQUALS"}],
"ComplianceStatus": [{"Value": "FAILED", "Comparison": "EQUALS"}],
"ResourceId": [{"Value": "arn:aws:lambda:ap-northeast-1:<Workload のアカウント ID>:function:ct-lambda-pv2-test-1", "Comparison": "EQUALS"}]
}' \
--query 'Findings[0].{ComplianceStatus:Compliance.Status,ResourceId:Resources[0].Id}'{
"ComplianceStatus": "FAILED",
"ResourceId": "arn:aws:lambda:ap-northeast-1:<Workload のアカウント ID>:function:ct-lambda-pv2-test-1"
}4. 対象の予防コントロールを有効化(例外設定なし)
Control Tower の管理アカウントで、対象の OU にコントロールが有効になっていないことを確認する。
aws controltower list-enabled-controls \
--target-identifier <OU の ARN> \
--profile Master{
"enabledControls": []
}コントロールを有効化する。
aws controltower enable-control \
--control-identifier arn:aws:controlcatalog:::control/aksh80tizisat4i4ou8qg1hdy \
--target-identifier <OU の ARN> \
--profile Master{
"arn": "arn:aws:controltower:ap-northeast-1:<管理アカウント ID>:enabledcontrol/<enabledcontrol ID>",
"operationIdentifier": "<オペレーション ID>"
}有効化が完了するまで待機する。
aws controltower get-control-operation \
--operation-identifier <オペレーション ID> \
--query 'controlOperation.{operationType:operationType,status:status,statusMessage:statusMessage}' \
--profile Master{
"operationType": "ENABLE_CONTROL",
"status": "SUCCEEDED",
"statusMessage": "Operation was successful."
}5. 既存設定は維持されることの確認
有効化前に設定したリソースベースポリシーが維持されていることを確認する。
aws lambda get-policy --function-name ct-lambda-pv2-test-1 \
--output json | jq '.Policy | fromjson'{
"Version": "2012-10-17",
"Id": "default",
"Statement": [
{
"Sid": "public-access",
"Effect": "Allow",
"Principal": "*",
"Action": "lambda:InvokeFunction",
"Resource": "arn:aws:lambda:ap-northeast-1:<Workload のアカウント ID>:function:ct-lambda-pv2-test-1"
}
]
}有効化前に設定した Principal: * のリソースベースポリシーは、コントロール有効化後も維持される。
6. Security Hub Lambda.1 が FAILED のまま変化しないことの確認
SCP は API コールをブロックするだけで既存のリソースベースポリシーには遡及しないため、Security Hub [Lambda.1] は FAILED のまま変化しない。
aws securityhub get-findings \
--filters '{
"ComplianceSecurityControlId": [{"Value": "Lambda.1", "Comparison": "EQUALS"}],
"ComplianceStatus": [{"Value": "FAILED", "Comparison": "EQUALS"}],
"ResourceId": [{"Value": "arn:aws:lambda:ap-northeast-1:<Workload のアカウント ID>:function:ct-lambda-pv2-test-1", "Comparison": "EQUALS"}]
}' \
--query 'Findings[0].{ComplianceStatus:Compliance.Status,ResourceId:Resources[0].Id}'{
"ComplianceStatus": "FAILED",
"ResourceId": "arn:aws:lambda:ap-northeast-1:<Workload のアカウント ID>:function:ct-lambda-pv2-test-1"
}7. 新規操作は制限(拒否)されることの確認
別の関数で Principal: * を設定する。
aws lambda add-permission \
--function-name ct-lambda-pv2-test-2 \
--statement-id public-access \
--action lambda:InvokeFunction \
--principal '*'An error occurred (AccessDeniedException) when calling the AddPermission operation:
User: arn:aws:sts::<Workload のアカウント ID>:assumed-role/<ロール名>/<セッション名>
is not authorized to perform: lambda:AddPermission on resource:
arn:aws:lambda:ap-northeast-1:<Workload のアカウント ID>:function:ct-lambda-pv2-test-2
with an explicit deny in a service control policy:
arn:aws:organizations::<管理アカウント ID>:policy/<組織 ID>/service_control_policy/<ポリシー ID>クロスアカウントの add-permission も拒否されることの確認
クロスアカウント(特定の外部アカウント ID)の add-permission も同様にブロックされることを確認する。
aws lambda add-permission \
--function-name ct-lambda-pv2-test-2 \
--statement-id crossaccount-access \
--action lambda:InvokeFunction \
--principal <組織外アカウント ID>An error occurred (AccessDeniedException) when calling the AddPermission operation:
User: arn:aws:sts::<Workload のアカウント ID>:assumed-role/<ロール名>/<セッション名>
is not authorized to perform: lambda:AddPermission on resource:
arn:aws:lambda:ap-northeast-1:<Workload のアカウント ID>:function:ct-lambda-pv2-test-2
with an explicit deny in a service control policy:
arn:aws:organizations::<管理アカウント ID>:policy/<組織 ID>/service_control_policy/<ポリシー ID>8. CloudTrail での確認
拒否されたイベントが CloudTrail に記録されていることを確認する。
Workload アカウントの CloudTrail イベント履歴から確認する。Lambda の AddPermission は CloudTrail 上では AddPermission20150331v2 というイベント名で記録される。
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=AddPermission20150331v2 \
--start-time <検索開始時刻> \
--end-time <検索終了時刻> \
--query 'Events[].CloudTrailEvent' \
--output text | jq 'select(.errorCode != null) | {eventName, errorCode, errorMessage}'{
"eventName": "AddPermission20150331v2",
"errorCode": "AccessDenied",
"errorMessage": "User: arn:aws:sts::<Workload のアカウント ID>:assumed-role/<ロール名>/<セッション名> is not authorized to perform: lambda:AddPermission on resource: arn:aws:lambda:ap-northeast-1:<Workload のアカウント ID>:function:ct-lambda-pv2-test-2 with an explicit deny in a service control policy: arn:aws:organizations::<管理アカウント ID>:policy/<組織 ID>/service_control_policy/<ポリシー ID>"
}errorMessage に explicit deny in a service control policy と記録されており、SCP による拒否であることがわかる。なお、EC2 系の SCP 拒否では errorCode が Client.UnauthorizedOperation だったが、Lambda では AccessDenied となる。
管理アカウントの Organization Trail(CloudWatch Logs)からも同じイベントを確認できる。
aws logs start-query \
--log-group-name "aws-controltower/CloudTrailLogs" \
--start-time $(date -d '<検索開始時刻>' +%s) \
--end-time $(date -d '<検索終了時刻>' +%s) \
--query-string 'fields @timestamp, eventName, errorCode, errorMessage
| filter eventName like /AddPermission/ and errorCode like /AccessDenied/
| sort @timestamp asc' \
--profile MasterqueryId が返るので、これを使って結果を取得する。
{
"queryId": "<クエリ ID>"
}aws logs get-query-results \
--query-id <クエリ ID> \
--query '{results:results,status:status}' \
--profile Master{
"results": [
[
{"field": "@timestamp", "value": "<タイムスタンプ>"},
{"field": "eventName", "value": "AddPermission20150331v2"},
{"field": "errorCode", "value": "AccessDenied"},
{"field": "errorMessage", "value": "User: arn:aws:sts::<Workload のアカウント ID>:assumed-role/<ロール名>/<セッション名> is not authorized to perform: lambda:AddPermission on resource: arn:aws:lambda:ap-northeast-1:<Workload のアカウント ID>:function:ct-lambda-pv2-test-2 with an explicit deny in a service control policy: arn:aws:organizations::<管理アカウント ID>:policy/<組織 ID>/service_control_policy/<ポリシー ID>"},
{"field": "@ptr", "value": "<ログポインタ>"}
]
],
"status": "Complete"
}管理アカウントの CloudWatch Logs にも同じ拒否イベントが記録されていることを確認できた。
9. 対象の予防コントロールを無効化
検証完了後、コントロールを無効化する。
aws controltower disable-control \
--control-identifier arn:aws:controlcatalog:::control/aksh80tizisat4i4ou8qg1hdy \
--target-identifier <OU の ARN> \
--profile Master{
"operationIdentifier": "<オペレーション ID>"
}aws controltower get-control-operation \
--operation-identifier <オペレーション ID> \
--query 'controlOperation.{operationType:operationType,status:status,statusMessage:statusMessage}' \
--profile Master{
"operationType": "DISABLE_CONTROL",
"status": "SUCCEEDED",
"statusMessage": "Operation was successful."
}10. 制限解除の確認
コントロール無効化後、再び Principal: * の設定が可能になることを確認する。
aws lambda add-permission \
--function-name ct-lambda-pv2-test-2 \
--statement-id public-access \
--action lambda:InvokeFunction \
--principal '*'{
"Statement": "{\"Sid\":\"public-access\",\"Effect\":\"Allow\",\"Principal\":\"*\",\"Action\":\"lambda:InvokeFunction\",\"Resource\":\"arn:aws:lambda:ap-northeast-1:<Workload のアカウント ID>:function:ct-lambda-pv2-test-2\"}"
}11. クリーンアップ
検証で作成したリソースをすべて削除する。
# リソースベースポリシーを削除
aws lambda remove-permission --function-name ct-lambda-pv2-test-1 --statement-id public-access
aws lambda remove-permission --function-name ct-lambda-pv2-test-2 --statement-id public-access
# Lambda関数を削除
aws lambda delete-function --function-name ct-lambda-pv2-test-1
aws lambda delete-function --function-name ct-lambda-pv2-test-2
# IAMロールを削除
aws iam delete-role --role-name ct-lambda-pv2-test-role補足1: 関数 URL のパブリック公開について
Lambda には関数 URL という機能があり、認証タイプを NONE に設定すると、URL を知っていれば誰でもアクセスできる状態になります。
ただし、実際にパブリックアクセスを有効にするには、lambda:AddPermission で Principal: * のリソースベースポリシーを追加する必要があります。本コントロール有効化中に CreateFunctionUrlConfig(認証タイプ NONE)を実行したところ、関数 URL 自体は作成できましたが、リソースベースポリシーは自動追加されず、パブリックアクセスは 403 で拒否されました。AddPermission で Principal: * を追加しようとするとコントロールにブロックされるため、関数 URL のパブリック公開も間接的に防ぐことができます。
なお、AuthType: NONE での関数 URL 作成自体をブロックしたい場合は、CT.LAMBDA.PV.1 を併用してください。
補足2: Config ルールの手動トリガーで Security Hub の検出を早める
Security Hub の検出はスケジュールベースのため、リソース作成直後には検出されないことがある。Config ルールを手動トリガーすることで、数十秒〜数分程度で検出を確認できる。
Lambda.1 に対応する Config ルール名を確認する。
aws configservice describe-config-rules \
--query 'ConfigRules[?starts_with(ConfigRuleName, `securityhub-lambda-function-public-access-prohibited`)].ConfigRuleName'Config ルールを手動トリガーする。
aws configservice start-config-rules-evaluation \
--config-rule-names securityhub-lambda-function-public-access-prohibited-<ハッシュ>Config の評価結果を直接確認する(Security Hub への反映前に状態を把握できる)。
aws configservice get-compliance-details-by-config-rule \
--config-rule-name securityhub-lambda-function-public-access-prohibited-<ハッシュ> \
--compliance-types NON_COMPLIANT \
--query 'EvaluationResults[].{ResourceId:EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId,ComplianceType:ComplianceType}'