Browse Source

Palo Alto, Panorama, and Infrastructure Modules

Creates palo altos and accompanying infrastructure. While this work is
not complete, it should not be thrown away yet. Instances are still
created though they are presently in a stopped state.
Fred Damstra 5 years ago
parent
commit
11f3adcb08

+ 6 - 0
base/palo_alto/TODO

@@ -0,0 +1,6 @@
+1. Fix bug that when you terragrunt apply twice, it wants to recreate because of the EIP association (maybe try an awe_eip_association?)
+1. Add route to internet for management segment routing table?  Not sure.
+
+done:
+1. test again in govcloud now that it works - DOESN"T WORK
+1. Test with 2? WORKS

+ 4 - 0
base/palo_alto/bootstrap/README.md

@@ -0,0 +1,4 @@
+# palo_alto_bootstrap
+
+Creates a boostrap S3 bucket for provisioning the palo altos.
+

+ 323 - 0
base/palo_alto/bootstrap/bootstrap.xml.tmpl.unused

@@ -0,0 +1,323 @@
+<?xml version="1.0"?>
+<config version="9.1.0" urldb="paloaltonetworks">
+  <mgt-config>
+    <users>
+      <entry name="admin">
+        <phash>$1$uqgidqyx$ycpOZ/xupErAt1rjIxvQc0</phash>
+        <permissions>
+          <role-based>
+            <superuser>yes</superuser>
+          </role-based>
+        </permissions>
+        <public-key>c3NoLXJzYSBBQUFBQjNOemFDMXljMkVBQUFBREFRQUJBQUFCQVFERjNwR1U5K0h1ZmdmRWhQUDdQMEx0N2txZkdXTFRHZDZzZkpnU3lwY1NvM0ZQMVhod0ZPV2thTnZaSXBvSWVRWGh1eDV2VG0rUm9xWVovM0dqN2hjR01MZG9IV0FydkxIRDJBR2p4YkZuc21pQ2lvUWdzQy9yWUxCamlXTnNEZFZGNUFyb2ZieS9Sd3ppdk1BaTd5aXZoWTRuR3pYUHNIWm91Y0IwV2kzNC85QW14YnZYV3Y2Y2t1V2tNanJYVmUrdXdGamUzVTdqUUhSVzlqUVJwQ1JSZlVqVkE0Rm1IMFBXcVdGQmx0L3pxc0RQT3pieE5OaEF2eXJKaG83alZCTmpDTHNxMCsrbFQ4QkRLclliYVppVDBGMmM5dUlEUnBISlNkanBxVkNmOWJnaG1lSldZTW9OSEFrR1I3V0NGalBDSjdRTTU3YTJvUkJ0bTFBL0VXY3IgZmRhbXN0cmE=</public-key>
+      </entry>
+    </users>
+    <password-complexity>
+      <enabled>yes</enabled>
+      <minimum-length>8</minimum-length>
+    </password-complexity>
+  </mgt-config>
+  <shared>
+    <application/>
+    <application-group/>
+    <service/>
+    <service-group/>
+    <botnet>
+      <configuration>
+        <http>
+          <dynamic-dns>
+            <enabled>yes</enabled>
+            <threshold>5</threshold>
+          </dynamic-dns>
+          <malware-sites>
+            <enabled>yes</enabled>
+            <threshold>5</threshold>
+          </malware-sites>
+          <recent-domains>
+            <enabled>yes</enabled>
+            <threshold>5</threshold>
+          </recent-domains>
+          <ip-domains>
+            <enabled>yes</enabled>
+            <threshold>10</threshold>
+          </ip-domains>
+          <executables-from-unknown-sites>
+            <enabled>yes</enabled>
+            <threshold>5</threshold>
+          </executables-from-unknown-sites>
+        </http>
+        <other-applications>
+          <irc>yes</irc>
+        </other-applications>
+        <unknown-applications>
+          <unknown-tcp>
+            <destinations-per-hour>10</destinations-per-hour>
+            <sessions-per-hour>10</sessions-per-hour>
+            <session-length>
+              <maximum-bytes>100</maximum-bytes>
+              <minimum-bytes>50</minimum-bytes>
+            </session-length>
+          </unknown-tcp>
+          <unknown-udp>
+            <destinations-per-hour>10</destinations-per-hour>
+            <sessions-per-hour>10</sessions-per-hour>
+            <session-length>
+              <maximum-bytes>100</maximum-bytes>
+              <minimum-bytes>50</minimum-bytes>
+            </session-length>
+          </unknown-udp>
+        </unknown-applications>
+      </configuration>
+      <report>
+        <topn>100</topn>
+        <scheduled>yes</scheduled>
+      </report>
+    </botnet>
+  </shared>
+  <devices>
+    <entry name="localhost.localdomain">
+      <network>
+        <interface>
+          <ethernet/>
+        </interface>
+        <profiles>
+          <monitor-profile>
+            <entry name="default">
+              <interval>3</interval>
+              <threshold>5</threshold>
+              <action>wait-recover</action>
+            </entry>
+          </monitor-profile>
+        </profiles>
+        <ike>
+          <crypto-profiles>
+            <ike-crypto-profiles>
+              <entry name="default">
+                <encryption>
+                  <member>aes-128-cbc</member>
+                  <member>3des</member>
+                </encryption>
+                <hash>
+                  <member>sha1</member>
+                </hash>
+                <dh-group>
+                  <member>group2</member>
+                </dh-group>
+                <lifetime>
+                  <hours>8</hours>
+                </lifetime>
+              </entry>
+              <entry name="Suite-B-GCM-128">
+                <encryption>
+                  <member>aes-128-cbc</member>
+                </encryption>
+                <hash>
+                  <member>sha256</member>
+                </hash>
+                <dh-group>
+                  <member>group19</member>
+                </dh-group>
+                <lifetime>
+                  <hours>8</hours>
+                </lifetime>
+              </entry>
+              <entry name="Suite-B-GCM-256">
+                <encryption>
+                  <member>aes-256-cbc</member>
+                </encryption>
+                <hash>
+                  <member>sha384</member>
+                </hash>
+                <dh-group>
+                  <member>group20</member>
+                </dh-group>
+                <lifetime>
+                  <hours>8</hours>
+                </lifetime>
+              </entry>
+            </ike-crypto-profiles>
+            <ipsec-crypto-profiles>
+              <entry name="default">
+                <esp>
+                  <encryption>
+                    <member>aes-128-cbc</member>
+                    <member>3des</member>
+                  </encryption>
+                  <authentication>
+                    <member>sha1</member>
+                  </authentication>
+                </esp>
+                <dh-group>group2</dh-group>
+                <lifetime>
+                  <hours>1</hours>
+                </lifetime>
+              </entry>
+              <entry name="Suite-B-GCM-128">
+                <esp>
+                  <encryption>
+                    <member>aes-128-gcm</member>
+                  </encryption>
+                  <authentication>
+                    <member>none</member>
+                  </authentication>
+                </esp>
+                <dh-group>group19</dh-group>
+                <lifetime>
+                  <hours>1</hours>
+                </lifetime>
+              </entry>
+              <entry name="Suite-B-GCM-256">
+                <esp>
+                  <encryption>
+                    <member>aes-256-gcm</member>
+                  </encryption>
+                  <authentication>
+                    <member>none</member>
+                  </authentication>
+                </esp>
+                <dh-group>group20</dh-group>
+                <lifetime>
+                  <hours>1</hours>
+                </lifetime>
+              </entry>
+            </ipsec-crypto-profiles>
+            <global-protect-app-crypto-profiles>
+              <entry name="default">
+                <encryption>
+                  <member>aes-128-cbc</member>
+                </encryption>
+                <authentication>
+                  <member>sha1</member>
+                </authentication>
+              </entry>
+            </global-protect-app-crypto-profiles>
+          </crypto-profiles>
+        </ike>
+        <qos>
+          <profile>
+            <entry name="default">
+              <class-bandwidth-type>
+                <mbps>
+                  <class>
+                    <entry name="class1">
+                      <priority>real-time</priority>
+                    </entry>
+                    <entry name="class2">
+                      <priority>high</priority>
+                    </entry>
+                    <entry name="class3">
+                      <priority>high</priority>
+                    </entry>
+                    <entry name="class4">
+                      <priority>medium</priority>
+                    </entry>
+                    <entry name="class5">
+                      <priority>medium</priority>
+                    </entry>
+                    <entry name="class6">
+                      <priority>low</priority>
+                    </entry>
+                    <entry name="class7">
+                      <priority>low</priority>
+                    </entry>
+                    <entry name="class8">
+                      <priority>low</priority>
+                    </entry>
+                  </class>
+                </mbps>
+              </class-bandwidth-type>
+            </entry>
+          </profile>
+        </qos>
+        <virtual-router>
+          <entry name="default">
+            <protocol>
+              <bgp>
+                <enable>no</enable>
+                <dampening-profile>
+                  <entry name="default">
+                    <cutoff>1.25</cutoff>
+                    <reuse>0.5</reuse>
+                    <max-hold-time>900</max-hold-time>
+                    <decay-half-life-reachable>300</decay-half-life-reachable>
+                    <decay-half-life-unreachable>900</decay-half-life-unreachable>
+                    <enable>yes</enable>
+                  </entry>
+                </dampening-profile>
+              </bgp>
+            </protocol>
+          </entry>
+        </virtual-router>
+      </network>
+      <deviceconfig>
+        <system>
+          <type>
+            <dhcp-client>
+              <send-hostname>yes</send-hostname>
+              <send-client-id>yes</send-client-id>
+              <accept-dhcp-hostname>yes</accept-dhcp-hostname>
+              <accept-dhcp-domain>yes</accept-dhcp-domain>
+            </dhcp-client>
+          </type>
+          <update-server>updates.paloaltonetworks.com</update-server>
+          <update-schedule>
+            <threats>
+              <recurring>
+                <weekly>
+                  <day-of-week>wednesday</day-of-week>
+                  <at>01:02</at>
+                  <action>download-only</action>
+                </weekly>
+              </recurring>
+            </threats>
+          </update-schedule>
+          <timezone>US/Pacific</timezone>
+          <service>
+            <disable-telnet>yes</disable-telnet>
+            <disable-http>yes</disable-http>
+          </service>
+          <hostname>xdr_palo_commercial_common_${index}</hostname>
+          <dns-setting>
+            <servers>
+              <primary>169.254.169.253</primary>
+              <secondary>8.8.8.8</secondary>
+            </servers>
+          </dns-setting>
+        </system>
+        <setting>
+          <config>
+            <rematch>yes</rematch>
+          </config>
+          <management>
+            <hostname-type-in-syslog>FQDN</hostname-type-in-syslog>
+            <initcfg>
+              <public-key>c3NoLXJzYSBBQUFBQjNOemFDMXljMkVBQUFBREFRQUJBQUFCQVFERjNwR1U5K0h1ZmdmRWhQUDdQMEx0N2txZkdXTFRHZDZzZkpnU3lwY1NvM0ZQMVhod0ZPV2thTnZaSXBvSWVRWGh1eDV2VG0rUm9xWVovM0dqN2hjR01MZG9IV0FydkxIRDJBR2p4YkZuc21pQ2lvUWdzQy9yWUxCamlXTnNEZFZGNUFyb2ZieS9Sd3ppdk1BaTd5aXZoWTRuR3pYUHNIWm91Y0IwV2kzNC85QW14YnZYV3Y2Y2t1V2tNanJYVmUrdXdGamUzVTdqUUhSVzlqUVJwQ1JSZlVqVkE0Rm1IMFBXcVdGQmx0L3pxc0RQT3pieE5OaEF2eXJKaG83alZCTmpDTHNxMCsrbFQ4QkRLclliYVppVDBGMmM5dUlEUnBISlNkanBxVkNmOWJnaG1lSldZTW9OSEFrR1I3V0NGalBDSjdRTTU3YTJvUkJ0bTFBL0VXY3IgZmRhbXN0cmE=</public-key>
+              <type>
+                <dhcp-client>
+                  <send-hostname>yes</send-hostname>
+                  <send-client-id>yes</send-client-id>
+                  <accept-dhcp-hostname>yes</accept-dhcp-hostname>
+                  <accept-dhcp-domain>yes</accept-dhcp-domain>
+                </dhcp-client>
+              </type>
+              <hostname>xdr_palo_commercial_common_${index}</hostname>
+              <tplname>Inbound-Stack-${index}</tplname>
+              <dgname>XDR-Interconnects</dgname>
+            </initcfg>
+          </management>
+        </setting>
+      </deviceconfig>
+      <vsys>
+        <entry name="vsys1">
+          <application/>
+          <application-group/>
+          <zone/>
+          <service/>
+          <service-group/>
+          <schedule/>
+          <rulebase/>
+        </entry>
+      </vsys>
+    </entry>
+  </devices>
+</config>

+ 20 - 0
base/palo_alto/bootstrap/init-cfg.txt.tmpl

@@ -0,0 +1,20 @@
+type=dhcp-client
+ip-address=
+default-gateway=
+netmask=
+ipv6-address=
+ipv6-default-gateway=
+hostname=${hostname}
+vm-auth-key=${authkey}
+panorama-server=${panorama_primary}
+panorama-server-2=${panorama_secondary}
+tplname=${tplname}
+dgname=${dgname}
+dns-primary=169.254.169.253
+dns-secondary=8.8.8.8
+op-command-modes=${op-command-modes}
+op-cmd-dpdk-pkt-io=
+dhcp-send-hostname=yes
+dhcp-send-client-id=yes
+dhcp-accept-server-hostname=no
+dhcp-accept-server-domain=no

+ 10 - 0
base/palo_alto/bootstrap/locals.tf

@@ -0,0 +1,10 @@
+locals {
+  file_list = fileset("${path.root}/files", "**/[^.]*") 
+  bootstrap_dirs = [
+    "config/",
+    "content/",
+    "software/",
+    "license/",
+    "plugins/"
+  ]
+}

+ 114 - 0
base/palo_alto/bootstrap/main.tf

@@ -0,0 +1,114 @@
+resource "aws_s3_bucket" "bucket" {
+  count = var.palo_alto_count
+
+  bucket = "xdr-palo-alto-bootstrap-${count.index}"
+  acl    = "private"
+}
+
+locals {
+  # Bootstrap process requires that folders exist, so we must create them in each bucket. This looks complicated,
+  # but it's just doing a foreach bucket: foreach directory: ...
+  bucket_folder_map = { for p in setproduct(range(var.palo_alto_count), local.bootstrap_dirs): "${p[0]}/${p[1]}" => {
+        num = p[0]
+        folder = p[1]
+    }
+  }
+}
+
+resource "aws_s3_bucket_object" "bootstrap_dirs" {
+  for_each = local.bucket_folder_map
+
+  bucket  = aws_s3_bucket.bucket[each.value["num"]].id
+  key     = each.value["folder"]
+  content = "/dev/null"
+}
+
+resource "aws_s3_bucket_object" "init_cfg" {
+  count = var.palo_alto_count
+  bucket = aws_s3_bucket.bucket[count.index].id
+  key    = "config/init-cfg.txt"
+  content = templatefile("${path.module}/init-cfg.txt.tmpl",
+    {
+      "hostname"         = "xdr_palo_${var.aws_partition_alias}_${var.environment}_${count.index}" 
+      "authkey"          = var.palo_alto_auth_keys[count.index]
+      "tplname"          = "XDR-Interconnect-Stack-${count.index}"
+      "dgname"           = "XDR-Interconnects"
+      "op-command-modes" = "jumbo-frame, mgmt-interface-swap"
+      "panorama_primary"   = var.panorama_servers[0]
+      "panorama_secondary" = var.panorama_servers[1]
+    }
+  )
+}
+
+# No bootstrap configuration, as we're registered to panorama
+#resource "aws_s3_bucket_object" "bootstrap_xml" {
+#  count = var.palo_alto_count
+#  bucket = aws_s3_bucket.bucket[count.index].id
+#  key    = "config/bootstrap.xml"
+#  content = templatefile("${path.module}/bootstrap.xml.tmpl",
+#    {
+#      index = count.index
+#    }
+#  )
+#}
+
+resource "aws_s3_bucket_object" "authcodes" {
+  count = var.palo_alto_count
+  bucket = aws_s3_bucket.bucket[count.index].id
+  key    = "license/authcodes"
+  content = <<EOF
+${var.palo_alto_license_keys[count.index]}
+EOF
+}
+
+resource "aws_iam_role" "bootstrap_role" {
+  count = var.palo_alto_count
+  name = "palo_alto_bootstrap_${count.index}"
+  path = "/instance/"
+
+  assume_role_policy = <<EOF
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Principal": {
+      "Service": "ec2.amazonaws.com"
+    },
+      "Action": "sts:AssumeRole"
+    }
+  ]
+}
+EOF
+}
+
+resource "aws_iam_role_policy" "bootstrap_policy" {
+  count = var.palo_alto_count
+  name = "palo_alto_bootstrap_${count.index}"
+  role = aws_iam_role.bootstrap_role[count.index].id
+
+  policy = <<EOF
+{
+  "Version" : "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": "s3:ListBucket",
+      "Resource": "arn:${var.aws_partition}:s3:::${aws_s3_bucket.bucket[count.index].bucket}"
+    },
+    {
+    "Effect": "Allow",
+    "Action": "s3:GetObject",
+    "Resource": "arn:${var.aws_partition}:s3:::${aws_s3_bucket.bucket[count.index].bucket}/*"
+    }
+  ]
+}
+EOF
+}
+
+resource "aws_iam_instance_profile" "bootstrap" {
+  count = var.palo_alto_count
+  name = "palo_alto_bootstrap_${count.index}"
+  role = aws_iam_role.bootstrap_role[count.index].name
+  path = "/instance/"
+}

+ 2 - 0
base/palo_alto/bootstrap/outputs.tf

@@ -0,0 +1,2 @@
+output "bucket_ids" { value=aws_s3_bucket.bucket[*].id }
+output "instance_profile_names" { value=aws_iam_instance_profile.bootstrap[*].name }

+ 20 - 0
base/palo_alto/bootstrap/vars.tf

@@ -0,0 +1,20 @@
+variable "tags" {
+  description = "Tags to add to the resource (in addition to global standard tags)"
+  type        = map
+  default     = { }
+}
+
+variable "palo_alto_count" { type = number }
+variable "palo_alto_auth_keys" { type = list }
+variable "palo_alto_license_keys" { type = list }
+variable "aws_partition" { type = string }
+variable "panorama_servers" { type = list }
+
+# ----------------------------------
+# Below this line are variables inherited from higher levels, so they
+# do not need to be explicitly passed to this module.
+variable "standard_tags" { type = map }
+variable "inside_domain" { type = string }
+variable "aws_region"    { type = string }
+variable "environment"   { type = string }
+variable "aws_partition_alias" { type = string }

+ 5 - 0
base/palo_alto/firewall_nodes/README.md

@@ -0,0 +1,5 @@
+# Security VPCs for Palo firewalls
+
+Creates a VPC for the PA firewalls, consisting of two AZs, each with a public and a management VPC. In the interest of keeping security VPCs clean, this has a fewer VPC endpoints. The Palo Altos should not need them.
+
+These VPCs are NOT connected to the transit gateways. Instead, the Palo Alto creates a VPN connection to the TGW.

+ 42 - 0
base/palo_alto/firewall_nodes/ami.tf

@@ -0,0 +1,42 @@
+# I'd much rather do a find_ami here.
+variable "pavm_byol_ami_id" {
+    default = {
+        us-east-1 = "ami-06962643040d7363f"
+        us-gov-east-1 = "ami-0f0d54de4d1212aae" # 9.1.3
+        #us-gov-east-1 = "ami-a6dd31d7" # 9.1.2
+        us-gov-west-1 = "ami-019045558d9d46abe"
+
+        # The below are old and suspect
+        ap-south-1 = "ami-5c187233",
+        eu-west-1 = "ami-73971600",
+        ap-southeast-1 = "ami-0c60aa6f",
+        ap-southeast-2 = "ami-f9c4e79a",
+        ap-northeast-2 = "ami-fa08c194",
+        eu-central-1 = "ami-74e5041b",
+        ap-northeast-1 = "ami-e44b5a8a",
+        us-west-1 = "ami-acd7aacc",
+        sa-east-1 = "ami-1d860971",
+        us-west-2 = "ami-e7be4b87"
+    }
+}
+
+# From their examples:
+# data "aws_ami" "panos_firewall_ami" {
+#  most_recent = "${var.fw_version == "latest" ? true : false}"
+#  owners      = ["679593333241"]
+#
+#  filter {
+#    name   = "name"
+#    values = ["PA-VM-AWS-${replace(var.fw_version, "/latest/", "")}*"]
+#  }
+#
+#  filter {
+#    name   = "product-code.type"
+#    values = ["marketplace"]
+#  }
+#
+#  filter {
+#    name   = "product-code"
+#    values = ["${lookup(var.bundles, var.fw_bundle)}"]
+#  }
+#}

+ 132 - 0
base/palo_alto/firewall_nodes/main.tf

@@ -0,0 +1,132 @@
+resource "aws_network_interface" "FWManagementNetworkInterface" {
+  count = var.palo_alto_count
+  subnet_id       = var.subnet_id_map["management"][count.index % 2]
+  security_groups = var.management_security_group_ids
+  source_dest_check = false
+  private_ips_count = 0
+  private_ips = [ cidrhost(var.subnet_cidr_map["management"][count.index % 2], 10 + (count.index % 2)) ]
+  description = "Palo Alto XDR Interconnect ${count.index} management interface"
+  tags = {
+    Name = "xdr-interconnect-${count.index}_management_interface"
+  }
+}
+
+resource "aws_network_interface" "FWPublicNetworkInterface" {
+  count = var.palo_alto_count
+  subnet_id       = var.subnet_id_map["untrusted"][count.index % 2]
+  security_groups = var.untrusted_security_group_ids
+  source_dest_check = false
+  private_ips_count = 0
+  private_ips = [ cidrhost(var.subnet_cidr_map["untrusted"][count.index % 2], 10 + (count.index % 2)) ]
+  description = "Palo Alto XDR Interconnect ${count.index} untrusted interface"
+  tags = {
+    Name = "xdr-interconnect-${count.index}_untrusted_interface"
+  }
+}
+
+resource "aws_network_interface" "FWPrivateNetworkInterface" {
+  count = var.palo_alto_count
+  subnet_id       = var.subnet_id_map["private"][count.index % 2]
+  security_groups = var.untrusted_security_group_ids
+  source_dest_check = false
+  private_ips_count = 0
+  private_ips = [ cidrhost(var.subnet_cidr_map["private"][count.index % 2], 10 + (count.index % 2)) ]
+  description = "Palo Alto XDR Interconnect ${count.index} private interface"
+  tags = {
+    Name = "xdr-interconnect-${count.index}_private_interface"
+  }
+}
+
+resource "aws_network_interface" "FWTGWNetworkInterface" {
+  count = var.palo_alto_count
+  subnet_id       = var.subnet_id_map["tgw_standalone"][count.index % 2]
+  security_groups = var.untrusted_security_group_ids
+  source_dest_check = false
+  private_ips_count = 0
+  private_ips = [ cidrhost(var.subnet_cidr_map["tgw_standalone"][count.index % 2], 10 + (count.index % 2)) ]
+  description = "Palo Alto XDR Interconnect ${count.index} tgw interface"
+  tags = {
+    Name = "xdr-interconnect-${count.index}_tgw_interface"
+  }
+}
+
+resource "aws_eip" "untrusted_eip" {
+  count = var.palo_alto_count
+  vpc = true
+}
+
+resource "aws_eip" "management_eip" {
+  count = var.palo_alto_count
+  vpc = true
+}
+
+resource "aws_eip_association" "FWEIPManagementAssociation" {
+  count = var.palo_alto_count
+  network_interface_id = aws_network_interface.FWManagementNetworkInterface[count.index].id
+  allocation_id = aws_eip.management_eip[count.index].id
+}
+
+resource "aws_eip_association" "FWEIPPublicAssociation" {
+  count = var.palo_alto_count
+  network_interface_id   = aws_network_interface.FWPublicNetworkInterface[count.index].id
+  allocation_id = aws_eip.untrusted_eip[count.index].id
+}
+
+resource "aws_placement_group" "palo_group" {
+  name = "Palo Alto Placement Group"
+  strategy = "spread"
+}
+
+resource "aws_instance" "palo" {
+  count = var.palo_alto_count
+  ami = lookup(var.pavm_byol_ami_id, var.aws_region)
+  availability_zone = var.azs[count.index % 2]
+  placement_group = aws_placement_group.palo_group.id
+  tenancy = "default"
+  ebs_optimized = true
+  disable_api_termination = var.instance_termination_protection
+  instance_initiated_shutdown_behavior = "stop"
+  instance_type = var.palo_alto_instance_type
+  key_name = var.palo_alto_key_name
+  monitoring = false
+  #subnet_id = var.subnet_id_map["untrusted"][count.index % 2]
+  #associate_public_ip_address = true # causes a recreate on apply if you set this!
+  #private_ip = cidrhost(var.subnet_cidr_map["untrusted"][count.index % 2], 10 + (count.index % 2))
+  #source_dest_check = false
+
+  tags = merge(
+    var.standard_tags,
+    var.tags,
+    { Name = "xdr-interconnect-${count.index}" }
+  )
+
+  root_block_device {
+      volume_type = "gp2"
+      volume_size = "60"
+      delete_on_termination = true
+  }
+
+  network_interface {
+    device_index = 0
+    network_interface_id = aws_network_interface.FWPublicNetworkInterface[count.index].id
+  }
+
+  network_interface {
+    device_index = 1
+    network_interface_id = aws_network_interface.FWManagementNetworkInterface[count.index].id
+  }
+
+  network_interface {
+    device_index = 2
+    network_interface_id = aws_network_interface.FWPrivateNetworkInterface[count.index].id
+  }
+
+  network_interface {
+    device_index = 3
+    network_interface_id = aws_network_interface.FWTGWNetworkInterface[count.index].id
+  }
+
+  user_data = base64encode("vmseries-bootstrap-aws-s3bucket=${var.bucket_ids[count.index]}")
+
+  iam_instance_profile = var.instance_profile_names[count.index]
+}

+ 18 - 0
base/palo_alto/firewall_nodes/notes.md

@@ -0,0 +1,18 @@
+TODO:
+* Create an ebs key to encrypt the drive!
+
+
+
+
+```
+Ignore the below, HA is only possible in the same AZ.
+
+For HA, it needs an instance policy to move ENIs: (???)
+ AttachNetworkInterface—For permission to attach an ENI to an instance.
+ DescribeNetworkInterface—For fetching the ENI parameters in order to attach an interface to the instance.
+ DetachNetworkInterface—For permission to detach the ENI from the EC2 instance.
+ DescribeInstances—For permission to obtain information on the EC2 instances in the VPC.
+ Wild card (*)—In the Amazon Resource Name (ARN) field use the * as a wild card.
+screenshot here:
+https://docs.paloaltonetworks.com/vm-series/7-1/vm-series-deployment/set-up-the-vm-series-firewall-in-aws/high-availability-for-vm-series-firewall-in-aws.html#22689
+```

+ 20 - 0
base/palo_alto/firewall_nodes/outputs.tf

@@ -0,0 +1,20 @@
+# Output data from PA
+output "instance_ids" {
+    value = aws_instance.palo[*].id
+}
+
+output "untrusted_ips_private" {
+    value = aws_instance.palo[*].private_ip
+}
+
+output "untrusted_ips" {
+    value = aws_eip.untrusted_eip[*].public_ip
+}
+
+output "management_ips_private" {
+    value = aws_eip.management_eip[*].private_ip
+}
+
+output "management_ips" {
+    value = aws_eip.management_eip[*].public_ip
+}

+ 57 - 0
base/palo_alto/firewall_nodes/vars.tf

@@ -0,0 +1,57 @@
+variable "azs" {
+  description = "List of AZs for the palo altos"
+  type = list
+}
+
+variable "management_security_group_ids" {
+  description = "List of security groups for PA interfaces."
+  type = list
+}
+
+variable "untrusted_security_group_ids" {
+  description = "List of security groups for PA interfaces."
+  type = list
+}
+
+variable "palo_alto_count" {
+  description = "How many to create, should be divisible by 2 for production"
+  type = number
+  default = 2
+}
+
+variable "tags" {
+  description = "Tags to add to the resource (in addition to global standard tags)"
+  type        = map
+  default     = { }
+}
+
+variable "palo_alto_instance_type" { type = string }
+variable "palo_alto_key_name" { type = string }
+variable "instance_termination_protection" { type = bool }
+variable "subnet_id_map" { type = map }
+variable "subnet_cidr_map" { type = map }
+variable "bucket_ids" { type = list }
+variable "instance_profile_names" { type = list }
+
+# ----------------------------------
+# Below this line are variables inherited from higher levels, so they
+# do not need to be explicitly passed to this module.
+variable "standard_tags" {
+  type        = map
+}
+
+variable "inside_domain" {
+  type        = string
+}
+
+variable "aws_region" {
+  type        = string
+}
+
+variable "environment" {
+  type        = string
+}
+
+variable "aws_partition_alias" {
+  type        = string
+}

+ 3 - 0
base/palo_alto/firewall_nodes/version.tf

@@ -0,0 +1,3 @@
+terraform {
+  required_version = "~> 0.12"
+}

+ 5 - 0
base/palo_alto/panorama/README.md

@@ -0,0 +1,5 @@
+# Security VPCs for Palo firewalls
+
+Creates a VPC for the PA firewalls, consisting of two AZs, each with a public and a management VPC. In the interest of keeping security VPCs clean, this has a fewer VPC endpoints. The Palo Altos should not need them.
+
+These VPCs are NOT connected to the transit gateways. Instead, the Palo Alto creates a VPN connection to the TGW.

+ 28 - 0
base/palo_alto/panorama/ami.tf

@@ -0,0 +1,28 @@
+# I'd much rather do a find_ami here.
+variable "panorama_ami" {
+    default = {
+        us-east-1 = "ami-0d223a3ed251da3a0"
+        us-gov-east-1 = "ami-12cc2063"
+    }
+}
+
+# From their examples:
+# data "aws_ami" "panos_firewall_ami" {
+#  most_recent = "${var.fw_version == "latest" ? true : false}"
+#  owners      = ["679593333241"]
+#
+#  filter {
+#    name   = "name"
+#    values = ["PA-VM-AWS-${replace(var.fw_version, "/latest/", "")}*"]
+#  }
+#
+#  filter {
+#    name   = "product-code.type"
+#    values = ["marketplace"]
+#  }
+#
+#  filter {
+#    name   = "product-code"
+#    values = ["${lookup(var.bundles, var.fw_bundle)}"]
+#  }
+#}

+ 72 - 0
base/palo_alto/panorama/main.tf

@@ -0,0 +1,72 @@
+# Panorama
+resource "aws_placement_group" "panorama_group" {
+  name = "Panorama Placement Group"
+  strategy = "spread"
+}
+
+resource "aws_instance" "panorama" {
+  count = var.panorama_count
+  ami = lookup(var.panorama_ami, var.aws_region)
+  availability_zone = var.azs[count.index % 2]
+  placement_group = aws_placement_group.panorama_group.id
+  tenancy = "default"
+  ebs_optimized = true
+  disable_api_termination = var.instance_termination_protection
+  instance_initiated_shutdown_behavior = "stop"
+  instance_type = var.panorama_instance_type
+  key_name = var.panorama_key_name
+  monitoring = false
+  vpc_security_group_ids = var.panorama_security_group_ids
+  subnet_id = var.subnet_id_map["management"][count.index % 2]
+  #associate_public_ip_address = true # causes a recreate on apply if you set this!
+  private_ip = cidrhost(var.subnet_cidr_map["management"][count.index % 2], 5 + (count.index % 2))
+  source_dest_check = true
+
+  tags = merge(
+    var.standard_tags,
+    var.tags,
+    { Name = "xdr-panorama-${count.index}" }
+  )
+
+  root_block_device {
+    volume_type = "gp2"
+    volume_size = "81"
+    delete_on_termination = true
+    encrypted = true
+    kms_key_id = var.ebs_key
+  }
+
+  # The provisioner doesn't do anything
+  #connection {
+  #  type = "ssh"
+  #  user = "admin"
+  #  private_key = file("~/.ssh/id_rsa") # Use your private key
+  #  host = aws_eip.management_eip[count.index].public_ip
+  #} 
+  #
+  #provisioner "remote-exec" {
+  #  # Used by a provisioner
+  #
+  #  inline = [
+  #    "set mgt-config users admin password",
+  #    "testme",
+  #    "testme",
+  #    "commit"
+  #  ]
+  #  on_failure = continue
+  #}
+}
+
+# EIP for Management Interface, declared separately so they're easier to preserve
+resource "aws_eip" "management_eip" {
+    count = var.panorama_count
+    vpc = true
+}
+
+resource "aws_eip_association" "eip_assoc" {
+  count = var.panorama_count
+  instance_id   = aws_instance.panorama[count.index].id
+  allocation_id = aws_eip.management_eip[count.index].id
+  private_ip_address = cidrhost(var.subnet_cidr_map["management"][count.index % 2], 5 + (count.index % 2))
+}
+

+ 18 - 0
base/palo_alto/panorama/notes.md

@@ -0,0 +1,18 @@
+TODO:
+* Create an ebs key to encrypt the drive!
+
+
+
+
+```
+Ignore the below, HA is only possible in the same AZ.
+
+For HA, it needs an instance policy to move ENIs: (???)
+ AttachNetworkInterface—For permission to attach an ENI to an instance.
+ DescribeNetworkInterface—For fetching the ENI parameters in order to attach an interface to the instance.
+ DetachNetworkInterface—For permission to detach the ENI from the EC2 instance.
+ DescribeInstances—For permission to obtain information on the EC2 instances in the VPC.
+ Wild card (*)—In the Amazon Resource Name (ARN) field use the * as a wild card.
+screenshot here:
+https://docs.paloaltonetworks.com/vm-series/7-1/vm-series-deployment/set-up-the-vm-series-firewall-in-aws/high-availability-for-vm-series-firewall-in-aws.html#22689
+```

+ 12 - 0
base/palo_alto/panorama/outputs.tf

@@ -0,0 +1,12 @@
+# Output data from PA
+output "instance_ids" {
+    value = aws_instance.panorama[*].id
+}
+
+output "management_private_ips" {
+    value = aws_instance.panorama[*].private_ip
+}
+
+output "management_ips" {
+    value = aws_eip.management_eip[*].public_ip
+}

+ 56 - 0
base/palo_alto/panorama/vars.tf

@@ -0,0 +1,56 @@
+variable "azs" {
+  description = "List of AZs for the devices"
+  type = list
+}
+
+variable "panorama_security_group_ids" {
+  description = "List of security groups for interfaces."
+  type = list
+}
+
+
+variable "ebs_key" {
+  description = "Key with which to encrypt the panorama ebs"
+  type = string
+}
+
+variable "tags" {
+  description = "Tags to add to the resource (in addition to global standard tags)"
+  type        = map
+  default     = { }
+}
+
+variable "panorama_count" {
+  description = "How many to create, should be divisible by 2 for production"
+  type = number
+  default = 2
+}
+
+variable "panorama_instance_type" { type = string }
+variable "panorama_key_name" { type = string }
+variable "instance_termination_protection" { type = bool }
+variable "subnet_id_map" { type = map }
+variable "subnet_cidr_map" { type = map }
+
+# ----------------------------------
+# Below this line are variables inherited from higher levels, so they
+# do not need to be explicitly passed to this module.
+variable "standard_tags" {
+  type        = map
+}
+
+variable "inside_domain" {
+  type        = string
+}
+
+variable "aws_region" {
+  type        = string
+}
+
+variable "environment" {
+  type        = string
+}
+
+variable "aws_partition_alias" {
+  type        = string
+}

+ 3 - 0
base/palo_alto/panorama/version.tf

@@ -0,0 +1,3 @@
+terraform {
+  required_version = "~> 0.12"
+}

+ 5 - 0
base/palo_alto/security_vpc/README.md

@@ -0,0 +1,5 @@
+# Security VPCs for Palo firewalls
+
+Creates a VPC for the PA firewalls, consisting of two AZs, each with a public and a management VPC. In the interest of keeping security VPCs clean, this has a fewer VPC endpoints. The Palo Altos should not need them.
+
+These VPCs are NOT connected to the transit gateways. Instead, the Palo Alto creates a VPN connection to the TGW.

+ 14 - 0
base/palo_alto/security_vpc/ebs-kms-key.tf

@@ -0,0 +1,14 @@
+module "kms_palo" {
+  source = "../../../submodules/kms/ebs-key"
+
+  name = "palo_alto_ebs"
+  alias = "alias/palo_alto_ebs"
+  description = "Used for encrypting palo alto and panorama images."
+  tags = merge(var.standard_tags, var.tags)
+  key_admin_arns = [ ]
+  key_user_arns = [ ]
+  key_attacher_arns =  [ ]
+  standard_tags = var.standard_tags
+  aws_account_id = var.aws_account_id
+  aws_partition = var.aws_partition
+}

+ 91 - 0
base/palo_alto/security_vpc/main.tf

@@ -0,0 +1,91 @@
+locals {
+  azs = slice(data.aws_availability_zones.available.names,0,2)
+  subnets = [
+    cidrsubnet(var.security_vpc_cidr,3,0),
+    cidrsubnet(var.security_vpc_cidr,3,1),
+    cidrsubnet(var.security_vpc_cidr,3,2),
+    cidrsubnet(var.security_vpc_cidr,3,3),
+    cidrsubnet(var.security_vpc_cidr,3,4),
+    cidrsubnet(var.security_vpc_cidr,3,5),
+    cidrsubnet(var.security_vpc_cidr,3,6),
+    cidrsubnet(var.security_vpc_cidr,3,7),
+  ]
+}
+
+data "aws_availability_zones" "available" {
+  state = "available"
+}
+
+module "vpc" {
+  source = "terraform-aws-modules/vpc/aws"
+  version = "~> v2.0"
+  name = "security_vpc_${var.aws_partition_alias}_${var.environment}"
+  cidr = var.security_vpc_cidr
+
+  azs = local.azs
+
+  # 2 private and 2 public here, but 2 more of each will be created after in the same azs
+  private_subnets = [
+    local.subnets[0],
+    local.subnets[1],
+  ]
+  private_subnet_tags = {
+    "Name" = "FW private (private)"
+  }
+
+  public_subnets = [ 
+    local.subnets[4],
+    local.subnets[5]
+  ]
+  public_subnet_tags = {
+    "Name" = "FW Untrusted (Public)"
+  }
+
+  enable_nat_gateway = false
+  enable_vpn_gateway = false
+  enable_dns_hostnames = true
+  enable_s3_endpoint = true
+  enable_dynamodb_endpoint = false
+  enable_sts_endpoint = false
+  enable_kms_endpoint = false
+  enable_dhcp_options = true
+
+  enable_ec2_endpoint              = true # PA likes a local ec2 endpoint
+  ec2_endpoint_security_group_ids  = [ module.aws_endpoints_sg.this_security_group_id ]
+
+  dhcp_options_domain_name = var.inside_domain
+
+  tags = merge(var.standard_tags, var.tags)
+}
+
+resource "aws_subnet" "mgmt" {
+  count = 2
+  depends_on = [ module.vpc ]
+  vpc_id     = module.vpc.vpc_id
+  cidr_block = local.subnets[6 + count.index]
+  availability_zone = local.azs[count.index]
+
+  tags = {
+    Name = "FW Management (Public)"
+  }
+}
+
+resource "aws_route_table_association" "mgmt-to-internet" {
+  count = 2
+  depends_on = [ aws_subnet.mgmt, module.vpc ]
+  subnet_id = aws_subnet.mgmt[count.index].id
+  route_table_id = module.vpc.public_route_table_ids[0] # only 1 public route table
+}
+
+resource "aws_subnet" "standalone_tgw" {
+  # A standalone private subnet that could be connected to the tgw
+  count = 2
+  depends_on = [ module.vpc ]
+  vpc_id     = module.vpc.vpc_id
+  cidr_block = local.subnets[2 + count.index]
+  availability_zone = local.azs[count.index]
+
+  tags = {
+    Name = "Standalone TGW"
+  }
+}

+ 60 - 0
base/palo_alto/security_vpc/outputs.tf

@@ -0,0 +1,60 @@
+output vpc_id {
+  value = module.vpc.vpc_id
+}
+
+output public_subnets {
+  value = concat(
+    module.vpc.public_subnets,
+    aws_subnet.mgmt[*].id
+  )
+}
+
+output private_subnets {
+  value = concat(
+    module.vpc.private_subnets,
+    aws_subnet.standalone_tgw[*].id
+  )
+}
+
+output subnet_id_map {
+  value = {
+    "untrusted" = module.vpc.public_subnets,
+    "management" = aws_subnet.mgmt[*].id,
+    "private" = module.vpc.private_subnets,
+    "tgw_standalone" = aws_subnet.standalone_tgw[*].id
+  }
+}
+
+output subnet_cidr_map {
+  value = {
+    "untrusted" = module.vpc.public_subnets_cidr_blocks,
+    "management" = aws_subnet.mgmt[*].cidr_block,
+    "private" = module.vpc.private_subnets_cidr_blocks,
+    "tgw_standalone" = aws_subnet.standalone_tgw[*].cidr_block,
+  }
+}
+
+output security_groups {
+  value = {
+    allow_all = module.allow_all_sg.this_security_group_id
+    allow_all_outbound = module.allow_all_outbound_sg.this_security_group_id
+    allow_trusted = module.allow_trusted_sg.this_security_group_id
+    allow_all_intravpc = module.allow_all_intravpc.this_security_group_id
+  }
+}
+
+output private_route_tables {
+  value = module.vpc.private_route_table_ids
+}
+
+output public_route_tables {
+  value = module.vpc.public_route_table_ids
+}
+
+output azs {
+  value = module.vpc.azs
+}
+
+output ebs_kms_arn {
+  value = module.kms_palo.key_arn
+}

+ 73 - 0
base/palo_alto/security_vpc/security-groups.tf

@@ -0,0 +1,73 @@
+# Several of these security groups will have customer IPs listed in them to allow
+# POP systems to access our services.
+#
+
+locals {
+}
+
+module "aws_endpoints_sg" {
+  use_name_prefix = false
+  source = "terraform-aws-modules/security-group/aws"
+  version = "~> 3"
+  name        = "aws_endpoints"
+  tags        = merge(var.standard_tags, var.tags)
+  vpc_id      = module.vpc.vpc_id
+
+  ingress_cidr_blocks = [ module.vpc.vpc_cidr_block ]
+  egress_cidr_blocks = [ module.vpc.vpc_cidr_block ]
+  egress_ipv6_cidr_blocks = [ ]
+
+  egress_rules = [ "all-all" ]
+  ingress_rules = [ "all-all" ]
+}
+
+module "allow_all_sg" {
+  use_name_prefix = false
+  source = "terraform-aws-modules/security-group/aws"
+  version = "~> 3"
+  name        = "allow-all"
+  tags        = merge(var.standard_tags, var.tags)
+  vpc_id      = module.vpc.vpc_id
+
+  ingress_cidr_blocks = [ "0.0.0.0/0" ]
+  egress_cidr_blocks = [ "0.0.0.0/0" ]
+  ingress_rules = [ "all-all" ]
+  egress_rules = [ "all-all" ]
+}
+
+module "allow_all_outbound_sg" {
+  use_name_prefix = false
+  source = "terraform-aws-modules/security-group/aws"
+  version = "~> 3"
+  name        = "allow-all-outbound"
+  tags        = merge(var.standard_tags, var.tags)
+  vpc_id      = module.vpc.vpc_id
+
+  egress_rules = [ "all-all" ]
+}
+
+module "allow_trusted_sg" {
+  use_name_prefix = false
+  source = "terraform-aws-modules/security-group/aws"
+  version = "~> 3"
+  name        = "allow_trusted"
+  tags        = merge(var.standard_tags, var.tags)
+  vpc_id      = module.vpc.vpc_id
+
+  egress_rules = [ "all-all" ]
+  ingress_rules = [ "http-80-tcp", "https-443-tcp", "ssh-tcp", "all-icmp" ]
+  ingress_cidr_blocks = concat(var.trusted_ips, [ module.vpc.vpc_cidr_block ])
+}
+
+module "allow_all_intravpc" {
+  use_name_prefix = false
+  source = "terraform-aws-modules/security-group/aws"
+  version = "~> 3"
+  name        = "allow_all_intravpc"
+  tags        = merge(var.standard_tags, var.tags)
+  vpc_id      = module.vpc.vpc_id
+
+  egress_rules = [ "all-all" ]
+  ingress_rules = [ "all-all" ]
+  ingress_cidr_blocks = [ module.vpc.vpc_cidr_block ]
+}

+ 25 - 0
base/palo_alto/security_vpc/vars.tf

@@ -0,0 +1,25 @@
+variable "security_vpc_cidr" {
+  description = "The CIDR Block for the security VPC"
+  type        = string
+}
+
+variable "tags" {
+  description = "Tags to add to the resource (in addition to global standard tags)"
+  type        = map
+  default     = { }
+}
+
+variable "palo_alto_instance_type" { type = string }
+variable "palo_alto_key_name" { type = string }
+
+# ----------------------------------
+# Below this line are variables inherited from higher levels, so they
+# do not need to be explicitly passed to this module.
+variable "standard_tags" { type = map }
+variable "inside_domain" { type = string }
+variable "aws_account_id" { type = string }
+variable "aws_region" { type = string }
+variable "environment" { type = string }
+variable "aws_partition" { type = string }
+variable "aws_partition_alias" { type = string }
+variable "trusted_ips" { type = list }

+ 3 - 0
base/palo_alto/security_vpc/version.tf

@@ -0,0 +1,3 @@
+terraform {
+  required_version = "~> 0.12"
+}

+ 10 - 1
thirdparty/README.md

@@ -2,7 +2,16 @@
 
 This directory contains modules developed by third parties. Please be sure to include:
 * The URL of the original repository
-* The URL of a copy of the original repository (fork it to our github, please!)
 * The version, if any, that is present.
 * The date of the copy
 * Any custom changes
+
+## terraform-aws-panos-bootstrap
+
+Creates the bootstrap S3 bucket and instance profile for a PA
+
+Original URL: https://github.com/PaloAltoNetworks/terraform-aws-panos-bootstrap
+Version: latest master
+Date: 2020-06-29
+
+