Tor内にあるMastodonインスタンスと投稿が相互に流れるようにする為のメモ

※ この記事はMastodonにTor連合対応が組み入れられていなかった当時のバージョンの解説をしています。2.4.0から公式に対応が入りましたのでそれに関する記事は別途ご参照下さい。[→2.4.0版](https://qiita.com/obsolete-standard/items/97b1274fe0abf147f659)

## 前置き

Mastodonはリクエストを受け付けるだけではなく、相手の情報を得たりこちらの投稿を配信したりするために自分から外へのアクセスが通るようにしなければなりません。しかし制限されたネットワークへの(あるいは、からの)アクセスを捌くためのプロクシを通すようにするための処理がありません。このままではインターネットからTorネットワークへのアクセスはおろか、Tor内同士での連携すらままなりません。

この文章はMastodonをプロクシによるアクセス対応を追加するための簡単なメモに留め、Mastodonインスタンスを1から作る手順は書きません。

また、この文章ではTorへの接続を主眼としていますが、やりたいことはMastodonのHTTPプロクシ対応なので、制限されたネットワークから出るためといった目的には汎用的に使えるものと思います。その場合にはTorとPrivoxyのインストールする節を飛ばしても構いません。

## 必要なもの・インターネット側

* プロクシ対応コード追加
* Tor
* Privoxy
* Mastodon環境変数設定

ここでは既に動いているMastodonと同一のサーバーにTorとPrivoxyを立てるという前提で進めていきます。OSはDebianを想定しますので、インストールなどのコマンド・設定ファイルの置き場所は適宜読み替えて下さい。(※deb系ではインストール後にすぐにサービスが開始するようになっています)

### プロクシ対応コード追加

Mastodonにプロクシ対応を追加するためのコミットを含むブランチを
 https://github.com/hcmiya/v6don/tree/mod-proxy に作りましたので、それを含むリポジトリをfetchした上でmergeします。改変内容については後述。

```shell-session
$ git remote add v6don https://github.com/hcmiya/v6don.git
$ git fetch v6don
$ git merge v6don/mod-proxy
```

gem改変を含むため、masterからマージするとGemfile.lockで多分競合します。本体の動作に影響を与えない大したことないものなので適当にチェックアウトだけして解決してください。mod-proxy自体はタグv2.0.0から分岐しています。2017-12-05現在のバニラなコードではGemfile.lock以外で競合を起こさないはずですが、起こってしまったら後の改変内容の説明を見てなんとかしてください。

```shell-session
$ git checkout --theirs Gemfile.lock
$ git add Gemfile.lock
$ git commit
```

そしてbundle install。

```shell-session
$ bin/bundle install
```

Tor対応HTTPプロクシが既にしてある、またはプロクシ対応コードだけ必要であるという場合は環境変数設定へジャンプ。

### Tor

Debianでは公式リポジトリにパッケージがありますのでそのままAPTでインストールします。

```shell-session
# apt install tor
```

Torネットワークに単にアクセスするだけならばポート開放などネットワークの変更とTorの追加の設定は必要無い事が多いです。標準ではそこにアクセスするためのSOCKS5プロクシが`127.0.0.1:9050`に作られますので`curl(1)`などのHTTPクライアントでアクセスを確認しておきましょう。

```shell-session
$ curl --proxy socks5h://127.0.0.1:9050 https://www.facebookcorewwwi.onion/
```

### Privoxy

Torで作られるプロクシはSOCKS5プロクシです。これをそのまま使うと全てのアクセスがTorネットワークを通過してしまいます。接続先のドメインを見て.onionだけTorを通すようにしたいということをするにはHTTPプロクシを使うのが簡単です。そのプロクシをPrivoxyを使って作ります。

まずはインストール。

```shell-session
$ apt install privoxy
```

普通のアクセスは素通し、.onionはTorへ通すという機能を提供するため設定ファイルに以下の1行を追記します。

```text:/etc/privoxy/config
forward-socks5t .onion 127.0.0.1:9050 .
```

Privoxyを再起動。その後標準では`127.0.0.1:8118`と`[::1]:8118`でアクセスを待ち受けています。


```shell-session
# systemctl restart privoxy
```

インターネットとTorでアクセス先の振り分けが出来ていることを確認します。まずはインターネット側へ。

```shell-session
$ curl -Ss --proxy http://127.0.0.1:8118 https://check.torproject.org/ |sed '/<h1/,/<\/h1>/!d'
```

Tor経由でなければ以下の様に出力されるはずです(2017-12-05現在)。

```text
  <h1 class="off">
    
      Sorry. You are not using Tor.
    
  </h1>
```

同じプロクシでTor内のサイトへもアクセスできていることを確認します。

```shell-session
$ curl --proxy https://127.0.0.1:8118 https://www.facebookcorewwwi.onion/
```

なお、インターネット側のサーバーが外向きのアクセスをすべてTorを通すようにするのは無意味で迷惑なのでやめましょう。

### Mastodon環境変数設定

.env.productionに先程構築したプロクシを使うよう指定します

```text:.env.production
http_proxy=http://127.0.0.1:8118
## ↓Tor対応だけならHTTPSは基本的に無いので不要
# https_proxy=http://127.0.0.1:8118
```

この設定を反映させるためpuma・sidekiq・nodeを再起動したら完了です。

### 確認

投稿欄の上の検索フォームに私のアカウント[@root@nq5jmc5rsyo4fiph.onion](http://nq5jmc5rsyo4fiph.onion/@root)を入れて検索してみましょう。ちょっと時間が掛かりますが木之本桜とDebianの渦巻きアイコンが表示されるはずです。

## 必要なもの・Tor側

※ 2017-12-09にsocksifyを使う方法に書き換えました。

* プロクシ対応コード追加
* Tor
* Dante (socksify)
* Mastodon環境変数設定・起動方法の変更

### プロクシ対応コード追加

インターネット側と同じようにプロクシ対応コードを適用させます。

```shell-session
$ git remote add v6don https://github.com/hcmiya/v6don.git
$ git fetch v6don
$ git merge v6don/mod-proxy
$ bin/bundle install
```

### Tor

インストールしていなければ入れます。必要に応じてHidden Serviceに関する設定をtorrcに追加します(説明略)。

```shell-session
# apt install tor
```

### Dante (socksify)

* 2017-12-11以前の`/etc/dante.conf`にはIPv6通信に関する問題があったので書き直しています。

Tor側から外へ出るアクセスは**例外なくすべて**Torネットワークを経由させるようにします。上記のHTTPプロクシを使う方法を使うと、プロクシ対応部分以外のライブラリで通信が発生した場合に生のアクセスが相手のサーバーに行き匿名性が失われる事になります。それを実現するプログラムとしてDanteのsocksify(1)を使います。

```shell-session
# apt install dante-client
```

プログラムをsocksify経由で起動させることにより、そのプログラムで発せられた通信を全てSOCKSプロクシ経由で行わせるようにすることが出来ます。ただし、データベースやオブジェクトストレージなどへのアクセスはプロクシを通さず直接接続させる必要があります。以下はそれを実現させるための設定の一例です。

```text:/etc/dante.conf
## 名前解決をSOCKSの先でさせるようにする
resolveprotocol: fake

## bindは素通しさせる
route {
    from: 0.0.0.0/0 to: 0.0.0.0/0 via: direct
    command: bind
}

## データベースなど、サービスに必要なローカルネットへのアクセスを
## SOCKS接続から除外させるためのルールを列挙していく。
## ポスグレが192.168.16.2:5432にある時
route {
    from: 0.0.0.0/0 to: 192.168.16.2/32 port = 5432 via: direct
}
## redisが127.0.0.1:6379にある時
route {
    from: 0.0.0.0/0 to: 127.0.0.1/32 port = 6379 via: direct
}
## その他メールサーバーやminioなどがあればそれにも同じように指定していく

## 列挙が面倒臭ければネットワークの範囲を指定して除外させるという設定も可
#route {
#    from: 0.0.0.0/0 to: 127.0.0.0/8 via: direct
#}
#route {
#    from: 0.0.0.0/0 to: 192.168.16.0/24 via: direct
#}

## 残りの外向きの通信はTorに投げる
route {
    from: 0.0.0.0/0 to: 0.0.0.0/0 via: 127.0.0.1 port = 9050
    proxyprotocol: socks_v5
    method: none
}
```

以上の設定でプログラムをsocksifyで起動した時に外部への接続にTorが使われていること、ローカルのデータベース等への接続が出来ていることを確認します。

```shell-session
$ socksify curl -sS https://check.torproject.org/ |sed '/<h1/,/<\/h1>/!d'
$ socksify psql -U mastodon -h 192.168.16.2 mastodon_production
$ socksify redis-cli keys \*
```

* なお、socksifyは今の処IPv6のプロクシに対応していません。`resolveprotocol`が`fake`だと接続が出来なくなり、それ以外だとプロクシは使われずスルーされます(!)。他のローカルのサービスとの通信にIPv6を使っていた場合は諦めてIPv4を使うようにネットワークを変更するか、Mastodonサービスをしているホストと同居させるようにしてUNIXドメインソケットを使うようにする必要が出てきます。IPv6使いたいのにとてもつらい……

### Mastodon環境変数設定・起動方法の変更

Tor上ではHTTPS通信をさせないようにします。

```text:.env.production
LOCAL_HTTPS=false
```

サービスを起動させているコマンドの前にsocksifyを追加します。例えばpumaをsystemdのユニットで起動させている場合、ExecStartは以下のようになるでしょう。

```text:mastodon-web.service
ExecStart=/usr/bin/socksify /home/mastodon/live/bin/bundle exec puma -C config/puma.rb -b tcp://127.0.0.1:3000
```

同じ改変をSidekiqとストリーミングサービスに対して行います。

* socksify(1)は`LD_PRELOAD`をexportしたあとでexecをする簡単なシェルスクリプトなので、起動スクリプトの中で`LD_PRELOAD`を設定すればsocksifyは不要になります。

各種サービスを再起動させたあと、他のTor上インスタンス、またはTor接続に対応したインターネット上のユーザーを検索できること、そのユーザーをフォローしたときにフォローボタンが砂時計にならないことを確認しましょう。[IPv6ドン](https://mastodon.home.js4.in/)はTor接続に対応しているインスタンスです。

### 注意

SOCKSに通信を全部投げているためHTTPプロクシの使用はありませんが、ソースの改変は必要です。ユーザー情報を取得する処理にHTTPS通信を必須としている箇所があり、Tor上のインスタンスに対してはTLSの無いHTTP通信を投げるという処理が必要になるからです。

## 改変内容について

### http(s)_proxy 読み取り

デーモンの起動時に`config/initializers/http_proxy.rb`で環境変数からプロクシ設定を読み取り、HTTPリクエストライブラリにオプションとして渡せるようにしています。

### Request

Mastodon自身が処理を書いている部分に於いては、インスタンスから出ていくリクエストは`app/lib/request.rb`のRequestクラスが司っており、さらにその内部ではgemの[http.rb](https://github.com/httprb/http)を使っています。Requestをnewしたときにオプションをデーモン初期化時に読み取ったプロクシ設定と常にマージするようにしています。

### Goldfinger

[Goldfinger](https://github.com/tootsuite/goldfinger)は[Webfinger](https://webfinger.net/)を処理するためのgemです。内部ではMastodonと同じくhttp.rbを使っています。

v2.0.1の時点では、Goldfinger.fingerの引数がWebfinger URIしかなく、プロクシ対応などに必要なhttp.rbに渡されるオプションを指定することが出来ません。

また[RFC 7033](https://tools.ietf.org/html/rfc7033)の規定でWebfingerのリクエストはHTTPSでしなければならない(MUST)とあるためv2.0.0からHTTPリクエストは出来ないようになったのですが、Tor内のような普通はHTTPS通信が使われないところではそれでは困ってしまいます。

その問題を解決するため[Goldfingerをfork](https://github.com/hcmiya/goldfinger/tree/mod-opts)し、上記2つの使用有無を指定するためのI/Fを追加しました。そしてGemfileでGoldfingerをforkしたリポジトリとそのブランチを指すようにしています。

Goldfingerを使うときはいつでもプロクシを使うためのオプションを渡すことと、.onionだったときにTLSを使うかの判別をしないといけないので、それを直接使っていたところではMastodon::goldfingerというラッパー用モジュールを作ってそれを使うように置き換えています。

### twitter-text

ドメイン名に使えるTLDに.onionはあっても.i2pが無かったのでそれを追加しています。ただ、追加する方法はそれでよかったのか感はある。

----
これらプロクシ対応を本体に組み込んでほしいというのはあってプルリクを出すつもりはあるのですが、その前段階としてGoldfingerに出したプルリクが執筆時点で受理されていないので、本体の方にまだ出せないでいます。

## Tor内サービスで更に考慮すべきもの

### メールのTor対応

2017-12-05現在のところ、Mastodonのアカウント作成にはメールアドレスが必須ですが、Tor上のアドレスにメール送信できるようにするにはメールデーモンのTor対応が必要です。これはMastodon本体の設定の範囲外になるので解説しません。

また、私が構築したTor上のインスタンス「[Mastodon (Darknet)](http://nq5jmc5rsyo4fiph.onion/)」はメールをTor外から送信するので、匿名性はその程度になります。このインスタンスを立てた目的が表題の通り「Tor内にあるMastodonインスタンスと投稿が相互に流れるようにする為」の実験なので匿名性を重視していないからです。

### User Agentの秘匿

Mastodonはインスタンス間の通信だけではなくoEmbedの取得のために任意のサイトに対してリクエストを送ります。その際のUser AgentにはインスタンスのURLが含まれているため、インスタンス内で話題にされていることが容易に知られてしまいます。

### リファラの秘匿

リファラを制御するためのヘッダやmeta要素を入れたほうが良いのですがまだ入れていません。

## 宣伝

というわけで、Tor上にあるインスタンスと繋がれるのが[IPv6ドン](https://mastodon.home.js4.in/)、実際にTor上にあるのが[Mastodon (Tor)](http://nq5jmc5rsyo4fiph.onion/)です。Torインスタンスを作ったらこちらのインスタンスのユーザーをフォローできるか試してみてください。私のそれぞれのアカウントは[@hcm (v6)](https://mastodon.home.js4.in/@hcm)、[@hcm (Tor)](http://nq5jmc5rsyo4fiph.onion/@hcm)です。