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::SecretsProvider;
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,
}
}
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))),
};
tracing::debug!(source = %provider.describe(), "loading secrets from vault");
let document = match provider.fetch().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, "vault 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
}