Managing Azure Bastion with Terraform
Learn how to set up and manage Azure Bastion using Terraform, including host configuration and security settings
Managing Azure Bastion with Terraform
Azure Bastion is a fully managed PaaS service that provides secure and seamless RDP and SSH access to your virtual machines. This guide demonstrates how to set up and manage Bastion using Terraform.
Video Tutorial
Learn more about managing Azure Bastion with Terraform in this comprehensive video tutorial:
Prerequisites
- Azure CLI configured with appropriate permissions
- Terraform installed (version 1.0.0 or later)
- Resource group created
- Understanding of networking concepts
Project Structure
terraform-azure-bastion/
├── main.tf
├── variables.tf
├── outputs.tf
├── modules/
│ └── bastion/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── configs/
└── settings.json
Bastion Configuration
Create modules/bastion/main.tf
:
# Virtual Network
resource "azurerm_virtual_network" "main" {
name = "${var.project_name}-vnet"
address_space = ["10.0.0.0/16"]
location = var.location
resource_group_name = var.resource_group_name
tags = var.tags
}
# AzureBastionSubnet
resource "azurerm_subnet" "bastion" {
name = "AzureBastionSubnet"
resource_group_name = var.resource_group_name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.0.1.0/24"]
}
# Public IP
resource "azurerm_public_ip" "bastion" {
name = "${var.project_name}-bastion-pip"
location = var.location
resource_group_name = var.resource_group_name
allocation_method = "Static"
sku = "Standard"
zones = ["1", "2", "3"]
tags = var.tags
}
# Bastion Host
resource "azurerm_bastion_host" "main" {
name = "${var.project_name}-bastion"
location = var.location
resource_group_name = var.resource_group_name
sku = "Standard"
scale_units = 2
ip_configuration {
name = "configuration"
subnet_id = azurerm_subnet.bastion.id
public_ip_address_id = azurerm_public_ip.bastion.id
}
copy_paste_enabled = true
file_copy_enabled = true
shareable_link_enabled = true
tunneling_enabled = true
ip_connect_enabled = true
tags = var.tags
}
# Network Security Group
resource "azurerm_network_security_group" "bastion" {
name = "${var.project_name}-bastion-nsg"
location = var.location
resource_group_name = var.resource_group_name
security_rule {
name = "AllowHttpsInbound"
priority = 120
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "Internet"
destination_address_prefix = "*"
}
security_rule {
name = "AllowGatewayManagerInbound"
priority = 130
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "GatewayManager"
destination_address_prefix = "*"
}
security_rule {
name = "AllowAzureLoadBalancerInbound"
priority = 140
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "AzureLoadBalancer"
destination_address_prefix = "*"
}
security_rule {
name = "AllowBastionHostCommunication"
priority = 150
direction = "Inbound"
access = "Allow"
protocol = "*"
source_port_range = "*"
destination_port_ranges = ["8080", "5701"]
source_address_prefix = "VirtualNetwork"
destination_address_prefix = "VirtualNetwork"
}
security_rule {
name = "AllowSshRdpOutbound"
priority = 100
direction = "Outbound"
access = "Allow"
protocol = "*"
source_port_range = "*"
destination_port_ranges = ["22", "3389"]
source_address_prefix = "*"
destination_address_prefix = "VirtualNetwork"
}
security_rule {
name = "AllowAzureCloudOutbound"
priority = 110
direction = "Outbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "*"
destination_address_prefix = "AzureCloud"
}
tags = var.tags
}
resource "azurerm_subnet_network_security_group_association" "bastion" {
subnet_id = azurerm_subnet.bastion.id
network_security_group_id = azurerm_network_security_group.bastion.id
}
# Target VM Subnet
resource "azurerm_subnet" "vm" {
name = "vm-subnet"
resource_group_name = var.resource_group_name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.0.2.0/24"]
}
# VM Network Security Group
resource "azurerm_network_security_group" "vm" {
name = "${var.project_name}-vm-nsg"
location = var.location
resource_group_name = var.resource_group_name
security_rule {
name = "AllowBastionInbound"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "*"
source_port_range = "*"
destination_port_ranges = ["22", "3389"]
source_address_prefix = "10.0.1.0/24"
destination_address_prefix = "VirtualNetwork"
}
tags = var.tags
}
resource "azurerm_subnet_network_security_group_association" "vm" {
subnet_id = azurerm_subnet.vm.id
network_security_group_id = azurerm_network_security_group.vm.id
}
# Role Assignment
resource "azurerm_role_assignment" "bastion" {
scope = azurerm_virtual_network.main.id
role_definition_name = "Network Contributor"
principal_id = data.azurerm_client_config.current.object_id
}
## Network Configuration
1. **Route Table**
```hcl
resource "azurerm_route_table" "bastion" {
name = "${var.project_name}-bastion-rt"
location = var.location
resource_group_name = var.resource_group_name
route {
name = "ToInternet"
address_prefix = "0.0.0.0/0"
next_hop_type = "Internet"
}
tags = var.tags
}
resource "azurerm_subnet_route_table_association" "bastion" {
subnet_id = azurerm_subnet.bastion.id
route_table_id = azurerm_route_table.bastion.id
}
Monitoring Configuration
- Diagnostic Settings
resource "azurerm_monitor_diagnostic_setting" "bastion" {
name = "${var.project_name}-diag"
target_resource_id = azurerm_bastion_host.main.id
log_analytics_workspace_id = var.log_analytics_workspace_id
log {
category = "BastionAuditLogs"
enabled = true
retention_policy {
enabled = true
days = 30
}
}
metric {
category = "AllMetrics"
enabled = true
retention_policy {
enabled = true
days = 30
}
}
}
resource "azurerm_monitor_metric_alert" "bastion" {
name = "${var.project_name}-session-alert"
resource_group_name = var.resource_group_name
scopes = [azurerm_bastion_host.main.id]
description = "Alert when session count is high"
criteria {
metric_namespace = "Microsoft.Network/bastionHosts"
metric_name = "sessions"
aggregation = "Average"
operator = "GreaterThan"
threshold = 50
}
action {
action_group_id = var.action_group_id
}
}
Best Practices
-
Performance
- Use Standard SKU
- Configure scale units
- Enable file copy
- Optimize networking
-
Security
- Configure NSGs
- Enable auditing
- Monitor access
- Control permissions
-
High Availability
- Use multiple scale units
- Configure zones
- Monitor health
- Implement backup
-
Cost Optimization
- Choose appropriate SKU
- Monitor usage
- Scale appropriately
- Review configurations
Conclusion
You’ve learned how to set up and manage Azure Bastion using Terraform. This setup provides:
- Secure remote access
- Native RDP/SSH
- File transfer
- Session management
Remember to:
- Monitor sessions
- Review security
- Update configurations
- Maintain access