Skip to main content

Reference Architecture with Terraform: VM-Series in AWS, Centralized Design Model, Common NGFW Option

Palo Alto Networks produces several validated reference architecture design and deployment documentation guides, which describe well-architected and tested deployments. When deploying VM-Series in a public cloud, the reference architectures guide users toward the best security outcomes, whilst reducing rollout time and avoiding common integration efforts. The Terraform code presented here will deploy Palo Alto Networks VM-Series firewalls in AWS based on the centralized design; for a discussion of other options, please see the design guide from the reference architecture guides.

GitHub Logo Terraform Logo

Reference Architecture Design

Simplified High Level Topology Diagram

This code implements:

  • a centralized design, which secures outbound, inbound, and east-west traffic flows using an AWS transit gateway (dTGW). Application resources are segmented across multiple VPCs that connect in a hub-and-spoke topology, with a dedicated VPC for security services where the VM-Series are deployed

Detailed Architecture and Design

Centralized Design

This design supports interconnecting a large number of VPCs, with a scalable solution to secure outbound, inbound, and east-west traffic flows using a transit gateway to connect the VPCs. The centralized design model offers the benefits of a highly scalable design for multiple VPCs connecting to a central hub for inbound, outbound, and VPC-to-VPC traffic control and visibility. In the Centralized design model, you segment application resources across multiple VPCs that connect in a hub-and-spoke topology. The hub of the topology, or transit gateway, is the central point of connectivity between VPCs and Prisma Access or enterprise network resources attached through a VPN or AWS Direct Connect. This model has a dedicated VPC for security services where you deploy VM-Series firewalls for traffic inspection and control. The security VPC does not contain any application resources. The security VPC centralizes resources that multiple workloads can share. The TGW ensures that all spoke-to-spoke and spoke-to-enterprise traffic transits the VM-Series.

image

Prerequisites

The following steps should be followed before deploying the Terraform code presented here.

  1. Deploy Panorama e.g. by using Panorama example
  2. Prepare device group, template, template stack in Panorama
  3. Download and install plugin sw_fw_license for managing licenses
  4. Configure bootstrap definition and license manager
  5. Configure license API key
  6. Configure security rules and NAT rules for outbound traffic
  7. Configure interface management profile to enable health checks from GWLB
  8. Configure network interfaces and subinterfaces, zones and virtual router in template

In example VM-Series are licensed using Panorama-Based Software Firewall License Management sw_fw_license, from which after configuring license manager values of panorama-server, auth-key, dgname, tplname can be used in terraform.tfvars file. Another way to bootstrap and license VM-Series is using VM Auth Key. This approach requires preparing license (auth code) in file stored in S3 bucket or putting it in authcodes option. More information can be found in document describing how to choose a bootstrap method. Please note, that other bootstrapping methods may requires additional changes in example code (e.g. adding options vm-auth-key, authcodes) and/or creating additional resources (e.g. S3 buckets).

Usage

  1. Copy example.tfvars into terraform.tfvars
  2. Review terraform.tfvars file, especially with lines commented by # TODO: update here
  3. Initialize Terraform: terraform init
  4. Prepare plan: terraform plan
  5. Deploy infrastructure: terraform apply -auto-approve
  6. Destroy infrastructure if needed: terraform destroy -auto-approve

Reference

Requirements

NameVersion
terraform>= 1.0.0, < 2.0.0
aws~> 5.17
local~> 2.4.0

Providers

NameVersion
aws~> 5.17

Modules

NameSourceVersion
app_lb../../modules/nlbn/a
gwlb../../modules/gwlbn/a
gwlbe_endpoint../../modules/gwlb_endpoint_setn/a
natgw_set../../modules/nat_gateway_setn/a
public_alb../../modules/albn/a
public_nlb../../modules/nlbn/a
subnet_sets../../modules/subnet_setn/a
transit_gateway../../modules/transit_gatewayn/a
transit_gateway_attachment../../modules/transit_gateway_attachmentn/a
vmseries../../modules/vmseriesn/a
vpc../../modules/vpcn/a
vpc_routes../../modules/vpc_routen/a

Resources

NameType
aws_ec2_transit_gateway_route.from_security_to_panoramaresource
aws_ec2_transit_gateway_route.from_spokes_to_securityresource
aws_iam_instance_profile.spoke_vm_iam_instance_profileresource
aws_iam_instance_profile.vm_series_iam_instance_profileresource
aws_iam_role.spoke_vm_ec2_iam_roleresource
aws_iam_role.vm_series_ec2_iam_roleresource
aws_iam_role_policy.vm_series_ec2_iam_policyresource
aws_instance.spoke_vmsresource
aws_lb_target_group_attachment.thisresource
aws_ami.thisdata source
aws_caller_identity.thisdata source
aws_ebs_default_kms_key.currentdata source
aws_kms_alias.current_arndata source
aws_partition.thisdata source

Inputs

NameDescriptionTypeDefaultRequired
global_tagsGlobal tags configured for all provisioned resourcesanyn/ayes
gwlb_endpointsA map defining GWLB endpoints.

Following properties are available:
- name: name of the GWLB endpoint
- gwlb: key of GWLB
- vpc: key of VPC
- vpc_subnet: key of the VPC and subnet connected by '-' character
- act_as_next_hop: set to true if endpoint is part of an IGW route table e.g. for inbound traffic
- to_vpc_subnets: subnets to which traffic from IGW is routed to the GWLB endpoint

Example:
gwlb_endpoints = {
security_gwlb_eastwest = {
name = "eastwest-gwlb-endpoint"
gwlb = "security_gwlb"
vpc = "security_vpc"
vpc_subnet = "security_vpc-gwlbe_eastwest"
act_as_next_hop = false
to_vpc_subnets = null
}
}
map(object({
name = string
gwlb = string
vpc = string
vpc_subnet = string
act_as_next_hop = bool
to_vpc_subnets = string
}))
{}no
gwlbsA map defining Gateway Load Balancers.

Following properties are available:
- name: name of the GWLB
- vpc_subnet: key of the VPC and subnet connected by '-' character

Example:
gwlbs = {
security_gwlb = {
name = "security-gwlb"
vpc_subnet = "security_vpc-gwlb"
}
}
map(object({
name = string
vpc_subnet = string
}))
{}no
name_prefixPrefix used in names for the resources (VPCs, EC2 instances, autoscaling groups etc.)stringn/ayes
natgwsA map defining NAT Gateways.

Following properties are available:
- name: name of NAT Gateway
- vpc_subnet: key of the VPC and subnet connected by '-' character

Example:
natgws = {
security_nat_gw = {
name = "natgw"
vpc_subnet = "security_vpc-natgw"
}
}
map(object({
name = string
vpc_subnet = string
}))
{}no
panorama_attachmentA object defining TGW attachment and CIDR for Panorama.

Following properties are available:
- transit_gateway_attachment_id: ID of attachment for Panorama
- vpc_cidr: CIDR of the VPC, where Panorama is deployed

Example:
panorama = {
transit_gateway_attachment_id = "tgw-attach-123456789"
vpc_cidr = "10.255.0.0/24"
}
object({
transit_gateway_attachment_id = string
vpc_cidr = string
})
nullno
regionAWS region used to deploy whole infrastructurestringn/ayes
spoke_lbsA map defining Network Load Balancers deployed in spoke VPCs.

Following properties are available:
- vpc_subnet: key of the VPC and subnet connected by '-' character
- vms: keys of spoke VMs

Example:
spoke_lbs = {
"app1-nlb" = {
vpc_subnet = "app1_vpc-app1_lb"
vms = ["app1_vm01", "app1_vm02"]
}
}
map(object({
vpc_subnet = string
vms = list(string)
}))
{}no
spoke_vmsA map defining VMs in spoke VPCs.

Following properties are available:
- az: name of the Availability Zone
- vpc: name of the VPC (needs to be one of the keys in map vpcs)
- vpc_subnet: key of the VPC and subnet connected by '-' character
- security_group: security group assigned to ENI used by VM
- type: EC2 type VM

Example:
spoke_vms = {
"app1_vm01" = {
az = "eu-central-1a"
vpc = "app1_vpc"
vpc_subnet = "app1_vpc-app1_vm"
security_group = "app1_vm"
type = "t2.micro"
}
}
map(object({
az = string
vpc = string
vpc_subnet = string
security_group = string
type = string
}))
{}no
ssh_key_nameName of the SSH key pair existing in AWS key pairs and used to authenticate to VM-Series or test boxesstringn/ayes
tgwA object defining Transit Gateway.

Following properties are available:
- create: set to false, if existing TGW needs to be reused
- id: id of existing TGW or null
- name: name of TGW to create or use
- asn: ASN number
- route_tables: map of route tables
- attachments: map of TGW attachments

Example:
tgw = {
create = true
id = null
name = "tgw"
asn = "64512"
route_tables = {
"from_security_vpc" = {
create = true
name = "from_security"
}
}
attachments = {
security = {
name = "vmseries"
vpc_subnet = "security_vpc-tgw_attach"
route_table = "from_security_vpc"
propagate_routes_to = "from_spoke_vpc"
}
}
}
object({
create = bool
id = string
name = string
asn = string
route_tables = map(object({
create = bool
name = string
}))
attachments = map(object({
name = string
vpc_subnet = string
route_table = string
propagate_routes_to = string
}))
})
nullno
vmseriesA map defining VM-Series instances

Following properties are available:
- instances: map of VM-Series instances
- bootstrap_options: VM-Seriess bootstrap options used to connect to Panorama
- panos_version: PAN-OS version used for VM-Series
- ebs_kms_id: alias for AWS KMS used for EBS encryption in VM-Series
- vpc: key of VPC
- gwlb: key of GWLB
- subinterfaces: configuration of network subinterfaces used to map with GWLB endpoints
- system_services: map of system services
- application_lb: ALB placed in front of the Firewalls' public interfaces
- network_lb: NLB placed in front of the Firewalls' public interfaces

Example:
vmseries = {
vmseries = {
instances = {
"01" = { az = "eu-central-1a" }
"02" = { az = "eu-central-1b" }
}

# Value of panorama-server, auth-key, dgname, tplname can be taken from plugin sw\_fw\_license
bootstrap_options = {
mgmt-interface-swap = "enable"
plugin-op-commands = "panorama-licensing-mode-on,aws-gwlb-inspect:enable,aws-gwlb-overlay-routing:enable"
dhcp-send-hostname = "yes"
dhcp-send-client-id = "yes"
dhcp-accept-server-hostname = "yes"
dhcp-accept-server-domain = "yes"
}

panos_version = "10.2.3" # TODO: update here
ebs_kms_id = "alias/aws/ebs" # TODO: update here

# Value of vpc must match key of objects stored in vpcs
vpc = "security_vpc"

# Value of gwlb must match key of objects stored in gwlbs
gwlb = "security_gwlb"

interfaces = {
private = {
device_index = 0
security_group = "vmseries_private"
vpc_subnet = "security_vpc-private"
create_public_ip = false
source_dest_check = false
}
mgmt = {
device_index = 1
security_group = "vmseries_mgmt"
vpc_subnet = "security_vpc-mgmt"
create_public_ip = true
source_dest_check = true
}
public = {
device_index = 2
security_group = "vmseries_public"
vpc_subnet = "security_vpc-public"
create_public_ip = true
source_dest_check = false
}
}

# Value of gwlb\_endpoint must match key of objects stored in gwlb\_endpoints
subinterfaces = {
inbound = {
app1 = {
gwlb_endpoint = "app1_inbound"
subinterface = "ethernet1/1.11"
}
app2 = {
gwlb_endpoint = "app2_inbound"
subinterface = "ethernet1/1.12"
}
}
outbound = {
only_1_outbound = {
gwlb_endpoint = "security_gwlb_outbound"
subinterface = "ethernet1/1.20"
}
}
eastwest = {
only_1_eastwest = {
gwlb_endpoint = "security_gwlb_eastwest"
subinterface = "ethernet1/1.30"
}
}
}

system_services = {
dns_primary = "4.2.2.2" # TODO: update here
dns_secondy = null # TODO: update here
ntp_primary = "pool.ntp.org" # TODO: update here
ntp_secondy = null # TODO: update here
}

application_lb = null
network_lb = null
}
}
map(object({
instances = map(object({
az = string
}))

bootstrap_options = object({
mgmt-interface-swap = string
plugin-op-commands = string
panorama-server = string
auth-key = string
dgname = string
tplname = string
dhcp-send-hostname = string
dhcp-send-client-id = string
dhcp-accept-server-hostname = string
dhcp-accept-server-domain = string
})

panos_version = string
ebs_kms_id = string

vpc = string
gwlb = string

interfaces = map(object({
device_index = number
security_group = string
vpc_subnet = string
create_public_ip = bool
source_dest_check = bool
}))

subinterfaces = map(map(object({
gwlb_endpoint = string
subinterface = string
})))

system_services = object({
dns_primary = string
dns_secondy = string
ntp_primary = string
ntp_secondy = string
})

application_lb = object({
name = string
rules = any
})

network_lb = object({
name = string
rules = any
})
}))
{}no
vpcsA map defining VPCs with security groups and subnets.

Following properties are available:
- name: VPC name
- cidr: CIDR for VPC
- security_groups: map of security groups
- subnets: map of subnets with properties:
- az: availability zone
- set: internal identifier referenced by main.tf
- routes: map of routes with properties:
- vpc_subnet - built from key of VPCs concatenate with - and key of subnet in format: VPCKEY-SUBNETKEY
- next_hop_key - must match keys use to create TGW attachment, IGW, GWLB endpoint or other resources
- next_hop_type - internet_gateway, nat_gateway, transit_gateway_attachment or gwlbe_endpoint

Example:
vpcs = {
example_vpc = {
name = "example-spoke-vpc"
cidr = "10.104.0.0/16"
nacls = {
trusted_path_monitoring = {
name = "trusted-path-monitoring"
rules = {
allow_inbound = {
rule_number = 300
egress = false
protocol = "-1"
rule_action = "allow"
cidr_block = "0.0.0.0/0"
from_port = null
to_port = null
}
}
}
}
security_groups = {
example_vm = {
name = "example_vm"
rules = {
all_outbound = {
description = "Permit All traffic outbound"
type = "egress", from_port = "0", to_port = "0", protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
}
}
subnets = {
"10.104.0.0/24" = { az = "eu-central-1a", set = "vm", nacl = null }
"10.104.128.0/24" = { az = "eu-central-1b", set = "vm", nacl = null }
}
routes = {
vm_default = {
vpc_subnet = "app1_vpc-app1_vm"
to_cidr = "0.0.0.0/0"
next_hop_key = "app1"
next_hop_type = "transit_gateway_attachment"
}
}
}
}
map(object({
name = string
cidr = string
nacls = map(object({
name = string
rules = map(object({
rule_number = number
egress = bool
protocol = string
rule_action = string
cidr_block = string
from_port = string
to_port = string
}))
}))
security_groups = any
subnets = map(object({
az = string
set = string
nacl = string
}))
routes = map(object({
vpc_subnet = string
to_cidr = string
next_hop_key = string
next_hop_type = string
}))
}))
{}no

Outputs

NameDescription
app_inspected_dns_nameFQDN of App Internal Load Balancer.
Can be used in VM-Series configuration to balance traffic between the application instances.
public_alb_dns_nameFQDN of VM-Series External Application Load Balancer used in centralized design.
public_nlb_dns_nameFQDN of VM-Series External Network Load Balancer used in centralized design.
vmseries_public_ipsMap of public IPs created within vmseries module instances.