はじめに
Azure Portal からウェブブラウザを通して VM へのアクセスを提供する、Azure Bastion がある
Public IP を持たない VM へのアクセス手段にもなる
Azure Bastion の概要を調べ、Terraform でコード化して構築したのでその内容を記載する
Azure Bastion
- Azure portal を使用して仮想マシンに接続できるようにするサービス
- ユーザの仮想ネットワーク内でプロビジョニングする、フル プラットフォーム マネージド PaaS サービス
- Azure portal から直接、TLS 経由で仮想マシンに RDP/SSH 接続
- 接続先の仮想マシンにパブリック IP アドレス、エージェント、クライアント ソフトウェアはいずれも不要
利点はドキュメントに表でまとまっているのでそちらを参照
サブネット
Azure Bastion は指定された名前で指定されたサイズ以上で Subnet を作成する必要がある (ドキュメント)
- サブネット名: AzureBastionSubnet
- サイズ : /26 以上
NSG (Network Security Group)
AzureBastionSubnet への NSG 設定は、指定されたルールを設定しないと設定ができない
必要なルールはドキュメントから抜粋したものが下記の通り
Ingress Traffic
内容 | Port | Protocol | Source | Destination |
---|---|---|---|---|
パブリック インターネットからの Ingress トラフィック | 443 | Tcp | Internet or 指定したパブリック IP アドレスのセット |
Any |
Azure Bastion からの Ingress トラフィックのコントロール プレーン | 443 | Tcp | GatewayManager | Any |
Azure Load Balancer からの Ingress トラフィック | 443 | Tcp | AzureLoadBalancer | Any |
Azure Bastion データ プレーンからの Ingress トラフィック | 8080, 5701 | Any | VirtualNetwork | VirtualNetwork |
Egress Traffic
内容 | Port | Protocol | Source | Destination |
---|---|---|---|---|
ターゲット VM への Egress トラフィック | 22, 3389 | Any | Any | VirtualNetwork |
Azure の他のパブリックエンド ポイントへの Egress トラフィック | 443 | Tcp | Any | AzureCloud |
Azure Bastion データ プレーンへの Egress トラフィック | 8080, 5701 | Any | VirtualNetwork | VirtualNetwork |
インターネットへの Egress トラフィック | 80 | Any | Any | Internet |
SKU / コスト
SKU は Basic
と Standard
がある (ドキュメント)
価格を Azure サイトから抜粋したもの (2022.04.09 時点 / 1 USD = 122.71 JPY)
SKU | 料金/時間 | 料金/月(31日) | 機能 |
---|---|---|---|
Basic | ¥23.315 | ¥17,346.36 | ・ピアリングされた仮想ネットワーク内のターゲット VM に接続する ・Azure Key Vault (AKV) で Linux VM のプライベート キーにアクセスする ・SSH を使用した Linux VM への接続 ・RDP を使用した Windows VM への接続 |
Standard | ¥35.586 | ¥26,475.984 | Basic の機能に加えて下記の機能が追加される ・ホストのスケーリング (料金は2インスタンス含. それ以上の追加は ¥17.180 / 時間) ・カスタム受信ポートの指定 ・RDP を使用した Linux VM への接続 ・SSH を使用した Windows VM への接続 ・ファイルのアップロードまたはダウンロード |
追加で送信データ転送コストがかかる (ドキュメント参照. 最初の 5 GB/月は無料)
ひと月で約1万7千円以上かかるので、個人利用の場合は注意が必要
対応ブラウザ
2022.04.09 時点の FAQ ページより抜粋
- HTML 5 をサポートしている必要がある
- Windows : Microsoft Edge または Google Chrome
- Mac : Google Chrome
IPv6 非サポート
2022.04.09 時点、サポートされていない (FAQから抜粋)
Terraform での構築・テスト
実際に Terraform でコード化して、テスト構築して利用テストをしたので、
サンプルコードと試した内容を記載する
前提
Azure Bastion を構築する上で下記構築済み
- Azure 環境は構築済み
- VNET や VM は構築済み
また、下記も Hub Spoke で Hub に Bastion を作成して Spoke の VM にアクセスできることを確認するため実施済み
- Hub-Spoke 構成を構築済み
上記の構築の詳細は別 Qiita 記事で記載している
Terraform Version は下記の通り
- terraform : v1.1.7
- provider registry.terraform.io/hashicorp/azurerm : v3.1.0
構成
下記構成で構築・試している
Hub/Spoke 構成で、Hub 側に Azure Bastion を構築して、Spoke 側の VM へ Azure Portal 経由でアクセスする
Terraform コード
Azure Bastion を作成するコードを module 化したコードを記載する
Azure Bastion
と Azure Bastion が所属するAzureBastionSubnet
向けの NSG 設定をコード化している
module 利用コード
記載する module コードの利用コード例が下記の通り
- hostname
- Azure Bastion の名前
- location
- Azure Bastion を設置するリージョン
- resource_group_name
- Azure Bastion を設置するリソースグループ名
- subnet_id
- Azure Bastion を設置する VNET で作成した Subnet ID (作成サブネット名は AzureBastionSubnet)
- nsg_public_source_ips
- ウェブブラウザで Azure Portal からアクセスする送信元の PC の Public IP のリスト
module "hub-azure-batsion" {
source = "../../../../../modules/hub/azure-bastion"
hostname = "bas-testinfra-japaneast-001"
location = "japaneast"
resource_group_name = "rg-hub-testinfra-japaneast-001"
subnet_id = "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg-hub-testinfra-japaneast-001/providers/Microsoft.Network/virtualNetworks/vnet-testinfra-japaneast-001/subnets/AzureBastionSubnet"
nsg_public_source_ips = ["x.x.x.x/32(HomeLabo Public IP)", ]
}
module コード
Terraform コード例は下記の通り
- variables.tf : 設定する変数
- main.tf : リソース定義
variable "resource_group_name" {}
variable "location" {}
variable "hostname" {}
variable "subnet_id" {} # https://docs.microsoft.com/ja-jp/azure/bastion/configuration-settings#subnet /26 以上
# https://docs.microsoft.com/ja-jp/azure/bastion/configuration-settings#skus
## Basic -> Standard 可能 https://docs.microsoft.com/ja-jp/azure/bastion/upgrade-sku
variable "sku" {
type = string
default = "Basic" # Basic or Standard
}
# azurerm version 2.93.0 以上が必須
variable "copy_paste_enabled" {
type = bool
default = true
}
# azurerm version 2.93.0 以上が必須
# sku = "Standard" が必須
variable "file_copy_enabled" {
type = bool
default = true
}
# azurerm version 2.93.0 以上が必須
# sku = "Standard" が必須
variable "scale_units" {
type = number
default = 2
}
variable "nsg_public_source_ips" {
type = list(string)
default = null
}
# NSG / https://docs.microsoft.com/ja-jp/azure/bastion/bastion-nsg
resource "azurerm_network_security_group" "nsg" {
name = "nsg-azurebastion-001"
location = var.location
resource_group_name = var.resource_group_name
}
resource "azurerm_network_security_rule" "ingress-rule-1" {
name = "AllowHttpsInbound"
priority = 1000
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_ranges = ["443", ]
source_address_prefix = var.nsg_public_source_ips == null ? "Internet" : null
source_address_prefixes = var.nsg_public_source_ips == null ? null : var.nsg_public_source_ips
destination_address_prefix = "*"
resource_group_name = azurerm_network_security_group.nsg.resource_group_name
network_security_group_name = azurerm_network_security_group.nsg.name
}
resource "azurerm_network_security_rule" "ingress-rule-2" {
name = "AllowGatewayManagerInbound"
priority = 1010
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_ranges = ["443", ]
source_address_prefix = "GatewayManager"
destination_address_prefix = "*"
resource_group_name = azurerm_network_security_group.nsg.resource_group_name
network_security_group_name = azurerm_network_security_group.nsg.name
}
resource "azurerm_network_security_rule" "ingress-rule-3" {
name = "AllowAzureLoadBalancerInbound"
priority = 1020
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_ranges = ["443", ]
source_address_prefix = "AzureLoadBalancer"
destination_address_prefix = "*"
resource_group_name = azurerm_network_security_group.nsg.resource_group_name
network_security_group_name = azurerm_network_security_group.nsg.name
}
resource "azurerm_network_security_rule" "ingress-rule-4" {
name = "AllowBastionHostCommunicationInbound"
priority = 1030
direction = "Inbound"
access = "Allow"
protocol = "*"
source_port_range = "*"
destination_port_ranges = ["8080", "5701", ]
source_address_prefix = "VirtualNetwork"
destination_address_prefix = "VirtualNetwork"
resource_group_name = azurerm_network_security_group.nsg.resource_group_name
network_security_group_name = azurerm_network_security_group.nsg.name
}
resource "azurerm_network_security_rule" "ingress-rule-deny" {
name = "DenyAllInbound"
priority = 4096
direction = "Inbound"
access = "Deny"
protocol = "*"
source_port_range = "*"
destination_port_range = "*"
source_address_prefix = "*"
destination_address_prefix = "*"
resource_group_name = azurerm_network_security_group.nsg.resource_group_name
network_security_group_name = azurerm_network_security_group.nsg.name
}
resource "azurerm_network_security_rule" "egress-rule-1" {
name = "AllowSshRdpOutbound"
priority = 1000
direction = "Outbound"
access = "Allow"
protocol = "*"
source_port_range = "*"
destination_port_ranges = ["22", "3389", ]
source_address_prefix = "*"
destination_address_prefix = "VirtualNetwork"
resource_group_name = azurerm_network_security_group.nsg.resource_group_name
network_security_group_name = azurerm_network_security_group.nsg.name
}
resource "azurerm_network_security_rule" "egress-rule-2" {
name = "AllowAzureCloudOutbound"
priority = 1010
direction = "Outbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_ranges = ["443", ]
source_address_prefix = "*"
destination_address_prefix = "AzureCloud"
resource_group_name = azurerm_network_security_group.nsg.resource_group_name
network_security_group_name = azurerm_network_security_group.nsg.name
}
resource "azurerm_network_security_rule" "egress-rule-3" {
name = "AllowBastionHostCommunicationOutbound"
priority = 1020
direction = "Outbound"
access = "Allow"
protocol = "*"
source_port_range = "*"
destination_port_ranges = ["8080", "5701", ]
source_address_prefix = "VirtualNetwork"
destination_address_prefix = "VirtualNetwork"
resource_group_name = azurerm_network_security_group.nsg.resource_group_name
network_security_group_name = azurerm_network_security_group.nsg.name
}
resource "azurerm_network_security_rule" "egress-rule-4" {
name = "AllowGetSessionInfomation"
priority = 1030
direction = "Outbound"
access = "Allow"
protocol = "*"
source_port_range = "*"
destination_port_ranges = ["80", ]
source_address_prefix = "*"
destination_address_prefix = "Internet"
resource_group_name = azurerm_network_security_group.nsg.resource_group_name
network_security_group_name = azurerm_network_security_group.nsg.name
}
resource "azurerm_network_security_rule" "egress-rule-deny" {
name = "DenyAllOutbound"
priority = 4096
direction = "Outbound"
access = "Deny"
protocol = "*"
source_port_range = "*"
destination_port_range = "*"
source_address_prefix = "*"
destination_address_prefix = "*"
resource_group_name = azurerm_network_security_group.nsg.resource_group_name
network_security_group_name = azurerm_network_security_group.nsg.name
}
resource "azurerm_subnet_network_security_group_association" "nsg" {
subnet_id = var.subnet_id
network_security_group_id = azurerm_network_security_group.nsg.id
depends_on = [
azurerm_network_security_rule.ingress-rule-1,
azurerm_network_security_rule.ingress-rule-2,
azurerm_network_security_rule.ingress-rule-3,
azurerm_network_security_rule.ingress-rule-4,
azurerm_network_security_rule.ingress-rule-deny,
azurerm_network_security_rule.egress-rule-1,
azurerm_network_security_rule.egress-rule-2,
azurerm_network_security_rule.egress-rule-3,
azurerm_network_security_rule.egress-rule-4,
azurerm_network_security_rule.egress-rule-deny,
]
}
# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/bastion_host
resource "azurerm_public_ip" "main" {
name = "pip-bas-${var.location}-001"
location = var.location
resource_group_name = var.resource_group_name
allocation_method = "Static"
sku = "Standard"
}
resource "azurerm_bastion_host" "main" {
name = var.hostname
location = var.location
resource_group_name = var.resource_group_name
sku = var.sku
copy_paste_enabled = var.copy_paste_enabled
file_copy_enabled = var.sku == "Basic" ? null : var.file_copy_enabled
scale_units = var.sku == "Basic" ? null : var.scale_units
ip_configuration {
name = "configuration"
subnet_id = var.subnet_id
public_ip_address_id = azurerm_public_ip.main.id
}
depends_on = [
azurerm_subnet_network_security_group_association.nsg,
]
}
構築後のアクセステスト
Linux VM への接続テスト
仮想マシンの「接続」からBastion
を選択する
ユーザー名
と、認証の種類
と必要な項目を入力して接続
をクリックする
上記実施すると、下記のようなタブが追加され HTML5 で操作可能
NSG で送信元を制限する場合、許可してない IP からアクセス時は下記のようにエラーとなる
Windows VM への接続テスト
仮想マシンの「接続」からBastion
を選択して下記画面を表示して、
ユーザー名
とパスワード
を入力して、接続
をクリックする
ログインが始まり、操作可能になる
以上、テスト終了。
おわりに
Azure Bastion の作成とテストまでを実施した
Terraform 化したので必要時に terraform apply
で作成して、不要時は金額が高額なのでterraform destroy
で削除しておくのが、個人ラボ利用としては良さそう
参考