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