diff --git a/.ci/cloudbuild.lint.yaml b/.ci/cloudbuild.lint.yaml index 4a178185..745cc433 100644 --- a/.ci/cloudbuild.lint.yaml +++ b/.ci/cloudbuild.lint.yaml @@ -43,11 +43,13 @@ steps: args: [ "/workspace/tools/check_documentation.py", - "modules", "cloud-operations", "data-solutions", "data-solutions/data-platform-foundations", + "factories", + "factories/firewall-vpc-rules" "foundations", + "modules", "networking", ] 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/factories/README.md b/factories/README.md new file mode 100644 index 00000000..b102f0b4 --- /dev/null +++ b/factories/README.md @@ -0,0 +1,40 @@ +# 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) + +## Example implementation + +See [Example environments](./example-environments) + +## Using the modules + +Each module is specialised and comes with a `README.md` file which describes the module interface, as well as the directory structure each module requires. + +## 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 build IaC leveraging 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) + +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 for a human +- CSV isn't often expressive enough (e.g. doesn't allow for nested structures) + +If needed, converting factories to consume JSON instead 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..a90219ab --- /dev/null +++ b/factories/example-environments/README.md @@ -0,0 +1,42 @@ +# Resource Factories + +The example in this folder are derived from actual production use cases, and show how to use a factory module and how you could structure your 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 +- `main.tf`, a simple terraform file which consumes the [`subnets`](../subnets/) module + + +Each environment directory structure is meant to mimic your GCP resources structure + +``` +. +├── dev # Environment +│ ├── 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 +├── prod +│ └── project-prod-a +│ └── vpc-alpha +│ ├── subnet-alpha-a.yaml +│ └── subnet-alpha-b.yaml +├── main.tf +└── README.md +``` + +Since this resource factory only creates subnets, projects and VPCs are expected to exist. + +In this example, a single `main.tf` file (hence a single state) drives the creation of both the `dev` and the `prod` environment. Another option you might want to consider, in line with the CI/CD pipeline or processes you have in place, might be to move the `main.tf` to the each environment directory, so that states (and pipelines) can be separated. + diff --git a/factories/example-environments/dev/project-dev-a/vpc-alpha/subnet-alpha-a.yaml b/factories/example-environments/dev/project-dev-a/vpc-alpha/subnet-alpha-a.yaml new file mode 100644 index 00000000..61844e76 --- /dev/null +++ b/factories/example-environments/dev/project-dev-a/vpc-alpha/subnet-alpha-a.yaml @@ -0,0 +1,6 @@ +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/project-dev-a/vpc-alpha/subnet-alpha-b.yaml b/factories/example-environments/dev/project-dev-a/vpc-alpha/subnet-alpha-b.yaml new file mode 100644 index 00000000..64e5ba7b --- /dev/null +++ b/factories/example-environments/dev/project-dev-a/vpc-alpha/subnet-alpha-b.yaml @@ -0,0 +1,4 @@ +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/project-dev-b/vpc-beta/subnet-beta-a.yaml b/factories/example-environments/dev/project-dev-b/vpc-beta/subnet-beta-a.yaml new file mode 100644 index 00000000..276b5063 --- /dev/null +++ b/factories/example-environments/dev/project-dev-b/vpc-beta/subnet-beta-a.yaml @@ -0,0 +1,5 @@ +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/project-dev-b/vpc-gamma/subnet-gamma-a.yaml b/factories/example-environments/dev/project-dev-b/vpc-gamma/subnet-gamma-a.yaml new file mode 100644 index 00000000..2cf95c16 --- /dev/null +++ b/factories/example-environments/dev/project-dev-b/vpc-gamma/subnet-gamma-a.yaml @@ -0,0 +1,3 @@ +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/main.tf b/factories/example-environments/main.tf new file mode 100644 index 00000000..08906178 --- /dev/null +++ b/factories/example-environments/main.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. + */ + + +module "subnets-dev" { + source = "../subnets" + config_folder = "dev" +} + +module "subnets-prod" { + source = "../subnets" + config_folder = "prod" +} diff --git a/factories/example-environments/prod/project-prod-a/vpc-alpha/subnet-alpha-a.yaml b/factories/example-environments/prod/project-prod-a/vpc-alpha/subnet-alpha-a.yaml new file mode 100644 index 00000000..7d9d70e0 --- /dev/null +++ b/factories/example-environments/prod/project-prod-a/vpc-alpha/subnet-alpha-a.yaml @@ -0,0 +1,6 @@ +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/project-prod-a/vpc-alpha/subnet-alpha-b.yaml b/factories/example-environments/prod/project-prod-a/vpc-alpha/subnet-alpha-b.yaml new file mode 100644 index 00000000..216fd01e --- /dev/null +++ b/factories/example-environments/prod/project-prod-a/vpc-alpha/subnet-alpha-b.yaml @@ -0,0 +1,4 @@ +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/firewall-hierarchical-policies/README.md b/factories/firewall-hierarchical-policies/README.md new file mode 100644 index 00000000..b3adbb19 --- /dev/null +++ b/factories/firewall-hierarchical-policies/README.md @@ -0,0 +1,161 @@ +# 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 for a definition of variable templates, allowing for the definition and centralization of 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` folder requires + +- the first directory layer to be named after the organization ID we're creating the policies for +- each file to be either 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` 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 + +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 both for `$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 + source_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 | | + \ No newline at end of file diff --git a/factories/firewall-hierarchical-policies/main.tf b/factories/firewall-hierarchical-policies/main.tf new file mode 100644 index 00000000..ec3a6431 --- /dev/null +++ b/factories/firewall-hierarchical-policies/main.tf @@ -0,0 +1,101 @@ +/** + * 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_organization_security_policy" "default" { + provider = google-beta + for_each = { for rule in local.rules : rule.parent_id => rule.name... } + display_name = replace("hierarchical-fw-policy-${each.key}", "/", "-") + parent = each.key +} + +resource "google_compute_organization_security_policy_rule" "default" { + provider = google-beta + for_each = { for rule in local.rules : "${rule.parent_id}-${rule.name}" => rule } + policy_id = google_compute_organization_security_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 { + config { + src_ip_ranges = each.value.source_ranges + dynamic "layer4_config" { + for_each = each.value.ports + iterator = port + content { + ip_protocol = port.key + ports = port.value + } + } + } + } +} + +resource "google_compute_organization_security_policy_association" "default" { + provider = google-beta + for_each = { for rule in local.rules : rule.parent_id => rule.name... } + name = replace("hierarchical-fw-policy-${each.key}", "/", "-") + attachment_id = google_compute_organization_security_policy.default[each.key].parent + policy_id = google_compute_organization_security_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..25062aaf --- /dev/null +++ b/factories/firewall-hierarchical-policies/outputs.tf @@ -0,0 +1,27 @@ +/** + * 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_organization_security_policy_rule.default : + k => { + parent_id = split("-", k)[0] + id = v.id + description = v.match[0].description + } + } +} diff --git a/factories/firewall-hierarchical-policies/variables.tf b/factories/firewall-hierarchical-policies/variables.tf new file mode 100644 index 00000000..4b686f8d --- /dev/null +++ b/factories/firewall-hierarchical-policies/variables.tf @@ -0,0 +1,9 @@ +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..7d33b561 --- /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 the maximum flexibility, and a custom logical grouping of resources which can be trasversal to the traditional resources hierarchy, and could be useful in scenarios where network 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. \ No newline at end of file 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..47fba3b1 --- /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) through properly formatted `yaml` files. + +`yaml` configurations are stored on 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 a definition of variable templates, allowing for the definition and centralization of 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 | | + \ No newline at end of file diff --git a/factories/firewall-vpc-rules/nested/main.tf b/factories/firewall-vpc-rules/nested/main.tf new file mode 100644 index 00000000..1c966eda --- /dev/null +++ b/factories/firewall-vpc-rules/nested/main.tf @@ -0,0 +1,151 @@ +/** + * 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" + } + } + + lifecycle { + create_before_destroy = true + } +} + + +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" + } + } + + lifecycle { + create_before_destroy = true + } +} diff --git a/factories/firewall-vpc-rules/nested/outputs.tf b/factories/firewall-vpc-rules/nested/outputs.tf new file mode 100644 index 00000000..041a8b8e --- /dev/null +++ b/factories/firewall-vpc-rules/nested/outputs.tf @@ -0,0 +1,4 @@ +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..4b686f8d --- /dev/null +++ b/factories/firewall-vpc-rules/nested/variables.tf @@ -0,0 +1,9 @@ +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/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..539694a5 --- /dev/null +++ b/tests/factories/firewall_hierarchical_policies/fixture/conf/rules/1234567890/0987654321-foobar.yaml @@ -0,0 +1,11 @@ +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..2183fc05 --- /dev/null +++ b/tests/factories/firewall_hierarchical_policies/fixture/conf/rules/1234567890/org.yaml @@ -0,0 +1,11 @@ +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..4f4e158f --- /dev/null +++ b/tests/factories/firewall_hierarchical_policies/fixture/conf/templates/cidrs.yaml @@ -0,0 +1,8 @@ +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..697be919 --- /dev/null +++ b/tests/factories/firewall_hierarchical_policies/fixture/conf/templates/service_accounts.yaml @@ -0,0 +1,2 @@ +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..c66519bc --- /dev/null +++ b/tests/factories/firewall_hierarchical_policies/test_plan.py @@ -0,0 +1,43 @@ +# 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_organization_security_policy_rule", "google_compute_organization_security_policy_association", "google_compute_organization_security_policy" + ]) + rule_ssh = [r["values"] for r in resources if r["type"]== "google_compute_organization_security_policy_rule" and r["values"]["priority"]==1001] + rule_icmp = [r["values"] for r in resources if r["type"]== "google_compute_organization_security_policy_rule" and r["values"]["priority"]==1000] + association_org = [r["values"] for r in resources if r["type"]== "google_compute_organization_security_policy_association" and r["values"]["attachment_id"]=="organizations/1234567890"] + association_folder = [r["values"] for r in resources if r["type"]== "google_compute_organization_security_policy_association" and r["values"]["attachment_id"]=="folders/0987654321"] + policies_org = [r["values"] for r in resources if r["type"]== "google_compute_organization_security_policy" and r["values"]["parent"]=="organizations/1234567890"] + policies_folder = [r["values"] for r in resources if r["type"]== "google_compute_organization_security_policy" and r["values"]["parent"]=="folders/0987654321"] + + assert set(rule_ssh[0]["match"][0]["config"][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]["config"][0]["layer4_config"][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]["display_name"]=="hierarchical-fw-policy-organizations-1234567890" + assert policies_folder[0]["display_name"]=="hierarchical-fw-policy-folders-0987654321" + \ No newline at end of file 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..39a26145 --- /dev/null +++ b/tests/factories/subnets/fixture/conf/project-a/vpc-a/subnet-a.yaml @@ -0,0 +1,6 @@ +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..212bf5ea --- /dev/null +++ b/tests/factories/subnets/fixture/conf/project-a/vpc-a/subnet-b.yaml @@ -0,0 +1,4 @@ +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..321557a3 --- /dev/null +++ b/tests/factories/subnets/fixture/conf/project-a/vpc-b/subnet-one.yaml @@ -0,0 +1,5 @@ +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..c451b0f8 --- /dev/null +++ b/tests/factories/subnets/fixture/conf/project-b/vpc-x/subnet-alpha.yaml @@ -0,0 +1,5 @@ +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..abd5ad25 --- /dev/null +++ b/tests/factories/subnets/test_plan.py @@ -0,0 +1,64 @@ +# 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" \ No newline at end of file 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..c71b9066 --- /dev/null +++ b/tests/factories/vpc_firewall/nested/fixture/conf/rules/resource-factory-playground/vpc-a/ingress-loadbalancers.yaml @@ -0,0 +1,23 @@ +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..4f4e158f --- /dev/null +++ b/tests/factories/vpc_firewall/nested/fixture/conf/templates/cidrs.yaml @@ -0,0 +1,8 @@ +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..b4dd9237 --- /dev/null +++ b/tests/factories/vpc_firewall/nested/fixture/conf/templates/service_accounts.yaml @@ -0,0 +1,2 @@ +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..c3cce2c2 --- /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'}