SNS サブスクリプションが認証なしで Unsubscribe できる状態でないか一括で確認するワンライナー

あなたの環境にある SNS サブスクリプションはメール内のリンクから登録解除できる状態になっていませんか?

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

Amazon SNS から送信されるメールの中のリンクをうっかり押して、サブスクリプションが解除されてしまったことはありませんか?

そんな事態を避けるための方法を先日ブログにしました。

初回登録時にひと工夫することで SNS サブスクリプションのConfirmationWasAuthenticatedという属性が true になり、AWS 認証なしでの登録解除ができなくなります。

具体的な設定方法は上記のブログをご確認いただくとして、既存のサブスクリプションの設定がどうなっているか?の確認手段を見ていきます。

% aws sns get-subscription-attributes\
  --subscription-arn arn:aws:sns:ap-northeast-1:000000000000:aes-siem-alert:8a6c4ff2-7818-4790-9e06-a33427e43ad4
{
    "Attributes": {
        "Owner": "000000000000",
        "RawMessageDelivery": "false",
        "TopicArn": "arn:aws:sns:ap-northeast-1:000000000000:aes-siem-alert",
        "Endpoint": "#メールアドレス#",
        "Protocol": "email",
        "PendingConfirmation": "false",
        "ConfirmationWasAuthenticated": "true",
        "SubscriptionArn": "arn:aws:sns:ap-northeast-1:000000000000:aes-siem-alert:8a6c4ff2-7818-4790-9e06-a33427e43ad4"
    }
}

↑上記のようにサブスクリプションの ARN を指定することで属性はチェックできるのですが、一つずつしかチェックできないのが辛いところです。

環境に複数のサブスクリプションが存在する場合に、効率よくチェックできる手段はないものか……と検討しました。

SNS サブスクリプションの属性を一括でチェックするワンライナー

以下を実行することで実現できます。

% echo '"SubscriptionArn","ConfirmationWasAuthenticated","Protocol","Endpoint"' && aws sns list-subscriptions | jq -r '.Subscriptions[].SubscriptionArn' | xargs -I{} -P 5 aws sns get-subscription-attributes --subscription-arn {} | jq -r '.Attributes |  [.SubscriptionArn,.ConfirmationWasAuthenticated,.Protocol,.Endpoint] | @csv'

実行すると、以下のように CSV 形式で結果が表示されます。

% echo '"SubscriptionArn","ConfirmationWasAuthenticated","Protocol","Endpoint"' && aws sns list-subscriptions | jq -r '.Subscriptions[].SubscriptionArn' | xargs -I{} -P 5 aws sns get-subscription-attributes --subscription-arn {} | jq -r '.Attributes |  [.SubscriptionArn,.ConfirmationWasAuthenticated,.Protocol,.Endpoint] | @csv'
"SubscriptionArn","ConfirmationWasAuthenticated","Protocol","Endpoint"
"arn:aws:sns:ap-northeast-1:000000000000:SampleTopic1:c236965a-8957-4301-a67c-a1100cda2c8b","false","email","example@example.com"
"arn:aws:sns:ap-northeast-1:000000000000:SampleTopic2:0b62ba3a-9c1d-40e7-a9ac-9ad96e9f6054","true","lambda","arn:aws:lambda:ap-northeast-1:000000000000:function:SampleFunction"
"arn:aws:sns:ap-northeast-1:000000000000:SampleTopic3:195d7b21-33ef-47e8-abf3-4aac8a0c55bf","false","email","example@example.com"
"arn:aws:sns:ap-northeast-1:000000000000:SampleTopic4:65d05c6e-d851-4cad-a49d-23ea6952be4b","false","email","example@example.com"
"arn:aws:sns:ap-northeast-1:000000000000:SampleTopic5:b39b66e5-0330-4f79-b7e2-44597af7715c","false","email","example@example.com"
.....

結果をスプレッドシートなどに転記してお好みの形で取り扱ってください。

subscription_attribute

初めから「プロトコルがemailで ConfirmationWasAuthenticated がfalse」のもののみ出力したいという場合は、末尾に| grep email | grep falseを追加してください。

SNS サブスクリプション属性チェックワンライナーの内訳

改行を挟むと以下となります。

$ echo '"SubscriptionArn","ConfirmationWasAuthenticated","Protocol","Endpoint"' &&\
  aws sns list-subscriptions | jq -r '.Subscriptions[].SubscriptionArn'\
  | xargs -I{} -P 5 aws sns get-subscription-attributes --subscription-arn {}\
  | jq -r '.Attributes |  [.SubscriptionArn,.ConfirmationWasAuthenticated,.Protocol,.Endpoint] | @csv'

1行目から順に以下の処理を行なっています。

  1. CSV のヘッダー行を出力
  2. SNS サブスクリプションの一覧を取得し、SubscriptionArnのみ取得
  3. 2行目で取得したSubscriptionArnを引数にしてサブスリプションの属性を取得(ここでは最大5個のプロセスで並列処理)
  4. サブスクリプションの ARN のうち必要な項目のみ抽出して取得し、CSV 形式に変換

いくつか要素を取り上げます。

aws sns list-subscriptions

リファレンスは以下です。

出力例は以下です。今回はこのうちSubscriptionArnのみを使用します。

{
    "Subscriptions": [
        {
            "SubscriptionArn": "arn:aws:sns:ap-northeast-1:000000000000:SampleTopic1:c236965a-8957-4301-a67c-a1100cda2c8b",
            "Owner": "000000000000",
            "Protocol": "email",
            "Endpoint": "example@example.com",
            "TopicArn": "arn:aws:sns:ap-northeast-1:000000000000:SampleTopic1"
        },
        {
            "SubscriptionArn": "arn:aws:sns:ap-northeast-1:000000000000:SampleTopic2:0b62ba3a-9c1d-40e7-a9ac-9ad96e9f6054",
            "Owner": "000000000000",
            "Protocol": "lambda",
            "Endpoint": "arn:aws:lambda:ap-northeast-1:000000000000:function:SampleFunction",
            "TopicArn": "arn:aws:sns:ap-northeast-1:000000000000:SampleTopic2"
        },
        ......

一度に取得できるサブスクリプションの上限は 100 なので、それより多い場合は今回のワンライナーだけでは賄いきれません。

aws sns get-subscription-attributes

リファレンスは以下です。

実行例は以下です。今回はハイライト部の値を取得しましたが、お好みのものにカスタマイズしてください。

% aws sns get-subscription-attributes\
  --subscription-arn arn:aws:sns:ap-northeast-1:000000000000:aes-siem-alert:8a6c4ff2-7818-4790-9e06-a33427e43ad4
{
    "Attributes": {
        "Owner": "000000000000",
        "RawMessageDelivery": "false",
        "TopicArn": "arn:aws:sns:ap-northeast-1:000000000000:aes-siem-alert",
        "Endpoint": "#メールアドレス#",
        "Protocol": "email",
        "PendingConfirmation": "false",
        "ConfirmationWasAuthenticated": "true",
        "SubscriptionArn": "arn:aws:sns:ap-northeast-1:000000000000:aes-siem-alert:8a6c4ff2-7818-4790-9e06-a33427e43ad4"
    }
}

AWS CLI で一覧取得したリソースを xargs で次のコマンドの引数にする

今回のaws sns get-subscription-attributesのように、同時に一つのリソースしか対象にできない AWS CLI コマンドは多々あります。環境に存在するリソースに対して同じ処理を実行したい、というケースはよくあるかと思いますが、パイプと xargs を用いることで効率的に処理できることを知りました。

先行するコマンドで得られたリスト内のリソースそれぞれに対して後続のコマンドを実行したいという場合、ループ処理を用いると直列で処理していくことになるので時間がかかります。xargs で-Pオプションを付与することでパラレルで処理できるようになるためより効率的です。

使う機会が多いのでぜひ覚えておきたいテクニックです。

SNS サブスクリプションの属性を一括でチェックするシェルスクリプト

大抵の場合は上記のワンライナーで充足するかと思いますが、シェルスクリプトも作ってみました。

違いがあるとすれば、SNS サブスクリプションが 101 個以上あっても対応できるようにNextTokenの有無を判断する処理が入っているくらいです。

#!/usr/bin/bash

echo $(date "+%H:%M:%S") "サブスクリプションリストの取得開始"

result=$(aws sns list-subscriptions)
subscription_list=$(echo "$result" | jq -r '.Subscriptions[].SubscriptionArn')
next_token=$(echo "$result" | jq -r '.NextToken')

LF="
"

while [ "${next_token}" != "null" ]; do
    result=$(aws sns list-subscriptions --starting-token "$next_token")
    subscription_list+="$LF"$(echo "$result" | jq -r '.Subscriptions[].SubscriptionArn')
    next_token=$(echo "$result" | jq -r '.NextToken')
done

echo $(date "+%H:%M:%S") "サブスクリプションリストの取得完了"
echo $(date "+%H:%M:%S") "サブスクリプション属性の取得開始"
echo "----------------------"

echo ""SubscriptionArn","ConfirmationWasAuthenticated","Protocol","Endpoint""

echo "$subscription_list" | while read i; do
    aws sns get-subscription-attributes --subscription-arn $i | jq -r '.Attributes |  [.SubscriptionArn,.ConfirmationWasAuthenticated,.Protocol,.Endpoint] | @csv'
done

echo "----------------------"
echo $(date "+%H:%M:%S") "サブスクリプション属性の取得完了"

シェルスクリプトよく分からん……となりながら突貫で作ったものなので改善の余地はあるかと思いますが、手元の macOS と AWS CloudShell で最低限の動作は確認しました。せっかくなのでハマったところを備忘として残しておきます。

シェルスクリプト内で変数を echo する際に改行を維持したい

aws sns list-subscriptionsの結果を格納した変数resultを後続の処理で echo する際に、変数をダブルクォーテーションで囲まないと一行で出力されるという事象にハマりました。

# 以下の指定だと結果の JSON が一行で表示される
echo $result

# 以下の指定だと改行が反映された JSON で表示される
echo "$result"

環境変数に追記するときに改行を挟みたい

変数subscription_list+=で値を追記していく際に、そのままでは元々の値の直後に追記されるため改行を挟みたいとなりました。

\nを付与する方式は CloudShell 上ではうまく機能しなかったので、以下のように変数LFに改行を格納するという力技で回避しました。

LF="
"

これはこれでうまくいったのですが、そもそものロジックを見直した方が簡単でした。

先にリストを全て取得するのでなく、取得できた分に関してaws sns get-subscription-attributesをループで実行し、そのあとに再度NextTokenを使用して続きを取得するような処理にすればよかったのでは、と後から気づきました。

慣れないシェルスクリプトの作成で不恰好になってしまいましたが、何事も経験、ということでよしとします。

終わりに

SNS サブスクリプションの属性を一括で確認するワンライナー(とシェルスクリプト)のご紹介でした。

直前までこのブログのタイトルは「SNS サブスクリプションが認証なしで Unsubscribe できる状態でないか一括で確認するシェルスクリプト」でありワンライナーは思いついていなかったのですが、一通り書き上げたあとに以下のエントリが目に入りました。

対象となる AWS サービスは全く違うものの「AWS CLI で一覧取得したのちにそれぞれに対してコマンドを実行するワンライナー」が紹介されていたので、明らかにこっちの方が楽じゃん……となり、ワンライナーを作成しメインに差し替えました。

いろいろと使い回しは効きそうなので、何かの機会に思い出してもらえれば幸いです。

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