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 <ludomagno@google.com>
Co-authored-by: Simone Ruffilli <sruffilli@google.com>
This commit is contained in:
lopezvit
2026-03-31 11:54:54 +03:00
committed by GitHub
parent fcb3b7908d
commit 956ada4ddd
14 changed files with 321 additions and 0 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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": [

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"
}

View File

@@ -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

View File

@@ -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: