diff --git a/google_fastly_waf/README.md b/google_fastly_waf/README.md index 9da31bb5..acc93266 100644 --- a/google_fastly_waf/README.md +++ b/google_fastly_waf/README.md @@ -148,8 +148,10 @@ module "fastly_stage" { | [cache\_settings](#input\_cache\_settings) | List of cache settings for the Fastly service. |
list(object({
name = string
action = optional(string)
cache_condition = optional(string)
stale_ttl = optional(number)
ttl = optional(number)
}))
| `[]` | no | | [conditions](#input\_conditions) | List of Fastly conditions to create (REQUEST, RESPONSE or CACHE). |
list(object({
name = string # required, unique
statement = string # VCL conditional expression
type = string # one of: REQUEST, RESPONSE, CACHE
priority = optional(number) # lower runs first, default 10
}))
| `[]` | no | | [ddos\_protection](#input\_ddos\_protection) | Optional DDoS Protection configuration for the Fastly service product enablement. |
object({
enabled = bool
mode = string
})
| `null` | no | +| [ddos\_protection\_alert](#input\_ddos\_protection\_alert) | Optional Slack alerting for Fastly DDoS Protection. When set, the module creates a Slack `fastly_integration` and a `fastly_alert` on the `ddos_protection_requests_detect_count` stats metric that notifies the channel behind the webhook. Intended to be paired with `ddos_protection` being enabled. Set to `null` (the default) to create no alerting resources. |
object({
enabled = optional(bool, true)
slack_webhook_secret = string
threshold = optional(number, 1)
period = optional(string, "5m")
})
| `null` | no | | [domains](#input\_domains) | A list of domains | `list(any)` | `[]` | no | | [https\_redirect\_enabled](#input\_https\_redirect\_enabled) | n/a | `bool` | `true` | no | +| [legacy\_edge\_deployment](#input\_legacy\_edge\_deployment) | If true (default), deploy NGWAF via the legacy sigsci EdgeDeployment APIs and Fastly dynamic snippets. If false, deploy via Fastly's product\_enablement ngwaf block. Default preserves behavior for services still on the legacy method. | `bool` | `true` | no | | [log\_sampling\_enabled](#input\_log\_sampling\_enabled) | n/a | `bool` | `false` | no | | [log\_sampling\_percent](#input\_log\_sampling\_percent) | n/a | `string` | `"10"` | no | | [ngwaf\_agent\_level](#input\_ngwaf\_agent\_level) | This is the site wide blocking level | `string` | `"log"` | no | diff --git a/google_fastly_waf/alerts.tf b/google_fastly_waf/alerts.tf new file mode 100644 index 00000000..52e0a726 --- /dev/null +++ b/google_fastly_waf/alerts.tf @@ -0,0 +1,40 @@ +# DDoS Protection alerting +# When var.ddos_protection_alert is set, create a Slack integration and a +# Fastly stats alert that fires on the `ddos_protection_requests_detect_count` +# metric (requests classified as DDoS attacks). This is only meaningful when +# ddos_protection is enabled (mode "log" or "block"). + +locals { + ddos_protection_alert_enabled = ( + var.ddos_protection_alert != null && var.ddos_protection_alert.enabled + ) +} + +resource "fastly_integration" "ddos_protection_slack" { + count = local.ddos_protection_alert_enabled ? 1 : 0 + + name = "${var.application}-${var.realm}-${var.environment} DDoS Protection Slack integration" + description = "Slack notifications for Fastly DDoS Protection detection events" + type = "slack" + + config = { + webhook = sensitive(var.ddos_protection_alert.slack_webhook_secret) + } +} + +resource "fastly_alert" "ddos_protection" { + count = local.ddos_protection_alert_enabled ? 1 : 0 + + name = "${var.application}-${var.realm}-${var.environment} DDoS Protection events" + service_id = fastly_service_vcl.default.id + source = "stats" + metric = "ddos_protection_requests_detect_count" + + evaluation_strategy { + type = "above_threshold" + period = var.ddos_protection_alert.period + threshold = var.ddos_protection_alert.threshold + } + + integration_ids = [fastly_integration.ddos_protection_slack[0].id] +} diff --git a/google_fastly_waf/variables.tf b/google_fastly_waf/variables.tf index 725d095e..43290cf6 100644 --- a/google_fastly_waf/variables.tf +++ b/google_fastly_waf/variables.tf @@ -143,6 +143,41 @@ variable "ddos_protection" { } } +variable "ddos_protection_alert" { + description = <<-EOT + Optional Slack alerting for Fastly DDoS Protection. When set, the module + creates a Slack `fastly_integration` and a `fastly_alert` on the + `ddos_protection_requests_detect_count` stats metric that notifies the + channel behind the webhook. Intended to be paired with `ddos_protection` + being enabled. + Set to `null` (the default) to create no alerting resources. + EOT + type = object({ + enabled = optional(bool, true) # set false to keep config but tear the alert down + slack_webhook_secret = string # Slack incoming-webhook URL (sensitive) + threshold = optional(number, 1) # ddos_protection_requests_detect_count that fires the alert + period = optional(string, "5m") # evaluation window: 2m, 3m, 5m, 15m, or 30m + }) + default = null + validation { + condition = var.ddos_protection_alert == null || contains( + ["2m", "3m", "5m", "15m", "30m"], var.ddos_protection_alert.period + ) + error_message = "ddos_protection_alert.period must be one of: 2m, 3m, 5m, 15m, or 30m." + } + validation { + # The detect-count metric is only populated when DDoS Protection is actively + # inspecting traffic, so alerting requires ddos_protection enabled in log or + # block mode. (Cross-variable validation requires Terraform >= 1.9.) + condition = var.ddos_protection_alert == null || ( + var.ddos_protection != null && + var.ddos_protection.enabled && + var.ddos_protection.mode != "off" + ) + error_message = "ddos_protection_alert requires ddos_protection to be enabled with mode \"log\" or \"block\"." + } +} + ## NGWAF variable "legacy_edge_deployment" { type = bool diff --git a/google_fastly_waf/versions.tf b/google_fastly_waf/versions.tf index b7721a65..5c90a7fb 100644 --- a/google_fastly_waf/versions.tf +++ b/google_fastly_waf/versions.tf @@ -1,4 +1,8 @@ terraform { + # >= 1.9 is required for cross-variable references in validation blocks + # (see ddos_protection_alert in variables.tf). + required_version = ">= 1.9" + required_providers { fastly = { source = "fastly/fastly"