WardrikVault (wardrik_vault v0.1.0-rc.1)

Copy Markdown

Keeps credentials sealed in a WardrikStore, and gives them to WardrikCore agents' tools.

children = [
  {WardrikStore, store},
  {WardrikVault, name: MyApp.Vault, store: store, keys: [System.fetch_env!("VAULT_KEY")]}
]

WardrikCore.start_agent(
  agent_id: "digest",
  model: "anthropic:claude-haiku-4-5",
  principal: %WardrikCore.Principal{id: "user:jon"},
  credentials: {WardrikVault, MyApp.Vault}
)

The vault implements WardrikCore.Credentials. A tool gets only what the agent's principal holds.

Functions that take a vault raise ArgumentError if it isn't started.

Summary

Types

A secret someone put in, or a grant someone gave by authorizing.

t()

A vault, by the name your app gave it.

An option that holds a secret: the secret itself, or {:secret, name} for one of the deployment's.

Functions

Returns a specification to start this module under a supervisor.

Removes what principal holds as name. Returns :ok, also when there's nothing to remove.

Returns the principal that holds the deployment's own secrets.

Returns the value principal holds as name, unsealed.

Returns a new key, base64-encoded, for :keys.

Returns every credential as {principal_id, name, kind}. Needs no key.

Returns what principal holds as name, unsealed, with its kind and version.

Returns the names principal holds. Needs no key.

Seals value as name for principal. Returns {:ok, vsn}, or {:error, :no_key} without keys.

Returns the value principal holds as name, without a running vault.

Returns the secret an option holds, as value/2 does, without a running vault. See read/4.

Starts the vault.

Returns the secret an option holds.

Types

kind()

@type kind() :: :secret | :grant

A secret someone put in, or a grant someone gave by authorizing.

t()

@type t() :: atom()

A vault, by the name your app gave it.

value()

@type value() :: String.t() | {:secret, String.t()} | nil

An option that holds a secret: the secret itself, or {:secret, name} for one of the deployment's.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

delete(vault, principal, name)

@spec delete(t(), WardrikCore.Principal.t(), String.t()) :: :ok | {:error, term()}

Removes what principal holds as name. Returns :ok, also when there's nothing to remove.

deployment()

@spec deployment() :: WardrikCore.Principal.t()

Returns the principal that holds the deployment's own secrets.

These are your app's keys, such as a model provider's, and the secrets options name with {:secret, name}. No agent works for this principal.

{:ok, _vsn} = WardrikVault.put(MyApp.Vault, WardrikVault.deployment(), "SLACK_SIGNING_SECRET", secret)

fetch(vault, principal, name)

@spec fetch(t(), WardrikCore.Principal.t(), String.t()) ::
  {:ok, binary()} | {:error, term()}

Returns the value principal holds as name, unsealed.

WardrikVault.fetch(MyApp.Vault, principal, "github")
#=> {:ok, "ghp_..."}

Returns {:error, :not_found} if there's none, and {:error, :no_key} without keys.

generate_key()

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

Returns a new key, base64-encoded, for :keys.

list(vault)

@spec list(t()) :: [{String.t(), String.t(), kind()}]

Returns every credential as {principal_id, name, kind}. Needs no key.

WardrikVault.list(MyApp.Vault)
#=> [{"user:jon", "github", :secret}]

Names aren't sealed. Anyone who can read the store can see who holds a credential of which name.

lookup(vault, principal, name)

@spec lookup(t(), WardrikCore.Principal.t(), String.t()) ::
  {:ok, kind(), binary(), WardrikStore.vsn()}
  | {:error, term()}
  | {:error, kind(), term()}

Returns what principal holds as name, unsealed, with its kind and version.

WardrikVault.lookup(MyApp.Vault, principal, "github")
#=> {:ok, :secret, "ghp_...", vsn}

Returns {:error, kind, reason} if the value is there but won't unseal.

names(vault, principal)

@spec names(t(), WardrikCore.Principal.t()) :: [String.t()]

Returns the names principal holds. Needs no key.

put(vault, principal, name, value, opts \\ [])

@spec put(t(), WardrikCore.Principal.t(), String.t(), binary(),
  kind: kind(),
  if_vsn: WardrikStore.vsn()
) :: {:ok, WardrikStore.vsn()} | {:error, term()}

Seals value as name for principal. Returns {:ok, vsn}, or {:error, :no_key} without keys.

{:ok, vsn} = WardrikVault.put(MyApp.Vault, principal, "github", "ghp_...")

Options

  • :kind - :secret or :grant. :secret unless set.
  • :if_vsn - writes only over this version. See WardrikStore.put/4.

read(store, keys, principal, name)

@spec read(WardrikStore.t(), [String.t()], WardrikCore.Principal.t(), String.t()) ::
  {:ok, binary()} | {:error, term()}

Returns the value principal holds as name, without a running vault.

Use it to read a secret before the vault starts, such as while reading config. store and keys are what the vault is started with.

WardrikVault.read(store, [System.fetch_env!("VAULT_KEY")], principal, "github")
#=> {:ok, "ghp_..."}

read_value(store, keys, value)

@spec read_value(WardrikStore.t(), [String.t()], value()) ::
  {:ok, String.t() | nil} | {:error, term()}

Returns the secret an option holds, as value/2 does, without a running vault. See read/4.

start_link(opts)

@spec start_link(keyword()) :: GenServer.on_start()

Starts the vault.

WardrikVault.start_link(
  name: MyApp.Vault,
  store: store,
  keys: [System.fetch_env!("VAULT_KEY"), System.fetch_env!("OLD_VAULT_KEY")]
)

Values are sealed with AES-256-GCM. The first key seals. Any key unseals. To rotate, put a new key first and keep the old one until nothing needs it.

Options

  • :name - required. What the vault is reached by.
  • :store - required. The WardrikStore to keep credentials in.
  • :keys - base64 keys from generate_key/0. [] unless set. Without keys, nothing is sealed or unsealed. A key that isn't 32 bytes is dropped with a warning.
  • :grants - {module, opts}, where module implements WardrikVault.Grants. nil unless set.

value(vault, value)

@spec value(t(), value()) :: {:ok, String.t() | nil} | {:error, term()}

Returns the secret an option holds.

WardrikVault.value(MyApp.Vault, {:secret, "SLACK_SIGNING_SECRET"})
#=> {:ok, "8f14..."}

WardrikVault.value(MyApp.Vault, "8f14...")
#=> {:ok, "8f14..."}

{:secret, name} reads the deployment's secret on each call, so a new one takes effect at once. A string or nil is returned as it is.