この記事はTSG Advent Calendar 2022のN日目の記事です。N=17になりました。
adventar.org
ブログの書き方を忘れかけています。思い出すためにがんばります。
Slack appでclassic Slack appからnew Slack appに移行した話です。大分前なこともあり、できるだけ根拠へのリンクを含めたとはいえ嘘とかも含まれると思います。間違いがあったらちょっと申し訳ない気持ちにはなりますが、責任は負いません。
SlackのClassic AppをNewer App with granular scopesに更新したので飯がうまい
— こおしいず(っ'ヮ'c) (@kcz146) August 30, 2022
前置き
以前にも書いたとおり、TSGのSlackではbotが動いています。sushibotを含め、たくさんのbotが動いています。
cookies.hatenablog.jp
「たくさんのbot」が動いているとはいっても、無料Slackの制限などから、モノレポ、あるいはモノbotのような運用をしていて、Slackのトークンや設定は基本共通です。今年の前半に、これのSlack appの移行を行ったので、それについて書いておこうと思います。たぶんもう十分に、期限切れの(あんまり誰の役にも立たない)ネタなんですけど。
github.com
Slackというやつは、なかなかプログラマたちの間で流行っている印象で、そのときに必須なのがAPIの提供です*1。SlackのAPIは日進月歩しており、その中ではしょっちゅう非推奨化や削除が行われます。覚えているものとして次のようなものがあります
- Legacy Tokenの新規作成停止、inactiveなものの随時削除 https://api.slack.com/legacy/custom-integrations/legacy-tokens
- Legacy Custom Integrationとかいうやつ
- のなかの、Botsという存在 https://api.slack.com/legacy/custom-integrations/bot-users
- とか、Slack appに紐付かないIncoming Webhooks*2。 https://api.slack.com/legacy/custom-integrations/messaging/webhooks
- channel/imなどのconversationへの統合 https://api.slack.com/changelog/2020-01-deprecating-antecedents-to-the-conversations-api
- rtm.startがなんか止まる https://api.slack.com/changelog/2021-10-rtm-start-to-stop
- node-slack-sdkもどんどん止まりたがってるような気がする https://github.com/slackapi/node-slack-sdk/tree/%40slack/web-api%406.8.0#getting-started
なかなかですね。動いているものが非推奨化されたり削除されたりすると少し悲しい気持ちになりますが、今回取り扱うのは、new Slack appという新しいバージョンのSlack appへの移行です。これは前向きになれる変更もまあまあ含まれています。
new Slack app vs classic Slack app
- Quickstart: differences between old and new Slack apps | Slack
- Migration guide for classic Slack apps | Slack
ここから最後までに書くことはだいたい全部上の2つのリンクのどちらかに書いてあります*3。
この種類の移行では、移行前のSlack appは「classic Slack app」と呼ばれます。移行後のSlack appは「new Slack app」と呼びそうです。
classic Slack appは、以下のような特徴を持ちます。
- "bot" scopeを持つ「Legacy bot token」を発行できる
- RTM (Real Time Messaging) APIが使用できる
- 普通の導線ではなかなかたどり着けない、 https://api.slack.com/apps?new_classic_app=1 というリンクから新規作成できる *4
new Slack appは、以下のような特徴を持ちます。
- OAuthのendpointにv2がつく https://api.slack.com/methods/oauth.v2.access
- OAuthのscopeがclassic Slack appに比べてずっと細かく、そしてうまく整理されている。特にbot tokenとuser tokenの間の直交性が高い気がする。
- Socket Modeというモードをappに対して有効化することができる。
- App Manifestが利用できる*5。
嬉しいポイントは2つ目のところとかで、bot scopeとかいう怪しい存在がなくなり、だいぶbotのアイデンティティが確立されます。bot tokenを利用したかuser tokenを利用したかによってでのみ大抵の挙動は変化し、そしてbot tokenを使用したときのbotはイチuserと比較的だいぶ同等な存在として扱えて、たとえばconversations.setTopicを呼ぶことができたり、conversations.joinができるようになったりします。setTopicは等しく誰でもできるべきだろ、botは招待されないと話に参加できないような低位の存在じゃない、自分から会話に参加できる権利を*6!!
移行の概略
RTMの引き剥がし
まず一番大きな変更点は、Real Time Messaging APIの停止です。これをどうにかしないといけません。今までは根幹を成していたものですから、おそらくあなたのbotもRTMに依存しているはずです*7。
RTMの代替は、基本的にはEvents APIです。ただし、アーキテクチャは異なります。
RTMはslackbot側からSlackサーバに対して認証にSlack tokenを使ったWebSocketコネクションを張り、そこにSlackが随時Eventを流してくれるものでした。
一方、Events APIは、あなたがpublic reachableなHTTP endpointを用意し、それをSlack bot設定画面で登録しておくと、そこに対して1 eventごとに1 HTTP requestが*8飛んでくる、という代物です。
注意すべき相違点や考慮点は次の章に書きます。
ただし、このアーキテクチャの違いを受け容れられない人のために、Slackは、Socket Modeという、RTMと似た仕組みも用意しています。つまり、slackbot側からサーバ側にWebSocketを張るというものです。これについても次の章に続きます。
botの権限(OAuth scope)の見直し
このnew Slack appへの移行のうれしさに、Slack appの権限やアイデンティティの綺麗さをあげました。逆に言えば、これはclassic Slack appからの変更を含みます。
一番問題が大きいのは、"bot scope"という代物です。この存在はなくなり、より細かな権限(granular permission)に置き換わっています。また、bot tokenにどの権限を与えるか、user tokenにどの権限を与えるか、という設定画面が現れることになります。
理想的には、現在のslackbotが利用している全てのSlack API methodを洗い出し、それを整理することになります。まあ、そんなことしなくても、雰囲気で乗り越えることは十分出来ます。
非互換な変更、APIの変更の対処
いくつか、廃止された機能や、変更されるAPIが存在するので、これを利用していたのであればプログラムを直す必要があります。
これ以外にもあるかもしれません、
- RTMの時は、特定のpayloadをWebSocketに流すことによって、普通のuserのように、botが、"xxx is typing..."というindicatorを送ることができました。RTMの廃止後は、typing indicatorを送ることはできません*9。
- chat.postMessageにおけるas_userパラメータは、new Slack appではinvalidなパラメータであるため、含めるべきではありません*10。ただし、少し注意点があるため、次の章に続きます。
- OAuthのendpoint URLにv2が含まれるようになります。authorizeのURLはoauth/v2/authorizeみたいになり、exchangeのAPIはoauth.v2.accessみたいになります。
移行に際する注意点
注意点も、実はだいたいマイグレーションガイドに書いてあるのですけども、書いておきます。
RTMがEvents APIに切り替わるとは、どういうことか
Events APIというのが出来て、RTMを代替し、なるほどコネクション確立のイニシエータが変わるのか、という理解は正しいのですが、それに伴ういくつかの注意事項があります。
登録可能なEvent受信URLはひとつ
RTMのときは、同じtokenで、複数のWebSocket通信を確立し、すべてのWebSocketにEventを流すことができました。通信を開始する場所は全くかけ離れた別のサーバでも良かったわけです。
Events APIの場合、Slack appに対してひとつだけURLを登録できる、という形になるので、Eventを流せる先はひとつに制約されます。しかも、public reachableなURLが必要となるので、レンタルサーバ借りるでもglobal IPを用意してサーバ建てるでもどっかのFaaSを使うでもなんでもいいですけど、気軽さはだいぶ失われます。ngrok、まぁそうだね。じゃあそれで。
複数workspaceの振り分け
RTMを確立するための種は、botのもつtokenでした。tokenとは、各workspaceに紐付きます。workspaceごとに、そのworkspaceに対してOAuthのscopeを許可された結果生成されるのが、tokenです。*11
Events APIのEvent受信URLはSlack appに対して定まるものです。tokenはworkspaceに対するものでしたが、これはappに対して定まるものです。その結果、RTMでは別コネクションに来ていた別workspaceのEventが、すべて同じEvent受信URLに混ざって流れてくることになります。
Events APIの全てのEventデータには、team_idが必ず入ってくるはずなので、それを見て適当に振り分けるとかしてください。参考JSON
もちろん、知ってりゃ対処するだけなんですが、微妙に嵌まる。
ひとつのworkspaceだけで動いているappなら関係ないです。
なるほどね、でもSocket Modeっていうのがあるんでしょ
それでよくない?ってかもはやRTMとおんなじじゃん? ーー うーん、まぁ?
まずworkspace単位ではなくてappに紐付くからちょっと変わるというのはそうです。それ以外にもちょっと注意点があります。
Socket Modeはゼロかイチか。
Socket Modeというのは、Slack appに対して有効化することができる機能です。
そして、これを有効化した瞬間に、HTTP URLに届く方のEvents APIは止まります。Socket Mode専用appになります。またSocket Mode設定を無効化すると、戻ってHTTPの方だけ届くようになります。
App Directoryに乗らなくなる
実は「App Directoryに乗らなくなる」の意味を正確には調べていません。結局Socket Modeは使わなかったので。
Slack的には、Socket Modeは開発用あるいは会社の内部用(ポートを開けられないとか、ね)というつもりらしく、Slack App Directoryに載せるSlack appではSocket Modeの利用は許可されていません。
ただし、App Directoryに乗らなくなって困る人はいまこの記事を読んでいないはずだと思います。classic Slack appは今年2022年の5月にApp Directoryに載らなくなったようです。
new Slack appのみで利用可能
classic Slack appでは、Socket Modeは使えないです。RTM使えるからいいじゃん、といえばそうなんだけど、別に一緒ではないし、、
HTTPに流れてくる方のEvents APIは、基本的にclassic Slack appでも使えます。だからRTMと同時に使える。移行をちょっとずつできる。
Socket Modeは移行に関する全てを解決するのでしょうか。どうでしょうね。
OAuth無印とv2
この移行に際し、OAuthのscopeが洗練化され、そのためにOAuth endpointの"バージョン"がv2になりました。
user token / bot tokenも、無印時代のtokenと、v2時代のtokenというものが存在することになります。どうなるの。
Slack appの設定画面から移行ボタンを押すと、新scopeの設定画面が現れ、移行後のclassic Slack appではどのscopeを要求するかを選択したあと、「移行実行ボタン」みたいなのを押すことになります。押した瞬間にそのSlack appは、晴れてclassic Slack appからnew Slack appへと進化するわけです。
- この瞬間、OAuth無印のtokenの新規発行はできなくなります。oauth.access methodは失敗するようになります。oauth.v2.access methodを使わなくてはなりません。OAuthの同意画面も(granular scopeと一緒の)v2用のものに差し替えなければなりません。
- ただし、無印のときに発行されていたtokenは生き続け、使用可能状態が続きます。仮に誰かがOAuth v2 endpointでtokenを発行しても、無印tokenは生き続けます。また、それは無印のときに許可された権限や仕様で生き続けています。たとえば、chat.postMessageのas_userパラメータは使用可能です。移行が一段落し、無印時代のことを完全に忘れたいのであれば、無印時代のtokenを早めにrevokeすることを検討すべきです。
結果最後の移行手順
- 一応別appで簡単なテストとかはしたけど、基本的にstaging appみたいなのは作らなかった。
- Socket Modeは利用しなかった
- RTM使用箇所をちびちびEvents APIを使用するように置換した
- 複数workspaceのEventが混ざってくるのにビビりながら、直した
- RTM使用箇所を完全にゼロにした
- granular scopesをなんとなく決めて、うおりゃ、ってnew Slack appに移行した
- OAuth v2のtokenを発行し、使うようにした
- oauth.v2を使うようにコード変更をした
- as_userパラメータを消そうとしたけど、生存中の無印tokenが微妙に残ってるので、なんかこう、hack codeが入ってる、いつか消したい
おわりです。おつかれさまでした。
*2:あなたの使ってるそれ、そのIncoming Webhooksも実は非推奨化済みかもしれません。まあ、当分削除はされないんじゃないかな、なんて思っているけれど。
*3:こういうの移行を終えてから気づくんだよね。「苦労したけど、実は全部書いてあるじゃん。。」
*4:https://api.slack.com/authentication/migration#classic にボタンがある
*5:今現在、TSGではあんまりまともに使ってません。よってこの記事では扱いません。
*6:たぶんclassic Slack appではできなかったはず、くらいの記憶で書いています。もし誤情報であればコメントください
*7:TwitterのUserStreamを思い出しますね~~。どうしてなくなっちゃったんだ。Twitter API v1.1もなくならないでほしいな~~~
*8:リトライ時はこの限りではありません
*9:もちろん、今もuserのtyping indicatorが残っていることを考えれば、undocumented APIやprivileged tokenでは出来る可能性はありますけども、ねぇ。
*10:場合によっては、as_user:falseをつけることで呼び出しに失敗するようになる気がします。このへんほんと複雑でもう検証したくない。
*11:この文は曖昧あるいは不正確です。new Slack appのbot tokenについてはおそらく正しいと思います。user tokenについては、「誰の」tokenかという情報もあります。classic Slack appのbot tokenは、ウーン、どうなってるのかなぁ、許可レコードに対するscopeのOR取ってるのか?よくわかんないです。 https://api.slack.com/authentication/quickstart#deactivation