From 09f25d6e0ccfd3009e24fc3e4d4cd2e5f1ed2a05 Mon Sep 17 00:00:00 2001 From: Andrew Dryga Date: Wed, 1 May 2024 10:31:18 -0600 Subject: [PATCH] refactor(infra): Install gateways without using Docker (#4839) --- .github/workflows/_build_artifacts.yml | 4 +- .github/workflows/publish.yml | 38 ++++ scripts/gateway-systemd-install.sh | 26 ++- .../production/.terraform.lock.hcl | 80 +++---- terraform/environments/production/gateways.tf | 13 +- terraform/environments/production/main.tf | 37 ++++ .../environments/staging/.terraform.lock.hcl | 116 +++++------ terraform/environments/staging/ci.tf | 12 +- terraform/environments/staging/demo.tf | 128 +++--------- .../gateway-region-instance-group/main.tf | 82 ++------ .../templates/cloud-init.yaml | 197 ++++-------------- .../variables.tf | 108 +++------- terraform/modules/google-cloud/apps/vm/iam.tf | 54 +++++ .../modules/google-cloud/apps/vm/main.tf | 138 ++++++++++++ .../modules/google-cloud/apps/vm/outputs.tf | 15 ++ .../modules/google-cloud/apps/vm/services.tf | 83 ++++++++ .../modules/google-cloud/apps/vm/variables.tf | 82 ++++++++ 17 files changed, 699 insertions(+), 514 deletions(-) create mode 100644 terraform/modules/google-cloud/apps/vm/iam.tf create mode 100644 terraform/modules/google-cloud/apps/vm/main.tf create mode 100644 terraform/modules/google-cloud/apps/vm/outputs.tf create mode 100644 terraform/modules/google-cloud/apps/vm/services.tf create mode 100644 terraform/modules/google-cloud/apps/vm/variables.tf diff --git a/.github/workflows/_build_artifacts.yml b/.github/workflows/_build_artifacts.yml index fcbe7b01f..4fa905a88 100644 --- a/.github/workflows/_build_artifacts.yml +++ b/.github/workflows/_build_artifacts.yml @@ -238,11 +238,11 @@ jobs: run: | gcloud storage cp \ ${BINARY_DEST_PATH} \ - gs://firezone-binaries/${{ matrix.name.artifact }}/${{ env.VERSION }}-${{ inputs.sha }}/${{ matrix.arch.shortname }} + gs://firezone-staging-artifacts/${{ matrix.name.artifact }}/${{ env.VERSION }}-${{ inputs.sha }}/${{ matrix.arch.shortname }} gcloud storage cp \ ${BINARY_DEST_PATH}.sha256sum.txt \ - gs://firezone-binaries/${{ matrix.name.artifact }}/${{ env.VERSION }}-${{ inputs.sha }}/${{ matrix.arch.shortname }}.sha256sum.txt + gs://firezone-staging-artifacts/${{ matrix.name.artifact }}/${{ env.VERSION }}-${{ inputs.sha }}/${{ matrix.arch.shortname }}.sha256sum.txt - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ca14692f9..8b15d0abc 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -59,3 +59,41 @@ jobs: -t ghcr.io/firezone/${image}:${MAJOR_MINOR_VERSION} \ $SOURCE_TAG done + - name: Authenticate to Google Cloud + id: auth + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: "projects/397012414171/locations/global/workloadIdentityPools/github-actions-pool/providers/github-actions" + service_account: "github-actions@github-iam-387915.iam.gserviceaccount.com" + export_environment_variables: true + create_credentials_file: true + - name: Copy Google Cloud Storage binaries to "latest" version + run: | + set -xe + + IMAGES=(firezone-gateway) + ARCHITECTURES=(x86_64 aarch64 armv7) + MAJOR_VERSION="${VERSION%%.*}" + MAJOR_MINOR_VERSION="${VERSION%.*}" + + for image in "${IMAGES[@]}"; do + for arch in "${ARCHITECTURES[@]}"; do + # Copy sha256sum.txt + gcloud storage cp \ + gs://firezone-staging-artifacts/${{ matrix.name.artifact }}/${{ env.VERSION }}-${{ inputs.sha }}/${{ matrix.arch.shortname }}.sha256sum.txt \ + gs://firezone-prod-artifacts/${image}/latest/${arch}.sha256sum.txt + + gcloud storage cp \ + gs://firezone-staging-artifacts/${{ matrix.name.artifact }}/${{ env.VERSION }}-${{ inputs.sha }}/${{ matrix.arch.shortname }}.sha256sum.txt \ + gs://firezone-prod-artifacts/${image}/${{ env.VERSION }}/${arch}.sha256sum.txt + + # Copy binaries + gcloud storage cp \ + gs://firezone-staging-artifacts/${{ matrix.name.artifact }}/${{ env.VERSION }}-${{ inputs.sha }}/${{ matrix.arch.shortname }} \ + gs://firezone-prod-artifacts/${image}/latest/${arch} + + gcloud storage cp \ + gs://firezone-staging-artifacts/${{ matrix.name.artifact }}/${{ env.VERSION }}-${{ inputs.sha }}/${{ matrix.arch.shortname }} \ + gs://firezone-prod-artifacts/${image}/${{ env.VERSION }}/${arch} + done + done diff --git a/scripts/gateway-systemd-install.sh b/scripts/gateway-systemd-install.sh index 84d069a06..fd82afeca 100755 --- a/scripts/gateway-systemd-install.sh +++ b/scripts/gateway-systemd-install.sh @@ -9,6 +9,15 @@ FIREZONE_TOKEN=${FIREZONE_TOKEN:-} FIREZONE_API_URL=${FIREZONE_API_URL:-wss://api.firezone.dev} RUST_LOG=${RUST_LOG:-str0m=warn,info} +# Can be used to download a specific version of the gateway from a custom URL +FIREZONE_VERSION=${FIREZONE_VERSION:-latest} +FIREZONE_ARTIFACT_URL=${FIREZONE_ARTIFACT_URL:-https://www.firezone.dev/dl/firezone-gateway} + +# Optional environment variables to configure logging and tracing +FIREZONE_OTLP_GRPC_ENDPOINT=${OTLP_GRPC_ENDPOINT:-} +FIREZONE_GOOGLE_CLOUD_PROJECT_ID=${GOOGLE_CLOUD_PROJECT_ID:-} +FIREZONE_LOG_FORMAT=${FIREZONE_LOG_FORMAT:-} + if [ -z "$FIREZONE_TOKEN" ]; then echo "FIREZONE_TOKEN is required" exit 1 @@ -32,9 +41,12 @@ Environment="FIREZONE_ID=$FIREZONE_ID" Environment="FIREZONE_TOKEN=$FIREZONE_TOKEN" Environment="FIREZONE_API_URL=$FIREZONE_API_URL" Environment="RUST_LOG=$RUST_LOG" +Environment="LOG_FORMAT=$FIREZONE_LOG_FORMAT" +Environment="GOOGLE_CLOUD_PROJECT_ID=$FIREZONE_GOOGLE_CLOUD_PROJECT_ID" +Environment="OTLP_GRPC_ENDPOINT=$FIREZONE_OTLP_GRPC_ENDPOINT" ExecStartPre=/usr/local/bin/firezone-gateway-init ExecStart=/usr/bin/sudo \ - --preserve-env=FIREZONE_NAME,FIREZONE_ID,FIREZONE_TOKEN,FIREZONE_API_URL,RUST_LOG \ + --preserve-env=FIREZONE_NAME,FIREZONE_ID,FIREZONE_TOKEN,FIREZONE_API_URL,RUST_LOG,LOG_FORMAT,GOOGLE_CLOUD_PROJECT_ID,OTLP_GRPC_ENDPOINT \ -u firezone \ -g firezone \ /usr/local/bin/firezone-gateway @@ -53,19 +65,20 @@ cat < /dev/null \ - && sudo apt update -y \ - && sudo apt install docker-ce docker-ce-cli containerd.io -y \ - && sudo usermod -aG docker $(whoami) \ - && sudo systemctl enable docker \ - && sudo systemctl start docker \ - && sudo docker run -d --restart always --name=httpbin -p 80:80 kennethreitz/httpbin \ - && echo ${module.metabase.internal_ip} metabase.fz >> /etc/hosts \ - && echo 127.0.0.1 host.firezone.local >> /etc/hosts \ - && curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh \ - && sudo bash add-google-cloud-ops-agent-repo.sh \ - && sudo apt-get update \ - && sudo apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install google-cloud-ops-agent + vm_name = "demo" + vm_network_tag = "app-demo" + cloud_init = < /dev/null + - sudo apt update -y + - sudo apt install docker-ce docker-ce-cli containerd.io -y + - sudo usermod -aG docker $(whoami) + - sudo systemctl enable docker + - sudo systemctl start docker + - sudo docker run -d --restart always --name=httpbin -p 80:80 kennethreitz/httpbin + - echo ${module.metabase.internal_ip} metabase.fz >> /etc/hosts + - echo 127.0.0.1 host.firezone.local >> /etc/hosts EOT - - allow_stopping_for_update = true - - service_account { - # Google recommends custom service accounts that have cloud-platform scope and permissions granted via IAM Roles. - email = google_service_account.demo.email - scopes = ["cloud-platform"] - } -} - -# Grant demo users access to demo instance -resource "google_compute_instance_iam_binding" "demo-os-login" { - project = module.google-cloud-project.project.project_id - zone = google_compute_instance.demo.zone - instance_name = google_compute_instance.demo.name - - role = "roles/compute.osAdminLogin" - members = formatlist("user:%s", local.demo_access) -} - -resource "google_compute_instance_iam_binding" "demo-instance-admin" { - project = module.google-cloud-project.project.project_id - zone = google_compute_instance.demo.zone - instance_name = google_compute_instance.demo.name - - role = "roles/compute.instanceAdmin.v1" - members = formatlist("user:%s", local.demo_access) -} - -resource "google_project_iam_binding" "demo-proejct-sa" { - project = module.google-cloud-project.project.project_id - - role = "roles/iam.serviceAccountUser" - members = formatlist("user:%s", local.demo_access) } # Create a demo DB and PostgreSQL user so that we can demo accessing the database @@ -181,5 +107,5 @@ resource "google_compute_firewall" "demo-ssh-ipv4" { } source_ranges = ["0.0.0.0/0"] - target_tags = google_compute_instance.demo.tags + target_tags = module.demo.target_tags } diff --git a/terraform/modules/google-cloud/apps/gateway-region-instance-group/main.tf b/terraform/modules/google-cloud/apps/gateway-region-instance-group/main.tf index 44f610000..3248e1082 100644 --- a/terraform/modules/google-cloud/apps/gateway-region-instance-group/main.tf +++ b/terraform/modules/google-cloud/apps/gateway-region-instance-group/main.tf @@ -4,60 +4,18 @@ data "google_compute_zones" "in_region" { } locals { - application_name = var.application_name != null ? var.application_name : var.image - application_version = var.application_version != null ? var.application_version : replace(var.image_tag, ".", "-") - - application_labels = merge({ + labels = merge({ managed_by = "terraform" - application = local.application_name - }, var.application_labels) + application = "firezone-gateway" + }, var.labels) - application_tags = ["app-${local.application_name}"] + network_tags = ["firezone-gateways-${var.name}"] google_health_check_ip_ranges = [ "130.211.0.0/22", "35.191.0.0/16", ] - environment_variables = concat([ - { - name = "LISTEN_ADDRESS_DISCOVERY_METHOD" - value = "gce_metadata" - }, - { - name = "RUST_LOG" - value = var.observability_log_level - }, - { - name = "RUST_BACKTRACE" - value = "full" - }, - { - name = "LOG_FORMAT" - value = "google-cloud" - }, - { - name = "GOOGLE_CLOUD_PROJECT_ID" - value = var.project_id - }, - { - name = "OTLP_GRPC_ENDPOINT" - value = "127.0.0.1:4317" - }, - { - name = "FIREZONE_TOKEN" - value = var.token - }, - { - name = "FIREZONE_API_URL" - value = var.api_url - }, - { - name = "FIREZONE_ENABLE_MASQUERADE" - value = "1" - } - ], var.application_environment_variables) - compute_region_zones = length(var.compute_instance_availability_zones) == 0 ? data.google_compute_zones.in_region.names : var.compute_instance_availability_zones } @@ -71,20 +29,20 @@ data "google_compute_image" "ubuntu" { resource "google_compute_instance_template" "application" { project = var.project_id - name_prefix = "${local.application_name}-" + name_prefix = "${var.name}-" - description = "This template is used to create ${local.application_name} instances." + description = "This template is used to create ${var.name} Firezone Gateway instances." machine_type = var.compute_instance_type can_ip_forward = true - tags = local.application_tags + tags = local.network_tags labels = merge({ container-vm = data.google_compute_image.ubuntu.name - version = local.application_version - }, local.application_labels) + version = var.vsn + }, local.labels) scheduling { automatic_restart = true @@ -144,9 +102,13 @@ resource "google_compute_instance_template" "application" { metadata = { user-data = templatefile("${path.module}/templates/cloud-init.yaml", { - container_name = local.application_name != null ? local.application_name : var.image - container_image = "${var.container_registry}/${var.image_repo}/${var.image}:${var.image_tag}" - container_environment = local.environment_variables + project_id = var.project_id + otlp_grpc_endpoint = "127.0.0.1:4317" + + firezone_token = var.token + firezone_api_url = var.api_url + firezone_version = var.vsn + firezone_artifact_url = "https://storage.googleapis.com/firezone-prod-artifacts/firezone-gateway" }) google-logging-enabled = "true" @@ -184,7 +146,7 @@ resource "google_compute_instance_template" "application" { resource "google_compute_health_check" "port" { project = var.project_id - name = "${local.application_name}-${var.health_check.name}" + name = "${var.name}-${var.health_check.name}" check_interval_sec = var.health_check.check_interval_sec != null ? var.health_check.check_interval_sec : 5 timeout_sec = var.health_check.timeout_sec != null ? var.health_check.timeout_sec : 5 @@ -212,9 +174,9 @@ resource "google_compute_health_check" "port" { resource "google_compute_region_instance_group_manager" "application" { project = var.project_id - name = "${local.application_name}-${var.compute_region}" + name = "${var.name}-${var.compute_region}" - base_instance_name = local.application_name + base_instance_name = var.name region = var.compute_region distribution_policy_zones = local.compute_region_zones @@ -225,7 +187,7 @@ resource "google_compute_region_instance_group_manager" "application" { wait_for_instances_status = "STABLE" version { - name = local.application_version + name = var.vsn instance_template = google_compute_instance_template.application.self_link } @@ -258,11 +220,11 @@ resource "google_compute_region_instance_group_manager" "application" { resource "google_compute_firewall" "http-health-checks" { project = var.project_id - name = "${local.application_name}-healthcheck" + name = "${var.name}-healthcheck" network = var.compute_network source_ranges = local.google_health_check_ip_ranges - target_tags = local.application_tags + target_tags = local.network_tags allow { protocol = var.health_check.protocol diff --git a/terraform/modules/google-cloud/apps/gateway-region-instance-group/templates/cloud-init.yaml b/terraform/modules/google-cloud/apps/gateway-region-instance-group/templates/cloud-init.yaml index 0203fcb62..ff664215e 100644 --- a/terraform/modules/google-cloud/apps/gateway-region-instance-group/templates/cloud-init.yaml +++ b/terraform/modules/google-cloud/apps/gateway-region-instance-group/templates/cloud-init.yaml @@ -1,165 +1,50 @@ #cloud-config -users: - - name: cloudservice - uid: 2000 +package_update: true +package_upgrade: true +package_reboot_if_required: true + +packages: + - apt-transport-https + - ca-certificates + - software-properties-common write_files: - - path: /etc/otelcol-contrib/config.yaml + - path: /etc/apt/keyrings + permissions: "0755" + owner: root:root + content: "" + + - path: /etc/google-cloud-ops-agent/config.yaml permissions: "0644" owner: root content: | - receivers: - otlp: - protocols: - grpc: - endpoint: localhost:4317 - exporters: - googlecloud: - log: - default_log_name: opentelemetry.io/collector-exported-log - processors: - memory_limiter: - check_interval: 1s - limit_percentage: 65 - spike_limit_percentage: 20 - batch: - resourcedetection: - detectors: [gcp] - timeout: 10s - transform: - # Several metrics labels are reserved on Google Cloud. We need to prefix them with `exported_` to prevent the exporter from failing. - # See https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/exporter/googlecloudexporter/README.md#preventing-metric-label-collisions for example. - metric_statements: - - context: datapoint - statements: - - set(attributes["exported_location"], attributes["location"]) - - delete_key(attributes, "location") - - set(attributes["exported_cluster"], attributes["cluster"]) - - delete_key(attributes, "cluster") - - set(attributes["exported_namespace"], attributes["namespace"]) - - delete_key(attributes, "namespace") - - set(attributes["exported_job"], attributes["job"]) - - delete_key(attributes, "job") - - set(attributes["exported_instance"], attributes["instance"]) - - delete_key(attributes, "instance") - - set(attributes["exported_project_id"], attributes["project_id"]) - - delete_key(attributes, "project_id") - - set(attributes["exported_service_name"], attributes["service_name"]) - - delete_key(attributes, "service_name") - - set(attributes["exported_service_namespace"], attributes["service_namespace"]) - - delete_key(attributes, "service_namespace") - - set(attributes["exported_service_instance_id"], attributes["service_instance_id"]) - - delete_key(attributes, "service_instance_id") - - set(attributes["exported_instrumentation_source"], attributes["instrumentation_source"]) - - delete_key(attributes, "instrumentation_source") - - set(attributes["exported_instrumentation_version"], attributes["instrumentation_version"]) - - delete_key(attributes, "instrumentation_version") - service: - telemetry: - logs: - level: "debug" - pipelines: - traces: - receivers: [otlp] - processors: [memory_limiter, batch] - exporters: [googlecloud] - metrics: - receivers: [otlp] - processors: [memory_limiter, batch, transform] - exporters: [googlecloud] - logs: - receivers: [otlp] - processors: [memory_limiter, batch] - exporters: [googlecloud] - - - path: /etc/systemd/system/otel-collector.service - permissions: "0644" - owner: root - content: | - [Unit] - Description=Start an OpenTelemetry collector docker container - - [Service] - TimeoutStartSec=0 - Restart=always - ExecStartPre=/usr/bin/docker pull otel/opentelemetry-collector-contrib:0.97.0 - ExecStart=/usr/bin/docker run --rm -u 2000 --name=otel-collector --network host --volume /etc/otelcol-contrib/:/etc/otelcol-contrib/ otel/opentelemetry-collector-contrib:0.97.0 - ExecStop=/usr/bin/docker stop otel-collector - ExecStopPost=/usr/bin/docker rm otel-collector - - - path: /etc/firezone-gateway/.env - permissions: "0644" - owner: root - content: | - %{ for env in container_environment ~} - ${env.name}=${env.value} - %{ endfor ~} - - - path: /etc/systemd/system/gateway.service - permissions: "0644" - owner: root - content: | - [Unit] - Description=Start an Firezone Gateway container - - [Service] - TimeoutStartSec=0 - Restart=always - ExecStartPre=/usr/bin/docker pull ${container_image} - ExecStart=/bin/sh -c 'docker run --rm --name=${container_name} --cap-add=NET_ADMIN --publish=8080:8080 --volume /etc/firezone --device="/dev/net/tun:/dev/net/tun" --env FIREZONE_NAME=$(hostname) --env FIREZONE_ID=$(echo $RANDOM$(hostname) | md5sum | head -c 20; echo;) --env-file="/etc/firezone-gateway/.env" ${container_image}' - ExecStop=/usr/bin/docker stop gateway - ExecStopPost=/usr/bin/docker rm gateway - - - path: /etc/iptables/rules.v6 - permissions: "0644" - owner: root - content: | - *filter - :INPUT DROP [0:0] - :FORWARD DROP [0:0] - :OUTPUT DROP [0:0] - :DOCKER - [0:0] - :DOCKER-ISOLATION-STAGE-1 - [0:0] - :DOCKER-ISOLATION-STAGE-2 - [0:0] - :DOCKER-USER - [0:0] - -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT - -A INPUT -i lo -j ACCEPT - -A INPUT -p ipv6-icmp -j ACCEPT - -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT - -A INPUT -p tcp -j ACCEPT - -A INPUT -p udp -j ACCEPT - -A FORWARD -j DOCKER-USER - -A FORWARD -j DOCKER-ISOLATION-STAGE-1 - -A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT - -A FORWARD -o docker0 -j DOCKER - -A FORWARD -i docker0 ! -o docker0 -j ACCEPT - -A FORWARD -i docker0 -o docker0 -j ACCEPT - -A FORWARD -p tcp -j ACCEPT - -A FORWARD -p udp -j ACCEPT - -A FORWARD -p ipv6-icmp -j ACCEPT - -A OUTPUT -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT - -A OUTPUT -o lo -j ACCEPT - -A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2 - -A DOCKER-ISOLATION-STAGE-1 -j RETURN - -A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP - -A DOCKER-ISOLATION-STAGE-2 -j RETURN - -A DOCKER-USER -j RETURN - COMMIT + combined: + receivers: + otlp: + type: otlp + metrics_mode: googlecloudmonitoring + metrics: + service: + pipelines: + otlp: + receivers: [otlp] + traces: + service: + pipelines: + otlp: + receivers: [otlp] runcmd: - - sudo apt update -y - - sudo apt install postgresql-client jq iperf3 -y - - sudo apt install apt-transport-https ca-certificates curl software-properties-common -y - - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg - - echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null - - sudo apt update -y - - sudo apt install docker-ce docker-ce-cli containerd.io -y - - sudo usermod -aG docker $(whoami) - - sudo systemctl enable docker - - sudo echo '{"ipv6":true,"fixed-cidr-v6":"fd00::/80"}' > /etc/docker/daemon.json - - sudo systemctl start docker - - sudo ip6tables-restore < /etc/iptables/rules.v6 - - sudo systemctl daemon-reload - - sudo systemctl start otel-collector.service - - sudo systemctl start gateway.service + # Install Ops Agent + - curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh + - sudo bash add-google-cloud-ops-agent-repo.sh --also-install + # Install Firezone Gateway + - FIREZONE_TOKEN="${firezone_token}" \ + FIREZONE_API_URL="${firezone_api_url}" \ + FIREZONE_VERSION="${firezone_version}" \ + FIREZONE_ARTIFACT_URL="${firezone_artifact_url}" \ + FIREZONE_LOG_FORMAT="google-cloud" \ + FIREZONE_GOOGLE_CLOUD_PROJECT_ID=$(project_id) \ + FIREZONE_OTLP_GRPC_ENDPOINT=$(otlp_grpc_endpoint) \ + bash <(curl -fsSL https://raw.githubusercontent.com/firezone/firezone/main/scripts/gateway-systemd-install.sh) diff --git a/terraform/modules/google-cloud/apps/gateway-region-instance-group/variables.tf b/terraform/modules/google-cloud/apps/gateway-region-instance-group/variables.tf index 891dbffe9..ebe1b29b7 100644 --- a/terraform/modules/google-cloud/apps/gateway-region-instance-group/variables.tf +++ b/terraform/modules/google-cloud/apps/gateway-region-instance-group/variables.tf @@ -45,44 +45,6 @@ variable "compute_provision_public_ipv6_address" { description = "Whether to provision public IPv4 address for the instances." } -################################################################################ -## Container Registry -################################################################################ - -variable "container_registry" { - type = string - nullable = false - default = "ghcr.io" - description = "Container registry URL to pull the image from." -} - -################################################################################ -## Container Image -################################################################################ - -variable "image_repo" { - type = string - nullable = false - default = "firezone" - - description = "Repo of a container image used to deploy the application." -} - -variable "image" { - type = string - nullable = false - default = "gateway" - - description = "Container image used to deploy the application." -} - -variable "image_tag" { - type = string - nullable = false - - description = "Container image used to deploy the application." -} - ################################################################################ ## Observability ################################################################################ @@ -96,26 +58,18 @@ variable "observability_log_level" { } ################################################################################ -## Application +## Regional Instance Group ################################################################################ -variable "application_name" { +variable "name" { type = string nullable = true default = "gateway" - description = "Name of the application. Defaults to value of `var.image_name` with `_` replaced to `-`." + description = "Name of the application." } -variable "application_version" { - type = string - nullable = true - default = null - - description = "Version of the application. Defaults to value of `var.image_tag`." -} - -variable "application_labels" { +variable "labels" { type = map(string) nullable = false default = {} @@ -123,6 +77,33 @@ variable "application_labels" { description = "Labels to add to all created by this module resources." } +################################################################################ +## Firezone Gateway +################################################################################ + +variable "token" { + type = string + description = "Portal token to use for authentication." +} + +variable "api_url" { + type = string + default = "wss://api.firezone.dev" + description = "URL of the control plane endpoint." +} + +variable "artifact_url" { + type = string + default = "https://storage.googleapis.com/firezone-prod-artifacts/firezone-gateway" + description = "URL from which Firezone install script will download the gateway binary" +} + +variable "vsn" { + type = string + default = "latest" + description = "Version of the Firezone gateway that is downloaded from `artifact_url`." +} + variable "health_check" { type = object({ name = string @@ -164,30 +145,3 @@ variable "health_check" { description = "Health check which will be used for auto healing policy." } - -variable "application_environment_variables" { - type = list(object({ - name = string - value = string - })) - - nullable = false - default = [] - - description = "List of environment variables to set for all application containers." -} - -################################################################################ -## Firezone -################################################################################ - -variable "token" { - type = string - description = "Portal token to use for authentication." -} - -variable "api_url" { - type = string - default = "wss://api.firezone.dev" - description = "URL of the control plane endpoint." -} diff --git a/terraform/modules/google-cloud/apps/vm/iam.tf b/terraform/modules/google-cloud/apps/vm/iam.tf new file mode 100644 index 000000000..211484112 --- /dev/null +++ b/terraform/modules/google-cloud/apps/vm/iam.tf @@ -0,0 +1,54 @@ + +# Create IAM role for the application instances +resource "google_service_account" "application" { + project = var.project_id + + account_id = "vm-${local.vm_name}" + display_name = "${local.vm_name} app" + description = "Service account for ${local.vm_name} VM." +} + +## Allow fluentbit/OPS Agent to injest logs +resource "google_project_iam_member" "logs" { + project = var.project_id + + role = "roles/logging.logWriter" + + member = "serviceAccount:${google_service_account.application.email}" +} + +## Allow reporting application errors +resource "google_project_iam_member" "errors" { + project = var.project_id + + role = "roles/errorreporting.writer" + + member = "serviceAccount:${google_service_account.application.email}" +} + +## Allow reporting metrics +resource "google_project_iam_member" "metrics" { + project = var.project_id + + role = "roles/monitoring.metricWriter" + + member = "serviceAccount:${google_service_account.application.email}" +} + +## Allow reporting metrics +resource "google_project_iam_member" "service_management" { + project = var.project_id + + role = "roles/servicemanagement.reporter" + + member = "serviceAccount:${google_service_account.application.email}" +} + +## Allow appending traces +resource "google_project_iam_member" "cloudtrace" { + project = var.project_id + + role = "roles/cloudtrace.agent" + + member = "serviceAccount:${google_service_account.application.email}" +} diff --git a/terraform/modules/google-cloud/apps/vm/main.tf b/terraform/modules/google-cloud/apps/vm/main.tf new file mode 100644 index 000000000..6a92b5fe5 --- /dev/null +++ b/terraform/modules/google-cloud/apps/vm/main.tf @@ -0,0 +1,138 @@ +locals { + vm_name = var.vm_name + + vm_labels = merge({ + managed_by = "terraform" + }, var.vm_labels) + + vm_network_tags = [var.vm_network_tag] + + google_health_check_ip_ranges = [ + "130.211.0.0/22", + "35.191.0.0/16" + ] +} + +# Find the latest boot image +data "google_compute_image" "boot" { + family = var.boot_image_family + project = var.boot_image_project +} + +# Provision an internal IPv4 address for the VM +resource "google_compute_address" "ipv4" { + project = var.project_id + + region = var.compute_region + name = local.vm_name + subnetwork = var.compute_subnetwork + + address_type = "INTERNAL" +} + +resource "google_compute_instance" "vm" { + project = var.project_id + + name = local.vm_name + description = "This template is used to create ${local.vm_name} instances." + + zone = var.compute_instance_availability_zone + + machine_type = var.compute_instance_type + + can_ip_forward = true + + tags = local.vm_network_tags + + labels = merge({ + boot_image_family = var.boot_image_family + boot_image_project = var.boot_image_project + }, local.vm_labels) + + boot_disk { + auto_delete = true + + initialize_params { + image = data.google_compute_image.boot.self_link + + labels = { + managed_by = "terraform" + boot_image_family = var.boot_image_family + boot_image_project = var.boot_image_project + } + } + } + + network_interface { + subnetwork = var.compute_subnetwork + stack_type = "IPV4_ONLY" + network_ip = google_compute_address.ipv4.address + + access_config { + network_tier = "PREMIUM" + # Ephemeral IP address + } + } + + service_account { + email = google_service_account.application.email + + scopes = [ + # Those are default scopes + "https://www.googleapis.com/auth/devstorage.read_only", + "https://www.googleapis.com/auth/logging.write", + "https://www.googleapis.com/auth/monitoring.write", + "https://www.googleapis.com/auth/service.management.readonly", + "https://www.googleapis.com/auth/servicecontrol", + "https://www.googleapis.com/auth/trace.append", + ] + } + + shielded_instance_config { + enable_integrity_monitoring = true + enable_secure_boot = false + enable_vtpm = true + } + + metadata = { + user-data = var.cloud_init + + # Report logs to Cloud Logging and errors to Cloud Error Reporting + google-logging-enabled = "true" + google-logging-use-fluentbit = "true" + + # Report VM metrics to Cloud Monitoring + google-monitoring-enabled = "true" + } + + # Install the Ops Agent and some other tools that are helpful for debugging (curl, jq, etc.) + metadata_startup_script = <