SNS トピック
概要
IAM Access Analyzer が SNS トピックの外部アクセスをどのように検出するかを検証する。
SNS トピックはトピックポリシーにより外部プリンシパルへのアクセスを許可できる。デフォルトのトピックポリシーは Principal: {"AWS": "*"} を含むが、Condition で自アカウントのみに制限されており、明示的にポリシーを変更しない限り外部アクセスは発生しない。トピックポリシーに Principal: "*"(Condition なし)を設定するとパブリックアクセスとして検出され、特定の外部アカウントを設定するとクロスアカウントアクセスとして検出される。
SNS トピックの外部アクセス制御レイヤー
| レイヤー | SNS トピックの状況 |
|---|---|
| デフォルト保護 | なし(デフォルトポリシーは Condition で自アカウントのみに制限) |
| 予防(SCP / RCP) | 予防コントロールなし(カスタム SCP で対応可能) |
| Security Hub CSPM | SNS.4(パブリックアクセスのチェック) |
| IAM Access Analyzer | パブリック + クロスアカウントの到達可能性を分析 |
結果
- デフォルトのトピックポリシー: デフォルトポリシーは
Principal: {"AWS": "*"}を含むが、Condition: {"StringEquals": {"AWS:SourceOwner": "<自アカウント ID>"}}で自アカウントのみに制限されている。外部アクセスは許可されていない - パブリックアクセスの検出: トピックポリシーに
Principal: "*"(Condition なし)を追加すると、isPublic: trueの finding が生成された。action はsns:Publish,sns:Subscribe - クロスアカウントアクセスの検出: 組織外アカウントをトピックポリシーに設定すると、
isPublic: falseの finding が生成された。principal に組織外アカウント ID が特定された - 予防コントロール: SNS トピックに対応する Control Tower の予防コントロールは存在しない。クロスアカウントアクセスの予防にはカスタム SCP での対応が必要
- クリーンアップ: トピックポリシーからパブリック・クロスアカウントのステートメントを削除すると finding は RESOLVED になる
検証環境
本記事のコマンドは、--profile 指定がない場合は Workload アカウントで実行する。IAM Access Analyzer の finding 確認は Audit アカウントで実行する。
検証の流れ
flowchart LR
A[1. 既存 finding の<br>確認] --> B[2. テスト用トピック<br>作成]
B --> C[3. パブリックアクセス<br>検出の確認]
C --> D[4. クロスアカウント<br>アクセス検出]
D --> E[5. クリーンアップ]
1. 既存 finding の確認
テスト用トピックを作成する前に、デフォルトのトピックポリシーと既存の SNS トピック finding を確認する。
デフォルトのトピックポリシーの確認
SNS トピックのデフォルトポリシーを確認する。
aws sns get-topic-attributes \
--topic-arn arn:aws:sns:ap-northeast-1:<Workload アカウント ID>:<トピック名> \
--query 'Attributes.Policy' \
--output text | python3 -m json.tool{
"Version": "2008-10-17",
"Id": "__default_policy_ID",
"Statement": [
{
"Sid": "__default_statement_ID",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": [
"SNS:GetTopicAttributes",
"SNS:SetTopicAttributes",
"SNS:AddPermission",
"SNS:RemovePermission",
"SNS:DeleteTopic",
"SNS:Subscribe",
"SNS:ListSubscriptionsByTopic",
"SNS:Publish"
],
"Resource": "arn:aws:sns:ap-northeast-1:<Workload アカウント ID>:<トピック名>",
"Condition": {
"StringEquals": {
"AWS:SourceOwner": "<Workload アカウント ID>"
}
}
}
]
}デフォルトポリシーは Principal: {"AWS": "*"} を含むが、Condition で AWS:SourceOwner が自アカウント ID の場合のみに制限されている。この Condition により、外部アカウントからのアクセスは拒否される。
既存 finding の確認
aws accessanalyzer list-findings-v2 \
--analyzer-arn arn:aws:access-analyzer:ap-northeast-1:<Audit アカウント ID>:analyzer/org-access-analyzer \
--filter '{"status": {"eq": ["ACTIVE"]}, "resourceType": {"eq": ["AWS::SNS::Topic"]}}' \
--query 'findings[].{id:id,resource:resource,status:status}' \
--region ap-northeast-1 \
--profile Audit[]2. テスト用トピックの作成
2 つのテスト用 SNS トピックを作成し、トピックポリシーで外部アクセスを設定する。デフォルトポリシーのステートメント(自アカウント管理用)は残したまま、外部アクセス用のステートメントを追加する。
パブリックアクセス検証用トピック
aws sns create-topic \
--name iaa-sns-public-test{
"TopicArn": "arn:aws:sns:ap-northeast-1:<Workload アカウント ID>:iaa-sns-public-test"
}トピックポリシーに Principal: "*"(Condition なし)のステートメントを追加する。
aws sns set-topic-attributes \
--topic-arn arn:aws:sns:ap-northeast-1:<Workload アカウント ID>:iaa-sns-public-test \
--attribute-name Policy \
--attribute-value '{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "default",
"Effect": "Allow",
"Principal": {"AWS": "*"},
"Action": ["SNS:GetTopicAttributes", "SNS:SetTopicAttributes", "SNS:AddPermission", "SNS:RemovePermission", "SNS:DeleteTopic", "SNS:Subscribe", "SNS:ListSubscriptionsByTopic", "SNS:Publish"],
"Resource": "arn:aws:sns:ap-northeast-1:<Workload アカウント ID>:iaa-sns-public-test",
"Condition": {"StringEquals": {"AWS:SourceOwner": "<Workload アカウント ID>"}}
},
{
"Sid": "AllowPublic",
"Effect": "Allow",
"Principal": "*",
"Action": ["SNS:Publish", "SNS:Subscribe"],
"Resource": "arn:aws:sns:ap-northeast-1:<Workload アカウント ID>:iaa-sns-public-test"
}
]
}'
(出力なし)クロスアカウントアクセス検証用トピック
aws sns create-topic \
--name iaa-sns-crossaccount-test{
"TopicArn": "arn:aws:sns:ap-northeast-1:<Workload アカウント ID>:iaa-sns-crossaccount-test"
}トピックポリシーに組織外アカウントのステートメントを追加する。
aws sns set-topic-attributes \
--topic-arn arn:aws:sns:ap-northeast-1:<Workload アカウント ID>:iaa-sns-crossaccount-test \
--attribute-name Policy \
--attribute-value '{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "default",
"Effect": "Allow",
"Principal": {"AWS": "*"},
"Action": ["SNS:GetTopicAttributes", "SNS:SetTopicAttributes", "SNS:AddPermission", "SNS:RemovePermission", "SNS:DeleteTopic", "SNS:Subscribe", "SNS:ListSubscriptionsByTopic", "SNS:Publish"],
"Resource": "arn:aws:sns:ap-northeast-1:<Workload アカウント ID>:iaa-sns-crossaccount-test",
"Condition": {"StringEquals": {"AWS:SourceOwner": "<Workload アカウント ID>"}}
},
{
"Sid": "AllowCrossAccount",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::<組織外アカウント ID>:root"},
"Action": ["SNS:Publish", "SNS:Subscribe"],
"Resource": "arn:aws:sns:ap-northeast-1:<Workload アカウント ID>:iaa-sns-crossaccount-test"
}
]
}'
(出力なし)3. パブリックアクセスの検出
パブリックアクセス検証用トピック(Principal: "*"、Condition なし)の finding を確認する。
aws accessanalyzer list-findings-v2 \
--analyzer-arn arn:aws:access-analyzer:ap-northeast-1:<Audit アカウント ID>:analyzer/org-access-analyzer \
--filter '{"status": {"eq": ["ACTIVE"]}, "resource": {"contains": ["iaa-sns-public-test"]}}' \
--query 'findings[].{id:id,resource:resource,resourceType:resourceType,status:status}' \
--region ap-northeast-1 \
--profile Audit[
{
"id": "<パブリックトピック finding ID>",
"resource": "arn:aws:sns:ap-northeast-1:<Workload アカウント ID>:iaa-sns-public-test",
"resourceType": "AWS::SNS::Topic",
"status": "ACTIVE"
}
]finding の詳細を確認する。
aws accessanalyzer get-finding-v2 \
--analyzer-arn arn:aws:access-analyzer:ap-northeast-1:<Audit アカウント ID>:analyzer/org-access-analyzer \
--id <パブリックトピック finding ID> \
--region ap-northeast-1 \
--profile Audit{
"findingDetails": [
{
"externalAccessDetails": {
"action": ["sns:Publish", "sns:Subscribe"],
"condition": {},
"isPublic": true,
"principal": {"AWS": "*"},
"resourceControlPolicyRestriction": "NOT_APPLICABLE"
}
}
],
"resource": "arn:aws:sns:ap-northeast-1:<Workload アカウント ID>:iaa-sns-public-test",
"status": "ACTIVE",
"resourceType": "AWS::SNS::Topic",
"findingType": "ExternalAccess",
"resourceOwnerAccount": "<Workload アカウント ID>",
"id": "<パブリックトピック finding ID>"
}以下を確認する。
isPublicがtrueprincipalが{"AWS": "*"}actionにsns:Publish,sns:Subscribeが含まれるresourceControlPolicyRestrictionがNOT_APPLICABLE
組織外アカウントからの実際のアクセス確認
組織外アカウントからパブリックトピックへの Publish が成功することを確認する。
aws sns publish \
--topic-arn arn:aws:sns:ap-northeast-1:<Workload アカウント ID>:iaa-sns-public-test \
--message "test" \
--query '{MessageId:MessageId}' \
--profile <組織外プロファイル> \
--region ap-northeast-1{
"MessageId": "<メッセージ ID>"
}4. クロスアカウントアクセスの検出
クロスアカウントアクセス検証用トピックの finding を確認する。
aws accessanalyzer list-findings-v2 \
--analyzer-arn arn:aws:access-analyzer:ap-northeast-1:<Audit アカウント ID>:analyzer/org-access-analyzer \
--filter '{"status": {"eq": ["ACTIVE"]}, "resource": {"contains": ["iaa-sns-crossaccount-test"]}}' \
--query 'findings[].{id:id,resource:resource,resourceType:resourceType,status:status}' \
--region ap-northeast-1 \
--profile Audit[
{
"id": "<クロスアカウントトピック finding ID>",
"resource": "arn:aws:sns:ap-northeast-1:<Workload アカウント ID>:iaa-sns-crossaccount-test",
"resourceType": "AWS::SNS::Topic",
"status": "ACTIVE"
}
]finding の詳細を確認する。
aws accessanalyzer get-finding-v2 \
--analyzer-arn arn:aws:access-analyzer:ap-northeast-1:<Audit アカウント ID>:analyzer/org-access-analyzer \
--id <クロスアカウントトピック finding ID> \
--region ap-northeast-1 \
--profile Audit{
"findingDetails": [
{
"externalAccessDetails": {
"action": ["sns:Publish", "sns:Subscribe"],
"condition": {},
"isPublic": false,
"principal": {"AWS": "<組織外アカウント ID>"},
"resourceControlPolicyRestriction": "NOT_APPLICABLE"
}
}
],
"resource": "arn:aws:sns:ap-northeast-1:<Workload アカウント ID>:iaa-sns-crossaccount-test",
"status": "ACTIVE",
"resourceType": "AWS::SNS::Topic",
"findingType": "ExternalAccess",
"resourceOwnerAccount": "<Workload アカウント ID>",
"id": "<クロスアカウントトピック finding ID>"
}以下を確認する。
isPublicがfalseprincipalに組織外アカウント ID が特定されているactionにsns:Publish,sns:Subscribeが含まれるresourceControlPolicyRestrictionがNOT_APPLICABLE
組織外アカウントからの実際のアクセス確認
組織外アカウントからクロスアカウントトピックへの Publish が成功することを確認する。
aws sns publish \
--topic-arn arn:aws:sns:ap-northeast-1:<Workload アカウント ID>:iaa-sns-crossaccount-test \
--message "test" \
--query '{MessageId:MessageId}' \
--profile <組織外プロファイル> \
--region ap-northeast-1{
"MessageId": "<メッセージ ID>"
}5. クリーンアップ
トピックポリシーのリセット
トピックポリシーからパブリック・クロスアカウントのステートメントを削除し、デフォルトポリシー(自アカウントのみ)に戻す。
aws sns set-topic-attributes \
--topic-arn arn:aws:sns:ap-northeast-1:<Workload アカウント ID>:iaa-sns-public-test \
--attribute-name Policy \
--attribute-value '{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "default",
"Effect": "Allow",
"Principal": {"AWS": "*"},
"Action": ["SNS:GetTopicAttributes", "SNS:SetTopicAttributes", "SNS:AddPermission", "SNS:RemovePermission", "SNS:DeleteTopic", "SNS:Subscribe", "SNS:ListSubscriptionsByTopic", "SNS:Publish"],
"Resource": "arn:aws:sns:ap-northeast-1:<Workload アカウント ID>:iaa-sns-public-test",
"Condition": {"StringEquals": {"AWS:SourceOwner": "<Workload アカウント ID>"}}
}
]
}'
(出力なし)aws sns set-topic-attributes \
--topic-arn arn:aws:sns:ap-northeast-1:<Workload アカウント ID>:iaa-sns-crossaccount-test \
--attribute-name Policy \
--attribute-value '{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "default",
"Effect": "Allow",
"Principal": {"AWS": "*"},
"Action": ["SNS:GetTopicAttributes", "SNS:SetTopicAttributes", "SNS:AddPermission", "SNS:RemovePermission", "SNS:DeleteTopic", "SNS:Subscribe", "SNS:ListSubscriptionsByTopic", "SNS:Publish"],
"Resource": "arn:aws:sns:ap-northeast-1:<Workload アカウント ID>:iaa-sns-crossaccount-test",
"Condition": {"StringEquals": {"AWS:SourceOwner": "<Workload アカウント ID>"}}
}
]
}'
(出力なし)finding の確認
ポリシーリセット後、finding が RESOLVED になることを確認する。
aws accessanalyzer list-findings-v2 \
--analyzer-arn arn:aws:access-analyzer:ap-northeast-1:<Audit アカウント ID>:analyzer/org-access-analyzer \
--filter '{"resourceType": {"eq": ["AWS::SNS::Topic"]}}' \
--query 'findings[].{id:id,resource:resource,status:status}' \
--region ap-northeast-1 \
--profile Audit[]テスト用トピックの削除
aws sns delete-topic \
--topic-arn arn:aws:sns:ap-northeast-1:<Workload アカウント ID>:iaa-sns-public-test
(出力なし)aws sns delete-topic \
--topic-arn arn:aws:sns:ap-northeast-1:<Workload アカウント ID>:iaa-sns-crossaccount-test
(出力なし)