背景
例えばEventBridgeから既存のAWS SNSトピックをパブリッシュするルールを作成するとき。
1. AWSコンソール上から手動でEventBridgeを作成した場合
→ パブリッシュ先SNSのアクセスポリシーにも自動的にEventBridgeの許可ルールが追加されます。
2. 一方、CLIやCFnから同様にEventBridgeを作成した場合
→ SNSにアクセス許可が自動追加されることはありません。そのままではEventBridgeからのパブリッシュがSNS側で拒否されてしまいます。
そのため、EventBridgeからの許可ポリシーもCFnで自動追加できないかなーと思いました。
(手動でポチポチ追加しても良いけど、現場では多数のAWSアカウントを管理しているため、簡単に追加できるよう自動化したい。要は楽したい。)
解決策1と懸念点
クラメソさんの記事にヒントをもらって、Lambda-BackedカスタムリソースをCFnに記述しアクセスポリシーを自動更新できました。
AWS SDK(python)でset_topic_attributes
により更新できます。
懸念点
ただ、このset_topic_attributes
だと**SNSアクセスポリシーの「追加」ではなく「上書き」**になります。
そのためもし既存のSNSトピックに予め別のアクセスポリシーが追加されている状態でこのカスタムリソースを適用した場合、予めあったポリシーは削除されてしまいます。
こういうトラブルの原因になるカスタムリソースは作りたくないなと思い、改善策を模索しました。
解決策2
答えはシンプル。SDKのget_topic_attribets
でSNSのトピックポリシーを取得しEventBridgeのアクセス許可を追記して、set_topic_attributes
するだけ。
カスタムリソースで使用したLambda関数は以下の通り。
TopicArn
、AwsAccountId
はカスタムリソースから渡すパラメータです。
import json
import boto3
import logging
import cfnresponse
logger = logging.getLogger()
logger.setLevel(logging.INFO)
sns = boto3.client('sns')
def lambda_handler(event, context):
logger.info("event == {}".format(event))
response = None
topicArn = event['ResourceProperties']['TopicArn']
awsAccountId = event['ResourceProperties']['AwsAccountId']
addPolicy = {
"Sid": "AWSEvent_Allow",
"Effect": "Allow",
"Principal": {
"Service": "events.amazonaws.com"
},
"Action": "sns:Publish",
"Resource": topicArn,
"Condition": {
"StringEquals": {
"AWS:SourceAccount": awsAccountId
}
}
}
if event['RequestType'] == 'Create':
# 既存のトピックポリシーを取得
attributes = sns.get_topic_attributes(TopicArn = topicArn)
policy = eval(attributes["Attributes"]["Policy"])
logger.info("policy = {}".format(policy))
# EventBridgeからのパブリッシュ許可を追記
policy["Statement"].append(addPolicy)
logger.info("updatepolicy = {}".format(policy))
# トピックポリシーを上書き
responce=sns.set_topic_attributes(
TopicArn=topicArn,
AttributeName='Policy',
AttributeValue=json.dumps(policy)
)
if event['RequestType'] == 'Update':
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
if event['RequestType'] == 'Update':
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
logger.info("response == {}".format(responce))
cfnresponse.send(event, context, cfnresponse.SUCCESS, responce)
このLambda関数、カスタムリソース等のCloudFormationテンプレートはGitHubリポジトリにあげています。
詳しくご覧になりたい方はどうぞ。
実際にやってみる
まず、既存SNSのアクセスポリシーを確認します。デフォルトのポリシーと、S3からのパブリッシュ許可が設定されている状態です。
$ aws sns get-topic-attributes --topic-arn arn:aws:sns:ap-northeast-1:{AWSアカウントID}:TestTopic
(ポリシーの部分だけ抜粋)
"Policy":
"{\"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:{AWSアカウントID}:TestTopic\",\"Condition\":{\"StringEquals\":{\"AWS:SourceOwner\":\"{AWSアカウントID}\"}}},
↓S3からのパブリッシュ許可ポリシー
{\"Sid\":\"S3_Allow\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"s3.amazonaws.com\"},\"Action\":\"sns:Publish\",\"Resource\":\"arn:aws:sns:ap-northeast-1:{AWSアカウントID}:TestTopic\",\"Condition\":{\"StringEquals\":{\"AWS:SourceAccount\":\"{AWSアカウントID}\"}}}
]}"
アクセスポリシーを再確認してみます。EventBridgeからのパブリッシュ許可ポリシーが追加されていることが確認できました!!もともと設定されていたポリシーも消されずに残ってますね。
$ aws sns get-topic-attributes --topic-arn arn:aws:sns:ap-northeast-1:{AWSアカウントID}:TestTopic
(ポリシーの部分だけ抜粋)
"Policy":
"{\"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:{AWSアカウントID}:TestTopic\",\"Condition\":{\"StringEquals\":{\"AWS:SourceOwner\":\"{AWSアカウントID}\"}}},
{\"Sid\":\"S3_Allow\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"s3.amazonaws.com\"},\"Action\":\"sns:Publish\",\"Resource\":\"arn:aws:sns:ap-northeast-1:{AWSアカウントID}:TestTopic\",\"Condition\":{\"StringEquals\":{\"AWS:SourceAccount\":\"{AWSアカウントID}\"}}},
↓ 今回追加されたEventBridgeからの許可ポリシー
{\"Sid\":\"AWSEvent_Allow\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"events.amazonaws.com\"},\"Action\":\"sns:Publish\",\"Resource\":\"arn:aws:sns:ap-northeast-1:{AWSアカウントID}:TestTopic\",\"Condition\":{\"StringEquals\":{\"AWS:SourceAccount\":\"{AWSアカウントID}\"}}}
]}"
最後に
調べてもこの自動追加方法の記事が意外と無く、自分で作ってみました。面倒なSNSアクセスポリシーの管理を楽にできて良かったです。
(解決策はシンプルですが、地味に右往左往してAWSサポートに聞いたりしてましたw)
カスタムリソースはあんまり使ってこなかったけど、自由度高くて便利ですね。良い勉強になりました。
参考
・【クラメソ記事】EventBridgeからSNSに通知ができなくてハマった話
・【ドキュメント】Amazon EventBridge のリソースベースのポリシーを使用する - Amazon SNS のアクセス許可
・【GitHubリポジトリ】CFn Lambda-backedカスタムリソースを使ってSNSトピックポリシー追加を自動化する