onionサービスはtorネットワークで公開する以上、強固なセキュリティが求められます。
今回は、Distrolessコンテナを利用して、torネットワークにWEBサイトを公開してみようと思います。
なお、この記事を書くにあたってO'Reilly Dockerを参考にしました。
TL;DR
ここで公開しています。
用語の説明
torネットワークのonionサービスとは?
torネットワークは検閲されることなく通信するためのネットワークで、onionサービスはインターネット上に情報が出ることなく完全にtorネットワーク上だけで完結するWEBサイトを公開する仕組みです。
しばしば「ダークウェブ」と呼ばれることもあるtorネットワークですが、内部告発サイトや人権活動サイトなどを誰にも検閲されることなくインターネット上に公開できることからイギリスの公共放送BBCもonionサービスで全世界にニュースを公開しているなど活用が進んでいます。
たとえばあなたが内部告発Webシステムを開発する場合、この記事で紹介する知識が役に立つはずです。
Distrolessコンテナとは?
Distrolessコンテナは、必要最小限の構成で作られたコンテナです。
aptどころか、viもshも、なんとbusyboxすら入っていません。gcr.io/distroless/base-nossl-debian12:latestでは、入っているのは主に
- CA証明書
- rootユーザの
/etc/passwd
-
/tmp
ディレクトリ - tzdata
- glibc
だけという徹底ぶりです。
セキュリティの観点からも不要なアプリケーションが入っていないことは(OSコマンドインジェクションを防げるなど)非常に好ましいです。
gcr.io/distroless/base-nossl-debian12:latestは現在14.79MBしかなく、非常に軽量なのも大きなメリットです。
これを土台にして、必要なファイルだけをマルチステージビルドで入れていきます。
実践
構築の土台となる部分を最小構成で作成します。
最小構成で一旦動かしてみることで動きなども理解できます。
Docker
まずcompose.yaml
を作ります。
version: '3.8'
services:
nginx:
build:
dockerfile: ./nginx/Dockerfile
networks:
- tor_network
volumes:
- ./nginx/settings:/etc/nginx/sites-enabled:ro
- ./htdocs:/usr/share/nginx/html:ro
restart: unless-stopped
tor:
build:
dockerfile: ./tor/Dockerfile
networks:
- tor_network
- front
volumes:
- ./data:/var/lib/tor
- ./tor/settings:/etc/tor
restart: unless-stopped
networks:
front:
tor_network:
internal: true
ネットワークは、外部に接続するためのfront
ネットワークと、外部とつながっていない内部だけで独立したネットワークのtor_network
を用意します。
tor
コンテナだけ外部と接続して、nginx
コンテナは外部と遮断し、そしてtor
コンテナとnginx
コンテナをtor_network
ネットワークでつなげます。
ネットワーク図に表すと以下のようになります。
nginx
コンテナとtor
コンテナは、後述のtor/Dockerfile
とnginx/Dockerfile
で記述しています。
また、どちらもrestart: unless-stopped
で、落ちたら再起動するようにしています。
Tor
debian:12
にtorをインストールして、動作に必要なファイルだけをgcr.io/distroless/base-nossl-debian12:latest
に移動しています。
FROM debian:12 as build
RUN apt update && \
apt install tor -y --no-install-recommends && \
cp -a --parents /usr/bin/tor /opt && \
cp -a --parents /usr/sbin/tor /opt && \
cp -a --parents /etc/tor /opt && \
cp -aL --parents /usr/share/tor /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libz.so.* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libm.so.* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libevent-2.1.so.* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libssl.so.* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libcrypto.so.* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libsystemd.so.* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/liblzma.so.* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libzstd.so.* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libseccomp.so.* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libcap.so.* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libc.so.* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libgcrypt.so.* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/liblz4.so.* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libgpg-error.so.* /opt
FROM gcr.io/distroless/base-nossl-debian12:latest
COPY --from=build /opt /
ENTRYPOINT ["/usr/bin/tor"]
torの設定ファイルであるtorrc
で、80ポートへの接続をnginx
コンテナに向けています。
※Dockerネットワーク内ではコンテナ名で名前解決をすることができます
SocksPort 0
HiddenServiceDir /var/lib/tor/hidden_service/
HiddenServicePort 80 nginx:80
誰かの踏み台にされるのだけは防ぐためにSocksPort 0
の設定をしています。
Nginx
これもdebian:12
にnginxをインストールして、動作に必要なファイルだけをgcr.io/distroless/base-nossl-debian12:latest
に移動しています。
FROM debian:12 as build
RUN apt update && \
apt install nginx -y --no-install-recommends && \
cp -a --parents /usr/share/nginx /opt && \
cp -a --parents /var/log/nginx /opt && \
cp -a --parents /var/lib/nginx /opt && \
cp -aL --parents /var/run /opt && \
cp -a --parents /etc/nginx /opt && \
cp -a --parents /etc/passwd /opt && \
cp -a --parents /etc/group /opt && \
cp -a --parents /usr/sbin/nginx /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libcrypt.so.* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libpcre2-8.so.* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libssl.so.* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libcrypto.so.* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libz.so.* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libc.so.* /opt
FROM gcr.io/distroless/base-nossl-debian12:latest
COPY --from=build /opt /
EXPOSE 80
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]
nginxの設定ファイルを配置します。
server {
listen 80;
listen [::]:80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
なにも表示されないと動いているかどうかわかりにくいのでindex.html
を配置しておきます。
<!DOCTYPE html>
<html>
<head>
<title>docker hidden service test title</title>
</head>
<body>
<div>docker hidden service test body div</div>
</body>
</html>
以上で最小構成は作成完了です。
起動する
Dockerは普通にdocker-compose up -d
コマンドで起動します。
起動確認(onionサービスとの疎通確認)
正常に起動すると、data/hidden_service/hostname
に以下のようなファイルが出力されています。
dbutwdcq576d6p5435sbhnbbl3lafi6stfyu3xgt6v6eitenbefnolid.onion
このアドレスにtorブラウザなどで接続し、作成したindex.htmlが表示されていることを確認します。
無事に動いていることが確認できました。
このように、Distrolessを使えばセキュリティが非常に強固なサービスを簡単に立ち上げることができます!
セキュリティの強固さが求められる場合、ぜひDistrolessを使ってください!