個人的に開発しているtbls-ask-agent-slack
という Slackbot があるのだが、先日、退職された P さんという方からこんな質問が来た。
コード内に Signing Secret
を使っている箇所がないが、リクエストの検証は正しく行われているのだろうか?という質問だった。
もう少し噛み砕い説明すると、例えばこのブログに書かれているように、Slack アプリはリクエストの検証のためにSlack Signing Secret
を使うことが推奨されており、これがないと全世界からのリクエストを受け付ける状態になってしまい、あまりよろしくない。
最初にこの質問を受けたときは、なるほど、と思ったが、その後色々調べていくうちに、Socket モードを使っている場合はSlack Signing Secret
が不要であることがわかったのでメモしておく。
Signing Secret の役割
まず最初に Slack Signing Secret の役割について整理しておく。主に前掲のブログの焼き増しになってしまうが、Slack Signing Secret の役割は HTTP リクエストの正当性を検証することである。
Slack からのリクエストには X-Slack-Request-Timestamp
と X-Slack-Signature
というパラメータがヘッダーに含まれており、Slack アプリ側は自身が持っているSigning Secret
を使って、リクエストボディとタイムスタンプを使って生成されるハッシュがX-Slack-Signature
と一致するかどうかを検証することができる。
これの何が嬉しいかというと、同じハッシュキーを共有している Slack からのリクエストに対しては、同じ内容に対して同じキーを使ってハッシュを生成しているので必然的に一致する。一方で、自身が信頼していない送信元からのリクエストに対しては、同じ内容に対してハッシュ化を行っているが、ハッシュキーが異なるため、一致しない。
非常にシンプルな仕組みでありながら、ハッシュキーそのものは通信経路上に流れることはないため、安全にリクエストの検証を行うことができる。
Socket モードとは
そしてここから Socket モードの話に入ってくるのだが、大事な観点として、Slack の Socket モードは HTTP リクエストではないということを覚えておく必要がある。
先日 WebSocket の実装についての gurasan の資料がはてブで流れてきたが、まさにこれが今回の話ドンピシャの内容だった。
Socket モードには HTTP リクエストと異なり、リクエスト・レスポンスという概念がない。つまり、通信の正当性はリクエストに対して行われるのではなく、接続そのものに対して行われることになる。
Socket モードでのリクエスト検証
前掲の資料にもあるとおり、Socketモードにおけるリクエスト検証は最初の接続確立時(ハンドシェイク)に行われる。ここで使われるのが Signing Secret
と対になる概念であるSlack App Token
である。
Slack アプリ側はこのSlack App Token
を初回接続時のBearer Token
に指定することで、Slack 側が正当なアプリであることを検証することができる(ref)。
つまり、HTTP リクエストでは Slack アプリ側がリクエストの検証をしていたのに対して、Socket モードを使ったアプリでは Slack 側がリクエストの検証を行うことで通信の正当性を担保しているということだ。
その後の対応
というわけで、最初にお伝えした通り、私が開発しているtbls-ask-agent-slack は Socket モードを使っているため、Slack Signing Secret
は不要である。
とはいえたしかに何もドキュメントに書いていないのは不親切だなと思ったので、レポジトリの README にその旨を追記しておいた。
https://github.com/kromiii/tbls-ask-agent-slack/pull/79/files
というわけで、Socket モードを使っている場合はSlack Signing Secret
は不要であることを覚えておこう。
気づきを与えてくれた P さんに感謝。