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

  1. 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

  1. Performance

    • Use Standard SKU
    • Configure scale units
    • Enable file copy
    • Optimize networking
  2. Security

    • Configure NSGs
    • Enable auditing
    • Monitor access
    • Control permissions
  3. High Availability

    • Use multiple scale units
    • Configure zones
    • Monitor health
    • Implement backup
  4. 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