API GatewayからSNSトピックにメッセージ発行してメール通知

Webhookで呼び出されたAPI Gatewayから、SNSにトピックを発行してメール通知する仕組みを作ってみました。本記事では簡単のため、Webhook役を`curl`コマンドが演じています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51871/fde4d320-57b0-08bc-4be6-f07b3c286e24.png)

図中の四角数字は、以下手順の番号と対応しています。

# 【手順1】ロールの作成（IAM）

API GatewayのAPIにアタッチする用のロールを作成していきます。ユースケースから「API Gateway」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51871/bbd17ccd-09df-0756-1688-1bb074c2d242.png)

ここでは、ロール名は「`APIGatewayAWSProxyExecRole`」とします。

ロール作成後はデフォルトで「`AmazonAPIGatewayPushToCloudWatchLogs`」がアタッチ済みとなっています。「`AmazonSNSFullAccess`」もアタッチしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51871/81e6ac50-6324-460c-2356-147d32117030.png)

#【手順2】トピックの作成（SNS）

まず、トピックを作成します。トピック名は「MyTopic」とします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51871/165f5e4a-a4a7-884c-0106-072185006b03.png)

つぎに、「サブスクリプションの作成」をクリックして、作成したトピックの購読設定を行います。

* Eメールに通知する場合
 * プロトコルは「Eメール」を選択
 * エンドポイントにはSNSからの通知を受信するメールアドレスを入力

* SMSに通知する場合
 * プロトコルは「SMS」を選択
 * エンドポイントにはSNSからの通知を受信する電話番号を入力（電話番号が日本の場合、「+81」の後に、電話番号先頭の0を除外した番号を入力する（例「08012345678」の場合「+818012345678」を入力））

最後に「サブスクリプションの作成」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51871/aad6f2dc-7325-099a-edf2-1f579247ca38.png)

「サブスクリプションの作成」をクリックしたタイミングで、Amazon SNSから「AWS Notification - Subscription Confirmation」という件名のメールが（上で入力したメールアドレス宛に）送信されますので、メールを開いてConfirmします。

#【手順3】REST APIの作成（API Gateway）
API Gatewayで、REST APIを作成します。APIタイプは「REST API」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51871/3da0c372-97fd-8186-4431-57d2372623d4.png)
API名は「MyAPI」としておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51871/dde1add2-d99a-4515-b06e-db5684c47ce4.png)

APIのリソースとメソッドを作成します。
ここでは「/mail」リソースに対するPOSTメソッド、というインタフェースとしています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51871/6121d284-dded-1aa5-abff-e0625c414256.png)

* 実行ロールには、【手順1】で作成したロールのARNを入力します。ARNは、IAMコンソールのロールから確認できます。

|#|項目|値|
|:--|:--|:--|
|1|統合タイプ|AWSサービス|
|2|AWSリージョン|（SNSトピックを作成したリージョン）|
|3|AWSサービス|Simple Notification Service (SNS)|
|4|HTTPメソッド|POST|
|5|アクション|Publish|
|6|実行ロール|arn:aws:iam::************:role/APIGatewayAWSProxyExecRole|

次に、リクエストデータをバックエンド向け（SNS向け）に加工するため、統合リクエストの設定を行います。
「統合リクエスト」のリンクをクリックすると、設定画面が開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51871/b3ed2091-b0a5-adbe-8e39-3866f8684a96.png)

統合リクエストの設定画面で、SNS向けにクエリ文字列を追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51871/2e17255c-bca3-3cc5-e7c7-2b0714cec255.png)

|#|キー|値|
|:--|:--|:--|
|1|Message|method.request.body|
|2|TopicArn|'arn:aws:sns:ap-northeast-1:************:MyTopic'|

`TopicArn`の値には、【手順2】で作成したSNSトピックのARNを指定します。ARNは、SNSのコンソール画面から確認できます。

設定保存後、「アクション」メニューの「デプロイ」をクリックして、デプロイを行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51871/98bd2564-35e8-7812-9b74-ae128ff110f5.png)

デプロイ後にURLが表示されますので、メモしておきます。
URLは`https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/test/mail`の形式になります。

##（補足）リソースポリシーの設定（API Gateway）

作成したAPIをデプロイ後はインターネットに公開されるため、誰でもアクセスできてしまいます。リクエスト元のIPアドレスを制限したい場合、リソースポリシーを定義することでアクセス元を制限することができます。

今回は、ホワイトリスト形式で登録します。以下をベースにして作成します。

* `Resource`は、本APIのARNを指定してください。
* `aws:SourceIP`に、APIへのアクセスを許可するIPアドレスを指定してください。

```json:リソースポリシー（ホワイトリスト形式）
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "arn:aws:execute-api:ap-northeast-1:xxxxxxxxxxxx:xxxxxxxxxx/*/*/*",
            "Condition": {
                "IpAddress": {
                    "aws:SourceIp": "xxx.xxx.xxx.xxx"
                }
            }
        }
    ]
}
```

作成したリソースポリシーを、以下のテキストエリアにペーストして保存します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51871/3be7e4f9-c3af-275a-1b96-73965bf9e3db.png)

リソースポリシーを保存し、APIをデプロイした後は、許可された送信元（IPアドレス）からのアクセスのみリクエストを受け付けます。
許可されない送信元からのアクセスに対しては、`403 Forbidden`で以下のエラーメッセージが返されます。

```json:403_Forbidden
{
  "Message": "User: anonymous is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:ap-northeast-1:************:**********/test/POST/mail"
}
```

リソースポリシーをテストする際、固定のグローバルIPをもつホストからリクエストを投げる必要がありますが、パブリックサブネットにEC2インスタンスを立てればいいですかね。

#【手順4】REST API呼出しのテスト（curlコマンド）

今回作成したAPIを、curlコマンドで呼出してみます。

```bash:
$ curl -X POST -H "Content-Type: application/json" -d '{"id":"1024", "name":"yz2cm"}' https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/test/mail
```

【手順2】で購読設定したメールアドレスに、メールが送信されているはずです。

* 送信元は`MyTopic <no-reply@sns.amazonaws.com>`。
* 件名は`AWS Notification Message`固定。

```text:SNSからの受信メール

{"id":"1024","name":"yz2cm"}

--
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:xxxxxxxxxxxx:MyTopic:e9709da9-e2fc-4098-9b1b-89bcd50af664&Endpoint=foo@gmail.com

Please do not reply directly to this email. If you have any questions or comments regarding this email, please contact us at https://aws.amazon.com/support

```

```:コマンドの標準出力
{
    "PublishResponse": {
        "PublishResult": {
            "MessageId":"dbb7488c-6c45-5c31-8038-1fc87bc2f28d",
            "SequenceNumber":null
        },
        "ResponseMetadata": {
            "RequestId":"347548a3-f894-552f-a8ef-1fe7ef138b93"
        }
    }
}
```

# 参考ページ
本記事を書くにあたり、参考にさせていただいたページです。

* [API GatewayからLambdaを介さずにSNSトピックへ送信](https://www.yamamanx.com/api-gateway-sns/)
* [Amazon API Gateway で API (Webhook) の呼び出し元 IP アドレスを制限する](https://muziyoshiz.hatenablog.com/entry/2018/07/29/113028)
* （公式）[チュートリアル: API Gateway 統合を使用して REST API AWS をビルドする](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/getting-started-aws-proxy.html)