Add a credential to your tool's config¶
Embedding a CredentialRef gives operators all four configuration
routes — environment variable, OS keychain, literal, ecosystem fallback
— without you writing any of them.
Add the dependency¶
tokio is needed because the store methods are async and
KeyringStore requires a Tokio runtime specifically.
Embed the reference in your config struct¶
use rtb_credentials::CredentialRef;
#[derive(serde::Deserialize)]
struct Config {
anthropic: CredentialRef,
}
Your config struct can derive Deserialize but not Serialize once
it holds a CredentialRef. If you need to write config back, keep
credential fields in a separate struct from the one you serialise.
Resolve it where the secret is used¶
Resolve at the point of use rather than at startup. Nothing is cached, but resolving late keeps the secret out of memory until it is needed and means a credential that is never used never has to be present.
use rtb_credentials::{ExposeSecret, Resolver};
let resolver = Resolver::with_platform_default();
let key = resolver.resolve(&config.anthropic).await?;
let header = format!("Bearer {}", key.expose_secret());
with_platform_default() builds a resolver over the OS keychain. To
supply a different backend for the keychain leg, use
Resolver::new(Arc::new(my_store)).
What operators can now write¶
All four keys are optional, and any combination is valid:
# an environment variable this tool defines
anthropic:
env: MYTOOL_ANTHROPIC_KEY
# the OS keychain
anthropic:
keychain:
service: mytool
account: anthropic
# an ecosystem-wide variable, as a last resort
anthropic:
fallback_env: ANTHROPIC_API_KEY
Sources are tried in the order env, keychain, literal,
fallback_env, and the first hit wins.
Give the credential a fallback_env if a conventional variable exists¶
Setting fallback_env to an ecosystem name such as ANTHROPIC_API_KEY
means the tool works on a machine already configured for that provider,
with no config at all.
// in whatever supplies your defaults
CredentialRef {
fallback_env: Some("ANTHROPIC_API_KEY".to_string()),
..CredentialRef::default()
}
Because it is the last leg, it never overrides something the operator configured deliberately for your tool.
Handle the failure cases you have just created¶
use rtb_credentials::CredentialError;
match resolver.resolve(&config.anthropic).await {
Ok(secret) => { /* use it */ }
Err(CredentialError::NotFound { name }) => {
// nothing configured — name is a hint, not the full list of
// sources tried
}
Err(CredentialError::LiteralRefusedInCi) => {
// a literal is configured and CI=true; the fallback_env leg
// was NOT tried
}
Err(CredentialError::Keychain(msg)) => {
// the keychain backend failed; resolution stopped here and
// lower-precedence sources were not tried
}
Err(e) => return Err(e.into()),
}
CredentialError is #[non_exhaustive], so a wildcard arm is required.
Do not check whether the secret is non-empty by resolving it¶
A variable that is set but empty resolves successfully to an empty
SecretString, and stops the chain. If an empty credential is a problem
for your tool, check for it explicitly after resolving:
Nothing in the crate does this for you.