Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
m13t committed Apr 17, 2024
1 parent 42829bd commit 7d05e40
Show file tree
Hide file tree
Showing 10 changed files with 283 additions and 10 deletions.
28 changes: 23 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,39 @@ The `terraform-docs` utility is used to generate this README. Follow the below s

## Providers

No providers.
| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 5.0.0 |

## Modules

No modules.
| Name | Source | Version |
|------|--------|---------|
| <a name="module_parser"></a> [parser](#module\_parser) | ./modules/rules_parser | n/a |

## Resources

No resources.
| Name | Type |
|------|------|
| [aws_networkfirewall_rule_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/networkfirewall_rule_group) | resource |

## Inputs

No inputs.
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_name"></a> [name](#input\_name) | Name of the AWS network firewall rule group | `string` | n/a | yes |
| <a name="input_rule_files"></a> [rule\_files](#input\_rule\_files) | List of rule files to load into the rule group | `list(string)` | n/a | yes |
| <a name="input_capacity"></a> [capacity](#input\_capacity) | Capacity defining the maximum number of rules within the rule group | `number` | `50` | no |
| <a name="input_ip_references"></a> [ip\_references](#input\_ip\_references) | Map consisting of string keys and values denoting IP prefix list variable definitions | `map(string)` | `{}` | no |
| <a name="input_ip_variables"></a> [ip\_variables](#input\_ip\_variables) | Map consisting of string keys with string list values denoting IP variable definitions | `map(list(string))` | `{}` | no |
| <a name="input_ordering"></a> [ordering](#input\_ordering) | Specifies the type of ordering when evaluating rules within the group | `string` | `"DEFAULT_ACTION_ORDER"` | no |
| <a name="input_port_variables"></a> [port\_variables](#input\_port\_variables) | Map consisting of string keys with string list values denoting port variable definitions | `map(list(string))` | `{}` | no |
| <a name="input_tags"></a> [tags](#input\_tags) | Tags to be applied to resources created by this module | `map(string)` | `{}` | no |

## Outputs

No outputs.
| Name | Description |
|------|-------------|
| <a name="output_arn"></a> [arn](#output\_arn) | ARN of the AWS network firewall rule group |
| <a name="output_rules"></a> [rules](#output\_rules) | List of applied rules within the network firewall rule group |
<!-- END_TF_DOCS -->
6 changes: 6 additions & 0 deletions locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
locals {
rules = compact(concat([
for f in var.rule_files :
split("\n", format("# --- %s\n%s", basename(f), file(f)))
]...))
}
66 changes: 66 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
module "parser" {
source = "./modules/rules_parser"

rules = local.rules
}

resource "aws_networkfirewall_rule_group" "this" {
name = var.name
capacity = var.capacity
description = "Stateful rule group for ${var.name}"
type = "STATEFUL"

rule_group {
stateful_rule_options {
rule_order = var.ordering
}

rule_variables {
dynamic "ip_sets" {
for_each = var.ip_variables

content {
key = upper(ip_sets.key)
ip_set {
definition = ip_sets.value
}
}
}

dynamic "port_sets" {
for_each = var.port_variables

content {
key = upper(port_sets.key)
port_set {
definition = port_sets.value
}
}
}
}

reference_sets {
dynamic "ip_set_references" {
for_each = var.ip_references

content {
key = upper(ip_set_references.key)
ip_set_reference {
reference_arn = ip_set_references.value
}
}
}
}

rules_source {
rules_string = module.parser.generated
}
}

tags = merge(var.tags, {
"Name" : var.name
"RuleFiles" : join(" ", [
for name in var.rule_files : basename(name)
])
})
}
36 changes: 36 additions & 0 deletions modules/rules_parser/checks.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
check "disabled_rules" {
assert {
condition = length(local.disabled_rules) == 0
error_message = format(
"The following rules are valid but marked as disabled:\n%s",
join("\n", formatlist("\t- %s", [
for rule in local.disabled_rules :
rule.raw
])),
)
}
}

check "invalid_rules" {
assert {
condition = length(local.invalid_rules) == 0
error_message = format(
"The following rule are invalid or malformed:\n%s",
join("\n", formatlist("\t- %s", [
for rule in local.invalid_rules : rule
])),
)
}
}

check "duplicate_sids" {
assert {
condition = length(local.duplicate_sids) == 0
error_message = format(
"The following duplicate statement identifiers have been found:\n%s",
join("\n", formatlist("\t- %s", [
for sid in local.duplicate_sids : sid
])),
)
}
}
52 changes: 52 additions & 0 deletions modules/rules_parser/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
locals {
rule_regex = "^(?P<enabled>#)*[\\s#]*(?P<raw>(?P<header>[^()]+)\\((?P<options>.*)\\)$)"

option_regex = "(?P<key>[^\\;\\:]+)(?:\\:\\s?(?P<value>[^\\;]+))?\\;"

raw_rules = [
for rule in flatten([
for rule in var.rules : [
try(regex(local.rule_regex, trimspace(rule)), {
parsed = false,
raw = rule,
})
]
]) : merge(rule, {
parsed = lookup(rule, "parsed", true)
enabled = lookup(rule, "enabled", null) != "#"
header = trimspace(lookup(rule, "header", ""))
options = regexall(local.option_regex, lookup(rule, "options", ""))
})
]

invalid_rules = [
for rule in local.raw_rules :
rule.raw if rule.parsed == false && !startswith(rule.raw, "#")
]

enabled_rules = [
for rule in local.raw_rules :
rule if rule.parsed == true && rule.enabled == true
]

disabled_rules = [
for rule in local.raw_rules :
rule if rule.parsed == true && rule.enabled == false
]

comments = [
for rule in local.raw_rules :
rule.raw if rule.parsed == false && startswith(rule.raw, "#")
]

sids = sort(flatten([
for rule in local.enabled_rules : [
for opt in rule.options : opt.value if trimspace(opt.key) == "sid"
]
]))

duplicate_sids = [
for a, b in local.sids :
b if index(local.sids, b) != a
]
}
30 changes: 30 additions & 0 deletions modules/rules_parser/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
output "invalid" {
value = local.invalid_rules
description = "List of rules that failed to be parsed"
}

output "enabled" {
value = local.enabled_rules
description = "List of enabled rules"
}

output "disabled" {
value = local.disabled_rules
description = "List of disabled rules"
}

output "comments" {
value = local.comments
description = "List of comments"
}

output "duplicate_sids" {
value = local.duplicate_sids
description = "List of duplicate statement identifiers for enabled rules"
}

output "generated" {
value = join("\n", [
for rule in local.enabled_rules : rule.raw
])
}
4 changes: 4 additions & 0 deletions modules/rules_parser/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
variable "rules" {
type = list(string)
description = "List of rules. Each entry should be a single rule declaration"
}
12 changes: 12 additions & 0 deletions outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
output "arn" {
value = aws_networkfirewall_rule_group.this.arn
description = "ARN of the AWS network firewall rule group"
}

output "rules" {
value = [
for rule in module.parser.enabled : rule.raw
]

description = "List of applied rules within the network firewall rule group"
}
5 changes: 0 additions & 5 deletions terraform.tf
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

terraform {
required_version = ">= 1.0.7"

Expand All @@ -7,9 +6,5 @@ terraform {
source = "hashicorp/aws"
version = ">= 5.0.0"
}
awscc = {
source = "hashicorp/awscc"
version = ">= 0.24.0"
}
}
}
54 changes: 54 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
variable "name" {
type = string
description = "Name of the AWS network firewall rule group"
}

variable "capacity" {
type = number
default = 50
description = "Capacity defining the maximum number of rules within the rule group"
}

variable "ordering" {
type = string
default = "DEFAULT_ACTION_ORDER"
description = "Specifies the type of ordering when evaluating rules within the group"

validation {
condition = contains([
"DEFAULT_ACTION_ORDER",
"STRICT_ORDER",
], var.ordering)

error_message = "Invalid ordering type specified. Must be one of 'DEFAULT_ACTION_ORDER', 'STRICT_ORDER'."
}
}

variable "rule_files" {
type = list(string)
description = "List of rule files to load into the rule group"
}

variable "ip_variables" {
type = map(list(string))
default = {}
description = "Map consisting of string keys with string list values denoting IP variable definitions"
}

variable "ip_references" {
type = map(string)
default = {}
description = "Map consisting of string keys and values denoting IP prefix list variable definitions"
}

variable "port_variables" {
type = map(list(string))
default = {}
description = "Map consisting of string keys with string list values denoting port variable definitions"
}

variable "tags" {
type = map(string)
default = {}
description = "Tags to be applied to resources created by this module"
}

0 comments on commit 7d05e40

Please sign in to comment.