LambdaとSNSでTypetalkに通知する仕組みを作る

8月上旬に　WAF Managed rule への変更が SNS トピック( aws-managed-waf-rule-notifications )に対して通知される機能がリリースされた。
今回は、　Lambda を使って Typetalk にそれを通知する仕組みを作ってみる。

#使用するサービス
##Amazon SNS
アプリケーション間のメッセージ、HTTP、Eメール、スマホのプッシュ通知を行うことができる。
ユーザーアクションをトリガーにできる。
今回は aws-managed-waf-rule-notifications という SNS トピックを使用し、 Lambda 関数に通知する。

##AWS Lambda
サーバレスでプラグラムを実行できるサービス。
今回は SNS からの通知を受け Typetalk api を叩く。

##Typetalk bot
Type talk 上のボット、 Typetalk api を叩くことで動かすことができる。

#システム構成
![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1488939/b7a6ec06-d26a-e3cf-6eb4-04514759096d.png)

#作成手順
###Typetalk bot の作成
Typetalk の通知したいトピックで bot を作成する。
ボットの設定画面から Typetalk Token と 「メッセージの取得と投稿の URL」をメモする。

###関数ファイルの作成
pip を使用するので必要であればインストール。

https://qiita.com/ohbashunsuke/items/e7c673db606a6dced8a6

Lambda にアップロードする関数を作成する。

```
~ % mkdir lambda
~ % cd lambda
lambda % pip install requests -t./
lambda % vim lambda_function.py
```

```lambda_function.py
import requests
import json

token = '{Typetalk Token}'
url = '{メッセージの取得と投稿の URL}'

def lambda_handler(event, lambda_context):
    data = {'message':json.dumps(event, indent=2)}
    headers = {'X-TYPETALK-TOKEN': token}
    r = requests.post(url, json = data, headers = headers)
    print(r.status_code)
    print(r.json())
    None
```

```
lambda % zip -r lambda.zip ./
```

###Lambda 関数を作成
関数名を適当に設定し、ランタイムを Python に設定。
実行ロールはデフォルトを使用。
![スクリーンショット 2021-09-02 17.34.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1488939/011cd3e6-f486-37c7-9d9a-1074024f346b.png)

 SNS トピックに ```arn:aws:sns:us-east-1:248400274283:aws-managed-waf-rule-notifications``` を設定しトリガーを追加。
![スクリーンショット 2021-09-02 17.39.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1488939/38a412a9-aabc-7247-4ba9-728bfd90996d.png)

lambda.zip ファイルをアップロードする。
![スクリーンショット 2021-09-02 17.43.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1488939/0846ce47-2e98-ae96-3424-c99a66a45ea7.png)

テストイベントを作成する。
テンプレートの```sns-notification```を設定し、名前を適当に決めたらテストをクリック。
![スクリーンショット 2021-09-02 17.47.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1488939/2929231e-13b7-9c4f-93fd-0970a7241f33.png)

以下の表示がでて、 Typetalk に Bot が投稿していれば成功。
![スクリーンショット 2021-09-02 17.53.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1488939/30ddbb02-4c54-de6e-e36d-37331f9ad5cd.png)

#補足
AWS document などを探したが SNS 通知の具体的な内容、テンプレートは見つけられなかった。
aws-managed-waf-rule-notifications がリリースされてから managed rule が変更されておらず、一度も SNS 通知は飛んでいない。
AWS サポートいわく提供可能なものはないとのこと。
そのため、通知をそのまま Typetalk に丸投げする仕組みにしてある。

###感想
Lambda をまともに触るのは初めてで、かなり不安があったがなんとか完成できた。
Lambda の使い方や、 api との連携方法などなど多くの学びがあった。
今回は上長からの指示で仕事として Lambda 関数を作成したが、私用でも（ Discord api など ）チャレンジしたくなった。

#追記 : 通知分の整形
先日aws-managed-waf-rule-notificationsが通知され、テンプレートを把握できたので整形してtypetalkに通知するようにlambda関数を変更する。

通知されたaws-managed-waf-rule-notificationsの平文。

```
{
  "Records": [
    {
      "EventSource": "aws:sns",
      "EventVersion": "1.0",
      "EventSubscriptionArn": "arn:aws:sns:us-east-1:248400274283:aws-managed-waf-rule-notifications:b228a2dd-d3c4-4100-b66d-605c15c67f73",
      "Sns": {
        "Type": "Notification",
        "MessageId": "e2c454b9-3391-5c55-b6ef-0d76464587f6",
        "TopicArn": "arn:aws:sns:us-east-1:248400274283:aws-managed-waf-rule-notifications",
        "Subject": "Notification of upcoming version release for AWSManagedRulesWordPressRuleSet",
        "Message": "We will be releasing a new version of the AWS Managed Rules (AMR) for AWS WAF. The new version will add label support for all AWS Managed Rules rule groups that don't already have labeling. This release doesn't include any rule signature changes.\n\nStarting next week, October 6, 2021, we will begin rolling out this new version. The rollout will be completed over a period of two weeks, with changes deployed incrementally across each Region. You will receive a final notification when this new version is available globally. If you use the default version of an AMR rule group, AWS WAF will automatically update you to the latest version. If you use a static version of a rule group, you can update the version to adopt these changes.\n",
        "Timestamp": "2021-09-30T08:21:40.082Z",
        "SignatureVersion": "1",
        "Signature": "NjZzvXRQZnsv0FfDUONU8NhxcxZPDxuI9t5nUeWbgEH8VDlFzMzrQ+F2y/PK3M2d3wP0+T8tFLrwc8b8zeTxlyGBCll/68daiRnFpyjU+5D9AkpUzcAPheDH+ZiIJa4xfjPx+tzXSERT6AhchpXLbW63VRP//+eDVk3tAyCs+QINHHLtUHOe5kMxv25YQRG15XSc8z3E09ouiYkzjlS6DLzUHws9gA/F0gDnfxzogWD5U9HyVGNrqhgbLiMsFkUB8CHpYdQ1XvhF8ufDGb8LSli9uuONGg/Jmg1M/2K14a6hN3Q36VCejOHo5HqJvHyiB7oIyp7VIWxaOY/0GT8Bdw==",
        "SigningCertUrl": "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-7ff5318490ec183fbaddaa2a969abfda.pem",
        "UnsubscribeUrl": "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:248400274283:aws-managed-waf-rule-notifications:b228a2dd-d3c4-4100-b66d-605c15c67f73",
        "MessageAttributes": {
          "major_version": {
            "Type": "String",
            "Value": "v1"
          },
          "managed_rule_group": {
            "Type": "String",
            "Value": "AWSManagedRulesWordPressRuleSet"
          }
        }
      }
    }
  ]
}
```

###整形方法
通知分に以下の処理を行う。
1. 必要箇所の抽出
2. 翻訳

必要なのはSubjectとMessageの部分なので、以下のように抽出する。

```
subject = event['Records'][0]['Sns']['Subject']
message = event['Records'][0]['Sns']['Message']
```

また、翻訳にはDeepL apiを使用した。

https://www.deepl.com/pro-api?cta=header-pro/

全ての機能を含めた最終的なlambda関数は以下のような感じになった。

```lambda_function.py
import requests
import json
import os

#環境変数からtypetalkのアクセス情報を読み込む
token = os.environ.get('TOKEN')
url = os.environ.get('URL')

#通知の英語部分、日本語部分をグローバル変数で定義
text_en = ""
text_jp = ""

#環境変数からdeeplのアクセス情報を読み込む
deepl_token = os.environ.get('DEEPL_TOKEN')
deepl_url = os.environ.get('DEEPL_URL')

def lambda_handler(event, lambda_context):
    #SNS通知から必要箇所を抽出
    subject = event['Records'][0]['Sns']['Subject']
    message = event['Records'][0]['Sns']['Message']

    #通知の英語部を整形
    text_en = subject + '\n\n' + '> ' + message.replace('\n', '\n>')
    print(text_en)
    
    #deeplの設定
    params = {
            "auth_key": deepl_token,
            "text": message,
            "source_lang": 'EN',
            "target_lang": 'JA'
        }
    
    #deeplにリクエスト
    request = requests.post(deepl_url, data=params)
    result = request.json()
    
    #deeplの結果から通知の日本語部を整形
    text_jp = result["translations"][0]["text"]
    text_jp = '> ' + text_jp.replace('\n', '\n>')
    print(text_jp)
    
    #英語部、日本語部を連結し通知文を整形
    text_full = '[aws-managed-waf-rule-notifications]' + '\n' + text_en + '\n\n' + text_jp
    data = {'message':text_full}
    headers = {'X-TYPETALK-TOKEN': token}
    print(data)
    
    #typetalkにリクエスト
    r = requests.post(url, json = data, headers = headers)
    print(r.status_code)
    print(r.json())
    None
```

###課題
DeepL api からエラーが返ってきた場合の動作を追加する必要がある。

#参考資料
https://business.ntt-east.co.jp/content/cloudsolution/column-134.html

https://qiita.com/miyuki_samitani/items/e44bc7e92a47531fd536

https://www.bit-drive.ne.jp/managed-cloud/column/column_14.html

https://confrage.jp/amazon-sns%E3%81%A8lambda%E9%96%A2%E6%95%B0%E3%81%AE%E9%80%A3%E6%90%BA%E6%96%B9%E6%B3%95/