Merge pull request #208 from terraform-google-modules/averbuks-fw-yaml

Added FW-Yaml module for distributed-firewall network example.
This commit is contained in:
Aleksandr Averbukh 2021-03-14 12:26:30 +01:00 committed by GitHub
commit 1d9290c74a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 352 additions and 0 deletions

View File

@ -0,0 +1,147 @@
# Google Cloud VPC Firewall - Yaml
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.
## Example
### Terraform code
```hcl
module "prod-firewall" {
source = "./modules/net-vpc-firewall-yaml"
project_id = "my-prod-project"
network = "my-prod-network"
config_path = "./prod"
log_config = {
metadata = "INCLUDE_ALL_METADATA"
}
}
module "dev-firewall" {
source = "./modules/net-vpc-firewall-yaml"
project_id = "my-dev-project"
network = "my-dev-network"
config_path = "./dev"
}
# tftest:skip
```
### Configuration Structure
```bash
├── dev
│   ├── core
│   │   └── common-rules.yaml
│   ├── team-a
│   │   ├── databases.yaml
│   │   └── webb-app-a.yaml
│   └── team-b
│   ├── backend.yaml
│   └── frontend.yaml
└── prod
├── core
│   └── common-rules.yaml
├── team-a
│   ├── databases.yaml
│   └── webb-app-a.yaml
└── team-b
├── backend.yaml
└── frontend.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
allow: # `allow` or `deny`
- ports: ['443', '80'] # ports for a specific protocol, keep empty list `[]` for all ports
protocol: tcp # protocol, put `all` for any protocol
direction: EGRESS # EGRESS or INGRESS
disabled: false # `false` or `true`, FW rule is disabled when `true`, default value is `true`
priority: 1000 # rule priority value, default value is 1000
source_ranges: # list of source ranges, should be specified only for `INGRESS` rule
- 0.0.0.0/0
destination_ranges: # list of destination ranges, should be specified only for `EGRESS` rule
- 0.0.0.0/0
source_tags: ['some-tag'] # list of source tags, should be specified only for `INGRESS` rule
source_service_accounts: # list of source service accounts, should be specified only for `INGRESS` rule, can not be specified together with `source_tags` or `target_tags`
- myapp@myproject-id.iam.gserviceaccount.com
target_tags: ['some-tag'] # list of target tags
target_service_accounts: # list of target service accounts, , can not be specified together with `source_tags` or `target_tags`
- myapp@myproject-id.iam.gserviceaccount.com
```
Firewall rules example yaml configuration
```bash
cat ./prod/core-network/common-rules.yaml
# allow ingress from GCLB to all instances in the network
lb-health-checks:
allow:
- ports: []
protocol: tcp
direction: INGRESS
priority: 1001
source_ranges:
- 35.191.0.0/16
- 130.211.0.0/22
# deny all egress
deny-all:
deny:
- ports: []
protocol: all
direction: EGRESS
priority: 65535
destination_ranges:
- 0.0.0.0/0
cat ./dev/team-a/web-app-a.yaml
# Myapp egress
web-app-a-egress:
allow:
- ports: [443]
protocol: tcp
direction: EGRESS
destination_ranges:
- 192.168.0.0/24
target_service_accounts:
- myapp@myproject-id.iam.gserviceaccount.com
# Myapp ingress
web-app-a-ingress:
allow:
- ports: [1234]
protocol: tcp
direction: INGRESS
source_service_accounts:
- frontend-sa@myproject-id.iam.gserviceaccount.com
target_service_accounts:
- web-app-a@myproject-id.iam.gserviceaccount.com
```
<!-- BEGIN TFDOC -->
## Variables
| name | description | type | required | default |
|---|---|:---: |:---:|:---:|
| config_path | Path to a folder where firewall configs are stored in yaml format. Folder may include subfolders with configuration files. Files suffix must be `.yaml` | <code title="">string</code> | ✓ | |
| network | Name of the network this set of firewall rules applies to. | <code title="">string</code> | ✓ | |
| project_id | Project Id. | <code title="">string</code> | ✓ | |
| *log_config* | Log configuration. Possible values for `metadata` are `EXCLUDE_ALL_METADATA` and `INCLUDE_ALL_METADATA`. Set to `null` for disabling firewall logging. | <code title="object&#40;&#123;&#10;metadata &#61; string&#10;&#125;&#41;">object({...})</code> | | <code title="">null</code> |
## Outputs
| name | description | sensitive |
|---|---|:---:|
| egress_allow_rules | Egress rules with allow blocks. | |
| egress_deny_rules | Egress rules with allow blocks. | |
| ingress_allow_rules | Ingress rules with allow blocks. | |
| ingress_deny_rules | Ingress rules with deny blocks. | |
<!-- END TFDOC -->

View File

@ -0,0 +1,100 @@
/**
* 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 {
firewall_rules = merge(
[
for config_file in fileset("${path.root}/${var.config_path}", "**/*.yaml") :
try(yamldecode(file("${path.root}/${var.config_path}/${config_file}")), {})
]...
)
}
resource "time_static" "timestamp" {
for_each = local.firewall_rules
triggers = {
name = md5(jsonencode(each.value))
}
}
resource "google_compute_firewall" "rules" {
for_each = local.firewall_rules
project = var.project_id
name = format(
"fwr-%s-%s-%s-%s",
var.network,
(try(each.value.target_service_accounts, null) != null ? "sac" : try(each.value.target_tags, null) != null ? "vpc" : "all"),
substr(lower(each.value.direction), 0, 1),
each.key
)
description = format(
"%s rule in network %s for %s created at %s",
each.value.direction,
var.network,
each.key,
time_static.timestamp[each.key].rfc3339
)
network = var.network
direction = each.value.direction
priority = try(each.value.priority, 1000)
disabled = try(each.value.disabled, null)
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 block in try(each.value.allow, []) :
"${block.protocol}-${join("-", block.ports)}" => {
ports = [for port in block.ports : tostring(port)]
protocol = block.protocol
}
}
content {
protocol = allow.value.protocol
ports = allow.value.ports
}
}
dynamic "deny" {
for_each = { for block in try(each.value.deny, []) :
"${block.protocol}-${join("-", block.ports)}" => {
ports = [for port in block.ports : tostring(port)]
protocol = block.protocol
}
}
content {
protocol = deny.value.protocol
ports = deny.value.ports
}
}
dynamic "log_config" {
for_each = var.log_config != null ? [""] : []
content {
metadata = var.log_config.metadata
}
}
lifecycle {
create_before_destroy = true
}
}

View File

@ -0,0 +1,47 @@
/**
* 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 "ingress_allow_rules" {
description = "Ingress rules with allow blocks."
value = [
for rule in google_compute_firewall.rules :
rule.name if rule.direction == "INGRESS" && length(rule.allow) > 0
]
}
output "ingress_deny_rules" {
description = "Ingress rules with deny blocks."
value = [
for rule in google_compute_firewall.rules :
rule.name if rule.direction == "INGRESS" && length(rule.deny) > 0
]
}
output "egress_allow_rules" {
description = "Egress rules with allow blocks."
value = [
for rule in google_compute_firewall.rules :
rule.name if rule.direction == "EGRESS" && length(rule.allow) > 0
]
}
output "egress_deny_rules" {
description = "Egress rules with allow blocks."
value = [
for rule in google_compute_firewall.rules :
rule.name if rule.direction == "EGRESS" && length(rule.deny) > 0
]
}

View File

@ -0,0 +1,38 @@
/**
* 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 "network" {
description = "Name of the network this set of firewall rules applies to."
type = string
}
variable "project_id" {
description = "Project Id."
type = string
}
variable "config_path" {
description = "Path to a folder where firewall configs are stored in yaml format. Folder may include subfolders with configuration files. Files suffix must be `.yaml`"
type = string
}
variable "log_config" {
description = "Log configuration. Possible values for `metadata` are `EXCLUDE_ALL_METADATA` and `INCLUDE_ALL_METADATA`. Set to `null` for disabling firewall logging."
type = object({
metadata = string
})
default = null
}

View File

@ -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.
*/
terraform {
required_version = ">= 0.13.3"
}