lib/openagents/deployments/secret_resolver.ex

58e6347eeb72 · 1 KB

defmodule OpenAgents.Deployments.SecretResolver do
  @moduledoc """
  Resolves an environment's declared secret references into values, at execution
  time only.

  Durable records hold references. Values exist for the duration of one attempt,
  inside one `OpenAgents.Deployments.Execution`, and are handed only to the
  provider bound to that environment. Nothing writes a value to a run, an event,
  a receipt, or a log.

  A resolver may only resolve references the environment declares. That bound is
  what stops a provider or a tenant configuration change from reading a
  credential belonging to another environment.
  """

  alias OpenAgents.Deployments.Environment

  @callback resolve(Environment.t(), [String.t()]) ::
              {:ok, %{optional(String.t()) => String.t()}}
              | {:error, {:missing_secret_reference, String.t()}}

  @doc """
  Resolve the references the environment declares, refusing anything else.

  An undeclared reference is a programming error in a provider, so it is refused
  rather than resolved.
  """
  @spec resolve(Environment.t(), [String.t()]) ::
          {:ok, %{optional(String.t()) => String.t()}}
          | {:error,
             {:missing_secret_reference, String.t()} | {:undeclared_secret_reference, String.t()}}
  def resolve(%Environment{} = environment, references) when is_list(references) do
    declared = MapSet.new(environment.secret_references)

    case Enum.find(references, &(not MapSet.member?(declared, &1))) do
      nil -> impl().resolve(environment, references)
      reference -> {:error, {:undeclared_secret_reference, reference}}
    end
  end

  defp impl do
    Application.get_env(
      :openagents,
      :deployment_secret_resolver,
      OpenAgents.Deployments.SecretResolver.Environment
    )
  end
end