3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Azure Bastion を Terraform で構築

Posted at

はじめに

Azure Portal からウェブブラウザを通して VM へのアクセスを提供する、Azure Bastion がある
Public IP を持たない VM へのアクセス手段にもなる
Azure Bastion の概要を調べ、Terraform でコード化して構築したのでその内容を記載する

スクリーンショット 2022-04-09 12.42.12.png

Azure Bastion

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 は BasicStandard がある (ドキュメント)
価格を 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 経由でアクセスする

AzureBastion構成.png

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 : リソース定義
modules/hub/azure-bastion/variables.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
}

modules/hub/azure-bastion/main.tf
# 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を選択する

スクリーンショット 2022-04-09 12.42.12.png

ユーザー名と、認証の種類と必要な項目を入力して接続をクリックする

スクリーンショット 2022-04-09 14.49.53.png

上記実施すると、下記のようなタブが追加され HTML5 で操作可能

スクリーンショット 2022-04-09 14.52.07.png

NSG で送信元を制限する場合、許可してない IP からアクセス時は下記のようにエラーとなる

スクリーンショット 2022-04-09 15.04.09.png

Windows VM への接続テスト

仮想マシンの「接続」からBastionを選択して下記画面を表示して、
ユーザー名パスワードを入力して、接続をクリックする

スクリーンショット 2022-04-09 18.17.20.png

ログインが始まり、操作可能になる

スクリーンショット 2022-04-09 18.13.43.png
スクリーンショット 2022-04-09 18.14.58.png

以上、テスト終了。

おわりに

Azure Bastion の作成とテストまでを実施した
Terraform 化したので必要時に terraform apply で作成して、不要時は金額が高額なのでterraform destroyで削除しておくのが、個人ラボ利用としては良さそう

参考

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?