diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9edb978 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +.terraform.lock.hcl +.terraform +.terraform.tfstate.lock.info +terraform.tfstate +terraform.tfstate.backup +terraform.tfvars +bootstrap.xml \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0375682 --- /dev/null +++ b/README.md @@ -0,0 +1,529 @@ +# Secure IPv4 & IPv6 with VM-Series on Google Cloud + +This tutorial shows how to deploy and configure Palo Alto Networks VM-Series to secure IPv4 and IPv6 traffic on Google Cloud. + +This guide is intended for network administrators, solution architects, and security professionals who are very familiar with [Compute Engine](https://cloud.google.com/compute) and [Virtual Private Cloud (VPC) networking](https://cloud.google.com/vpc). + +>[!WARNING] +>IPv6 support for VM-Series on Google Cloud is currently not supported. This open-source tutorial represents a best-effort to demonstrate how VM-Series secures IPv6 traffic on Google Cloud. + +## Requirements + +The following are required for this tutorial: + +1. A Google Cloud project. +2. Access to Google Cloud Shell to deploy the resources. +3. If using BYOL, an VM-Series authkey to license the firewall. + +## Architecture + +The diagram shows the resources created with Terraform. + + + +The VM-Series has 3 network interfaces, each belonging to a dual-stack subnet in separate VPC networks. The VM-Series is deployed to an unmanaged instance group which is a backend service of an external pass-through load balancer. The load balancer is configured with IPv4 and IPv6 frontend addresses to distribute internet inbound traffic to the VM-Series for inspection. + +Test workloads are deployed to test north/south traffic. The `external-vm` will be used to test internet inbound traffic through the VM-Series to the `internal-vm` in the trust network. + +>[!CAUTION] +>At the time of this writing, IPv6 traffic cannot be routed to an internal load balancer as the next hop. + + +## Prepare for Deployment + +On your local machine or in Google Cloud Shell, perform the following. + +1. Enable the required APIs, generate an SSH key, and clone the repository. + + ``` + gcloud services enable compute.googleapis.com + git clone https://github.com/PaloAltoNetworks/google-cloud-vmseries-ipv6-tutorial + cd google-cloud-vmseries-ipv6-tutorial + ``` + +2. Create an SSH key to assign to the GCE instances created. + + ``` + ssh-keygen -f ~/.ssh/vmseries-tutorial -t rsa + ``` + +3. Create a `terraform.tfvars`. + + ``` + cp terraform.tfvars.example terraform.tfvars + ``` + +4. Edit the `terraform.tfvars` file and set values for the following variables: + + + | Key | Value | Default | + | ----------------------- | ------------------------------------------------------------------------------------ | ------------------------------ | + | `project_id` | The Project ID within Google Cloud. | `null` | + | `public_key_path` | The local path of the public key you previously created | `~/.ssh/vmseries-tutorial.pub` | + | `mgmt_allow_ips` | A list of IPv4 addresses that can have access to the VM-Series management interface. | `["0.0.0.0/0"]` | + | `create_test_vms` | Set to `false` if you do not want to create the test VMs. | `true` | + | `vmseries_image_name` | Set to the VM-Series image you want to deploy. | `vmseries-flex-bundle1-1102` | + +1. Save your `terraform.tfvars` file. + + +## Deployment +When no further changes are necessary, deploy the resources: + +1. Initialize and apply the Terraform plan. + + ``` + terraform init + terraform apply + ``` + +2. Enter `yes` to start the deployment. + +3. After the resources are created, Terraform displays the following message: + + ``` + Apply complete! + + Outputs: + + EXTLB_IPv4 = "1.2.3.4/32" + EXTLB_IPv6 = "2600:1900:4000:eba6:8000::/32" + SSH_INTERNAL_VM = "gcloud compute ssh paloalto@internal-vm --zone=us-central1-a" + SSH_EXTERNAL_VM = "gcloud compute ssh paloalto@external-vm --zone=us-central1-a" + VMSERIES_CLI = "ssh admin@1.1.1.1 -i ~/.ssh/vmseries-tutorial" + VMSERIES_GUI = "https://1.1.1.1" + ``` + +### Accessing the VM-Series firewall + +To access the VM-Series user interface, a password must be set for the `admin` user. + +> [!NOTE] +> It may take an additional 10 minutes for the VM-Series to be accessible. + +1. Use the `VMSERIES_CLI` output to access the VM-Series CLI. + + ``` + ssh admin@1.1.1.1 -i ~/.ssh/vmseries-tutorial + ``` + + +2. On the VM-Series, set a password for the `admin` username. + + ``` + configure + set mgt-config users admin password + ``` + +3. Commit the changes. + + ``` + commit + ``` + +5. Enter `exit` twice to terminate the session. + +6. In a browser, use the `VMSERIES_GUI` output to access the VM-Series. + + + +## Outbound IPv4/IPv6 Traffic Configuration + +In this step, retrieve the required network parameters and apply them to the VM-Series configuration. + +> [!TIP] +> DHCPv6 is available in PAN-OS 11.0 and eliminates the need to configure static IPv6 addresses. + +### Configure Interfaces + +Enable DHCPv4 and DHCPv6 on the VM-Series network interfaces to handle IPv4/IPv6 traffic. + +1. On the VM-Series, go to **Network → Zones**. Click **Add**. + +2. Create two zones: `untrust` & `trust`. + + + +3. Go to **Network → Interfaces → Ethernet**. + +4. Configure `ethernet1/1` (`untrust`) as follows: + + + + > In IPv4 tab, **check** `Automatically create default route`.
+ > In IPv6 tab, **check** `Accept Router Advertised Route` and **uncheck** `Enable Prefix Delegation`. + +5. Configure `ethernet1/2` (`trust`) as follows: + + + + > In IPv4 tab, **uncheck** `Automatically create default route`.
+ > In IPv6 tab, **uncheck** `Accept Router Advertised Route` and **uncheck** `Enable Prefix Delegation`. + +6. **Commit the changes.** + + +### Retrieve IPv6 Parameters + +Retrieve the default gateways for the untrust & trust subnets and the ULA for the trust VPC. + +1. On `ethernet1/1`, click **Dynamic-DHCPv6 Client**. + +2. Record the **Server** and **IPv6 Address (Non-Temporary)** addresses. + + + + > **Server** address is the IPv6 default gateway for the untrust network.
+ > **IPv6 Address** is the external IPv6 address assigned to the untrust interface. + +3. On `ethernet1/2`, click **Dynamic-DHCPv6 Client**. + +4. Record the **Server** address. + + + + > **Server** address is the IPv6 default gateway of the trust network. + +5. In to Google Cloud, go to **VPC Networks →** `trust-vpc`. + +6. Record the **VPC network ULA internal IPv6 range**. + + + + > The ULA covers all of the possible IPv6 prefixes within the trust VPC. + +### Configure Virtual Router + +On the VM-Series, create an IPv4 & IPv6 routes to correctly return traffic to the trust VPC. + +1. Go to **Network → Virtual Routers**. Select the `default` virtual router. + +2. Click **Static Routes → IPv4**. Click **+ Add**. + +3. Configure the IPv4 return route as follows: + + + +4. Click **Static Routes → IPv6**. Click **+ Add**. + +5. Configure the IPv6 return route as follows: + + + + | | IPv4 Route | IPv6 Route | + |--------------------|-------------------------------------|------------------------------| + | **Name** | `ipv4-trust` | `ipv6-trust` | + | **Destination** | `IPv4 CIDR of trust network` | `ULA range of trust VPC` | + | **Next Hop** | `IP Address` | `IPv6 Address` | + | **Next Hop Value** | `eth1/2 IPv4 gateway IP` | `eth1/2 IPv6 Server Address` | + +6. Click **OK**. + + + +### Configure IPv4/IPv6 NAT Policies for Outbound Traffic + +Create a NAT rule to translate trust VPC traffic to the external IPv4/v6 addresses attached to the untrust interface. + +1. Go to **Policies → NAT**. Click **Add**. + +2. Create a NAT policy to translate outbound IPv4 traffic. + + + +3. Create a NPTv6 NAT policy to translate outbound IPv6 traffic. + + + + >Set the **IPv6 Address (Non-Temporary)** IP on `eth1/1` as the translated address (use a `/96` prefix). + +### Create Security Policy + +For the purposes of this tutorial, create a security policy to allow `ping`, `ping6`, & `web-browsing`. + +>[!CAUTION] +>This tutorial does not provide guidance on security policy implementation. + + +1. Go to **Policies → Security**. Click **Add**. + +2. Configure the security policy to allow `ping`, `ping6`, & `web-browsing`. + + + +4. **Commit the changes**. + +5. In Cloud Shell, create default routes in the `trust-vpc` to steer IPv4/IPv6 traffic to the VM-Series trust interface for inspection. + + ``` + gcloud compute routes create ipv4-default \ + --network=trust-vpc \ + --destination-range=0.0.0.0/0 \ + --next-hop-instance=vmseries \ + --next-hop-instance-zone=us-central1-a + + gcloud beta compute routes create ipv6-default \ + --network=trust-vpc \ + --destination-range=::0/0 \ + --next-hop-instance=vmseries \ + --next-hop-instance-zone=us-central1-a + ``` + + + + + +### Test Outbound Internet Traffic + +Access the `internal-vm` in the trust network and generate outbound IPv4/IPv6 internet traffic. + +1. In Cloud Shell, SSH to the `internal-vm`. + + ``` + gcloud compute ssh paloalto@internal-vm --zone=us-central1-a + ``` + +2. Ping an external IPv4 address to test outbound IPv4 traffic. + + ``` + ping 8.8.8.8 + ``` + +3. Ping an external IPv6 address to test outbound IPv6 traffic. + + ``` + ping6 2600:: + ``` + +4. On the VM-Series, go to **Monitor → Traffic**. Enter the filter below to search for the outbound traffic. + + ``` + ( app eq 'ping6' ) or ( app eq 'ping' ) + ``` + + + + >You should see that IPv4 & IPv6 traffic from the `internal-vm` is translated correctly by the VM-Series. + + + +## Inbound IPv4/IPv6 Traffic Configuration +In this section, you will configure the VM-Series to translate inbound internet traffic, which is distributed by an external pass-through load balancer, to reach the a web application running on the `internal-vm` in the trust VPC. + +>[!NOTE] +>The Terraform plan creates an external load balancer and health check for you. + + +### Configure Health Checks +Setup a loopback interface to receive the load balancer's IPv4/IPv6 health checks. Then, create a NAT policy to translate IPv4 health checks to the IPv4 loopback address and create a security policy to allow the health checks. + +#### Configure loopback interface + +1. In Google Cloud, go to **Network Services → Load Balancers**. + +2. Click the `vmseries-extlb` load balancer. Record the IPv6 address assigned to the forwarding rule. + + + +3. On the VM-Series, go to **Network → Zones**. Click **Add**. + +4. Create a zone called `lb-checks`. + + + +5. Go to **Network → Network Profiles → Interface Mgmt**. click **Add**. + +6. Enable `HTTP` and add the [Health Check Ranges](https://cloud.google.com/load-balancing/docs/health-checks#fw-netlb) (`35.191.0.0/16`, `209.85.152.0/22`, `209.85.204.0/22`, `2600:1901:8001::/48`) as permitted addresses. + + + +7. Go to **Network → Interfaces → Loopback**. Click **Add**. + +8. In the **Config Tab**, set tunnel to `1`, **Virtual Router** to `default`, & **Zone** to `lb-checks`. + + + +9. In the **IPv4 Tab**, set `100.64.0.1/32` as the address. + + + +10. In the **IPv6 Tab**, set load balancer's IPv6 forwarding rule address. + + + +11. In the **Advanced Tab**, set the **Management Profile** to `lb-checks` + + + + +#### Create NAT for IPv4 Health Checks + +1. Go to **Policies → NAT**. Click **Add**. + +2. Configure the policy to translate the IPv4 health check ranges to the IPv4 loopback address. + + + +#### Create Security Policy for IPv4/IPv6 Health Checks + +1. Go to **Policies → Security**. Click **Add**. + +2. Configure the policy to allow IPv4 & IPv6 health check ranges to the `lb-checks` zone. + + + +> [!Important] +> Move the policy to the top of the rule set before committing the changes. + +3. **Commit the changes.** + +4. In Google Cloud, verify the health checks are up on the `vmseries-extlb`. + + + + +### Configure NAT Policy for IPv4 Forwarding Rule + +Create a NAT policy to translate traffic destined to the IPv4 forwarding rule to a web app on the `internal-vm` in the trust VPC. + +1. In Google Cloud, record IPv4 & IPv6 addresses of the `internal-vm`. + + + +2. On the VM-Series, go to **Policies → NAT**. Click **Add**. + +3. Configure the policy to translate the IPv4 forwarding rule to the `internal-vm` IPv4 address. + + + + | NAT Policy | | | + |------------------------|-----------------------|-----------------------------------------------| + | **Original Packet** | Source Zone | `untrust` | + | | Destination Zone | `untrust` | + | | Destination Interface | `ethernet1/1` | + | | Destination Address | `34.29.169.107` (IPv4 fowarding rule address) | + | **Source Translation** | Translation Type | `Dynamic IP and Port` | + | | Address Type | `Interface Address` | + | | Interface | `ethernet1/2` | + | **DST Translation** | Translation Type | `Dynamic IP` | + | | Translated Address | `10.0.3.10` (IPv4 of `internal-vm`) | + + +> [!IMPORTANT] +> When load balancing internet inbound traffic through multiple firewalls, source translation is necessary to ensure a synchronous response from the backend application. + + + +### Configure NPTv6 Policy for IPv6 Forwarding Rule +Create an NPTv6 policy to translate traffic destined to the IPv6 forwarding rule to the web app on `internal-vm`. + +> [!NOTE] +> NPTv6 performs stateless translation, moving traffic from one IPv6 prefix to another by eliminating the IPv6 header checksum. +> Therefore, a checksum-neutral address must be calculated and used as the original packet's destination in the NPTv6 policy. + +#### Generate Checksum Neutral Address on VM-Series + +1. In Cloud Shell, SSH to the VM-Series using its management IP. + + ``` + ssh admin@1.1.1.1 + ``` + +2. Use the `test nptv6` command to generate the checksum for traffic between the IPv6 address of the `internal-vm` and the IPv6 forwarding rule address on the load balancer. + + ``` + test nptv6 cks-neutral source-ip fd20:eb0:af94:0:0:0:0:0 dest-network 2600:1900:4000:5db5:8000:1:0:0/96 + ``` + + > Replace `fd20:eb0:af94:0:0:0:0:0` with the IPv6 address of your internal-vm and replace `2600:1900:4000:5db5:8000:1:0:0/96` with the IPv6 address assigned to your load balancer's forwarding rule. + + +3. Record the generated checksum neutral address. + + **(Output)** +
+    The checksum neutral address of fd20:eb0:af94:: is 2600:1900:4000:5db5:8000:1:5eae:0 in 2600:1900:4000:5db5:8000:1:0:0/96 subnet
+    
+ + +#### Create NPTv6 Policy + +1. On the VM-Series, go to **Policies → NAT**. Click **Add**. + +2. Set **NAT Type** to `nptv6`. + +2. Configure the policy to translate the checksum IP to the `internal-vm` IPv6 address. + + + + | NPTv6 Policy | | | + |------------------------|-----------------------|----------------------------------------------------------------| + | **Original Packet** | Source Zone | `untrust` | + | | Destination Zone | `untrust` | + | | Destination Interface | `ethernet1/1` | + | | Destination Address | `2600:1900:4000:5db5:8000:1:5eae:0` (checksum neutral address) | + | **DST Translation** | Translation Type | `Dynamic IP` | + | | Translated Address | `fd20:eb0:af94:0:0:0:0:0/96` (IPv6 of `internal-vm`) | + + + + + +### Test Inbound Internet Traffic + +Access the `external-vm` to test internet inbound traffic through the IPv4/IPv6 external load balancer to the web application on `internal-vm`. + +1. In Cloud Shell, SSH to the external VM. + + ``` + gcloud compute ssh paloalto@external-vm --zone=us-central1-a + ``` + +2. Attempt to reach the web application using the load balancer's IPv4 address. + + ``` + curl http://34.29.169.107:80/?[1-3] + ``` + +3. Attempt to reach the web application using the **checksum neutral** IPv6 address. + + ``` + curl -6 'http://[2600:1900:4000:5db5:8000:1:5eae:0]:80/?[1-3]' + ``` + +4. On the VM-Series, go to **Monitor → Traffic**. Enter the filter below to search for the inbound traffic. + + ``` + ( zone.src eq 'untrust' ) and ( zone.dst eq 'trust' ) and ( app eq 'web-browsing' ) + ``` + + + + > You should see that both IPv4 and IPv6 traffic is inspected and translated correctly by the VM-Series firewall. + + +## Clean up + +1. To delete the created resources, run the commands below. + + ``` + gcloud compute routes delete ipv4-default -q + gcloud compute routes delete ipv6-default -q + terraform destroy + ``` + +2. At the prompt to perform the actions, enter `yes`. + + After all the resources are deleted, Terraform displays the following message: + + ``` + Destroy complete! + ``` + +## Additional information + +* Learn about the[ VM-Series on Google Cloud](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/set-up-the-vm-series-firewall-on-google-cloud-platform/about-the-vm-series-firewall-on-google-cloud-platform). +* Getting started with [Palo Alto Networks PAN-OS](https://docs.paloaltonetworks.com/pan-os). +* Read about [securing Google Cloud Networks with the VM-Series](https://cloud.google.com/architecture/partners/palo-alto-networks-ngfw). +* Learn about [VM-Series licensing on all platforms](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/license-the-vm-series-firewall/vm-series-firewall-licensing.html#id8fea514c-0d85-457f-b53c-d6d6193df07c). +* Use the [VM-Series Terraform modules for Google Cloud](https://registry.terraform.io/modules/PaloAltoNetworks/vmseries-modules/google/latest). diff --git a/images/diagram.png b/images/diagram.png new file mode 100644 index 0000000..52eae63 Binary files /dev/null and b/images/diagram.png differ diff --git a/images/image1.png b/images/image1.png new file mode 100644 index 0000000..6d6a4f4 Binary files /dev/null and b/images/image1.png differ diff --git a/images/image10.png b/images/image10.png new file mode 100644 index 0000000..96400a4 Binary files /dev/null and b/images/image10.png differ diff --git a/images/image11.png b/images/image11.png new file mode 100644 index 0000000..a89df96 Binary files /dev/null and b/images/image11.png differ diff --git a/images/image15.png b/images/image15.png new file mode 100644 index 0000000..fc2e046 Binary files /dev/null and b/images/image15.png differ diff --git a/images/image17.png b/images/image17.png new file mode 100644 index 0000000..5611451 Binary files /dev/null and b/images/image17.png differ diff --git a/images/image18.png b/images/image18.png new file mode 100644 index 0000000..656f28d Binary files /dev/null and b/images/image18.png differ diff --git a/images/image19.png b/images/image19.png new file mode 100644 index 0000000..b560dad Binary files /dev/null and b/images/image19.png differ diff --git a/images/image2.png b/images/image2.png new file mode 100644 index 0000000..22aaa4f Binary files /dev/null and b/images/image2.png differ diff --git a/images/image20.png b/images/image20.png new file mode 100644 index 0000000..8e43933 Binary files /dev/null and b/images/image20.png differ diff --git a/images/image21.png b/images/image21.png new file mode 100644 index 0000000..e6168f5 Binary files /dev/null and b/images/image21.png differ diff --git a/images/image22.png b/images/image22.png new file mode 100644 index 0000000..6977050 Binary files /dev/null and b/images/image22.png differ diff --git a/images/image23.png b/images/image23.png new file mode 100644 index 0000000..d7b2390 Binary files /dev/null and b/images/image23.png differ diff --git a/images/image24.png b/images/image24.png new file mode 100644 index 0000000..dbf2479 Binary files /dev/null and b/images/image24.png differ diff --git a/images/image25.png b/images/image25.png new file mode 100644 index 0000000..f97dfdf Binary files /dev/null and b/images/image25.png differ diff --git a/images/image26.png b/images/image26.png new file mode 100644 index 0000000..d03603f Binary files /dev/null and b/images/image26.png differ diff --git a/images/image27.png b/images/image27.png new file mode 100644 index 0000000..1b9df1e Binary files /dev/null and b/images/image27.png differ diff --git a/images/image28.png b/images/image28.png new file mode 100644 index 0000000..8d05502 Binary files /dev/null and b/images/image28.png differ diff --git a/images/image29.png b/images/image29.png new file mode 100644 index 0000000..cad2853 Binary files /dev/null and b/images/image29.png differ diff --git a/images/image3.png b/images/image3.png new file mode 100644 index 0000000..8d6a3d3 Binary files /dev/null and b/images/image3.png differ diff --git a/images/image30.png b/images/image30.png new file mode 100644 index 0000000..cc7a2a2 Binary files /dev/null and b/images/image30.png differ diff --git a/images/image4.png b/images/image4.png new file mode 100644 index 0000000..df8e31e Binary files /dev/null and b/images/image4.png differ diff --git a/images/image5.png b/images/image5.png new file mode 100644 index 0000000..5478603 Binary files /dev/null and b/images/image5.png differ diff --git a/images/image6.png b/images/image6.png new file mode 100644 index 0000000..396f36e Binary files /dev/null and b/images/image6.png differ diff --git a/images/image7.png b/images/image7.png new file mode 100644 index 0000000..0f2bb7f Binary files /dev/null and b/images/image7.png differ diff --git a/images/image8.png b/images/image8.png new file mode 100644 index 0000000..37ad221 Binary files /dev/null and b/images/image8.png differ diff --git a/images/image9.png b/images/image9.png new file mode 100644 index 0000000..17c4f10 Binary files /dev/null and b/images/image9.png differ diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..abfac1e --- /dev/null +++ b/main.tf @@ -0,0 +1,362 @@ +# -------------------------------------------------------------------------------------------- +# Provider configuration +# -------------------------------------------------------------------------------------------- + +terraform {} + +provider "google" { + project = local.project_id + region = local.region +} + +data "google_compute_zones" "main" {} + +# -------------------------------------------------------------------------------------------- +# Local variables +# -------------------------------------------------------------------------------------------- + +locals { + project_id = var.project_id + region = var.region + vmseries_image = var.vmseries_image + public_key_path = var.public_key_path + mgmt_allow_ips = var.mgmt_allow_ips + prefix = var.prefix + subnet_cidr_mgmt = var.subnet_cidr_mgmt + subnet_cidr_untrust = var.subnet_cidr_untrust + subnet_cidr_untrust_lb = var.subnet_cidr_untrust_lb + subnet_cidr_trust = var.subnet_cidr_trust + subnet_cidr_web = var.subnet_cidr_web + subnet_cidr_external = var.subnet_cidr_external + create_test_vms = var.create_test_vms +} + +# -------------------------------------------------------------------------------------------- +# Create VPC networks +# -------------------------------------------------------------------------------------------- + +resource "google_compute_network" "mgmt" { + name = "${local.prefix}mgmt-vpc" + routing_mode = "GLOBAL" + auto_create_subnetworks = false + enable_ula_internal_ipv6 = true +} + + +resource "google_compute_network" "untrust" { + name = "${local.prefix}untrust-vpc" + routing_mode = "GLOBAL" + auto_create_subnetworks = false + enable_ula_internal_ipv6 = true +} + + +resource "google_compute_network" "trust" { + name = "${local.prefix}trust-vpc" + routing_mode = "GLOBAL" + auto_create_subnetworks = false + enable_ula_internal_ipv6 = true + delete_default_routes_on_create = true +} + +# -------------------------------------------------------------------------------------------- +# Create subnets +# -------------------------------------------------------------------------------------------- + +resource "google_compute_subnetwork" "mgmt" { + name = "${local.prefix}mgmt-subnet" + ip_cidr_range = local.subnet_cidr_mgmt + region = local.region + stack_type = "IPV4_IPV6" + ipv6_access_type = "EXTERNAL" + network = google_compute_network.mgmt.id +} + + +resource "google_compute_subnetwork" "untrust" { + name = "${local.prefix}untrust-subnet" + ip_cidr_range = local.subnet_cidr_untrust + region = local.region + stack_type = "IPV4_IPV6" + ipv6_access_type = "EXTERNAL" + network = google_compute_network.untrust.id +} + + +resource "google_compute_subnetwork" "untrust_lb" { + name = "${local.prefix}untrust-lb-subnet" + ip_cidr_range = local.subnet_cidr_untrust_lb + region = local.region + stack_type = "IPV4_IPV6" + ipv6_access_type = "EXTERNAL" + network = google_compute_network.untrust.id +} + + +resource "google_compute_subnetwork" "trust" { + name = "${local.prefix}trust-subnet" + ip_cidr_range = local.subnet_cidr_trust + region = local.region + stack_type = "IPV4_IPV6" + ipv6_access_type = "INTERNAL" + network = google_compute_network.trust.id +} + +resource "google_compute_subnetwork" "web" { + name = "${local.prefix}web-subnet" + ip_cidr_range = local.subnet_cidr_web + region = local.region + stack_type = "IPV4_IPV6" + ipv6_access_type = "INTERNAL" + network = google_compute_network.trust.id +} + +# -------------------------------------------------------------------------------------------- +# Create ingress firewall rules +# -------------------------------------------------------------------------------------------- + +resource "google_compute_firewall" "mgmt_ipv4" { + name = "${local.prefix}all-ingress-mgmt" + network = google_compute_network.mgmt.id + source_ranges = var.mgmt_allow_ips + + allow { + protocol = "tcp" + ports = ["443", "22"] + } +} + + +resource "google_compute_firewall" "untrust_ipv4" { + name = "${local.prefix}all-ingress-untrust" + network = google_compute_network.untrust.id + source_ranges = ["0.0.0.0/0"] + + allow { + protocol = "all" + } +} + + +resource "google_compute_firewall" "untrust_ipv6" { + name = "${local.prefix}all-ingress-untrust-ipv6" + network = google_compute_network.untrust.id + source_ranges = ["::/0"] + direction = "INGRESS" + + allow { + protocol = "all" + } +} + + +resource "google_compute_firewall" "trust_ipv4" { + name = "${local.prefix}all-ingress-trust" + network = google_compute_network.trust.id + source_ranges = ["0.0.0.0/0"] + + allow { + protocol = "all" + } +} + + +resource "google_compute_firewall" "trust_ipv6" { + name = "${local.prefix}all-ingress-trust-ipv6" + network = google_compute_network.trust.id + source_ranges = ["::/0"] + direction = "INGRESS" + + allow { + protocol = "all" + } +} + + +# -------------------------------------------------------------------------------------------- +# Create VM-Series +# -------------------------------------------------------------------------------------------- + +# Service account for bootstrapping +module "iam_service_account" { + source = "github.com/PaloAltoNetworks/terraform-google-vmseries-modules//modules/iam_service_account?ref=main" + service_account_id = "${local.prefix}vmseries-mig-sa" + project_id = local.project_id +} + + +# # Create the bootstrap storage bucket. +# module "bootstrap" { +# source = "PaloAltoNetworks/vmseries-modules/google//modules/bootstrap" +# service_account = module.iam_service_account.email +# location = "US" +# files = { +# "bootstrap_files/init-cfg.txt" = "config/init-cfg.txt" +# "bootstrap_files/bootstrap.xml" = "config/bootstrap.xml" +# "bootstrap_files/authcodes" = "license/authcodes" +# } +# } + +# Create VM-Series +resource "google_compute_instance" "vmseries" { + name = "${local.prefix}vmseries" + zone = data.google_compute_zones.main.names[0] + machine_type = "n2-standard-4" + can_ip_forward = true + allow_stopping_for_update = true + + metadata = { + mgmt-interface-swap = "enable" + # vmseries-bootstrap-gce-storagebucket = module.bootstrap.bucket_name + serial-port-enable = true + ssh-keys = "admin:${file(local.public_key_path)}" + } + + network_interface { + subnetwork = google_compute_subnetwork.untrust.id + stack_type = "IPV4_IPV6" + access_config { + network_tier = "PREMIUM" + } + ipv6_access_config { + network_tier = "PREMIUM" + } + } + + network_interface { + subnetwork = google_compute_subnetwork.mgmt.id + stack_type = "IPV4_IPV6" + access_config { + network_tier = "PREMIUM" + } + ipv6_access_config { + network_tier = "PREMIUM" + } + } + + network_interface { + subnetwork = google_compute_subnetwork.trust.id + stack_type = "IPV4_IPV6" + } + + boot_disk { + initialize_params { + image = local.vmseries_image + type = "pd-standard" + } + } + + service_account { + email = module.iam_service_account.email + scopes = [ + "https://www.googleapis.com/auth/compute.readonly", + "https://www.googleapis.com/auth/cloud.useraccounts.readonly", + "https://www.googleapis.com/auth/devstorage.read_only", + "https://www.googleapis.com/auth/logging.write", + "https://www.googleapis.com/auth/monitoring.write" + ] + } + + depends_on = [ + # module.bootstrap, + module.iam_service_account + ] +} + + +# Create instance group +resource "google_compute_instance_group" "vmseries" { + name = "${local.prefix}vmseries" + zone = data.google_compute_zones.main.names[0] + + instances = [ + google_compute_instance.vmseries.id + ] +} + + +# -------------------------------------------------------------------------------------------- +# Create an external load balancer to distribute traffic to VM-Series trust interfaces. +# -------------------------------------------------------------------------------------------- + +resource "google_compute_region_health_check" "extlb" { + name = "${local.prefix}vmseries-extlb-hc" + project = var.project_id + region = var.region + check_interval_sec = 3 + healthy_threshold = 1 + timeout_sec = 1 + unhealthy_threshold = 1 + + http_health_check { + port = 80 + request_path = "/php/login.php" + } +} + + +resource "google_compute_address" "extlb_ipv4" { + name = "${local.prefix}vmseries-extlb-pip-ipv4" + region = local.region + network_tier = "PREMIUM" + address_type = "EXTERNAL" +} + + +resource "google_compute_address" "extlb_ipv6" { + name = "${local.prefix}vmseries-extlb-pip-ipv6" + region = local.region + network_tier = "PREMIUM" + address_type = "EXTERNAL" + ip_version = "IPV6" + ipv6_endpoint_type = "NETLB" + subnetwork = google_compute_subnetwork.untrust_lb.id +} + + +resource "google_compute_forwarding_rule" "extlb_ipv4" { + name = "${local.prefix}vmseries-extlb-rule-ipv4" + project = var.project_id + region = var.region + network_tier = "PREMIUM" + load_balancing_scheme = "EXTERNAL" + ip_protocol = "L3_DEFAULT" + all_ports = true + backend_service = google_compute_region_backend_service.extlb.self_link + ip_address = google_compute_address.extlb_ipv4.address +} + + +resource "google_compute_forwarding_rule" "extlb_ipv6" { + name = "${local.prefix}vmseries-extlb-rule-ipv6" + project = var.project_id + region = var.region + network_tier = "PREMIUM" + load_balancing_scheme = "EXTERNAL" + ip_protocol = "L3_DEFAULT" + all_ports = true + backend_service = google_compute_region_backend_service.extlb.self_link + ip_address = google_compute_address.extlb_ipv6.id + ip_version = "IPV6" + subnetwork = google_compute_subnetwork.untrust_lb.id +} + + +resource "google_compute_region_backend_service" "extlb" { + provider = google-beta + name = "${local.prefix}vmseries-extlb" + project = var.project_id + region = var.region + load_balancing_scheme = "EXTERNAL" + health_checks = [google_compute_region_health_check.extlb.self_link] + protocol = "UNSPECIFIED" + + connection_tracking_policy { + tracking_mode = "PER_SESSION" + connection_persistence_on_unhealthy_backends = "NEVER_PERSIST" + } + + backend { + group = google_compute_instance_group.vmseries.self_link + } +} diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..54191a0 --- /dev/null +++ b/outputs.tf @@ -0,0 +1,66 @@ + +# -------------------------------------------------------------------------------------------- +# Outputs +# -------------------------------------------------------------------------------------------- + +output "EXTLB_IPv4" { + value = "${google_compute_address.extlb_ipv4.address}/32" +} + +output "EXTLB_IPv6" { + value = "${google_compute_address.extlb_ipv6.address}/32" +} + +output "VMSERIES_CLI" { + value = "ssh admin@${google_compute_instance.vmseries.network_interface.1.access_config.0.nat_ip} -i ${trim(local.public_key_path, ".pub")}" +} + +output "VMSERIES_GUI" { + value = "https://${google_compute_instance.vmseries.network_interface.1.access_config.0.nat_ip}" +} + +output "SSH_INTERNAL_VM" { + value = (var.create_test_vms == true ? "gcloud compute ssh paloalto@${google_compute_instance.internal_vm.0.name} --zone=${data.google_compute_zones.main.names[0]}" : "") +} +output "SSH_EXTERNAL_VM" { + value = (var.create_test_vms == true ? "gcloud compute ssh paloalto@${google_compute_instance.external_vm.0.name} --zone=${data.google_compute_zones.main.names[0]}" : "") +} + +# output "vmseries_untrust_ipv6" { +# value = google_compute_instance.vmseries.network_interface.0.ipv6_access_config.0.external_ipv6 +# } + +# data "google_compute_network" "trust" { +# name = "${local.prefix}trust-vpc" +# } + + + + + + + +# data google_compute_instance "vmseries" { +# name = "vmseries" +# zone = local.zone +# } +# output "test2" { +# value = data.google_compute_instance.vmseries +# # sensitive = true +# } + +# output "test3" { +# value = google_compute_instance.vmseries.network_interface.0.addresses[0].address_v6 +# } +# VM IP : fd20:d42:dc76:0:0:0:0:0 + +# FW IP : 2600:1900:4000:eba6:0:0:0:0 | 2600:1900:4000:eba6::7c32:0 | test nptv6 cks-neutral source-ip fd20:d42:dc76:0:0:0:0:0 dest-network 2600:1900:4000:eba6:0:0:0:0/96 +# LB IP : 2600:1900:4000:b1d3:8000:0:0:0 | 2600:1900:4000:b1d3:8000:0:3605:0 | test nptv6 cks-neutral source-ip fd20:d42:dc76:0:0:0:0:0 dest-network 2600:1900:4000:b1d3:8000:0:0:0/96 + +# FW +## curl -6 'http://[2600:1900:4000:eba6:0:0:0:0]:80/' +## curl -6 'http://[2600:1900:4000:eba6::7c32:0]:80/' + +# LB +## curl -6 'http://[2600:1900:4000:b1d3:8000:0:0:0]:80/' +## curl -6 'http://[2600:1900:4000:b1d3:8000:0:3605:0]:80/' diff --git a/terraform.tfvars.example b/terraform.tfvars.example new file mode 100644 index 0000000..0a2546e --- /dev/null +++ b/terraform.tfvars.example @@ -0,0 +1,15 @@ +# Required for VM-Series tutorial +project_id = null +public_key_path = "~/.ssh/vmseries-tutorial.pub" +mgmt_allow_ips = ["0.0.0.0/0"] +create_test_vms = true +vmseries_image = "https://www.googleapis.com/compute/v1/projects/paloaltonetworksgcp-public/global/images/vmseries-flex-bundle1-1104h1" // View public images: `gcloud compute images list --project paloaltonetworksgcp-public --no-standard-images --uri` + +# Optional for VM-Series tutorial +prefix = "" +region = "us-central1" +subnet_cidr_mgmt = "10.0.0.0/28" +subnet_cidr_untrust = "10.0.1.0/28" +subnet_cidr_untrust_lb = "10.0.1.16/28" +subnet_cidr_trust = "10.0.2.0/28" +subnet_cidr_external = "192.168.0.0/28" \ No newline at end of file diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..221a2e9 --- /dev/null +++ b/variables.tf @@ -0,0 +1,77 @@ +variable "project_id" { + description = "GCP Project ID" + type = string +} + +variable "public_key_path" { + description = "Local path to public SSH key. To generate the key pair use `ssh-keygen -t rsa -C admin -N '' -f id_rsa` If you do not have a public key, run `ssh-keygen -f ~/.ssh/demo-key -t rsa -C admin`" + type = string +} + +variable "mgmt_allow_ips" { + description = "A list of IP addresses to be added to the management network's ingress firewall rule. The IP addresses will be able to access to the VM-Series management interface." + type = list(string) +} + +variable "create_test_vms" { + description = "If set to true, test workloads will be deployed to test IPv6 and IPv4 traffic." + type = bool +} + +variable "vmseries_image" { + description = "Name of the VM-Series image within the paloaltonetworksgcp-public project. To list available images, run: `gcloud compute images list --project paloaltonetworksgcp-public --no-standard-images`. If you are using a custom image in a different project, please update `local.vmseries_iamge_url` in `main.tf`." + type = string +} + + + +variable "region" { + description = "GCP Region" + default = "us-central1" + type = string +} + +variable "prefix" { + description = "Prefix to GCP resource names, an arbitrary string" + default = "" + type = string +} + +variable "subnet_cidr_mgmt" { + description = "IPv4 CIDR for the VM-Series mgmt subnetwork." + default = "10.0.0.0/28" + type = string +} + +variable "subnet_cidr_untrust" { + description = "IPv4 CIDR for the VM-Series untrust subnetwork." + default = "10.0.1.0/28" + type = string +} + +variable "subnet_cidr_untrust_lb" { + description = "IPv4 CIDR for the external load balancer subnetwork." + default = "10.0.1.16/28" + type = string +} + +variable "subnet_cidr_trust" { + description = "IPv4 CIDR for the VM-Series trust subnetwork." + default = "10.0.2.0/28" + type = string +} + +variable "subnet_cidr_web" { + description = "IPv4 CIDR for the external network for ingress testing." + default = "10.0.3.0/28" + type = string +} + + +variable "subnet_cidr_external" { + description = "IPv4 CIDR for the external network for ingress testing." + default = "192.168.0.0/28" + type = string +} + + diff --git a/vms.tf b/vms.tf new file mode 100644 index 0000000..72bee41 --- /dev/null +++ b/vms.tf @@ -0,0 +1,111 @@ +# -------------------------------------------------------------------------------------------- +# Create external VPC & subnet +# -------------------------------------------------------------------------------------------- + +resource "google_compute_network" "external" { + count = (local.create_test_vms ? 1 : 0) + name = "${local.prefix}external-vpc" + routing_mode = "GLOBAL" + auto_create_subnetworks = false + enable_ula_internal_ipv6 = true +} + +resource "google_compute_subnetwork" "external" { + count = (local.create_test_vms ? 1 : 0) + name = "${local.prefix}external-subnet" + ip_cidr_range = local.subnet_cidr_external + region = local.region + stack_type = "IPV4_IPV6" + ipv6_access_type = "EXTERNAL" + network = google_compute_network.external[0].id +} + +# -------------------------------------------------------------------------------------------- +# Create ingress firewall rules +# -------------------------------------------------------------------------------------------- + +resource "google_compute_firewall" "external_ipv4" { + count = (local.create_test_vms ? 1 : 0) + name = "${local.prefix}all-ingress-external" + network = google_compute_network.external[0].id + source_ranges = ["0.0.0.0/0"] + + allow { + protocol = "all" + } +} + +resource "google_compute_firewall" "external_ipv6" { + count = (local.create_test_vms ? 1 : 0) + name = "${local.prefix}all-ingress-external-ipv6" + network = google_compute_network.external[0].id + source_ranges = ["::/0"] + direction = "INGRESS" + + allow { + protocol = "all" + } +} + +# -------------------------------------------------------------------------------------------- +# External VM +# -------------------------------------------------------------------------------------------- + +resource "google_compute_instance" "external_vm" { + count = (local.create_test_vms ? 1 : 0) + name = "${local.prefix}external-vm" + project = local.project_id + zone = data.google_compute_zones.main.names[0] + machine_type = "f1-micro" + allow_stopping_for_update = true + + metadata = { + serial-port-enable = true + } + + boot_disk { + initialize_params { + image = "https://www.googleapis.com/compute/v1/projects/panw-gcp-team-testing/global/images/ubuntu-2004-lts-apache" + } + } + + network_interface { + subnetwork = google_compute_subnetwork.external[0].id + stack_type = "IPV4_IPV6" + access_config { + network_tier = "PREMIUM" + } + ipv6_access_config { + network_tier = "PREMIUM" + } + } +} + +# -------------------------------------------------------------------------------------------- +# Internal VM +# -------------------------------------------------------------------------------------------- + +resource "google_compute_instance" "internal_vm" { + count = (local.create_test_vms ? 1 : 0) + name = "${local.prefix}internal-vm" + project = local.project_id + zone = data.google_compute_zones.main.names[0] + machine_type = "f1-micro" + allow_stopping_for_update = true + + metadata = { + serial-port-enable = true + } + + boot_disk { + initialize_params { + image = "https://www.googleapis.com/compute/v1/projects/panw-gcp-team-testing/global/images/ubuntu-2004-lts-jenkins" + } + } + + network_interface { + subnetwork = google_compute_subnetwork.web.id + network_ip = cidrhost(local.subnet_cidr_web, 10) + stack_type = "IPV4_IPV6" + } +} \ No newline at end of file