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..655935e6 --- /dev/null +++ b/deployment/modules/gcp/gce/tesseract/tests/variables_validation.tftest.hcl @@ -0,0 +1,150 @@ +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 = "" + } +} + +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 ad830aa5..ab0d0e9c 100644 --- a/deployment/modules/gcp/gce/tesseract/variables.tf +++ b/deployment/modules/gcp/gce/tesseract/variables.tf @@ -75,15 +75,30 @@ 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}(\\.\\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}(\\.\\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 { + condition = var.not_after_start == "" || var.not_after_limit == "" || timecmp(var.not_after_start, var.not_after_limit) < 0 + error_message = "not_after_start must be strictly before not_after_limit." + } } variable "trace_fraction" {