From 1795b1f27dba8b40c27c82675411a8ba4978563b Mon Sep 17 00:00:00 2001 From: Jacob Coffee Date: Mon, 16 Sep 2024 11:00:00 -0500 Subject: [PATCH 1/6] feat: add ngwaf (#2527) * feat: add ngwaf with successful tfplan * docs: update to latest var name * chore: apply formatting * feat: make ngwaf bits enabled via var * fix: use var fvor activation * fix: fix invalid syntax * fix: fix invalid syntax again * chore: use service account * chore: cleanup cruft * fix: apply patch for dynamic dynamic things * Update infra/cdn/README.md * Update infra/cdn/README.md --- infra/.terraform.lock.hcl | 22 +++++++++++++ infra/cdn/README.md | 28 ++++++++++++++-- infra/cdn/main.tf | 69 +++++++++++++++++++++++++++++++++++++++ infra/cdn/ngwaf.tf | 49 +++++++++++++++++++++++++++ infra/cdn/providers.tf | 8 +++++ infra/cdn/variables.tf | 36 +++++++++++++++++++- infra/cdn/versions.tf | 4 +++ infra/main.tf | 18 +++++++--- infra/variables.tf | 7 +++- 9 files changed, 233 insertions(+), 8 deletions(-) create mode 100644 infra/cdn/ngwaf.tf diff --git a/infra/.terraform.lock.hcl b/infra/.terraform.lock.hcl index 165cd9357..5844f52bd 100644 --- a/infra/.terraform.lock.hcl +++ b/infra/.terraform.lock.hcl @@ -22,3 +22,25 @@ provider "registry.terraform.io/fastly/fastly" { "zh:ec8d899cafd925d3492f00c6523c90599aebc43c1373ad4bd6c55f12d2376230", ] } + +provider "registry.terraform.io/signalsciences/sigsci" { + version = "3.3.0" + constraints = "3.3.0" + hashes = [ + "h1:DIoFVzfofY8lQSxFTw9wmQQC28PPMq+5l3xbPNw9gLc=", + "zh:07c25e1cca9c13314429a8430c2e999ad94c7d5e2f2a11501ee2608182387e61", + "zh:07daf79b672f3e0bec7b48e3ac8dcdeec02af06b10d653bd8158a74236b0746b", + "zh:1e24a050c3d3571ec3224c4bb5c82635caf636e707b5993a1cc97c9a1f19fa8f", + "zh:24293ae24b3de13bda8512c47967f01814724805396a1bfbfbfc56f5627615cc", + "zh:2cc6ba7a38d9854146d1d05f4b7a2f8e18a33c1267b768506cbe37168dad01dc", + "zh:42065bfee0cfde04096d6140c65379253359bed49b481a97aff70aa65bf568b3", + "zh:6f7f4d96967dfd92f098b57647d396679b70d92548db6d100c4dc8723569d175", + "zh:a2e4431f045cef16ed152c0d1f8a377b6468351b775ad1ca7ce3fe74fb874be2", + "zh:b0ed1cb03d6f191fe211f10bb59ef8daed6f89e3d99136e7bb5d38f2ac72fa45", + "zh:b61ea18442a65d27b97dd1cd43bdd8d0a56c2b4b8db6355480e89f8507c6782a", + "zh:c31bb2f50ac2a636758f93afec0b9d173be6d7d7476f9e250b4554e70c6d8d82", + "zh:cb7337f7b4678ad7ece28741069c07ce5601d2a103a9667db568cf10ed0ee5a2", + "zh:d521a7dac51733aebb0905e25b8f7c1279d83c06136e87826e010c667528fd3e", + "zh:ef791688acee3b8b1191b3c6dc54dabf69612dbfb666720280b492ce348a3a06", + ] +} diff --git a/infra/cdn/README.md b/infra/cdn/README.md index 6ebe5a637..a667f63db 100644 --- a/infra/cdn/README.md +++ b/infra/cdn/README.md @@ -29,5 +29,29 @@ N/A ## Requirements Tested on -- Tested on Terraform 1.8.5 -- Fastly provider 5.13.0 \ No newline at end of file +- Tested on Terraform 1.9.5 +- Fastly provider 5.13.0 + +# Fastly's NGWAF + +This module also conditionally can set up the Fastly Next-Gen Web Application Firewall (NGWAF) +for our Fastly services related to python.org / test.python.org. + +## Usage + +```hcl +module "fastly_production" { + source = "./cdn" + + ... + activate_ngwaf_service = true + ... +} +``` + +## Requirements + +Tested on +- Terraform 1.9.5 +- Fastly provider 5.13.0 +- SigSci provider 3.3.0 \ No newline at end of file diff --git a/infra/cdn/main.tf b/infra/cdn/main.tf index 12d1fbba4..eb6c6858c 100644 --- a/infra/cdn/main.tf +++ b/infra/cdn/main.tf @@ -342,4 +342,73 @@ resource "fastly_service_vcl" "python_org" { response = "Forbidden" status = 403 } + + dynamic "dictionary" { + for_each = var.activate_ngwaf_service ? [1] : [] + content { + name = var.edge_security_dictionary + } + } + + dynamic "dynamicsnippet" { + for_each = var.activate_ngwaf_service ? [1] : [] + content { + name = "ngwaf_config_init" + type = "init" + priority = 0 + } + } + + dynamic "dynamicsnippet" { + for_each = var.activate_ngwaf_service ? [1] : [] + content { + name = "ngwaf_config_miss" + type = "miss" + priority = 9000 + } + } + + dynamic "dynamicsnippet" { + for_each = var.activate_ngwaf_service ? [1] : [] + content { + name = "ngwaf_config_pass" + type = "pass" + priority = 9000 + } + } + + dynamic "dynamicsnippet" { + for_each = var.activate_ngwaf_service ? [1] : [] + content { + name = "ngwaf_config_deliver" + type = "deliver" + priority = 9000 + } + } + + lifecycle { + ignore_changes = [ + product_enablement, + ] + } +} + +output "service_id" { + value = fastly_service_vcl.python_org.id + description = "The ID of the Fastly service" +} + +output "backend_address" { + value = var.backend_address + description = "The backend address for the service." +} + +output "service_name" { + value = var.name + description = "The name of the Fastly service" +} + +output "domain" { + value = var.domain + description = "The domain of the Fastly service" } diff --git a/infra/cdn/ngwaf.tf b/infra/cdn/ngwaf.tf new file mode 100644 index 000000000..8ca3a61f6 --- /dev/null +++ b/infra/cdn/ngwaf.tf @@ -0,0 +1,49 @@ +resource "fastly_service_dictionary_items" "edge_security_dictionary_items" { + count = var.activate_ngwaf_service ? 1 : 0 + service_id = fastly_service_vcl.python_org.id + dictionary_id = one([for d in fastly_service_vcl.python_org.dictionary : d.dictionary_id if d.name == var.edge_security_dictionary]) + items = { + Enabled : "100" + } +} + +resource "fastly_service_dynamic_snippet_content" "ngwaf_config_snippets" { + for_each = var.activate_ngwaf_service ? toset(["init", "miss", "pass", "deliver"]) : [] + service_id = fastly_service_vcl.python_org.id + snippet_id = one([for d in fastly_service_vcl.python_org.dynamicsnippet : d.snippet_id if d.name == "ngwaf_config_${each.key}"]) + content = "### Terraform managed ngwaf_config_${each.key}" + manage_snippets = false +} + +# NGWAF Edge Deployment on SignalSciences.net +resource "sigsci_edge_deployment" "ngwaf_edge_site_service" { + count = var.activate_ngwaf_service ? 1 : 0 + provider = sigsci.firewall + site_short_name = var.ngwaf_site_name +} + +resource "sigsci_edge_deployment_service" "ngwaf_edge_service_link" { + count = var.activate_ngwaf_service ? 1 : 0 + provider = sigsci.firewall + site_short_name = var.ngwaf_site_name + fastly_sid = fastly_service_vcl.python_org.id + activate_version = var.activate_ngwaf_service + percent_enabled = 100 + depends_on = [ + sigsci_edge_deployment.ngwaf_edge_site_service, + fastly_service_vcl.python_org, + fastly_service_dictionary_items.edge_security_dictionary_items, + fastly_service_dynamic_snippet_content.ngwaf_config_snippets, + ] +} + +resource "sigsci_edge_deployment_service_backend" "ngwaf_edge_service_backend_sync" { + count = var.activate_ngwaf_service ? 1 : 0 + provider = sigsci.firewall + site_short_name = var.ngwaf_site_name + fastly_sid = fastly_service_vcl.python_org.id + fastly_service_vcl_active_version = fastly_service_vcl.python_org.active_version + depends_on = [ + sigsci_edge_deployment_service.ngwaf_edge_service_link, + ] +} diff --git a/infra/cdn/providers.tf b/infra/cdn/providers.tf index 201f5de4a..bdee7a807 100644 --- a/infra/cdn/providers.tf +++ b/infra/cdn/providers.tf @@ -2,3 +2,11 @@ provider "fastly" { alias = "cdn" api_key = var.fastly_key } + +provider "sigsci" { + alias = "firewall" + corp = var.ngwaf_corp_name + email = var.ngwaf_email + auth_token = var.ngwaf_token + fastly_api_key = var.fastly_key +} diff --git a/infra/cdn/variables.tf b/infra/cdn/variables.tf index 4cbf6db6e..5c1be4562 100644 --- a/infra/cdn/variables.tf +++ b/infra/cdn/variables.tf @@ -40,4 +40,38 @@ variable "backend_address" { variable "default_ttl" { type = number description = "The default TTL for the service." -} \ No newline at end of file +} + +## NGWAF +variable "activate_ngwaf_service" { + type = bool + description = "Whether to activate the NGWAF service." +} +variable "edge_security_dictionary" { + type = string + description = "The dictionary name for the Edge Security product." + default = "" +} +variable "ngwaf_corp_name" { + type = string + description = "Corp name for NGWAF" + default = "python" +} +variable "ngwaf_site_name" { + type = string + description = "Site SHORT name for NGWAF" + + validation { + condition = can(regex("^(test|stage|prod)$", var.ngwaf_site_name)) + error_message = "'ngwaf_site_name' must be one of the following: test, stage, or prod" + } +} +variable "ngwaf_email" { + type = string + description = "Email address associated with the token for the NGWAF API." +} +variable "ngwaf_token" { + type = string + description = "Secret token for the NGWAF API." + sensitive = true +} diff --git a/infra/cdn/versions.tf b/infra/cdn/versions.tf index da9c01f79..f8c137ba6 100644 --- a/infra/cdn/versions.tf +++ b/infra/cdn/versions.tf @@ -4,5 +4,9 @@ terraform { source = "fastly/fastly" version = "5.13.0" } + sigsci = { + source = "signalsciences/sigsci" + version = "3.3.0" + } } } diff --git a/infra/main.tf b/infra/main.tf index b3ec26a77..90c2ba9c5 100644 --- a/infra/main.tf +++ b/infra/main.tf @@ -12,15 +12,20 @@ module "fastly_production" { fastly_key = var.FASTLY_API_KEY fastly_header_token = var.FASTLY_HEADER_TOKEN s3_logging_keys = var.fastly_s3_logging + + ngwaf_site_name = "prod" + ngwaf_email = "infrastructure-staff@python.org" + ngwaf_token = var.ngwaf_token + activate_ngwaf_service = false } module "fastly_staging" { source = "./cdn" - name = "test.python.org" - domain = "test.python.org" - subdomain = "www.test.python.org" - extra_domains = ["www.test.python.org"] + name = "test.python.org" + domain = "test.python.org" + subdomain = "www.test.python.org" + extra_domains = ["www.test.python.org"] # TODO: adjust to test-pythondotorg when done testing NGWAF backend_address = "pythondotorg.ingress.us-east-2.psfhosted.computer" default_ttl = 3600 @@ -29,4 +34,9 @@ module "fastly_staging" { fastly_key = var.FASTLY_API_KEY fastly_header_token = var.FASTLY_HEADER_TOKEN s3_logging_keys = var.fastly_s3_logging + + ngwaf_site_name = "test" + ngwaf_email = "infrastructure-staff@python.org" + ngwaf_token = var.ngwaf_token + activate_ngwaf_service = true } diff --git a/infra/variables.tf b/infra/variables.tf index ec23b23ec..33fc1dda5 100644 --- a/infra/variables.tf +++ b/infra/variables.tf @@ -17,4 +17,9 @@ variable "fastly_s3_logging" { type = map(string) description = "S3 bucket keys for Fastly logging" sensitive = true -} \ No newline at end of file +} +variable "ngwaf_token" { + type = string + description = "Secret token for the NGWAF API." + sensitive = true +} From 097df08265800240d092645328f271dfad3582a5 Mon Sep 17 00:00:00 2001 From: Jacob Coffee Date: Mon, 16 Sep 2024 12:13:12 -0500 Subject: [PATCH 2/6] fix: recreate dictionary each run (#2568) * fix: recreate dictionary each run * Update infra/cdn/main.tf --- infra/cdn/main.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/infra/cdn/main.tf b/infra/cdn/main.tf index eb6c6858c..c50888e3f 100644 --- a/infra/cdn/main.tf +++ b/infra/cdn/main.tf @@ -347,6 +347,7 @@ resource "fastly_service_vcl" "python_org" { for_each = var.activate_ngwaf_service ? [1] : [] content { name = var.edge_security_dictionary + force_destroy = true } } From edee24abe775f9ac7133134a9fe16b8adbe11254 Mon Sep 17 00:00:00 2001 From: Ee Durbin Date: Tue, 17 Sep 2024 11:38:35 -0400 Subject: [PATCH 3/6] add a name for the ngwaf edge dictionary (#2579) --- infra/cdn/variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/cdn/variables.tf b/infra/cdn/variables.tf index 5c1be4562..ec0a11a83 100644 --- a/infra/cdn/variables.tf +++ b/infra/cdn/variables.tf @@ -50,7 +50,7 @@ variable "activate_ngwaf_service" { variable "edge_security_dictionary" { type = string description = "The dictionary name for the Edge Security product." - default = "" + default = "Edge_Security" } variable "ngwaf_corp_name" { type = string From 2856d9cc906b1f5f35debe00f19b0e9887947b0f Mon Sep 17 00:00:00 2001 From: Jacob Coffee Date: Tue, 17 Sep 2024 11:03:47 -0500 Subject: [PATCH 4/6] infra: activate by default (#2580) * infra: activate by default * Update infra/cdn/main.tf --- infra/cdn/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/cdn/main.tf b/infra/cdn/main.tf index c50888e3f..e7aa77108 100644 --- a/infra/cdn/main.tf +++ b/infra/cdn/main.tf @@ -4,7 +4,7 @@ resource "fastly_service_vcl" "python_org" { http3 = false stale_if_error = false stale_if_error_ttl = 43200 - activate = false + activate = true domain { name = var.domain From 6b4c6815f06313ba84b7f5bb1d787faa14c4ff6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Sep 2024 14:49:59 -0500 Subject: [PATCH 5/6] Bump django-ordered-model from 3.4.3 to 3.7.4 (#2544) Bumps [django-ordered-model](https://github.com/django-ordered-model/django-ordered-model) from 3.4.3 to 3.7.4. - [Release notes](https://github.com/django-ordered-model/django-ordered-model/releases) - [Changelog](https://github.com/django-ordered-model/django-ordered-model/blob/master/CHANGES.md) - [Commits](https://github.com/django-ordered-model/django-ordered-model/compare/3.4.3...3.7.4) --- updated-dependencies: - dependency-name: django-ordered-model dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jacob Coffee --- base-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base-requirements.txt b/base-requirements.txt index 680685cfc..f616b8cb6 100644 --- a/base-requirements.txt +++ b/base-requirements.txt @@ -42,7 +42,7 @@ django-waffle==2.2.1 djangorestframework==3.14.0 # 3.14.0 is first version that supports Django 4.1, 4.2 support hasnt been "released" django-filter==2.4.0 -django-ordered-model==3.4.3 +django-ordered-model==3.7.4 django-widget-tweaks==1.5.0 django-countries==7.2.1 num2words==0.5.10 From 306a73df3c120664031670a47b04c3d8cc56c8c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Sep 2024 15:08:46 -0500 Subject: [PATCH 6/6] Bump num2words from 0.5.10 to 0.5.13 (#2574) Bumps [num2words](https://github.com/savoirfairelinux/num2words) from 0.5.10 to 0.5.13. - [Release notes](https://github.com/savoirfairelinux/num2words/releases) - [Changelog](https://github.com/savoirfairelinux/num2words/blob/master/CHANGES.rst) - [Commits](https://github.com/savoirfairelinux/num2words/compare/v0.5.10...v0.5.13) --- updated-dependencies: - dependency-name: num2words dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- base-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base-requirements.txt b/base-requirements.txt index f616b8cb6..d8b1295c9 100644 --- a/base-requirements.txt +++ b/base-requirements.txt @@ -45,7 +45,7 @@ django-filter==2.4.0 django-ordered-model==3.7.4 django-widget-tweaks==1.5.0 django-countries==7.2.1 -num2words==0.5.10 +num2words==0.5.13 django-polymorphic==3.1.0 # 3.1.0 is first version that supports Django 4.0, unsure if it fully supports 4.2 sorl-thumbnail==12.7.0 django-extensions==3.1.4