Ruby
lambda
Slack
ServerlessFramework

Serverless Frameworkでタロットカードを引くSlack botを作った


はじめに

自分は昔、たろっとさんというTwitter Botを作っていました。このたろっとさんを復活させたいなぁと思ったのですが、TwitterのAPI申請がめんどくさそうなので、まずはSlack Bot化してみました。


作ったもの

まだ公式アプリではありません。

実際動いているコードから機密情報や、タロットカードを引くロジック(門外不出)を除いたものをGitHubに置いています。


システム構成

以下の図のような構成で作りました。

slack-tarot3-構成.png


  • AWS Lambdaで4つ関数を作成


    • 認可エンドポイントへのリダイレクタ

    • OAuth 2.0のコールバックエンドポイント

    • Event Subscriber

    • Reply



  • アクセストークン保存用にDynamoDBを使用

  • Event SubscriberでAmazon SNSにpublishし、Replyをトリガーで呼び出し

  • API GatewayとRoute 53でURLを独自ドメインに紐づけ

リダイレクタを作ったのは、クライアントIDやscopeが変わってもURLを変更する必要がなく管理が楽だからです。注意点として、リダイレクトする際はHTTPステータスコードは302にする必要があります。


All your server needs to do is build the same URL to slack.com/oauth/authorize you would build for your on-site "Add to Slack" URL and instead of presenting it in a button, send a HTTP 302 redirect to it instead.


Event Subscriberとリプライを分けたのは、SlackのEvents APIには、3秒以内で応答する必要があるためです。Event Subscriberとリプライを分けることで、リプライ側の処理が複雑になっても安心です。


Your app should respond to the event request with an HTTP 2xx within three seconds.



処理の流れ

処理の流れは以下のようになります。

slack-tarot3-フロー.png

詳細は以下のとおりです。1〜6までがOAuth 2.0による認可コードフロー、7〜12がタロットを引くところです。


  1. リダイレクタにアクセス

  2. Authorizeエンドポイントにリダイレクト

  3. Slack上でユーザが認証 & 認可

  4. コールバックエンドポイントにリダイレクト(アプリ管理画面のOAuth & Permissions → Redirect URLsに登録する必要あり)


  5. oauth.accessでcodeとアクセストークンを交換

  6. 取得したアクセストークンをDynamoDBに保存

  7. ユーザが @tarot3 と呼ぶ

  8. イベントが発生し、Events Subscriberが呼ばれる(アプリ管理画面のEvent Subscriptionsに登録する必要あり)

  9. Amazon SNSに通知を投げる

  10. Amazon SNSをトリガーとしてReply Lambda関数が呼び出される

  11. DynamoDBからアクセストークンを取得

  12. タロットカードを引いてリプライを送る


ポイント


まず画面で操作してみる

最終的にはServerless Frameworkを使いましたが、開発の最初はLambdaの管理コンソールで直接コードを書き、API Gatewayを手動で設定していました。ある程度コンソールで慣れてからServerless Frameworkを使うことで、設定ファイルの書き方がスムーズに理解できました。


scope=bot

scopeはいろいろ定義されているようですが、botだけで良さそうです。

注意しないといけないのは、scope=botの場合はボット専用のアクセストークンが含まれることです。


DNS設定

DNS設定にはserverless-domain-managerというプラグインを使ったのですが、証明書の指定でエラーになりました。

原因は、ACM証明書を東京リージョンで作ったためです。この場合は endpointType: regional の設定を付ける必要があります。


Events APIの署名検証

Events APIを使う場合、Slackから来たメッセージであることの検証を行う必要があります。その方法は以下にかかれています。

概要を書くと、以下のようになります。


  1. 署名用のシークレットキーを取得(アプリ管理画面の "Basic Information" にある "Signing Secret")

  2. HTTPヘッダ X-Slack-Request-Timestamp からリクエストした時刻を取得し、現時刻と大きくずれてないことを検証(例では5分以内の誤差はOK)。これはリプレイアタックを防止するためです。

  3. 署名のベースとなる文字列を sig_basestring = 'v0:' + timestamp + ':' + request_body のようにタイムスタンプとリクエストボディから作成します。

  4. この文字列とシークレットキーより、 HMAC-SHA256 の値を計算します。

  5. 4で計算した文字列の最初に v0= を付けた文字列が HTTPヘッダ X-Slack-Signature と一致することを確認します。


Serverless Frameworkが要求するメモリ

serverless.ymlのリファレンスによれば、メモリサイズ(memorySize)のデフォルトは1024です。もったいないので変更しましょう。最小の128MBで十分でした。


ロールは一括で定義(個別定義はプラグイン)

Serverless Frameworkでは、IAMのロールは一括で定義します。関数ごとに個別に定義したい場合は、serverless-iam-roles-per-functionというプラグインを使ってください(今回は未使用)


追記

Slackの審査通しました。