From 114696c0baf4089d9cae55b45f5bab27e972be30 Mon Sep 17 00:00:00 2001 From: Andrew Dryga Date: Sat, 16 Mar 2024 11:54:06 -0600 Subject: [PATCH] chore(infra): Split terraform files into folders and add domain to production app (#4172) --- .github/workflows/_terraform.yml | 6 +- docker-compose.yml | 2 +- docs/README.md | 4 +- elixir/apps/api/test/support/channel_case.ex | 2 +- .../openid_connect/settings/changeset.ex | 1 - elixir/apps/domain/lib/domain/gateways.ex | 2 +- .../lib/domain/network/address/query.ex | 2 +- elixir/apps/domain/mix.exs | 2 +- elixir/config/test.exs | 2 +- rust/snownet-tests/docker-compose.lan.yml | 2 +- rust/snownet-tests/docker-compose.wan-hp.yml | 2 +- .../docker-compose.wan-relay.yml | 2 +- terraform/environments/production/bi.tf | 2 +- terraform/environments/production/gateways.tf | 2 +- terraform/environments/production/main.tf | 721 +--------------- terraform/environments/production/portal.tf | 628 ++++++++++++++ terraform/environments/production/relays.tf | 112 +++ terraform/environments/production/versions.tf | 2 +- terraform/environments/production/website.tf | 1 - terraform/environments/staging/bi.tf | 2 +- terraform/environments/staging/main.tf | 804 +----------------- terraform/environments/staging/portal.tf | 631 ++++++++++++++ terraform/environments/staging/relays.tf | 79 ++ terraform/environments/staging/versions.tf | 4 +- .../apps/elixir}/dns.tf | 0 .../apps/elixir}/iam.tf | 0 .../apps/elixir}/main.tf | 0 .../apps/elixir}/network.tf | 0 .../apps/elixir}/outputs.tf | 0 .../apps/elixir}/services.tf | 0 .../apps/elixir}/templates/cloud-init.yaml | 0 .../apps/elixir}/variables.tf | 0 .../gateway-region-instance-group}/iam.tf | 0 .../gateway-region-instance-group}/main.tf | 0 .../gateway-region-instance-group}/outputs.tf | 0 .../services.tf | 0 .../templates/cloud-init.yaml | 0 .../variables.tf | 0 .../apps/metabase}/iam.tf | 0 .../apps/metabase}/main.tf | 0 .../apps/metabase}/outputs.tf | 0 .../apps/metabase}/services.tf | 0 .../apps/metabase}/variables.tf | 0 .../apps/relay}/main.tf | 0 .../apps/relay}/outputs.tf | 0 .../apps/relay}/services.tf | 0 .../apps/relay}/templates/cloud-init.yaml | 0 .../apps/relay}/variables.tf | 0 .../artifact-registry}/main.tf | 0 .../artifact-registry}/outputs.tf | 0 .../artifact-registry}/variables.tf | 0 .../dns}/main.tf | 0 .../dns}/outputs.tf | 0 .../dns}/variables.tf | 0 .../ops}/main.tf | 41 - .../ops}/outputs.tf | 0 .../ops}/variables.tf | 4 - .../project}/main.tf | 0 .../project}/outputs.tf | 0 .../project}/variables.tf | 0 .../sql}/main.tf | 0 .../sql}/outputs.tf | 0 .../sql}/variables.tf | 0 .../storage}/main.tf | 0 .../storage}/variables.tf | 0 .../vpc}/main.tf | 0 .../vpc}/outputs.tf | 0 .../vpc}/variables.tf | 0 68 files changed, 1509 insertions(+), 1553 deletions(-) create mode 100644 terraform/environments/production/portal.tf create mode 100644 terraform/environments/production/relays.tf create mode 100644 terraform/environments/staging/portal.tf create mode 100644 terraform/environments/staging/relays.tf rename terraform/modules/{elixir-app => google-cloud/apps/elixir}/dns.tf (100%) rename terraform/modules/{elixir-app => google-cloud/apps/elixir}/iam.tf (100%) rename terraform/modules/{elixir-app => google-cloud/apps/elixir}/main.tf (100%) rename terraform/modules/{elixir-app => google-cloud/apps/elixir}/network.tf (100%) rename terraform/modules/{elixir-app => google-cloud/apps/elixir}/outputs.tf (100%) rename terraform/modules/{elixir-app => google-cloud/apps/elixir}/services.tf (100%) rename terraform/modules/{elixir-app => google-cloud/apps/elixir}/templates/cloud-init.yaml (100%) rename terraform/modules/{elixir-app => google-cloud/apps/elixir}/variables.tf (100%) rename terraform/modules/{gateway-google-cloud-compute => google-cloud/apps/gateway-region-instance-group}/iam.tf (100%) rename terraform/modules/{gateway-google-cloud-compute => google-cloud/apps/gateway-region-instance-group}/main.tf (100%) rename terraform/modules/{gateway-google-cloud-compute => google-cloud/apps/gateway-region-instance-group}/outputs.tf (100%) rename terraform/modules/{gateway-google-cloud-compute => google-cloud/apps/gateway-region-instance-group}/services.tf (100%) rename terraform/modules/{gateway-google-cloud-compute => google-cloud/apps/gateway-region-instance-group}/templates/cloud-init.yaml (100%) rename terraform/modules/{gateway-google-cloud-compute => google-cloud/apps/gateway-region-instance-group}/variables.tf (100%) rename terraform/modules/{metabase-app => google-cloud/apps/metabase}/iam.tf (100%) rename terraform/modules/{metabase-app => google-cloud/apps/metabase}/main.tf (100%) rename terraform/modules/{metabase-app => google-cloud/apps/metabase}/outputs.tf (100%) rename terraform/modules/{metabase-app => google-cloud/apps/metabase}/services.tf (100%) rename terraform/modules/{metabase-app => google-cloud/apps/metabase}/variables.tf (100%) rename terraform/modules/{relay-app => google-cloud/apps/relay}/main.tf (100%) rename terraform/modules/{relay-app => google-cloud/apps/relay}/outputs.tf (100%) rename terraform/modules/{relay-app => google-cloud/apps/relay}/services.tf (100%) rename terraform/modules/{relay-app => google-cloud/apps/relay}/templates/cloud-init.yaml (100%) rename terraform/modules/{relay-app => google-cloud/apps/relay}/variables.tf (100%) rename terraform/modules/{google-artifact-registry => google-cloud/artifact-registry}/main.tf (100%) rename terraform/modules/{google-artifact-registry => google-cloud/artifact-registry}/outputs.tf (100%) rename terraform/modules/{google-artifact-registry => google-cloud/artifact-registry}/variables.tf (100%) rename terraform/modules/{google-cloud-dns => google-cloud/dns}/main.tf (100%) rename terraform/modules/{google-cloud-dns => google-cloud/dns}/outputs.tf (100%) rename terraform/modules/{google-cloud-dns => google-cloud/dns}/variables.tf (100%) rename terraform/modules/{google-cloud-ops => google-cloud/ops}/main.tf (92%) rename terraform/modules/{google-cloud-ops => google-cloud/ops}/outputs.tf (100%) rename terraform/modules/{google-cloud-ops => google-cloud/ops}/variables.tf (92%) rename terraform/modules/{google-cloud-project => google-cloud/project}/main.tf (100%) rename terraform/modules/{google-cloud-project => google-cloud/project}/outputs.tf (100%) rename terraform/modules/{google-cloud-project => google-cloud/project}/variables.tf (100%) rename terraform/modules/{google-cloud-sql => google-cloud/sql}/main.tf (100%) rename terraform/modules/{google-cloud-sql => google-cloud/sql}/outputs.tf (100%) rename terraform/modules/{google-cloud-sql => google-cloud/sql}/variables.tf (100%) rename terraform/modules/{google-cloud-storage => google-cloud/storage}/main.tf (100%) rename terraform/modules/{google-cloud-storage => google-cloud/storage}/variables.tf (100%) rename terraform/modules/{google-cloud-vpc => google-cloud/vpc}/main.tf (100%) rename terraform/modules/{google-cloud-vpc => google-cloud/vpc}/outputs.tf (100%) rename terraform/modules/{google-cloud-vpc => google-cloud/vpc}/variables.tf (100%) diff --git a/.github/workflows/_terraform.yml b/.github/workflows/_terraform.yml index fda62bc82..7bbfe2f54 100644 --- a/.github/workflows/_terraform.yml +++ b/.github/workflows/_terraform.yml @@ -28,9 +28,9 @@ jobs: - name: Validate cloud-init run: | sudo apt-get install -y cloud-init - sudo cloud-init schema --config-file terraform/modules/relay-app/templates/cloud-init.yaml - sudo cloud-init schema --config-file terraform/modules/elixir-app/templates/cloud-init.yaml - sudo cloud-init schema --config-file terraform/modules/gateway-google-cloud-compute/templates/cloud-init.yaml + sudo cloud-init schema --config-file terraform/modules/google-cloud/apps/relay/templates/cloud-init.yaml + sudo cloud-init schema --config-file terraform/modules/google-cloud/apps/elixir/templates/cloud-init.yaml + sudo cloud-init schema --config-file terraform/modules/google-cloud/apps/gateway-region-instance-group/templates/cloud-init.yaml - name: Check Formatting working-directory: terraform run: | diff --git a/docker-compose.yml b/docker-compose.yml index 97fe81098..0eb06a867 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -406,7 +406,7 @@ services: api: condition: "service_healthy" ports: - # XXX: Only 111 ports are used for local dev / testing because Docker Desktop + # NOTE: Only 111 ports are used for local dev / testing because Docker Desktop # allocates a userland proxy process for each forwarded port X_X. # # Large ranges here will bring your machine to its knees. diff --git a/docs/README.md b/docs/README.md index 6f0a60d3a..17ba9d98b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -106,9 +106,9 @@ product documentation, organized as follows: - [terraform/examples/gcp/nat_gateway](../terraform/examples/gcp/nat_gateway): Example Terraform configurations for deploying a cluster of Firezone gateways behind a NAT gateway on GCP with single egress IP. - - [terraform/modules/gateway-google-cloud-compute](../terraform/modules/gateway-google-cloud-compute): + - [terraform/modules/google-cloud/apps/gateway-region-instance-group](../terraform/modules/google-cloud/apps/gateway-region-instance-group): Production-ready Terraform module for deploying regional Firezone gateways - to Google Cloud Compute. + to Google Cloud Compute using Regional Instance Groups. ## Quickstart diff --git a/elixir/apps/api/test/support/channel_case.ex b/elixir/apps/api/test/support/channel_case.ex index 6299d66ec..91b4e8f41 100644 --- a/elixir/apps/api/test/support/channel_case.ex +++ b/elixir/apps/api/test/support/channel_case.ex @@ -24,7 +24,7 @@ defmodule API.ChannelCase do setup tags do for presence <- @presences, pid <- presence.fetchers_pids() do - # XXX: If we start using Presence.fetch/2 callback we might want to + # TODO: If we start using Presence.fetch/2 callback we might want to # contribute to Phoenix.Presence a way to propagate sandbox access from # the parent to the task supervisor it spawns in start_link/1 every time # it's used. Because this would not work as is: diff --git a/elixir/apps/domain/lib/domain/auth/adapters/openid_connect/settings/changeset.ex b/elixir/apps/domain/lib/domain/auth/adapters/openid_connect/settings/changeset.ex index 01c3943a4..2f9a842b4 100644 --- a/elixir/apps/domain/lib/domain/auth/adapters/openid_connect/settings/changeset.ex +++ b/elixir/apps/domain/lib/domain/auth/adapters/openid_connect/settings/changeset.ex @@ -32,7 +32,6 @@ defmodule Domain.Auth.Adapters.OpenIDConnect.Settings.Changeset do {:error, %Jason.DecodeError{} = _error} -> [{:discovery_document_uri, "is invalid, unable to parse response"}] - # XXX: Do these occur with Mint? {:error, {404, _body}} -> [{:discovery_document_uri, "does not exist"}] diff --git a/elixir/apps/domain/lib/domain/gateways.ex b/elixir/apps/domain/lib/domain/gateways.ex index 6d1396b3e..9b8e71fd8 100644 --- a/elixir/apps/domain/lib/domain/gateways.ex +++ b/elixir/apps/domain/lib/domain/gateways.ex @@ -352,7 +352,7 @@ defmodule Domain.Gateways do gateways = connected_gateways |> Map.keys() - # XXX: This will create a pretty large query to send to Postgres, + # TODO: This will create a pretty large query to send to Postgres, # we probably want to load connected resources once when gateway connects, # and persist them in the memory not to query DB every time with a # `WHERE ... IN (...)`. diff --git a/elixir/apps/domain/lib/domain/network/address/query.ex b/elixir/apps/domain/lib/domain/network/address/query.ex index a2c385077..728f2e176 100644 --- a/elixir/apps/domain/lib/domain/network/address/query.ex +++ b/elixir/apps/domain/lib/domain/network/address/query.ex @@ -81,7 +81,7 @@ defmodule Domain.Network.Address.Query do # # At the same time offset will fit to bigint even for largest CIDR ranges that Firezone supports. # - # XXX: We can make this code prettier once https://github.com/elixir-ecto/ecto/commit/8f7bb2665bce30dfab18cfed01585c96495575a6 is released. + # TODO: We can make this code prettier once https://github.com/elixir-ecto/ecto/commit/8f7bb2665bce30dfab18cfed01585c96495575a6 is released. defp series_from_offset_inclusive_to_end_of_cidr(network_cidr, offset) do from( i in fragment( diff --git a/elixir/apps/domain/mix.exs b/elixir/apps/domain/mix.exs index 06c7fa4f9..269ce668c 100644 --- a/elixir/apps/domain/mix.exs +++ b/elixir/apps/domain/mix.exs @@ -75,7 +75,7 @@ defmodule Domain.MixProject do {:opentelemetry_finch, "~> 0.2.0"}, # Mailer deps - # XXX: This is a workaround for the following issue: + # TODO: This is a workaround for the following issue: # https://github.com/elixir-lang/elixir/issues/12777 # Remove Swoosh from Domain once this is fixed. {:phoenix_swoosh, "~> 1.0"}, diff --git a/elixir/config/test.exs b/elixir/config/test.exs index 4cb4ee675..d1978175e 100644 --- a/elixir/config/test.exs +++ b/elixir/config/test.exs @@ -69,7 +69,7 @@ config :bureaucrat, :json_library, Jason config :wallaby, driver: Wallaby.Chrome, screenshot_on_failure: true, - # XXX: Contribute to Wallaby to make this configurable on the per-process level, + # TODO: Contribute to Wallaby to make this configurable on the per-process level, # along with buffer to write logs only on process failure js_logger: false, hackney_options: [timeout: 10_000, recv_timeout: 10_000] diff --git a/rust/snownet-tests/docker-compose.lan.yml b/rust/snownet-tests/docker-compose.lan.yml index 5887630f8..d7d5c67a7 100644 --- a/rust/snownet-tests/docker-compose.lan.yml +++ b/rust/snownet-tests/docker-compose.lan.yml @@ -119,7 +119,7 @@ services: firezone-relay ports: - # XXX: Only 111 ports are used for local dev / testing because Docker Desktop + # NOTE: Only 111 ports are used for local dev / testing because Docker Desktop # allocates a userland proxy process for each forwarded port X_X. # # Large ranges here will bring your machine to its knees. diff --git a/rust/snownet-tests/docker-compose.wan-hp.yml b/rust/snownet-tests/docker-compose.wan-hp.yml index 2871386c4..b47786257 100644 --- a/rust/snownet-tests/docker-compose.wan-hp.yml +++ b/rust/snownet-tests/docker-compose.wan-hp.yml @@ -128,7 +128,7 @@ services: firezone-relay ports: - # XXX: Only 111 ports are used for local dev / testing because Docker Desktop + # NOTE: Only 111 ports are used for local dev / testing because Docker Desktop # allocates a userland proxy process for each forwarded port X_X. # # Large ranges here will bring your machine to its knees. diff --git a/rust/snownet-tests/docker-compose.wan-relay.yml b/rust/snownet-tests/docker-compose.wan-relay.yml index 44e30b8e1..a6cc8340b 100644 --- a/rust/snownet-tests/docker-compose.wan-relay.yml +++ b/rust/snownet-tests/docker-compose.wan-relay.yml @@ -132,7 +132,7 @@ services: firezone-relay ports: - # XXX: Only 111 ports are used for local dev / testing because Docker Desktop + # NOTE: Only 111 ports are used for local dev / testing because Docker Desktop # allocates a userland proxy process for each forwarded port X_X. # # Large ranges here will bring your machine to its knees. diff --git a/terraform/environments/production/bi.tf b/terraform/environments/production/bi.tf index dca6beca2..a33c099d0 100644 --- a/terraform/environments/production/bi.tf +++ b/terraform/environments/production/bi.tf @@ -59,7 +59,7 @@ resource "postgresql_grant" "grant_execute_on_all_functions_schema_to_metabase" } module "metabase" { - source = "../../modules/metabase-app" + source = "../../modules/google-cloud/apps/metabase" project_id = module.google-cloud-project.project.project_id compute_network = module.google-cloud-vpc.id diff --git a/terraform/environments/production/gateways.tf b/terraform/environments/production/gateways.tf index 9bb175ab8..8c29ae103 100644 --- a/terraform/environments/production/gateways.tf +++ b/terraform/environments/production/gateways.tf @@ -7,7 +7,7 @@ locals { module "gateways" { count = var.gateway_token != null ? 1 : 0 - source = "../../modules/gateway-google-cloud-compute" + source = "../../modules/google-cloud/apps/gateway-region-instance-group" project_id = module.google-cloud-project.project.project_id compute_network = module.google-cloud-vpc.id diff --git a/terraform/environments/production/main.tf b/terraform/environments/production/main.tf index cb35f8bb6..d8869ee81 100644 --- a/terraform/environments/production/main.tf +++ b/terraform/environments/production/main.tf @@ -35,7 +35,7 @@ provider "google-beta" {} # Create the project module "google-cloud-project" { - source = "../../modules/google-cloud-project" + source = "../../modules/google-cloud/project" id = "firezone-prod" name = "Production Environment" @@ -91,7 +91,7 @@ resource "google_project_iam_binding" "project_owners" { # Grant GitHub Actions ability to write to the container registry module "google-artifact-registry" { - source = "../../modules/google-artifact-registry" + source = "../../modules/google-cloud/artifact-registry" project_id = module.google-cloud-project.project.project_id project_name = module.google-cloud-project.name @@ -107,7 +107,7 @@ module "google-artifact-registry" { # Create a VPC module "google-cloud-vpc" { - source = "../../modules/google-cloud-vpc" + source = "../../modules/google-cloud/vpc" project_id = module.google-cloud-project.project.project_id name = module.google-cloud-project.project.project_id @@ -117,14 +117,14 @@ module "google-cloud-vpc" { # Enable Google Cloud Storage for the project module "google-cloud-storage" { - source = "../../modules/google-cloud-storage" + source = "../../modules/google-cloud/storage" project_id = module.google-cloud-project.project.project_id } # Create DNS managed zone module "google-cloud-dns" { - source = "../../modules/google-cloud-dns" + source = "../../modules/google-cloud/dns" project_id = module.google-cloud-project.project.project_id @@ -134,7 +134,7 @@ module "google-cloud-dns" { # Create the Cloud SQL database module "google-cloud-sql" { - source = "../../modules/google-cloud-sql" + source = "../../modules/google-cloud/sql" project_id = module.google-cloud-project.project.project_id network = module.google-cloud-vpc.id @@ -175,678 +175,10 @@ module "google-cloud-sql" { } } -# Generate secrets -resource "random_password" "erlang_cluster_cookie" { - length = 64 - special = false -} - -resource "random_password" "tokens_key_base" { - length = 64 - special = false -} - -resource "random_password" "tokens_salt" { - length = 32 - special = false -} - -resource "random_password" "secret_key_base" { - length = 64 - special = false -} - -resource "random_password" "live_view_signing_salt" { - length = 32 - special = false -} - -resource "random_password" "cookie_signing_salt" { - length = 32 - special = false -} - -resource "random_password" "cookie_encryption_salt" { - length = 32 - special = false -} - -# Create VPC subnet for the application instances, -# we want all apps to be in the same VPC in order for Erlang clustering to work -resource "google_compute_subnetwork" "apps" { +resource "google_compute_firewall" "ssh-ipv4" { project = module.google-cloud-project.project.project_id - name = "app" - - stack_type = "IPV4_IPV6" - - ip_cidr_range = "10.128.0.0/20" - region = local.region - network = module.google-cloud-vpc.id - - ipv6_access_type = "EXTERNAL" - - private_ip_google_access = true -} - -# Create VPN subnet for tooling instances -resource "google_compute_subnetwork" "tools" { - project = module.google-cloud-project.project.project_id - - name = "tooling" - - stack_type = "IPV4_IPV6" - - ip_cidr_range = "10.129.0.0/20" - region = local.region - network = module.google-cloud-vpc.id - - ipv6_access_type = "EXTERNAL" - - private_ip_google_access = true -} - -# Create SQL user and database -resource "random_password" "firezone_db_password" { - length = 16 - - min_lower = 1 - min_upper = 1 - min_numeric = 1 - min_special = 1 - - lifecycle { - ignore_changes = [min_lower, min_upper, min_numeric, min_special] - } -} - -resource "google_sql_user" "firezone" { - project = module.google-cloud-project.project.project_id - - instance = module.google-cloud-sql.master_instance_name - - name = "firezone" - password = random_password.firezone_db_password.result -} - -resource "google_sql_database" "firezone" { - project = module.google-cloud-project.project.project_id - - name = "firezone" - instance = module.google-cloud-sql.master_instance_name -} - -# Create IAM users for the database for all project owners -resource "google_sql_user" "iam_users" { - for_each = toset(local.project_owners) - - project = module.google-cloud-project.project.project_id - instance = module.google-cloud-sql.master_instance_name - - type = "CLOUD_IAM_USER" - name = each.value -} - -# We can't remove passwords complete because for IAM users we still need to execute those GRANT statements -provider "postgresql" { - scheme = "gcppostgres" - host = "${module.google-cloud-project.project.project_id}:${local.region}:${module.google-cloud-sql.master_instance_name}" - port = 5432 - username = google_sql_user.firezone.name - password = random_password.firezone_db_password.result - superuser = false - sslmode = "disable" -} - -resource "postgresql_grant" "grant_select_on_all_tables_schema_to_iam_users" { - for_each = toset(local.project_owners) - - database = google_sql_database.firezone.name - - privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"] - objects = [] # ALL - object_type = "table" - schema = "public" - role = each.key - - depends_on = [ - google_sql_user.iam_users - ] -} - -resource "postgresql_grant" "grant_execute_on_all_functions_schema_to_iam_users" { - for_each = toset(local.project_owners) - - database = google_sql_database.firezone.name - - privileges = ["EXECUTE"] - objects = [] # ALL - object_type = "function" - schema = "public" - role = each.key - - depends_on = [ - google_sql_user.iam_users - ] -} - -# Create bucket for client logs -resource "google_storage_bucket" "client-logs" { - project = module.google-cloud-project.project.project_id - name = "${module.google-cloud-project.project.project_id}-client-logs" - - location = "US" - - lifecycle_rule { - condition { - age = 3 - } - - action { - type = "Delete" - } - } - - lifecycle_rule { - condition { - age = 1 - } - - action { - type = "AbortIncompleteMultipartUpload" - } - } - - logging { - log_bucket = true - log_object_prefix = "firezone.dev/clients" - } - - public_access_prevention = "enforced" - uniform_bucket_level_access = true - - lifecycle { - prevent_destroy = true - ignore_changes = [] - } -} - -locals { - cluster = { - name = "firezone" - cookie = base64encode(random_password.erlang_cluster_cookie.result) - } - - shared_application_environment_variables = [ - # Database - { - name = "DATABASE_HOST" - value = module.google-cloud-sql.master_instance_ip_address - }, - { - name = "DATABASE_NAME" - value = google_sql_database.firezone.name - }, - { - name = "DATABASE_USER" - value = google_sql_user.firezone.name - }, - { - name = "DATABASE_PASSWORD" - value = google_sql_user.firezone.password - }, - # Secrets - { - name = "SECRET_KEY_BASE" - value = random_password.secret_key_base.result - }, - { - name = "TOKENS_KEY_BASE" - value = base64encode(random_password.tokens_key_base.result) - }, - { - name = "TOKENS_SALT" - value = base64encode(random_password.tokens_salt.result) - }, - { - name = "SECRET_KEY_BASE" - value = base64encode(random_password.secret_key_base.result) - }, - { - name = "LIVE_VIEW_SIGNING_SALT" - value = base64encode(random_password.live_view_signing_salt.result) - }, - { - name = "COOKIE_SIGNING_SALT" - value = base64encode(random_password.cookie_signing_salt.result) - }, - { - name = "COOKIE_ENCRYPTION_SALT" - value = base64encode(random_password.cookie_encryption_salt.result) - }, - # Erlang - { - name = "ERLANG_DISTRIBUTION_PORT" - value = "9000" - }, - { - name = "CLUSTER_NAME" - value = local.cluster.name - }, - { - name = "ERLANG_CLUSTER_ADAPTER" - value = "Elixir.Domain.Cluster.GoogleComputeLabelsStrategy" - }, - { - name = "ERLANG_CLUSTER_ADAPTER_CONFIG" - value = jsonencode({ - project_id = module.google-cloud-project.project.project_id - cluster_name = local.cluster.name - cluster_name_label = "cluster_name" - cluster_version_label = "cluster_version" - cluster_version = split(".", var.image_tag)[0] - node_name_label = "application" - polling_interval_ms = 7000 - }) - }, - { - name = "RELEASE_COOKIE" - value = local.cluster.cookie - }, - # Auth - { - name = "AUTH_PROVIDER_ADAPTERS" - value = "email,openid_connect,google_workspace,token,microsoft_entra,okta" - }, - # Registry from which Docker install scripts pull from - { - name = "DOCKER_REGISTRY" - value = "ghcr.io/firezone" - }, - # Billing system - { - name = "BILLING_ENABLED" - value = "true" - }, - { - name = "STRIPE_SECRET_KEY" - value = var.stripe_secret_key - }, - { - name = "STRIPE_WEBHOOK_SIGNING_SECRET" - value = var.stripe_webhook_signing_secret - }, - { - name = "STRIPE_DEFAULT_PRICE_ID" - value = var.stripe_default_price_id - }, - # Telemetry - { - name = "INSTRUMENTATION_CLIENT_LOGS_ENABLED" - value = true - }, - { - name = "INSTRUMENTATION_CLIENT_LOGS_BUCKET" - value = google_storage_bucket.client-logs.name - }, - # Emails - { - name = "OUTBOUND_EMAIL_ADAPTER" - value = "Elixir.Swoosh.Adapters.Mailgun" - }, - { - name = "OUTBOUND_EMAIL_FROM" - value = "notifications@firezone.dev" - }, - { - name = "OUTBOUND_EMAIL_ADAPTER_OPTS" - value = jsonencode({ - api_key = var.mailgun_server_api_token, - domain = local.tld - }) - }, - # Feature Flags - { - name = "FEATURE_SIGN_UP_ENABLED" - value = false - } - ] -} - -module "web" { - source = "../../modules/elixir-app" - project_id = module.google-cloud-project.project.project_id - - compute_instance_type = "n1-standard-1" - compute_instance_region = local.region - compute_instance_availability_zones = ["${local.region}-d"] - - dns_managed_zone_name = module.google-cloud-dns.zone_name - - vpc_network = module.google-cloud-vpc.self_link - vpc_subnetwork = google_compute_subnetwork.apps.self_link - - container_registry = module.google-artifact-registry.url - - image_repo = module.google-artifact-registry.repo - image = "web" - image_tag = var.image_tag - - scaling_horizontal_replicas = 2 - - observability_log_level = "debug" - - erlang_release_name = "firezone" - erlang_cluster_cookie = random_password.erlang_cluster_cookie.result - - application_name = "web" - application_version = replace(var.image_tag, ".", "-") - - application_dns_tld = "app.${local.tld}" - - application_ports = [ - { - name = "http" - protocol = "TCP" - port = 8080 - - health_check = { - initial_delay_sec = 60 - - check_interval_sec = 15 - timeout_sec = 10 - healthy_threshold = 1 - unhealthy_threshold = 2 - - http_health_check = { - request_path = "/healthz" - } - } - } - ] - - application_environment_variables = concat([ - # Web Server - { - name = "EXTERNAL_URL" - value = "https://app.${local.tld}" - }, - { - name = "PHOENIX_HTTP_WEB_PORT" - value = "8080" - } - ], local.shared_application_environment_variables) - - application_labels = { - "cluster_name" = local.cluster.name - "cluster_version" = split(".", var.image_tag)[0] - } -} - -module "api" { - source = "../../modules/elixir-app" - project_id = module.google-cloud-project.project.project_id - - compute_instance_type = "n1-standard-1" - compute_instance_region = local.region - compute_instance_availability_zones = ["${local.region}-d"] - - dns_managed_zone_name = module.google-cloud-dns.zone_name - - vpc_network = module.google-cloud-vpc.self_link - vpc_subnetwork = google_compute_subnetwork.apps.self_link - - container_registry = module.google-artifact-registry.url - - image_repo = module.google-artifact-registry.repo - image = "api" - image_tag = var.image_tag - - scaling_horizontal_replicas = 2 - - observability_log_level = "debug" - - erlang_release_name = "firezone" - erlang_cluster_cookie = random_password.erlang_cluster_cookie.result - - application_name = "api" - application_version = replace(var.image_tag, ".", "-") - - application_dns_tld = "api.${local.tld}" - - application_ports = [ - { - name = "http" - protocol = "TCP" - port = 8080 - - health_check = { - initial_delay_sec = 60 - - check_interval_sec = 15 - timeout_sec = 10 - healthy_threshold = 1 - unhealthy_threshold = 3 - - http_health_check = { - request_path = "/healthz" - } - } - } - ] - - application_environment_variables = concat([ - # Web Server - { - name = "EXTERNAL_URL" - value = "https://api.${local.tld}" - }, - { - name = "PHOENIX_HTTP_API_PORT" - value = "8080" - }, - ], local.shared_application_environment_variables) - - application_labels = { - "cluster_name" = local.cluster.name - "cluster_version" = split(".", var.image_tag)[0] - } - - application_token_scopes = [ - "https://www.googleapis.com/auth/cloud-platform" - ] -} - -## Allow API nodes to sign URLs for Google Cloud Storage -resource "google_storage_bucket_iam_member" "sign-urls" { - bucket = google_storage_bucket.client-logs.name - role = "roles/storage.objectAdmin" - member = "serviceAccount:${module.api.service_account.email}" -} - -resource "google_project_iam_custom_role" "sign-urls" { - project = module.google-cloud-project.project.project_id - - title = "Sign URLs for Google Cloud Storage" - - role_id = "iam.sign_urls" - - permissions = [ - "iam.serviceAccounts.signBlob" - ] -} - -resource "google_project_iam_member" "sign-urls" { - project = module.google-cloud-project.project.project_id - role = "projects/${module.google-cloud-project.project.project_id}/roles/${google_project_iam_custom_role.sign-urls.role_id}" - member = "serviceAccount:${module.api.service_account.email}" -} - -# Erlang Cluster -## Allow traffic between Elixir apps for Erlang clustering -resource "google_compute_firewall" "erlang-distribution" { - project = module.google-cloud-project.project.project_id - - name = "erlang-distribution" - network = module.google-cloud-vpc.self_link - - allow { - protocol = "tcp" - ports = [4369, 9000] - } - - allow { - protocol = "udp" - ports = [4369, 9000] - } - - source_ranges = [google_compute_subnetwork.apps.ip_cidr_range] - target_tags = concat(module.web.target_tags, module.api.target_tags) -} - -## Allow service account to list running instances -resource "google_project_iam_custom_role" "erlang-discovery" { - project = module.google-cloud-project.project.project_id - - title = "Read list of Compute instances" - description = "This role is used for Erlang Cluster discovery and allows to list running instances." - - role_id = "compute.list_instances" - permissions = [ - "compute.instances.list", - "compute.zones.list" - ] -} - -resource "google_project_iam_member" "application" { - for_each = { - api = module.api.service_account.email - web = module.web.service_account.email - } - - project = module.google-cloud-project.project.project_id - - role = "projects/${module.google-cloud-project.project.project_id}/roles/${google_project_iam_custom_role.erlang-discovery.role_id}" - member = "serviceAccount:${each.value}" -} - -# Deploy relays -module "relays" { - count = var.relay_token != null ? 1 : 0 - - source = "../../modules/relay-app" - project_id = module.google-cloud-project.project.project_id - - instances = { - # XXX: We may need these in the future, but for now, we don't have many - # (if any) Enterprise customers in these regions - # "asia-east1" = { - # cidr_range = "10.129.0.0/24" - # type = "n1-standard-1" - # replicas = 1 - # zones = ["asia-east1-a"] - # } - # - # "asia-south1" = { - # cidr_range = "10.130.0.0/24" - # type = "n1-standard-1" - # replicas = 1 - # zones = ["asia-south1-a"] - # } - # - # "australia-southeast1" = { - # cidr_range = "10.131.0.0/24" - # type = "n1-standard-1" - # replicas = 1 - # zones = ["australia-southeast1-a"] - # } - # - # "me-central1" = { - # cidr_range = "10.133.0.0/24" - # type = "n2-standard-2" - # replicas = 1 - # zones = ["me-central1-a"] - # } - # - # "southamerica-east1" = { - # cidr_range = "10.134.0.0/24" - # type = "n1-standard-1" - # replicas = 1 - # zones = ["southamerica-east1-b"] - # } - # - # "us-central1" = { - # cidr_range = "10.135.0.0/24" - # type = "n1-standard-1" - # replicas = 1 - # zones = ["us-central1-b"] - # } - - "europe-west1" = { - cidr_range = "10.132.0.0/24" - type = "n1-standard-1" - replicas = 1 - zones = ["europe-west1-d"] - } - - "europe-west2" = { - cidr_range = "10.140.0.0/24" - type = "n1-standard-1" - replicas = 1 - zones = ["europe-west2-c"] - } - - "us-east1" = { - cidr_range = "10.136.0.0/24" - type = "n1-standard-1" - replicas = 1 - zones = ["us-east1-d"] - } - - "us-west2" = { - cidr_range = "10.137.0.0/24" - type = "n1-standard-1" - replicas = 1 - zones = ["us-west2-b"] - } - } - - container_registry = module.google-artifact-registry.url - - image_repo = module.google-artifact-registry.repo - image = "relay" - image_tag = var.image_tag - - observability_log_level = "info,hyper=off,h2=warn,tower=warn" - - application_name = "relay" - application_version = replace(var.image_tag, ".", "-") - - health_check = { - name = "health" - protocol = "TCP" - port = 8080 - - initial_delay_sec = 60 - - check_interval_sec = 15 - timeout_sec = 10 - healthy_threshold = 1 - unhealthy_threshold = 3 - - http_health_check = { - request_path = "/healthz" - } - } - - api_url = "wss://api.${local.tld}" - token = var.relay_token -} - -resource "google_compute_firewall" "portal-ssh-ipv4" { - project = module.google-cloud-project.project.project_id - - name = "portal-ssh-ipv4" + name = "iap-ssh-ipv4" network = module.google-cloud-vpc.self_link allow { @@ -866,39 +198,16 @@ resource "google_compute_firewall" "portal-ssh-ipv4" { # Only allows connections using IAP source_ranges = local.iap_ipv4_ranges - target_tags = concat(module.web.target_tags, module.api.target_tags) -} - -resource "google_compute_firewall" "relays-ssh-ipv4" { - count = length(module.relays) > 0 ? 1 : 0 - - project = module.google-cloud-project.project.project_id - - name = "relays-ssh-ipv4" - network = module.google-cloud-vpc.self_link - - allow { - protocol = "tcp" - ports = [22] - } - - allow { - protocol = "udp" - ports = [22] - } - - allow { - protocol = "sctp" - ports = [22] - } - - # Only allows connections using IAP - source_ranges = local.iap_ipv4_ranges - target_tags = module.relays[0].target_tags + target_tags = concat( + module.web.target_tags, + module.api.target_tags, + module.domain.target_tags, + length(module.relays) > 0 ? module.relays[0].target_tags : [] + ) } module "ops" { - source = "../../modules/google-cloud-ops" + source = "../../modules/google-cloud/ops" project_id = module.google-cloud-project.project.project_id diff --git a/terraform/environments/production/portal.tf b/terraform/environments/production/portal.tf new file mode 100644 index 000000000..84b1287fd --- /dev/null +++ b/terraform/environments/production/portal.tf @@ -0,0 +1,628 @@ + +# Generate secrets +resource "random_password" "erlang_cluster_cookie" { + length = 64 + special = false +} + +resource "random_password" "tokens_key_base" { + length = 64 + special = false +} + +resource "random_password" "tokens_salt" { + length = 32 + special = false +} + +resource "random_password" "secret_key_base" { + length = 64 + special = false +} + +resource "random_password" "live_view_signing_salt" { + length = 32 + special = false +} + +resource "random_password" "cookie_signing_salt" { + length = 32 + special = false +} + +resource "random_password" "cookie_encryption_salt" { + length = 32 + special = false +} + +# Create VPC subnet for the application instances, +# we want all apps to be in the same VPC in order for Erlang clustering to work +resource "google_compute_subnetwork" "apps" { + project = module.google-cloud-project.project.project_id + + name = "app" + + stack_type = "IPV4_IPV6" + + ip_cidr_range = "10.128.0.0/20" + region = local.region + network = module.google-cloud-vpc.id + + ipv6_access_type = "EXTERNAL" + + private_ip_google_access = true +} + +# Create VPN subnet for tooling instances +resource "google_compute_subnetwork" "tools" { + project = module.google-cloud-project.project.project_id + + name = "tooling" + + stack_type = "IPV4_IPV6" + + ip_cidr_range = "10.129.0.0/20" + region = local.region + network = module.google-cloud-vpc.id + + ipv6_access_type = "EXTERNAL" + + private_ip_google_access = true +} + +# Create SQL user and database +resource "random_password" "firezone_db_password" { + length = 16 + + min_lower = 1 + min_upper = 1 + min_numeric = 1 + min_special = 1 + + lifecycle { + ignore_changes = [min_lower, min_upper, min_numeric, min_special] + } +} + +resource "google_sql_user" "firezone" { + project = module.google-cloud-project.project.project_id + + instance = module.google-cloud-sql.master_instance_name + + name = "firezone" + password = random_password.firezone_db_password.result +} + +resource "google_sql_database" "firezone" { + project = module.google-cloud-project.project.project_id + + name = "firezone" + instance = module.google-cloud-sql.master_instance_name +} + +# Create IAM users for the database for all project owners +resource "google_sql_user" "iam_users" { + for_each = toset(local.project_owners) + + project = module.google-cloud-project.project.project_id + instance = module.google-cloud-sql.master_instance_name + + type = "CLOUD_IAM_USER" + name = each.value +} + +# We can't remove passwords complete because for IAM users we still need to execute those GRANT statements +provider "postgresql" { + scheme = "gcppostgres" + host = "${module.google-cloud-project.project.project_id}:${local.region}:${module.google-cloud-sql.master_instance_name}" + port = 5432 + username = google_sql_user.firezone.name + password = random_password.firezone_db_password.result + superuser = false + sslmode = "disable" +} + +resource "postgresql_grant" "grant_select_on_all_tables_schema_to_iam_users" { + for_each = toset(local.project_owners) + + database = google_sql_database.firezone.name + + privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"] + objects = [] # ALL + object_type = "table" + schema = "public" + role = each.key + + depends_on = [ + google_sql_user.iam_users + ] +} + +resource "postgresql_grant" "grant_execute_on_all_functions_schema_to_iam_users" { + for_each = toset(local.project_owners) + + database = google_sql_database.firezone.name + + privileges = ["EXECUTE"] + objects = [] # ALL + object_type = "function" + schema = "public" + role = each.key + + depends_on = [ + google_sql_user.iam_users + ] +} + +# Create bucket for client logs +resource "google_storage_bucket" "client-logs" { + project = module.google-cloud-project.project.project_id + name = "${module.google-cloud-project.project.project_id}-client-logs" + + location = "US" + + lifecycle_rule { + condition { + age = 3 + } + + action { + type = "Delete" + } + } + + lifecycle_rule { + condition { + age = 1 + } + + action { + type = "AbortIncompleteMultipartUpload" + } + } + + logging { + log_bucket = true + log_object_prefix = "firezone.dev/clients" + } + + public_access_prevention = "enforced" + uniform_bucket_level_access = true + + lifecycle { + prevent_destroy = true + ignore_changes = [] + } +} + +locals { + cluster = { + name = "firezone" + cookie = base64encode(random_password.erlang_cluster_cookie.result) + } + + shared_application_environment_variables = [ + # Database + { + name = "DATABASE_HOST" + value = module.google-cloud-sql.master_instance_ip_address + }, + { + name = "DATABASE_NAME" + value = google_sql_database.firezone.name + }, + { + name = "DATABASE_USER" + value = google_sql_user.firezone.name + }, + { + name = "DATABASE_PASSWORD" + value = google_sql_user.firezone.password + }, + # Secrets + { + name = "SECRET_KEY_BASE" + value = random_password.secret_key_base.result + }, + { + name = "TOKENS_KEY_BASE" + value = base64encode(random_password.tokens_key_base.result) + }, + { + name = "TOKENS_SALT" + value = base64encode(random_password.tokens_salt.result) + }, + { + name = "SECRET_KEY_BASE" + value = base64encode(random_password.secret_key_base.result) + }, + { + name = "LIVE_VIEW_SIGNING_SALT" + value = base64encode(random_password.live_view_signing_salt.result) + }, + { + name = "COOKIE_SIGNING_SALT" + value = base64encode(random_password.cookie_signing_salt.result) + }, + { + name = "COOKIE_ENCRYPTION_SALT" + value = base64encode(random_password.cookie_encryption_salt.result) + }, + # Erlang + { + name = "ERLANG_DISTRIBUTION_PORT" + value = "9000" + }, + { + name = "CLUSTER_NAME" + value = local.cluster.name + }, + { + name = "ERLANG_CLUSTER_ADAPTER" + value = "Elixir.Domain.Cluster.GoogleComputeLabelsStrategy" + }, + { + name = "ERLANG_CLUSTER_ADAPTER_CONFIG" + value = jsonencode({ + project_id = module.google-cloud-project.project.project_id + cluster_name = local.cluster.name + cluster_name_label = "cluster_name" + cluster_version_label = "cluster_version" + cluster_version = split(".", var.image_tag)[0] + node_name_label = "application" + polling_interval_ms = 7000 + }) + }, + { + name = "RELEASE_COOKIE" + value = local.cluster.cookie + }, + # Auth + { + name = "AUTH_PROVIDER_ADAPTERS" + value = "email,openid_connect,google_workspace,token,microsoft_entra,okta" + }, + # Registry from which Docker install scripts pull from + { + name = "DOCKER_REGISTRY" + value = "ghcr.io/firezone" + }, + # Billing system + { + name = "BILLING_ENABLED" + value = "true" + }, + { + name = "STRIPE_SECRET_KEY" + value = var.stripe_secret_key + }, + { + name = "STRIPE_WEBHOOK_SIGNING_SECRET" + value = var.stripe_webhook_signing_secret + }, + { + name = "STRIPE_DEFAULT_PRICE_ID" + value = var.stripe_default_price_id + }, + # Telemetry + { + name = "INSTRUMENTATION_CLIENT_LOGS_ENABLED" + value = true + }, + { + name = "INSTRUMENTATION_CLIENT_LOGS_BUCKET" + value = google_storage_bucket.client-logs.name + }, + # Emails + { + name = "OUTBOUND_EMAIL_ADAPTER" + value = "Elixir.Swoosh.Adapters.Mailgun" + }, + { + name = "OUTBOUND_EMAIL_FROM" + value = "notifications@firezone.dev" + }, + { + name = "OUTBOUND_EMAIL_ADAPTER_OPTS" + value = jsonencode({ + api_key = var.mailgun_server_api_token, + domain = local.tld + }) + }, + # Feature Flags + { + name = "FEATURE_SIGN_UP_ENABLED" + value = false + } + ] +} + +module "domain" { + source = "../../modules/google-cloud/apps/elixir" + project_id = module.google-cloud-project.project.project_id + + compute_instance_type = "n1-standard-2" + compute_instance_region = local.region + compute_instance_availability_zones = ["${local.region}-d"] + + dns_managed_zone_name = module.google-cloud-dns.zone_name + + vpc_network = module.google-cloud-vpc.self_link + vpc_subnetwork = google_compute_subnetwork.apps.self_link + + container_registry = module.google-artifact-registry.url + + image_repo = module.google-artifact-registry.repo + image = "domain" + image_tag = var.image_tag + + scaling_horizontal_replicas = 2 + + observability_log_level = "debug" + + erlang_release_name = "firezone" + erlang_cluster_cookie = random_password.erlang_cluster_cookie.result + + application_name = "domain" + application_version = replace(var.image_tag, ".", "-") + + application_ports = [ + { + name = "http" + protocol = "TCP" + port = 4000 + + health_check = { + initial_delay_sec = 60 + + check_interval_sec = 15 + timeout_sec = 10 + healthy_threshold = 1 + unhealthy_threshold = 2 + + http_health_check = { + request_path = "/healthz" + } + } + } + ] + + application_environment_variables = concat([ + # Background Jobs + { + name = "BACKGROUND_JOBS_ENABLED" + value = "true" + }, + ], local.shared_application_environment_variables) + + application_labels = { + "cluster_name" = local.cluster.name + "cluster_version" = split(".", var.image_tag)[0] + } +} + +module "web" { + source = "../../modules/google-cloud/apps/elixir" + project_id = module.google-cloud-project.project.project_id + + compute_instance_type = "n1-standard-1" + compute_instance_region = local.region + compute_instance_availability_zones = ["${local.region}-d"] + + dns_managed_zone_name = module.google-cloud-dns.zone_name + + vpc_network = module.google-cloud-vpc.self_link + vpc_subnetwork = google_compute_subnetwork.apps.self_link + + container_registry = module.google-artifact-registry.url + + image_repo = module.google-artifact-registry.repo + image = "web" + image_tag = var.image_tag + + scaling_horizontal_replicas = 2 + + observability_log_level = "debug" + + erlang_release_name = "firezone" + erlang_cluster_cookie = random_password.erlang_cluster_cookie.result + + application_name = "web" + application_version = replace(var.image_tag, ".", "-") + + application_dns_tld = "app.${local.tld}" + + application_ports = [ + { + name = "http" + protocol = "TCP" + port = 8080 + + health_check = { + initial_delay_sec = 60 + + check_interval_sec = 15 + timeout_sec = 10 + healthy_threshold = 1 + unhealthy_threshold = 2 + + http_health_check = { + request_path = "/healthz" + } + } + } + ] + + application_environment_variables = concat([ + # Web Server + { + name = "EXTERNAL_URL" + value = "https://app.${local.tld}" + }, + { + name = "PHOENIX_HTTP_WEB_PORT" + value = "8080" + }, + { + name = "BACKGROUND_JOBS_ENABLED" + value = "false" + }, + ], local.shared_application_environment_variables) + + application_labels = { + "cluster_name" = local.cluster.name + "cluster_version" = split(".", var.image_tag)[0] + } +} + +module "api" { + source = "../../modules/google-cloud/apps/elixir" + project_id = module.google-cloud-project.project.project_id + + compute_instance_type = "n1-standard-1" + compute_instance_region = local.region + compute_instance_availability_zones = ["${local.region}-d"] + + dns_managed_zone_name = module.google-cloud-dns.zone_name + + vpc_network = module.google-cloud-vpc.self_link + vpc_subnetwork = google_compute_subnetwork.apps.self_link + + container_registry = module.google-artifact-registry.url + + image_repo = module.google-artifact-registry.repo + image = "api" + image_tag = var.image_tag + + scaling_horizontal_replicas = 2 + + observability_log_level = "debug" + + erlang_release_name = "firezone" + erlang_cluster_cookie = random_password.erlang_cluster_cookie.result + + application_name = "api" + application_version = replace(var.image_tag, ".", "-") + + application_dns_tld = "api.${local.tld}" + + application_ports = [ + { + name = "http" + protocol = "TCP" + port = 8080 + + health_check = { + initial_delay_sec = 60 + + check_interval_sec = 15 + timeout_sec = 10 + healthy_threshold = 1 + unhealthy_threshold = 3 + + http_health_check = { + request_path = "/healthz" + } + } + } + ] + + application_environment_variables = concat([ + # Web Server + { + name = "EXTERNAL_URL" + value = "https://api.${local.tld}" + }, + { + name = "PHOENIX_HTTP_API_PORT" + value = "8080" + }, + { + name = "BACKGROUND_JOBS_ENABLED" + value = "false" + }, + ], local.shared_application_environment_variables) + + application_labels = { + "cluster_name" = local.cluster.name + "cluster_version" = split(".", var.image_tag)[0] + } + + application_token_scopes = [ + "https://www.googleapis.com/auth/cloud-platform" + ] +} + +## Allow API nodes to sign URLs for Google Cloud Storage +resource "google_storage_bucket_iam_member" "sign-urls" { + bucket = google_storage_bucket.client-logs.name + role = "roles/storage.objectAdmin" + member = "serviceAccount:${module.api.service_account.email}" +} + +resource "google_project_iam_custom_role" "sign-urls" { + project = module.google-cloud-project.project.project_id + + title = "Sign URLs for Google Cloud Storage" + + role_id = "iam.sign_urls" + + permissions = [ + "iam.serviceAccounts.signBlob" + ] +} + +resource "google_project_iam_member" "sign-urls" { + project = module.google-cloud-project.project.project_id + role = "projects/${module.google-cloud-project.project.project_id}/roles/${google_project_iam_custom_role.sign-urls.role_id}" + member = "serviceAccount:${module.api.service_account.email}" +} + +# Erlang Cluster +## Allow traffic between Elixir apps for Erlang clustering +resource "google_compute_firewall" "erlang-distribution" { + project = module.google-cloud-project.project.project_id + + name = "erlang-distribution" + network = module.google-cloud-vpc.self_link + + allow { + protocol = "tcp" + ports = [4369, 9000] + } + + allow { + protocol = "udp" + ports = [4369, 9000] + } + + source_ranges = [google_compute_subnetwork.apps.ip_cidr_range] + target_tags = concat(module.web.target_tags, module.api.target_tags, module.domain.target_tags) +} + +## Allow service account to list running instances +resource "google_project_iam_custom_role" "erlang-discovery" { + project = module.google-cloud-project.project.project_id + + title = "Read list of Compute instances" + description = "This role is used for Erlang Cluster discovery and allows to list running instances." + + role_id = "compute.list_instances" + permissions = [ + "compute.instances.list", + "compute.zones.list" + ] +} + +resource "google_project_iam_member" "application" { + for_each = { + api = module.api.service_account.email + web = module.web.service_account.email + domain = module.domain.service_account.email + } + + project = module.google-cloud-project.project.project_id + + role = "projects/${module.google-cloud-project.project.project_id}/roles/${google_project_iam_custom_role.erlang-discovery.role_id}" + member = "serviceAccount:${each.value}" +} diff --git a/terraform/environments/production/relays.tf b/terraform/environments/production/relays.tf new file mode 100644 index 000000000..4cb6d7c46 --- /dev/null +++ b/terraform/environments/production/relays.tf @@ -0,0 +1,112 @@ +module "relays" { + count = var.relay_token != null ? 1 : 0 + + source = "../../modules/google-cloud/apps/relay" + project_id = module.google-cloud-project.project.project_id + + instances = { + # We may deploy more relays as more enterprise + # customers are onboarded from those regions + + # "asia-east1" = { + # cidr_range = "10.129.0.0/24" + # type = "n1-standard-1" + # replicas = 1 + # zones = ["asia-east1-a"] + # } + # + # "asia-south1" = { + # cidr_range = "10.130.0.0/24" + # type = "n1-standard-1" + # replicas = 1 + # zones = ["asia-south1-a"] + # } + # + # "australia-southeast1" = { + # cidr_range = "10.131.0.0/24" + # type = "n1-standard-1" + # replicas = 1 + # zones = ["australia-southeast1-a"] + # } + + "europe-west1" = { + cidr_range = "10.132.0.0/24" + type = "f1-micro" + replicas = 1 + zones = ["europe-west1-d"] + } + + # "me-central1" = { + # cidr_range = "10.133.0.0/24" + # type = "n2-standard-2" + # replicas = 1 + # zones = ["me-central1-a"] + # } + # + # "southamerica-east1" = { + # cidr_range = "10.134.0.0/24" + # type = "n1-standard-1" + # replicas = 1 + # zones = ["southamerica-east1-b"] + # } + + "us-central1" = { + cidr_range = "10.135.0.0/24" + type = "f1-micro" + replicas = 1 + zones = ["us-central1-b"] + } + + "us-east1" = { + cidr_range = "10.136.0.0/24" + type = "f1-micro" + replicas = 2 + zones = ["us-east1-d"] + } + + "us-west2" = { + cidr_range = "10.137.0.0/24" + type = "f1-micro" + replicas = 2 + zones = ["us-west2-b"] + } + + "europe-west2" = { + cidr_range = "10.140.0.0/24" + type = "f1-micro" + replicas = 1 + zones = ["europe-west2-c"] + } + } + + container_registry = module.google-artifact-registry.url + + image_repo = module.google-artifact-registry.repo + image = "relay" + image_tag = var.image_tag + + observability_log_level = "info,hyper=off,h2=warn,tower=warn" + + application_name = "relay" + application_version = replace(var.image_tag, ".", "-") + + health_check = { + name = "health" + protocol = "TCP" + port = 8080 + + initial_delay_sec = 60 + + check_interval_sec = 15 + timeout_sec = 10 + healthy_threshold = 1 + unhealthy_threshold = 3 + + http_health_check = { + request_path = "/healthz" + } + } + + api_url = "wss://api.${local.tld}" + token = var.relay_token +} diff --git a/terraform/environments/production/versions.tf b/terraform/environments/production/versions.tf index add556987..4e33e59ce 100644 --- a/terraform/environments/production/versions.tf +++ b/terraform/environments/production/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { random = { source = "hashicorp/random" - version = "~> 3.5" + version = "~> 3.6" } null = { diff --git a/terraform/environments/production/website.tf b/terraform/environments/production/website.tf index 68e7912e6..49a39744d 100644 --- a/terraform/environments/production/website.tf +++ b/terraform/environments/production/website.tf @@ -1,4 +1,3 @@ - resource "google_monitoring_uptime_check_config" "website-https" { project = module.google-cloud-project.project.project_id diff --git a/terraform/environments/staging/bi.tf b/terraform/environments/staging/bi.tf index 0056ef51c..da17fe49c 100644 --- a/terraform/environments/staging/bi.tf +++ b/terraform/environments/staging/bi.tf @@ -59,7 +59,7 @@ resource "postgresql_grant" "grant_execute_on_all_functions_schema_to_metabase" } module "metabase" { - source = "../../modules/metabase-app" + source = "../../modules/google-cloud/apps/metabase" project_id = module.google-cloud-project.project.project_id compute_network = module.google-cloud-vpc.id diff --git a/terraform/environments/staging/main.tf b/terraform/environments/staging/main.tf index 266c6eb60..77ca486aa 100644 --- a/terraform/environments/staging/main.tf +++ b/terraform/environments/staging/main.tf @@ -8,6 +8,7 @@ locals { "trish@firezone.dev" ] + # list of emails for users that should be able to SSH into a demo instance demo_access = [] region = "us-east1" @@ -44,7 +45,7 @@ provider "google-beta" {} # Create the project module "google-cloud-project" { - source = "../../modules/google-cloud-project" + source = "../../modules/google-cloud/project" id = "firezone-staging" name = "Staging Environment" @@ -61,7 +62,7 @@ resource "google_project_iam_binding" "project_owners" { # Grant GitHub Actions ability to write to the container registry module "google-artifact-registry" { - source = "../../modules/google-artifact-registry" + source = "../../modules/google-cloud/artifact-registry" project_id = module.google-cloud-project.project.project_id project_name = module.google-cloud-project.name @@ -76,7 +77,7 @@ module "google-artifact-registry" { # Create a VPC module "google-cloud-vpc" { - source = "../../modules/google-cloud-vpc" + source = "../../modules/google-cloud/vpc" project_id = module.google-cloud-project.project.project_id name = module.google-cloud-project.project.project_id @@ -86,14 +87,14 @@ module "google-cloud-vpc" { # Enable Google Cloud Storage for the project module "google-cloud-storage" { - source = "../../modules/google-cloud-storage" + source = "../../modules/google-cloud/storage" project_id = module.google-cloud-project.project.project_id } # Create DNS managed zone module "google-cloud-dns" { - source = "../../modules/google-cloud-dns" + source = "../../modules/google-cloud/dns" project_id = module.google-cloud-project.project.project_id @@ -103,7 +104,7 @@ module "google-cloud-dns" { # Create the Cloud SQL database module "google-cloud-sql" { - source = "../../modules/google-cloud-sql" + source = "../../modules/google-cloud/sql" project_id = module.google-cloud-project.project.project_id network = module.google-cloud-vpc.id @@ -144,723 +145,11 @@ module "google-cloud-sql" { } } -# Generate secrets -resource "random_password" "erlang_cluster_cookie" { - length = 64 - special = false -} - -resource "random_password" "tokens_key_base" { - length = 64 - special = false -} - -resource "random_password" "tokens_salt" { - length = 32 - special = false -} - -resource "random_password" "secret_key_base" { - length = 64 - special = false -} - -resource "random_password" "live_view_signing_salt" { - length = 32 - special = false -} - -resource "random_password" "cookie_signing_salt" { - length = 32 - special = false -} - -resource "random_password" "cookie_encryption_salt" { - length = 32 - special = false -} - -# Create VPC subnet for the application instances, -# we want all apps to be in the same VPC in order for Erlang clustering to work -resource "google_compute_subnetwork" "apps" { - project = module.google-cloud-project.project.project_id - - name = "app" - - stack_type = "IPV4_IPV6" - - ip_cidr_range = "10.128.0.0/20" - region = local.region - network = module.google-cloud-vpc.id - - ipv6_access_type = "EXTERNAL" - - private_ip_google_access = true -} - -# Deploy the web app to the GCE -resource "random_password" "web_db_password" { - length = 16 - - min_lower = 1 - min_upper = 1 - min_numeric = 1 - min_special = 1 - - lifecycle { - ignore_changes = [min_lower, min_upper, min_numeric, min_special] - } -} - -# TODO: raname it to "firezone" -resource "google_sql_user" "web" { - project = module.google-cloud-project.project.project_id - - instance = module.google-cloud-sql.master_instance_name - - name = "web" - password = random_password.web_db_password.result -} - -resource "google_sql_database" "firezone" { - project = module.google-cloud-project.project.project_id - - name = "firezone" - instance = module.google-cloud-sql.master_instance_name -} - -# Create IAM users for the database for all project owners -resource "google_sql_user" "iam_users" { - for_each = toset(local.project_owners) - - project = module.google-cloud-project.project.project_id - instance = module.google-cloud-sql.master_instance_name - - type = "CLOUD_IAM_USER" - name = each.value -} - -# We can't remove passwords complete because for IAM users we still need to execute those GRANT statements -provider "postgresql" { - scheme = "gcppostgres" - host = "${module.google-cloud-project.project.project_id}:${local.region}:${module.google-cloud-sql.master_instance_name}" - port = 5432 - username = google_sql_user.web.name - password = random_password.web_db_password.result - superuser = false - sslmode = "disable" -} - -resource "postgresql_grant" "grant_select_on_all_tables_schema_to_iam_users" { - for_each = toset(local.project_owners) - - database = google_sql_database.firezone.name - - privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"] - objects = [] # ALL - object_type = "table" - schema = "public" - role = each.key - - depends_on = [ - google_sql_user.iam_users - ] -} - -resource "postgresql_grant" "grant_execute_on_all_functions_schema_to_iam_users" { - for_each = toset(local.project_owners) - - database = google_sql_database.firezone.name - - privileges = ["EXECUTE"] - objects = [] # ALL - object_type = "function" - schema = "public" - role = each.key - - depends_on = [ - google_sql_user.iam_users - ] -} - -resource "google_storage_bucket" "client-logs" { - project = module.google-cloud-project.project.project_id - name = "${module.google-cloud-project.project.project_id}-client-logs" - - location = "US" - - lifecycle_rule { - condition { - age = 3 - } - - action { - type = "Delete" - } - } - - lifecycle_rule { - condition { - age = 1 - } - - action { - type = "AbortIncompleteMultipartUpload" - } - } - - logging { - log_bucket = true - log_object_prefix = "firezone.dev/clients" - } - - public_access_prevention = "enforced" - uniform_bucket_level_access = true - - lifecycle { - prevent_destroy = true - ignore_changes = [] - } -} - -locals { - cluster = { - name = "firezone" - cookie = base64encode(random_password.erlang_cluster_cookie.result) - } - - shared_application_environment_variables = [ - # Database - { - name = "DATABASE_HOST" - value = module.google-cloud-sql.master_instance_ip_address - }, - { - name = "DATABASE_NAME" - value = google_sql_database.firezone.name - }, - { - name = "DATABASE_USER" - value = google_sql_user.web.name - }, - { - name = "DATABASE_PASSWORD" - value = google_sql_user.web.password - }, - # Secrets - { - name = "SECRET_KEY_BASE" - value = random_password.secret_key_base.result - }, - { - name = "TOKENS_KEY_BASE" - value = base64encode(random_password.tokens_key_base.result) - }, - { - name = "TOKENS_SALT" - value = base64encode(random_password.tokens_salt.result) - }, - { - name = "SECRET_KEY_BASE" - value = base64encode(random_password.secret_key_base.result) - }, - { - name = "LIVE_VIEW_SIGNING_SALT" - value = base64encode(random_password.live_view_signing_salt.result) - }, - { - name = "COOKIE_SIGNING_SALT" - value = base64encode(random_password.cookie_signing_salt.result) - }, - { - name = "COOKIE_ENCRYPTION_SALT" - value = base64encode(random_password.cookie_encryption_salt.result) - }, - # Erlang - { - name = "ERLANG_DISTRIBUTION_PORT" - value = "9000" - }, - { - name = "CLUSTER_NAME" - value = local.cluster.name - }, - { - name = "ERLANG_CLUSTER_ADAPTER" - value = "Elixir.Domain.Cluster.GoogleComputeLabelsStrategy" - }, - { - name = "ERLANG_CLUSTER_ADAPTER_CONFIG" - value = jsonencode({ - project_id = module.google-cloud-project.project.project_id - cluster_name = local.cluster.name - cluster_name_label = "cluster_name" - cluster_version_label = "cluster_version" - cluster_version = split(".", var.image_tag)[0] - node_name_label = "application" - polling_interval_ms = 7000 - }) - }, - { - name = "RELEASE_COOKIE" - value = local.cluster.cookie - }, - # Auth - { - name = "AUTH_PROVIDER_ADAPTERS" - value = "email,openid_connect,google_workspace,token,microsoft_entra,okta" - }, - # Registry from which Docker install scripts pull from - { - name = "DOCKER_REGISTRY" - value = "${module.google-artifact-registry.url}/${module.google-artifact-registry.repo}" - }, - # Billing system - { - name = "BILLING_ENABLED" - value = "true" - }, - { - name = "STRIPE_SECRET_KEY" - value = var.stripe_secret_key - }, - { - name = "STRIPE_WEBHOOK_SIGNING_SECRET" - value = var.stripe_webhook_signing_secret - }, - { - name = "STRIPE_DEFAULT_PRICE_ID" - value = var.stripe_default_price_id - }, - # Telemetry - { - name = "INSTRUMENTATION_CLIENT_LOGS_ENABLED" - value = true - }, - { - name = "INSTRUMENTATION_CLIENT_LOGS_BUCKET" - value = google_storage_bucket.client-logs.name - }, - # Emails - { - name = "OUTBOUND_EMAIL_ADAPTER" - value = "Elixir.Swoosh.Adapters.Mailgun" - }, - { - name = "OUTBOUND_EMAIL_FROM" - value = "notifications@firez.one" - }, - { - name = "OUTBOUND_EMAIL_ADAPTER_OPTS" - value = jsonencode({ - api_key = var.mailgun_server_api_token, - domain = local.tld - }) - }, - # Feature Flags - { - name = "FEATURE_FLOW_ACTIVITIES_ENABLED" - value = true - }, - { - name = "FEATURE_TRAFFIC_FILTERS_ENABLED" - value = true - }, - { - name = "FEATURE_SELF_HOSTED_RELAYS_ENABLED" - value = true - }, - { - name = "FEATURE_MULTI_SITE_RESOURCES_ENABLED" - value = true - }, - { - name = "FEATURE_SIGN_UP_ENABLED" - value = false - } - ] -} - -module "domain" { - source = "../../modules/elixir-app" - project_id = module.google-cloud-project.project.project_id - - compute_instance_type = "n1-standard-2" - compute_instance_region = local.region - compute_instance_availability_zones = ["${local.region}-d"] - - dns_managed_zone_name = module.google-cloud-dns.zone_name - - vpc_network = module.google-cloud-vpc.self_link - vpc_subnetwork = google_compute_subnetwork.apps.self_link - - container_registry = module.google-artifact-registry.url - - image_repo = module.google-artifact-registry.repo - image = "domain" - image_tag = var.image_tag - - scaling_horizontal_replicas = 2 - - observability_log_level = "debug" - - erlang_release_name = "firezone" - erlang_cluster_cookie = random_password.erlang_cluster_cookie.result - - application_name = "domain" - application_version = replace(var.image_tag, ".", "-") - - application_ports = [ - { - name = "http" - protocol = "TCP" - port = 4000 - - health_check = { - initial_delay_sec = 60 - - check_interval_sec = 15 - timeout_sec = 10 - healthy_threshold = 1 - unhealthy_threshold = 2 - - http_health_check = { - request_path = "/healthz" - } - } - } - ] - - application_environment_variables = concat([ - # Background Jobs - { - name = "BACKGROUND_JOBS_ENABLED" - value = "true" - }, - ], local.shared_application_environment_variables) - - application_labels = { - "cluster_name" = local.cluster.name - "cluster_version" = split(".", var.image_tag)[0] - } -} - -module "web" { - source = "../../modules/elixir-app" - project_id = module.google-cloud-project.project.project_id - - compute_instance_type = "n1-standard-1" - compute_instance_region = local.region - compute_instance_availability_zones = ["${local.region}-d"] - - dns_managed_zone_name = module.google-cloud-dns.zone_name - - vpc_network = module.google-cloud-vpc.self_link - vpc_subnetwork = google_compute_subnetwork.apps.self_link - - container_registry = module.google-artifact-registry.url - - image_repo = module.google-artifact-registry.repo - image = "web" - image_tag = var.image_tag - - scaling_horizontal_replicas = 2 - - observability_log_level = "debug" - - erlang_release_name = "firezone" - erlang_cluster_cookie = random_password.erlang_cluster_cookie.result - - application_name = "web" - application_version = replace(var.image_tag, ".", "-") - - application_dns_tld = "app.${local.tld}" - - application_ports = [ - { - name = "http" - protocol = "TCP" - port = 8080 - - health_check = { - initial_delay_sec = 60 - - check_interval_sec = 15 - timeout_sec = 10 - healthy_threshold = 1 - unhealthy_threshold = 2 - - http_health_check = { - request_path = "/healthz" - } - } - } - ] - - application_environment_variables = concat([ - # Web Server - { - name = "EXTERNAL_URL" - value = "https://app.${local.tld}" - }, - { - name = "PHOENIX_HTTP_WEB_PORT" - value = "8080" - }, - { - name = "API_URL_OVERRIDE" - value = "wss://api.${local.tld}" - }, - { - name = "BACKGROUND_JOBS_ENABLED" - value = "false" - }, - ], local.shared_application_environment_variables) - - application_labels = { - "cluster_name" = local.cluster.name - "cluster_version" = split(".", var.image_tag)[0] - } -} - -module "api" { - source = "../../modules/elixir-app" - project_id = module.google-cloud-project.project.project_id - - compute_instance_type = "n1-standard-1" - compute_instance_region = local.region - compute_instance_availability_zones = ["${local.region}-d"] - - dns_managed_zone_name = module.google-cloud-dns.zone_name - - vpc_network = module.google-cloud-vpc.self_link - vpc_subnetwork = google_compute_subnetwork.apps.self_link - - container_registry = module.google-artifact-registry.url - - image_repo = module.google-artifact-registry.repo - image = "api" - image_tag = var.image_tag - - scaling_horizontal_replicas = 2 - - observability_log_level = "debug" - - erlang_release_name = "firezone" - erlang_cluster_cookie = random_password.erlang_cluster_cookie.result - - application_name = "api" - application_version = replace(var.image_tag, ".", "-") - - application_dns_tld = "api.${local.tld}" - - application_ports = [ - { - name = "http" - protocol = "TCP" - port = 8080 - - health_check = { - initial_delay_sec = 60 - - check_interval_sec = 15 - timeout_sec = 10 - healthy_threshold = 1 - unhealthy_threshold = 3 - - http_health_check = { - request_path = "/healthz" - } - } - } - ] - - application_environment_variables = concat([ - # Web Server - { - name = "EXTERNAL_URL" - value = "https://api.${local.tld}" - }, - { - name = "PHOENIX_HTTP_API_PORT" - value = "8080" - }, - { - name = "BACKGROUND_JOBS_ENABLED" - value = "false" - }, - ], local.shared_application_environment_variables) - - application_labels = { - "cluster_name" = local.cluster.name - "cluster_version" = split(".", var.image_tag)[0] - } - - application_token_scopes = [ - "https://www.googleapis.com/auth/cloud-platform" - ] -} - -## Allow API nodes to sign URLs for Google Cloud Storage -resource "google_storage_bucket_iam_member" "sign-urls" { - bucket = google_storage_bucket.client-logs.name - role = "roles/storage.objectAdmin" - member = "serviceAccount:${module.api.service_account.email}" -} - -resource "google_project_iam_custom_role" "sign-urls" { - project = module.google-cloud-project.project.project_id - - title = "Sign URLs for Google Cloud Storage" - - role_id = "iam.sign_urls" - - permissions = [ - "iam.serviceAccounts.signBlob" - ] -} - -resource "google_project_iam_member" "sign-urls" { - project = module.google-cloud-project.project.project_id - role = "projects/${module.google-cloud-project.project.project_id}/roles/${google_project_iam_custom_role.sign-urls.role_id}" - member = "serviceAccount:${module.api.service_account.email}" -} - -# Erlang Cluster -## Allow traffic between Elixir apps for Erlang clustering -resource "google_compute_firewall" "erlang-distribution" { - project = module.google-cloud-project.project.project_id - - name = "erlang-distribution" - network = module.google-cloud-vpc.self_link - - allow { - protocol = "tcp" - ports = [4369, 9000] - } - - allow { - protocol = "udp" - ports = [4369, 9000] - } - - source_ranges = [google_compute_subnetwork.apps.ip_cidr_range] - target_tags = concat(module.web.target_tags, module.api.target_tags, module.domain.target_tags) -} - -## Allow service account to list running instances -resource "google_project_iam_custom_role" "erlang-discovery" { - project = module.google-cloud-project.project.project_id - - title = "Read list of Compute instances" - description = "This role is used for Erlang Cluster discovery and allows to list running instances." - - role_id = "compute.list_instances" - permissions = [ - "compute.instances.list", - "compute.zones.list" - ] -} - -resource "google_project_iam_member" "application" { - for_each = { - api = module.api.service_account.email - web = module.web.service_account.email - domain = module.domain.service_account.email - } - - project = module.google-cloud-project.project.project_id - - role = "projects/${module.google-cloud-project.project.project_id}/roles/${google_project_iam_custom_role.erlang-discovery.role_id}" - member = "serviceAccount:${each.value}" -} - -# Deploy relays -module "relays" { - count = var.relay_token != null ? 1 : 0 - - source = "../../modules/relay-app" - project_id = module.google-cloud-project.project.project_id - - instances = { - "australia-southeast1" = { - cidr_range = "10.131.0.0/24" - - type = "f1-micro" - replicas = 1 - zones = ["australia-southeast1-a"] - } - - "southamerica-east1" = { - cidr_range = "10.134.0.0/24" - - type = "f1-micro" - replicas = 1 - zones = ["southamerica-east1-b"] - } - - "us-east1" = { - cidr_range = "10.136.0.0/24" - - type = "f1-micro" - replicas = 1 - zones = ["us-east1-d"] - } - - "us-west2" = { - cidr_range = "10.137.0.0/24" - - type = "f1-micro" - replicas = 1 - zones = ["us-west2-b"] - } - - "us-central1" = { - cidr_range = "10.135.0.0/24" - - type = "f1-micro" - replicas = 1 - zones = ["us-central1-b"] - } - } - - container_registry = module.google-artifact-registry.url - - image_repo = module.google-artifact-registry.repo - image = "relay" - image_tag = var.image_tag - - observability_log_level = "debug,firezone_relay=trace,hyper=off,h2=warn,tower=warn,wire=trace" - - application_name = "relay" - application_version = replace(var.image_tag, ".", "-") - - health_check = { - name = "health" - protocol = "TCP" - port = 8080 - - initial_delay_sec = 60 - - check_interval_sec = 15 - timeout_sec = 10 - healthy_threshold = 1 - unhealthy_threshold = 3 - - http_health_check = { - request_path = "/healthz" - } - } - - api_url = "wss://api.${local.tld}" - token = var.relay_token -} - # Enable SSH on staging resource "google_compute_firewall" "ssh-ipv4" { project = module.google-cloud-project.project.project_id - name = "staging-ssh-ipv4" + name = "ssh-ipv4" network = module.google-cloud-vpc.self_link allow { @@ -879,13 +168,18 @@ resource "google_compute_firewall" "ssh-ipv4" { } source_ranges = ["0.0.0.0/0"] - target_tags = concat(module.web.target_tags, module.api.target_tags, module.domain.target_tags) + target_tags = concat( + module.web.target_tags, + module.api.target_tags, + module.domain.target_tags, + length(module.relays) > 0 ? module.relays[0].target_tags : [] + ) } resource "google_compute_firewall" "ssh-ipv6" { project = module.google-cloud-project.project.project_id - name = "staging-ssh-ipv6" + name = "ssh-ipv6" network = module.google-cloud-vpc.self_link allow { @@ -904,72 +198,22 @@ resource "google_compute_firewall" "ssh-ipv6" { } source_ranges = ["::/0"] - target_tags = concat(module.web.target_tags, module.api.target_tags, module.domain.target_tags) -} - -resource "google_compute_firewall" "relays-ssh-ipv4" { - count = length(module.relays) > 0 ? 1 : 0 - - project = module.google-cloud-project.project.project_id - - name = "staging-relays-ssh-ipv4" - network = module.relays[0].network - - allow { - protocol = "tcp" - ports = [22] - } - - allow { - protocol = "udp" - ports = [22] - } - - allow { - protocol = "sctp" - ports = [22] - } - - source_ranges = ["0.0.0.0/0"] - target_tags = module.relays[0].target_tags -} - -resource "google_compute_firewall" "relays-ssh-ipv6" { - count = length(module.relays) > 0 ? 1 : 0 - - project = module.google-cloud-project.project.project_id - - name = "staging-relays-ssh-ipv6" - network = module.relays[0].network - - allow { - protocol = "tcp" - ports = [22] - } - - allow { - protocol = "udp" - ports = [22] - } - - allow { - protocol = "sctp" - ports = [22] - } - - source_ranges = ["::/0"] - target_tags = module.relays[0].target_tags + target_tags = concat( + module.web.target_tags, + module.api.target_tags, + module.domain.target_tags, + length(module.relays) > 0 ? module.relays[0].target_tags : [] + ) } module "ops" { - source = "../../modules/google-cloud-ops" + source = "../../modules/google-cloud/ops" project_id = module.google-cloud-project.project.project_id slack_alerts_auth_token = var.slack_alerts_auth_token slack_alerts_channel = var.slack_alerts_channel - api_host = module.api.host - web_host = module.web.host - domain_host = module.domain.host + api_host = module.api.host + web_host = module.web.host } diff --git a/terraform/environments/staging/portal.tf b/terraform/environments/staging/portal.tf new file mode 100644 index 000000000..c8ed9fe36 --- /dev/null +++ b/terraform/environments/staging/portal.tf @@ -0,0 +1,631 @@ + +# Generate secrets +resource "random_password" "erlang_cluster_cookie" { + length = 64 + special = false +} + +resource "random_password" "tokens_key_base" { + length = 64 + special = false +} + +resource "random_password" "tokens_salt" { + length = 32 + special = false +} + +resource "random_password" "secret_key_base" { + length = 64 + special = false +} + +resource "random_password" "live_view_signing_salt" { + length = 32 + special = false +} + +resource "random_password" "cookie_signing_salt" { + length = 32 + special = false +} + +resource "random_password" "cookie_encryption_salt" { + length = 32 + special = false +} + +# Create VPC subnet for the application instances, +# we want all apps to be in the same VPC in order for Erlang clustering to work +resource "google_compute_subnetwork" "apps" { + project = module.google-cloud-project.project.project_id + + name = "app" + + stack_type = "IPV4_IPV6" + + ip_cidr_range = "10.128.0.0/20" + region = local.region + network = module.google-cloud-vpc.id + + ipv6_access_type = "EXTERNAL" + + private_ip_google_access = true +} + +# Deploy the web app to the GCE +resource "random_password" "web_db_password" { + length = 16 + + min_lower = 1 + min_upper = 1 + min_numeric = 1 + min_special = 1 + + lifecycle { + ignore_changes = [min_lower, min_upper, min_numeric, min_special] + } +} + +# TODO: raname it to "firezone" +resource "google_sql_user" "web" { + project = module.google-cloud-project.project.project_id + + instance = module.google-cloud-sql.master_instance_name + + name = "web" + password = random_password.web_db_password.result +} + +resource "google_sql_database" "firezone" { + project = module.google-cloud-project.project.project_id + + name = "firezone" + instance = module.google-cloud-sql.master_instance_name +} + +# Create IAM users for the database for all project owners +resource "google_sql_user" "iam_users" { + for_each = toset(local.project_owners) + + project = module.google-cloud-project.project.project_id + instance = module.google-cloud-sql.master_instance_name + + type = "CLOUD_IAM_USER" + name = each.value +} + +# We can't remove passwords complete because for IAM users we still need to execute those GRANT statements +provider "postgresql" { + scheme = "gcppostgres" + host = "${module.google-cloud-project.project.project_id}:${local.region}:${module.google-cloud-sql.master_instance_name}" + port = 5432 + username = google_sql_user.web.name + password = random_password.web_db_password.result + superuser = false + sslmode = "disable" +} + +resource "postgresql_grant" "grant_select_on_all_tables_schema_to_iam_users" { + for_each = toset(local.project_owners) + + database = google_sql_database.firezone.name + + privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"] + objects = [] # ALL + object_type = "table" + schema = "public" + role = each.key + + depends_on = [ + google_sql_user.iam_users + ] +} + +resource "postgresql_grant" "grant_execute_on_all_functions_schema_to_iam_users" { + for_each = toset(local.project_owners) + + database = google_sql_database.firezone.name + + privileges = ["EXECUTE"] + objects = [] # ALL + object_type = "function" + schema = "public" + role = each.key + + depends_on = [ + google_sql_user.iam_users + ] +} + +resource "google_storage_bucket" "client-logs" { + project = module.google-cloud-project.project.project_id + name = "${module.google-cloud-project.project.project_id}-client-logs" + + location = "US" + + lifecycle_rule { + condition { + age = 3 + } + + action { + type = "Delete" + } + } + + lifecycle_rule { + condition { + age = 1 + } + + action { + type = "AbortIncompleteMultipartUpload" + } + } + + logging { + log_bucket = true + log_object_prefix = "firezone.dev/clients" + } + + public_access_prevention = "enforced" + uniform_bucket_level_access = true + + lifecycle { + prevent_destroy = true + ignore_changes = [] + } +} + +locals { + cluster = { + name = "firezone" + cookie = base64encode(random_password.erlang_cluster_cookie.result) + } + + shared_application_environment_variables = [ + # Database + { + name = "DATABASE_HOST" + value = module.google-cloud-sql.master_instance_ip_address + }, + { + name = "DATABASE_NAME" + value = google_sql_database.firezone.name + }, + { + name = "DATABASE_USER" + value = google_sql_user.web.name + }, + { + name = "DATABASE_PASSWORD" + value = google_sql_user.web.password + }, + # Secrets + { + name = "SECRET_KEY_BASE" + value = random_password.secret_key_base.result + }, + { + name = "TOKENS_KEY_BASE" + value = base64encode(random_password.tokens_key_base.result) + }, + { + name = "TOKENS_SALT" + value = base64encode(random_password.tokens_salt.result) + }, + { + name = "SECRET_KEY_BASE" + value = base64encode(random_password.secret_key_base.result) + }, + { + name = "LIVE_VIEW_SIGNING_SALT" + value = base64encode(random_password.live_view_signing_salt.result) + }, + { + name = "COOKIE_SIGNING_SALT" + value = base64encode(random_password.cookie_signing_salt.result) + }, + { + name = "COOKIE_ENCRYPTION_SALT" + value = base64encode(random_password.cookie_encryption_salt.result) + }, + # Erlang + { + name = "ERLANG_DISTRIBUTION_PORT" + value = "9000" + }, + { + name = "CLUSTER_NAME" + value = local.cluster.name + }, + { + name = "ERLANG_CLUSTER_ADAPTER" + value = "Elixir.Domain.Cluster.GoogleComputeLabelsStrategy" + }, + { + name = "ERLANG_CLUSTER_ADAPTER_CONFIG" + value = jsonencode({ + project_id = module.google-cloud-project.project.project_id + cluster_name = local.cluster.name + cluster_name_label = "cluster_name" + cluster_version_label = "cluster_version" + cluster_version = split(".", var.image_tag)[0] + node_name_label = "application" + polling_interval_ms = 7000 + }) + }, + { + name = "RELEASE_COOKIE" + value = local.cluster.cookie + }, + # Auth + { + name = "AUTH_PROVIDER_ADAPTERS" + value = "email,openid_connect,google_workspace,token,microsoft_entra,okta" + }, + # Registry from which Docker install scripts pull from + { + name = "DOCKER_REGISTRY" + value = "${module.google-artifact-registry.url}/${module.google-artifact-registry.repo}" + }, + # Billing system + { + name = "BILLING_ENABLED" + value = "true" + }, + { + name = "STRIPE_SECRET_KEY" + value = var.stripe_secret_key + }, + { + name = "STRIPE_WEBHOOK_SIGNING_SECRET" + value = var.stripe_webhook_signing_secret + }, + { + name = "STRIPE_DEFAULT_PRICE_ID" + value = var.stripe_default_price_id + }, + # Telemetry + { + name = "INSTRUMENTATION_CLIENT_LOGS_ENABLED" + value = true + }, + { + name = "INSTRUMENTATION_CLIENT_LOGS_BUCKET" + value = google_storage_bucket.client-logs.name + }, + # Emails + { + name = "OUTBOUND_EMAIL_ADAPTER" + value = "Elixir.Swoosh.Adapters.Mailgun" + }, + { + name = "OUTBOUND_EMAIL_FROM" + value = "notifications@firez.one" + }, + { + name = "OUTBOUND_EMAIL_ADAPTER_OPTS" + value = jsonencode({ + api_key = var.mailgun_server_api_token, + domain = local.tld + }) + }, + # Feature Flags + { + name = "FEATURE_FLOW_ACTIVITIES_ENABLED" + value = true + }, + { + name = "FEATURE_TRAFFIC_FILTERS_ENABLED" + value = true + }, + { + name = "FEATURE_SELF_HOSTED_RELAYS_ENABLED" + value = true + }, + { + name = "FEATURE_MULTI_SITE_RESOURCES_ENABLED" + value = true + }, + { + name = "FEATURE_SIGN_UP_ENABLED" + value = false + } + ] +} + +module "domain" { + source = "../../modules/google-cloud/apps/elixir" + project_id = module.google-cloud-project.project.project_id + + compute_instance_type = "n1-standard-2" + compute_instance_region = local.region + compute_instance_availability_zones = ["${local.region}-d"] + + dns_managed_zone_name = module.google-cloud-dns.zone_name + + vpc_network = module.google-cloud-vpc.self_link + vpc_subnetwork = google_compute_subnetwork.apps.self_link + + container_registry = module.google-artifact-registry.url + + image_repo = module.google-artifact-registry.repo + image = "domain" + image_tag = var.image_tag + + scaling_horizontal_replicas = 2 + + observability_log_level = "debug" + + erlang_release_name = "firezone" + erlang_cluster_cookie = random_password.erlang_cluster_cookie.result + + application_name = "domain" + application_version = replace(var.image_tag, ".", "-") + + application_ports = [ + { + name = "http" + protocol = "TCP" + port = 4000 + + health_check = { + initial_delay_sec = 60 + + check_interval_sec = 15 + timeout_sec = 10 + healthy_threshold = 1 + unhealthy_threshold = 2 + + http_health_check = { + request_path = "/healthz" + } + } + } + ] + + application_environment_variables = concat([ + # Background Jobs + { + name = "BACKGROUND_JOBS_ENABLED" + value = "true" + }, + ], local.shared_application_environment_variables) + + application_labels = { + "cluster_name" = local.cluster.name + "cluster_version" = split(".", var.image_tag)[0] + } +} + +module "web" { + source = "../../modules/google-cloud/apps/elixir" + project_id = module.google-cloud-project.project.project_id + + compute_instance_type = "n1-standard-1" + compute_instance_region = local.region + compute_instance_availability_zones = ["${local.region}-d"] + + dns_managed_zone_name = module.google-cloud-dns.zone_name + + vpc_network = module.google-cloud-vpc.self_link + vpc_subnetwork = google_compute_subnetwork.apps.self_link + + container_registry = module.google-artifact-registry.url + + image_repo = module.google-artifact-registry.repo + image = "web" + image_tag = var.image_tag + + scaling_horizontal_replicas = 2 + + observability_log_level = "debug" + + erlang_release_name = "firezone" + erlang_cluster_cookie = random_password.erlang_cluster_cookie.result + + application_name = "web" + application_version = replace(var.image_tag, ".", "-") + + application_dns_tld = "app.${local.tld}" + + application_ports = [ + { + name = "http" + protocol = "TCP" + port = 8080 + + health_check = { + initial_delay_sec = 60 + + check_interval_sec = 15 + timeout_sec = 10 + healthy_threshold = 1 + unhealthy_threshold = 2 + + http_health_check = { + request_path = "/healthz" + } + } + } + ] + + application_environment_variables = concat([ + # Web Server + { + name = "EXTERNAL_URL" + value = "https://app.${local.tld}" + }, + { + name = "PHOENIX_HTTP_WEB_PORT" + value = "8080" + }, + { + name = "API_URL_OVERRIDE" + value = "wss://api.${local.tld}" + }, + { + name = "BACKGROUND_JOBS_ENABLED" + value = "false" + }, + ], local.shared_application_environment_variables) + + application_labels = { + "cluster_name" = local.cluster.name + "cluster_version" = split(".", var.image_tag)[0] + } +} + +module "api" { + source = "../../modules/google-cloud/apps/elixir" + project_id = module.google-cloud-project.project.project_id + + compute_instance_type = "n1-standard-1" + compute_instance_region = local.region + compute_instance_availability_zones = ["${local.region}-d"] + + dns_managed_zone_name = module.google-cloud-dns.zone_name + + vpc_network = module.google-cloud-vpc.self_link + vpc_subnetwork = google_compute_subnetwork.apps.self_link + + container_registry = module.google-artifact-registry.url + + image_repo = module.google-artifact-registry.repo + image = "api" + image_tag = var.image_tag + + scaling_horizontal_replicas = 2 + + observability_log_level = "debug" + + erlang_release_name = "firezone" + erlang_cluster_cookie = random_password.erlang_cluster_cookie.result + + application_name = "api" + application_version = replace(var.image_tag, ".", "-") + + application_dns_tld = "api.${local.tld}" + + application_ports = [ + { + name = "http" + protocol = "TCP" + port = 8080 + + health_check = { + initial_delay_sec = 60 + + check_interval_sec = 15 + timeout_sec = 10 + healthy_threshold = 1 + unhealthy_threshold = 3 + + http_health_check = { + request_path = "/healthz" + } + } + } + ] + + application_environment_variables = concat([ + # Web Server + { + name = "EXTERNAL_URL" + value = "https://api.${local.tld}" + }, + { + name = "PHOENIX_HTTP_API_PORT" + value = "8080" + }, + { + name = "BACKGROUND_JOBS_ENABLED" + value = "false" + }, + ], local.shared_application_environment_variables) + + application_labels = { + "cluster_name" = local.cluster.name + "cluster_version" = split(".", var.image_tag)[0] + } + + application_token_scopes = [ + "https://www.googleapis.com/auth/cloud-platform" + ] +} + +## Allow API nodes to sign URLs for Google Cloud Storage +resource "google_storage_bucket_iam_member" "sign-urls" { + bucket = google_storage_bucket.client-logs.name + role = "roles/storage.objectAdmin" + member = "serviceAccount:${module.api.service_account.email}" +} + +resource "google_project_iam_custom_role" "sign-urls" { + project = module.google-cloud-project.project.project_id + + title = "Sign URLs for Google Cloud Storage" + + role_id = "iam.sign_urls" + + permissions = [ + "iam.serviceAccounts.signBlob" + ] +} + +resource "google_project_iam_member" "sign-urls" { + project = module.google-cloud-project.project.project_id + role = "projects/${module.google-cloud-project.project.project_id}/roles/${google_project_iam_custom_role.sign-urls.role_id}" + member = "serviceAccount:${module.api.service_account.email}" +} + +# Erlang Cluster +## Allow traffic between Elixir apps for Erlang clustering +resource "google_compute_firewall" "erlang-distribution" { + project = module.google-cloud-project.project.project_id + + name = "erlang-distribution" + network = module.google-cloud-vpc.self_link + + allow { + protocol = "tcp" + ports = [4369, 9000] + } + + allow { + protocol = "udp" + ports = [4369, 9000] + } + + source_ranges = [google_compute_subnetwork.apps.ip_cidr_range] + target_tags = concat(module.web.target_tags, module.api.target_tags, module.domain.target_tags) +} + +## Allow service account to list running instances +resource "google_project_iam_custom_role" "erlang-discovery" { + project = module.google-cloud-project.project.project_id + + title = "Read list of Compute instances" + description = "This role is used for Erlang Cluster discovery and allows to list running instances." + + role_id = "compute.list_instances" + permissions = [ + "compute.instances.list", + "compute.zones.list" + ] +} + +resource "google_project_iam_member" "application" { + for_each = { + api = module.api.service_account.email + web = module.web.service_account.email + domain = module.domain.service_account.email + } + + project = module.google-cloud-project.project.project_id + + role = "projects/${module.google-cloud-project.project.project_id}/roles/${google_project_iam_custom_role.erlang-discovery.role_id}" + member = "serviceAccount:${each.value}" +} diff --git a/terraform/environments/staging/relays.tf b/terraform/environments/staging/relays.tf new file mode 100644 index 000000000..8f58f5224 --- /dev/null +++ b/terraform/environments/staging/relays.tf @@ -0,0 +1,79 @@ +module "relays" { + count = var.relay_token != null ? 1 : 0 + + source = "../../modules/google-cloud/apps/relay" + project_id = module.google-cloud-project.project.project_id + + instances = { + "australia-southeast1" = { + cidr_range = "10.131.0.0/24" + + type = "f1-micro" + replicas = 1 + zones = ["australia-southeast1-a"] + } + + "southamerica-east1" = { + cidr_range = "10.134.0.0/24" + + type = "f1-micro" + replicas = 1 + zones = ["southamerica-east1-b"] + } + + "us-east1" = { + cidr_range = "10.136.0.0/24" + + type = "f1-micro" + replicas = 1 + zones = ["us-east1-d"] + } + + "us-west2" = { + cidr_range = "10.137.0.0/24" + + type = "f1-micro" + replicas = 1 + zones = ["us-west2-b"] + } + + "us-central1" = { + cidr_range = "10.135.0.0/24" + + type = "f1-micro" + replicas = 1 + zones = ["us-central1-b"] + } + } + + container_registry = module.google-artifact-registry.url + + image_repo = module.google-artifact-registry.repo + image = "relay" + image_tag = var.image_tag + + observability_log_level = "debug,firezone_relay=trace,hyper=off,h2=warn,tower=warn,wire=trace" + + application_name = "relay" + application_version = replace(var.image_tag, ".", "-") + + health_check = { + name = "health" + protocol = "TCP" + port = 8080 + + initial_delay_sec = 60 + + check_interval_sec = 15 + timeout_sec = 10 + healthy_threshold = 1 + unhealthy_threshold = 3 + + http_health_check = { + request_path = "/healthz" + } + } + + api_url = "wss://api.${local.tld}" + token = var.relay_token +} diff --git a/terraform/environments/staging/versions.tf b/terraform/environments/staging/versions.tf index add556987..80727a80f 100644 --- a/terraform/environments/staging/versions.tf +++ b/terraform/environments/staging/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = "1.6.6" + required_version = "1.7.5" required_providers { random = { source = "hashicorp/random" - version = "~> 3.5" + version = "~> 3.6" } null = { diff --git a/terraform/modules/elixir-app/dns.tf b/terraform/modules/google-cloud/apps/elixir/dns.tf similarity index 100% rename from terraform/modules/elixir-app/dns.tf rename to terraform/modules/google-cloud/apps/elixir/dns.tf diff --git a/terraform/modules/elixir-app/iam.tf b/terraform/modules/google-cloud/apps/elixir/iam.tf similarity index 100% rename from terraform/modules/elixir-app/iam.tf rename to terraform/modules/google-cloud/apps/elixir/iam.tf diff --git a/terraform/modules/elixir-app/main.tf b/terraform/modules/google-cloud/apps/elixir/main.tf similarity index 100% rename from terraform/modules/elixir-app/main.tf rename to terraform/modules/google-cloud/apps/elixir/main.tf diff --git a/terraform/modules/elixir-app/network.tf b/terraform/modules/google-cloud/apps/elixir/network.tf similarity index 100% rename from terraform/modules/elixir-app/network.tf rename to terraform/modules/google-cloud/apps/elixir/network.tf diff --git a/terraform/modules/elixir-app/outputs.tf b/terraform/modules/google-cloud/apps/elixir/outputs.tf similarity index 100% rename from terraform/modules/elixir-app/outputs.tf rename to terraform/modules/google-cloud/apps/elixir/outputs.tf diff --git a/terraform/modules/elixir-app/services.tf b/terraform/modules/google-cloud/apps/elixir/services.tf similarity index 100% rename from terraform/modules/elixir-app/services.tf rename to terraform/modules/google-cloud/apps/elixir/services.tf diff --git a/terraform/modules/elixir-app/templates/cloud-init.yaml b/terraform/modules/google-cloud/apps/elixir/templates/cloud-init.yaml similarity index 100% rename from terraform/modules/elixir-app/templates/cloud-init.yaml rename to terraform/modules/google-cloud/apps/elixir/templates/cloud-init.yaml diff --git a/terraform/modules/elixir-app/variables.tf b/terraform/modules/google-cloud/apps/elixir/variables.tf similarity index 100% rename from terraform/modules/elixir-app/variables.tf rename to terraform/modules/google-cloud/apps/elixir/variables.tf diff --git a/terraform/modules/gateway-google-cloud-compute/iam.tf b/terraform/modules/google-cloud/apps/gateway-region-instance-group/iam.tf similarity index 100% rename from terraform/modules/gateway-google-cloud-compute/iam.tf rename to terraform/modules/google-cloud/apps/gateway-region-instance-group/iam.tf diff --git a/terraform/modules/gateway-google-cloud-compute/main.tf b/terraform/modules/google-cloud/apps/gateway-region-instance-group/main.tf similarity index 100% rename from terraform/modules/gateway-google-cloud-compute/main.tf rename to terraform/modules/google-cloud/apps/gateway-region-instance-group/main.tf diff --git a/terraform/modules/gateway-google-cloud-compute/outputs.tf b/terraform/modules/google-cloud/apps/gateway-region-instance-group/outputs.tf similarity index 100% rename from terraform/modules/gateway-google-cloud-compute/outputs.tf rename to terraform/modules/google-cloud/apps/gateway-region-instance-group/outputs.tf diff --git a/terraform/modules/gateway-google-cloud-compute/services.tf b/terraform/modules/google-cloud/apps/gateway-region-instance-group/services.tf similarity index 100% rename from terraform/modules/gateway-google-cloud-compute/services.tf rename to terraform/modules/google-cloud/apps/gateway-region-instance-group/services.tf diff --git a/terraform/modules/gateway-google-cloud-compute/templates/cloud-init.yaml b/terraform/modules/google-cloud/apps/gateway-region-instance-group/templates/cloud-init.yaml similarity index 100% rename from terraform/modules/gateway-google-cloud-compute/templates/cloud-init.yaml rename to terraform/modules/google-cloud/apps/gateway-region-instance-group/templates/cloud-init.yaml diff --git a/terraform/modules/gateway-google-cloud-compute/variables.tf b/terraform/modules/google-cloud/apps/gateway-region-instance-group/variables.tf similarity index 100% rename from terraform/modules/gateway-google-cloud-compute/variables.tf rename to terraform/modules/google-cloud/apps/gateway-region-instance-group/variables.tf diff --git a/terraform/modules/metabase-app/iam.tf b/terraform/modules/google-cloud/apps/metabase/iam.tf similarity index 100% rename from terraform/modules/metabase-app/iam.tf rename to terraform/modules/google-cloud/apps/metabase/iam.tf diff --git a/terraform/modules/metabase-app/main.tf b/terraform/modules/google-cloud/apps/metabase/main.tf similarity index 100% rename from terraform/modules/metabase-app/main.tf rename to terraform/modules/google-cloud/apps/metabase/main.tf diff --git a/terraform/modules/metabase-app/outputs.tf b/terraform/modules/google-cloud/apps/metabase/outputs.tf similarity index 100% rename from terraform/modules/metabase-app/outputs.tf rename to terraform/modules/google-cloud/apps/metabase/outputs.tf diff --git a/terraform/modules/metabase-app/services.tf b/terraform/modules/google-cloud/apps/metabase/services.tf similarity index 100% rename from terraform/modules/metabase-app/services.tf rename to terraform/modules/google-cloud/apps/metabase/services.tf diff --git a/terraform/modules/metabase-app/variables.tf b/terraform/modules/google-cloud/apps/metabase/variables.tf similarity index 100% rename from terraform/modules/metabase-app/variables.tf rename to terraform/modules/google-cloud/apps/metabase/variables.tf diff --git a/terraform/modules/relay-app/main.tf b/terraform/modules/google-cloud/apps/relay/main.tf similarity index 100% rename from terraform/modules/relay-app/main.tf rename to terraform/modules/google-cloud/apps/relay/main.tf diff --git a/terraform/modules/relay-app/outputs.tf b/terraform/modules/google-cloud/apps/relay/outputs.tf similarity index 100% rename from terraform/modules/relay-app/outputs.tf rename to terraform/modules/google-cloud/apps/relay/outputs.tf diff --git a/terraform/modules/relay-app/services.tf b/terraform/modules/google-cloud/apps/relay/services.tf similarity index 100% rename from terraform/modules/relay-app/services.tf rename to terraform/modules/google-cloud/apps/relay/services.tf diff --git a/terraform/modules/relay-app/templates/cloud-init.yaml b/terraform/modules/google-cloud/apps/relay/templates/cloud-init.yaml similarity index 100% rename from terraform/modules/relay-app/templates/cloud-init.yaml rename to terraform/modules/google-cloud/apps/relay/templates/cloud-init.yaml diff --git a/terraform/modules/relay-app/variables.tf b/terraform/modules/google-cloud/apps/relay/variables.tf similarity index 100% rename from terraform/modules/relay-app/variables.tf rename to terraform/modules/google-cloud/apps/relay/variables.tf diff --git a/terraform/modules/google-artifact-registry/main.tf b/terraform/modules/google-cloud/artifact-registry/main.tf similarity index 100% rename from terraform/modules/google-artifact-registry/main.tf rename to terraform/modules/google-cloud/artifact-registry/main.tf diff --git a/terraform/modules/google-artifact-registry/outputs.tf b/terraform/modules/google-cloud/artifact-registry/outputs.tf similarity index 100% rename from terraform/modules/google-artifact-registry/outputs.tf rename to terraform/modules/google-cloud/artifact-registry/outputs.tf diff --git a/terraform/modules/google-artifact-registry/variables.tf b/terraform/modules/google-cloud/artifact-registry/variables.tf similarity index 100% rename from terraform/modules/google-artifact-registry/variables.tf rename to terraform/modules/google-cloud/artifact-registry/variables.tf diff --git a/terraform/modules/google-cloud-dns/main.tf b/terraform/modules/google-cloud/dns/main.tf similarity index 100% rename from terraform/modules/google-cloud-dns/main.tf rename to terraform/modules/google-cloud/dns/main.tf diff --git a/terraform/modules/google-cloud-dns/outputs.tf b/terraform/modules/google-cloud/dns/outputs.tf similarity index 100% rename from terraform/modules/google-cloud-dns/outputs.tf rename to terraform/modules/google-cloud/dns/outputs.tf diff --git a/terraform/modules/google-cloud-dns/variables.tf b/terraform/modules/google-cloud/dns/variables.tf similarity index 100% rename from terraform/modules/google-cloud-dns/variables.tf rename to terraform/modules/google-cloud/dns/variables.tf diff --git a/terraform/modules/google-cloud-ops/main.tf b/terraform/modules/google-cloud/ops/main.tf similarity index 92% rename from terraform/modules/google-cloud-ops/main.tf rename to terraform/modules/google-cloud/ops/main.tf index 15609902b..ea9e1aa6f 100644 --- a/terraform/modules/google-cloud-ops/main.tf +++ b/terraform/modules/google-cloud/ops/main.tf @@ -33,47 +33,6 @@ locals { ) } -resource "google_monitoring_uptime_check_config" "domain-https" { - project = var.project_id - - display_name = "domain-https" - timeout = "60s" - - http_check { - port = "443" - use_ssl = true - validate_ssl = true - - request_method = "GET" - path = "/healthz" - - accepted_response_status_codes { - status_class = "STATUS_CLASS_2XX" - } - } - - monitored_resource { - type = "uptime_url" - - labels = { - project_id = var.project_id - host = var.domain_host - } - } - - content_matchers { - content = "\"ok\"" - matcher = "MATCHES_JSON_PATH" - - json_path_matcher { - json_path = "$.status" - json_matcher = "EXACT_MATCH" - } - } - - checker_type = "STATIC_IP_CHECKERS" -} - resource "google_monitoring_uptime_check_config" "api-https" { project = var.project_id diff --git a/terraform/modules/google-cloud-ops/outputs.tf b/terraform/modules/google-cloud/ops/outputs.tf similarity index 100% rename from terraform/modules/google-cloud-ops/outputs.tf rename to terraform/modules/google-cloud/ops/outputs.tf diff --git a/terraform/modules/google-cloud-ops/variables.tf b/terraform/modules/google-cloud/ops/variables.tf similarity index 92% rename from terraform/modules/google-cloud-ops/variables.tf rename to terraform/modules/google-cloud/ops/variables.tf index 775de58c0..4a105ed6c 100644 --- a/terraform/modules/google-cloud-ops/variables.tf +++ b/terraform/modules/google-cloud/ops/variables.tf @@ -25,7 +25,3 @@ variable "api_host" { variable "web_host" { type = string } - -variable "domain_host" { - type = string -} diff --git a/terraform/modules/google-cloud-project/main.tf b/terraform/modules/google-cloud/project/main.tf similarity index 100% rename from terraform/modules/google-cloud-project/main.tf rename to terraform/modules/google-cloud/project/main.tf diff --git a/terraform/modules/google-cloud-project/outputs.tf b/terraform/modules/google-cloud/project/outputs.tf similarity index 100% rename from terraform/modules/google-cloud-project/outputs.tf rename to terraform/modules/google-cloud/project/outputs.tf diff --git a/terraform/modules/google-cloud-project/variables.tf b/terraform/modules/google-cloud/project/variables.tf similarity index 100% rename from terraform/modules/google-cloud-project/variables.tf rename to terraform/modules/google-cloud/project/variables.tf diff --git a/terraform/modules/google-cloud-sql/main.tf b/terraform/modules/google-cloud/sql/main.tf similarity index 100% rename from terraform/modules/google-cloud-sql/main.tf rename to terraform/modules/google-cloud/sql/main.tf diff --git a/terraform/modules/google-cloud-sql/outputs.tf b/terraform/modules/google-cloud/sql/outputs.tf similarity index 100% rename from terraform/modules/google-cloud-sql/outputs.tf rename to terraform/modules/google-cloud/sql/outputs.tf diff --git a/terraform/modules/google-cloud-sql/variables.tf b/terraform/modules/google-cloud/sql/variables.tf similarity index 100% rename from terraform/modules/google-cloud-sql/variables.tf rename to terraform/modules/google-cloud/sql/variables.tf diff --git a/terraform/modules/google-cloud-storage/main.tf b/terraform/modules/google-cloud/storage/main.tf similarity index 100% rename from terraform/modules/google-cloud-storage/main.tf rename to terraform/modules/google-cloud/storage/main.tf diff --git a/terraform/modules/google-cloud-storage/variables.tf b/terraform/modules/google-cloud/storage/variables.tf similarity index 100% rename from terraform/modules/google-cloud-storage/variables.tf rename to terraform/modules/google-cloud/storage/variables.tf diff --git a/terraform/modules/google-cloud-vpc/main.tf b/terraform/modules/google-cloud/vpc/main.tf similarity index 100% rename from terraform/modules/google-cloud-vpc/main.tf rename to terraform/modules/google-cloud/vpc/main.tf diff --git a/terraform/modules/google-cloud-vpc/outputs.tf b/terraform/modules/google-cloud/vpc/outputs.tf similarity index 100% rename from terraform/modules/google-cloud-vpc/outputs.tf rename to terraform/modules/google-cloud/vpc/outputs.tf diff --git a/terraform/modules/google-cloud-vpc/variables.tf b/terraform/modules/google-cloud/vpc/variables.tf similarity index 100% rename from terraform/modules/google-cloud-vpc/variables.tf rename to terraform/modules/google-cloud/vpc/variables.tf