クラウドのサーバーに届いたパケットをVPN経由でローカルに引き込む方法

概要

ローカルのパソコンやサーバーで外部からアクセスを受けるサービスを運用したい時、クラウドのサーバー経由にしなければならないことは往々にしてあります。例えば

  • 自身のグローバルIPを秘匿して運用したい
  • 自身の回線でポート開放をする権限がない

などです。こういった場合、クラウド上のサーバーに届いたパケットをポートフォワードして、VPN経由でローカルに引き込むことで問題が解決する場合があります。

必読: 免責事項

自身の回線でポート開放をする権限がないという状態は、すなわち「その回線が自分の管理下ではない」ということを意味します。大抵の場合ネットワーク管理者に規制されているか、共同回線を使っているケースが考えられます。

このような回線において本記事の手順を実行する際には、自分が今何を行おうとしているのか、十分に理解してから行う必要があります。前者の場合は規制を回避していることになりますし、後者の場合は概念的に1ユーザーに割り当てられたリソースを超過して利用し、他ユーザーの回線利用に支障が出る可能性があります。

本記事の手順を利用して直接・間接的に生じた損失等に対し、私は何ら責任を負いません。
実行する場合はご自身の責任において実行してください。

考え方

WireGuard(VPN) と iptables を使って、クラウドのサーバーに来た通信をIPマスカレードして WireGuard のインターフェースに送り込みます。

structure

外部からはクラウドのサーバーのIPしか見えません。また、VPNの接続はローカルからクラウド方向へ行うようにすれば、ローカル側でのポート開放は不要です。

ただし、この方法を使うとローカルのサーバー視点では接続元IPの情報が失われます。これを好まない場合はリバースプロキシを用いる形に変更することで解決する場合があります。

実践環境

今回使用する環境は以下の通りです。

  • ローカル側のサーバー1台 (今回はこちらもクラウドのサーバーを利用)
  • クラウド側のサーバー1台
  • OS: Ubuntu 23.10

今回はクラウドのサーバー2台を使って実践していますが、特に意識する必要はありません。
また、WireGuard は Windows でも実行可能ですので、同じ設定ファイルの形式で扱えます。詳しい GUI の操作方法は省略します。

WireGuardのインストール

両方のサーバーに WireGuard をインストールしてください。Ubuntu であれば以下のコマンドでインストールできます。

Install WireGuard on Ubuntu

sudo apt install wireguard-tools

後で使う情報のメモ

設定ファイルや実行コマンドを組み立てるために以下の情報が必要なため、まずそれらを用意します。

  • クラウドのサーバーのIP
  • ローカルで待ち受けるサービスのポート番号

また、以下の情報を各自で決定します。

  • クラウドでサービスを待ち受けるポート番号 ( ローカルのものと同じでも可 )
  • クラウドでVPN接続を待ち受けるポート番号 ( 5桁が望ましい )

加えて、以下のコマンドで WireGuard を用いて鍵ペアを生成します。( GUI 形式であれば設定ファイルを作成しようとした時に自動生成されるので不要です )

Generate WireGuard Private Key

wg genkey > /etc/wireguard/key

公開鍵は

Generate Public Key from Private Key

wg pubkey < /etc/wireguard/key

で見ることが出来ます。これらの値は後で設定ファイルの生成に利用するためメモしておきます。なお秘密鍵の取り扱いには十分注意してください。もし秘密鍵が外部に漏れてしまった場合は、必ず再生成してください。

今回の例では以下の値を利用します。

クラウドのサーバーのIP: 172.233.72.209
ローカル側で待ち受けるサービスのポート番号: 80
クラウド側でサービスを待ち受けるポート番号: 8080
クラウド側でVPN接続を待ち受けるポート番号: 40000
ローカル側のWireGuard秘密鍵: LocalWgPrivKeyPfC1T1Cpgi/MZgf3cDYdRZz8y3k=
ローカル側のWireGuard公開鍵: LocalWgPubKeyXj0dpBQhe8YV8H/nomZwDahU03lo=
クラウド側のWireGuard秘密鍵: CloudWgPrivKeyBShk28zZ77vMsAuYcqg5oUubAUk=
クラウド側のWireGuard公開鍵: CloudWgPubKeyGUIsb0q7oT62M5BUL14V9xS0KGyc=

今回は検証のために以下の compose.yaml ファイルを使って、ローカル側のサーバーのポート番号80番で nginx を起動しておきます。

/opt/nginx/compose.yaml

services:
  nginx:
    image: nginx:latest
    ports:
      - 80:80

ローカル側のセットアップ

WireGuard の設定ファイルを作ります。Ubuntu の場合は /etc/wireguard/wg0.conf に作成します。Windows の場合は GUI 左下の「トンネルを追加」のドロップダウンに「空のトンネルを追加」があると思うので、そこを選択します。

/etc/wireguard/wg0.conf

[Interface]
PrivateKey = <ローカル側の秘密鍵>
Address = 10.0.0.2/24

[Peer]
PublicKey = <クラウド側の公開鍵>
AllowedIPs = 10.0.0.1/32
Endpoint = <クラウドのサーバーのIP>:<クラウド側でVPN接続を待ち受けるポート番号>
PersistentKeepalive = 30

秘密鍵と公開鍵を間違えないように気を付けてください。今回の例ではこのようになります。

/etc/wireguard/wg0.conf

[Interface]
PrivateKey = LocalWgPrivKeyPfC1T1Cpgi/MZgf3cDYdRZz8y3k=
Address = 10.0.0.2/24

[Peer]
PublicKey = CloudWgPubKeyGUIsb0q7oT62M5BUL14V9xS0KGyc=
AllowedIPs = 10.0.0.1/32
Endpoint = 172.233.72.209:40000
PersistentKeepalive = 30

PersistentKeepalive を利用することにより、30秒ごとにクラウド側のサーバーへ KeepAlive パケットを送信します。これにより VPN 接続が切れるのを回避しています。

クラウド側のセットアップ

こちらでも同様に WireGuard の設定ファイルを作成します。フォーマットは以下のようになります。

/etc/wireguard/wg0.conf

[Interface]
PrivateKey = <クラウド側の秘密鍵>
Address = 10.0.0.1/24
ListenPort = <クラウド側でVPN接続を待ち受けるポート番号>

[Peer]
PublicKey = <ローカル側の公開鍵>
AllowedIPs = 10.0.0.2/32

今回の例ではこのようになります。

/etc/wireguard/wg0.conf

[Interface]
PrivateKey = CloudWgPrivKeyBShk28zZ77vMsAuYcqg5oUubAUk=
Address = 10.0.0.1/24
ListenPort = 40000

[Peer]
PublicKey = LocalWgPubKeyXj0dpBQhe8YV8H/nomZwDahU03lo=
AllowedIPs = 10.0.0.2/32

VPNの疎通確認

設定ファイルが出来たら疎通確認をしてみます。WireGuard を クラウド側 -> ローカル側 の順番で起動します。Ubuntu であれば以下のコマンドで起動できます。

Start WireGuard

wg-quick up wg0

そしてローカル側からクラウド側のWireGuard IP (10.0.0.1) に ping を打ってみます。

root@local-server:/etc/wireguard# ping 10.0.0.1
PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data.
64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=0.391 ms
64 bytes from 10.0.0.1: icmp_seq=2 ttl=64 time=0.530 ms
64 bytes from 10.0.0.1: icmp_seq=3 ttl=64 time=0.406 ms
64 bytes from 10.0.0.1: icmp_seq=4 ttl=64 time=1.79 ms

上記のように ping が通ればVPN接続は成功です。通らない場合はファイアーウォールの設定を見直したり、各々のコンピューターで WireGuard が正常に起動しているかを確かめてみてください。ターミナルであれば wg と打つことで、現在起動している WireGuard の設定情報が閲覧できます。何も出ない場合は起動していません。

wg と打って情報が出た場合は、そこに記載されている public key が相手側の設定ファイルに適切に書かれているかなどを確認してみてください。

自動起動の設定

サーバー起動時に自動で WireGuard を起動したい場合、Ubuntu であれば以下のコマンドを実行します。

Auto Start WireGuard on Startup

sudo systemctl enable wg-quick@wg0

iptablesの設定

クラウド側のサーバーの目的のポートに来た通信を、ローカル側のサーバーに WireGuard 経由で転送する設定を行います。設定コマンドは以下の記事を大いに参考にさせて頂きました。

まずフォワーディングを許可するために以下のコマンドをクラウド側のサーバーで実行します。

Enable IP Forwarding

sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf

続いて以下のコマンドを用いて iptables にフォワーディングのルールを追加します。
このコマンドは TCP 用です。UDP の場合は -p tcp-p udp に置き換えて実行してください。

Configure iptables

iptables -t nat -A PREROUTING -p tcp --dport <クラウド側でサービスを待ち受けるポート番号> -j DNAT --to-destination 10.0.0.2:<ローカル側で待ち受けるサービスのポート番号>
iptables -t nat -A POSTROUTING -p tcp --dst 10.0.0.2 --dport <ローカル側で待ち受けるサービスのポート番号> -j SNAT --to-source 10.0.0.1
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT

今回の例では以下のようになります。

Configure iptables Example

iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 10.0.0.2:80
iptables -t nat -A POSTROUTING -p tcp --dst 10.0.0.2 --dport 80 -j SNAT --to-source 10.0.0.1
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT

1行目のコマンドの設定により、目的のポートに来たパケットの宛先が 10.0.0.2 の目的のポートに変更されます。
続いて2行目のコマンドの設定により、帰り道を見失わないように送信元を自身のアドレスに書き換えます。
そして3行目のコマンドの設定により、初回接続以外のパケットを正常に通過させるようにします。

複数のポートについてフォワーディングを設定したい場合は、前2行のコマンドをそれぞれのポートに対して実行してやれば動作します。

デフォルトでは FORWARD チェインは ACCEPT なので以下の設定は不要なことが多いのですが、iptables -L FORWARD と打って (policy DROP)(policy REJECT) と出た場合は、以下のコマンドも併せて実行してください。

Allow Forwarding

iptables -A FORWARD -m tcp -p tcp --dst 10.0.0.2 --dport <ローカル側で待ち受けるサービスのポート番号> -j ACCEPT

接続確認

この時点で接続が可能となったはずです。ローカル側のサーバーで公開したいサービスがちゃんと起動していることを確認した上で、クラウド側のサーバーに接続しようとしてみてください。

今回の例では nginx を起動しているので、curl でクラウド側のサーバーの 8080 番を叩いてみます。

$ curl http://172.233.72.209:8080/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

(以下省略)

無事に疎通していることを確認できました。

iptablesのセーブを忘れずに

iptables はセーブしないと再起動で変更が失われます。Ubuntu では以下のようにすると簡単です。

Save iptables on Ubuntu

# 初回 ( Save current IPv4 rules? とか出てくるので yes を選択する )
sudo apt install iptables-persistent

# 次回以降
sudo netfilter-persistent save

iptables はディストリビューションによってセーブ方法が異なる上に、Ubuntu は少し特殊です。別のディストリビューションを利用している場合は色々と調べながらやってみてください。

あとがき

最近になってやっと VPN や NAT の理解が深まってきた気がします。ネットワークむずかしい。