diff --git a/infrastructure/gcp/cloud-dns/.terraform.lock.hcl b/infrastructure/gcp/cloud-dns/.terraform.lock.hcl
new file mode 100644
index 00000000..a947a847
--- /dev/null
+++ b/infrastructure/gcp/cloud-dns/.terraform.lock.hcl
@@ -0,0 +1,20 @@
+# This file is maintained automatically by "tofu init".
+# Manual edits may be lost in future updates.
+
+provider "registry.opentofu.org/hashicorp/google" {
+ version = "6.50.0"
+ constraints = ">= 5.0.0, < 7.0.0"
+ hashes = [
+ "h1:MAAe4zFFdqS9M5rpmJK/vKgdb6ZMD/s/0Xd97yTDipA=",
+ "zh:1d4695f807d998f11fcdcfa174766287b82a8093513af857bcdad2d81c642480",
+ "zh:3173ac5df0294624d113812e49e2a55714aff7db617488168cecdf4168df9e29",
+ "zh:34d2b3d44c23bd6354fc4ab5917b302872ea1ab8de107034567f955b1717fa5b",
+ "zh:3a77f3cc2f3664cd5aaeeef4d044e6ec1695a079588fffec3ca03953664e5f04",
+ "zh:6b444e4b629ea8dc8cb112a39dde098dc5584d26d6de4177558f556a9a226696",
+ "zh:96545c8cd4d3a57069c5d1799eab5aedd887e16d98b5559a195f6d2c2d9bc674",
+ "zh:ba464caafde95ee16671d6b5ec90f053ed77a9d06c567456db6efd9160fa3165",
+ "zh:d876938e5b0d3f57a984d9be72467995f87fef6569968623415dc51d9f54d30b",
+ "zh:dfd908d873e314ab807d0abc9cfd42d2611cd06dc1b9ec719ebdbb738e8e68d6",
+ "zh:f9f16819a7738d564afd45fd169ba61004ec4e4e7089d2a4950cb8895be1fe1f",
+ ]
+}
diff --git a/infrastructure/gcp/cloud-dns/README.md b/infrastructure/gcp/cloud-dns/README.md
index 5dc4c9d1..8320ae47 100644
--- a/infrastructure/gcp/cloud-dns/README.md
+++ b/infrastructure/gcp/cloud-dns/README.md
@@ -49,7 +49,7 @@ resource "example_resource" "this" {
| Name | Version |
|------|---------|
-| [google](#provider\_google) | >= 5.0, < 7.0 |
+| [google](#provider\_google) | 6.50.0 |
## Resources
@@ -61,6 +61,7 @@ resource "example_resource" "this" {
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
+| [dnssec\_enabled](#input\_dnssec\_enabled) | Enable DNSSEC for the zone. Only applies to public zones; signing is inert until the DS record is published at the domain registrar. | `bool` | `true` | no |
| [domain\_name](#input\_domain\_name) | The domain name for the DNS zone (without trailing dot, e.g. example.com) | `string` | n/a | yes |
| [private\_zone\_networks](#input\_private\_zone\_networks) | VPC network self-links for private zones | `list(string)` | `[]` | no |
| [project\_id](#input\_project\_id) | The GCP project ID | `string` | n/a | yes |
diff --git a/infrastructure/gcp/cloud-dns/main.tf b/infrastructure/gcp/cloud-dns/main.tf
index 630b6a70..c6ec6880 100644
--- a/infrastructure/gcp/cloud-dns/main.tf
+++ b/infrastructure/gcp/cloud-dns/main.tf
@@ -8,6 +8,13 @@ resource "google_dns_managed_zone" "zone" {
dns_name = "${var.domain_name}."
visibility = var.visibility
+ dynamic "dnssec_config" {
+ for_each = var.visibility == "public" && var.dnssec_enabled ? [1] : []
+ content {
+ state = "on"
+ }
+ }
+
dynamic "private_visibility_config" {
for_each = var.visibility == "private" ? [1] : []
content {
diff --git a/infrastructure/gcp/cloud-dns/tests/cloud_dns.tftest.hcl b/infrastructure/gcp/cloud-dns/tests/cloud_dns.tftest.hcl
index d8dabbfd..d4a3ba85 100644
--- a/infrastructure/gcp/cloud-dns/tests/cloud_dns.tftest.hcl
+++ b/infrastructure/gcp/cloud-dns/tests/cloud_dns.tftest.hcl
@@ -74,3 +74,39 @@ run "labels_applied_from_tags" {
error_message = "Labels should be applied from tags variable"
}
}
+
+run "dnssec_enabled_by_default_on_public_zone" {
+ command = plan
+
+ assert {
+ condition = google_dns_managed_zone.zone.dnssec_config[0].state == "on"
+ error_message = "DNSSEC should be on by default for public zones"
+ }
+}
+
+run "dnssec_can_be_disabled" {
+ command = plan
+
+ variables {
+ dnssec_enabled = false
+ }
+
+ assert {
+ condition = length(google_dns_managed_zone.zone.dnssec_config) == 0
+ error_message = "DNSSEC should not be configured when dnssec_enabled is false"
+ }
+}
+
+run "dnssec_ignored_on_private_zone" {
+ command = plan
+
+ variables {
+ visibility = "private"
+ dnssec_enabled = true
+ }
+
+ assert {
+ condition = length(google_dns_managed_zone.zone.dnssec_config) == 0
+ error_message = "DNSSEC should not be configured on private zones even when dnssec_enabled is true"
+ }
+}
diff --git a/infrastructure/gcp/cloud-dns/variables.tf b/infrastructure/gcp/cloud-dns/variables.tf
index 5a0b9b9d..57857d60 100644
--- a/infrastructure/gcp/cloud-dns/variables.tf
+++ b/infrastructure/gcp/cloud-dns/variables.tf
@@ -26,6 +26,12 @@ variable "private_zone_networks" {
default = []
}
+variable "dnssec_enabled" {
+ type = bool
+ description = "Enable DNSSEC for the zone. Only applies to public zones; signing is inert until the DS record is published at the domain registrar."
+ default = true
+}
+
variable "tags" {
type = map(string)
description = "A mapping of labels to assign to the DNS managed zone"