From 956ada4ddd2d24634f898c3d2e6c0f30d306a5ba Mon Sep 17 00:00:00 2001 From: lopezvit Date: Tue, 31 Mar 2026 11:54:54 +0300 Subject: [PATCH] feat(2-networking): add NS And DS record dynamically from sub-zones records (#3818) * feat(2-networking): add NS And DS record dynamically from sub-zones records * tfdoc * feat(test): 2-networking adding dns_delegations test with and without DSSEC --------- Co-authored-by: Ludovico Magnocavallo Co-authored-by: Simone Ruffilli --- fast/stages/2-networking/README.md | 63 +++++++++++++++++++ fast/stages/2-networking/factory-dns.tf | 37 +++++++++++ .../2-networking/schemas/dns.schema.json | 7 +++ .../stages/2-networking/schemas/dns.schema.md | 2 + .../data-testdns-delegation/defaults.yaml | 42 +++++++++++++ .../zones/net-core-0/pub-child-dnssec.yaml | 12 ++++ .../dns/zones/net-core-0/pub-child.yaml | 12 ++++ .../dns/zones/net-core-0/pub-parent.yaml | 15 +++++ .../projects/net-core-0.yaml | 20 ++++++ .../vpcs/core/.config.yaml | 10 +++ .../vpcs/core/subnets/core-default.yaml | 9 +++ .../s2_networking/dns_delegations.tfvars | 35 +++++++++++ .../stages/s2_networking/dns_delegations.yaml | 52 +++++++++++++++ tests/fast/stages/s2_networking/tftest.yaml | 5 ++ 14 files changed, 321 insertions(+) create mode 100644 tests/fast/stages/s2_networking/data-testdns-delegation/defaults.yaml create mode 100644 tests/fast/stages/s2_networking/data-testdns-delegation/dns/zones/net-core-0/pub-child-dnssec.yaml create mode 100644 tests/fast/stages/s2_networking/data-testdns-delegation/dns/zones/net-core-0/pub-child.yaml create mode 100644 tests/fast/stages/s2_networking/data-testdns-delegation/dns/zones/net-core-0/pub-parent.yaml create mode 100644 tests/fast/stages/s2_networking/data-testdns-delegation/projects/net-core-0.yaml create mode 100644 tests/fast/stages/s2_networking/data-testdns-delegation/vpcs/core/.config.yaml create mode 100644 tests/fast/stages/s2_networking/data-testdns-delegation/vpcs/core/subnets/core-default.yaml create mode 100644 tests/fast/stages/s2_networking/dns_delegations.tfvars create mode 100644 tests/fast/stages/s2_networking/dns_delegations.yaml diff --git a/fast/stages/2-networking/README.md b/fast/stages/2-networking/README.md index 4c29ab3a9..166979295 100644 --- a/fast/stages/2-networking/README.md +++ b/fast/stages/2-networking/README.md @@ -12,6 +12,7 @@ - [Networking projects](#networking-projects) - [VPCs](#vpcs) - [DNS](#dns) + - [Delegated Public Zones](#delegated-public-zones) - [Firewall Policies](#firewall-policies) - [Cloud NAT and Routers](#cloud-nat-and-routers) - [VPC Connectivity](#vpc-connectivity) @@ -200,6 +201,68 @@ In the default dataset, DNS is centralized in the `net-core-0` (hub) project. It The spoke VPCs have their own private zones for subdomains (e.g., `dev.test.`) and use the hub for all other DNS lookups. +#### Delegated Public Zones + +The factory also supports delegating public zones, which is useful for scenarios where a parent public zone is managed in one project, and you want to delegate a subdomain to a different project. + +To configure this, you first define the parent public zone. Then, for the delegated zone, you specify `delegation_config` pointing to the parent zone. This automatically creates the necessary `NS` (and `DS` records if you are using DNSSEC) records in the parent zone. + +Here's an example of how to set this up: + +```yaml +# Parent public zone (e.g., in dns/zones/net-core-0/pub-gcp-example-com.yaml) +# +# yaml-language-server: $schema=../../../../../schemas/dns.schema.json + +project_id: $project_ids:net-core-0 +domain: gcp.example.com. +public: + enable_logging: false + dnssec_config: + state: "on" + non_existence: "nsec3" + key_signing_key: + algorithm: "ecdsap256sha256" + key_length: 256 + zone_signing_key: + algorithm: "ecdsap256sha256" + key_length: 256 +recordsets: + "A localhost": + records: ["127.0.0.1"] +delegations: + - net-dev-0/pub-dev-gcp-example-com +``` + +```yaml +# Delegated child zone (e.g., in dns/zones/net-dev-0/pub-dev-gcp-example-com.yaml) +# +# yaml-language-server: $schema=../../../../../schemas/dns.schema.json + +project_id: $project_ids:net-dev-0 +domain: dev.gcp.example.com. +public: + enable_logging: false + dnssec_config: + state: "on" + non_existence: "nsec3" + key_signing_key: + algorithm: "ecdsap256sha256" + key_length: 256 + zone_signing_key: + algorithm: "ecdsap256sha256" + key_length: 256 +recordsets: + "A localhost": + records: ["127.0.0.1"] + +``` + +In this example: +- A public zone for `gcp.example.com.` is created in the `net-core-0` project. +- A separate public zone for `dev.gcp.example.com.` is created in the `net-dev-0` project. +- The `delegation_config` in the parent zone tells the factory to find the child zone named `pub-dev-gcp-example-com` in the `net-dev-0` project and create the necessary `NS` and `DS` records to make the delegation effective. + ### Firewall Policies This stage supports both VPC-level firewall rules and hierarchical firewall policies. diff --git a/fast/stages/2-networking/factory-dns.tf b/fast/stages/2-networking/factory-dns.tf index 60f0a6792..cc93c2f61 100644 --- a/fast/stages/2-networking/factory-dns.tf +++ b/fast/stages/2-networking/factory-dns.tf @@ -75,6 +75,30 @@ locals { } ) } + # DNS delegations: auto-create NS and DS records in parent zones + # from child zone outputs (name_servers, dns_keys). + # Grouped by parent zone key so a single module call per parent + # handles all its delegation recordsets. + dns_delegation_recordsets = { + for zone_key, zone_config in local.dns_zones : + zone_key => merge( + { + for child_key in zone_config.delegations : + "NS ${module.dns-zones[child_key].domain}" => { + records = module.dns-zones[child_key].name_servers + } + }, + { + for child_key in zone_config.delegations : + "DS ${module.dns-zones[child_key].domain}" => { + records = [module.dns-zones[child_key].dns_keys.key_signing_keys[0].ds_record] + } + if try(local.dns_zones[child_key].zone_config.public.dnssec_config.state, "off") == "on" + } + ) + if length(try(zone_config.delegations, [])) > 0 + } + # DNS response policies _dns_response_policies_files = try( fileset(local.paths.dns_response_policies, "**/*.yaml"), [] @@ -115,6 +139,19 @@ module "dns-zones" { depends_on = [module.vpc-factory] } +module "dns-delegations" { + source = "../../../modules/dns" + for_each = local.dns_delegation_recordsets + project_id = local.dns_zones[each.key].project_id + name = replace(each.key, "/", "-") + recordsets = each.value + context = { + project_ids = local.ctx_projects.project_ids + networks = local.ctx_vpcs.self_links + } + depends_on = [module.dns-zones] +} + module "dns-response-policies" { source = "../../../modules/dns-response-policy" for_each = local.dns_response_policies diff --git a/fast/stages/2-networking/schemas/dns.schema.json b/fast/stages/2-networking/schemas/dns.schema.json index 9aa054276..05485fd0f 100644 --- a/fast/stages/2-networking/schemas/dns.schema.json +++ b/fast/stages/2-networking/schemas/dns.schema.json @@ -38,6 +38,13 @@ }, "public": { "$ref": "#/$defs/public_zone" + }, + "delegations": { + "type": "array", + "description": "List of child zone keys (e.g. 'net-dev-0/pvt-dev-zone') whose NS and DS records are auto-created in this parent zone.", + "items": { + "type": "string" + } } }, "required": [ diff --git a/fast/stages/2-networking/schemas/dns.schema.md b/fast/stages/2-networking/schemas/dns.schema.md index e61d55d28..cfbde994e 100644 --- a/fast/stages/2-networking/schemas/dns.schema.md +++ b/fast/stages/2-networking/schemas/dns.schema.md @@ -16,6 +16,8 @@ - **peering**: *reference([peering_zone](#refs-peering_zone))* - **forwarding**: *reference([forwarding_zone](#refs-forwarding_zone))* - **public**: *reference([public_zone](#refs-public_zone))* +- **delegations**: *array* + - items: *string* ## Definitions diff --git a/tests/fast/stages/s2_networking/data-testdns-delegation/defaults.yaml b/tests/fast/stages/s2_networking/data-testdns-delegation/defaults.yaml new file mode 100644 index 000000000..3e9522c07 --- /dev/null +++ b/tests/fast/stages/s2_networking/data-testdns-delegation/defaults.yaml @@ -0,0 +1,42 @@ +# 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. + +# yaml-language-server: $schema=../../schemas/defaults.schema.json + +context: + cidr_ranges_sets: + healthchecks: + - 35.191.0.0/16 + - 130.211.0.0/22 + - 209.85.152.0/22 + - 209.85.204.0/22 + rfc1918: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + locations: + primary: europe-west1 + secondary: europe-west3 + iam_principals: {} +projects: + defaults: + locations: + storage: eu +vpcs: + auto_create_subnetworks: false + delete_default_route_on_create: true + mtu: 1500 +output_files: + storage_bucket: $storage_buckets:iac-0/iac-outputs + diff --git a/tests/fast/stages/s2_networking/data-testdns-delegation/dns/zones/net-core-0/pub-child-dnssec.yaml b/tests/fast/stages/s2_networking/data-testdns-delegation/dns/zones/net-core-0/pub-child-dnssec.yaml new file mode 100644 index 000000000..a8dbf8721 --- /dev/null +++ b/tests/fast/stages/s2_networking/data-testdns-delegation/dns/zones/net-core-0/pub-child-dnssec.yaml @@ -0,0 +1,12 @@ +# skip boilerplate check +--- +# start of document (---) avoids errors if the file only contains comments + +# yaml-language-server: $schema=../../../../../schemas/dns.schema.json + +project_id: $project_ids:net-core-0 +domain: child-dnssec.example.com. +public: + dnssec_config: + state: "on" + diff --git a/tests/fast/stages/s2_networking/data-testdns-delegation/dns/zones/net-core-0/pub-child.yaml b/tests/fast/stages/s2_networking/data-testdns-delegation/dns/zones/net-core-0/pub-child.yaml new file mode 100644 index 000000000..1491f31e4 --- /dev/null +++ b/tests/fast/stages/s2_networking/data-testdns-delegation/dns/zones/net-core-0/pub-child.yaml @@ -0,0 +1,12 @@ +# skip boilerplate check +--- +# start of document (---) avoids errors if the file only contains comments + +# yaml-language-server: $schema=../../../../../schemas/dns.schema.json + +project_id: $project_ids:net-core-0 +domain: child.example.com. +public: + dnssec_config: + state: "off" + diff --git a/tests/fast/stages/s2_networking/data-testdns-delegation/dns/zones/net-core-0/pub-parent.yaml b/tests/fast/stages/s2_networking/data-testdns-delegation/dns/zones/net-core-0/pub-parent.yaml new file mode 100644 index 000000000..945a35d4e --- /dev/null +++ b/tests/fast/stages/s2_networking/data-testdns-delegation/dns/zones/net-core-0/pub-parent.yaml @@ -0,0 +1,15 @@ +# skip boilerplate check +--- +# start of document (---) avoids errors if the file only contains comments + +# yaml-language-server: $schema=../../../../../schemas/dns.schema.json + +project_id: $project_ids:net-core-0 +domain: example.com. +public: + dnssec_config: + state: "off" +delegations: + - net-core-0/pub-child + - net-core-0/pub-child-dnssec + diff --git a/tests/fast/stages/s2_networking/data-testdns-delegation/projects/net-core-0.yaml b/tests/fast/stages/s2_networking/data-testdns-delegation/projects/net-core-0.yaml new file mode 100644 index 000000000..8400369ea --- /dev/null +++ b/tests/fast/stages/s2_networking/data-testdns-delegation/projects/net-core-0.yaml @@ -0,0 +1,20 @@ +# skip boilerplate check +--- +# start of document (---) avoids errors if the file only contains comments + +# yaml-language-server: $schema=../../../schemas/project.schema.json +name: prod-net-core-0 +parent: $folder_ids:networking +services: + - container.googleapis.com + - compute.googleapis.com + - dns.googleapis.com + - iap.googleapis.com + - networkmanagement.googleapis.com + - networksecurity.googleapis.com + - servicenetworking.googleapis.com + - stackdriver.googleapis.com + - vpcaccess.googleapis.com +shared_vpc_host_config: + enabled: true + diff --git a/tests/fast/stages/s2_networking/data-testdns-delegation/vpcs/core/.config.yaml b/tests/fast/stages/s2_networking/data-testdns-delegation/vpcs/core/.config.yaml new file mode 100644 index 000000000..4b5b7b1f4 --- /dev/null +++ b/tests/fast/stages/s2_networking/data-testdns-delegation/vpcs/core/.config.yaml @@ -0,0 +1,10 @@ +# skip boilerplate check +--- +# start of document (---) avoids errors if the file only contains comments + +# yaml-language-server: $schema=../../../../schemas/vpc.schema.json + +project_id: $project_ids:net-core-0 +name: core-0 +delete_default_routes_on_create: true + diff --git a/tests/fast/stages/s2_networking/data-testdns-delegation/vpcs/core/subnets/core-default.yaml b/tests/fast/stages/s2_networking/data-testdns-delegation/vpcs/core/subnets/core-default.yaml new file mode 100644 index 000000000..03c8066be --- /dev/null +++ b/tests/fast/stages/s2_networking/data-testdns-delegation/vpcs/core/subnets/core-default.yaml @@ -0,0 +1,9 @@ +# skip boilerplate check + +# yaml-language-server: $schema=../../../../../schemas/subnet.schema.json + +name: core-default +region: $locations:primary +ip_cidr_range: 10.71.0.0/24 +description: Default primary-region subnet for core + diff --git a/tests/fast/stages/s2_networking/dns_delegations.tfvars b/tests/fast/stages/s2_networking/dns_delegations.tfvars new file mode 100644 index 000000000..b48dcc5c2 --- /dev/null +++ b/tests/fast/stages/s2_networking/dns_delegations.tfvars @@ -0,0 +1,35 @@ +automation = { + outputs_bucket = "test" +} +billing_account = { + id = "000000-111111-222222" +} +factories_config = { + dataset = "./data-testdns-delegation" + paths = { + defaults = "defaults.yaml" + } +} +folder_ids = { + "networking" = "folders/12345678" + "networking/prod" = "folders/23456789" + "networking/dev" = "folders/34567890" +} +organization = { + domain = "fast.example.com" + id = 123456789012 + customer_id = "C00000000" +} +prefix = "fast" +service_accounts = { + "iac-0/iac-pf-rw" = "iac-pf-rw@test.iam.gserviceaccount.com" + "iac-0/iac-pf-ro" = "iac-pf-ro@test.iam.gserviceaccount.com" +} +storage_buckets = { + "iac-0/iac-outputs" = "test" +} +tag_values = { + "environment/development" = "tagValues/12345" + "environment/production" = "tagValues/12346" +} + diff --git a/tests/fast/stages/s2_networking/dns_delegations.yaml b/tests/fast/stages/s2_networking/dns_delegations.yaml new file mode 100644 index 000000000..94811b34d --- /dev/null +++ b/tests/fast/stages/s2_networking/dns_delegations.yaml @@ -0,0 +1,52 @@ +# 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. + +counts: + google_compute_network: 1 + google_compute_route: 3 + google_compute_shared_vpc_host_project: 1 + google_compute_subnetwork: 1 + google_dns_managed_zone: 4 + google_dns_record_set: 3 + google_project: 1 + google_project_iam_member: 7 + google_project_service: 9 + google_project_service_identity: 7 + google_storage_bucket_object: 2 + modules: 9 + resources: 42 + terraform_data: 2 +values: + module.dns-delegations["net-core-0/pub-parent"].google_dns_record_set.dns_record_set["NS child.example.com."]: + managed_zone: net-core-0-pub-parent + name: child.example.com. + project: fast-prod-net-core-0 + routing_policy: [] + ttl: 300 + type: NS + module.dns-delegations["net-core-0/pub-parent"].google_dns_record_set.dns_record_set["NS child-dnssec.example.com."]: + managed_zone: net-core-0-pub-parent + name: child-dnssec.example.com. + project: fast-prod-net-core-0 + routing_policy: [] + ttl: 300 + type: NS + module.dns-delegations["net-core-0/pub-parent"].google_dns_record_set.dns_record_set["DS child-dnssec.example.com."]: + managed_zone: net-core-0-pub-parent + name: child-dnssec.example.com. + project: fast-prod-net-core-0 + routing_policy: [] + ttl: 300 + type: DS + diff --git a/tests/fast/stages/s2_networking/tftest.yaml b/tests/fast/stages/s2_networking/tftest.yaml index 28e9fae4a..9b1a487cf 100644 --- a/tests/fast/stages/s2_networking/tftest.yaml +++ b/tests/fast/stages/s2_networking/tftest.yaml @@ -17,6 +17,11 @@ module: fast/stages/2-networking tests: # peering is renamed to simple so as to trigger tflint simple: + dns_delegations: + inventory: + - dns_delegations.yaml + extra_dirs: + - ../../../tests/fast/stages/s2_networking/data-testdns-delegation ncc: nva: vlan_attachments: