ほとんどすべての小規模な OpenVPN ゲートウェイで、二つの名前の問題が現れる。接続中のノート PC には証明書の名前では到達できず、トンネルの向こう側のサービスには名前がまったくない。どちらも 一 つの問題に見えるが、一方のアドレスはセッションごとに動き、もう一方は動かないため、必要な対処は正反対だ。何かを編集する前に、どちらの種類の名前を足すのかを決めよう。

ゲートウェイ単位ではなく名前単位で決める

テストは簡単で、クライアントが再接続したとき、このアドレスは変わるか。変わるなら、名前は VPN セッションから生成しなければならない。変わらないなら、読んで確認できる静的エントリに入る。

Name kindSource of truthWhere it lives
Connected clientThe address OpenVPN just assignedGenerated hosts file, rewritten per session
Service behind the tunnelA fixed address you choseStatic address= drop-in
Anything with a certificate nameThe certificate, not the poolEither, but the name must match the certificate

両者を混ぜるのがよくある失敗で、生成ファイルに固定したサービスは、スクリプトが最初に書き直した時点で消える。静的エントリに書いたクライアントは、そのアドレスを最後に持っていた相手を指す。どちらの誤りも、名前が誤ったマシンに解決されるまで見えない。その頃には、調べている人は DNS が壊れていると思い込み、ファイルを疑わない。

動的な経路:learn-address とプール

OpenVPN は割り当てを知っているので、教えてもらおう。次のサーバーオプションで、割り当ては永続化され、スクリプトから扱えるようになる:

client-config-dir /etc/openvpn/ccd
ifconfig-pool-persist /etc/openvpn/ip-pool 60
script-security 2
learn-address /etc/openvpn/learn-address.sh

learn-address は、アドレスが追加・更新・削除されるたびにスクリプトを実行し、操作・アドレス・コモンネームを渡す。最小限のスクリプトはクライアントごとに 一 行を追加し、削除時にその行を消す:

#!/bin/sh
HOSTS=/etc/openvpn/hosts.lan
case "$1" in
  add|update) echo "$2 $3" >> "$HOSTS" ;;
  delete)     sed -i "/^$2 /d" "$HOSTS" ;;
esac
kill -HUP $(cat /var/run/dnsmasq.pid)

tun デバイスでは 二番目の引数がクライアントのトンネルアドレスなので、その行はそのまま使える。tap デバイスでは OpenVPN は代わりにクライアントの MAC アドレスを渡すが、tap セグメントは IPv4 以外のプロトコルも運べるため、その場合は何かを書く前にプールファイルをコモンネームで引く必要がある。

生成ファイルは、いま接続しているのは誰かのスナップショットだと明確にしよう。名前は切断で消えるが、ネットワーク間を移動するノート PC にはまさに望ましい挙動で、一晩中動かすサーバーにはまさに望ましくない挙動だ。

静的な経路:address= とドメイン展開

システムの hosts ファイルではなく生成ファイルを dnsmasq に向け、ドメインを与えて短い名前が完全修飾名に展開されるようにする:

domain=corp.example
no-hosts
addn-hosts=/etc/openvpn/hosts.lan
listen-address=127.0.0.1,10.8.0.1
bind-interfaces

domain を設定すると、10.8.0.6 alice のような行は alice にも alice.corp.example にも応答する。listen-address はトンネルのゲートウェイと loopback に限定しよう(LAN インターフェースでうっかり公開されたリゾルバは、提供するつもりのなかったサービスだから)。

プールから決して来ないサービスには独自のエントリを与える。ディストリビューションの設定ディレクトリ内の別の drop-in ファイルに置き、パッケージ更新で上書きされないようにする:

address=/files.corp.example/10.8.0.20
address=/db.corp.example/10.9.0.5

リゾルバを渡さなければ何も起きない

誰も問い合わせないリゾルバは何も変えない。サーバーがそれを広告しなければならない:

push "dhcp-option DNS 10.8.0.1"
push "dhcp-option DOMAIN corp.example"

Windows クライアントは両方のオプションを自分で適用するが、ほとんどの Linux クライアントは適用しない。ディストリビューションの update-resolv-conf フックや NetworkManager の DNS 処理のような対応するリゾルバ統合、あるいは手動の検索ドメインとネームサーバーが必要だ。push が守られたと決めつけず、クライアントの実際のリゾルバを確認しよう(無視された push は、応答しないサーバーとまったく同じに見えるからだ)。

三つの層で検証する

まずサーバーに直接問い合わせ、次にクライアント、最後にアプリケーションへ:

dig +short @10.8.0.1 alice.corp.example
getent hosts files.corp.example

直接の問い合わせが答え、アプリケーションが答えないなら、原因はゾーンではなくクライアントへの受け渡しにある。名前が解決しても接続が失敗するなら、名前は正しく、次に見るべきはルートかファイアウォールだ。

二つのカテゴリを、どの名前が生成され、どれが固定され、それぞれがどの証明書と一致すべきかとともに設定の横に書き留めよう。次にサーバーを追加する人は、最初に見つけたパターンをそのまま真似る。

関連記事