Resource factories
This commit is contained in:
parent
c67aad3bb8
commit
36fb785ea9
|
@ -43,11 +43,13 @@ steps:
|
||||||
args:
|
args:
|
||||||
[
|
[
|
||||||
"/workspace/tools/check_documentation.py",
|
"/workspace/tools/check_documentation.py",
|
||||||
"modules",
|
|
||||||
"cloud-operations",
|
"cloud-operations",
|
||||||
"data-solutions",
|
"data-solutions",
|
||||||
"data-solutions/data-platform-foundations",
|
"data-solutions/data-platform-foundations",
|
||||||
|
"factories",
|
||||||
|
"factories/firewall-vpc-rules"
|
||||||
"foundations",
|
"foundations",
|
||||||
|
"modules",
|
||||||
"networking",
|
"networking",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ steps:
|
||||||
- -vv
|
- -vv
|
||||||
- tests/cloud_operations
|
- tests/cloud_operations
|
||||||
- tests/data_solutions
|
- tests/data_solutions
|
||||||
|
- tests/factories
|
||||||
- tests/foundations
|
- tests/foundations
|
||||||
- tests/networking
|
- tests/networking
|
||||||
env:
|
env:
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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: []
|
|
@ -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
|
|
@ -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"
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- BEGIN TFDOC -->
|
||||||
|
## Variables
|
||||||
|
|
||||||
|
| name | description | type | required | default |
|
||||||
|
|---|---|:---: |:---:|:---:|
|
||||||
|
| config_folder | Relative path of the folder containing the hierarchical firewall configuration | <code title="">string</code> | ✓ | |
|
||||||
|
| templates_folder | Relative path of the folder containing the cidr/service account templates | <code title="">string</code> | ✓ | |
|
||||||
|
|
||||||
|
## Outputs
|
||||||
|
|
||||||
|
| name | description | sensitive |
|
||||||
|
|---|---|:---:|
|
||||||
|
| hierarchical-firewall-rules | Generated Hierarchical Firewall Rules | |
|
||||||
|
<!-- END TFDOC -->
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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.
|
|
@ -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.
|
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.
|
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
|
## Example
|
||||||
|
|
|
@ -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
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- BEGIN TFDOC -->
|
||||||
|
## Variables
|
||||||
|
|
||||||
|
| name | description | type | required | default |
|
||||||
|
|---|---|:---: |:---:|:---:|
|
||||||
|
| config_folder | Relative path of the folder containing the hierarchical firewall configuration | <code title="">string</code> | ✓ | |
|
||||||
|
| templates_folder | Relative path of the folder containing the cidr/service account templates | <code title="">string</code> | ✓ | |
|
||||||
|
|
||||||
|
## Outputs
|
||||||
|
|
||||||
|
| name | description | sensitive |
|
||||||
|
|---|---|:---:|
|
||||||
|
| vpc-firewall-rules | Generated VPC Firewall Rules | |
|
||||||
|
<!-- END TFDOC -->
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- BEGIN TFDOC -->
|
||||||
|
## Variables
|
||||||
|
|
||||||
|
| name | description | type | required | default |
|
||||||
|
|---|---|:---: |:---:|:---:|
|
||||||
|
| config_folder | Relative path of the folder containing the subnet configuration | <code title="">string</code> | ✓ | |
|
||||||
|
|
||||||
|
## Outputs
|
||||||
|
|
||||||
|
| name | description | sensitive |
|
||||||
|
|---|---|:---:|
|
||||||
|
| subnet | Generated subnets | |
|
||||||
|
<!-- END TFDOC -->
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
||||||
|
example:
|
||||||
|
- example-service-account@resource-factory-playground.iam.gserviceaccount.com
|
|
@ -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"
|
||||||
|
}
|
|
@ -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"
|
||||||
|
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -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: []
|
|
@ -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: []
|
|
@ -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"
|
||||||
|
}
|
|
@ -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"
|
|
@ -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.
|
|
@ -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.
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module "firewall" {
|
module "firewall" {
|
||||||
source = "../../../../modules/net-vpc-firewall-yaml"
|
source = "../../../../../factories/firewall-vpc-rules/flat"
|
||||||
project_id = "my-project"
|
project_id = "my-project"
|
||||||
network = "my-network"
|
network = "my-network"
|
||||||
config_directories = [
|
config_directories = [
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
||||||
|
couchbase:
|
||||||
|
- example-service-account@resource-factory-playground.iam.gserviceaccount.com
|
|
@ -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"
|
||||||
|
}
|
|
@ -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'}
|
Loading…
Reference in New Issue