From e78737c4c80b06ec389c1138a5094496ca976aec Mon Sep 17 00:00:00 2001 From: Brian Manifold Date: Wed, 7 Aug 2024 12:30:18 -0700 Subject: [PATCH] fix(portal): Refactor API URL config for Web app (#6202) Why: * The Swagger UI is currently served from the API application. This means that the Web application does not have access to the external URL in the API configuration during/after compilation. Without the API external URL, we cannot generate a proper link in the portal to the Swagger UI. This commit refactors how the API external URL is set from the environment variables and allows the Web app to have access to the value of the API URL. Co-authored-by: Jamil --- docker-compose.yml | 11 +++-- .../domain/lib/domain/config/definitions.ex | 22 +++++++-- .../apps/domain/test/domain/config_test.exs | 8 ++-- .../lib/web/live/settings/api_clients/beta.ex | 14 ++++-- .../web/live/settings/api_clients/index.ex | 3 +- elixir/config/config.exs | 3 ++ elixir/config/dev.exs | 3 ++ elixir/config/runtime.exs | 48 ++++++++++++------- terraform/environments/production/portal.tf | 33 ++++++------- terraform/environments/staging/portal.tf | 33 ++++++------- 10 files changed, 113 insertions(+), 65 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index cdca568bc..826f7a303 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -60,8 +60,10 @@ services: - 8080:8080/tcp environment: # Web Server - EXTERNAL_URL: http://localhost:8080/ + WEB_EXTERNAL_URL: http://localhost:8080/ + API_EXTERNAL_URL: http://localhost:8081/ PHOENIX_HTTP_WEB_PORT: "8080" + PHOENIX_HTTP_API_PORT: "8081" PHOENIX_SECURE_COOKIES: "false" # Erlang ERLANG_DISTRIBUTION_PORT: 9000 @@ -129,7 +131,9 @@ services: - 8081:8081/tcp environment: # Web Server - EXTERNAL_URL: http://localhost:8081/ + WEB_EXTERNAL_URL: http://localhost:8080/ + API_EXTERNAL_URL: http://localhost:8081/ + PHOENIX_HTTP_WEB_PORT: "8080" PHOENIX_HTTP_API_PORT: "8081" PHOENIX_SECURE_COOKIES: "false" # Erlang @@ -262,7 +266,8 @@ services: hostname: elixir environment: # Web Server - EXTERNAL_URL: http://localhost:8081/ + WEB_EXTERNAL_URL: http://localhost:8080/ + API_EXTERNAL_URL: http://localhost:8081/ # Erlang ERLANG_DISTRIBUTION_PORT: 9000 RELEASE_COOKIE: "NksuBhJFBhjHD1uUa9mDOHV" diff --git a/elixir/apps/domain/lib/domain/config/definitions.ex b/elixir/apps/domain/lib/domain/config/definitions.ex index e914b8330..a4043a33b 100644 --- a/elixir/apps/domain/lib/domain/config/definitions.ex +++ b/elixir/apps/domain/lib/domain/config/definitions.ex @@ -48,7 +48,8 @@ defmodule Domain.Config.Definitions do ]}, {"WebServer", [ - :external_url, + :web_external_url, + :api_external_url, :phoenix_secure_cookies, :phoenix_listen_address, :phoenix_http_web_port, @@ -149,11 +150,26 @@ defmodule Domain.Config.Definitions do ############################################## @doc """ - The external URL the UI/API will be accessible at. + The external URL the UI will be accessible at. If this field is not set or set to `nil`, the server for `api` and `web` apps will not start. """ - defconfig(:external_url, :string, + defconfig(:web_external_url, :string, + default: nil, + changeset: fn changeset, key -> + changeset + |> Domain.Repo.Changeset.validate_uri(key, require_trailing_slash: true) + |> Domain.Repo.Changeset.normalize_url(key) + end + ) + + @doc """ + The external URL the API will be accessible at. + + If this field is not set or set to `nil`, the server for `api` and `web` apps will not start. + """ + + defconfig(:api_external_url, :string, default: nil, changeset: fn changeset, key -> changeset diff --git a/elixir/apps/domain/test/domain/config_test.exs b/elixir/apps/domain/test/domain/config_test.exs index 68d1a7799..61f88c98b 100644 --- a/elixir/apps/domain/test/domain/config_test.exs +++ b/elixir/apps/domain/test/domain/config_test.exs @@ -171,10 +171,10 @@ defmodule Domain.ConfigTest do end test "raises an error when value is invalid", %{account: account} do - put_system_env_override(:external_url, "https://example.com/vpn") + put_system_env_override(:web_external_url, "https://example.com/vpn") message = """ - Invalid configuration for 'external_url' retrieved from environment variable EXTERNAL_URL. + Invalid configuration for 'web_external_url' retrieved from environment variable WEB_EXTERNAL_URL. Errors: @@ -182,7 +182,7 @@ defmodule Domain.ConfigTest do ## Documentation - The external URL the UI/API will be accessible at. + The external URL the UI will be accessible at. If this field is not set or set to `nil`, the server for `api` and `web` apps will not start. @@ -191,7 +191,7 @@ defmodule Domain.ConfigTest do """ assert_raise RuntimeError, message, fn -> - fetch_resolved_configs_with_sources!(account.id, [:external_url]) + fetch_resolved_configs_with_sources!(account.id, [:web_external_url]) end end end diff --git a/elixir/apps/web/lib/web/live/settings/api_clients/beta.ex b/elixir/apps/web/lib/web/live/settings/api_clients/beta.ex index b7a7af6a0..5392fe8cf 100644 --- a/elixir/apps/web/lib/web/live/settings/api_clients/beta.ex +++ b/elixir/apps/web/lib/web/live/settings/api_clients/beta.ex @@ -6,9 +6,12 @@ defmodule Web.Settings.ApiClients.Beta do {:ok, push_navigate(socket, to: ~p"/#{socket.assigns.account}/settings/api_clients")} else socket = - socket - |> assign(:page_title, "API Clients") - |> assign(:requested, false) + assign( + socket, + page_title: "API Clients", + requested: false, + api_url: Domain.Config.get_env(:web, :api_external_url) + ) {:ok, socket} end @@ -25,7 +28,10 @@ defmodule Web.Settings.ApiClients.Beta do <:title><%= @page_title %> <:help> API Clients are used to manage Firezone configuration through a REST API. See our - interactive API docs + <.link navigate={"#{@api_url}/swaggerui"} class={link_style()} target="_blank"> + OpenAPI-powered docs + + for more information. <:content> <.flash kind={:info}> diff --git a/elixir/apps/web/lib/web/live/settings/api_clients/index.ex b/elixir/apps/web/lib/web/live/settings/api_clients/index.ex index c974f476e..54aa8daf3 100644 --- a/elixir/apps/web/lib/web/live/settings/api_clients/index.ex +++ b/elixir/apps/web/lib/web/live/settings/api_clients/index.ex @@ -7,6 +7,7 @@ defmodule Web.Settings.ApiClients.Index do socket = socket |> assign(page_title: "API Clients") + |> assign(api_url: Domain.Config.get_env(:web, :api_external_url)) |> assign_live_table("actors", query_module: Actors.Actor.Query, sortable_fields: [ @@ -56,7 +57,7 @@ defmodule Web.Settings.ApiClients.Index do <:title><%= @page_title %> <:help> API Clients are used to manage Firezone configuration through a REST API. See our - <.link navigate="https://api.firezone.dev/swaggerui" class={link_style()}> + <.link navigate={"#{@api_url}/swaggerui"} class={link_style()} target="_blank"> OpenAPI-powered docs for more information. diff --git a/elixir/config/config.exs b/elixir/config/config.exs index 5ea7d1a31..50c4b8988 100644 --- a/elixir/config/config.exs +++ b/elixir/config/config.exs @@ -128,6 +128,9 @@ config :web, Web.Endpoint, signing_salt: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDejX" ] +config :web, + api_external_url: "http://localhost:13001" + config :web, cookie_secure: false, cookie_signing_salt: "WjllcThpb2Y=", diff --git a/elixir/config/dev.exs b/elixir/config/dev.exs index 1f55052a5..192a20c75 100644 --- a/elixir/config/dev.exs +++ b/elixir/config/dev.exs @@ -50,6 +50,9 @@ config :web, Web.Endpoint, reloadable_apps: [:domain, :web], server: true +config :web, + api_external_url: "http://localhost:13001" + root_path = __ENV__.file |> Path.dirname() diff --git a/elixir/config/runtime.exs b/elixir/config/runtime.exs index c977d7d22..3d2b88e6d 100644 --- a/elixir/config/runtime.exs +++ b/elixir/config/runtime.exs @@ -98,13 +98,13 @@ if config_env() == :prod do config :domain, Domain.Auth.Adapters.Okta.Jobs.SyncDirectory, enabled: compile_config!(:background_jobs_enabled) - if external_url = compile_config!(:external_url) do + if web_external_url = compile_config!(:web_external_url) do %{ - scheme: external_url_scheme, - host: external_url_host, - port: external_url_port, - path: external_url_path - } = URI.parse(external_url) + scheme: web_external_url_scheme, + host: web_external_url_host, + port: web_external_url_port, + path: web_external_url_path + } = URI.parse(web_external_url) ############################### ##### Web ##################### @@ -117,17 +117,17 @@ if config_env() == :prod do 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 + scheme: web_external_url_scheme, + host: web_external_url_host, + port: web_external_url_port, + path: web_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}" + "#{web_external_url_scheme}://#{web_external_url_host}:#{web_external_url_port}", + "#{web_external_url_scheme}://*.#{web_external_url_host}:#{web_external_url_port}", + "#{web_external_url_scheme}://#{web_external_url_host}", + "#{web_external_url_scheme}://*.#{web_external_url_host}" ], live_view: [ signing_salt: compile_config!(:live_view_signing_salt) @@ -143,6 +143,15 @@ if config_env() == :prod do cookie_encryption_salt: compile_config!(:cookie_encryption_salt) config :web, api_url_override: compile_config!(:api_url_override) + end + + if api_external_url = compile_config!(:api_external_url) do + %{ + scheme: api_external_url_scheme, + host: api_external_url_host, + port: api_external_url_port, + path: api_external_url_path + } = URI.parse(api_external_url) ############################### ##### API ##################### @@ -155,10 +164,10 @@ if config_env() == :prod do 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 + scheme: api_external_url_scheme, + host: api_external_url_host, + port: api_external_url_port, + path: api_external_url_path ], secret_key_base: compile_config!(:secret_key_base) @@ -170,6 +179,9 @@ if config_env() == :prod do config :api, external_trusted_proxies: compile_config!(:phoenix_external_trusted_proxies), private_clients: compile_config!(:phoenix_private_clients) + + config :web, + api_external_url: api_external_url end ############################### diff --git a/terraform/environments/production/portal.tf b/terraform/environments/production/portal.tf index 23c46fbf6..bd2fcf4c2 100644 --- a/terraform/environments/production/portal.tf +++ b/terraform/environments/production/portal.tf @@ -218,6 +218,23 @@ locals { } shared_application_environment_variables = [ + # Apps + { + name = "WEB_EXTERNAL_URL" + value = "https://app.${local.tld}" + }, + { + name = "API_EXTERNAL_URL" + value = "https://api.${local.tld}" + }, + { + name = "PHOENIX_HTTP_WEB_PORT" + value = "8080" + }, + { + name = "PHOENIX_HTTP_API_PORT" + value = "8080" + }, # Database { name = "DATABASE_HOST" @@ -519,14 +536,6 @@ module "web" { application_environment_variables = concat([ # Web Server - { - name = "EXTERNAL_URL" - value = "https://app.${local.tld}" - }, - { - name = "PHOENIX_HTTP_WEB_PORT" - value = "8080" - }, { name = "BACKGROUND_JOBS_ENABLED" value = "false" @@ -595,14 +604,6 @@ module "api" { application_environment_variables = concat([ # Web Server - { - name = "EXTERNAL_URL" - value = "https://api.${local.tld}" - }, - { - name = "PHOENIX_HTTP_API_PORT" - value = "8080" - }, { name = "BACKGROUND_JOBS_ENABLED" value = "false" diff --git a/terraform/environments/staging/portal.tf b/terraform/environments/staging/portal.tf index 830452af1..cef319c05 100644 --- a/terraform/environments/staging/portal.tf +++ b/terraform/environments/staging/portal.tf @@ -191,6 +191,23 @@ locals { } shared_application_environment_variables = [ + # Apps + { + name = "WEB_EXTERNAL_URL" + value = "https://app.${local.tld}" + }, + { + name = "API_EXTERNAL_URL" + value = "https://api.${local.tld}" + }, + { + name = "PHOENIX_HTTP_WEB_PORT" + value = "8080" + }, + { + name = "PHOENIX_HTTP_API_PORT" + value = "8080" + }, # Database { name = "DATABASE_HOST" @@ -498,14 +515,6 @@ module "web" { application_environment_variables = concat([ # Web Server - { - name = "EXTERNAL_URL" - value = "https://app.${local.tld}" - }, - { - name = "PHOENIX_HTTP_WEB_PORT" - value = "8080" - }, { name = "API_URL_OVERRIDE" value = "wss://api.${local.tld}" @@ -577,14 +586,6 @@ module "api" { application_environment_variables = concat([ # Web Server - { - name = "EXTERNAL_URL" - value = "https://api.${local.tld}" - }, - { - name = "PHOENIX_HTTP_API_PORT" - value = "8080" - }, { name = "BACKGROUND_JOBS_ENABLED" value = "false"