その外部 IP 本当に必要ですか？クラウド上の仮想マシンにセキュアに接続する方法

私自身は Google Cloud を主に触ってきましたが、最近は AWS や Azure も学ぶ機会がありそれぞれ違いがあって面白いなと感じることが増えました。
各クラウドの対応表は公式ドキュメント含め各所で目にするものの、具体的なユースケースをベースにサービスの差を説明した記事はあまり見当たらなかったので、今回は `仮想マシンへのセキュアな接続方法` について各クラウドにある接続サポート機能を調べて比較してみました。

# TL;DR
外部からのアクセスが制限されたプライベートなネットワークにある仮想マシンに、閉域網や VPN を使用せず安心・安全に接続する方法をクラウド別にご紹介します。
内容はクラウド初学者向けです。
「個別にサービスだけ知りたい」という方は以下のリンク先より各章に飛んでください。

【基礎知識】
[はじめに](#はじめに)
[外部 IP 割り当て時の注意点](#外部-ip-割り当て時の注意点)
[ファイアウォールによるセキュリティ対策](#ファイアウォールによるセキュリティ対策)
[外部 IP アドレス維持コスト](#外部-ip-アドレス維持コスト)

【サービス紹介】
[本題](#本題)
[Google Cloud - Cloud IAP](#google-cloud---cloud-iap)
[AWS - EC2 Instance Connect Endpoint](#aws---ec2-instance-connect-endpoint)
[Azure - Azure Bation](#azure---azure-bation)
[（おまけ）AWS - AWS Systems Manager (Session Manager)](#おまけaws---aws-systems-manager-session-manager)

# はじめに
各クラウドプロバイダーのサービスで一番基礎となるのが仮想マシンを構築できる仮想サーバーサービスです。
仮想サーバーサービスはユーザーが最も自由に設計・構築できるサービスであり、各社とユーザーの責任範囲を定義した `責任共有モデル` の中では最もユーザーに管理責任が問われる IaaS にあたります。
クラウドネイティブな開発をしている方にとって IaaS である仮想マシンに触る機会は非常に少なくなったかと思いますが、それでも全くないかというとそういうこともなく、クラウドプロバイダーで提供されていない機能を仮想マシンで再現したり、特定のネットワーク内での挙動を確認するために開発・検証サーバーを立ち上げるといったことが必要になります。
以下の図は各クラウドプロバイダーにおける仮想マシンとネットワークサービス例です。

![multi_cloud_secure_connect_instance-page1.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2767381/3755f076-e220-8a54-c93d-4d30618065d2.png)

| クラウド | 仮想マシンサービス | ネットワークサービス |
| :---: | :---: | :---: |
| Google Cloud | Google Compute Engine (GCE) | VPC |
| AWS | Amazon EC2 | Amazon VPC |
| Azure | Azure Virtual Machines (VMs) | Azure Virtual Network (VNet) |

その際にクラウドにまだ慣れていない方は以下のような構成をまず試してみるのではないでしょうか。

![multi_cloud_secure_connect_instance-page2.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2767381/6c9ab7a9-b76d-a4cd-5a38-aaa3805e840d.png)

実際、各クラウドプロバイダーの Quickstart でも上記のような構成が紹介されています。

#### Google Cloud (Quickstart)

https://cloud.google.com/compute/docs/create-linux-vm-instance

以下は公式ドキュメントの画像ですが `External IP` が割り当てられています。

<img src="https://cloud.google.com/static/docs/images/establish-ssh-connection-1.png" />

#### AWS (Quickstart)
https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/get-set-up-for-amazon-ec2.html

以下は公式ドキュメントで最初に紹介されるセキュリティグループの例です。
HTTP, HTTPS をグローバルから受け入れ、SSH を特定のネットワークから受け入れています。

```shell
aws ec2 authorize-security-group-ingress \
    --region us-west-2 \
    --group-id $(aws ec2 create-security-group \
    --group-name myname_SG_uswest2 \
    --description "Security group description" \
    --vpc-id vpc-12345678 \
    --output text \
    --region us-west-2) \
    --ip-permissions \
        IpProtocol=tcp,FromPort=80,ToPort=80,IpRanges='[{CidrIp=0.0.0.0/0,Description="HTTP from anywhere"}]' \
        IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges='[{CidrIp=0.0.0.0/0,Description="HTTPS from anywhere"}]' \
        IpProtocol=tcp,FromPort=22,ToPort=22,IpRanges='[{CidrIp=172.31.0.0/16,Description="SSH from private network"}]' \
        IpProtocol=tcp,FromPort=22,ToPort=22,IpRanges='[{CidrIp=203.0.113.25/32,Description="SSH from public IP"}]'
```

#### Azure (Quickstart)

https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-cli

以下は公式ドキュメントの仮想マシン作成コマンドです。
オプションで Public IP を割り当てています。

```shell
az vm create \
  --resource-group $RESOURCE_GROUP_NAME \
  --name $VM_NAME \
  --image $VM_IMAGE \
  --admin-username $ADMIN_USERNAME \
  --generate-ssh-keys \
  --public-ip-sku Standard
```

図中の `External IP` は `パブリック IP` や `外部 IP` などの呼び方もしますが、公衆のインターネットに公開されるグローバルに一意な IP アドレスです。
本記事の中でも引用の都合上、用語の表記ゆれを残していますが同じ意味で解釈してください。
External IP には仮想マシンを起動する度に自動的に割り当てられる `動的 IP アドレス` と、事前に確保した IP アドレスを割り当てることでリソースの状態に関わらずアドレスを固定化する `静的 IP アドレス` があります。

各社の Quickstart はいずれも動的 IP アドレスによる仮想マシンの構築例ですが、再起動するたびに接続する IP アドレスが変わってしまっては実務上問題となるため、次のステップとして IP アドレスを固定するために静的 IP アドレスの割り当てを検討されることでしょう。

各クラウドプロバイダーでは静的 IP アドレスは仮想マシンやネットワークとは異なる１つの独立したリソースとして扱います。
静的 IP アドレスのリソースが独立していることで、仮想マシンだけではなくロードバランサーやマネージド NAT など様々なリソースに割り当てることが可能となります。

# 外部 IP 割り当て時の注意点

さて、任意のネットワーク上に仮想マシンを起動し固定の外部 IP アドレスを割り当て、自宅や自社拠点からインターネットを経由しクラウド上の仮想マシンに接続できる環境を構築した場合は以下の点に注意する必要があります。

## ファイアウォールによるセキュリティ対策

外部 IP はグローバルに公開されるため適切なセキュリティ対策が必要です。
たとえ DNS でドメインと IP アドレスを紐づけていないとしても、世界中に公開されている IP アドレスに対しては悪意のあるトラフィックが日々届きます。
そのため、ファイアウォールで宛先ポートや接続元 IP アドレスを絞り、サイバー攻撃をブロックする必要があります。

Google Cloud の VPC は暗黙のルールとしてインバインドトラフィック（外部からの VPC 内部へのアクセス）は全て拒否されます。 **暗黙のルールはリソースとして表示されません。** GCP プロジェクト作成時に自動で作成されるデフォルトネットワーク（ `default` ）には `default-allow-icmp` や `default-allow-ssh` など ICMP や SSH 等のポートがグローバル（ `0.0.0.0/0` ）に許可される事前定義されたファイアウォールルールが設定されるため、グローバルなアクセスを一切許可したくない場合は新規で VPC を作成しましょう。

https://cloud.google.com/firewall/docs/firewalls#more_rules_default_vpc

default ネットワークの使用を制限したい場合は、少し高度な設定になりますが組織ポリシー `compute.skipDefaultNetworkCreation` を使用して GCP プロジェクト作成時に default ネットワークの自動作成を無効にすることもできます。[^1]
また、アウトバウンドトラフィックは暗黙的に全て許可されているため設定不要で仮想マシンからインターネットに接続することが可能です。

[^1]:組織で多数の GCP プロジェクトを管理される担当者の方は組織ポリシー制約の導入を検討してください。https://cloud.google.com/vpc/docs/vpc#org-policies

AWS の場合はサブネット単位でトラフィックを制御するステートレスなネットワーク ACL と、仮想マシン単位でトラフィックを制御するステートフルなセキュリティグループの 2層構造となっています。
[デフォルトのネットワーク ACL](https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/vpc-network-acls.html#default-network-acl) ではトラフィックの出入りは全て許可されています。

セキュリティグループを省略した場合は自動でインバウンドトラフィックを拒否されている[デフォルトのセキュリティグループ](https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/default-security-group.html)が設定されます。デフォルトのセキュリティグループは VPC に付属しており削除することはできません。任意のセキュリティグループを作成し割り当てることもできますが、こちらもインバウンドルールを作成するまではインバウンドトラフィックは全て拒否されます。

https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/vpc-network-acls.html#nacl-examples

Azure の `ネットワークセキュリティグループ（以降、NSG）` ではインバンドトラフィックは `Azure Virtual Network　（VNet）` と `Azure Load Balancer` からのみ許可され、インターネットからの受信は全て拒否されます。アウトバウンドトラフィックは VNet / インターネットの両方で許可されます。このルールは Google Cloud とは異なりユーザーに表示されますが削除することはできません。

https://learn.microsoft.com/en-us/azure/virtual-network/network-security-groups-overview#default-security-rules

NSG はサブネットと仮想マシンのネットワークインターフェス（NIC）のそれぞれに割り当てることができ、 AWS のネットワーク ACL ＆セキュリティグループのような使い方をすることができます[^2]。また、こちらは有償ですが Azure Firewall を使用することでインターネットと VNet の境界に L7（アプリケーションレイヤー）レベルの防御層を構築することができます。

[^2]: NSG はステートフルに動作します

さて、各クラウドのサービスの基本的なネットワークセキュリティについて駆け足で見てきましたが、ここでお伝えしたいことはどのクラウドにおいても 外部 IP を割り当てた仮想マシンを構築した場合 **ネットワークに対しグローバルからのアクセスを意識した許可ルールを設定する必要がある** ということです。

## 外部 IP アドレス維持コスト

AWS は VPC 内のリソースや、Amazon Global Accelerator、AWS Site-to-Site VPN トンネルに割り当てられたパブリック IP アドレスは **2024年2月1日 から有料化しました。**

https://aws.amazon.com/jp/blogs/news/new-aws-public-ipv4-address-charge-public-ip-insights/

この価格改定により１時間あたりの `$0.005` の課金が発生しますので、年間の使用料は １つのパブリック IP アドレスあたり **`$0.005/時間 * 24H * 365日 = $43.8/年`** の増額となります。
日本円に換算すると、執筆時のレート(147 円)で **`6438.6円/年`** です。
これは仮想マシンの状態や割り当て有無に関わらず発生しますので、個人/ビジネス利用に関わらず無視できない金額ではないかと思います。

Google Cloud は以前から有料でしたが、あまり話題になっていませんが **2024年2月1日からパブリック IP アドレスの課金額は値上がりしています。** [^3]
以下の表は価格の新旧表です（単位は毎時）。

[^3]:https://cloud.google.com/vpc/network-pricing

| Type | 旧 | 新 |
| :--- | :---: | :---: |
|Standard VM Instances | $0.004 | $0.005 |
|preemptible and Spot VM instances | $0.002 | $0.0025 |
| Cloud NAT Gateways | No charge | $0.005 |

通常の仮想マシンに割り当てる固定の外部 IP の維持コストだけみると AWS と同水準となりますが、Google Cloud は割り当てる対象リソースによってコストは少し異なります。
また、未割り当てのパブリック IP アドレスは AWS の場合は割り当てられたアドレスと同じく `$0.005` の費用が発生しますが、 **Google Cloud の場合は未割り当ての場合 `$0.015` と3倍の費用を請求されるため IP アドレスの管理にはご注意ください。** [^4]

[^4]:リージョンによっては `$0.012` や `$0.01` の場合があります。日本は少し高めの設定のようです。

Azure はパブリック IP には2024年現在 [Basic と Standard の２つの SKU があります。](https://learn.microsoft.com/en-us/azure/virtual-network/ip-services/public-ip-addresses#ip-address-assignment) SKU は在庫管理で商品を扱う最小単位で使われる小売業の用語ですが、 **Azure では各サービスの中で定義されているいわゆる料金プラン** のことで、サービスによりますが `Free` `Basic` `Standard` などが設定されているようです。

ゾーンで冗長化されない `Basic` は `$0.0036/hour` と前述 2 社に比べ最安値ですが、[Basic のパブリック IP は 2025年9月30日に廃止される](https://azure.microsoft.com/en-us/updates/upgrade-to-standard-sku-public-ip-addresses-in-azure-by-30-september-2025-basic-sku-will-be-retired/)ので実質的に `Standard` 一択であり、 Standard の価格体系である `$0.005/hour` は（あくまで価格面に関してだけ言えば）各クラウド横並びとなります。

https://azure.microsoft.com/en-us/pricing/details/ip-addresses/

### 維持コストに関する留意事項
価格に関しては一見横並びですが、各社外部 IP サービスの仕様は異なります。
例えば、AWS の固定の外部 IP サービスである Elastic IP は特定のリージョン専用で別のリージョンに移動することはできません[^5]が、Google Cloud はリージョンリソースとグローバル（クロスリージョン）リソースの２種類の外部 IP を選択することができ[^6]費用も変わりません。 Azure もクロスリージョンで使える外部 IP をオプションで選択[^7]できますが、料金が `$0.01/hour` と通常の Standard の2倍の費用がかかります。

[^5]:https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html#eip-basics
[^6]:https://cloud.google.com/vpc/docs/ip-addresses#external
[^7]:https://learn.microsoft.com/en-us/azure/virtual-network/ip-services/public-ip-addresses#other-public-ip-address-features

また、いずれのクラウドにおいても上記価格体系は IPv4 のパブリック IP アドレスに対する課金となり、**IPv6 の外部 IP は無料となります。**
これは [IP アドレスの枯渇問題](https://ja.wikipedia.org/wiki/IP%E3%82%A2%E3%83%89%E3%83%AC%E3%82%B9%E6%9E%AF%E6%B8%87%E5%95%8F%E9%A1%8C) により世界的に新規 IPv4 アドレスの取得が困難となったことに起因しています。

# 本題
さて、クラウド環境における外部 IP アドレスの維持コストについては少なくとも IPv6 対応することで気に掛けなくてよくなりますが、セキュリティについては一定の注意を払う必要があります。
各クラウドプロバイダーの Quickstart を元に環境を構築すると仮想マシンへのアクセスに関して動的・静的問わず外部 IP を用意することになりますが、そもそも要件として外部 IP は必要なのでしょうか？
グローバルに公開された外部 IP を使用せずプライベートなネットワーク内にある仮想マシンにアクセスできればセキュリティと維持コストの問題は解決できそうですし、なにより入り口を塞いでしまうことは心理的にも安心感が違います。

![multi_cloud_secure_connect_instance-page3.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2767381/ce066422-842d-7f7b-3ce6-7e85838e65e3.png)

前置きが長くなってしまいましたが、以降で各クラウドプロバイダー毎のプライベートネットワークに配置された外部 IP を持たない仮想マシンへの接続方法をご紹介します。

### Google Cloud - Cloud IAP

Google Cloud の場合は、 `Cloud Identity-Aware Proxy（Cloud IAP）` というマネージドなプロキシを経由することで外部 IP を持たない仮想マシンにセキュアに接続することができます。 

https://cloud.google.com/iap/docs/tcp-forwarding-overview

以下は公式ドキュメントからの引用画像です。

<img src="https://cloud.google.com/iap/images/iap-tcp-forwarding-diagram.png" />

手順を簡単に纏めますと、まずはプロキシからのアクセスを許可するためにファイアウォールルールで `35.235.240.0/20` からの内向きトラフィックを許可します。以下は `SSH` のためにポート 22 を指定していますが、 `RDP` の場合はポート 3389 を追加します。 **`35.235.240.0/20` が Cloud IAP から仮想マシンへの接続を示しています。**

```shell
gcloud compute firewall-rules create allow-ssh-ingress-from-iap \
  --direction=INGRESS \
  --action=allow \
  --rules=tcp:22 \
  --source-ranges=35.235.240.0/20
```

次に、IAM で権限付与設定を行います。
Cloud IAP からの接続を受け付けるためには GCP プロジェクトまたは接続したい仮想マシンに以下のロールが必要です。

- [IAP-secured Tunnel User](https://cloud.google.com/iam/docs/understanding-roles#iap.tunnelResourceAccessor)
- [Compute Instance Admin (v1)](https://cloud.google.com/iam/docs/understanding-roles#compute.instanceAdmin.v1)

たったこれだけの設定を行うだけで設定は終了です。
ご使用中の PC で以下のコマンドを実行するだけで外部 IP アドレスを持たないプライベートな仮想マシンにセキュアに接続することができます。

```
gcloud compute ssh [INSTANCE_NAME]
```

外部 IP アドレスを持たない仮想マシンには自動で Cloud IAP による TCP 転送が使用されますが、既に外部 IP を持っている仮想マシンに接続する際は `--tunnel-through-iap` オプションを指定します。
**本サービスは無料で利用でき、タイムアウトや同時接続数といった制限もありません。**
ファイル転送が必要な場合は `gcloud compute scp` コマンドを使用してください。

また、これは必須ではありませんが、仮想マシンが Windows Server や Fedora CoreOS VM でなければ `OS Login` を用いて IAM ユーザーを Linux ユーザーとしてそのまま利用しアクセス制御することもできます。OS Login を有効にすることで2段階認証の設定や接続時の監査ログ出力などセキュアな環境に求められる要件を満たすことができるため併用することをお勧めします。

https://cloud.google.com/compute/docs/oslogin

### AWS - EC2 Instance Connect Endpoint

AWS では Google Cloud とイメージは同じですが `EC2 Instance Connect Endpoint（以降、EIC Endpoint）` というエンドポイントを作成することでプライベートサブネット内の仮想マシンにセキュアに接続することができます。

https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/connect-with-ec2-instance-connect-endpoint.html

以下は公式ドキュメントからの引用画像です。

<img src="https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/images/ec2-instance-connect-endpoint.png" />

設定は Google Cloud よりは少し複雑です。
まずは EIC Endpoint 用のセキュリティグループと EC2 用のセキュリティグループをそれぞれ作成し適切な通信許可を設定する必要があります。
以下の画像は公式ドキュメントからの引用です。

<img src="https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/images/ec2-instance-connect-endpoint-security-groups-example.svg" />

公式の画像には記載がありませんが双方のセキュリティグループで通信許可が必要です。
また、EIC Endpoint 経由でアクセスするクライアントの IP アドレスを識別して厳密にアクセスを制限する `クライアント IP 保存` 機能を利用する場合はクライアントの IP アドレスをインバウンドルールに追記しますが、インスタンスタイプによってこの機能は利用できないため注意が必要です。本記事では EIC Endpoint のクライアント IP の保存は無効 `preserveClientIp=false` として解説します。
以下は設定するセキュリティグループのサンプルです。

#### EIC Endpoint の Security Group （アウトバウンドルール）
| セキュリティグループ名 | タイプ | プロトコル | ポート範囲 | 送信先 |
| :---: | :---: | :---: | :---: | :---: |
| （例）EIC-sg | SSH | TCP | 22 | EC2-sg|

#### EC2 の Security Group （インバウンドルール）
| セキュリティグループ名 | タイプ | プロトコル | ポート範囲 | 送信元 |
| :---: | :---: | :---: | :---: | :---: |
| （例）EC2-sg | SSH | TCP | 22 | EIC-sg|

次に EIC Endpoint の実体を作成しますが、ここでも注意事項があり **EIC は作成後に更新できません。** そのため例えば **対象の VPC を間違って指定してしまった場合はリソースを作り直す必要があります。**
前述したクライアント IP 保護は接続対象の仮想マシンと同一の VPC に EIC Endpoint を作成する必要があるなど制限もありますので、作成時はご注意ください。

最後に IAM 権限を設定します。
ここでは `AWS CLI` を用いた SSH で接続することを想定します。
[公式ドキュメントのサンプル](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/permissions-for-ec2-instance-connect-endpoint.html#iam-OpenTunnel)との変更点は以降で解説します。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "EC2InstanceConnect",
            "Action": "ec2-instance-connect:OpenTunnel",
            "Effect": "Allow",
            "Resource": "arn:aws:ec2:[REGION]:[ACCOUNT_ID]:instance-connect-endpoint/[EIC_ENDPOINT_RESOURCE_ID]",
            "Condition": {
                "NumericEquals": {
                    "ec2-instance-connect:remotePort": "22"
                },
                "IpAddress": {
                    "ec2-instance-connect:privateIpAddress": "[DESTINATION_IP_ADDRESS_RANGE]"
                }
            }
        },
        {
            "Sid": "SSHPublicKey",
            "Effect": "Allow",
            "Action": "ec2-instance-connect:SendSSHPublicKey",
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "aws:ResourceTag/[TAG_KEY]": "[TAG_VALUE]"
                }
            }
        },
        {
            "Sid": "Describe",
            "Action": [
                "ec2:DescribeInstances",
                "ec2:DescribeInstanceConnectEndpoints"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}
```

変更した点ですが、これは大いにハマったのですがサンプルの通りに `ec2-instance-connect:OpenTunnel` アクションに対して `"ec2-instance-connect:maxTunnelDuration": "3600"` を設定すると、コマンド実行時に `--max-tunnel-duration` が必須となり、オプションを付けない場合は以下のエラーメッセージで `AccessDenied` となります。

`
User: [IAM_USER_ARN] is not authorized to perform: ec2-instance-connect:OpenTunnel on resource: [EIC_ENDPOINT_ARN] because no identity-based policy allows the ec2-instance-connect:OpenTunnel action
`

つまり、最大接続期間を制限したい場合は EIC Endpoint で制御するのではなく、IAM ポリシーにより呼び出し元のオプションで制御することになります。
エラーメッセージからは接続期間によるポリシーエラーかどうかは判別できませんので、不用意に設定すると利用ユーザーが接続できずに頭を悩ませる可能性がありますので指定する際は十分ご注意ください。
また、サンプルでは `ec2-instance-connect:SendSSHPublicKey` アクションに対して `"ec2:osuser": "ami-username"` で OS ユーザー名による制限を指定していますが、 [EC2 のデフォルトのユーザー名は AMI ごとに異なる](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/managing-users.html#ami-default-user-names)ため柔軟性を考慮し IAM ユーザーに付与したタグによる制限に変更しています。

あとはクライアント PC から SSH コマンドを実行します。
以下はシングル接続の例です。

```shell
ssh -i [KEY_PAIR_FILE] ec2-user@[INSTANCE_ID] \
    -o ProxyCommand='aws ec2-instance-connect open-tunnel --instance-id [INSTANCE_ID]'
```

以上でプライベートなサブネット内にある EC2 にセキュアに接続することができます。
但し、EIC は前述した Google Cloud と違い以下のような制限があります。

- 最大接続時間：1時間
- 最大同時接続数：20
- トラフィック制限：非公開（大量のデータ転送時にスロットリング）

また、これは元々の AWS のネットワーク仕様なのですが、プライベートサブネット内の仮想マシンがインターネットに接続するためには `NAT Gateway` が必要となり別途コストが発生します。
NAT Gateway は複数の仮想マシンが外部にアクセスするための出口にすることができますが、外部 IP の維持コストを上回りますのでプライベートな仮想マシンからのアクセスは AWS サービスのみに限定しエンドポイントを作成するなど、外部アクセスが必要な場合の費用管理にはご注意ください。

### Azure - Azure Bation

Azure でプライベートな仮想マシンにセキュアに接続するには、フルマネージドな RDP/SSH プロキシ環境をプロビジョニングできる `Azure Bation` を使用します。

https://learn.microsoft.com/en-us/azure/bastion/bastion-overview

以下は公式ドキュメントの画像です。ドキュメントに `PaaS` と記載があるように、ユーザーの環境に `AzureBationSubnet` と `Public IP` を作成する必要があるため、前述の２つのクラウドサービスとは異なりリソースの所有者はユーザーでありサービス利用料も発生します。

<img src="https://learn.microsoft.com/ja-jp/azure/bastion/media/bastion-overview/architecture.png#lightbox" />

フルマネージドであるため基本的にユーザーが細かい設定を意識する必要はありませんが、Bation リソースに割り当てる Public IP は同じリージョンにある必要がある点についてはご注意ください。 
リソースの作成や接続に必要なロールについては公式ドキュメントを参照してください。

https://learn.microsoft.com/en-us/azure/bastion/bastion-faq#roles

Bation リソースには外部 IP が割り当てられますが、ユーザーが NSG の設定を行う必要はなくポートスキャンやゼロデイ攻撃の保護も Azure が行ってくれます。
SKU には `Developer(Preview)` `Basic` `Standard` がありますが、 `Azure CLI` を使用した接続やファイルのアップロード / ダウンロードは `Standard` のみサポートしています。

Azure CLI を使用することでプライベートな仮想マシンにリソース名で接続することができます。
リソース名ではなく内部 IP アドレスで接続することもできます。
Azure CLI による接続を構成する場合は事前に Bation のデプロイ時に `ネイティブ クライアント機能` を有効にします。
以下は SSH 接続の例です。
事前に `az extension add --name bastion` コマンドを実行し拡張機能をインストールする必要があります。

```shell
az network bastion ssh --name "<BastionName>" --resource-group "<ResourceGroupName>" --target-resource-id "<VMResourceId>" --auth-type "ssh-key" --username "<Username>" --ssh-key "<Filepath>"
```

また、Azure Entra ID（旧称：Azure Active Directory）を使用して Microsoft Entra の資格情報を使用して接続することもできます。
こちらは Google Cloud の OSLogin と同じようなイメージで接続ユーザーをクラウドで統合的に管理することができます。

```shell
az network bastion ssh --name "<BastionName>" --resource-group "<ResourceGroupName>" --target-resource-id "<VMResourceId or VMSSInstanceResourceId>" --auth-type "AAD"
```

ファイルのアップロードを行う場合は `az network bastion tunnel` コマンドを使用します。注意事項として、 **[Azure CLI コマンド経由の SSH 接続はファイルのダウンロードに対応していません。](https://learn.microsoft.com/en-us/azure/bastion/native-client#connect)** 対象が Windows VM でかつ RDP 接続の場合はアップロード / ダウンロードをサポートしています。

また、繰り返しになりますが Azure Bation は有償サービスであり維持コストが発生します。Basic でも月で数万円程度かかってしまうため個人利用には向きません。Standard の場合は 2 つの仮想マシンが基準価格に含まれますが、スケールする場合は追加料金が発生します。また、 Bation は一度 SKU をアップグレードするとダウングレードはできませんのでご注意ください。

https://azure.microsoft.com/en-us/pricing/details/azure-bastion/

Developer SKU はプレビュー期間中は無料で利用でき、GA 後の価格はまだ公開されていませんが低コストで運用できる予定だそうです。 Azure CLI を使用せず仮想マシンに接続するなどプロダクション以外の目的に適しています。但し、2024年2月現在 [Developer SKU は日本リージョンでは利用できません](https://learn.microsoft.com/en-us/azure/bastion/quickstart-developer-sku)ので、環境を構築する際は **前述した Public IP の制限事項にご注意ください。**

### （おまけ）AWS - AWS Systems Manager (Session Manager)

EIC Endpoint が登場する前のプライベートな仮想マシンに接続する方法が Systems Manager の `Session Manager` となります。
インストールしたエージェント（ 以降、`SSM Agent` ）経由でログインする方法となりますが、 EIC Endpoint がリリースされた現在、接続のためだけにこちらを選択するケースは少ないかもしれませんが、利点もありますので補足として簡単にご紹介します。

まずはエージェント用に以下のポリシーを持つ IAM ロールを EC2 にアタッチします。`AWS System Manager` の `高速セットアップ` を利用すると IAM ロールを自動作成することができます。

- AmazonSSMManagedInstanceCore
- SSMInstanceProfile

Session Manager を利用するためには仮想マシンで SSM Agent が起動している必要がありますが、[プリインストールされた AMI(Amazon Machine Image)](https://docs.aws.amazon.com/systems-manager/latest/userguide/ami-preinstalled-agent.html) を使用することでセットアップ手順を省略することができます。

SSM Agent は以下のエンドポイントに HTTPS 通信できる必要があります。
今回は仮想マシンがプライベートなサブネットにあるという条件ですので NAT Gateway を用いてインターネットゲートウェイにルーティングするか、AWS PrivateLink を用いる必要があります。AWS PrivateLink を用いることでトラフィックがインターネットに出ることなく任意の API を呼び出せるようになりますのでよりセキュアな環境を実現できます。但し、 AWS PrivateLink は VPC エンドポイントとは異なり有料です。

- ec2messages.region.amazonaws.com
- ssm.region.amazonaws.com
- ssmmessages.region.amazonaws.com

KMS などの暗号化オプションを使用する場合は以下のエンドポイントとも通信できる必要があります。

- kms.region.amazonaws.com
- logs.region.amazonaws.com
- s3.region.amazonaws.com

セッションマネージャーで適切に仮想マシンが認識されれば[セッションを開始](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-sessions-start.html)し仮想マシンに接続します。
接続方法は `System Manager コンソール` `EC2 コンソール` `AWS CLI` `SSH` から選択することができ、 EIC Endpoint と違いポート 22 を意識したネットワークセキュリティ設定も不要です。また、[セッションマネージャー自体は無料で利用でき](https://aws.amazon.com/systems-manager/pricing/)、[セッション時間の上限もありません](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-preferences-max-timeout.html)。

Session Manager は Systems Manager の１機能であり、 Systems Manager には仮想マシンを運用するための豊富な機能がありますので、大規模に仮想マシンを管理されている場合はこちらの機能を用いてプライベートな仮想マシンに接続すると良いでしょう。

## まとめ
３大クラウドにおいてプライベートな仮想マシンに安心・安全に接続する各種機能をご紹介しました。
個人的な感覚ですが、主題である `仮想マシンへのセキュアな接続方法` に関してコストや設定難易度で比較すると `Google Cloud > AWS > Azure` で優れているように感じました。とはいえ、各クラウドは互いに切磋琢磨し日々進化していますので、今後より良い機能がリリースされ差は縮まると思います。
私自身は Google Cloud を専門としており AWS / Azure についてはかじった程度ですので、内容等に間違い等がありましたらご指摘ください。

記事の内容は以上です。
本投稿が安価でセキュアに仮想マシンを管理するための一助になれば幸いです。