Deploy Strongswan On Azure Part2
Deploying the StrongSwan Lab with Terraform
In Part 1, we manually configured StrongSwan inside Azure. For this lab, I am going to use Infrastructure as Code (IaC) to deploy the entire environment in minutes using Terraform. For now we are just going to build out the Azure side of the Lab but with the versatility of terriform we can easily add the Cisco routers to the config. By using terriform we are able to quickly and easily spin up labs in Azure and then take them down with nothing left behind. If you are learning Azure this is great way to deploy some complex Labs without have to endless click through menus.
What Is Terraform?
Terraform is an open-source Infrastructure as Code tool created by HashiCorp. It allows you to define infrastructure in declarative configuration files and then provision it automatically.
Instead of manually clicking through the Azure portal, we will describe:
- Resource Groups
- Virtual Networks
- Subnets
- Network Interfaces
- Security Rules
- Virtual Machines
Terraform reads the configuration, builds a dependency graph, and deploys everything in the correct order.
Installing Terraform
I am using an Ubuntu Machine to run Terriform but it is supported on Mac and Windows as well. Here is the install for Ubuntu Linux.
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
sudo apt-get update && sudo apt-get install terraform
Verify installation:
terraform version
Basic Command
Before we start looking at the script I want to go over the 4 commands that are used the most and there fucntion. We will use the following workflow to deploy and manage the lab environment.
Initialize
terraform init
Initializes the working directory. This downloads the required providers (such as AzureRM) and prepares Terraform to manage the configuration. You only need to run this once per directory, or whenever provider versions change.
Preview Changes
terraform plan
Generates an execution plan showing what Terraform will create, modify, or delete. This allows you to review changes before applying them.
Apply Changes
terraform apply
Creates or updates the infrastructure based on the configuration files. Terraform will prompt for confirmation before making changes.
Destroy Resources
terraform destroy
Removes all resources defined in the configuration. This is especially useful for lab environments, allowing you to tear everything down cleanly when finished.
The Terriform Script
First thing that we need to do is create a file called main.tf this is where the terriform configutaion is stored and the file that we will be editing.
1. Terraform and Provider Configuration
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
}
}
provider "azurerm" {
features {}
}
What this does:
terraformblock: Declares which providers we need. Think of providers as plugins that let Terraform talk to different cloud platforms.required_providers: Specifies we need the Azure Resource Manager provider (azurerm) version 3.xprovider "azurerm": Configures the Azure provider. The emptyfeatures {}block is required by the Azure provider.
Authentication: Terraform uses environment variables (ARM_CLIENT_ID, ARM_CLIENT_SECRET, ARM_SUBSCRIPTION_ID, ARM_TENANT_ID) to authenticate with Azure. These should be set before running Terraform commands. I store thse varibles in a file called “set-azure-creds.sh.” It is best practice to create a service user just for terriform to use.
2. Variables - Making Configuration Flexible
variable "resource_group_name" {
default = "strongswan-rg"
}
variable "location" {
default = "eastus"
}
variable "vm_name" {
default = "strongswan-vm"
}
variable "admin_username" {
default = "azureuser"
}
variable "admin_password" {
description = "ADMIN PASSWORD"
type = string
sensitive = true
}
What this does:
- Defines input variables with default values
- Makes the configuration reusable - change the defaults or pass different values at runtime
defaultvalues mean you don’t have to specify them when runningterraform apply- Defines a password that we will later use to login to the VM
3. Resource Group - The Container
resource "azurerm_resource_group" "main" {
name = var.resource_group_name
location = var.location
}
What this does:
- Creates an Azure Resource Group - a logical container for related resources
- Uses the variables we defined above (notice
var.resource_group_name)
Why it matters: Resource groups let you manage lifecycle, permissions, and billing for related resources as a unit.
4. Virtual Network and Subnet
resource "azurerm_virtual_network" "main" {
name = "${var.vm_name}-vnet"
address_space = ["10.250.0.0/20"]
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
}
resource "azurerm_subnet" "outside" {
name = "outside-subnet"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.250.1.0/24"]
}
resource "azurerm_subnet" "server" {
name = "server-subnet-1"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.250.2.0/24"]
}
What this does:
- VNet: Creates a virtual network with a 10.0.0.0/16 address space (65,536 addresses)
- Subnet: Carves out a 10.0.1.0/24 subnet (256 addresses) within that VNet
- String interpolation:
"${var.vm_name}-vnet"creates dynamic names like “strongswan-vm-vnet”
Key Terraform concept - Resource References:
azurerm_resource_group.main.locationreferences the location from the resource group we createdazurerm_virtual_network.main.namereferences the VNet’s name- Terraform automatically understands dependencies: it will create the VNet before the subnet, and the resource group before both
5. Public IP Address
resource "azurerm_public_ip" "main" {
name = "${var.vm_name}-public-ip"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
allocation_method = "Static"
sku = "Standard"
}
What this does:
- Creates a static public IP address
- Static vs Dynamic: Static means the IP won’t change even if the VM is stopped/started
- Standard SKU: Required for zone-redundant deployments and certain load balancer configurations
Important detail: This public IP will be automatically injected into the StrongSwan configuration later!
6. Network Security Group - The Firewall Rules
resource "azurerm_network_security_group" "main" {
name = "${var.vm_name}-nsg"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
security_rule {
name = "SSH"
priority = 1001
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
}
security_rule {
name = "IPSec-IKE"
priority = 1002
direction = "Inbound"
access = "Allow"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "500"
source_address_prefix = "*"
destination_address_prefix = "*"
}
security_rule {
name = "IPSec-NAT-T"
priority = 1003
direction = "Inbound"
access = "Allow"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "4500"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
What this does:
- Creates firewall rules for the network
- SSH (port 22): For remote management
- UDP 500: IKE (Internet Key Exchange) - IPsec negotiation
- UDP 4500: NAT-T (NAT Traversal) - IPsec through NAT devices
Security note: In production, restrict source_address_prefix to specific IP ranges instead of “*” (anywhere).
7. Network Interface - Connecting the VM to the Network
resource "azurerm_network_interface" "outside" {
name = "${var.vm_name}-nic-outside"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
ip_forwarding_enabled = true
ip_configuration {
name = "external"
subnet_id = azurerm_subnet.outside.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.main.id
}
}
resource "azurerm_network_interface" "server" {
name = "${var.vm_name}-nic-server"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
ip_forwarding_enabled = true
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.server.id
private_ip_address_allocation = "Dynamic"
}
}
resource "azurerm_network_interface_security_group_association" "outside" {
network_interface_id = azurerm_network_interface.outside.id
network_security_group_id = azurerm_network_security_group.main.id
}
resource "azurerm_network_interface_security_group_association" "server" {
network_interface_id = azurerm_network_interface.server.id
network_security_group_id = azurerm_network_security_group.main.id
}
What this does:
- Creates a virtual NIC for the VM
- Attaches it to the subnet
- Associates the public IP address
- Applies the security group rules
Key point: The NIC is the “glue” that connects the VM to the network infrastructure.
8. The Virtual Machine
resource "azurerm_linux_virtual_machine" "main" {
name = var.vm_name
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
size = "Standard_B2s"
admin_username = var.admin_username
admin_password = var.admin_password
disable_password_authentication = false
network_interface_ids = [
azurerm_network_interface.outside.id,
azurerm_network_interface.server.id,
]
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts-gen2"
version = "latest"
}
}
What this does:
- VM size: Standard_B2s (2 vCPU, 4GB RAM) - burstable, cost-effective
- OS image: Ubuntu 22.04 LTS from Canonical
- authentication: Choose a the admin/password login
Deploying the Infrastructure
Now that we have our complete main.tf file, let’s deploy it!
Step 1: Initialize Terraform
terraform init
This downloads the Azure provider and prepares your working directory.
Step 2: Validate the Configuration
terraform validate
This checks for syntax errors in your configuration files.
Step 3: Preview the Changes
terraform plan
Review what Terraform will create. You should see output showing 7 resources to be added:
- 1 Resource Group
- 1 Virtual Network
- 1 Subnet
- 1 Public IP
- 1 Network Security Group
- 1 Network Interface (and its NSG association)
- 1 Virtual Machine
Step 4: Apply the Configuration
terraform apply
Terraform will prompt you to enter the admin password (since we marked it as sensitive and didn’t provide a default). Type a secure password and press Enter.
Type yes when prompted to confirm the deployment.
The deployment will take approximately 3-5 minutes. You’ll see output showing the progress of each resource being created.