各リージョンに存在する SNS トピックの一覧と SNS サブスクリプションの詳細を一括で取得したい

あれ、うっかり SNS サブスクリプションが解除されている SNS トピックないよね……?をリージョンを横断して一括で確認したいことがありました。

コンバンハ、千葉(幸)です。

今回のシチュエーションは以下です。

  • 複数のリージョンに SNS トピックおよび SNS サブスクリプションが存在する
  • いくつかの SNS サブスクリプションがアンサブスクライブされている可能性がある
  • SNS サブスクリプションが認証なしで無効化できる状態であるか確認したい

sns topics and subscriptions

全量を確認しつつ、SNS トピックと SNS サブスクリプションの突き合わせ、現状の設定確認をしたいです。

ひとまず各リージョンにおいて「SNS トピックの一覧」と「SNS サブスクリプションの詳細」を AWS CLI で一括で取ることにしました。

各リージョンで SNSトピックの一覧を取得する

ひとまずベースとなる SNS トピックの一覧を取得します。

シンプルに以下を実行しました。

for region in $(aws ec2 describe-regions --query 'Regions[].[RegionName]' --output text);
do
  echo "### Listing SNS topics for region $region"
  aws sns list-topics --query 'Topics[]' --output text --region $region
done

出力イメージは以下です。

### Listing SNS topics for region ap-south-1
arn:aws:sns:ap-south-1:000000000000:GuardDutyTopic
arn:aws:sns:ap-south-1:000000000000:notify-mumbai
### Listing SNS topics for region eu-north-1
### Listing SNS topics for region eu-west-3
arn:aws:sns:eu-west-3:000000000000:GuardDutyTopic
### Listing SNS topics for region eu-west-2
arn:aws:sns:eu-west-2:000000000000:GuardDutyTopic
### Listing SNS topics for region eu-west-1
arn:aws:sns:eu-west-1:000000000000:GuardDutyTopic
### Listing SNS topics for region ap-northeast-3
……

aws ec2 describe-regionsで有効なリージョンの一覧を取得し、各リージョンに対してループで処理を実行します。

今回のケースに限らず、複数リージョンを対象に実行したい処理で使い回しができる形かと思います。


余談ですが、--all-regionsオプションを付与することで有効でない(オプトインが済んでいない)リージョンも取得できます。

% aws ec2 describe-regions --all-regions --output table
---------------------------------------------------------------------------------
|                                DescribeRegions                                |
+-------------------------------------------------------------------------------+
||                                   Regions                                   ||
|+-----------------------------------+-----------------------+-----------------+|
||             Endpoint              |      OptInStatus      |   RegionName    ||
|+-----------------------------------+-----------------------+-----------------+|
||  ec2.ap-south-2.amazonaws.com     |  not-opted-in         |  ap-south-2     ||
||  ec2.ap-south-1.amazonaws.com     |  opt-in-not-required  |  ap-south-1     ||
||  ec2.eu-south-1.amazonaws.com     |  not-opted-in         |  eu-south-1     ||
||  ec2.eu-south-2.amazonaws.com     |  not-opted-in         |  eu-south-2     ||
||  ec2.me-central-1.amazonaws.com   |  not-opted-in         |  me-central-1   ||
||  ec2.ca-central-1.amazonaws.com   |  opt-in-not-required  |  ca-central-1   ||
||  ec2.eu-central-1.amazonaws.com   |  opt-in-not-required  |  eu-central-1   ||
||  ec2.eu-central-2.amazonaws.com   |  not-opted-in         |  eu-central-2   ||
||  ec2.us-west-1.amazonaws.com      |  opt-in-not-required  |  us-west-1      ||
||  ec2.us-west-2.amazonaws.com      |  opt-in-not-required  |  us-west-2      ||
||  ec2.af-south-1.amazonaws.com     |  not-opted-in         |  af-south-1     ||
||  ec2.eu-north-1.amazonaws.com     |  opt-in-not-required  |  eu-north-1     ||
||  ec2.eu-west-3.amazonaws.com      |  opt-in-not-required  |  eu-west-3      ||
||  ec2.eu-west-2.amazonaws.com      |  opt-in-not-required  |  eu-west-2      ||
||  ec2.eu-west-1.amazonaws.com      |  opt-in-not-required  |  eu-west-1      ||
||  ec2.ap-northeast-3.amazonaws.com |  opt-in-not-required  |  ap-northeast-3 ||
||  ec2.ap-northeast-2.amazonaws.com |  opt-in-not-required  |  ap-northeast-2 ||
||  ec2.me-south-1.amazonaws.com     |  not-opted-in         |  me-south-1     ||
||  ec2.ap-northeast-1.amazonaws.com |  opt-in-not-required  |  ap-northeast-1 ||
||  ec2.sa-east-1.amazonaws.com      |  opt-in-not-required  |  sa-east-1      ||
||  ec2.ap-east-1.amazonaws.com      |  not-opted-in         |  ap-east-1      ||
||  ec2.ap-southeast-1.amazonaws.com |  opt-in-not-required  |  ap-southeast-1 ||
||  ec2.ap-southeast-2.amazonaws.com |  opt-in-not-required  |  ap-southeast-2 ||
||  ec2.ap-southeast-3.amazonaws.com |  not-opted-in         |  ap-southeast-3 ||
||  ec2.ap-southeast-4.amazonaws.com |  not-opted-in         |  ap-southeast-4 ||
||  ec2.us-east-1.amazonaws.com      |  opt-in-not-required  |  us-east-1      ||
||  ec2.us-east-2.amazonaws.com      |  opt-in-not-required  |  us-east-2      ||
|+-----------------------------------+-----------------------+-----------------+|

各リージョンでaws sns list-topicsを実行します。このコマンドは SNS トピックが取得できるだけのシンプルなものです。

各リージョンの SNS サブスクリプションの詳細を一括で取得する

続いて各リージョンの SNS サブスクリプションの情報を取得していきます。

aws sns list-subscriptionsで必要な情報が取得できれば先ほどと同じようなコマンドで実現できるのですが、残念ながらそうはいきません。

取得できる情報例は以下で、ここには今回取得したいConfirmationWasAuthenticatedが含まれていません。

リファレンスより引用

{
    "Subscriptions": [
        {
            "Owner": "123456789012",
            "Endpoint": "my-email@example.com",
            "Protocol": "email",
            "TopicArn": "arn:aws:sns:us-west-2:123456789012:my-topic",
            "SubscriptionArn": "arn:aws:sns:us-west-2:123456789012:my-topic:8a21d249-4329-4871-acc6-7be709c6ea7f"
        }
    ]
}

ConfirmationWasAuthenticatedとは SNS サブスクリプションの確認(初回登録)が認証有りで行われたかどうかを表す属性であり、これがfalseの場合、IAM 認証なしでサブスクリプション解除できてしまう状態です。

以下エントリのように「いつの間にかサブスクリプションが削除されていた」という事態になりかねません。

ConfirmationWasAuthenticated属性を確認するにはaws sns get-subscription-attributesコマンドの使用が必要です。

そのため、コマンドは以下の流れで実行します。

  • aws ec2 describe-regionsでリージョン一覧を取得
  • リージョンごとに以下を実行
    • aws sns list-subscriptionsで SNS サブスクリプション一覧を取得
    • SNS サブスクリプションごとにaws sns get-subscription-attributesで情報を取得

実際に組み立てたコマンド例は以下です。

for region in $(aws ec2 describe-regions --query 'Regions[].[RegionName]' --output text);
do
  echo "### Listing SNS subscriptions for region $region"
  for subscription in $(aws sns list-subscriptions --region $region | jq -r '.Subscriptions | sort_by(.SubscriptionArn) | .[].SubscriptionArn')
  do aws sns get-subscription-attributes\
    --subscription-arn $subscription --region $region\
    | jq -r '.Attributes |  [.SubscriptionArn,.ConfirmationWasAuthenticated,.Protocol,.Endpoint] | @tsv'
  done
done

コマンドの実行結果例は以下の通り。

### Listing SNS subscriptions for region ap-south-1
arn:aws:sns:ap-south-1:000000000000:notify-mumbai:6e6ce52f-a64e-473d-b28d-e3b5d7f650c7	false	email	example@example.com
### Listing SNS subscriptions for region eu-north-1
### Listing SNS subscriptions for region eu-west-3
### Listing SNS subscriptions for region eu-west-2
### Listing SNS subscriptions for region eu-west-1
### Listing SNS subscriptions for region ap-northeast-3
arn:aws:sns:ap-northeast-3:000000000000:notify-osaka:9ee0bad1-b7ec-42f5-8113-2cb48044b9fa	false	email	example@example.com
### Listing SNS subscriptions for region ap-northeast-2
arn:aws:sns:ap-northeast-2:000000000000:notify-seoul:7faad0d8-b2ac-4cff-b8b1-7dc1351f9791	false	email	example@example.com
### Listing SNS subscriptions for region ap-northeast-1
arn:aws:sns:ap-northeast-1:000000000000:Publish-to-Lambda:8851927b-d074-4a48-84e2-f01f2a6f6e7d	false	email	example@example.com
arn:aws:sns:ap-northeast-1:000000000000:Publish-to-Lambda:b54a1670-6021-4da7-9a58-86ba7ff436e1	true	lambda	arn:aws:lambda:ap-northeast-1:000000000000:function:sns-message-python
arn:aws:sns:ap-northeast-1:000000000000:aes-siem-alert:65d05c6e-d851-4cad-a49d-23ea6952be4b	false	email	example@example.com
arn:aws:sns:ap-northeast-1:000000000000:insightwatch-InvokeLambdaFunctionTopic:0b62ba3a-9c1d-40e7-a9ac-9ad96e9f6054	true	lambda	arn:aws:lambda:ap-northeast-1:000000000000:function:insightwatch-create-message
arn:aws:sns:ap-northeast-1:000000000000:insightwatch-NotificationTopic:c236965a-8957-4301-a67c-a1100cda2c8b	false	email	example@example.com
arn:aws:sns:ap-northeast-1:000000000000:notify-mail:4aab0fa9-7e22-4fe6-aea6-f99564ea4698	false	email	example@example.com
arn:aws:sns:ap-northeast-1:000000000000:tedttopic:195d7b21-33ef-47e8-abf3-4aac8a0c55bf	false	email	example@example.com
### Listing SNS subscriptions for region ca-central-1
……以下略

1行ごとに以下情報がタブ区切りで出力されます。

  • SNS サブスクリプションの ARN
  • ConfirmationWasAuthenticatedの値
  • プロトコル
  • エンドポイント

あとはスプレッドシートなどで頑張ろう

ここまでで各リージョンの SNS トピックと SNS サブスクリプションの一覧が取得できました。

愚直にそれぞれの結果をスプレッドシートに貼り付けた例が以下です。(条件付き書式でリージョン部分は灰色に編みかけしている)

Spreadsheet_sns_list_topic_subscription

あとは両者を照らし合わせながら、

  • あるべき SNS サブスクリプションが紐づいていない SNS トピックがないか
  • 紐づく SNS トピックがない SNS サブスクリプションがないか *1
  • 既存の SNS サブスクリプションのConfirmationWasAuthenticated属性がどうなっているか

などなど、お好みの形でこねくりまわしましょう。

終わりに

各リージョンにおいて、SNS トピック一覧と SNS サブスクリプションの詳細一覧を取得したい、という話でした。

少しニッチなケースだったかもしれませんが、実際に試す際にコマンドを組み立てるのに30分くらい要したのでブログにまとめてみました。

  • aws ec2 describe-regionsで取得したリージョン一覧ごとにループ処理を実行する方法
  • ループ処理の中でさらにループ処理を実行する方法

が今回学べたので、そこそこ使い回しできるのではないかと思います。

なんらか参考になれば幸いです。

以上、 チバユキ (@batchicchi) がお送りしました。

参考

脚注

  1. てっきり SNS トピックを消したらそこに紐づく SNS サブスクリプションが併せて削除されると思っていたのですが、そうではないのですね……