P2S VPN - Azure 証明書認証 のクライアント設定を GUI で構成できるのか？（否）

# はじめに
以下の記事で Azure P2S VPN の接続の記事を書きました。

https://qiita.com/carol0226/items/283497c10c94785ebd28

上記の記事の内容は完成していて、その通りにやってもらえば 正常に構成できます。

上記の記事では、VPN の接続を手動で追加する方法として、以下の２通りの方法を紹介しています。
- 3-2-① VpnClientSetupAmd64.exe を使用して導入する
- 3-2-② VpnProfileSetup.ps1 を使用して導入する

これらの方法は、EXE や スクリプト を実行する方法です。

今回は、そうではなく OS の GUI を使って出来ないの？・・・という疑問が生じたため、検証した際の結果をまとめました。GUI で設定できれば、それを踏まえて GPO や Intune から VPN プロファイルを配れば良いことになるので、それを見越した検証です。

しかし、結論は、不採用となりました。
さて、その理由とは？

この先は、興味をもっていただいた人だけが読めば十分だと思います。

:::note info
結果がうまく出なかった検証だったので、記事にして 供養できれば・・・との思いで投稿しています。
:::

# 0. 前提
冒頭の記事をもとに、クライアント証明書 を、PC に入れておきます。
これがないと、VPN 接続時に 認証エラーになるので。

# 1. まず最初に試したこと
0. 続いて、VPN プロファイルを作成するために、設定パネル を開いて **ネットワークとインターネット** を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/f98a2589-939e-eaab-82be-a5ec6eda8067.png)
　<br>
0. 左ペインから **VPN** を選択して、**＋VPN 接続を追加する** を押します。
ここから 手動で VPN 接続を追加できないのか・・・というのが、今回の目標です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/fc2c97c2-febe-f366-a5fd-804d59afec83.png)
　<br>
0. 以下のパラメーターを入力します。
アドレス欄には、VPN ゲートウェイ のパブリック IP アドレスを入力しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/fcb1f80d-4282-dc7e-d3b0-9a02a0cd6583.png)

結果、接続できずに 以下の エラーになりました。そう うまくは行かないようです。
どうやら アドレス と 証明書が合っていない・・的な気がします（勘）
DNS 名で登録する必要があるんじゃないか？ という疑問が湧きました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/36d848e0-fb3a-478e-4400-708b68dcba53.png)

# 2. 接続先の名前を ホスト名 にしてみる
0. VPN ゲートウェイ の 左ペインから、**ポイント対サイトの構成** を選択して **VPN クライアントのダウンロード** のボタンを押して、ダウンロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/1a2923c9-00ae-e95a-ec79-11fc22332a05.png)
　<br>
0. 以下のように VPN ゲートウェイ と同一名の ZIP ファイルがダウンロードされるため、解凍 を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/b9eb2adf-f57a-f7d2-ea14-f20716c74294.png)
　<br>
0. 解凍後は、以下のフォルダを確認することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/7ae3d16c-24d9-a91f-118b-3ca9a33879af.png)
　<br>
0. Generic フォルダを開き、Vpnsettings.xml ファイルを 右クリックして **開く** を選択します。
※ Generic フォルダは、VPN ゲートウェイが "IKEv2" or "IKEv2 + SSTP" で構成させている場合のみ生成されるようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/3d8dc247-068f-b6c9-4be0-c4b56a7b826d.png)
　<br>
0. Vpnsettings.xml を開くと、以下のような内容になっています。
このうち 赤枠で囲まれた箇所をコピーします。
※この名前が、VPN ゲートウェイの ユニークな ホスト名 になっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/1432a196-60f0-64e7-17b4-c456d6cba3b3.png)
　<br>
0. 下図の通り、パラメーターを設定します。
ここで、サーバー名 欄に 先ほどコピーした ホスト名 を貼り付けます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/677e6dd8-e793-d8f9-8f07-987640865705.png)
　<br>
0. 以下のように 接続情報 が追加されれば OK です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/d9ef6c5f-8368-5ffb-77be-ac90a18ae70c.png)
　<br>
0. 初回のみ 以下のメッセージが表示されるため **接続** を押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/cef0a175-8fdf-b4ec-3991-ee94fa989870.png)
　<br>
0. 以下のように表示されれば、VPN 接続 は正常に行われているように見えます。
よし、これで うまく繋がったのか？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/926c411b-0324-9543-85df-f8e6c1957a1c.png)

さて、これで 仮想ネットワーク上の VM とも通信をすることが出来て、うまく行ったような気になりました。
しかし、色々と検証してみると、インターネットのサイトと通信することができません。

routeprint コマンドで ルーティングテーブル を見てみると、全ての通信が VPN となるように構成されています（つまり フルトンネルの状態）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/21c6363b-01f4-cb02-dbd3-695e979f683c.png)

VPN ゲートウェイ は、Azure 仮想ネットワーク に存在しないアドレスは、パケットを破棄する仕様となっているため、これでは問題が出てしまいます。

# 3. スタティックルートを追加してみる
インターネットへのアクセスは、VPN を経由せず 仮想ネットワーク への通信のみを VPN 経由となるように ルートテーブルを調整してみました。

route add 0.0.0.0 mask 128.0.0.0 192.168.0.1
route add 128.0.0.0 mask 128.0.0.0 192.168.0.1
※192.168.0.1 は、自宅LAN のデフォルトゲートウェイ です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/6a2d6f92-afca-7a49-7f17-9e6fd021468b.png)

ネットワークに詳しい人なら判ると思いますが、0.0.0.0/0 というネットワーク（赤枠）を 0.0.0.0/1 と 128.0.0.0/1 という２つのネットワーク（緑枠）に分割しています。こうする事で、/0 よりも /1 の方が より詳細なネットワークになるため、ロンゲストマッチが適用されて 優先されたルートになります。
|ネットワーク<br>アドレス|開始<br>アドレス|終了<br>アドレス|デフォルト<br>ゲートウェイ|
|:-:|:-:|:-:|:-:|
|0.0.0.0/0|0.0.0.0|255.255.255.255|リンク上<br>（つまり VPNトンネル）|
|0.0.0.0/1|0.0.0.0|127.255.255.255|192.168.0.1|
|128.0.0.0/1|128.0.0.0|255.255.255.255|192.168.0.1|

※もう１つの 0.0.0.0 のルートは、メトリックが 小さいほうが優先されるので、使われません。

これで、インターネットへのアクセスを、デフォルトゲートウェイ に戻すことに成功しました。
ですが、そうすると 仮想ネットワーク への通信ができません。

以下のコマンドを実行して 仮想ネットワーク へのルーティングを追加します。
route add 10.10.0.0 mask 255.255.0.0 0.0.0.0 IF 43
※IF 43 は、VPN で追加された IF の番号です（route print で確認できます）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/e7e23aa0-3513-3015-ab8b-544c86b0ce3a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/94e24055-2c56-2ae3-118b-f49f824358f0.png)

これで、仮想ネットワーク への通信が戻りました。
結果的に、インターネットにもアクセスできるし、仮想ネットワーク にもアクセスができており、スプリットトンネルが構成できました。

:::note alert
**課題**
これで、通信は問題なくできるようになりましたが、route add コマンドだと VPN の 接続 と 切断 を行うたびに、ルーティングの変更を行う必要が生じます。

**-p** を付加して route add を実行すれば、恒久的なルートを追加できますが、PC を移動して 別のネットワークに接続した場合に、デフォルトゲートウェイが変更されかもしれません。その場合に 通信が出来ずに困る場合が生じます。

つまり、検証レベルでは動くけど、運用に支障をきたしてしまう・・・という事になります。
:::

# 4. 運用を考慮したスクリプトを考慮してみる
現在の デフォルトゲートウェイ を 読み取って、0.0.0.0/1 と 128.0.0.0/1 のルートに割り当てる方法を考えてみました。

以下のスクリプトを Copilot に考えてもらったところ、うまく動作しました。
Copilot スゴイ。

~~~
# インターネット宛のルートを より詳細なネットワークとして定義します。
# デフォルトゲートウェイの取得
$defaultGateway = (Get-NetRoute -DestinationPrefix "0.0.0.0/0").NextHop
# 取得したデフォルトゲートウェイを使用して route コマンドを実行
$routeCommand1 = "route add 0.0.0.0 mask 128.0.0.0 $defaultGateway"
$routeCommand2 = "route add 128.0.0.0 mask 128.0.0.0 $defaultGateway"
Invoke-Expression $routeCommand1
Invoke-Expression $routeCommand2
~~~

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/5341f2ff-8ca6-5002-f5b8-8de5e71e32d4.png)
ちゃんと、意図したルートが追加されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/8a8456cf-c45f-1ce4-13db-19d15ddad956.png)

さて、このスクリプトを NIC が持つ アドレスが変更されたタイミングで実行させられれば良さそうです。
タスクスケジューラ で、イベントログを監視して、スクリプトを実行するか・・・

:::note warn
**初心に返ってみる**
ここまで、無心に進めてしまいましたが、そもそも GUI から設定する方法を見い出そうとしていたのに、スクリプトが必要なのは、本末転倒です。
しかも、タスクスケジューラ とか仕込んだら、それの展開も面倒だし、動作しても タイムラグとかあるんじゃないか・・・・

すでに、GPO や Intune でシンプルに配れる構造ではなくなってきている（汗）
:::

# 5. 純正のスクリプトを解析してみる


0. 以下のファイル（VpnProfileSetup.ps1）を メモ帳 で開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/9d01e42d-cbf7-986f-557d-92f5d33ae048.png)
　<br>
0. 最下段までスクロールすると、以下のコマンドが見つかりました。
Add-VpnConnectionRoute というコマンドがあるんですね。
指定した VPN プロファイル を使って 接続を実施した時だけ採用される ルーティングだそうです。

~~~
# VPN 接続時に追加する Azure 側の ネットワークアドレス
Add-VpnConnectionRoute -ConnectionName AZ-DefaultVNet -DestinationPrefix 10.10.0.0/16
Add-VpnConnectionRoute -ConnectionName AZ-DefaultVNet -DestinationPrefix 10.90.0.0/24
~~~

これと同じコマンドを 仮想ネットワーク用のルートとして仕込めば良さそうです。

さらに、インターネット向けのルートを VPN 接続の時に VPN に向かないように仕込んでいる箇所はないものか？
・・・と思ったら、ありました。
かなり、ややこしい事をやっていそうです。

~~~
{
	((Get-Content -Raw -path '~\AppData\Roaming\Microsoft\Network\Connections\Pbk\rasphone.pbk') -replace "(?s)(.*)DisableClassBasedDefaultRoute=0(.*)","`$1DisableClassBasedDefaultRoute=1`$2") | Set-Content -path '~\AppData\Roaming\Microsoft\Network\Connections\Pbk\rasphone.pbk'
	((Get-Content -Raw -path '~\AppData\Roaming\Microsoft\Network\Connections\Pbk\rasphone.pbk') -replace "(?s)(.*)PlumbIKEv2TSAsRoutes=0(.*)","`$1PlumbIKEv2TSAsRoutes=1`$2") | Set-Content -path '~\AppData\Roaming\Microsoft\Network\Connections\Pbk\rasphone.pbk'
	((Get-Content -Raw -path '~\AppData\Roaming\Microsoft\Network\Connections\Pbk\rasphone.pbk') -replace "(?s)(.*)AutoTiggerCapable=0(.*)","`$1AutoTiggerCapable=1`$2") | Set-Content -path '~\AppData\Roaming\Microsoft\Network\Connections\Pbk\rasphone.pbk'
    Write-Host "Edited pbk file with required changes"
}
catch
{
	Write-Host "Error while editing the PBK file: $_"
}
~~~

デフォルトゲートウェイ の値をいじってそうなのですが、何をやっているのか？
Copilot に聞いてみました。

**プロンプト**
以下のスクリプトが、VPNの接続を行った際に、デフォルトゲートウェイ を制御しているのかどうかを分析して判りやすく教えてください。
（このあと、先ほどのスクリプトを貼り付けます）

**Copilot の回答**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/381b0e07-89c4-fb61-a1d2-1cac5aac50cd.png)

どうやら、このスクリプトの部分で デフォルトゲートウェイ を良さげに制御して、フルトンネルにならないようにしているみたいです。VPN 接続時と そうではない時に、切り替わるようにもなっている。
純正のスクリプトでも、こんな面倒なことをして実現しているのか・・・

# GUI で設定する場合の最終形
0. 以下の設定を実施して、VPN 接続プロファイルが作られます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/1b8299db-7a00-96d2-e06f-8bad9cedcdf3.png)
　<br>
0. GUI 設定後に、以下のコマンドを叩いて、ルーティングを構成すれば OK です。
~~~
{
	((Get-Content -Raw -path '~\AppData\Roaming\Microsoft\Network\Connections\Pbk\rasphone.pbk') -replace "(?s)(.*)DisableClassBasedDefaultRoute=0(.*)","`$1DisableClassBasedDefaultRoute=1`$2") | Set-Content -path '~\AppData\Roaming\Microsoft\Network\Connections\Pbk\rasphone.pbk'
	((Get-Content -Raw -path '~\AppData\Roaming\Microsoft\Network\Connections\Pbk\rasphone.pbk') -replace "(?s)(.*)PlumbIKEv2TSAsRoutes=0(.*)","`$1PlumbIKEv2TSAsRoutes=1`$2") | Set-Content -path '~\AppData\Roaming\Microsoft\Network\Connections\Pbk\rasphone.pbk'
	((Get-Content -Raw -path '~\AppData\Roaming\Microsoft\Network\Connections\Pbk\rasphone.pbk') -replace "(?s)(.*)AutoTiggerCapable=0(.*)","`$1AutoTiggerCapable=1`$2") | Set-Content -path '~\AppData\Roaming\Microsoft\Network\Connections\Pbk\rasphone.pbk'
    Write-Host "Edited pbk file with required changes"
}
{
	Write-Host "Error while editing the PBK file: $_"
}

Add-VpnConnectionRoute -ConnectionName AZ-DefaultVNet -DestinationPrefix 10.10.0.0/16
Add-VpnConnectionRoute -ConnectionName AZ-DefaultVNet -DestinationPrefix 10.90.0.0/24
~~~

これで、目的に近づくことができました。


でも、これを動かすようにしても、GUI の設定を行って ps1 のスクリプトを実行するという２度手間になるだけ。
それならば、VpnProfileSetup.ps1 を実行するだけで、全部やってくれるので、そちらの方が良いのでは・・・

# まとめ
結局、GUI で、 VPN 設定を追加する方法だと、フルトンネル になってしまいます。
そして、VPN ゲートウェイ は、フルトンネルに対応していないので、そもそも相性が悪い。

ルートを調整すれば良いが、VPN 接続時と そうではない時に、動的にルートを切替える仕組みが必要。

それを自作しようとしたが、面倒なので 純正スクリプトを解析した結果、純正でも 似たようなことをやっていた。これ採用で十分。

つまり、OS の GUI の VPN の設定だけでは どうにもならない。

# 結論
Azure VPN P2S を利用する場合は、GUI から利用するのはあきらめましょう。

VpnProfileSetup.ps1 を実行するだけで 全部やってくれます。
正規で用意されている手順 を採用するしかないと思った方が良さそうです。