chore(infra): Deploy domain app on a separate instance and enable background jobs on it (#4160)

Closes #3801
This commit is contained in:
Andrew Dryga
2024-03-16 08:58:20 -06:00
committed by GitHub
parent 692fc408e8
commit a85b9ab185
28 changed files with 1075 additions and 983 deletions

View File

@@ -82,6 +82,10 @@ jobs:
fail-fast: false
matrix:
include:
- image_name: domain
target: runtime
build-args: |
APPLICATION_NAME=domain
- image_name: api
target: runtime
build-args: |

View File

@@ -48,7 +48,7 @@ jobs:
- name: Pull and push images
run: |
set -xe
IMAGES=(client relay gateway api web)
IMAGES=(domain api web gateway relay client)
MAJOR_VERSION="${VERSION%%.*}"
MAJOR_MINOR_VERSION="${VERSION%.*}"

View File

@@ -3,6 +3,14 @@ run-name: Triggered from ${{ github.event_name }} by ${{ github.actor }}
on:
workflow_call:
inputs:
domain_image:
required: false
type: string
default: 'us-east1-docker.pkg.dev/firezone-staging/firezone/domain'
domain_tag:
required: false
type: string
default: ${{ github.sha }}
api_image:
required: false
type: string
@@ -61,6 +69,8 @@ jobs:
id-token: write
pull-requests: write
env:
DOMAIN_IMAGE: ${{ inputs.domain_image }}
DOMAIN_TAG: ${{ inputs.domain_tag }}
API_IMAGE: ${{ inputs.api_image }}
API_TAG: ${{ inputs.api_tag }}
WEB_IMAGE: ${{ inputs.web_image }}
@@ -99,7 +109,7 @@ jobs:
run: |
# Start one-by-one to avoid variability in service startup order
docker compose up -d dns.httpbin httpbin
docker compose up -d api web --no-build
docker compose up -d api web domain --no-build
docker compose up -d relay --no-build
docker compose up -d gateway --no-build
docker compose up -d client --no-build

View File

@@ -185,7 +185,7 @@ jobs:
# Start services in the same order each time for the tests
docker compose up -d iperf3
docker compose up -d api web --no-build
docker compose up -d api web domain --no-build
docker compose up -d relay --no-build
docker compose up -d gateway --no-build
docker compose up -d client --no-build

View File

@@ -41,7 +41,7 @@ jobs:
- name: Pull and push
run: |
set -xe
IMAGES=(relay api gateway web client)
IMAGES=(domain api web gateway relay client)
MAJOR_VERSION="${VERSION%%.*}"
MAJOR_MINOR_VERSION="${VERSION%.*}"

View File

@@ -68,7 +68,7 @@ services:
# Erlang
ERLANG_DISTRIBUTION_PORT: 9000
ERLANG_CLUSTER_ADAPTER: "Elixir.Cluster.Strategy.Epmd"
ERLANG_CLUSTER_ADAPTER_CONFIG: '{"hosts":["api@api.cluster.local","web@web.cluster.local"]}'
ERLANG_CLUSTER_ADAPTER_CONFIG: '{"hosts":["api@api.cluster.local","web@web.cluster.local","domain@web.cluster.local"]}'
RELEASE_COOKIE: "NksuBhJFBhjHD1uUa9mDOHV"
RELEASE_HOSTNAME: "web.cluster.local"
RELEASE_NAME: "web"
@@ -87,8 +87,68 @@ services:
LIVE_VIEW_SIGNING_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
COOKIE_SIGNING_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
COOKIE_ENCRYPTION_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
# Telemetry
TELEMETRY_ENABLED: "false"
# Debugging
LOG_LEVEL: "debug"
# Emails
OUTBOUND_EMAIL_FROM: "public-noreply@firez.one"
OUTBOUND_EMAIL_ADAPTER: "Elixir.Swoosh.Adapters.Postmark"
## Warning: The token is for the blackhole Postmark server created in a separate isolated account,
## that WILL NOT send any actual emails, but you can see and debug them in the Postmark dashboard.
OUTBOUND_EMAIL_ADAPTER_OPTS: '{"api_key":"7da7d1cd-111c-44a7-b5ac-4027b9d230e5"}'
# Seeds
STATIC_SEEDS: "true"
healthcheck:
test: ["CMD-SHELL", "curl -f localhost:8080/healthz"]
start_period: 10s
interval: 30s
retries: 5
timeout: 5s
depends_on:
vault:
condition: "service_healthy"
postgres:
condition: "service_healthy"
networks:
- app
api:
build:
context: elixir
cache_from:
- type=registry,ref=us-east1-docker.pkg.dev/firezone-staging/cache/api:main
args:
APPLICATION_NAME: api
image: ${API_IMAGE:-us-east1-docker.pkg.dev/firezone-staging/firezone/api}:${API_TAG:-main}
hostname: api.cluster.local
ports:
- 8081:8081/tcp
environment:
# Web Server
EXTERNAL_URL: http://localhost:8081/
PHOENIX_HTTP_API_PORT: "8081"
PHOENIX_SECURE_COOKIES: "false"
# Erlang
ERLANG_DISTRIBUTION_PORT: 9000
ERLANG_CLUSTER_ADAPTER: "Elixir.Cluster.Strategy.Epmd"
ERLANG_CLUSTER_ADAPTER_CONFIG: '{"hosts":["api@api.cluster.local","web@web.cluster.local","domain@web.cluster.local"]}'
RELEASE_COOKIE: "NksuBhJFBhjHD1uUa9mDOHV"
RELEASE_HOSTNAME: "api.cluster.local"
RELEASE_NAME: "api"
# Database
DATABASE_HOST: postgres
DATABASE_PORT: 5432
DATABASE_NAME: firezone_dev
DATABASE_USER: postgres
DATABASE_PASSWORD: postgres
# Auth
AUTH_PROVIDER_ADAPTERS: "email,openid_connect,userpass,token,google_workspace,microsoft_entra,okta"
# Secrets
TOKENS_KEY_BASE: "5OVYJ83AcoQcPmdKNksuBhJFBhjHD1uUa9mDOHV/6EIdBQ6pXksIhkVeWIzFk5S2"
TOKENS_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
SECRET_KEY_BASE: "5OVYJ83AcoQcPmdKNksuBhJFBhjHD1uUa9mDOHV/6EIdBQ6pXksIhkVeWIzFk5S2"
LIVE_VIEW_SIGNING_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
COOKIE_SIGNING_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
COOKIE_ENCRYPTION_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
# Debugging
LOG_LEVEL: "debug"
# Emails
@@ -104,6 +164,122 @@ services:
condition: "service_healthy"
postgres:
condition: "service_healthy"
healthcheck:
test: ["CMD-SHELL", "curl -f localhost:8081/healthz"]
start_period: 10s
interval: 30s
retries: 5
timeout: 5s
networks:
- app
domain:
build:
context: elixir
cache_from:
- type=registry,ref=us-east1-docker.pkg.dev/firezone-staging/cache/domain:main
args:
APPLICATION_NAME: domain
image: ${API_IMAGE:-us-east1-docker.pkg.dev/firezone-staging/firezone/domain}:${API_TAG:-main}
hostname: domain.cluster.local
environment:
# Erlang
ERLANG_DISTRIBUTION_PORT: 9000
ERLANG_CLUSTER_ADAPTER: "Elixir.Cluster.Strategy.Epmd"
ERLANG_CLUSTER_ADAPTER_CONFIG: '{"hosts":["api@api.cluster.local","web@web.cluster.local","domain@domain.cluster.local"]}'
RELEASE_COOKIE: "NksuBhJFBhjHD1uUa9mDOHV"
RELEASE_HOSTNAME: "domain.cluster.local"
RELEASE_NAME: "domain"
# Database
DATABASE_HOST: postgres
DATABASE_PORT: 5432
DATABASE_NAME: firezone_dev
DATABASE_USER: postgres
DATABASE_PASSWORD: postgres
# Auth
AUTH_PROVIDER_ADAPTERS: "email,openid_connect,userpass,token,google_workspace,microsoft_entra,okta"
# Secrets
TOKENS_KEY_BASE: "5OVYJ83AcoQcPmdKNksuBhJFBhjHD1uUa9mDOHV/6EIdBQ6pXksIhkVeWIzFk5S2"
TOKENS_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
SECRET_KEY_BASE: "5OVYJ83AcoQcPmdKNksuBhJFBhjHD1uUa9mDOHV/6EIdBQ6pXksIhkVeWIzFk5S2"
LIVE_VIEW_SIGNING_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
COOKIE_SIGNING_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
COOKIE_ENCRYPTION_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
# Debugging
LOG_LEVEL: "debug"
# Emails
OUTBOUND_EMAIL_FROM: "public-noreply@firez.one"
OUTBOUND_EMAIL_ADAPTER: "Elixir.Swoosh.Adapters.Postmark"
## Warning: The token is for the blackhole Postmark server created in a separate isolated account,
## that WILL NOT send any actual emails, but you can see and debug them in the Postmark dashboard.
OUTBOUND_EMAIL_ADAPTER_OPTS: '{"api_key":"7da7d1cd-111c-44a7-b5ac-4027b9d230e5"}'
# Seeds
STATIC_SEEDS: "true"
healthcheck:
test: ["CMD-SHELL", "curl -f localhost:4000/healthz"]
start_period: 10s
interval: 30s
retries: 5
timeout: 5s
depends_on:
vault:
condition: "service_healthy"
postgres:
condition: "service_healthy"
networks:
- app
# This is a service container which allows to run mix tasks for local development
# without having to install Elixir and Erlang on the host machine.
elixir:
build:
context: elixir
target: compiler
cache_from:
- type=registry,ref=us-east1-docker.pkg.dev/firezone-staging/cache/elixir:main
args:
APPLICATION_NAME: api
image: ${ELIXIR_IMAGE:-us-east1-docker.pkg.dev/firezone-staging/firezone/elixir}:${ELIXIR_TAG:-main}
hostname: elixir
environment:
# Web Server
EXTERNAL_URL: http://localhost:8081/
# Erlang
ERLANG_DISTRIBUTION_PORT: 9000
RELEASE_COOKIE: "NksuBhJFBhjHD1uUa9mDOHV"
RELEASE_HOSTNAME: "mix.cluster.local"
RELEASE_NAME: "mix"
# Database
DATABASE_HOST: postgres
DATABASE_PORT: 5432
DATABASE_NAME: firezone_dev
DATABASE_USER: postgres
DATABASE_PASSWORD: postgres
# Auth
AUTH_PROVIDER_ADAPTERS: "email,openid_connect,userpass,token,google_workspace,microsoft_entra,okta"
# Secrets
TOKENS_KEY_BASE: "5OVYJ83AcoQcPmdKNksuBhJFBhjHD1uUa9mDOHV/6EIdBQ6pXksIhkVeWIzFk5S2"
TOKENS_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
SECRET_KEY_BASE: "5OVYJ83AcoQcPmdKNksuBhJFBhjHD1uUa9mDOHV/6EIdBQ6pXksIhkVeWIzFk5S2"
LIVE_VIEW_SIGNING_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
COOKIE_SIGNING_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
COOKIE_ENCRYPTION_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
# Higher log level not to make seeds output too verbose
LOG_LEVEL: "info"
# Emails
OUTBOUND_EMAIL_FROM: "public-noreply@firez.one"
OUTBOUND_EMAIL_ADAPTER: "Elixir.Swoosh.Adapters.Postmark"
## Warning: The token is for the blackhole Postmark server created in a separate isolated account,
## that WILL NOT send any actual emails, but you can see and debug them in the Postmark dashboard.
OUTBOUND_EMAIL_ADAPTER_OPTS: '{"api_key":"7da7d1cd-111c-44a7-b5ac-4027b9d230e5"}'
# Mix env should be set to prod to use secrets declared above,
# otherwise seeds will generate invalid tokens
MIX_ENV: "prod"
# Seeds
STATIC_SEEDS: "true"
depends_on:
postgres:
condition: "service_healthy"
networks:
- app
@@ -240,125 +416,6 @@ services:
app:
ipv4_address: 172.28.0.101
api:
build:
context: elixir
cache_from:
- type=registry,ref=us-east1-docker.pkg.dev/firezone-staging/cache/api:main
args:
APPLICATION_NAME: api
image: ${API_IMAGE:-us-east1-docker.pkg.dev/firezone-staging/cache/api}:${API_TAG:-main}
hostname: api.cluster.local
ports:
- 8081:8081/tcp
environment:
# Web Server
EXTERNAL_URL: http://localhost:8081/
PHOENIX_HTTP_API_PORT: "8081"
PHOENIX_SECURE_COOKIES: "false"
# Erlang
ERLANG_DISTRIBUTION_PORT: 9000
ERLANG_CLUSTER_ADAPTER: "Elixir.Cluster.Strategy.Epmd"
ERLANG_CLUSTER_ADAPTER_CONFIG: '{"hosts":["api@api.cluster.local","web@web.cluster.local"]}'
RELEASE_COOKIE: "NksuBhJFBhjHD1uUa9mDOHV"
RELEASE_HOSTNAME: "api.cluster.local"
RELEASE_NAME: "api"
# Database
DATABASE_HOST: postgres
DATABASE_PORT: 5432
DATABASE_NAME: firezone_dev
DATABASE_USER: postgres
DATABASE_PASSWORD: postgres
# Auth
AUTH_PROVIDER_ADAPTERS: "email,openid_connect,userpass,token,google_workspace,microsoft_entra,okta"
# Secrets
TOKENS_KEY_BASE: "5OVYJ83AcoQcPmdKNksuBhJFBhjHD1uUa9mDOHV/6EIdBQ6pXksIhkVeWIzFk5S2"
TOKENS_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
SECRET_KEY_BASE: "5OVYJ83AcoQcPmdKNksuBhJFBhjHD1uUa9mDOHV/6EIdBQ6pXksIhkVeWIzFk5S2"
LIVE_VIEW_SIGNING_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
COOKIE_SIGNING_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
COOKIE_ENCRYPTION_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
# Telemetry
TELEMETRY_ENABLED: "false"
# Debugging
LOG_LEVEL: "debug"
# Emails
OUTBOUND_EMAIL_FROM: "public-noreply@firez.one"
OUTBOUND_EMAIL_ADAPTER: "Elixir.Swoosh.Adapters.Postmark"
## Warning: The token is for the blackhole Postmark server created in a separate isolated account,
## that WILL NOT send any actual emails, but you can see and debug them in the Postmark dashboard.
OUTBOUND_EMAIL_ADAPTER_OPTS: '{"api_key":"7da7d1cd-111c-44a7-b5ac-4027b9d230e5"}'
# Seeds
STATIC_SEEDS: "true"
depends_on:
vault:
condition: "service_healthy"
postgres:
condition: "service_healthy"
healthcheck:
test: ["CMD-SHELL", "curl -f localhost:8081/healthz"]
start_period: 10s
interval: 30s
retries: 5
timeout: 5s
networks:
- app
# This is a service container which allows to run mix tasks for local development
# without having to install Elixir and Erlang on the host machine.
elixir:
build:
context: elixir
target: compiler
cache_from:
- type=registry,ref=us-east1-docker.pkg.dev/firezone-staging/cache/elixir:main
args:
APPLICATION_NAME: api
image: ${ELIXIR_IMAGE:-us-east1-docker.pkg.dev/firezone-staging/firezone/elixir}:${ELIXIR_TAG:-main}
hostname: elixir
environment:
# Web Server
EXTERNAL_URL: http://localhost:8081/
# Erlang
ERLANG_DISTRIBUTION_PORT: 9000
RELEASE_COOKIE: "NksuBhJFBhjHD1uUa9mDOHV"
RELEASE_HOSTNAME: "mix.cluster.local"
RELEASE_NAME: "mix"
# Database
DATABASE_HOST: postgres
DATABASE_PORT: 5432
DATABASE_NAME: firezone_dev
DATABASE_USER: postgres
DATABASE_PASSWORD: postgres
# Auth
AUTH_PROVIDER_ADAPTERS: "email,openid_connect,userpass,token,google_workspace,microsoft_entra,okta"
# Secrets
TOKENS_KEY_BASE: "5OVYJ83AcoQcPmdKNksuBhJFBhjHD1uUa9mDOHV/6EIdBQ6pXksIhkVeWIzFk5S2"
TOKENS_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
SECRET_KEY_BASE: "5OVYJ83AcoQcPmdKNksuBhJFBhjHD1uUa9mDOHV/6EIdBQ6pXksIhkVeWIzFk5S2"
LIVE_VIEW_SIGNING_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
COOKIE_SIGNING_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
COOKIE_ENCRYPTION_SALT: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"
# Telemetry
TELEMETRY_ENABLED: "false"
# Higher log level not to make seeds output too verbose
LOG_LEVEL: "info"
# Emails
OUTBOUND_EMAIL_FROM: "public-noreply@firez.one"
OUTBOUND_EMAIL_ADAPTER: "Elixir.Swoosh.Adapters.Postmark"
## Warning: The token is for the blackhole Postmark server created in a separate isolated account,
## that WILL NOT send any actual emails, but you can see and debug them in the Postmark dashboard.
OUTBOUND_EMAIL_ADAPTER_OPTS: '{"api_key":"7da7d1cd-111c-44a7-b5ac-4027b9d230e5"}'
# Mix env should be set to prod to use secrets declared above,
# otherwise seeds will generate invalid tokens
MIX_ENV: "prod"
# Seeds
STATIC_SEEDS: "true"
depends_on:
postgres:
condition: "service_healthy"
networks:
- app
# IPv6 is currently causing flakiness with GH actions and on our testbed.
# Disabling until there's more time to debug.

View File

@@ -37,10 +37,10 @@ defmodule Domain.Application do
Domain.Relays,
Domain.Gateways,
Domain.Clients,
Domain.Billing
Domain.Billing,
# Observability
# Domain.Telemetry
Domain.Telemetry
]
end
end

View File

@@ -39,6 +39,13 @@ defmodule Domain.Config.Definitions do
def doc_sections do
[
{"Background Jobs",
"""
You need to make sure that at least one of the nodes in the cluster has background jobs enabled.
""",
[
:background_jobs_enabled
]},
{"WebServer",
[
:external_url,
@@ -114,28 +121,30 @@ defmodule Domain.Config.Definitions do
[
:instrumentation_client_logs_enabled,
:instrumentation_client_logs_bucket
]},
{"Telemetry",
[
:telemetry_enabled,
:telemetry_id
]}
]
end
##############################################
## Background Jobs
##############################################
@doc """
Enabled or disable background job workers (eg. syncing IdP directory) for the app instance.
"""
defconfig(:background_jobs_enabled, :boolean, default: false)
##############################################
## Web Server
##############################################
@doc """
The external URL the web UI will be accessible at.
The external URL the UI/API will be accessible at.
Must be a valid and public FQDN for ACME SSL issuance to function.
You can add a path suffix if you want to serve firezone from a non-root path,
eg: `https://firezone.mycorp.com/vpn/`.
If this field is not set or set to `nil`, the server for `api` and `web` apps will not start.
"""
defconfig(:external_url, :string,
default: nil,
changeset: fn changeset, key ->
changeset
|> Domain.Validator.validate_uri(key, require_trailing_slash: true)
@@ -151,7 +160,7 @@ defmodule Domain.Config.Definitions do
defconfig(:phoenix_listen_address, Types.IP, default: "0.0.0.0")
@doc """
Internal port to listen on for the Phoenix web server.
Internal port to listen on for the Phoenix server for the `web` application.
"""
defconfig(:phoenix_http_web_port, :integer,
default: 13_000,
@@ -164,7 +173,7 @@ defmodule Domain.Config.Definitions do
)
@doc """
Internal port to listen on for the Phoenix api server.
Internal port to listen on for the Phoenix server for the `api` application.
"""
defconfig(:phoenix_http_api_port, :integer,
default: 13_000,
@@ -444,25 +453,6 @@ defmodule Domain.Config.Definitions do
"""
defconfig(:instrumentation_client_logs_bucket, :string, default: "logs")
##############################################
## Telemetry
##############################################
@doc """
Enable or disable the Firezone telemetry collection.
For more details see https://docs.firezone.dev/reference/telemetry/.
"""
defconfig(:telemetry_enabled, :boolean, default: true)
defconfig(:telemetry_id, :string,
default: fn ->
:crypto.hash(:sha256, compile_config!(:external_url))
|> Base.url_encode64(padding: false)
end,
legacy_keys: [{:env, "TID", nil}]
)
##############################################
## Gateways
##############################################

View File

@@ -1,189 +1,50 @@
# TODO: when app starts for migrations set env to disable connectivity checks and telemetry
# defmodule Domain.Telemetry do
# @moduledoc """
# Functions for various telemetry events.
# """
# use Supervisor
# alias Domain.Telemetry.{Timer, PostHog}
# require Logger
defmodule Domain.Telemetry do
use Supervisor
import Telemetry.Metrics
alias Domain.Telemetry
# def start_link(opts) do
# Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
# end
def start_link(arg) do
Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
end
# def init(_opts) do
# config = Domain.Config.fetch_env!(:domain, Domain.Telemetry)
@impl true
def init(_arg) do
children = [
# We start a /healthz endpoint that is used for liveness probes
{Bandit, plug: Telemetry.HealthzPlug, scheme: :http, port: 4000},
# if Keyword.fetch!(config, :enabled) == true do
# children = [Timer]
# Supervisor.init(children, strategy: :one_for_one)
# else
# :ignore
# end
# end
# Telemetry poller will execute the given period measurements
# every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics
{:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
# Add reporters as children of your supervision tree.
# {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
]
# def create_api_token do
# PostHog.capture("add_api_token", common_fields())
# :ok
# end
Supervisor.init(children, strategy: :one_for_one)
end
# def delete_api_token(api_token) do
# PostHog.capture(
# "delete_api_token",
# common_fields() ++
# [
# api_token_created_at: api_token.inserted_at
# ]
# )
def metrics do
[
# Database Metrics
summary("domain.repo.query.total_time", unit: {:native, :millisecond}),
summary("domain.repo.query.decode_time", unit: {:native, :millisecond}),
summary("domain.repo.query.query_time", unit: {:native, :millisecond}),
summary("domain.repo.query.queue_time", unit: {:native, :millisecond}),
summary("domain.repo.query.idle_time", unit: {:native, :millisecond}),
# :ok
# end
# VM Metrics
summary("vm.memory.total", unit: {:byte, :kilobyte}),
summary("vm.total_run_queue_lengths.total"),
summary("vm.total_run_queue_lengths.cpu"),
summary("vm.total_run_queue_lengths.io")
]
end
# def add_client do
# PostHog.capture("add_client", common_fields())
# :ok
# end
# def add_actor do
# PostHog.capture("add_actor", common_fields())
# :ok
# end
# def add_rule do
# PostHog.capture("add_rule", common_fields())
# :ok
# end
# def delete_client do
# PostHog.capture("delete_client", common_fields())
# :ok
# end
# def delete_actor do
# PostHog.capture("delete_actor", common_fields())
# :ok
# end
# def delete_rule do
# PostHog.capture("delete_rule", common_fields())
# :ok
# end
# def login do
# PostHog.capture("login", common_fields())
# :ok
# end
# def enable_actor do
# PostHog.capture("enable_actor", common_fields())
# :ok
# end
# def disable_actor do
# PostHog.capture("disable_actor", common_fields())
# :ok
# end
# def domain_started do
# PostHog.capture("domain_started", common_fields())
# :ok
# end
# def ping do
# PostHog.capture("ping", ping_data())
# :ok
# end
# # How far back to count handshakes as an active client
# # @active_client_window 86_400
# def ping_data do
# %{
# local_auth_enabled: {_, local_auth_enabled},
# logo: {_, logo}
# } =
# Domain.Config.fetch_resolved_configs_with_sources!([
# :local_auth_enabled,
# :logo
# ])
# common_fields() ++
# [
# # clients_active_within_24h: Clients.count_active_within(@active_client_window),
# # admin_count: Users.count_by_role(:account_admin_user),
# # actor_count: Users.count(),
# in_docker: in_docker?(),
# # client_count: Clients.count(),
# # max_clients_for_actors: Clients.count_maximum_for_a_actor(),
# # actors_with_mfa: MFA.count_actors_with_mfa_enabled(),
# # actors_with_mfa_totp: MFA.count_actors_with_totp_method(),
# local_authentication: local_auth_enabled,
# # outbound_email: Web.Mailer.active?(),
# external_database:
# external_database?(Map.new(Domain.Config.fetch_env!(:domain, Domain.Repo))),
# logo_type: Domain.Config.Logo.type(logo)
# ]
# end
# defp in_docker? do
# File.exists?("/.dockerenv")
# end
# defp common_fields do
# [
# distinct_id: id(),
# fqdn: fqdn(),
# version: version(),
# kernel_version: "#{os_type()} #{os_version()}"
# ]
# end
# def id do
# Domain.Config.fetch_env!(:domain, __MODULE__)
# |> Keyword.fetch!(:id)
# end
# defp fqdn do
# :web
# |> Domain.Config.fetch_env!(Web.Endpoint)
# |> Keyword.get(:url)
# |> Keyword.get(:host)
# end
# defp version do
# Application.spec(:domain, :vsn) |> to_string()
# end
# defp external_database?(repo_conf) when is_map_key(repo_conf, :hostname) do
# is_external_db?(repo_conf.hostname)
# end
# defp external_database?(repo_conf) when is_map_key(repo_conf, :url) do
# %{host: host} = URI.parse(repo_conf.url)
# is_external_db?(host)
# end
# defp is_external_db?(host) do
# host != "localhost" && host != "127.0.0.1"
# end
# defp os_type do
# case :os.type() do
# {:unix, type} ->
# "#{type}"
# _ ->
# "other"
# end
# end
# defp os_version do
# case :os.version() do
# {major, minor, patch} ->
# "#{major}.#{minor}.#{patch}"
# _ ->
# "0.0.0"
# end
# end
# end
defp periodic_measurements do
[
# A module, function and arguments to be invoked periodically.
# This function must call :telemetry.execute/3 and a metric must be added above.
# {Web, :count_users, []}
]
end
end

View File

@@ -0,0 +1,19 @@
defmodule Domain.Telemetry.HealthzPlug do
@moduledoc """
A plug that returns a 200 OK response for health checks.
"""
import Plug.Conn
@behaviour Plug
@impl true
def init(opts), do: opts
@impl true
def call(conn, _opts) do
conn
|> put_resp_content_type("application/json")
|> send_resp(200, Jason.encode!(%{status: :ok}))
|> halt()
end
end

View File

@@ -60,7 +60,11 @@ defmodule Domain.MixProject do
{:libcluster, "~> 3.3"},
# Observability and Runtime debugging
{:bandit, "~> 1.0"},
{:plug, "~> 1.15"},
{:telemetry, "~> 1.0"},
{:telemetry_poller, "~> 1.0"},
{:telemetry_metrics, "~> 0.6.2"},
{:logger_json, "~> 5.1"},
{:recon, "~> 2.5"},
{:observer_cli, "~> 1.7"},

View File

@@ -97,7 +97,7 @@ defmodule Domain.ConfigTest do
test "raises an error when value is missing", %{account: account} do
message = """
Missing required configuration value for 'external_url'.
Missing required configuration value for 'secret_key_base'.
## How to fix?
@@ -105,24 +105,19 @@ defmodule Domain.ConfigTest do
You can set this configuration via environment variable by adding it to `.env` file:
EXTERNAL_URL=YOUR_VALUE
SECRET_KEY_BASE=YOUR_VALUE
## Documentation
The external URL the web UI will be accessible at.
Must be a valid and public FQDN for ACME SSL issuance to function.
You can add a path suffix if you want to serve firezone from a non-root path,
eg: `https://firezone.mycorp.com/vpn/`.
Primary secret key base for the Phoenix application.
You can find more information on configuration here: https://www.firezone.dev/docs/reference/env-vars/#environment-variable-listing
"""
assert_raise RuntimeError, message, fn ->
fetch_resolved_configs!(account.id, [:external_url])
fetch_resolved_configs!(account.id, [:secret_key_base])
end
end
end
@@ -151,7 +146,7 @@ defmodule Domain.ConfigTest do
test "raises an error when value is missing", %{account: account} do
message = """
Missing required configuration value for 'external_url'.
Missing required configuration value for 'secret_key_base'.
## How to fix?
@@ -159,24 +154,19 @@ defmodule Domain.ConfigTest do
You can set this configuration via environment variable by adding it to `.env` file:
EXTERNAL_URL=YOUR_VALUE
SECRET_KEY_BASE=YOUR_VALUE
## Documentation
The external URL the web UI will be accessible at.
Must be a valid and public FQDN for ACME SSL issuance to function.
You can add a path suffix if you want to serve firezone from a non-root path,
eg: `https://firezone.mycorp.com/vpn/`.
Primary secret key base for the Phoenix application.
You can find more information on configuration here: https://www.firezone.dev/docs/reference/env-vars/#environment-variable-listing
"""
assert_raise RuntimeError, message, fn ->
fetch_resolved_configs_with_sources!(account.id, [:external_url])
fetch_resolved_configs_with_sources!(account.id, [:secret_key_base])
end
end
@@ -192,12 +182,9 @@ defmodule Domain.ConfigTest do
## Documentation
The external URL the web UI will be accessible at.
The external URL the UI/API will be accessible at.
Must be a valid and public FQDN for ACME SSL issuance to function.
You can add a path suffix if you want to serve firezone from a non-root path,
eg: `https://firezone.mycorp.com/vpn/`.
If this field is not set or set to `nil`, the server for `api` and `web` apps will not start.
You can find more information on configuration here: https://www.firezone.dev/docs/reference/env-vars/#environment-variable-listing

View File

@@ -49,13 +49,7 @@ defmodule Web.Telemetry do
summary("phoenix.channel_handled_in.duration",
tags: [:event],
unit: {:native, :millisecond}
),
# VM Metrics
summary("vm.memory.total", unit: {:byte, :kilobyte}),
summary("vm.total_run_queue_lengths.total"),
summary("vm.total_run_queue_lengths.cpu"),
summary("vm.total_run_queue_lengths.io")
)
]
end

View File

@@ -18,15 +18,6 @@ if config_env() == :prod do
ssl_opts: compile_config!(:database_ssl_opts),
parameters: compile_config!(:database_parameters)
external_url = compile_config!(:external_url)
%{
scheme: external_url_scheme,
host: external_url_host,
port: external_url_port,
path: external_url_path
} = URI.parse(external_url)
config :domain, Domain.Tokens,
key_base: compile_config!(:tokens_key_base),
salt: compile_config!(:tokens_salt)
@@ -35,10 +26,6 @@ if config_env() == :prod do
gateway_ipv4_masquerade: compile_config!(:gateway_ipv4_masquerade),
gateway_ipv6_masquerade: compile_config!(:gateway_ipv6_masquerade)
config :domain, Domain.Telemetry,
enabled: compile_config!(:telemetry_enabled),
id: compile_config!(:telemetry_id)
config :domain, Domain.Auth.Adapters.GoogleWorkspace.APIClient,
finch_transport_opts: compile_config!(:http_client_ssl_opts)
@@ -78,70 +65,91 @@ if config_env() == :prod do
config :domain, outbound_email_adapter_configured?: !!compile_config!(:outbound_email_adapter)
###############################
##### Web #####################
###############################
# Enable background jobs only on dedicated nodes
config :domain, Domain.Tokens.Jobs, enabled: compile_config!(:background_jobs_enabled)
config :web, Web.Endpoint,
http: [
ip: compile_config!(:phoenix_listen_address).address,
port: compile_config!(:phoenix_http_web_port),
protocol_options: compile_config!(:phoenix_http_protocol_options)
],
url: [
config :domain, Domain.Auth.Adapters.GoogleWorkspace.Jobs,
enabled: compile_config!(:background_jobs_enabled)
config :domain, Domain.Auth.Adapters.MicrosoftEntra.Jobs,
enabled: compile_config!(:background_jobs_enabled)
config :domain, Domain.Auth.Adapters.Okta.Jobs,
enabled: compile_config!(:background_jobs_enabled)
if external_url = compile_config!(:external_url) do
%{
scheme: external_url_scheme,
host: external_url_host,
port: external_url_port,
path: external_url_path
],
secret_key_base: compile_config!(:secret_key_base),
check_origin: [
"#{external_url_scheme}://#{external_url_host}:#{external_url_port}",
"#{external_url_scheme}://*.#{external_url_host}:#{external_url_port}",
"#{external_url_scheme}://#{external_url_host}",
"#{external_url_scheme}://*.#{external_url_host}"
],
live_view: [
signing_salt: compile_config!(:live_view_signing_salt)
]
} = URI.parse(external_url)
config :web,
external_trusted_proxies: compile_config!(:phoenix_external_trusted_proxies),
private_clients: compile_config!(:phoenix_private_clients)
###############################
##### Web #####################
###############################
config :web,
cookie_secure: compile_config!(:phoenix_secure_cookies),
cookie_signing_salt: compile_config!(:cookie_signing_salt),
cookie_encryption_salt: compile_config!(:cookie_encryption_salt)
config :web, Web.Endpoint,
http: [
ip: compile_config!(:phoenix_listen_address).address,
port: compile_config!(:phoenix_http_web_port),
protocol_options: compile_config!(:phoenix_http_protocol_options)
],
url: [
scheme: external_url_scheme,
host: external_url_host,
port: external_url_port,
path: external_url_path
],
secret_key_base: compile_config!(:secret_key_base),
check_origin: [
"#{external_url_scheme}://#{external_url_host}:#{external_url_port}",
"#{external_url_scheme}://*.#{external_url_host}:#{external_url_port}",
"#{external_url_scheme}://#{external_url_host}",
"#{external_url_scheme}://*.#{external_url_host}"
],
live_view: [
signing_salt: compile_config!(:live_view_signing_salt)
]
config :web, api_url_override: compile_config!(:api_url_override)
config :web,
external_trusted_proxies: compile_config!(:phoenix_external_trusted_proxies),
private_clients: compile_config!(:phoenix_private_clients)
###############################
##### API #####################
###############################
config :web,
cookie_secure: compile_config!(:phoenix_secure_cookies),
cookie_signing_salt: compile_config!(:cookie_signing_salt),
cookie_encryption_salt: compile_config!(:cookie_encryption_salt)
config :api, API.Endpoint,
http: [
ip: compile_config!(:phoenix_listen_address).address,
port: compile_config!(:phoenix_http_api_port),
protocol_options: compile_config!(:phoenix_http_protocol_options)
],
url: [
scheme: external_url_scheme,
host: external_url_host,
port: external_url_port,
path: external_url_path
],
secret_key_base: compile_config!(:secret_key_base)
config :web, api_url_override: compile_config!(:api_url_override)
config :api,
cookie_secure: compile_config!(:phoenix_secure_cookies),
cookie_signing_salt: compile_config!(:cookie_signing_salt),
cookie_encryption_salt: compile_config!(:cookie_encryption_salt)
###############################
##### API #####################
###############################
config :api,
external_trusted_proxies: compile_config!(:phoenix_external_trusted_proxies),
private_clients: compile_config!(:phoenix_private_clients)
config :api, API.Endpoint,
http: [
ip: compile_config!(:phoenix_listen_address).address,
port: compile_config!(:phoenix_http_api_port),
protocol_options: compile_config!(:phoenix_http_protocol_options)
],
url: [
scheme: external_url_scheme,
host: external_url_host,
port: external_url_port,
path: external_url_path
],
secret_key_base: compile_config!(:secret_key_base)
config :api,
cookie_secure: compile_config!(:phoenix_secure_cookies),
cookie_signing_salt: compile_config!(:cookie_signing_salt),
cookie_encryption_salt: compile_config!(:cookie_encryption_salt)
config :api,
external_trusted_proxies: compile_config!(:phoenix_external_trusted_proxies),
private_clients: compile_config!(:phoenix_private_clients)
end
###############################
##### Third-party configs #####

View File

@@ -63,6 +63,15 @@ defmodule Firezone.MixProject do
defp releases do
[
domain: [
include_executables_for: [:unix],
validate_compile_env: true,
applications: [
domain: :permanent,
opentelemetry_exporter: :permanent,
opentelemetry: :temporary
]
],
web: [
include_executables_for: [:unix],
validate_compile_env: true,

View File

@@ -1,6 +1,7 @@
%{
"acceptor_pool": {:hex, :acceptor_pool, "1.0.0", "43c20d2acae35f0c2bcd64f9d2bde267e459f0f3fd23dab26485bf518c281b21", [:rebar3], [], "hexpm", "0cbcd83fdc8b9ad2eee2067ef8b91a14858a5883cb7cd800e6fcd5803e158788"},
"argon2_elixir": {:hex, :argon2_elixir, "4.0.0", "7f6cd2e4a93a37f61d58a367d82f830ad9527082ff3c820b8197a8a736648941", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f9da27cf060c9ea61b1bd47837a28d7e48a8f6fa13a745e252556c14f9132c7f"},
"bandit": {:hex, :bandit, "1.3.0", "6a4e8d7c9ea721edd02c389e2cc867890cd96f83116e71ddf1ccbdd80661550c", [:mix], [{:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "bda37d6c614d74778a5dc43b8bcdc3245cd30619eab0342f58042f968f2165da"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"bureaucrat": {:hex, :bureaucrat, "0.2.9", "d98e4d2b9bdbf22e4a45c2113ce8b38b5b63278506c6ff918e3b943a4355d85b", [:mix], [{:inflex, ">= 1.10.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.2.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, ">= 1.0.0", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0 or ~> 4.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "111c8dd84382a62e1026ae011d592ceee918553e5203fe8448d9ba6ccbdfff7d"},
"bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"},
@@ -99,6 +100,7 @@
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
"telemetry_registry": {:hex, :telemetry_registry, "0.3.1", "14a3319a7d9027bdbff7ebcacf1a438f5f5c903057b93aee484cca26f05bdcba", [:mix, :rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6d0ca77b691cf854ed074b459a93b87f4c7f5512f8f7743c635ca83da81f939e"},
"tesla": {:hex, :tesla, "1.8.0", "d511a4f5c5e42538d97eef7c40ec4f3e44effdc5068206f42ed859e09e51d1fd", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "10501f360cd926a309501287470372af1a6e1cbed0f43949203a4c13300bc79f"},
"thousand_island": {:hex, :thousand_island, "1.3.5", "6022b6338f1635b3d32406ff98d68b843ba73b3aa95cfc27154223244f3a6ca5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2be6954916fdfe4756af3239fb6b6d75d0b8063b5df03ba76fd8a4c87849e180"},
"tls_certificate_check": {:hex, :tls_certificate_check, "1.21.0", "042ab2c0c860652bc5cf69c94e3a31f96676d14682e22ec7813bd173ceff1788", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "6cee6cffc35a390840d48d463541d50746a7b0e421acaadb833cfc7961e490e7"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
"wallaby": {:hex, :wallaby, "0.30.6", "7dc4c1213f3b52c4152581d126632bc7e06892336d3a0f582853efeeabd45a71", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:web_driver_client, "~> 0.2.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "50950c1d968549b54c20e16175c68c7fc0824138e2bb93feb11ef6add8eb23d4"},

View File

@@ -481,10 +481,6 @@ locals {
value = var.stripe_default_price_id
},
# Telemetry
{
name = "TELEMETRY_ENABLED"
value = "false"
},
{
name = "INSTRUMENTATION_CLIENT_LOGS_ENABLED"
value = true

View File

@@ -14,12 +14,12 @@ terraform {
google = {
source = "hashicorp/google"
version = "~> 5.2"
version = "~> 5.20"
}
google-beta = {
source = "hashicorp/google-beta"
version = "~> 5.2"
version = "~> 5.20"
}
tls = {

View File

@@ -24,65 +24,65 @@ provider "registry.terraform.io/cyrilgdn/postgresql" {
}
provider "registry.terraform.io/hashicorp/aws" {
version = "5.35.0"
constraints = ">= 3.29.0, >= 5.20.0"
version = "5.41.0"
constraints = ">= 3.29.0, >= 5.30.0"
hashes = [
"h1:MKNFmhsOIirK7Qzr6TWkVaBcVGN81lCU0BPiaPOeQ8s=",
"zh:3a2a6f40db82d30ea8c5e3e251ca5e16b08e520570336e7e342be823df67e945",
"zh:420a23b69b412438a15b8b2e2c9aac2cf2e4976f990f117e4bf8f630692d3949",
"zh:4d8b887f6a71b38cff77ad14af9279528433e279eed702d96b81ea48e16e779c",
"zh:4edd41f8e1c7d29931608a7b01a7ae3d89d6f95ef5502cf8200f228a27917c40",
"zh:6337544e2ded5cf37b55a70aa6ce81c07fd444a2644ff3c5aad1d34680051bdc",
"zh:668faa3faaf2e0758bf319ea40d2304340f4a2dc2cd24460ddfa6ab66f71b802",
"zh:79ddc6d7c90e59fdf4a51e6ea822ba9495b1873d6a9d70daf2eeaf6fc4eb6ff3",
"zh:885822027faf1aa57787f980ead7c26e7d0e55b4040d926b65709b764f804513",
"zh:8c50a8f397b871388ff2e048f5eb280af107faa2e8926694f1ffd9f32a7a7cdf",
"h1:DiX7N35G2NUQRyRGy90+gyePnhP4w77f8LrJUronotE=",
"zh:0553331a6287c146353b6daf6f71987d8c000f407b5e29d6e004ea88faec2e67",
"zh:1a11118984bb2950e8ee7ef17b0f91fc9eb4a42c8e7a9cafd7eb4aca771d06e4",
"zh:236fedd266d152a8233a7fe27ffdd99ca27d9e66a9618a988a4c3da1ac24a33f",
"zh:34bc482ea04cf30d4d216afa55eecf66854e1acf93892cb28a6b5af91d43c9b7",
"zh:39d7eb15832fe339bf46e3bab9852280762a1817bf1afc459eecd430e20e3ad5",
"zh:39fb07429c51556b05170ec2b6bd55e2487adfe1606761eaf1f2a43c4bb20e47",
"zh:71d7cd3013e2f3fa0f65194af29ee6f5fa905e0df2b72b723761dc953f4512ea",
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
"zh:a2f5d2553df5573a060641f18ee7585587047c25ba73fd80617f59b5893d22b4",
"zh:c43833ae2a152213ee92eb5be7653f9493779eddbe0ce403ea49b5f1d87fd766",
"zh:dab01527a3a55b4f0f958af6f46313d775e27f9ad9d10bedbbfea4a35a06dc5f",
"zh:ed49c65620ec42718d681a7fc00c166c295ff2795db6cede2c690b83f9fb3e65",
"zh:f0a358c0ae1087c466d0fbcc3b4da886f33f881a145c3836ec43149878b86a1a",
"zh:9b271ae12394e7e2ce6da568b42226a146e90fd705e02a670fcb93618c4aa19f",
"zh:a884dd978859d001709681f9513ba0fbb0753d1d459a7f3434ecc5f1b8699c49",
"zh:b8c3c7dc10ae4f6143168042dcf8dee63527b103cc37abc238ea06150af38b6e",
"zh:ba94ffe0893ad60c0b70c402e163b4df2cf417e93474a9cc1a37535bba18f22d",
"zh:d5ba851d971ff8d796afd9a100acf55eaac0c197c6ab779787797ce66f419f0e",
"zh:e8c090d0c4f730c4a610dc4f0c22b177a0376d6f78679fc3f1d557b469e656f4",
"zh:ed7623acde26834672969dcb5befdb62900d9f216d32e7478a095d2b040a0ea7",
]
}
provider "registry.terraform.io/hashicorp/google" {
version = "5.18.0"
version = "5.20.0"
constraints = "~> 5.2"
hashes = [
"h1:WicMa7ZYUyJlYmePh4jCaPfxAeM8U/8/8x632gBMMPY=",
"zh:1f9c144b0bd83c125d27b20c0d3970ba817bbd8eaea8778291c74b54f79e9de5",
"zh:4c34f6e875919623bcd6fdc67ee51c54aa469d5184b6a3899cfc97c6dfb83a81",
"zh:661443c63c3f976448eff875262e98a5564fef4bc11047b2d841b71034c7fb26",
"zh:6a1ace1d2a42d4c7fdc13f67cd582d11ffb27b62309d353fb24609032f992acd",
"zh:886bcc6cb6757aa38c6dc31988be1de3aa1880067a8e984aa2592c436f62781f",
"zh:92e4a74ca1bc5128054ba207f996b665f474c0b338e675dafd0d08615faf2693",
"zh:945a41443df9f3dbe16414a9612cbdf8f41f654307207c0d94822b94d2b5d26e",
"zh:9cd6395a435539c088f8d8713599cb05d944f6d348ef08b5804aee71dc956022",
"zh:c526621d5353adc12d3d277eb18738619542f400dc8d1d9938ebdaca1dc32390",
"zh:c896deccd58176e9530023fe58b673269a0e235affae0f344a1b49faf938806e",
"zh:f4dbfab40f58044669215387348565aeb94e6d6ef4916c0594ca5db98ad91e37",
"h1:zVFayCyqprY+NCbUCWe9RgpH3BGuAtOyLzqhA8f3VE8=",
"zh:10197fce4ddf2be32717fb3a5992b45ee1a134f8c66207ba7a2d89339bcad17e",
"zh:428c22bc9ae637adaa3c99e7ec2f5df3828c1625cebfc0ef680e520abdaff820",
"zh:4474139669a13997abd3d8282dc00905a08c695895b1b3d09e5a87753be54a24",
"zh:4bcd7a09577303d71146899f70413a7376531c940015eb6bfa047df55778d2ef",
"zh:5c5083c8deae3093a63d9a7ff18844985e35f2108fca49a812be85f3ec8e7409",
"zh:72ab7bf2a7a1ae98bf1ed790cc3babcd6399db58aa2c9bcef005f9b709b8ad98",
"zh:95cf50e8aa4fe5495b983aca1478cd7909fafc92c0e1942b764343076f367e3f",
"zh:9efd8613897beee98c11befc1f8ebfea14b5a0e88b0fdd2f737a1a2acd5e2a2f",
"zh:b91ddced2f7916338e1f7d0fa003a15bb5700d8d4f389d906f2944334d67bbc2",
"zh:db0270eb90eda9ce98668afc517365d5876c81bb12f0375d6837d204231f6df5",
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
"zh:f58e490c192698ff8655081d467e57ee13558aa47f950f1d249318bf5cc93e5e",
]
}
provider "registry.terraform.io/hashicorp/google-beta" {
version = "5.18.0"
version = "5.20.0"
constraints = "~> 5.2"
hashes = [
"h1:jTtimo6WpBCeznYLOcvFM+pP0CT6rQ5ZV4FxeeP3+ZU=",
"zh:2340414b1aaa37b8625b7ebb49d7724923a1970e93d183345779a4344c4932aa",
"zh:36a65d4ca5baebe59c232971052b13e01a67e8d514e4360c86747b631e2068a4",
"zh:40c2e336ea593f69af062a3fffeca34a081f311bf38862ed0ec87257e54b3942",
"zh:418d78834ea0051e0e9b15a609b676c5079cd2cf279c80f9847b71634c280c40",
"zh:79c004b57c5034bc2a5a08c07848b59eecf75ef901524da5f5868ec865cc774d",
"zh:9608a333f6dfe7278fdbbbd6aeb2864fe7e539fa4a5ebca42304cc3b25374d72",
"zh:9bcae66eeaf5d532baa17d5e9c84c0997e8d4d558106c74c3e3d13852a94cfc8",
"zh:9c0a9b6faa2b5fc2fa9190f8222da9d12ea0d6ddcf37f55f9d86c0efc02f594d",
"zh:a21603acb95c8403bcd2b9b9f2d38d1aadbb8e329f0072d1e6c3abf10fa1944b",
"zh:a7ebe06b056571693948004f0015dba209a8304713cbb65507d3f6020e5c1ac4",
"zh:e009cdd91f19e847912ca522efd592d0937dbca297addce9672caa747bcd0a3f",
"h1:NV6nLDJYo9Y0d06ggDM05WJapYg0rFF52RmjxGkD+88=",
"zh:2792639ca660f373ce0c0d152f28d1d2e59b590c19d960eddea3c7b70be2e811",
"zh:5a29c775934d5fdf3960687222b0c1505741104ade9a94e42a11d6bef73c1656",
"zh:71fcdf323e7e5bf91d12450ad7f948eef3df935e1875764d5f3c6316b57faa1b",
"zh:89ca5d8cb4d17d7855b7ee7c347a3bb57d5c935c4cc6d18ac78098c9c1c6008a",
"zh:c2a1e2d093ade9a5a4d5170fa4d439c2542f7d01a114af2a5a92b071268d193e",
"zh:c4999b0bb00ce68bea7b203fdd6685c8133f7593d8ce7a9214cedac4844c22b0",
"zh:c912e8f6baa182780678b1caf44863e7d829942c0e9f4a3f04bd623e1bf5f430",
"zh:d553df714bc0d94b3bcdba37a3fca2eb099d7e494502a64a20b3d0f5213b979b",
"zh:d92266f87f3679b179a238b2a3df5309da6b9d86bdcb9311cce5f4b9537938fb",
"zh:e0daa80c3691c6b977c64d22a0c4f6e180d5d3dc888306f7057adb179baab761",
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
"zh:fee824aeb0f16837d3aaeadbabdf9e40d162344415acba8c521e43cf76d0efa8",
]
}

View File

@@ -433,10 +433,6 @@ locals {
value = var.stripe_default_price_id
},
# Telemetry
{
name = "TELEMETRY_ENABLED"
value = "false"
},
{
name = "INSTRUMENTATION_CLIENT_LOGS_ENABLED"
value = true
@@ -485,6 +481,70 @@ locals {
]
}
module "domain" {
source = "../../modules/elixir-app"
project_id = module.google-cloud-project.project.project_id
compute_instance_type = "n1-standard-2"
compute_instance_region = local.region
compute_instance_availability_zones = ["${local.region}-d"]
dns_managed_zone_name = module.google-cloud-dns.zone_name
vpc_network = module.google-cloud-vpc.self_link
vpc_subnetwork = google_compute_subnetwork.apps.self_link
container_registry = module.google-artifact-registry.url
image_repo = module.google-artifact-registry.repo
image = "domain"
image_tag = var.image_tag
scaling_horizontal_replicas = 2
observability_log_level = "debug"
erlang_release_name = "firezone"
erlang_cluster_cookie = random_password.erlang_cluster_cookie.result
application_name = "domain"
application_version = replace(var.image_tag, ".", "-")
application_ports = [
{
name = "http"
protocol = "TCP"
port = 4000
health_check = {
initial_delay_sec = 60
check_interval_sec = 15
timeout_sec = 10
healthy_threshold = 1
unhealthy_threshold = 2
http_health_check = {
request_path = "/healthz"
}
}
}
]
application_environment_variables = concat([
# Background Jobs
{
name = "BACKGROUND_JOBS_ENABLED"
value = "true"
},
], local.shared_application_environment_variables)
application_labels = {
"cluster_name" = local.cluster.name
"cluster_version" = split(".", var.image_tag)[0]
}
}
module "web" {
source = "../../modules/elixir-app"
project_id = module.google-cloud-project.project.project_id
@@ -551,6 +611,10 @@ module "web" {
name = "API_URL_OVERRIDE"
value = "wss://api.${local.tld}"
},
{
name = "BACKGROUND_JOBS_ENABLED"
value = "false"
},
], local.shared_application_environment_variables)
application_labels = {
@@ -621,6 +685,10 @@ module "api" {
name = "PHOENIX_HTTP_API_PORT"
value = "8080"
},
{
name = "BACKGROUND_JOBS_ENABLED"
value = "false"
},
], local.shared_application_environment_variables)
application_labels = {
@@ -677,7 +745,7 @@ resource "google_compute_firewall" "erlang-distribution" {
}
source_ranges = [google_compute_subnetwork.apps.ip_cidr_range]
target_tags = concat(module.web.target_tags, module.api.target_tags)
target_tags = concat(module.web.target_tags, module.api.target_tags, module.domain.target_tags)
}
## Allow service account to list running instances
@@ -696,8 +764,9 @@ resource "google_project_iam_custom_role" "erlang-discovery" {
resource "google_project_iam_member" "application" {
for_each = {
api = module.api.service_account.email
web = module.web.service_account.email
api = module.api.service_account.email
web = module.web.service_account.email
domain = module.domain.service_account.email
}
project = module.google-cloud-project.project.project_id
@@ -810,7 +879,7 @@ resource "google_compute_firewall" "ssh-ipv4" {
}
source_ranges = ["0.0.0.0/0"]
target_tags = concat(module.web.target_tags, module.api.target_tags)
target_tags = concat(module.web.target_tags, module.api.target_tags, module.domain.target_tags)
}
resource "google_compute_firewall" "ssh-ipv6" {
@@ -835,7 +904,7 @@ resource "google_compute_firewall" "ssh-ipv6" {
}
source_ranges = ["::/0"]
target_tags = concat(module.web.target_tags, module.api.target_tags)
target_tags = concat(module.web.target_tags, module.api.target_tags, module.domain.target_tags)
}
resource "google_compute_firewall" "relays-ssh-ipv4" {
@@ -900,6 +969,7 @@ module "ops" {
slack_alerts_auth_token = var.slack_alerts_auth_token
slack_alerts_channel = var.slack_alerts_channel
api_host = module.api.host
web_host = module.web.host
api_host = module.api.host
web_host = module.web.host
domain_host = module.domain.host
}

View File

@@ -14,12 +14,12 @@ terraform {
google = {
source = "hashicorp/google"
version = "~> 5.2"
version = "~> 5.20"
}
google-beta = {
source = "hashicorp/google-beta"
version = "~> 5.2"
version = "~> 5.20"
}
tls = {

View File

@@ -0,0 +1,38 @@
# 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,
]
}

View File

@@ -0,0 +1,62 @@
# 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}"
}

View File

@@ -16,12 +16,14 @@ locals {
},
{
name = "PHOENIX_EXTERNAL_TRUSTED_PROXIES"
value = jsonencode([
"35.191.0.0/16",
"130.211.0.0/22",
google_compute_global_address.ipv4.address,
google_compute_global_address.ipv6.address
])
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"
@@ -49,16 +51,6 @@ locals {
], var.application_environment_variables)
application_ports_by_name = { for port in var.application_ports : port.name => port }
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"
]
}
# Fetch most recent COS image
@@ -67,69 +59,6 @@ data "google_compute_image" "coreos" {
project = "cos-cloud"
}
# 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}"
}
# Deploy app
resource "google_compute_instance_template" "application" {
project = var.project_id
@@ -358,408 +287,3 @@ resource "google_compute_region_instance_group_manager" "application" {
google_compute_instance_template.application
]
}
# 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" {
project = var.project_id
name = local.application_name
type = "CLOUD_ARMOR"
rule {
action = "allow"
priority = "2147483647"
match {
versioned_expr = "SRC_IPS_V1"
config {
src_ip_ranges = ["*"]
}
}
description = "default allow rule"
}
# TODO: Configure more WAF rules
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.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 = false
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.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" {
project = var.project_id
name = local.application_name
min_tls_version = "TLS_1_2"
profile = "MODERN"
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" {
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,
]
}
## Create URL map for the application
resource "google_compute_url_map" "default" {
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" {
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" {
project = var.project_id
name = "${local.application_name}-http"
url_map = google_compute_url_map.https_redirect.self_link
}
resource "google_compute_target_https_proxy" "default" {
project = var.project_id
name = "${local.application_name}-https"
url_map = google_compute_url_map.default.self_link
ssl_certificates = [google_compute_managed_ssl_certificate.default.self_link]
ssl_policy = google_compute_ssl_policy.application.self_link
quic_override = "NONE"
}
# Allocate global addresses for the load balancer and set up forwarding rules
## IPv4
resource "google_compute_global_address" "ipv4" {
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" {
project = var.project_id
name = local.application_name
labels = local.application_labels
target = google_compute_target_http_proxy.default.self_link
ip_address = google_compute_global_address.ipv4.address
port_range = "80"
load_balancing_scheme = "EXTERNAL"
}
resource "google_compute_global_forwarding_rule" "https" {
project = var.project_id
name = "${local.application_name}-https"
labels = local.application_labels
target = google_compute_target_https_proxy.default.self_link
ip_address = google_compute_global_address.ipv4.address
port_range = "443"
load_balancing_scheme = "EXTERNAL"
}
## IPv6
resource "google_compute_global_address" "ipv6" {
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" {
project = var.project_id
name = "${local.application_name}-ipv6-http"
labels = local.application_labels
target = google_compute_target_http_proxy.default.self_link
ip_address = google_compute_global_address.ipv6.address
port_range = "80"
load_balancing_scheme = "EXTERNAL"
}
resource "google_compute_global_forwarding_rule" "https_ipv6" {
project = var.project_id
name = "${local.application_name}-ipv6-https"
labels = local.application_labels
target = google_compute_target_https_proxy.default.self_link
ip_address = google_compute_global_address.ipv6.address
port_range = "443"
load_balancing_scheme = "EXTERNAL"
}
## Open HTTP(S) ports for the load balancer
resource "google_compute_firewall" "http" {
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(S) 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" {
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" {
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,
]
}
# Create DNS records for the application
resource "google_dns_record_set" "application-ipv4" {
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" {
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,
]
}

View File

@@ -0,0 +1,411 @@
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"
rule {
action = "allow"
priority = "2147483647"
match {
versioned_expr = "SRC_IPS_V1"
config {
src_ip_ranges = ["*"]
}
}
description = "default allow rule"
}
# TODO: Configure more WAF rules
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 = false
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 = "MODERN"
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,
]
}
## Create URL map for the application
resource "google_compute_url_map" "default" {
count = try(google_compute_backend_service.default["http"], null) != null ? 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 = try(google_compute_backend_service.default["http"], null) != null ? 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,
]
}

View File

@@ -222,7 +222,8 @@ variable "application_token_scopes" {
variable "application_dns_tld" {
type = string
nullable = false
nullable = true
default = null
description = "DNS host which will be used to create DNS records for the application and provision SSL-certificates."
}

View File

@@ -33,6 +33,47 @@ locals {
)
}
resource "google_monitoring_uptime_check_config" "domain-https" {
project = var.project_id
display_name = "domain-https"
timeout = "60s"
http_check {
port = "443"
use_ssl = true
validate_ssl = true
request_method = "GET"
path = "/healthz"
accepted_response_status_codes {
status_class = "STATUS_CLASS_2XX"
}
}
monitored_resource {
type = "uptime_url"
labels = {
project_id = var.project_id
host = var.domain_host
}
}
content_matchers {
content = "\"ok\""
matcher = "MATCHES_JSON_PATH"
json_path_matcher {
json_path = "$.status"
json_matcher = "EXACT_MATCH"
}
}
checker_type = "STATIC_IP_CHECKERS"
}
resource "google_monitoring_uptime_check_config" "api-https" {
project = var.project_id

View File

@@ -25,3 +25,7 @@ variable "api_host" {
variable "web_host" {
type = string
}
variable "domain_host" {
type = string
}