TerraformでAzure Bastionを構築する
はじめに
みなさんは踏み台サーバを利用されていますか?
踏み台サーバーを使用する理由は、中継サーバを作りセキュリティ強化を行うことなどが挙げられます。Azureには、プロビジョニングされる仮想ネットワーク内のすべてのVMに対して安全な RDPおよびSSH接続を提供する、Bastionという製品があります。
最近Azure Bastinを使う場面が何度かあったので少し書いていきます。
Azure Bastionとは
Azure Bastion は、ブラウザーと Azure portal を使用して仮想マシンに接続できるようにするサービスです。
Bastionを使うことで、TLS 経由で Azure portal から直接、仮想マシンに安全かつシームレスに RDP/SSH 接続できます。 Azure Bastion 経由で接続する場合、仮想マシンにパブリック IP アドレス、エージェント、クライアント または特別なクライアントソフトウェアは必要ありません。
詳細はこちら
動作環境
- macOS Big Sur: 11.5.1
- azure-cli: 2.35.0
- Terraform: v1.1.3 on darwin_amd64
- Provider azurerm: 3.7.0
Teraformによる構築
以下の構成で作っていきます。
※Diagramsというツールを使って図を作成してみたのですが、なかなか上手く作れず。。慣れたらいい感じに作れるようになるのだろうか。
Terraform backendは特に指定せず、ローカル環境を使います。
リソースグループ作成
最初にリソースグループを作成します。お好きなリージョンで作成してください。
resource "azurerm_resource_group" "rg_bastion" {
name = "rg-bas"
location = "japaneast"
}
ネットワークリソース作成
Bastionを使用する際はSubnetの名前をAzureBastionSubnetにする必要があります。
resource "azurerm_virtual_network" "vn_bastion" {
name = "vn-bas"
location = azurerm_resource_group.rg_bastion.location
resource_group_name = azurerm_resource_group.rg_bastion.name
address_space = ["10.3.0.0/16"]
dns_servers = ["10.0.0.4", "10.0.0.5"]
}
resource "azurerm_subnet" "sn_bastion" {
name = "AzureBastionSubnet"
resource_group_name = azurerm_resource_group.rg_bastion.name
virtual_network_name = azurerm_virtual_network.vn_bastion.name
address_prefixes = ["10.3.1.0/24"]
}
resource "azurerm_subnet" "sn_private" {
name = "sn-private"
resource_group_name = azurerm_resource_group.rg_bastion.name
virtual_network_name = azurerm_virtual_network.vn_bastion.name
address_prefixes = ["10.3.2.0/24"]
}
resource "azurerm_subnet_network_security_group_association" "nsg_association_bastion" {
subnet_id = azurerm_subnet.sn_bastion.id
network_security_group_id = azurerm_network_security_group.nsg_bastion.id
}
resource "azurerm_subnet_network_security_group_association" "nsg_association_private" {
subnet_id = azurerm_subnet.sn_private.id
network_security_group_id = azurerm_network_security_group.nsg_private.id
}
Bastion作成
BastionのSKUはBasicとStandardが選択できます。
tunneling_enabled
などの設定値はSKUがStandardの場合に使用できます。
違いはこちら
resource "azurerm_public_ip" "pip_bastion" {
name = "pip-bas"
location = azurerm_resource_group.rg_bastion.location
resource_group_name = azurerm_resource_group.rg_bastion.name
allocation_method = "Static"
sku = "Standard"
}
resource "azurerm_bastion_host" "host_bastion" {
name = "host-bas"
location = azurerm_resource_group.rg_bastion.location
resource_group_name = azurerm_resource_group.rg_bastion.name
sku = Standard
tunneling_enabled = true
ip_configuration {
name = "vm_ip_configuration"
subnet_id = azurerm_subnet.sn_bastion.id
public_ip_address_id = azurerm_public_ip.pip_bastion.id
}
}
NSGの作成
BastionのパブリックIPでは、イグレストラフィック用にポート443が有効になっている必要があります。
その他、細々したルールはこちらにあります。
resource "azurerm_network_security_group" "nsg_bastion" {
name = "nsg-bastion"
location = azurerm_resource_group.rg_bastion.location
resource_group_name = azurerm_resource_group.rg_bastion.name
security_rule {
name = "AllowGatewayInBound"
priority = 1000
direction = "Inbound"
access = "Allow"
protocol = "TCP"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "GatewayManager"
destination_address_prefix = "*"
}
security_rule {
name = "AllowInternetInBound"
priority = 1001
direction = "Inbound"
access = "Allow"
protocol = "TCP"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "Internet"
destination_address_prefix = "*"
}
security_rule {
name = "AllowPrivateVnetOutBound"
priority = 1001
direction = "Outbound"
access = "Allow"
protocol = "TCP"
source_port_range = "*"
destination_port_ranges = ["3389","22"]
source_address_prefix = "*"
destination_address_prefix = "VirtualNetwork"
}
security_rule {
name = "AllowAzureCloudOutBound"
priority = 1002
direction = "Outbound"
access = "Allow"
protocol = "TCP"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "*"
destination_address_prefix = "AzureCloud"
}
}
resource "azurerm_network_security_group" "nsg_private" {
name = "nsg-private"
location = azurerm_resource_group.rg_bastion.location
resource_group_name = azurerm_resource_group.rg_bastion.name
security_rule {
name = "AllowBastionVnetInBound"
priority = 1000
direction = "Inbound"
access = "Allow"
protocol = "*"
source_port_range = "*"
destination_port_ranges = ["3389","22"]
source_address_prefix = "VirtualNetwork"
destination_address_prefix = "*"
}
security_rule {
name = "AllowBastionVnetOutBound"
priority = 1000
direction = "Outbound"
access = "Allow"
protocol = "*"
source_port_range = "*"
destination_port_ranges = ["3389","22"]
source_address_prefix = "VirtualNetwork"
destination_address_prefix = "*"
}
}
VMの作成
Bastion経由でアクセスするのでパブリックIPは割り当てません。
resource "azurerm_network_interface" "nic_testvm" {
name = "nic-testvm"
location = azurerm_resource_group.rg_bastion.location
resource_group_name = azurerm_resource_group.rg_bastion.name
ip_configuration {
name = "NicConfiguration"
subnet_id = azurerm_subnet.sn_private.id
private_ip_address_allocation = "Dynamic"
}
}
resource "azurerm_linux_virtual_machine" "vm_test" {
name = "vm-test"
location = azurerm_resource_group.rg_bastion.location
resource_group_name = azurerm_resource_group.rg_bastion.name
network_interface_ids = [azurerm_network_interface.nic_testvm.id]
size = "Standard_B2s"
os_disk {
name = "testVmOsDisk"
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-focal"
sku = "20_04-lts-gen2"
version = "latest"
}
computer_name = "mgmt"
admin_username = "basuser"
admin_ssh_key {
username = "basuser"
public_key = tls_private_key.ssh_bastion.public_key_openssh
}
}
resource "tls_private_key" "ssh_bastion" {
algorithm = "RSA"
rsa_bits = 4096
}
resource "local_file" "private_key_pem" {
filename = "./bas_ssh_key.pem"
content = tls_private_key.ssh_bastion.private_key_pem
provisioner "local-exec" {
command = "chmod 600 ./bas_ssh_key.pem"
}
}
Terraform実行
❯❯❯ terraform init
❯❯❯ terraform plan
❯❯❯ terraform apply
リソースの確認
Terraformで作成したリソースがポータル上から確認できました。
さらに、ネイティブクライアントを利用してBastionへのアクセスを参考にiTermからのアクセスを試してみます。
BastionはWeb画面から操作できるので便利ですが、やっぱり普段使い慣れたツールが使える方がいいですよね。
set VM_ID (az vm list -g rg-bas --query "[].id" -otsv)
❯❯❯ az network bastion ssh -n host-bas -g rg-bas --target-resource-id $VM_ID --auth-type ssh-key --username basuser --ssh-key ./bas_ssh_key.pem
Command group 'network bastion' is in preview and under development. Reference and support levels: https://aka.ms/CLI_refstatus
Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.13.0-1023-azure x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Mon May 23 14:32:04 UTC 2022
System load: 0.0 Processes: 114
Usage of /: 4.9% of 28.90GB Users logged in: 0
Memory usage: 6% IPv4 address for eth0: 10.3.2.4
Swap usage: 0%
1 update can be applied immediately.
To see these additional updates run: apt list --upgradable
The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Last login: Sat May 21 04:46:38 2022 from 10.3.1.4
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
basuser@mgmt:~$
Bastionを通して、パブリックIPを持たないVMにアクセスすることができました。
他にもパスワードやAADを利用したアクセスも可能でした。
ただ、Azure Key Vaultとの連携間まだできないようでした。(これからできるようになるのかな?)
まとめ
Terraformを使って簡単にBastionを利用した接続感橋を構築することができました。マネージドサービスなので簡単にセキュアな接続環境が構築できる点がとても嬉しいですね。
ただそこそこな金額(最初の 5 GB/月)なので、自分でセキュアな環境を構築できる人はあまり有り難みがないのかもしれませんね。
また今回初めてネイティブクライアントを使用してBastion経由で仮想マシンにアクセスしてみました。今までBastionを使うときはWebブラウザからアクセスしていたので、普段使い慣れたクライアントアプリからアクセスできるのはとても便利だと感じました。
Discussion