🧅

Next.js ウェブサイトを Docker を使って Tor に公開してみる

2023/12/07に公開

この記事は Docker/コンテナ仮想環境 Advent Calendar 2023 7日目の記事です。ですが、そんなに Docker に詳しいわけじゃないので何か間違っていることなどあれば教えてください。


突然 Web サイトを Tor で公開したくなることってあると思います。

でも、日常的に作っている Web サイトは Vercel だったりにデプロイするのが一般的で、多くの Tor 導入記事にあるような Apache とか Nginx とかの Web サーバーを使うことはあんまり少なくなっていると思います。

そこで今回は Next.js で作った Web サイトを Docker を使って少しモダンな感じで Tor に公開してみます。ここでは Next.js を使っていますが、Remix や Vite など別のフレームワークでも大体同じようにできるはずです。

成果物

Web サイト: (家の PC でホストしているので気分で落ちているかも)

http://nextjs3vm7rw667eqzgfi66c6k7rfc5q5l4yi2qakvcm76wtf3u7nzad.onion

レポ:

https://github.com/p1atdev/next-onion

今回の環境

  • Node.js v18.18.0
    • pnpm 8.11.0 (npm や yarn でも可能ですが読み替える必要があります)
  • Docker 24.0.6
  • Tor Browser 13.0.5
  • Windows 11

Next.js セットアップ

ここは公式解説ほぼそのままですが、pnpm を使います。

pnpm dlx create-next-app@latest

聞かれる質問は好きなものを答えていいですが、 src ディレクトリは使うようにするとこの後の説明と合うので作業が楽です。

今回はこのプロジェクトのルートディレクトリで作業していく形になります。

Tor セットアップ

Tor に関するものをセットアップしていきます。

Tor Browser インストール

もしまだ Tor ブラウザをインストールしていない場合、.onion ドメインの Web サイトを見れないのでインストールする必要があります。インストールしていたら飛ばしてください。

公式サイト からダウンロードしてもいいですが、wingetbrew からも入れることができます。

brew (macOS)
brew install --cask tor-browser
winget (Windows)
winget install TorProject.TorBrowser

これでインストールができました。

Tor Browser の起動画面

ウィンドウサイズに関する余談

少し使っていると、ウィンドウサイズによっては画面の左右や下に謎の余白が発生することに気づくかもしれません。

Tor Browser で DuckDuckGo を開いた時のスクリーンショット

これはレターボックスと呼ばれ、多くのユーザーのウィンドウサイズを統一することで、ウィンドウサイズによる個人の特定を難しくするために導入されています。[1]


.onion ドメインを取る

Tor では、通常の Web サイトとは異なる .onion というドメインを使います。これは、Tor のネットワーク内でのみ有効なドメインで、Tor 以外のブラウザではアクセスすることができません。

例えば DuckDuckGo の .onion ドメインは duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion です。

このようなクソ長いドメインは V3 Onion Service のドメインで、この 56 文字のドメイン自体に Ed25519 公開鍵 とバージョン番号 (3)、チェックサムが含まれています。[2]V2 は RSA 公開鍵のハッシュが利用されていましたが、現在は非推奨になっており、最新の Tor Browser では V2 のドメインにはアクセスできません。[3]

めちゃくちゃ長いドメインなので、判別しやすくするために先頭をわかりやすくしたドメインがよく使われます。

DuckDuckGo では duckduckgogg42x... となっているのがわかります。他にも、

  • ProtonMail: protonmailrmez3lotccipshtkleegetolb73fuirgj7r4o4vfu7ozyd.onion
  • Twitter: twitter3e4tixl4xyajtrzo62zg5vztmjuricljdp2c5kshju4avyoid.onion

などがあります。[4]このように、判別しやすくしたアドレスを Vanity Address と呼びます。[5]

このようなドメインですが、先程も言ったように公開鍵なので、すべての文字を任意に決めることはできません。先頭の数文字が欲しい文字列に一致するまで総当たりで探していくことになります。

今回は V3 対応の Vanity Address を作るためのツールである mkp224o を使って独自の .onion ドメインを取っていきます。

https://github.com/cathugger/mkp224o

Releases から使っている OS に合わせてダウンロードします。今回は Windows なので mkp224o-1.6.1-w64.zip をダウンロードしました。

解凍すると次のようになっていると思います。

mkp224o の解凍後フォルダ

mkp224o.exe を使って取りたいドメインを総当りで探していきます。

例えば先頭が zenn から始まって欲しい場合は次のようになります。

mkp2240解凍したディレクトリ
mkp224o.exe -d domains -n 5 zenn
引数の詳しい説明
  • -d: 見つけたドメインの鍵を出力するディレクトリ。指定しないと実行フォルダ直下になります。
  • -n: ドメインをいくつまで探すかどうか。指定しないと無限に探すことになります。

mkp2240 を実行した様子

今回のように 4 文字だけであれば結構速く見つかります。6文字以上になると少し時間がかかるようになってきます。

処理中は CPU をフルで使うので他の作業がほとんどできなくなることに注意が必要です。(RunCat の猫がチカチカするレベル)

ここで見つかったドメインの鍵などは domains ディレクトリに保存され、後の作業で使うことになります。

.onion ドメインの余談

.onion ドメインの特別さは RFC 7686 で定義されています。(翻訳)

.onion ドメインには例えば以下の特性があります

  • レジストラは .onion ドメインを登録してはならない
  • 権威 DNS サーバーは .onion ドメインの解決を求められたら NXDOMAIN を返さなければならない

Tor の設定

先程作成したプロジェクトフォルダのルートに tor ディレクトリを作成して、 Tor に関する設定ファイルなどを置くことにします。

プロジェクトのディレクトリに tor を追加した様子

まず、Tor の設定を記述する torrctor フォルダに作成します。

/tor/torrc
HiddenServiceDir /app/tor/hidden_service/
HiddenServicePort 80 127.0.0.1:3000

それぞれ解説すると、HiddenServiceDir は Tor が生成した秘密鍵や .onion ドメインを保存するディレクトリを指定します。(このディレクトリはパーミッションの設定が厳しいです)。 HiddenServicePort は公開するポートと、そのポートに対してどのようなアクセスをするかを指定します。ここでは 80 ポートへのアクセスを Next.js のデフォルトのポートである 3000 ポートに転送するようにしています。ここの 80 は、Docker の設定に合わせて変更することができます。

次に、先程作成したドメインを使うための設定をします。作成したドメインのフォルダの中には次のファイルが入っていると思います。

Onion ドメインのファイル

これらのファイルを /tor/hidden_service にコピーします。

Onion ドメインのファイルを移した様子

これで Tor の設定は完了です。

Docker セットアップ

Dockerfile

Dockerfile を書いていきます。

Dockerfile
FROM node:20.4.0-bookworm-slim AS base

ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable

WORKDIR /app

# Setting up Tor
RUN apt update
RUN apt install -y tor
COPY ./tor/torrc /etc/tor/torrc
COPY ./tor/hidden_service /app/tor/hidden_service

# Installing pnpm
FROM base AS deps
COPY package.json pnpm-lock.yaml* ./

RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile

# Building Next.js
FROM deps AS build
COPY . /app

RUN pnpm build

FROM deps AS prod
COPY --from=build /app/node_modules /app/node_modules
COPY --from=build /app/public /app/public
COPY --from=build /app/.next /app/.next
COPY --from=build /app/next.config.js /app

RUN chmod 700 /app/tor/hidden_service

CMD tor & pnpm start

上から順に解説していきます。

FROM node:20.4.0-bookworm-slim AS base

軽量な Node.js の slim イメージを使います。[6]

ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable

pnpm を使えるようにします。もし yarn を使う場合は変更が必要になりそうですが、調べてないので具体的な変更はわかりません。

WORKDIR /app

作業ディレクトリを /app にします。

# Setting up Tor
RUN apt update
RUN apt install -y tor
COPY ./tor/torrc /etc/tor/torrc
COPY ./tor/hidden_service /app/tor/hidden_service

ここで先程作成した Tor の設定ファイルやドメインのファイルをコピーして Tor をセットアップしています。

# Installing pnpm
FROM base AS deps
COPY package.json pnpm-lock.yaml* ./

RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile

pnpm で依存関係をインストールしています。

# Building Next.js
FROM deps AS build
COPY . /app

RUN pnpm build

Next.js のビルドをしています。

FROM deps AS prod
COPY --from=build /app/node_modules /app/node_modules
COPY --from=build /app/public /app/public
COPY --from=build /app/.next /app/.next
COPY --from=build /app/next.config.js /app

RUN chmod 700 /app/tor/hidden_service

CMD tor & pnpm start

ビルドしたものや依存関係をコピーします。また、/app/tor/hidden_service フォルダのパーミッションを変更しています。権限ゆるゆるだと怒られて起動できません。

最後に torpnpm start で Tor と Next.js を起動します。

docker-compose.yaml

docker-compose.yaml も書いていきます。

docker-compose.yml
services:
  app:
    build:
      context: .
      target: prod
    ports:
      - "3000:3000" # 一般ネットワークでの確認用
      - "8888:80"
    environment:
      - NODE_ENV=production

Dockerfile で定義した prod ターゲットを使ってビルドします。

また、3000:3000 で Next.js のポートをローカルホストに公開している他、8888:80 で Tor のポートもローカルホストに公開しています。この 80torrc で指定したポートと同じにする必要がありますが、 8888 は適当でいいと思います。(だめだったら教えてください)

Tor に公開!

さて、最低限のファイルは作成できたので Docker を起動して Tor に最初の Web サイトを公開してみましょう。

プロジェクトルート
docker compose up --build

[notice] Bootstrapped 100% というログが出たら Tor の接続完了です。Tor Browser で先程作成したドメインにアクセスしてみましょう!

Next.js on Tor Browser

ちゃんと Next.js のテンプレページが表示されました!

ホットリロード対応する

これだけでも十分なのですが、開発中も Tor 経由で表示を確認したくなるかもしれません。そこで、ホットリロードにも対応してみましょう。

Dockerfile の変更

Dockerfile に追記していきます。

Dockerfile
# For development
FROM deps AS dev

COPY next.config.js /app
COPY tsconfig.json /app

RUN chmod 700 /app/tor/hidden_service

CMD tor & pnpm dev

docker-compose.dev.yaml の作成

また、docker-compose.dev.yaml を作成して次のようにします。

docker-compose.dev.yaml
services:
  app:
    build:
      context: .
      target: dev
    environment:
      - NODE_ENV=development
    ports:
      - "3000:3000"
      - "8888:80"
    volumes:
      - ./public:/app/public
      - ./src:/app/src

docker-compose.yaml と比べて変更された点は以下です。

  • buildtargetdev になっている
  • environmentNODE_ENVdevelopment になっている
  • volumes が追加されている

target は先程と同じように、Dockerfile で定義した dev ターゲットを使ってビルドします。

volumes では、開発中に編集する可能性があるディレクトリを、直接コンテナ内の特定のディレクトリと同期するように設定しています。これによって、エディタでファイルを編集すると、コンテナを再起動しなくてもコンテナ内のコードも更新されるようになります。(package.json などを変更した場合は再起動が必要になります)

next.config.js の変更

next.config.js にも追記が必要です。

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  webpackDevMiddleware: (config) => {
    config.watchOptions = {
      poll: 1000,
      aggregateTimeout: 300,
    };
    return config;
  },
};

module.exports = nextConfig;

現在(2923/12/07時点) の Next.js では Docker 環境でのホットリロードに少し問題があるようで、webpackDevMiddleware を指定する必要があります。[7]

ここまで設定すると、

docker compose -f ./docker-compose.dev.yaml up --build

を実行することで Tor Browser でのホットリロードを確認することができます。

package.json の変更

コマンド名が長いので、"scripts" を編集して簡単にコンテナを起動できるようにします。

package.json
  ...
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "dev:tor": "docker compose -f ./docker-compose.dev.yaml up --build",
    "start:tor": "docker compose up --build"
  },
  ...

このようにすることで、

pnpm dev:tor

ホットリロードが有効な状態で Tor 経由で Web サイトを公開できるようになります。

ホットリロードしてる動画 (等倍速です):

https://youtu.be/ghz_M48W5a8?si=Z_0Pu3FEjyw60jWd

.onion ドメインでホットリロードが効いているのを見ると、少し奇妙な感覚があって面白いですね。

おまけ

Tor Browser の規定の保護では、クライアントサイドの JavaScript の実行は特に制限されません。

Tor Browser のセキュリティレベル
「最大限の保護」に設定すると JavaScript やフォントの読み込みが制限されるようになる

なので、React の Client Component とかも問題なく動作します。

最初の方に載せた成果物の Web サイトでは React のカウンターを置いてあるので動作を確認することができます。

他にも PandaCSS を使って見た目をいじったりしているので、興味があれば見て行ってね。

WELCOME TO UNDERGROUND!

まとめ

今回は Docker を使って Next.js の Web サイトを Tor に公開してみました。

利点

  • Tor なので セキュア
  • 無料独自ドメインの Web サイトが公開できる
    • Cloudflare Tunnel や ngrok の設定が不要
  • Docker なので環境構築が楽

欠点

  • 超遅い!!!!!

感想

Tor で Web サイトを公開する記事を探すと Next.js とか Vite を使った記事が全然なかったのでやってみました。これで突然 Tor に Web サイトを公開したくなったときも安心ですね。

個人的には Tor の Web サイトは機能が制限されていたり、モダンな技術が使えないと思いこんでいたのですが、特にそんなことはなかったですね。重いことだけが難点です。

実際にダークウェブと聞いて我々 (私だけかも?) がイメージするような Web サイトが、フォントがダサかったりデザインが終わっていたりなんか怪しい色づかいをしている理由としては

  • 通信量減らすため
  • 余計な情報を排除して特定の可能性を減らしている
  • 「最大限の保護」を利用することを想定して無駄なものを排除している
  • デザイナーがいない
  • わざと雰囲気出すためにダサくしている

のどれかなんじゃないかなと思います。詳しい人がいたら教えてください。

参考

https://www.jamieweb.net/blog/onionv3-hidden-service/

https://knmts.com/become-engineer-19/

https://zenn.dev/k_hojo/articles/318d18e0e5b9ac

https://zenn.dev/peishim/articles/de0dd58ba89ca8

https://zenn.dev/kazumax4395/articles/427cc791f6145b

https://pnpm.io/ja/docker

https://cookin.dev/website/324/

脚注
  1. https://support.torproject.org/tbb/maximized-torbrowser-window/ ↩︎

  2. https://spec.torproject.org/rend-spec/encoding-onion-addresses.html ↩︎

  3. https://blog.torproject.org/v2-deprecation-timeline/ ↩︎

  4. 参考: https://ja.wikipedia.org/wiki/Onionドメインの一覧 ↩︎

  5. https://community.torproject.org/onion-services/advanced/vanity-addresses/ ↩︎

  6. https://zenn.dev/jrsyo/articles/e42de409e62f5d ↩︎

  7. https://github.com/vercel/next.js/issues/36774 ↩︎

  8. ここの -d は docker compose の --detach になり、バックグラウンドで動作してくれる。 ↩︎

GitHubで編集を提案

Discussion