diff --git a/fast/stages/0-org-setup/schemas/project.schema.json b/fast/stages/0-org-setup/schemas/project.schema.json index 89b0aeaba..4f3b15610 100644 --- a/fast/stages/0-org-setup/schemas/project.schema.json +++ b/fast/stages/0-org-setup/schemas/project.schema.json @@ -481,6 +481,36 @@ "descriptive_name": { "type": "string" }, + "dns_threat_detector": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded_networks": { + "type": "array", + "items": { + "type": "string" + } + }, + "labels": { + "type": "object" + }, + "location": { + "type": "string" + }, + "name": { + "type": "string" + }, + "threat_detector_provider": { + "type": "string", + "enum": [ + "INFOBLOX" + ] + } + } + }, "org_policies": { "type": "object", "additionalProperties": false, diff --git a/fast/stages/0-org-setup/schemas/project.schema.md b/fast/stages/0-org-setup/schemas/project.schema.md index d2cceab50..eadce0d3c 100644 --- a/fast/stages/0-org-setup/schemas/project.schema.md +++ b/fast/stages/0-org-setup/schemas/project.schema.md @@ -149,6 +149,16 @@ - items: *string* - **name**: *string* - **descriptive_name**: *string* +- **dns_threat_detector**: *object* +
*additional properties: false* + - **enabled**: *boolean* + - **excluded_networks**: *array* + - items: *string* + - **labels**: *object* + - **location**: *string* + - **name**: *string* + - **threat_detector_provider**: *string* +
*enum: ['INFOBLOX']* - **org_policies**: *object*
*additional properties: false* - **`^[a-z]+\.`**: *object* diff --git a/fast/stages/2-networking/schemas/project.schema.json b/fast/stages/2-networking/schemas/project.schema.json index 89b0aeaba..4f3b15610 100644 --- a/fast/stages/2-networking/schemas/project.schema.json +++ b/fast/stages/2-networking/schemas/project.schema.json @@ -481,6 +481,36 @@ "descriptive_name": { "type": "string" }, + "dns_threat_detector": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded_networks": { + "type": "array", + "items": { + "type": "string" + } + }, + "labels": { + "type": "object" + }, + "location": { + "type": "string" + }, + "name": { + "type": "string" + }, + "threat_detector_provider": { + "type": "string", + "enum": [ + "INFOBLOX" + ] + } + } + }, "org_policies": { "type": "object", "additionalProperties": false, diff --git a/fast/stages/2-networking/schemas/project.schema.md b/fast/stages/2-networking/schemas/project.schema.md index d2cceab50..eadce0d3c 100644 --- a/fast/stages/2-networking/schemas/project.schema.md +++ b/fast/stages/2-networking/schemas/project.schema.md @@ -149,6 +149,16 @@ - items: *string* - **name**: *string* - **descriptive_name**: *string* +- **dns_threat_detector**: *object* +
*additional properties: false* + - **enabled**: *boolean* + - **excluded_networks**: *array* + - items: *string* + - **labels**: *object* + - **location**: *string* + - **name**: *string* + - **threat_detector_provider**: *string* +
*enum: ['INFOBLOX']* - **org_policies**: *object*
*additional properties: false* - **`^[a-z]+\.`**: *object* diff --git a/fast/stages/2-project-factory/schemas/project.schema.json b/fast/stages/2-project-factory/schemas/project.schema.json index 89b0aeaba..4f3b15610 100644 --- a/fast/stages/2-project-factory/schemas/project.schema.json +++ b/fast/stages/2-project-factory/schemas/project.schema.json @@ -481,6 +481,36 @@ "descriptive_name": { "type": "string" }, + "dns_threat_detector": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded_networks": { + "type": "array", + "items": { + "type": "string" + } + }, + "labels": { + "type": "object" + }, + "location": { + "type": "string" + }, + "name": { + "type": "string" + }, + "threat_detector_provider": { + "type": "string", + "enum": [ + "INFOBLOX" + ] + } + } + }, "org_policies": { "type": "object", "additionalProperties": false, diff --git a/fast/stages/2-project-factory/schemas/project.schema.md b/fast/stages/2-project-factory/schemas/project.schema.md index d2cceab50..eadce0d3c 100644 --- a/fast/stages/2-project-factory/schemas/project.schema.md +++ b/fast/stages/2-project-factory/schemas/project.schema.md @@ -149,6 +149,16 @@ - items: *string* - **name**: *string* - **descriptive_name**: *string* +- **dns_threat_detector**: *object* +
*additional properties: false* + - **enabled**: *boolean* + - **excluded_networks**: *array* + - items: *string* + - **labels**: *object* + - **location**: *string* + - **name**: *string* + - **threat_detector_provider**: *string* +
*enum: ['INFOBLOX']* - **org_policies**: *object*
*additional properties: false* - **`^[a-z]+\.`**: *object* diff --git a/fast/stages/2-security/schemas/project.schema.json b/fast/stages/2-security/schemas/project.schema.json index 89b0aeaba..4f3b15610 100644 --- a/fast/stages/2-security/schemas/project.schema.json +++ b/fast/stages/2-security/schemas/project.schema.json @@ -481,6 +481,36 @@ "descriptive_name": { "type": "string" }, + "dns_threat_detector": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded_networks": { + "type": "array", + "items": { + "type": "string" + } + }, + "labels": { + "type": "object" + }, + "location": { + "type": "string" + }, + "name": { + "type": "string" + }, + "threat_detector_provider": { + "type": "string", + "enum": [ + "INFOBLOX" + ] + } + } + }, "org_policies": { "type": "object", "additionalProperties": false, diff --git a/fast/stages/2-security/schemas/project.schema.md b/fast/stages/2-security/schemas/project.schema.md index d2cceab50..eadce0d3c 100644 --- a/fast/stages/2-security/schemas/project.schema.md +++ b/fast/stages/2-security/schemas/project.schema.md @@ -149,6 +149,16 @@ - items: *string* - **name**: *string* - **descriptive_name**: *string* +- **dns_threat_detector**: *object* +
*additional properties: false* + - **enabled**: *boolean* + - **excluded_networks**: *array* + - items: *string* + - **labels**: *object* + - **location**: *string* + - **name**: *string* + - **threat_detector_provider**: *string* +
*enum: ['INFOBLOX']* - **org_policies**: *object*
*additional properties: false* - **`^[a-z]+\.`**: *object* diff --git a/modules/project-factory/README.md b/modules/project-factory/README.md index 94512fd73..da99713d1 100644 --- a/modules/project-factory/README.md +++ b/modules/project-factory/README.md @@ -622,6 +622,8 @@ labels: app: app-0 team: team-a parent: $folder_ids:team-a/app-0 +dns_threat_detector: + enabled: true iam_by_principals: $iam_principals:service_accounts/dev-ta-app0-be/app-0-be: - roles/storage.objectViewer @@ -864,6 +866,7 @@ compute.disableSerialPortAccess: | [projects-bigquery.tf](./projects-bigquery.tf) | None | bigquery-dataset | | | [projects-buckets.tf](./projects-buckets.tf) | None | gcs | | | [projects-defaults.tf](./projects-defaults.tf) | None | | | +| [projects-dns-armor.tf](./projects-dns-armor.tf) | None | | google_network_security_dns_threat_detector | | [projects-kms.tf](./projects-kms.tf) | None | kms | | | [projects-log-buckets.tf](./projects-log-buckets.tf) | None | logging-bucket | | | [projects-pubsub.tf](./projects-pubsub.tf) | None | pubsub | | @@ -878,11 +881,11 @@ compute.disableSerialPortAccess: | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [factories_config](variables.tf#L165) | Path to folder with YAML resource description data files. | object({…}) | ✓ | | +| [factories_config](variables.tf#L166) | Path to folder with YAML resource description data files. | object({…}) | ✓ | | | [context](variables.tf#L17) | Context-specific interpolations. | object({…}) | | {} | -| [data_defaults](variables.tf#L42) | Optional default values used when corresponding project or folder data from files are missing. | object({…}) | | {} | -| [data_merges](variables.tf#L107) | Optional values that will be merged with corresponding data from files. Combines with `data_defaults`, file data, and `data_overrides`. | object({…}) | | {} | -| [data_overrides](variables.tf#L126) | Optional values that override corresponding data from files. Takes precedence over file data and `data_defaults`. | object({…}) | | {} | +| [data_defaults](variables.tf#L43) | Optional default values used when corresponding project or folder data from files are missing. | object({…}) | | {} | +| [data_merges](variables.tf#L108) | Optional values that will be merged with corresponding data from files. Combines with `data_defaults`, file data, and `data_overrides`. | object({…}) | | {} | +| [data_overrides](variables.tf#L127) | Optional values that override corresponding data from files. Takes precedence over file data and `data_defaults`. | object({…}) | | {} | | [folders](variables-folders.tf#L17) | Folders data merged with factory data. | map(object({…})) | | {} | | [notification_channels](variables-billing.tf#L17) | Notification channels used by budget alerts. | map(object({…})) | | {} | | [projects](variables-projects.tf#L17) | Projects data merged with factory data. | map(object({…})) | | {} | diff --git a/modules/project-factory/projects-dns-armor.tf b/modules/project-factory/projects-dns-armor.tf new file mode 100644 index 000000000..7646054f5 --- /dev/null +++ b/modules/project-factory/projects-dns-armor.tf @@ -0,0 +1,40 @@ +/** + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +resource "google_network_security_dns_threat_detector" "dns_threat_detector" { + provider = google-beta + for_each = { + for k, v in local.projects_input : k => v + if try(v.dns_threat_detector.enabled, false) + } + project = module.projects[each.key].project_id + name = ( + try(each.value.dns_threat_detector.name, null) != null + ? each.value.dns_threat_detector.name + : ( + each.value.prefix == null + ? each.value.name + : "${each.value.prefix}-${each.value.name}" + ) + ) + excluded_networks = [ + for s in try(each.value.dns_threat_detector.excluded_networks, []) : + lookup(local.ctx.networks, s, s) + ] + threat_detector_provider = try(each.value.dns_threat_detector.threat_detector_provider, null) + labels = try(each.value.dns_threat_detector.labels, {}) + location = try(each.value.dns_threat_detector.location, null) +} diff --git a/modules/project-factory/schemas/project.schema.json b/modules/project-factory/schemas/project.schema.json index 89b0aeaba..4f3b15610 100644 --- a/modules/project-factory/schemas/project.schema.json +++ b/modules/project-factory/schemas/project.schema.json @@ -481,6 +481,36 @@ "descriptive_name": { "type": "string" }, + "dns_threat_detector": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded_networks": { + "type": "array", + "items": { + "type": "string" + } + }, + "labels": { + "type": "object" + }, + "location": { + "type": "string" + }, + "name": { + "type": "string" + }, + "threat_detector_provider": { + "type": "string", + "enum": [ + "INFOBLOX" + ] + } + } + }, "org_policies": { "type": "object", "additionalProperties": false, diff --git a/modules/project-factory/schemas/project.schema.md b/modules/project-factory/schemas/project.schema.md index d2cceab50..eadce0d3c 100644 --- a/modules/project-factory/schemas/project.schema.md +++ b/modules/project-factory/schemas/project.schema.md @@ -149,6 +149,16 @@ - items: *string* - **name**: *string* - **descriptive_name**: *string* +- **dns_threat_detector**: *object* +
*additional properties: false* + - **enabled**: *boolean* + - **excluded_networks**: *array* + - items: *string* + - **labels**: *object* + - **location**: *string* + - **name**: *string* + - **threat_detector_provider**: *string* +
*enum: ['INFOBLOX']* - **org_policies**: *object*
*additional properties: false* - **`^[a-z]+\.`**: *object* diff --git a/modules/project-factory/variables-projects.tf b/modules/project-factory/variables-projects.tf index ee613f7a7..5e228fa5c 100644 --- a/modules/project-factory/variables-projects.tf +++ b/modules/project-factory/variables-projects.tf @@ -231,6 +231,14 @@ variable "projects" { friendly_name = optional(string) location = optional(string) })), {}) + dns_threat_detector = optional(object({ + enabled = optional(bool, false) + excluded_networks = optional(list(string), []) + labels = optional(map(string), {}) + location = optional(string) + name = optional(string) + threat_detector_provider = optional(string) + }), {}) factories_config = optional(object({ custom_roles = optional(string) observability = optional(string) diff --git a/modules/project-factory/variables.tf b/modules/project-factory/variables.tf index 3c4aa3919..09badec0b 100644 --- a/modules/project-factory/variables.tf +++ b/modules/project-factory/variables.tf @@ -25,6 +25,7 @@ variable "context" { kms_keys = optional(map(string), {}) locations = optional(map(string), {}) log_buckets = optional(map(string), {}) + networks = optional(map(string), {}) notification_channels = optional(map(string), {}) project_ids = optional(map(string), {}) project_numbers = optional(map(string), {}) diff --git a/tests/modules/project_factory/examples/example.yaml b/tests/modules/project_factory/examples/example.yaml index 67b46798f..d01b9e74a 100644 --- a/tests/modules/project_factory/examples/example.yaml +++ b/tests/modules/project_factory/examples/example.yaml @@ -972,6 +972,7 @@ counts: google_kms_crypto_key_iam_member: 2 google_kms_key_ring: 1 google_monitoring_notification_channel: 1 + google_network_security_dns_threat_detector: 1 google_org_policy_policy: 3 google_privileged_access_manager_entitlement: 2 google_project: 4 @@ -995,5 +996,5 @@ counts: google_tags_tag_value: 2 google_tags_tag_value_iam_binding: 1 modules: 34 - resources: 118 + resources: 119 terraform_data: 2