diff --git a/.ci/cloudbuild.lint.yaml b/.ci/cloudbuild.lint.yaml
index 4a178185..a4fe27a4 100644
--- a/.ci/cloudbuild.lint.yaml
+++ b/.ci/cloudbuild.lint.yaml
@@ -43,12 +43,14 @@ steps:
args:
[
"/workspace/tools/check_documentation.py",
- "modules",
"cloud-operations",
"data-solutions",
"data-solutions/data-platform-foundations",
+ "factories",
+ "factories/firewall-vpc-rules",
"foundations",
- "networking",
+ "modules",
+ "networking"
]
substitutions:
diff --git a/.ci/cloudbuild.test.environments.yaml b/.ci/cloudbuild.test.environments.yaml
index 9c5b2a6f..180f33de 100644
--- a/.ci/cloudbuild.test.environments.yaml
+++ b/.ci/cloudbuild.test.environments.yaml
@@ -32,6 +32,7 @@ steps:
- -vv
- tests/cloud_operations
- tests/data_solutions
+ - tests/factories
- tests/foundations
- tests/networking
env:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 550698fe..ce4af682 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
+- new `factories` top-level folder with initial `subnets`, `firewall-hierarchical-policies`, `firewall-vpc-rules` and `example-environments` examples
- new cloud operations example showing how to deploy infrastructure for [Compute Engine image builder based on Hashicorp Packer](./cloud-operations/packer-image-builder)
- **incompatible change** the format of the `records` variable in the `dns` module has changed, to better support dynamic values
- new `naming-convention` module
diff --git a/README.md b/README.md
index f00bc0e3..3931c8fd 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ Both the examples and modules require some measure of Terraform skills to be use
## End-to-end examples
-The examples in this repository are split in several main sections: **foundational examples** that bootstrap the organizational hierarchy and automation prerequisites, **networking examples** that implement core patterns or features, **data solutions examples** that demonstrate how to integrate data services in complete scenarios, and **cloud operations examples** that leverage specific products to meet specific operational needs.
+The examples in this repository are split in several main sections: **foundational examples** that bootstrap the organizational hierarchy and automation prerequisites, **networking examples** that implement core patterns or features, **data solutions examples** that demonstrate how to integrate data services in complete scenarios, **cloud operations examples** that leverage specific products to meet specific operational needs and **factories** that implement resource factories for the repetitive creation of specific resources.
Currently available examples:
@@ -21,8 +21,9 @@ Currently available examples:
- **data solutions** - [GCE/GCS CMEK via centralized Cloud KMS](./data-solutions/cmek-via-centralized-kms/), [Cloud Storage to Bigquery with Cloud Dataflow](./data-solutions/gcs-to-bq-with-dataflow/)
- **cloud operations** - [Resource tracking and remediation via Cloud Asset feeds](.//cloud-operations/asset-inventory-feed-remediation), [Granular Cloud DNS IAM via Service Directory](./cloud-operations/dns-fine-grained-iam), [Granular Cloud DNS IAM for Shared VPC](./cloud-operations/dns-shared-vpc), [Compute Engine quota monitoring](./cloud-operations/quota-monitoring), [Scheduled Cloud Asset Inventory Export to Bigquery](./cloud-operations/scheduled-asset-inventory-export-bq), [Packer image builder](./cloud-operations/packer-image-builder)
- **third party solutions** - [OpenShift cluster on Shared VPC](./third-party-solutions/openshift)
+- **factories** - [Example environments](./factories/example-environments), [Hierarchical Firewall Policies](./factories/firewall-hierarchical-policies), [VPC Firewall Rules](./factories/firewall-vpc-rules), [Subnets](./factories/subnets)
-For more information see the README files in the [foundations](./foundations/), [networking](./networking/), [data solutions](./data-solutions/) and [cloud operations](./cloud-operations/) folders.
+For more information see the README files in the [foundations](./foundations/), [networking](./networking/), [data solutions](./data-solutions/), [cloud operations](./cloud-operations/) and [factories](./factories/) folders.
## Modules
diff --git a/factories/README.md b/factories/README.md
new file mode 100644
index 00000000..f23e7990
--- /dev/null
+++ b/factories/README.md
@@ -0,0 +1,67 @@
+# Resource Factories
+
+This set of modules creates specialized resource factories, made of two distinct components:
+
+- a module, which implements the factory logic in Terraform syntax, and
+- a set of directories, which hold the configuration for the factory in YAML syntax.
+
+## Available modules
+
+- [Hierarchical Firewall policies](./firewall-hierarchical-policies)
+- [VPC Firewall rules](./firewall-vpc-rules)
+- [Subnets](./subnets)
+
+## Using the modules
+
+Each module specialises on a single resource type, and comes with a `README.md` file which describes the module interface, and the directory/file structure each module requires.
+
+All modules consume specialized `yaml` configurations - located on a well-defined directory structure that carries metadata. Let's observe an example from the [Example environments](example-environments/) directory:
+
+```yaml
+# ../example-environments/prod/conf/project-prod-a/vpc-alpha/subnet-alpha-a.yaml
+
+region: europe-west3
+ip_cidr_range: 10.0.0.0/24
+description: Sample Subnet in project project-prod-a, vpc-alpha
+secondary_ip_ranges:
+ secondary-range-a: 192.168.0.0/24
+ secondary-range-b: 192.168.1.0/24
+```
+
+This configuration creates the `subnet-alpha-a` subnet, located in VPC `vpc-alpha`, inside project `project-prod-a`.
+
+All modules consume specialized `yaml` configurations - located on a well-defined directory structure that carries metadata. Let's observe an example from the [Example environments](example-environments/) directory:
+
+```yaml
+# ../example-environments/prod/conf/project-prod-a/vpc-alpha/subnet-alpha-a.yaml
+
+region: europe-west3
+ip_cidr_range: 10.0.0.0/24
+description: Sample Subnet in project project-prod-a, vpc-alpha
+secondary_ip_ranges:
+ secondary-range-a: 192.168.0.0/24
+ secondary-range-b: 192.168.1.0/24
+```
+
+This configuration creates the `subnet-alpha-a` subnet, located in VPC `vpc-alpha`, inside project `project-prod-a`.
+
+## Rationale
+
+This approach is based on modules implementing the factory logic using Terraform code, and a set of directories having a well-defined, semantic structure holding the configuration for the resources in YaML syntax.
+
+Resource factories are designed to:
+
+- accelerate and rationalize the repetitive creation of common resources, such as firewall rules and subnets
+- enable teams without Terraform specific knowledge to leverage IaC via human-friendly and machine-parseable YAML files
+- make it simple to implement specific requirements and best practices (e.g. "always enable PGA for GCP subnets", or "only allow using regions `europe-west1` and `europe-west3`")
+- codify and centralise business logics and policies (e.g. labels and naming conventions)
+- allow to easily parse and understand sets of specific resources, for documentation purposes
+
+Terraform natively supports YaML, JSON and CSV parsing - however we've decided to embrace YaML for the following reasons:
+
+- YaML is easier to parse for a human, and allows for comments and nested, complex structures
+- JSON and CSV can't include comments, which can be used to document configurations, but are often useful to bridge from other systems in automated pipelines
+- JSON is more verbose (reads: longer) and harder to parse visually for humans
+- CSV isn't often expressive enough (e.g. dit oesn't allow for nested structures)
+
+If needed, converting factories to consume JSON is a matter of switching from `yamldecode()` to `jsondecode()` in the right place on each module.
diff --git a/factories/example-environments/README.md b/factories/example-environments/README.md
new file mode 100644
index 00000000..3bac9098
--- /dev/null
+++ b/factories/example-environments/README.md
@@ -0,0 +1,43 @@
+# Resource Factories
+
+The examples in this folder are derived from actual production use cases, and show how to use a factory module, and how to structure a codebase for multiple environments.
+
+## Resource Factories usage - Managing subnets
+
+At the top level of this directory, besides the `README.md` your're reading now, you'll find
+
+- `dev/`, a directory which holds all configurations for the *development* environment
+- `prod/`, a directory which holds all configurations for the *production* environment
+
+and on each directory, `main.tf`, a simple terraform file which consumes the [`subnets`](../subnets/) module and the configurations.
+
+Each environment directory structure is meant to mimic your GCP resource structure
+
+```
+.
+├── dev # Environment
+│ ├── conf # Configuration directory
+│ │ ├── project-dev-a # Project id
+│ │ │ └── vpc-alpha # VPC name
+│ │ │ ├── subnet-alpha-a.yaml # Subnet name (one file per subnet)
+│ │ │ └── subnet-alpha-b.yaml
+│ │ └── project-dev-b
+│ │ ├── vpc-beta
+│ │ │ └── subnet-beta-a.yaml
+│ │ └── vpc-gamma
+│ │ └── subnet-gamma-a.yaml
+│ └── main.tf
+└── prod
+ ├── conf
+ │ └── project-prod-a
+ │ └── vpc-alpha
+ │ ├── subnet-alpha-a.yaml
+ │ └── subnet-alpha-b.yaml
+ └── main.tf
+```
+
+Since this resource factory only creates subnets, projects and VPCs are expected to exist.
+
+In this example, each environment is implemented as a distinct factory, and each has its own `main.tf` file (and hence a dedicated state).
+Another option you might want to consider, in line with the CI/CD pipeline or processes you have in place, might be to leverage a single `main.tf` consuming both environment directories.
+
diff --git a/factories/example-environments/dev/conf/project-dev-a/vpc-alpha/subnet-alpha-a.yaml b/factories/example-environments/dev/conf/project-dev-a/vpc-alpha/subnet-alpha-a.yaml
new file mode 100644
index 00000000..13973106
--- /dev/null
+++ b/factories/example-environments/dev/conf/project-dev-a/vpc-alpha/subnet-alpha-a.yaml
@@ -0,0 +1,8 @@
+# skip boilerplate check
+
+region: europe-west3
+ip_cidr_range: 10.0.0.0/24
+description: Sample Subnet in project project-dev-a, vpc-alpha
+secondary_ip_ranges:
+ secondary-range-a: 192.168.0.0/24
+ secondary-range-b: 192.168.1.0/24
diff --git a/factories/example-environments/dev/conf/project-dev-a/vpc-alpha/subnet-alpha-b.yaml b/factories/example-environments/dev/conf/project-dev-a/vpc-alpha/subnet-alpha-b.yaml
new file mode 100644
index 00000000..fb859e72
--- /dev/null
+++ b/factories/example-environments/dev/conf/project-dev-a/vpc-alpha/subnet-alpha-b.yaml
@@ -0,0 +1,6 @@
+# skip boilerplate check
+
+region: europe-west3
+ip_cidr_range: 10.0.1.0/24
+description: Sample Subnet in project project-dev-a, vpc-alpha
+private_ip_google_access: false
diff --git a/factories/example-environments/dev/conf/project-dev-b/vpc-beta/subnet-beta-a.yaml b/factories/example-environments/dev/conf/project-dev-b/vpc-beta/subnet-beta-a.yaml
new file mode 100644
index 00000000..c3512f8c
--- /dev/null
+++ b/factories/example-environments/dev/conf/project-dev-b/vpc-beta/subnet-beta-a.yaml
@@ -0,0 +1,7 @@
+# skip boilerplate check
+
+region: europe-west4
+ip_cidr_range: 10.0.2.0/24
+description: Sample Subnet in project project-dev-b, vpc-beta
+iam_users: ["sruffilli@google.com"]
+iam_groups: []
diff --git a/factories/example-environments/dev/conf/project-dev-b/vpc-gamma/subnet-gamma-a.yaml b/factories/example-environments/dev/conf/project-dev-b/vpc-gamma/subnet-gamma-a.yaml
new file mode 100644
index 00000000..9a9b8e17
--- /dev/null
+++ b/factories/example-environments/dev/conf/project-dev-b/vpc-gamma/subnet-gamma-a.yaml
@@ -0,0 +1,5 @@
+# skip boilerplate check
+
+region: europe-west4
+ip_cidr_range: 10.0.3.0/24
+description: Sample Subnet in project project-dev-b, vpc-gamma
diff --git a/factories/example-environments/dev/main.tf b/factories/example-environments/dev/main.tf
new file mode 100644
index 00000000..a7c58c1e
--- /dev/null
+++ b/factories/example-environments/dev/main.tf
@@ -0,0 +1,21 @@
+/**
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+module "subnets" {
+ source = "../../subnets"
+ config_folder = "conf"
+}
diff --git a/factories/example-environments/prod/conf/project-prod-a/vpc-alpha/subnet-alpha-a.yaml b/factories/example-environments/prod/conf/project-prod-a/vpc-alpha/subnet-alpha-a.yaml
new file mode 100644
index 00000000..26bb4bdd
--- /dev/null
+++ b/factories/example-environments/prod/conf/project-prod-a/vpc-alpha/subnet-alpha-a.yaml
@@ -0,0 +1,8 @@
+# skip boilerplate check
+
+region: europe-west3
+ip_cidr_range: 10.0.0.0/24
+description: Sample Subnet in project project-prod-a, vpc-alpha
+secondary_ip_ranges:
+ secondary-range-a: 192.168.0.0/24
+ secondary-range-b: 192.168.1.0/24
diff --git a/factories/example-environments/prod/conf/project-prod-a/vpc-alpha/subnet-alpha-b.yaml b/factories/example-environments/prod/conf/project-prod-a/vpc-alpha/subnet-alpha-b.yaml
new file mode 100644
index 00000000..cb1a77a1
--- /dev/null
+++ b/factories/example-environments/prod/conf/project-prod-a/vpc-alpha/subnet-alpha-b.yaml
@@ -0,0 +1,6 @@
+# skip boilerplate check
+
+region: europe-west3
+ip_cidr_range: 10.0.1.0/24
+description: Sample Subnet in project project-prod-a, vpc-alpha
+private_ip_google_access: false
diff --git a/factories/example-environments/prod/main.tf b/factories/example-environments/prod/main.tf
new file mode 100644
index 00000000..a7c58c1e
--- /dev/null
+++ b/factories/example-environments/prod/main.tf
@@ -0,0 +1,21 @@
+/**
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+module "subnets" {
+ source = "../../subnets"
+ config_folder = "conf"
+}
diff --git a/factories/firewall-hierarchical-policies/README.md b/factories/firewall-hierarchical-policies/README.md
new file mode 100644
index 00000000..da317a6f
--- /dev/null
+++ b/factories/firewall-hierarchical-policies/README.md
@@ -0,0 +1,164 @@
+# Google Cloud Resource Factories - Hierarchical Firewall Policies
+
+This module implements a resource factory which allows the creation and management of [hierarchical firewall policies](https://cloud.google.com/vpc/docs/firewall-policies) through properly formatted `yaml` files.
+
+`yaml` configurations are stored on a well-defined folder structure, whose entry point can be customized, and which allows for simple grouping of policies by Organization ID.
+
+This module also allows defining custom template variables, to centralize common CIDRs or Service Account lists, which enables re-using them across different policies.
+
+## Example
+
+### Terraform code
+
+```hcl
+module "hierarchical" {
+ source = "./modules/resource-factories/hierarchical-firewall"
+ config_folder = "firewall/hierarchical"
+ templates_folder = "firewall/templates"
+}
+# tftest:skip
+```
+
+### Configuration Structure
+
+The naming convention for the `config_folder` variable requires
+
+- the first directory layer to be named after the organization ID we're creating the policies for
+- each file to be named either `$folder_id-$description.yaml` (e.g. `1234567890-sharedinfra.yaml`) for policies applying to regular folders or `org.yaml` for the root folder.
+
+Organizations and folders should exist prior to running this module, or set as an explicit dependency to this module, leveraging `depends_on`.
+
+The optional `templates_folder` variable can have two files.
+
+- `cidrs.yaml` - a YAML map defining lists of CIDRs
+- `service_accounts.yaml` - a YAML map defining lists of Service Accounts
+
+Examples for both files are shown in the following section.
+
+```bash
+└── firewall
+ ├── hierarchical
+ │ ├── 31415926535
+ │ │ ├── 1234567890-sharedinfra.yaml # Maps to folders/1234567890
+ │ │ └── org.yaml # Maps to organizations/31415926535
+ │ └── 27182818284
+ │ └── 1234567891-sharedinfra.yaml # Maps to folders/1234567891
+ └── templates
+ ├── cidrs.yaml
+ └── service_accounts.yaml
+```
+
+### Hierarchical firewall policies format and structure
+
+The following syntax applies for both `$folder_id-$description.yaml` and for `org.yaml` files, with the former applying at the `$folder_id` level and the latter at the Organization level.
+
+Each file can contain an arbitrary number of policies.
+
+```yaml
+# Policy name
+allow-icmp:
+ # Description
+ description: Sample policy
+ # Direction {INGRESS, EGRESS}
+ direction: INGRESS
+ # Action {allow, deny}
+ action: allow
+ # Priority (must be unique on a node)
+ priority: 1000
+ # List of CIDRs this rule applies to (for INGRESS rules)
+ source_ranges:
+ - 0.0.0.0/0
+ # List of CIDRs this rule applies to (for EGRESS rules)
+ destination_ranges:
+ - 0.0.0.0/0
+ # List of ports this rule applies to (empty array means all ports)
+ ports:
+ tcp: []
+ udp: []
+ icmp: []
+ # List of VPCs this rule applies to - a null value implies all VPCs
+ target_resources: null
+ # Opt - List of target Service Accounts this rule applies to
+ target_service_accounts:
+ - example-service-account@foobar.iam.gserviceaccount.com
+ # Opt - Whether to enable logs - defaults to false
+ enable_logging: true
+```
+
+A sample configuration file might look like the following one:
+
+```yaml
+allow-icmp:
+ description: Enable ICMP for all hosts
+ direction: INGRESS
+ action: allow
+ priority: 1000
+ source_ranges:
+ - 0.0.0.0/0
+ ports:
+ icmp: []
+ target_resources: null
+ enable_logging: false
+
+allow-ssh-from-onprem:
+ description: Enable SSH for on prem hosts
+ direction: INGRESS
+ action: allow
+ priority: 1001
+ source_ranges:
+ - $onprem
+ ports:
+ tcp: ["22"]
+ target_resources: null
+ enable_logging: false
+
+allow-443-from-clients:
+ description: Enable HTTPS for web clients
+ direction: INGRESS
+ action: allow
+ priority: 1001
+ source_ranges:
+ - $web_clients
+ ports:
+ tcp: ["443"]
+ target_resources: null
+ target_service_accounts:
+ - $web_frontends
+ enable_logging: false
+```
+
+with `firewall/templates/cidrs.yaml` defined as follows:
+
+```yaml
+onprem:
+ - 10.0.0.0/8
+ - 192.168.0.0/16
+
+web_clients:
+ - 172.16.0.0/16
+ - 10.0.10.0/24
+ - 10.0.250.0/24
+```
+
+and `firewall/templates/service_accounts.yaml`:
+
+```yaml
+web_frontends:
+ - web-frontends@project-wf1.iam.gserviceaccount.com
+ - web-frontends@project-wf2.iam.gserviceaccount.com
+```
+
+
+## Variables
+
+| name | description | type | required | default |
+|---|---|:---: |:---:|:---:|
+| config_folder | Relative path of the folder containing the hierarchical firewall configuration | string
| ✓ | |
+| templates_folder | Relative path of the folder containing the cidr/service account templates | string
| ✓ | |
+
+## Outputs
+
+| name | description | sensitive |
+|---|---|:---:|
+| hierarchical-firewall-rules | Generated Hierarchical Firewall Rules | |
+
diff --git a/factories/firewall-hierarchical-policies/main.tf b/factories/firewall-hierarchical-policies/main.tf
new file mode 100644
index 00000000..3b049236
--- /dev/null
+++ b/factories/firewall-hierarchical-policies/main.tf
@@ -0,0 +1,98 @@
+/**
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+locals {
+ cidrs = try({ for name, cidrs in yamldecode(file("${var.templates_folder}/cidrs.yaml")) :
+ name => cidrs
+ }, {})
+
+ service_accounts = try({ for name, service_accounts in yamldecode(file("${var.templates_folder}/service_accounts.yaml")) :
+ name => service_accounts
+ }, {})
+
+ org_paths = {
+ for file in fileset(var.config_folder, "**/*.yaml") :
+ file => split("/", file)[1] == "org.yaml"
+ ? "organizations/${split("/", file)[0]}"
+ : "folders/${split("-", split("/", file)[1])[0]}"
+ }
+
+ rules = flatten([
+ for file in fileset(var.config_folder, "**/*.yaml") : [
+ for key, ruleset in yamldecode(file("${var.config_folder}/${file}")) :
+ merge(ruleset, {
+ parent_id = local.org_paths[file]
+ name = "${replace(local.org_paths[file], "/", "-")}-${key}"
+ source_ranges = try(ruleset.source_ranges, null) == null ? null : flatten(
+ [for cidr in ruleset.source_ranges :
+ can(regex("^\\$", cidr))
+ ? local.cidrs[trimprefix(cidr, "$")]
+ : [cidr]
+ ])
+ destination_ranges = try(ruleset.destination_ranges, null) == null ? null : flatten(
+ [for cidr in ruleset.destination_ranges :
+ can(regex("^\\$", cidr))
+ ? local.cidrs[trimprefix(cidr, "$")]
+ : [cidr]
+ ])
+ target_service_accounts = try(ruleset.target_service_accounts, null) == null ? null : flatten(
+ [for service_account in ruleset.target_service_accounts :
+ can(regex("^\\$", service_account))
+ ? local.service_accounts[trimprefix(service_account, "$")]
+ : [service_account]
+ ])
+ })
+ ]
+ ])
+}
+
+resource "google_compute_firewall_policy" "default" {
+ for_each = { for rule in local.rules : rule.parent_id => rule.name... }
+ short_name = replace("hierarchical-fw-policy-${each.key}", "/", "-")
+ description = replace("hierarchical-fw-policy-${each.key}", "/", "-")
+ parent = each.key
+}
+
+resource "google_compute_firewall_policy_rule" "default" {
+ for_each = { for rule in local.rules : "${rule.parent_id}-${rule.name}" => rule }
+ firewall_policy = google_compute_firewall_policy.default[each.value.parent_id].id
+ action = each.value.action
+ direction = each.value.direction
+ priority = each.value.priority
+ target_resources = each.value.target_resources
+ target_service_accounts = each.value.target_service_accounts
+ enable_logging = try(each.value.enable_logging, false)
+ # preview = each.value.preview
+ match {
+ src_ip_ranges = each.value.direction == "INGRESS" ? each.value.source_ranges : null
+ dest_ip_ranges = each.value.direction == "EGRESS" ? each.value.destination_ranges : null
+ dynamic "layer4_configs" {
+ for_each = each.value.ports
+ iterator = port
+ content {
+ ip_protocol = port.key
+ ports = port.value
+ }
+ }
+ }
+}
+
+resource "google_compute_firewall_policy_association" "default" {
+ for_each = { for rule in local.rules : rule.parent_id => rule.name... }
+ name = replace("hierarchical-fw-policy-${each.key}", "/", "-")
+ attachment_target = google_compute_firewall_policy.default[each.key].parent
+ firewall_policy = google_compute_firewall_policy.default[each.key].id
+}
diff --git a/factories/firewall-hierarchical-policies/outputs.tf b/factories/firewall-hierarchical-policies/outputs.tf
new file mode 100644
index 00000000..42d6738d
--- /dev/null
+++ b/factories/firewall-hierarchical-policies/outputs.tf
@@ -0,0 +1,26 @@
+/**
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+output "hierarchical-firewall-rules" {
+ description = "Generated Hierarchical Firewall Rules"
+ value = {
+ for k, v in google_compute_firewall_policy_rule.default :
+ k => {
+ parent_id = split("-", k)[0]
+ id = v.id
+ }
+ }
+}
diff --git a/factories/firewall-hierarchical-policies/variables.tf b/factories/firewall-hierarchical-policies/variables.tf
new file mode 100644
index 00000000..503a2e01
--- /dev/null
+++ b/factories/firewall-hierarchical-policies/variables.tf
@@ -0,0 +1,25 @@
+/**
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+variable "config_folder" {
+ description = "Relative path of the folder containing the hierarchical firewall configuration"
+ type = string
+}
+
+variable "templates_folder" {
+ description = "Relative path of the folder containing the cidr/service account templates"
+ type = string
+}
diff --git a/factories/firewall-vpc-rules/README.md b/factories/firewall-vpc-rules/README.md
new file mode 100644
index 00000000..fd65bf21
--- /dev/null
+++ b/factories/firewall-vpc-rules/README.md
@@ -0,0 +1,6 @@
+# Google Cloud VPC Firewall Factories
+
+This collection of modules implement two different metodologies for the creation of VPC firewall rules, both based on leveraging well-defined `yaml` configuration files.
+
+- The [flat module](flat/) delegates the definition of all firewall rules metadata (project, network amongst other) to the individual `yaml` configuration. This module allows for maximum flexibility, and a custom logical grouping of resources which can be trasversal to the traditional resource hierarchy, and could be useful in scenarios where networking is not managed centrally by a single team.
+- The [nested module](nested/) requires and enforces a semantical folder structure that carries some of the rules metadata (project and network), and leaves the rest to each `yaml` configuration. This solution allows for the definition of a resource hierarchy that is aligned with the organisational resource structure.
diff --git a/modules/net-vpc-firewall-yaml/README.md b/factories/firewall-vpc-rules/flat/README.md
similarity index 95%
rename from modules/net-vpc-firewall-yaml/README.md
rename to factories/firewall-vpc-rules/flat/README.md
index 2d4ab90e..c13c5f86 100644
--- a/modules/net-vpc-firewall-yaml/README.md
+++ b/factories/firewall-vpc-rules/flat/README.md
@@ -1,10 +1,10 @@
-# Google Cloud VPC Firewall - Yaml
+# Google Cloud VPC Firewall Factory - Flat hierarchy
This module allows creation and management of different types of firewall rules by defining them in well formatted `yaml` files.
Yaml abstraction for FW rules can simplify users onboarding and also makes rules definition simpler and clearer comparing to HCL.
-Nested folder structure for yaml configurations is supported, which allows better and structured code management for multiple teams and environments.
+Nested folder structure for yaml configurations is optionally supported, which allows better and structured code management for multiple teams and environments.
## Example
diff --git a/modules/net-vpc-firewall-yaml/main.tf b/factories/firewall-vpc-rules/flat/main.tf
similarity index 100%
rename from modules/net-vpc-firewall-yaml/main.tf
rename to factories/firewall-vpc-rules/flat/main.tf
diff --git a/modules/net-vpc-firewall-yaml/outputs.tf b/factories/firewall-vpc-rules/flat/outputs.tf
similarity index 100%
rename from modules/net-vpc-firewall-yaml/outputs.tf
rename to factories/firewall-vpc-rules/flat/outputs.tf
diff --git a/modules/net-vpc-firewall-yaml/variables.tf b/factories/firewall-vpc-rules/flat/variables.tf
similarity index 100%
rename from modules/net-vpc-firewall-yaml/variables.tf
rename to factories/firewall-vpc-rules/flat/variables.tf
diff --git a/modules/net-vpc-firewall-yaml/versions.tf b/factories/firewall-vpc-rules/flat/versions.tf
similarity index 100%
rename from modules/net-vpc-firewall-yaml/versions.tf
rename to factories/firewall-vpc-rules/flat/versions.tf
diff --git a/factories/firewall-vpc-rules/nested/README.md b/factories/firewall-vpc-rules/nested/README.md
new file mode 100644
index 00000000..b618ca8c
--- /dev/null
+++ b/factories/firewall-vpc-rules/nested/README.md
@@ -0,0 +1,140 @@
+# Google Cloud VPC Firewall Factory - Nested hierarchy
+
+This module implements a resource factory which allows the creation and management of [VPC firewall rules](https://cloud.google.com/vpc/docs/firewalls) via properly formatted `yaml` files.
+
+`yaml` configurations are stored in a well-defined folder structure, whose entry point can be customized, and which represents and forces the resource hierarchy a firewall rule belongs to (Project > VPC > Firewall Rule).
+
+This module also allows for the definition of template variables, allowing to centralize common CIDRs or Service Account lists, which enables re-using them across different policies.
+
+## Example
+
+### Terraform code
+
+```hcl
+module "vpc-firewall" {
+ source = "../../cloud-foundation-fabric/modules/resource-factories/vpc-firewall"
+ config_folder = "firewall/vpc"
+ templates_folder = "firewall/templates"
+}
+
+# tftest:skip
+```
+
+### Configuration Structure
+
+The naming convention for the `config_folder` folder **requires**
+
+- the first directory layer to be named after the project ID which contains the VPC we're creating the firewall rules for
+- the second directory layer to be named after the VPC we're creating the firewall rules for
+- `yaml` files contained in the "VPC" directory can be arbitrarily named, to allow for an easier logical grouping.
+
+Projects and VPCs should exist prior to running this module, or set as an explicit dependency to this module, leveraging `depends_on`.
+
+The optional `templates_folder` folder can have two files.
+
+- `cidrs.yaml` - a YAML map defining lists of CIDRs
+- `service_accounts.yaml` - a YAML map definint lists of Service Accounts
+
+```bash
+└── firewall
+ ├── vpc
+ │ ├── project-resource-factory-dev
+ │ │ └── vpc-resource-factory-dev-one
+ │ │ │ ├── frontend.yaml
+ │ │ │ └── backend.yaml
+ │ │ └── vpc-resource-factory-dev-two
+ │ │ ├── foo.yaml
+ │ │ └── bar.yaml
+ │ └── project-resource-factory-prod
+ │ │ └── vpc-resource-factory-prod-alpha
+ │ │ ├── lorem.yaml
+ │ │ └── ipsum.yaml
+ └── templates
+ ├── cidrs.yaml
+ └── service_accounts.yaml
+```
+
+### Rule definition format and structure
+
+Firewall rules configuration should be placed in a set of yaml files in a folder/s. Firewall rule entry structure is following:
+
+```yaml
+rule-name: # descriptive name, naming convention is adjusted by the module
+ description: "Allow icmp" # rule description
+ action: allow # `allow` or `deny`
+ direction: INGRESS # EGRESS or INGRESS
+ ports:
+ icmp: [] # {tcp, udp, icmp, all}: [ports], use [] for any port
+ priority: 1000 # rule priority value, default value is 1000
+ source_ranges: # list of source ranges
+ - 0.0.0.0/0
+ destination_ranges: # list of destination ranges
+ - 0.0.0.0/0
+ source_tags: ['some-tag'] # list of source tags
+ source_service_accounts: # list of source service accounts
+ - myapp@myproject-id.iam.gserviceaccount.com
+ target_tags: ['some-tag'] # list of target tags
+ target_service_accounts: # list of target service accounts
+ - myapp@myproject-id.iam.gserviceaccount.com
+ enable_logging: true # `false` or `true`, logging is enabled when `true`
+```
+
+A sample configuration file might look like the following one:
+
+```yaml
+allow-healthchecks:
+ description: "Allow traffic from healthcheck"
+ direction: INGRESS
+ action: allow
+ priority: 1000
+ source_ranges:
+ - $healthcheck
+ ports:
+ tcp: ["80"]
+ enable_logging: false
+
+allow-http:
+ description: "Allow traffic to LB backend"
+ direction: INGRESS
+ action: allow
+ priority: 1000
+ source_ranges:
+ - 0.0.0.0/0
+ target_service_accounts:
+ - $web_frontends
+ ports:
+ tcp: ["80", "443"]
+ enable_logging: false
+
+```
+
+with `firewall/templates/cidrs.yaml` defined as follows:
+
+```yaml
+healthcheck:
+ - 35.191.0.0/16
+ - 130.211.0.0/22
+```
+
+and `firewall/templates/service_accounts.yaml`:
+
+```yaml
+web_frontends:
+ - web-frontends@project-wf1.iam.gserviceaccount.com
+ - web-frontends@project-wf2.iam.gserviceaccount.com
+```
+
+
+## Variables
+
+| name | description | type | required | default |
+|---|---|:---: |:---:|:---:|
+| config_folder | Relative path of the folder containing the hierarchical firewall configuration | string
| ✓ | |
+| templates_folder | Relative path of the folder containing the cidr/service account templates | string
| ✓ | |
+
+## Outputs
+
+| name | description | sensitive |
+|---|---|:---:|
+| vpc-firewall-rules | Generated VPC Firewall Rules | |
+
diff --git a/factories/firewall-vpc-rules/nested/main.tf b/factories/firewall-vpc-rules/nested/main.tf
new file mode 100644
index 00000000..62f535bc
--- /dev/null
+++ b/factories/firewall-vpc-rules/nested/main.tf
@@ -0,0 +1,143 @@
+/**
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+locals {
+ cidrs = try({ for name, cidrs in yamldecode(file("${var.templates_folder}/cidrs.yaml")) :
+ name => cidrs
+ }, {})
+
+ service_accounts = try({ for name, service_accounts in yamldecode(file("${var.templates_folder}/service_accounts.yaml")) :
+ name => service_accounts
+ }, {})
+ rules = flatten([
+ for file in fileset(var.config_folder, "**/*.yaml") : [
+ for key, ruleset in yamldecode(file("${var.config_folder}/${file}")) :
+ merge(ruleset, {
+ project_id = split("/", file)[0]
+ network = split("/", file)[1]
+ name = "${key}-${split("/", file)[1]}"
+
+ source_ranges = try(ruleset.source_ranges, null) == null ? null : flatten(
+ [for cidr in ruleset.source_ranges :
+ can(regex("^\\$", cidr))
+ ? local.cidrs[trimprefix(cidr, "$")]
+ : [cidr]
+ ])
+ destination_ranges = try(ruleset.destination_ranges, null) == null ? null : flatten(
+ [for cidr in ruleset.destination_ranges :
+ can(regex("^\\$", cidr))
+ ? local.cidrs[trimprefix(cidr, "$")]
+ : [cidr]
+ ])
+ source_service_accounts = try(ruleset.source_service_accounts, null) == null ? null : flatten(
+ [for service_account in ruleset.source_service_accounts :
+ can(regex("^\\$", service_account))
+ ? local.service_accounts[trimprefix(service_account, "$")]
+ : [service_account]
+ ])
+ target_service_accounts = try(ruleset.target_service_accounts, null) == null ? null : flatten(
+ [for service_account in ruleset.target_service_accounts :
+ can(regex("^\\$", service_account))
+ ? local.service_accounts[trimprefix(service_account, "$")]
+ : [service_account]
+ ])
+ })
+ ]
+ ])
+
+ rules_allow = [for item in local.rules : item if item.action == "allow"]
+ rules_deny = [for item in local.rules : item if item.action == "deny"]
+
+}
+
+
+resource "google_compute_firewall" "rules-allow" {
+ for_each = { for rule in local.rules_allow : "${rule.network}-${rule.name}" => rule }
+ project = each.value.project_id
+ name = each.value.name
+ description = each.value.description
+ network = each.value.network
+ direction = each.value.direction
+ priority = each.value.priority
+
+ source_ranges = try(each.value.source_ranges, each.value.direction == "INGRESS" ? [] : null)
+ source_tags = try(each.value.source_tags, null)
+ source_service_accounts = try(each.value.source_service_accounts, null)
+
+ destination_ranges = try(each.value.destination_ranges, each.value.direction == "EGRESS" ? [] : null)
+ target_tags = try(each.value.target_tags, null)
+ target_service_accounts = try(each.value.target_service_accounts, null)
+
+ dynamic "allow" {
+ for_each = { for proto, ports in try(each.value.ports, []) :
+ "${proto}-${join("-", ports)}" => {
+ ports = [for port in ports : tostring(port)]
+ protocol = proto
+ }
+ }
+ content {
+ protocol = allow.value.protocol
+ ports = allow.value.ports
+ }
+ }
+
+ dynamic "log_config" {
+ for_each = (each.value.enable_logging == null) || (each.value.enable_logging == false) ? [] : [""]
+ content {
+ metadata = "INCLUDE_ALL_METADATA"
+ }
+ }
+}
+
+
+resource "google_compute_firewall" "rules-deny" {
+ for_each = { for rule in local.rules_deny : "${rule.network}-${rule.name}" => rule }
+ project = each.value.project_id
+ name = each.value.name
+ description = each.value.description
+ network = each.value.network
+ direction = each.value.direction
+ priority = each.value.priority
+
+ source_ranges = try(each.value.source_ranges, each.value.direction == "INGRESS" ? [] : null)
+ source_tags = try(each.value.source_tags, null)
+ source_service_accounts = try(each.value.source_service_accounts, null)
+
+ destination_ranges = try(each.value.destination_ranges, each.value.direction == "EGRESS" ? [] : null)
+ target_tags = try(each.value.target_tags, null)
+ target_service_accounts = try(each.value.target_service_accounts, null)
+
+ dynamic "deny" {
+ for_each = { for proto, ports in try(each.value.ports, []) :
+ "${proto}-${join("-", ports)}" => {
+ ports = [for port in ports : tostring(port)]
+ protocol = proto
+ }
+ }
+ content {
+ protocol = deny.value.protocol
+ ports = deny.value.ports
+ }
+ }
+
+ dynamic "log_config" {
+ for_each = (each.value.enable_logging == null) || (each.value.enable_logging == false) ? [] : [""]
+ content {
+ metadata = "INCLUDE_ALL_METADATA"
+ }
+ }
+}
diff --git a/factories/firewall-vpc-rules/nested/outputs.tf b/factories/firewall-vpc-rules/nested/outputs.tf
new file mode 100644
index 00000000..36b9e166
--- /dev/null
+++ b/factories/firewall-vpc-rules/nested/outputs.tf
@@ -0,0 +1,20 @@
+/**
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+output "vpc-firewall-rules" {
+ description = "Generated VPC Firewall Rules"
+ value = merge(google_compute_firewall.rules-allow, google_compute_firewall.rules-deny)
+}
diff --git a/factories/firewall-vpc-rules/nested/variables.tf b/factories/firewall-vpc-rules/nested/variables.tf
new file mode 100644
index 00000000..503a2e01
--- /dev/null
+++ b/factories/firewall-vpc-rules/nested/variables.tf
@@ -0,0 +1,25 @@
+/**
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+variable "config_folder" {
+ description = "Relative path of the folder containing the hierarchical firewall configuration"
+ type = string
+}
+
+variable "templates_folder" {
+ description = "Relative path of the folder containing the cidr/service account templates"
+ type = string
+}
diff --git a/factories/subnets/README.md b/factories/subnets/README.md
new file mode 100644
index 00000000..07cec90a
--- /dev/null
+++ b/factories/subnets/README.md
@@ -0,0 +1,68 @@
+# Google Cloud Resource Factories - VPC Subnets
+
+This module implements a resource factory which allows the creation and management of subnets through properly formatted `yaml` files.
+
+`yaml` configurations are stored on a well-defined folder structure, whose entry point can be customized, and which allows for simple grouping of subnets by Project > VPC.
+
+## Example
+
+### Terraform code
+
+```hcl
+module "subnets" {
+ source = "./modules/resource-factories/subnets"
+ config_folder = "subnets"
+}
+# tftest:skip
+```
+
+### Configuration Structure
+
+The directory structure implies the project and the VPC each subnet belongs to.
+Per the structure below, a subnet named `subnet-a` (after filename `subnet-a.yaml`) will be created on VPC `vpc-alpha-one` which belongs to project `project-alpha`.
+
+Projects and VPCs should exist prior to running this module, or set as an explicit dependency to this module, leveraging `depends_on`.
+
+```bash
+└── subnets
+ ├── project-alpha
+ │ ├── vpc-alpha-one
+ │ │ ├── subnet-a.yaml
+ │ │ └── subnet-b.yaml
+ │ └── vpc-alpha-two
+ │ └── subnet-c.yaml
+ └── project-bravo
+ └── vpc-bravo-one
+ └── subnet-d.yaml
+```
+
+### Subnet definition format and structure
+
+```yaml
+region: europe-west1 # Region where the subnet will be creted
+description: Sample description # Description
+ip_cidr_range: 10.0.0.0/24 # Primary IP range for the subnet
+private_ip_google_access: false # Opt- Enables PGA. Defaults to true
+iam_users: ["foobar@example.com"] # Opt- Users to grant compute/networkUser to
+iam_groups: ["lorem@example.com"] # Opt- Groups to grant compute/networkUser to
+iam_service_accounts: ["foobar@project-id.iam.gserviceaccount.com"]
+ # Opt- SAs to grant compute/networkUser to
+secondary_ip_ranges: # Opt- List of secondary IP ranges
+ - name: secondary-range-a # Name for the secondary range
+ ip_cidr_range: 192.168.0.0/24 # IP range for the secondary range
+
+```
+
+
+## Variables
+
+| name | description | type | required | default |
+|---|---|:---: |:---:|:---:|
+| config_folder | Relative path of the folder containing the subnet configuration | string
| ✓ | |
+
+## Outputs
+
+| name | description | sensitive |
+|---|---|:---:|
+| subnet | Generated subnets | |
+
diff --git a/factories/subnets/main.tf b/factories/subnets/main.tf
new file mode 100644
index 00000000..0bed8407
--- /dev/null
+++ b/factories/subnets/main.tf
@@ -0,0 +1,72 @@
+/**
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+locals {
+ _data = {
+ for f in fileset(var.config_folder, "**/*.yaml") :
+ trimsuffix(split("/", f)[2], ".yaml") => merge(
+ yamldecode(file("${var.config_folder}/${f}")),
+ {
+ project_id = split("/", f)[0]
+ network = split("/", f)[1]
+ }
+ )
+ }
+
+ data = {
+ for k, v in local._data : k => merge(v,
+ {
+ network_users : concat(
+ formatlist("group:%s", try(v.iam_groups, [])),
+ formatlist("user:%s", try(v.iam_users, [])),
+ formatlist("serviceAccount:%s", try(v.iam_service_accounts, []))
+ )
+ }
+ )
+ }
+}
+
+resource "google_compute_subnetwork" "default" {
+ for_each = local.data
+ project = each.value.project_id
+ network = each.value.network
+ name = each.key
+ region = each.value.region
+ description = each.value.description
+ ip_cidr_range = each.value.ip_cidr_range
+ private_ip_google_access = try(each.value.private_ip_google_access, true)
+
+ dynamic "secondary_ip_range" {
+ for_each = try(each.value.secondary_ip_ranges, [])
+ iterator = secondary_range
+ content {
+ range_name = secondary_range.key
+ ip_cidr_range = secondary_range.value
+ }
+ }
+}
+
+
+resource "google_compute_subnetwork_iam_binding" "default" {
+ for_each = {
+ for k, v in local.data : k => v if length(v.network_users) > 0
+ }
+ project = each.value.project_id
+ subnetwork = google_compute_subnetwork.default[each.key].name
+ region = each.value.region
+ role = "roles/compute.networkUser"
+ members = each.value.network_users
+}
diff --git a/factories/subnets/outputs.tf b/factories/subnets/outputs.tf
new file mode 100644
index 00000000..e3032c10
--- /dev/null
+++ b/factories/subnets/outputs.tf
@@ -0,0 +1,28 @@
+/**
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+output "subnet" {
+ description = "Generated subnets"
+ value = {
+ for k, v in google_compute_subnetwork.default :
+ k => {
+ network = v.network
+ project = v.project
+ range = v.ip_cidr_range
+ region = v.region
+ }
+ }
+}
diff --git a/factories/subnets/variables.tf b/factories/subnets/variables.tf
new file mode 100644
index 00000000..fbea28bb
--- /dev/null
+++ b/factories/subnets/variables.tf
@@ -0,0 +1,20 @@
+/**
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+variable "config_folder" {
+ description = "Relative path of the folder containing the subnet configuration"
+ type = string
+}
diff --git a/networking/decentralized-firewall/main.tf b/networking/decentralized-firewall/main.tf
index d8fa029e..b9267124 100644
--- a/networking/decentralized-firewall/main.tf
+++ b/networking/decentralized-firewall/main.tf
@@ -109,7 +109,7 @@ module "dns-api-dev" {
###############################################################################
module "vpc-firewall-prod" {
- source = "../../modules/net-vpc-firewall-yaml"
+ source = "../../factories/firewall-vpc-rules/flat"
project_id = module.project-host-prod.project_id
network = module.vpc-prod.name
@@ -125,7 +125,7 @@ module "vpc-firewall-prod" {
}
module "vpc-firewall-dev" {
- source = "../../modules/net-vpc-firewall-yaml"
+ source = "../../factories/firewall-vpc-rules/flat"
project_id = module.project-host-dev.project_id
network = module.vpc-dev.name
diff --git a/tests/modules/net_vpc_firewall_yaml/__init__.py b/tests/factories/__init__.py
similarity index 100%
rename from tests/modules/net_vpc_firewall_yaml/__init__.py
rename to tests/factories/__init__.py
diff --git a/tests/factories/firewall_hierarchical_policies/__init__.py b/tests/factories/firewall_hierarchical_policies/__init__.py
new file mode 100644
index 00000000..d46dbae5
--- /dev/null
+++ b/tests/factories/firewall_hierarchical_policies/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2021 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
diff --git a/tests/factories/firewall_hierarchical_policies/fixture/conf/rules/1234567890/0987654321-foobar.yaml b/tests/factories/firewall_hierarchical_policies/fixture/conf/rules/1234567890/0987654321-foobar.yaml
new file mode 100644
index 00000000..49385718
--- /dev/null
+++ b/tests/factories/firewall_hierarchical_policies/fixture/conf/rules/1234567890/0987654321-foobar.yaml
@@ -0,0 +1,13 @@
+# skip boilerplate check
+
+allow-ssh-from-onprem:
+ description: Enable SSH for onprem ranges
+ direction: INGRESS
+ action: allow
+ priority: 1001
+ source_ranges:
+ - $example
+ ports:
+ tcp: ["22"]
+ target_resources: null
+ enable_logging: false
diff --git a/tests/factories/firewall_hierarchical_policies/fixture/conf/rules/1234567890/org.yaml b/tests/factories/firewall_hierarchical_policies/fixture/conf/rules/1234567890/org.yaml
new file mode 100644
index 00000000..649561b9
--- /dev/null
+++ b/tests/factories/firewall_hierarchical_policies/fixture/conf/rules/1234567890/org.yaml
@@ -0,0 +1,13 @@
+# skip boilerplate check
+
+allow-icmp:
+ description: Enable ICMP for all hosts
+ direction: INGRESS
+ action: allow
+ priority: 1000
+ source_ranges:
+ - 0.0.0.0/0
+ ports:
+ icmp: []
+ target_resources: null
+ enable_logging: false
diff --git a/tests/factories/firewall_hierarchical_policies/fixture/conf/templates/cidrs.yaml b/tests/factories/firewall_hierarchical_policies/fixture/conf/templates/cidrs.yaml
new file mode 100644
index 00000000..789ea960
--- /dev/null
+++ b/tests/factories/firewall_hierarchical_policies/fixture/conf/templates/cidrs.yaml
@@ -0,0 +1,10 @@
+# skip boilerplate check
+
+example:
+ - 10.0.0.0/24
+ - 10.0.10.0/24
+ - 192.168.1.1/32
+
+healthcheck:
+ - 35.191.0.0/16
+ - 130.211.0.0/22
\ No newline at end of file
diff --git a/tests/factories/firewall_hierarchical_policies/fixture/conf/templates/service_accounts.yaml b/tests/factories/firewall_hierarchical_policies/fixture/conf/templates/service_accounts.yaml
new file mode 100644
index 00000000..e4c68edd
--- /dev/null
+++ b/tests/factories/firewall_hierarchical_policies/fixture/conf/templates/service_accounts.yaml
@@ -0,0 +1,4 @@
+# skip boilerplate check
+
+example:
+ - example-service-account@resource-factory-playground.iam.gserviceaccount.com
diff --git a/tests/factories/firewall_hierarchical_policies/fixture/main.tf b/tests/factories/firewall_hierarchical_policies/fixture/main.tf
new file mode 100644
index 00000000..9e171641
--- /dev/null
+++ b/tests/factories/firewall_hierarchical_policies/fixture/main.tf
@@ -0,0 +1,21 @@
+/**
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+module "hierarchical-firewall-rules" {
+ source = "../../../../factories/firewall-hierarchical-policies/"
+ config_folder = "conf/rules"
+ templates_folder = "conf/templates"
+}
diff --git a/tests/factories/firewall_hierarchical_policies/test_plan.py b/tests/factories/firewall_hierarchical_policies/test_plan.py
new file mode 100644
index 00000000..aef8ec1d
--- /dev/null
+++ b/tests/factories/firewall_hierarchical_policies/test_plan.py
@@ -0,0 +1,55 @@
+# Copyright 2021 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import os
+import pytest
+
+
+FIXTURES_DIR = os.path.join(os.path.dirname(__file__), "fixture")
+
+
+def test_firewall(plan_runner):
+ "Test hierarchical firewall rules from conf/rules"
+ _, resources = plan_runner(FIXTURES_DIR)
+ assert len(resources) == 6
+ assert set(r["type"] for r in resources) == set([
+ "google_compute_firewall_policy_rule", "google_compute_firewall_policy_association", "google_compute_firewall_policy"
+ ])
+ rule_ssh = [r["values"] for r in resources if r["type"] ==
+ "google_compute_firewall_policy_rule"
+ and r["values"]["priority"] == 1001]
+ rule_icmp = [r["values"] for r in resources if r["type"] ==
+ "google_compute_firewall_policy_rule"
+ and r["values"]["priority"] == 1000]
+ association_org = [r["values"] for r in resources if r["type"] ==
+ "google_compute_firewall_policy_association"
+ and r["values"]["attachment_target"] == "organizations/1234567890"]
+ association_folder = [r["values"] for r in resources if r["type"] ==
+ "google_compute_firewall_policy_association"
+ and r["values"]["attachment_target"] == "folders/0987654321"]
+ policies_org = [r["values"] for r in resources if r["type"] ==
+ "google_compute_firewall_policy"
+ and r["values"]["parent"] == "organizations/1234567890"]
+ policies_folder = [r["values"] for r in resources if r["type"] ==
+ "google_compute_firewall_policy"
+ and r["values"]["parent"] == "folders/0987654321"]
+
+ assert set(rule_ssh[0]["match"][0]["src_ip_ranges"]) == set(
+ ["10.0.0.0/24", "10.0.10.0/24", "192.168.1.1/32"])
+ assert rule_icmp[0]["match"][0]["layer4_configs"][0]["ip_protocol"] == "icmp"
+ assert association_org[0]["name"] == "hierarchical-fw-policy-organizations-1234567890"
+ assert association_folder[0]["name"] == "hierarchical-fw-policy-folders-0987654321"
+ assert policies_org[0]["short_name"] == "hierarchical-fw-policy-organizations-1234567890"
+ assert policies_folder[0]["short_name"] == "hierarchical-fw-policy-folders-0987654321"
diff --git a/tests/factories/subnets/__init__.py b/tests/factories/subnets/__init__.py
new file mode 100644
index 00000000..d46dbae5
--- /dev/null
+++ b/tests/factories/subnets/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2021 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
diff --git a/tests/factories/subnets/fixture/conf/project-a/vpc-a/subnet-a.yaml b/tests/factories/subnets/fixture/conf/project-a/vpc-a/subnet-a.yaml
new file mode 100644
index 00000000..9cf4a1e2
--- /dev/null
+++ b/tests/factories/subnets/fixture/conf/project-a/vpc-a/subnet-a.yaml
@@ -0,0 +1,8 @@
+# skip boilerplate check
+
+region: europe-west1
+ip_cidr_range: 10.0.0.0/24
+description: Sample Subnet in project project-a, vpc-a
+secondary_ip_ranges:
+ secondary-range-a: 192.168.0.0/24
+ secondary-range-b: 192.168.1.0/24
diff --git a/tests/factories/subnets/fixture/conf/project-a/vpc-a/subnet-b.yaml b/tests/factories/subnets/fixture/conf/project-a/vpc-a/subnet-b.yaml
new file mode 100644
index 00000000..f8dab01a
--- /dev/null
+++ b/tests/factories/subnets/fixture/conf/project-a/vpc-a/subnet-b.yaml
@@ -0,0 +1,6 @@
+# skip boilerplate check
+
+region: europe-west3
+ip_cidr_range: 10.0.1.0/24
+description: Sample Subnet in project project-a, vpc-a
+private_ip_google_access: false
diff --git a/tests/factories/subnets/fixture/conf/project-a/vpc-b/subnet-one.yaml b/tests/factories/subnets/fixture/conf/project-a/vpc-b/subnet-one.yaml
new file mode 100644
index 00000000..85b5607b
--- /dev/null
+++ b/tests/factories/subnets/fixture/conf/project-a/vpc-b/subnet-one.yaml
@@ -0,0 +1,7 @@
+# skip boilerplate check
+
+region: europe-west4
+ip_cidr_range: 10.0.2.0/24
+description: Sample Subnet in project project-a, vpc-b
+iam_users: ["sruffilli@google.com"]
+iam_groups: []
diff --git a/tests/factories/subnets/fixture/conf/project-b/vpc-x/subnet-alpha.yaml b/tests/factories/subnets/fixture/conf/project-b/vpc-x/subnet-alpha.yaml
new file mode 100644
index 00000000..0af68295
--- /dev/null
+++ b/tests/factories/subnets/fixture/conf/project-b/vpc-x/subnet-alpha.yaml
@@ -0,0 +1,7 @@
+# skip boilerplate check
+
+region: europe-west4
+ip_cidr_range: 172.16.0.0/24
+description: Sample Subnet in project project-b, vpc-x
+iam_users: ["sruffilli@google.com"]
+iam_groups: []
diff --git a/tests/factories/subnets/fixture/main.tf b/tests/factories/subnets/fixture/main.tf
new file mode 100644
index 00000000..815bac66
--- /dev/null
+++ b/tests/factories/subnets/fixture/main.tf
@@ -0,0 +1,20 @@
+/**
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+module "subnets" {
+ source = "../../../../factories/subnets"
+ config_folder = "conf"
+}
\ No newline at end of file
diff --git a/tests/factories/subnets/test_plan.py b/tests/factories/subnets/test_plan.py
new file mode 100644
index 00000000..0020964b
--- /dev/null
+++ b/tests/factories/subnets/test_plan.py
@@ -0,0 +1,67 @@
+# Copyright 2021 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import pytest
+
+FIXTURES_DIR = os.path.join(os.path.dirname(__file__), "fixture")
+
+
+def test_firewall(plan_runner):
+ "Test hierarchical firewall rules from conf/rules"
+ _, resources = plan_runner(FIXTURES_DIR)
+ assert len(resources) == 6
+ assert set(r["type"] for r in resources) == set(
+ ["google_compute_subnetwork", "google_compute_subnetwork_iam_binding"])
+ subnets = [
+ r["values"] for r in resources
+ if r["type"] == "google_compute_subnetwork"
+ ]
+ iam_bindings = [
+ r["values"] for r in resources
+ if r["type"] == "google_compute_subnetwork_iam_binding"
+ ]
+
+ subnet_a_a = [
+ s for s in subnets if s["project"] == "project-a"
+ and s["network"] == "vpc-a" and s["name"] == "subnet-a"
+ ][0]
+ assert subnet_a_a["ip_cidr_range"] == "10.0.0.0/24"
+ assert subnet_a_a["private_ip_google_access"] == True
+ assert subnet_a_a["region"] == "europe-west1"
+ assert subnet_a_a["secondary_ip_range"] == [{
+ "ip_cidr_range":
+ "192.168.0.0/24",
+ "range_name":
+ "secondary-range-a"
+ }, {
+ "ip_cidr_range":
+ "192.168.1.0/24",
+ "range_name":
+ "secondary-range-b"
+ }]
+
+ subnet_a_b = [
+ s for s in subnets if s["project"] == "project-a"
+ and s["network"] == "vpc-a" and s["name"] == "subnet-b"
+ ][0]
+ assert subnet_a_b["private_ip_google_access"] == False
+
+ iam_binding_b_alpha = [
+ b for b in iam_bindings if b["project"] == "project-b"
+ ][0]
+ assert set(iam_binding_b_alpha["members"]) == set(
+ ["user:sruffilli@google.com"])
+ assert iam_binding_b_alpha["role"] == "roles/compute.networkUser"
+ assert iam_binding_b_alpha["subnetwork"] == "subnet-alpha"
diff --git a/tests/factories/vpc_firewall/__init__.py b/tests/factories/vpc_firewall/__init__.py
new file mode 100644
index 00000000..d46dbae5
--- /dev/null
+++ b/tests/factories/vpc_firewall/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2021 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
diff --git a/tests/factories/vpc_firewall/flat/__init__.py b/tests/factories/vpc_firewall/flat/__init__.py
new file mode 100644
index 00000000..d46dbae5
--- /dev/null
+++ b/tests/factories/vpc_firewall/flat/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2021 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
diff --git a/tests/modules/net_vpc_firewall_yaml/fixture/main.tf b/tests/factories/vpc_firewall/flat/fixture/main.tf
similarity index 91%
rename from tests/modules/net_vpc_firewall_yaml/fixture/main.tf
rename to tests/factories/vpc_firewall/flat/fixture/main.tf
index 4dcc9b7c..2fca3a87 100644
--- a/tests/modules/net_vpc_firewall_yaml/fixture/main.tf
+++ b/tests/factories/vpc_firewall/flat/fixture/main.tf
@@ -15,7 +15,7 @@
*/
module "firewall" {
- source = "../../../../modules/net-vpc-firewall-yaml"
+ source = "../../../../../factories/firewall-vpc-rules/flat"
project_id = "my-project"
network = "my-network"
config_directories = [
diff --git a/tests/modules/net_vpc_firewall_yaml/fixture/rules/common.yaml b/tests/factories/vpc_firewall/flat/fixture/rules/common.yaml
similarity index 100%
rename from tests/modules/net_vpc_firewall_yaml/fixture/rules/common.yaml
rename to tests/factories/vpc_firewall/flat/fixture/rules/common.yaml
diff --git a/tests/modules/net_vpc_firewall_yaml/fixture/variables.tf b/tests/factories/vpc_firewall/flat/fixture/variables.tf
similarity index 100%
rename from tests/modules/net_vpc_firewall_yaml/fixture/variables.tf
rename to tests/factories/vpc_firewall/flat/fixture/variables.tf
diff --git a/tests/modules/net_vpc_firewall_yaml/test_plan.py b/tests/factories/vpc_firewall/flat/test_plan.py
similarity index 100%
rename from tests/modules/net_vpc_firewall_yaml/test_plan.py
rename to tests/factories/vpc_firewall/flat/test_plan.py
diff --git a/tests/factories/vpc_firewall/nested/__init__.py b/tests/factories/vpc_firewall/nested/__init__.py
new file mode 100644
index 00000000..d46dbae5
--- /dev/null
+++ b/tests/factories/vpc_firewall/nested/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2021 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
diff --git a/tests/factories/vpc_firewall/nested/fixture/conf/rules/resource-factory-playground/vpc-a/ingress-loadbalancers.yaml b/tests/factories/vpc_firewall/nested/fixture/conf/rules/resource-factory-playground/vpc-a/ingress-loadbalancers.yaml
new file mode 100644
index 00000000..46acd4ea
--- /dev/null
+++ b/tests/factories/vpc_firewall/nested/fixture/conf/rules/resource-factory-playground/vpc-a/ingress-loadbalancers.yaml
@@ -0,0 +1,25 @@
+# skip boilerplate check
+
+allow-healthchecks:
+ description: "Allow traffic from healthcheck"
+ direction: INGRESS
+ action: allow
+ priority: 1000
+ source_ranges:
+ - $healthcheck
+ ports:
+ tcp: ["80"]
+ enable_logging: false
+
+allow-http:
+ description: "Allow traffic to LB backend"
+ direction: INGRESS
+ action: allow
+ priority: 1000
+ source_ranges:
+ - 0.0.0.0/0
+ target_service_accounts:
+ - example-service-account@resource-factory-playground.iam.gserviceaccount.com
+ ports:
+ tcp: ["80", "443"]
+ enable_logging: true
diff --git a/tests/factories/vpc_firewall/nested/fixture/conf/templates/cidrs.yaml b/tests/factories/vpc_firewall/nested/fixture/conf/templates/cidrs.yaml
new file mode 100644
index 00000000..789ea960
--- /dev/null
+++ b/tests/factories/vpc_firewall/nested/fixture/conf/templates/cidrs.yaml
@@ -0,0 +1,10 @@
+# skip boilerplate check
+
+example:
+ - 10.0.0.0/24
+ - 10.0.10.0/24
+ - 192.168.1.1/32
+
+healthcheck:
+ - 35.191.0.0/16
+ - 130.211.0.0/22
\ No newline at end of file
diff --git a/tests/factories/vpc_firewall/nested/fixture/conf/templates/service_accounts.yaml b/tests/factories/vpc_firewall/nested/fixture/conf/templates/service_accounts.yaml
new file mode 100644
index 00000000..0a28b28b
--- /dev/null
+++ b/tests/factories/vpc_firewall/nested/fixture/conf/templates/service_accounts.yaml
@@ -0,0 +1,4 @@
+# skip boilerplate check
+
+couchbase:
+ - example-service-account@resource-factory-playground.iam.gserviceaccount.com
diff --git a/tests/factories/vpc_firewall/nested/fixture/main.tf b/tests/factories/vpc_firewall/nested/fixture/main.tf
new file mode 100644
index 00000000..d50ebb79
--- /dev/null
+++ b/tests/factories/vpc_firewall/nested/fixture/main.tf
@@ -0,0 +1,21 @@
+/**
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+module "vpc-firewall-rules" {
+ source = "../../../../../factories/firewall-vpc-rules/nested"
+ config_folder = "conf/rules"
+ templates_folder = "conf/templates"
+}
diff --git a/tests/factories/vpc_firewall/nested/test_plan.py b/tests/factories/vpc_firewall/nested/test_plan.py
new file mode 100644
index 00000000..01cee1b7
--- /dev/null
+++ b/tests/factories/vpc_firewall/nested/test_plan.py
@@ -0,0 +1,45 @@
+# Copyright 2021 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import pytest
+
+FIXTURES_DIR = os.path.join(os.path.dirname(__file__), "fixture")
+
+
+def test_firewall(plan_runner):
+ "Test hierarchical firewall rules from conf/rules"
+ _, resources = plan_runner(FIXTURES_DIR)
+ assert len(resources) == 2
+
+ assert set(r["type"]
+ for r in resources) == set(["google_compute_firewall"])
+
+ rule_hc = [
+ r["values"] for r in resources
+ if r["values"]["name"] == "allow-healthchecks-vpc-a"
+ ][0]
+ rule_be = [
+ r["values"] for r in resources
+ if r["values"]["description"] == "Allow traffic to LB backend"
+ ][0]
+
+ assert set(rule_hc["source_ranges"]) == set(
+ ["130.211.0.0/22", "35.191.0.0/16"])
+ assert rule_hc["direction"] == "INGRESS"
+ assert rule_hc["network"] == "vpc-a"
+ assert rule_hc["priority"] == 1000
+ assert rule_hc["project"] == "resource-factory-playground"
+ assert rule_hc["allow"][0] == {'ports': ['80'], 'protocol': 'tcp'}
+ assert rule_be["log_config"][0] == {'metadata': 'INCLUDE_ALL_METADATA'}