Deployment for the cloud version (#1638)

TODO:
- [x] Cluster formation for all API and web nodes
- [x] Injest Docker logs to Stackdriver
- [x] Fix assets building for prod

To finish later:
- [ ] Structured logging:
https://issuetracker.google.com/issues/285950891
- [ ] Better networking policy (eg. use public postmark ranges and deny
all unwanted egress)
- [ ] OpenTelemetry collector for Google Stackdriver
- [ ] LoggerJSON.Plug integration

---------

Signed-off-by: Andrew Dryga <andrew@dryga.com>
Co-authored-by: Jamil <jamilbk@users.noreply.github.com>
This commit is contained in:
Andrew Dryga
2023-06-06 15:03:26 -06:00
committed by GitHub
parent d27856a8f1
commit d9eb2d18df
87 changed files with 4616 additions and 427 deletions

View File

@@ -1,3 +1,4 @@
@import "@fontsource/source-sans-pro";
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
@import "tailwindcss/utilities";

View File

@@ -1,19 +1,5 @@
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
// to get started and then uncomment the line below.
// import "./user_socket.js"
// You can include dependencies in two ways.
//
// The simplest option is to put them in assets/vendor and
// import them using relative paths:
//
// import "../vendor/some-package.js"
//
// Alternatively, you can `npm install some-package --prefix assets` and import
// them using a path starting with the package name:
//
// import "some-package"
//
// IMPORTANT: DO NOT INCLUDE ANY CSS FILES HERE
// Otherwise, esbuild will also build app.css and override anything that tailwind generated.
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
import "phoenix_html"
@@ -21,14 +7,12 @@ import "phoenix_html"
// Flowbite's Phoenix LiveView integration
import "flowbite/dist/flowbite.phoenix.js"
// Custom fonts
import "@fontsource/source-sans-pro"
// Establish Phoenix Socket and LiveView configuration.
import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"
import topbar from "../vendor/topbar"
// Read CSRF token from the meta tag and use it in the LiveSocket params
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})

View File

@@ -3,6 +3,10 @@ defmodule Web.Application do
@impl true
def start(_type, _args) do
_ = OpentelemetryLiveView.setup()
_ = :opentelemetry_cowboy.setup()
_ = OpentelemetryPhoenix.setup(adapter: :cowboy2)
children = [
Web.Telemetry,
{Phoenix.PubSub, name: Web.PubSub},

View File

@@ -0,0 +1,9 @@
defmodule Web.HealthController do
use Web, :controller
def healthz(conn, _params) do
conn
|> put_resp_content_type("application/json")
|> send_resp(200, Jason.encode!(%{status: "ok"}))
end
end

View File

@@ -1,6 +1,11 @@
defmodule Web.Endpoint do
use Phoenix.Endpoint, otp_app: :web
plug Plug.RewriteOn, [:x_forwarded_host, :x_forwarded_port, :x_forwarded_proto]
plug Plug.MethodOverride
plug :put_hsts_header
plug Plug.Head
socket "/live", Phoenix.LiveView.Socket,
websocket: [
connect_info: [
@@ -46,14 +51,33 @@ defmodule Web.Endpoint do
pass: ["*/*"],
json_decoder: Phoenix.json_library()
plug Plug.MethodOverride
plug Plug.Head
# TODO: ensure that phoenix configured to resolve opts at runtime
plug Plug.Session, Web.Session.options()
# We wrap Plug.Session because it's options are resolved at compile-time,
# which doesn't work with Elixir releases and runtime configuration
plug :session
plug Web.Router
def put_hsts_header(conn, _opts) do
scheme =
config(:url, [])
|> Keyword.get(:scheme)
if scheme == "https" do
put_resp_header(
conn,
"strict-transport-security",
"max-age=63072000; includeSubDomains; preload"
)
else
conn
end
end
def session(conn, _opts) do
opts = Web.Session.options()
Plug.Session.call(conn, Plug.Session.init(opts))
end
def external_trusted_proxies do
Domain.Config.fetch_env!(:web, :external_trusted_proxies)
|> Enum.map(&to_string/1)

View File

@@ -15,7 +15,7 @@ defmodule Web.Router do
# TODO: auth
end
pipeline :browser_static do
pipeline :public do
plug :accepts, ["html", "xml"]
end
@@ -72,8 +72,10 @@ defmodule Web.Router do
end
scope "/browser", Web do
pipe_through :browser_static
pipe_through :public
get "/config.xml", BrowserController, :config
end
get "/healthz", Web.HealthController, :healthz
end

View File

@@ -2,9 +2,7 @@ defmodule Web.Session do
# 4 hours
@max_cookie_age 14_400
# The session will be stored in the cookie and signed,
# this means its contents can be read but not tampered with.
# Set :encryption_salt if you would also like to encrypt it.
# The session will be stored in the cookie signed and encrypted for 4 hours
@session_options [
store: :cookie,
key: "_firezone_key",

View File

@@ -18,7 +18,7 @@ defmodule Web.MixProject do
end
def version do
System.get_env("VERSION", "0.0.0+git.0.deadbeef")
System.get_env("APPLICATION_VERSION", "0.0.0+git.0.deadbeef")
end
def application do
@@ -47,7 +47,7 @@ defmodule Web.MixProject do
{:remote_ip, "~> 1.0"},
# Asset pipeline deps
{:esbuild, "~> 0.5", runtime: Mix.env() == :dev},
{:esbuild, "~> 0.7", runtime: Mix.env() == :dev},
{:tailwind, "~> 0.2.0", runtime: Mix.env() == :dev},
# Observability and debugging deps
@@ -60,6 +60,12 @@ defmodule Web.MixProject do
{:phoenix_swoosh, "~> 1.0"},
{:gen_smtp, "~> 1.0"},
# Observability
{:opentelemetry_cowboy, "~> 0.2.1"},
{:opentelemetry_liveview, "~> 1.0.0-rc.4"},
{:opentelemetry_phoenix, "~> 1.1"},
{:nimble_options, "~> 1.0", override: true},
# Other deps
{:jason, "~> 1.2"},
{:file_size, "~> 3.0.1"},
@@ -76,12 +82,12 @@ defmodule Web.MixProject do
[
setup: ["deps.get", "assets.setup", "assets.build"],
"assets.setup": [
"cmd cd assets && yarn install",
"cmd cd assets && yarn install --frozen-lockfile",
"tailwind.install --if-missing",
"esbuild.install --if-missing"
],
"assets.build": ["tailwind default", "esbuild default"],
"assets.deploy": ["tailwind default --minify", "esbuild default --minify", "phx.digest"],
"assets.build": ["tailwind web", "esbuild web"],
"assets.deploy": ["tailwind web --minify", "esbuild web --minify", "phx.digest"],
"ecto.seed": ["ecto.create", "ecto.migrate", "run ../domain/priv/repo/seeds.exs"],
"ecto.setup": ["ecto.create", "ecto.migrate"],
"ecto.reset": ["ecto.drop", "ecto.setup"],

View File

@@ -0,0 +1,10 @@
defmodule Web.HealthControllerTest do
use Web.ConnCase, async: true
describe "healthz/2" do
test "returns valid JSON health status", %{conn: conn} do
test_conn = get(conn, ~p"/healthz")
assert json_response(test_conn, 200) == %{"status" => "ok"}
end
end
end