use systemprompt_manifest::profile::VaultSecretsConfig;
use systemprompt_manifest::secrets::{OAUTH_AT_REST_PEPPER_MIN_LENGTH, Secrets};
use super::SecretsBootstrapError;
use super::logging::log_secrets_failure;
use super::provider::DynSecretsProvider;
use super::registry::{build_external_provider, process_env_lookup};
use super::resolve::{ResolvedSource, resolve_source};
use super::vault::VaultKvProvider;
use crate::bootstrap::profile::ProfileBootstrap;
use crate::bootstrap::secrets::sources::{env, file};
use crate::error::{ConfigError, ConfigResult};
pub(super) async fn load_from_profile_config() -> ConfigResult<Secrets> {
let is_deployment_host =
systemprompt_models::subprocess::is_deployment_host(|name| std::env::var(name).ok());
let is_subprocess = std::env::var("SYSTEMPROMPT_SUBPROCESS").is_ok();
let has_valid_pepper_in_env = std::env::var("OAUTH_AT_REST_PEPPER")
.is_ok_and(|pepper| pepper.len() >= OAUTH_AT_REST_PEPPER_MIN_LENGTH);
let secrets_config = match ProfileBootstrap::get() {
Ok(profile) => profile.secrets.as_ref(),
Err(_e) if (is_subprocess || is_deployment_host) && has_valid_pepper_in_env => None,
Err(_e) => return Err(SecretsBootstrapError::ProfileNotInitialized.into()),
};
let resolved = resolve_source(
secrets_config,
is_subprocess,
is_deployment_host,
has_valid_pepper_in_env,
)?;
match resolved {
ResolvedSource::SubprocessEnv | ResolvedSource::DeploymentHostEnv => {
tracing::debug!("Loading secrets from environment");
env::load_from_env()
},
ResolvedSource::LocalEnvWithFileFallback(path) => {
tracing::debug!("Profile source is 'env' but running locally, trying file first");
match file::resolve_and_load_file(path) {
Err(ConfigError::Secrets(SecretsBootstrapError::FileNotFound { path })) => {
tracing::debug!(path, "No secrets file; falling back to environment");
env::load_from_env()
},
other => other,
}
},
ResolvedSource::File(path) => {
tracing::debug!("Loading secrets from file (profile source: file)");
file::resolve_and_load_file(path).inspect_err(log_secrets_failure)
},
ResolvedSource::Vault(cfg) => load_from_vault(cfg).await,
ResolvedSource::External(cfg) => {
let provider = match build_external_provider(cfg, process_env_lookup()) {
Ok(provider) => provider,
Err(e) => return Err(fail_closed(e)),
};
load_from_provider(provider.as_ref(), "external").await
},
}
}
async fn load_from_vault(cfg: &VaultSecretsConfig) -> ConfigResult<Secrets> {
let provider = match VaultKvProvider::from_config(cfg, |name| std::env::var(name).ok()) {
Ok(provider) => provider,
Err(e) => return Err(fail_closed(SecretsBootstrapError::from(e))),
};
load_from_provider(&provider, "vault").await
}
async fn load_from_provider(
provider: &dyn DynSecretsProvider,
source_name: &str,
) -> ConfigResult<Secrets> {
tracing::debug!(source = %provider.describe(), source_name, "loading secrets from provider");
let document = match provider.fetch_boxed().await {
Ok(document) => document,
Err(e) => return Err(fail_closed(e)),
};
let key_names = document.key_names();
match document.into_secrets() {
Ok(secrets) => {
tracing::debug!(keys = ?key_names, source_name, "secrets document parsed");
Ok(secrets)
},
Err(e) => Err(fail_closed(e)),
}
}
fn fail_closed(e: SecretsBootstrapError) -> ConfigError {
let error = ConfigError::from(e);
log_secrets_failure(&error);
error
}