From 5ef07fc9215518444e59026e081fb375485cbfbb Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Tue, 30 May 2023 19:18:43 +0200 Subject: [PATCH] feat(example/centralized_design): New example for centralized design model (#307) --- examples/centralized_design/README.md | 97 +++ examples/centralized_design/example.tfvars | 667 +++++++++++++++++++++ examples/centralized_design/main.tf | 377 ++++++++++++ examples/centralized_design/outputs.tf | 28 + examples/centralized_design/variables.tf | 518 ++++++++++++++++ examples/centralized_design/versions.tf | 16 + scripts/install.sh | 2 +- 7 files changed, 1704 insertions(+), 1 deletion(-) create mode 100644 examples/centralized_design/README.md create mode 100644 examples/centralized_design/example.tfvars create mode 100644 examples/centralized_design/main.tf create mode 100644 examples/centralized_design/outputs.tf create mode 100644 examples/centralized_design/variables.tf create mode 100644 examples/centralized_design/versions.tf diff --git a/examples/centralized_design/README.md b/examples/centralized_design/README.md new file mode 100644 index 00000000..732094d0 --- /dev/null +++ b/examples/centralized_design/README.md @@ -0,0 +1,97 @@ +# VM-Series Reference Architecture - Centralized Model + +A Terraform example for deploying VM-Series firewalls in centralized model for inbound, outbound and east-west traffic inspection. + +## Topology + +Code was prepared according to presented below diagram for *centralized model*. + +![](https://github.com/PaloAltoNetworks/terraform-aws-vmseries-modules/assets/9674179/21d0f29e-d0da-4b50-a33b-e37f260e9c13) + +## Prerequisites + +Prepare Panorama in similar way as described for [Combined model example - VM-Series Auto Scaling](https://github.com/PaloAltoNetworks/terraform-aws-vmseries-modules/tree/main/examples/combined_vmseries_and_autoscale). + +In example VM-Series are licensed using [Panorama-Based Software Firewall License Management `sw_fw_license`](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/license-the-vm-series-firewall/use-panorama-based-software-firewall-license-management), 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](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/bootstrap-the-vm-series-firewall/generate-the-vm-auth-key-on-panorama). 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](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/bootstrap-the-vm-series-firewall/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` +5. Prepare plan: `terraform plan` +6. Deploy infrastructure: `terraform apply -auto-approve` +7. Destroy infrastructure if needed: `terraform destroy -auto-approve` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0.0, < 2.0.0 | +| [aws](#requirement\_aws) | ~> 4.25 | +| [local](#requirement\_local) | ~> 2.4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | ~> 4.25 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [app\_lb](#module\_app\_lb) | ../../modules/nlb | n/a | +| [gwlb](#module\_gwlb) | ../../modules/gwlb | n/a | +| [gwlbe\_endpoint](#module\_gwlbe\_endpoint) | ../../modules/gwlb_endpoint_set | n/a | +| [natgw\_set](#module\_natgw\_set) | ../../modules/nat_gateway_set | n/a | +| [public\_alb](#module\_public\_alb) | ../../modules/alb | n/a | +| [public\_nlb](#module\_public\_nlb) | ../../modules/nlb | n/a | +| [subnet\_sets](#module\_subnet\_sets) | ../../modules/subnet_set | n/a | +| [transit\_gateway](#module\_transit\_gateway) | ../../modules/transit_gateway | n/a | +| [transit\_gateway\_attachment](#module\_transit\_gateway\_attachment) | ../../modules/transit_gateway_attachment | n/a | +| [vmseries](#module\_vmseries) | ../../modules/vmseries | n/a | +| [vpc](#module\_vpc) | ../../modules/vpc | n/a | +| [vpc\_routes](#module\_vpc\_routes) | ../../modules/vpc_route | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_ec2_transit_gateway_route.from_security_to_panorama](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route) | resource | +| [aws_ec2_transit_gateway_route.from_spokes_to_security](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route) | resource | +| [aws_iam_instance_profile.vm_series_iam_instance_profile](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource | +| [aws_iam_role.vm_series_ec2_iam_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy.vm_series_ec2_iam_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_instance.spoke_vms](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) | resource | +| [aws_lb_target_group_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group_attachment) | resource | +| [aws_ami.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [global\_tags](#input\_global\_tags) | Global tags configured for all provisioned resources | `any` | n/a | yes | +| [gwlb\_endpoints](#input\_gwlb\_endpoints) | A 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 | +| [gwlbs](#input\_gwlbs) | A 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\_prefix](#input\_name\_prefix) | Prefix used in names for the resources (VPCs, EC2 instances, autoscaling groups etc.) | `string` | n/a | yes | +| [natgws](#input\_natgws) | A 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\_attachment](#input\_panorama\_attachment) | A 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
})
| `null` | no | +| [region](#input\_region) | AWS region used to deploy whole infrastructure | `string` | n/a | yes | +| [spoke\_lbs](#input\_spoke\_lbs) | A 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\_vms](#input\_spoke\_vms) | A 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\_name](#input\_ssh\_key\_name) | Name of the SSH key pair existing in AWS key pairs and used to authenticate to VM-Series or test boxes | `string` | n/a | yes | +| [tgw](#input\_tgw) | A 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
}))
})
| `null` | no | +| [vmseries](#input\_vmseries) | A 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 | +| [vpcs](#input\_vpcs) | A 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 = map(object({
name = string
rules = map(object({
description = string
type = string,
from_port = string
to_port = string,
protocol = string
cidr_blocks = list(string)
}))
}))
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 + +| Name | Description | +|------|-------------| +| [app\_inspected\_dns\_name](#output\_app\_inspected\_dns\_name) | FQDN of App Internal Load Balancer.
Can be used in VM-Series configuration to balance traffic between the application instances. | +| [public\_alb\_dns\_name](#output\_public\_alb\_dns\_name) | FQDN of VM-Series External Application Load Balancer used in centralized design. | +| [public\_nlb\_dns\_name](#output\_public\_nlb\_dns\_name) | FQDN of VM-Series External Network Load Balancer used in centralized design. | +| [vmseries\_public\_ips](#output\_vmseries\_public\_ips) | Map of public IPs created within `vmseries` module instances. | + diff --git a/examples/centralized_design/example.tfvars b/examples/centralized_design/example.tfvars new file mode 100644 index 00000000..04f0a288 --- /dev/null +++ b/examples/centralized_design/example.tfvars @@ -0,0 +1,667 @@ +### General +region = "eu-central-1" # TODO: update here +name_prefix = "example-" # TODO: update here + +global_tags = { + ManagedBy = "terraform" + Application = "Palo Alto Networks VM-Series NGFW" + Owner = "PS Team" +} + +ssh_key_name = "example-ssh-key" # TODO: update here + +### VPC +vpcs = { + # Do not use `-` in key for VPC as this character is used in concatation of VPC and subnet for module `subnet_set` in `main.tf` + security_vpc = { + name = "security-vpc" + cidr = "10.100.0.0/16" + nacls = { + trusted_path_monitoring = { + name = "trusted-path-monitoring" + rules = { + block_outbound_icmp_1 = { + rule_number = 110 + egress = true + protocol = "icmp" + rule_action = "deny" + cidr_block = "10.100.1.0/24" + from_port = null + to_port = null + } + block_outbound_icmp_2 = { + rule_number = 120 + egress = true + protocol = "icmp" + rule_action = "deny" + cidr_block = "10.100.65.0/24" + from_port = null + to_port = null + } + allow_other_outbound = { + rule_number = 200 + egress = true + protocol = "-1" + rule_action = "allow" + cidr_block = "0.0.0.0/0" + from_port = null + to_port = null + } + 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 = { + vmseries_private = { + name = "vmseries_private" + 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"] + } + geneve = { + description = "Permit GENEVE to GWLB subnets" + type = "ingress", from_port = "6081", to_port = "6081", protocol = "udp" + cidr_blocks = [ + "10.100.5.0/24", "10.100.69.0/24" + ] + } + health_probe = { + description = "Permit Port 80 Health Probe to GWLB subnets" + type = "ingress", from_port = "80", to_port = "80", protocol = "tcp" + cidr_blocks = [ + "10.100.5.0/24", "10.100.69.0/24" + ] + } + } + } + vmseries_mgmt = { + name = "vmseries_mgmt" + 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"] + } + https = { + description = "Permit HTTPS" + type = "ingress", from_port = "443", to_port = "443", protocol = "tcp" + cidr_blocks = ["130.41.247.0/24"] # TODO: update here + } + ssh = { + description = "Permit SSH" + type = "ingress", from_port = "22", to_port = "22", protocol = "tcp" + cidr_blocks = ["130.41.247.0/24"] # TODO: update here + } + panorama_ssh = { + description = "Permit Panorama SSH (Optional)" + type = "ingress", from_port = "22", to_port = "22", protocol = "tcp" + cidr_blocks = ["10.0.0.0/8"] + } + panorama_mgmt = { + description = "Permit Panorama Management" + type = "ingress", from_port = "3978", to_port = "3978", protocol = "tcp" + cidr_blocks = ["10.0.0.0/8"] + } + panorama_log = { + description = "Permit Panorama Logging" + type = "ingress", from_port = "28443", to_port = "28443", protocol = "tcp" + cidr_blocks = ["10.0.0.0/8"] + } + } + } + vmseries_public = { + name = "vmseries_public" + 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"] + } + ssh = { + description = "Permit SSH" + type = "ingress", from_port = "22", to_port = "22", protocol = "tcp" + cidr_blocks = ["130.41.247.0/24", "10.104.0.0/16", "10.105.0.0/16"] # TODO: update here + } + https = { + description = "Permit HTTPS" + type = "ingress", from_port = "443", to_port = "443", protocol = "tcp" + cidr_blocks = ["130.41.247.0/24", "10.104.0.0/16", "10.105.0.0/16"] # TODO: update here + } + http = { + description = "Permit HTTP" + type = "ingress", from_port = "80", to_port = "80", protocol = "tcp" + cidr_blocks = ["130.41.247.0/24", "10.104.0.0/16", "10.105.0.0/16"] # TODO: update here + } + health_probe_8081 = { + description = "Permit Port 8081 Health Probe to ALB" + type = "ingress", from_port = "8081", to_port = "8081", protocol = "tcp" + cidr_blocks = ["10.100.6.0/24", "10.100.70.0/24"] + } + health_probe_8082 = { + description = "Permit Port 8082 Health Probe to ALB" + type = "ingress", from_port = "8082", to_port = "8082", protocol = "tcp" + cidr_blocks = ["10.100.6.0/24", "10.100.70.0/24"] + } + health_probe_2021 = { + description = "Permit Port 2021 Health Probe to NLB" + type = "ingress", from_port = "2021", to_port = "2021", protocol = "tcp" + cidr_blocks = ["10.100.7.0/24", "10.100.71.0/24"] + } + health_probe_2022 = { + description = "Permit Port 2022 Health Probe to NLB" + type = "ingress", from_port = "2022", to_port = "2022", protocol = "tcp" + cidr_blocks = ["10.100.7.0/24", "10.100.71.0/24"] + } + } + } + application_load_balancer = { + name = "alb" + rules = { + http_inbound_8081 = { + description = "Permit incoming APP1 traffic" + type = "ingress", from_port = "8081", to_port = "8081", protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + http_inbound_8082 = { + description = "Permit incoming APP2 traffic" + type = "ingress", from_port = "8082", to_port = "8082", protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + 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 = { + # Do not modify value of `set=`, it is an internal identifier referenced by main.tf + "10.100.0.0/24" = { az = "eu-central-1a", set = "mgmt", nacl = null } + "10.100.64.0/24" = { az = "eu-central-1b", set = "mgmt", nacl = null } + "10.100.1.0/24" = { az = "eu-central-1a", set = "private", nacl = "trusted_path_monitoring" } + "10.100.65.0/24" = { az = "eu-central-1b", set = "private", nacl = "trusted_path_monitoring" } + "10.100.2.0/24" = { az = "eu-central-1a", set = "public", nacl = null } + "10.100.66.0/24" = { az = "eu-central-1b", set = "public", nacl = null } + "10.100.3.0/24" = { az = "eu-central-1a", set = "tgw_attach", nacl = null } + "10.100.67.0/24" = { az = "eu-central-1b", set = "tgw_attach", nacl = null } + "10.100.4.0/24" = { az = "eu-central-1a", set = "gwlbe_outbound", nacl = null } + "10.100.68.0/24" = { az = "eu-central-1b", set = "gwlbe_outbound", nacl = null } + "10.100.5.0/24" = { az = "eu-central-1a", set = "gwlb", nacl = null } + "10.100.69.0/24" = { az = "eu-central-1b", set = "gwlb", nacl = null } # AWS reccomends to always go up to the last possible AZ for GWLB service + "10.100.10.0/24" = { az = "eu-central-1a", set = "gwlbe_eastwest", nacl = null } + "10.100.74.0/24" = { az = "eu-central-1b", set = "gwlbe_eastwest", nacl = null } + "10.100.6.0/24" = { az = "eu-central-1a", set = "alb", nacl = null } + "10.100.70.0/24" = { az = "eu-central-1b", set = "alb", nacl = null } + "10.100.7.0/24" = { az = "eu-central-1a", set = "nlb", nacl = null } + "10.100.71.0/24" = { az = "eu-central-1b", set = "nlb", nacl = null } + } + routes = { + # Value of `vpc_subnet` is built from key of VPCs concatenate with `-` and key of subnet in format: `VPCKEY-SUBNETKEY` + # Value of `next_hop_key` must match keys use to create TGW attachment, IGW, GWLB endpoint or other resources + # Value of `next_hop_type` is internet_gateway, nat_gateway, transit_gateway_attachment or gwlbe_endpoint + mgmt_default = { + vpc_subnet = "security_vpc-mgmt" + to_cidr = "0.0.0.0/0" + next_hop_key = "security_vpc" + next_hop_type = "internet_gateway" + } + mgmt_panorama = { + vpc_subnet = "security_vpc-mgmt" + to_cidr = "10.255.0.0/16" + next_hop_key = "security" + next_hop_type = "transit_gateway_attachment" + } + mgmt_rfc1918 = { + vpc_subnet = "security_vpc-mgmt" + to_cidr = "10.0.0.0/8" + next_hop_key = "security" + next_hop_type = "transit_gateway_attachment" + } + tgw_default = { + vpc_subnet = "security_vpc-tgw_attach" + to_cidr = "0.0.0.0/0" + next_hop_key = "security_gwlb_outbound" + next_hop_type = "gwlbe_endpoint" + } + tgw_rfc1918 = { + vpc_subnet = "security_vpc-tgw_attach" + to_cidr = "10.0.0.0/8" + next_hop_key = "security_gwlb_eastwest" + next_hop_type = "gwlbe_endpoint" + } + public_default = { + vpc_subnet = "security_vpc-public" + to_cidr = "0.0.0.0/0" + next_hop_key = "security_vpc" + next_hop_type = "internet_gateway" + } + gwlbe_outbound_rfc1918 = { + vpc_subnet = "security_vpc-gwlbe_outbound" + to_cidr = "10.0.0.0/8" + next_hop_key = "security" + next_hop_type = "transit_gateway_attachment" + } + gwlbe_eastwest_rfc1918 = { + vpc_subnet = "security_vpc-gwlbe_eastwest" + to_cidr = "10.0.0.0/8" + next_hop_key = "security" + next_hop_type = "transit_gateway_attachment" + } + private_app1 = { + vpc_subnet = "security_vpc-private" + to_cidr = "10.104.0.0/16" + next_hop_key = "security" + next_hop_type = "transit_gateway_attachment" + } + private_app2 = { + vpc_subnet = "security_vpc-private" + to_cidr = "10.105.0.0/16" + next_hop_key = "security" + next_hop_type = "transit_gateway_attachment" + } + alb_default = { + vpc_subnet = "security_vpc-alb" + to_cidr = "0.0.0.0/0" + next_hop_key = "security_vpc" + next_hop_type = "internet_gateway" + } + nlb_default = { + vpc_subnet = "security_vpc-nlb" + to_cidr = "0.0.0.0/0" + next_hop_key = "security_vpc" + next_hop_type = "internet_gateway" + } + } + } + app1_vpc = { + name = "app1-spoke-vpc" + cidr = "10.104.0.0/16" + nacls = {} + security_groups = { + app1_vm = { + name = "app1_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"] + } + private_inbound = { + description = "Permit All traffic inbound from 10.0.0.0/8" + type = "ingress", from_port = "0", to_port = "0", protocol = "-1" + cidr_blocks = ["10.0.0.0/8"] # TODO: update here + } + ssh = { + description = "Permit SSH" + type = "ingress", from_port = "22", to_port = "22", protocol = "tcp" + cidr_blocks = ["130.41.247.0/24", "10.104.0.0/16", "10.105.0.0/16"] # TODO: update here + } + https = { + description = "Permit HTTPS" + type = "ingress", from_port = "443", to_port = "443", protocol = "tcp" + cidr_blocks = ["130.41.247.0/24", "10.104.0.0/16", "10.105.0.0/16"] # TODO: update here + } + http = { + description = "Permit HTTP" + type = "ingress", from_port = "80", to_port = "80", protocol = "tcp" + cidr_blocks = ["130.41.247.0/24", "10.104.0.0/16", "10.105.0.0/16"] # TODO: update here + } + } + } + } + subnets = { + # Do not modify value of `set=`, it is an internal identifier referenced by main.tf. + "10.104.0.0/24" = { az = "eu-central-1a", set = "app1_vm", nacl = null } + "10.104.128.0/24" = { az = "eu-central-1b", set = "app1_vm", nacl = null } + "10.104.2.0/24" = { az = "eu-central-1a", set = "app1_lb", nacl = null } + "10.104.130.0/24" = { az = "eu-central-1b", set = "app1_lb", nacl = null } + } + routes = { + # Value of `vpc_subnet` is built from key of VPCs concatenate with `-` and key of subnet in format: `VPCKEY-SUBNETKEY` + # Value of `next_hop_key` must match keys use to create TGW attachment, IGW, GWLB endpoint or other resources + # Value of `next_hop_type` is internet_gateway, nat_gateway, transit_gateway_attachment or gwlbe_endpoint + 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" + } + lb_default = { + vpc_subnet = "app1_vpc-app1_lb" + to_cidr = "0.0.0.0/0" + next_hop_key = "app1" + next_hop_type = "transit_gateway_attachment" + } + } + } + app2_vpc = { + name = "app2-spoke-vpc" + cidr = "10.105.0.0/16" + nacls = {} + security_groups = { + app2_vm = { + name = "app2_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"] + } + private_inbound = { + description = "Permit All traffic inbound from 10.0.0.0/8" + type = "ingress", from_port = "0", to_port = "0", protocol = "-1" + cidr_blocks = ["10.0.0.0/8"] # TODO: update here + } + ssh = { + description = "Permit SSH" + type = "ingress", from_port = "22", to_port = "22", protocol = "tcp" + cidr_blocks = ["130.41.247.0/24", "10.104.0.0/16", "10.105.0.0/16"] # TODO: update here + } + https = { + description = "Permit HTTPS" + type = "ingress", from_port = "443", to_port = "443", protocol = "tcp" + cidr_blocks = ["130.41.247.0/24", "10.104.0.0/16", "10.105.0.0/16"] # TODO: update here + } + http = { + description = "Permit HTTP" + type = "ingress", from_port = "80", to_port = "80", protocol = "tcp" + cidr_blocks = ["130.41.247.0/24", "10.104.0.0/16", "10.105.0.0/16"] # TODO: update here + } + } + } + } + subnets = { + # Do not modify value of `set=`, it is an internal identifier referenced by main.tf. + "10.105.0.0/24" = { az = "eu-central-1a", set = "app2_vm", nacl = null } + "10.105.128.0/24" = { az = "eu-central-1b", set = "app2_vm", nacl = null } + "10.105.2.0/24" = { az = "eu-central-1a", set = "app2_lb", nacl = null } + "10.105.130.0/24" = { az = "eu-central-1b", set = "app2_lb", nacl = null } + } + routes = { + # Value of `vpc_subnet` is built from key of VPCs concatenate with `-` and key of subnet in format: `VPCKEY-SUBNETKEY` + # Value of `next_hop_key` must match keys use to create TGW attachment, IGW, GWLB endpoint or other resources + # Value of `next_hop_type` is internet_gateway, nat_gateway, transit_gateway_attachment or gwlbe_endpoint + vm_default = { + vpc_subnet = "app2_vpc-app2_vm" + to_cidr = "0.0.0.0/0" + next_hop_key = "app2" + next_hop_type = "transit_gateway_attachment" + } + lb_default = { + vpc_subnet = "app2_vpc-app2_lb" + to_cidr = "0.0.0.0/0" + next_hop_key = "app2" + next_hop_type = "transit_gateway_attachment" + } + } + } +} + +### TRANSIT GATEWAY +tgw = { + create = true + id = null + name = "tgw" + asn = "64512" + route_tables = { + # Do not change keys `from_security_vpc` and `from_spoke_vpc` as they are used in `main.tf` and attachments + "from_security_vpc" = { + create = true + name = "from_security" + } + "from_spoke_vpc" = { + create = true + name = "from_spokes" + } + } + attachments = { + # Value of `vpc_subnet` is built from key of VPCs concatenate with `-` and key of subnet in format: `VPCKEY-SUBNETKEY` + # Value of `route_table` and `propagate_routes_to` must match `route_tables` stores under `tgw` + security = { + name = "vmseries" + vpc_subnet = "security_vpc-tgw_attach" + route_table = "from_security_vpc" + propagate_routes_to = "from_spoke_vpc" + } + app1 = { + name = "app1-spoke-vpc" + vpc_subnet = "app1_vpc-app1_vm" + route_table = "from_spoke_vpc" + propagate_routes_to = "from_security_vpc" + } + app2 = { + name = "app2-spoke-vpc" + vpc_subnet = "app2_vpc-app2_vm" + route_table = "from_spoke_vpc" + propagate_routes_to = "from_security_vpc" + } + } +} + +### NAT GATEWAY +natgws = {} + +### GATEWAY LOADBALANCER +gwlbs = { + # Value of `vpc_subnet` is built from key of VPCs concatenate with `-` and key of subnet in format: `VPCKEY-SUBNETKEY` + security_gwlb = { + name = "security-gwlb" + vpc_subnet = "security_vpc-gwlb" + } +} +gwlb_endpoints = { + # Value of `gwlb` must match key of objects stored in `gwlbs` + # Value of `vpc` must match key of objects stored in `vpcs` + # Value of `vpc_subnet` is built from key of VPCs concatenate with `-` and key of subnet in format: `VPCKEY-SUBNETKEY` + security_gwlb_outbound = { + name = "outbound-gwlb-endpoint" + gwlb = "security_gwlb" + vpc = "security_vpc" + vpc_subnet = "security_vpc-gwlbe_outbound" + act_as_next_hop = false + to_vpc_subnets = null + } + 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 + } +} + +### VM-SERIES +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" # TODO: update here + panorama-server = "10.255.0.10" # TODO: update here + auth-key = "" # TODO: update here + dgname = "centralized" # TODO: update here + tplname = "centralized-stack" # TODO: update here + dhcp-send-hostname = "yes" # TODO: update here + dhcp-send-client-id = "yes" # TODO: update here + dhcp-accept-server-hostname = "yes" # TODO: update here + dhcp-accept-server-domain = "yes" # TODO: update here + } + + 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 = {} + 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 = { + name = "public-alb" + rules = { + "app1" = { + protocol = "HTTP" + port = 8081 + health_check_port = "8081" + health_check_matcher = "200" + health_check_path = "/" + health_check_interval = 10 + listener_rules = { + "1" = { + target_protocol = "HTTP" + target_port = 8081 + path_pattern = ["/"] + } + } + } + "app2" = { + protocol = "HTTP" + port = 8082 + health_check_port = "8082" + health_check_matcher = "200" + health_check_path = "/" + health_check_interval = 10 + listener_rules = { + "1" = { + target_protocol = "HTTP" + target_port = 8082 + path_pattern = ["/"] + } + } + } + } + } + network_lb = { + name = "public-nlb" + rules = { + "ssh1" = { + protocol = "TCP" + port = "2021" + target_type = "ip" + stickiness = true + } + "ssh2" = { + protocol = "TCP" + port = "2022" + target_type = "ip" + stickiness = true + } + } + } + } +} + +### PANORAMA +panorama_attachment = { + transit_gateway_attachment_id = null # TODO: update here + vpc_cidr = "10.255.0.0/24" # TODO: update here +} + +### SPOKE VMS +spoke_vms = { + "app1_vm01" = { + az = "eu-central-1a" + vpc = "app1_vpc" + vpc_subnet = "app1_vpc-app1_vm" + security_group = "app1_vm" + type = "t2.micro" + } + "app1_vm02" = { + az = "eu-central-1b" + vpc = "app1_vpc" + vpc_subnet = "app1_vpc-app1_vm" + security_group = "app1_vm" + type = "t2.micro" + } + "app2_vm01" = { + az = "eu-central-1a" + vpc = "app2_vpc" + vpc_subnet = "app2_vpc-app2_vm" + security_group = "app2_vm" + type = "t2.micro" + } + "app2_vm02" = { + az = "eu-central-1b" + vpc = "app2_vpc" + vpc_subnet = "app2_vpc-app2_vm" + security_group = "app2_vm" + type = "t2.micro" + } +} + +### SPOKE LOADBALANCERS +spoke_lbs = { + "app1-nlb" = { + vpc_subnet = "app1_vpc-app1_lb" + vms = ["app1_vm01", "app1_vm02"] + } + "app2-nlb" = { + vpc_subnet = "app2_vpc-app2_lb" + vms = ["app2_vm01", "app2_vm02"] + } +} diff --git a/examples/centralized_design/main.tf b/examples/centralized_design/main.tf new file mode 100644 index 00000000..bd6aa273 --- /dev/null +++ b/examples/centralized_design/main.tf @@ -0,0 +1,377 @@ +### VPCS ### + +module "vpc" { + source = "../../modules/vpc" + + for_each = var.vpcs + + name = "${var.name_prefix}${each.value.name}" + cidr_block = each.value.cidr + nacls = each.value.nacls + security_groups = each.value.security_groups + create_internet_gateway = true + enable_dns_hostnames = true + enable_dns_support = true + instance_tenancy = "default" +} + +### SUBNETS ### + +module "subnet_sets" { + for_each = toset(flatten([for _, v in { for vk, vv in var.vpcs : vk => distinct([for sk, sv in vv.subnets : "${vk}-${sv.set}"]) } : v])) + source = "../../modules/subnet_set" + + name = split("-", each.key)[1] + vpc_id = module.vpc[split("-", each.key)[0]].id + has_secondary_cidrs = module.vpc[split("-", each.key)[0]].has_secondary_cidrs + nacl_associations = { + for i in flatten([ + for vk, vv in var.vpcs : [ + for sk, sv in vv.subnets : + { + az : sv.az, + nacl_id : lookup(module.vpc[split("-", each.key)[0]].nacl_ids, sv.nacl, null) + } if sv.nacl != null && each.key == "${vk}-${sv.set}" + ]]) : i.az => i.nacl_id + } + cidrs = { + for i in flatten([ + for vk, vv in var.vpcs : [ + for sk, sv in vv.subnets : + { + cidr : sk, + subnet : sv + } if each.key == "${vk}-${sv.set}" + ]]) : i.cidr => i.subnet + } +} + +### ROUTES ### + +locals { + vpc_routes = flatten(concat([ + for vk, vv in var.vpcs : [ + for rk, rv in vv.routes : { + subnet_key = rv.vpc_subnet + to_cidr = rv.to_cidr + next_hop_set = ( + rv.next_hop_type == "internet_gateway" ? module.vpc[rv.next_hop_key].igw_as_next_hop_set : ( + rv.next_hop_type == "nat_gateway" ? module.natgw_set[rv.next_hop_key].next_hop_set : ( + rv.next_hop_type == "transit_gateway_attachment" ? module.transit_gateway_attachment[rv.next_hop_key].next_hop_set : ( + rv.next_hop_type == "gwlbe_endpoint" ? module.gwlbe_endpoint[rv.next_hop_key].next_hop_set : null + ) + ) + ) + ) + } + ] + ])) +} + +module "vpc_routes" { + for_each = { for route in local.vpc_routes : "${route.subnet_key}_${route.to_cidr}" => route } + source = "../../modules/vpc_route" + + route_table_ids = module.subnet_sets[each.value.subnet_key].unique_route_table_ids + to_cidr = each.value.to_cidr + next_hop_set = each.value.next_hop_set +} + +### NATGW ### + +module "natgw_set" { + source = "../../modules/nat_gateway_set" + + for_each = var.natgws + + subnets = module.subnet_sets[each.value.vpc_subnet].subnets +} + +### TGW ### + +module "transit_gateway" { + source = "../../modules/transit_gateway" + + create = var.tgw.create + id = var.tgw.id + name = "${var.name_prefix}${var.tgw.name}" + asn = var.tgw.asn + route_tables = var.tgw.route_tables +} + +### TGW ATTACHMENTS ### + +module "transit_gateway_attachment" { + source = "../../modules/transit_gateway_attachment" + + for_each = var.tgw.attachments + + name = "${var.name_prefix}${each.value.name}" + vpc_id = module.subnet_sets[each.value.vpc_subnet].vpc_id + subnets = module.subnet_sets[each.value.vpc_subnet].subnets + transit_gateway_route_table = module.transit_gateway.route_tables[each.value.route_table] + propagate_routes_to = { + to1 = module.transit_gateway.route_tables[each.value.propagate_routes_to].id + } +} + +resource "aws_ec2_transit_gateway_route" "from_spokes_to_security" { + transit_gateway_route_table_id = module.transit_gateway.route_tables["from_spoke_vpc"].id + transit_gateway_attachment_id = module.transit_gateway_attachment["security"].attachment.id + destination_cidr_block = "0.0.0.0/0" + blackhole = false +} + +resource "aws_ec2_transit_gateway_route" "from_security_to_panorama" { + count = var.panorama_attachment.transit_gateway_attachment_id != null ? 1 : 0 + transit_gateway_route_table_id = module.transit_gateway.route_tables["from_security_vpc"].id + transit_gateway_attachment_id = var.panorama_attachment.transit_gateway_attachment_id + destination_cidr_block = var.panorama_attachment.vpc_cidr + blackhole = false +} + +### GWLB ### + +module "gwlb" { + source = "../../modules/gwlb" + + for_each = var.gwlbs + + name = "${var.name_prefix}${each.value.name}" + vpc_id = module.subnet_sets[each.value.vpc_subnet].vpc_id + subnets = module.subnet_sets[each.value.vpc_subnet].subnets +} + +resource "aws_lb_target_group_attachment" "this" { + for_each = { for vmseries in local.vmseries_instances : "${vmseries.group}-${vmseries.instance}" => { + gwlb = vmseries.common.gwlb + id = module.vmseries["${vmseries.group}-${vmseries.instance}"].instance.id + } } + + target_group_arn = module.gwlb[each.value.gwlb].target_group.arn + target_id = each.value.id +} + +### GWLB ENDPOINTS ### + +module "gwlbe_endpoint" { + source = "../../modules/gwlb_endpoint_set" + + for_each = var.gwlb_endpoints + + name = "${var.name_prefix}${each.value.name}" + gwlb_service_name = module.gwlb[each.value.gwlb].endpoint_service.service_name + vpc_id = module.subnet_sets[each.value.vpc_subnet].vpc_id + subnets = module.subnet_sets[each.value.vpc_subnet].subnets + + act_as_next_hop_for = each.value.act_as_next_hop ? { + "from-igw-to-lb" = { + route_table_id = module.vpc[each.value.vpc].internet_gateway_route_table.id + to_subnets = module.subnet_sets[each.value.to_vpc_subnets].subnets + } + # The routes in this section are special in that they are on the "edge", that is they are part of an IGW route table, + # and AWS allows their destinations to only be: + # - The entire IPv4 or IPv6 CIDR block of your VPC. (Not interesting, as we always want AZ-specific next hops.) + # - The entire IPv4 or IPv6 CIDR block of a subnet in your VPC. (This is used here.) + # Source: https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Route_Tables.html#gateway-route-table + } : {} +} + +### SPOKE VM INSTANCES #### + +data "aws_ami" "this" { + most_recent = true # newest by time, not by version number + + filter { + name = "name" + values = ["bitnami-nginx-1.21*-linux-debian-10-x86_64-hvm-ebs-nami"] + # The wildcard '*' causes re-creation of the whole EC2 instance when a new image appears. + } + + owners = ["979382823631"] # bitnami = 979382823631 +} + +resource "aws_instance" "spoke_vms" { + for_each = var.spoke_vms + + ami = data.aws_ami.this.id + instance_type = each.value.type + key_name = var.ssh_key_name + subnet_id = module.subnet_sets[each.value.vpc_subnet].subnets[each.value.az].id + vpc_security_group_ids = [module.vpc[each.value.vpc].security_group_ids[each.value.security_group]] + tags = merge({ Name = "${var.name_prefix}${each.key}" }, var.global_tags) +} + +### SPOKE INBOUND NETWORK LOAD BALANCER ### + +module "app_lb" { + source = "../../modules/nlb" + + for_each = var.spoke_lbs + + name = "${var.name_prefix}${each.key}" + internal_lb = true + subnets = { for k, v in module.subnet_sets[each.value.vpc_subnet].subnets : k => { id = v.id } } + vpc_id = module.subnet_sets[each.value.vpc_subnet].vpc_id + + balance_rules = { + "SSH-traffic" = { + protocol = "TCP" + port = "22" + target_type = "instance" + stickiness = true + targets = { for vm in each.value.vms : vm => aws_instance.spoke_vms[vm].id } + } + "HTTP-traffic" = { + protocol = "TCP" + port = "80" + target_type = "instance" + stickiness = false + targets = { for vm in each.value.vms : vm => aws_instance.spoke_vms[vm].id } + } + "HTTPS-traffic" = { + protocol = "TCP" + port = "443" + target_type = "instance" + stickiness = false + targets = { for vm in each.value.vms : vm => aws_instance.spoke_vms[vm].id } + } + } + + tags = var.global_tags +} + +### GWLB ASSOCIATIONS WITH VM-Series ENDPOINTS ### + +locals { + subinterface_gwlb_endpoint_eastwest = { for i, j in var.vmseries : i => join(",", compact(concat(flatten([ + for sk, sv in j.subinterfaces.eastwest : [for k, v in module.gwlbe_endpoint[sv.gwlb_endpoint].endpoints : format("aws-gwlb-associate-vpce:%s@%s", v.id, sv.subinterface)] + ])))) } + subinterface_gwlb_endpoint_outbound = { for i, j in var.vmseries : i => join(",", compact(concat(flatten([ + for sk, sv in j.subinterfaces.outbound : [for k, v in module.gwlbe_endpoint[sv.gwlb_endpoint].endpoints : format("aws-gwlb-associate-vpce:%s@%s", v.id, sv.subinterface)] + ])))) } + subinterface_gwlb_endpoint_inbound = { for i, j in var.vmseries : i => join(",", compact(concat(flatten([ + for sk, sv in j.subinterfaces.inbound : [for k, v in module.gwlbe_endpoint[sv.gwlb_endpoint].endpoints : format("aws-gwlb-associate-vpce:%s@%s", v.id, sv.subinterface)] + ])))) } + plugin_op_commands_with_endpoints_mapping = { for i, j in var.vmseries : i => format("%s,%s,%s,%s", j.bootstrap_options["plugin-op-commands"], + local.subinterface_gwlb_endpoint_eastwest[i], local.subinterface_gwlb_endpoint_outbound[i], local.subinterface_gwlb_endpoint_inbound[i]) } + bootstrap_options_with_endpoints_mapping = { for i, j in var.vmseries : i => [ + for k, v in j.bootstrap_options : k != "plugin-op-commands" ? "${k}=${v}" : "${k}=${local.plugin_op_commands_with_endpoints_mapping[i]}" + ] } +} + +### IAM ROLES AND POLICIES ### + +resource "aws_iam_role" "vm_series_ec2_iam_role" { + name = "${var.name_prefix}vmseries" + assume_role_policy = < vmseries } + source = "../../modules/vmseries" + + name = "${var.name_prefix}${each.key}" + vmseries_version = each.value.common.panos_version + + interfaces = { + for k, v in each.value.common.interfaces : k => { + device_index = v.device_index + security_group_ids = try([module.vpc[each.value.common.vpc].security_group_ids[v.security_group]], []) + source_dest_check = try(v.source_dest_check, false) + subnet_id = module.subnet_sets[v.vpc_subnet].subnets[each.value.az].id + create_public_ip = try(v.create_public_ip, false) + } + } + + bootstrap_options = join(";", compact(concat(local.bootstrap_options_with_endpoints_mapping[each.value.group]))) + + iam_instance_profile = aws_iam_instance_profile.vm_series_iam_instance_profile.name + ssh_key_name = var.ssh_key_name + tags = var.global_tags +} + +### Public ALB and NLB used in centralized model ### + +module "public_alb" { + for_each = { for k, v in var.vmseries : k => v } + source = "../../modules/alb" + + lb_name = "${var.name_prefix}${each.value.application_lb.name}" + subnets = { for k, v in module.subnet_sets["security_vpc-alb"].subnets : k => { id = v.id } } + vpc_id = module.vpc["security_vpc"].id + security_groups = [module.vpc["security_vpc"].security_group_ids["application_load_balancer"]] + rules = each.value.application_lb.rules + targets = { for vmseries in local.vmseries_instances : "${vmseries.group}-${vmseries.instance}" => module.vmseries["${vmseries.group}-${vmseries.instance}"].interfaces["public"].private_ip } + + tags = var.global_tags +} + +module "public_nlb" { + for_each = { for k, v in var.vmseries : k => v } + source = "../../modules/nlb" + + name = "${var.name_prefix}${each.value.network_lb.name}" + internal_lb = false + subnets = { for k, v in module.subnet_sets["security_vpc-nlb"].subnets : k => { id = v.id } } + vpc_id = module.vpc["security_vpc"].id + + balance_rules = { for k, v in each.value.network_lb.rules : k => { + protocol = v.protocol + port = v.port + target_type = v.target_type + stickiness = v.stickiness + targets = { for vmseries in local.vmseries_instances : "${vmseries.group}-${vmseries.instance}" => module.vmseries["${vmseries.group}-${vmseries.instance}"].interfaces["public"].private_ip } + } } + + tags = var.global_tags +} diff --git a/examples/centralized_design/outputs.tf b/examples/centralized_design/outputs.tf new file mode 100644 index 00000000..ebf17f74 --- /dev/null +++ b/examples/centralized_design/outputs.tf @@ -0,0 +1,28 @@ +##### Security VPC ##### + +output "vmseries_public_ips" { + description = "Map of public IPs created within `vmseries` module instances." + value = { for k, v in module.vmseries : k => v.public_ips } +} + +##### App VPC ##### + +output "app_inspected_dns_name" { + description = <<-EOF + FQDN of App Internal Load Balancer. + Can be used in VM-Series configuration to balance traffic between the application instances. + EOF + value = [for l in module.app_lb : l.lb_fqdn] +} + +##### VM-Series ALB & NLB ##### + +output "public_alb_dns_name" { + description = "FQDN of VM-Series External Application Load Balancer used in centralized design." + value = { for k, v in module.public_alb : k => v.lb_fqdn } +} + +output "public_nlb_dns_name" { + description = "FQDN of VM-Series External Network Load Balancer used in centralized design." + value = { for k, v in module.public_nlb : k => v.lb_fqdn } +} diff --git a/examples/centralized_design/variables.tf b/examples/centralized_design/variables.tf new file mode 100644 index 00000000..7d42f62e --- /dev/null +++ b/examples/centralized_design/variables.tf @@ -0,0 +1,518 @@ +### GENERAL +variable "region" { + description = "AWS region used to deploy whole infrastructure" + type = string +} +variable "name_prefix" { + description = "Prefix used in names for the resources (VPCs, EC2 instances, autoscaling groups etc.)" + type = string +} +variable "global_tags" { + description = "Global tags configured for all provisioned resources" +} +variable "ssh_key_name" { + description = "Name of the SSH key pair existing in AWS key pairs and used to authenticate to VM-Series or test boxes" + type = string +} + +### VPC +variable "vpcs" { + description = <<-EOF + A 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" + } + } + } + } + ``` + EOF + default = {} + type = 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 = map(object({ + name = string + rules = map(object({ + description = string + type = string, + from_port = string + to_port = string, + protocol = string + cidr_blocks = list(string) + })) + })) + 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 + })) + })) +} + +### TRANSIT GATEWAY +variable "tgw" { + description = <<-EOF + A 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" + } + } + } + ``` + EOF + default = null + type = 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 + })) + }) +} + +### NAT GATEWAY +variable "natgws" { + description = <<-EOF + A 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" + } + } + ``` + EOF + default = {} + type = map(object({ + name = string + vpc_subnet = string + })) +} + +### GATEWAY LOADBALANCER +variable "gwlbs" { + description = <<-EOF + A 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" + } + } + ``` + EOF + default = {} + type = map(object({ + name = string + vpc_subnet = string + })) +} +variable "gwlb_endpoints" { + description = <<-EOF + A 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 + } + } + ``` + EOF + default = {} + type = map(object({ + name = string + gwlb = string + vpc = string + vpc_subnet = string + act_as_next_hop = bool + to_vpc_subnets = string + })) +} + +### VM-SERIES +variable "vmseries" { + description = <<-EOF + A 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 + } + } + ``` + EOF + default = {} + type = 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 + }) + })) +} + +### PANORAMA +variable "panorama_attachment" { + description = <<-EOF + A 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" + } + ``` + EOF + default = null + type = object({ + transit_gateway_attachment_id = string + vpc_cidr = string + }) +} + +### SPOKE VMS +variable "spoke_vms" { + description = <<-EOF + A 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" + } + } + ``` + EOF + default = {} + type = map(object({ + az = string + vpc = string + vpc_subnet = string + security_group = string + type = string + })) +} + +### SPOKE LOADBALANCERS +variable "spoke_lbs" { + description = <<-EOF + A 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"] + } + } + ``` + EOF + default = {} + type = map(object({ + vpc_subnet = string + vms = list(string) + })) +} diff --git a/examples/centralized_design/versions.tf b/examples/centralized_design/versions.tf new file mode 100644 index 00000000..1e0c2c29 --- /dev/null +++ b/examples/centralized_design/versions.tf @@ -0,0 +1,16 @@ +terraform { + required_version = ">= 1.0.0, < 2.0.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 4.25" + } + local = { + version = "~> 2.4.0" + } + } +} + +provider "aws" { + region = var.region +} diff --git a/scripts/install.sh b/scripts/install.sh index 084827ca..4ad8d07b 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -10,7 +10,7 @@ set -euo pipefail cd "$(dirname $0)" -curl -sL https://github.com/terraform-docs/terraform-docs/releases/download/v0.15.0/terraform-docs-v0.15.0-linux-amd64.tar.gz > terraform-docs.tar.gz & \ +curl -sL https://github.com/terraform-docs/terraform-docs/releases/download/v0.16.0/terraform-docs-v0.16.0-linux-amd64.tar.gz > terraform-docs.tar.gz & \ curl -sL https://github.com/tfsec/tfsec/releases/download/v0.34.0/tfsec-linux-amd64 > tfsec & \ curl -sL https://github.com/terraform-linters/tflint/releases/download/v0.29.0/tflint_linux_amd64.zip > tflint.zip & \ wait