Skip to content

Commit

Permalink
feat: add waf acl rules using MP module (#8146)
Browse files Browse the repository at this point in the history
* feat: enable optional logging for waf acl and update the docs

* docs: update TF_DOCS

* fix: added count but forgot to add resource index

* feat: add waf acl rules using MP module

* feat: enable logging
  • Loading branch information
georgepstaylor authored Oct 10, 2024
1 parent f41855f commit 5ee2377
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 57 deletions.
115 changes: 61 additions & 54 deletions terraform/environments/delius-jitbit/waf.tf
Original file line number Diff line number Diff line change
@@ -1,67 +1,74 @@
resource "aws_wafv2_web_acl" "this" {
name = "${local.application_name}-acl"
description = "Web ACL for ${local.application_name}"
scope = "REGIONAL"
default_action {
allow {}
module "shield" {
source = "../../modules/shield_advanced"

providers = {
aws.modernisation-platform = aws.modernisation-platform
}
rule {
name = "AWSManagedRulesCommonRuleSet"
priority = 0
override_action {
# Dont do anything but count requests that match the rules in the ruleset
count {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "${local.application_name}-common-ruleset"
sampled_requests_enabled = true

application_name = local.application_name

enable_logging = true

resources = {
alb = {
arn = aws_lb.external.arn
}
}
rule {
name = "AWSManagedRulesSQLiRuleSet"
priority = 1
override_action {
# Dont do anything but count requests that match the rules in the ruleset
count {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesSQLiRuleSet"
vendor_name = "AWS"

waf_acl_rules = {
AWSManagedRulesCommonRuleSet = {
"action" = "count"
"name" = "AWSManagedRulesCommonRuleSet"
"priority" = 0
"threshold" = 1000
"statement" = {
"managed_rule_group_statement" = {
"name" = "AWSManagedRulesCommonRuleSet"
"vendor_name" = "AWS"
}
}
"visibility_config" = {
"cloudwatch_metrics_enabled" = true
"metric_name" = "${local.application_name}-common-ruleset"
"sampled_requests_enabled" = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "${local.application_name}-SQLi-ruleset"
sampled_requests_enabled = true
AWSManagedRulesSQLiRuleSet = {
"action" = "count"
"name" = "AWSManagedRulesSQLiRuleSet"
"priority" = 1
"threshold" = 1000
"statement" = {
"managed_rule_group_statement" = {
"name" = "AWSManagedRulesSQLiRuleSet"
"vendor_name" = "AWS"
}
}
"visibility_config" = {
"cloudwatch_metrics_enabled" = true
"metric_name" = "${local.application_name}-SQLi-ruleset"
"sampled_requests_enabled" = true
}
}
}

tags = local.tags
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "${local.application_name}-waf-metrics"
sampled_requests_enabled = true
}
}
resource "aws_wafv2_web_acl_association" "this" {
resource_arn = aws_lb.external.arn
web_acl_arn = aws_wafv2_web_acl.this.arn

data "external" "shield_waf" {
program = [
"bash", "-c",
"aws wafv2 list-web-acls --scope REGIONAL --output json | jq -c '{arn: .WebACLs[] | select(.Name | contains(\"FMManagedWebACL\")) | .ARN, name: .WebACLs[] | select(.Name | contains(\"FMManagedWebACL\")) | .Name}'"
]
}

resource "aws_cloudwatch_log_group" "waf" {
name = "aws-waf-logs-${local.application_name}"
retention_in_days = 60
tags = local.tags
locals {
split_arn = split("regional/webacl/", data.external.shield_waf.result["arn"])[1]
name = data.external.shield_waf.result["name"]
id = split("/", local.split_arn)[1]
scope = "REGIONAL"

}
resource "aws_wafv2_web_acl_logging_configuration" "waf" {
log_destination_configs = [aws_cloudwatch_log_group.waf.arn]
resource_arn = aws_wafv2_web_acl.this.arn
import {
id = "${local.id}/${local.name}/${local.scope}"
to = module.shield.aws_wafv2_web_acl.main
}
33 changes: 31 additions & 2 deletions terraform/modules/shield_advanced/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,37 @@ without needing to resort to ClickOps processes.
Because the `aws_wafv2_web_acl` is pre-created by AWS Firewall Manager via the MOJ Root Account code, it needs to be
imported as part of the module setup. This can be done via the command line, or with an import block.

```shell
```hcl
import {
id = "c6f3ba81-c457-40f6-bd1f-30e777f60c27/FMManagedWebACLV2-shield_advanced_auto_remediate-1652297838425/REGIONAL"
to = module.shield.aws_wafv2_web_acl.main
}
```

Using a data source it is possible to create an import block that is dynamic, allowing you to import for all environments in one run.

```hcl
data "external" "shield_waf" {
program = [
"bash", "-c",
"aws wafv2 list-web-acls --scope REGIONAL --output json | jq -c '{arn: .WebACLs[] | select(.Name | contains(\"FMManagedWebACL\")) | .ARN, name: .WebACLs[] | select(.Name | contains(\"FMManagedWebACL\")) | .Name}'"
]
}
locals {
split_arn = split("regional/webacl/", data.external.shield_waf.result["arn"])[1]
name = data.external.shield_waf.result["name"]
id = split("/", local.split_arn)[1]
scope = "REGIONAL"
}
import {
id = "${local.id}/${local.name}/${local.scope}"
to = module.shield.aws_wafv2_web_acl.main
}
```

<!-- BEGIN_TF_DOCS -->
## Requirements

| Name | Version |
Expand All @@ -41,12 +65,14 @@ import {

| Name | Type |
|------|------|
| [aws_cloudwatch_log_group.waf](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource |
| [aws_cloudwatch_metric_alarm.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_metric_alarm) | resource |
| [aws_shield_application_layer_automatic_response.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/shield_application_layer_automatic_response) | resource |
| [aws_shield_drt_access_role_arn_association.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/shield_drt_access_role_arn_association) | resource |
| [aws_sns_topic.module_ddos_alarm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic) | resource |
| [aws_wafv2_web_acl.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl) | resource |
| [aws_wafv2_web_acl_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl_association) | resource |
| [aws_wafv2_web_acl_logging_configuration.waf](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl_logging_configuration) | resource |
| [aws_iam_role.srt_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_role) | data source |
| [aws_kms_key.sns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/kms_key) | data source |
| [aws_secretsmanager_secret.pagerduty_integration_keys](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/secretsmanager_secret) | data source |
Expand All @@ -59,10 +85,13 @@ import {
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_application_name"></a> [application\_name](#input\_application\_name) | Name of application being protected. | `string` | n/a | yes |
| <a name="input_excluded_protections"></a> [excluded\_protections](#input\_excluded\_protections) | A list of strings to not associate with the AWS Shield WAF ACL. | `set(string)` | n/a | yes |
| <a name="input_enable_logging"></a> [enable\_logging](#input\_enable\_logging) | Enable logging for the WAF ACL. | `bool` | `false` | no |
| <a name="input_excluded_protections"></a> [excluded\_protections](#input\_excluded\_protections) | A list of strings to not associate with the AWS Shield WAF ACL. | `set(string)` | `[]` | no |
| <a name="input_log_retention_in_days"></a> [log\_retention\_in\_days](#input\_log\_retention\_in\_days) | Number of days to retain logs in CloudWatch Logs. | `number` | `60` | no |
| <a name="input_resources"></a> [resources](#input\_resources) | Map of resource ARNs and optional automatic response actions. | `map(any)` | n/a | yes |
| <a name="input_waf_acl_rules"></a> [waf\_acl\_rules](#input\_waf\_acl\_rules) | A map of values to be used in a dynamic WAF ACL rule block. | `map(any)` | n/a | yes |

## Outputs

No outputs.
<!-- END_TF_DOCS -->
12 changes: 12 additions & 0 deletions terraform/modules/shield_advanced/shield.tf
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,15 @@ resource "aws_wafv2_web_acl" "main" {
}
}
}

resource "aws_cloudwatch_log_group" "waf" {
count = var.enable_logging ? 1 : 0
name = "aws-waf-logs-${data.external.shield_waf.result["name"]}"
retention_in_days = var.log_retention_in_days
}

resource "aws_wafv2_web_acl_logging_configuration" "waf" {
count = var.enable_logging ? 1 : 0
log_destination_configs = try([aws_cloudwatch_log_group.waf[0].arn], [])
resource_arn = aws_wafv2_web_acl.main.arn
}
14 changes: 13 additions & 1 deletion terraform/modules/shield_advanced/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,16 @@ variable "resources" {
variable "waf_acl_rules" {
type = map(any)
description = "A map of values to be used in a dynamic WAF ACL rule block."
}
}

variable "enable_logging" {
type = bool
default = false
description = "Enable logging for the WAF ACL."
}

variable "log_retention_in_days" {
type = number
default = 60
description = "Number of days to retain logs in CloudWatch Logs."
}

0 comments on commit 5ee2377

Please sign in to comment.