はじめに(この記事で分かること)#
Webサイトを公開するサーバーのIPアドレスを隠すには、2台のVPSを使う。公開側のedgeサーバーでTLSを終端し、WireGuardトンネルでoriginサーバーに転送する。originサーバーはグローバルネットワークにポートを1つも開かない。
この構成は、CDNで隠してもoriginが特定される問題に対する、CDNを使わない解決策だ。検証環境はUbuntu 26.04 LTSである。
前提: 脅威モデルの整理#
originサーバーのIPを隠す設計が有効な相手は、脅威モデル入門で整理した5種類の相手のうち、主に2者だ。
走査エンジン(Shodan、Censys)は、originサーバーのIPアドレスがグローバルネットワークに露出していれば、開放ポートとサービスを収集する。originにポートがなければ、走査は何も得られない。
標的型攻撃者は、originサーバーのIPを特定してCDNの防御を迂回しようとする。originにポートがなければ、攻撃の入口が存在しない。
踏み台サーバーとWireGuardで説明したSSH非公開の構成と同じ原則を、Web公開に適用する。
アーキテクチャ#
利用者 (HTTPS) → edge VPS(公開 IP・TLS 終端・nginx)
↓ WireGuard トンネル
origin VPS(グローバルポートなし・nginx)
→ Web サーバー(トンネル IP でのみリッスン)
edgeはTLS終端とリバースプロキシのみを担う単機能アプライアンスだ。データベース、アプリケーションコード、シェルスクリプトは置かない。
originはWebサーバーをWireGuardのトンネルIPでのみリッスンさせる。グローバルインターフェースはdefault-denyで、80/443の例外もない。
ステップ1: WireGuardトンネルの構築#
originがedgeにdial outする構成にする。origin側がEndpointを持ち、edgeはリッスンのみを行う。
# 両マシンで鍵ペアを生成
umask 077; wg genkey | tee privkey | wg pubkey > pubkey
# /etc/wireguard/wg0.conf(edge)
[Interface]
Address = 10.66.0.1/24
ListenPort = 51820
PrivateKey = <edge-privkey>
[Peer]
PublicKey = <origin-pubkey>
AllowedIPs = 10.66.0.2/32
# /etc/wireguard/wg0.conf(origin)
[Interface]
Address = 10.66.0.2/24
PrivateKey = <origin-privkey>
[Peer]
PublicKey = <edge-pubkey>
Endpoint = <edge-public-ip>:51820
AllowedIPs = 10.66.0.1/32
PersistentKeepalive = 25
systemctl enable --now wg-quick@wg0
wg show
ステップ2: originのWebサーバーをトンネルIPでのみリッスンさせる#
# /etc/nginx/sites-available/app(origin)— トンネル IP のみでリッスン
listen 10.66.0.2:8080;
Dockerを使う場合は、ポートバインディングをトンネルIPに限定する。
# docker-compose.yml(origin)
ports:
- "10.66.0.2:8080:8080"
グローバルIPにバインドされたサービスが1つでも残っていれば、それはリークだ。確認コマンド:
ss -tulpn | grep -Ev '10\.66\.0\.2|127\.0\.0|\[::1\]'
このコマンドでパブリックアドレスにバインドされたサービスが出たら、ファイアウォールで隠すのではなくバインドアドレスを直す。
ステップ3: originのファイアウォール設定#
ufw --force reset
ufw default deny incoming
ufw default allow outgoing
ufw allow in on wg0 to any port 8080 proto tcp
ufw allow in on wg0 to any port 22 proto tcp
ufw allow from <edge-public-ip> to any port 51820 proto udp
ufw --force enable
ステップ4: edgeでTLSを終端し、トンネル経由でoriginにプロキシする#
# /etc/nginx/sites-available/example.com(edge)
server {
listen 443 ssl;
http2 on;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
add_header Strict-Transport-Security "max-age=63072000" always;
location / {
proxy_pass http://10.66.0.2:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
}
}
# Host ヘッダなしの IP 直打ちスキャナーに何も返さない
server {
listen 80 default_server;
listen 443 ssl default_server;
ssl_reject_handshake on;
return 444;
}
デフォルトサーバーブロックは、IP直打ちスキャナーに何も返さない。サーバー名のないTLSハンドシェイクを拒否し、HTTPでは444を返す。このブロックがないと、edgeがoriginの存在を示す指紋になる。
ステップ5: origin側でreal_ipを設定する#
プロキシ経由では、originのアクセスログに全リクエストが10.66.0.1として記録される。このままではfail2banがedgeをBANしてサイトが落ちる。
# /etc/nginx/conf.d/realip.conf(origin)
set_real_ip_from 10.66.0.1;
real_ip_header X-Forwarded-For;
real_ip_recursive off;
この設定がないままfail2banを有効にすると、edgeのIPがBANリストに入り、サイト全体が停止する。
ステップ6: 運用上の警告#
設定変更中はSSHセッションを2本開いたまま作業する。トンネル越しSSHの再構成ミスでロックアウトされると、匿名ホストにはサポート窓口がないため、再デプロイ一択になる。
ステップ7: egressリークの制御#
originサーバーは接続を受けないだけでなく、自分から外に向けて接続を開く。NTP、パッケージミラー、Webhook、エラーレポーター、Git remote、送信SMTP。これらの宛先から見ると、接続元のIPアドレスはoriginのIPである。
ServPrivacyのガイドは、この問題を明確に定義している。攻撃者が用意したURLを、リンクプレビューを生成する機能やSSRF脆弱性に貼らせると、originが攻撃者の監視下ホストに接続し、2分でデアノニマイズが完了する。
対策は二つある。
対策A(厳格): originの全発信トラフィックをWireGuardトンネル経由にし、edgeでNATを行う。wg-quickは0.0.0.0/0ルートでfwmarkルールを自動インストールする。
対策B(実用的): ユーザー供給URLを取得する機能の前に必ずプロキシを挟む。
どちらの対策を採ったかを明示的に決め、自分のホストへリクエストしてログの送信元IPで検証することまでを手順に含める。
限界と残るリスク#
origin IPの隠蔽は、IPアドレスを隠すだけで、人を匿名化するわけではない。決済の痕跡、ドメイン登録、アカウント情報は別の問題として存在する。決済手段の匿名化はVPNを匿名で支払う方法で、ドメインの匿名取得はWEBサイトを匿名で運営するで扱う。
origin IPの隠蔽は、アプリケーションの脆弱性を埋めない。originに到達できる攻撃者は、CDNの防御を迂回して直接攻撃できる。originの硬化は、origin IPの隠蔽と並行して行う必要がある。
ホスト業者は、どのマシンがどのアドレスで稼働しているかを知っている。この情報は、業者への法的要請で提出されうる。
まとめ#
この構成は、3つの層でoriginを保護する。
第一層は、WireGuardトンネルによるoriginのネットワーク分離だ。originはグローバルポートを持たない。第二層は、edgeでのTLS終端とIP直打ちスキャナー対策だ。第三層は、egressリークの制御だ。
origin IPの隠蔽は「アドレスを隠す」だけで「人を匿名化する」わけではない。決済、ドメイン、アカウントの匿名化は、匿名ドメインの取得と匿名VPSの比較で扱う。
出典#
- ServPrivacy — Hide your origin IP: an offshore reverse proxy that holds(edge/origin分離、egressリークの定義、wg-quickのfwmarkルール、
ssl_reject_handshake on; return 444;、real_ip設定、SSHセッション2本の警告) - arkhost — Securing Inter-Server Communication with WireGuard and Reverse Proxy(wg0.confの設定例、Dockerバインド、UFW)
- martian1337 — Two-VPS Private Proxy Architecture(2 VPS構成、AllowedIPsを /32にする理由、0.0.0.0/0をbackendに設定してはならない理由)
- security.stackexchange — Can an NGINX reverse proxy reveal the backend?(ローカルマシンが自己露出する条件の整理)