Secret Providers¶
A secret provider connects the imbi-api secrets broker to an external secret system. It is not a capability: an organization configures a provider in Admin, Secrets, not in an Integration, and a provider has no Integration credentials.
A plugin declares its provider classes in
PluginManifest.secret_providers:
imbi-api creates one instance of each class when it first needs the
provider type. Two plugins cannot declare the same type: the second one
is ignored, and imbi-api logs an error.
| Plugin | Type | Issues |
|---|---|---|
| AWS | aws_secrets_manager |
Static and rotated secrets from the organization's account |
| GitHub | github_app |
GitHub App installation tokens, one for each resolution |
The contract¶
Subclass SecretProvider (imbi.common.plugins.secrets) and set three
class attributes:
type— the provider type that the provider rows storelabel— the name that the UI showscapabilities— theSecretCapabilityvalues that the provider has. The broker refuses a binding whose credential class needs a capability that the provider does not have
Every provider must override validate_config and validate_locator:
the base methods raise NotImplementedError. Then override health,
validate_binding, resolve, renew, and revoke for the capabilities
that the provider has. The base methods raise BrokerError
(operation_not_supported, provider_lease_not_renewable, or
provider_lease_not_revocable). The base check_config and
check_locator do nothing: override them only when the provider must
check a configuration or a locator against the external system.
Two optional methods are for providers that issue repository tokens:
repository(config, url)— return(host, owner/name)for the repository link of a project. The agent runner clones that repository. The default returnsNone.check_key_host(host)— validate the host of a provider key (see below). The default raisesValueError.
Rules¶
- Put secret material only in
Material. It has no readablerepr, and it cannot be pickled or serialized. - Translate every error of the external system into a
BrokerErrorwith a safe reason code. Never put a provider response, a header, or a value in an error or a log. - Use
validate_modelto validate a configuration or a locator. Its error names the fields only, never the input values.
The host¶
The constructor gets a SecretProviderHost, which gives the broker
services to the provider:
graph()— the graph of imbi-apiprovider_key(...)— read a provider key: a binding that only imbi-api reads, for example the private key of a GitHub App. No grant gives a provider key to a task.check_provider_key(...)— check that a binding is a provider key for a host and App id, without reading it
API reference¶
SecretProvider ¶
The provider contract.
Subclasses set :attr:type, :attr:label, and :attr:capabilities,
and override the methods of the capabilities they have. The broker
creates one instance for each type, with its :class:SecretProviderHost.
Source code in libraries/common/src/imbi/common/plugins/secrets.py
check_config
async
¶
Check a validated configuration against the provider.
Called when a provider is created and when its configuration changes.
Raises:
| Type | Description |
|---|---|
InvalidConfiguration
|
The configuration is not safe to use. |
Source code in libraries/common/src/imbi/common/plugins/secrets.py
check_key_host ¶
Return the normalized host of a provider key that this
provider reads (provider_key_usage.host).
Raises:
| Type | Description |
|---|---|
ValueError
|
The host is not allowed, or the provider reads no provider keys (the default). |
Source code in libraries/common/src/imbi/common/plugins/secrets.py
check_locator
async
¶
Check a validated locator against other bindings.
Called when a binding is written, and for each binding of a provider when its configuration changes.
Raises:
| Type | Description |
|---|---|
InvalidConfiguration
|
The locator is not safe to use. |
Source code in libraries/common/src/imbi/common/plugins/secrets.py
repository ¶
Return (host, owner/name) of the repository at url.
For a provider that issues repository tokens: the agent runner
clones the repository and asks the provider for a token on
that host. None: url is not a repository on the host of
this configuration (the default).
Source code in libraries/common/src/imbi/common/plugins/secrets.py
validate_binding
async
¶
validate_binding(
config: dict[str, Any],
locator: dict[str, Any],
organization_id: str,
binding_id: UUID | None = None,
) -> dict[str, typing.Any]
Return metadata of the bound secret, never its value.
Source code in libraries/common/src/imbi/common/plugins/secrets.py
validate_config ¶
Return the normalized configuration.
Raises:
| Type | Description |
|---|---|
InvalidConfiguration
|
The configuration is not valid. |
Source code in libraries/common/src/imbi/common/plugins/secrets.py
validate_locator ¶
validate_locator(
locator: dict[str, Any],
credential_class: CredentialClass,
) -> dict[str, typing.Any]
Return the normalized binding locator. No provider call.
Raises:
| Type | Description |
|---|---|
InvalidConfiguration
|
The locator is not valid. |
Source code in libraries/common/src/imbi/common/plugins/secrets.py
SecretProviderHost ¶
Bases: Protocol
The broker services that imbi-api gives each provider.
check_provider_key
async
¶
Check that a binding is a provider key for host and
app_id, without reading it.
Raises:
| Type | Description |
|---|---|
BrokerError
|
The binding is not such a provider key. |
Source code in libraries/common/src/imbi/common/plugins/secrets.py
graph ¶
Return the graph of imbi-api.
Raises:
| Type | Description |
|---|---|
BrokerError
|
|
provider_key
async
¶
provider_key(
organization_id: str,
reference: str,
*,
consumer: UUID | None,
host: str,
app_id: str,
task_id: UUID | None = None,
) -> Material
Return the material of a provider key for one consumer.
A provider key is a binding that only imbi-api reads, for
example the private key of a GitHub App. Its
provider_key_usage names the host, the app_id, and
the consumer bindings that may use it. No grant gives it to a
task.
Raises:
| Type | Description |
|---|---|
BrokerError
|
The binding is not a provider key for this consumer, host, and App id, or its provider failed. |
Source code in libraries/common/src/imbi/common/plugins/secrets.py
vendor_token
async
¶
vendor_token(
organization_id: str,
exchanger: TokenExchanger,
target: dict[str, Any],
*,
agent_id: str,
) -> identity.AccessToken
Mint a vendor access token for an agent (WIF plan 4.4).
Core gets a new identity JWT from the organization's identity
source and sets every claim. target must come from the
binding of the agent, never from a request.
Raises:
| Type | Description |
|---|---|
BrokerError
|
Core or the vendor refused:
|
Source code in libraries/common/src/imbi/common/plugins/secrets.py
Material ¶
Material(
value: bytes,
*,
binary: bool = False,
bearer: bool = False,
credential_expires_at: datetime | None = None,
provider_version: str | None = None,
lease_id: str | None = None,
renewable: bool = False,
revocable: bool = False,
)
Secret material from a provider.
No readable repr, no pickling, and no dataclass or pydantic
serialization, so the value cannot reach a log or a stored row by
accident.
Source code in libraries/common/src/imbi/common/plugins/secrets.py
ResolveContext
dataclass
¶
ResolveContext(
organization_id: str,
task_id: UUID,
project_id: str | None,
purpose: str,
ttl_seconds: int,
credential_class: CredentialClass,
binding_id: UUID | None = None,
repository: str | None = None,
agent_id: str | None = None,
)
What a provider may know about the request. No handle.
BrokerError ¶
Bases: Exception
A broker failure with a code from :data:ERROR_STATUS.
reason is a safe reason code for audit events. Neither holds
provider text or secret material.