use crate::runtime::security::SecurityConfig;
use std::sync::OnceLock;
pub(super) trait KeyProvider: Send + Sync {
fn signing_key_pem(&self) -> Result<String, String>;
}
pub(super) struct EnvKeyProvider<'a> {
security: &'a SecurityConfig,
}
impl<'a> EnvKeyProvider<'a> {
pub(super) fn new(security: &'a SecurityConfig) -> Self {
Self { security }
}
}
impl KeyProvider for EnvKeyProvider<'_> {
fn signing_key_pem(&self) -> Result<String, String> {
Ok(self.security.jwt_private_pem().unwrap_or_default())
}
}
pub(super) struct UnavailableKeyProvider {
reason: String,
}
impl UnavailableKeyProvider {
pub(super) fn new(reason: impl Into<String>) -> Self {
Self {
reason: reason.into(),
}
}
}
impl KeyProvider for UnavailableKeyProvider {
fn signing_key_pem(&self) -> Result<String, String> {
Err(self.reason.clone())
}
}
struct ProviderSettings {
kind: String,
}
fn provider_settings() -> &'static ProviderSettings {
static SETTINGS: OnceLock<ProviderSettings> = OnceLock::new();
SETTINGS.get_or_init(|| ProviderSettings {
kind: std::env::var("UDB_SIGNING_KEY_PROVIDER")
.ok()
.map(|value| value.trim().to_ascii_lowercase())
.filter(|value| !value.is_empty())
.unwrap_or_else(|| "env".to_string()),
})
}
pub(super) fn active_key_provider(security: &SecurityConfig) -> Box<dyn KeyProvider + '_> {
let settings = provider_settings();
match settings.kind.as_str() {
"env" => Box::new(EnvKeyProvider::new(security)),
"aws-kms" | "kms" => Box::new(UnavailableKeyProvider::new(
"UDB_SIGNING_KEY_PROVIDER=aws-kms is not built into this binary: the AWS \
KMS signing provider (aws-sdk-kms dependency + AwsKmsProvider impl) is a \
deferred extension point — see the KMS note in key_provider.rs. The \
signing-key seed never silently falls back to the env key.",
)),
other => Box::new(UnavailableKeyProvider::new(format!(
"unknown UDB_SIGNING_KEY_PROVIDER '{other}' (expected 'env' or 'aws-kms')"
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn security_with_private(key: Option<String>) -> SecurityConfig {
SecurityConfig {
jwt_private_key: key,
..Default::default()
}
}
#[test]
fn env_provider_returns_configured_key() {
let pem = "-----BEGIN PRIVATE KEY-----\nMIICONTENT\n-----END PRIVATE KEY-----";
let security = security_with_private(Some(pem.to_string()));
let provider = EnvKeyProvider::new(&security);
let provided = provider
.signing_key_pem()
.expect("env provider is infallible");
assert_eq!(provided, pem);
assert_eq!(provided, security.jwt_private_pem().unwrap_or_default());
}
#[test]
fn env_provider_absent_key_matches_prior_default() {
let security = security_with_private(None);
let provider = EnvKeyProvider::new(&security);
let provided = provider.signing_key_pem().expect("infallible");
assert_eq!(provided, String::new());
assert_eq!(provided, security.jwt_private_pem().unwrap_or_default());
}
#[test]
fn env_provider_empty_key_matches_prior_default() {
let security = security_with_private(Some(" ".to_string()));
let provider = EnvKeyProvider::new(&security);
let provided = provider.signing_key_pem().expect("infallible");
assert_eq!(provided, String::new());
assert_eq!(provided, security.jwt_private_pem().unwrap_or_default());
}
#[test]
fn unavailable_provider_surfaces_reason() {
let provider = UnavailableKeyProvider::new("boom");
assert_eq!(provider.signing_key_pem(), Err("boom".to_string()));
}
#[test]
fn selector_is_oncelock_stable() {
let first = provider_settings();
let second = provider_settings();
assert!(std::ptr::eq(first, second));
}
#[test]
fn active_provider_defaults_to_env() {
if provider_settings().kind != "env" {
return;
}
let pem = "-----BEGIN PRIVATE KEY-----\nMIICONTENT\n-----END PRIVATE KEY-----";
let security = security_with_private(Some(pem.to_string()));
let provider = active_key_provider(&security);
assert_eq!(provider.signing_key_pem().expect("env infallible"), pem);
}
}