From 083f497306c4fd254a33fcb6b0b639255002ce70 Mon Sep 17 00:00:00 2001 From: Roger Ng Date: Mon, 30 Mar 2026 17:33:10 +0000 Subject: [PATCH 1/4] Add RFC3339 format validation to date-related input variables --- deployment/modules/gcp/gce/tesseract/variables.tf | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/deployment/modules/gcp/gce/tesseract/variables.tf b/deployment/modules/gcp/gce/tesseract/variables.tf index ad830aa5..18b12d35 100644 --- a/deployment/modules/gcp/gce/tesseract/variables.tf +++ b/deployment/modules/gcp/gce/tesseract/variables.tf @@ -78,12 +78,22 @@ variable "not_after_start" { description = "Start of the range of acceptable NotAfter values, inclusive. Leaving this empty implies no lower bound to the range. RFC3339 UTC format, e.g: 2024-01-02T15:04:05Z." default = "" type = string + + validation { + condition = var.not_after_start == "" || can(regex("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$", var.not_after_start)) + error_message = "The not_after_start must be in RFC3339 UTC format (e.g., 2024-01-02T15:04:05Z) or empty." + } } variable "not_after_limit" { description = "Cut off point of notAfter dates - only notAfter dates strictly *before* notAfterLimit will be accepted. Leaving this empty means no upper bound on the accepted range. RFC3339 UTC format, e.g: 2024-01-02T15:04:05Z." default = "" type = string + + validation { + condition = var.not_after_limit == "" || can(regex("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$", var.not_after_limit)) + error_message = "The not_after_limit must be in RFC3339 UTC format (e.g., 2024-01-02T15:04:05Z) or empty." + } } variable "trace_fraction" { From 9696d992860102c84a381d98221dde0edf22ad0d Mon Sep 17 00:00:00 2001 From: Roger Ng Date: Mon, 30 Mar 2026 19:54:47 +0000 Subject: [PATCH 2/4] Add validation for not_after_start and not_after_limit ordering and include corresponding test suite --- .../gcp/gce/tesseract/.terraform.lock.hcl | 24 ++++ .../tests/variables_validation.tftest.hcl | 106 ++++++++++++++++++ .../modules/gcp/gce/tesseract/variables.tf | 5 + 3 files changed, 135 insertions(+) create mode 100644 deployment/modules/gcp/gce/tesseract/.terraform.lock.hcl create mode 100644 deployment/modules/gcp/gce/tesseract/tests/variables_validation.tftest.hcl diff --git a/deployment/modules/gcp/gce/tesseract/.terraform.lock.hcl b/deployment/modules/gcp/gce/tesseract/.terraform.lock.hcl new file mode 100644 index 00000000..4db01d7d --- /dev/null +++ b/deployment/modules/gcp/gce/tesseract/.terraform.lock.hcl @@ -0,0 +1,24 @@ +# This file is maintained automatically by "tofu init". +# Manual edits may be lost in future updates. + +provider "registry.opentofu.org/hashicorp/google" { + version = "7.25.0" + constraints = ">= 7.0.0" + hashes = [ + "h1:cUA+uZ1iJtIn09XqwYNaUoSDHrgOZAxwyqW3avXIKaE=", + "zh:00560362d225142cf3ee0a38c1d9ebf2c2982551c9bedc7bd69e7e5c07797e92", + "zh:1312c205b9658effcbaa5828033ad724a1f829c82be0fdd29dde689023a47f44", + "zh:29c649ba246a0e0e8565c6a16d6aa28a56e9bac8860054d15f58d8b5983d5c08", + "zh:3ae5c0da5e4447cf89ea7e7dcf7ed12154747734e11c00e711c861248347d182", + "zh:41cdb10f48f1f2c5ec2afc0e8ba10fabc79dca02725e898f445dc3d9e858a673", + "zh:58fd365605f9b9b4e57ea46a4c05757047ba69a56a4a66adebd90355b2da1155", + "zh:6ac5988d027cbdcdab866e7114a7285de91bbff58e4594241166c105357a6c83", + "zh:7ec06c956a9e4381e1430e79558bb60e0fce907942a404bb03165deb2ab334cd", + "zh:805a7a06cf06f14151be1769ac729d254fa754622689b223dffac29b479a9035", + "zh:b1e10cb0bad9111ab0f1646adc49cc11bdafa31e956ef305b67ebd1a52590787", + "zh:c20039282e751c382f03d7f7133bd4354bbce947978c029a801c1261d44f5a07", + "zh:d010fdf618333ea86fda9be2f5369fcf7adbfe7b8d9f1f02d7392a2cfc9f516e", + "zh:f8ae6e509745166456ee3250b100b95648e7769f383e4f63df642cf12625f788", + "zh:fc9971298c996df21c4a43c9b39fda6457d3c744d9605a78f514c842d4b10afc", + ] +} diff --git a/deployment/modules/gcp/gce/tesseract/tests/variables_validation.tftest.hcl b/deployment/modules/gcp/gce/tesseract/tests/variables_validation.tftest.hcl new file mode 100644 index 00000000..12add8b6 --- /dev/null +++ b/deployment/modules/gcp/gce/tesseract/tests/variables_validation.tftest.hcl @@ -0,0 +1,106 @@ +provider "google" { + project = "dummy-project" +} + +variables { + project_id = "dummy-project" + base_name = "dummy-base" + origin = "dummy-origin" + location = "us-central1" + env = "dev" + docker_repo = "gcr.io/dummy" + server_docker_image = "dummy-image:latest" + bucket = "dummy-bucket" + log_spanner_instance = "dummy-instance" + log_spanner_db = "dummy-db" + antispam_spanner_db = "dummy-antispam-db" + additional_signer_private_key_secret_names = [] + signer_public_key_secret_name = "projects/dummy/secrets/pub/versions/1" + signer_private_key_secret_name = "projects/dummy/secrets/priv/versions/1" +} + +run "valid_dates" { + command = plan + + variables { + not_after_start = "2024-01-01T00:00:00Z" + not_after_limit = "2024-01-02T00:00:00Z" + } +} + +run "invalid_dates_not_after_start_format" { + command = plan + + variables { + not_after_start = "2024-01-01" + } + + expect_failures = [ + var.not_after_start, + ] +} + +run "invalid_dates_not_after_limit_format" { + command = plan + + variables { + not_after_limit = "2024-01-02T00:00:00Z]" + } + + expect_failures = [ + var.not_after_limit, + ] +} + +run "invalid_dates_start_after_limit" { + command = plan + + variables { + not_after_start = "2024-01-02T00:00:00Z" + not_after_limit = "2024-01-01T00:00:00Z" + } + + expect_failures = [ + var.not_after_limit, + ] +} + +run "invalid_dates_equal" { + command = plan + + variables { + not_after_start = "2024-01-01T00:00:00Z" + not_after_limit = "2024-01-01T00:00:00Z" + } + + expect_failures = [ + var.not_after_limit, + ] +} + +run "valid_dates_start_empty" { + command = plan + + variables { + not_after_start = "" + not_after_limit = "2024-01-02T00:00:00Z" + } +} + +run "valid_dates_limit_empty" { + command = plan + + variables { + not_after_start = "2024-01-01T00:00:00Z" + not_after_limit = "" + } +} + +run "valid_dates_both_empty" { + command = plan + + variables { + not_after_start = "" + not_after_limit = "" + } +} diff --git a/deployment/modules/gcp/gce/tesseract/variables.tf b/deployment/modules/gcp/gce/tesseract/variables.tf index 18b12d35..9e911423 100644 --- a/deployment/modules/gcp/gce/tesseract/variables.tf +++ b/deployment/modules/gcp/gce/tesseract/variables.tf @@ -94,6 +94,11 @@ variable "not_after_limit" { condition = var.not_after_limit == "" || can(regex("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$", var.not_after_limit)) error_message = "The not_after_limit must be in RFC3339 UTC format (e.g., 2024-01-02T15:04:05Z) or empty." } + + validation { + condition = var.not_after_start == "" || var.not_after_limit == "" || timecmp(var.not_after_start, var.not_after_limit) < 0 + error_message = "The not_after_start must be strictly before not_after_limit." + } } variable "trace_fraction" { From 2b68d6ea014c8379e2c75c8536d3cfc6453749a7 Mon Sep 17 00:00:00 2001 From: Roger Ng Date: Tue, 31 Mar 2026 15:25:18 +0000 Subject: [PATCH 3/4] Address comment --- deployment/modules/gcp/gce/tesseract/variables.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deployment/modules/gcp/gce/tesseract/variables.tf b/deployment/modules/gcp/gce/tesseract/variables.tf index 9e911423..3e314f1d 100644 --- a/deployment/modules/gcp/gce/tesseract/variables.tf +++ b/deployment/modules/gcp/gce/tesseract/variables.tf @@ -81,7 +81,7 @@ variable "not_after_start" { validation { condition = var.not_after_start == "" || can(regex("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$", var.not_after_start)) - error_message = "The not_after_start must be in RFC3339 UTC format (e.g., 2024-01-02T15:04:05Z) or empty." + error_message = "not_after_start must be in RFC3339 UTC format (e.g., 2024-01-02T15:04:05Z) or empty." } } @@ -92,12 +92,12 @@ variable "not_after_limit" { validation { condition = var.not_after_limit == "" || can(regex("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$", var.not_after_limit)) - error_message = "The not_after_limit must be in RFC3339 UTC format (e.g., 2024-01-02T15:04:05Z) or empty." + error_message = "not_after_limit must be in RFC3339 UTC format (e.g., 2024-01-02T15:04:05Z) or empty." } validation { condition = var.not_after_start == "" || var.not_after_limit == "" || timecmp(var.not_after_start, var.not_after_limit) < 0 - error_message = "The not_after_start must be strictly before not_after_limit." + error_message = "not_after_start must be strictly before not_after_limit." } } From b1e6fb6ba0b267d48da86b64492e2c6f1e9555f0 Mon Sep 17 00:00:00 2001 From: Roger Ng Date: Tue, 31 Mar 2026 15:31:32 +0000 Subject: [PATCH 4/4] Address comment --- .../tests/variables_validation.tftest.hcl | 44 +++++++++++++++++++ .../modules/gcp/gce/tesseract/variables.tf | 12 ++--- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/deployment/modules/gcp/gce/tesseract/tests/variables_validation.tftest.hcl b/deployment/modules/gcp/gce/tesseract/tests/variables_validation.tftest.hcl index 12add8b6..655935e6 100644 --- a/deployment/modules/gcp/gce/tesseract/tests/variables_validation.tftest.hcl +++ b/deployment/modules/gcp/gce/tesseract/tests/variables_validation.tftest.hcl @@ -104,3 +104,47 @@ run "valid_dates_both_empty" { not_after_limit = "" } } + +run "valid_dates_fractional_seconds" { + command = plan + + variables { + not_after_start = "2024-01-01T00:00:00.123Z" + not_after_limit = "2024-01-02T00:00:00.456Z" + } +} + +run "valid_dates_offsets" { + command = plan + + variables { + not_after_start = "2024-01-01T00:00:00+01:00" + not_after_limit = "2024-01-02T00:00:00-05:00" + } +} + +run "invalid_dates_not_after_start_lowercase" { + command = plan + + variables { + not_after_start = "2024-01-01t00:00:00z" + not_after_limit = "2024-01-02T00:00:00Z" + } + + expect_failures = [ + var.not_after_start, + ] +} + +run "invalid_dates_not_after_limit_lowercase" { + command = plan + + variables { + not_after_start = "2024-01-01T00:00:00Z" + not_after_limit = "2024-01-02t00:00:00z" + } + + expect_failures = [ + var.not_after_limit, + ] +} diff --git a/deployment/modules/gcp/gce/tesseract/variables.tf b/deployment/modules/gcp/gce/tesseract/variables.tf index 3e314f1d..ab0d0e9c 100644 --- a/deployment/modules/gcp/gce/tesseract/variables.tf +++ b/deployment/modules/gcp/gce/tesseract/variables.tf @@ -75,24 +75,24 @@ variable "signer_private_key_secret_name" { } variable "not_after_start" { - description = "Start of the range of acceptable NotAfter values, inclusive. Leaving this empty implies no lower bound to the range. RFC3339 UTC format, e.g: 2024-01-02T15:04:05Z." + description = "Start of the range of acceptable NotAfter values, inclusive. Leaving this empty implies no lower bound to the range. RFC3339 format, e.g: 2024-01-02T15:04:05Z." default = "" type = string validation { - condition = var.not_after_start == "" || can(regex("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$", var.not_after_start)) - error_message = "not_after_start must be in RFC3339 UTC format (e.g., 2024-01-02T15:04:05Z) or empty." + condition = var.not_after_start == "" || can(regex("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})$", var.not_after_start)) + error_message = "not_after_start must be in RFC3339 format (e.g., 2024-01-02T15:04:05Z, 2024-01-02T15:04:05.123Z, or 2024-01-02T15:04:05+01:00) or empty." } } variable "not_after_limit" { - description = "Cut off point of notAfter dates - only notAfter dates strictly *before* notAfterLimit will be accepted. Leaving this empty means no upper bound on the accepted range. RFC3339 UTC format, e.g: 2024-01-02T15:04:05Z." + description = "Cut off point of notAfter dates - only notAfter dates strictly *before* notAfterLimit will be accepted. Leaving this empty means no upper bound on the accepted range. RFC3339 format, e.g: 2024-01-02T15:04:05Z." default = "" type = string validation { - condition = var.not_after_limit == "" || can(regex("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$", var.not_after_limit)) - error_message = "not_after_limit must be in RFC3339 UTC format (e.g., 2024-01-02T15:04:05Z) or empty." + condition = var.not_after_limit == "" || can(regex("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})$", var.not_after_limit)) + error_message = "not_after_limit must be in RFC3339 format (e.g., 2024-01-02T15:04:05Z, 2024-01-02T15:04:05.123Z, or 2024-01-02T15:04:05+01:00) or empty." } validation {