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
:directory
option, 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_callback
option 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
:callback
option 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
:value
option specifies the initial value of the secret (default tonil
for in-memory secrets). Supersed the value from the file if the:directory
option has been set.👉 You can add in-memory secrets dynamically with
SecretAgent.put_secret/3
.
-
Configure and add
secret_agent
to 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
:name
option,SecretAgent
will be used by default.👉 By default,
secret_agent
trim watched secrets read on disk withString.trim/1
. You can deactivate this behavior with the optiontrim_secrets
set 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_agent
erases secrets when accessing them. You can override this behavior with the optionerase: false
. -
You can manually update secrets with
SecretAgent.put_secret/3
andSecretAgent.erase_secret/2
.