diff --git a/.tool-versions b/.tool-versions index c98806b49..8967f5ac1 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,9 +1,9 @@ # These are used for the dev environment. # This should match the versions used in the built product. nodejs 18.16.0 -elixir 1.15.6-otp-26 -erlang 26.1.1 -terraform 1.6.2 +elixir 1.15.7-otp-26 +erlang 26.1.2 +terraform 1.6.5 # Used for static analysis python 3.9.13 diff --git a/elixir/Dockerfile b/elixir/Dockerfile index 986015856..1ace8f8cd 100644 --- a/elixir/Dockerfile +++ b/elixir/Dockerfile @@ -1,8 +1,8 @@ -ARG ALPINE_VERSION="3.18.4" -ARG ERLANG_VERSION="26.1.1" -ARG ERLANG_DOWNLOAD_SHA256="30de56e687cef15c73ef2e2e5bc8a94d28f959656e716e0a65092af7d360af57" -ARG ELIXIR_VERSION="1.15.6" -ARG ELIXIR_DOWNLOAD_SHA256="385fc1958bcf9023a748acf8c42179a0c6123c89744396840bdcd661ee130177" +ARG ALPINE_VERSION="3.19.0" +ARG ERLANG_VERSION="26.1.2" +ARG ERLANG_DOWNLOAD_SHA256="f1074cf3a54f1f87e66027d5abebab2fa76a0243453fa58bc5f30d0ce0313921" +ARG ELIXIR_VERSION="1.15.7" +ARG ELIXIR_DOWNLOAD_SHA256="78bde2786b395515ae1eaa7d26faa7edfdd6632bfcfcd75bccb6341a18e8798f" FROM alpine:${ALPINE_VERSION} as base diff --git a/elixir/README.md b/elixir/README.md index 18f0b8fef..dd268e2d7 100644 --- a/elixir/README.md +++ b/elixir/README.md @@ -356,6 +356,31 @@ iex(web@web-xxxx.us-east1-d.c.firezone-staging.internal)3> Domain.Relays.encode_ ... ``` +## 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: + +```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}" +``` + +If you have issues with credentials try refreshing the application default +token: + +```bash +gcloud auth application-default login +``` + ## Viewing logs Logs can be viewed via th [Logs Explorer](https://console.cloud.google.com/logs) diff --git a/elixir/apps/domain/lib/domain/auth.ex b/elixir/apps/domain/lib/domain/auth.ex index 235efb27d..c2e8678b4 100644 --- a/elixir/apps/domain/lib/domain/auth.ex +++ b/elixir/apps/domain/lib/domain/auth.ex @@ -6,7 +6,7 @@ defmodule Domain.Auth do alias Domain.Auth.{Adapters, Provider} @default_session_duration_hours %{ - account_admin_user: 3, + account_admin_user: 24 * 7 - 1, account_user: 24 * 7 } diff --git a/elixir/apps/domain/lib/domain/auth/adapters/openid_connect.ex b/elixir/apps/domain/lib/domain/auth/adapters/openid_connect.ex index 5b8fcb16b..5ed190e85 100644 --- a/elixir/apps/domain/lib/domain/auth/adapters/openid_connect.ex +++ b/elixir/apps/domain/lib/domain/auth/adapters/openid_connect.ex @@ -59,18 +59,23 @@ defmodule Domain.Auth.Adapters.OpenIDConnect do {:ok, provider} end - def authorization_uri(%Provider{} = provider, redirect_uri) when is_binary(redirect_uri) do + def authorization_uri(%Provider{} = provider, redirect_uri, params \\ %{}) + when is_binary(redirect_uri) do config = config_for_provider(provider) verifier = PKCE.code_verifier() state = State.new() - params = %{ - access_type: :offline, - state: state, - code_challenge_method: PKCE.code_challenge_method(), - code_challenge: PKCE.code_challenge(verifier) - } + params = + Map.merge( + %{ + access_type: :offline, + state: state, + code_challenge_method: PKCE.code_challenge_method(), + code_challenge: PKCE.code_challenge(verifier) + }, + params + ) with {:ok, uri} <- OpenIDConnect.authorization_uri(config, redirect_uri, params) do {:ok, uri, {state, verifier}} diff --git a/elixir/apps/domain/lib/domain/gateways.ex b/elixir/apps/domain/lib/domain/gateways.ex index b91eaf740..5872a9421 100644 --- a/elixir/apps/domain/lib/domain/gateways.ex +++ b/elixir/apps/domain/lib/domain/gateways.ex @@ -469,14 +469,12 @@ defmodule Domain.Gateways do end def connect_gateway(%Gateway{} = gateway) do - {:ok, _} = - Presence.track(self(), "gateways:#{gateway.account_id}", gateway.id, %{ - online_at: System.system_time(:second) - }) + meta = %{online_at: System.system_time(:second)} - {:ok, _} = Presence.track(self(), "gateway_groups:#{gateway.group_id}", gateway.id, %{}) - - :ok + with {:ok, _} <- Presence.track(self(), "gateways:#{gateway.account_id}", gateway.id, meta) do + {:ok, _} = Presence.track(self(), "gateway_groups:#{gateway.group_id}", gateway.id, %{}) + :ok + end end def subscribe_for_gateways_presence_in_account(%Accounts.Account{} = account) do diff --git a/elixir/apps/domain/lib/domain/relays.ex b/elixir/apps/domain/lib/domain/relays.ex index 04fa1cba7..cf33b452b 100644 --- a/elixir/apps/domain/lib/domain/relays.ex +++ b/elixir/apps/domain/lib/domain/relays.ex @@ -277,7 +277,13 @@ defmodule Domain.Relays do |> filter.() |> Repo.all() |> Enum.map(fn relay -> - %{metas: [%{secret: stamp_secret}]} = Map.get(connected_relays, relay.id) + %{metas: metas} = Map.get(connected_relays, relay.id) + + %{secret: stamp_secret} = + metas + |> Enum.sort_by(& &1.online_at, :desc) + |> List.first() + %{relay | stamp_secret: stamp_secret} end) @@ -381,15 +387,15 @@ defmodule Domain.Relays do "" end - {:ok, _} = - Presence.track(self(), "relays#{scope}", relay.id, %{ - online_at: System.system_time(:second), - secret: secret - }) + meta = %{ + online_at: System.system_time(:second), + secret: secret + } - {:ok, _} = Presence.track(self(), "relay_groups:#{relay.group_id}", relay.id, %{}) - - :ok + with {:ok, _} <- Presence.track(self(), "relays#{scope}", relay.id, meta) do + {:ok, _} = Presence.track(self(), "relay_groups:#{relay.group_id}", relay.id, %{}) + :ok + end end def subscribe_for_relays_presence_in_account(%Accounts.Account{} = account) do diff --git a/elixir/apps/domain/test/domain/auth_test.exs b/elixir/apps/domain/test/domain/auth_test.exs index 527d3c97a..0d334e60b 100644 --- a/elixir/apps/domain/test/domain/auth_test.exs +++ b/elixir/apps/domain/test/domain/auth_test.exs @@ -1950,8 +1950,8 @@ defmodule Domain.AuthTest do assert {:ok, %Auth.Subject{} = subject} = sign_in(provider, identity.provider_identifier, secret, user_agent, remote_ip) - three_hours = 3 * 60 * 60 - assert_datetime_diff(subject.expires_at, DateTime.utc_now(), three_hours) + one_week = 7 * 24 * 60 * 60 + assert_datetime_diff(subject.expires_at, DateTime.utc_now(), one_week - 60 * 60) actor = Fixtures.Actors.create_actor(type: :account_user, account: account) identity = Fixtures.Auth.create_identity(account: account, provider: provider, actor: actor) @@ -1960,7 +1960,6 @@ defmodule Domain.AuthTest do assert {:ok, %Auth.Subject{} = subject} = sign_in(provider, identity.provider_identifier, secret, user_agent, remote_ip) - one_week = 7 * 24 * 60 * 60 assert_datetime_diff(subject.expires_at, DateTime.utc_now(), one_week) end @@ -2191,7 +2190,7 @@ defmodule Domain.AuthTest do assert_datetime_diff(subject.expires_at, DateTime.utc_now(), one_week) end - test "returned expiration duration is capped at 3 hours for account admin users", %{ + test "returned expiration duration is capped at 1 week for account admin users", %{ bypass: bypass, account: account, provider: provider, @@ -2217,8 +2216,8 @@ defmodule Domain.AuthTest do assert {:ok, %Auth.Subject{} = subject} = sign_in(provider, payload, user_agent, remote_ip) - three_hours = 3 * 60 * 60 - assert_datetime_diff(subject.expires_at, DateTime.utc_now(), three_hours) + one_week = 7 * 24 * 60 * 60 + assert_datetime_diff(subject.expires_at, DateTime.utc_now(), one_week - 60 * 60) end test "returns error when provider is disabled", %{ diff --git a/elixir/apps/domain/test/domain/gateways_test.exs b/elixir/apps/domain/test/domain/gateways_test.exs index 96d337bc4..be000a951 100644 --- a/elixir/apps/domain/test/domain/gateways_test.exs +++ b/elixir/apps/domain/test/domain/gateways_test.exs @@ -1060,4 +1060,13 @@ defmodule Domain.GatewaysTest do assert {:managed, :turn} == relay_strategy([managed_group]) end end + + describe "connect_gateway/2" do + test "does not allow duplicate presence", %{account: account} do + gateway = Fixtures.Gateways.create_gateway(account: account) + + assert connect_gateway(gateway) == :ok + assert {:error, {:already_tracked, _pid, _topic, _key}} = connect_gateway(gateway) + end + end end diff --git a/elixir/apps/domain/test/domain/relays_test.exs b/elixir/apps/domain/test/domain/relays_test.exs index b189aad15..e2d0eb483 100644 --- a/elixir/apps/domain/test/domain/relays_test.exs +++ b/elixir/apps/domain/test/domain/relays_test.exs @@ -521,15 +521,17 @@ defmodule Domain.RelaysTest do test "returns list of connected account relays", %{account: account} do resource = Fixtures.Resources.create_resource(account: account) - relay = Fixtures.Relays.create_relay(account: account) + relay1 = Fixtures.Relays.create_relay(account: account) + relay2 = Fixtures.Relays.create_relay(account: account) stamp_secret = Ecto.UUID.generate() - assert connect_relay(relay, stamp_secret) == :ok + assert connect_relay(relay1, stamp_secret) == :ok + assert connect_relay(relay2, stamp_secret) == :ok - assert {:ok, [connected_relay]} = list_connected_relays_for_resource(resource, :self_hosted) + assert {:ok, connected_relays} = list_connected_relays_for_resource(resource, :self_hosted) - assert connected_relay.id == relay.id - assert connected_relay.stamp_secret == stamp_secret + assert Enum.all?(connected_relays, &(&1.stamp_secret == stamp_secret)) + assert Enum.sort(Enum.map(connected_relays, & &1.id)) == Enum.sort([relay1.id, relay2.id]) end test "returns list of connected global relays", %{account: account} do @@ -961,4 +963,14 @@ defmodule Domain.RelaysTest do assert authorize_relay(Ecto.UUID.generate()) == {:error, :invalid_token} end end + + describe "connect_relay/2" do + test "does not allow duplicate presence", %{account: account} do + relay = Fixtures.Relays.create_relay(account: account) + stamp_secret = Ecto.UUID.generate() + + assert connect_relay(relay, stamp_secret) == :ok + assert {:error, {:already_tracked, _pid, _topic, _key}} = connect_relay(relay, stamp_secret) + end + end end diff --git a/elixir/apps/web/lib/web/controllers/auth_controller.ex b/elixir/apps/web/lib/web/controllers/auth_controller.ex index b5865811f..97747fd3d 100644 --- a/elixir/apps/web/lib/web/controllers/auth_controller.ex +++ b/elixir/apps/web/lib/web/controllers/auth_controller.ex @@ -205,9 +205,14 @@ defmodule Web.AuthController do end end - def redirect_to_idp(%Plug.Conn{} = conn, redirect_url, %Domain.Auth.Provider{} = provider) do + def redirect_to_idp( + %Plug.Conn{} = conn, + redirect_url, + %Domain.Auth.Provider{} = provider, + params \\ %{} + ) do {:ok, authorization_url, {state, code_verifier}} = - OpenIDConnect.authorization_uri(provider, redirect_url) + OpenIDConnect.authorization_uri(provider, redirect_url, params) key = state_cookie_key(provider.id) value = :erlang.term_to_binary({state, code_verifier}) diff --git a/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/connect.ex b/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/connect.ex index ddc073bfc..0509d890b 100644 --- a/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/connect.ex +++ b/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/connect.ex @@ -15,7 +15,7 @@ defmodule Web.Settings.IdentityProviders.GoogleWorkspace.Connect do ~p"/#{provider.account_id}/settings/identity_providers/google_workspace/#{provider}/handle_callback" ) - Web.AuthController.redirect_to_idp(conn, redirect_url, provider) + Web.AuthController.redirect_to_idp(conn, redirect_url, provider, %{prompt: "consent"}) else {:error, :not_found} -> conn diff --git a/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/connect_test.exs b/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/connect_test.exs index 48cc40ed3..d93d68dc5 100644 --- a/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/connect_test.exs +++ b/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/connect_test.exs @@ -94,7 +94,8 @@ defmodule Web.Live.Settings.IdentityProviders.GoogleWorkspace.Connect do "https://www.googleapis.com/auth/admin.directory.orgunit.readonly " <> "https://www.googleapis.com/auth/admin.directory.group.readonly " <> "https://www.googleapis.com/auth/admin.directory.user.readonly", - "state" => state + "state" => state, + "prompt" => "consent" } end end diff --git a/rust/Dockerfile b/rust/Dockerfile index 315b2608b..981931ebd 100644 --- a/rust/Dockerfile +++ b/rust/Dockerfile @@ -1,9 +1,57 @@ # Global args to use in build commands -ARG ALPINE_VERSION="3.18" +ARG ALPINE_VERSION="3.19" ARG CARGO_CHEF_VERSION="0.1.62" +ARG RUSTUP_VERSION="1.26.0" +ARG RUSTUP_x86_DOWNLOAD_SHA256="7aa9e2a380a9958fc1fc426a3323209b2c86181c6816640979580f62ff7d48d4" +ARG RUSTUP_aarch64_DOWNLOAD_SHA256="b1962dfc18e1fd47d01341e6897cace67cddfabf547ef394e8883939bd6e002e" +ARG RUST_VERSION="1.74.1" + +FROM alpine:${ALPINE_VERSION} as rust + +# Important! Update this no-op ENV variable when this Dockerfile +# is updated with the current date. It will force refresh of all +# of the base images and things like `apk add` won't be using +# old cached versions when the Dockerfile is built. +ENV REFRESHED_AT=2023-12-11 \ + LANG=C.UTF-8 \ + TERM=xterm + +RUN set -xe \ + # Upgrade Alpine and base packages + && apk --no-cache --update-cache --available upgrade \ + # Install required deps + && apk add --no-cache --update-cache \ + ca-certificates \ + gcc + +ENV RUSTUP_HOME=/usr/local/rustup \ + CARGO_HOME=/usr/local/cargo \ + PATH=/usr/local/cargo/bin:$PATH + +ARG RUSTUP_VERSION +ARG RUSTUP_x86_DOWNLOAD_SHA256 +ARG RUSTUP_aarch64_DOWNLOAD_SHA256 +ARG RUST_VERSION +RUN set -eux; \ + apkArch="$(apk --print-arch)"; \ + case "$apkArch" in \ + x86_64) rustArch='x86_64-unknown-linux-musl'; rustupSha256=${RUSTUP_x86_DOWNLOAD_SHA256} ;; \ + aarch64) rustArch='aarch64-unknown-linux-musl'; rustupSha256=${RUSTUP_aarch64_DOWNLOAD_SHA256} ;; \ + *) echo >&2 "unsupported architecture: $apkArch"; exit 1 ;; \ + esac; \ + url="https://static.rust-lang.org/rustup/archive/${RUSTUP_VERSION}/${rustArch}/rustup-init"; \ + wget "$url"; \ + echo "${rustupSha256} *rustup-init" | sha256sum -c -; \ + chmod +x rustup-init; \ + ./rustup-init -y --no-modify-path --profile minimal --default-toolchain ${RUST_VERSION} --default-host ${rustArch}; \ + rm rustup-init; \ + chmod -R a+w $RUSTUP_HOME $CARGO_HOME; \ + rustup --version; \ + cargo --version; \ + rustc --version; # This image is used to prepare Cargo Chef which is used to cache dependencies -FROM rust:1-alpine${ALPINE_VERSION} as chef +FROM rust as chef ARG CARGO_CHEF_VERSION RUN set -xe \ diff --git a/terraform/environments/production/.terraform.lock.hcl b/terraform/environments/production/.terraform.lock.hcl index 54ea60282..05cf2f2ac 100644 --- a/terraform/environments/production/.terraform.lock.hcl +++ b/terraform/environments/production/.terraform.lock.hcl @@ -1,43 +1,65 @@ # This file is maintained automatically by "terraform init". # Manual edits may be lost in future updates. +provider "registry.terraform.io/cyrilgdn/postgresql" { + version = "1.21.1-beta.1" + constraints = "1.21.1-beta.1" + hashes = [ + "h1:fOpIaAmm9W7Ku58FQBaxQsqV1bdeIisWsbSD+RnVEFs=", + "zh:03619b7c00b869567fe0a8cd391c0137ca4f7b6ef0173035e625d610e9a5233a", + "zh:1a1d1aec60a58052f9292cfe6e1b780c327d70a95d7e35f80eadea1e0c19820f", + "zh:1d559c12f6f7965fcb0e246ed2b211b0f21a6635755c6545bd3692fdb356c1e3", + "zh:26730d86fcf64395d31bf8f3f30cd49260df947f94deaed46cbe866f9ae2b859", + "zh:26b3ffd5a02cdd68340fe9296bf5e79b78bd0e5a4729ab9136b384476dfb31b7", + "zh:2c57d364b3302c7e9e7bc10b0768212ff238cb8b136ba0fe47a421b0b645de8c", + "zh:653674a2807f3b0818743ea9ac1ef885c47a7971c8025db66a410da1fc13cf5e", + "zh:696c9f6452e182932f5de1665a91ef7dcfa60c62d5b38641088c7aaad661bf1b", + "zh:69a284e91441785da80c7a421e0d92c34f83954ca47e793cfa9c875fd6e4d1dc", + "zh:c5a08bfb638ed660995abc4a82f3eeaa17587c5aecb4bd5073366359494d660f", + "zh:c6e630b1f9025bd9b9001ef88b89e530fe644dfde8ab5a572a4e9fce98384fd1", + "zh:c7df7ce987ce7e405999cbbac2a08c1a8d08cf076c9c1a0081d533e47665dfa2", + "zh:c9b46c3f065c923a6ea82e1b692de8541eb13f508d440222696125b2be64ad9a", + "zh:ee7c7e74882158662733a097d100cd232c82a72fcf38d4d5c8e391389c3d0dfe", + ] +} + provider "registry.terraform.io/hashicorp/google" { - version = "5.2.0" + version = "5.9.0" constraints = "~> 5.2" hashes = [ - "h1:psy0RRnGgKCsDKjdXCxQMKt4A1BlcbspWLB5UZK3A5U=", - "zh:1d4c5b154d4764a0e3e8893193dc71ba5a4cdb2d9d9dd20f69312cc75399b038", - "zh:26c5c6ad5edc27c643f43d950ffe982267b732723a09fef74c672ede7a7459f7", - "zh:2b48824692ecc7fe8ae3366010a7cf8b441aa2ecb4b6e9777638952844eff19e", - "zh:2f77cbb0528e58228117c7976e8864e7604614123c8b33d7329ffb0d084505b9", - "zh:408e6a680c4b7235dc677b8ba6ccbda0bf07ffcbd3d13767474eea2c5177488f", - "zh:68c2e914cf71ff490b4dbc6487900c35f702285cb0047614eccafb6ff057b748", - "zh:849052c81c2ea4c703b22af9ae524d3f45e42c7e9a3553c1ff7a95f49fde6886", - "zh:8f764a4ddcd5eea9f81cc72bb2fd29e2549a91b66faf8df8583c584298a26a86", - "zh:dddc597b4af5e2dc772ec4291e39daffb4dc46f2cccde1d3a6d2cbe8d291743d", - "zh:de9752d744bd91fd35e589fea0d8a72f983fe6fc872cfd19841758dcb8629a3b", - "zh:ec40d112e5022e2ba408bdfab1fd2d4f30c0183db02a771fdf26cd3a8c7e9949", + "h1:jG9wcaMKIuI8JSf8T+SjAcw5vhEpW/fnFfPjSXMbuEY=", + "zh:19c618c257b2d9e30a0978b1282b1e418748323ae74d9c1ad63a858cb159cd86", + "zh:2c1f18b6714062fe8eab633918b41c622423693f2a4fd747dc516f3578b8e738", + "zh:440b31f85e2d823919639c4d248a058cd90020724a2fa543546e36611ca18df4", + "zh:453edfad0fbd30e6d694f1b38cc9d5f0b8ec356bbce3f2919f1c4622518c46ca", + "zh:47965b68bf9afd2f6a7412792083911d22b6a1a17f0052c9a8329b5ade47bbe9", + "zh:5621990ad07b8cd9af6862f7a66b593b19bbdf20986d7c8cfd8948302810de51", + "zh:74e2380a9acceb552d067697c38b4679e950fc2ba4bf47025d8917910b08df3d", + "zh:a588be4fa16331c406a15e784d419a04e995741ed09eb2e14ec58b53f3ecd8cc", + "zh:a60af7611f69b76ff727ee569b1ebefee82a5e5e1f1809d2df04286ee2c0aa4e", + "zh:c15d781c9a198d343201eb1a4bc17c616ca8cb38bb33739c3e138db06022a171", + "zh:d5c15eeb3be0e01b17ed67ab9b52137480139816edd7e90e93643be57564d2d0", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } provider "registry.terraform.io/hashicorp/google-beta" { - version = "5.7.0" + version = "5.9.0" constraints = "~> 5.2" hashes = [ - "h1:Rvo7YTb0Uu3oN+2FG0NZ1hBAg1Ra8630U6vkK8OQ2IA=", - "zh:1720258635c34406db44e3d08df9cc02e6897f804d6d558e1c73772324df7b30", - "zh:1a385e9b3d7cda4d76b242f78086f33600c566386fde97d625f3bd39e1dcb1ff", - "zh:26e01a0059c3d9954d0af22ccc05bb21a5b8af7f91a790311cf8ba13e10a5646", - "zh:2acdab4240714cc31407324773280eeeb8ec9ab420404a3f3b8c0f25d73a5e00", - "zh:4836143995c83c925b040c8bc7d3958fa6743c2179a098e783bf919e17db16ab", - "zh:4ca384ea0153ba101af3ca97276f59e76586f935c99b444bd360a4009756af2d", - "zh:54cc3398e8c336bd3f836954f87d3a7ae8fc4bced070baac66a244af656163d4", - "zh:a3e5d9eacc27047ea0c3c00753da3966516dfc982dee180a3a5f4a405689447e", - "zh:cc2052e93d0b52151a2374aa49e56de823ff11dce0570fa7c4bbbcf89467670f", - "zh:d8dc64ade395715dbb41753bda72b1bce6348cd2011f95b1cea1b2c675770c58", - "zh:da1fe36816881dac4acd7ab37291365c4b92584b8689c7c745c7a5b6800049c0", + "h1:/bizlmcNQaK/mYn8+WFayo2ZUVeKkWov2QXwfNGrofY=", + "zh:2fcb82487f8c5335646375bd98a44f22df5b01a3290317e138f638cc156da8a8", + "zh:452959d6aa53837b613dfa8d9e9f301cc7dba6c0176e03ac0d50408f1b1c6eb5", + "zh:49d1f65ba5a8fa5462a95c238aec17b8638b09bee28bb848b9f38d2911f7e8b7", + "zh:528821fcbf788721f71f1e78e38b24cf47da1785521637d211f95d01f53519d2", + "zh:54f9d5e2df07a463f23d40d04014eedad49b274280d220a81dc8f0bde5591226", + "zh:5767e527b13f66a7098fc3059786ade0eaa39fb6d6d3199a4976ccf2b3cdf280", + "zh:582d800aa8c5ff345ce9b5494f83d5d5684d553f5b317daf8e832aa30e708e47", + "zh:723ef344fb8e60244a18b9a0d3a38941f89b568aa09d2c5d7f5fb2693982da61", + "zh:b1ad47427a67f8c8a14868cd474ac8973a068b72dee4d1b828749017dc241212", + "zh:c5b980159090c9d8b5e664c79e324c60bc4c1a5f3cfa904999f027949869eef3", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:ff5fea7d6c6b259938d1d2f008fd4d31242513e2ef592a68b0b6de7eab494a10", ] } @@ -62,21 +84,22 @@ provider "registry.terraform.io/hashicorp/null" { } provider "registry.terraform.io/hashicorp/random" { - version = "3.5.1" + version = "3.6.0" + constraints = "~> 3.5" hashes = [ - "h1:IL9mSatmwov+e0+++YX2V6uel+dV6bn+fC/cnGDK3Ck=", - "zh:04e3fbd610cb52c1017d282531364b9c53ef72b6bc533acb2a90671957324a64", - "zh:119197103301ebaf7efb91df8f0b6e0dd31e6ff943d231af35ee1831c599188d", - "zh:4d2b219d09abf3b1bb4df93d399ed156cadd61f44ad3baf5cf2954df2fba0831", - "zh:6130bdde527587bbe2dcaa7150363e96dbc5250ea20154176d82bc69df5d4ce3", - "zh:6cc326cd4000f724d3086ee05587e7710f032f94fc9af35e96a386a1c6f2214f", + "h1:I8MBeauYA8J8yheLJ8oSMWqB0kovn16dF/wKZ1QTdkk=", + "zh:03360ed3ecd31e8c5dac9c95fe0858be50f3e9a0d0c654b5e504109c2159287d", + "zh:1c67ac51254ba2a2bb53a25e8ae7e4d076103483f55f39b426ec55e47d1fe211", + "zh:24a17bba7f6d679538ff51b3a2f378cedadede97af8a1db7dad4fd8d6d50f829", + "zh:30ffb297ffd1633175d6545d37c2217e2cef9545a6e03946e514c59c0859b77d", + "zh:454ce4b3dbc73e6775f2f6605d45cee6e16c3872a2e66a2c97993d6e5cbd7055", "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:b6d88e1d28cf2dfa24e9fdcc3efc77adcdc1c3c3b5c7ce503a423efbdd6de57b", - "zh:ba74c592622ecbcef9dc2a4d81ed321c4e44cddf7da799faa324da9bf52a22b2", - "zh:c7c5cde98fe4ef1143bd1b3ec5dc04baf0d4cc3ca2c5c7d40d17c0e9b2076865", - "zh:dac4bad52c940cd0dfc27893507c1e92393846b024c5a9db159a93c534a3da03", - "zh:de8febe2a2acd9ac454b844a4106ed295ae9520ef54dc8ed2faf29f12716b602", - "zh:eab0d0495e7e711cca367f7d4df6e322e6c562fc52151ec931176115b83ed014", + "zh:91df0a9fab329aff2ff4cf26797592eb7a3a90b4a0c04d64ce186654e0cc6e17", + "zh:aa57384b85622a9f7bfb5d4512ca88e61f22a9cea9f30febaa4c98c68ff0dc21", + "zh:c4a3e329ba786ffb6f2b694e1fd41d413a7010f3a53c20b432325a94fa71e839", + "zh:e2699bc9116447f96c53d55f2a00570f982e6f9935038c3810603572693712d0", + "zh:e747c0fd5d7684e5bfad8aa0ca441903f15ae7a98a737ff6aca24ba223207e2c", + "zh:f1ca75f417ce490368f047b63ec09fd003711ae48487fba90b4aba2ccf71920e", ] } diff --git a/terraform/environments/production/bi.tf b/terraform/environments/production/bi.tf index 8b57846f7..dca6beca2 100644 --- a/terraform/environments/production/bi.tf +++ b/terraform/environments/production/bi.tf @@ -14,12 +14,6 @@ resource "random_password" "metabase_db_password" { min_special = 1 } -# This user can also be used to connect to the Firezone database, -# but following SQL should be run manually using the Cloud SQL Proxy: -# -# GRANT SELECT ON ALL TABLES IN SCHEMA public TO metabase; -# GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO metabase; -# resource "google_sql_user" "metabase" { project = module.google-cloud-project.project.project_id @@ -36,6 +30,34 @@ resource "google_sql_database" "metabase" { instance = module.google-cloud-sql.master_instance_name } +resource "postgresql_grant" "grant_select_on_all_tables_schema_to_metabase" { + database = google_sql_database.firezone.name + + privileges = ["SELECT"] + objects = [] # ALL + object_type = "table" + schema = "public" + role = google_sql_user.metabase.name + + depends_on = [ + google_sql_user.metabase + ] +} + +resource "postgresql_grant" "grant_execute_on_all_functions_schema_to_metabase" { + database = google_sql_database.firezone.name + + privileges = ["EXECUTE"] + objects = [] # ALL + object_type = "function" + schema = "public" + role = google_sql_user.metabase.name + + depends_on = [ + google_sql_user.metabase + ] +} + module "metabase" { source = "../../modules/metabase-app" project_id = module.google-cloud-project.project.project_id diff --git a/terraform/environments/production/main.tf b/terraform/environments/production/main.tf index 3fae42e0d..73abda1ba 100644 --- a/terraform/environments/production/main.tf +++ b/terraform/environments/production/main.tf @@ -43,6 +43,45 @@ module "google-cloud-project" { billing_account_id = "01DFC9-3D6951-579BE1" } +# Enable audit logs for the production project +resource "google_project_iam_audit_config" "audit" { + project = module.google-cloud-project.project.project_id + + service = "allServices" + + audit_log_config { + log_type = "ADMIN_READ" + } + + audit_log_config { + log_type = "DATA_READ" + + exempted_members = concat( + [ + module.web.service_account.member, + module.api.service_account.member, + module.metabase.service_account.member, + ], + module.gateways[*].service_account.member, + module.relays[*].service_account.member + ) + } + + audit_log_config { + log_type = "DATA_WRITE" + + exempted_members = concat( + [ + module.web.service_account.member, + module.api.service_account.member, + module.metabase.service_account.member, + ], + module.gateways[*].service_account.member, + module.relays[*].service_account.member + ) + } +} + # Grant owner access to the project resource "google_project_iam_binding" "project_owners" { project = module.google-cloud-project.project.project_id @@ -255,7 +294,60 @@ resource "google_sql_database" "firezone" { name = "firezone" instance = module.google-cloud-sql.master_instance_name +} +# Create IAM users for the database for all project owners +resource "google_sql_user" "iam_users" { + for_each = toset(local.project_owners) + + project = module.google-cloud-project.project.project_id + instance = module.google-cloud-sql.master_instance_name + + type = "CLOUD_IAM_USER" + name = each.value +} + +# We can't remove passwords complete because for IAM users we still need to execute those GRANT statements +provider "postgresql" { + scheme = "gcppostgres" + host = "${module.google-cloud-project.project.project_id}:${local.region}:${module.google-cloud-sql.master_instance_name}" + port = 5432 + username = google_sql_user.firezone.name + password = random_password.firezone_db_password.result + superuser = false + sslmode = "disable" +} + +resource "postgresql_grant" "grant_select_on_all_tables_schema_to_iam_users" { + for_each = toset(local.project_owners) + + database = google_sql_database.firezone.name + + privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"] + objects = [] # ALL + object_type = "table" + schema = "public" + role = each.key + + depends_on = [ + google_sql_user.iam_users + ] +} + +resource "postgresql_grant" "grant_execute_on_all_functions_schema_to_iam_users" { + for_each = toset(local.project_owners) + + database = google_sql_database.firezone.name + + privileges = ["EXECUTE"] + objects = [] # ALL + object_type = "function" + schema = "public" + role = each.key + + depends_on = [ + google_sql_user.iam_users + ] } # Create bucket for client logs diff --git a/terraform/environments/production/variables.tf b/terraform/environments/production/variables.tf index 92fca59f6..dc42e40a9 100644 --- a/terraform/environments/production/variables.tf +++ b/terraform/environments/production/variables.tf @@ -36,10 +36,3 @@ variable "postmark_server_api_token" { variable "pagerduty_auth_token" { type = string } - -# TODO: add this variable to make sure you can't accidentally run script from staging to deploy prod -# variable "deploy_to_production" { -# type = bool -# description = "Whether to deploy to production or not" -# default = false -# } diff --git a/terraform/environments/production/versions.tf b/terraform/environments/production/versions.tf index 17ea0b03d..0f8cc1757 100644 --- a/terraform/environments/production/versions.tf +++ b/terraform/environments/production/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = "1.6.2" + required_version = "1.6.5" required_providers { random = { @@ -26,5 +26,10 @@ terraform { source = "hashicorp/tls" version = "~> 4.0" } + + postgresql = { + source = "cyrilgdn/postgresql" + version = "1.21.1-beta.1" + } } } diff --git a/terraform/environments/staging/.terraform.lock.hcl b/terraform/environments/staging/.terraform.lock.hcl index a28f68e5e..05cf2f2ac 100644 --- a/terraform/environments/staging/.terraform.lock.hcl +++ b/terraform/environments/staging/.terraform.lock.hcl @@ -1,43 +1,65 @@ # This file is maintained automatically by "terraform init". # Manual edits may be lost in future updates. +provider "registry.terraform.io/cyrilgdn/postgresql" { + version = "1.21.1-beta.1" + constraints = "1.21.1-beta.1" + hashes = [ + "h1:fOpIaAmm9W7Ku58FQBaxQsqV1bdeIisWsbSD+RnVEFs=", + "zh:03619b7c00b869567fe0a8cd391c0137ca4f7b6ef0173035e625d610e9a5233a", + "zh:1a1d1aec60a58052f9292cfe6e1b780c327d70a95d7e35f80eadea1e0c19820f", + "zh:1d559c12f6f7965fcb0e246ed2b211b0f21a6635755c6545bd3692fdb356c1e3", + "zh:26730d86fcf64395d31bf8f3f30cd49260df947f94deaed46cbe866f9ae2b859", + "zh:26b3ffd5a02cdd68340fe9296bf5e79b78bd0e5a4729ab9136b384476dfb31b7", + "zh:2c57d364b3302c7e9e7bc10b0768212ff238cb8b136ba0fe47a421b0b645de8c", + "zh:653674a2807f3b0818743ea9ac1ef885c47a7971c8025db66a410da1fc13cf5e", + "zh:696c9f6452e182932f5de1665a91ef7dcfa60c62d5b38641088c7aaad661bf1b", + "zh:69a284e91441785da80c7a421e0d92c34f83954ca47e793cfa9c875fd6e4d1dc", + "zh:c5a08bfb638ed660995abc4a82f3eeaa17587c5aecb4bd5073366359494d660f", + "zh:c6e630b1f9025bd9b9001ef88b89e530fe644dfde8ab5a572a4e9fce98384fd1", + "zh:c7df7ce987ce7e405999cbbac2a08c1a8d08cf076c9c1a0081d533e47665dfa2", + "zh:c9b46c3f065c923a6ea82e1b692de8541eb13f508d440222696125b2be64ad9a", + "zh:ee7c7e74882158662733a097d100cd232c82a72fcf38d4d5c8e391389c3d0dfe", + ] +} + provider "registry.terraform.io/hashicorp/google" { - version = "5.7.0" + version = "5.9.0" constraints = "~> 5.2" hashes = [ - "h1:dGrS2F0C3frYTdaYvev6fZDWAGBE6O7Q4DhfO/0P7r8=", - "zh:0c0cf15cc034d5f92cc1cd5ee4615012553a674b69ee1802e46c4b87f1c339aa", - "zh:28e64a798320866c4dc84c323b66eef94ec98043dba016cf01d6adbe2dc85de4", - "zh:3b6e6443a9000354f93682d847737d6e9f702a77c53a492a39b200134b3e8dfd", - "zh:3ed6af130702d9da8fc14f94b3b2c9a93917cda31d50d934dd6de0ca48044572", - "zh:784a0feae2a48aa9a63fe6feb86ad29e8d35647fa29eb42303b799f09ee92060", - "zh:828e0198d99b7f9e53994470d6b51012566660a560da9c8266d1eaf2b140635c", - "zh:8dcb7537d95ec14e75ca71cfce62323682ce0fb453902dc9f890b7c524a915d3", - "zh:a7e760dc5707603091a0c3de0d47d6f8e51d8cce523b5c90587b05f113c5e09c", - "zh:b5c79a5e5b9bcaf0158f5f704d31cf90fb93826085151f06dfc3ef48276ed17a", - "zh:c44a2726dcfbf7d538aa0d5abd2473108f625d1d82485a340e62dfc04043288f", - "zh:f4da66ba04847138949a6a178b8836182f7960e9d069bfe76f1203d9af99cd22", + "h1:jG9wcaMKIuI8JSf8T+SjAcw5vhEpW/fnFfPjSXMbuEY=", + "zh:19c618c257b2d9e30a0978b1282b1e418748323ae74d9c1ad63a858cb159cd86", + "zh:2c1f18b6714062fe8eab633918b41c622423693f2a4fd747dc516f3578b8e738", + "zh:440b31f85e2d823919639c4d248a058cd90020724a2fa543546e36611ca18df4", + "zh:453edfad0fbd30e6d694f1b38cc9d5f0b8ec356bbce3f2919f1c4622518c46ca", + "zh:47965b68bf9afd2f6a7412792083911d22b6a1a17f0052c9a8329b5ade47bbe9", + "zh:5621990ad07b8cd9af6862f7a66b593b19bbdf20986d7c8cfd8948302810de51", + "zh:74e2380a9acceb552d067697c38b4679e950fc2ba4bf47025d8917910b08df3d", + "zh:a588be4fa16331c406a15e784d419a04e995741ed09eb2e14ec58b53f3ecd8cc", + "zh:a60af7611f69b76ff727ee569b1ebefee82a5e5e1f1809d2df04286ee2c0aa4e", + "zh:c15d781c9a198d343201eb1a4bc17c616ca8cb38bb33739c3e138db06022a171", + "zh:d5c15eeb3be0e01b17ed67ab9b52137480139816edd7e90e93643be57564d2d0", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } provider "registry.terraform.io/hashicorp/google-beta" { - version = "5.7.0" + version = "5.9.0" constraints = "~> 5.2" hashes = [ - "h1:Rvo7YTb0Uu3oN+2FG0NZ1hBAg1Ra8630U6vkK8OQ2IA=", - "zh:1720258635c34406db44e3d08df9cc02e6897f804d6d558e1c73772324df7b30", - "zh:1a385e9b3d7cda4d76b242f78086f33600c566386fde97d625f3bd39e1dcb1ff", - "zh:26e01a0059c3d9954d0af22ccc05bb21a5b8af7f91a790311cf8ba13e10a5646", - "zh:2acdab4240714cc31407324773280eeeb8ec9ab420404a3f3b8c0f25d73a5e00", - "zh:4836143995c83c925b040c8bc7d3958fa6743c2179a098e783bf919e17db16ab", - "zh:4ca384ea0153ba101af3ca97276f59e76586f935c99b444bd360a4009756af2d", - "zh:54cc3398e8c336bd3f836954f87d3a7ae8fc4bced070baac66a244af656163d4", - "zh:a3e5d9eacc27047ea0c3c00753da3966516dfc982dee180a3a5f4a405689447e", - "zh:cc2052e93d0b52151a2374aa49e56de823ff11dce0570fa7c4bbbcf89467670f", - "zh:d8dc64ade395715dbb41753bda72b1bce6348cd2011f95b1cea1b2c675770c58", - "zh:da1fe36816881dac4acd7ab37291365c4b92584b8689c7c745c7a5b6800049c0", + "h1:/bizlmcNQaK/mYn8+WFayo2ZUVeKkWov2QXwfNGrofY=", + "zh:2fcb82487f8c5335646375bd98a44f22df5b01a3290317e138f638cc156da8a8", + "zh:452959d6aa53837b613dfa8d9e9f301cc7dba6c0176e03ac0d50408f1b1c6eb5", + "zh:49d1f65ba5a8fa5462a95c238aec17b8638b09bee28bb848b9f38d2911f7e8b7", + "zh:528821fcbf788721f71f1e78e38b24cf47da1785521637d211f95d01f53519d2", + "zh:54f9d5e2df07a463f23d40d04014eedad49b274280d220a81dc8f0bde5591226", + "zh:5767e527b13f66a7098fc3059786ade0eaa39fb6d6d3199a4976ccf2b3cdf280", + "zh:582d800aa8c5ff345ce9b5494f83d5d5684d553f5b317daf8e832aa30e708e47", + "zh:723ef344fb8e60244a18b9a0d3a38941f89b568aa09d2c5d7f5fb2693982da61", + "zh:b1ad47427a67f8c8a14868cd474ac8973a068b72dee4d1b828749017dc241212", + "zh:c5b980159090c9d8b5e664c79e324c60bc4c1a5f3cfa904999f027949869eef3", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:ff5fea7d6c6b259938d1d2f008fd4d31242513e2ef592a68b0b6de7eab494a10", ] } @@ -62,40 +84,41 @@ provider "registry.terraform.io/hashicorp/null" { } provider "registry.terraform.io/hashicorp/random" { - version = "3.5.1" + version = "3.6.0" + constraints = "~> 3.5" hashes = [ - "h1:IL9mSatmwov+e0+++YX2V6uel+dV6bn+fC/cnGDK3Ck=", - "zh:04e3fbd610cb52c1017d282531364b9c53ef72b6bc533acb2a90671957324a64", - "zh:119197103301ebaf7efb91df8f0b6e0dd31e6ff943d231af35ee1831c599188d", - "zh:4d2b219d09abf3b1bb4df93d399ed156cadd61f44ad3baf5cf2954df2fba0831", - "zh:6130bdde527587bbe2dcaa7150363e96dbc5250ea20154176d82bc69df5d4ce3", - "zh:6cc326cd4000f724d3086ee05587e7710f032f94fc9af35e96a386a1c6f2214f", + "h1:I8MBeauYA8J8yheLJ8oSMWqB0kovn16dF/wKZ1QTdkk=", + "zh:03360ed3ecd31e8c5dac9c95fe0858be50f3e9a0d0c654b5e504109c2159287d", + "zh:1c67ac51254ba2a2bb53a25e8ae7e4d076103483f55f39b426ec55e47d1fe211", + "zh:24a17bba7f6d679538ff51b3a2f378cedadede97af8a1db7dad4fd8d6d50f829", + "zh:30ffb297ffd1633175d6545d37c2217e2cef9545a6e03946e514c59c0859b77d", + "zh:454ce4b3dbc73e6775f2f6605d45cee6e16c3872a2e66a2c97993d6e5cbd7055", "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:b6d88e1d28cf2dfa24e9fdcc3efc77adcdc1c3c3b5c7ce503a423efbdd6de57b", - "zh:ba74c592622ecbcef9dc2a4d81ed321c4e44cddf7da799faa324da9bf52a22b2", - "zh:c7c5cde98fe4ef1143bd1b3ec5dc04baf0d4cc3ca2c5c7d40d17c0e9b2076865", - "zh:dac4bad52c940cd0dfc27893507c1e92393846b024c5a9db159a93c534a3da03", - "zh:de8febe2a2acd9ac454b844a4106ed295ae9520ef54dc8ed2faf29f12716b602", - "zh:eab0d0495e7e711cca367f7d4df6e322e6c562fc52151ec931176115b83ed014", + "zh:91df0a9fab329aff2ff4cf26797592eb7a3a90b4a0c04d64ce186654e0cc6e17", + "zh:aa57384b85622a9f7bfb5d4512ca88e61f22a9cea9f30febaa4c98c68ff0dc21", + "zh:c4a3e329ba786ffb6f2b694e1fd41d413a7010f3a53c20b432325a94fa71e839", + "zh:e2699bc9116447f96c53d55f2a00570f982e6f9935038c3810603572693712d0", + "zh:e747c0fd5d7684e5bfad8aa0ca441903f15ae7a98a737ff6aca24ba223207e2c", + "zh:f1ca75f417ce490368f047b63ec09fd003711ae48487fba90b4aba2ccf71920e", ] } provider "registry.terraform.io/hashicorp/tls" { - version = "4.0.4" + version = "4.0.5" constraints = "~> 4.0" hashes = [ - "h1:GZcFizg5ZT2VrpwvxGBHQ/hO9r6g0vYdQqx3bFD3anY=", - "zh:23671ed83e1fcf79745534841e10291bbf34046b27d6e68a5d0aab77206f4a55", - "zh:45292421211ffd9e8e3eb3655677700e3c5047f71d8f7650d2ce30242335f848", - "zh:59fedb519f4433c0fdb1d58b27c210b27415fddd0cd73c5312530b4309c088be", - "zh:5a8eec2409a9ff7cd0758a9d818c74bcba92a240e6c5e54b99df68fff312bbd5", - "zh:5e6a4b39f3171f53292ab88058a59e64825f2b842760a4869e64dc1dc093d1fe", - "zh:810547d0bf9311d21c81cc306126d3547e7bd3f194fc295836acf164b9f8424e", - "zh:824a5f3617624243bed0259d7dd37d76017097dc3193dac669be342b90b2ab48", - "zh:9361ccc7048be5dcbc2fafe2d8216939765b3160bd52734f7a9fd917a39ecbd8", - "zh:aa02ea625aaf672e649296bce7580f62d724268189fe9ad7c1b36bb0fa12fa60", - "zh:c71b4cd40d6ec7815dfeefd57d88bc592c0c42f5e5858dcc88245d371b4b8b1e", - "zh:dabcd52f36b43d250a3d71ad7abfa07b5622c69068d989e60b79b2bb4f220316", + "h1:zeG5RmggBZW/8JWIVrdaeSJa0OG62uFX5HY1eE8SjzY=", + "zh:01cfb11cb74654c003f6d4e32bbef8f5969ee2856394a96d127da4949c65153e", + "zh:0472ea1574026aa1e8ca82bb6df2c40cd0478e9336b7a8a64e652119a2fa4f32", + "zh:1a8ddba2b1550c5d02003ea5d6cdda2eef6870ece86c5619f33edd699c9dc14b", + "zh:1e3bb505c000adb12cdf60af5b08f0ed68bc3955b0d4d4a126db5ca4d429eb4a", + "zh:6636401b2463c25e03e68a6b786acf91a311c78444b1dc4f97c539f9f78de22a", + "zh:76858f9d8b460e7b2a338c477671d07286b0d287fd2d2e3214030ae8f61dd56e", + "zh:a13b69fb43cb8746793b3069c4d897bb18f454290b496f19d03c3387d1c9a2dc", + "zh:a90ca81bb9bb509063b736842250ecff0f886a91baae8de65c8430168001dad9", + "zh:c4de401395936e41234f1956ebadbd2ed9f414e6908f27d578614aaa529870d4", + "zh:c657e121af8fde19964482997f0de2d5173217274f6997e16389e7707ed8ece8", + "zh:d68b07a67fbd604c38ec9733069fbf23441436fecf554de6c75c032f82e1ef19", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } diff --git a/terraform/environments/staging/bi.tf b/terraform/environments/staging/bi.tf index fba515552..0056ef51c 100644 --- a/terraform/environments/staging/bi.tf +++ b/terraform/environments/staging/bi.tf @@ -14,12 +14,6 @@ resource "random_password" "metabase_db_password" { min_special = 1 } -# This user can also be used to connect to the Firezone database, -# but following SQL should be run manually using the Cloud SQL Proxy: -# -# GRANT SELECT ON ALL TABLES IN SCHEMA public TO metabase; -# GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO metabase; -# resource "google_sql_user" "metabase" { project = module.google-cloud-project.project.project_id @@ -36,6 +30,34 @@ resource "google_sql_database" "metabase" { instance = module.google-cloud-sql.master_instance_name } +resource "postgresql_grant" "grant_select_on_all_tables_schema_to_metabase" { + database = google_sql_database.firezone.name + + privileges = ["SELECT"] + objects = [] # ALL + object_type = "table" + schema = "public" + role = google_sql_user.metabase.name + + depends_on = [ + google_sql_user.metabase + ] +} + +resource "postgresql_grant" "grant_execute_on_all_functions_schema_to_metabase" { + database = google_sql_database.firezone.name + + privileges = ["EXECUTE"] + objects = [] # ALL + object_type = "function" + schema = "public" + role = google_sql_user.metabase.name + + depends_on = [ + google_sql_user.metabase + ] +} + module "metabase" { source = "../../modules/metabase-app" project_id = module.google-cloud-project.project.project_id diff --git a/terraform/environments/staging/main.tf b/terraform/environments/staging/main.tf index c34901b11..3692f47b2 100644 --- a/terraform/environments/staging/main.tf +++ b/terraform/environments/staging/main.tf @@ -250,6 +250,60 @@ resource "google_sql_database" "firezone" { instance = module.google-cloud-sql.master_instance_name } +# Create IAM users for the database for all project owners +resource "google_sql_user" "iam_users" { + for_each = toset(local.project_owners) + + project = module.google-cloud-project.project.project_id + instance = module.google-cloud-sql.master_instance_name + + type = "CLOUD_IAM_USER" + name = each.value +} + +# We can't remove passwords complete because for IAM users we still need to execute those GRANT statements +provider "postgresql" { + scheme = "gcppostgres" + host = "${module.google-cloud-project.project.project_id}:${local.region}:${module.google-cloud-sql.master_instance_name}" + port = 5432 + username = google_sql_user.web.name + password = random_password.web_db_password.result + superuser = false + sslmode = "disable" +} + +resource "postgresql_grant" "grant_select_on_all_tables_schema_to_iam_users" { + for_each = toset(local.project_owners) + + database = google_sql_database.firezone.name + + privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"] + objects = [] # ALL + object_type = "table" + schema = "public" + role = each.key + + depends_on = [ + google_sql_user.iam_users + ] +} + +resource "postgresql_grant" "grant_execute_on_all_functions_schema_to_iam_users" { + for_each = toset(local.project_owners) + + database = google_sql_database.firezone.name + + privileges = ["EXECUTE"] + objects = [] # ALL + object_type = "function" + schema = "public" + role = each.key + + depends_on = [ + google_sql_user.iam_users + ] +} + resource "google_storage_bucket" "client-logs" { project = module.google-cloud-project.project.project_id name = "${module.google-cloud-project.project.project_id}-client-logs" diff --git a/terraform/environments/staging/outputs.tf b/terraform/environments/staging/outputs.tf index 4116fdbe9..b31a1adfe 100644 --- a/terraform/environments/staging/outputs.tf +++ b/terraform/environments/staging/outputs.tf @@ -11,3 +11,7 @@ output "demo_postgresql_connection_url" { sensitive = true value = "postgres://${google_sql_user.demo.name}:${random_password.demo_db_password.result}@${module.google-cloud-sql.master_instance_ip_address}/${google_sql_database.demo.name}" } + +output "image_tag" { + value = var.image_tag +} diff --git a/terraform/environments/staging/versions.tf b/terraform/environments/staging/versions.tf index 17ea0b03d..0f8cc1757 100644 --- a/terraform/environments/staging/versions.tf +++ b/terraform/environments/staging/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = "1.6.2" + required_version = "1.6.5" required_providers { random = { @@ -26,5 +26,10 @@ terraform { source = "hashicorp/tls" version = "~> 4.0" } + + postgresql = { + source = "cyrilgdn/postgresql" + version = "1.21.1-beta.1" + } } } diff --git a/terraform/modules/elixir-app/templates/cloud-init.yaml b/terraform/modules/elixir-app/templates/cloud-init.yaml index 0cf1647bd..cff1714a2 100644 --- a/terraform/modules/elixir-app/templates/cloud-init.yaml +++ b/terraform/modules/elixir-app/templates/cloud-init.yaml @@ -53,8 +53,8 @@ write_files: [Service] TimeoutStartSec=0 Restart=always - ExecStartPre=/usr/bin/docker pull otel/opentelemetry-collector-contrib:0.87.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.87.0 + ExecStartPre=/usr/bin/docker pull otel/opentelemetry-collector-contrib:0.90.1 + 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.90.1 ExecStop=/usr/bin/docker stop otel-collector ExecStopPost=/usr/bin/docker rm otel-collector diff --git a/terraform/modules/gateway-google-cloud-compute/templates/cloud-init.yaml b/terraform/modules/gateway-google-cloud-compute/templates/cloud-init.yaml index 9a4eca3ff..c5fc60f69 100644 --- a/terraform/modules/gateway-google-cloud-compute/templates/cloud-init.yaml +++ b/terraform/modules/gateway-google-cloud-compute/templates/cloud-init.yaml @@ -83,8 +83,8 @@ write_files: [Service] TimeoutStartSec=0 Restart=always - ExecStartPre=/usr/bin/docker pull otel/opentelemetry-collector-contrib:0.87.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.87.0 + ExecStartPre=/usr/bin/docker pull otel/opentelemetry-collector-contrib:0.90.1 + 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.90.1 ExecStop=/usr/bin/docker stop otel-collector ExecStopPost=/usr/bin/docker rm otel-collector @@ -157,6 +157,7 @@ runcmd: - sudo apt install docker-ce docker-ce-cli containerd.io -y - sudo usermod -aG docker $(whoami) - sudo systemctl enable docker + - sudo echo '{"ipv6":true,"fixed-cidr-v6":"fd00::/80"}' > /etc/docker/daemon.json - sudo systemctl start docker - sudo ip6tables-restore < /etc/iptables/rules.v6 - sudo systemctl daemon-reload diff --git a/terraform/modules/google-cloud-ops/main.tf b/terraform/modules/google-cloud-ops/main.tf index 36fac3437..265e9f1f7 100644 --- a/terraform/modules/google-cloud-ops/main.tf +++ b/terraform/modules/google-cloud-ops/main.tf @@ -304,7 +304,7 @@ resource "google_monitoring_alert_policy" "sql_disk_utiliziation_policy" { resource "google_monitoring_alert_policy" "genservers_crash_policy" { project = var.project_id - display_name = "GenServer Crashes" + display_name = "Errors in logs" combiner = "OR" notification_channels = local.notification_channels @@ -330,6 +330,45 @@ resource "google_monitoring_alert_policy" "genservers_crash_policy" { } } +resource "google_monitoring_alert_policy" "production_db_access_policy" { + project = var.project_id + + display_name = "Production DB Access" + combiner = "OR" + + notification_channels = [ + google_monitoring_notification_channel.slack.name + ] + + documentation { + content = "Somebody just accessed the production database, this notification incident will be automatically discarded in 1 hour." + mime_type = "text/markdown" + } + + conditions { + display_name = "Log match condition" + + condition_matched_log { + filter = <<-EOT + protoPayload.methodName="cloudsql.instances.connect" + protoPayload.authenticationInfo.principalEmail!="terraform-cloud@terraform-iam-387817.iam.gserviceaccount.com" + EOT + + label_extractors = { + "Email" = "EXTRACT(protoPayload.authenticationInfo.principalEmail)" + } + } + } + + alert_strategy { + auto_close = "3600s" + + notification_rate_limit { + period = "28800s" + } + } +} + resource "google_monitoring_alert_policy" "ssl_certs_expiring_policy" { project = var.project_id diff --git a/terraform/modules/google-cloud-sql/main.tf b/terraform/modules/google-cloud-sql/main.tf index d97864d04..71cac6c7a 100644 --- a/terraform/modules/google-cloud-sql/main.tf +++ b/terraform/modules/google-cloud-sql/main.tf @@ -120,6 +120,11 @@ resource "google_sql_database_instance" "master" { name = "maintenance_work_mem" value = floor(var.compute_instance_memory_size * 1024 / 100 * 5) } + + database_flags { + name = "cloudsql.iam_authentication" + value = "on" + } } lifecycle { diff --git a/terraform/modules/relay-app/templates/cloud-init.yaml b/terraform/modules/relay-app/templates/cloud-init.yaml index 0ea8ab50c..658ab7bfa 100644 --- a/terraform/modules/relay-app/templates/cloud-init.yaml +++ b/terraform/modules/relay-app/templates/cloud-init.yaml @@ -83,8 +83,8 @@ write_files: [Service] TimeoutStartSec=0 Restart=always - ExecStartPre=/usr/bin/docker pull otel/opentelemetry-collector-contrib:0.87.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.87.0 + ExecStartPre=/usr/bin/docker pull otel/opentelemetry-collector-contrib:0.90.1 + 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.90.1 ExecStop=/usr/bin/docker stop otel-collector ExecStopPost=/usr/bin/docker rm otel-collector