西出正美です。有益なことや無益なことなどいろいろ書いています。

OnionドメインのウェブサイトをDockerでDistrolessコンテナで構築して安全に公開する

onionサービスはtorネットワークで公開する以上、強固なセキュリティが求められます。 今回は、Distrolessコンテナを利用して、torネットワークにWEBサイトを公開してみようと思います。

なお、この記事を書くにあたってO'Reilly Dockerを参考にしました。

TL;DR

https://github.com/nishidemasami/distroless-tor-nginx

ここで公開しています。

用語の説明

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を作ります。

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ネットワークでつなげます。

ネットワーク図に表すと以下のようになります。

  fronttor_networkDocker内部 nginxtorネットワークDocker仮想ブリッジtornginx

nginxコンテナとtorコンテナは、後述のtor/Dockerfilenginx/Dockerfileで記述しています。

また、どちらもrestart: unless-stoppedで、落ちたら再起動するようにしています。

Tor

debian:12にtorをインストールして、動作に必要なファイルだけをgcr.io/distroless/base-nossl-debian12:latestに移動しています。

tor/Dockerfile
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ネットワーク内ではコンテナ名で名前解決をすることができます

tor/settings/torrc
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に移動しています。

nginx/Dockerfile
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の設定ファイルを配置します。

nginx\settings\tor.conf
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を配置しておきます。

htdocs/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に以下のようなファイルが出力されています。

data/hidden_service/hostname
kxal3j4zblhjt4jinnbly6icciqbh2ihq4u2opsu4qhqyqazwsgaq3ad.onion

このアドレスにtorブラウザなどで接続し、作成したindex.htmlが表示されていることを確認します。

Torブラウザで表示できたところ
Torブラウザで表示できたところ

無事に動いていることが確認できました。

このように、Distrolessを使えばセキュリティが非常に強固なサービスを簡単に立ち上げることができます! セキュリティの強固さが求められる場合、ぜひDistrolessを使ってください!

NISHIDEMASAMI.GITHUB.IO
NISHIDE, Masami

西出正美です。有益なことや無益なことなどいろいろ書いています。

©NISHIDE, Masami Some Rights Reserved