LemonCore.Secrets.MasterKey (lemon_core v0.1.0)

View Source

Master key resolution and initialization for encrypted secrets.

The key is looked up through a chain of LemonCore.Secrets.KeyProvider modules. The default chain preserves the historical order:

  1. macOS Keychain entry (macOS only)
  2. LEMON_SECRETS_MASTER_KEY environment variable
  3. ~/.lemon/secrets_master_key file

Both the chain and the locations it looks at are configurable:

config :lemon_core, LemonCore.Secrets,
  key_providers: [:env, :file],
  key_file: "/etc/lemon/master_key",
  env_var: "LEMON_SECRETS_MASTER_KEY"

Key material

A master key is 32 random bytes, stored base64-encoded — exactly what generate_encoded_key/0, mix lemon.secrets.init or openssl rand -base64 32 produce. Passphrase-like values are rejected with :weak_master_key because they are used as key material verbatim, without password stretching. Setups that already encrypted secrets under such a value can keep working by opting in explicitly:

config :lemon_core, LemonCore.Secrets, allow_legacy_raw_keys: true

which logs a deprecation warning on first use. Re-encrypting under a proper key is the real fix; see the "Key rotation" section of LemonCore.Secrets.

Summary

Types

source()

@type source() :: atom()

Functions

env_var(opts \\ [])

@spec env_var(keyword()) :: String.t()

generate_encoded_key()

@spec generate_encoded_key() :: String.t()

init(opts \\ [])

@spec init(keyword()) :: {:ok, map()} | {:error, atom() | tuple()}

Generates a master key and stores it with the first writable provider.

On macOS that is the Keychain; elsewhere the keychain provider reports itself unavailable and the key lands in the configured key file (0600). Pass target: :file (or any provider name) to force one, and force: true to replace an existing key file — note that doing so makes every secret already encrypted under the old key unreadable.

key_file(opts \\ [])

@spec key_file(keyword()) :: Path.t() | nil

resolve(opts \\ [])

@spec resolve(keyword()) :: {:ok, binary(), source()} | {:error, atom() | tuple()}

status(opts \\ [])

@spec status(keyword()) :: map()