Why is it recommended to use a GenServer when reading the Cloak encryption key as an env var?

Why is it recommended (from the official Cloak doc) to do this:

# Assumes that you have a CLOAK_KEY environment variable containing a key in
# Base64 encoding.
#
# export CLOAK_KEY="A7x+qcFD9yeRfl3GohiOFZM5bNCdHNu27B0Ozv8X4dE="

defmodule MyApp.Vault do
  use Cloak.Vault, otp_app: :my_app

  @impl GenServer
  def init(config) do
    config =
      Keyword.put(config, :ciphers, [
        default: {
          Cloak.Ciphers.AES.GCM, 
          tag: "AES.GCM.V1", 
          key: decode_env!("CLOAK_KEY"),
          iv_length: 12
        }
      ])

    {:ok, config}
  end

  defp decode_env!(var) do
    var
    |> System.get_env()
    |> Base.decode64!()
  end
end

instead of this:

config :my_app, MyApp.Vault,
  ciphers: [
    default: {
      Cloak.Ciphers.AES.GCM, 
      tag: "AES.GCM.V1", 
      # Reading the encryption key from an env var directly in the config
      key: Base.decode64!(System.fetch_env!("CLOAK_ENCRYPTION_KEY")),
      # In AES.GCM, it is important to specify 12-byte IV length for
      # interoperability with other encryption software. See this GitHub
      # issue for more details:
      # https://github.com/danielberkompas/cloak/issues/93
      # 
      # In Cloak 2.0, this will be the default iv length for AES.GCM.
      iv_length: 12
    }
  ]

Reading the encryption key from an env var directly in the config allows me to very quickly have different configs (and different encrypt. keys) for different Mix.env/0.

I can embed the env var into the test.exs and dev.exs file doing like so:

System.put_env("CLOAK_ENCRYPTION_KEY", "some_encryption_key")

But it feels weird + I still don’t understand why the official doc suggests creating a GenServer.

Saying use Cloak.Vault already is making a GenServer, adding the init function is just customizing what happens at startup:

The main reason to do it there vs in prod.exs etc is timing: if you’re building a release, prod.exs gets evaluated at compile time when the release is built rather than when the released application boots.

Those instructions predate the introduction of runtime config in runtime.exs (in 1.11) by several years, which explains why they don’t suggest it.

Yet another way to handle this config would be to pass it along at the supervisor level, similar to how libraries like Finch do it, so instead of adding just MyApp.Vault you’d add {MyApp.Vault, [ciphers: ...etc...]}

2 Likes

Those instructions predate the introduction of runtime config in runtime.exs (in 1.11) by several years, which explains why they don’t suggest it.

Make complete sense. Many thanks for the reply!