diff --git a/.dockerignore b/.dockerignore
index e64d845d7..beb32eed4 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -13,10 +13,5 @@ www
.gitmodules
.github
-# Terraform
-.terraform
-*.tfstate.backup
-terraform.tfstate.d
-
rust/connlib/clients/android/connlib/build/
rust/connlib/clients/android/connlib/jniLibs/
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 90748a542..0773b1016 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,7 +1,6 @@
-# .github/ @AndrewDryga @jamilbk
-# elixir/ @AndrewDryga @bmanifold
-# terraform/ @AndrewDryga @bmanifold
-# website/ @jamilbk @AndrewDryga
-# rust/ @thomaseizinger @conectado @ReactorScram
-# swift/ @jamilbk @ReactorScram
-# kotlin/ @jamilbk @conectado
+# .github/ @jamilbk @bmanifold @thomaseizinger
+# elixir/ @jamilbk @bmanifold
+# website/ @jamilbk @bmanifold @thomaseizinger
+# rust/ @jamilbk @thomaseizinger
+# swift/ @jamilbk @thomaseizinger
+# kotlin/ @jamilbk @thomaseizinger
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index ad854ecfd..d4e257800 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -144,19 +144,3 @@ updates:
directory: elixir/apps/web/assets/
schedule:
interval: monthly
- - package-ecosystem: terraform
- directory: terraform/environments/staging/
- schedule:
- interval: monthly
- groups:
- google:
- patterns:
- - hashicorp/google*
- - package-ecosystem: terraform
- directory: terraform/environments/production/
- schedule:
- interval: monthly
- groups:
- google:
- patterns:
- - hashicorp/google*
diff --git a/.github/workflows/_deploy_production.yml b/.github/workflows/_deploy_production.yml
deleted file mode 100644
index a4c6e89f5..000000000
--- a/.github/workflows/_deploy_production.yml
+++ /dev/null
@@ -1,203 +0,0 @@
-name: Deploy Production
-run-name: Triggered by ${{ github.actor }}
-on:
- workflow_call:
- inputs:
- tag:
- description: "Image tag to deploy. Defaults to the last commit SHA in the branch."
- type: string
- default: ${{ github.sha }}
- required: false
-
-concurrency:
- group: "production-deploy"
- cancel-in-progress: false
-
-jobs:
- sanity-check:
- runs-on: ubuntu-22.04
- steps:
- - name: Ensure CI passed for the given sha
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
- gh api \
- -H "Accept: application/vnd.github+json" \
- -H "X-GitHub-Api-Version: 2022-11-28" \
- "repos/firezone/firezone/actions/runs?head_sha=${{ inputs.tag }}&status=success" \
- | jq -e '.workflow_runs | length > 0' || exit 1
-
- push:
- needs: sanity-check
- runs-on: ubuntu-22.04
- permissions:
- packages: write
- id-token: write
- strategy:
- fail-fast: false
- matrix:
- image: [domain, api, web, gateway, relay, client]
- steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- - name: Login to staging registry
- uses: ./.github/actions/gcp-docker-login
- id: login-staging
- with:
- project: firezone-staging
- - name: Login to production registry
- uses: ./.github/actions/gcp-docker-login
- id: login-production
- with:
- project: firezone-prod
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- - name: Pull and push images
- run: |
- set -xe
-
- SOURCE_TAG=${{ steps.login-staging.outputs.registry }}/firezone/${{ matrix.image }}:${{ inputs.tag }}
-
- docker buildx imagetools create \
- -t ${{ steps.login-production.outputs.registry }}/firezone/${{ matrix.image }}:${{ inputs.tag }} \
- $SOURCE_TAG
- - name: Authenticate to Google Cloud
- id: auth
- uses: google-github-actions/auth@ba79af03959ebeac9769e648f473a284504d9193 # v2.1.10
- 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 "edge" version
- # TODO: Add relay here when we deploy Relay from prod artifacts instead of Docker
- # To do that properly we need to:
- # - Update publish.yml to publish versioned Relays too (and start versioning Relay changes)
- # - Add arm64 and armv7l architectures to the Relay builds (we only build for amd64 currently because that's all we need to)
- if: ${{ matrix.image == 'gateway' || matrix.image == 'client' }}
- run: |
- set -xe
-
- ARCHITECTURES=(x86_64 aarch64 armv7)
-
- for arch in "${ARCHITECTURES[@]}"; do
- # Copy sha256sum.txt
- gcloud storage cp \
- gs://firezone-staging-artifacts/firezone-${{ matrix.image }}/${{ github.sha }}/${arch}.sha256sum.txt \
- gs://firezone-prod-artifacts/firezone-${{ matrix.image }}/edge/${arch}.sha256sum.txt
- gcloud storage cp \
- gs://firezone-staging-artifacts/firezone-${{ matrix.image }}/${{ github.sha }}/${arch}.sha256sum.txt \
- gs://firezone-prod-artifacts/firezone-${{ matrix.image }}/${{ github.sha }}/${arch}.sha256sum.txt
-
- # Copy binaries
- gcloud storage cp \
- gs://firezone-staging-artifacts/firezone-${{ matrix.image }}/${{ github.sha }}/${arch} \
- gs://firezone-prod-artifacts/firezone-${{ matrix.image }}/edge/${arch}
- gcloud storage cp \
- gs://firezone-staging-artifacts/firezone-${{ matrix.image }}/${{ github.sha }}/${arch} \
- gs://firezone-prod-artifacts/firezone-${{ matrix.image }}/${{ github.sha }}/${arch}
- done
-
- deploy-production:
- needs: push
- runs-on: ubuntu-22.04
- environment: gcp_production
- permissions:
- contents: write
- env:
- TF_CLOUD_ORGANIZATION: "firezone"
- TF_API_TOKEN: "${{ secrets.TF_API_TOKEN }}"
- TF_WORKSPACE: "production"
- steps:
- # First, checkout the main ref for setting up Terraform
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- with:
- submodules: true
- ssh-key: ${{ secrets.ENVIRONMENTS_REPO_DEPLOY_KEY }}
- - name: Tool Versions
- id: versions
- uses: marocchino/tool-versions-action@18a164fa2b0db1cc1edf7305fcb17ace36d1c306 # v1.2.0
- - uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
- with:
- terraform_version: ${{ steps.versions.outputs.terraform }}
- # Then, checkout the ref specified in the workflow run
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- with:
- ref: ${{ github.event.workflow_run.head_branch }}
- submodules: true
- ssh-key: ${{ secrets.ENVIRONMENTS_REPO_DEPLOY_KEY }}
- - name: Upload Configuration
- uses: hashicorp/tfc-workflows-github/actions/upload-configuration@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2
- id: apply-upload
- with:
- workspace: ${{ env.TF_WORKSPACE }}
- # Subdirectory is set in the project settings:
- # https://app.terraform.io/app/firezone/workspaces/production/settings/general
- directory: "./"
- - name: Create Plan Run
- uses: hashicorp/tfc-workflows-github/actions/create-run@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2
- id: apply-run
- env:
- TF_VAR_image_tag: '"${{ inputs.tag }}"'
- with:
- workspace: ${{ env.TF_WORKSPACE }}
- configuration_version: ${{ steps.apply-upload.outputs.configuration_version_id }}
- - name: Apply
- uses: hashicorp/tfc-workflows-github/actions/apply-run@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2
- if: fromJSON(steps.apply-run.outputs.payload).data.attributes.actions.IsConfirmable
- id: apply
- with:
- run: ${{ steps.apply-run.outputs.run_id }}
- comment: "Apply Run from GitHub Actions CI ${{ inputs.tag }}"
-
- # Some intrepid users are self-hosting these, so support them as best we can by making our
- # infrastructure images available to them.
- publish-infra-images:
- # Only publish if our own deploy was successful
- needs: deploy-production
- runs-on: ubuntu-22.04
- permissions:
- packages: write
- id-token: write
- strategy:
- fail-fast: false
- matrix:
- image: [domain, api, web, relay]
- steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- - name: Login to staging registry
- uses: ./.github/actions/gcp-docker-login
- id: login-staging
- with:
- project: firezone-staging
- - name: Login to GitHub Container Registry
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
- with:
- registry: ghcr.io
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- - name: Pull and push
- run: |
- set -xe
-
- SOURCE_TAG=${{ steps.login-staging.outputs.registry }}/firezone/${{ matrix.image }}:${{ inputs.tag }}
-
- docker buildx imagetools create \
- -t ghcr.io/firezone/${{ matrix.image }}:${{ inputs.tag }} \
- -t ghcr.io/firezone/${{ matrix.image }}:latest \
- $SOURCE_TAG
-
- update-vercel:
- needs: deploy-production
- runs-on: ubuntu-22.04
- env:
- VERCEL_TEAM_ID: firezone
- VERCEL_EDGE_CONFIG_ID: ecfg_hmorgeez26rwyncgsuj1yaibfx4p
- steps:
- - name: Update FIREZONE_DEPLOYED_SHA
- run: |
- curl --fail -X PATCH "https://api.vercel.com/v1/edge-config/${VERCEL_EDGE_CONFIG_ID}/items?teamId=${VERCEL_TEAM_ID}" \
- -H "Authorization: Bearer ${{ secrets.VERCEL_TOKEN }}" \
- -H "Content-Type: application/json" \
- -d '{ "items": [ { "operation": "upsert", "key": "deployed_sha", "value": "${{ inputs.tag }}" } ] }'
diff --git a/.github/workflows/_terraform.yml b/.github/workflows/_terraform.yml
deleted file mode 100644
index 2cb5ce9d9..000000000
--- a/.github/workflows/_terraform.yml
+++ /dev/null
@@ -1,109 +0,0 @@
-name: Terraform
-on:
- workflow_call:
-
-jobs:
- plan-deploy:
- runs-on: ubuntu-22.04
- permissions:
- contents: read
- pull-requests: write
- env:
- TF_CLOUD_ORGANIZATION: "firezone"
- TF_API_TOKEN: "${{ secrets.TF_API_TOKEN }}"
- TF_WORKSPACE: "staging"
- steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- with:
- submodules: true
- ssh-key: ${{ secrets.ENVIRONMENTS_REPO_DEPLOY_KEY }}
- - run: ls -alR terraform/environments
- - name: Tool Versions
- id: versions
- uses: marocchino/tool-versions-action@18a164fa2b0db1cc1edf7305fcb17ace36d1c306 # v1.2.0
- - uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
- with:
- terraform_version: ${{ steps.versions.outputs.terraform }}
- - name: Validate cloud-init
- run: |
- sudo apt-get update
- sudo apt-get install -y cloud-init
- cloud-init schema --config-file terraform/modules/google-cloud/apps/relay/templates/cloud-init.yaml
- # This doesn't work if the file contains interpolated variables
- # cloud-init schema --config-file terraform/modules/google-cloud/apps/elixir/templates/cloud-init.yaml
- - name: Check Formatting
- working-directory: terraform
- run: |
- terraform fmt --check --recursive
- - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
- id: changes
- with:
- filters: |
- terraform:
- - 'terraform/**'
- - if: steps.changes.outputs.terraform == 'true'
- name: Upload Configuration
- uses: hashicorp/tfc-workflows-github/actions/upload-configuration@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2
- id: plan-upload
- with:
- workspace: ${{ env.TF_WORKSPACE }}
- # Subdirectory is set in the project settings:
- # https://app.terraform.io/app/firezone/workspaces/staging/settings/general
- directory: "./"
- speculative: true
- - if: steps.changes.outputs.terraform == 'true'
- name: Create Plan Run
- uses: hashicorp/tfc-workflows-github/actions/create-run@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2
- id: plan-run
- env:
- TF_VAR_image_tag: '"${{ github.sha }}"'
- with:
- workspace: ${{ env.TF_WORKSPACE }}
- configuration_version: ${{ steps.plan-upload.outputs.configuration_version_id }}
- plan_only: true
- - if: steps.changes.outputs.terraform == 'true'
- name: Get Plan Output
- uses: hashicorp/tfc-workflows-github/actions/plan-output@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2
- id: plan-output
- with:
- plan: ${{ fromJSON(steps.plan-run.outputs.payload).data.relationships.plan.data.id }}
- - name: Update PR
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
- id: plan-comment
- if: ${{ github.event_name == 'pull_request' && steps.changes.outputs.terraform == 'true' }}
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
- script: |
- // 1. Retrieve existing bot comments for the PR
- const { data: comments } = await github.rest.issues.listComments({
- owner: context.repo.owner,
- repo: context.repo.repo,
- issue_number: context.issue.number,
- });
- const botComment = comments.find(comment => {
- return comment.user.type === 'Bot' && comment.body.includes('Terraform Cloud Plan Output')
- });
- const output = `#### Terraform Cloud Plan Output
-
- \`\`\`
- Plan: ${{ steps.plan-output.outputs.add }} to add, ${{ steps.plan-output.outputs.change }} to change, ${{ steps.plan-output.outputs.destroy }} to destroy.
- \`\`\`
-
- [Terraform Cloud Plan](${{ steps.plan-run.outputs.run_link }})
- `;
- // 3. Update previous comment or create new one
- if (botComment) {
- github.rest.issues.updateComment({
- owner: context.repo.owner,
- repo: context.repo.repo,
- comment_id: botComment.id,
- body: output
- });
- } else {
- github.rest.issues.createComment({
- issue_number: context.issue.number,
- owner: context.repo.owner,
- repo: context.repo.repo,
- body: output
- });
- }
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index 9e903ccac..422b3a0ce 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -2,12 +2,6 @@ name: Continuous Delivery
on:
# Used for debugging the workflow by manually calling it
workflow_dispatch:
- inputs:
- deploy-staging:
- description: "Also deploy to staging. By default the deploy is not executed when triggering this workflow manually."
- type: boolean
- default: false
- required: false
push:
branches:
- main
@@ -40,60 +34,3 @@ jobs:
with:
profile: "release"
stage: "release"
-
- deploy-staging:
- if: ${{ github.event_name != 'workflow_dispatch' || inputs.deploy-staging }}
- runs-on: ubuntu-22.04
- environment: gcp_staging
- permissions:
- contents: write
- # Cancel old workflow runs if new code is pushed
- concurrency:
- group: "staging-deploy-${{ github.workflow }}-${{ github.ref }}"
- cancel-in-progress: false
- needs: ci
- env:
- TF_CLOUD_ORGANIZATION: "firezone"
- TF_API_TOKEN: "${{ secrets.TF_API_TOKEN }}"
- TF_WORKSPACE: "staging"
- steps:
- # First, checkout the main ref for setting up Terraform
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- with:
- submodules: true
- ssh-key: ${{ secrets.ENVIRONMENTS_REPO_DEPLOY_KEY }}
- - name: Tool Versions
- id: versions
- uses: marocchino/tool-versions-action@18a164fa2b0db1cc1edf7305fcb17ace36d1c306 # v1.2.0
- - uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
- with:
- terraform_version: ${{ steps.versions.outputs.terraform }}
- # Then, checkout the ref specified in the workflow run
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- with:
- ref: ${{ github.event.workflow_run.head_branch }}
- submodules: true
- ssh-key: ${{ secrets.ENVIRONMENTS_REPO_DEPLOY_KEY }}
- - name: Upload Configuration
- uses: hashicorp/tfc-workflows-github/actions/upload-configuration@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2
- id: apply-upload
- with:
- workspace: ${{ env.TF_WORKSPACE }}
- # Subdirectory is set in the project settings:
- # https://app.terraform.io/app/firezone/workspaces/staging/settings/general
- directory: "./"
- - name: Create Plan Run
- uses: hashicorp/tfc-workflows-github/actions/create-run@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2
- id: apply-run
- env:
- TF_VAR_image_tag: '"${{ github.sha }}"'
- with:
- workspace: ${{ env.TF_WORKSPACE }}
- configuration_version: ${{ steps.apply-upload.outputs.configuration_version_id }}
- - name: Apply
- uses: hashicorp/tfc-workflows-github/actions/apply-run@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2
- if: fromJSON(steps.apply-run.outputs.payload).data.attributes.actions.IsConfirmable
- id: apply
- with:
- run: ${{ steps.apply-run.outputs.run_id }}
- comment: "Apply Run from GitHub Actions CI ${{ github.sha }}"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c0b61ef1a..d090f43f0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -32,9 +32,6 @@ jobs:
secrets: inherit
static-analysis:
uses: ./.github/workflows/_static-analysis.yml
- terraform:
- uses: ./.github/workflows/_terraform.yml
- secrets: inherit
codeql:
uses: ./.github/workflows/_codeql.yml
secrets: inherit
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
deleted file mode 100644
index 77aaa6ce0..000000000
--- a/.github/workflows/deploy.yml
+++ /dev/null
@@ -1,39 +0,0 @@
-name: Deploy Production
-run-name: Triggered by ${{ github.actor }} on ${{ github.event_name }}
-on:
- workflow_dispatch:
- inputs:
- confirmation:
- description: "Are you SURE you want to deploy all changes from the selected commit to production?"
- type: boolean
- tag:
- description: "Image tag to deploy. Defaults to the last commit SHA in the branch."
- type: string
- required: false
-
-concurrency:
- group: "deploy-production-${{ github.event_name }}-${{ github.workflow }}-${{ github.ref }}"
- cancel-in-progress: false
-
-jobs:
- # This is *not* run in CI on main in order to allow
- # breaking changes to be merged as administrator and have the
- # resulting CI green on main.
- # So run them here.
- compatibility-tests:
- uses: ./.github/workflows/_integration_tests.yml
- secrets: inherit
- with:
- relay_image: "us-east1-docker.pkg.dev/firezone-staging/firezone/relay"
- gateway_image: "ghcr.io/firezone/gateway"
- gateway_tag: "latest"
- client_image: "ghcr.io/firezone/client"
- client_tag: "latest"
-
- deploy-production:
- if: ${{ inputs.confirmation }}
- needs: compatibility-tests
- secrets: inherit
- uses: ./.github/workflows/_deploy_production.yml
- with:
- tag: ${{ inputs.tag || github.sha }}
diff --git a/.gitmodules b/.gitmodules
index 4b41f7f4a..e69de29bb 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +0,0 @@
-[submodule "terraform/environments"]
- path = terraform/environments
- url = git@github.com:firezone/environments.git
diff --git a/.terraformignore b/.terraformignore
deleted file mode 100644
index 2f34da043..000000000
--- a/.terraformignore
+++ /dev/null
@@ -1,3 +0,0 @@
-/*
-/*/
-!/terraform/
diff --git a/.tool-versions b/.tool-versions
index 6cee0bf9f..b419fa676 100644
--- a/.tool-versions
+++ b/.tool-versions
@@ -3,7 +3,6 @@
nodejs 20.14.0
elixir 1.18.2-otp-27
erlang 27.2.1
-terraform 1.10.4
# Used for static analysis
python 3.11.9
diff --git a/docs/README.md b/docs/README.md
index 146ceaabe..4e078f3af 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -94,13 +94,6 @@ product documentation, organized as follows:
- [swift/](../swift/apple): macOS / iOS clients.
- [kotlin/](../kotlin/android): Android / ChromeOS clients.
- [website/](../website): Marketing website and product documentation.
-- [terraform/](../terraform): Terraform files for various example deployments.
- - [terraform/examples/google-cloud/nat-gateway](../terraform/examples/google-cloud/nat-gateway):
- Example Terraform configuration for deploying a cluster of Firezone Gateways
- behind a NAT gateway on GCP with a single egress IP.
- - [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 using Regional Instance Groups.
## Quickstart
diff --git a/elixir/.dockerignore b/elixir/.dockerignore
index 9db7c80ac..0d5da4950 100644
--- a/elixir/.dockerignore
+++ b/elixir/.dockerignore
@@ -18,8 +18,3 @@ docs
.gitignore
.gitmodules
.github
-
-# Terraform
-.terraform
-*.tfstate.backup
-terraform.tfstate.d
diff --git a/elixir/README.md b/elixir/README.md
index acd55272e..b5e854d55 100644
--- a/elixir/README.md
+++ b/elixir/README.md
@@ -1,680 +1 @@
-# Welcome to Elixir-land!
-
-This README provides an overview for running and managing Firezone's
-Elixir-based control plane.
-
-## Running Control Plane for local development
-
-You can use the [Top-Level Docker Compose](../docker-compose.yml) to start any
-services locally. The `web` and `api` compose services are built application
-releases that are pretty much the same as the ones we run in production, while
-the `elixir` compose service runs raw Elixir code, without a built release.
-
-This means you'll want to use the `elixir` compose service to run Mix tasks and
-any Elixir code on-the-fly, but you can't do that in `web`/`api` so easily
-because Elixir strips out Mix and other tooling
-[when building an application release](https://hexdocs.pm/mix/Mix.Tasks.Release.html).
-
-`elixir` additionally caches `_build` and `node_modules` to speed up compilation
-time and syncs `/apps`, `/config` and other folders with the host machine.
-
-```bash
-# Make sure to run this every time code in elixir/ changes,
-# docker doesn't do that for you!
-❯ docker-compose build
-
-# Create the database
-#
-# Hint: you can run any mix commands like this,
-# eg. mix ecto.reset will reset your database
-#
-# Also to drop the database you need to stop all active connections,
-# so if you get an error stop all services first:
-#
-# docker-compose down
-#
-# Or you can just run both reset and seed in one-liner:
-#
-# docker-compose run elixir /bin/sh -c "cd apps/domain && mix do ecto.reset, ecto.seed"
-#
-❯ docker-compose run elixir /bin/sh -c "cd apps/domain && mix ecto.create"
-
-# Ensure database is migrated before running seeds
-❯ docker-compose run api bin/migrate
-# or
-❯ docker-compose run elixir /bin/sh -c "cd apps/domain && mix ecto.migrate"
-
-# Seed the database
-# Hint: some access tokens will be generated and written to stdout,
-# don't forget to save them for later
-❯ docker-compose run api bin/seed
-# or
-❯ docker-compose run elixir /bin/sh -c "cd apps/domain && mix ecto.seed"
-
-# Start the API service for control plane sockets while listening to STDIN
-# (where you will see all the logs)
-❯ docker-compose up api --build
-```
-
-Now you can verify that it's working by connecting to a websocket:
-
-
- Gateway
-
-```bash
-# Note: The token value below is an example. The token value you will need is generated and printed out when seeding the database, as described earlier in the document.
-❯ export GATEWAY_TOKEN_FROM_SEEDS=".SFMyNTY.g2gDaANtAAAAJGM4OWJjYzhjLTkzOTItNGRhZS1hNDBkLTg4OGFlZjZkMjhlMG0AAAAkMjI3NDU2MGItZTk3Yi00NWU0LThiMzQtNjc5Yzc2MTdlOThkbQAAADhPMDJMN1VTMkozVklOT01QUjlKNklMODhRSVFQNlVPOEFRVk82VTVJUEwwVkpDMjJKR0gwPT09PW4GAF3gLBONAWIAAVGA.DCT0Qv80qzF5OQ6CccLKXPLgzC3Rzx5DqzDAh9mWAww"
-
-❯ websocat --header="User-Agent: iOS/12.7 (iPhone) connlib/0.7.412" "ws://127.0.0.1:13000/gateway/websocket?token=${GATEWAY_TOKEN_FROM_SEEDS}&external_id=thisisrandomandpersistent&name=kkX1&public_key=kceI60D6PrwOIiGoVz6hD7VYCgD1H57IVQlPJTTieUE="
-
-# After this you need to join the `gateway` topic.
-# For details on this structure see https://hexdocs.pm/phoenix/Phoenix.Socket.Message.html
-❯ {"event":"phx_join","topic":"gateway","payload":{},"ref":"unique_string_ref","join_ref":"unique_join_ref"}
-
-{"ref":"unique_string_ref","payload":{"status":"ok","response":{}},"topic":"gateway","event":"phx_reply"}
-{"ref":null,"payload":{"interface":{"ipv6":"fd00:2021:1111::35:f630","ipv4":"100.77.125.87"},"ipv4_masquerade_enabled":true,"ipv6_masquerade_enabled":true},"topic":"gateway","event":"init"}
-```
-
-
-
- Relay
-
-```bash
-# Note: The token value below is an example. The token value you will need is generated and printed out when seeding the database, as described earlier in the document.
-❯ export RELAY_TOKEN_FROM_SEEDS=".SFMyNTY.g2gDaANtAAAAJGM4OWJjYzhjLTkzOTItNGRhZS1hNDBkLTg4OGFlZjZkMjhlMG0AAAAkNTQ5YzQxMDctMTQ5Mi00ZjhmLWE0ZWMtYTlkMmE2NmQ4YWE5bQAAADhQVTVBSVRFMU84VkRWTk1ITU9BQzc3RElLTU9HVERJQTY3MlM2RzFBQjAyT1MzNEg1TUUwPT09PW4GAJeo1TONAWIAAVGA.Vi3gCkFKoWH03uSUshAYYzRhw7eKQxYw1piFnkFPGtA"
-
-❯ websocat --header="User-Agent: Linux/5.2.6 (Debian; x86_64) relay/0.7.412" "ws://127.0.0.1:8081/relay/websocket?token=${RELAY_TOKEN_FROM_SEEDS}&ipv4=24.12.79.100&ipv6=4d36:aa7f:473c:4c61:6b9e:2416:9917:55cc"
-
-# Here is what you will see in docker logs firezone-api-1
-# {"time":"2023-06-05T23:16:01.537Z","severity":"info","message":"CONNECTED TO API.Relay.Socket in 251ms\n Transport: :websocket\n Serializer: Phoenix.Socket.V1.JSONSerializer\n Parameters: %{\"ipv4\" => \"24.12.79.100\", \"ipv6\" => \"4d36:aa7f:473c:4c61:6b9e:2416:9917:55cc\", \"stamp_secret\" => \"[FILTERED]\", \"token\" => \"[FILTERED]\"}","metadata":{"domain":["elixir"],"erl_level":"info"}}
-
-# After this you need to join the `relay` topic and pass a `stamp_secret` in the payload.
-# For details on this structure see https://hexdocs.pm/phoenix/Phoenix.Socket.Message.html
-❯ {"event":"phx_join","topic":"relay","payload":{"stamp_secret":"makemerandomplz"},"ref":"unique_string_ref","join_ref":"unique_join_ref"}
-
-{"event":"phx_reply","payload":{"response":{},"status":"ok"},"ref":"unique_string_ref","topic":"relay"}
-{"event":"init","payload":{},"ref":null,"topic":"relay"}
-```
-
-
-
- Client
-
-```bash
-# Note: The token value below is an example. The token value you will need is generated and printed out when seeding the database, as described earlier in the document.
-❯ export CLIENT_TOKEN_FROM_SEEDS="n.SFMyNTY.g2gDaANtAAAAJGM4OWJjYzhjLTkzOTItNGRhZS1hNDBkLTg4OGFlZjZkMjhlMG0AAAAkN2RhN2QxY2QtMTExYy00NGE3LWI1YWMtNDAyN2I5ZDIzMGU1bQAAACtBaUl5XzZwQmstV0xlUkFQenprQ0ZYTnFJWktXQnMyRGR3XzJ2Z0lRdkZnbgYAGUmu74wBYgABUYA.UN3vSLLcAMkHeEh5VHumPOutkuue8JA6wlxM9JxJEPE"
-
-# Panel will only accept token if it's coming with this User-Agent header and from IP 172.28.0.1
-❯ export CLIENT_USER_AGENT="iOS/12.5 (iPhone) connlib/0.7.412"
-
-❯ websocat --header="User-Agent: ${CLIENT_USER_AGENT}" "ws://127.0.0.1:8081/client/websocket?token=${CLIENT_TOKEN_FROM_SEEDS}&external_id=thisisrandomandpersistent&name=kkX1&public_key=kceI60D6PrwOIiGoVz6hD7VYCgD1H57IVQlPJTTieUE="
-
-# Here is what you will see in docker logs firezone-api-1
-# firezone-api-1 | {"domain":["elixir"],"erl_level":"info","logging.googleapis.com/sourceLocation":{"file":"lib/phoenix/logger.ex","line":306,"function":"Elixir.Phoenix.Logger.phoenix_socket_connected/4"},"message":"CONNECTED TO API.Client.Socket in 83ms\n Transport: :websocket\n Serializer: Phoenix.Socket.V1.JSONSerializer\n Parameters: %{\"external_id\" => \"thisisrandomandpersistent\", \"name\" => \"kkX1\", \"public_key\" => \"[FILTERED]\", \"token\" => \"[FILTERED]\"}","severity":"INFO","time":"2023-06-23T21:01:49.566Z"}
-
-# After this you need to join the `client` topic and pass a `stamp_secret` in the payload.
-# For details on this structure see https://hexdocs.pm/phoenix/Phoenix.Socket.Message.html
-❯ {"event":"phx_join","topic":"client","payload":{},"ref":"unique_string_ref","join_ref":"unique_join_ref"}
-
-{"ref":"unique_string_ref","topic":"client","event":"phx_reply","payload":{"status":"ok","response":{}}}
-{"ref":null,"topic":"client","event":"init","payload":{"interface":{"ipv6":"fd00:2021:1111::11:f4bd","upstream_dns":[],"ipv4":"100.71.71.245"},"resources":[{"id":"4429d3aa-53ea-4c03-9435-4dee2899672b","name":"172.20.0.1/16","type":"cidr","address":"172.20.0.0/16"},{"id":"85a1cffc-70d3-46dd-aa6b-776192af7b06","name":"gitlab.mycorp.com","type":"dns","address":"gitlab.mycorp.com","ipv6":"fd00:2021:1111::5:b370","ipv4":"100.85.109.146"}]}}
-
-# List online relays for a Resource
-❯ {"event":"prepare_connection","topic":"client","payload":{"resource_id":"1f27735f-651d-49e8-840c-8f1ba581d88e"},"ref":"unique_prepare_connection_ref"}
-
-{"ref":"unique_prepare_connection_ref","payload":{"status":"ok","response":{"relays":[{"type":"turn","uri":"turn:189.172.72.111:3478","username":"1738022400:4ZxvSNDzU98MJiEjsR8DOA","password":"TVZvSgIGFK0TtNDXFVU9gv9a1WDz2Ou7RTEUis4E6To","expires_at":1738022400},{"type":"turn","uri":"turn:[::1]:3478","username":"1738022400:KCYrRTRmfGNAEEe7KyjHkA","password":"8KYplQOKBf5smJRZDhC54kiKKNVmUxsVxH1V8xfY/do","expires_at":1738022400}],"resource_id":"1f27735f-651d-49e8-840c-8f1ba581d88e","gateway_remote_ip":"127.0.0.1","gateway_id":"6e52c0ce-ccd9-46d9-8715-796ec9812719"}},"topic":"client","event":"phx_reply"}
-{"event":"request_connection","topic":"client","payload":{"resource_id":"1f27735f-651d-49e8-840c-8f1ba581d88e","client_payload":"RTC_SD","client_preshared_key":"+HapiGI5UdeRjKuKTwk4ZPPYpCnlXHvvqebcIevL+2A="},"ref":"unique_request_connection_ref"}
-
-# Initiate connection to a resource
-❯ {"event":"request_connection","topic":"client","payload":{"gateway_id":"6e52c0ce-ccd9-46d9-8715-796ec9812719","resource_id":"1f27735f-651d-49e8-840c-8f1ba581d88e","client_payload":"RTC_SD","client_preshared_key":"+HapiGI5UdeRjKuKTwk4ZPPYpCnlXHvvqebcIevL+2A="},"ref":"unique_request_connection_ref"}
-
-```
-
-Note: when you run multiple commands it can hang because Phoenix expects a
-heartbeat packet every 5 seconds, so it can kill your websocket if you send
-commands slower than that.
-
-
-
-
-You can reset the database (eg. when there is a migration that breaks data model
-for unreleased versions) using following command:
-
-```bash
-❯ docker-compose run elixir /bin/sh -c "cd apps/domain && mix ecto.reset"
-```
-
-Stopping everything is easy too:
-
-```bash
-docker-compose down
-```
-
-## Useful commands for local testing and debugging
-
-Connecting to an IEx interactive console:
-
-```bash
-❯ docker-compose run elixir /bin/sh -c "cd apps/domain && iex -S mix"
-```
-
-Connecting to a running api/web instance shell:
-
-```bash
-❯ docker exec -it firezone-api-1 sh
-/app
-```
-
-Connecting to a running api/web instance to run Elixir code from them:
-
-```bash
-# Start all services in daemon mode (in background)
-❯ docker-compose up -d --build
-
-# Connect to a running API node
-❯ docker exec -it firezone-api-1 bin/api remote
-Erlang/OTP 25 [erts-13.1.4] [source] [64-bit] [smp:5:5] [ds:5:5:10] [async-threads:1]
-
-Interactive Elixir (1.14.3) - press Ctrl+C to exit (type h() ENTER for help)
-iex(api@127.0.0.1)1>
-
-# Connect to a running Web node
-❯ docker exec -it firezone-web-1 bin/web remote
-Erlang/OTP 25 [erts-13.1.4] [source] [64-bit] [smp:5:5] [ds:5:5:10] [async-threads:1]
-
-Interactive Elixir (1.14.3) - press Ctrl+C to exit (type h() ENTER for help)
-iex(web@127.0.0.1)1>
-```
-
-From `iex` shell you can run any Elixir code, for example you can emulate a full
-flow using process messages, just keep in mind that you need to run seeds before
-executing this example:
-
-```elixir
-[gateway | _rest_gateways] = Domain.Repo.all(Domain.Gateways.Gateway)
-:ok = Events.Hooks.Gateways.connect(gateway)
-
-[relay | _rest_relays] = Domain.Repo.all(Domain.Relays.Relay)
-relay_secret = Domain.Crypto.random_token()
-:ok = Domain.Relays.connect_relay(relay, relay_secret)
-```
-
-Now if you connect and list resources there will be one online because there is
-a relay and gateway online.
-
-Some of the functions require authorization, here is how you can obtain a
-subject:
-
-```elixir
-user_agent = "User-Agent: iOS/12.7 (iPhone) connlib/0.7.412"
-remote_ip = {127, 0, 0, 1}
-
-# For a client
-context = %Domain.Auth.Context{type: :client, user_agent: user_agent, remote_ip: remote_ip}
-{:ok, subject} = Domain.Auth.authenticate(client_token, context)
-
-# For an admin user, imitating the browser session
-context = %Domain.Auth.Context{type: :browser, user_agent: user_agent, remote_ip: remote_ip}
-provider = Domain.Repo.get_by(Domain.Auth.Provider, adapter: :userpass)
-identity = Domain.Repo.get_by(Domain.Auth.Identity, provider_id: provider.id, provider_identifier: "firezone@localhost.local")
-token = Domain.Auth.create_token(identity, context, "", nil)
-browser_token = Domain.Tokens.encode_fragment!(token)
-{:ok, subject} = Domain.Auth.authenticate(browser_token, context)
-```
-
-Listing connected gateways, relays, clients for an account:
-
-```elixir
-account_id = "c89bcc8c-9392-4dae-a40d-888aef6d28e0"
-
-%{
- gateways: Domain.Gateways.Presence.list("gateways:#{account_id}"),
- relays: Domain.Relays.Presence.list("relays:#{account_id}"),
- clients: Domain.Clients.Presence.list("clients:#{account_id}"),
-}
-```
-
-### Connecting billing in dev mode for manual testing
-
-Prerequisites:
-
-- A Stripe account (Note: for the Firezone team, you will need to be invited to
- the Firezone Stripe account)
-- [Stripe CLI](https://github.com/stripe/stripe-cli)
-
-Steps:
-
-1. Use static seeds to provision account ID that corresponds to staging setup on
- Stripe:
-
- ```bash
- STATIC_SEEDS=true mix do ecto.reset, ecto.seed
- ```
-
-1. Start Stripe CLI webhook proxy:
-
- ```bash
- stripe listen --forward-to localhost:13001/integrations/stripe/webhooks
- ```
-
-1. Start the Phoenix server with enabled billing from the [`elixir/`](./) folder
- using a [test mode token](https://dashboard.stripe.com/test/apikeys):
-
- ```bash
- cd elixir/
- BILLING_ENABLED=true STRIPE_SECRET_KEY="...copy from stripe dashboard..." STRIPE_WEBHOOK_SIGNING_SECRET="...copy from stripe cli tool.." mix phx.server
- ```
-
-When updating the billing plan in stripe, use the
-[Stripe Testing Docs](https://docs.stripe.com/testing#testing-interactively) for
-how to add test payment info
-
-### WorkOS integration
-
-WorkOS is currently being used for JumpCloud directory sync integration. This
-allows JumpCloud users to use SCIM on the JumpCloud side, rather than having to
-give Firezone an admin JumpCloud API token.
-
-#### Connecting WorkOS in dev mode for manual testing
-
-If you are not planning to use the JumpCloud provider in your local development
-setup, then no additional setup is needed. However, if you do need to use the
-JumpCloud provider locally, you will need to obtain an API Key and Client ID
-from the [WorkOS Dashboard](https://dashboard.workos.com/api-keys).
-
-To obtain a WorkOS dashboard login, contact one of the following Firezone team
-members:
-
-- @jamilbk
-- @bmanifold
-- @AndrewDryga
-
-Once you are able to login to the WorkOS Dashboard, make sure that you have
-selected the 'Staging' environment within WorkOS. Navigate to the API Keys page
-and use the `Create Key` button to obtain credentials.
-
-After obtaining WorkOS API credentials, you will need to make sure they are set
-in the environment ENVs when starting your local dev instance of Firezone. As an
-example:
-
-```bash
-cd elixir/
-WORKOS_API_KEY="..." WORKOS_CLIENT_ID="..." mix phx.server
-```
-
-### Acceptance tests
-
-You can disable headless mode for the browser by adding
-
-```elixir
-
- @tag debug: true
- feature ....
-```
-
-to the acceptance test that you are running.
-
-## Connecting to a staging or production instance
-
-We use Google Cloud Platform for all our staging and production infrastructure.
-You'll need access to this env to perform the commands below; to request access
-you need to complete the following process:
-
-- Open a PR adding yourself to `project_owners` in `main.tf` for each of the
- [environments](../terraform/environments) you need access.
-- Request a review from an existing project owner.
-- Once approved, merge the PR and verify access by continuing with one of the
- steps below.
-
-This is a danger zone so first of all, ALWAYS make sure on which environment
-your code is running:
-
-```bash
-❯ gcloud config get project
-firezone-staging
-```
-
-Then you want to figure out which specific instance you want to connect to:
-
-```bash
-❯ gcloud compute instances list
-NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS
-api-b02t us-east1-d n1-standard-1 10.128.0.22 RUNNING
-api-srkp us-east1-d n1-standard-1 10.128.0.23 RUNNING
-web-51wd us-east1-d n1-standard-1 10.128.0.21 RUNNING
-web-6k3n us-east1-d n1-standard-1 10.128.0.20 RUNNING
-```
-
-SSH into the host VM:
-
-```bash
-❯ gcloud compute ssh api-b02t --tunnel-through-iap
-No zone specified. Using zone [us-east1-d] for instance: [api-b02t].
-...
-
- ########################[ Welcome ]########################
- # You have logged in to the guest OS. #
- # To access your containers use 'docker attach' command #
- ###########################################################
-
-
-andrew@api-b02t ~ $ $(docker ps | grep klt- | head -n 1 | awk '{split($NF, arr, "-"); print "docker exec -it "$NF" bin/"arr[2]" remote";}')
-Erlang/OTP 25 [erts-13.1.4] [source] [64-bit] [smp:1:1] [ds:1:1:10] [async-threads:1] [jit]
-
-Interactive Elixir (1.14.3) - press Ctrl+C to exit (type h() ENTER for help)
-iex(api@api-b02t.us-east1-d.c.firezone-staging.internal)1>
-```
-
-One-liner to connect to a running application container:
-
-```bash
-❯ gcloud compute ssh $(gcloud compute instances list | grep "web-" | tail -n 1 | awk '{ print $1 }') --tunnel-through-iap -- '$(docker ps | grep klt- | head -n 1 | awk '\''{split($NF, arr, "-"); print "docker exec -it " $NF " bin/" arr[2] " remote";}'\'')'
-
-Interactive Elixir (1.15.2) - press Ctrl+C to exit (type h() ENTER for help)
-iex(web@web-w2f6.us-east1-d.c.firezone-staging.internal)1>
-```
-
-### Quickly provisioning an account
-
-Useful for onboarding beta customers. See the `Domain.Ops.provision_account/1`
-function:
-
-```elixir
-iex> Domain.Ops.create_and_provision_account(%{
- name: "Customer Account",
- slug: "customer_account",
- admin_name: "Test User",
- admin_email: "test@firezone.localhost"
-})
-```
-
-### Creating an account on staging instance using CLI
-
-```elixir
-❯ gcloud compute ssh web-3vmw --tunnel-through-iap
-
-andrew@web-3vmw ~ $ docker ps --format json | jq '"\(.ID) \(.Image)"'
-"09eff3c0ebe8 us-east1-docker.pkg.dev/firezone-staging/firezone/web:b9c11007a4e230ab28f0138afc98188b1956dfd3"
-
-andrew@web-3vmw ~ $ docker exec -it 09eff3c0ebe8 bin/web remote
-Erlang/OTP 26 [erts-14.0.2] [source] [64-bit] [smp:1:1] [ds:1:1:20] [async-threads:1] [jit]
-
-Interactive Elixir (1.15.2) - press Ctrl+C to exit (type h() ENTER for help)
-
-iex(web@web-3vmw.us-east1-d.c.firezone-staging.internal)1> {:ok, account} = Domain.Accounts.create_account(%{name: "Firezone", slug: "firezone"})
-{:ok, ...}
-
-iex(web@web-3vmw.us-east1-d.c.firezone-staging.internal)2> {:ok, email_provider} = Domain.Auth.create_provider(account, %{name: "Email (OTP)", adapter: :email, adapter_config: %{}})
-{:ok, ...}
-
-iex(web@web-3vmw.us-east1-d.c.firezone-staging.internal)3> {:ok, actor} = Domain.Actors.create_actor(account, %{type: :account_admin_user, name: "Andrii Dryga"})
-{:ok, ...}
-
-iex(web@web-3vmw.us-east1-d.c.firezone-staging.internal)4> {:ok, identity} = Domain.Auth.upsert_identity(actor, email_provider, %{provider_identifier: "a@firezone.dev", provider_identifier_confirmation: "a@firezone.dev"})
-...
-
-iex(web@web-3vmw.us-east1-d.c.firezone-staging.internal)5> context = %Domain.Auth.Context{type: :browser, user_agent: "User-Agent: iOS/12.7 (iPhone) connlib/0.7.412", remote_ip: {127, 0, 0, 1}}
-
-iex(web@web-3vmw.us-east1-d.c.firezone-staging.internal)6> {:ok, identity} = Domain.Auth.Adapters.Email.request_sign_in_token(identity, context)
-{:ok, ...}
-
-iex(web@web-3vmw.us-east1-d.c.firezone-staging.internal)7> Domain.Mailer.AuthEmail.sign_in_link_email(identity) |> Domain.Mailer.deliver()
-{:ok, %{id: "d24dbe9a-d0f5-4049-ac0d-0df793725a80"}}
-```
-
-### Obtaining admin subject on staging
-
-```elixir
-
-❯ gcloud compute ssh web-2f4j --tunnel-through-iap -- '$(docker ps | grep klt- | head -n 1 | awk '\''{split($NF, arr, "-"); print "docker exec -it " $NF " bin/" arr[2] " remote";}'\'')'
-Erlang/OTP 26 [erts-14.0.2] [source] [64-bit] [smp:1:1] [ds:1:1:20] [async-threads:1] [jit]
-
-Interactive Elixir (1.15.2) - press Ctrl+C to exit (type h() ENTER for help)
-
-iex(web@web-2f4j.us-east1-d.c.firezone-staging.internal)1> account_id = "REPLACE_ME"
-...
-
-iex(web@web-2f4j.us-east1-d.c.firezone-staging.internal)2> context = %Domain.Auth.Context{type: :browser, user_agent: "User-Agent: iOS/12.7 (iPhone) connlib/0.7.412", remote_ip: {127, 0, 0, 1}}
-...
-
-iex(web@web-2f4j.us-east1-d.c.firezone-staging.internal)3> [actor | _] = Domain.Actors.Actor.Query.by_type(:account_admin_user) |> Domain.Actors.Actor.Query.by_account_id(account_id) |> Domain.Repo.all()
-...
-
-iex(web@web-2f4j.us-east1-d.c.firezone-staging.internal)4> [identity | _] = Domain.Auth.Identity.Query.by_actor_id(actor.id) |> Domain.Repo.all()
-...
-
-iex(web@web-2f4j.us-east1-d.c.firezone-staging.internal)5> token = Domain.Auth.create_token(identity, context, "", nil)
-...
-
-iex(web@web-2f4j.us-east1-d.c.firezone-staging.internal)6> browser_token = Domain.Tokens.encode_fragment!(token)
-...
-
-iex(web@web-2f4j.us-east1-d.c.firezone-staging.internal)7> {:ok, subject} = Domain.Auth.authenticate(browser_token, context)
-```
-
-### Rotate relay token
-
-```elixir
-
-iex(web@web-xxxx.us-east1-d.c.firezone-staging.internal)1> group = Domain.Repo.one!(Domain.Relays.Group.Query.global())
-...
-
-iex(web@web-xxxx.us-east1-d.c.firezone-staging.internal)2> {:ok, token} = Domain.Relays.create_token(group, %{})
-...
-```
-
-## Connection to production Cloud SQL instance
-
-Install
-[`cloud-sql-proxy`](https://cloud.google.com/sql/docs/postgres/connect-instance-auth-proxy)
-(eg. `brew install cloud-sql-proxy`) and run:
-
-First, obtain a fresh token:
-
-```bash
-gcloud auth application-default login
-```
-
-```bash
-cloud-sql-proxy --auto-iam-authn "firezone-prod:us-east1:firezone-prod?address=0.0.0.0&port=9000"
-```
-
-Then you can connect to the PostgreSQL using `psql`:
-
-```bash
-# Use your work email as username to connect
-PG_USER=$(gcloud auth list --filter=status:ACTIVE --format="value(account)" | head -n 1)
-psql "host=localhost port=9000 sslmode=disable dbname=firezone user=${PG_USER}"
-```
-
-### Connecting to Cloud SQL instance as the `firezone` user
-
-Some operations like DROP'ing indexes to recreate them require you to connect as the table owner, which in our case is the `firezone` user.
-
-The password for this user is randomly generated by Terraform, so to connect as this user you need to obtain the password
-from the Application configuration inside a running elixir container.
-
-First, [obtain an iex shell](#connecting-to-a-staging-or-production-instances), then view the password with:
-
-```elixir
-Application.get_env(:domain, Domain.Repo)
-```
-
-Now, you can connect to the Cloud SQL instance as the `firezone` user:
-
-```bash
-psql "host=localhost port=9000 sslmode=disable dbname=firezone user=firezone"
-```
-
-## Deploying
-
-### Apply Terraform changes without deploying new containers
-
-This can be helpful when you want to quickly iterate over Terraform configuration in staging environment, without
-having to merge for every single apply attempt.
-
-Switch to the staging environment:
-
-```bash
-cd terraform/environments/staging
-```
-
-and apply changes reusing previous container versions:
-
-```bash
-terraform apply -var image_tag=$(terraform output -raw image_tag)
-```
-
-### Deploying production
-
-Before deploying, check if the `main` branch has any breaking changes since the last deployment. You can do this by comparing the `main` branch with the last deployed commit, which you can find [here](https://github.com/firezone/firezone/deployments/gcp_production).
-
-Here is a one-liner to open the comparison in your browser:
-
-```bash
-open "https://github.com/firezone/firezone/compare/$(curl -L -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" "https://api.github.com/repos/firezone/firezone/actions/workflows/deploy.yml/runs?status=completed&per_page=1" | jq -r '.workflow_runs[0].head_commit.id')...main"
-```
-
-If there are any breaking changes, make sure to confirm with the rest of the team on a rollout strategy before proceeding with any of the steps listed below.
-
-Then, go to ["Deploy Production"](https://github.com/firezone/firezone/actions/workflows/deploy.yml) CI workflow and click "Run Workflow".
-
-1. In the form that appears, read the warning and check the checkbox next to it.
-2. The main branch is selected by default for deployment. To deploy a previous version, enter the commit SHA in the "Image tag to deploy" field.
- The commit MUST be from the `main` branch.
-3. Click "Run Workflow" to start the process.
-
-The workflow will run all the way till the `deploy-production` step (which runs `terraform apply`) and wait for an approval from one of the project owners,
-message one of your colleagues to approve it.
-
-#### Deployment Takes Too Long to Complete
-
-Typically, `terraform apply` takes around 15 minutes in production. If it's taking longer (or you want to monitor the status), here are a few things you can check:
-
-1. **Monitor the run status in [Terraform Cloud](https://app.terraform.io/app/firezone/workspaces/production/runs).**
-2. **Check the status of Instance Groups in [Google Cloud Console](https://console.cloud.google.com/compute/instanceGroups/list?project=firezone-prod).**
-3. [Check the logs](#viewing-logs) for the deployed instances.
-
-For instance groups stuck in the `UPDATING` state:
-
-- Open the group and look for any errors. Typically, if deployment is stuck, you'll find one instance in the group with an error (and a recent creation time), while the others are pending updates.
-- To quickly view logs for that instance, click the instance name and then click the `Logging` link.
-
-_Do not panic—our production environment should remain stable. GCP and Terraform are designed to keep old instances running until the new ones are healthy._
-
-#### Common Reasons for Deployment Issues
-
-**1. A Bug in the Code**
-
-- This can either crash the instance or make it unresponsive (you’ll notice failing health checks and error logs).
-- If this happens, ensure there were no database migrations as part of the changes (check `priv/repo/migrations`).
-- If no migrations are involved, rollback the deployment. To do this, cancel the currently running deployment,
- find the last successful deployment in Terraform Cloud, copy the `image_tag` from its output, and run:
-
- ```bash
- cd terraform/environments/production
- terraform apply -var image_tag=
- ```
-
-- You can also rollback a specific component by overriding its image tag in the `terraform apply` command:
-
- ```bash
- terraform apply -var image_tag= -var _image_tag=
- ```
-
- _If there were migrations and they’ve already been applied, proceed to the next option._
-
-**2. An Issue with the Migration**
-
-- You’ll notice failing health checks and error logs related to the migration.
-- You can either:
- - Fix the data causing the migration to fail (refer to [Connection to Production Cloud SQL Instance](#connection-to-production-cloud-sql-instance)).
- - Fix the migration code and redeploy.
-
-**3. Insufficient Resources to Deploy New Instances**
-
-- If there are no errors but updates are pending, there might not be enough resources to deploy new instances.
-- This can be found in the Errors tab of the instance group.
-
- Typically, this issue resolves itself as old reservations are freed up.
-
-## Monitoring and Troubleshooting
-
-### Viewing logs
-
-Logs can be viewed via th [Logs Explorer](https://console.cloud.google.com/logs)
-in GCP, or via the `gcloud` CLI:
-
-```bash
-# First, login
-> gcloud auth login
-
-# Always make sure you're in the correct environment
-> gcloud config get project
-firezone-staging
-
-# Now you can stream logs directly to your terminal.
-
-############
-# Examples #
-############
-
-# Stream all Elixir error logs:
-> gcloud logging read "jsonPayload.message.severity=ERROR"
-
-# Stream Web app logs (portal UI):
-> gcloud logging read 'jsonPayload."cos.googleapis.com/container_name":web'
-
-# Stream API app logs (connlib control plane):
-> gcloud logging read 'jsonPayload."cos.googleapis.com/container_name":api'
-
-# For more info on the filter expression syntax, see:
-# https://cloud.google.com/logging/docs/view/logging-query-language
-```
-
-Here is a helpful filter to show all errors and crashes:
-
-```
-resource.type="gce_instance"
-(severity>=ERROR OR "Kernel pid terminated" OR "Crash dump is being written")
--protoPayload.@type="type.googleapis.com/google.cloud.audit.AuditLog"
--logName:"/logs/GCEGuestAgent"
--logName:"/logs/OSConfigAgent"
--logName:"/logs/ops-agent-fluent-bit"
-```
-
-An alert will be sent to the `#feed-proudction` Slack channel when a new error is logged that matches this filter.
-You can also see all errors in [Google Cloud Error Reporting](https://console.cloud.google.com/errors?project=firezone-prod).
-
-Sometimes logs will not provide enough context to understand the issue. In those cases you can
-try to filter by the `trace` field to get more information. Copy the `trace` value from a log entry
-and use it in the filter:
-
-```
-resource.type="gce_instance"
-jsonPayload.trace:""
-```
-
-Note: If you simply click "Show entries for this trace" in the log entry, it will
-automatically **append** the filter for you. You might want to remove rest of filters
-so you can see all logs for that trace.
-
-## Viewing metrics
-
-Metrics can be viewed via the [Metrics Explorer](https://console.cloud.google.com/monitoring/metrics-explorer) in GCP.
-
-## Viewing traces
-
-Traces can be viewed via the [Trace Explorer](https://console.cloud.google.com/traces/list) in GCP.
-They are mostly helpful for debugging Clients, Relays and Gateways.
-
-For example, if you want to find all traces for client management processes, you can use the following filter:
-
-```
-RootSpan: client.connect
-```
-
-Then you can drill down either by using a `client_id: ` or an `account_id: `.
-
-Note: For WS API processes, the total trace duration might not be helpful since a single trace is defined for
-the entire connection lifespan.
+See [CONTRIBUTING](../docs/CONTRIBUTING.md)
diff --git a/scripts/nix/flake.nix b/scripts/nix/flake.nix
index 7ad6c3719..2ea395583 100644
--- a/scripts/nix/flake.nix
+++ b/scripts/nix/flake.nix
@@ -35,7 +35,6 @@
zenity
desktop-file-utils
android-tools
- terraform
llvmPackages.bintools-unwrapped
bpftools
diff --git a/terraform/.gitignore b/terraform/.gitignore
deleted file mode 100644
index 03d6e5af8..000000000
--- a/terraform/.gitignore
+++ /dev/null
@@ -1,13 +0,0 @@
-# Ignore Terraform state and temporary files
-**/.terraform
-**/*.tfstate.backup
-**/terraform.tfstate.d
-**/terraform.tfvars
-out.plan
-*.tfstate
-
-# Don't ever commit these files to git
-*.p12
-*id_rsa*
-*.key
-*.csr
diff --git a/terraform/environments b/terraform/environments
deleted file mode 160000
index ec1cb1393..000000000
--- a/terraform/environments
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit ec1cb13935864a441c4090e8bc49e847cf9f4e29
diff --git a/terraform/examples/.gitignore b/terraform/examples/.gitignore
deleted file mode 100644
index 809648bda..000000000
--- a/terraform/examples/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-**/.terraform.*
diff --git a/terraform/examples/README.md b/terraform/examples/README.md
deleted file mode 100644
index 5cfa88f85..000000000
--- a/terraform/examples/README.md
+++ /dev/null
@@ -1,16 +0,0 @@
-# Terraform Examples
-
-This directory contains examples of how to use Terraform to deploy Firezone
-Gateways to your infrastructure.
-
-## Examples
-
-Each example below is self-contained and includes a `README.md` with
-instructions on how to deploy the example.
-
-### Google Cloud Platform (GCP)
-
-- [NAT Gateway](./google-cloud/nat-gateway): This example shows how to deploy
- one or more Firezone Gateways in a single GCP VPC that is configured with a
- Cloud NAT for egress. Read this if you're looking to deploy Firezone Gateways
- behind a single, shared static IP address on GCP.
diff --git a/terraform/examples/google-cloud/nat-gateway/README.md b/terraform/examples/google-cloud/nat-gateway/README.md
deleted file mode 100644
index 82f9d5d0f..000000000
--- a/terraform/examples/google-cloud/nat-gateway/README.md
+++ /dev/null
@@ -1,4 +0,0 @@
-# Deploy Firezone on GCP with Terraform
-
-See [our docs for a detailed guide](/kb/automate/terraform/gcp) on deploying
-Firezone on GCP with Terraform using this example.
diff --git a/terraform/examples/google-cloud/nat-gateway/main.tf b/terraform/examples/google-cloud/nat-gateway/main.tf
deleted file mode 100644
index 9eb183a58..000000000
--- a/terraform/examples/google-cloud/nat-gateway/main.tf
+++ /dev/null
@@ -1,212 +0,0 @@
-module "google_firezone_gateway" {
- source = "github.com/firezone/firezone/terraform/modules/google-cloud/apps/gateway-region-instance-group"
- # If you are changing this example along with the module, you should use the local path:
- # source = "../../../modules/google-cloud/apps/gateway-region-instance-group"
-
- project_id = var.project_id
-
- compute_network = google_compute_network.firezone.id
- compute_subnetwork = google_compute_subnetwork.firezone.id
-
- compute_instance_replicas = var.replicas
- compute_instance_type = var.machine_type
- compute_region = var.region
-
- # Since we are behind a NAT gateway, we don't need public IP addresses
- # to be automatically provisioned for the instances
- compute_provision_public_ipv6_address = false
- compute_provision_public_ipv4_address = false
-
- vsn = "latest"
-
- observability_log_level = "info"
-
- token = var.token
-}
-
-################################################################################
-## Google Cloud Project
-################################################################################
-
-variable "project_id" {
- type = string
- description = "Google Cloud Project ID"
-}
-
-################################################################################
-## Compute
-################################################################################
-
-variable "region" {
- type = string
- description = "Region to deploy the Gateway(s) in."
-}
-
-variable "replicas" {
- type = number
- description = "Number of Gateway replicas to deploy in the availability zone."
- default = 3
-}
-
-variable "machine_type" {
- type = string
- default = "n1-standard-1"
-}
-
-################################################################################
-## Observability
-################################################################################
-
-variable "log_level" {
- type = string
- nullable = false
- default = "info"
-
- description = "Sets RUST_LOG environment variable to configure the Gateway's log level. Default: 'info'."
-}
-
-################################################################################
-## Firezone
-################################################################################
-
-variable "token" {
- type = string
- description = "Gateway token to use for authentication."
-}
-
-variable "subnet_cidr" {
- type = string
- description = "CIDR Range to use for subnet where Gateway(s) are deployed"
-}
-
-provider "google" {
- project = var.project_id
- region = var.region
-}
-
-resource "google_project_service" "compute-api" {
- project = var.project_id
- service = "compute.googleapis.com"
-}
-
-resource "google_service_account" "firezone" {
- account_id = "firezone-gateway"
- display_name = "Firezone Gateway Service Account"
-}
-
-# We create a new network and subnetwork. In real-world scenarios,
-# you would likely use an existing ones where your application is deployed.
-resource "google_compute_network" "firezone" {
- name = "firezone-gateway"
- auto_create_subnetworks = false
- enable_ula_internal_ipv6 = true
- depends_on = [google_project_service.compute-api]
-}
-
-resource "google_compute_subnetwork" "firezone" {
- project = var.project_id
-
- name = "firezone-gateways"
-
- stack_type = "IPV4_IPV6"
-
- ip_cidr_range = var.subnet_cidr
- region = var.region
- network = google_compute_network.firezone.id
-
- ipv6_access_type = "INTERNAL"
-
- private_ip_google_access = true
-}
-
-# Allocate IPv4 addresses for the NAT gateway
-resource "google_compute_address" "ipv4" {
- project = var.project_id
- name = "firezone-gateway-nat-ipv4"
- ip_version = "IPV4"
-}
-
-# Create a router and NAT to allow outbound traffic
-resource "google_compute_router" "firezone" {
- name = "firezone-gateway-router"
- network = google_compute_network.firezone.id
-}
-
-resource "google_compute_router_nat" "firezone" {
- name = "firezone-gateway-nat"
- router = google_compute_router.firezone.name
-
- nat_ip_allocate_option = "MANUAL_ONLY"
- nat_ips = [
- google_compute_address.ipv4.self_link,
- ]
-
- source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS"
- subnetwork {
- name = google_compute_subnetwork.firezone.id
- source_ip_ranges_to_nat = ["ALL_IP_RANGES"]
- }
-}
-
-# Configure Firewall to allow outbound traffic
-resource "google_compute_firewall" "gateways-egress-ipv4" {
- project = var.project_id
-
- name = "firezone-gateways-egress-ipv4"
- network = google_compute_network.firezone.id
- direction = "EGRESS"
-
- target_tags = module.gateways.target_tags
- destination_ranges = ["0.0.0.0/0"]
-
- allow {
- protocol = "all"
- }
-}
-
-resource "google_compute_firewall" "gateways-egress-ipv6" {
- project = var.project_id
-
- name = "firezone-gateways-egress-ipv6"
- network = google_compute_network.firezone.id
- direction = "EGRESS"
-
- target_tags = module.gateways.target_tags
- destination_ranges = ["::/0"]
-
- allow {
- protocol = "all"
- }
-}
-
-# Allow SSH access to the gateways. This is optional but helpful for debugging
-# and administration of the gateways. Since they're not publicly accessible,
-# you need to tunnel through IAP:
-#
-# gcloud compute instances list --project
-# gcloud compute ssh --tunnel-through-iap --project gateway-XXXX
-resource "google_compute_firewall" "ssh-rule" {
- name = "allow-gateways-ssh"
- network = google_compute_network.firezone.id
-
- allow {
- protocol = "tcp"
- ports = ["22"]
- }
-
- target_tags = module.gateways.target_tags
- source_ranges = ["35.235.240.0/20"] // IAP CIDR
-}
-
-output "static_ip_addresses" {
- value = [google_compute_address.ipv4.address]
-}
-
-terraform {
- required_providers {
- google = {
- source = "hashicorp/google"
- version = "5.20"
- }
- }
-}
diff --git a/terraform/modules/aws/bastion/main.tf b/terraform/modules/aws/bastion/main.tf
deleted file mode 100644
index 38763f755..000000000
--- a/terraform/modules/aws/bastion/main.tf
+++ /dev/null
@@ -1,19 +0,0 @@
-resource "aws_instance" "this" {
- ami = var.ami
- instance_type = var.instance_type
- monitoring = var.monitoring
- subnet_id = var.subnet_id
- vpc_security_group_ids = var.vpc_security_group_ids
- associate_public_ip_address = var.associate_public_ip_address
- user_data_replace_on_change = true
-
- key_name = var.key_name
- user_data = file("${path.module}/scripts/setup.sh")
-
- root_block_device {
- volume_type = "gp3"
- volume_size = 20
- }
-
- tags = merge({ "Name" = var.name }, var.instance_tags, var.tags)
-}
diff --git a/terraform/modules/aws/bastion/outputs.tf b/terraform/modules/aws/bastion/outputs.tf
deleted file mode 100644
index 11677e76d..000000000
--- a/terraform/modules/aws/bastion/outputs.tf
+++ /dev/null
@@ -1,55 +0,0 @@
-output "id" {
- description = "The ID of the instance"
- value = try(
- aws_instance.this.id,
- null,
- )
-}
-
-output "arn" {
- description = "The ARN of the instance"
- value = try(
- aws_instance.this.arn,
- null,
- )
-}
-
-output "instance_state" {
- description = "The state of the instance"
- value = try(
- aws_instance.this.instance_state,
- null,
- )
-}
-
-output "primary_network_interface_id" {
- description = "The ID of the instance's primary network interface"
- value = try(
- aws_instance.this.primary_network_interface_id,
- null,
- )
-}
-
-output "public_ip" {
- description = "The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached"
- value = try(
- aws_instance.this.public_ip,
- null,
- )
-}
-
-output "private_ip" {
- description = "The private IP address assigned to the instance"
- value = try(
- aws_instance.this.private_ip,
- null,
- )
-}
-
-output "ipv6_addresses" {
- description = "The IPv6 address assigned to the instance, if applicable"
- value = try(
- aws_instance.this.ipv6_addresses,
- [],
- )
-}
diff --git a/terraform/modules/aws/bastion/scripts/setup.sh b/terraform/modules/aws/bastion/scripts/setup.sh
deleted file mode 100644
index 2d3065eea..000000000
--- a/terraform/modules/aws/bastion/scripts/setup.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/bash
-
-set -xe
-
-# Install fail2ban
-sudo apt-get update
-sudo apt-get install -y fail2ban
-
-ORIG_CONF="/etc/fail2ban/jail.conf"
-LOCAL_CONF="/etc/fail2ban/jail.local"
-
-if [ -f "${ORIG_CONF}" ]; then
- # Configure fail2ban
- sudo cp "${ORIG_CONF}" "${LOCAL_CONF}"
- sudo sed -i 's/^bantime\s*= 10m$/bantime = 30m/' "${LOCAL_CONF}"
- sudo sed -i 's/^findtime\s*= 10m/findtime = 30m/' "${LOCAL_CONF}"
- sudo sed -i 's/maxretry\s*= 5/maxretry = 3/' "${LOCAL_CONF}"
-
- # Enable and Start fail2ban
- sudo systemctl enable --now fail2ban
-else
- # If fail2ban is not on the sysytem, something has gone wrong
- echo "Fail2Ban was not found on the system! Exiting..."
-fi
-
-# Turn on automatic upgrades/reboots
-UPGRADE_CONF_FILE="/etc/apt/apt.conf.d/50unattended-upgrades"
-
-sudo cp $UPGRADE_CONF_FILE /tmp/unattended-upgrades.conf
-sudo sed -i 's/\/\/\(\s*"\${distro_id}:\${distro_codename}-updates";\)/ \1/' "${UPGRADE_CONF_FILE}"
-sudo sed -i 's/\/\/\(Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";\)/\1/' "${UPGRADE_CONF_FILE}"
-sudo sed -i 's/\/\/\(Unattended-Upgrade::Automatic-Reboot \)"false";/\1 "true";/' "${UPGRADE_CONF_FILE}"
-sudo sed -i 's/\/\/\(Unattended-Upgrade::Automatic-Reboot-Time \)"02:00";/\1 "07:00";/' "${UPGRADE_CONF_FILE}"
-sudo sed -i 's/\/\/\(Unattended-Upgrade::Automatic-Reboot-WithUsers "true";\)/\1/' "${UPGRADE_CONF_FILE}"
diff --git a/terraform/modules/aws/bastion/variables.tf b/terraform/modules/aws/bastion/variables.tf
deleted file mode 100644
index 5eb91483f..000000000
--- a/terraform/modules/aws/bastion/variables.tf
+++ /dev/null
@@ -1,82 +0,0 @@
-variable "ami" {
- type = string
- description = "AMI ID for the EC2 instance"
- default = "ami-0b2a9065573b0a9c9" # Ubuntu 22.04 in us-east-1
-
- validation {
- condition = length(var.ami) > 4 && substr(var.ami, 0, 4) == "ami-"
- error_message = "Please provide a valid value for variable AMI."
- }
-}
-
-variable "associate_public_ip_address" {
- description = "Whether to associate a public IP address with an instance in a VPC"
- type = bool
- default = true
-}
-
-variable "instance_type" {
- description = "The type of instance to start"
- type = string
- default = "t3.micro"
-}
-
-variable "instance_tags" {
- description = "Additional tags for the instance"
- type = map(string)
- default = {}
-}
-
-variable "ipv6_addresses" {
- description = "Specify one or more IPv6 addresses from the range of the subnet to associate with the primary network interface"
- type = list(string)
- default = null
-}
-
-variable "key_name" {
- description = "Key name of the Key Pair to use for the instance; which can be managed using the `aws_key_pair` resource"
- type = string
- default = null
-}
-
-variable "monitoring" {
- description = "If true, the launched EC2 instance will have detailed monitoring enabled"
- type = bool
- default = false
-}
-
-variable "name" {
- description = "Name to be used on EC2 instance created"
- type = string
- default = ""
-}
-
-variable "private_ip" {
- description = "Private IP address to associate with the instance in a VPC"
- type = string
- default = null
-}
-
-variable "root_block_device" {
- description = "Customize details about the root block device of the instance. See Block Devices below for details"
- type = list(any)
- default = []
-}
-
-variable "subnet_id" {
- description = "The VPC Subnet ID to launch in"
- type = string
- default = null
-}
-
-variable "tags" {
- description = "A mapping of tags to assign to the resource"
- type = map(string)
- default = {}
-}
-
-variable "vpc_security_group_ids" {
- description = "A list of security group IDs to associate with"
- type = list(string)
- default = null
-}
diff --git a/terraform/modules/aws/coredns/main.tf b/terraform/modules/aws/coredns/main.tf
deleted file mode 100644
index 822fe293a..000000000
--- a/terraform/modules/aws/coredns/main.tf
+++ /dev/null
@@ -1,25 +0,0 @@
-resource "aws_instance" "this" {
- ami = var.ami
- instance_type = var.instance_type
- monitoring = var.monitoring
- subnet_id = var.subnet_id
- vpc_security_group_ids = var.vpc_security_group_ids
- associate_public_ip_address = var.associate_public_ip_address
- private_ip = var.private_ip
- key_name = var.key_name
- user_data_replace_on_change = true
-
- user_data = templatefile("${path.module}/templates/cloud-init.yaml", {
- container_name = "coredns"
- container_image = "coredns/coredns"
- host_ip = var.private_ip
- dns_records = concat([{ name = "coredns", value = var.private_ip }], var.dns_records)
- })
-
- root_block_device {
- volume_type = "gp3"
- volume_size = 15
- }
-
- tags = merge({ "Name" = var.name }, var.instance_tags, var.tags)
-}
diff --git a/terraform/modules/aws/coredns/outputs.tf b/terraform/modules/aws/coredns/outputs.tf
deleted file mode 100644
index 11677e76d..000000000
--- a/terraform/modules/aws/coredns/outputs.tf
+++ /dev/null
@@ -1,55 +0,0 @@
-output "id" {
- description = "The ID of the instance"
- value = try(
- aws_instance.this.id,
- null,
- )
-}
-
-output "arn" {
- description = "The ARN of the instance"
- value = try(
- aws_instance.this.arn,
- null,
- )
-}
-
-output "instance_state" {
- description = "The state of the instance"
- value = try(
- aws_instance.this.instance_state,
- null,
- )
-}
-
-output "primary_network_interface_id" {
- description = "The ID of the instance's primary network interface"
- value = try(
- aws_instance.this.primary_network_interface_id,
- null,
- )
-}
-
-output "public_ip" {
- description = "The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached"
- value = try(
- aws_instance.this.public_ip,
- null,
- )
-}
-
-output "private_ip" {
- description = "The private IP address assigned to the instance"
- value = try(
- aws_instance.this.private_ip,
- null,
- )
-}
-
-output "ipv6_addresses" {
- description = "The IPv6 address assigned to the instance, if applicable"
- value = try(
- aws_instance.this.ipv6_addresses,
- [],
- )
-}
diff --git a/terraform/modules/aws/coredns/templates/cloud-init.yaml b/terraform/modules/aws/coredns/templates/cloud-init.yaml
deleted file mode 100644
index c82c2f049..000000000
--- a/terraform/modules/aws/coredns/templates/cloud-init.yaml
+++ /dev/null
@@ -1,67 +0,0 @@
-#cloud-config
-
-write_files:
- - path: /etc/coredns/Corefile
- permissions: "0644"
- owner: root
- content: |
- .:53 {
- forward . 1.1.1.1 9.9.9.9
- log
- errors
- }
-
- firezone.internal:53 {
- file /etc/coredns/db.firezone.internal
- log
- errors
- }
-
- - path: /etc/coredns/db.firezone.internal
- permissions: "0644"
- owner: root
- content: |
- $ORIGIN firezone.internal.
- $TTL 1h
- @ IN SOA ns1.firezone.internal. admin.firezone.internal. (
- 2024010501 ; Serial
- 1h ; Refresh (1 hour)
- 10m ; Retry (10 minutes)
- 7d ; Expire (7 days)
- 1h ; Minimum TTL (1 hour)
- )
-
- %{ for record in dns_records ~}
- ${record.name} IN A ${record.value}
- %{ endfor ~}
-
- - path: /etc/systemd/system/coredns.service
- permissions: "0644"
- owner: root
- content: |
- [Unit]
- Description=Start a CoreDNS container
-
- [Service]
- TimeoutStartSec=0
- Restart=always
- ExecStartPre=/usr/bin/docker pull ${container_image}
- ExecStart=/bin/sh -c 'docker run --name=${container_name} -p ${host_ip}:53:53 -p ${host_ip}:53:53/udp -v /etc/coredns:/etc/coredns --restart=unless-stopped --pull=always ${container_image} -conf /etc/coredns/Corefile'
- ExecStop=/usr/bin/docker stop coredns
- ExecStopPost=/usr/bin/docker rm coredns
-
-runcmd:
- - sudo apt-get update
- - sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common
- - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
- - echo "deb [arch=$(dpkg --print-architecture) 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-get update
- - sudo apt-get install -y docker-ce docker-ce-cli containerd.io
- - echo '{"experimental":true,"ip6tables":true,"ipv6":true,"fixed-cidr-v6":"fd00::/80"}' | sudo tee -a /etc/docker/daemon.json
- - sudo usermod -aG docker ubuntu
- - sudo systemctl enable docker
- - sudo systemctl stop docker
- - sudo systemctl start docker
- - sudo systemctl daemon-reload
- - sudo sed -r -i 's/^\s*(.* IN A .*)$/\1/' /etc/coredns/db.firezone.internal
- - sudo systemctl start coredns.service
diff --git a/terraform/modules/aws/coredns/variables.tf b/terraform/modules/aws/coredns/variables.tf
deleted file mode 100644
index 29f2f90de..000000000
--- a/terraform/modules/aws/coredns/variables.tf
+++ /dev/null
@@ -1,128 +0,0 @@
-variable "ami" {
- description = "AMI ID for the EC2 instance"
- type = string
- default = "ami-0b2a9065573b0a9c9" # Ubuntu 22.04 in us-east-1
-
- validation {
- condition = length(var.ami) > 4 && substr(var.ami, 0, 4) == "ami-"
- error_message = "Please provide a valid value for variable AMI."
- }
-}
-
-variable "api_url" {
- description = "URL of the control plane endpoint."
- type = string
- default = null
-}
-
-
-variable "application_name" {
- description = "Name of the application. Defaults to value of `var.image_name` with `_` replaced to `-`."
- type = string
- nullable = true
- default = null
-}
-
-variable "application_version" {
- description = "Version of the application. Defaults to value of `var.image_tag`."
- type = string
- nullable = true
- default = null
-}
-
-variable "associate_public_ip_address" {
- description = "Whether to associate a public IP address with an instance in a VPC"
- type = bool
- default = true
-}
-
-variable "dns_records" {
- description = "List of DNS records to set for CoreDNS."
- type = list(object({
- name = string
- value = string
- }))
- default = []
- nullable = false
-}
-
-variable "instance_type" {
- description = "The type of instance to start"
- type = string
- default = "t3.micro"
-}
-
-variable "instance_tags" {
- description = "Additional tags for the instance"
- type = map(string)
- default = {}
-}
-
-variable "ipv6_addresses" {
- description = "Specify one or more IPv6 addresses from the range of the subnet to associate with the primary network interface"
- type = list(string)
- default = null
-}
-
-variable "key_name" {
- description = "Key name of the Key Pair to use for the instance; which can be managed using the `aws_key_pair` resource"
- type = string
- default = null
-}
-
-variable "monitoring" {
- description = "If true, the launched EC2 instance will have detailed monitoring enabled"
- type = bool
- default = null
-}
-
-variable "name" {
- description = "Name to be used on EC2 instance created"
- type = string
- default = ""
-}
-
-variable "observability_log_level" {
- description = "Sets RUST_LOG environment variable which applications should use to configure Rust Logger. Default: 'info'."
- type = string
- nullable = false
- default = "info"
-
-}
-
-variable "private_ip" {
- description = "Private IP address to associate with the instance in a VPC"
- type = string
- default = null
-}
-
-variable "root_block_device" {
- description = "Customize details about the root block device of the instance. See Block Devices below for details"
- type = list(any)
- default = []
-}
-
-variable "subnet_id" {
- description = "The VPC Subnet ID to launch in"
- type = string
- default = null
-}
-
-variable "tags" {
- description = "A mapping of tags to assign to the resource"
- type = map(string)
- default = {}
-}
-
-variable "token" {
- description = "Portal token to use for authentication."
- type = string
- default = null
- sensitive = true
-}
-
-variable "vpc_security_group_ids" {
- description = "A list of security group IDs to associate with"
- type = list(string)
- default = null
-}
diff --git a/terraform/modules/aws/gateway/main.tf b/terraform/modules/aws/gateway/main.tf
deleted file mode 100644
index 29f39a695..000000000
--- a/terraform/modules/aws/gateway/main.tf
+++ /dev/null
@@ -1,48 +0,0 @@
-locals {
- application_name = var.application_name != null ? var.application_name : var.image
- application_version = var.application_version != null ? var.application_version : var.image_tag
-
- environment_variables = concat([
- {
- name = "RUST_LOG"
- value = var.observability_log_level
- },
- {
- name = "RUST_BACKTRACE"
- value = "full"
- },
- {
- name = "FIREZONE_TOKEN"
- value = var.token
- },
- {
- name = "FIREZONE_API_URL"
- value = var.api_url
- }
- ], var.application_environment_variables)
-}
-
-resource "aws_instance" "this" {
- ami = var.ami
- instance_type = var.instance_type
- monitoring = var.monitoring
- subnet_id = var.subnet_id
- vpc_security_group_ids = var.vpc_security_group_ids
- associate_public_ip_address = var.associate_public_ip_address
- private_ip = var.private_ip
- key_name = var.key_name
- user_data_replace_on_change = true
-
- 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
- })
-
- root_block_device {
- volume_type = "gp3"
- volume_size = 20
- }
-
- tags = merge({ "Name" = var.name }, var.instance_tags, var.tags)
-}
diff --git a/terraform/modules/aws/gateway/outputs.tf b/terraform/modules/aws/gateway/outputs.tf
deleted file mode 100644
index 11677e76d..000000000
--- a/terraform/modules/aws/gateway/outputs.tf
+++ /dev/null
@@ -1,55 +0,0 @@
-output "id" {
- description = "The ID of the instance"
- value = try(
- aws_instance.this.id,
- null,
- )
-}
-
-output "arn" {
- description = "The ARN of the instance"
- value = try(
- aws_instance.this.arn,
- null,
- )
-}
-
-output "instance_state" {
- description = "The state of the instance"
- value = try(
- aws_instance.this.instance_state,
- null,
- )
-}
-
-output "primary_network_interface_id" {
- description = "The ID of the instance's primary network interface"
- value = try(
- aws_instance.this.primary_network_interface_id,
- null,
- )
-}
-
-output "public_ip" {
- description = "The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached"
- value = try(
- aws_instance.this.public_ip,
- null,
- )
-}
-
-output "private_ip" {
- description = "The private IP address assigned to the instance"
- value = try(
- aws_instance.this.private_ip,
- null,
- )
-}
-
-output "ipv6_addresses" {
- description = "The IPv6 address assigned to the instance, if applicable"
- value = try(
- aws_instance.this.ipv6_addresses,
- [],
- )
-}
diff --git a/terraform/modules/aws/gateway/templates/cloud-init.yaml b/terraform/modules/aws/gateway/templates/cloud-init.yaml
deleted file mode 100644
index dfd13630a..000000000
--- a/terraform/modules/aws/gateway/templates/cloud-init.yaml
+++ /dev/null
@@ -1,47 +0,0 @@
-#cloud-config
-
-write_files:
- - 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
- After=docker.service
- Requires=docker.service
-
- [Service]
- TimeoutStartSec=0
- Restart=always
- ExecStartPre=-/usr/bin/docker stop ${container_name}
- ExecStartPre=-/usr/bin/docker rm ${container_name}
- ExecStartPre=/usr/bin/docker pull ${container_image}
- ExecStart=/bin/sh -c 'docker run --rm --name=${container_name} --cap-add=NET_ADMIN --volume /etc/firezone --sysctl net.ipv4.ip_forward=1 --sysctl net.ipv4.conf.all.src_valid_mark=1 --sysctl net.ipv6.conf.all.disable_ipv6=0 --sysctl net.ipv6.conf.all.forwarding=1 --sysctl net.ipv6.conf.default.forwarding=1 --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
-
- [Install]
- WantedBy=multi-user.target
-
-runcmd:
- - sudo apt-get update
- - sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common
- - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
- - echo "deb [arch=$(dpkg --print-architecture) 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-get update
- - sudo apt-get install -y docker-ce docker-ce-cli containerd.io
- - echo '{"experimental":true,"ip6tables":true,"ipv6":true,"fixed-cidr-v6":"fd00::/80"}' | sudo tee -a /etc/docker/daemon.json
- - sudo usermod -aG docker ubuntu
- - sudo systemctl enable docker
- - sudo systemctl stop docker
- - sudo systemctl start docker
- - sudo systemctl daemon-reload
- - sudo systemctl enable --now gateway.service
diff --git a/terraform/modules/aws/gateway/variables.tf b/terraform/modules/aws/gateway/variables.tf
deleted file mode 100644
index b8d24485c..000000000
--- a/terraform/modules/aws/gateway/variables.tf
+++ /dev/null
@@ -1,151 +0,0 @@
-variable "ami" {
- description = "AMI ID for the EC2 instance"
- type = string
- default = "ami-0b2a9065573b0a9c9" # Ubuntu 22.04 in us-east-1
-
- validation {
- condition = length(var.ami) > 4 && substr(var.ami, 0, 4) == "ami-"
- error_message = "Please provide a valid value for variable AMI."
- }
-}
-
-variable "api_url" {
- description = "URL of the control plane endpoint."
- type = string
- default = null
-}
-
-variable "application_environment_variables" {
- description = "List of environment variables to set for all application containers."
- type = list(object({
- name = string
- value = string
- }))
- default = []
- nullable = false
-}
-
-variable "application_name" {
- description = "Name of the application. Defaults to value of `var.image_name` with `_` replaced to `-`."
- type = string
- nullable = true
- default = null
-}
-
-variable "application_version" {
- description = "Version of the application. Defaults to value of `var.image_tag`."
- type = string
- nullable = true
- default = null
-}
-
-variable "associate_public_ip_address" {
- description = "Whether to associate a public IP address with an instance in a VPC"
- type = bool
- default = true
-}
-
-variable "container_registry" {
- description = "Container registry URL to pull the image from."
- type = string
- nullable = false
-}
-
-variable "image" {
- description = "Container image used to deploy the application."
- type = string
- nullable = false
-}
-
-variable "image_repo" {
- description = "Repo of a container image used to deploy the application."
- type = string
- nullable = false
-}
-
-variable "image_tag" {
- description = "Container image used to deploy the application."
- type = string
- nullable = false
-}
-
-variable "instance_type" {
- description = "The type of instance to start"
- type = string
- default = "t3.micro"
-}
-
-variable "instance_tags" {
- description = "Additional tags for the instance"
- type = map(string)
- default = {}
-}
-
-variable "ipv6_addresses" {
- description = "Specify one or more IPv6 addresses from the range of the subnet to associate with the primary network interface"
- type = list(string)
- default = null
-}
-
-variable "key_name" {
- description = "Key name of the Key Pair to use for the instance; which can be managed using the `aws_key_pair` resource"
- type = string
- default = null
-}
-
-variable "monitoring" {
- description = "If true, the launched EC2 instance will have detailed monitoring enabled"
- type = bool
- default = null
-}
-
-variable "name" {
- description = "Name to be used on EC2 instance created"
- type = string
- default = ""
-}
-
-variable "observability_log_level" {
- description = "Sets RUST_LOG environment variable which applications should use to configure Rust Logger. Default: 'info'."
- type = string
- nullable = false
- default = "info"
-
-}
-
-variable "private_ip" {
- description = "Private IP address to associate with the instance in a VPC"
- type = string
- default = null
-}
-
-variable "root_block_device" {
- description = "Customize details about the root block device of the instance. See Block Devices below for details"
- type = list(any)
- default = []
-}
-
-variable "subnet_id" {
- description = "The VPC Subnet ID to launch in"
- type = string
- default = null
-}
-
-variable "tags" {
- description = "A mapping of tags to assign to the resource"
- type = map(string)
- default = {}
-}
-
-variable "token" {
- description = "Portal token to use for authentication."
- type = string
- default = null
- sensitive = true
-}
-
-variable "vpc_security_group_ids" {
- description = "A list of security group IDs to associate with"
- type = list(string)
- default = null
-}
diff --git a/terraform/modules/aws/httpbin/main.tf b/terraform/modules/aws/httpbin/main.tf
deleted file mode 100644
index abfbd71a0..000000000
--- a/terraform/modules/aws/httpbin/main.tf
+++ /dev/null
@@ -1,20 +0,0 @@
-resource "aws_instance" "this" {
- ami = var.ami
- instance_type = var.instance_type
- monitoring = var.monitoring
- subnet_id = var.subnet_id
- vpc_security_group_ids = var.vpc_security_group_ids
- associate_public_ip_address = var.associate_public_ip_address
- private_ip = var.private_ip
- user_data_replace_on_change = true
-
- key_name = var.key_name
- user_data = file("${path.module}/scripts/setup.sh")
-
- root_block_device {
- volume_type = "gp3"
- volume_size = 20
- }
-
- tags = merge({ "Name" = var.name }, var.instance_tags, var.tags)
-}
diff --git a/terraform/modules/aws/httpbin/outputs.tf b/terraform/modules/aws/httpbin/outputs.tf
deleted file mode 100644
index 11677e76d..000000000
--- a/terraform/modules/aws/httpbin/outputs.tf
+++ /dev/null
@@ -1,55 +0,0 @@
-output "id" {
- description = "The ID of the instance"
- value = try(
- aws_instance.this.id,
- null,
- )
-}
-
-output "arn" {
- description = "The ARN of the instance"
- value = try(
- aws_instance.this.arn,
- null,
- )
-}
-
-output "instance_state" {
- description = "The state of the instance"
- value = try(
- aws_instance.this.instance_state,
- null,
- )
-}
-
-output "primary_network_interface_id" {
- description = "The ID of the instance's primary network interface"
- value = try(
- aws_instance.this.primary_network_interface_id,
- null,
- )
-}
-
-output "public_ip" {
- description = "The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached"
- value = try(
- aws_instance.this.public_ip,
- null,
- )
-}
-
-output "private_ip" {
- description = "The private IP address assigned to the instance"
- value = try(
- aws_instance.this.private_ip,
- null,
- )
-}
-
-output "ipv6_addresses" {
- description = "The IPv6 address assigned to the instance, if applicable"
- value = try(
- aws_instance.this.ipv6_addresses,
- [],
- )
-}
diff --git a/terraform/modules/aws/httpbin/scripts/setup.sh b/terraform/modules/aws/httpbin/scripts/setup.sh
deleted file mode 100644
index adace5390..000000000
--- a/terraform/modules/aws/httpbin/scripts/setup.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-
-set -xe
-
-sudo apt-get update
-sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common
-curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
-
-echo "deb [arch=$(dpkg --print-architecture) 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-get update
-sudo apt-get install -y docker-ce
-sudo usermod -aG docker ubuntu
-
-docker run \
- --restart=unless-stopped \
- --name=httpbin \
- -p "80:80" \
- kong/httpbin
diff --git a/terraform/modules/aws/httpbin/variables.tf b/terraform/modules/aws/httpbin/variables.tf
deleted file mode 100644
index 6ccc0678a..000000000
--- a/terraform/modules/aws/httpbin/variables.tf
+++ /dev/null
@@ -1,82 +0,0 @@
-variable "ami" {
- type = string
- description = "AMI ID for the EC2 instance"
- default = "ami-0b2a9065573b0a9c9" # Ubuntu 22.04 in us-east-1
-
- validation {
- condition = length(var.ami) > 4 && substr(var.ami, 0, 4) == "ami-"
- error_message = "Please provide a valid value for variable AMI."
- }
-}
-
-variable "associate_public_ip_address" {
- description = "Whether to associate a public IP address with an instance in a VPC"
- type = bool
- default = false
-}
-
-variable "instance_type" {
- description = "The type of instance to start"
- type = string
- default = "t3.micro"
-}
-
-variable "instance_tags" {
- description = "Additional tags for the instance"
- type = map(string)
- default = {}
-}
-
-variable "ipv6_addresses" {
- description = "Specify one or more IPv6 addresses from the range of the subnet to associate with the primary network interface"
- type = list(string)
- default = null
-}
-
-variable "key_name" {
- description = "Key name of the Key Pair to use for the instance; which can be managed using the `aws_key_pair` resource"
- type = string
- default = null
-}
-
-variable "monitoring" {
- description = "If true, the launched EC2 instance will have detailed monitoring enabled"
- type = bool
- default = false
-}
-
-variable "name" {
- description = "Name to be used on EC2 instance created"
- type = string
- default = ""
-}
-
-variable "private_ip" {
- description = "Private IP address to associate with the instance in a VPC"
- type = string
- default = null
-}
-
-variable "root_block_device" {
- description = "Customize details about the root block device of the instance. See Block Devices below for details"
- type = list(any)
- default = []
-}
-
-variable "subnet_id" {
- description = "The VPC Subnet ID to launch in"
- type = string
- default = null
-}
-
-variable "tags" {
- description = "A mapping of tags to assign to the resource"
- type = map(string)
- default = {}
-}
-
-variable "vpc_security_group_ids" {
- description = "A list of security group IDs to associate with"
- type = list(string)
- default = null
-}
diff --git a/terraform/modules/aws/iperf/main.tf b/terraform/modules/aws/iperf/main.tf
deleted file mode 100644
index abfbd71a0..000000000
--- a/terraform/modules/aws/iperf/main.tf
+++ /dev/null
@@ -1,20 +0,0 @@
-resource "aws_instance" "this" {
- ami = var.ami
- instance_type = var.instance_type
- monitoring = var.monitoring
- subnet_id = var.subnet_id
- vpc_security_group_ids = var.vpc_security_group_ids
- associate_public_ip_address = var.associate_public_ip_address
- private_ip = var.private_ip
- user_data_replace_on_change = true
-
- key_name = var.key_name
- user_data = file("${path.module}/scripts/setup.sh")
-
- root_block_device {
- volume_type = "gp3"
- volume_size = 20
- }
-
- tags = merge({ "Name" = var.name }, var.instance_tags, var.tags)
-}
diff --git a/terraform/modules/aws/iperf/outputs.tf b/terraform/modules/aws/iperf/outputs.tf
deleted file mode 100644
index 11677e76d..000000000
--- a/terraform/modules/aws/iperf/outputs.tf
+++ /dev/null
@@ -1,55 +0,0 @@
-output "id" {
- description = "The ID of the instance"
- value = try(
- aws_instance.this.id,
- null,
- )
-}
-
-output "arn" {
- description = "The ARN of the instance"
- value = try(
- aws_instance.this.arn,
- null,
- )
-}
-
-output "instance_state" {
- description = "The state of the instance"
- value = try(
- aws_instance.this.instance_state,
- null,
- )
-}
-
-output "primary_network_interface_id" {
- description = "The ID of the instance's primary network interface"
- value = try(
- aws_instance.this.primary_network_interface_id,
- null,
- )
-}
-
-output "public_ip" {
- description = "The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached"
- value = try(
- aws_instance.this.public_ip,
- null,
- )
-}
-
-output "private_ip" {
- description = "The private IP address assigned to the instance"
- value = try(
- aws_instance.this.private_ip,
- null,
- )
-}
-
-output "ipv6_addresses" {
- description = "The IPv6 address assigned to the instance, if applicable"
- value = try(
- aws_instance.this.ipv6_addresses,
- [],
- )
-}
diff --git a/terraform/modules/aws/iperf/scripts/setup.sh b/terraform/modules/aws/iperf/scripts/setup.sh
deleted file mode 100644
index b01eeb5d6..000000000
--- a/terraform/modules/aws/iperf/scripts/setup.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-
-set -xe
-
-sudo apt-get update
-sudo apt-get install -y iperf3
-
-sudo tee -a /etc/systemd/system/iperf3.service << EOF
-[Unit]
-Description=iperf3 server
-After=syslog.target network.target auditd.service
-
-[Service]
-ExecStart=/usr/bin/iperf3 -s
-
-[Install]
-WantedBy=multi-user.target
-EOF
-
-sudo systemctl enable --now iperf3
diff --git a/terraform/modules/aws/iperf/variables.tf b/terraform/modules/aws/iperf/variables.tf
deleted file mode 100644
index 6ccc0678a..000000000
--- a/terraform/modules/aws/iperf/variables.tf
+++ /dev/null
@@ -1,82 +0,0 @@
-variable "ami" {
- type = string
- description = "AMI ID for the EC2 instance"
- default = "ami-0b2a9065573b0a9c9" # Ubuntu 22.04 in us-east-1
-
- validation {
- condition = length(var.ami) > 4 && substr(var.ami, 0, 4) == "ami-"
- error_message = "Please provide a valid value for variable AMI."
- }
-}
-
-variable "associate_public_ip_address" {
- description = "Whether to associate a public IP address with an instance in a VPC"
- type = bool
- default = false
-}
-
-variable "instance_type" {
- description = "The type of instance to start"
- type = string
- default = "t3.micro"
-}
-
-variable "instance_tags" {
- description = "Additional tags for the instance"
- type = map(string)
- default = {}
-}
-
-variable "ipv6_addresses" {
- description = "Specify one or more IPv6 addresses from the range of the subnet to associate with the primary network interface"
- type = list(string)
- default = null
-}
-
-variable "key_name" {
- description = "Key name of the Key Pair to use for the instance; which can be managed using the `aws_key_pair` resource"
- type = string
- default = null
-}
-
-variable "monitoring" {
- description = "If true, the launched EC2 instance will have detailed monitoring enabled"
- type = bool
- default = false
-}
-
-variable "name" {
- description = "Name to be used on EC2 instance created"
- type = string
- default = ""
-}
-
-variable "private_ip" {
- description = "Private IP address to associate with the instance in a VPC"
- type = string
- default = null
-}
-
-variable "root_block_device" {
- description = "Customize details about the root block device of the instance. See Block Devices below for details"
- type = list(any)
- default = []
-}
-
-variable "subnet_id" {
- description = "The VPC Subnet ID to launch in"
- type = string
- default = null
-}
-
-variable "tags" {
- description = "A mapping of tags to assign to the resource"
- type = map(string)
- default = {}
-}
-
-variable "vpc_security_group_ids" {
- description = "A list of security group IDs to associate with"
- type = list(string)
- default = null
-}
diff --git a/terraform/modules/aws/nat/main.tf b/terraform/modules/aws/nat/main.tf
deleted file mode 100644
index 58a475e10..000000000
--- a/terraform/modules/aws/nat/main.tf
+++ /dev/null
@@ -1,20 +0,0 @@
-resource "aws_instance" "this" {
- ami = var.ami
- instance_type = var.instance_type
- monitoring = var.monitoring
- subnet_id = var.subnet_id
- vpc_security_group_ids = var.vpc_security_group_ids
- associate_public_ip_address = var.associate_public_ip_address
- source_dest_check = false
- user_data_replace_on_change = true
-
- key_name = var.key_name
- user_data = file("${path.module}/scripts/setup.sh")
-
- root_block_device {
- volume_type = "gp3"
- volume_size = 15
- }
-
- tags = merge({ "Name" = var.name }, var.instance_tags, var.tags)
-}
diff --git a/terraform/modules/aws/nat/outputs.tf b/terraform/modules/aws/nat/outputs.tf
deleted file mode 100644
index 11677e76d..000000000
--- a/terraform/modules/aws/nat/outputs.tf
+++ /dev/null
@@ -1,55 +0,0 @@
-output "id" {
- description = "The ID of the instance"
- value = try(
- aws_instance.this.id,
- null,
- )
-}
-
-output "arn" {
- description = "The ARN of the instance"
- value = try(
- aws_instance.this.arn,
- null,
- )
-}
-
-output "instance_state" {
- description = "The state of the instance"
- value = try(
- aws_instance.this.instance_state,
- null,
- )
-}
-
-output "primary_network_interface_id" {
- description = "The ID of the instance's primary network interface"
- value = try(
- aws_instance.this.primary_network_interface_id,
- null,
- )
-}
-
-output "public_ip" {
- description = "The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached"
- value = try(
- aws_instance.this.public_ip,
- null,
- )
-}
-
-output "private_ip" {
- description = "The private IP address assigned to the instance"
- value = try(
- aws_instance.this.private_ip,
- null,
- )
-}
-
-output "ipv6_addresses" {
- description = "The IPv6 address assigned to the instance, if applicable"
- value = try(
- aws_instance.this.ipv6_addresses,
- [],
- )
-}
diff --git a/terraform/modules/aws/nat/scripts/setup.sh b/terraform/modules/aws/nat/scripts/setup.sh
deleted file mode 100644
index bc7a9d76b..000000000
--- a/terraform/modules/aws/nat/scripts/setup.sh
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-
-set -xe
-
-sudo apt-get update
-
-# Enable IP forwarding
-echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf
-sudo sysctl -p
-
-# Setup iptables NAT
-sudo iptables -t nat -A POSTROUTING -o ens5 -s 0.0.0.0/0 -j MASQUERADE
-
-# Save iptables rules in case of reboot
-sudo DEBIAN_FRONTEND=noninteractive apt-get install -y iptables-persistent
-sudo systemctl enable --now netfilter-persistent.service
-sudo mkdir -p /etc/iptables
-sudo /usr/bin/iptables-save | sudo tee -a /etc/iptables/rules.v4
diff --git a/terraform/modules/aws/nat/variables.tf b/terraform/modules/aws/nat/variables.tf
deleted file mode 100644
index 5eb91483f..000000000
--- a/terraform/modules/aws/nat/variables.tf
+++ /dev/null
@@ -1,82 +0,0 @@
-variable "ami" {
- type = string
- description = "AMI ID for the EC2 instance"
- default = "ami-0b2a9065573b0a9c9" # Ubuntu 22.04 in us-east-1
-
- validation {
- condition = length(var.ami) > 4 && substr(var.ami, 0, 4) == "ami-"
- error_message = "Please provide a valid value for variable AMI."
- }
-}
-
-variable "associate_public_ip_address" {
- description = "Whether to associate a public IP address with an instance in a VPC"
- type = bool
- default = true
-}
-
-variable "instance_type" {
- description = "The type of instance to start"
- type = string
- default = "t3.micro"
-}
-
-variable "instance_tags" {
- description = "Additional tags for the instance"
- type = map(string)
- default = {}
-}
-
-variable "ipv6_addresses" {
- description = "Specify one or more IPv6 addresses from the range of the subnet to associate with the primary network interface"
- type = list(string)
- default = null
-}
-
-variable "key_name" {
- description = "Key name of the Key Pair to use for the instance; which can be managed using the `aws_key_pair` resource"
- type = string
- default = null
-}
-
-variable "monitoring" {
- description = "If true, the launched EC2 instance will have detailed monitoring enabled"
- type = bool
- default = false
-}
-
-variable "name" {
- description = "Name to be used on EC2 instance created"
- type = string
- default = ""
-}
-
-variable "private_ip" {
- description = "Private IP address to associate with the instance in a VPC"
- type = string
- default = null
-}
-
-variable "root_block_device" {
- description = "Customize details about the root block device of the instance. See Block Devices below for details"
- type = list(any)
- default = []
-}
-
-variable "subnet_id" {
- description = "The VPC Subnet ID to launch in"
- type = string
- default = null
-}
-
-variable "tags" {
- description = "A mapping of tags to assign to the resource"
- type = map(string)
- default = {}
-}
-
-variable "vpc_security_group_ids" {
- description = "A list of security group IDs to associate with"
- type = list(string)
- default = null
-}
diff --git a/terraform/modules/google-cloud/apps/client-monitor/iam.tf b/terraform/modules/google-cloud/apps/client-monitor/iam.tf
deleted file mode 100644
index 81367cbf0..000000000
--- a/terraform/modules/google-cloud/apps/client-monitor/iam.tf
+++ /dev/null
@@ -1,54 +0,0 @@
-
-# Create IAM role for the application instances
-resource "google_service_account" "application" {
- project = var.project_id
-
- account_id = "app-${local.application_name}"
- display_name = "${local.application_name} app"
- description = "Service account for ${local.application_name} application instances."
-}
-
-## Allow fluentbit 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/client-monitor/main.tf b/terraform/modules/google-cloud/apps/client-monitor/main.tf
deleted file mode 100644
index 22d247e02..000000000
--- a/terraform/modules/google-cloud/apps/client-monitor/main.tf
+++ /dev/null
@@ -1,155 +0,0 @@
-locals {
- application_name = var.application_name
- application_version = var.application_version
-
- application_labels = merge({
- managed_by = "terraform"
- application = local.application_name
- }, var.application_labels)
-
- application_tags = ["app-${local.application_name}"]
-
- google_health_check_ip_ranges = [
- "130.211.0.0/22",
- "35.191.0.0/16"
- ]
-
- environment_variables = concat([
- {
- name = "GOOGLE_CLOUD_PROJECT_ID"
- value = var.project_id
- }
- ], var.application_environment_variables)
-}
-
-# Find latest ubuntu 22.04 image
-data "google_compute_image" "ubuntu" {
- family = "ubuntu-2204-lts"
- project = "ubuntu-os-cloud"
-}
-
-# Deploy app
-resource "google_compute_address" "client_monitor" {
- project = var.project_id
-
- region = var.compute_region
- name = "firezone-monitor"
- subnetwork = var.compute_subnetwork
-
- address_type = "INTERNAL"
-}
-
-resource "google_compute_instance" "client_monitor" {
- project = var.project_id
-
- name = local.application_name
- description = "This template is used to create ${local.application_name} instances."
-
- zone = var.compute_instance_availability_zone
-
- machine_type = var.compute_instance_type
-
- can_ip_forward = true
-
- tags = local.application_tags
-
- labels = merge({
- ubuntu-vm = data.google_compute_image.ubuntu.name
- version = local.application_version
- }, local.application_labels)
-
- boot_disk {
- auto_delete = true
-
- initialize_params {
- image = data.google_compute_image.ubuntu.self_link
-
- labels = {
- managed_by = "terraform"
- }
- }
- }
-
- network_interface {
- subnetwork = var.compute_subnetwork
- stack_type = "IPV4_ONLY"
- network_ip = google_compute_address.client_monitor.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 = templatefile("${path.module}/templates/cloud-init.yaml", {
- client_container_image = "${var.container_registry}/${var.image_repo}/${var.image}:${var.image_tag}"
- firezone_token = var.firezone_token
- firezone_api_url = var.firezone_api_url
- firezone_client_id = var.firezone_client_id
- firezone_client_log_level = var.firezone_client_log_level
- })
-
- google-logging-enabled = "true"
- google-logging-use-fluentbit = "true"
-
- # Report health-related metrics to Cloud Monitoring
- google-monitoring-enabled = "true"
- }
-
- depends_on = [
- google_project_service.compute,
- google_project_service.pubsub,
- google_project_service.bigquery,
- google_project_service.container,
- google_project_service.stackdriver,
- google_project_service.logging,
- google_project_service.monitoring,
- google_project_service.cloudprofiler,
- google_project_service.cloudtrace,
- google_project_service.servicenetworking,
- google_project_iam_member.logs,
- google_project_iam_member.errors,
- google_project_iam_member.metrics,
- google_project_iam_member.service_management,
- google_project_iam_member.cloudtrace,
- ]
-
- allow_stopping_for_update = true
-}
-
-## Open metrics port for the health checks
-resource "google_compute_firewall" "http-health-checks" {
- project = var.project_id
-
- name = "${local.application_name}-healthcheck"
- network = var.compute_network
-
- source_ranges = local.google_health_check_ip_ranges
- target_tags = ["app-${local.application_name}"]
-
- allow {
- protocol = var.health_check.protocol
- ports = [var.health_check.port]
- }
-}
diff --git a/terraform/modules/google-cloud/apps/client-monitor/outputs.tf b/terraform/modules/google-cloud/apps/client-monitor/outputs.tf
deleted file mode 100644
index 70ecfe3f6..000000000
--- a/terraform/modules/google-cloud/apps/client-monitor/outputs.tf
+++ /dev/null
@@ -1,15 +0,0 @@
-output "service_account" {
- value = google_service_account.application
-}
-
-output "target_tags" {
- value = local.application_tags
-}
-
-output "instance" {
- value = google_compute_instance.client_monitor
-}
-
-output "internal_ip" {
- value = google_compute_address.client_monitor.address
-}
diff --git a/terraform/modules/google-cloud/apps/client-monitor/services.tf b/terraform/modules/google-cloud/apps/client-monitor/services.tf
deleted file mode 100644
index f8ac4715e..000000000
--- a/terraform/modules/google-cloud/apps/client-monitor/services.tf
+++ /dev/null
@@ -1,83 +0,0 @@
-resource "google_project_service" "compute" {
- project = var.project_id
- service = "compute.googleapis.com"
-
- disable_on_destroy = false
-}
-
-resource "google_project_service" "pubsub" {
- project = var.project_id
- service = "pubsub.googleapis.com"
-
- disable_on_destroy = false
-}
-
-resource "google_project_service" "bigquery" {
- project = var.project_id
- service = "bigquery.googleapis.com"
-
- disable_on_destroy = false
-}
-
-resource "google_project_service" "container" {
- project = var.project_id
- service = "container.googleapis.com"
-
- depends_on = [
- google_project_service.compute,
- google_project_service.pubsub,
- google_project_service.bigquery,
- ]
-
- disable_on_destroy = false
-}
-
-resource "google_project_service" "stackdriver" {
- project = var.project_id
- service = "stackdriver.googleapis.com"
-
- disable_on_destroy = false
-}
-
-resource "google_project_service" "logging" {
- project = var.project_id
- service = "logging.googleapis.com"
-
- disable_on_destroy = false
-
- depends_on = [google_project_service.stackdriver]
-}
-
-resource "google_project_service" "monitoring" {
- project = var.project_id
- service = "monitoring.googleapis.com"
-
- disable_on_destroy = false
-
- depends_on = [google_project_service.stackdriver]
-}
-
-resource "google_project_service" "cloudprofiler" {
- project = var.project_id
- service = "cloudprofiler.googleapis.com"
-
- disable_on_destroy = false
-
- depends_on = [google_project_service.stackdriver]
-}
-
-resource "google_project_service" "cloudtrace" {
- project = var.project_id
- service = "cloudtrace.googleapis.com"
-
- disable_on_destroy = false
-
- depends_on = [google_project_service.stackdriver]
-}
-
-resource "google_project_service" "servicenetworking" {
- project = var.project_id
- service = "servicenetworking.googleapis.com"
-
- disable_on_destroy = false
-}
diff --git a/terraform/modules/google-cloud/apps/client-monitor/templates/cloud-init.yaml b/terraform/modules/google-cloud/apps/client-monitor/templates/cloud-init.yaml
deleted file mode 100644
index 1bb567163..000000000
--- a/terraform/modules/google-cloud/apps/client-monitor/templates/cloud-init.yaml
+++ /dev/null
@@ -1,240 +0,0 @@
-#cloud-config
-
-write_files:
- - path: /etc/dev.firezone.client/token
- content: ${firezone_token}
- permissions: "0600"
- owner: root
-
- - path: /etc/systemd/system/firezone.service
- owner: root
- content: |
- [Unit]
- Description=Firezone Client
-
- [Service]
- AmbientCapabilities=CAP_NET_ADMIN
- CapabilityBoundingSet=CAP_CHOWN CAP_NET_ADMIN
- DeviceAllow=/dev/net/tun
- LockPersonality=true
- MemoryDenyWriteExecute=true
- NoNewPrivileges=true
- PrivateMounts=true
- PrivateTmp=true
- PrivateUsers=false
- ProcSubset=pid
- ProtectClock=true
- ProtectControlGroups=true
- ProtectHome=true
- ProtectHostname=true
- ProtectKernelLogs=true
- ProtectKernelModules=true
- ProtectKernelTunables=true
- ProtectProc=invisible
- ProtectSystem=full
- RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK AF_UNIX
- RestrictNamespaces=true
- RestrictRealtime=true
- RestrictSUIDSGID=true
- StateDirectory=dev.firezone.client
- SystemCallArchitectures=native
- SystemCallFilter=@aio @basic-io @file-system @io-event @ipc @network-io @signal @system-service
- UMask=077
-
- Environment="FIREZONE_API_URL=${firezone_api_url}"
- Environment="FIREZONE_ID=${firezone_client_id}"
- Environment="RUST_LOG=${firezone_client_log_level}"
- Environment="LOG_DIR=/var/log/firezone"
-
- ExecStart=/usr/local/bin/firezone-headless-client standalone
- Type=notify
- User=root
-
- [Install]
- WantedBy=default.target
-
- - path: /etc/google-cloud-ops-agent/config.yaml
- permissions: "0644"
- owner: root
- content: |
- logging:
- receivers:
- firezone_monitor:
- type: files
- include_paths:
- - /var/log/firezone_monitor/*.log
- service:
- pipelines:
- firezone_monitor_pipeline:
- receivers: [firezone_monitor]
-
- - path: /etc/cron.d/firezone_monitor
- owner: root
- content: |
- * * * * * root /usr/local/bin/firezone-monitor/tunnel.sh 2>&1 >> /var/log/firezone_monitor/tunnel.log
- */2 * * * * root /usr/local/bin/firezone-monitor/ping.sh 10.0.32.100 2>&1 >> /var/log/firezone_monitor/ping_internal.log
- */2 * * * * root /usr/local/bin/firezone-monitor/ping.sh 8.8.4.4 2>&1 >> /var/log/firezone_monitor/ping_google_dns_ipv4.log
- */2 * * * * root /usr/local/bin/firezone-monitor/ping6.sh 2001:4860:4860::8844 2>&1 >> /var/log/firezone_monitor/ping_google_dns_ipv6.log
- */10 * * * * root /usr/local/bin/firezone-monitor/iperf.sh 10.0.32.101 2>&1 >> /var/log/firezone_monitor/iperf.log
-
- - path: /usr/local/bin/firezone-monitor/common.sh
- permissions: "0555"
- owner: root
- content: |
- #!/bin/bash
-
- log() {
- local timestamp=$(date "+%Y/%m/%d-%H:%M:%S")
- echo "$timestamp >> $1"
- }
-
- check_tunnel() {
- log "Checking tunnel state"
-
- if $(ip address show tun-firezone > /dev/null 2>&1) ; then
- log "Firezone Tunnel is running"
- else
- log "Firezone Monitor Test ERROR: Firezone tunnel is not running"
- exit 1
- fi
- }
-
- - path: /usr/local/bin/firezone-monitor/tunnel.sh
- permissions: "0555"
- owner: root
- content: |
- #!/bin/bash
-
- set -euo pipefail
-
- source $(dirname "$0")/common.sh
-
- TEST_NAME="tunnel"
-
- main() {
- log "Start Firezone Monitor Test: $TEST_NAME"
- check_tunnel
- }
-
- finish() {
- log "Finish Firezone Monitor Test: $TEST_NAME"
- }
-
- trap finish EXIT
- main
-
- - path: /usr/local/bin/firezone-monitor/ping.sh
- permissions: "0555"
- owner: root
- content: |
- #!/bin/bash
-
- set -euo pipefail
-
- source $(dirname "$0")/common.sh
-
- TEST_NAME="ping"
- PING_HOST=$1
-
- run_test() {
- log "Test output:"
- ping -4 -c 10 -W 5 -I "tun-firezone" "$PING_HOST"
- }
-
- main() {
- log "Start Firezone Monitor Test: $TEST_NAME"
- check_tunnel
- run_test
- }
-
- finish() {
- log "Finish Firezone Monitor Test: $TEST_NAME"
- }
-
- trap finish EXIT
- main
-
- - path: /usr/local/bin/firezone-monitor/ping6.sh
- permissions: "0555"
- owner: root
- content: |
- #!/bin/bash
-
- set -euo pipefail
-
- source $(dirname "$0")/common.sh
-
- TEST_NAME="ping6"
- PING_HOST=$1
-
- run_test() {
- log "Test output:"
- ping -6 -c 10 -W 5 -I "tun-firezone" "$PING_HOST"
- }
-
- main() {
- log "Start Firezone Monitor Test: $TEST_NAME"
- check_tunnel
- run_test
- }
-
- finish() {
- log "Finish Firezone Monitor Test: $TEST_NAME"
- }
-
- trap finish EXIT
- main
-
- - path: /usr/local/bin/firezone-monitor/iperf.sh
- permissions: "0555"
- owner: root
- content: |
- #!/bin/bash
-
- set -euo pipefail
-
- source $(dirname "$0")/common.sh
-
- TEST_NAME="iperf"
- IPERF_HOST=$1
- TIMEOUT=5000
-
- run_test() {
- log "Test output:"
- iperf3 -c $IPERF_HOST -R --connect-timeout $TIMEOUT
- }
-
- main() {
- log "Start Firezone Monitor Test: $TEST_NAME"
- check_tunnel
- run_test
- }
-
- finish() {
- log "Finish Firezone Monitor Test: $TEST_NAME"
- }
-
- trap finish EXIT
- main
-
-runcmd:
- - sudo mkdir -m 0755 -p /var/log/firezone_monitor
- - sudo apt update -y
- - sudo apt install -y apt-transport-https ca-certificates curl software-properties-common iperf3
- - sudo install -m 0755 -d /etc/apt/keyrings
- - "sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc"
- - sudo chmod a+r /etc/apt/keyrings/docker.asc
- - 'echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null'
- - sudo apt-get update
- - sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
- - sudo systemctl enable --now docker.service
- - sudo systemctl enable --now containerd.service
- - 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'
- - sudo docker pull ${client_container_image}
- - sudo docker create --name fz-client ${client_container_image}
- - "sudo docker cp fz-client:/bin/firezone-headless-client /usr/local/bin/firezone-headless-client"
- - sudo docker rm -v fz-client
- - sudo systemctl enable --now firezone.service
diff --git a/terraform/modules/google-cloud/apps/client-monitor/variables.tf b/terraform/modules/google-cloud/apps/client-monitor/variables.tf
deleted file mode 100644
index d6e3c7314..000000000
--- a/terraform/modules/google-cloud/apps/client-monitor/variables.tf
+++ /dev/null
@@ -1,164 +0,0 @@
-variable "project_id" {
- type = string
- description = "ID of a Google Cloud Project"
-}
-
-################################################################################
-## Compute
-################################################################################
-
-variable "compute_network" {
- type = string
-}
-
-variable "compute_subnetwork" {
- type = string
-}
-
-variable "compute_region" {
- type = string
-}
-
-variable "compute_instance_availability_zone" {
- type = string
- description = "List of zones in the region defined in `compute_region` where replicas should be deployed."
-}
-
-variable "compute_instance_type" {
- type = string
-}
-
-################################################################################
-## Container Registry
-################################################################################
-
-variable "container_registry" {
- type = string
- nullable = false
- description = "Container registry URL to pull the image from."
-}
-
-###############################################################################
-# Container Image
-###############################################################################
-
-variable "image_repo" {
- type = string
- nullable = false
-
- description = "Repo of a container image used to deploy the application."
-}
-
-variable "image" {
- type = string
- nullable = false
-
- description = "Container image used to deploy the application."
-}
-
-variable "image_tag" {
- type = string
- nullable = false
-
- description = "Container image used to deploy the application."
-}
-
-################################################################################
-## Application
-################################################################################
-
-variable "application_name" {
- type = string
- nullable = true
- default = null
-
- description = "Name of the application. Defaults to value of `var.image_name` with `_` replaced to `-`."
-}
-
-variable "application_version" {
- type = string
- nullable = true
- default = null
-
- description = "Version of the application. Defaults to value of `var.image_tag`."
-}
-
-variable "application_labels" {
- type = map(string)
- nullable = false
- default = {}
-
- description = "Labels to add to all created by this module resources."
-}
-
-variable "health_check" {
- type = object({
- name = string
- protocol = string
- port = number
-
- initial_delay_sec = number
- check_interval_sec = optional(number)
- timeout_sec = optional(number)
- healthy_threshold = optional(number)
- unhealthy_threshold = optional(number)
-
- http_health_check = optional(object({
- host = optional(string)
- request_path = optional(string)
- port = optional(string)
- response = optional(string)
- }))
- })
-
- nullable = false
-
- 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 the application."
-}
-
-################################################################################
-## Firezone Client
-################################################################################
-
-variable "firezone_api_url" {
- type = string
- nullable = false
- default = "wss://api.firez.one"
-
- description = "URL the firezone client will connect to"
-}
-
-variable "firezone_client_id" {
- type = string
- nullable = false
- default = ""
-
- description = ""
-}
-
-variable "firezone_token" {
- type = string
- default = ""
-
- description = "Firezone token to allow client to connect to portal"
- sensitive = true
-}
-
-variable "firezone_client_log_level" {
- type = string
- default = "debug"
-
- description = "Firezone client Rust log level"
-}
diff --git a/terraform/modules/google-cloud/apps/elixir/dns.tf b/terraform/modules/google-cloud/apps/elixir/dns.tf
deleted file mode 100644
index 1995a2df9..000000000
--- a/terraform/modules/google-cloud/apps/elixir/dns.tf
+++ /dev/null
@@ -1,38 +0,0 @@
-# Create DNS records for the application
-resource "google_dns_record_set" "application-ipv4" {
- count = var.application_dns_tld != null ? 1 : 0
-
- project = var.project_id
-
- name = "${var.application_dns_tld}."
- type = "A"
- ttl = 300
-
- managed_zone = var.dns_managed_zone_name
-
- rrdatas = google_compute_global_address.ipv4[*].address
-
- depends_on = [
- google_project_service.compute,
- google_project_service.servicenetworking,
- ]
-}
-
-resource "google_dns_record_set" "application-ipv6" {
- count = var.application_dns_tld != null ? 1 : 0
-
- project = var.project_id
-
- name = "${var.application_dns_tld}."
- type = "AAAA"
- ttl = 300
-
- managed_zone = var.dns_managed_zone_name
-
- rrdatas = google_compute_global_address.ipv6[*].address
-
- depends_on = [
- google_project_service.compute,
- google_project_service.servicenetworking,
- ]
-}
diff --git a/terraform/modules/google-cloud/apps/elixir/iam.tf b/terraform/modules/google-cloud/apps/elixir/iam.tf
deleted file mode 100644
index 8832fc38c..000000000
--- a/terraform/modules/google-cloud/apps/elixir/iam.tf
+++ /dev/null
@@ -1,62 +0,0 @@
-# Create IAM role for the application instances
-resource "google_service_account" "application" {
- project = var.project_id
-
- account_id = "app-${local.application_name}"
- display_name = "${local.application_name} app"
- description = "Service account for ${local.application_name} application instances."
-}
-
-## Allow application service account to pull images from the container registry
-resource "google_project_iam_member" "artifacts" {
- project = var.project_id
-
- role = "roles/artifactregistry.reader"
-
- member = "serviceAccount:${google_service_account.application.email}"
-}
-
-## Allow fluentbit 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/elixir/main.tf b/terraform/modules/google-cloud/apps/elixir/main.tf
deleted file mode 100644
index c451f3bb6..000000000
--- a/terraform/modules/google-cloud/apps/elixir/main.tf
+++ /dev/null
@@ -1,375 +0,0 @@
-locals {
- application_name = var.application_name != null ? var.application_name : var.image
- application_version = var.application_version != null ? var.application_version : var.image_tag
-
- application_labels = merge({
- managed_by = "terraform"
-
- # Note: this labels are used to fetch a release name for Erlang Cluster
- application = local.application_name
- }, var.application_labels)
-
- application_environment_variables = concat([
- {
- name = "RELEASE_HOST_DISCOVERY_METHOD"
- value = "gce_metadata"
- },
- {
- name = "PHOENIX_EXTERNAL_TRUSTED_PROXIES"
- value = jsonencode(concat(
- [
- "35.191.0.0/16",
- "130.211.0.0/22"
- ],
- google_compute_global_address.ipv4[*].address,
- google_compute_global_address.ipv6[*].address
- ))
- },
- {
- name = "LOG_LEVEL"
- value = var.observability_log_level
- },
- {
- name = "OTLP_ENDPOINT",
- value = "http://localhost:4318"
- },
- {
- name = "OTEL_RESOURCE_ATTRIBUTES"
- value = "application.name=${local.application_name}"
- },
- {
- name = "TELEMETRY_METRICS_REPORTER"
- value = "Elixir.Domain.Telemetry.Reporter.GoogleCloudMetrics"
- },
- {
- name = "TELEMETRY_METRICS_REPORTER_OPTS"
- value = jsonencode({
- project_id = var.project_id
- })
- },
- {
- name = "GOOGLE_CLOUD_PROJECT",
- value = var.project_id
- },
- {
- name = "PLATFORM_ADAPTER"
- value = "Elixir.Domain.GoogleCloudPlatform"
- },
- {
- name = "PLATFORM_ADAPTER_CONFIG"
- value = jsonencode({
- project_id = var.project_id
- service_account_email = google_service_account.application.email
- })
- }
- ], var.application_environment_variables)
-
- application_ports_by_name = { for port in var.application_ports : port.name => port }
-}
-
-# Fetch most recent COS image
-data "google_compute_image" "coreos" {
- family = "cos-117-lts"
- project = "cos-cloud"
-}
-
-# Reserve instances for the application
-# If you don't reserve them deployment takes much longer and there is no guarantee that instances will be created at all,
-# Google Cloud Platform does not guarantee that instances will be available when you need them.
-resource "google_compute_reservation" "reservation" {
- # for_each = toset(var.compute_instance_availability_zones)
-
- project = var.project_id
-
- # name = "${local.application_name}-${each.key}-${var.compute_instance_type}"
- name = "${local.application_name}-${element(var.compute_instance_availability_zones, length(var.compute_instance_availability_zones) - 1)}-${var.compute_instance_type}"
- # zone = each.key
- zone = element(var.compute_instance_availability_zones, length(var.compute_instance_availability_zones) - 1)
-
- specific_reservation_required = true
-
- specific_reservation {
- count = var.scaling_horizontal_replicas * 2
-
- instance_properties {
- machine_type = var.compute_instance_type
- }
- }
-}
-
-# Deploy app
-resource "google_compute_instance_template" "application" {
- project = var.project_id
-
- name_prefix = "${local.application_name}-"
-
- description = "This template is used to create ${local.application_name} instances."
-
- machine_type = var.compute_instance_type
- region = var.compute_instance_region
-
- can_ip_forward = false
-
- tags = ["app-${local.application_name}"]
-
- labels = merge({
- container-vm = data.google_compute_image.coreos.name
-
- # This variable can be used by Erlang Cluster not to join nodes of older versions
- version = local.application_version
- }, local.application_labels)
-
-
- scheduling {
- automatic_restart = true
- on_host_maintenance = "MIGRATE"
- provisioning_model = "STANDARD"
- }
-
- reservation_affinity {
- type = "SPECIFIC_RESERVATION"
-
- specific_reservation {
- key = "compute.googleapis.com/reservation-name"
- # *Regional* instance group can consume only one reservation, which is zonal by default,
- # so we are always locked to one zone per region until Google Cloud Platform will fix that.
- # values = [for r in google_compute_reservation.reservation : r.name]
- values = [google_compute_reservation.reservation.name]
- }
- }
-
- disk {
- source_image = data.google_compute_image.coreos.self_link
- auto_delete = true
- boot = true
- disk_type = var.compute_boot_disk_type
- }
-
- network_interface {
- subnetwork = var.vpc_subnetwork
- nic_type = "GVNIC"
- queue_count = var.queue_count
- stack_type = "IPV4_IPV6"
-
- ipv6_access_config {
- network_tier = "PREMIUM"
- }
- }
-
- service_account {
- email = google_service_account.application.email
-
- scopes = concat([
- # 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",
- # Required to discover the other instances in the Erlang Cluster
- "https://www.googleapis.com/auth/compute.readonly"
- ], var.application_token_scopes)
- }
-
- shielded_instance_config {
- enable_integrity_monitoring = true
- enable_secure_boot = false
- enable_vtpm = true
- }
-
- metadata = {
- gce-container-declaration = yamlencode({
- spec = {
- containers = [{
- name = local.application_name != null ? local.application_name : var.image
- image = "${var.container_registry}/${var.image_repo}/${var.image}:${var.image_tag}"
- env = local.application_environment_variables
- }]
-
- volumes = []
-
- restartPolicy = "Always"
- }
- })
-
- user-data = templatefile("${path.module}/templates/cloud-init.yaml", {
- swap_size_gb = var.compute_swap_size_gb,
- otel_config_content = indent(6, var.otel_config)
- })
-
- google-logging-enabled = "true"
- google-logging-use-fluentbit = "true"
-
- # Report health-related metrics to Cloud Monitoring
- google-monitoring-enabled = "true"
- }
-
- depends_on = [
- google_project_service.compute,
- google_project_service.pubsub,
- google_project_service.bigquery,
- google_project_service.container,
- google_project_service.stackdriver,
- google_project_service.logging,
- google_project_service.monitoring,
- google_project_service.cloudprofiler,
- google_project_service.cloudtrace,
- google_project_service.servicenetworking,
- google_project_iam_member.artifacts,
- google_project_iam_member.logs,
- google_project_iam_member.errors,
- google_project_iam_member.metrics,
- google_project_iam_member.service_management,
- google_project_iam_member.cloudtrace,
- google_compute_reservation.reservation,
- ]
-
- lifecycle {
- create_before_destroy = true
- }
-}
-
-# Create health checks for the application ports
-resource "google_compute_health_check" "port" {
- for_each = { for port in var.application_ports : port.name => port if try(port.health_check, null) != null }
-
- project = var.project_id
-
- name = "${local.application_name}-${each.key}"
-
- check_interval_sec = each.value.health_check.check_interval_sec != null ? each.value.health_check.check_interval_sec : 5
- timeout_sec = each.value.health_check.timeout_sec != null ? each.value.health_check.timeout_sec : 5
- healthy_threshold = each.value.health_check.healthy_threshold != null ? each.value.health_check.healthy_threshold : 2
- unhealthy_threshold = each.value.health_check.unhealthy_threshold != null ? each.value.health_check.unhealthy_threshold : 2
-
- log_config {
- enable = false
- }
-
- dynamic "tcp_health_check" {
- for_each = try(each.value.health_check.tcp_health_check, null)[*]
-
- content {
- port = each.value.port
-
- response = lookup(tcp_health_check.value, "response", null)
- }
- }
-
- dynamic "http_health_check" {
- for_each = try(each.value.health_check.http_health_check, null)[*]
-
- content {
- port = each.value.port
-
- host = lookup(http_health_check.value, "host", null)
- request_path = lookup(http_health_check.value, "request_path", null)
- response = lookup(http_health_check.value, "response", null)
- }
- }
-
- dynamic "https_health_check" {
- for_each = try(each.value.health_check.https_health_check, null)[*]
-
- content {
- port = each.value.port
-
- host = lookup(https_health_check.value, "host", null)
- request_path = lookup(https_health_check.value, "request_path", null)
- response = lookup(http_health_check.value, "response", null)
- }
- }
-
- depends_on = [
- google_project_service.compute,
- google_project_service.servicenetworking,
- ]
-}
-
-# Use template to deploy zonal instance group
-resource "google_compute_region_instance_group_manager" "application" {
- project = var.project_id
-
- name = "${local.application_name}-group"
-
- base_instance_name = local.application_name
- region = var.compute_instance_region
- distribution_policy_zones = var.compute_instance_availability_zones
-
- target_size = var.scaling_horizontal_replicas
-
- wait_for_instances = true
- wait_for_instances_status = "STABLE"
-
- version {
- name = local.application_version
- instance_template = google_compute_instance_template.application.self_link
- }
-
- dynamic "named_port" {
- for_each = var.application_ports
-
- content {
- name = named_port.value.name
- port = named_port.value.port
- }
- }
-
- dynamic "auto_healing_policies" {
- for_each = try([google_compute_health_check.port["http"].self_link], [])
-
- content {
- initial_delay_sec = local.application_ports_by_name["http"].health_check.initial_delay_sec
-
- health_check = auto_healing_policies.value
- }
- }
-
- update_policy {
- type = "PROACTIVE"
- minimal_action = "REPLACE"
-
- # The number of instances that can be unavailable (from the target size) during the update. We set
- # this to 0 because we want all new instances to come online before we start taking down the old ones.
- max_unavailable_fixed = 0
-
- # The number of additional instances that can be created during the update. Since we are reserving 2 * the
- # number of instances in the group, we set this to the target number of instances.
- max_surge_fixed = var.scaling_horizontal_replicas
- }
-
- timeouts {
- create = "30m"
- update = "30m"
- delete = "20m"
- }
-
- depends_on = [
- google_compute_instance_template.application
- ]
-}
-
-# Auto-scale instances with high CPU and Memory usage
-resource "google_compute_region_autoscaler" "application" {
- count = var.scaling_max_horizontal_replicas != null ? 1 : 0
-
- project = var.project_id
-
- name = "${local.application_name}-autoscaler"
-
- region = var.compute_instance_region
- target = google_compute_region_instance_group_manager.application.id
-
- autoscaling_policy {
- max_replicas = var.scaling_max_horizontal_replicas
- min_replicas = var.scaling_horizontal_replicas
-
- # wait 3 minutes before trying to measure the CPU utilization for new instances
- cooldown_period = 180
-
- cpu_utilization {
- target = 0.8
- }
- }
-}
diff --git a/terraform/modules/google-cloud/apps/elixir/network.tf b/terraform/modules/google-cloud/apps/elixir/network.tf
deleted file mode 100644
index da9e68509..000000000
--- a/terraform/modules/google-cloud/apps/elixir/network.tf
+++ /dev/null
@@ -1,582 +0,0 @@
-locals {
- google_load_balancer_ip_ranges = [
- "130.211.0.0/22",
- "35.191.0.0/16",
- ]
-
- google_health_check_ip_ranges = [
- "130.211.0.0/22",
- "35.191.0.0/16"
- ]
-
- public_application = var.application_dns_tld != null
-}
-
-# Define a security policy which allows to filter traffic by IP address,
-# an edge security policy can also detect and block common types of web attacks
-resource "google_compute_security_policy" "default" {
- count = local.public_application ? 1 : 0
-
- project = var.project_id
-
- name = local.application_name
-
- type = "CLOUD_ARMOR"
-
- advanced_options_config {
- json_parsing = "STANDARD"
- log_level = "NORMAL"
- }
-
- adaptive_protection_config {
- layer_7_ddos_defense_config {
- enable = local.public_application
- rule_visibility = "STANDARD"
- }
- }
-
- rule {
- description = "rate limit all requests that match the default rule"
-
- # TODO: disable preview when we make sure that rate limited logs look good for some time
- preview = true
-
- action = "throttle"
- priority = "1"
-
- match {
- versioned_expr = "SRC_IPS_V1"
-
- config {
- src_ip_ranges = ["*"]
- }
- }
-
- rate_limit_options {
- conform_action = "allow"
- exceed_action = "deny(429)"
-
- enforce_on_key = "IP"
-
- rate_limit_threshold {
- count = 240
- interval_sec = 60
- }
- }
- }
-
- rule {
- description = "block sanctioned countries"
-
- action = "deny(403)"
- priority = "101"
-
- match {
- expr {
- # Required by US law due to sanctions.
- expression = "origin.region_code.matches('^RU|BY|KP|IR|SY|CU|VE|XC|XD|SD|MM$')"
- }
- }
- }
-
- rule {
- description = "log all requests that match preconfigured sqli-v33-stable OWASP rule"
- preview = true
-
- action = "deny(403)"
- priority = "1001"
-
- match {
- expr {
- expression = "evaluatePreconfiguredWaf('sqli-v33-stable', {'sensitivity': 1})"
- }
- }
- }
-
- rule {
- description = "log all requests that match preconfigured xss-v33-stable OWASP rule"
- preview = true
-
- action = "deny(403)"
- priority = "1002"
-
- match {
- expr {
- expression = "evaluatePreconfiguredWaf('xss-v33-stable', {'sensitivity': 1})"
- }
- }
- }
-
- rule {
- description = "log all requests that match preconfigured methodenforcement-v33-stable OWASP rule"
- preview = true
-
- action = "deny(403)"
- priority = "1003"
-
- match {
- expr {
- expression = "evaluatePreconfiguredWaf('methodenforcement-v33-stable', {'sensitivity': 1})"
- }
- }
- }
-
- rule {
- description = "log all requests that match preconfigured scannerdetection-v33-stable OWASP rule"
- preview = true
-
- action = "deny(403)"
- priority = "1004"
-
- match {
- expr {
- expression = "evaluatePreconfiguredWaf('scannerdetection-v33-stable', {'sensitivity': 1})"
- }
- }
- }
-
- rule {
- description = "log all requests that match preconfigured protocolattack-v33-stable OWASP rule"
- preview = true
-
- action = "deny(403)"
- priority = "1005"
-
- match {
- expr {
- expression = "evaluatePreconfiguredWaf('protocolattack-v33-stable', {'sensitivity': 1})"
- }
- }
- }
-
- rule {
- description = "log all requests that match preconfigured sessionfixation-v33-stable OWASP rule"
- preview = true
-
- action = "deny(403)"
- priority = "1006"
-
- match {
- expr {
- expression = "evaluatePreconfiguredWaf('sessionfixation-v33-stable', {'sensitivity': 1})"
- }
- }
- }
-
- rule {
- description = "log all requests that match preconfigured cve-canary GCP rule"
- preview = true
-
- action = "deny(403)"
- priority = "1007"
-
- match {
- expr {
- expression = "evaluatePreconfiguredWaf('cve-canary', {'sensitivity': 2})"
- }
- }
- }
-
- rule {
- description = "default allow rule"
-
- action = "allow"
- priority = "2147483647"
-
- match {
- versioned_expr = "SRC_IPS_V1"
-
- config {
- src_ip_ranges = ["*"]
- }
- }
- }
-
- depends_on = [
- google_project_service.compute,
- google_project_service.pubsub,
- google_project_service.bigquery,
- google_project_service.container,
- google_project_service.stackdriver,
- google_project_service.logging,
- google_project_service.monitoring,
- google_project_service.cloudprofiler,
- google_project_service.cloudtrace,
- google_project_service.servicenetworking,
- ]
-}
-
-# Expose the application ports via HTTP(S) load balancer with a managed SSL certificate and a static IP address
-resource "google_compute_backend_service" "default" {
- for_each = local.public_application ? local.application_ports_by_name : {}
-
- project = var.project_id
-
- name = "${local.application_name}-backend-${each.value.name}"
-
- load_balancing_scheme = "EXTERNAL"
-
- port_name = each.value.name
- protocol = "HTTP"
-
- timeout_sec = 86400
- connection_draining_timeout_sec = 120
-
- enable_cdn = var.application_cdn_enabled
-
- cdn_policy {
- cache_mode = "CACHE_ALL_STATIC"
-
- cache_key_policy {
- include_host = true
- include_protocol = true
- include_query_string = true
- }
-
- default_ttl = 3600
- client_ttl = 3600
- max_ttl = 86400
- }
-
- compression_mode = "DISABLED"
-
- custom_request_headers = [
- "X-Geo-Location-Region:{client_region}",
- "X-Geo-Location-City:{client_city}",
- "X-Geo-Location-Coordinates:{client_city_lat_long}",
- ]
-
- custom_response_headers = [
- "X-Cache-Hit: {cdn_cache_status}"
- ]
-
- session_affinity = "CLIENT_IP"
-
- health_checks = try([google_compute_health_check.port[each.key].self_link], null)
-
- security_policy = google_compute_security_policy.default[0].self_link
-
- backend {
- balancing_mode = "UTILIZATION"
- capacity_scaler = 1
- group = google_compute_region_instance_group_manager.application.instance_group
-
- # Do not send traffic to nodes that have CPU load higher than 80%
- # max_utilization = 0.8
- }
-
- log_config {
- enable = false
- sample_rate = "1.0"
- }
-
- depends_on = [
- google_compute_region_instance_group_manager.application,
- google_compute_health_check.port,
- ]
-}
-
-## Create a SSL policy
-resource "google_compute_ssl_policy" "application" {
- count = local.public_application ? 1 : 0
-
- project = var.project_id
-
- name = local.application_name
-
- min_tls_version = "TLS_1_2"
- profile = "RESTRICTED"
-
- depends_on = [
- google_project_service.compute,
- google_project_service.pubsub,
- google_project_service.bigquery,
- google_project_service.container,
- google_project_service.stackdriver,
- google_project_service.logging,
- google_project_service.monitoring,
- google_project_service.cloudprofiler,
- google_project_service.cloudtrace,
- google_project_service.servicenetworking,
- ]
-}
-
-## Create a managed SSL certificate
-resource "google_compute_managed_ssl_certificate" "default" {
- count = local.public_application ? 1 : 0
-
- project = var.project_id
-
- name = "${local.application_name}-mig-lb-cert"
-
- type = "MANAGED"
-
- managed {
- domains = [
- var.application_dns_tld,
- ]
- }
-
- depends_on = [
- google_project_service.compute,
- google_project_service.servicenetworking,
- ]
-
- lifecycle {
- create_before_destroy = true
- }
-}
-
-## Create URL map for the application
-resource "google_compute_url_map" "default" {
- count = local.public_application && contains(keys(local.application_ports_by_name), "http") ? 1 : 0
-
- project = var.project_id
-
- name = local.application_name
- default_service = google_compute_backend_service.default["http"].self_link
-
- depends_on = [
- google_project_service.compute,
- google_project_service.servicenetworking,
- ]
-}
-
-# Set up HTTP(s) proxies and redirect HTTP to HTTPS
-resource "google_compute_url_map" "https_redirect" {
- count = local.public_application && contains(keys(local.application_ports_by_name), "http") ? 1 : 0
-
- project = var.project_id
-
- name = "${local.application_name}-https-redirect"
-
- default_url_redirect {
- https_redirect = true
- redirect_response_code = "MOVED_PERMANENTLY_DEFAULT"
- strip_query = false
- }
-
- depends_on = [
- google_project_service.compute,
- google_project_service.servicenetworking,
- ]
-}
-
-resource "google_compute_target_http_proxy" "default" {
- count = length(google_compute_url_map.https_redirect) > 0 ? 1 : 0
-
- project = var.project_id
-
- name = "${local.application_name}-http"
-
- url_map = google_compute_url_map.https_redirect[0].self_link
-}
-
-resource "google_compute_target_https_proxy" "default" {
- count = local.public_application ? 1 : 0
-
- project = var.project_id
-
- name = "${local.application_name}-https"
-
- url_map = google_compute_url_map.default[0].self_link
-
- ssl_certificates = google_compute_managed_ssl_certificate.default[*].self_link
- ssl_policy = google_compute_ssl_policy.application[0].self_link
- quic_override = "NONE"
-}
-
-# Allocate global addresses for the load balancer and set up forwarding rules
-## IPv4
-resource "google_compute_global_address" "ipv4" {
- count = local.public_application ? 1 : 0
-
- project = var.project_id
-
- name = "${local.application_name}-ipv4"
-
- ip_version = "IPV4"
-
- depends_on = [
- google_project_service.compute,
- google_project_service.servicenetworking,
- ]
-}
-
-resource "google_compute_global_forwarding_rule" "http" {
- count = local.public_application ? 1 : 0
-
- project = var.project_id
-
- name = local.application_name
- labels = local.application_labels
-
- target = google_compute_target_http_proxy.default[0].self_link
- ip_address = google_compute_global_address.ipv4[0].address
- port_range = "80"
-
- load_balancing_scheme = "EXTERNAL"
-}
-
-resource "google_compute_global_forwarding_rule" "https" {
- count = local.public_application ? 1 : 0
-
- project = var.project_id
-
- name = "${local.application_name}-https"
- labels = local.application_labels
-
- target = google_compute_target_https_proxy.default[0].self_link
- ip_address = google_compute_global_address.ipv4[0].address
- port_range = "443"
-
- load_balancing_scheme = "EXTERNAL"
-}
-
-## IPv6
-resource "google_compute_global_address" "ipv6" {
- count = local.public_application ? 1 : 0
-
- project = var.project_id
-
- name = "${local.application_name}-ipv6"
-
- ip_version = "IPV6"
-
- depends_on = [
- google_project_service.compute,
- google_project_service.servicenetworking,
- ]
-}
-
-resource "google_compute_global_forwarding_rule" "http_ipv6" {
- count = local.public_application ? 1 : 0
-
- project = var.project_id
-
- name = "${local.application_name}-ipv6-http"
- labels = local.application_labels
-
- target = google_compute_target_http_proxy.default[0].self_link
- ip_address = google_compute_global_address.ipv6[0].address
- port_range = "80"
-
- load_balancing_scheme = "EXTERNAL"
-}
-
-resource "google_compute_global_forwarding_rule" "https_ipv6" {
- count = local.public_application ? 1 : 0
-
- project = var.project_id
-
- name = "${local.application_name}-ipv6-https"
- labels = local.application_labels
-
- target = google_compute_target_https_proxy.default[0].self_link
- ip_address = google_compute_global_address.ipv6[0].address
- port_range = "443"
-
- load_balancing_scheme = "EXTERNAL"
-}
-
-## Open HTTP ports for the load balancer
-resource "google_compute_firewall" "http" {
- count = local.public_application ? 1 : 0
-
- project = var.project_id
-
- name = "${local.application_name}-firewall-lb-to-instances-ipv4"
- network = var.vpc_network
-
- source_ranges = local.google_load_balancer_ip_ranges
- target_tags = ["app-${local.application_name}"]
-
- dynamic "allow" {
- for_each = var.application_ports
-
- content {
- protocol = allow.value.protocol
- ports = [allow.value.port]
- }
- }
-
- # We also enable UDP to allow QUIC if it's enabled
- dynamic "allow" {
- for_each = var.application_ports
-
- content {
- protocol = "udp"
- ports = [allow.value.port]
- }
- }
-}
-
-## Open HTTP ports for the health checks
-resource "google_compute_firewall" "http-health-checks" {
- project = var.project_id
-
- name = "${local.application_name}-healthcheck"
- network = var.vpc_network
-
- source_ranges = local.google_health_check_ip_ranges
- target_tags = ["app-${local.application_name}"]
-
- dynamic "allow" {
- for_each = var.application_ports
-
- content {
- protocol = allow.value.protocol
- ports = [allow.value.port]
- }
- }
-
- depends_on = [
- google_project_service.compute,
- google_project_service.servicenetworking,
- ]
-}
-
-# Allow outbound traffic
-resource "google_compute_firewall" "egress-ipv4" {
- count = local.public_application ? 1 : 0
-
- project = var.project_id
-
- name = "${local.application_name}-egress-ipv4"
- network = var.vpc_network
- direction = "EGRESS"
-
- target_tags = ["app-${local.application_name}"]
- destination_ranges = ["0.0.0.0/0"]
-
- allow {
- protocol = "all"
- }
-
- depends_on = [
- google_project_service.compute,
- google_project_service.servicenetworking,
- ]
-}
-
-resource "google_compute_firewall" "egress-ipv6" {
- count = local.public_application ? 1 : 0
-
- project = var.project_id
-
- name = "${local.application_name}-egress-ipv6"
- network = var.vpc_network
- direction = "EGRESS"
-
- target_tags = ["app-${local.application_name}"]
- destination_ranges = ["::/0"]
-
- allow {
- protocol = "all"
- }
-
- depends_on = [
- google_project_service.compute,
- google_project_service.servicenetworking,
- ]
-}
diff --git a/terraform/modules/google-cloud/apps/elixir/outputs.tf b/terraform/modules/google-cloud/apps/elixir/outputs.tf
deleted file mode 100644
index dfe82c90e..000000000
--- a/terraform/modules/google-cloud/apps/elixir/outputs.tf
+++ /dev/null
@@ -1,15 +0,0 @@
-output "service_account" {
- value = google_service_account.application
-}
-
-output "target_tags" {
- value = ["app-${local.application_name}"]
-}
-
-output "instance_group" {
- value = google_compute_region_instance_group_manager.application
-}
-
-output "host" {
- value = var.application_dns_tld
-}
diff --git a/terraform/modules/google-cloud/apps/elixir/services.tf b/terraform/modules/google-cloud/apps/elixir/services.tf
deleted file mode 100644
index 76e80a208..000000000
--- a/terraform/modules/google-cloud/apps/elixir/services.tf
+++ /dev/null
@@ -1,84 +0,0 @@
-
-resource "google_project_service" "compute" {
- project = var.project_id
- service = "compute.googleapis.com"
-
- disable_on_destroy = false
-}
-
-resource "google_project_service" "pubsub" {
- project = var.project_id
- service = "pubsub.googleapis.com"
-
- disable_on_destroy = false
-}
-
-resource "google_project_service" "bigquery" {
- project = var.project_id
- service = "bigquery.googleapis.com"
-
- disable_on_destroy = false
-}
-
-resource "google_project_service" "container" {
- project = var.project_id
- service = "container.googleapis.com"
-
- depends_on = [
- google_project_service.compute,
- google_project_service.pubsub,
- google_project_service.bigquery,
- ]
-
- disable_on_destroy = false
-}
-
-resource "google_project_service" "stackdriver" {
- project = var.project_id
- service = "stackdriver.googleapis.com"
-
- disable_on_destroy = false
-}
-
-resource "google_project_service" "logging" {
- project = var.project_id
- service = "logging.googleapis.com"
-
- disable_on_destroy = false
-
- depends_on = [google_project_service.stackdriver]
-}
-
-resource "google_project_service" "monitoring" {
- project = var.project_id
- service = "monitoring.googleapis.com"
-
- disable_on_destroy = false
-
- depends_on = [google_project_service.stackdriver]
-}
-
-resource "google_project_service" "cloudprofiler" {
- project = var.project_id
- service = "cloudprofiler.googleapis.com"
-
- disable_on_destroy = false
-
- depends_on = [google_project_service.stackdriver]
-}
-
-resource "google_project_service" "cloudtrace" {
- project = var.project_id
- service = "cloudtrace.googleapis.com"
-
- disable_on_destroy = false
-
- depends_on = [google_project_service.stackdriver]
-}
-
-resource "google_project_service" "servicenetworking" {
- project = var.project_id
- service = "servicenetworking.googleapis.com"
-
- disable_on_destroy = false
-}
diff --git a/terraform/modules/google-cloud/apps/elixir/templates/cloud-init.yaml b/terraform/modules/google-cloud/apps/elixir/templates/cloud-init.yaml
deleted file mode 100644
index e70936ad5..000000000
--- a/terraform/modules/google-cloud/apps/elixir/templates/cloud-init.yaml
+++ /dev/null
@@ -1,69 +0,0 @@
-#cloud-config
-
-write_files:
- - path: /etc/otelcol-contrib/config.yaml
- permissions: "0644"
- owner: root
- content: |
- ${otel_config_content}
-
- - 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.127.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.127.0
- ExecStop=/usr/bin/docker stop otel-collector
- ExecStopPost=/usr/bin/docker rm otel-collector
-
- - 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
-
-runcmd:
- - sudo ip6tables-restore < /etc/iptables/rules.v6
- - systemctl daemon-reload
- - systemctl start otel-collector.service
-
-swap:
- filename: /swapfile
- size: ${swap_size_gb}G
- maxsize: ${swap_size_gb}G
diff --git a/terraform/modules/google-cloud/apps/elixir/variables.tf b/terraform/modules/google-cloud/apps/elixir/variables.tf
deleted file mode 100644
index 321058f14..000000000
--- a/terraform/modules/google-cloud/apps/elixir/variables.tf
+++ /dev/null
@@ -1,321 +0,0 @@
-variable "project_id" {
- type = string
- description = "ID of a Google Cloud Project"
-}
-
-################################################################################
-## Compute
-################################################################################
-
-variable "compute_instance_type" {
- type = string
- description = "Type of the instance."
- default = "n1-standard-1"
-}
-
-variable "compute_instance_region" {
- type = string
- description = "Region which would be used to create compute resources."
-}
-
-variable "compute_instance_availability_zones" {
- type = list(string)
- description = "List of availability zone for the VMs. It must be in the same region as `var.compute_instance_region`."
-}
-
-variable "compute_boot_disk_type" {
- type = string
- default = "pd-ssd"
- description = "Type of the boot disk."
-}
-
-variable "compute_swap_size_gb" {
- type = number
- default = 0
- description = "Size of the swap disk in GB."
-}
-
-variable "queue_count" {
- type = number
- default = 2
- description = "Number of max RX / TX queues to assign to the NIC."
-
- validation {
- condition = var.queue_count >= 2
- error_message = "queue_count must be greater than or equal to 2."
- }
-
- validation {
- condition = var.queue_count % 2 == 0
- error_message = "queue_count must be an even number."
- }
-
- validation {
- condition = var.queue_count <= 16
- error_message = "queue_count must be less than or equal to 16."
- }
-}
-
-################################################################################
-## VPC
-################################################################################
-
-variable "vpc_network" {
- description = "ID of a VPC which will be used to deploy the application."
- type = string
-}
-
-variable "vpc_subnetwork" {
- description = "ID of a VPC subnet which will be used to deploy the application."
- type = string
-}
-
-################################################################################
-## Container Registry
-################################################################################
-
-variable "container_registry" {
- type = string
- nullable = false
- description = "Container registry URL to pull the image from."
-}
-
-################################################################################
-## Container Image
-################################################################################
-
-variable "image_repo" {
- type = string
- nullable = false
-
- description = "Repo of a container image used to deploy the application."
-}
-
-variable "image" {
- type = string
- nullable = false
-
- description = "Container image used to deploy the application."
-}
-
-variable "image_tag" {
- type = string
- nullable = false
-
- description = "Container image used to deploy the application."
-}
-
-################################################################################
-## Scaling
-################################################################################
-
-variable "scaling_horizontal_replicas" {
- type = number
- nullable = false
- default = 1
-
- validation {
- condition = var.scaling_horizontal_replicas > 0
- error_message = "Number of replicas should be greater or equal to 0."
- }
-
- description = "Number of replicas in an instance group."
-}
-
-variable "scaling_max_horizontal_replicas" {
- type = number
- nullable = true
- default = null
-
- description = "Maximum number of replacias an instance group can be auto-scaled to. `null` disables auto-scaling."
-}
-
-################################################################################
-## Observability
-################################################################################
-
-variable "observability_log_level" {
- type = string
- nullable = false
- default = "info"
-
- validation {
- condition = (
- contains(
- ["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"],
- var.observability_log_level
- )
- )
- error_message = "Only Elixir Logger log levels are accepted."
- }
-
- description = "Sets LOG_LEVEL environment variable which applications should use to configure Elixir Logger. Default: 'info'."
-}
-
-variable "otel_config" {
- type = string
- description = "otel-collector YAML config content"
-}
-
-################################################################################
-## Erlang
-################################################################################
-
-variable "erlang_release_name" {
- type = string
- nullable = true
- default = null
-
- description = <= 2
- error_message = "queue_count must be greater than or equal to 2."
- }
-
- validation {
- condition = var.queue_count % 2 == 0
- error_message = "queue_count must be an even number."
- }
-
- validation {
- condition = var.queue_count <= 16
- error_message = "queue_count must be less than or equal to 16."
- }
-}
-
-################################################################################
-## Container Registry
-################################################################################
-
-variable "container_registry" {
- type = string
- nullable = false
- description = "Container registry URL to pull the image from."
-}
-
-################################################################################
-## Container Image
-################################################################################
-
-variable "image_repo" {
- type = string
- nullable = false
-
- description = "Repo of a container image used to deploy the application."
-}
-
-variable "image" {
- type = string
- nullable = false
-
- description = "Container image used to deploy the application."
-}
-
-variable "image_tag" {
- type = string
- nullable = false
-
- description = "Container image used to deploy the application."
-}
-
-################################################################################
-## Observability
-################################################################################
-
-variable "observability_log_level" {
- type = string
- nullable = false
- default = "info"
-
- description = "Sets RUST_LOG environment variable which applications should use to configure Rust Logger. Default: 'info'."
-}
-
-################################################################################
-## Application
-################################################################################
-
-variable "application_name" {
- type = string
- nullable = true
- default = null
-
- description = "Name of the application. Defaults to value of `var.image_name` with `_` replaced to `-`."
-}
-
-variable "application_version" {
- type = string
- nullable = true
- default = null
-
- description = "Version of the application. Defaults to value of `var.image_tag`."
-}
-
-variable "application_labels" {
- type = map(string)
- nullable = false
- default = {}
-
- description = "Labels to add to all created by this module resources."
-}
-
-variable "health_check" {
- type = object({
- name = string
- protocol = string
- port = number
-
- initial_delay_sec = number
- check_interval_sec = optional(number)
- timeout_sec = optional(number)
- healthy_threshold = optional(number)
- unhealthy_threshold = optional(number)
-
- http_health_check = optional(object({
- host = optional(string)
- request_path = optional(string)
- port = optional(string)
- response = optional(string)
- }))
- })
-
- nullable = false
-
- 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."
- sensitive = true
-}
-
-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
deleted file mode 100644
index 211484112..000000000
--- a/terraform/modules/google-cloud/apps/vm/iam.tf
+++ /dev/null
@@ -1,54 +0,0 @@
-
-# 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
deleted file mode 100644
index 6a92b5fe5..000000000
--- a/terraform/modules/google-cloud/apps/vm/main.tf
+++ /dev/null
@@ -1,138 +0,0 @@
-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 = < 0 ? true : var.database_backups_enabled
- start_time = "10:00"
-
- # PITR backups must be enabled if read replicas are enabled
- point_in_time_recovery_enabled = length(var.database_read_replica_locations) > 0 ? true : var.database_backups_enabled
-
- backup_retention_settings {
- retained_backups = 30
- }
- }
-
- ip_configuration {
- ipv4_enabled = true
- private_network = var.network
- }
-
- maintenance_window {
- day = 7
- hour = 8
- update_track = "stable"
- }
-
- insights_config {
- query_insights_enabled = true
- record_application_tags = true
- record_client_address = false
-
- query_plans_per_minute = 20
- query_string_length = 4500
- }
-
- password_validation_policy {
- enable_password_policy = true
-
- complexity = "COMPLEXITY_DEFAULT"
-
- min_length = 16
- disallow_username_substring = true
- }
-
- dynamic "database_flags" {
- for_each = var.database_flags
-
- content {
- name = database_flags.key
- value = database_flags.value
- }
- }
-
- database_flags {
- name = "maintenance_work_mem"
- value = floor(var.compute_instance_memory_size * 1024 / 100 * 5)
- }
-
- database_flags {
- name = "cloudsql.iam_authentication"
- value = "on"
- }
-
- database_flags {
- name = "cloudsql.enable_pgaudit"
- value = "on"
- }
-
- database_flags {
- name = "pgaudit.log"
- value = "all"
- }
- }
-
- lifecycle {
- prevent_destroy = true
- ignore_changes = []
- }
-
- depends_on = [
- google_project_service.sqladmin,
- google_project_service.sql-component,
- google_service_networking_connection.connection,
- ]
-}
-
-# Create followers for the main Cloud SQL instance
-resource "google_sql_database_instance" "read-replica" {
- for_each = tomap({
- for location in var.database_read_replica_locations : location.region => location
- })
-
- project = var.project_id
-
- name = "${var.database_name}-read-replica-${each.key}"
- database_version = var.database_version
- region = each.value.region
-
- master_instance_name = var.database_name
-
- replica_configuration {
- connect_retry_interval = "30"
- }
-
- settings {
- # We must use the same tier as the master instance,
- # otherwise it might be lagging behind during the replication and won't be usable
- tier = "db-custom-${var.compute_instance_cpu_count}-${var.compute_instance_memory_size}"
-
- disk_type = "PD_SSD"
- disk_autoresize = true
-
- activation_policy = "ALWAYS"
- availability_type = "ZONAL"
-
- location_preference {
- zone = var.compute_availability_zone
- }
-
- ip_configuration {
- ipv4_enabled = each.value.ipv4_enabled
- private_network = each.value.network
- }
-
- insights_config {
- query_insights_enabled = true
- record_application_tags = true
- record_client_address = false
-
- query_plans_per_minute = 20
- query_string_length = 4500
- }
-
- dynamic "database_flags" {
- for_each = var.database_flags
-
- content {
- name = database_flags.key
- value = database_flags.value
- }
- }
- }
-
- lifecycle {
- prevent_destroy = true
- ignore_changes = []
- }
-
- depends_on = [google_sql_database_instance.master]
-}
diff --git a/terraform/modules/google-cloud/sql/outputs.tf b/terraform/modules/google-cloud/sql/outputs.tf
deleted file mode 100644
index 9fca87529..000000000
--- a/terraform/modules/google-cloud/sql/outputs.tf
+++ /dev/null
@@ -1,19 +0,0 @@
-output "master_instance_ip_address" {
- value = google_sql_database_instance.master.private_ip_address
-}
-
-output "master_instance_name" {
- value = google_sql_database_instance.master.name
-}
-
-output "master_instance_address" {
- value = google_sql_database_instance.master.private_ip_address
-}
-
-output "read-replicas" {
- value = google_sql_database_instance.read-replica
-}
-
-output "bi_instance_ip_address" {
- value = try(google_sql_database_instance.read-replica[var.database_read_replica_locations[0].region].ip_address[0], google_sql_database_instance.master.private_ip_address)
-}
diff --git a/terraform/modules/google-cloud/sql/variables.tf b/terraform/modules/google-cloud/sql/variables.tf
deleted file mode 100644
index 35a6f7ef5..000000000
--- a/terraform/modules/google-cloud/sql/variables.tf
+++ /dev/null
@@ -1,58 +0,0 @@
-variable "project_id" {
- description = "The ID of the project in which the resource belongs."
-}
-
-variable "compute_region" {
- description = "The region the instance will sit in."
-}
-
-variable "compute_availability_zone" {
- description = "The preferred compute engine zone. See https://cloud.google.com/compute/docs/regions-zones?hl=en"
-}
-
-variable "compute_instance_memory_size" {
- description = "Instance memory size. See https://cloud.google.com/compute/docs/instances/creating-instance-with-custom-machine-type#create"
-}
-
-variable "compute_instance_cpu_count" {
- description = "Count of CPUs. See https://cloud.google.com/compute/docs/instances/creating-instance-with-custom-machine-type#create"
-}
-
-variable "network" {
- description = "Full network identifier which is used to create private VPC connection with Cloud SQL instance"
-}
-
-variable "database_name" {
- description = "Name of the Cloud SQL database"
-}
-
-variable "database_version" {
- description = "Version of the Cloud SQL database"
- default = "POSTGRES_17"
-}
-
-variable "database_highly_available" {
- description = "Creates a failover copy for the master intancy and makes it availability regional."
- default = false
-}
-
-variable "database_backups_enabled" {
- description = "Should backups be enabled on this database?"
- default = false
-}
-
-variable "database_read_replica_locations" {
- description = "List of read-only replicas to create."
- type = list(object({
- region = string
- ipv4_enabled = bool
- network = string
- }))
- default = []
-}
-
-variable "database_flags" {
- description = "List of PostgreSQL database flags. Can be used to install Postgres extensions."
- type = map(string)
- default = {}
-}
diff --git a/terraform/modules/google-cloud/storage/main.tf b/terraform/modules/google-cloud/storage/main.tf
deleted file mode 100644
index ae42f56dd..000000000
--- a/terraform/modules/google-cloud/storage/main.tf
+++ /dev/null
@@ -1,15 +0,0 @@
-resource "google_project_service" "storage-api" {
- project = var.project_id
-
- service = "storage-api.googleapis.com"
-
- disable_on_destroy = false
-}
-
-resource "google_project_service" "storage-component" {
- project = var.project_id
-
- service = "storage-component.googleapis.com"
-
- disable_on_destroy = false
-}
diff --git a/terraform/modules/google-cloud/storage/variables.tf b/terraform/modules/google-cloud/storage/variables.tf
deleted file mode 100644
index 758865cb4..000000000
--- a/terraform/modules/google-cloud/storage/variables.tf
+++ /dev/null
@@ -1,3 +0,0 @@
-variable "project_id" {
- description = "The ID of the project in which the resource belongs."
-}
diff --git a/terraform/modules/google-cloud/vpc/main.tf b/terraform/modules/google-cloud/vpc/main.tf
deleted file mode 100644
index 072a8b0e7..000000000
--- a/terraform/modules/google-cloud/vpc/main.tf
+++ /dev/null
@@ -1,49 +0,0 @@
-resource "google_project_service" "compute" {
- project = var.project_id
- service = "compute.googleapis.com"
-
- disable_on_destroy = false
-}
-
-resource "google_compute_network" "vpc_network" {
- project = var.project_id
- name = var.name
-
- routing_mode = "GLOBAL"
-
- auto_create_subnetworks = false
-
- depends_on = [
- google_project_service.compute
- ]
-}
-
-## Router and Cloud NAT are required for instances without external IP address
-resource "google_compute_router" "default" {
- project = var.project_id
-
- name = google_compute_network.vpc_network.name
- network = google_compute_network.vpc_network.self_link
- region = var.nat_region
-}
-
-resource "google_compute_router_nat" "application" {
- project = var.project_id
-
- name = google_compute_network.vpc_network.name
- region = var.nat_region
-
- router = google_compute_router.default.name
-
- nat_ip_allocate_option = "AUTO_ONLY"
- source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
-
- enable_dynamic_port_allocation = false
- min_ports_per_vm = 32
-
- udp_idle_timeout_sec = 30
- icmp_idle_timeout_sec = 30
- tcp_established_idle_timeout_sec = 1200
- tcp_transitory_idle_timeout_sec = 30
- tcp_time_wait_timeout_sec = 120
-}
diff --git a/terraform/modules/google-cloud/vpc/outputs.tf b/terraform/modules/google-cloud/vpc/outputs.tf
deleted file mode 100644
index 89f552a4e..000000000
--- a/terraform/modules/google-cloud/vpc/outputs.tf
+++ /dev/null
@@ -1,11 +0,0 @@
-output "id" {
- value = google_compute_network.vpc_network.id
-}
-
-output "name" {
- value = google_compute_network.vpc_network.name
-}
-
-output "self_link" {
- value = google_compute_network.vpc_network.self_link
-}
diff --git a/terraform/modules/google-cloud/vpc/variables.tf b/terraform/modules/google-cloud/vpc/variables.tf
deleted file mode 100644
index c51cec897..000000000
--- a/terraform/modules/google-cloud/vpc/variables.tf
+++ /dev/null
@@ -1,11 +0,0 @@
-variable "project_id" {
- description = "The ID of the project in which the resource belongs."
-}
-
-variable "name" {
- description = "Name of the resource. Provided by the client when the resource is created."
-}
-
-variable "nat_region" {
- description = "Region where Cloud NAT will be created"
-}
diff --git a/website/src/app/kb/automate/terraform/gcp/readme.mdx b/website/src/app/kb/automate/terraform/gcp/readme.mdx
index be45aa391..194ddbd77 100644
--- a/website/src/app/kb/automate/terraform/gcp/readme.mdx
+++ b/website/src/app/kb/automate/terraform/gcp/readme.mdx
@@ -8,8 +8,8 @@ In this example, we will deploy one or more Firezone Gateways in a single VPC on
Google Cloud Platform (GCP) that are configured to egress traffic through a
single Cloud NAT that is assigned a single static IP address.
-This example is built on top of our example module for deploying a
-[Firezone Gateway in Google Cloud](https://github.com/firezone/terraform-google-gateway/tree/main/examples/nat-gateway).
+This example is built on top of our module for deploying a
+[Firezone Gateway in Google Cloud](/kb/automate/terraform/gcp).
## Common use cases
diff --git a/website/src/app/kb/reference/faq/readme.mdx b/website/src/app/kb/reference/faq/readme.mdx
index c2147c7b7..e13b79868 100644
--- a/website/src/app/kb/reference/faq/readme.mdx
+++ b/website/src/app/kb/reference/faq/readme.mdx
@@ -131,8 +131,8 @@ traffic.
Scaling Firezone to support your rapidly growing organization is as simple as
deploying additional Gateway servers. See our
-[Terraform Gateway deployment examples](https://github.com/firezone/firezone/tree/main/terraform/examples)
-for an idea of how to automate this process.
+[Terraform deployment examples](/kb/automate/terraform) for an idea of how to
+automate this process.
#### What protocol does Firezone use to encrypt traffic?
diff --git a/website/src/app/kb/use-cases/nat-gateway/readme.mdx b/website/src/app/kb/use-cases/nat-gateway/readme.mdx
index fadbce854..7d92aaf43 100644
--- a/website/src/app/kb/use-cases/nat-gateway/readme.mdx
+++ b/website/src/app/kb/use-cases/nat-gateway/readme.mdx
@@ -22,10 +22,8 @@ After completing this guide, your team's traffic will be routed to a Firezone
Gateway and then out to the internet using its public IP address.
- See our our [Terraform
- examples](https://www.github.com/firezone/firezone/tree/main/terraform/examples)
- for a high availability example of this guide using Terraform on Google Cloud
- Platform.
+ See our our [Terraform examples](/kb/automate/terraform) for a high
+ availability example of this guide using Terraform on Google Cloud Platform.
## Prerequisites
diff --git a/website/src/app/kb/use-cases/scale-vpc-access/readme.mdx b/website/src/app/kb/use-cases/scale-vpc-access/readme.mdx
index 4b4ccfd56..366700e43 100644
--- a/website/src/app/kb/use-cases/scale-vpc-access/readme.mdx
+++ b/website/src/app/kb/use-cases/scale-vpc-access/readme.mdx
@@ -31,7 +31,7 @@ balanced across multiple Gateways for high availability.
[Deploy a Gateway](/kb/deploy/gateways) if you haven't done so yet.
- See our [Terraform examples](/kb/automate) to learn how to automate
+ See our [Terraform examples](/kb/automate/terraform) to learn how to automate
deployments to various cloud providers.