diff --git a/cmd/gcp/Dockerfile b/cmd/gcp/Dockerfile index f94f7d8c..7a696762 100644 --- a/cmd/gcp/Dockerfile +++ b/cmd/gcp/Dockerfile @@ -21,4 +21,5 @@ RUN go build -o bin/sctfe-gcp ./cmd/gcp FROM alpine:3.20.2@sha256:0a4eaa0eecf5f8c050e5bba433f58c052be7587ee8af3e8b3910ef9ab5fbe9f5 COPY --from=builder /build/bin/sctfe-gcp /bin/sctfe-gcp + ENTRYPOINT ["/bin/sctfe-gcp"] diff --git a/cmd/gcp/ci/Dockerfile b/cmd/gcp/ci/Dockerfile new file mode 100644 index 00000000..1f599780 --- /dev/null +++ b/cmd/gcp/ci/Dockerfile @@ -0,0 +1,12 @@ +FROM sctfe-gcp:latest AS base + +# Build release image +FROM alpine:3.20.2@sha256:0a4eaa0eecf5f8c050e5bba433f58c052be7587ee8af3e8b3910ef9ab5fbe9f5 + +# Copy the fake CA certificate into the container +COPY ./testdata/fake-ca.cert /bin/ + +# Copy the sctfe-gcp binary +COPY --from=base /bin/sctfe-gcp /bin/ + +ENTRYPOINT ["/bin/sctfe-gcp"] diff --git a/deployment/live/gcp/ci/.terraform.lock.hcl b/deployment/live/gcp/ci/.terraform.lock.hcl new file mode 100644 index 00000000..c0334c4e --- /dev/null +++ b/deployment/live/gcp/ci/.terraform.lock.hcl @@ -0,0 +1,41 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/google" { + version = "6.1.0" + constraints = "6.1.0" + hashes = [ + "h1:okppWOAoIPz45VkydzAA74HRLgEKvP4CFXypPU228j8=", + "zh:2463510438c97c59e06ab1fb1ef76221c844abd1bc404c439401fc256e9928ab", + "zh:2afd9b76a81c51632bd54d3cc3bdc2685e8d89b8ace8ca7578b1ae42880228b5", + "zh:51e2fb64c7c8258ac0ec7315d488e5c655b392bf565f9bee2922ee72f6abfb90", + "zh:85aa39bad51132810ee6cd369f426614abff59cb0274fc737d087c17afa9b5ee", + "zh:989669bfed5ca7bf4d960eb9f27a62cbe2578ca2907da7c74fc93edae9a497fa", + "zh:a26665782e90ef3fd322d6a23a1de383c81ae93395e7c2bd9648a1aa85c69876", + "zh:d5e1b785b4c8569b91153eeba89280ffbbe7a0aaabb708833ada67544aeed057", + "zh:d748c69eab6acc4ab7ec369b3bd3ddd5d2e4120d99570743dafde74934959a20", + "zh:eb853ab5c4c0d3e536b8c77abf844b7893ac355967c95b6e0d39b12526e67989", + "zh:f4b50f0ae082412ba189041b6ac540523b7d6463905fed63be67eec03e1539b9", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:f6e7adcfafe267d9c657a6c087388f7e0c1e3be4dc179a9a823f75c830a499b7", + ] +} + +provider "registry.terraform.io/hashicorp/tls" { + version = "4.0.6" + hashes = [ + "h1:dYSb3V94K5dDMtrBRLPzBpkMTPn+3cXZ/kIJdtFL+2M=", + "zh:10de0d8af02f2e578101688fd334da3849f56ea91b0d9bd5b1f7a243417fdda8", + "zh:37fc01f8b2bc9d5b055dc3e78bfd1beb7c42cfb776a4c81106e19c8911366297", + "zh:4578ca03d1dd0b7f572d96bd03f744be24c726bfd282173d54b100fd221608bb", + "zh:6c475491d1250050765a91a493ef330adc24689e8837a0f07da5a0e1269e11c1", + "zh:81bde94d53cdababa5b376bbc6947668be4c45ab655de7aa2e8e4736dfd52509", + "zh:abdce260840b7b050c4e401d4f75c7a199fafe58a8b213947a258f75ac18b3e8", + "zh:b754cebfc5184873840f16a642a7c9ef78c34dc246a8ae29e056c79939963c7a", + "zh:c928b66086078f9917aef0eec15982f2e337914c5c4dbc31dd4741403db7eb18", + "zh:cded27bee5f24de6f2ee0cfd1df46a7f88e84aaffc2ecbf3ff7094160f193d50", + "zh:d65eb3867e8f69aaf1b8bb53bd637c99c6b649ba3db16ded50fa9a01076d1a27", + "zh:ecb0c8b528c7a619fa71852bb3fb5c151d47576c5aab2bf3af4db52588722eeb", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} \ No newline at end of file diff --git a/deployment/live/gcp/ci/README.md b/deployment/live/gcp/ci/README.md new file mode 100644 index 00000000..fd0a23a2 --- /dev/null +++ b/deployment/live/gcp/ci/README.md @@ -0,0 +1,48 @@ +# GCP SCTFE CI Environment + +## Overview + +This config uses the [gcp/conformance](/deployment/modules/gcp/conformance) module to +define a CI environment to run the SCTFE, backed by Trillian Tessera. + +At a high level, this environment consists of: +- One Spanner instance with two databases: + - one for Tessera + - one for deduplication +- A GCS Bucket +- Secret Manager +- Cloud Run + +### Manual Deployment + +First authenticate via `gcloud` as a principle with sufficient ACLs for +the project: + +```sh +gcloud auth application-default login +``` + +Set the required environment variables: + +```sh +export GOOGLE_PROJECT={VALUE} +export GOOGLE_REGION={VALUE} # e.g: us-central1 +unset TESSERA_BASE_NAME +``` + +TODO: Add the Artifact Registry which is in the Cloud Build pull request. The expected repository is `${GOOGLE_REGION}-docker.pkg.dev/${GOOGLE_PROJECT}/docker-ci`. + +Build and push the Docker image to Artifact Registry repository: + +```sh +gcloud auth configure-docker ${GOOGLE_REGION}-docker.pkg.dev +docker build -f ./cmd/gcp/Dockerfile -t sctfe-gcp:latest . +docker build -f ./cmd/gcp/ci/Dockerfile -t conformance-gcp:latest . +docker tag conformance-gcp:latest ${GOOGLE_REGION}-docker.pkg.dev/${GOOGLE_PROJECT}/docker-ci/conformance-gcp:latest +docker push ${GOOGLE_REGION}-docker.pkg.dev/${GOOGLE_PROJECT}/docker-ci/conformance-gcp +``` + +Terraforming the project can be done by: + 1. `cd` to the relevant directory (deployment/live/gcp/ci/) for the environment to deploy/change. + 2. Run `terragrunt apply`. + diff --git a/deployment/live/gcp/ci/terragrunt.hcl b/deployment/live/gcp/ci/terragrunt.hcl new file mode 100644 index 00000000..c27e2dd1 --- /dev/null +++ b/deployment/live/gcp/ci/terragrunt.hcl @@ -0,0 +1,19 @@ +terraform { + source = "${get_repo_root()}/deployment/modules/gcp//conformance" +} + +locals { + env = "ci" + base_name = "${local.env}-conformance" + server_docker_image = "us-central1-docker.pkg.dev/${include.root.locals.project_id}/docker-${local.env}/conformance-gcp:latest" +} + +include "root" { + path = find_in_parent_folders() + expose = true +} + +inputs = merge( + local, + include.root.locals, +) diff --git a/deployment/live/gcp/terragrunt.hcl b/deployment/live/gcp/terragrunt.hcl new file mode 100644 index 00000000..2b621361 --- /dev/null +++ b/deployment/live/gcp/terragrunt.hcl @@ -0,0 +1,22 @@ +locals { + env = path_relative_to_include() + project_id = get_env("GOOGLE_PROJECT", "phboneff-dev") + location = get_env("GOOGLE_REGION", "us-central1") + base_name = get_env("TESSERA_BASE_NAME", "${local.env}-static-ct") +} + +remote_state { + backend = "gcs" + + config = { + project = local.project_id + location = local.location + bucket = "${local.project_id}-${local.base_name}-terraform-state" + prefix = "terraform.tfstate" + + gcs_bucket_labels = { + name = "terraform_state" + env = "${local.env}" + } + } +} diff --git a/deployment/live/gcp/test/README.md b/deployment/live/gcp/test/README.md index 8fa83388..6e0bd39e 100644 --- a/deployment/live/gcp/test/README.md +++ b/deployment/live/gcp/test/README.md @@ -1,4 +1,4 @@ -# GCP SCTFE Configs +# GCP SCTFE Test Environment ## Prerequisites You'll need to have a VM running in the same GCP project that you can SSH to, @@ -8,7 +8,7 @@ installed, and your favourite terminal multiplexer. ## Overview -This config uses the [gcp/storage](/deployment/modules/gcp/conformance) module to +This config uses the [gcp/test](/deployment/modules/gcp/test) module to define a test environment to run the SCTFE, backed by Trillian Tessera. At a high level, this environment consists of: @@ -16,6 +16,7 @@ At a high level, this environment consists of: - one for Tessera - one for deduplication - A GCS Bucket +- Secret Manager ## Manual deployment @@ -31,12 +32,12 @@ Set the required environment variables: ```bash export GOOGLE_PROJECT={VALUE} export GOOGLE_REGION={VALUE} # e.g: us-central1 -export TESSERA_BASE_NAME={VALUE} # e.g: staticct +export TESSERA_BASE_NAME={VALUE} # e.g: test-static-ct ``` Terraforming the project can be done by: - 1. `cd` to the relevant directory for the environment to deploy/change (e.g. `ci`) - 2. Run `terragrunt apply` + 1. `cd` to the relevant directory for the environment to deploy/change (e.g. `ci`) + 2. Run `terragrunt apply` Store the Secret Manager resource ID of signer key pair into the environment variables: diff --git a/deployment/live/gcp/test/terragrunt.hcl b/deployment/live/gcp/test/terragrunt.hcl index f44d2db9..5a2980a4 100644 --- a/deployment/live/gcp/test/terragrunt.hcl +++ b/deployment/live/gcp/test/terragrunt.hcl @@ -1,26 +1,18 @@ terraform { - source = "${get_repo_root()}/deployment/modules/gcp//conformance" + source = "${get_repo_root()}/deployment/modules/gcp//test" } locals { - project_id = get_env("GOOGLE_PROJECT", "phboneff-dev") - location = get_env("GOOGLE_REGION", "us-central1") - base_name = get_env("TESSERA_BASE_NAME", "tessera-staticct") + env = "test" + base_name = get_env("TESSERA_BASE_NAME", "${local.env}-static-ct") } -inputs = local - -remote_state { - backend = "gcs" - - config = { - project = local.project_id - location = local.location - bucket = "${local.project_id}-${local.base_name}-terraform-state" - prefix = "terraform.tfstate" - - gcs_bucket_labels = { - name = "terraform_state_conformance" - } - } +include "root" { + path = find_in_parent_folders() + expose = true } + +inputs = merge( + local, + include.root.locals, +) diff --git a/deployment/modules/gcp/cloudrun/main.tf b/deployment/modules/gcp/cloudrun/main.tf new file mode 100644 index 00000000..ecc36c04 --- /dev/null +++ b/deployment/modules/gcp/cloudrun/main.tf @@ -0,0 +1,119 @@ +terraform { + required_providers { + google = { + source = "registry.terraform.io/hashicorp/google" + version = "6.1.0" + } + } +} + +# Cloud Run + +resource "google_project_service" "cloudrun_api" { + service = "run.googleapis.com" + disable_on_destroy = false +} + +resource "google_service_account" "cloudrun_service_account" { + account_id = "cloudrun-${var.env}-sa" + display_name = "Service Account for Cloud Run (${var.env})" +} + +resource "google_project_iam_member" "monitoring_metric_writer" { + project = var.project_id + role = "roles/monitoring.metricWriter" + member = "serviceAccount:${google_service_account.cloudrun_service_account.email}" +} + +resource "google_storage_bucket_iam_member" "member" { + bucket = var.bucket + role = "roles/storage.objectUser" + member = "serviceAccount:${google_service_account.cloudrun_service_account.email}" +} + +resource "google_project_iam_member" "iam_secret_accessor" { + project = var.project_id + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${google_service_account.cloudrun_service_account.email}" +} + +resource "google_spanner_database_iam_member" "iam_log_spanner_database_user" { + instance = var.log_spanner_instance + database = var.log_spanner_db + role = "roles/spanner.databaseUser" + member = "serviceAccount:${google_service_account.cloudrun_service_account.email}" +} + +resource "google_spanner_database_iam_member" "iam_dedup_spanner_database_user" { + instance = var.log_spanner_instance + database = var.dedup_spanner_db + role = "roles/spanner.databaseUser" + member = "serviceAccount:${google_service_account.cloudrun_service_account.email}" +} + +locals { + spanner_log_db_path = "projects/${var.project_id}/instances/${var.log_spanner_instance}/databases/${var.log_spanner_db}" + spanner_dedup_db_path = "projects/${var.project_id}/instances/${var.log_spanner_instance}/databases/${var.dedup_spanner_db}" +} + +resource "google_cloud_run_v2_service" "default" { + name = var.base_name + location = var.location + launch_stage = "GA" + + template { + service_account = google_service_account.cloudrun_service_account.account_id + max_instance_request_concurrency = 700 + timeout = "5s" + + scaling { + max_instance_count = 3 + } + + containers { + image = var.server_docker_image + name = "conformance" + args = [ + "--logtostderr", + "--v=1", + "--http_endpoint=:6962", + "--project_id=${var.project_id}", + "--bucket=${var.bucket}", + "--spanner_db_path=${local.spanner_log_db_path}", + "--spanner_dedup_db_path=${local.spanner_dedup_db_path}", + "--roots_pem_file=/bin/fake-ca.cert", + "--origin=${var.base_name}", + "--signer_public_key_secret_name=${var.signer_public_key_secret_name}", + "--signer_private_key_secret_name=${var.signer_private_key_secret_name}", + ] + ports { + container_port = 6962 + } + + resources { + limits = { + cpu = "2000m" + memory = "1Gi" + } + } + + startup_probe { + initial_delay_seconds = 1 + timeout_seconds = 1 + period_seconds = 10 + failure_threshold = 6 + tcp_socket { + port = 6962 + } + } + } + } + + deletion_protection = false + + client = "terraform" + + depends_on = [ + google_project_service.cloudrun_api, + ] +} diff --git a/deployment/modules/gcp/cloudrun/variables.tf b/deployment/modules/gcp/cloudrun/variables.tf new file mode 100644 index 00000000..37f976e6 --- /dev/null +++ b/deployment/modules/gcp/cloudrun/variables.tf @@ -0,0 +1,54 @@ +variable "project_id" { + description = "GCP project ID where the log is hosted" + type = string +} + +variable "base_name" { + description = "Base name to use when naming resources" + type = string +} + +variable "location" { + description = "Location in which to create resources" + type = string +} + +variable "env" { + description = "Unique identifier for the env, e.g. dev or ci or prod" + type = string +} + +variable "server_docker_image" { + description = "The full image URL (path & tag) for the Docker image to deploy in Cloud Run" + type = string +} + +variable "bucket" { + description = "Log GCS bucket" + type = string +} + +variable "log_spanner_instance" { + description = "Log Spanner instance" + type = string +} + +variable "log_spanner_db" { + description = "Log Spanner database" + type = string +} + +variable "dedup_spanner_db" { + description = "Dedup Spanner database" + type = string +} + +variable "signer_public_key_secret_name" { + description = "Public key secret name for checkpoints and SCTs signer. Format: projects/{projectId}/secrets/{secretName}/versions/{secretVersion}." + type = string +} + +variable "signer_private_key_secret_name" { + description = "Private key secret name for checkpoints and SCTs signer. Format: projects/{projectId}/secrets/{secretName}/versions/{secretVersion}." + type = string +} diff --git a/deployment/modules/gcp/conformance/main.tf b/deployment/modules/gcp/conformance/main.tf index 80f39527..319c24ee 100644 --- a/deployment/modules/gcp/conformance/main.tf +++ b/deployment/modules/gcp/conformance/main.tf @@ -12,4 +12,27 @@ module "storage" { module "secretmanager" { source = "../secretmanager" + + base_name = var.base_name +} + +module "cloudrun" { + source = "../cloudrun" + + env = var.env + project_id = var.project_id + base_name = var.base_name + location = var.location + server_docker_image = var.server_docker_image + bucket = module.storage.log_bucket.id + log_spanner_instance = module.storage.log_spanner_instance.name + log_spanner_db = module.storage.log_spanner_db.name + dedup_spanner_db = module.storage.dedup_spanner_db.name + signer_public_key_secret_name = module.secretmanager.ecdsa_p256_public_key_id + signer_private_key_secret_name = module.secretmanager.ecdsa_p256_private_key_id + + depends_on = [ + module.secretmanager, + module.storage + ] } diff --git a/deployment/modules/gcp/conformance/variables.tf b/deployment/modules/gcp/conformance/variables.tf index fa8142e7..7abea57e 100644 --- a/deployment/modules/gcp/conformance/variables.tf +++ b/deployment/modules/gcp/conformance/variables.tf @@ -12,3 +12,13 @@ variable "location" { description = "Location in which to create resources" type = string } + +variable "env" { + description = "Unique identifier for the env, e.g. dev or ci or prod" + type = string +} + +variable "server_docker_image" { + description = "The full image URL (path & tag) for the Docker image to deploy in Cloud Run" + type = string +} diff --git a/deployment/modules/gcp/secretmanager/main.tf b/deployment/modules/gcp/secretmanager/main.tf index cc168533..8c18eb98 100644 --- a/deployment/modules/gcp/secretmanager/main.tf +++ b/deployment/modules/gcp/secretmanager/main.tf @@ -28,7 +28,7 @@ resource "tls_private_key" "sctfe_ecdsa_p256" { } resource "google_secret_manager_secret" "sctfe_ecdsa_p256_public_key" { - secret_id = "sctfe-ecdsa-p256-public-key" + secret_id = "${var.base_name}-ecdsa-p256-public-key" labels = { label = "sctfe-public-key" @@ -48,7 +48,7 @@ resource "google_secret_manager_secret_version" "sctfe_ecdsa_p256_public_key" { } resource "google_secret_manager_secret" "sctfe_ecdsa_p256_private_key" { - secret_id = "sctfe-ecdsa-p256-private-key" + secret_id = "${var.base_name}-ecdsa-p256-private-key" labels = { label = "sctfe-private-key" diff --git a/deployment/modules/gcp/secretmanager/variables.tf b/deployment/modules/gcp/secretmanager/variables.tf new file mode 100644 index 00000000..8fbfd505 --- /dev/null +++ b/deployment/modules/gcp/secretmanager/variables.tf @@ -0,0 +1,4 @@ +variable "base_name" { + description = "Base name to use when naming resources" + type = string +} diff --git a/deployment/modules/gcp/storage/outputs.tf b/deployment/modules/gcp/storage/outputs.tf index c2239766..578b9ffa 100644 --- a/deployment/modules/gcp/storage/outputs.tf +++ b/deployment/modules/gcp/storage/outputs.tf @@ -3,16 +3,16 @@ output "log_bucket" { value = google_storage_bucket.log_bucket } -output "log_spanner_db" { - description = "Log Spanner database" - value = google_spanner_database.log_db -} - output "log_spanner_instance" { description = "Log Spanner instance" value = google_spanner_instance.log_spanner } +output "log_spanner_db" { + description = "Log Spanner database" + value = google_spanner_database.log_db +} + output "dedup_spanner_db" { description = "Dedup Spanner database" value = google_spanner_database.dedup_db diff --git a/deployment/modules/gcp/test/main.tf b/deployment/modules/gcp/test/main.tf new file mode 100644 index 00000000..48da0f8b --- /dev/null +++ b/deployment/modules/gcp/test/main.tf @@ -0,0 +1,17 @@ +terraform { + backend "gcs" {} +} + +module "storage" { + source = "../storage" + + project_id = var.project_id + base_name = var.base_name + location = var.location +} + +module "secretmanager" { + source = "../secretmanager" + + base_name = var.base_name +} diff --git a/deployment/modules/gcp/test/outputs.tf b/deployment/modules/gcp/test/outputs.tf new file mode 100644 index 00000000..8385bcf0 --- /dev/null +++ b/deployment/modules/gcp/test/outputs.tf @@ -0,0 +1,9 @@ +output "ecdsa_p256_public_key_id" { + description = "Signer public key (P256_SHA256)" + value = module.secretmanager.ecdsa_p256_public_key_id +} + +output "ecdsa_p256_private_key_id" { + description = "Signer private key (P256_SHA256)" + value = module.secretmanager.ecdsa_p256_private_key_id +} diff --git a/deployment/modules/gcp/test/variables.tf b/deployment/modules/gcp/test/variables.tf new file mode 100644 index 00000000..06c3a290 --- /dev/null +++ b/deployment/modules/gcp/test/variables.tf @@ -0,0 +1,19 @@ +variable "project_id" { + description = "GCP project ID where the log is hosted" + type = string +} + +variable "base_name" { + description = "Base name to use when naming resources" + type = string +} + +variable "location" { + description = "Location in which to create resources" + type = string +} + +variable "env" { + description = "Unique identifier for the env, e.g. dev or ci or prod" + type = string +}