unmask

docs

JA4 取得、LB 設定、対応 distro、よくある質問。

コンテナで動かす

リリースごとに GHCR へ 2 つのイメージを公開しています。unmask.sh/admin(daemon)と unmask.sh/nginx(公式 nginx イメージ + unmask module。その nginx バージョンに合わせてビルド)です。 2 つを組み合わせると、ホストへのインストールと同じ構成になります — nginx が TLS を終端して判定し、daemon が検証と状態を持つ。 さらに nginx イメージは任意の HTTP サーバーの前段に JA4 対応の gateway として置けます。 アプリを nginx で配信していない環境で unmask を使う方法がこれです。

1. クイックスタート(docker compose)

リポジトリの docker-compose.example.yml(各リリースに添付されるので、取得した copy はそのリリースのイメージと一致します) が、2 つのコンテナと仮の upstream を配線しています。用意するものはありません:

curl -fsSL -o docker-compose.example.yml https://unmask.sh/dl/docker/docker-compose.yml
docker compose -f docker-compose.example.yml up -d
docker compose -f docker-compose.example.yml logs admin | grep "setup token"

https://localhost/unmask/admin/ を開きます。初回起動は admin が生成した自己署名証明書を配信するのでブラウザが一度警告します (いったん受け入れてください)。管理画面もサイトと同じ保護の内側にあるので、次に短い security check が表示され、通過するとインストール ウィザードが続きます。ログのセットアップトークンを貼り、SQLite(既定)か自分の DB を選び、管理者ユーザーを作れば完了。 あとは 設定 → Gateway で、転送先 (upstream)・応答するホスト名・本番の証明書(Let's Encrypt / 貼り付け / mount したファイル)を設定します。

volume は必ず残してください。 /etc/unmask には描画済みの nginx include と、通過 cookie に署名する secret が入っています。 これを付けずに admin コンテナを動かすと、再起動のたびに secret が変わり、訪問者全員がログアウトして再チャレンジになります。 /var/lib/unmask はデータベース、/run/unmask は nginx から daemon へのアクセスログ socket です(ダッシュボードの cookie 使い回し系の表示はここから来ます)。

管理画面で変更した設定は共有 volume に描画され、nginx コンテナがファイルの変化を数秒以内に検知して自分で reload します。 事前に nginx -t を通すので、解釈できない描画があればログに残してスキップし、稼働中の設定はそのまま保ちます。手で実行するものはありません。 自分のタイミングで reload したい場合は UNMASK_AUTORELOAD=0 で watcher を止められます。

2. gateway モード: 任意の HTTP サーバーの前段に

UNMASK_GATEWAY=1 にすると、nginx コンテナが :443 で TLS を終端し(クライアントの ClientHello を直接見るので JA4 が取れます)、 チャレンジ判定を行い、通過したリクエストを upstream へ proxy します。アプリ側には何もインストールしません。 配信しているものが Node でも Rails でも Go でも Apache でも別の nginx でも、静的ファイルサーバーでも構いません。

gateway の設定は管理画面の 設定 → Gateway にあります: 転送先 (upstream)(Docker ホスト上のサーバーなら http://host.docker.internal:8080、別ホストなら http://10.0.0.5:8080、同じ compose 内のコンテナなら http://app:3000)、応答するホスト名、証明書。前段ロードバランサーの信頼するプロキシは 設定 → Network → 信頼する LB / CDN (GCP / Cloudflare などの preset あり)で、JA4 header を信頼する相手と同じ集合です。転送先が未設定の間、gateway はその旨の案内ページを返します。同じホストで動いているサーバーを転送先にする場合は、:80/:443 を gateway に譲って自分のサーバーを 8080 へ移します (Apache は Listen 8080、nginx は listen 8080;)。コンテナから届くアドレスに bind する必要があるので 127.0.0.1 は不可、8080 は firewall で外から閉じておきます。compose ファイルには host.docker.internal がホストを指す設定が最初から入っています。下の環境変数は初回起動時にタブへ 流し込むだけです(nginx サービス側の UNMASK_UPSTREAM / UNMASK_TRUSTED_PROXIES は、タブが空の間のフォールバックとして引き続き効きます)。

変数意味既定
UNMASK_GATEWAY1 で gateway モード(両サービスに指定: admin はタブを seed し、nginx コンテナは gateway の server を描画します)。未設定 = module-only
UNMASK_UPSTREAM通過したリクエストの転送先。例 http://app:3000admin サービスに書くとタブの初期値、nginx サービスに書くとタブが空の間のフォールバック(0.1.37 の方式。これでも gateway モードになります)。未設定 = タブで設定
UNMASK_SERVER_NAMEgateway が応答するホスト名(admin サービス側。初回起動時に Gateway タブへ流し込まれます)。_ = すべて、それ以外はスペース区切りのリストで、流し込まれる証明書のドメインにもなります。_(すべて)
UNMASK_ACME_EMAIL自動 HTTPS の初期値。UNMASK_SERVER_NAME の証明書を Let's Encrypt から取得し、nginx 自身が更新します。未設定 = 自分のファイル
UNMASK_ACME_DIRECTORYACME directory URL。検証中は staging を指定。Let's Encrypt 本番
UNMASK_TLS_CERTnginx コンテナ上の証明書チェーン(PEM)。ACME を使わない場合。/etc/unmask/tls/fullchain.pem
UNMASK_TLS_KEYnginx コンテナ上の秘密鍵(PEM)。ACME を使わない場合。/etc/unmask/tls/privkey.pem
UNMASK_TLS_MODE前段のロードバランサーが TLS を終端して :80 に転送する構成なら none(「待ち受け: http」のみを seed。:443 を開かず証明書不要)。ほか files / acme / upload / selfsigned自動: メールがあれば acme、なければ files
UNMASK_TRUSTED_PROXIESgateway の前段にあるロードバランサーのアドレス範囲(空白区切り)。訪問者のアドレスをその X-Forwarded-For から取ります。未設定 = 接続元がそのまま訪問者

ホスト名と証明書

どちらも管理画面の 設定 → Gateway で、互いに独立した 2 つの問いとして設定します。どのホスト名に応答するか: すべて(Host をそのまま upstream に渡す。upstream 側が複数サイトを持つ場合向け)か、指定したリストか — リストにない Host は TLS ハンドシェイクの段階で拒否します。どの証明書を持つか: 証明書ごとに 1 エントリで、入手元と「どのドメインの証明書か」を 持ちます。nginx は SNI で証明書を選び、1 枚目が既定としてどの証明書のドメインにも一致しないホスト名に配信されます(リストのホスト名に 証明書が無ければタブが警告します)。つまり 1 つの gateway で複数サイト・複数証明書を扱え、ホスト名を変えても証明書には触れません。 各証明書には :443 がその最初のドメインに実際に配信している証明書(発行者・有効期限・名前の一致)も表示されます。 入手元は 3 つ:

:80 は https へリダイレクトします。ロードバランサーの health check は user agent(GoogleHC、ELB-HealthChecker、kube-probe など。追加は 設定 → nginx → https redirect)でリダイレクトを免れます。 UNMASK_TRUSTED_PROXIES にロードバランサーの範囲を入れると、BAN・rate limit・geo ルールがロードバランサーではなく訪問者を見るようになります。upstream には HostX-Real-IPX-Forwarded-ForX-Forwarded-Proto: https が渡り、WebSocket の upgrade も通します。

gateway は body サイズの上限を設けず、1 時間までのレスポンスを許容します。アップロードやストリームの制限は upstream 側の設定がそのまま効き、 gateway が勝手に決めた既定値で切られることはありません。

すでに nginx を運用している場合、その前段に置く

動きます。二段 proxy の一般的な注意が 2 つあるだけです。後段の nginx には gateway のアドレスから素の HTTP が届くので、 (1) 実クライアントの所在を教えてください — set_real_ip_from に gateway のアドレス、real_ip_header X-Forwarded-For — でないとログ・rate limit・geo 判定がすべて gateway を見ます。(2) 後段が自前で http→https リダイレクトをしているなら、受け取ったスキームではなく $http_x_forwarded_proto で判定してください。そうしないと両者がリクエストを弾き合ってループします。 あるいは TLS を後段に残したまま転送先にその https リスナーを指定する手もあります。gateway は SNI を送るので vhost の選択も効きます。 クライアント証明書(mTLS)は TLS が gateway で終わるため通せません。

その nginx を変更できるなら、module-only の方が向いています。そちらに module を足せば二段目の proxy 自体が要りません。 gateway は、それができないときのためのものです。

gateway モードでは unmask がリクエスト経路に入ります(module-only のインストールでは入りません)。nginx は公式イメージそのままの stock バイナリで、 server.inc の daemon 停止時 fail-open も同じく効きます。admin コンテナに到達できないときは、拒否ではなくチャレンジなしで proxy します。

3. module-only: 自分の nginx イメージで

UNMASK_GATEWAY を設定しなければ、unmask.sh/nginx は module を読み込んだだけの素の nginx です。 自分の conf.d を mount し、ホストと同じ include を書きます:

# http スコープ(conf.d のファイルはすべて http スコープ)
include /etc/unmask/http.inc;
include /etc/unmask/upstream.conf;
include /etc/unmask/forward-auth-lbtrust.conf;

server {
    listen 443 ssl;
    ...
    include /etc/unmask/server.inc;
    location / {
        include /etc/unmask/protect.inc;
        proxy_pass http://app:3000;
    }
}

既存のイメージを使い続けて module だけ足すこともできます。.so は nginx のバージョンと一致し、--with-compat でビルドされている必要があります。 公式の nginx:<version> イメージはこの条件を満たしていて、 docker/nginx/Dockerfile は 2 段ビルドなのでそのまま流用できます。 COPY --from=unmask.sh/nginx:1.28 で module だけ取り出すのが最短です。admin コンテナとは /etc/unmask/run/unmask を共有し、 admin 側の UNMASK_DAEMON_ADDR には nginx から見た admin のアドレスを指定します(compose なら admin:9477)。

4. Kubernetes と他のリバースプロキシ

JA4 は TLS handshake から読むので、TLS を終端する場所にしか存在しません。ingress controller、Traefik、Caddy、クラウドの LB の裏では nginx コンテナはクライアントの ClientHello を見られず、unmask は forward-auth モードとして動きます。 前段が JA4 を転送してくれる場合は別です(LB / CDN 設定に対応表があります)。 行動 CAPTCHA、PoW、IP、UA の各軸はそのまま効き、fingerprint の軸だけが使えなくなります。

edge を自分で握れるなら、gateway イメージを edge にしてください。小さなクラスタなら LoadBalancer Service を直接向ければ JA4 の軸を保てます。 ingress-nginx との統合はありません — あの controller はサードパーティの module を読み込みません。

5. イメージのタグ

どちらのイメージもリリース workflow がタグの commit からビルドして push します。CI は push のたびに両方をビルドするので、 --with-compat で読み込めなくなる module の変更は出荷前に検出されます。