An Elixir library to manage secrets, with the possibily to watch for their changes on filesystem.
Thereafter, watched secrets are the secrets read from the filesystem, while in-memory secrets are secrets which do not have a corresponding file.
As per the recommandation of the EEF Security Workgroup, secrets are passed around as closures.
def deps do
[
{:secret_agent, "~> 0.8"}
]
end-
Establish the list of initial secrets:
secrets = %{ "credentials" => [value: "super-secret"], "secret.txt" => [ directory: "path/to/secrets/directory", init_callback: fn wrapped_secret-> do_something_with_secret(wrapped_secret) end, callback: fn wrapped_secret-> do_something_with_secret(wrapped_secret) end ], "sub/path/secret.txt" => [ directory: "path/to/secrets/directory" ] }
ℹ️ When using the
:directoryoption, the name of the secret is the name of the file to watch in the directory. The secret will be loaded from the file upon startup. It this option is not set, the secret is considered to be an in-memory secret.ℹ️ The
:init_callbackoption specifies a callback that will be invoked the first time the watched secret is read from disk. Default to a function with no effect.ℹ️ The
:callbackoption specifies a callback that will be invoked each time the watched secret has been updated on disk. Default to a function with no effect.ℹ️ The
:valueoption specifies the initial value of the secret (default tonilfor in-memory secrets). Supersed the value from the file if the:directoryoption has been set.👉 You can add in-memory secrets dynamically with
SecretAgent.put_secret/3.
-
Configure and add
secret_agentto your supervision tree:children = [ {SecretAgent, [ name: :secrets, secret_agent_config: [secrets: secrets] ]} ] opts = [strategy: :one_for_one, name: MyApp.Supervisor] Supervisor.start_link(children, opts)
ℹ️ If you don't specify the
:nameoption,SecretAgentwill be used by default.👉 By default,
secret_agenttrim watched secrets read on disk withString.trim/1. You can deactivate this behavior with the optiontrim_secretsset tofalse. -
Whenever you want to retrieve a secret, use
SecretAgent.get_secret/2:{:ok, wrapped_credentials} = SecretAgent.get_secret(:secrets, "credentials") secret = wrapped_credentials.()
👉 As a best practice,
secret_agenterases secrets when accessing them. You can override this behavior with the optionerase: false. -
You can manually update secrets with
SecretAgent.put_secret/3andSecretAgent.erase_secret/2.