スクリプトは、作者が選んだ層で VPN クライアントを起動するが、その層が仕事に必要な層であることは少ない。ベンダーのコマンドライン、OS のダイヤラ、モバイルアプリのインテントは、接続のライフサイクルの異なる時点で戻る。秘密の扱いも異なり、その違いがパスワードをマシン上の全ユーザーに見せるかどうかを決める。スクリプトの最初の行を書く前に層を選ぶこと。

層 1:ベンダーのコマンドライン

多くのデスクトップクライアントは、プロファイル名と、あまりにも頻繁にパスワードを受け取るコマンドラインを提供する。Cisco の旧クライアントはユーザーとパスワードの引数を伴う connect 動詞を受け付けた;macOS は networksetup 経由で組み込みサービスを公開する;NetworkManager は nmcli に応答する。これらの呼び出しはコマンドが終わった時点で戻るが、それはトンネルが使えるようになる時点ではない。導入するバージョンのベンダー自身のドキュメントを読むこと、これらのインターフェースは変わり、いくつかは完全に削除されるからだ。

nmcli connection up "Work VPN"
networksetup -connectpppoeservice "Work VPN"

利点は、ベンダーが自社のプロファイルストアを知っているため、呼び出しが GUI と同じ設定を使うことだ。欠点は、ベンダーごとに独自の動詞を作ることで、あるクライアント向けのスクリプトが別のクライアントには移らない。クライアントが固定でバージョンが固定されているなら、この層を最短経路と考える。

層 2:OS 自身のサービス

Windows ではダイヤラは OS の一部で、ベンダーのプロファイルストアではなく電話帳のエントリに対して動作する。そのためクライアント間の移植性が高く、サービスアカウントからスクリプト化しやすい。同時に、接続が一方の場所に存在して他方に存在しないことがあり、間違った一覧を見る人を混乱させる。

rasdial "Work VPN" user pass
Get-VpnConnection -Name "Work VPN" | Select-Object ConnectionStatus

エントリを作成または削除する必要があるときは、ダイヤルするのではなく PowerShell の面を使う。WMI と cmdlet の層は同じオブジェクトを別名で公開し、削除は切断とは別の操作だ。ダイヤルするだけのスクリプトは、移行後に古いエントリを残す。クリーンアップが起きると約束する前に、自動化がどのストアに書くかを確認すること。

Remove-VpnConnection -Name "Old VPN" -Force
Get-VpnConnection

macOS では、既存のサービスをその場で作るのではなく名前で接続するネットワークサービス一覧が相当する。

認証情報はどの層でも漏れるので、チャネルを選ぶ

コマンドラインで渡すものはすべて、ローカルユーザーごとのプロセステーブルに現れ、通常はシェル履歴にも残る。OpenVPN がサポートする代替は、 秘密をファイルから読む という形で --askpass を使い、ファイル自体にも置いた信頼に見合う権限が必要だ。名前付きパイプは秘密をディスクに書かずに済むが、開けるプロセスなら何でも書き込める。Windows では資格情報マネージャーか、サービスアカウントだけが読めるファイルに秘密を置き、バッチファイルにリテラルとして書かないこと。

openvpn --config /etc/openvpn/work.conf --askpass /etc/openvpn/work.secret --auth-nocache --daemon
ls -l /etc/openvpn/work.secret

同じ規則はトークンとワンタイムコードにも当てはまる:自動化が必要とするなら、コマンドライン引数ではないチャネルが必要だ。仕組みを変えるときは、古い値がどこかのログに残っているのだから、秘密をローテーションすること。動いて漏れるスクリプトは、動くスクリプトではない。

状態を待ち、それから単一の所有者を監督する

これらの呼び出しはどれもトンネルが整う前に戻るので、クライアントが公開する状態をポーリングするか、トンネル内の宛先をタイムアウト付きで試すこと。接続呼び出しの直後にファイルをコピーしたりデータベース接続を開くのは競合であり、ネットワーク障害に見える形で断続的に失敗する。

ip link show tun0
nmcli -t -f GENERAL.STATE connection show "Work VPN"
curl -sS --max-time 5 https://app.internal.example/health -o /dev/null

切断は逆方向に同じ問題を抱える:ルートが消える前にプロセスが終了しうる。クライアントにフックがあるなら、たとえば OpenVPN の up と down スクリプト、または systemd ユニットの ExecStartPost を使うこと(これらは本当に重要な時点で実行される)。フックは呼び出したものの権限で動くので、ネットワークを再起動するフックには想定ではなく明示的な規則が要る。接続のライフサイクルを通じて所有者はちょうど一人に保つこと。

二人の監督者が同じプロファイルを起動・停止すると、結果はプロバイダ障害のように読める再接続ループになる。層が文書化され、秘密が argv でないチャネルを持ち、待機条件がタイムアウト付きの状態確認であり、一つのプロセスがライフサイクルを所有したとき、ランブックは終わる。

関連記事