ポイント対サイトVPN接続でAzure VMへプライベート・アクセス

## はじめに
AzureのプライベートVMインスタンスにアクセスするには、一般的にBashionサブネットと呼ばれる踏み台サーバー(パブリックVMインスタンス)を経由して接続する形を取るかと思います。プライベートVMインスタンスはパブリックIPアドレスを持たないインスタンスの為、基本はAzure VNET(Virtual Network)内の通信しか行うことができません。
ですが、下記リンク先に記載のAzureポイント対サイトVPN接続という接続を行うことで、Azure VNET外にあるPCから直接プライベートVMインスタンスにアクセスすることが可能になります。また、接続元のPCにパブリックIPアドレスが付与されている必要もありません。

https://learn.microsoft.com/ja-jp/azure/vpn-gateway/point-to-site-about

WindowsからAzureへポイント対サイト接続する場合の構築事例などの記事はよく見かけますが、Linuxから接続する場合の記事はあまり見たことがありません。それ故、今回はせっかくなので、LinuxからAzureへポイント対サイト接続環境を構築、稼働検証を実施した際の構築内容や検証結果をご紹介いたします。

なお、AWSでは類似の機能として、Client VPNと呼ばれるサービスが提供されています。こちらについても昨年環境構築・検証した内容を下記の記事で紹介しておりますので、ご参考ください。

https://qiita.com/duelist2020jp/items/4ea07456de2fee99296b

## 検証環境構築
### 今回作成する環境
今回作成する環境の構成イメージは下記になります。
![構成図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/a7f7da6c-dd49-f90e-ae47-9b41b60828b8.png)
PCからインターネットを経由し、Azure VNET内のFrontend SubnetにあるVMインスタンスにプライベート通信するために必要なAzure固有のリソースは下記の2つです。

* VPN Gateway 
  * 暗号化されたトラフィックをインターネット経由でAzure VNETとオンプレミス間でプライベート通信を行うためのサービス。
* GatewaySubnet
  * VPN Gatewayを使用するために必要なサブネット。GatewaySubnetは、VNETのIPアドレス範囲の一部であり、VPN Gatewayのリソースとサービスが使用するIPアドレスを含む。 なお、サブネット名は、「GatewaySubnet」以外の名前を指定することはできません。

なお、接続元のPCで動かすLinuxは、Redhat Enterprise Linux V8.9を使用しています。

### 使用プロトコルの選定
Azureポイント対サイトVPN接続で使用できるプロトコルは下記の3種類から選択できます。
* OpenVPN
  * SSL/TLSベースのVPNプロトコル。Android、iOS(V11以上)、Windows、Linux、MacOS(V10.13以上)から接続する場合に使用可能。
* SSTP (Secure Socket Tunneling Protocol)
  * TLSベースの独自VPNプロトコル。Windowsから接続する場合のみ使用可能。 
* IKEv2 VPN
  * 標準ベースのIPSecプロトコル。 MacOS(V10.11以上)から接続する場合のみ使用可能。

今回はLinuxからポイント対サイトVPN接続を行うため、プロトコルはOpenVPNを使用することにしました。

### 認証方式の選定
Azureポイント対サイトVPN接続では、証明書認証、RADIUS認証、Microsoft Entra ID認証という3つの認証方式があります。今回は証明書認証を使用することにしました。

#### 証明書作成用パッケージの導入
まずは接続元のLinuxにて、CAルート証明書とクライアント証明書を作成します。AWSのClient VPN接続では、サーバー証明書も作成してAWS側にアップロードを行う必要がありましたが、Azureのポイント対サイト接続ではCAルート証明書のみをアップロードすれば良いので、今回はサーバー証明書の作成は不要です。
OpenVPNはSSL/TLSベースのVPN接続プロトコルですので、基本はopensslコマンドを使用して各種証明書を作成することになりますが、「easy-rsa」と呼ばれる各種証明書を容易に作成可能なパッケージが提供されています。

https://github.com/OpenVPN/easy-rsa

ですので、最初にこのパッケージをインストールします。
```
$ sudo dnf install easy-rsa
```
インストールが終わると、「/usr/share/easy-rsa/」というディレクトリが作成されます。後続で使用する「easyrsa」というコマンドもこの中に入っていますが、毎回こちらのディレクトリに移動、あるいは、指定して実行するのも不便ですので、ホーム・ディレクトリに適当なディレクトリ(ここでは、easy-rsaという名前のディレクトリとします。)を作成して、そこにシンボリック・リンクを張っておきます。

```
$ ln -s /usr/share/easy-rsa/* ~/easy-rsa/
```
シンボリック・リンク確立後は、ホーム・ディレクトリに作成した「easy-rsa」ディレクトリの中身は下記のようになります。
```
$ ls -l
合計 3
lrwxrwxrwx. 1 testuser testuser   21  3月 30 19:10 3 -> /usr/share/easy-rsa/3
lrwxrwxrwx. 1 testuser testuser   23  3月 30 19:10 3.0 -> /usr/share/easy-rsa/3.0
lrwxrwxrwx. 1 testuser testuser   25  3月 30 19:10 3.0.8 -> /usr/share/easy-rsa/3.0.8
```
以降の証明書作成で使用する「easyrsa」コマンドは、「~/easy-rsa/3.0.8/」ディレクトリの中にあります。

#### 証明書作成
「~/easy-rsa/3.0.8/」のディレクトリに移動し、以下の作業を行います。
* PKI環境の初期化
```
$ ./easyrsa init-pki

init-pki complete; you may now create a CA or requests.
Your newly created PKI dir is: /home/testuser/easy-rsa/pki
```
* CAルート証明書と秘密鍵の作成
```
$ ./easyrsa build-ca nopass
Using SSL: openssl OpenSSL 1.1.1k  FIPS 25 Mar 2021
：
：
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Common Name (eg: your user, host, or server name) [Easy-RSA CA]:My-Corp-CA  <== CA名を指定。自己署名とするので、何でもOK。

CA creation complete and you may now import and sign cert requests.
Your new CA certificate file for publishing is at:
/home/testuser/easy-rsa/pki/ca.crt
```
* クライアント証明書の作成
```
$ ./easyrsa build-client-full azure_client nopass    <== azure_clientは、クライアント証明書の名前
Using SSL: openssl OpenSSL 1.1.1k  FIPS 25 Mar 2021
:
:
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :ASN.1 12:'azure_client'
Certificate is to be certified until Jul 04 07:20:51 2026 GMT 

Write out database with 1 new entries
Data Base Updated
```
作成された、CAルート証明書とクライアント証明書の内容については、それぞれ、下記のコマンドで確認可能です。

* CAルート証明書の確認
```
$ ./easyrsa show-ca
Using SSL: openssl OpenSSL 1.1.1k  FIPS 25 Mar 2021

Showing  details for 'ca'.
This file is stored at:
/home/testuser/easy-rsa/pki/ca.crt

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            4c:b3:1b:79:57:67:15:1a:1c:12:99:e2:d6:1d:c5:d6:bf:26:63:a8
        Signature Algorithm: sha256WithRSAEncryption
        Issuer:
            commonName                = My-Corp-CA
        Validity
            Not Before: Mar 31 07:18:52 2024 GMT
            Not After : Mar 29 07:18:52 2034 GMT
        Subject:
            commonName                = My-Corp-CA
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                76:7F:48:27:94:97:05:19:05:D9:66:38:4F:B2:9F:CB:13:60:E0:69
            X509v3 Authority Key Identifier:
                keyid:76:7F:48:27:94:97:05:19:05:D9:66:38:4F:B2:9F:CB:13:60:E0:69
                DirName:/CN=My-Corp-CA
                serial:4C:B3:1B:79:57:67:15:1A:1C:12:99:E2:D6:1D:C5:D6:BF:26:63:A8

            X509v3 Basic Constraints:
                CA:TRUE
            X509v3 Key Usage:
                Certificate Sign, CRL Sign
```
* クライアント証明書の確認
```
$ ./easyrsa show-cert azure_client
Using SSL: openssl OpenSSL 1.1.1k  FIPS 25 Mar 2021

Showing cert details for 'azure_client'.
This file is stored at:
/home/testuser/easy-rsa/pki/issued/azure_client.crt

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            99:ee:82:90:48:09:65:2e:33:b3:d8:78:36:f2:6a:38
        Signature Algorithm: sha256WithRSAEncryption
        Issuer:
            commonName                = My-Corp-CA
        Validity
            Not Before: Mar 31 07:20:51 2024 GMT
            Not After : Jul  4 07:20:51 2026 GMT
        Subject:
            commonName                = azure_client
        X509v3 extensions:
            X509v3 Basic Constraints:
                CA:FALSE
            X509v3 Subject Key Identifier:
                3C:01:FF:9C:1A:69:5D:DA:EE:02:36:1B:D0:D3:A5:D6:05:B0:DC:AE
            X509v3 Authority Key Identifier:
                keyid:76:7F:48:27:94:97:05:19:05:D9:66:38:4F:B2:9F:CB:13:60:E0:69
                DirName:/CN=My-Corp-CA
                serial:4C:B3:1B:79:57:67:15:1A:1C:12:99:E2:D6:1D:C5:D6:BF:26:63:A8

            X509v3 Extended Key Usage:
                TLS Web Client Authentication
            X509v3 Key Usage:
                Digital Signature
```               
なお、クライアント証明書の秘密鍵は「/home/testuser/easy-rsa/pki/private/」ディレクトリ内に作成されています。

### 検証環境プロビジョニング用terraformコード作成
今回の環境構築用に作成したterraformコードのディレクトリ構造は下記になります。
```
P2S_Connect
├── main.tf
├── modules/
│   ├── frontend-setup/
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   ├── rg-setup/
│   │   ├── main.tf
│   │   └── outputs.tf
│   ├── vminstance-setup/
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   └── vng-setup/
│       ├── main.tf
│       └── variables.tf
├── outputs.tf
└── versions.tf
```
ルート・ディレクトリに配置しているmain.tf、outputs.tf。versions.tfは下記になります。

<details><summary>main.tf</summary>

```tf
# Configure the Microsoft Azure Provider
provider "azurerm" {
  features {}
}

# Create Resource Group
module "rg-setup" {
    source = "./modules/rg-setup"
}

#Create Frontend Network
module "frontend-setup" {
    source = "./modules/frontend-setup"

    rg_name = module.rg-setup.rg_name
    rg_location = module.rg-setup.rg_location

    depends_on = [
        module.rg-setup
    ]
}

#Create Virtual Machine
module "vminstance-setup"{
    source = "./modules/vminstance-setup"

    rg_name = module.rg-setup.rg_name
    rg_location = module.rg-setup.rg_location

    nic_id = module.frontend-setup.nic_id

    depends_on = [
        module.frontend-setup
    ]
}

#Create Virtual Private Gateway
module "vpg-setup" {
    source = "./modules/vng-setup"

    rg_name = module.rg-setup.rg_name
    rg_location = module.rg-setup.rg_location
    vnet_name = module.frontend-setup.vnet_name

    depends_on = [
        module.vminstance-setup
    ]
}

```
</details>

<details><summary>outputs.tf</summary>

```tf
output "frontend_privateIp"{
    value = module.vminstance-setup.frontend_privateIp
}
```
</details>

<details><summary>versions.tf</summary>

```tf
terraform{
    required_providers{
        azurerm={
            source  = "hashicorp/azurerm"
            version = "=3.98.0"
        }
    }
}
```
</details>

modulesディレクトリ配下に配置した各種モジュールの用途は以下になります。

* rg-setup ： リソース・グループの作成

<details><summary>maint.tf</summary>

```tf
resource "azurerm_resource_group" "rg"{
    name = "My-RG"
    location = "Japan East"
}
```
</details>

<details><summary>outputs.tf</summary>

```tf
output "rg_name" {
    value = azurerm_resource_group.rg.name
}

output "rg_location" {
    value = azurerm_resource_group.rg.location
}
```
</details>

* frontend-setup ： VMインスタンスを配置するプライベート・サブネット、NSGの作成

<details><summary>main.tf</summary>

```tf
#Create Virtual Network
resource "azurerm_virtual_network" "vnet"{
    name = "Test-VNET"
    resource_group_name = "${var.rg_name}"
    location = "${var.rg_location}"
    address_space = ["10.0.0.0/16"]

    tags = {
        Name = "Test"
    }

}

#Create Frontend Subnet
resource "azurerm_subnet" "frontend_subnet"{
    name = "FrontendSubnet"
    resource_group_name = "${var.rg_name}"
    virtual_network_name = azurerm_virtual_network.vnet.name
    address_prefixes = ["10.0.1.0/24"]

    depends_on = [ 
        azurerm_virtual_network.vnet
    ]
}

#Create Frontend Security Group
resource "azurerm_network_security_group" "nsg"{
    name = "TestVM-Security-Group"
    resource_group_name = "${var.rg_name}"
    location = "${var.rg_location}"

    security_rule{
        name = "sg-ssh22"
        protocol = "Tcp"
        priority = 300
        direction = "Inbound"
        access = "Allow"
        source_port_range = "*"
        destination_port_range = 22
        source_address_prefix = "*"
        destination_address_prefix = "*"
    }
    security_rule{
        name = "internet-ping"
        protocol = "Icmp"
        priority = 330
        direction = "Inbound"
        access = "Allow"
        source_port_range = "8"
        destination_port_range = "*"
        source_address_prefix = "*"
        destination_address_prefix = "*"
    }

    tags={
        Name = "Test-SG"
    } 
}

#Association between Subnet and Network Security Group
resource "azurerm_subnet_network_security_group_association" "nsga"{
    network_security_group_id = azurerm_network_security_group.nsg.id
    subnet_id = azurerm_subnet.frontend_subnet.id
}

#Create Network Interface Card
resource "azurerm_network_interface" "nic"{
    name = "Test-NIC"
    resource_group_name = "${var.rg_name}"
    location = "${var.rg_location}"

    ip_configuration{
        name = "internal"
        private_ip_address_allocation = "Dynamic"
        subnet_id = azurerm_subnet.frontend_subnet.id
    }

    tags = {
        Name = "Test-NIC"
    }

    depends_on = [
        azurerm_subnet.frontend_subnet
     ]

}

#Association between Network Interface Card and Network Security Group
resource "azurerm_network_interface_security_group_association" "nisga"{
    network_interface_id = azurerm_network_interface.nic.id
    network_security_group_id = azurerm_network_security_group.nsg.id

    depends_on = [
        azurerm_network_interface.nic,
        azurerm_network_security_group.nsg
     ]
}
```
</details>

<details><summary>variables.tf</summary>

```tf
variable "rg_name" {
    type = string
    description = "resource group name"
}

variable "rg_location" {
    type = string
    description = "resource group location"
}
```
</details>

<details><summary>outputs.tf</summary>

```tf
output "vnet_name"{
    value = azurerm_virtual_network.vnet.name
}

output "nic_id"{
    value = azurerm_network_interface.nic.id
}
```
</details>

* vminstance-setup ： VMインスタンスの作成

<details><summary>main.tf</summary>

```tf
#Create Linux VM Instance
resource "azurerm_linux_virtual_machine" "lvm"{
    name = "Test-VM"
    resource_group_name = "${var.rg_name}"
    location = "${var.rg_location}"
    size = "Standard_B1s"
    admin_username = "adminuser"
    
    network_interface_ids = [
        "${var.nic_id}",
    ]

    admin_ssh_key{
        username = "adminuser"
        public_key = file("~/.ssh/az-vm-rsa-key.pub")
    }

    os_disk{
        caching = "ReadWrite"
        storage_account_type = "Standard_LRS"
    }

    source_image_reference{
        publisher = "Canonical"
        offer = "0001-com-ubuntu-server-jammy"
        sku = "22_04-lts-gen2"
        version = "latest"
    }

    tags = {
        Name = "Test"
    }

}
```
</details>

<details><summary>variables.tf</summary>

```tf
variable "rg_name" {
    type = string
    description = "resource group name"
}

variable "rg_location" {
    type = string
    description = "resource group location"
}

variable "nic_id"{
    type = string
}
```
</details>

<details><summary>outputs.tf</summary>

```tf
output "frontend_privateIp"{
    value = azurerm_linux_virtual_machine.lvm.private_ip_address
}
```
</details>

* vng-setup ： ゲートウェイ・サブネットとVPNゲートウェイの作成

<details><summary>main.tf</summary>

```tf
#Create Gateway Subnet
resource "azurerm_subnet" "gateway_subnet"{
    name = "GatewaySubnet"
    resource_group_name = "${var.rg_name}"
    virtual_network_name = "${var.vnet_name}"
    address_prefixes = ["10.0.2.0/24"]
} 

#Create Public IP for Gateway Subnet
resource "azurerm_public_ip" "gateway_publicip" {
    name = "Test-VNG-PublicIP"
    resource_group_name = "${var.rg_name}"
    location = "${var.rg_location}"
    allocation_method = "Static"
    sku = "Standard"

    tags = {
        Name = "Test"
    }
}
  
#Create Virtual Network Gateway
resource "azurerm_virtual_network_gateway" "gateway"{
    name = "Test-VNG"
    resource_group_name = "${var.rg_name}"
    location = "${var.rg_location}"
    ip_configuration {
      name = "Test-VNET-Gateway-Config"
      subnet_id = azurerm_subnet.gateway_subnet.id
      public_ip_address_id = azurerm_public_ip.gateway_publicip.id
    }
    type = "Vpn"
    vpn_type = "RouteBased"
    enable_bgp = false
    active_active = false
    sku = "VpnGw2"
    generation = "Generation1"

    vpn_client_configuration {
      address_space = ["192.168.20.0/24"]
      root_certificate {
        name = "My-Corp-CA"
        public_cert_data = file("~/Documents/Azure/cert/ca.crt")
      }
      vpn_client_protocols = ["OpenVPN"]
    }

    depends_on = [ 
        azurerm_subnet.gateway_subnet,
        azurerm_public_ip.gateway_publicip
    ]
}
```
</details>

<details><summary>variables.tf</summary>

```tf
variable "rg_name" {
  type = string
  description = "resource group name"
}

variable "rg_location" {
  type = string
  description = "resource group location"
}

variable "vnet_name" {
  type = string
  description = "virtual network name"
}
```
</details>

#### コード作成時の考慮点
* VPNゲートウェイ作成時の負荷軽減

当初は、frontend-setupとvng-setupを1つのモジュールとして作成したかったのですが、そうすると通常だと10秒～20秒程度で処理が完了するNIC(ネットワーク・インタフェース・カード)の作成や、セキュリティー・グループのサブネットへのアタッチ処理が、<font color="red">30分経過しても終わらない状況になってしまいました。</font>どうもVPNゲートウェイの作成と並行に処理が走ることでAzureに負荷がかかってしまうのかもしれません。それ故、frontend-setupとvng-setupとモジュールを分けることにしました。


* CAルート証明書アップロード時の注意点

vng-setupのmain.tfの中で、CAルート証明書をアップロードする下記の処理があります。
```tf
vpn_client_configuration {
      address_space = ["192.168.20.0/24"]       <== 接続元のIPアドレス範囲を指定
      root_certificate {
        name = "My-Corp-CA"
        public_cert_data = file("~/Documents/Azure/cert/ca.crt")  <== アップロードするCAルート証明書を指定
      }
      vpn_client_protocols = ["OpenVPN"]
    }
```

:::note warn
通常、証明書の中身は「------BEGIN CERTIFICATE-----」と「-------END CERTIFICATE------」で囲まれていますが、証明書をアップロードする際には、証明書の中身から「------BEGIN CERTIFICATE-----」と「-------END CERTIFICATE------」を削除したものをアップロードする必要があります。そうしないと、プロビジョニング時にエラーになりますので、ご注意ください。
:::

terraformのコードを実行する際は、事前にAzureへの認証が完了している必要があります。Azureへの認証手順については、下記の記事に纏めていますので、そちらを参照の上実施ください。

https://qiita.com/duelist2020jp/items/c2b99ecfba2196ae65fc

## 接続確認検証
上記で紹介したterraformコードのルート・ディレクトリに移動し、「terraform validate」→ 「terraform plan」→「terraform apply」の順にコマンドを実行して、terraformのコードを実行します。
AzureのドキュメントにもVPNゲートウェイのプロビジョニングは45分以上作成に要する、terraformのドキュメントにも30分～1時間要すると記載されていますが、筆者の環境では20分程度でプロビジョニングが完了しました。ちなみに、VPNゲートウェイの削除は10分程度で終わりましたので、削除の時間はプロビジョニング時の半分程度と見積もっておけば良いかと思います。

### VPNクライアント構成ファイルの入手
VPNゲートウェイのプロビジョニングが終わると、AzureポータルからVPNクライアント構成ファイルをダウンロードすることができるようになります。リソース・グループの中から、作成されたVPNゲートウェイを選択後、VPNゲートウェイの「ポイント対サイトの構成」をクリックすると下記のような画面が表示されます。
![VNG画面.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/396c1e17-b24c-379a-705f-7ff06db12d47.jpeg)
画面の上の方の赤枠箇所に「VPNクライアントのダウンロード」というボタンがありますので、それを押下します。VPNクライアントと書いてあるので何かしらのツールをダウンロード・インストールするような印象を受けますが、あくまでも構成ファイルです。なお、ダウンロード時はzip圧縮されています。

### VPNクライアント構成ファイルの編集
zip圧縮されたファイルを解凍すると、「OpenVPN」というフォルダ配下に「vpnconfig.ovpn」という名前のクライアント構成ファイルがありますので、これをエディタで編集します。編集箇所は、下記の2箇所です。それ以外は編集されないよう、ご注意ください。

* クライアント証明書の指定

ファイル編集前は下記のように記述されています。
```
# P2S client certificate
# Please fill this field with a PEM formatted client certificate
# Alternatively, configure 'cert PATH_TO_CLIENT_CERT' to use input from a PEM certificate file.
<cert>
$CLIENTCERTIFICATE
</cert>
```
$CLIENTCERTIFICATEの部分に証明書の中身を貼り付けるのでも良いですが、行数が多くなり煩雑になるかと思いますので、\<cert>～\</cert>の部分を削除し、以下のように証明書ファイルの絶対パスを指定されることをお奨めします。(azure_client.crtは、今回作成したクライアント証明書です。)
```
cert = /home/testuser/cert/azure_client.crt
```

* クライアント証明書の秘密鍵の指定

ファイルの編集前は下記のように記述されています。
```
# P2S client certificate private key
# Please fill this field with a PEM formatted private key of the client certificate.
# Alternatively, configure 'key PATH_TO_CLIENT_KEY' to use input from a PEM key file.
<key>
$PRIVATEKEY
</key>
```
こちらも上記クライアント証明書の指定方法と同様に、秘密鍵ファイルの絶対パスを指定されることをお奨めします。(azure_client.keyは、今回作成したクライアント証明書の秘密鍵です。)
```
key = /home/testuser/cert/azure_client.key
```
### VPN接続確認
Linuxのコマンドラインから、下記のコマンドを実行します。以下は、構成ファイルの存在するディレクトリからコマンドを実行した例です。
```
$ sudo openvpn --config vpnconfig.ovpn --auth-nocache
```
構成ファイル内に「log openvpn.log」という指定があり、接続時のログは「openvpn.log」というファイルに出力されるようになっているため、コンソール上には何も表示されません。ちなみに、他のコンソールから「openvpn.log」を参照すると、下記のような出力が確認できます。
```
Sun Apr  7 18:54:23 2024 OpenVPN 2.4.12 x86_64-redhat-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] built on Nov 10 2023
Sun Apr  7 18:54:23 2024 library versions: OpenSSL 1.1.1k  FIPS 25 Mar 2021, LZO 2.08
Sun Apr  7 18:54:23 2024 Outgoing Control Channel Authentication: Using 256 bit message hash 'SHA256' for HMAC authentication
Sun Apr  7 18:54:23 2024 Incoming Control Channel Authentication: Using 256 bit message hash 'SHA256' for HMAC authentication
Sun Apr  7 18:54:24 2024 TCP/UDP: Preserving recently used remote address: [AF_INET]13.71.156.211:443
Sun Apr  7 18:54:24 2024 Socket Buffers: R=[87380->87380] S=[16384->16384]
Sun Apr  7 18:54:24 2024 Attempting to establish TCP connection with [AF_INET]13.71.156.211:443 [nonblock]
Sun Apr  7 18:54:25 2024 TCP connection established with [AF_INET]13.71.156.211:443
Sun Apr  7 18:54:25 2024 TCP_CLIENT link local: (not bound)
Sun Apr  7 18:54:25 2024 TCP_CLIENT link remote: [AF_INET]13.71.156.211:443
Sun Apr  7 18:54:25 2024 TLS: Initial packet from [AF_INET]13.71.156.211:443, sid=2868e54f 168cdad5
Sun Apr  7 18:54:25 2024 VERIFY OK: depth=2, C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root G2
Sun Apr  7 18:54:25 2024 VERIFY OK: depth=1, C=US, O=Microsoft Corporation, CN=Microsoft Azure RSA TLS Issuing CA 03
Sun Apr  7 18:54:25 2024 VERIFY KU OK
Sun Apr  7 18:54:25 2024 Validating certificate extended key usage
Sun Apr  7 18:54:25 2024 ++ Certificate has EKU (str) TLS Web Client Authentication, expects TLS Web Server Authentication
Sun Apr  7 18:54:25 2024 ++ Certificate has EKU (oid) 1.3.6.1.5.5.7.3.2, expects TLS Web Server Authentication
Sun Apr  7 18:54:25 2024 ++ Certificate has EKU (str) TLS Web Server Authentication, expects TLS Web Server Authentication
Sun Apr  7 18:54:25 2024 VERIFY EKU OK
Sun Apr  7 18:54:25 2024 VERIFY X509NAME OK: C=US, ST=WA, L=Redmond, O=Microsoft Corporation, CN=cc60006d-2a46-465f-a40a-aee6fb0b5c6a.vpn.azure.com
Sun Apr  7 18:54:25 2024 VERIFY OK: depth=0, C=US, ST=WA, L=Redmond, O=Microsoft Corporation, CN=cc60006d-2a46-465f-a40a-aee6fb0b5c6a.vpn.azure.com
Sun Apr  7 18:54:25 2024 WARNING: 'link-mtu' is used inconsistently, local='link-mtu 1551', remote='link-mtu 1500'
Sun Apr  7 18:54:25 2024 WARNING: 'tun-mtu' is present in local config but missing in remote config, local='tun-mtu 1500'
Sun Apr  7 18:54:25 2024 Control Channel: TLSv1.2, cipher TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384, 2048 bit RSA
Sun Apr  7 18:54:25 2024 [cc60006d-2a46-465f-a40a-aee6fb0b5c6a.vpn.azure.com] Peer Connection Initiated with [AF_INET]13.71.156.211:443
Sun Apr  7 18:54:26 2024 SENT CONTROL [cc60006d-2a46-465f-a40a-aee6fb0b5c6a.vpn.azure.com]: 'PUSH_REQUEST' (status=1)
Sun Apr  7 18:54:26 2024 PUSH: Received control message: 'PUSH_REPLY,route 10.0.0.0 255.255.0.0,route-gateway 192.168.20.1,topology subnet,ifconfig 192.168.20.2 255.255.255.0,cipher AES-256-GCM'
Sun Apr  7 18:54:26 2024 OPTIONS IMPORT: --ifconfig/up options modified
Sun Apr  7 18:54:26 2024 OPTIONS IMPORT: route options modified
Sun Apr  7 18:54:26 2024 OPTIONS IMPORT: route-related options modified
Sun Apr  7 18:54:26 2024 OPTIONS IMPORT: data channel crypto options modified
Sun Apr  7 18:54:26 2024 Outgoing Data Channel: Cipher 'AES-256-GCM' initialized with 256 bit key
Sun Apr  7 18:54:26 2024 Incoming Data Channel: Cipher 'AES-256-GCM' initialized with 256 bit key
Sun Apr  7 18:54:26 2024 ROUTE_GATEWAY 192.168.20.2/255.255.255.0 IFACE=bridge0 HWADDR=00:0c:29:27:53:90
Sun Apr  7 18:54:26 2024 TUN/TAP device tun0 opened
Sun Apr  7 18:54:26 2024 TUN/TAP TX queue length set to 100
Sun Apr  7 18:54:26 2024 /sbin/ip link set dev tun0 up mtu 1500
Sun Apr  7 18:54:26 2024 /sbin/ip addr add dev tun0 192.168.20.2/24 broadcast 192.168.20.255
Sun Apr  7 18:54:26 2024 /sbin/ip route add 10.0.0.0/16 via 192.168.20.1
Sun Apr  7 18:54:26 2024 Initialization Sequence Completed
```
注目すべき箇所は下記の部分になります。
```
Sun Apr  7 18:54:26 2024 TUN/TAP device tun0 opened
Sun Apr  7 18:54:26 2024 TUN/TAP TX queue length set to 100
Sun Apr  7 18:54:26 2024 /sbin/ip link set dev tun0 up mtu 1500
Sun Apr  7 18:54:26 2024 /sbin/ip addr add dev tun0 192.168.20.2/24 broadcast 192.168.20.255
Sun Apr  7 18:54:26 2024 /sbin/ip route add 10.0.0.0/16 via 192.168.20.1
Sun Apr  7 18:54:26 2024 Initialization Sequence Completed
```
構成ファイルに「dev tun」という指定がされており、VPN接続時にはこのtun(tunnel)デバイスが使用されます。「ip a show」コマンドを実行すると、下記のように「tun」デバイスに関する表示が追加されています。
```
10: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 100
    link/none 
    inet 192.168.20.2/24 brd 192.168.20.255 scope global tun0
       valid_lft forever preferred_lft forever
    inet6 fe80::6483:a36a:bd17:ed5f/64 scope link stable-privacy 
       valid_lft forever preferred_lft forever
```
Azureから見たクライアント側のIPアドレスとして、「192.168.20.2/24」の範囲のアドレスが使用されます。

### プライベートVMインスタンスへのSSH接続確認
ポイント対サイトVPN接続ができましたので、frontend-subnet内にあるプライベートVMインスタンスに対して、プライベートIPアドレスを使用してSSH接続確認を行います。Azureポータル上は、下記画面の赤枠箇所を確認することで、VMインスタンスのプライベートIPを確認できます。
![VMインスタンス画面.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/80873a84-8da1-019b-0a58-1eaf40e67390.jpeg)
なお、terraformのコードにおいても、プロビジョニング完了後にVMインスタンスのプライベートIPアドレスを表示するようにしていますので、そちらで確認することも可能です。
今回、接続先のVMインスタンスのプライベートIPアドレスは「10.0.1.4」なので、こちらに対してpingコマンドを実行してみます。
```
$ ping 10.0.1.4
64 bytes from 10.0.1.4: icmp_seq=1 ttl=64 time=13.3 ms
64 bytes from 10.0.1.4: icmp_seq=2 ttl=64 time=9.44 ms
64 bytes from 10.0.1.4: icmp_seq=3 ttl=64 time=10.10 ms
64 bytes from 10.0.1.4: icmp_seq=4 ttl=64 time=10.6 ms
64 bytes from 10.0.1.4: icmp_seq=5 ttl=64 time=12.1 ms
64 bytes from 10.0.1.4: icmp_seq=6 ttl=64 time=21.1 ms
^C
--- 10.0.1.4 ping statistics ---
6 packets transmitted, 6 received, 0% packet loss, time 5022ms
rtt min/avg/max/mdev = 9.443/12.913/21.131/3.869 ms
```
問題なくプライベートIPアドレスに対してpingコマンドの応答が返ってきています。後は、このVMインスタンスに対してSSH接続をしてみます。
```
$ ssh -i az-vm-rsa-key adminuser@10.0.1.4
The authenticity of host '10.0.1.4 (10.0.1.4)' can't be established.
ECDSA key fingerprint is SHA256:YyxT/rGqIOd1DwCg5AHXOoBa4S+KwU939TprJfe4tmA.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.0.1.4' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 6.5.0-1017-azure x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

  System information as of Sun Apr  7 09:55:29 UTC 2024

  System load:  0.080078125       Processes:             100
  Usage of /:   5.1% of 28.89GB   Users logged in:       0
  Memory usage: 31%               IPv4 address for eth0: 10.0.1.4
  Swap usage:   0%

Expanded Security Maintenance for Applications is not enabled.

0 updates can be applied immediately.

Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status


The list of available updates is more than a week old.
To check for new updates run: sudo apt update


The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
```
無事、プライベートIPアドレスを指定して、プライベートVMインスタンスへのSSH接続を行うことができました。

## 検証後の発生事象
Azureへのポイント対サイト接続は、teratermでSSHログオンしたLinuxコンソールからOpenVPNのVPN接続コマンドを実行することで行いましたが、AWSのClient VPN接続を使用した場合と異なり、以下のような事象が発生することになりました。
* 複数SSHログオンして使用していたteratermウィンドウが、コマンド入力不能となった上、VPN接続コマンドを実行したteratermウィンドウを残して自然終了してしまう。
* VPN接続中は、追加でPC上のLinuxにSSHログオンを行うことができない。

Linuxのデスクトップ環境上のターミナルからVPN接続を実施する場合には、他のターミナルが落ちたり、コマンド入力が不能になったりすることはなかったので、teraterm等SSHログオンを実施した状態での使用は想定されていないのかもしれません。ただ、AWS Client VPN接続では、こういったことは起きていなかったので、Azureだけ何故という感は否めませんが、事象の原因は残念ながらわかっていません。

## さいごに
今回は、Azureのポイント対サイト接続環境を構築して、PC上のLinuxからAzure VMに対してプライベート・アクセスできることの検証を実施し、その検証結果をご紹介しました。
VPN接続自体は実施できたものの、想定していなかった事象が発生することとなりました。ただ、通常はVPN接続と言えば、デスクトップ環境上で専用アプリを使用して実行することが多いかと思いますので、想定外の使い方だったのかもしれません。
座学だけだと、こういった気づきは得られず、実機を使用してみて初めてわかる知見もあるかと思いますので、今後も可能な範囲で色々なサービスの実機検証をしてみようと思います。