├── requirements.txt ├── .github ├── CODEOWNERS ├── workflows │ ├── lint_pr_title.yml │ ├── release_ci.yml │ ├── pr_ci.yml │ ├── hub_sync.yml │ ├── pre-commit-update.yml │ ├── plan-command.yml │ ├── apply-command.yml │ ├── idempotence-command.yml │ ├── validate-command.yml │ ├── help-command.yml │ ├── sca-command.yml │ └── chatops.yml ├── ISSUE_TEMPLATE │ ├── feature_request.yml │ └── bug_report.yml └── actions │ └── terratest │ └── action.yml ├── modules ├── vpc-peering │ ├── versions.tf │ ├── main_test.go │ ├── main.tf │ ├── variables.tf │ └── README.md ├── iam_service_account │ ├── outputs.tf │ ├── versions.tf │ ├── main_test.go │ ├── main.tf │ ├── variables.tf │ └── README.md ├── bootstrap │ ├── versions.tf │ ├── outputs.tf │ ├── main_test.go │ ├── variables.tf │ ├── main.tf │ └── README.md ├── lb_external │ ├── versions.tf │ ├── main_test.go │ ├── outputs.tf │ ├── variables.tf │ └── main.tf ├── lb_internal │ ├── versions.tf │ ├── outputs.tf │ ├── main_test.go │ ├── main.tf │ └── variables.tf ├── lb_http_ext_global │ ├── versions.tf │ ├── main_test.go │ ├── outputs.tf │ ├── howto_lb_region.md │ ├── main.tf │ └── variables.tf ├── vpc │ ├── versions.tf │ ├── main_test.go │ ├── outputs.tf │ ├── main.tf │ ├── variables.tf │ └── README.md ├── vpn │ ├── versions.tf │ ├── main_test.go │ ├── outputs.tf │ ├── variables.tf │ └── main.tf ├── autoscale │ ├── versions.tf │ ├── src │ │ └── requirements.txt │ ├── main_test.go │ └── outputs.tf ├── panorama │ ├── versions.tf │ ├── main_test.go │ ├── outputs.tf │ ├── main.tf │ └── variables.tf └── vmseries │ ├── versions.tf │ ├── main_test.go │ ├── outputs.tf │ └── main.tf ├── examples ├── panorama_standalone │ ├── versions.tf │ ├── outputs.tf │ ├── example.tfvars │ ├── main.tf │ ├── main_test.go │ ├── variables.tf │ └── README.md ├── standalone_vmseries_with_metadata_bootstrap │ ├── versions.tf │ ├── outputs.tf │ ├── main.tf │ ├── main_test.go │ ├── example.tfvars │ ├── variables.tf │ └── README.md ├── vpc_peering_common_with_network_tags │ ├── versions.tf │ ├── templates │ │ └── init-cfg.tmpl │ ├── outputs.tf │ └── main_test.go ├── vmseries_ha │ ├── versions.tf │ ├── templates │ │ └── init-cfg.tmpl │ ├── outputs.tf │ └── main_test.go ├── multi_nic_common │ ├── versions.tf │ ├── templates │ │ └── init-cfg.tmpl │ ├── outputs.tf │ └── main_test.go ├── vpc_peering_common │ ├── versions.tf │ ├── templates │ │ └── init-cfg.tmpl │ ├── outputs.tf │ └── main_test.go ├── vpc_peering_dedicated │ ├── versions.tf │ ├── templates │ │ └── init-cfg.tmpl │ ├── outputs.tf │ └── main_test.go ├── vpc_peering_common_with_autoscale │ ├── versions.tf │ ├── outputs.tf │ └── main_test.go └── vpc_peering_dedicated_with_autoscale │ ├── versions.tf │ ├── outputs.tf │ └── main_test.go ├── scripts ├── run.sh ├── install.sh ├── decisions.md └── requirements.txt ├── Makefile ├── SUPPORT.md ├── LICENSE ├── .gitignore ├── .pre-commit-config.yaml ├── .releaserc.json ├── go.mod ├── README.md └── CONTRIBUTING.md /requirements.txt: -------------------------------------------------------------------------------- 1 | pre-commit==3.4.0 -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @PaloAltoNetworks/gcp-vmseries-modules-codeowners 2 | -------------------------------------------------------------------------------- /modules/vpc-peering/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3, < 2.0" 3 | } -------------------------------------------------------------------------------- /modules/iam_service_account/outputs.tf: -------------------------------------------------------------------------------- 1 | output "email" { 2 | value = google_service_account.this.email 3 | } 4 | -------------------------------------------------------------------------------- /modules/bootstrap/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3, < 2.0" 3 | required_providers { 4 | google = { version = "~> 4.54" } 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /modules/iam_service_account/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3, < 2.0" 3 | required_providers { 4 | google = { version = "~> 4.54" } 5 | } 6 | } -------------------------------------------------------------------------------- /modules/lb_external/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3, < 2.0" 3 | required_providers { 4 | google = { version = "~> 4.54" } 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /modules/lb_internal/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3, < 2.0" 3 | required_providers { 4 | google = { version = "~> 4.54" } 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /modules/bootstrap/outputs.tf: -------------------------------------------------------------------------------- 1 | output "bucket_name" { 2 | value = google_storage_bucket.this.name 3 | } 4 | 5 | output "bucket" { 6 | value = google_storage_bucket.this 7 | } -------------------------------------------------------------------------------- /modules/lb_http_ext_global/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3, < 2.0" 3 | required_providers { 4 | google = { version = "~> 4.54" } 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /modules/vpc/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3, < 2.0" 3 | required_providers { 4 | google = { 5 | version = "~> 4.54" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /modules/vpn/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3, < 2.0" 3 | required_providers { 4 | google = { 5 | version = ">= 4.58" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/panorama_standalone/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3, < 2.0" 3 | } 4 | 5 | provider "google" { 6 | project = var.project 7 | region = var.region 8 | } -------------------------------------------------------------------------------- /modules/autoscale/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3, < 2.0" 3 | required_providers { 4 | google = { 5 | version = "~> 4.54" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /modules/panorama/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3, < 2.0" 3 | required_providers { 4 | google = { 5 | version = "~> 4.54" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/standalone_vmseries_with_metadata_bootstrap/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3, < 2.0" 3 | } 4 | 5 | provider "google" { 6 | project = var.project 7 | } 8 | -------------------------------------------------------------------------------- /modules/autoscale/src/requirements.txt: -------------------------------------------------------------------------------- 1 | # Function dependencies, for example: 2 | # package>=version 3 | google-api-python-client==2.86.0 4 | pan-os-python==1.11.0 5 | google-cloud-secret-manager==2.16.2 -------------------------------------------------------------------------------- /modules/vmseries/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3, < 2.0" 3 | required_providers { 4 | null = { version = "~> 3.1" } 5 | google = { version = "~> 4.54" } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /modules/lb_internal/outputs.tf: -------------------------------------------------------------------------------- 1 | output "forwarding_rule" { 2 | value = google_compute_forwarding_rule.this.self_link 3 | } 4 | 5 | output "address" { 6 | value = google_compute_forwarding_rule.this.ip_address 7 | } 8 | 9 | -------------------------------------------------------------------------------- /examples/vpc_peering_common_with_network_tags/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3, < 2.0" 3 | } 4 | 5 | provider "google" { 6 | project = var.project 7 | } 8 | 9 | provider "google-beta" { 10 | project = var.project 11 | } 12 | -------------------------------------------------------------------------------- /modules/vpc/main_test.go: -------------------------------------------------------------------------------- 1 | package vpc 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" 7 | ) 8 | 9 | func TestValidate(t *testing.T) { 10 | testskeleton.ValidateCode(t, nil) 11 | } -------------------------------------------------------------------------------- /modules/vpn/main_test.go: -------------------------------------------------------------------------------- 1 | package vpn 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" 7 | ) 8 | 9 | func TestValidate(t *testing.T) { 10 | testskeleton.ValidateCode(t, nil) 11 | } -------------------------------------------------------------------------------- /scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | # run.sh - Run the usual pre-commit checks. 4 | 5 | set -euo pipefail 6 | 7 | pre-commit run --all-files terraform_fmt 8 | pre-commit run --all-files terraform_docs 9 | pre-commit run --all-files terraform_tflint 10 | -------------------------------------------------------------------------------- /modules/panorama/main_test.go: -------------------------------------------------------------------------------- 1 | package panorama 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" 7 | ) 8 | 9 | func TestValidate(t *testing.T) { 10 | testskeleton.ValidateCode(t, nil) 11 | } -------------------------------------------------------------------------------- /modules/vmseries/main_test.go: -------------------------------------------------------------------------------- 1 | package vmseries 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" 7 | ) 8 | 9 | func TestValidate(t *testing.T) { 10 | testskeleton.ValidateCode(t, nil) 11 | } -------------------------------------------------------------------------------- /examples/vmseries_ha/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3, < 2.0" 3 | } 4 | 5 | provider "google" { 6 | project = var.project 7 | region = var.region 8 | } 9 | 10 | provider "google-beta" { 11 | project = var.project 12 | region = var.region 13 | } -------------------------------------------------------------------------------- /modules/autoscale/main_test.go: -------------------------------------------------------------------------------- 1 | package autoscale 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" 7 | ) 8 | 9 | func TestValidate(t *testing.T) { 10 | testskeleton.ValidateCode(t, nil) 11 | } -------------------------------------------------------------------------------- /modules/bootstrap/main_test.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" 7 | ) 8 | 9 | func TestValidate(t *testing.T) { 10 | testskeleton.ValidateCode(t, nil) 11 | } -------------------------------------------------------------------------------- /modules/lb_external/main_test.go: -------------------------------------------------------------------------------- 1 | package lb_external 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" 7 | ) 8 | 9 | func TestValidate(t *testing.T) { 10 | testskeleton.ValidateCode(t, nil) 11 | } -------------------------------------------------------------------------------- /modules/lb_internal/main_test.go: -------------------------------------------------------------------------------- 1 | package lb_internal 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" 7 | ) 8 | 9 | func TestValidate(t *testing.T) { 10 | testskeleton.ValidateCode(t, nil) 11 | } -------------------------------------------------------------------------------- /modules/vpc-peering/main_test.go: -------------------------------------------------------------------------------- 1 | package vpc_peering 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" 7 | ) 8 | 9 | func TestValidate(t *testing.T) { 10 | testskeleton.ValidateCode(t, nil) 11 | } -------------------------------------------------------------------------------- /examples/multi_nic_common/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3, < 2.0" 3 | } 4 | 5 | provider "google" { 6 | project = var.project 7 | region = var.region 8 | } 9 | 10 | provider "google-beta" { 11 | project = var.project 12 | region = var.region 13 | } 14 | -------------------------------------------------------------------------------- /examples/vpc_peering_common/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3, < 2.0" 3 | } 4 | 5 | provider "google" { 6 | project = var.project 7 | region = var.region 8 | } 9 | 10 | provider "google-beta" { 11 | project = var.project 12 | region = var.region 13 | } 14 | -------------------------------------------------------------------------------- /modules/lb_http_ext_global/main_test.go: -------------------------------------------------------------------------------- 1 | package lb_http_ext_global 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" 7 | ) 8 | 9 | func TestValidate(t *testing.T) { 10 | testskeleton.ValidateCode(t, nil) 11 | } -------------------------------------------------------------------------------- /examples/vpc_peering_dedicated/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3, < 2.0" 3 | } 4 | 5 | provider "google" { 6 | project = var.project 7 | region = var.region 8 | } 9 | 10 | provider "google-beta" { 11 | project = var.project 12 | region = var.region 13 | } 14 | -------------------------------------------------------------------------------- /modules/iam_service_account/main_test.go: -------------------------------------------------------------------------------- 1 | package iam_service_account 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" 7 | ) 8 | 9 | func TestValidate(t *testing.T) { 10 | testskeleton.ValidateCode(t, nil) 11 | } -------------------------------------------------------------------------------- /examples/vpc_peering_common_with_autoscale/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3, < 2.0" 3 | } 4 | 5 | provider "google" { 6 | project = var.project 7 | region = var.region 8 | } 9 | 10 | provider "google-beta" { 11 | project = var.project 12 | region = var.region 13 | } 14 | -------------------------------------------------------------------------------- /examples/vpc_peering_dedicated_with_autoscale/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3, < 2.0" 3 | } 4 | 5 | provider "google" { 6 | project = var.project 7 | region = var.region 8 | } 9 | 10 | provider "google-beta" { 11 | project = var.project 12 | region = var.region 13 | } 14 | -------------------------------------------------------------------------------- /examples/vmseries_ha/templates/init-cfg.tmpl: -------------------------------------------------------------------------------- 1 | %{ if panorama-server != "" ~} 2 | panorama-server=${panorama-server} 3 | %{ endif ~} 4 | %{ if type != "" ~} 5 | type=${type} 6 | %{ endif ~} 7 | %{ if dns-primary != "" ~} 8 | dns-primary=${dns-primary} 9 | %{ endif ~} 10 | %{ if dns-secondary != "" ~} 11 | dns-secondary=${dns-secondary} 12 | %{ endif ~} -------------------------------------------------------------------------------- /examples/multi_nic_common/templates/init-cfg.tmpl: -------------------------------------------------------------------------------- 1 | %{ if panorama-server != "" ~} 2 | panorama-server=${panorama-server} 3 | %{ endif ~} 4 | %{ if type != "" ~} 5 | type=${type} 6 | %{ endif ~} 7 | %{ if dns-primary != "" ~} 8 | dns-primary=${dns-primary} 9 | %{ endif ~} 10 | %{ if dns-secondary != "" ~} 11 | dns-secondary=${dns-secondary} 12 | %{ endif ~} -------------------------------------------------------------------------------- /examples/vpc_peering_common/templates/init-cfg.tmpl: -------------------------------------------------------------------------------- 1 | %{ if panorama-server != "" ~} 2 | panorama-server=${panorama-server} 3 | %{ endif ~} 4 | %{ if type != "" ~} 5 | type=${type} 6 | %{ endif ~} 7 | %{ if dns-primary != "" ~} 8 | dns-primary=${dns-primary} 9 | %{ endif ~} 10 | %{ if dns-secondary != "" ~} 11 | dns-secondary=${dns-secondary} 12 | %{ endif ~} -------------------------------------------------------------------------------- /examples/vpc_peering_dedicated/templates/init-cfg.tmpl: -------------------------------------------------------------------------------- 1 | %{ if panorama-server != "" ~} 2 | panorama-server=${panorama-server} 3 | %{ endif ~} 4 | %{ if type != "" ~} 5 | type=${type} 6 | %{ endif ~} 7 | %{ if dns-primary != "" ~} 8 | dns-primary=${dns-primary} 9 | %{ endif ~} 10 | %{ if dns-secondary != "" ~} 11 | dns-secondary=${dns-secondary} 12 | %{ endif ~} -------------------------------------------------------------------------------- /examples/vpc_peering_common_with_network_tags/templates/init-cfg.tmpl: -------------------------------------------------------------------------------- 1 | %{ if panorama-server != "" ~} 2 | panorama-server=${panorama-server} 3 | %{ endif ~} 4 | %{ if type != "" ~} 5 | type=${type} 6 | %{ endif ~} 7 | %{ if dns-primary != "" ~} 8 | dns-primary=${dns-primary} 9 | %{ endif ~} 10 | %{ if dns-secondary != "" ~} 11 | dns-secondary=${dns-secondary} 12 | %{ endif ~} -------------------------------------------------------------------------------- /examples/panorama_standalone/outputs.tf: -------------------------------------------------------------------------------- 1 | output "panorama_private_ips" { 2 | description = "Private IP address of the Panorama instance." 3 | value = { for k, v in module.panorama : k => v.panorama_private_ip } 4 | } 5 | 6 | output "panorama_public_ips" { 7 | description = "Public IP address of the Panorama instance." 8 | value = { for k, v in module.panorama : k => v.panorama_public_ip } 9 | } -------------------------------------------------------------------------------- /examples/standalone_vmseries_with_metadata_bootstrap/outputs.tf: -------------------------------------------------------------------------------- 1 | output "vmseries_private_ips" { 2 | description = "Private IP addresses of the vmseries instances." 3 | value = { for k, v in module.vmseries : k => v.private_ips } 4 | } 5 | 6 | output "vmseries_public_ips" { 7 | description = "Public IP addresses of the vmseries instances." 8 | value = { for k, v in module.vmseries : k => v.public_ips } 9 | } -------------------------------------------------------------------------------- /modules/iam_service_account/main.tf: -------------------------------------------------------------------------------- 1 | resource "google_service_account" "this" { 2 | account_id = var.service_account_id 3 | display_name = var.display_name 4 | project = var.project_id 5 | } 6 | 7 | resource "google_project_iam_member" "this" { 8 | for_each = var.roles 9 | 10 | project = var.project_id 11 | role = each.value 12 | member = "serviceAccount:${google_service_account.this.email}" 13 | } -------------------------------------------------------------------------------- /modules/panorama/outputs.tf: -------------------------------------------------------------------------------- 1 | output "panorama_public_ip" { 2 | description = "Private IP address of the Panorama instance." 3 | value = var.attach_public_ip ? google_compute_instance.this.network_interface[0].access_config[0].nat_ip : null 4 | } 5 | 6 | output "panorama_private_ip" { 7 | description = "Public IP address of the Panorama instance." 8 | value = google_compute_instance.this.network_interface[0].network_ip 9 | } 10 | -------------------------------------------------------------------------------- /modules/vpc/outputs.tf: -------------------------------------------------------------------------------- 1 | output "network" { 2 | description = "Created or read network attributes." 3 | value = try(data.google_compute_network.this[0], google_compute_network.this[0]) 4 | } 5 | 6 | output "subnetworks" { 7 | description = "Map containing key, value pairs of created or read subnetwork attributes." 8 | value = { for k, v in var.subnetworks : 9 | k => try(data.google_compute_subnetwork.this[k], google_compute_subnetwork.this[k], null) 10 | } 11 | } -------------------------------------------------------------------------------- /.github/workflows/lint_pr_title.yml: -------------------------------------------------------------------------------- 1 | # DESCRIPTION: 2 | # A workflow used to verify if PR titles matches conventional commits strategy. 3 | # END 4 | 5 | name: Lint PR Title 6 | run-name: "Lint PR - (#${{ github.event.number }}) ${{ github.event.pull_request.title }}" 7 | 8 | permissions: 9 | pull-requests: read 10 | 11 | on: 12 | pull_request_target: 13 | types: 14 | - opened 15 | - edited 16 | - ready_for_review 17 | 18 | jobs: 19 | lint_pr_title: 20 | name: Lint PR 21 | uses: PaloAltoNetworks/terraform-modules-vmseries-ci-workflows/.github/workflows/lint_pr_title.yml@v1.3.0 -------------------------------------------------------------------------------- /modules/lb_http_ext_global/outputs.tf: -------------------------------------------------------------------------------- 1 | output "address" { 2 | value = google_compute_global_address.default.address 3 | } 4 | 5 | output "all" { 6 | description = "Intended mainly for `depends_on` but currently succeeds prematurely (while forwarding rules and healtchecks are not yet usable)." 7 | value = { 8 | google_compute_global_forwarding_rule_http = google_compute_global_forwarding_rule.http 9 | google_compute_global_forwarding_rule_https = google_compute_global_forwarding_rule.https 10 | google_compute_health_check = google_compute_health_check.default 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /modules/vmseries/outputs.tf: -------------------------------------------------------------------------------- 1 | output "instance" { 2 | value = google_compute_instance.this 3 | } 4 | 5 | output "self_link" { 6 | value = google_compute_instance.this.self_link 7 | } 8 | 9 | output "instance_group" { 10 | value = try(google_compute_instance_group.this[0], null) 11 | } 12 | 13 | output "instance_group_self_link" { 14 | value = try(google_compute_instance_group.this[0].self_link, null) 15 | } 16 | 17 | output "private_ips" { 18 | value = { for k, v in google_compute_instance.this.network_interface : k => v.network_ip } 19 | } 20 | 21 | output "public_ips" { 22 | value = { for k, v in google_compute_instance.this.network_interface : k => v.access_config[0].nat_ip if length(v.access_config) != 0 } 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/release_ci.yml: -------------------------------------------------------------------------------- 1 | name: Release CI 2 | run-name: "Continous Release" 3 | 4 | 5 | permissions: 6 | contents: write 7 | issues: read 8 | id-token: write 9 | 10 | on: 11 | workflow_dispatch: 12 | schedule: 13 | - cron: '0 1 * * 4' # this means every Thursday @1am UTC 14 | 15 | concurrency: release 16 | 17 | jobs: 18 | release_wrkflw: 19 | name: Do release 20 | uses: PaloAltoNetworks/terraform-modules-vmseries-ci-workflows/.github/workflows/release_ci.yml@v2.2 21 | secrets: inherit 22 | with: 23 | cloud: gcp 24 | validate_max_parallel: 20 25 | test_max_parallel: 5 26 | fail_fast: false 27 | terratest_action: Idempotence # keep in mind that this has to start with capital letter -------------------------------------------------------------------------------- /modules/lb_http_ext_global/howto_lb_region.md: -------------------------------------------------------------------------------- 1 | # How to develop lb_http_ext_region 2 | 3 | Use replacement symbols for about everything: 4 | 5 | ``` 6 | google_compute_forwarding_rule 7 | google_compute_region_backend_service 8 | google_compute_region_target_http_proxy 9 | google_compute_region_target_https_proxy 10 | google_compute_region_ssl_certificate 11 | google_compute_region_url_map 12 | google_compute_region_health_check 13 | ``` 14 | 15 | Some attributes differ, e.g. for the `google_compute_region_backend_service` add: 16 | ``` 17 | backend { 18 | failover = lookup(backend.value, "failover", false) 19 | } 20 | ``` 21 | 22 | and remove: 23 | 24 | ``` 25 | security_policy = var.security_policy 26 | enable_cdn = var.cdn 27 | ``` 28 | -------------------------------------------------------------------------------- /modules/vpn/outputs.tf: -------------------------------------------------------------------------------- 1 | output "vpn_gw_name" { 2 | value = google_compute_ha_vpn_gateway.ha_gateway.name 3 | description = "HA VPN gateway name" 4 | } 5 | 6 | output "vpn_gw_self_link" { 7 | value = google_compute_ha_vpn_gateway.ha_gateway.self_link 8 | description = "HA VPN gateway self_link" 9 | } 10 | 11 | output "vpn_gw_local_address_1" { 12 | value = google_compute_ha_vpn_gateway.ha_gateway.vpn_interfaces[0].ip_address 13 | description = "HA VPN gateway IP address 1" 14 | } 15 | 16 | output "vpn_gw_local_address_2" { 17 | value = google_compute_ha_vpn_gateway.ha_gateway.vpn_interfaces[1].ip_address 18 | description = "HA VPN gateway IP address 2" 19 | } 20 | 21 | output "random_secret" { 22 | value = local.secret 23 | sensitive = true 24 | description = "HA VPN IPsec tunnels secret that has been randomly generated" 25 | } -------------------------------------------------------------------------------- /.github/workflows/pr_ci.yml: -------------------------------------------------------------------------------- 1 | name: PR CI 2 | run-name: "CI pipeline for PR - (#${{ github.event.number }}) ${{ github.event.pull_request.title }}" 3 | 4 | permissions: 5 | contents: read 6 | actions: read 7 | id-token: write 8 | 9 | on: 10 | pull_request: 11 | types: 12 | - opened 13 | - reopened 14 | - synchronize 15 | - ready_for_review 16 | branches: ['main'] 17 | 18 | jobs: 19 | pr_ci_wrkflw: 20 | name: Run CI 21 | uses: PaloAltoNetworks/terraform-modules-vmseries-ci-workflows/.github/workflows/pr_ci.yml@v2.2 22 | if: github.actor != 'dependabot[bot]' 23 | secrets: inherit 24 | with: 25 | cloud: gcp 26 | tf_version: 1.3 1.4 1.5 27 | validate_max_parallel: 20 28 | test_max_parallel: 10 29 | fail_fast: false 30 | terratest_action: Plan # keep in mind that this has to start with capital letter -------------------------------------------------------------------------------- /.github/workflows/hub_sync.yml: -------------------------------------------------------------------------------- 1 | name: Orchestrator Hub Sync System Workflow 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | workflow_dispatch: 8 | release: 9 | types: [released] 10 | 11 | jobs: 12 | hub_sync: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Generate GitHub token 16 | id: generate-token 17 | uses: tibdex/github-app-token@v1 18 | with: 19 | app_id: ${{ secrets.APP_ID }} 20 | private_key: ${{ secrets.APP_PRIVATE_KEY }} 21 | installation_id: ${{ secrets.APP_INSTALLATION_ID }} 22 | 23 | - name: Trigger Hub Sync Workflow 24 | uses: benc-uk/workflow-dispatch@v1 25 | with: 26 | workflow: run.yml 27 | repo: PaloAltoNetworks/automation-metadata-collector 28 | ref: main 29 | token: ${{ steps.generate-token.outputs.token }} 30 | inputs: '{"cloud-id": "gcp"}' 31 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .phony: all invalidate help 2 | 3 | all: 4 | @echo "Run [make help] for usage details." 5 | 6 | invalidate: 7 | 8 | help: 9 | @echo "This Makefile is run by specifying a module path as a target name." ; \ 10 | echo "It takes one argument: ACTION. Value of this argument is specific to a particular module." ; \ 11 | echo "It represents the name of a Terratest test function." ; \ 12 | echo "Typically this will be: Validate, Plan, Apply, Idempotence, but it should be verified with" ; \ 13 | echo " module's main_test.go file." ; \ 14 | echo ; \ 15 | echo "Example:" ; \ 16 | echo " make examples/common_vmseries ACTION=Plan" ; \ 17 | echo 18 | 19 | %: invalidate %/main.tf 20 | @cd $@ && \ 21 | echo "::group::DOWNLOADING GO DEPENDENCIES" && \ 22 | go get -v -t -d && \ 23 | go mod tidy && \ 24 | echo "::endgroup::" && \ 25 | echo "::group::ACTION >>$(ACTION)<<" && \ 26 | go test -run $(ACTION) -timeout 60m -count=1 && \ 27 | echo "::endgroup::" -------------------------------------------------------------------------------- /examples/vmseries_ha/outputs.tf: -------------------------------------------------------------------------------- 1 | output "vmseries_private_ips" { 2 | description = "Private IP addresses of the vmseries instances." 3 | value = { for k, v in module.vmseries : k => v.private_ips } 4 | } 5 | 6 | output "vmseries_public_ips" { 7 | description = "Public IP addresses of the vmseries instances." 8 | value = { for k, v in module.vmseries : k => v.public_ips } 9 | } 10 | 11 | output "lbs_internal_ips" { 12 | description = "Private IP addresses of internal network loadbalancers." 13 | value = { for k, v in module.lb_internal : k => v.address } 14 | } 15 | 16 | output "lbs_external_ips" { 17 | description = "Public IP addresses of external network loadbalancers." 18 | value = { for k, v in module.lb_external : k => v.ip_addresses } 19 | } 20 | 21 | output "linux_vm_ips" { 22 | description = "Private IP addresses of Linux VMs." 23 | value = { for k, v in resource.google_compute_instance.linux_vm : k => v.network_interface[0].network_ip } 24 | } 25 | -------------------------------------------------------------------------------- /examples/multi_nic_common/outputs.tf: -------------------------------------------------------------------------------- 1 | output "vmseries_private_ips" { 2 | description = "Private IP addresses of the vmseries instances." 3 | value = { for k, v in module.vmseries : k => v.private_ips } 4 | } 5 | 6 | output "vmseries_public_ips" { 7 | description = "Public IP addresses of the vmseries instances." 8 | value = { for k, v in module.vmseries : k => v.public_ips } 9 | } 10 | 11 | output "lbs_internal_ips" { 12 | description = "Private IP addresses of internal network loadbalancers." 13 | value = { for k, v in module.lb_internal : k => v.address } 14 | } 15 | 16 | output "lbs_external_ips" { 17 | description = "Public IP addresses of external network loadbalancers." 18 | value = { for k, v in module.lb_external : k => v.ip_addresses } 19 | } 20 | 21 | output "linux_vm_ips" { 22 | description = "Private IP addresses of Linux VMs." 23 | value = { for k, v in resource.google_compute_instance.linux_vm : k => v.network_interface[0].network_ip } 24 | } 25 | -------------------------------------------------------------------------------- /examples/vpc_peering_dedicated/outputs.tf: -------------------------------------------------------------------------------- 1 | output "vmseries_private_ips" { 2 | description = "Private IP addresses of the vmseries instances." 3 | value = { for k, v in module.vmseries : k => v.private_ips } 4 | } 5 | 6 | output "vmseries_public_ips" { 7 | description = "Public IP addresses of the vmseries instances." 8 | value = { for k, v in module.vmseries : k => v.public_ips } 9 | } 10 | 11 | output "lbs_internal_ips" { 12 | description = "Private IP addresses of internal network loadbalancers." 13 | value = { for k, v in module.lb_internal : k => v.address } 14 | } 15 | 16 | output "lbs_global_http" { 17 | description = "Public IP addresses of external Global HTTP(S) loadbalancers." 18 | value = { for k, v in module.glb : k => v.address } 19 | } 20 | 21 | output "linux_vm_ips" { 22 | description = "Private IP addresses of Linux VMs." 23 | value = { for k, v in resource.google_compute_instance.linux_vm : k => v.network_interface[0].network_ip } 24 | } 25 | -------------------------------------------------------------------------------- /examples/vpc_peering_common/outputs.tf: -------------------------------------------------------------------------------- 1 | output "vmseries_private_ips" { 2 | description = "Private IP addresses of the vmseries instances." 3 | value = { for k, v in module.vmseries : k => v.private_ips } 4 | } 5 | 6 | output "vmseries_public_ips" { 7 | description = "Public IP addresses of the vmseries instances." 8 | value = { for k, v in module.vmseries : k => v.public_ips } 9 | } 10 | 11 | output "lbs_internal_ips" { 12 | description = "Private IP addresses of internal network loadbalancers." 13 | value = { for k, v in module.lb_internal : k => v.address } 14 | } 15 | 16 | output "lbs_external_ips" { 17 | description = "Public IP addresses of external network loadbalancers." 18 | value = { for k, v in module.lb_external : k => v.ip_addresses } 19 | } 20 | 21 | output "linux_vm_ips" { 22 | description = "Private IP addresses of Linux VMs." 23 | value = { for k, v in resource.google_compute_instance.linux_vm : k => v.network_interface[0].network_ip } 24 | } 25 | -------------------------------------------------------------------------------- /examples/vpc_peering_common_with_network_tags/outputs.tf: -------------------------------------------------------------------------------- 1 | output "vmseries_private_ips" { 2 | description = "Private IP addresses of the vmseries instances." 3 | value = { for k, v in module.vmseries : k => v.private_ips } 4 | } 5 | 6 | output "vmseries_public_ips" { 7 | description = "Public IP addresses of the vmseries instances." 8 | value = { for k, v in module.vmseries : k => v.public_ips } 9 | } 10 | 11 | output "lbs_internal_ips" { 12 | description = "Private IP addresses of internal network loadbalancers." 13 | value = { for k, v in module.lb_internal : k => v.address } 14 | } 15 | 16 | output "lbs_external_ips" { 17 | description = "Public IP addresses of external network loadbalancers." 18 | value = { for k, v in module.lb_external : k => v.ip_addresses } 19 | } 20 | 21 | output "linux_vm_ips" { 22 | description = "Private IP addresses of Linux VMs." 23 | value = { for k, v in resource.google_compute_instance.linux_vm : k => v.network_interface[0].network_ip } 24 | } -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Community Supported 2 | 3 | The software and templates in the repo are released under an as-is, best effort, 4 | support policy. This software should be seen as community supported and Palo 5 | Alto Networks will contribute our expertise as and when possible. We do not 6 | provide technical support or help in using or troubleshooting the components of 7 | the project through our normal support options such as Palo Alto Networks 8 | support teams, or ASC (Authorized Support Centers) partners and backline support 9 | options. The underlying product used (the VM-Series firewall) by the scripts or 10 | templates are still supported, but the support is only for the product 11 | functionality and not for help in deploying or using the template or script 12 | itself. Unless explicitly tagged, all projects or work posted in our GitHub 13 | repository (at https://github.com/PaloAltoNetworks) or sites other than our 14 | official Downloads page on https://support.paloaltonetworks.com are provided 15 | under the best effort policy. 16 | 17 | -------------------------------------------------------------------------------- /modules/lb_external/outputs.tf: -------------------------------------------------------------------------------- 1 | output "forwarding_rules" { 2 | description = "The map of created forwarding rules." 3 | value = google_compute_forwarding_rule.rule 4 | } 5 | 6 | output "ip_addresses" { 7 | description = "The map of IP addresses of the forwarding rules." 8 | value = { for k, v in google_compute_forwarding_rule.rule : k => v.ip_address } 9 | } 10 | 11 | output "target_pool" { 12 | description = "The self-link of the target pool." 13 | value = try(google_compute_target_pool.this[0].self_link, null) 14 | } 15 | 16 | output "created_google_compute_http_health_check" { 17 | description = "The created health check resource. Null if `create_health_check` option was false." 18 | value = try(google_compute_http_health_check.this[0], null) 19 | } 20 | 21 | output "created_google_compute_region_health_check" { 22 | description = "The created health check resource. Null if `create_health_check` option was false." 23 | value = try(google_compute_region_health_check.this[0], null) 24 | } 25 | -------------------------------------------------------------------------------- /examples/vpc_peering_common_with_autoscale/outputs.tf: -------------------------------------------------------------------------------- 1 | output "pubsub_topic_id" { 2 | description = "The resource ID of the Pub/Sub Topic." 3 | value = try({ for k, v in module.autoscale : k => v.pubsub_topic_id }, null) 4 | } 5 | 6 | output "pubsub_subscription_id" { 7 | description = "The resource ID of the Pub/Sub Subscription." 8 | value = try({ for k, v in module.autoscale : k => v.pubsub_subscription_id }, null) 9 | } 10 | 11 | output "lbs_internal_ips" { 12 | description = "Private IP addresses of internal network loadbalancers." 13 | value = { for k, v in module.lb_internal : k => v.address } 14 | } 15 | 16 | output "lbs_external_ips" { 17 | description = "Public IP addresses of external network loadbalancers." 18 | value = { for k, v in module.lb_external : k => v.ip_addresses } 19 | } 20 | 21 | output "linux_vm_ips" { 22 | description = "Private IP addresses of Linux VMs." 23 | value = { for k, v in resource.google_compute_instance.linux_vm : k => v.network_interface[0].network_ip } 24 | } -------------------------------------------------------------------------------- /examples/vpc_peering_dedicated_with_autoscale/outputs.tf: -------------------------------------------------------------------------------- 1 | output "pubsub_topic_id" { 2 | description = "The resource ID of the Pub/Sub Topic." 3 | value = try({ for k, v in module.autoscale : k => v.pubsub_topic_id }, null) 4 | } 5 | 6 | output "pubsub_subscription_id" { 7 | description = "The resource ID of the Pub/Sub Subscription." 8 | value = try({ for k, v in module.autoscale : k => v.pubsub_subscription_id }, null) 9 | } 10 | 11 | output "lbs_internal_ips" { 12 | description = "Private IP addresses of internal network loadbalancers." 13 | value = { for k, v in module.lb_internal : k => v.address } 14 | } 15 | 16 | output "lbs_external_ips" { 17 | description = "Public IP addresses of external network loadbalancers." 18 | value = { for k, v in module.lb_external : k => v.ip_addresses } 19 | } 20 | 21 | output "linux_vm_ips" { 22 | description = "Private IP addresses of Linux VMs." 23 | value = { for k, v in resource.google_compute_instance.linux_vm : k => v.network_interface[0].network_ip } 24 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Palo Alto Networks, inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /modules/autoscale/outputs.tf: -------------------------------------------------------------------------------- 1 | output "zonal_instance_group_ids" { 2 | description = "The resource IDs of the zonal VM-Series managed instance groups. This output should only be used when `regional_mig` is set to `false`." 3 | value = var.regional_mig ? null : { for k, v in google_compute_instance_group_manager.zonal : k => v.instance_group } 4 | } 5 | 6 | output "regional_instance_group_id" { 7 | description = "The resource ID of the regional VM-Series managed instance group. This output should only be used when `regional_mig` is set to `true`." 8 | value = var.regional_mig ? google_compute_region_instance_group_manager.regional[0].instance_group : null 9 | } 10 | 11 | output "pubsub_topic_id" { 12 | description = "The resource ID of the Pub/Sub Topic." 13 | value = var.create_pubsub_topic ? google_pubsub_topic.main[0].id : null 14 | } 15 | 16 | output "pubsub_subscription_id" { 17 | description = "The resource ID of the Pub/Sub Subscription." 18 | value = var.create_pubsub_topic ? google_pubsub_subscription.main[0].id : null 19 | } 20 | 21 | output "pubsub_subscription_iam_member_etag" { 22 | description = "The etag of the Pub/Sub IAM Member." 23 | value = var.create_pubsub_topic ? google_pubsub_subscription_iam_member.main[0].etag : null 24 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This is a minimal list, OK to add things per repo 2 | # mac specific 3 | .DS_Store 4 | .ansible 5 | .azure/ 6 | .bash_history 7 | # don't check storage creds into GH 8 | .boto 9 | .cache 10 | *.code-workspace 11 | # may contain gcloud files 12 | .config 13 | .gitconfig 14 | .local 15 | # .netrc contains secrets for service tokens 16 | .netrc 17 | *.plan 18 | .sentinel 19 | # .ssh dir may contain private keys 20 | .ssh 21 | .terrascan 22 | # Terraform dot files 23 | .terraform 24 | .terraform.lock.hcl 25 | .terraformrc 26 | **/.terraform/* 27 | .terraform.d 28 | *.tfstate 29 | *.tfstate.* 30 | .vscode 31 | .idea 32 | *.tfsec 33 | # Terraform crash log files 34 | crash.*.log 35 | 36 | # Terraform overrides 37 | override.tf 38 | override.tf.json 39 | *_override.tf 40 | *_override.tf.json 41 | 42 | # Palo auth codes 43 | authcodes 44 | # Crash log files 45 | crash.log 46 | credentials.json 47 | # Palo specific 48 | init-cfg.txt 49 | temp 50 | terraform.rc 51 | tmp 52 | # Auto variables 53 | terraform.tfvars 54 | terraform.tfvars.json 55 | *.auto.tfvars 56 | *.auto.tfvars.json 57 | 58 | # Exclude all .tfvars files, which are likely to contain sensitive data, such as 59 | # password, private keys, and other secrets. But allow example files. 60 | *.tfvars 61 | *.tfvars.json 62 | !**/example.tfvars 63 | -------------------------------------------------------------------------------- /modules/vpc-peering/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | local_network_name = reverse(split("/", var.local_network))[0] 3 | peer_network_name = reverse(split("/", var.peer_network))[0] 4 | } 5 | 6 | resource "google_compute_network_peering" "local" { 7 | name = coalesce(var.local_peering_name, "${var.name_prefix}${local.local_network_name}-${local.peer_network_name}") 8 | network = var.local_network 9 | peer_network = var.peer_network 10 | 11 | export_custom_routes = var.local_export_custom_routes 12 | import_custom_routes = var.local_import_custom_routes 13 | 14 | export_subnet_routes_with_public_ip = var.local_export_subnet_routes_with_public_ip 15 | import_subnet_routes_with_public_ip = var.local_import_subnet_routes_with_public_ip 16 | } 17 | 18 | resource "google_compute_network_peering" "peer" { 19 | name = coalesce(var.peer_peering_name, "${var.name_prefix}${local.peer_network_name}-${local.local_network_name}") 20 | network = var.peer_network 21 | peer_network = var.local_network 22 | 23 | export_custom_routes = var.peer_export_custom_routes 24 | import_custom_routes = var.peer_import_custom_routes 25 | 26 | export_subnet_routes_with_public_ip = var.peer_export_subnet_routes_with_public_ip 27 | import_subnet_routes_with_public_ip = var.peer_import_subnet_routes_with_public_ip 28 | } -------------------------------------------------------------------------------- /modules/iam_service_account/variables.tf: -------------------------------------------------------------------------------- 1 | variable "service_account_id" { 2 | default = "The google_service_account.account_id of the created IAM account, unique string per project." 3 | type = string 4 | } 5 | 6 | variable "display_name" { 7 | default = "Palo Alto Networks Firewall Service Account" 8 | } 9 | 10 | variable "roles" { 11 | description = "List of IAM role names, such as [\"roles/compute.viewer\"] or [\"project/A/roles/B\"]. The default list is suitable for Palo Alto Networks Firewall to run and publish custom metrics to GCP Stackdriver." 12 | default = [ 13 | "roles/compute.networkViewer", 14 | "roles/logging.logWriter", 15 | "roles/monitoring.metricWriter", 16 | "roles/monitoring.viewer", 17 | "roles/viewer", # to reach a bootstrap bucket (project's storage.buckets.list with bucket's roles/storage.objectViewer insufficient) 18 | # per https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/set-up-the-vm-series-firewall-on-google-cloud-platform/deploy-vm-series-on-gcp/enable-google-stackdriver-monitoring-on-the-vm-series-firewall.html 19 | "roles/stackdriver.accounts.viewer", 20 | "roles/stackdriver.resourceMetadata.writer", 21 | ] 22 | type = set(string) 23 | } 24 | 25 | variable "project_id" { 26 | description = "ID of a project in which the service account will be created." 27 | type = string 28 | } -------------------------------------------------------------------------------- /.github/workflows/pre-commit-update.yml: -------------------------------------------------------------------------------- 1 | name: Pre-Commit update 2 | run-name: "Update Pre-Commit dependencies" 3 | 4 | permissions: 5 | contents: write 6 | pull-requests: write 7 | 8 | on: 9 | workflow_dispatch: 10 | schedule: 11 | - cron: 0 1 1 * * # 1am of every 1st day of every month 12 | 13 | jobs: 14 | update: 15 | name: "Update Pre-Commit dependencies" 16 | uses: PaloAltoNetworks/terraform-modules-vmseries-ci-workflows/.github/workflows/_pre-commit-update.yml@v2.3 17 | 18 | pre-commit: 19 | name: Run Pre-Commit with the udpated config 20 | needs: [update] 21 | if: needs.update.outputs.pr_operation == 'created' || needs.update.outputs.pr_operation == 'updated' 22 | uses: PaloAltoNetworks/terraform-modules-vmseries-ci-workflows/.github/workflows/_pre_commit.yml@v2.3 23 | with: 24 | pre-commit-hooks: terraform_fmt terraform_docs terraform_tflint checkov 25 | branch: pre-commit-dependencies-update 26 | 27 | comment-pr: 28 | name: Give comment on the PR if pre-commit failed 29 | needs: [pre-commit, update] 30 | if: always() && (needs.pre-commit.result == 'failure' || needs.pre-commit.result == 'success') 31 | uses: PaloAltoNetworks/terraform-modules-vmseries-ci-workflows/.github/workflows/_comment_pr.yml@v2.3 32 | with: 33 | pr_number: ${{ needs.update.outputs.pr_number }} 34 | job_result: ${{ needs.pre-commit.result }} -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project 3 | # title: '[Enhancement] ' 4 | labels: enhancement 5 | assignees: aws-vmseries-modules-codeowners 6 | body: 7 | - type: textarea 8 | attributes: 9 | label: Is your feature request related to a problem? 10 | description: A clear and concise description of what the problem is. 11 | placeholder: eg. I'm always frustrated when [...] 12 | validations: 13 | required: true 14 | - type: textarea 15 | attributes: 16 | label: Describe the solution you'd like 17 | description: A clear and concise description of what you want to happen. 18 | placeholder: How should it work? 19 | validations: 20 | required: true 21 | - type: textarea 22 | attributes: 23 | label: Describe alternatives you've considered. 24 | description: A clear and concise description of any alternative solutions, features, or workarounds you've considered. 25 | validations: 26 | required: false 27 | - type: textarea 28 | attributes: 29 | label: Additional context 30 | description: How has this issue affected you? What are you trying to accomplish? 31 | placeholder: Providing context helps us come up with a solution that is useful in the real world. Drag any screenshot here to help illustrate 32 | validations: 33 | required: false 34 | -------------------------------------------------------------------------------- /examples/panorama_standalone/example.tfvars: -------------------------------------------------------------------------------- 1 | # General 2 | project = "<PROJECT_ID>" 3 | region = "us-central1" 4 | name_prefix = "" 5 | 6 | # VPC 7 | 8 | networks = { 9 | "panorama-vpc" = { 10 | vpc_name = "firewall-vpc" 11 | create_network = true 12 | delete_default_routes_on_create = "false" 13 | mtu = "1460" 14 | routing_mode = "REGIONAL" 15 | subnetworks = { 16 | "panorama-sub" = { 17 | name = "panorama-subnet" 18 | create_subnetwork = true 19 | ip_cidr_range = "172.21.21.0/24" 20 | region = "us-central1" 21 | } 22 | } 23 | firewall_rules = { 24 | "allow-panorama-ingress" = { 25 | name = "panorama-mgmt" 26 | source_ranges = ["1.1.1.1/32", "2.2.2.2/32"] 27 | priority = "1000" 28 | allowed_protocol = "all" 29 | allowed_ports = [] 30 | } 31 | } 32 | } 33 | } 34 | 35 | # Panorama 36 | 37 | panoramas = { 38 | "panorama-01" = { 39 | zone = "us-central1-a" 40 | panorama_name = "panorama-01" 41 | vpc_network_key = "panorama-vpc" 42 | subnetwork_key = "panorama-sub" 43 | panorama_version = "panorama-byol-1000" 44 | ssh_keys = "admin:<ssh-rsa AAAA...>" 45 | attach_public_ip = true 46 | private_static_ip = "172.21.21.2" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/panorama_standalone/main.tf: -------------------------------------------------------------------------------- 1 | module "vpc" { 2 | source = "../../modules/vpc" 3 | 4 | for_each = var.networks 5 | 6 | project_id = var.project 7 | name = "${var.name_prefix}${each.value.vpc_name}" 8 | create_network = each.value.create_network 9 | delete_default_routes_on_create = each.value.delete_default_routes_on_create 10 | mtu = each.value.mtu 11 | routing_mode = each.value.routing_mode 12 | subnetworks = { for k, v in each.value.subnetworks : k => merge(v, { 13 | name = "${var.name_prefix}${v.name}" 14 | }) 15 | } 16 | firewall_rules = try({ for k, v in each.value.firewall_rules : k => merge(v, { 17 | name = "${var.name_prefix}${v.name}" 18 | }) 19 | }, {}) 20 | } 21 | 22 | module "panorama" { 23 | source = "../../modules/panorama" 24 | 25 | for_each = var.panoramas 26 | 27 | name = "${var.name_prefix}${each.value.panorama_name}" 28 | project = var.project 29 | region = var.region 30 | zone = each.value.zone 31 | panorama_version = each.value.panorama_version 32 | ssh_keys = each.value.ssh_keys 33 | subnet = module.vpc[each.value.vpc_network_key].subnetworks[each.value.subnetwork_key].self_link 34 | private_static_ip = each.value.private_static_ip 35 | attach_public_ip = each.value.attach_public_ip 36 | log_disks = try(each.value.log_disks, []) 37 | depends_on = [module.vpc] 38 | } -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/antonbabenko/pre-commit-terraform 3 | rev: v1.68.1 # Get the latest from: https://github.com/antonbabenko/pre-commit-terraform/releases 4 | hooks: 5 | - id: terraform_fmt 6 | - id: terraform_docs 7 | args: ['--args=--lockfile=false', '--args=--indent=3'] 8 | - id: terraform_tflint 9 | args: [ 10 | # --args=--module, # TODO enable it after ensuring `terraform init` 11 | # --args=--only=terraform_comment_syntax, 12 | --args=--only=terraform_deprecated_interpolation, 13 | --args=--only=terraform_deprecated_index, 14 | # --args=--only=terraform_documented_variables, 15 | --args=--only=terraform_module_pinned_source, 16 | --args=--only=terraform_naming_convention, 17 | # --args=--only=terraform_required_providers, 18 | # --args=--only=terraform_required_version, 19 | # --args=--only=terraform_unused_declarations, 20 | --args=--only=terraform_workspace_remote, 21 | ] 22 | - repo: https://github.com/bridgecrewio/checkov.git 23 | rev: '2.2.125' 24 | hooks: 25 | - id: checkov 26 | verbose: true 27 | args: [ 28 | --compact, 29 | --quiet, 30 | --skip-check, "CKV_GCP_26,CKV_GCP_32,CKV_GCP_35,CKV_GCP_36,CKV_GCP_39,CKV_GCP_40,CKV_GCP_76,CKV_GCP_62,CKV_GCP_37,CKV_GCP_38,CKV_GCP_74,CKV_GCP_83,CKV2_GHA_1,CKV_SECRET_6", 31 | --soft-fail-on, "CKV_GCP_37,CKV_GCP_38,CKV_GCP_74,CKV_GCP_83,CKV2_GHA_1", 32 | ] 33 | -------------------------------------------------------------------------------- /.github/actions/terratest/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Terratest' 2 | description: 'Runs Terratest for a specified path.' 3 | inputs: 4 | tf_version: 5 | description: 'TF version used.' 6 | required: true 7 | path: 8 | description: 'Path to Terraform module.' 9 | required: true 10 | terratest_action: 11 | description: The action (name of a test in Terratest) that will be passed to the Makefile's ACTION parameter 12 | type: string 13 | required: true 14 | pr-id: 15 | description: A PR number. Optional value, you might want to use it to prefix resources created for a particular PR to identify them easily. 16 | type: string 17 | default: "" 18 | required: false 19 | 20 | runs: 21 | using: "composite" 22 | steps: 23 | 24 | - name: setup Terraform 25 | uses: hashicorp/setup-terraform@v2 26 | with: 27 | terraform_version: ${{ inputs.tf_version }} 28 | terraform_wrapper: false 29 | 30 | - name: setup Go 31 | uses: actions/setup-go@v4 32 | with: 33 | go-version: '1.20' 34 | 35 | - name: login to GCP 36 | uses: google-github-actions/auth@v1 37 | with: 38 | workload_identity_provider: ${{ env.WORKLOAD_IDENTITY_PROVIDER }} 39 | service_account: ${{ env.GCP_SERVICE_ACCOUNT}} 40 | 41 | - name: ${{ inputs.terratest_action }} infrastructure 42 | env: 43 | TPATH: ${{ inputs.path }} 44 | ACTION: ${{ inputs.terratest_action }} 45 | PRID: ${{ inputs.pr-id }} 46 | PROJECT_ID: ${{ env.PROJECT_ID }} 47 | shell: bash 48 | run: make $TPATH ACTION=$ACTION -------------------------------------------------------------------------------- /.github/workflows/plan-command.yml: -------------------------------------------------------------------------------- 1 | name: ChatOPS Plan 2 | run-name: "On demand Plan test for PR - (#${{ github.event.inputs.pr-id }}) ${{ github.event.inputs.pr-title }}" 3 | 4 | permissions: 5 | contents: read 6 | 7 | concurrency: chatops-plan 8 | 9 | on: 10 | workflow_dispatch: 11 | inputs: 12 | paths: 13 | description: Space delimited list of module paths to test 14 | type: string 15 | required: true 16 | tf_version: 17 | description: Terraform versions to use for tests, comma-separated list 18 | type: string 19 | pr-id: 20 | description: ID of the PR that triggered this workflow 21 | type: string 22 | required: true 23 | pr-title: 24 | description: Title of the PR that triggered this workflow 25 | type: string 26 | required: true 27 | comment-id: 28 | description: 'The comment-id of the slash command' 29 | type: string 30 | required: true 31 | branch: 32 | description: Branch on which the tests should run 33 | type: string 34 | default: main 35 | 36 | jobs: 37 | test: 38 | name: Run plan test 39 | permissions: 40 | contents: read 41 | pull-requests: write 42 | id-token: write 43 | uses: PaloAltoNetworks/terraform-modules-vmseries-ci-workflows/.github/workflows/test_command.yml@v2.3 44 | secrets: inherit 45 | with: 46 | cloud: azure 47 | paths: ${{ inputs.paths }} 48 | tf_version: ${{ inputs.tf_version }} 49 | pr-id: ${{ inputs.pr-id }} 50 | comment-id: ${{ inputs.comment-id }} 51 | branch: ${{ inputs.branch }} 52 | terratest_action: Plan -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "branches": [ 3 | "main" 4 | ], 5 | "plugins": [ 6 | [ 7 | "@semantic-release/commit-analyzer", 8 | { 9 | "releaseRules": [ 10 | { 11 | "breaking": true, 12 | "release": "minor" 13 | }, 14 | { 15 | "type": "feat", 16 | "release": "patch" 17 | }, 18 | { 19 | "type": "feat", 20 | "scope" : "MAJOR", 21 | "release": "major" 22 | } 23 | ] 24 | } 25 | ], 26 | "@semantic-release/release-notes-generator", 27 | [ 28 | "@semantic-release/git", 29 | { 30 | "assets": [ 31 | "README.md" 32 | ], 33 | "message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}" 34 | } 35 | ], 36 | [ 37 | "@semantic-release/github", 38 | { 39 | "successComment": ":tada: This ${issue.pull_request ? 'PR is included' : 'issue has been resolved'} in version ${nextRelease.version} :tada:\n\nThe release is available on [Terraform Registry](https://registry.terraform.io/modules/PaloAltoNetworks/vmseries-modules/google/latest) and [GitHub release](../releases/tag/v${nextRelease.version})\n\n> Posted by [semantic-release](https://github.com/semantic-release/semantic-release) bot" 40 | } 41 | ] 42 | ], 43 | "preset": "conventionalcommits" 44 | } 45 | -------------------------------------------------------------------------------- /.github/workflows/apply-command.yml: -------------------------------------------------------------------------------- 1 | name: ChatOPS Apply 2 | run-name: "On demand Apply test for PR - (#${{ github.event.inputs.pr-id }}) ${{ github.event.inputs.pr-title }}" 3 | 4 | permissions: 5 | contents: read 6 | 7 | concurrency: chatops-apply 8 | 9 | on: 10 | workflow_dispatch: 11 | inputs: 12 | paths: 13 | description: Space delimited list of module paths to test 14 | type: string 15 | required: true 16 | tf_version: 17 | description: Terraform versions to use for tests, comma-separated list 18 | type: string 19 | pr-id: 20 | description: ID of the PR that triggered this workflow 21 | type: string 22 | required: true 23 | pr-title: 24 | description: Title of the PR that triggered this workflow 25 | type: string 26 | required: true 27 | comment-id: 28 | description: 'The comment-id of the slash command' 29 | type: string 30 | required: true 31 | branch: 32 | description: Branch on which the tests should run 33 | type: string 34 | default: main 35 | 36 | jobs: 37 | test: 38 | name: Run apply test 39 | permissions: 40 | contents: read 41 | pull-requests: write 42 | id-token: write 43 | uses: PaloAltoNetworks/terraform-modules-vmseries-ci-workflows/.github/workflows/test_command.yml@v2.3 44 | secrets: inherit 45 | with: 46 | cloud: azure 47 | paths: ${{ inputs.paths }} 48 | tf_version: ${{ inputs.tf_version }} 49 | pr-id: ${{ inputs.pr-id }} 50 | comment-id: ${{ inputs.comment-id }} 51 | branch: ${{ inputs.branch }} 52 | terratest_action: Apply 53 | apply_timeout: 60 -------------------------------------------------------------------------------- /.github/workflows/idempotence-command.yml: -------------------------------------------------------------------------------- 1 | name: ChatOPS Idempotence 2 | run-name: "On demand Idempotence test for PR - (#${{ github.event.inputs.pr-id }}) ${{ github.event.inputs.pr-title }}" 3 | 4 | permissions: 5 | contents: read 6 | 7 | concurrency: chatops-apply 8 | 9 | on: 10 | workflow_dispatch: 11 | inputs: 12 | paths: 13 | description: Space delimited list of module paths to test 14 | type: string 15 | required: true 16 | tf_version: 17 | description: Terraform versions to use for tests, comma-separated list 18 | type: string 19 | pr-id: 20 | description: ID of the PR that triggered this workflow 21 | type: string 22 | required: true 23 | pr-title: 24 | description: Title of the PR that triggered this workflow 25 | type: string 26 | required: true 27 | comment-id: 28 | description: 'The comment-id of the slash command' 29 | type: string 30 | required: true 31 | branch: 32 | description: Branch on which the tests should run 33 | type: string 34 | default: main 35 | 36 | jobs: 37 | test: 38 | name: Run idempotence test 39 | permissions: 40 | contents: read 41 | pull-requests: write 42 | id-token: write 43 | uses: PaloAltoNetworks/terraform-modules-vmseries-ci-workflows/.github/workflows/test_command.yml@v2.3 44 | secrets: inherit 45 | with: 46 | cloud: azure 47 | paths: ${{ inputs.paths }} 48 | tf_version: ${{ inputs.tf_version }} 49 | pr-id: ${{ inputs.pr-id }} 50 | comment-id: ${{ inputs.comment-id }} 51 | branch: ${{ inputs.branch }} 52 | terratest_action: Idempotence 53 | apply_timeout: 60 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Create an issue to help us improve 3 | title: '[Bug Report] <Short title of the bug>' 4 | assignees: aws-vmseries-modules-codeowners 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: Describe the bug 9 | description: A clear and concise description of what is wrong and steps to reproduce. 10 | validations: 11 | required: true 12 | - type: input 13 | attributes: 14 | label: Module Version 15 | description: What is the module version in use (https://github.com/PaloAltoNetworks/terraform-google-vmseries-modules/releases)? Please include the commit hash if you're using an unreleased version. 16 | placeholder: eg. v0.4.1 17 | validations: 18 | required: true 19 | - type: input 20 | attributes: 21 | label: Terraform version 22 | description: What is the Terraform version in use? 23 | placeholder: Execute terraform -version 24 | validations: 25 | required: false 26 | - type: textarea 27 | attributes: 28 | label: Expected behavior 29 | description: Tell us what should happen or how it should work. 30 | validations: 31 | required: false 32 | - type: textarea 33 | attributes: 34 | label: Current behavior 35 | description: Tell us what happens instead of the expected behavior. 36 | validations: 37 | required: false 38 | - type: textarea 39 | attributes: 40 | label: Anything else to add? 41 | description: If you would like to add any more information, please add it below. 42 | placeholder: | 43 | Drag any screenshot here to help illustrate, other relevant details about the environment you experienced the bug in, terraform debug logs or relevant outputs. 44 | -------------------------------------------------------------------------------- /.github/workflows/validate-command.yml: -------------------------------------------------------------------------------- 1 | name: ChatOPS Validate 2 | run-name: "On demand Validate test for PR - (#${{ github.event.inputs.pr-id }}) ${{ github.event.inputs.pr-title }}" 3 | 4 | permissions: 5 | contents: read 6 | 7 | on: 8 | workflow_dispatch: 9 | inputs: 10 | paths: 11 | description: Space delimited list of module paths to test 12 | type: string 13 | required: true 14 | tf_version: 15 | description: Terraform versions to use for tests, comma-separated list 16 | type: string 17 | pr-id: 18 | description: ID of the PR that triggered this workflow 19 | type: string 20 | required: true 21 | pr-title: 22 | description: Title of the PR that triggered this workflow 23 | type: string 24 | required: true 25 | comment-id: 26 | description: 'The comment-id of the slash command' 27 | type: string 28 | required: true 29 | branch: 30 | description: Branch on which the tests should run 31 | type: string 32 | default: main 33 | 34 | jobs: 35 | test: 36 | name: Run validate test 37 | concurrency: 38 | group: chatops-validate-${{ github.event.issue.number }} 39 | cancel-in-progress: true 40 | permissions: 41 | contents: read 42 | pull-requests: write 43 | id-token: write 44 | uses: PaloAltoNetworks/terraform-modules-vmseries-ci-workflows/.github/workflows/test_command.yml@v2.3 45 | secrets: inherit 46 | with: 47 | cloud: azure 48 | paths: ${{ inputs.paths }} 49 | tf_version: ${{ inputs.tf_version }} 50 | pr-id: ${{ inputs.pr-id }} 51 | comment-id: ${{ inputs.comment-id }} 52 | branch: ${{ inputs.branch }} 53 | terratest_action: Validate -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | # install.sh - prepare the dependencies for the run.sh 4 | # 5 | # It only handles installing from scratch and will probably fail on a subsequent run. 6 | # It overuses the &&, &, and backslash line continuation so it could be easily converted 7 | # into a Dockerfile, just by adding `RUN` directives (and `COPY requirements.txt .`). 8 | 9 | set -euo pipefail 10 | 11 | cd "$(dirname $0)" 12 | 13 | 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 & \ 14 | curl -sL https://github.com/tfsec/tfsec/releases/download/v0.34.0/tfsec-linux-amd64 > tfsec & \ 15 | curl -sL https://github.com/terraform-linters/tflint/releases/download/v0.29.0/tflint_linux_amd64.zip > tflint.zip & \ 16 | wait 17 | echo Finished successfully all parallel downloads ------------------------------------------------------------------ 18 | 19 | tar zxf terraform-docs.tar.gz 20 | rm terraform-docs.tar.gz 21 | mv terraform-docs /usr/local/bin/ 22 | 23 | chmod +x tfsec 24 | mv tfsec /usr/local/bin/ 25 | 26 | unzip tflint.zip 27 | rm tflint.zip 28 | mv tflint /usr/local/bin/ 29 | 30 | git --version 31 | terraform-docs --version 32 | tfsec --version 33 | tflint --version 34 | terraform version 35 | 36 | echo "Also, the newest release: $(curl -s https://api.github.com/repos/terraform-docs/terraform-docs/releases/latest | grep -o -E "https://.+?-linux-amd64")" 37 | echo "Also, the newest release: $(curl -s https://api.github.com/repos/tfsec/tfsec/releases/latest | grep -o -E "https://.+?tfsec-linux-amd64")" 38 | echo "Also, the newest release: $(curl -s https://api.github.com/repos/terraform-linters/tflint/releases/latest | grep -o -E "https://.+?_linux_amd64.zip")" 39 | 40 | python3 -m pip install -r requirements.txt 41 | -------------------------------------------------------------------------------- /scripts/decisions.md: -------------------------------------------------------------------------------- 1 | # Architecture Decision Record (ADR) For Tests and CI/CD 2 | 3 | ## macOS and Linux 4 | 5 | - Decision: Tests compatible with macOS and Linux, including WSL1 and WSL2. 6 | - Reason: Those are the main systems of our users and our developers. 7 | 8 | ## Microsoft Windows (non-WSL) 9 | 10 | - Decision: Maintain open road for test compatibility with Microsoft Windows (non-WSL). 11 | - Reason: predictable customer requirements. 12 | 13 | ## GitHub Actions for CI/CD 14 | 15 | - Decision: Use GitHub Actions for CI/CD. 16 | - Reason: The code was placed on GitHub. Therefore it's just the easiest CI/CD runner to implement. 17 | 18 | ## CLI Commands 19 | 20 | - Decision: Have a command that is runnable from a laptop that runs all the tests. Run exactly the same command in CI/CD. 21 | - Reason: Short feedback loop when developing. It takes less time to run locally the same actions that CI/CD normally does. Time is saved on git-committing, git-pushing, preparing the fresh runner containers. 22 | - Reason: Less vendor lock-in with GitHub Actions (or any other CI/CD runner). 23 | 24 | ## Python 25 | 26 | - Decision: Use Python 3.6 script as the command for the main test. 27 | - Alternative was: Use bash script as the command for the main test. 28 | - Reason: Less pitfalls, cleaner syntax. 29 | - Reason: Predicted MS Windows compatibility. 30 | - Cost: Need to install Python and set it up. 31 | 32 | ## Test the examples 33 | 34 | - Decision: Test the examples to the maximum extent possible, including `terraform apply` on a real cloud. 35 | - Reason: Examples are by far the main contact point of any developer 36 | who starts work on the modules. Typically a developer tries to copy 37 | a relevant example, do minimal customizations and it would be best if they could simply succeed. A successful first run enables to enter a feedback loop and become productive on the main project. 38 | -------------------------------------------------------------------------------- /modules/bootstrap/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name_prefix" { 2 | description = "Prefix of the name of Google Cloud Storage bucket, followed by 10 random characters" 3 | default = "paloaltonetworks-firewall-bootstrap-" 4 | type = string 5 | } 6 | 7 | variable "files" { 8 | description = "Map of all files to copy to bucket. The keys are local paths, the values are remote paths. For example `{\"dir/my.txt\" = \"config/init-cfg.txt\"}`" 9 | default = {} 10 | type = map(string) 11 | } 12 | 13 | variable "service_account" { 14 | description = "Optional IAM Service Account (just an email) that will be granted read-only access to this bucket" 15 | default = null 16 | type = string 17 | } 18 | 19 | variable "location" { 20 | description = "Location in which the GCS Bucket will be deployed. Available locations can be found under https://cloud.google.com/storage/docs/locations." 21 | type = string 22 | } 23 | 24 | 25 | variable "bootstrap_files_dir" { 26 | description = <<-EOF 27 | Bootstrap file directory. If the variable has a value of `null` (default) - then it will not upload any other files other than the ones specified in the `files` variable. 28 | More information can be found at https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/bootstrap-the-vm-series-firewall/bootstrap-package. 29 | EOF 30 | type = string 31 | default = null 32 | } 33 | 34 | 35 | variable "folders" { 36 | description = <<-EOF 37 | List of folder paths that will be used to create dedicated boostrap package folder sets per firewall or firewall group (for example to distinguish configuration per region, per inbound/obew role, etc) within the created storage bucket. 38 | 39 | A default value (empty list) will result in the creation of a single bootstrap package folder set in the bucket top-level directory. 40 | EOF 41 | default = [] 42 | type = list(any) 43 | } 44 | 45 | -------------------------------------------------------------------------------- /scripts/requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile requirements.txt 6 | # 7 | appdirs==1.4.4 8 | # via 9 | # -r requirements.txt 10 | # virtualenv 11 | cfgv==3.2.0 12 | # via 13 | # -r requirements.txt 14 | # pre-commit 15 | click==7.1.2 16 | # via 17 | # -r requirements.txt 18 | # pip-tools 19 | distlib==0.3.1 20 | # via 21 | # -r requirements.txt 22 | # virtualenv 23 | filelock==3.0.12 24 | # via 25 | # -r requirements.txt 26 | # virtualenv 27 | identify==2.2.4 28 | # via 29 | # -r requirements.txt 30 | # pre-commit 31 | importlib-metadata==4.0.1 32 | # via 33 | # -r requirements.txt 34 | # pep517 35 | # pre-commit 36 | # virtualenv 37 | importlib-resources==5.1.2 38 | # via 39 | # -r requirements.txt 40 | # pre-commit 41 | # virtualenv 42 | nodeenv==1.6.0 43 | # via 44 | # -r requirements.txt 45 | # pre-commit 46 | pep517==0.10.0 47 | # via 48 | # -r requirements.txt 49 | # pip-tools 50 | pip-tools==6.1.0 51 | # via -r requirements.txt 52 | pre-commit==2.7.1 53 | # via -r requirements.txt 54 | pyyaml==5.4.1 55 | # via 56 | # -r requirements.txt 57 | # pre-commit 58 | six==1.16.0 59 | # via 60 | # -r requirements.txt 61 | # virtualenv 62 | toml==0.10.2 63 | # via 64 | # -r requirements.txt 65 | # pep517 66 | # pre-commit 67 | typing-extensions==3.10.0.0 68 | # via 69 | # -r requirements.txt 70 | # importlib-metadata 71 | virtualenv==20.4.6 72 | # via 73 | # -r requirements.txt 74 | # pre-commit 75 | zipp==3.4.1 76 | # via 77 | # -r requirements.txt 78 | # importlib-metadata 79 | # importlib-resources 80 | # pep517 81 | 82 | # The following packages are considered to be unsafe in a requirements file: 83 | # pip 84 | -------------------------------------------------------------------------------- /examples/standalone_vmseries_with_metadata_bootstrap/main.tf: -------------------------------------------------------------------------------- 1 | module "vpc" { 2 | source = "../../modules/vpc" 3 | 4 | for_each = var.networks 5 | 6 | project_id = var.project 7 | name = "${var.name_prefix}${each.value.vpc_name}" 8 | create_network = each.value.create_network 9 | delete_default_routes_on_create = each.value.delete_default_routes_on_create 10 | mtu = each.value.mtu 11 | routing_mode = each.value.routing_mode 12 | subnetworks = { for k, v in each.value.subnetworks : k => merge(v, { 13 | name = "${var.name_prefix}${v.name}" 14 | }) 15 | } 16 | firewall_rules = try({ for k, v in each.value.firewall_rules : k => merge(v, { 17 | name = "${var.name_prefix}${v.name}" 18 | }) 19 | }, {}) 20 | } 21 | 22 | module "vmseries" { 23 | source = "../../modules/vmseries" 24 | 25 | for_each = var.vmseries 26 | 27 | name = "${var.name_prefix}${each.value.name}" 28 | zone = each.value.zone 29 | ssh_keys = try(each.value.ssh_keys, var.vmseries_common.ssh_keys) 30 | vmseries_image = try(each.value.vmseries_image, var.vmseries_common.vmseries_image) 31 | machine_type = try(each.value.machine_type, var.vmseries_common.machine_type) 32 | min_cpu_platform = try(each.value.min_cpu_platform, var.vmseries_common.min_cpu_platform, "Intel Cascade Lake") 33 | tags = try(each.value.tags, var.vmseries_common.tags, []) 34 | scopes = try(each.value.scopes, var.vmseries_common.scopes, []) 35 | create_instance_group = true 36 | 37 | bootstrap_options = try(each.value.bootstrap_options, {}) 38 | 39 | named_ports = try(each.value.named_ports, []) 40 | 41 | network_interfaces = [for v in each.value.network_interfaces : 42 | { 43 | subnetwork = module.vpc[v.vpc_network_key].subnetworks[v.subnetwork_key].self_link 44 | private_ip = v.private_ip 45 | create_public_ip = try(v.create_public_ip, false) 46 | public_ip = try(v.public_ip, null) 47 | }] 48 | } -------------------------------------------------------------------------------- /examples/vmseries_ha/main_test.go: -------------------------------------------------------------------------------- 1 | package vmseries_ha 2 | 3 | import ( 4 | "testing" 5 | "log" 6 | 7 | "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" 8 | "github.com/gruntwork-io/terratest/modules/logger" 9 | "github.com/gruntwork-io/terratest/modules/terraform" 10 | ) 11 | 12 | func CreateTerraformOptions(t *testing.T) *terraform.Options { 13 | varsInfo, err := testskeleton.GenerateTerraformVarsInfo("gcp") 14 | if err != nil { 15 | // Handle the error 16 | log.Fatalf("Error generating terraform vars info: %v", err) 17 | } 18 | 19 | // define options for Terraform 20 | terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ 21 | TerraformDir: ".", 22 | VarFiles: []string{"example.tfvars"}, 23 | Vars: map[string]interface{}{ 24 | "name_prefix": varsInfo.NamePrefix, 25 | "project": varsInfo.GoogleProjectId, 26 | }, 27 | Logger: logger.Default, 28 | Lock: true, 29 | Upgrade: true, 30 | SetVarsAfterVarFiles: true, 31 | }) 32 | 33 | return terraformOptions 34 | } 35 | 36 | func TestValidate(t *testing.T) { 37 | testskeleton.ValidateCode(t, nil) 38 | } 39 | 40 | func TestPlan(t *testing.T) { 41 | // define options for Terraform 42 | terraformOptions := CreateTerraformOptions(t) 43 | // prepare list of items to check 44 | assertList := []testskeleton.AssertExpression{} 45 | // plan test infrastructure and verify outputs 46 | testskeleton.PlanInfraCheckErrors(t, terraformOptions, assertList, "No errors are expected") 47 | } 48 | 49 | func TestApply(t *testing.T) { 50 | // define options for Terraform 51 | terraformOptions := CreateTerraformOptions(t) 52 | // prepare list of items to check 53 | assertList := []testskeleton.AssertExpression{} 54 | // deploy test infrastructure and verify outputs and check if there are no planned changes after deployment 55 | testskeleton.DeployInfraCheckOutputs(t, terraformOptions, assertList) 56 | } 57 | 58 | func TestIdempotence(t *testing.T) { 59 | // define options for Terraform 60 | terraformOptions := CreateTerraformOptions(t) 61 | // prepare list of items to check 62 | assertList := []testskeleton.AssertExpression{} 63 | // deploy test infrastructure and verify outputs and check if there are no planned changes after deployment 64 | testskeleton.DeployInfraCheckOutputsVerifyChanges(t, terraformOptions, assertList) 65 | } -------------------------------------------------------------------------------- /examples/multi_nic_common/main_test.go: -------------------------------------------------------------------------------- 1 | package multi_nic_common 2 | 3 | import ( 4 | "testing" 5 | "log" 6 | 7 | "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" 8 | "github.com/gruntwork-io/terratest/modules/logger" 9 | "github.com/gruntwork-io/terratest/modules/terraform" 10 | ) 11 | 12 | func CreateTerraformOptions(t *testing.T) *terraform.Options { 13 | varsInfo, err := testskeleton.GenerateTerraformVarsInfo("gcp") 14 | if err != nil { 15 | // Handle the error 16 | log.Fatalf("Error generating terraform vars info: %v", err) 17 | } 18 | 19 | // define options for Terraform 20 | terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ 21 | TerraformDir: ".", 22 | VarFiles: []string{"example.tfvars"}, 23 | Vars: map[string]interface{}{ 24 | "name_prefix": varsInfo.NamePrefix, 25 | "project": varsInfo.GoogleProjectId, 26 | }, 27 | Logger: logger.Default, 28 | Lock: true, 29 | Upgrade: true, 30 | SetVarsAfterVarFiles: true, 31 | }) 32 | 33 | return terraformOptions 34 | } 35 | 36 | func TestValidate(t *testing.T) { 37 | testskeleton.ValidateCode(t, nil) 38 | } 39 | 40 | func TestPlan(t *testing.T) { 41 | // define options for Terraform 42 | terraformOptions := CreateTerraformOptions(t) 43 | // prepare list of items to check 44 | assertList := []testskeleton.AssertExpression{} 45 | // plan test infrastructure and verify outputs 46 | testskeleton.PlanInfraCheckErrors(t, terraformOptions, assertList, "No errors are expected") 47 | } 48 | 49 | func TestApply(t *testing.T) { 50 | // define options for Terraform 51 | terraformOptions := CreateTerraformOptions(t) 52 | // prepare list of items to check 53 | assertList := []testskeleton.AssertExpression{} 54 | // deploy test infrastructure and verify outputs and check if there are no planned changes after deployment 55 | testskeleton.DeployInfraCheckOutputs(t, terraformOptions, assertList) 56 | } 57 | 58 | func TestIdempotence(t *testing.T) { 59 | // define options for Terraform 60 | terraformOptions := CreateTerraformOptions(t) 61 | // prepare list of items to check 62 | assertList := []testskeleton.AssertExpression{} 63 | // deploy test infrastructure and verify outputs and check if there are no planned changes after deployment 64 | testskeleton.DeployInfraCheckOutputsVerifyChanges(t, terraformOptions, assertList) 65 | } -------------------------------------------------------------------------------- /examples/panorama_standalone/main_test.go: -------------------------------------------------------------------------------- 1 | package panorama_standalone 2 | 3 | import ( 4 | "testing" 5 | "log" 6 | 7 | "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" 8 | "github.com/gruntwork-io/terratest/modules/logger" 9 | "github.com/gruntwork-io/terratest/modules/terraform" 10 | ) 11 | 12 | func CreateTerraformOptions(t *testing.T) *terraform.Options { 13 | varsInfo, err := testskeleton.GenerateTerraformVarsInfo("gcp") 14 | if err != nil { 15 | // Handle the error 16 | log.Fatalf("Error generating terraform vars info: %v", err) 17 | } 18 | 19 | // define options for Terraform 20 | terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ 21 | TerraformDir: ".", 22 | VarFiles: []string{"example.tfvars"}, 23 | Vars: map[string]interface{}{ 24 | "name_prefix": varsInfo.NamePrefix, 25 | "project": varsInfo.GoogleProjectId, 26 | }, 27 | Logger: logger.Default, 28 | Lock: true, 29 | Upgrade: true, 30 | SetVarsAfterVarFiles: true, 31 | }) 32 | 33 | return terraformOptions 34 | } 35 | 36 | func TestValidate(t *testing.T) { 37 | testskeleton.ValidateCode(t, nil) 38 | } 39 | 40 | func TestPlan(t *testing.T) { 41 | // define options for Terraform 42 | terraformOptions := CreateTerraformOptions(t) 43 | // prepare list of items to check 44 | assertList := []testskeleton.AssertExpression{} 45 | // plan test infrastructure and verify outputs 46 | testskeleton.PlanInfraCheckErrors(t, terraformOptions, assertList, "No errors are expected") 47 | } 48 | 49 | func TestApply(t *testing.T) { 50 | // define options for Terraform 51 | terraformOptions := CreateTerraformOptions(t) 52 | // prepare list of items to check 53 | assertList := []testskeleton.AssertExpression{} 54 | // deploy test infrastructure and verify outputs and check if there are no planned changes after deployment 55 | testskeleton.DeployInfraCheckOutputs(t, terraformOptions, assertList) 56 | } 57 | 58 | func TestIdempotence(t *testing.T) { 59 | // define options for Terraform 60 | terraformOptions := CreateTerraformOptions(t) 61 | // prepare list of items to check 62 | assertList := []testskeleton.AssertExpression{} 63 | // deploy test infrastructure and verify outputs and check if there are no planned changes after deployment 64 | testskeleton.DeployInfraCheckOutputsVerifyChanges(t, terraformOptions, assertList) 65 | } -------------------------------------------------------------------------------- /examples/vpc_peering_common/main_test.go: -------------------------------------------------------------------------------- 1 | package vpc_peering_common 2 | 3 | import ( 4 | "testing" 5 | "log" 6 | 7 | "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" 8 | "github.com/gruntwork-io/terratest/modules/logger" 9 | "github.com/gruntwork-io/terratest/modules/terraform" 10 | ) 11 | 12 | func CreateTerraformOptions(t *testing.T) *terraform.Options { 13 | varsInfo, err := testskeleton.GenerateTerraformVarsInfo("gcp") 14 | if err != nil { 15 | // Handle the error 16 | log.Fatalf("Error generating terraform vars info: %v", err) 17 | } 18 | 19 | // define options for Terraform 20 | terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ 21 | TerraformDir: ".", 22 | VarFiles: []string{"example.tfvars"}, 23 | Vars: map[string]interface{}{ 24 | "name_prefix": varsInfo.NamePrefix, 25 | "project": varsInfo.GoogleProjectId, 26 | }, 27 | Logger: logger.Default, 28 | Lock: true, 29 | Upgrade: true, 30 | SetVarsAfterVarFiles: true, 31 | }) 32 | 33 | return terraformOptions 34 | } 35 | 36 | func TestValidate(t *testing.T) { 37 | testskeleton.ValidateCode(t, nil) 38 | } 39 | 40 | func TestPlan(t *testing.T) { 41 | // define options for Terraform 42 | terraformOptions := CreateTerraformOptions(t) 43 | // prepare list of items to check 44 | assertList := []testskeleton.AssertExpression{} 45 | // plan test infrastructure and verify outputs 46 | testskeleton.PlanInfraCheckErrors(t, terraformOptions, assertList, "No errors are expected") 47 | } 48 | 49 | func TestApply(t *testing.T) { 50 | // define options for Terraform 51 | terraformOptions := CreateTerraformOptions(t) 52 | // prepare list of items to check 53 | assertList := []testskeleton.AssertExpression{} 54 | // deploy test infrastructure and verify outputs and check if there are no planned changes after deployment 55 | testskeleton.DeployInfraCheckOutputs(t, terraformOptions, assertList) 56 | } 57 | 58 | func TestIdempotence(t *testing.T) { 59 | // define options for Terraform 60 | terraformOptions := CreateTerraformOptions(t) 61 | // prepare list of items to check 62 | assertList := []testskeleton.AssertExpression{} 63 | // deploy test infrastructure and verify outputs and check if there are no planned changes after deployment 64 | testskeleton.DeployInfraCheckOutputsVerifyChanges(t, terraformOptions, assertList) 65 | } -------------------------------------------------------------------------------- /examples/vpc_peering_dedicated/main_test.go: -------------------------------------------------------------------------------- 1 | package vpc_peering_dedicated 2 | 3 | import ( 4 | "testing" 5 | "log" 6 | 7 | "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" 8 | "github.com/gruntwork-io/terratest/modules/logger" 9 | "github.com/gruntwork-io/terratest/modules/terraform" 10 | ) 11 | 12 | func CreateTerraformOptions(t *testing.T) *terraform.Options { 13 | varsInfo, err := testskeleton.GenerateTerraformVarsInfo("gcp") 14 | if err != nil { 15 | // Handle the error 16 | log.Fatalf("Error generating terraform vars info: %v", err) 17 | } 18 | 19 | // define options for Terraform 20 | terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ 21 | TerraformDir: ".", 22 | VarFiles: []string{"example.tfvars"}, 23 | Vars: map[string]interface{}{ 24 | "name_prefix": varsInfo.NamePrefix, 25 | "project": varsInfo.GoogleProjectId, 26 | }, 27 | Logger: logger.Default, 28 | Lock: true, 29 | Upgrade: true, 30 | SetVarsAfterVarFiles: true, 31 | }) 32 | 33 | return terraformOptions 34 | } 35 | 36 | func TestValidate(t *testing.T) { 37 | testskeleton.ValidateCode(t, nil) 38 | } 39 | 40 | func TestPlan(t *testing.T) { 41 | // define options for Terraform 42 | terraformOptions := CreateTerraformOptions(t) 43 | // prepare list of items to check 44 | assertList := []testskeleton.AssertExpression{} 45 | // plan test infrastructure and verify outputs 46 | testskeleton.PlanInfraCheckErrors(t, terraformOptions, assertList, "No errors are expected") 47 | } 48 | 49 | func TestApply(t *testing.T) { 50 | // define options for Terraform 51 | terraformOptions := CreateTerraformOptions(t) 52 | // prepare list of items to check 53 | assertList := []testskeleton.AssertExpression{} 54 | // deploy test infrastructure and verify outputs and check if there are no planned changes after deployment 55 | testskeleton.DeployInfraCheckOutputs(t, terraformOptions, assertList) 56 | } 57 | 58 | func TestIdempotence(t *testing.T) { 59 | // define options for Terraform 60 | terraformOptions := CreateTerraformOptions(t) 61 | // prepare list of items to check 62 | assertList := []testskeleton.AssertExpression{} 63 | // deploy test infrastructure and verify outputs and check if there are no planned changes after deployment 64 | testskeleton.DeployInfraCheckOutputsVerifyChanges(t, terraformOptions, assertList) 65 | } -------------------------------------------------------------------------------- /examples/vpc_peering_common_with_autoscale/main_test.go: -------------------------------------------------------------------------------- 1 | package vpc_peering_common_with_autoscale 2 | 3 | import ( 4 | "testing" 5 | "log" 6 | 7 | "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" 8 | "github.com/gruntwork-io/terratest/modules/logger" 9 | "github.com/gruntwork-io/terratest/modules/terraform" 10 | ) 11 | 12 | func CreateTerraformOptions(t *testing.T) *terraform.Options { 13 | varsInfo, err := testskeleton.GenerateTerraformVarsInfo("gcp") 14 | if err != nil { 15 | // Handle the error 16 | log.Fatalf("Error generating terraform vars info: %v", err) 17 | } 18 | 19 | // define options for Terraform 20 | terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ 21 | TerraformDir: ".", 22 | VarFiles: []string{"example.tfvars"}, 23 | Vars: map[string]interface{}{ 24 | "name_prefix": varsInfo.NamePrefix, 25 | "project": varsInfo.GoogleProjectId, 26 | }, 27 | Logger: logger.Default, 28 | Lock: true, 29 | Upgrade: true, 30 | SetVarsAfterVarFiles: true, 31 | }) 32 | 33 | return terraformOptions 34 | } 35 | 36 | func TestValidate(t *testing.T) { 37 | testskeleton.ValidateCode(t, nil) 38 | } 39 | 40 | func TestPlan(t *testing.T) { 41 | // define options for Terraform 42 | terraformOptions := CreateTerraformOptions(t) 43 | // prepare list of items to check 44 | assertList := []testskeleton.AssertExpression{} 45 | // plan test infrastructure and verify outputs 46 | testskeleton.PlanInfraCheckErrors(t, terraformOptions, assertList, "No errors are expected") 47 | } 48 | 49 | func TestApply(t *testing.T) { 50 | // define options for Terraform 51 | terraformOptions := CreateTerraformOptions(t) 52 | // prepare list of items to check 53 | assertList := []testskeleton.AssertExpression{} 54 | // deploy test infrastructure and verify outputs and check if there are no planned changes after deployment 55 | testskeleton.DeployInfraCheckOutputs(t, terraformOptions, assertList) 56 | } 57 | 58 | func TestIdempotence(t *testing.T) { 59 | // define options for Terraform 60 | terraformOptions := CreateTerraformOptions(t) 61 | // prepare list of items to check 62 | assertList := []testskeleton.AssertExpression{} 63 | // deploy test infrastructure and verify outputs and check if there are no planned changes after deployment 64 | testskeleton.DeployInfraCheckOutputsVerifyChanges(t, terraformOptions, assertList) 65 | } 66 | -------------------------------------------------------------------------------- /examples/vpc_peering_common_with_network_tags/main_test.go: -------------------------------------------------------------------------------- 1 | package vpc_peering_common_with_network_tags 2 | 3 | import ( 4 | "testing" 5 | "log" 6 | 7 | "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" 8 | "github.com/gruntwork-io/terratest/modules/logger" 9 | "github.com/gruntwork-io/terratest/modules/terraform" 10 | ) 11 | 12 | func CreateTerraformOptions(t *testing.T) *terraform.Options { 13 | varsInfo, err := testskeleton.GenerateTerraformVarsInfo("gcp") 14 | if err != nil { 15 | // Handle the error 16 | log.Fatalf("Error generating terraform vars info: %v", err) 17 | } 18 | 19 | // define options for Terraform 20 | terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ 21 | TerraformDir: ".", 22 | VarFiles: []string{"example.tfvars"}, 23 | Vars: map[string]interface{}{ 24 | "name_prefix": varsInfo.NamePrefix, 25 | "project": varsInfo.GoogleProjectId, 26 | }, 27 | Logger: logger.Default, 28 | Lock: true, 29 | Upgrade: true, 30 | SetVarsAfterVarFiles: true, 31 | }) 32 | 33 | return terraformOptions 34 | } 35 | 36 | func TestValidate(t *testing.T) { 37 | testskeleton.ValidateCode(t, nil) 38 | } 39 | 40 | func TestPlan(t *testing.T) { 41 | // define options for Terraform 42 | terraformOptions := CreateTerraformOptions(t) 43 | // prepare list of items to check 44 | assertList := []testskeleton.AssertExpression{} 45 | // plan test infrastructure and verify outputs 46 | testskeleton.PlanInfraCheckErrors(t, terraformOptions, assertList, "No errors are expected") 47 | } 48 | 49 | func TestApply(t *testing.T) { 50 | // define options for Terraform 51 | terraformOptions := CreateTerraformOptions(t) 52 | // prepare list of items to check 53 | assertList := []testskeleton.AssertExpression{} 54 | // deploy test infrastructure and verify outputs and check if there are no planned changes after deployment 55 | testskeleton.DeployInfraCheckOutputs(t, terraformOptions, assertList) 56 | } 57 | 58 | func TestIdempotence(t *testing.T) { 59 | // define options for Terraform 60 | terraformOptions := CreateTerraformOptions(t) 61 | // prepare list of items to check 62 | assertList := []testskeleton.AssertExpression{} 63 | // deploy test infrastructure and verify outputs and check if there are no planned changes after deployment 64 | testskeleton.DeployInfraCheckOutputsVerifyChanges(t, terraformOptions, assertList) 65 | } -------------------------------------------------------------------------------- /examples/vpc_peering_dedicated_with_autoscale/main_test.go: -------------------------------------------------------------------------------- 1 | package vpc_peering_dedicated_with_autoscale 2 | 3 | import ( 4 | "testing" 5 | "log" 6 | 7 | "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" 8 | "github.com/gruntwork-io/terratest/modules/logger" 9 | "github.com/gruntwork-io/terratest/modules/terraform" 10 | ) 11 | 12 | func CreateTerraformOptions(t *testing.T) *terraform.Options { 13 | varsInfo, err := testskeleton.GenerateTerraformVarsInfo("gcp") 14 | if err != nil { 15 | // Handle the error 16 | log.Fatalf("Error generating terraform vars info: %v", err) 17 | } 18 | 19 | // define options for Terraform 20 | terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ 21 | TerraformDir: ".", 22 | VarFiles: []string{"example.tfvars"}, 23 | Vars: map[string]interface{}{ 24 | "name_prefix": varsInfo.NamePrefix, 25 | "project": varsInfo.GoogleProjectId, 26 | }, 27 | Logger: logger.Default, 28 | Lock: true, 29 | Upgrade: true, 30 | SetVarsAfterVarFiles: true, 31 | }) 32 | 33 | return terraformOptions 34 | } 35 | 36 | func TestValidate(t *testing.T) { 37 | testskeleton.ValidateCode(t, nil) 38 | } 39 | 40 | func TestPlan(t *testing.T) { 41 | // define options for Terraform 42 | terraformOptions := CreateTerraformOptions(t) 43 | // prepare list of items to check 44 | assertList := []testskeleton.AssertExpression{} 45 | // plan test infrastructure and verify outputs 46 | testskeleton.PlanInfraCheckErrors(t, terraformOptions, assertList, "No errors are expected") 47 | } 48 | 49 | func TestApply(t *testing.T) { 50 | // define options for Terraform 51 | terraformOptions := CreateTerraformOptions(t) 52 | // prepare list of items to check 53 | assertList := []testskeleton.AssertExpression{} 54 | // deploy test infrastructure and verify outputs and check if there are no planned changes after deployment 55 | testskeleton.DeployInfraCheckOutputs(t, terraformOptions, assertList) 56 | } 57 | 58 | func TestIdempotence(t *testing.T) { 59 | // define options for Terraform 60 | terraformOptions := CreateTerraformOptions(t) 61 | // prepare list of items to check 62 | assertList := []testskeleton.AssertExpression{} 63 | // deploy test infrastructure and verify outputs and check if there are no planned changes after deployment 64 | testskeleton.DeployInfraCheckOutputsVerifyChanges(t, terraformOptions, assertList) 65 | } 66 | -------------------------------------------------------------------------------- /examples/standalone_vmseries_with_metadata_bootstrap/main_test.go: -------------------------------------------------------------------------------- 1 | package standalone_vmseries_with_metadata_bootstrap 2 | 3 | import ( 4 | "testing" 5 | "log" 6 | 7 | "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" 8 | "github.com/gruntwork-io/terratest/modules/logger" 9 | "github.com/gruntwork-io/terratest/modules/terraform" 10 | ) 11 | 12 | func CreateTerraformOptions(t *testing.T) *terraform.Options { 13 | varsInfo, err := testskeleton.GenerateTerraformVarsInfo("gcp") 14 | if err != nil { 15 | // Handle the error 16 | log.Fatalf("Error generating terraform vars info: %v", err) 17 | } 18 | 19 | // define options for Terraform 20 | terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ 21 | TerraformDir: ".", 22 | VarFiles: []string{"example.tfvars"}, 23 | Vars: map[string]interface{}{ 24 | "name_prefix": varsInfo.NamePrefix, 25 | "project": varsInfo.GoogleProjectId, 26 | }, 27 | Logger: logger.Default, 28 | Lock: true, 29 | Upgrade: true, 30 | SetVarsAfterVarFiles: true, 31 | }) 32 | 33 | return terraformOptions 34 | } 35 | 36 | func TestValidate(t *testing.T) { 37 | testskeleton.ValidateCode(t, nil) 38 | } 39 | 40 | func TestPlan(t *testing.T) { 41 | // define options for Terraform 42 | terraformOptions := CreateTerraformOptions(t) 43 | // prepare list of items to check 44 | assertList := []testskeleton.AssertExpression{} 45 | // plan test infrastructure and verify outputs 46 | testskeleton.PlanInfraCheckErrors(t, terraformOptions, assertList, "No errors are expected") 47 | } 48 | 49 | func TestApply(t *testing.T) { 50 | // define options for Terraform 51 | terraformOptions := CreateTerraformOptions(t) 52 | // prepare list of items to check 53 | assertList := []testskeleton.AssertExpression{} 54 | // deploy test infrastructure and verify outputs and check if there are no planned changes after deployment 55 | testskeleton.DeployInfraCheckOutputs(t, terraformOptions, assertList) 56 | } 57 | 58 | func TestIdempotence(t *testing.T) { 59 | // define options for Terraform 60 | terraformOptions := CreateTerraformOptions(t) 61 | // prepare list of items to check 62 | assertList := []testskeleton.AssertExpression{} 63 | // deploy test infrastructure and verify outputs and check if there are no planned changes after deployment 64 | testskeleton.DeployInfraCheckOutputsVerifyChanges(t, terraformOptions, assertList) 65 | } -------------------------------------------------------------------------------- /examples/standalone_vmseries_with_metadata_bootstrap/example.tfvars: -------------------------------------------------------------------------------- 1 | project = "<PROJECT_ID>" 2 | name_prefix = "" 3 | 4 | networks = { 5 | "vmseries-vpc" = { 6 | vpc_name = "firewall-vpc" 7 | create_network = true 8 | delete_default_routes_on_create = false 9 | mtu = "1460" 10 | routing_mode = "REGIONAL" 11 | subnetworks = { 12 | "vmseries-sub" = { 13 | name = "vmseries-subnet" 14 | create_subnetwork = true 15 | ip_cidr_range = "10.10.10.0/24" 16 | region = "us-central1" 17 | } 18 | } 19 | firewall_rules = { 20 | "allow-vmseries-ingress" = { 21 | name = "vmseries-mgmt" 22 | source_ranges = ["1.1.1.1/32"] # Replace 1.1.1.1/32 with your own souurce IP address for management purposes. 23 | priority = "1000" 24 | allowed_protocol = "all" 25 | allowed_ports = [] 26 | } 27 | } 28 | } 29 | } 30 | 31 | vmseries = { 32 | "fw-vmseries-01" = { 33 | name = "fw-vmseries-01" 34 | zone = "us-central1-b" 35 | vmseries_image = "vmseries-flex-byol-1022h2" 36 | ssh_keys = "admin:<YOUR_SSH_KEY>" 37 | machine_type = "n2-standard-4" 38 | min_cpu_platform = "Intel Cascade Lake" 39 | tags = ["vmseries"] 40 | scopes = [ 41 | "https://www.googleapis.com/auth/compute.readonly", 42 | "https://www.googleapis.com/auth/cloud.useraccounts.readonly", 43 | "https://www.googleapis.com/auth/devstorage.read_only", 44 | "https://www.googleapis.com/auth/logging.write", 45 | "https://www.googleapis.com/auth/monitoring.write", 46 | ] 47 | bootstrap_options = { 48 | panorama-server = "1.1.1.1" # Modify this value as per deployment requirements 49 | dns-primary = "8.8.8.8" # Modify this value as per deployment requirements 50 | dns-secondary = "8.8.4.4" # Modify this value as per deployment requirements 51 | } 52 | named_ports = [ 53 | { 54 | name = "http" 55 | port = 80 56 | }, 57 | { 58 | name = "https" 59 | port = 443 60 | } 61 | ] 62 | network_interfaces = [ 63 | { 64 | vpc_network_key = "vmseries-vpc" 65 | subnetwork_key = "vmseries-sub" 66 | private_ip = "10.10.10.2" 67 | create_public_ip = true 68 | } 69 | ] 70 | } 71 | } -------------------------------------------------------------------------------- /modules/vpc-peering/variables.tf: -------------------------------------------------------------------------------- 1 | variable "local_network" { 2 | description = "Self-link or id of the first network (local) in pair." 3 | type = string 4 | } 5 | 6 | variable "peer_network" { 7 | description = "Self-link or id of the second network (peer) in pair." 8 | type = string 9 | } 10 | 11 | variable "local_peering_name" { 12 | description = "Name for 'local->peer' direction peering resource. If not specified defaults to `<name_prefix><local network name>-<peer network name>`." 13 | default = null 14 | type = string 15 | } 16 | 17 | variable "peer_peering_name" { 18 | description = "Name for 'peer->local' direction peering resource. If not specified defaults to `<name_prefix><peer network name>-<local network name>`." 19 | default = null 20 | type = string 21 | } 22 | 23 | variable "name_prefix" { 24 | description = "Optional prefix for auto-generated peering resource names." 25 | default = "" 26 | type = string 27 | } 28 | 29 | variable "local_export_custom_routes" { 30 | description = "Export custom routes setting for 'local->peer' direction." 31 | default = false 32 | type = bool 33 | } 34 | 35 | variable "local_import_custom_routes" { 36 | description = "Import custom routes setting for 'local->peer' direction." 37 | default = false 38 | type = bool 39 | } 40 | 41 | variable "local_export_subnet_routes_with_public_ip" { 42 | description = "Export subnet routes with public IP setting for 'local->peer' direction." 43 | default = false 44 | type = bool 45 | } 46 | 47 | variable "local_import_subnet_routes_with_public_ip" { 48 | description = "Import subnet routes with public IP setting for 'local->peer' direction." 49 | default = false 50 | type = bool 51 | } 52 | 53 | variable "peer_export_custom_routes" { 54 | description = "Export custom routes setting for 'peer->local' direction." 55 | default = false 56 | type = bool 57 | } 58 | 59 | variable "peer_import_custom_routes" { 60 | description = "Import custom routes setting for 'peer->local' direction." 61 | default = false 62 | type = bool 63 | } 64 | 65 | variable "peer_export_subnet_routes_with_public_ip" { 66 | description = "Export subnet routes with public IP setting for 'peer->local' direction." 67 | default = false 68 | type = bool 69 | } 70 | 71 | variable "peer_import_subnet_routes_with_public_ip" { 72 | description = "Import subnet routes with public IP setting for 'peer->local' direction." 73 | default = false 74 | type = bool 75 | } 76 | -------------------------------------------------------------------------------- /modules/vpc/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | subnetworks_existing = { 3 | for k, v in var.subnetworks 4 | : k => v 5 | if try(v.create_subnetwork == false, false) 6 | } 7 | 8 | // Some subnetworks need to be created: 9 | subnetworks_to_create = { 10 | for k, v in var.subnetworks 11 | : k => v 12 | if !(try(v.create_subnetwork == false, false)) 13 | } 14 | } 15 | 16 | data "google_compute_network" "this" { 17 | count = var.create_network == true ? 0 : 1 18 | 19 | name = var.name 20 | project = var.project_id 21 | } 22 | 23 | resource "google_compute_network" "this" { 24 | count = var.create_network == true ? 1 : 0 25 | 26 | name = var.name 27 | project = var.project_id 28 | delete_default_routes_on_create = var.delete_default_routes_on_create 29 | mtu = var.mtu 30 | auto_create_subnetworks = false 31 | routing_mode = var.routing_mode 32 | } 33 | 34 | data "google_compute_subnetwork" "this" { 35 | for_each = local.subnetworks_existing 36 | 37 | name = each.value.name 38 | project = var.project_id 39 | region = each.value.region 40 | } 41 | 42 | resource "google_compute_subnetwork" "this" { 43 | for_each = local.subnetworks_to_create 44 | 45 | name = each.value.name 46 | ip_cidr_range = each.value.ip_cidr_range 47 | network = try(data.google_compute_network.this[0].self_link, google_compute_network.this[0].self_link) 48 | region = each.value.region 49 | project = var.project_id 50 | } 51 | 52 | resource "google_compute_firewall" "this" { 53 | for_each = var.firewall_rules 54 | 55 | name = "${each.value.name}-ingress" 56 | network = try(data.google_compute_network.this[0].self_link, google_compute_network.this[0].self_link) 57 | direction = "INGRESS" 58 | source_ranges = each.value.source_ranges 59 | source_tags = each.value.source_tags 60 | source_service_accounts = each.value.source_service_accounts 61 | project = var.project_id 62 | priority = each.value.priority 63 | target_service_accounts = each.value.target_service_accounts 64 | target_tags = each.value.target_tags 65 | 66 | 67 | allow { 68 | protocol = each.value.allowed_protocol 69 | ports = each.value.allowed_ports 70 | } 71 | 72 | dynamic "log_config" { 73 | for_each = compact(try([each.value.log_metadata], [])) 74 | 75 | content { 76 | metadata = log_config.value 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /modules/bootstrap/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | bootstrap_filenames = var.bootstrap_files_dir != null ? { for f in fileset(var.bootstrap_files_dir, "**") : f => "${var.bootstrap_files_dir}/${f}" } : {} 3 | # invert var.files map 4 | inverted_files = { for k, v in var.files : v => k } 5 | inverted_filenames = merge(local.bootstrap_filenames, local.inverted_files) 6 | # invert local.filenames map 7 | filenames = { for k, v in local.inverted_filenames : v => k } 8 | } 9 | resource "random_string" "randomstring" { 10 | length = 10 11 | min_lower = 10 12 | special = false 13 | } 14 | 15 | resource "google_storage_bucket" "this" { 16 | name = join("", [var.name_prefix, random_string.randomstring.result]) 17 | force_destroy = true 18 | uniform_bucket_level_access = true 19 | location = var.location 20 | 21 | versioning { 22 | enabled = true 23 | } 24 | } 25 | 26 | locals { 27 | folders = length(var.folders) == 0 ? [""] : var.folders 28 | } 29 | 30 | 31 | resource "google_storage_bucket_object" "config_empty" { 32 | for_each = toset(local.folders) 33 | 34 | name = each.value != "" ? "${each.value}/config/" : "config/" 35 | content = "config/" 36 | bucket = google_storage_bucket.this.name 37 | } 38 | 39 | resource "google_storage_bucket_object" "content_empty" { 40 | for_each = toset(local.folders) 41 | 42 | name = each.value != "" ? "${each.value}/content/" : "content/" 43 | content = "content/" 44 | bucket = google_storage_bucket.this.name 45 | } 46 | 47 | resource "google_storage_bucket_object" "license_empty" { 48 | for_each = toset(local.folders) 49 | 50 | name = each.value != "" ? "${each.value}/license/" : "license/" 51 | content = "license/" 52 | bucket = google_storage_bucket.this.name 53 | } 54 | 55 | resource "google_storage_bucket_object" "software_empty" { 56 | for_each = toset(local.folders) 57 | 58 | name = each.value != "" ? "${each.value}/software/" : "software/" 59 | content = "software/" 60 | bucket = google_storage_bucket.this.name 61 | } 62 | 63 | resource "google_storage_bucket_object" "file" { 64 | for_each = local.filenames 65 | 66 | name = each.value 67 | source = each.key 68 | bucket = google_storage_bucket.this.name 69 | } 70 | 71 | data "google_compute_default_service_account" "this" {} 72 | 73 | resource "google_storage_bucket_iam_member" "member" { 74 | bucket = google_storage_bucket.this.name 75 | role = "roles/storage.objectViewer" 76 | member = "serviceAccount:${var.service_account != null ? var.service_account : data.google_compute_default_service_account.this.email}" 77 | } -------------------------------------------------------------------------------- /modules/panorama/main.tf: -------------------------------------------------------------------------------- 1 | data "google_compute_image" "this" { 2 | count = var.custom_image != null ? 0 : 1 3 | 4 | project = "paloaltonetworksgcp-public" 5 | name = var.panorama_version 6 | } 7 | 8 | # Permanent private address, not ephemeral, because the managed firewalls keep it saved. 9 | resource "google_compute_address" "private" { 10 | name = "${var.name}-private" 11 | project = var.project 12 | region = var.region 13 | subnetwork = var.subnet 14 | address = try(var.private_static_ip, null) 15 | address_type = "INTERNAL" 16 | } 17 | 18 | # Permanent public address, not ephemeral. 19 | resource "google_compute_address" "public" { 20 | count = var.attach_public_ip ? 1 : 0 21 | 22 | name = "${var.name}-public" 23 | project = var.project 24 | region = var.region 25 | address = try(var.public_static_ip, null) 26 | } 27 | 28 | resource "google_compute_disk" "this" { 29 | for_each = { for k, v in var.log_disks : k => v } 30 | 31 | name = each.value.name 32 | project = var.project 33 | zone = var.zone 34 | type = each.value.type 35 | size = each.value.size 36 | } 37 | 38 | resource "google_compute_instance" "this" { 39 | name = var.name 40 | zone = var.zone 41 | machine_type = var.machine_type 42 | min_cpu_platform = var.min_cpu_platform 43 | deletion_protection = var.deletion_protection 44 | labels = var.labels 45 | tags = var.tags 46 | project = var.project 47 | can_ip_forward = false 48 | allow_stopping_for_update = true 49 | 50 | metadata = merge({ 51 | serial-port-enable = true 52 | ssh-keys = var.ssh_keys 53 | }, var.metadata) 54 | 55 | service_account { 56 | email = var.service_account 57 | scopes = var.scopes 58 | } 59 | 60 | network_interface { 61 | 62 | dynamic "access_config" { 63 | for_each = var.attach_public_ip ? [""] : [] 64 | content { 65 | nat_ip = google_compute_address.public[0].address 66 | } 67 | } 68 | 69 | network_ip = google_compute_address.private.address 70 | subnetwork = var.subnet 71 | } 72 | 73 | boot_disk { 74 | initialize_params { 75 | image = coalesce(var.custom_image, try(data.google_compute_image.this[0].id, null)) 76 | size = var.disk_size 77 | type = var.disk_type 78 | } 79 | } 80 | 81 | dynamic "attached_disk" { 82 | for_each = google_compute_disk.this 83 | content { 84 | source = google_compute_disk.this[attached_disk.key].id 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /.github/workflows/help-command.yml: -------------------------------------------------------------------------------- 1 | name: ChatOPS Help 2 | run-name: "Display ChatOPS help (#${{ github.event.inputs.pr-id }}) ${{ github.event.inputs.pr-title }}" 3 | 4 | on: 5 | workflow_dispatch: 6 | inputs: 7 | pr-id: 8 | description: ID of the PR that triggered this workflow 9 | type: string 10 | required: true 11 | pr-title: 12 | description: Title of the PR that triggered this workflow 13 | type: string 14 | required: true 15 | comment-id: 16 | description: 'The comment-id of the slash command' 17 | type: string 18 | required: true 19 | branch: 20 | description: Branch on which the tests should run 21 | type: string 22 | default: main 23 | 24 | jobs: 25 | help: 26 | name: Add help comment to originating PR 27 | permissions: 28 | contents: read 29 | pull-requests: write 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: add help comment 33 | uses: peter-evans/create-or-update-comment@v3 34 | with: 35 | comment-id: ${{ inputs.comment-id }} 36 | issue-number: ${{ inputs.pr-id }} 37 | body: | 38 | 39 | ## ChatOPS built in help: 40 | 41 | Currently supported commands include: 42 | 43 | * `/sca` - run all SCA tests via `pre-commit` 44 | * `/validate` - run `terraform validate` 45 | * `/plan` - plan the infrastructure (only examples) 46 | * `/apply` - deploy the infrastructure and destroy afterwards (only examples) 47 | * `/idempotence` - test idempotence: deploy, plan and destroy afterwards (only examples). 48 | 49 | The 1<sup>st</sup> command does not take arguments, the remaining take two: 50 | 51 | * `paths` - a space delimitied list of module paths 52 | * `tf_version` - (optional, defaults to the latest available) a space delimited list of Terraform versions to test the infrastrucure against. 53 | 54 | Examples: 55 | 56 | ```bash 57 | # run idempotence tests on listed modules with Terraform versions: 1.2 (latest patch available), 1.4 (latest patch available), 1.5.4. 58 | /idempotence paths="examples/common_vmseries examples/panorama_standalone" tf_version="1.2 1.4 1.5.4" 59 | ``` 60 | 61 | ```bash 62 | # run validation tests with the latest available Terraform version on listed modules. 63 | /validate paths="modules/vmseries modules/vnet examples/dedicated_vmseries" 64 | ``` 65 | 66 | reactions: '+1' 67 | reactions-edit-mode: replace -------------------------------------------------------------------------------- /modules/lb_internal/main.tf: -------------------------------------------------------------------------------- 1 | resource "google_compute_health_check" "this" { 2 | name = "${var.name}-${var.region}-check-tcp${var.health_check_port}" 3 | project = var.project 4 | 5 | tcp_health_check { 6 | port = var.health_check_port 7 | } 8 | } 9 | 10 | resource "google_compute_region_backend_service" "this" { 11 | provider = google-beta 12 | 13 | name = var.name 14 | network = var.network 15 | project = var.project 16 | region = var.region 17 | 18 | health_checks = [var.health_check != null ? var.health_check : google_compute_health_check.this.self_link] 19 | session_affinity = var.session_affinity 20 | timeout_sec = var.timeout_sec 21 | connection_draining_timeout_sec = var.connection_draining_timeout_sec 22 | 23 | dynamic "backend" { 24 | for_each = var.backends 25 | content { 26 | group = backend.value 27 | failover = false 28 | } 29 | } 30 | 31 | dynamic "backend" { 32 | for_each = var.failover_backends 33 | content { 34 | group = backend.value 35 | failover = true 36 | } 37 | } 38 | 39 | # This feature requires beta provider as of 2023-03-16 40 | dynamic "connection_tracking_policy" { 41 | for_each = var.connection_tracking_policy != null ? ["this"] : [] 42 | content { 43 | tracking_mode = try(var.connection_tracking_policy.mode, null) 44 | idle_timeout_sec = try(var.connection_tracking_policy.idle_timeout_sec, null) 45 | connection_persistence_on_unhealthy_backends = try(var.connection_tracking_policy.persistence_on_unhealthy_backends, null) 46 | } 47 | } 48 | 49 | dynamic "failover_policy" { 50 | for_each = var.disable_connection_drain_on_failover != null || var.drop_traffic_if_unhealthy != null || var.failover_ratio != null ? ["one"] : [] 51 | 52 | content { 53 | disable_connection_drain_on_failover = var.disable_connection_drain_on_failover 54 | drop_traffic_if_unhealthy = var.drop_traffic_if_unhealthy 55 | failover_ratio = var.failover_ratio 56 | } 57 | } 58 | } 59 | 60 | resource "google_compute_forwarding_rule" "this" { 61 | name = var.name 62 | project = var.project 63 | region = var.region 64 | 65 | load_balancing_scheme = "INTERNAL" 66 | ip_address = var.ip_address 67 | ip_protocol = var.ip_protocol 68 | all_ports = var.all_ports 69 | ports = var.ports 70 | subnetwork = var.subnetwork 71 | allow_global_access = var.allow_global_access 72 | backend_service = google_compute_region_backend_service.this.self_link 73 | } 74 | -------------------------------------------------------------------------------- /.github/workflows/sca-command.yml: -------------------------------------------------------------------------------- 1 | name: ChatOPS SCA 2 | run-name: "On demand SCA test for PR - (#${{ github.event.inputs.pr-id }}) ${{ github.event.inputs.pr-title }}" 3 | 4 | permissions: 5 | contents: read 6 | 7 | on: 8 | workflow_dispatch: 9 | inputs: 10 | pr-id: 11 | description: ID of the PR that triggered this workflow 12 | type: string 13 | required: true 14 | pr-title: 15 | description: Title of the PR that triggered this workflow 16 | type: string 17 | required: true 18 | comment-id: 19 | description: 'The comment-id of the slash command' 20 | type: string 21 | required: true 22 | branch: 23 | description: Branch on which the tests should run 24 | type: string 25 | default: main 26 | 27 | jobs: 28 | init: 29 | name: Add a comment to originating PR with job ID 30 | permissions: 31 | contents: read 32 | pull-requests: write 33 | runs-on: ubuntu-latest 34 | outputs: 35 | paths: ${{ steps.paths_reformat.outputs.paths }} 36 | steps: 37 | - name: add comment 38 | uses: peter-evans/create-or-update-comment@v3 39 | with: 40 | comment-id: ${{ inputs.comment-id }} 41 | issue-number: ${{ inputs.pr-id }} 42 | body: | 43 | > Testing job ID: [${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) 44 | 45 | - name: reformat paths input property 46 | id: paths_reformat 47 | env: 48 | IN_PATHS: ${{ inputs.paths }} 49 | run: echo "paths=$(echo $IN_PATHS | tr " " "," )" >> $GITHUB_OUTPUT 50 | 51 | test: 52 | name: Run SCA test 53 | needs: init 54 | permissions: 55 | contents: read 56 | uses: PaloAltoNetworks/terraform-modules-vmseries-ci-workflows/.github/workflows/_pre_commit.yml@v2.3 57 | secrets: inherit 58 | with: 59 | pre-commit-hooks: terraform_fmt terraform_docs terraform_tflint checkov 60 | branch: ${{ inputs.branch }} 61 | 62 | finish_comment_pr: 63 | name: Add a comment to originating PR 64 | needs: test 65 | if: always() 66 | permissions: 67 | contents: read 68 | pull-requests: write 69 | runs-on: ubuntu-latest 70 | steps: 71 | - name: add comment 72 | uses: peter-evans/create-or-update-comment@v3 73 | with: 74 | comment-id: ${{ inputs.comment-id }} 75 | issue-number: ${{ inputs.pr-id }} 76 | body: | 77 | > Job result: ${{ needs.test.result == 'success' && 'SUCCESS' || 'FAILURE' }} 78 | reactions: ${{ needs.test.result == 'success' && '+1' || '-1' }} 79 | reactions-edit-mode: replace -------------------------------------------------------------------------------- /modules/iam_service_account/README.md: -------------------------------------------------------------------------------- 1 | # IAM Service Account 2 | 3 | Create a dedicated IAM Service Account that will be used to run firewall instances. 4 | This module is optional - even if you don't use it, firewalls run fine on the default Google Service Account. 5 | 6 | The account produced by this module is intended to have minimal required permissions. 7 | 8 | [Google Cloud Docs](https://cloud.google.com/compute/docs/access/create-enable-service-accounts-for-instances#best_practices) 9 | 10 | ## Reference 11 | <!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> 12 | ### Requirements 13 | 14 | | Name | Version | 15 | |------|---------| 16 | | <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.3, < 2.0 | 17 | | <a name="requirement_google"></a> [google](#requirement\_google) | ~> 4.54 | 18 | 19 | ### Providers 20 | 21 | | Name | Version | 22 | |------|---------| 23 | | <a name="provider_google"></a> [google](#provider\_google) | ~> 4.54 | 24 | 25 | ### Modules 26 | 27 | No modules. 28 | 29 | ### Resources 30 | 31 | | Name | Type | 32 | |------|------| 33 | | [google_project_iam_member.this](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_member) | resource | 34 | | [google_service_account.this](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account) | resource | 35 | 36 | ### Inputs 37 | 38 | | Name | Description | Type | Default | Required | 39 | |------|-------------|------|---------|:--------:| 40 | | <a name="input_display_name"></a> [display\_name](#input\_display\_name) | n/a | `string` | `"Palo Alto Networks Firewall Service Account"` | no | 41 | | <a name="input_project_id"></a> [project\_id](#input\_project\_id) | ID of a project in which the service account will be created. | `string` | n/a | yes | 42 | | <a name="input_roles"></a> [roles](#input\_roles) | List of IAM role names, such as ["roles/compute.viewer"] or ["project/A/roles/B"]. The default list is suitable for Palo Alto Networks Firewall to run and publish custom metrics to GCP Stackdriver. | `set(string)` | <pre>[<br> "roles/compute.networkViewer",<br> "roles/logging.logWriter",<br> "roles/monitoring.metricWriter",<br> "roles/monitoring.viewer",<br> "roles/viewer",<br> "roles/stackdriver.accounts.viewer",<br> "roles/stackdriver.resourceMetadata.writer"<br>]</pre> | no | 43 | | <a name="input_service_account_id"></a> [service\_account\_id](#input\_service\_account\_id) | n/a | `string` | `"The google_service_account.account_id of the created IAM account, unique string per project."` | no | 44 | 45 | ### Outputs 46 | 47 | | Name | Description | 48 | |------|-------------| 49 | | <a name="output_email"></a> [email](#output\_email) | n/a | 50 | <!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> 51 | -------------------------------------------------------------------------------- /modules/vpn/variables.tf: -------------------------------------------------------------------------------- 1 | variable "project" { 2 | default = null 3 | type = string 4 | } 5 | 6 | variable "region" { 7 | description = "Region to deploy VPN gateway in" 8 | type = string 9 | } 10 | 11 | variable "vpn_gateway_name" { 12 | description = "VPN gateway name. Gateway created by the module" 13 | type = string 14 | } 15 | 16 | variable "router_name" { 17 | description = "Cloud router name. The router is created by the module" 18 | type = string 19 | default = null 20 | } 21 | 22 | variable "network" { 23 | description = "VPC network ID that should be used for deployment" 24 | type = string 25 | } 26 | 27 | variable "labels" { 28 | description = "Labels for VPN components" 29 | type = map(string) 30 | default = {} 31 | } 32 | 33 | variable "vpn_config" { 34 | type = any 35 | description = <<-EOF 36 | VPN configuration from GCP to on-prem or from GCP to GCP. 37 | If you'd like secrets to be randomly generated set `shared_secret` to empty string (""). 38 | 39 | Example: 40 | 41 | ``` 42 | vpn_config = { 43 | router_asn = 65000 44 | local_network = "vpc-vpn" 45 | 46 | router_advertise_config = { 47 | ip_ranges = { 48 | "10.10.0.0/16" : "GCP range 1" 49 | } 50 | mode = "CUSTOM" 51 | groups = null 52 | } 53 | 54 | instances = { 55 | vpn-to-onprem = { 56 | name = "vpn-to-onprem", 57 | peer_external_gateway = { 58 | redundancy_type = "TWO_IPS_REDUNDANCY" 59 | interfaces = [{ 60 | id = 0 61 | ip_address = "1.1.1.1" 62 | }, { 63 | id = 1 64 | ip_address = "2.2.2.2" 65 | }] 66 | }, 67 | tunnels = { 68 | remote0 = { 69 | bgp_peer = { 70 | address = "169.254.1.2" 71 | asn = 65001 72 | } 73 | bgp_peer_options = null 74 | bgp_session_range = "169.254.1.1/30" 75 | ike_version = 2 76 | vpn_gateway_interface = 0 77 | peer_external_gateway_interface = 0 78 | shared_secret = "secret" 79 | } 80 | remote1 = { 81 | bgp_peer = { 82 | address = "169.254.1.6" 83 | asn = 65001 84 | } 85 | bgp_peer_options = null 86 | bgp_session_range = "169.254.1.5/30" 87 | ike_version = 2 88 | vpn_gateway_interface = 1 89 | peer_external_gateway_interface = 1 90 | shared_secret = "secret" 91 | } 92 | } 93 | } 94 | } 95 | } 96 | ``` 97 | EOF 98 | } -------------------------------------------------------------------------------- /examples/panorama_standalone/variables.tf: -------------------------------------------------------------------------------- 1 | # General 2 | variable "project" { 3 | description = "The project name to deploy the infrastructure in to." 4 | type = string 5 | default = null 6 | } 7 | variable "region" { 8 | description = "The region into which to deploy the infrastructure in to" 9 | type = string 10 | default = "us-central1" 11 | } 12 | variable "name_prefix" { 13 | description = "A string to prefix resource namings" 14 | type = string 15 | default = "" 16 | } 17 | 18 | # VPC 19 | variable "networks" { 20 | description = <<-EOF 21 | A map containing each network setting. 22 | 23 | Example of variable deployment : 24 | 25 | ``` 26 | networks = { 27 | "panorama-vpc" = { 28 | vpc_name = "firewall-vpc" 29 | create_network = true 30 | delete_default_routes_on_create = "false" 31 | mtu = "1460" 32 | routing_mode = "REGIONAL" 33 | subnetworks = { 34 | "panorama-sub" = { 35 | name = "panorama-subnet" 36 | create_subnetwork = true 37 | ip_cidr_range = "172.21.21.0/24" 38 | region = "us-central1" 39 | } 40 | } 41 | firewall_rules = { 42 | "allow-panorama-ingress" = { 43 | name = "panorama-mgmt" 44 | source_ranges = ["1.1.1.1/32", "2.2.2.2/32"] 45 | priority = "1000" 46 | allowed_protocol = "all" 47 | allowed_ports = [] 48 | } 49 | } 50 | } 51 | ``` 52 | 53 | For a full list of available configuration items - please refer to [module documentation](https://github.com/PaloAltoNetworks/terraform-google-vmseries-modules/tree/main/modules/vpc#input_networks) 54 | 55 | Multiple keys can be added and will be deployed by the code 56 | EOF 57 | } 58 | 59 | # Panorama 60 | variable "panoramas" { 61 | description = <<-EOF 62 | A map containing each panorama setting. 63 | 64 | Example of variable deployment : 65 | 66 | ``` 67 | panoramas = { 68 | "panorama-01" = { 69 | panorama_name = "panorama-01" 70 | panorama_vpc = "panorama-vpc" 71 | panorama_subnet = "panorama-subnet" 72 | panorama_version = "panorama-byol-1000" 73 | ssh_keys = "admin:PUBLIC-KEY" 74 | attach_public_ip = true 75 | private_static_ip = "172.21.21.2" 76 | } 77 | } 78 | ``` 79 | 80 | For a full list of available configuration items - please refer to [module documentation](https://github.com/PaloAltoNetworks/terraform-google-vmseries-modules/tree/main/modules/panorama#inputs) 81 | 82 | Multiple keys can be added and will be deployed by the code 83 | EOF 84 | } 85 | -------------------------------------------------------------------------------- /.github/workflows/chatops.yml: -------------------------------------------------------------------------------- 1 | name: ChatOPS dispatcher 2 | run-name: "ChatOPS bot for PR - (#${{ github.event.issue.number }}) ${{ github.event.issue.title }}" 3 | 4 | permissions: 5 | contents: read 6 | 7 | on: 8 | issue_comment: 9 | types: [created] 10 | 11 | concurrency: 12 | group: chat-${{ github.event.issue.number }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | dispatch: 17 | name: Dispatch a test job 18 | if: ${{ github.event.issue.pull_request }} 19 | runs-on: ubuntu-latest 20 | permissions: 21 | contents: read 22 | pull-requests: write 23 | steps: 24 | - name: get PR head branch 25 | uses: actions/github-script@v6 26 | id: pr 27 | with: 28 | result-encoding: string 29 | script: | 30 | let pr = await github.rest.pulls.get({ 31 | owner: context.repo.owner, 32 | repo: context.repo.repo, 33 | pull_number: context.issue.number, 34 | }) 35 | console.log(pr.data.head.ref) 36 | return pr.data.head.ref 37 | 38 | - name: Generate GitHub token 39 | id: generate-token 40 | uses: tibdex/github-app-token@v2 41 | with: 42 | app_id: ${{ secrets.CHATOPS_APP_ID }} 43 | private_key: ${{ secrets.CHATOPS_APP_PRIVATE_KEY }} 44 | installation_retrieval_mode: id 45 | installation_retrieval_payload: ${{ secrets.CHATOPS_APP_INSTALLATION_ID }} 46 | 47 | - name: "dispatch test command on branch: ${{ steps.pr.outputs.result }}" 48 | id: scd 49 | uses: peter-evans/slash-command-dispatch@v3 50 | with: 51 | token: ${{ steps.generate-token.outputs.token }} 52 | issue-type: pull-request 53 | dispatch-type: workflow 54 | permission: maintain 55 | commands: | 56 | validate 57 | plan 58 | apply 59 | idempotence 60 | sca 61 | help 62 | static-args: | 63 | comment-id=${{ github.event.comment.id }} 64 | pr-id=${{ github.event.issue.number }} 65 | pr-title=${{ github.event.issue.title }} 66 | branch=${{ steps.pr.outputs.result }} 67 | 68 | - name: Edit comment with error message 69 | if: steps.scd.outputs.error-message 70 | uses: peter-evans/create-or-update-comment@v3 71 | with: 72 | comment-id: ${{ github.event.comment.id }} 73 | body: | 74 | > ${{ steps.scd.outputs.error-message }} 75 | reactions: '-1' 76 | reactions-edit-mode: replace 77 | 78 | - name: Concurency ratio fallback 79 | if: cancelled() 80 | uses: peter-evans/create-or-update-comment@v3 81 | with: 82 | comment-id: ${{ github.event.comment.id }} 83 | body: | 84 | > ChatOPS run cancelled. 85 | > See [job run log](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details. 86 | reactions: 'confused' 87 | reactions-edit-mode: replace -------------------------------------------------------------------------------- /modules/lb_http_ext_global/main.tf: -------------------------------------------------------------------------------- 1 | resource "google_compute_global_forwarding_rule" "http" { 2 | count = var.http_forward ? 1 : 0 3 | name = "${var.name}-http" 4 | target = google_compute_target_http_proxy.default[0].self_link 5 | ip_address = google_compute_global_address.default.address 6 | port_range = "80" 7 | } 8 | 9 | resource "google_compute_global_forwarding_rule" "https" { 10 | count = var.ssl ? 1 : 0 11 | name = "${var.name}-https" 12 | target = google_compute_target_https_proxy.default[0].self_link 13 | ip_address = google_compute_global_address.default.address 14 | port_range = "443" 15 | } 16 | 17 | resource "google_compute_global_address" "default" { 18 | name = "${var.name}-address" 19 | ip_version = var.ip_version 20 | } 21 | 22 | # HTTP proxy when ssl is false 23 | resource "google_compute_target_http_proxy" "default" { 24 | count = var.http_forward ? 1 : 0 25 | name = "${var.name}-http-proxy" 26 | url_map = (var.url_map != null ? var.url_map : google_compute_url_map.default.self_link) 27 | } 28 | 29 | # HTTPS proxy when ssl is true 30 | resource "google_compute_target_https_proxy" "default" { 31 | count = var.ssl ? 1 : 0 32 | name = "${var.name}-https-proxy" 33 | url_map = (var.url_map != null ? var.url_map : google_compute_url_map.default.self_link) 34 | ssl_certificates = compact(concat(var.ssl_certificates, google_compute_ssl_certificate.default[*].self_link, ), ) 35 | } 36 | 37 | resource "google_compute_ssl_certificate" "default" { 38 | count = var.ssl && !var.use_ssl_certificates ? 1 : 0 39 | name_prefix = "${var.name}-certificate" 40 | private_key = var.private_key 41 | certificate = var.certificate 42 | 43 | lifecycle { 44 | create_before_destroy = true 45 | } 46 | } 47 | 48 | resource "google_compute_url_map" "default" { 49 | name = var.name 50 | default_service = google_compute_backend_service.default.self_link 51 | } 52 | 53 | resource "google_compute_backend_service" "default" { 54 | name = var.name 55 | port_name = var.backend_port_name 56 | protocol = var.backend_protocol 57 | custom_request_headers = var.custom_request_headers 58 | timeout_sec = var.timeout_sec 59 | dynamic "backend" { 60 | for_each = var.backend_groups 61 | content { 62 | group = backend.value 63 | balancing_mode = var.balancing_mode 64 | capacity_scaler = var.capacity_scaler 65 | max_connections_per_instance = var.max_connections_per_instance 66 | max_rate_per_instance = var.max_rate_per_instance 67 | max_utilization = var.max_utilization 68 | } 69 | } 70 | health_checks = [google_compute_health_check.default.self_link] 71 | security_policy = var.security_policy 72 | enable_cdn = var.cdn 73 | } 74 | 75 | resource "google_compute_health_check" "default" { 76 | name = coalesce(var.health_check_name, "${var.name}-healthcheck") 77 | tcp_health_check { 78 | port = var.health_check_port 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PaloAltoNetworks/terraform-google-vmseries-modules 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton v1.1.0 7 | github.com/gruntwork-io/terratest v0.43.6 8 | ) 9 | 10 | require ( 11 | cloud.google.com/go v0.110.2 // indirect 12 | cloud.google.com/go/compute v1.19.3 // indirect 13 | cloud.google.com/go/compute/metadata v0.2.3 // indirect 14 | cloud.google.com/go/iam v1.1.0 // indirect 15 | cloud.google.com/go/storage v1.31.0 // indirect 16 | github.com/agext/levenshtein v1.2.3 // indirect 17 | github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect 18 | github.com/aws/aws-sdk-go v1.44.122 // indirect 19 | github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 22 | github.com/golang/protobuf v1.5.3 // indirect 23 | github.com/google/go-cmp v0.5.9 // indirect 24 | github.com/google/s2a-go v0.1.4 // indirect 25 | github.com/google/uuid v1.3.0 // indirect 26 | github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect 27 | github.com/googleapis/gax-go/v2 v2.11.0 // indirect 28 | github.com/hashicorp/errwrap v1.0.0 // indirect 29 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 30 | github.com/hashicorp/go-getter v1.7.1 // indirect 31 | github.com/hashicorp/go-multierror v1.1.0 // indirect 32 | github.com/hashicorp/go-safetemp v1.0.0 // indirect 33 | github.com/hashicorp/go-version v1.6.0 // indirect 34 | github.com/hashicorp/hcl/v2 v2.9.1 // indirect 35 | github.com/hashicorp/terraform-json v0.17.1 // indirect 36 | github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a // indirect 37 | github.com/jmespath/go-jmespath v0.4.0 // indirect 38 | github.com/klauspost/compress v1.15.11 // indirect 39 | github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326 // indirect 40 | github.com/mitchellh/go-homedir v1.1.0 // indirect 41 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 42 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 43 | github.com/pmezard/go-difflib v1.0.0 // indirect 44 | github.com/stretchr/testify v1.8.4 // indirect 45 | github.com/tmccombs/hcl2json v0.3.3 // indirect 46 | github.com/ulikunitz/xz v0.5.10 // indirect 47 | github.com/zclconf/go-cty v1.13.2 // indirect 48 | go.opencensus.io v0.24.0 // indirect 49 | golang.org/x/crypto v0.9.0 // indirect 50 | golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect 51 | golang.org/x/net v0.10.0 // indirect 52 | golang.org/x/oauth2 v0.8.0 // indirect 53 | golang.org/x/sys v0.8.0 // indirect 54 | golang.org/x/text v0.9.0 // indirect 55 | golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect 56 | google.golang.org/api v0.126.0 // indirect 57 | google.golang.org/appengine v1.6.7 // indirect 58 | google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect 59 | google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect 60 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect 61 | google.golang.org/grpc v1.55.0 // indirect 62 | google.golang.org/protobuf v1.31.0 // indirect 63 | gopkg.in/yaml.v3 v3.0.1 // indirect 64 | ) 65 | -------------------------------------------------------------------------------- /modules/vpc-peering/README.md: -------------------------------------------------------------------------------- 1 | # VPC peering 2 | 3 | The module allows to create VPC peering between two networks in both directions. 4 | 5 | By default, no routes are exported/imported for each direction, every option has to be explicitely enabled by setting appropriate value to `true`. 6 | 7 | ## Reference 8 | <!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> 9 | ### Requirements 10 | 11 | | Name | Version | 12 | |------|---------| 13 | | <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.3, < 2.0 | 14 | 15 | ### Providers 16 | 17 | | Name | Version | 18 | |------|---------| 19 | | <a name="provider_google"></a> [google](#provider\_google) | n/a | 20 | 21 | ### Modules 22 | 23 | No modules. 24 | 25 | ### Resources 26 | 27 | | Name | Type | 28 | |------|------| 29 | | [google_compute_network_peering.local](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_network_peering) | resource | 30 | | [google_compute_network_peering.peer](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_network_peering) | resource | 31 | 32 | ### Inputs 33 | 34 | | Name | Description | Type | Default | Required | 35 | |------|-------------|------|---------|:--------:| 36 | | <a name="input_local_export_custom_routes"></a> [local\_export\_custom\_routes](#input\_local\_export\_custom\_routes) | Export custom routes setting for 'local->peer' direction. | `bool` | `false` | no | 37 | | <a name="input_local_export_subnet_routes_with_public_ip"></a> [local\_export\_subnet\_routes\_with\_public\_ip](#input\_local\_export\_subnet\_routes\_with\_public\_ip) | Export subnet routes with public IP setting for 'local->peer' direction. | `bool` | `false` | no | 38 | | <a name="input_local_import_custom_routes"></a> [local\_import\_custom\_routes](#input\_local\_import\_custom\_routes) | Import custom routes setting for 'local->peer' direction. | `bool` | `false` | no | 39 | | <a name="input_local_import_subnet_routes_with_public_ip"></a> [local\_import\_subnet\_routes\_with\_public\_ip](#input\_local\_import\_subnet\_routes\_with\_public\_ip) | Import subnet routes with public IP setting for 'local->peer' direction. | `bool` | `false` | no | 40 | | <a name="input_local_network"></a> [local\_network](#input\_local\_network) | Self-link or id of the first network (local) in pair. | `string` | n/a | yes | 41 | | <a name="input_local_peering_name"></a> [local\_peering\_name](#input\_local\_peering\_name) | Name for 'local->peer' direction peering resource. If not specified defaults to `<name_prefix><local network name>-<peer network name>`. | `string` | `null` | no | 42 | | <a name="input_name_prefix"></a> [name\_prefix](#input\_name\_prefix) | Optional prefix for auto-generated peering resource names. | `string` | `""` | no | 43 | | <a name="input_peer_export_custom_routes"></a> [peer\_export\_custom\_routes](#input\_peer\_export\_custom\_routes) | Export custom routes setting for 'peer->local' direction. | `bool` | `false` | no | 44 | | <a name="input_peer_export_subnet_routes_with_public_ip"></a> [peer\_export\_subnet\_routes\_with\_public\_ip](#input\_peer\_export\_subnet\_routes\_with\_public\_ip) | Export subnet routes with public IP setting for 'peer->local' direction. | `bool` | `false` | no | 45 | | <a name="input_peer_import_custom_routes"></a> [peer\_import\_custom\_routes](#input\_peer\_import\_custom\_routes) | Import custom routes setting for 'peer->local' direction. | `bool` | `false` | no | 46 | | <a name="input_peer_import_subnet_routes_with_public_ip"></a> [peer\_import\_subnet\_routes\_with\_public\_ip](#input\_peer\_import\_subnet\_routes\_with\_public\_ip) | Import subnet routes with public IP setting for 'peer->local' direction. | `bool` | `false` | no | 47 | | <a name="input_peer_network"></a> [peer\_network](#input\_peer\_network) | Self-link or id of the second network (peer) in pair. | `string` | n/a | yes | 48 | | <a name="input_peer_peering_name"></a> [peer\_peering\_name](#input\_peer\_peering\_name) | Name for 'peer->local' direction peering resource. If not specified defaults to `<name_prefix><peer network name>-<local network name>`. | `string` | `null` | no | 49 | 50 | ### Outputs 51 | 52 | No outputs. 53 | <!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!WARNING] 2 | > This repository is now considered archived, and all future development will take place at our new location. For more details see https://github.com/PaloAltoNetworks/terraform-google-vmseries-modules/issues/236 3 | 4 | > [!IMPORTANT] 5 | > #### New Modules 6 | > - GitHub - https://github.com/PaloAltoNetworks/terraform-google-swfw-modules 7 | > - Terraform Registry - https://registry.terraform.io/modules/PaloAltoNetworks/swfw-modules/google/latest 8 | 9 | ![GitHub release (latest by date)](https://img.shields.io/github/v/release/PaloAltoNetworks/terraform-google-vmseries-modules?style=flat-square) 10 | ![GitHub](https://img.shields.io/github/license/PaloAltoNetworks/terraform-modules-vmseries-ci-workflows?style=flat-square) 11 | ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/PaloAltoNetworks/terraform-google-vmseries-modules/release_ci.yml?style=flat-square) 12 | ![GitHub issues](https://img.shields.io/github/issues/PaloAltoNetworks/terraform-google-vmseries-modules?style=flat-square) 13 | ![GitHub pull requests](https://img.shields.io/github/issues-pr/PaloAltoNetworks/terraform-google-vmseries-modules?style=flat-square) 14 | ![Terraform registry downloads total](https://img.shields.io/badge/dynamic/json?color=green&label=downloads%20total&query=data.attributes.total&url=https%3A%2F%2Fregistry.terraform.io%2Fv2%2Fmodules%2FPaloAltoNetworks%2Fvmseries-modules%2Fgoogle%2Fdownloads%2Fsummary&style=flat-square) 15 | ![Terraform registry download month](https://img.shields.io/badge/dynamic/json?color=green&label=downloads%20this%20month&query=data.attributes.month&url=https%3A%2F%2Fregistry.terraform.io%2Fv2%2Fmodules%2FPaloAltoNetworks%2Fvmseries-modules%2Fgoogle%2Fdownloads%2Fsummary&style=flat-square) 16 | 17 | # Terraform Modules for Palo Alto Networks VM-Series on Google Cloud Platform 18 | 19 | ## Overview 20 | 21 | A set of modules for using **Palo Alto Networks VM-Series firewalls** to provide control and protection 22 | to your applications running on Google Cloud Platform (GCP). It deploys VM-Series as virtual machine 23 | instances and it configures aspects such as Shared VPC connectivity, IAM access, Service Accounts, Panorama virtual 24 | machine instances, and more. 25 | 26 | The design is heavily based on the [Reference Architecture Guide for Google Cloud Platform](https://pandocs.tech/fw/160p-prime). 27 | 28 | For copyright and license see the LICENSE file. 29 | 30 | ## Structure 31 | 32 | This repository has the following directory structure: 33 | 34 | * [modules](./modules): This directory contains several standalone, reusable, production-grade Terraform modules. Each module is individually documented. 35 | * [examples](./examples): This directory shows examples of different ways to combine the modules contained in the 36 | `modules` directory. 37 | 38 | ## Compatibility 39 | 40 | The compatibility with Terraform is defined individually per each module. In general, expect the earliest compatible 41 | Terraform version to be 1.0.0 across most of the modules. 42 | <!-- [FUTURE] If you need to stay on Terraform 0.15.3 and need to use these modules, the recommended last compatible release is 1.2.3. --> 43 | 44 | ## Roadmap 45 | 46 | We are maintaining a [public roadmap](https://github.com/orgs/PaloAltoNetworks/projects/33/views/8) to help users understand when we will release new features, bug fixes and enhancements. 47 | 48 | ## Versioning 49 | 50 | These modules follow the principles of [Semantic Versioning](http://semver.org/). You can find each new release, 51 | along with the changelog, on the GitHub [Releases](https://github.com/PaloAltoNetworks/terraform-google-vmseries-modules/releases) page. 52 | 53 | ## Getting Help 54 | 55 | [Open an issue](https://github.com/PaloAltoNetworks/terraform-google-vmseries-modules/issues) on Github. 56 | 57 | ## Contributing 58 | 59 | Contributions are welcome, and they are greatly appreciated! Every little bit helps, 60 | and credit will always be given. Please follow our [contributing guide](https://github.com/PaloAltoNetworks/terraform-best-practices/blob/main/CONTRIBUTING.md). 61 | 62 | <!-- ## Who maintains these modules? 63 | 64 | This repository is maintained by [Palo Alto Networks](https://www.paloaltonetworks.com/). 65 | If you're looking for commercial support or services, send an email to [address not known yet]. --> 66 | -------------------------------------------------------------------------------- /modules/lb_http_ext_global/variables.tf: -------------------------------------------------------------------------------- 1 | variable "ip_version" { 2 | description = "IP version for the Global address (IPv4 or v6) - Empty defaults to IPV4" 3 | type = string 4 | default = "" 5 | } 6 | 7 | variable "name" { 8 | description = "Name for the forwarding rule and prefix for supporting resources" 9 | type = string 10 | } 11 | 12 | variable "backend_groups" { 13 | description = "The map containing the names of instance groups (IGs) or network endpoint groups (NEGs) to serve. The IGs can be managed or unmanaged or a mix of both. All IGs must handle named port `backend_port_name`. The NEGs just handle unnamed port." 14 | default = {} 15 | type = map(string) 16 | } 17 | 18 | variable "backend_port_name" { 19 | description = "The port_name of the backend groups that this load balancer will serve (default is 'http')" 20 | default = "http" 21 | type = string 22 | } 23 | 24 | variable "backend_protocol" { 25 | description = "The protocol used to talk to the backend service" 26 | default = "HTTP" 27 | type = string 28 | } 29 | 30 | variable "health_check_name" { 31 | description = "Name for the health check. If not provided, defaults to `<var.name>-healthcheck`." 32 | default = null 33 | type = string 34 | } 35 | 36 | variable "health_check_port" { 37 | description = "TCP port to use for health check." 38 | default = 80 39 | type = number 40 | } 41 | 42 | variable "timeout_sec" { 43 | description = "Timeout to consider a connection dead, in seconds (default 30)" 44 | default = null 45 | type = number 46 | } 47 | 48 | variable "balancing_mode" { 49 | description = "" 50 | default = "RATE" 51 | type = string 52 | } 53 | 54 | variable "capacity_scaler" { 55 | description = "" 56 | default = null 57 | type = number 58 | } 59 | 60 | variable "max_connections_per_instance" { 61 | description = "" 62 | default = null 63 | type = number 64 | } 65 | 66 | variable "max_rate_per_instance" { 67 | description = "" 68 | default = null 69 | type = number 70 | } 71 | 72 | variable "max_utilization" { 73 | description = "" 74 | default = null 75 | type = number 76 | } 77 | 78 | variable "url_map" { 79 | description = "The url_map resource to use. Default is to send all traffic to first backend." 80 | type = string 81 | default = null 82 | } 83 | 84 | variable "http_forward" { 85 | description = "Set to `false` to disable HTTP port 80 forward" 86 | type = bool 87 | default = true 88 | } 89 | 90 | variable "custom_request_headers" { 91 | type = list(string) 92 | default = [] 93 | description = "(Optional) Headers that the HTTP/S load balancer should add to proxied responses." 94 | } 95 | variable "ssl" { 96 | description = "Set to `true` to enable SSL support, requires variable `ssl_certificates` - a list of self_link certs" 97 | type = bool 98 | default = false 99 | } 100 | 101 | variable "private_key" { 102 | description = "Content of the private SSL key. Required if `ssl` is `true` and `ssl_certificates` is empty." 103 | type = string 104 | default = "" 105 | } 106 | 107 | variable "certificate" { 108 | description = "Content of the SSL certificate. Required if `ssl` is `true` and `ssl_certificates` is empty." 109 | type = string 110 | default = "" 111 | } 112 | 113 | variable "use_ssl_certificates" { 114 | description = "If true, use the certificates provided by `ssl_certificates`, otherwise, create cert from `private_key` and `certificate`" 115 | type = bool 116 | default = false 117 | } 118 | 119 | variable "ssl_certificates" { 120 | description = "SSL cert self_link list. Required if `ssl` is `true` and no `private_key` and `certificate` is provided." 121 | type = list(string) 122 | default = [] 123 | } 124 | 125 | variable "security_policy" { 126 | description = "The resource URL for the security policy to associate with the backend service" 127 | type = string 128 | default = "" 129 | } 130 | 131 | variable "cdn" { 132 | description = "Set to `true` to enable cdn on backend." 133 | type = bool 134 | default = false 135 | } 136 | -------------------------------------------------------------------------------- /modules/bootstrap/README.md: -------------------------------------------------------------------------------- 1 | # Google Cloud Storage Bucket For Initial Boot Of Palo Alto Networks VM-Series 2 | 3 | ## Reference 4 | <!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> 5 | ### Requirements 6 | 7 | | Name | Version | 8 | |------|---------| 9 | | <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.3, < 2.0 | 10 | | <a name="requirement_google"></a> [google](#requirement\_google) | ~> 4.54 | 11 | 12 | ### Providers 13 | 14 | | Name | Version | 15 | |------|---------| 16 | | <a name="provider_google"></a> [google](#provider\_google) | ~> 4.54 | 17 | | <a name="provider_random"></a> [random](#provider\_random) | n/a | 18 | 19 | ### Modules 20 | 21 | No modules. 22 | 23 | ### Resources 24 | 25 | | Name | Type | 26 | |------|------| 27 | | [google_storage_bucket.this](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket) | resource | 28 | | [google_storage_bucket_iam_member.member](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket_iam_member) | resource | 29 | | [google_storage_bucket_object.config_empty](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket_object) | resource | 30 | | [google_storage_bucket_object.content_empty](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket_object) | resource | 31 | | [google_storage_bucket_object.file](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket_object) | resource | 32 | | [google_storage_bucket_object.license_empty](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket_object) | resource | 33 | | [google_storage_bucket_object.software_empty](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket_object) | resource | 34 | | [random_string.randomstring](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource | 35 | | [google_compute_default_service_account.this](https://registry.terraform.io/providers/hashicorp/google/latest/docs/data-sources/compute_default_service_account) | data source | 36 | 37 | ### Inputs 38 | 39 | | Name | Description | Type | Default | Required | 40 | |------|-------------|------|---------|:--------:| 41 | | <a name="input_bootstrap_files_dir"></a> [bootstrap\_files\_dir](#input\_bootstrap\_files\_dir) | Bootstrap file directory. If the variable has a value of `null` (default) - then it will not upload any other files other than the ones specified in the `files` variable.<br>More information can be found at https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/bootstrap-the-vm-series-firewall/bootstrap-package. | `string` | `null` | no | 42 | | <a name="input_files"></a> [files](#input\_files) | Map of all files to copy to bucket. The keys are local paths, the values are remote paths. For example `{"dir/my.txt" = "config/init-cfg.txt"}` | `map(string)` | `{}` | no | 43 | | <a name="input_folders"></a> [folders](#input\_folders) | List of folder paths that will be used to create dedicated boostrap package folder sets per firewall or firewall group (for example to distinguish configuration per region, per inbound/obew role, etc) within the created storage bucket.<br><br>A default value (empty list) will result in the creation of a single bootstrap package folder set in the bucket top-level directory. | `list(any)` | `[]` | no | 44 | | <a name="input_location"></a> [location](#input\_location) | Location in which the GCS Bucket will be deployed. Available locations can be found under https://cloud.google.com/storage/docs/locations. | `string` | n/a | yes | 45 | | <a name="input_name_prefix"></a> [name\_prefix](#input\_name\_prefix) | Prefix of the name of Google Cloud Storage bucket, followed by 10 random characters | `string` | `"paloaltonetworks-firewall-bootstrap-"` | no | 46 | | <a name="input_service_account"></a> [service\_account](#input\_service\_account) | Optional IAM Service Account (just an email) that will be granted read-only access to this bucket | `string` | `null` | no | 47 | 48 | ### Outputs 49 | 50 | | Name | Description | 51 | |------|-------------| 52 | | <a name="output_bucket"></a> [bucket](#output\_bucket) | n/a | 53 | | <a name="output_bucket_name"></a> [bucket\_name](#output\_bucket\_name) | n/a | 54 | <!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> 55 | -------------------------------------------------------------------------------- /modules/vmseries/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | create_public_ip = { 3 | for k, v in var.network_interfaces : k => try(v.create_public_ip, false) 4 | } 5 | access_configs = { 6 | for k, v in var.network_interfaces : k => { 7 | nat_ip = try(v.public_ip, google_compute_address.public[k].address, null) 8 | public_ptr_domain_name = try(v.public_ptr_domain_name, google_compute_address.public[k].public_ptr_domain_name, null) 9 | } 10 | if try(v.public_ip, null) != null || local.create_public_ip[k] 11 | } 12 | } 13 | 14 | data "google_compute_image" "vmseries" { 15 | count = var.custom_image == null ? 1 : 0 16 | 17 | name = var.vmseries_image 18 | project = "paloaltonetworksgcp-public" 19 | } 20 | 21 | data "google_compute_subnetwork" "this" { 22 | for_each = { for k, v in var.network_interfaces : k => v } 23 | project = var.project 24 | self_link = each.value.subnetwork 25 | } 26 | 27 | resource "null_resource" "dependency_getter" { 28 | provisioner "local-exec" { 29 | command = "echo ${length(var.dependencies)}" 30 | } 31 | } 32 | 33 | resource "google_compute_address" "private" { 34 | for_each = { for k, v in var.network_interfaces : k => v } 35 | 36 | name = try(each.value.private_ip_name, "${var.name}-${each.key}-private") 37 | address_type = "INTERNAL" 38 | address = try(each.value.private_ip, null) 39 | project = var.project 40 | subnetwork = each.value.subnetwork 41 | region = data.google_compute_subnetwork.this[each.key].region 42 | } 43 | 44 | resource "google_compute_address" "public" { 45 | for_each = { for k, v in var.network_interfaces : k => v if local.create_public_ip[k] && try(v.public_ip, null) == null } 46 | 47 | name = try(each.value.public_ip_name, "${var.name}-${each.key}-public") 48 | address_type = "EXTERNAL" 49 | project = var.project 50 | region = data.google_compute_subnetwork.this[each.key].region 51 | } 52 | 53 | resource "google_compute_instance" "this" { 54 | 55 | name = var.name 56 | zone = var.zone 57 | machine_type = var.machine_type 58 | min_cpu_platform = var.min_cpu_platform 59 | deletion_protection = var.deletion_protection 60 | labels = var.labels 61 | tags = var.tags 62 | metadata_startup_script = var.metadata_startup_script 63 | project = var.project 64 | resource_policies = var.resource_policies 65 | can_ip_forward = true 66 | allow_stopping_for_update = true 67 | 68 | metadata = merge({ 69 | serial-port-enable = true 70 | ssh-keys = var.ssh_keys 71 | }, 72 | var.bootstrap_options, 73 | var.metadata 74 | ) 75 | 76 | service_account { 77 | email = var.service_account 78 | scopes = var.scopes 79 | } 80 | 81 | dynamic "network_interface" { 82 | for_each = var.network_interfaces 83 | 84 | content { 85 | network_ip = google_compute_address.private[network_interface.key].address 86 | subnetwork = network_interface.value.subnetwork 87 | 88 | dynamic "access_config" { 89 | for_each = try(local.access_configs[network_interface.key] != null, false) ? ["one"] : [] 90 | content { 91 | nat_ip = local.access_configs[network_interface.key].nat_ip 92 | public_ptr_domain_name = local.access_configs[network_interface.key].public_ptr_domain_name 93 | } 94 | } 95 | 96 | dynamic "alias_ip_range" { 97 | for_each = try(network_interface.value.alias_ip_ranges, []) 98 | content { 99 | ip_cidr_range = alias_ip_ranges.value.ip_cidr_range 100 | subnetwork_range_name = try(alias_ip_ranges.value.subnetwork_range_name, null) 101 | } 102 | } 103 | } 104 | } 105 | 106 | boot_disk { 107 | initialize_params { 108 | image = coalesce(var.custom_image, try(data.google_compute_image.vmseries[0].self_link, null)) 109 | type = var.disk_type 110 | } 111 | } 112 | 113 | depends_on = [ 114 | null_resource.dependency_getter 115 | ] 116 | } 117 | 118 | # The Deployment Guide Jan 2020 recommends per-zone instance groups (instead of regional IGMs). 119 | resource "google_compute_instance_group" "this" { 120 | count = var.create_instance_group ? 1 : 0 121 | 122 | name = "${var.name}-${var.zone}" 123 | zone = var.zone 124 | project = var.project 125 | instances = [google_compute_instance.this.self_link] 126 | 127 | dynamic "named_port" { 128 | for_each = var.named_ports 129 | content { 130 | name = named_port.value.name 131 | port = named_port.value.port 132 | } 133 | } 134 | } 135 | 136 | -------------------------------------------------------------------------------- /examples/standalone_vmseries_with_metadata_bootstrap/variables.tf: -------------------------------------------------------------------------------- 1 | # General 2 | variable "project" { 3 | description = "The project name to deploy the infrastructure in to." 4 | type = string 5 | default = null 6 | } 7 | variable "name_prefix" { 8 | description = "A string to prefix resource namings" 9 | type = string 10 | default = "" 11 | } 12 | 13 | # VPC 14 | variable "networks" { 15 | description = <<-EOF 16 | A map containing each network setting. 17 | 18 | Example of variable deployment : 19 | 20 | ``` 21 | networks = { 22 | "vmseries-vpc" = { 23 | vpc_name = "firewall-vpc" 24 | create_network = true 25 | delete_default_routes_on_create = "false" 26 | mtu = "1460" 27 | routing_mode = "REGIONAL" 28 | subnetworks = { 29 | "vmseries-sub" = { 30 | name = "vmseries-subnet" 31 | create_subnetwork = true 32 | ip_cidr_range = "172.21.21.0/24" 33 | region = "us-central1" 34 | } 35 | } 36 | firewall_rules = { 37 | "allow-vmseries-ingress" = { 38 | name = "vmseries-mgmt" 39 | source_ranges = ["1.1.1.1/32", "2.2.2.2/32"] 40 | priority = "1000" 41 | allowed_protocol = "all" 42 | allowed_ports = [] 43 | } 44 | } 45 | } 46 | ``` 47 | 48 | For a full list of available configuration items - please refer to [module documentation](https://github.com/PaloAltoNetworks/terraform-google-vmseries-modules/tree/main/modules/vpc#input_networks) 49 | 50 | Multiple keys can be added and will be deployed by the code 51 | EOF 52 | } 53 | 54 | variable "vmseries" { 55 | description = <<-EOF 56 | A map containing each individual vmseries setting. 57 | 58 | Example of variable deployment : 59 | 60 | ``` 61 | vmseries = { 62 | "fw-vmseries-01" = { 63 | name = "fw-vmseries-01" 64 | zone = "us-central1-b" 65 | vmseries_image = "vmseries-flex-byol-1022h2" 66 | ssh_keys = "admin:<YOUR_SSH_KEY>" 67 | machine_type = "n2-standard-4" 68 | min_cpu_platform = "Intel Cascade Lake" 69 | tags = ["vmseries"] 70 | scopes = [ 71 | "https://www.googleapis.com/auth/compute.readonly", 72 | "https://www.googleapis.com/auth/cloud.useraccounts.readonly", 73 | "https://www.googleapis.com/auth/devstorage.read_only", 74 | "https://www.googleapis.com/auth/logging.write", 75 | "https://www.googleapis.com/auth/monitoring.write", 76 | ] 77 | bootstrap_options = { 78 | panorama-server = "1.1.1.1" # Modify this value as per deployment requirements 79 | dns-primary = "8.8.8.8" # Modify this value as per deployment requirements 80 | dns-secondary = "8.8.4.4" # Modify this value as per deployment requirements 81 | } 82 | named_ports = [ 83 | { 84 | name = "http" 85 | port = 80 86 | }, 87 | { 88 | name = "https" 89 | port = 443 90 | } 91 | ] 92 | network_interfaces = [ 93 | { 94 | vpc_network_key = "vmseries-vpc" 95 | subnetwork_key = "fw-mgmt-sub" 96 | private_ip = "10.10.10.2" 97 | create_public_ip = true 98 | } 99 | ] 100 | } 101 | } 102 | ``` 103 | For a full list of available configuration items - please refer to [module documentation](https://github.com/PaloAltoNetworks/terraform-google-vmseries-modules/tree/main/modules/vmseries#inputs) 104 | 105 | The bootstrap_template_map contains variables that will be applied to the bootstrap template. Each firewall Day 0 bootstrap will be parametrised based on these inputs. 106 | Multiple keys can be added and will be deployed by the code. 107 | 108 | EOF 109 | } 110 | 111 | variable "vmseries_common" { 112 | description = <<-EOF 113 | A map containing common vmseries setting. 114 | 115 | Example of variable deployment : 116 | 117 | ``` 118 | vmseries_common = { 119 | ssh_keys = "admin:AAAABBBB..." 120 | vmseries_image = "vmseries-flex-byol-1022h2" 121 | machine_type = "n2-standard-4" 122 | min_cpu_platform = "Intel Cascade Lake" 123 | service_account_key = "sa-vmseries-01" 124 | bootstrap_options = { 125 | type = "dhcp-client" 126 | mgmt-interface-swap = "enable" 127 | } 128 | } 129 | ``` 130 | 131 | Bootstrap options can be moved between vmseries individual instance variable (`vmseries`) and this common vmserie variable (`vmseries_common`). 132 | EOF 133 | default = {} 134 | } -------------------------------------------------------------------------------- /modules/lb_external/variables.tf: -------------------------------------------------------------------------------- 1 | variable "project" { 2 | description = "The project to deploy to. If unset the default provider project is used." 3 | type = string 4 | default = "" 5 | } 6 | 7 | variable "region" { 8 | description = "GCP region to deploy to. If unset the default provider region is used." 9 | type = string 10 | default = null 11 | } 12 | 13 | variable "name" { 14 | description = "Name of the backend_service, target_pool and of the associated health check." 15 | type = string 16 | } 17 | 18 | variable "rules" { 19 | description = <<-EOF 20 | Map of objects, the keys are names of the external forwarding rules, each of the objects has the following attributes: 21 | 22 | - `port_range`: (Required) The port your service is listening on. Can be a number (80) or a range (8080-8089, or even 1-65535). 23 | - `ip_address`: (Optional) A public IP address on which to listen, must be in the same region as the LB and must be IPv4. If empty, automatically generates a new non-ephemeral IP on a PREMIUM tier. 24 | - `ip_protocol`: (Optional) The IP protocol for the frontend forwarding rule: TCP, UDP, ESP, ICMP, or L3_DEFAULT. Default is TCP. 25 | - `all_ports`: (Optional) Allows all ports to be forwarded to the Backend Service 26 | 27 | EOF 28 | } 29 | 30 | variable "instances" { 31 | description = "List of links to the instances. Expected to be empty when using an autoscaler, as the autoscaler inserts entries to the target pool dynamically. The nic0 of each instance gets the traffic. Even when this list is shifted or re-ordered, it doesn't re-create any resources and such modifications often proceed without any noticeable downtime." 32 | type = list(string) 33 | default = null 34 | } 35 | 36 | variable "backend_instance_groups" { 37 | description = "List of backend instance groups" 38 | default = [] 39 | } 40 | 41 | variable "session_affinity" { 42 | description = <<-EOF 43 | Controls distribution of new connections (or fragmented UDP packets) from clients to the backends, can influence available connection tracking configurations. 44 | Valid values are: NONE (default), CLIENT_IP, CLIENT_IP_PROTO, CLIENT_IP_PORT_PROTO (only available for backend service based rules). 45 | EOF 46 | type = string 47 | default = "NONE" 48 | } 49 | 50 | variable "connection_tracking_policy" { 51 | description = <<-EOF 52 | Connection tracking policy settings, only available for backend service based rules. Following options are available: 53 | - `mode` - (Optional|string) `PER_CONNECTION` (default) or `PER_SESSION` 54 | - `persistence_on_unhealthy_backends` - (Optional|string) `DEFAULT_FOR_PROTOCOL` (default), `ALWAYS_PERSIST` or `NEVER_PERSIST` 55 | 56 | More information about supported configurations in conjunction with `session_affinity` is available in [Backend service-based external Network Load Balancing](https://cloud.google.com/load-balancing/docs/network/networklb-backend-service#connection-tracking) documentation. 57 | EOF 58 | default = null 59 | type = map(any) 60 | } 61 | 62 | variable "network_tier" { 63 | description = "The networking tier used for configuring this address. If this field is not specified, it is assumed to be PREMIUM. Possible values are PREMIUM and STANDARD." 64 | type = string 65 | default = "PREMIUM" 66 | } 67 | 68 | variable "create_health_check" { 69 | description = "Whether to create a health check on the target pool." 70 | type = bool 71 | default = true 72 | } 73 | 74 | variable "health_check_interval_sec" { 75 | description = "Health check parameter, see [provider doc](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_http_health_check)" 76 | type = number 77 | default = null 78 | } 79 | 80 | variable "health_check_healthy_threshold" { 81 | description = "Health check parameter, see [provider doc](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_http_health_check)" 82 | type = number 83 | default = null 84 | } 85 | 86 | variable "health_check_timeout_sec" { 87 | description = "Health check parameter, see [provider doc](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_http_health_check)" 88 | type = number 89 | default = null 90 | } 91 | 92 | variable "health_check_unhealthy_threshold" { 93 | description = "Health check parameter, see [provider doc](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_http_health_check)" 94 | type = number 95 | default = null 96 | } 97 | 98 | variable "health_check_http_port" { 99 | description = "Health check parameter, see [provider doc](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_http_health_check)" 100 | type = number 101 | default = null 102 | } 103 | 104 | variable "health_check_http_request_path" { 105 | description = "Health check http request path, with the default adjusted to /php/login.php to be able to check the health of the PAN-OS webui." 106 | type = string 107 | default = "/php/login.php" 108 | } 109 | 110 | variable "health_check_http_host" { 111 | description = "Health check http request host header, with the default adjusted to localhost to be able to check the health of the PAN-OS webui." 112 | type = string 113 | default = "localhost" 114 | } 115 | -------------------------------------------------------------------------------- /modules/lb_internal/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | description = "Name of the load balancer (that is, both the forwarding rule and the backend service)" 3 | type = string 4 | } 5 | 6 | variable "project" { 7 | description = "The project to deploy to. If unset the default provider project is used." 8 | type = string 9 | default = null 10 | } 11 | 12 | variable "region" { 13 | description = "Region to create ILB in." 14 | type = string 15 | default = null 16 | } 17 | 18 | variable "health_check_port" { 19 | description = "(Optional) Port number for TCP healthchecking, default 22. This setting is ignored when `health_check` is provided." 20 | default = 22 21 | type = number 22 | } 23 | 24 | variable "health_check" { 25 | description = "(Optional) Name of either the global google_compute_health_check or google_compute_region_health_check to use. Conflicts with health_check_port." 26 | default = null 27 | type = string 28 | } 29 | 30 | variable "backends" { 31 | description = "Names of primary backend groups (IGs or IGMs). Typically use `module.vmseries.instance_group_self_links` here." 32 | type = map(string) 33 | } 34 | 35 | variable "failover_backends" { 36 | description = "(Optional) Names of failover backend groups (IGs or IGMs). Failover groups are ignored unless the primary groups do not meet collective health threshold." 37 | default = {} 38 | type = map(string) 39 | } 40 | 41 | variable "subnetwork" { 42 | type = string 43 | } 44 | 45 | variable "ip_address" { 46 | default = null 47 | } 48 | 49 | variable "ip_protocol" { 50 | description = "The IP protocol for the frontend forwarding rule, valid values are TCP and UDP." 51 | default = "TCP" 52 | type = string 53 | } 54 | 55 | variable "all_ports" { 56 | description = "Forward all ports of the ip_protocol from the frontend to the backends. Needs to be null if `ports` are provided." 57 | default = null 58 | type = bool 59 | } 60 | 61 | variable "ports" { 62 | description = "Which port numbers are forwarded to the backends (up to 5 ports). Conflicts with all_ports." 63 | default = [] 64 | type = list(number) 65 | } 66 | 67 | variable "network" { 68 | default = null 69 | } 70 | 71 | variable "session_affinity" { 72 | description = <<-EOF 73 | Controls distribution of new connections (or fragmented UDP packets) from clients to the backends, can influence available connection tracking configurations. 74 | Valid values are: NONE (default), CLIENT_IP_NO_DESTINATION, CLIENT_IP, CLIENT_IP_PROTO, CLIENT_IP_PORT_PROTO. 75 | EOF 76 | default = null 77 | type = string 78 | } 79 | 80 | variable "connection_tracking_policy" { 81 | description = <<-EOF 82 | Connection tracking policy settings. Following options are available: 83 | - `mode` - (Optional|string) `PER_CONNECTION` (default) or `PER_SESSION` 84 | - `idle_timeout_sec` - (Optional|number) Defaults to 600 seconds, can only be modified in specific conditions (see link below) 85 | - `persistence_on_unhealthy_backends` - (Optional|string) `DEFAULT_FOR_PROTOCOL` (default), `ALWAYS_PERSIST` or `NEVER_PERSIST` 86 | 87 | More information about supported configurations in conjunction with `session_affinity` is available in [Internal TCP/UDP Load Balancing](https://cloud.google.com/load-balancing/docs/internal#connection-tracking) documentation. 88 | EOF 89 | default = null 90 | type = map(any) 91 | } 92 | 93 | variable "timeout_sec" { 94 | description = "(Optional) How many seconds to wait for the backend before dropping the connection. Default is 30 seconds. Valid range is [1, 86400]." 95 | default = null 96 | type = number 97 | } 98 | 99 | variable "disable_connection_drain_on_failover" { 100 | description = "(Optional) On failover or failback, this field indicates whether connection drain will be honored. Setting this to true has the following effect: connections to the old active pool are not drained. Connections to the new active pool use the timeout of 10 min (currently fixed). Setting to false has the following effect: both old and new connections will have a drain timeout of 10 min. This can be set to true only if the protocol is TCP. The default is false." 101 | default = null 102 | type = bool 103 | } 104 | 105 | variable "drop_traffic_if_unhealthy" { 106 | description = "(Optional) Used only when no healthy VMs are detected in the primary and backup instance groups. When set to true, traffic is dropped. When set to false, new connections are sent across all VMs in the primary group. The default is false." 107 | default = null 108 | type = bool 109 | } 110 | 111 | variable "failover_ratio" { 112 | description = "(Optional) The value of the field must be in [0, 1]. If the ratio of the healthy VMs in the primary backend is at or below this number, traffic arriving at the load-balanced IP will be directed to the failover_backends. In case where 'failoverRatio' is not set or all the VMs in the backup backend are unhealthy, the traffic will be directed back to the primary backend in the `force` mode, where traffic will be spread to the healthy VMs with the best effort, or to all VMs when no VM is healthy. This field is only used with l4 load balancing." 113 | default = null 114 | type = number 115 | } 116 | 117 | variable "allow_global_access" { 118 | description = "(Optional) If true, clients can access ILB from all regions. By default false, only allow from the ILB's local region; useful if the ILB is a next hop of a route." 119 | default = false 120 | type = bool 121 | } 122 | 123 | variable "connection_draining_timeout_sec" { 124 | type = number 125 | description = "(Optional) Time for which instance will be drained (not accept new connections, but still work to finish started)." 126 | default = null 127 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are welcome, and they are greatly appreciated! Every little bit helps, 4 | and credit will always be given. 5 | 6 | ## Areas of contribution 7 | 8 | Contributions are welcome across the entire project: 9 | 10 | - Code 11 | - Documentation 12 | - Testing 13 | - Packaging/distribution 14 | 15 | ## Contributing workflow 16 | 17 | ### New Contributors 18 | 19 | 1. Search the [issues](https://github.com/PaloAltoNetworks/terraform-google-vmseries-modules.git/issues) to see if there is an existing issue. If not, please open one. 20 | 21 | 1. Fork the repository to your personal namespace (only needed to do this once). 22 | 23 | 1. Clone the repo from your personal namespace. 24 | 25 | `git clone https://github.com/{username}/terraform-google-vmseries-modules.git` 26 | Ensure that `{username}` is _your_ user name. 27 | 28 | 1. Add the source repository as an upsteam. 29 | 30 | `git remote add upstream https://github.com/PaloAltoNetworks/terraform-google-vmseries-modules.git` 31 | 32 | 1. Create a branch which corresponds to the issue ID created in step 1. 33 | 34 | For example, if the issue ID is 101: 35 | `git checkout -b 101-updating-wildfire-templates` 36 | 37 | 1. Make the desired changes and commit to your local repository. 38 | 39 | 1. Run the `pre-commit` script. See the [tools](#tools) section for more information. 40 | *NOTE* If making changes that will update the Terraform docs, this will need to be run twice. 41 | 42 | 1. Push changes to _your_ repository 43 | 44 | `git push origin/101-updating-wildfire-templates` 45 | 46 | 1. Rebase with the upstream to resolve any potential conflicts. 47 | 48 | `git rebase upstream dev` 49 | 50 | 1. Open a Pull Request and link it to the issue (reference the issue, i.e. "fixes #233") 51 | 52 | 1. Once the PR has been merged, delete your local branch 53 | 54 | `git branch -D 101-updating-wildfire-templates` 55 | 56 | ### Existing Contributors 57 | 58 | 1. Search the [issues](https://github.com/PaloAltoNetworks/terraform-google-vmseries-modules.git/issues) to see if there is an existing issue. If not, open an issue (note the issue ID). 59 | 1. Update from the source repository. 60 | 61 | `git pull upstream dev` 62 | 63 | 1. Create a branch which corresponds to the issue ID created in step 1. 64 | 65 | For example, if the issue ID is 101: 66 | `git checkout -b 101-updating-wildfire-templates` 67 | 68 | 1. Make any changes, and ensure the commit messages are clear and consistent (reference the issue ID and type of change in all commit messages) 69 | 70 | 1. Document the changes (update the README and any other relevant documents) 71 | 72 | 1. Run the `pre-commit` script. See the [tools](#tools) section for more information. 73 | *NOTE* If making changes that will update the Terraform docs, this will need to be run twice. 74 | 75 | 1. Push changes to _your_ repository 76 | 77 | `git push origin/101-updating-wildfire-templates` 78 | 1. Rebase with the upstream to resolve any potential conflicts. 79 | 80 | `git rebase upstream dev` 81 | 82 | 1. Open a Pull Request and link it to the issue (reference the issue, i.e. "fixes #233") 83 | 84 | 1. Once the PR has been merged, delete your local branch 85 | 86 | `git branch -D 101-updating-wildfire-templates` 87 | 88 | ## Tools 89 | 90 | Any serious changes, especially any changes of variables or providers, require the 91 | `pre-commit` tool. Install the recommended versions: 92 | 93 | - pre-commit 2.9.3 - [installation instruction](https://pre-commit.com/#installation) (a Python3 package) 94 | - terraform-docs 0.12.1 - download the binary from [GitHub releases](https://github.com/terraform-docs/terraform-docs/releases) 95 | - tflint 0.20.2 - download the binary from [GitHub releases](https://github.com/terraform-linters/tflint/releases) 96 | - coreutils - required only on macOS (due to use of `realpath`), simply execute `brew install coreutils` 97 | 98 | For more details and a Docker-compatible alternative see the [official guide](https://github.com/antonbabenko/pre-commit-terraform#how-to-install) of the author of pre-commit-terraform, Anton Babenko. 99 | 100 | For these Contributors who prefer *not* to use the recommended git hooks, the command 101 | to fully update the auto-generated README files and to run formatters/tests: 102 | 103 | ```sh 104 | pre-commit run -a 105 | ``` 106 | 107 | This command does not commit/add/push any changes to Git. It only changes local files. 108 | 109 | The first time `pre-commit` is run, it is possible to show "FAILED" if any docs were updated. This is expected behavior. Simply run `pre-commit` again and it should pass. Once all pre-commit tests pass, make another commit to check in those changes and push. 110 | 111 | ## Coding Standards 112 | 113 | Please follow the [Terraform conventions](https://github.com/PaloAltoNetworks/terraform-best-practices/blob/master/README.md). 114 | 115 | ## Publish a new release (for maintainers) 116 | 117 | ### Test the release process 118 | 119 | Testing the workflow requires node, npm, and semantic-release to be installed locally: 120 | 121 | ```sh 122 | npm install -g semantic-release@^17.1.1 @semantic-release/git@^9.0.0 @semantic-release/exec@^5.0.0 conventional-changelog-conventionalcommits@^4.4.0 123 | ``` 124 | 125 | Run `semantic-release` on develop: 126 | 127 | ```sh 128 | semantic-release --dry-run --no-ci --branches=develop 129 | ``` 130 | 131 | Verify in the output that the next version is set correctly, and the release notes are generated correctly. 132 | 133 | ### Merge develop to master and push 134 | 135 | ```sh 136 | git checkout master 137 | git merge develop 138 | git push origin master 139 | ``` 140 | 141 | At this point, GitHub Actions builds and tags the release. 142 | 143 | ### Merge master to develop and push 144 | 145 | Now, sync develop to master to add any commits made by the release bot. 146 | 147 | ```sh 148 | git fetch --all --tags 149 | git pull origin master 150 | git checkout develop 151 | git merge master 152 | git push origin develop 153 | ``` 154 | -------------------------------------------------------------------------------- /examples/standalone_vmseries_with_metadata_bootstrap/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | show_in_hub: false 3 | --- 4 | # Palo Alto Networks VM-Series NGFW Module Example 5 | 6 | A Terraform module example for deploying a VM-Series NGFW in GCP using the [metadata](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/bootstrap-the-vm-series-firewall/choose-a-bootstrap-method#idf6412176-e973-488e-9d7a-c568fe1e33a9) bootstrap method. 7 | 8 | This example can be used to familarize oneself with both the VM-Series NGFW and Terraform - it creates a single instance of virtualized firewall in a Security VPC with a management-only interface and lacks any traffic inspection. 9 | 10 | ## Reference 11 | <!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> 12 | ### Requirements 13 | 14 | | Name | Version | 15 | |------|---------| 16 | | <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.3, < 2.0 | 17 | 18 | ### Providers 19 | 20 | No providers. 21 | 22 | ### Modules 23 | 24 | | Name | Source | Version | 25 | |------|--------|---------| 26 | | <a name="module_vmseries"></a> [vmseries](#module\_vmseries) | ../../modules/vmseries | n/a | 27 | | <a name="module_vpc"></a> [vpc](#module\_vpc) | ../../modules/vpc | n/a | 28 | 29 | ### Resources 30 | 31 | No resources. 32 | 33 | ### Inputs 34 | 35 | | Name | Description | Type | Default | Required | 36 | |------|-------------|------|---------|:--------:| 37 | | <a name="input_name_prefix"></a> [name\_prefix](#input\_name\_prefix) | A string to prefix resource namings | `string` | `""` | no | 38 | | <a name="input_networks"></a> [networks](#input\_networks) | A map containing each network setting.<br><br>Example of variable deployment :<pre>networks = {<br> "vmseries-vpc" = {<br> vpc_name = "firewall-vpc"<br> create_network = true<br> delete_default_routes_on_create = "false"<br> mtu = "1460"<br> routing_mode = "REGIONAL"<br> subnetworks = {<br> "vmseries-sub" = {<br> name = "vmseries-subnet"<br> create_subnetwork = true<br> ip_cidr_range = "172.21.21.0/24"<br> region = "us-central1"<br> }<br> }<br> firewall_rules = {<br> "allow-vmseries-ingress" = {<br> name = "vmseries-mgmt"<br> source_ranges = ["1.1.1.1/32", "2.2.2.2/32"]<br> priority = "1000"<br> allowed_protocol = "all"<br> allowed_ports = []<br> }<br> }<br> }</pre>For a full list of available configuration items - please refer to [module documentation](https://github.com/PaloAltoNetworks/terraform-google-vmseries-modules/tree/main/modules/vpc#input_networks)<br><br>Multiple keys can be added and will be deployed by the code | `any` | n/a | yes | 39 | | <a name="input_project"></a> [project](#input\_project) | The project name to deploy the infrastructure in to. | `string` | `null` | no | 40 | | <a name="input_vmseries"></a> [vmseries](#input\_vmseries) | A map containing each individual vmseries setting.<br><br>Example of variable deployment :<pre>vmseries = {<br> "fw-vmseries-01" = {<br> name = "fw-vmseries-01"<br> zone = "us-central1-b"<br> vmseries_image = "vmseries-flex-byol-1022h2"<br> ssh_keys = "admin:<YOUR_SSH_KEY>"<br> machine_type = "n2-standard-4"<br> min_cpu_platform = "Intel Cascade Lake"<br> tags = ["vmseries"]<br> scopes = [<br> "https://www.googleapis.com/auth/compute.readonly",<br> "https://www.googleapis.com/auth/cloud.useraccounts.readonly",<br> "https://www.googleapis.com/auth/devstorage.read_only",<br> "https://www.googleapis.com/auth/logging.write",<br> "https://www.googleapis.com/auth/monitoring.write",<br> ]<br> bootstrap_options = {<br> panorama-server = "1.1.1.1" # Modify this value as per deployment requirements<br> dns-primary = "8.8.8.8" # Modify this value as per deployment requirements<br> dns-secondary = "8.8.4.4" # Modify this value as per deployment requirements<br> }<br> named_ports = [<br> {<br> name = "http"<br> port = 80<br> },<br> {<br> name = "https"<br> port = 443<br> }<br> ]<br> network_interfaces = [<br> {<br> vpc_network_key = "vmseries-vpc"<br> subnetwork_key = "fw-mgmt-sub"<br> private_ip = "10.10.10.2"<br> create_public_ip = true<br> }<br> ]<br> }<br> }</pre>For a full list of available configuration items - please refer to [module documentation](https://github.com/PaloAltoNetworks/terraform-google-vmseries-modules/tree/main/modules/vmseries#inputs)<br><br>The bootstrap\_template\_map contains variables that will be applied to the bootstrap template. Each firewall Day 0 bootstrap will be parametrised based on these inputs.<br>Multiple keys can be added and will be deployed by the code. | `any` | n/a | yes | 41 | | <a name="input_vmseries_common"></a> [vmseries\_common](#input\_vmseries\_common) | A map containing common vmseries setting.<br><br>Example of variable deployment :<pre>vmseries_common = {<br> ssh_keys = "admin:AAAABBBB..."<br> vmseries_image = "vmseries-flex-byol-1022h2"<br> machine_type = "n2-standard-4"<br> min_cpu_platform = "Intel Cascade Lake"<br> service_account_key = "sa-vmseries-01"<br> bootstrap_options = {<br> type = "dhcp-client"<br> mgmt-interface-swap = "enable"<br> }<br>}</pre>Bootstrap options can be moved between vmseries individual instance variable (`vmseries`) and this common vmserie variable (`vmseries_common`). | `map` | `{}` | no | 42 | 43 | ### Outputs 44 | 45 | | Name | Description | 46 | |------|-------------| 47 | | <a name="output_vmseries_private_ips"></a> [vmseries\_private\_ips](#output\_vmseries\_private\_ips) | Private IP addresses of the vmseries instances. | 48 | | <a name="output_vmseries_public_ips"></a> [vmseries\_public\_ips](#output\_vmseries\_public\_ips) | Public IP addresses of the vmseries instances. | 49 | <!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> -------------------------------------------------------------------------------- /modules/lb_external/main.tf: -------------------------------------------------------------------------------- 1 | data "google_client_config" "this" {} 2 | 3 | locals { 4 | # If we were told an exact region, use it, otherwise fall back to a client-default region 5 | region = coalesce(var.region, data.google_client_config.this.region) 6 | 7 | # Check for `L3_DEFAULT` as this requires `google_compute_region_backend_service` and `google_compute_region_health_check` resources. 8 | backend_service_needed = contains([for k, v in var.rules : lookup(v, "ip_protocol", null)], "L3_DEFAULT") 9 | 10 | # Check for protocols that require a `google_compute_target_pool` backend and `google_compute_http_health_check` health check 11 | target_pool_protocols = ["TCP", "UDP", "ESP", "AH", "SCTP", "ICMP"] 12 | target_pool_needed = contains([for k, v in var.rules : contains(local.target_pool_protocols, lookup(v, "ip_protocol", "TCP"))], true) 13 | } 14 | 15 | # Create external IP addresses if non-specified 16 | resource "google_compute_address" "this" { 17 | for_each = { for k, v in var.rules : k => v if !can(v.ip_address) } 18 | 19 | name = each.key 20 | address_type = "EXTERNAL" 21 | region = var.region 22 | project = var.project 23 | } 24 | 25 | # Create forwarding rule for each specified rule 26 | resource "google_compute_forwarding_rule" "rule" { 27 | for_each = var.rules 28 | 29 | name = each.key 30 | project = var.project 31 | region = local.region 32 | 33 | # Check if `ip_protocol` is specified (if not assume default of `TCP`) != `L3_DEFAULT` if true then use `google_compute_target_pool` as backend 34 | target = lookup(each.value, "ip_protocol", "TCP") != "L3_DEFAULT" ? google_compute_target_pool.this[0].self_link : null 35 | 36 | # Check if `ip_protocol` is specified (if not assume default of `TCP`) == `L3_DEFAULT` if true then use `google_compute_region_backend_service` as backend 37 | backend_service = lookup(each.value, "ip_protocol", "TCP") == "L3_DEFAULT" ? google_compute_region_backend_service.this[0].self_link : null 38 | load_balancing_scheme = "EXTERNAL" 39 | 40 | # Check if `ip_protocol` is specified (if not assume default of `TCP`) == `L3_DEFAULT`. 41 | # If true then set `all_ports` to `true`. 42 | # If false set value to the value of `all_ports`. If `all_ports` isn't specified, then set the value to `null`. 43 | all_ports = lookup(each.value, "ip_protocol", "TCP") == "L3_DEFAULT" ? true : lookup(each.value, "all_ports", null) 44 | 45 | # Check if `ip_protocol` is specified (if not assume default of `TCP`) == `L3_DEFAULT`. 46 | # If true then set `port_range` to `null`. 47 | # If false set value to the value of `port_range`. If `port_range` isn't specified, then set the value to `null`. 48 | port_range = lookup(each.value, "ip_protocol", "TCP") == "L3_DEFAULT" ? null : lookup(each.value, "port_range", null) 49 | 50 | ip_address = try(each.value.ip_address, google_compute_address.this[each.key].address) 51 | ip_protocol = lookup(each.value, "ip_protocol", "TCP") 52 | } 53 | 54 | # Create `google_compute_target_pool` if required by `var.rules` 55 | resource "google_compute_target_pool" "this" { 56 | count = local.target_pool_needed ? 1 : 0 57 | 58 | name = var.name 59 | project = var.project 60 | region = local.region 61 | 62 | instances = var.instances 63 | health_checks = var.create_health_check ? [google_compute_http_health_check.this[0].self_link] : [] 64 | session_affinity = var.session_affinity 65 | 66 | lifecycle { 67 | # Ignore changes because autoscaler changes this in the background. 68 | ignore_changes = [instances] 69 | } 70 | } 71 | 72 | # Create `google_compute_http_health_check` if required by `var.rules` 73 | resource "google_compute_http_health_check" "this" { 74 | count = var.create_health_check && local.target_pool_needed ? 1 : 0 75 | 76 | name = "${var.name}-${local.region}" 77 | check_interval_sec = var.health_check_interval_sec 78 | healthy_threshold = var.health_check_healthy_threshold 79 | timeout_sec = var.health_check_timeout_sec 80 | unhealthy_threshold = var.health_check_unhealthy_threshold 81 | port = var.health_check_http_port 82 | request_path = var.health_check_http_request_path 83 | host = var.health_check_http_host 84 | project = var.project 85 | } 86 | 87 | # Create `google_compute_region_backend_service` if require by `var.rules` 88 | resource "google_compute_region_backend_service" "this" { 89 | provider = google-beta 90 | 91 | count = local.backend_service_needed ? 1 : 0 92 | 93 | name = var.name 94 | project = var.project 95 | region = local.region 96 | 97 | load_balancing_scheme = "EXTERNAL" 98 | health_checks = var.create_health_check ? [google_compute_region_health_check.this[0].self_link] : [] 99 | protocol = "UNSPECIFIED" 100 | session_affinity = var.session_affinity 101 | 102 | dynamic "backend" { 103 | for_each = var.backend_instance_groups 104 | content { 105 | group = backend.value 106 | } 107 | } 108 | 109 | # This feature requires beta provider as of 2023-03-16 110 | dynamic "connection_tracking_policy" { 111 | for_each = var.connection_tracking_policy != null ? ["this"] : [] 112 | content { 113 | tracking_mode = try(var.connection_tracking_policy.mode, null) 114 | idle_timeout_sec = try(var.connection_tracking_policy.idle_timeout_sec, null) 115 | connection_persistence_on_unhealthy_backends = try(var.connection_tracking_policy.persistence_on_unhealthy_backends, null) 116 | } 117 | } 118 | } 119 | 120 | # Create `google_compute_region_backend_service` if require by `var.rules` 121 | resource "google_compute_region_health_check" "this" { 122 | count = var.create_health_check && local.backend_service_needed ? 1 : 0 123 | 124 | name = "${var.name}-${local.region}" 125 | project = var.project 126 | region = local.region 127 | check_interval_sec = var.health_check_interval_sec 128 | healthy_threshold = var.health_check_healthy_threshold 129 | timeout_sec = var.health_check_timeout_sec 130 | unhealthy_threshold = var.health_check_unhealthy_threshold 131 | 132 | http_health_check { 133 | port = var.health_check_http_port 134 | request_path = var.health_check_http_request_path 135 | host = var.health_check_http_host 136 | } 137 | } 138 | 139 | -------------------------------------------------------------------------------- /modules/vpn/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | secret = random_id.secret.b64_url 3 | 4 | tunnels_tmp = flatten([ 5 | for vpn_instance_name, vpn_instance_config in var.vpn_config.instances : [ 6 | for tunnel_name, tunnel_config in vpn_instance_config.tunnels : { 7 | tunnel_name = "${vpn_instance_name}-${tunnel_name}" 8 | tunnel_config = merge( 9 | tunnel_config, 10 | { 11 | vpn_instance_name = vpn_instance_name 12 | peer_external_gateway = try(vpn_instance_config.peer_external_gateway, null) 13 | peer_gcp_gateway = try(vpn_instance_config.peer_gcp_gateway, null) 14 | } 15 | ) 16 | } 17 | ] 18 | ]) 19 | 20 | tunnels = { 21 | for k, v in local.tunnels_tmp : v.tunnel_name => v.tunnel_config 22 | } 23 | } 24 | 25 | resource "google_compute_ha_vpn_gateway" "ha_gateway" { 26 | name = var.vpn_gateway_name 27 | project = var.project 28 | region = var.region 29 | network = var.network 30 | } 31 | 32 | resource "google_compute_router" "router" { 33 | name = coalesce(var.router_name, "${var.vpn_gateway_name}-rtr") 34 | project = var.project 35 | region = var.region 36 | network = var.network 37 | bgp { 38 | advertise_mode = ( 39 | var.vpn_config.router_advertise_config == null 40 | ? null 41 | : var.vpn_config.router_advertise_config.mode 42 | ) 43 | advertised_groups = ( 44 | var.vpn_config.router_advertise_config == null ? null : ( 45 | var.vpn_config.router_advertise_config.mode != "CUSTOM" 46 | ? null 47 | : var.vpn_config.router_advertise_config.groups 48 | ) 49 | ) 50 | dynamic "advertised_ip_ranges" { 51 | for_each = ( 52 | var.vpn_config.router_advertise_config == null ? {} : ( 53 | var.vpn_config.router_advertise_config.mode != "CUSTOM" 54 | ? {} 55 | : var.vpn_config.router_advertise_config.ip_ranges 56 | ) 57 | ) 58 | iterator = range 59 | content { 60 | range = range.key 61 | description = range.value 62 | } 63 | } 64 | asn = var.vpn_config.router_asn 65 | keepalive_interval = try(var.vpn_config.keepalive_interval, 20) 66 | } 67 | } 68 | 69 | # Represents a VPN gateway managed outside of GCP 70 | resource "google_compute_external_vpn_gateway" "external_gateway" { 71 | for_each = { for k, v in var.vpn_config.instances : k => v if try(v.peer_external_gateway, null) != null } 72 | 73 | name = try(each.value.peer_external_gateway.name, null) != null ? each.value.peer_external_gateway.name : "${each.value.name}-external-gw" 74 | project = var.project 75 | redundancy_type = each.value.peer_external_gateway.redundancy_type 76 | description = try(each.value.external_vpn_gateway_description, null) 77 | labels = var.labels 78 | dynamic "interface" { 79 | for_each = each.value.peer_external_gateway.interfaces 80 | content { 81 | id = interface.value.id 82 | ip_address = interface.value.ip_address 83 | } 84 | } 85 | } 86 | 87 | resource "google_compute_router_peer" "bgp_peer" { 88 | for_each = local.tunnels 89 | region = var.region 90 | project = var.project 91 | name = try(each.value.bgp_session_name, null) != null ? each.value.bgp_session_name : "${var.vpn_gateway_name}-${each.key}" 92 | router = google_compute_router.router.name 93 | peer_ip_address = each.value.bgp_peer.address 94 | peer_asn = each.value.bgp_peer.asn 95 | ip_address = each.value.bgp_peer_options == null ? null : each.value.bgp_peer_options.ip_address 96 | advertised_route_priority = ( 97 | each.value.bgp_peer_options == null ? try(each.value.route_priority, 1000) : ( 98 | each.value.bgp_peer_options.route_priority == null 99 | ? each.value.route_priority 100 | : each.value.bgp_peer_options.route_priority 101 | ) 102 | ) 103 | advertise_mode = ( 104 | each.value.bgp_peer_options == null ? null : each.value.bgp_peer_options.advertise_mode 105 | ) 106 | advertised_groups = ( 107 | each.value.bgp_peer_options == null ? null : ( 108 | each.value.bgp_peer_options.advertise_mode != "CUSTOM" 109 | ? null 110 | : each.value.bgp_peer_options.advertise_groups 111 | ) 112 | ) 113 | dynamic "advertised_ip_ranges" { 114 | for_each = ( 115 | each.value.bgp_peer_options == null ? {} : ( 116 | each.value.bgp_peer_options.advertise_mode != "CUSTOM" 117 | ? {} 118 | : each.value.bgp_peer_options.advertise_ip_ranges 119 | ) 120 | ) 121 | iterator = range 122 | content { 123 | range = range.key 124 | description = range.value 125 | } 126 | } 127 | interface = google_compute_router_interface.router_interface[each.key].name 128 | } 129 | 130 | resource "google_compute_router_interface" "router_interface" { 131 | for_each = local.tunnels 132 | project = var.project 133 | region = var.region 134 | name = try(each.value.bgp_session_name, null) != null ? each.value.bgp_session_name : each.key 135 | router = google_compute_router.router.name 136 | ip_range = each.value.bgp_session_range == "" ? null : each.value.bgp_session_range 137 | vpn_tunnel = google_compute_vpn_tunnel.tunnels[each.key].name 138 | } 139 | 140 | resource "google_compute_vpn_tunnel" "tunnels" { 141 | provider = google-beta 142 | for_each = local.tunnels 143 | project = var.project 144 | region = var.region 145 | name = "${var.vpn_gateway_name}-${each.key}" 146 | router = google_compute_router.router.name 147 | peer_external_gateway = try(google_compute_external_vpn_gateway.external_gateway[each.value.vpn_instance_name].self_link, null) 148 | peer_external_gateway_interface = each.value.peer_external_gateway_interface 149 | peer_gcp_gateway = each.value.peer_gcp_gateway 150 | vpn_gateway_interface = each.value.vpn_gateway_interface 151 | ike_version = each.value.ike_version 152 | shared_secret = each.value.shared_secret == "" ? local.secret : each.value.shared_secret 153 | vpn_gateway = google_compute_ha_vpn_gateway.ha_gateway.self_link 154 | labels = var.labels 155 | } 156 | 157 | resource "random_id" "secret" { 158 | byte_length = 16 159 | } 160 | -------------------------------------------------------------------------------- /modules/panorama/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | description = "Google Cloud region to deploy the resources into." 3 | type = string 4 | } 5 | 6 | variable "zone" { 7 | description = "Deployment area for Google Cloud resources within a region." 8 | type = string 9 | } 10 | 11 | variable "subnet" { 12 | description = "A regional resource, defining a range of IPv4 addresses. In Google Cloud, the terms subnet and subnetwork are synonymous." 13 | type = string 14 | } 15 | 16 | variable "project" { 17 | description = "The ID of the project in which the resource belongs. If it is not provided, the provider project is used." 18 | default = null 19 | type = string 20 | } 21 | 22 | variable "name" { 23 | description = "Name of the Panorama instance." 24 | type = string 25 | default = "panorama" 26 | } 27 | 28 | variable "private_static_ip" { 29 | description = <<EOF 30 | The static private IP address for Panorama. Only IPv4 is supported. An address may only be specified for INTERNAL address types. 31 | The IP address must be inside the specified subnetwork, if any. Set by the API if undefined. 32 | EOF 33 | type = string 34 | default = null 35 | } 36 | 37 | variable "attach_public_ip" { 38 | description = "Determines if a Public IP should be assigned to Panorama. Set by the API if the `public_static_ip` variable is not defined." 39 | type = bool 40 | default = false 41 | } 42 | 43 | variable "public_static_ip" { 44 | description = "The static external IP address for Panorama instance. Only IPv4 is supported. Set by the API if undefined." 45 | type = string 46 | default = null 47 | } 48 | 49 | variable "log_disks" { 50 | description = <<-EOF 51 | List of disks to create and attach to Panorama to store traffic logs. 52 | Available options: 53 | - `name` (Required) Name of the resource. The name must be 1-63 characters long, and comply with [`RFC1035`](https://datatracker.ietf.org/doc/html/rfc1035). 54 | - `type` (Optional) Disk type resource describing which disk type to use to create the disk. For available options, check the providers [documentation](https://cloud.google.com/compute/docs/disks#disk-types). 55 | - `size` (Optional) Size of the disk for Panorama logs (Gigabytes). 56 | 57 | Example: 58 | ``` 59 | log_disks = [ 60 | { 61 | name = "example-disk-1" 62 | type = "pd-ssd" 63 | size = "2000" 64 | }, 65 | { 66 | name = "example-disk-2" 67 | type = "pd-ssd" 68 | size = "3000" 69 | }, 70 | ] 71 | ``` 72 | EOF 73 | default = [] 74 | } 75 | 76 | variable "machine_type" { 77 | description = "See the [Terraform manual](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_instance)" 78 | type = string 79 | default = "n1-standard-16" 80 | } 81 | 82 | variable "min_cpu_platform" { 83 | description = "See the [Terraform manual](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_instance)" 84 | type = string 85 | default = "Intel Broadwell" 86 | } 87 | 88 | variable "deletion_protection" { 89 | description = "Enable deletion protection on the instance." 90 | default = false 91 | type = bool 92 | } 93 | 94 | variable "labels" { 95 | description = "See the [Terraform manual](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_instance)" 96 | type = map(any) 97 | default = {} 98 | } 99 | 100 | variable "tags" { 101 | description = "See the [Terraform manual](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_instance)" 102 | type = list(string) 103 | default = [] 104 | } 105 | 106 | variable "disk_type" { 107 | description = "Type of boot disk. For available options, check the providers [documentation](https://cloud.google.com/compute/docs/disks#disk-types)." 108 | type = string 109 | default = "pd-ssd" 110 | } 111 | 112 | variable "disk_size" { 113 | description = "Size of boot disk in gigabytes. Default is the same as the OS image." 114 | type = string 115 | default = null 116 | } 117 | 118 | variable "ssh_keys" { 119 | description = <<EOF 120 | In order to connect via SSH to Panorama, provide your SSH public key here. 121 | Remember to add the `admin` prefix before you insert your public SSH key. 122 | More than one key can be added. 123 | 124 | Example: 125 | `ssh_keys = "admin:ssh-rsa AAAAB4NzaC5yc9EAACABBACBgQDAcjYw6xa2zUZ6reqHqDp9bYDLTu7Rnk5Sa3hthIsIsFaKenFLe4w3mm5eF3ebsfAAnuzI9ua9g7aB/ThIsIsAlSoFaKeN2VhUMDmlBYO5m1D4ip6eugS6uM="` 126 | EOF 127 | type = string 128 | } 129 | 130 | variable "panorama_version" { 131 | description = <<EOF 132 | Panorama version - based on the name of the Panorama public image - allows to specify which Panorama version will be deployed. 133 | For more details regarding available Panorama versions in the Google Cloud Platform, please run the following command: 134 | `gcloud compute images list --filter="name ~ .*panorama.*" --project paloaltonetworksgcp-public --no-standard-images` 135 | EOF 136 | type = string 137 | default = "panorama-byol-1000" 138 | } 139 | 140 | variable "custom_image" { 141 | description = <<-EOF 142 | Custom image for your Panorama instances. Custom images are available only to your Cloud project. 143 | You can create a custom image from boot disks and other images. 144 | For more information, please check the provider [documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_instance#image) 145 | as well as the [Panorama Administrator's Guide](https://docs.paloaltonetworks.com/panorama/10-2/panorama-admin/set-up-panorama/set-up-the-panorama-virtual-appliance/install-the-panorama-virtual-appliance/install-panorama-on-gcp.html). 146 | 147 | If a `custom_image` is not specified, `image_project` and `image_family` are used to determine a Public image to use for Panorama. 148 | EOF 149 | type = string 150 | default = null 151 | } 152 | 153 | variable "metadata" { 154 | description = "See the [Terraform manual](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_instance)" 155 | type = map(string) 156 | default = {} 157 | } 158 | 159 | variable "service_account" { 160 | description = "IAM Service Account for running Panorama instance (just the email)" 161 | type = string 162 | default = null 163 | } 164 | 165 | variable "scopes" { 166 | description = "Access scopes for the compute instance - both OAuth2 URLs and gcloud short names are supported" 167 | type = list(string) 168 | default = [] 169 | } 170 | -------------------------------------------------------------------------------- /examples/panorama_standalone/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | short_title: Standalone Panorama Deployment 3 | type: example 4 | show_in_hub: true 5 | --- 6 | # Palo Alto Panorama deployment example 7 | 8 | The scope of this code is to deploy one or more vpc networks and subnetworks along with one or more panorama instances in a single project and region in Google Cloud. The example deploys panorama to be used in management only mode (without additional logging disks). For option on how to add additional logging disks - please refer to panorama [module documentation](https://github.com/PaloAltoNetworks/terraform-google-vmseries-modules/tree/main/modules/panorama#inputs) 9 | 10 | 11 | ## Topology 12 | 13 | The topology consists of : 14 | - A VPC network and a subnetwork 15 | - A panorama instance with a Public IP address attached to the created vpc network and subnetwork 16 | - Firewall rules that allow access to the panorama management interface 17 | 18 | ![panorama-topology](https://user-images.githubusercontent.com/43091730/230029801-3acea62e-aa3d-46f3-b638-6b09bf5ef35e.png) 19 | 20 | ## Prerequisites 21 | 22 | 1. Prepare [panorama license](https://support.paloaltonetworks.com/) 23 | 24 | 2. Configure the terraform [google provider](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/getting_started#configuring-the-provider) 25 | 26 | ## Build 27 | 28 | 1. Access Google Cloud Shell or any other environment which has access to your GCP project 29 | 30 | 2. Clone the repository and fill out any modifications to tfvars file (`example.tfvars` - at least `project`, `ssh_keys` and `source_ranges` should be filled in for successful deployment and access to the instance after deployment) 31 | 32 | ``` 33 | git clone https://github.com/PaloAltoNetworks/terraform-google-vmseries-modules 34 | cd terraform-google-vmseries-modules/examples/panorama 35 | ``` 36 | 37 | 3. Apply the terraform code 38 | 39 | ``` 40 | terraform init 41 | terraform apply -var-file=example.tfvars 42 | ``` 43 | 44 | 4. Check the output plan and confirm the apply 45 | 46 | 5. Check the successful application and outputs of the resulting infrastructure: 47 | 48 | ``` 49 | Apply complete! Resources: 8 added, 0 changed, 0 destroyed. (Number of resources can vary based on how many instances you push through tfvars) 50 | 51 | Outputs: 52 | 53 | panorama_private_ips = { 54 | "panorama-01" = "172.21.21.2" 55 | } 56 | panorama_public_ips = { 57 | "panorama-01" = "x.x.x.x" 58 | } 59 | ``` 60 | 61 | 62 | ## Post build 63 | 64 | Connect to the panorama instance(s) via SSH using your associated private key and set a password : 65 | 66 | ``` 67 | ssh admin@x.x.x.x -i /PATH/TO/YOUR/KEY/id_rsa 68 | Welcome admin. 69 | admin@Panorama> configure 70 | Entering configuration mode 71 | [edit] 72 | admin@Panorama# set mgt-config users admin password 73 | Enter password : 74 | Confirm password : 75 | 76 | [edit] 77 | admin@Panorama# commit 78 | Configuration committed successfully 79 | ``` 80 | 81 | ## Check access via web UI 82 | 83 | Use a web browser to access https://x.x.x.x and login with admin and your previously configured password 84 | 85 | ## Reference 86 | <!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> 87 | ### Requirements 88 | 89 | | Name | Version | 90 | |------|---------| 91 | | <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.3, < 2.0 | 92 | 93 | ### Providers 94 | 95 | No providers. 96 | 97 | ### Modules 98 | 99 | | Name | Source | Version | 100 | |------|--------|---------| 101 | | <a name="module_panorama"></a> [panorama](#module\_panorama) | ../../modules/panorama | n/a | 102 | | <a name="module_vpc"></a> [vpc](#module\_vpc) | ../../modules/vpc | n/a | 103 | 104 | ### Resources 105 | 106 | No resources. 107 | 108 | ### Inputs 109 | 110 | | Name | Description | Type | Default | Required | 111 | |------|-------------|------|---------|:--------:| 112 | | <a name="input_name_prefix"></a> [name\_prefix](#input\_name\_prefix) | A string to prefix resource namings | `string` | `""` | no | 113 | | <a name="input_networks"></a> [networks](#input\_networks) | A map containing each network setting.<br><br>Example of variable deployment :<pre>networks = {<br> "panorama-vpc" = {<br> vpc_name = "firewall-vpc"<br> create_network = true<br> delete_default_routes_on_create = "false"<br> mtu = "1460"<br> routing_mode = "REGIONAL"<br> subnetworks = {<br> "panorama-sub" = {<br> name = "panorama-subnet"<br> create_subnetwork = true<br> ip_cidr_range = "172.21.21.0/24"<br> region = "us-central1"<br> }<br> }<br> firewall_rules = {<br> "allow-panorama-ingress" = {<br> name = "panorama-mgmt"<br> source_ranges = ["1.1.1.1/32", "2.2.2.2/32"]<br> priority = "1000"<br> allowed_protocol = "all"<br> allowed_ports = []<br> }<br> }<br> }</pre>For a full list of available configuration items - please refer to [module documentation](https://github.com/PaloAltoNetworks/terraform-google-vmseries-modules/tree/main/modules/vpc#input_networks)<br><br>Multiple keys can be added and will be deployed by the code | `any` | n/a | yes | 114 | | <a name="input_panoramas"></a> [panoramas](#input\_panoramas) | A map containing each panorama setting.<br><br>Example of variable deployment :<pre>panoramas = {<br> "panorama-01" = {<br> panorama_name = "panorama-01"<br> panorama_vpc = "panorama-vpc"<br> panorama_subnet = "panorama-subnet"<br> panorama_version = "panorama-byol-1000"<br> ssh_keys = "admin:PUBLIC-KEY"<br> attach_public_ip = true<br> private_static_ip = "172.21.21.2"<br> }<br>}</pre>For a full list of available configuration items - please refer to [module documentation](https://github.com/PaloAltoNetworks/terraform-google-vmseries-modules/tree/main/modules/panorama#inputs)<br><br>Multiple keys can be added and will be deployed by the code | `any` | n/a | yes | 115 | | <a name="input_project"></a> [project](#input\_project) | The project name to deploy the infrastructure in to. | `string` | `null` | no | 116 | | <a name="input_region"></a> [region](#input\_region) | The region into which to deploy the infrastructure in to | `string` | `"us-central1"` | no | 117 | 118 | ### Outputs 119 | 120 | | Name | Description | 121 | |------|-------------| 122 | | <a name="output_panorama_private_ips"></a> [panorama\_private\_ips](#output\_panorama\_private\_ips) | Private IP address of the Panorama instance. | 123 | | <a name="output_panorama_public_ips"></a> [panorama\_public\_ips](#output\_panorama\_public\_ips) | Public IP address of the Panorama instance. | 124 | <!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> 125 | -------------------------------------------------------------------------------- /modules/vpc/variables.tf: -------------------------------------------------------------------------------- 1 | variable "project_id" { 2 | description = "Project in which to create or look for VPCs and subnets" 3 | default = null 4 | type = string 5 | } 6 | 7 | variable "name" { 8 | description = "The name of the created or already existing VPC Network." 9 | type = string 10 | } 11 | 12 | variable "create_network" { 13 | description = <<-EOF 14 | A flag to indicate the creation or import of a VPC network. 15 | Setting this to `true` will create a new network managed by Terraform. 16 | Setting this to `false` will try to read the existing network identified by `name` and `project` variables. 17 | EOF 18 | default = true 19 | type = bool 20 | } 21 | 22 | variable "subnetworks" { 23 | description = <<-EOF 24 | A map containing subnetworks configuration. Subnets can belong to different regions. 25 | List of available attributes of each subnetwork entry: 26 | - `name` : Name of the subnetwork. 27 | - `create_subnetwork` : Boolean value to control the creation or reading of the subnetwork. If set to `true` - this will create the subnetwork. If set to `false` - this will read a subnet with provided information. 28 | - `ip_cidr_range` : A string that contains the subnetwork to create. Only IPv4 format is supported. 29 | - `region` : Region where to configure or import the subnet. 30 | 31 | Example: 32 | ``` 33 | subnetworks = { 34 | my-sub = { 35 | name = "my-sub" 36 | create_subnetwork = true 37 | ip_cidr_range = "192.168.0.0/24" 38 | region = "us-east1" 39 | } 40 | } 41 | ``` 42 | EOF 43 | default = {} 44 | type = map(object({ 45 | name = string 46 | create_subnetwork = optional(bool, true) 47 | ip_cidr_range = string 48 | region = string 49 | })) 50 | } 51 | 52 | variable "firewall_rules" { 53 | description = <<-EOF 54 | A map containing each firewall rule configuration. 55 | Action of the firewall rule is always `allow`. 56 | The only possible direction of the firewall rule is `INGRESS`. 57 | 58 | List of available attributes of each firewall rule entry: 59 | - `name` : Name of the firewall rule. 60 | - `source_ranges` : (Optional) A list of strings containing the source IP ranges to be allowed on the firewall rule. 61 | - `source_tags` : (Optional) A list of strings containing the source network tags to be allowed on the firewall rule. 62 | - `source_service_accounts` : (Optional) A list of strings containg the source servce accounts to be allowed on the firewall rule. 63 | - `target_service_accounts` : (Optional) A list of strings containing the service accounts for which the firewall rule applies to. 64 | - `target_tags` : (Optional) A list of strings containing the network tags for which the firewall rule applies to. 65 | - `allowed_protocol` : The protocol type to match in the firewall rule. Possible values are: `tcp`, `udp`, `icmp`, `esp`, `ah`, `sctp`, `ipip`, `all`. 66 | - `ports` : A list of strings containing TCP or UDP port numbers to match in the firewall rule. This type of setting can only be configured if allowing TCP and UDP as protocols. 67 | - `priority` : (Optional) A priority value for the firewall rule. The lower the number - the more preferred the rule is. 68 | - `log_metadata` : (Optional) This field denotes whether to include or exclude metadata for firewall logs. Possible values are: `EXCLUDE_ALL_METADATA`, `INCLUDE_ALL_METADATA`. 69 | 70 | Example : 71 | ``` 72 | firewall_rules = { 73 | firewall-rule-1 = { 74 | name = "first-rule" 75 | source_ranges = ["10.10.10.0/24", "1.1.1.0/24"] 76 | priority = "2000" 77 | target_tags = ["vmseries-firewalls"] 78 | allowed_protocol = "TCP" 79 | allowed_ports = ["443", "22"] 80 | } 81 | } 82 | ``` 83 | EOF 84 | default = {} 85 | type = map(object({ 86 | name = string 87 | source_ranges = optional(list(string)) 88 | source_tags = optional(list(string)) 89 | source_service_accounts = optional(list(string)) 90 | allowed_protocol = string 91 | allowed_ports = list(string) 92 | priority = optional(string) 93 | target_service_accounts = optional(list(string)) 94 | target_tags = optional(list(string)) 95 | log_metadata = optional(string) 96 | })) 97 | validation { 98 | condition = length(var.firewall_rules) > 0 ? alltrue([ 99 | for rule in var.firewall_rules : ( 100 | (rule.source_ranges != null && rule.source_tags == null && rule.source_service_accounts == null) || 101 | (rule.source_ranges == null && rule.source_tags != null && rule.source_service_accounts == null) || 102 | (rule.source_ranges == null && rule.source_tags == null && rule.source_service_accounts != null) 103 | ) 104 | ]) : true 105 | error_message = "Only one of the following source types can be selected per firewall rule : source_ranges, source_tags or source_service_accounts ." 106 | } 107 | validation { 108 | condition = length(var.firewall_rules) > 0 ? alltrue([ 109 | for rule in var.firewall_rules : ( 110 | (rule.target_tags != null && rule.target_service_accounts == null) || 111 | (rule.target_tags == null && rule.target_service_accounts != null) || 112 | (rule.target_tags == null && rule.target_service_accounts == null) 113 | ) 114 | ]) : true 115 | error_message = "Only one of the following target types can be selected per firewall rule : target_tags, target_service_accounts or neither (not configuring either of them will apply the firewall rule to all instances in the network)." 116 | } 117 | } 118 | 119 | variable "delete_default_routes_on_create" { 120 | description = <<-EOF 121 | A flag to indicate the deletion of the default routes at VPC creation. 122 | Setting this to `true` the default route `0.0.0.0/0` will be deleted upon network creation. 123 | Setting this to `false` the default route `0.0.0.0/0` will be not be deleted upon network creation. 124 | EOF 125 | default = false 126 | type = bool 127 | } 128 | 129 | variable "mtu" { 130 | description = <<-EOF 131 | MTU value for VPC Network. Acceptable values are between 1300 and 8896. 132 | EOF 133 | default = 1460 134 | type = number 135 | validation { 136 | condition = var.mtu >= 1300 && var.mtu <= 8896 137 | error_message = "MTU value must be between 1300 and 8896." 138 | } 139 | } 140 | 141 | variable "routing_mode" { 142 | description = <<-EOF 143 | Type of network-wide routing mode to use. Possible types are: REGIONAL and GLOBAL. 144 | REGIONAL routing mode will set the cloud routers to only advertise subnetworks within the same region as the router. 145 | GLOBAL routing mode will set the cloud routers to advertise all the subnetworks that belong to this network. 146 | EOF 147 | default = "REGIONAL" 148 | type = string 149 | validation { 150 | condition = var.routing_mode == "REGIONAL" || var.routing_mode == "GLOBAL" 151 | error_message = "Routing mode must be either 'REGIONAL' or 'GLOBAL'." 152 | } 153 | } -------------------------------------------------------------------------------- /modules/vpc/README.md: -------------------------------------------------------------------------------- 1 | # VPC Network Module for GCP 2 | 3 | A Terraform module for deploying a VPC and associated subnetworks and firewall rules in GCP. 4 | 5 | One advantage of this module over the [terraform-google-network](https://github.com/terraform-google-modules/terraform-google-network/tree/master) module is that this module lets you use existing VPC networks and subnetworks to support brownfield deployments. 6 | 7 | ## Reference 8 | <!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> 9 | ### Requirements 10 | 11 | | Name | Version | 12 | |------|---------| 13 | | <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.3, < 2.0 | 14 | | <a name="requirement_google"></a> [google](#requirement\_google) | ~> 4.54 | 15 | 16 | ### Providers 17 | 18 | | Name | Version | 19 | |------|---------| 20 | | <a name="provider_google"></a> [google](#provider\_google) | ~> 4.54 | 21 | 22 | ### Modules 23 | 24 | No modules. 25 | 26 | ### Resources 27 | 28 | | Name | Type | 29 | |------|------| 30 | | [google_compute_firewall.this](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_firewall) | resource | 31 | | [google_compute_network.this](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_network) | resource | 32 | | [google_compute_subnetwork.this](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_subnetwork) | resource | 33 | | [google_compute_network.this](https://registry.terraform.io/providers/hashicorp/google/latest/docs/data-sources/compute_network) | data source | 34 | | [google_compute_subnetwork.this](https://registry.terraform.io/providers/hashicorp/google/latest/docs/data-sources/compute_subnetwork) | data source | 35 | 36 | ### Inputs 37 | 38 | | Name | Description | Type | Default | Required | 39 | |------|-------------|------|---------|:--------:| 40 | | <a name="input_create_network"></a> [create\_network](#input\_create\_network) | A flag to indicate the creation or import of a VPC network.<br>Setting this to `true` will create a new network managed by Terraform.<br>Setting this to `false` will try to read the existing network identified by `name` and `project` variables. | `bool` | `true` | no | 41 | | <a name="input_delete_default_routes_on_create"></a> [delete\_default\_routes\_on\_create](#input\_delete\_default\_routes\_on\_create) | A flag to indicate the deletion of the default routes at VPC creation.<br>Setting this to `true` the default route `0.0.0.0/0` will be deleted upon network creation.<br>Setting this to `false` the default route `0.0.0.0/0` will be not be deleted upon network creation. | `bool` | `false` | no | 42 | | <a name="input_firewall_rules"></a> [firewall\_rules](#input\_firewall\_rules) | A map containing each firewall rule configuration.<br>Action of the firewall rule is always `allow`.<br>The only possible direction of the firewall rule is `INGRESS`.<br><br>List of available attributes of each firewall rule entry:<br>- `name` : Name of the firewall rule.<br>- `source_ranges` : (Optional) A list of strings containing the source IP ranges to be allowed on the firewall rule.<br>- `source_tags` : (Optional) A list of strings containing the source network tags to be allowed on the firewall rule.<br>- `source_service_accounts` : (Optional) A list of strings containg the source servce accounts to be allowed on the firewall rule.<br>- `target_service_accounts` : (Optional) A list of strings containing the service accounts for which the firewall rule applies to.<br>- `target_tags` : (Optional) A list of strings containing the network tags for which the firewall rule applies to. <br>- `allowed_protocol` : The protocol type to match in the firewall rule. Possible values are: `tcp`, `udp`, `icmp`, `esp`, `ah`, `sctp`, `ipip`, `all`.<br>- `ports` : A list of strings containing TCP or UDP port numbers to match in the firewall rule. This type of setting can only be configured if allowing TCP and UDP as protocols.<br>- `priority` : (Optional) A priority value for the firewall rule. The lower the number - the more preferred the rule is.<br>- `log_metadata` : (Optional) This field denotes whether to include or exclude metadata for firewall logs. Possible values are: `EXCLUDE_ALL_METADATA`, `INCLUDE_ALL_METADATA`.<br><br>Example :<pre>firewall_rules = {<br> firewall-rule-1 = {<br> name = "first-rule"<br> source_ranges = ["10.10.10.0/24", "1.1.1.0/24"]<br> priority = "2000"<br> target_tags = ["vmseries-firewalls"]<br> allowed_protocol = "TCP"<br> allowed_ports = ["443", "22"]<br> }<br>}</pre> | <pre>map(object({<br> name = string<br> source_ranges = optional(list(string))<br> source_tags = optional(list(string))<br> source_service_accounts = optional(list(string))<br> allowed_protocol = string<br> allowed_ports = list(string)<br> priority = optional(string)<br> target_service_accounts = optional(list(string))<br> target_tags = optional(list(string))<br> log_metadata = optional(string)<br> }))</pre> | `{}` | no | 43 | | <a name="input_mtu"></a> [mtu](#input\_mtu) | MTU value for VPC Network. Acceptable values are between 1300 and 8896. | `number` | `1460` | no | 44 | | <a name="input_name"></a> [name](#input\_name) | The name of the created or already existing VPC Network. | `string` | n/a | yes | 45 | | <a name="input_project_id"></a> [project\_id](#input\_project\_id) | Project in which to create or look for VPCs and subnets | `string` | `null` | no | 46 | | <a name="input_routing_mode"></a> [routing\_mode](#input\_routing\_mode) | Type of network-wide routing mode to use. Possible types are: REGIONAL and GLOBAL.<br>REGIONAL routing mode will set the cloud routers to only advertise subnetworks within the same region as the router.<br>GLOBAL routing mode will set the cloud routers to advertise all the subnetworks that belong to this network. | `string` | `"REGIONAL"` | no | 47 | | <a name="input_subnetworks"></a> [subnetworks](#input\_subnetworks) | A map containing subnetworks configuration. Subnets can belong to different regions.<br>List of available attributes of each subnetwork entry:<br>- `name` : Name of the subnetwork.<br>- `create_subnetwork` : Boolean value to control the creation or reading of the subnetwork. If set to `true` - this will create the subnetwork. If set to `false` - this will read a subnet with provided information.<br>- `ip_cidr_range` : A string that contains the subnetwork to create. Only IPv4 format is supported.<br>- `region` : Region where to configure or import the subnet.<br><br>Example:<pre>subnetworks = {<br> my-sub = {<br> name = "my-sub"<br> create_subnetwork = true<br> ip_cidr_range = "192.168.0.0/24"<br> region = "us-east1"<br> }<br>}</pre> | <pre>map(object({<br> name = string<br> create_subnetwork = optional(bool, true)<br> ip_cidr_range = string<br> region = string<br> }))</pre> | `{}` | no | 48 | 49 | ### Outputs 50 | 51 | | Name | Description | 52 | |------|-------------| 53 | | <a name="output_network"></a> [network](#output\_network) | Created or read network attributes. | 54 | | <a name="output_subnetworks"></a> [subnetworks](#output\_subnetworks) | Map containing key, value pairs of created or read subnetwork attributes. | 55 | <!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> 56 | --------------------------------------------------------------------------------