From 0eb171b21e311c45f553f22baab83f9f21ecf3f8 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Thu, 26 Feb 2026 11:54:44 +0100 Subject: [PATCH] Add support for URL filtering profiles to FAST NGFW add-on (#3768) * add support for URL filtering profiles to FAST NGFW add-on * fix YAML linting, add yamllint to pre-commit --- fast/addons/2-networking-ngfw/README.md | 18 +- .../2-networking-ngfw/security-profiles.tf | 51 ++++- fast/addons/2-networking-ngfw/variables.tf | 43 +++- .../addons/a2_networking_ngfw/simple.yaml | 192 +++++++++++++++++- tools/lint.sh | 3 + tools/requirements.txt | 1 + 6 files changed, 293 insertions(+), 15 deletions(-) diff --git a/fast/addons/2-networking-ngfw/README.md b/fast/addons/2-networking-ngfw/README.md index 8de37c378..93f6540bb 100644 --- a/fast/addons/2-networking-ngfw/README.md +++ b/fast/addons/2-networking-ngfw/README.md @@ -198,6 +198,18 @@ security_profiles = { } } } + url_filtering_profile = { + allow-example = { + action = "ALLOW" + priority = 100 + urls = ["example.com"] + } + deny-all = { + action = "DENY" + priority = 200 + # urls defaults to ["*"] + } + } } } tls_inspection_policies = { @@ -271,8 +283,8 @@ Security profiles group defined here are exported via output variable file, and | [host_project_ids](variables-fast.tf#L48) | Networking stage host project id aliases. | map(string) | | {} | 2-networking | | [names](variables.tf#L104) | Configuration for names used for output files. | object({…}) | | {} | | | [outputs_location](variables.tf#L128) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | | -| [security_profiles](variables.tf#L140) | Security profile groups for Layer 7 inspection. Null environment list means all environments. | map(object({…})) | | {…} | | -| [tls_inspection_policies](variables.tf#L182) | TLS inspection policies configuration. CA pools, trust configs and host project ids support interpolation. | map(object({…})) | | {} | | -| [trust_configs](variables.tf#L224) | Certificate Manager trust configurations for TLS inspection policies. Project ids and region can reference keys in the relevant FAST variables. | map(object({…})) | | {…} | | +| [security_profiles](variables.tf#L140) | Security profile groups for Layer 7 inspection. Null environment list means all environments. | map(object({…})) | | {…} | | +| [tls_inspection_policies](variables.tf#L223) | TLS inspection policies configuration. CA pools, trust configs and host project ids support interpolation. | map(object({…})) | | {} | | +| [trust_configs](variables.tf#L265) | Certificate Manager trust configurations for TLS inspection policies. Project ids and region can reference keys in the relevant FAST variables. | map(object({…})) | | {…} | | | [vpc_self_links](variables-fast.tf#L66) | VPC network self links. | map(string) | | {} | 2-networking | diff --git a/fast/addons/2-networking-ngfw/security-profiles.tf b/fast/addons/2-networking-ngfw/security-profiles.tf index 5549a4163..9c6a67ee4 100644 --- a/fast/addons/2-networking-ngfw/security-profiles.tf +++ b/fast/addons/2-networking-ngfw/security-profiles.tf @@ -1,5 +1,5 @@ /** - * Copyright 2025 Google LLC + * Copyright 2026 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,9 +25,14 @@ locals { ) }) } + url_filtering_profiles = { + for k, v in var.security_profiles : k => v.url_filtering_profile + if v.url_filtering_profile != {} + } } resource "google_network_security_security_profile" "default" { + provider = google-beta for_each = local.security_profiles name = each.key description = each.value.description @@ -56,11 +61,41 @@ resource "google_network_security_security_profile" "default" { } } -resource "google_network_security_security_profile_group" "default" { - for_each = var.security_profiles - name = each.key - description = each.value.description - parent = "organizations/${var.organization.id}" - location = "global" - threat_prevention_profile = google_network_security_security_profile.default[each.key].id +resource "google_network_security_security_profile" "url_filtering" { + provider = google-beta + for_each = local.url_filtering_profiles + name = "url-${each.key}" + description = var.security_profiles[each.key].description + parent = "organizations/${var.organization.id}" + location = "global" + type = "URL_FILTERING" + dynamic "url_filtering_profile" { + for_each = length(each.value) > 0 ? [""] : [] + content { + dynamic "url_filters" { + for_each = each.value + content { + filtering_action = url_filters.value.action + priority = url_filters.value.priority + urls = url_filters.value.urls + } + } + } + } +} + +resource "google_network_security_security_profile_group" "default" { + provider = google-beta + for_each = var.security_profiles + name = each.key + description = each.value.description + parent = "organizations/${var.organization.id}" + location = "global" + threat_prevention_profile = ( + google_network_security_security_profile.default[each.key].id + ) + url_filtering_profile = try( + google_network_security_security_profile.url_filtering[each.key].id, + null + ) } diff --git a/fast/addons/2-networking-ngfw/variables.tf b/fast/addons/2-networking-ngfw/variables.tf index e1832d518..c0e3a7cba 100644 --- a/fast/addons/2-networking-ngfw/variables.tf +++ b/fast/addons/2-networking-ngfw/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright 2024 Google LLC + * Copyright 2026 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -151,6 +151,11 @@ variable "security_profiles" { threat_id = string }))) }), {}) + url_filtering_profile = optional(map(object({ + action = string + priority = number + urls = optional(list(string), ["*"]) + })), {}) })) nullable = false default = { @@ -167,6 +172,16 @@ variable "security_profiles" { ])) error_message = "Incorrect severity override token." } + validation { + condition = alltrue(flatten([ + for k, v in var.security_profiles : [ + for lk, lv in coalesce(v.threat_prevention_profile.severity_overrides, {}) : ( + contains(["ALERT", "ALLOW", "DEFAULT_ACTION", "DENY"], lv.action) + ) + ] + ])) + error_message = "Severity override action must be one of ALERT, ALLOW, DEFAULT_ACTION, DENY." + } validation { condition = alltrue(flatten([ for _, v in var.security_profiles : [ @@ -177,6 +192,32 @@ variable "security_profiles" { ])) error_message = "Incorrect threat override token." } + validation { + condition = alltrue(flatten([ + for k, v in var.security_profiles : [ + for lk, lv in coalesce(v.threat_prevention_profile.threat_overrides, {}) : ( + contains(["ALERT", "ALLOW", "DEFAULT_ACTION", "DENY"], lv.action) + ) + ] + ])) + error_message = "Threat override action must be one of ALERT, ALLOW, DEFAULT_ACTION, DENY." + } + validation { + condition = alltrue(flatten([ + for k, v in var.security_profiles : [ + for lk, lv in coalesce(v.url_filtering_profile, {}) : length(lv.urls) > 0 + ] + ])) + error_message = "URL filtering rules must have at least one URL." + } + validation { + condition = alltrue(flatten([ + for k, v in var.security_profiles : [ + for lk, lv in coalesce(v.url_filtering_profile, {}) : contains(["ALLOW", "DENY"], lv.action) + ] + ])) + error_message = "URL filtering rule action must be ALLOW or DENY." + } } variable "tls_inspection_policies" { diff --git a/tests/fast/addons/a2_networking_ngfw/simple.yaml b/tests/fast/addons/a2_networking_ngfw/simple.yaml index 8fa8ebc28..b43c75e84 100644 --- a/tests/fast/addons/a2_networking_ngfw/simple.yaml +++ b/tests/fast/addons/a2_networking_ngfw/simple.yaml @@ -1,4 +1,4 @@ -# Copyright 2024 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,11 +16,197 @@ counts: google_certificate_manager_trust_config: 1 google_network_security_firewall_endpoint: 1 google_network_security_firewall_endpoint_association: 1 - google_network_security_security_profile: 1 + google_network_security_security_profile: 2 google_network_security_security_profile_group: 1 google_network_security_tls_inspection_policy: 1 google_privateca_ca_pool: 1 google_privateca_certificate_authority: 1 google_storage_bucket_object: 1 modules: 1 - resources: 9 + resources: 10 + +values: + google_network_security_firewall_endpoint.default["europe-west8-b"]: + billing_project_id: xxx-prod-net-landing-0 + effective_labels: + goog-terraform-provisioned: 'true' + endpoint_settings: [] + labels: null + location: europe-west8-b + name: ngfw-0 + parent: organizations/123456789012 + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + google_network_security_firewall_endpoint_association.default["europe-west8-b-prod"]: + disabled: false + effective_labels: + goog-terraform-provisioned: 'true' + labels: null + location: europe-west8-b + name: ngfw-0-europe-west8-b-prod + network: projects/xxx-prod-net-spoke-0/global/networks/prod-spoke-0 + parent: projects/xxx-prod-net-spoke-0 + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + google_network_security_security_profile.default["ngfw-0"]: + custom_intercept_profile: [] + custom_mirroring_profile: [] + description: null + effective_labels: + goog-terraform-provisioned: 'true' + labels: null + location: global + name: ngfw-0 + parent: organizations/123456789012 + terraform_labels: + goog-terraform-provisioned: 'true' + threat_prevention_profile: + - antivirus_overrides: [] + severity_overrides: + - action: ALLOW + severity: INFORMATIONAL + threat_overrides: + - action: ALLOW + threat_id: '280647' + timeouts: null + type: THREAT_PREVENTION + url_filtering_profile: [] + google_network_security_security_profile.url_filtering["ngfw-0"]: + custom_intercept_profile: [] + custom_mirroring_profile: [] + description: null + effective_labels: + goog-terraform-provisioned: 'true' + labels: null + location: global + name: url-ngfw-0 + parent: organizations/123456789012 + terraform_labels: + goog-terraform-provisioned: 'true' + threat_prevention_profile: [] + timeouts: null + type: URL_FILTERING + url_filtering_profile: [] + google_network_security_security_profile_group.default["ngfw-0"]: + custom_intercept_profile: null + custom_mirroring_profile: null + description: null + effective_labels: + goog-terraform-provisioned: 'true' + labels: null + location: global + name: ngfw-0 + parent: organizations/123456789012 + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + google_network_security_tls_inspection_policy.default["ngfw-0"]: + custom_tls_features: null + description: null + exclude_public_ca_set: null + location: europe-west8 + min_tls_version: TLS_VERSION_UNSPECIFIED + name: ngfw-0 + project: xxx-prod-net-landing-0 + timeouts: null + tls_feature_profile: PROFILE_UNSPECIFIED + google_storage_bucket_object.tfvars: + bucket: test + cache_control: null + content_disposition: null + content_encoding: null + content_language: null + contexts: [] + customer_encryption: [] + deletion_policy: null + detect_md5hash: different hash + event_based_hold: null + force_empty_content_type: null + metadata: null + name: tfvars/2-networking-ngfw.auto.tfvars.json + retention: [] + source: null + source_md5hash: null + temporary_hold: null + timeouts: null + module.cas["ngfw-0"].google_privateca_ca_pool.default[0]: + effective_labels: + goog-terraform-provisioned: 'true' + encryption_spec: [] + issuance_policy: [] + labels: null + location: europe-west8 + name: ngfw-0 + project: xxx-prod-net-landing-0 + publishing_options: [] + terraform_labels: + goog-terraform-provisioned: 'true' + tier: DEVOPS + timeouts: null + module.cas["ngfw-0"].google_privateca_certificate_authority.default["ca-0"]: + certificate_authority_id: ca-0 + config: + - subject_config: + - subject: + - common_name: fast.example.com + country_code: null + locality: null + organization: FAST Test + organizational_unit: null + postal_code: null + province: null + street_address: null + subject_alt_name: [] + subject_key_id: [] + x509_config: + - additional_extensions: [] + aia_ocsp_servers: null + ca_options: + - is_ca: true + max_issuer_path_length: null + non_ca: null + zero_max_issuer_path_length: null + key_usage: + - base_key_usage: + - cert_sign: true + content_commitment: false + crl_sign: true + data_encipherment: false + decipher_only: false + digital_signature: false + encipher_only: false + key_agreement: false + key_encipherment: true + extended_key_usage: + - client_auth: false + code_signing: false + email_protection: false + ocsp_signing: false + server_auth: true + time_stamping: false + unknown_extended_key_usages: [] + name_constraints: [] + policy_ids: [] + deletion_protection: false + desired_state: null + effective_labels: + goog-terraform-provisioned: 'true' + gcs_bucket: null + ignore_active_certificates_on_deletion: false + key_spec: + - algorithm: RSA_PKCS1_2048_SHA256 + cloud_kms_key_version: null + labels: null + lifetime: 315360000s + location: europe-west8 + pem_ca_certificate: null + project: xxx-prod-net-landing-0 + skip_grace_period: true + subordinate_config: [] + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + type: SELF_SIGNED + user_defined_access_urls: [] diff --git a/tools/lint.sh b/tools/lint.sh index 38da3dc6a..2674acb09 100755 --- a/tools/lint.sh +++ b/tools/lint.sh @@ -31,6 +31,9 @@ python3 tools/check_links.py --no-show-summary $PWD echo -- FAST Names -- python3 tools/check_names.py --prefix-length=10 --failed-only fast/stages +echo -- YAML linting -- +yamllint -c .yamllint . + echo -- Python formatting -- yapf -p -d -r \ tools/*.py \ diff --git a/tools/requirements.txt b/tools/requirements.txt index 08ee0172d..e46e1129c 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -6,6 +6,7 @@ iso8601 marko requests yamale +yamllint yapf jsonschema BeautifulSoup4