I often need a Palo Alto firewall to test new features or just to try out a lab exercise. But a hardware appliance is not always available. Running a virtual firewall in a home lab environment using GNS3 or Eve-ng is useful. But typically, I need to be connected to the home network to use it.
Due to both restrictions mentioned above, my preference is often to spin up a Palo Alto VM-series firewall in AWS when I need a test instance for a short period of time. Once I finish using it, I shut it down.
But provisioning a Palo Alto VM firewall manually via the AWS console is also not ideal. First of all, it takes time to create all the different components manually through the console. Secondly, it is prone to error.
To get around these issues, my preference has always been to use Terraform to provision my Palo Alto firewalls. With my ready-made Terraform script, all I need to do is run ‘terraform apply’ when I want to provision a firewall for a test, and run ‘terraform destroy’ when I’m done with my testing.
In this post, I share the terraform script that I use for provisioning my Palo Alto VM series firewall
Provision a VPC
My first step is to provision the VPC where the firewall will reside. Within the VPC, I create a subnet, route table and internet gateway. The internet gateway provides internet access outside the VPC, allowing me access the firewall via HTTPs or SSH from the list of IPs I specify in my security group. The code for the VPC is shown below:
provider "aws" {
region = "eu-west-1"
}
resource "aws_vpc" "palo_vpc" {
cidr_block = var.cidr_block
tags = {
Name = "base-palo-alto-firewall-vpc"
}
}
resource "aws_internet_gateway" "palo_vpc_igw" {
vpc_id = aws_vpc.palo_vpc.id
tags = {
Name = "palo-vpc-igw"
}
}
resource "aws_subnet" "palo_vpc_subnet" {
vpc_id = aws_vpc.palo_vpc.id
cidr_block = var.cidr_block
tags = {
Name = "palo-vpc-main-subnet"
}
}
resource "aws_route_table_association" "palo_vpc_rt_assoc" {
subnet_id = aws_subnet.palo_vpc_subnet.id
route_table_id = aws_route_table.palo_vpc_rt.id
}
resource "aws_route_table" "palo_vpc_rt" {
vpc_id = aws_vpc.palo_vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.palo_vpc_igw.id
}
tags = {
Name = "base-palo-vpc-rt"
}
}
Provision my EC2 instance
With my VPC now provisioned, I provision my EC2 instance. Besides provisioning the EC2 instance, I also use the code to create my SSH key pair, and security group.
It is not best practice to provision your keypair in a way that stores it in your terraform state file. It has been used for demonstrative purposes in this case.
The code below is used to provision the EC2 instance, SSH key pair and security group
provider "aws" {
region = "eu-west-1"
}
locals {
public_key_filename = "${var.path}/${var.key_name}.pub"
private_key_filename = "${var.path}/${var.key_name}.pem"
}
resource "tls_private_key" "temp_key" {
algorithm = "RSA"
rsa_bits = 4096
}
resource "aws_key_pair" "palo_keypair" {
key_name = var.key_name
public_key = tls_private_key.temp_key.public_key_openssh
}
resource "local_file" "save_private_key" {
count = var.path != "" ? 1 : 0
filename = local.private_key_filename
content = tls_private_key.temp_key.private_key_pem
}
resource "local_file" "save_public_key" {
count = var.path != "" ? 1 : 0
filename = local.public_key_filename
content = tls_private_key.temp_key.public_key_openssh
}
resource "aws_instance" "palo_fw_instance" {
ami = "ami-057aac61cf7aab8c9" # Use the correct AMI for your requirement
instance_type = "m4.xlarge"
subnet_id = data.terraform_remote_state.vpc_info.outputs.subnet_id
associate_public_ip_address = true
tags = {
Name = "palo-alto-test-instance"
}
key_name = aws_key_pair.palo_keypair.key_name
vpc_security_group_ids = [aws_security_group.base_sg.id]
}
resource "aws_security_group" "base_sg" {
vpc_id = data.terraform_remote_state.vpc_info.outputs.vpc_id
description = "Allow required inbound and all outbound traffic"
}
resource "aws_vpc_security_group_egress_rule" "allow_outbound" {
security_group_id = aws_security_group.base_sg.id
ip_protocol = "-1"
description = "Allow all outbound"
cidr_ipv4 = "0.0.0.0/0"
}
resource "aws_vpc_security_group_ingress_rule" "inbound_ssh" {
for_each = (var.remote_access_ips)
security_group_id = aws_security_group.base_sg.id
cidr_ipv4 = each.key
ip_protocol = "tcp"
from_port = 22
to_port = 22
}
resource "aws_vpc_security_group_ingress_rule" "inbound_https" {
for_each = var.remote_access_ips
security_group_id = aws_security_group.base_sg.id
cidr_ipv4 = each.key
ip_protocol = "tcp"
from_port = 443
to_port = 443
}
########### Variables ###################
variable "key_name" {
default = "test-firewall-key"
}
variable "path" {
default = "./key"
}
# Replace with required IP addresses
variable "remote_access_ips" {
type = set(string)
default = ["1.1.1.1/32", "2.2.2.2/32"]
}
Now, with these blocks of code, I’m able to provision my VPC and test firewall within a few minutes when ever I need a test Palo Alto firewall instance. Once I’m done, I can also terminate the instance within a matter of seconds.