誰かサブスクリプション解除した?を無くすために Amazon SNS によるメール通知の停止(Unsubscribe)リンクを無効化してみた

そのメーリングリスト、何名含まれていますか?その中の誰一人としてうっかり Unsubscribe リンクを押さないという保証がありますか……?

あれ、いつの間にか Amazon SNS のサブスクリプションがなくなってる……


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

Amazon SNS によるメール送信を行なっている場合、対象のメールには登録解除用のリンクが記載されています。デフォルトではこのリンクを押すとそのまま登録解除(Unsubscribe)が行われます。

メールの末尾に記載されているリンク

--
If you wish to stop receiving notifications from this topic, please click or visit the link below to unsubscribe:
https://sns.ap-northeast-1.amazonaws.com/unsubscribe.html?SubscriptionArn=arn:aws:sns:ap-northeast-1:000000000000:トピック名:サブスクリプション名&Endpoint=メールアドレス

メール送信先がメーリングリストである場合、複数の宛先に届くことになります。その中の誰か1人が(自分への通知の停止を意図して)上記のリンクを押したとすると、メーリングリストがまるまるサブスクリプション解除されてしまいます。

メールからの登録解除はイベントにも記録されないため、いつの間にか登録が解除されメール通知が行われない状態になっているという事態になりかねません。Amazon SNSを使う上でのいにしえからのあるあるだと思います。

リンクを押しても Unsubscribe されない状態にする登録の仕方がありますので、今回はそれをご紹介します。その方法自体は過去にも DevelopersIO でも取り上げられているため、少し観点をずらして記していきます。

メール内リンクからの Unsubscribe を無効にするには

先に結論から言うと、初回の SNS サブスクリプションの Confirm をメールでなくマネジメントコンソールから行なってください。

SNS Topic Subscribe

SNS のサブスクリプションの作成時、指定したエンドポイント(ここではメールアドレス)宛にAWS Notification - Subscription Confirmationという件名のメールが届きます。

初回登録メールの本文

You have chosen to subscribe to the topic:
arn:aws:sns:ap-northeast-1:000000000000:トピック名

To confirm this subscription, click or visit the link below (If this was in error no action is necessary):
Confirm subscription # ここがリンクになっている

Please do not reply directly to this email. If you wish to remove yourself from receiving all future SNS subscription confirmation requests please send an email to sns-opt-out

上記の本文のConfirm subscriptionという部分がリンクになっており、それを押下することで登録完了させるケースが多いかと思います。今回はそれはやらないでください。

ここでのリンクをコピーし、マネジメントコンソールで登録を行うことで冒頭の例での Unsubscribe を無効化できます。

コピーしたリンク先は以下のような内容になっているはずです。

https://sns.ap-northeast-1.amazonaws.com/confirmation.html?TopicArn=arn:aws:sns:ap-northeast-1:000000000000:トピック名&Token=(そこそこ長いトークンが続く)&Endpoint=メールアドレス

このリンクを控えてマネジメントコンソールに移りましょう。

SNS トピックの画面から保留中のサブスクリプションを選択し、「サブスクリプションの確認」を押下します。

Simple_Notification_Service_Subscription

URL の入力画面が現れるため、先ほどコピーしたリンク先の URL を入力し、「サブスクリプションの確認」を押下します。

Simple_Notification_Service_Confirm_Subscripstion

サブスクライブが完了し、サブスクリプションの ID が払い出されステータスが「確認済み」となります。

Simple_Notification_Service_Configr_Subscription

この状態であれば、メール内の登録解除用 URL を押下しても Unsbuscribe が行われません。

マネジメントコンソールから Confirm することで何が変わるのか

メール内のリンクを押下して Confirm を行うのと上記の手順をとるのとで明確に変わる点があります。サブスクリプションのConfirmationWasAuthenticated属性が true になることです。

デフォルトでは false になっており、Unsubscribe に AWS の認証が不要な状態です。そのため、誰でもメール内のリンクを押下するだけで登録解除ができてしまいます。 true の場合は AWS 認証が必要になりますので、メール内のリンクを押すだけでは Unsubscribe できません。別途 IAM ユーザーなどを使用してマネジメントコンソールや AWS CLI などで Unsubscribe の操作を行う必要があります。

初回のメール内リンクを押下して Confirm を行う場合、ConfirmationWasAuthenticated属性は false になります。マネジメントコンソールで確認(Confirm)を行うことで、true にできます。(あえて false にしたいケースはないでしょうが、マネジメントコンソールからの確認では false にはできません。)

この属性の値がどうなっているかを確認したい場合、マネジメントコンソールからは参照できないため AWS CLI などで確認が必要です。

先ほどのマネジメントコンソールでの Confirm を行ったサブスクリプションの属性を参照してみると、ConfirmationWasAuthenticatedが true になっていることが分かります。

% 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"
    }
}

追記:複数あるサブスクリプションの設定値を一括で確認したい

以下にてコマンドを整備しましたので、あわせてご参照ください。

既存の SNS サブクスリプションの ConfirmationWasAuthenticated 属性を変更できるか?

できません。

つまり、Confirm をメール内リンクを押下して行った場合、そのサブスクリプションを「メール内リンクによる Unsubscribe 無効」の状態に切り替えはできません。

既存のサブスクリプションを削除し、新規に登録し直してください。具体的な手順は以下が参考になるかと思います。

ConfirmSubscription は AWS CLI でもできます

ここまではマネジメントコンソールから「サブスクリプションの確認」を行うものとして紹介してきましたが、もちろん AWS CLI でもできます。

むしろ以前は AWS CLI でのみ、ConfirmationWasAuthenticated属性を true に設定できたかと記憶しています。具体的な手順は以下を参照してください。

サブスクリプション関連のコマンドは以下が該当します。

番外編:誰が(メールから) Unsubscribe したか確認できるか?

ここまで見てきたような手順を経なかったために、ConfirmationWasAuthenticated属性が false のサブスクリプションをうっかりメールから Unsubscribe してしまうこともあるでしょう。

それをいつ誰が行ったのか?を確認する術はあるのでしょうか。解除時に AWS 認証を経ないため、恐らくないと考えます。試した限りでは CloudTrail でも AWS Config でも記録されませんでした。

以下のような操作を試行し、記録内容を確認してみました。

# 時刻 操作
1 14:03 サブスクリプションの作成、メールからの Confirm
2 14:05 メール内リンクからの Unsubscribe
3 14:07 サブスクリプションの作成、メールからの Confirm
4 14:08 マネジメントコンソールからの Unsubscribe

CloudTrail での Unsubscribe の記録

該当時間のイベントを確認すると、#2 のメール内リンクからの Unsubscribe が記録されていません。

SNS_Subscribe

イベント名Unsubscribeとして記録されているのはマネジメントコンソールから行ったもので、以下画面での操作を指します。

Simple_Notification_Service_Unsubscribe

参考までにイベントの内容を記載します。マネジメントコンソールの操作に用いた IAM ロールのセッションがプリンシパルとして記載されています。

{
    "eventVersion": "1.08",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "AROAQ3BIIH732QEGJXXXX:cm-chiba.yukihiro",
        "arn": "arn:aws:sts::000000000000:assumed-role/cm-chiba.yukihiro/cm-chiba.yukihiro",
        "accountId": "000000000000",
        "accessKeyId": "ASIAQ3BIIH73XWTRXXXX",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "AROAQ3BIIH732QEGJXXXX",
                "arn": "arn:aws:iam::000000000000:role/cm-chiba.yukihiro",
                "accountId": "000000000000",
                "userName": "cm-chiba.yukihiro"
            },
            "webIdFederationData": {},
            "attributes": {
                "creationDate": "2021-10-25T05:03:06Z",
                "mfaAuthenticated": "true"
            }
        }
    },
    "eventTime": "2021-10-25T05:08:04Z",
    "eventSource": "sns.amazonaws.com",
    "eventName": "Unsubscribe",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "219.xx.xx.xx",
    "userAgent": "aws-internal/3 aws-sdk-java/1.12.59 Linux/5.4.141-78.230.amzn2int.x86_64 OpenJDK_64-Bit_Server_VM/25.302-b08 java/1.8.0_302 vendor/Oracle_Corporation cfg/retry-mode/standard",
    "requestParameters": {
        "subscriptionArn": "arn:aws:sns:ap-northeast-1:000000000000:aes-siem-alert:a76848d2-32bb-4bec-afd0-b5950b100f5c"
    },
    "responseElements": null,
    "requestID": "8296ddd4-c5e6-51f5-b29c-8424f812d6f5",
    "eventID": "3c63212e-8ded-447c-a632-a3e0a61d0ff4",
    "readOnly": false,
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "000000000000",
    "eventCategory": "Management"
}

↑メール内リンクからの Unsubscribe ではこういったプリンシパルが存在しませんので、Trail には記録されないものであると理解しています。

AWS Config での Unsubscribe の記録

サブスクリプションと紐付けた SNS トピックのリソースタイムラインを確認してみます。

リソースとしての設定変更は記録されておらず、関連 CloudTrail イベントとして記録されているのはSubscribeのイベントのみでした。

AWS_Config_Console_SNS_Subscribe2

AWS Config による記録がサポートされているリソースタイプを確認すると、 Amazon SNS においてはAWS::SNS::Topicのみが該当し、サブスクリプションは含まれていません。

AWS Config に記録される SNS トピックの JSON の設定項目は以下の通りです。あくまで SNS トピックの設定のみであり、サブスクリプションに関する内容が含まれていないことが分かります。

{
  "version": "1.3",
  "accountId": "000000000000",
  "configurationItemCaptureTime": "2021-09-21T06:22:17.690Z",
  "configurationItemStatus": "ResourceDiscovered",
  "configurationStateId": "1632205337690",
  "configurationItemMD5Hash": "",
  "arn": "arn:aws:sns:ap-northeast-1:000000000000:aes-siem-alert",
  "resourceType": "AWS::SNS::Topic",
  "resourceId": "arn:aws:sns:ap-northeast-1:000000000000:aes-siem-alert",
  "resourceName": "aes-siem-alert",
  "awsRegion": "ap-northeast-1",
  "availabilityZone": "Not Applicable",
  "tags": {},
  "relatedEvents": [],
  "relationships": [],
  "configuration": {
    "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\",\"SNS:Receive\"],\"Resource\":\"arn:aws:sns:ap-northeast-1:000000000000:aes-siem-alert\",\"Condition\":{\"StringEquals\":{\"AWS:SourceOwner\":\"000000000000\"}}}]}",
    "Owner": "000000000000",
    "TopicArn": "arn:aws:sns:ap-northeast-1:000000000000:aes-siem-alert",
    "EffectiveDeliveryPolicy": "{\"http\":{\"defaultHealthyRetryPolicy\":{\"minDelayTarget\":20,\"maxDelayTarget\":20,\"numRetries\":3,\"numMaxDelayRetries\":0,\"numNoDelayRetries\":0,\"numMinDelayRetries\":0,\"backoffFunction\":\"linear\"},\"disableSubscriptionOverrides\":false}}",
    "DisplayName": "AES SIEM"
  },
  "supplementaryConfiguration": {
    "Tags": []
  },
  "resourceTransitionStatus": "None"
}

「この時点ではこの SNS トピックにこのサブスクリプションが関連づいていた」というのが分かれば追跡の余地がありましたが、現状ではそれは無理そうです。

起こってしまったことは諦めて、サブスクリプションのConfirmationWasAuthenticated属性を true にして Confirm することで対応しましょう。

もうメール内のリンクから Unsubscribe させない

SNS トピックによるメール送信、その本文内に含まれる登録解除用リンクからのメール通知停止を防ぐ手段について確認しました。単純にメールで Confirm するより手間は増えますが、うっかり解除されてしまった時のインパクトを鑑みて設定をご検討ください。

ConfirmationWasAuthenticatedという属性の名前を知ったので、ここぞとばかりにエントリ内に散りばめてみました。ちなみにこれはConfirmSubscriptionという API を実行する際にAuthenticateOnUnsubscribeというパラメータを true にし、合わせてトークンを指定することで true にできます。

AWS CLI で Confirm するときは上記のパラメータについて存分に意識が必要ですが、マネジメントコンソールから「確認」するときはよしなにやってくれます。「『サブスクリプションの確認』からリンクをペーストすればいいんだな」という理解でも十分ですが、こういった設定値を意識しておくと捗る時があるかもしれません。

参考になれば幸いです。

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

参考