diff --git a/CHANGELOG.md b/CHANGELOG.md index 747f7cfe35d63..6505b3682d65d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ All notable changes to this project will be documented in this file. - Make TCP connections try IPv6 first with IPv4 fallback in CE plausible/analytics#4245 - `is` and `is not` filters in dashboard no longer support wildcards. Use contains/does not contain filter instead. - `bounce_rate` metric now returns 0 instead of null for event:page breakdown when page has never been entry page. +- Make `TOTP_VAULT_KEY` optional plausible/analytics#4317 ### Fixed diff --git a/config/runtime.exs b/config/runtime.exs index 170f39933d459..b25ba6970a327 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -153,19 +153,30 @@ end # Can be generated with `Base.encode64(:crypto.strong_rand_bytes(32))` from # iex shell or `openssl rand -base64 32` from command line. -totp_vault_key = get_var_from_path_or_env(config_dir, "TOTP_VAULT_KEY", nil) - -case totp_vault_key do - nil -> - raise "TOTP_VAULT_KEY configuration option is required. See https://github.com/plausible/community-edition/tree/v2.1.0?tab=readme-ov-file#quick-start" - - key -> - if byte_size(Base.decode64!(key)) != 32 do - raise "TOTP_VAULT_KEY must exactly 32 bytes long. See https://github.com/plausible/community-edition/tree/v2.1.0?tab=readme-ov-file#quick-start" +totp_vault_key = + if totp_vault_key_base64 = get_var_from_path_or_env(config_dir, "TOTP_VAULT_KEY") do + case Base.decode64(totp_vault_key_base64) do + {:ok, totp_vault_key} -> + if byte_size(totp_vault_key) == 32 do + totp_vault_key + else + raise ArgumentError, """ + TOTP_VAULT_KEY must be exactly 32 bytes long, e.g. Got #{byte_size(totp_vault_key)} bytes. + See https://github.com/plausible/community-edition/tree/v2.1.1#quick-start + """ + end + + :error -> + raise ArgumentError, """ + TOTP_VAULT_KEY must be Base64 encoded, e.g. `openssl rand -base64 32` + See https://github.com/plausible/community-edition/tree/v2.1.1#quick-start + """ end -end + else + Plug.Crypto.KeyGenerator.generate(secret_key_base, "totp", length: 32, iterations: 310_000) + end -### Mandatory params End +config :plausible, Plausible.Auth.TOTP, vault_key: totp_vault_key build_metadata_raw = get_var_from_path_or_env(config_dir, "BUILD_METADATA", "{}") @@ -197,8 +208,6 @@ runtime_metadata = [ config :plausible, :runtime_metadata, runtime_metadata -config :plausible, Plausible.Auth.TOTP, vault_key: totp_vault_key - sentry_dsn = get_var_from_path_or_env(config_dir, "SENTRY_DSN") honeycomb_api_key = get_var_from_path_or_env(config_dir, "HONEYCOMB_API_KEY") honeycomb_dataset = get_var_from_path_or_env(config_dir, "HONEYCOMB_DATASET") diff --git a/lib/plausible/application.ex b/lib/plausible/application.ex index 12ba22c31f0de..d94af97314db5 100644 --- a/lib/plausible/application.ex +++ b/lib/plausible/application.ex @@ -115,7 +115,6 @@ defmodule Plausible.Application do :plausible |> Application.fetch_env!(Plausible.Auth.TOTP) |> Keyword.fetch!(:vault_key) - |> Base.decode64!() end defp finch_pool_config() do diff --git a/test/plausible/config_test.exs b/test/plausible/config_test.exs index 0ccfe18046a03..5c455bf89d742 100644 --- a/test/plausible/config_test.exs +++ b/test/plausible/config_test.exs @@ -369,6 +369,68 @@ defmodule Plausible.ConfigTest do end end + describe "totp" do + test "pbkdf2 if not set" do + env = [ + {"TOTP_VAULT_KEY", nil} + ] + + config = runtime_config(env) + + assert [vault_key: vault_key] = get_in(config, [:plausible, Plausible.Auth.TOTP]) + assert byte_size(vault_key) == 32 + + assert vault_key == + <<11, 246, 172, 65, 214, 208, 149, 122, 53, 80, 12, 12, 15, 71, 109, 13, 83, 0, 26, + 77, 252, 92, 238, 26, 76, 25, 57, 105, 221, 96, 245, 127>> + end + + test "can be Base64-encoded 32 bytes (with padding)" do + # $ openssl rand -base64 32 + # dx2W6PNd/QIC6IyYVWMEaG2fI8/5WVylryM3mRaOpAo= + env = [ + {"TOTP_VAULT_KEY", "dx2W6PNd/QIC6IyYVWMEaG2fI8/5WVylryM3mRaOpAo="} + ] + + config = runtime_config(env) + + assert [vault_key: vault_key] = get_in(config, [:plausible, Plausible.Auth.TOTP]) + assert byte_size(vault_key) == 32 + assert vault_key == Base.decode64!("dx2W6PNd/QIC6IyYVWMEaG2fI8/5WVylryM3mRaOpAo=") + end + + test "fails on invalid key length" do + assert_raise ArgumentError, + ~r/TOTP_VAULT_KEY must be exactly 32 bytes long, e.g. Got 31 bytes./, + fn -> + runtime_config( + _env = [{"TOTP_VAULT_KEY", Base.encode64(:crypto.strong_rand_bytes(31))}] + ) + end + + assert_raise ArgumentError, + ~r/TOTP_VAULT_KEY must be exactly 32 bytes long, e.g. Got 33 bytes./, + fn -> + runtime_config( + _env = [{"TOTP_VAULT_KEY", Base.encode64(:crypto.strong_rand_bytes(33))}] + ) + end + end + + test "fails on invalid encoding" do + assert_raise ArgumentError, + ~r/TOTP_VAULT_KEY must be Base64 encoded/, + fn -> + runtime_config( + _env = [ + {"TOTP_VAULT_KEY", + "openssl" <> Base.encode64(:crypto.strong_rand_bytes(32))} + ] + ) + end + end + end + defp runtime_config(env) do put_system_env_undo(env) Config.Reader.read!("config/runtime.exs", env: :prod)