カラクリスタ

「輝かしい青春」なんて失かった人のブログ

GoToSocial を Misskey と問題なく連合が組めるように改造した

私は去年の 11月頃から SNS の主軸を Fediverse に移し、自分のインスタンスを立てるために、

GoToSocial

を利用をしていて、今も GoToSocial を使っています。

それでその時に困っていたのが、

Misskeyインスタンスと連合が上手く組めない時がある

と言う事案で、Misskey 系インスタンスの最大手である Misskey.io と連合が組めないとかそう言う問題がありました。

またこの問題は Misskey インスタンスの設定に起因する問題で、Misskey インタンスの設定において、

signToActivityPubGet: false

を設定すれば解決できる話ではあったのですが、通信できないインスタンスがある度に設定を修正してくれ! と言うのも現実的はない ため、

それなら GoToSocial の方を改造してなんとかするか!

と言う感じでやっていったところ無事出来たので、今回はその辺りの報告とどう問題を解消したか、 の話を書きたいと思います。

追記: 2023-09-09

現在使っている gotosocial の fork ではこの記事の内容と差異があります。

まず大きな違いは internal/middleware/signaturecheck.go へ加えていた変更を revert して点です。 これは実際にはこのファイルを変更する必要が無かったためで、それに気がついた時点で変更を元のコードに戻しました。

また設定項目を名称を KalaclistaUnathorizedGet とリネームしていて、 これは自分が加えた変更とオリジナルの実装の差異を区別するために行ないました。

なお私の GoToSocial の fork の最新版は下記から参照できるので、よろしければそちらを参照して頂けると助かります:

https://github.com/nyarla/gotosocial-modified/tree/kalaclistaGitHub - nyarla/gotosocial-modified: Personal fork for https://kalaclista.com

そもそもなんで GoToSocial と Misskey で問題が出ていたのか

これは GoToSocial の設計思想に起因する話だと思うのですが、 まず GoToSocial は Mastodon との API 互換が優先して実装が進められているプロジェクトです。

また GoToSocial は現状では headless fediverse software と言える状態のプロジェクトで、 特に投稿 UI がある訳でもなく、Mastodon 互換の API 経由で投稿をやってくれ、と言う感じになっています。

その上で GoToSocial ではプライバシー面での機能向上のため、Server-to-Server 、要は ActivityPub 通信をする際に、 Mastodon で言うところの、

Authorized Fetch (Secure Mode)

と言うリクエスト仕様を要求する様になっていました。

それでこの仕様は何かと言うと、実装面で言うなら GET リクエスト時でも HTTP Signature を要求 し、 かいつまんで言うと GET リクエストを飛ばす時でも誰がリクエストしたか署名付けてね! と言う仕掛けになっています。

またこの仕様のオリジナルは Mastodon に存在するのですが、これもドキュメントにも書かれている様に、 この設定が有効だと一部の Fediverse Software に対して相互互換性の問題が生じます。

そのためこの仕様に準拠する GoToSocial もまた Authorized Fetch (Secure Mode) に起因する相互互換性の問題が発生し、 これが Misskey.io などの Misskey 系インスタンスと連合を組む上での障害になっていました。

その上で GoToSocial はプライバシーを重視するために敢えてこの挙動を示すことを選択していたため、 ここへの変更をGoToSocialの開発者に求めるのは無理筋、と言う事が今まで流れでした。

だったらじゃあ改造しよう!

となったので、今回ここを何とかするために私は実際にその辺りに手を加え、 本当に Misskey(特に Misskey.io)と連合が組める様になっため、その辺りをどう改変して行ったかに触れたいと思います。

まず前提として 私が手を加えた差分を参考に話を進めて行きます。

私が今回手を加えた流れとしては、

  • internal/config 以下へ InstanceAllowUnauthorizedFetch を加えた
  • internal/middleware/signaturecheck.go で特定条件下で処理を飛ばす様にした
  • 各種コントローラーで HTTP Signature が無かった場合に処理を対応させた

と言う感じになっていて、実際の処理の中身については上記のリンクの差分を見た方が早いです。

それで GoToSocial の設定ファイルの機構はわりと面白いことになっていて、GoToSocial では次の方法で GoToSocial の挙動を決定することが出来ます:

  • GTS_ で始まる環境変数
  • gotosocial server への引数
  • config.yaml などの設定ファイル

また GoToSocial へ新たに設定を加える際には、

  • internal/config/config.go
  • internal/config/defaults.go

にそれっぽく値と初期値を設定して、

go run ./internal/config/gen/ -out ./internal/config/helpers.gen.go

を実行すればヘルパー関数が生えてくるので、その点では楽でした。

次に GoToSocial では HTTP Signature が常に有効、と言う前提の機構で各処理が実装されていたため、 これを外すために、HTTP Signature が無くても GET 時であれば 401 Unauthorized を返さない、と言う変更を加えました。 なおその際の差分は、ここでも掲載すると以下の様な感じです:

diff --git a/internal/middleware/signaturecheck.go b/internal/middleware/signaturecheck.go
index 87c7aac01..f29f610ad 100644
--- a/internal/middleware/signaturecheck.go
+++ b/internal/middleware/signaturecheck.go
@@ -22,6 +22,7 @@ import (
    "net/http"
    "net/url"

+   "github.com/superseriousbusiness/gotosocial/internal/config"
    "github.com/superseriousbusiness/gotosocial/internal/gtscontext"
    "github.com/superseriousbusiness/gotosocial/internal/log"

@@ -54,6 +55,14 @@ func SignatureCheck(uriBlocked func(context.Context, *url.URL) (bool, error)) fu
        // Create the signature verifier from the request;
        // this will error if the request wasn't signed.
        verifier, err := httpsig.NewVerifier(c.Request)
+
+       // INSECURE MODIFIED by @nyarla@kalaclista.com
+       // If instance allowed unauthoeized fetch by configuration,
+       // this middleware skip verification HTTP Sigunature on GET requests.
+       if config.GetInstanceAllowUnauthorizedFetch() && verifier == nil && c.Request.Method == http.MethodGet {
+           return
+       }
+
        if err != nil {
            // Only actually *abort* the request with 401
            // if a signature was present but malformed.

最後にこれが一番手間取った箇所だったのですが、GoToSocial HTTP Signature を必要とする場面で、

  • internal/federationFederator#AuthenticateFederatedRequest
  • internal/processing/fediProcessor#authenticate

を呼んでいます。

まず前者の Federator#AuthenticateFederatedRequestHTTP Signature を引く関数で、 後者の Processor#authenticateFederator#AuthenticateFederatedRequest を元に認証する関数です。

次に GET 時の HTTP Signature 認証を任意にする と言う変更では Federator#AuthenticateFederatedRequest が何も返さないと言う事を意味するため、 この点をコントローラーで対応する必要がありました。

またこの辺りの GET 用コントローラーは、

  • internal/processing/fedi パッケージ

に集結していたので、このパッケージ内の GET と言う名が付く関数に変更を加えて行くことになりました。

そして最後に全体の修正が終わった後、

VERSION=kalaclista-600954e ./scripts/build.sh

を走らせれば、無事に修正が施された GoToSocial を得ることが出来ます。

それで結果はどうだった?

最後にこう言う感じで、特に連合が組めなくて困っていた Misskey.io と下記の様に連合が組めるようになりました!

Misskey.io と GoToSocial が連合できた様子

……まぁ実際には手元で成功したけど、デプロイした時に若干の不具合とかがあって一番最初はエラーが出ていた、などありましたが、 今では無事に Misskey.io と自分の GoToSocial のインスタンス間で連合が組めています!やったぜ!

以上

なんかこうやって記事にして行ったら結構長くなってしまいましたが、 話としてはそう言う感じで連合が組めなくて一番困っていた Misskey.io と自前のお一人様インスタンスと連合が組めたので、 これが実現して本当よかった、と感じています。

ちなみに実装を修正していく上で実際に作業を進めるために使った実験環境は、

Tailscale ネットワーク内に Fediverse を構築する

をそのまま使ってます。と言うか今回のためにこの環境を構築してたんですけどね。

ちなみにここまで苦労してなぜ Misskey.io と自分の GoToSocial の連合が組みたかったか、と言うと、 これは我が家の猫の写真を、

と言う流れで流し込みたかったからです。まぁ他にも Misskey 系インタンスのアカウントを follow したかった、 と言うもあったりしますが。

まあそう言う流れで今回は GoToSocialを改造して Authorized Fetch を無効化すると言う諸行をやりましたが、 他にも Frontend の HTML を改善したい!とか Admin の UI を変えたい!とか色々と改造したい部分はあったりするので、 今後はそちらの方にも手を付けらたら良いなーと思っています。

あとはまぁこのブログで ActivityPub を吐ける様にする、と言う目標もあったりしますが、それは追々とやって行こうと思っとります。はい。