- Wagは、WireGuard に多要素認証、ルート制限、デバイス登録を追加するプロジェクトで、MFA が必要なルートと常時アクセス可能な公開ルートを区別できる
- 新規クライアント登録 API、高可用性、リアルタイムのユーザー更新と通知、Security Key・SSO・PAM・TOTP など複数の MFA 統合を提供する
- サーバー運用には IP フォワーディングの有効化が必要で、手動実行時には
iptables と libpam のインストール、iptables と WireGuard デバイス管理のための root 実行が必要
- 管理は Web UI と CLI で可能で、CLI は
start、registration、devices、users、webadmin の各サブコマンドで登録トークン、デバイスロック、MFA 初期化、Web 管理者アカウントを扱う
- 制約として、クライアントごとに 1 つの
AllowedIP のみをサポートし、主に Linux 専用で、Windows も一部の作業を行えば動作する可能性がある
Wagが追加するWireGuard機能
- Wagは WireGuard に MFA、ルート制限、デバイス登録を追加する
- ルートは、MFA 認証が必要な経路と、常時アクセス可能な 公開ルート に分けて定義できる
- 新規クライアント登録のための簡単な API を提供する
- 高可用性、リアルタイムのユーザー更新と通知をサポートする
- MFA 統合には次の方式が含まれる
- Security Key
- SSO
- PAM
- TOTP
- ドキュメントは Documentation で提供されている
インストールと実行条件
- サーバーで フォワーディング を有効化する必要がある
- IPv4 では
net.ipv4.ip_forward=1 設定を使用する
- IPv6 では
net.ipv6.conf.all.forwarding=1 などの関連 sysctl 設定を使用する
- Docker Compose の実行例では
wagvpn/wag:latest イメージを使用する
- 管理ページのポート例は
4433/tcp
- 公開登録ページのポート例は
8081/tcp
- WireGuard のポート例は
53230/udp
/dev/net/tun デバイスをコンテナに接続する
- 手動インストールには
iptables と libpam が必要
- Wag は
iptables と WireGuard デバイス を管理するため root で実行する必要がある
- バイナリリリースには
glibc 2.31+ が必要
- ソースビルドには
go1.23.1 と npm が必要
管理方法
- 管理 UI を有効化したうえで Wag を設定すると、最初の管理者を作成し、パスワードを STDOUT に出力する
- その後 Web UI にログインしてユーザーを管理できる
- root ユーザーは CLI で Wag サーバーを管理できる
- CLI の形式は
wag subcommand [-options]
- 対応するサブコマンドは次のとおり
start: Wag サーバーを起動し、デーモン化はしない
registration: 登録トークンの作成、削除、一覧表示を処理する
devices: WireGuard デバイスの一覧表示、削除、ロック、ロック解除、有効な MFA セッションの確認を処理する
users: ユーザー MFA 管理、ユーザー削除、アカウントロック、MFA 初期化を処理する
webadmin: Web UI 管理者ユーザーの追加、削除、一覧表示、アカウントのロックと解除を処理する
version、firewall も対応コマンドに含まれる
登録トークンとMFAフロー
- 新しいデバイス登録では、まず
wag registration -add -username tester のようなコマンドで 登録トークン を作成する
- 作成したトークンを公開登録エンドポイントに渡すと、WireGuard 設定のレスポンスを受け取れる
- 返される設定には
Interface、PrivateKey、Address、Peer、Endpoint、PublicKey、AllowedIPs、PersistentKeepAlive などの項目が含まれる
- ユーザーはサーバーの VPN アドレスに接続して 2FA コード を入力する
- セッションが期限切れになるまで継続する時間は設定ファイルで指定する
Web管理コンソール
- 管理コンソールにログインするには
Webserver.Management.Enabled を true に設定する必要がある
- コンソールでは
sudo ./wag webadmin -add -username <your_username> -password <your-password-here> で Web 管理者アカウントを追加する
- その後、管理リスニングアドレスにアクセスして認証情報を入力する
- Web インターフェース自体では 管理者ユーザーの追加 はできない
- 管理ポータルは外部に公開しないことが推奨され、
ListenAddress を 127.0.0.1 または localhost に設定し、SSH フォワーディングで公開する方法が推奨される
主な設定項目
NumberProxies はクライアントの前段にある信頼できるリバースプロキシの数を指定し、Wag が X-Forward-For を反映してクライアント IP を解析できるようにする
Socket は Wag の制御ソケットで、変更すると同一マシン上で複数の Wag インスタンスを実行できる
NAT はマスカレードの有効・無効を切り替え、有効化するとすべてのトラフィックが VPN サーバーから始まったように見える
NATExcludeRanges は NAT=true のとき NAT から除外する CIDR 範囲を指定する
ExposePorts は VPN サーバーのポートをクライアントに公開し、iptables ルールを追加する
CheckUpdates はデフォルトで無効で、有効化すると管理 UI が新しい Wag バージョンの通知を表示し、api.github.com にアクセスする
Acls はグループとポリシーを定義するが、初回実行時のみ 反映され、実行中は Web UI で編集する
Webserver には公開登録エンドポイント、トンネル MFA ポータル、管理ポータルの設定が含まれる
Wireguard ではデバイス名、リスニングポート、秘密鍵、VPN が担当するサブネット、MTU、DNS サーバーを設定する
Clustering にはクラスタ名、etcd クラスタ状態、ログレベル、witness ノード、データベースの場所、クラスタ証明書関連の設定が含まれる
ACLポリシーの動作
Policies は VPN がキャプチャするルートと、Wag を通過できるポート・プロトコルを定義する
- ルール適用には サブネットのプレフィックス長 を使い、最も具体的なマッチがルートアクセスレベルを決定する
- たとえば
/16 を MFA として定義し、その中の特定の /32 を Allow として定義すると、より具体的な /32 が優先され、MFA なしでアクセス可能になる
- この動作は
v6.0.0 で変更され、それ以前は MFA ルートが常に優先されていた
- 1 つのルートに複数のポリシーが定義されるとポリシーは合成され、MFA ルールが優先される
- まだリリースされていないバージョンからは
Deny ルールでルートアクセスを遮断できる
- 最も具体的なルールは新しいルールの「バケット」を作るため、
/32 バケットに deny しかない場合、同じ /32 の他ポートへのアクセスも許可されない可能性がある
ポートとプロトコルのルール
- サービスアクセスはポートとプロトコルのルールで定義できる
- サポートされるルールタイプは 3 種類
- Any: 個別ルールがないか
any キーワードを使うと、すべてのサービスとポートの組み合わせを許可する
- Single Service:
192.168.1.1 22/tcp 53/udp のように、ホストの特定の TCP・UDP ポートを許可する
- Ranges:
192.168.1.1 22-1024/tcp 23-53/any のように、ポート範囲を指定する
- ポート範囲は低いポートを先に書く必要がある
- ICMP にはポートがないため、
1.1.1.1 icmp のようにポートなしで指定できる
制限事項と開発
- Wag はクライアントごとに 1 つの
AllowedIP のみをサポートする
- この制限は、クライアントからサーバーへ接続する構成に適している
- 主に Linux 専用 で、Windows は一部の作業を行えば動作する可能性がある
- 開発モードでは、トンネルに入ってくるリクエストの IP をクライアント IP として設定するための環境変数を使える
- テスト例として
internal/router で sudo go test -v . を実行する
- 外部からの貢献については、機能追加やバグ修正の際、可能であればテストを書いて Pull Request を開くよう案内している
1件のコメント
Hacker News のコメント
見たところ良さそうだが、いくつか気になる点がある。
curl [http://public.server.address:8080/register_device?key=e83253...](<http://public.server.address/register_device/…;)という例と、「サービスは完全にテンプレート化されたレスポンスを返す」という説明を見ると、登録プロセスではクライアントが秘密鍵を作って公開鍵をサーバーに送るのではなく、サーバーが秘密鍵を作ってクライアントに送っているように見える。さらに例が HTTP なので、人々が HTTP でも問題ない選択肢だと思わないよう、少なくともそこは変えたほうがよさそうだ。
セッションが期限切れになったとき、クライアントがそれに気づく方法があるのかも気になる。あるいは SSH セッションのようなものが単に止まるだけなのだろうか。
Wi-Fi のキャプティブポータル検知のように動く WireGuard クライアントを時々探しているが、理想的には設定ファイルに
persistentkeepaliveのような 1 行を追加して URL を取得し、定期的に確認する方式だとよい。OKが返れば正常、応答がなければネットワーク問題、Locationヘッダーが返ればブラウザをその場所で開いてセッションの再認証などを行う、という形だ。まだそういうクライアントは見つけていない。
pubkeyパラメータも受け取れるので、サーバーが秘密鍵を生成する方式に依存しなくてもよい。ドキュメントが不足していて紛らわしいのは確かだ。最後の質問に答えると、自分が使っている eBPF XDP では
PASS、DROP、REDIRECTしかできない。そのため最も簡単な結果であるPASS/DROPで処理しており、接続は単に止まる。ただしキャプティブポータル検知ページを wag の MFA リストに追加すれば、検知自体は構成でき、その後はブラウザが処理してくれるはずだ。
wag でインターセプトやプロキシのように動作する機能を実装するつもりはない。それをやれば認証の期限切れやログアウト処理は多少簡単になるだろうが、目指す方向ではない。
Mac 向けに Go クライアントを1つ書き、Brew のコマンドライン
wgを使って鍵生成も処理したが、粗削りでsudoが必要だった。ネットワーク権限を使うきちんとしたネイティブアプリがあればよいのだが、自分の手に余る。
セッション管理の問題をすでに扱っているのか、あるいは扱う予定があるのか気になる。
本質的に WireGuard の鍵は永続的なセッションキーのようなものだ。
WireGuard のトランスポート層を実装するソフトウェアがきちんとした VPN サーバーソリューションであるなら、セッション管理も実装すべきだと思う。つまりサーバーとの第2チャネルを通じて、セッションキーを定期的にローテーションし、セッションを終了し、IP アドレスを変更し、新しい経路を設定し、必要なら認証を繰り返すべきだ。
WireGuard の鍵は wag サーバーと通信できるようにするものだが、実際のセッションはユーザーが認証済みかどうかを保持する eBPF マップで管理される。
そのため、誰かが秘密鍵の材料を盗んだとしても、MFA で制限された経路にはアクセスできない。
TOTP コードの総当たりを防いでいるのか気になる。たとえばレート制限や再試行回数制限のようなものだ。
コードをざっと見たが、そうした処理は見つけられなかった。
想定しているシナリオは、誰かがブラウザで TOTP 入力 UI を開き、開発者ツールを立ち上げて、可能なすべての TOTP コードを繰り返し試すケースだ。
特に、なぜデバイスが認証を強制的に試みようとしているのかをユーザーに考えさせる意図もある。そうした状況はエンドポイント侵害を示している可能性があるためだ。
Headscale や Tailscale と非常によく似ているように聞こえる。WireGuard ネットワークを管理する代替手段が見えるのは良いことだ。
機能がどこまで重なり、何が追加され、何が異なり、今後も実装しないものは何なのかを理解できる比較資料があるのか気になる。
ドキュメントに直接の比較は入れていないが、今のところ自分が向かいたい方向ではない。このプロジェクトは自分の必要に合っていて、かなり楽しい。
Wag は、すべてが互いに到達でき、ルールがオーバーレイを定義する Tailscale 式のメッシュというより、明確な境界を望むハブアンドスポーク構成に向いている。
wag と Tailscale はどちらも SSO 連携と、ユーザー保護のための事実上の 2FA を追加する。
どちらにも登録方法と管理のための Web UI があるが、自分は Web 開発が好きではない個人開発者なので、Tailscale のほうがずっと洗練されているはずだ。
確実に実装しないのは、セッションログアウト後にユーザーをリダイレクトするためのインターセプトや TLS プロキシの方面だ。主な理由は、今すぐそれを eBPF でやるのは自分には少し手に余り、動かすにはおそらく書く必要がある DNAT/SNAT コンポーネントを使いたくないからだ。
IPv4 専用というのは、WireGuard を選ぶサイトならもっとモダンな構成を持ち、自前のサービス用 ULA を多用していてもよさそうだ。
ULA に言及したとき、特に念頭に置いていた点があったのか気になる。