【Terraform】 Azure Bastionを使ってAzure Virtual Machineに接続してみる
はじめに
こんにちは、コンサルティング部の神野です。
Azure環境では、踏み台サーバーを自前で構築・運用するのではなく、マネージドサービスである「Azure Bastion」を活用することでAzure Virtual Machine(以下VM)に対してセキュアに接続することを可能とします。
今回は、Terraformを使ってAzure BastionとVMを構築し、簡単に安全なリモートアクセス環境を作る方法をご紹介します。「とりあえず試してみたい」という方にも参考になるよう、シンプルな構成で試してみました。
Azure Bastionとは
Azure Bastionは、Azureが提供するマネージドサービスで、仮想マシンへの安全なリモートアクセスを実現するためのサービスです。従来のように踏み台サーバーを自前で構築・運用する必要がなく、AzureポータルからブラウザベースでSSHやRDP接続が可能になります。
主な特徴としては、仮想マシン自体にパブリックIPを割り当てる必要がなくインターネットに直接公開されないセキュアなアクセスが可能であることや、追加のクライアントソフトウェアやエージェントのインストールが不要なブラウザベースの接続を提供していることが挙げられます。さらに、特定のポート(SSH:22、RDP:3389など)をインターネットに公開する必要がないため、Azure Network Security Group(以下NSG)の設定が簡素化されるというメリットもあります。
Azure Bastionには、各種SKUのサービスレベルがあります。Standard SKUでは、ポート転送やファイル転送などの高度な機能が利用できますが、その分コストも高くなります。(補足に各SKUの比較を記載しておりますので、必要に応じてご参照ください)
今回は、より柔軟性の高いStandard SKUを使用して環境を構築していきます。
今回構築する環境
今回は以下のようなシンプルな環境を構築します。
- Azure Bastion(Standard SKU、トンネリング有効)
- Azure BastionにアタッチするパブリックIPアドレス
- 仮想ネットワーク
- サブネット(Bastion用とVM用)
- 仮想マシン(Ubuntu 22.04 LTS)
- VM用ネットワークセキュリティグループ
- Bastionからの接続のみを許可(Port:22)
- インターネットからの接続を全て拒否
構成のイメージ図
前提条件
-
Terraformがインストールされていること(v1.0.0以上推奨)
- 使用したバージョン:Terraform v1.11.4 on darwin_arm64
-
Azure CLIがインストールされ、認証済みであること
- 使用したバージョン:azure-cli 2.70.0
-
自身の権限が適応できるAzureサブスクリプション・リソースグループを保持していること
SSH鍵の準備
今回のコードでは、仮想マシンへのSSH接続のために公開鍵認証を使用します。
SSH鍵を用意していない場合は、以下のコマンドで生成してください。
下記ではazure_key
といった名前で作成しています。任意の名前をお使いください。
# SSH鍵を生成(パスフレーズなし)
ssh-keygen -t rsa -b 2048 -f ~/.ssh/azure_key -N ""
# 鍵が生成されたことを確認
ls -la ~/.ssh/azure_key*
Terraformコードの作成
まずは、必要なファイルを作成していきます。今回は以下の3つのファイルを用意します。
-
main.tf
- プロバイダーの設定
-
network.tf
- ネットワークリソースの定義
-
vm.tf
- 仮想マシンの定義
サクッと作成することに主眼を置いているため、一部おざなりになっている箇所もあるかと思います。実際のワークロードや設計方針に合わせて修正ください。
main.tf
サブスクリプションとリソースグループは存在するため、すでに存在するサブスクリプションのIDとdataブロックでリソースグループは参照します。変数などで定義するもいいかと思いますし、リソースグループが存在しない場合は作成するのもいいかと思います。
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.27.0"
}
}
required_version = ">= 1.0"
}
provider "azurerm" {
features {}
subscription_id = "任意のサブスクリプションID"
}
data "azurerm_resource_group" "rg" {
name = "任意のリソースグループ名"
}
network.tf
サブネットを2つ作成します。Azure Bastion用サブネットとVM用のサブネットです。
BastionにはPublic IPを付与して外部からのアクセスを可能とし、VM用のNSGはBastionサブネットからの22ポート(SSH)へのアクセスのみを許可し、インターネットからの直接アクセスは拒否する設定にします。これによってVM環境のセキュリティを確保しつつ、Bastionを経由した安全なアクセスのみを許可する構成とします。
BastionのSKUはStandardとしています。ローカル端末のAzure CLIからのアクセスも実施したいためです。
またAzureBastionSubnetにTLSのみを許可するNSGもアタッチすることが可能ですが今回は省略しております。
もしNSGを設定される場合は下記ドキュメントに従ってポートを開放する設定にしてください。
resource "azurerm_virtual_network" "vnet" {
name = "bastion-vnet"
address_space = ["10.0.0.0/16"]
location = data.azurerm_resource_group.rg.location
resource_group_name = data.azurerm_resource_group.rg.name
}
resource "azurerm_subnet" "vm_subnet" {
name = "vm-subnet"
resource_group_name = data.azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.1.0/24"]
}
resource "azurerm_subnet" "bastion_subnet" {
name = "AzureBastionSubnet" # この名前は固定である必要があります
resource_group_name = data.azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.0.0/24"]
}
resource "azurerm_public_ip" "bastion_pip" {
name = "bastion-pip"
location = data.azurerm_resource_group.rg.location
resource_group_name = data.azurerm_resource_group.rg.name
allocation_method = "Static"
sku = "Standard"
}
resource "azurerm_bastion_host" "bastion" {
name = "demo-bastion"
sku = "Standard"
tunneling_enabled = true
location = data.azurerm_resource_group.rg.location
resource_group_name = data.azurerm_resource_group.rg.name
ip_configuration {
name = "configuration"
subnet_id = azurerm_subnet.bastion_subnet.id
public_ip_address_id = azurerm_public_ip.bastion_pip.id
}
}
resource "azurerm_network_security_group" "vm_nsg" {
name = "vm-nsg"
location = data.azurerm_resource_group.rg.location
resource_group_name = data.azurerm_resource_group.rg.name
# Bastionからの22番ポートへのアクセスを許可
security_rule {
name = "allow-ssh-from-bastion"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "10.0.0.0/24" # Bastionサブネットのアドレス範囲
destination_address_prefix = "*"
}
# 外部からの直接アクセスは全て拒否
security_rule {
name = "deny-inbound-all"
priority = 1000
direction = "Inbound"
access = "Deny"
protocol = "*"
source_port_range = "*"
destination_port_range = "*"
source_address_prefix = "Internet"
destination_address_prefix = "*"
}
}
resource "azurerm_subnet_network_security_group_association" "vm_nsg_association" {
subnet_id = azurerm_subnet.vm_subnet.id
network_security_group_id = azurerm_network_security_group.vm_nsg.id
}
tunneling_enabled
はローカル端末からAzure CLIを使ってネイティブ クライアント機能を実施するために有効にしています。
vm.tf
こちらはVMの実装を書いています。internal
としてPrivateIPのみ付与しています。
また、今回は手順の簡略化のために事前に作成したローカルファイルの公開鍵を直接登録する仕組みとしています。
resource "azurerm_network_interface" "vm_nic" {
name = "vm-nic"
location = data.azurerm_resource_group.rg.location
resource_group_name = data.azurerm_resource_group.rg.name
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.vm_subnet.id
private_ip_address_allocation = "Dynamic"
}
}
resource "azurerm_linux_virtual_machine" "vm" {
name = "demo-vm"
resource_group_name = data.azurerm_resource_group.rg.name
location = data.azurerm_resource_group.rg.location
size = "Standard_B1s"
admin_username = "azureuser"
network_interface_ids = [
azurerm_network_interface.vm_nic.id,
]
admin_ssh_key {
username = "azureuser"
public_key = file("~/.ssh/azure_key.pub") # ローカルのSSH公開鍵ファイル
}
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"
}
}
デプロイ手順
作成したTerraformコードをデプロイするには、以下の手順で実行します。
- まず、Terraformの初期化を行います。
terraform init
- 作成されるリソースを確認します。
terraform plan
- リソースをデプロイします。
terraform apply
yes
と入力して、デプロイを開始します。デプロイには10〜15分程度かかることがあります。特にAzure Bastionのプロビジョニングには時間がかかりますので、少々お待ちください。
Azure Bastionを使ってVMに接続する
Azure Portalから接続
デプロイが完了したら、Azure Portalから仮想マシンに接続してみましょう。
-
Azure Portalにログインします。
-
作成した仮想マシン(demo-vm)のページに移動します。
-
「接続」ボタンをクリックし、「Bastion」を選択します。
-
認証方法として「SSH秘密キー」を選択し、以下の情報を入力します。
- ユーザー名: azureuser
- 認証タイプ: SSH秘密キー
- 秘密キーファイル: 生成した秘密鍵(~/.ssh/azure_key)を選択
- 「接続」をクリックすると、ブラウザ上でSSHセッションが開始されます。
これで、インターネットに公開されたポートを持たない仮想マシンにアクセスできました!
ローカル端末のAzure CLIからアクセス
一方でAzure CLIからもローカル上の秘密鍵を使用してSSHで接続することが可能です。
下記コマンドを実行します。
コマンド引数の説明
引数 | 説明 |
---|---|
--name |
接続に使用するAzure Bastionホストの名前を指定します。例: "demo-bastion" |
--resource-group |
Azure BastionホストとVMが属するリソースグループの名前を指定します。<your-resource-group-name> を実際の名前に置き換えてください。 |
--target-resource-id |
接続先の仮想マシンのリソースIDを指定します。Azure PortalでVMのプロパティから確認できます。<your-vm-resource-id> を実際のIDに置き換えてください。 |
--auth-type |
認証方法を指定します。SSHキー認証の場合は "ssh-key" を指定します。 |
--username |
仮想マシンにSSH接続する際のユーザー名を指定します。例: "azureuser" |
--ssh-key |
SSH接続に使用する秘密鍵ファイルのパスを指定します。例: "~/.ssh/azure_key" |
実行コマンド
az network bastion ssh \
--name "demo-bastion" \
--resource-group "<your-resource-group-name>" \
--target-resource-id "<your-vm-resource-id>" \
--auth-type "ssh-key" \
--username "azureuser" \
--ssh-key "~/.ssh/azure_key"
実行結果
こちらも問題なく接続できていますね!Azure CLIでローカル上からアクセスできるのも便利ですね。
ハマりポイントと注意点
一部特に気になった箇所を記載しました。より詳細な注意点は公式ドキュメントに記載があるので、必要に応じてご参照ください。
Bastionサブネット名は固定
Azure Bastionを配置するサブネットの名前は必ずAzureBastionSubnet
である必要があります。これは、Azureの仕様によるものです。名前を変更するとデプロイに失敗しますので注意してください。
サブネットのサイズ
Bastionサブネットは少なくとも/26以上のサイズ(最低64個のIPアドレス)が必要です。今回は余裕を持って/24(256個のIPアドレス)を割り当てています。
パブリックIPのSKU
Azure BastionはStandard SKUのパブリックIPアドレスを要求します。Basic SKUを指定するとエラーになりますので注意してください。
削除について
Azure Bastionは便利なサービスですが、常時稼働させると月額で数千円〜1万円程度のコストがかかります。検証環境や一時的な利用の場合は、使用後にterraform destroy
コマンドでリソースを削除することをお勧めします。
terraform destroy
おわりに
今回は、Terraformを使ってAzure Bastionと仮想マシンを構築し、セキュアなリモートアクセス環境を簡単に作成する方法をご紹介しました。
Azure Bastionを使うことで、踏み台サーバーの構築・運用が不要になり、仮想マシンにパブリックIPを割り当てる必要もなくなります。また、SSHポートを外部に公開する必要がないためセキュリティが向上し、ブラウザから直接SSH接続が可能になるため利便性も高まります。
セキュリティを向上させつつ、運用の手間を減らせるAzure Bastionは、多くのシナリオで有用なサービスだと思います。ぜひ、皆さんも試してみてください。
今回のコードはシンプルな例ですが、実際の環境では複数のVMを管理したり、より複雑なネットワーク構成が必要になることもあるかと思いますが、少しでも参考になりましたら幸いです。
最後までお読みいただき、ありがとうございました!
補足 :Azure BastionのSKU比較
Azure Bastionには、Developer、Basic、Standard、Premiumの4つのSKUがあり、それぞれ利用できる機能や料金が異なります。
各SKUの主な特徴と比較
機能/特徴 | Developer SKU | Basic SKU | Standard SKU | Premium SKU |
---|---|---|---|---|
料金 | 無料 | 有料 | 有料 | 有料 |
同じ仮想ネットワーク内のVM接続 | 可能 | 可能 | 可能 | 可能 |
ピアリングされた仮想ネットワーク内のVM接続 | 不可 | 可能 | 可能 | 可能 |
ホストのスケーリング | 不可 | 不可 | 可能 | 可能 |
ファイルのアップロード/ダウンロード | 不可 | 不可 | 可能 | 可能 |
カスタム受信ポートの指定 | 不可 | 不可 | 可能 | 可能 |
Azure CLIを使用したVM接続 | 不可 | 不可 | 可能 | 可能 |
共有可能リンク | 不可 | 不可 | 可能 | 可能 |
IPアドレスベースの接続 | 不可 | 不可 | 可能 | 可能 |
ネイティブクライアントサポート | 不可 | 不可 | 可能 | 可能 |
セッションの記録 | 不可 | 不可 | 不可 | 可能 |
プライベート専用デプロイ | 不可 | 不可 | 不可 | 可能 |
利用可能なリージョン | 一部限定 | 制限なし | 制限なし | 制限なし |
Bastion専用サブネット | 不要 | 必要 | 必要 | 必要 |
SKU選択のポイント
- Developer SKU: 無料で基本的なSSH/RDP接続機能を利用したい場合に適していますが、同じ仮想ネットワーク内のVMにしか接続できず、利用可能なリージョンも限定されています。開発/テスト環境のコスト削減に役立ちます。
- Basic SKU: ピアリングされた仮想ネットワーク内のVMへの接続が必要な場合の基本的な選択肢です。
- Standard SKU: ホストのスケーリング、ファイルのアップロード/ダウンロード、ネイティブクライアント接続、共有可能リンク、IPアドレスベースの接続など、より高度な機能が必要な場合に選択します。
- Premium SKU: Standard SKUの機能に加え、セッション記録やプライベート専用のデプロイといった、セキュリティやコンプライアンス要件が厳しい場合に適しています。
SKUは後からアップグレードできますが、ダウングレードはサポートされておらず、その場合はBastionを削除して再作成する必要があります。
公式ドキュメントにも情報が記載されているので、必要に応じてご参照ください。