Fix keyring backend env override (#3423)

This commit is contained in:
Steven Enamakel
2026-06-06 05:00:28 -04:00
committed by GitHub
parent 2ea4540766
commit 7ef726c56a
4 changed files with 98 additions and 19 deletions
+1 -1
View File
@@ -191,7 +191,7 @@ fn run_sentry_test_command(args: &[String]) -> Result<()> {
/// 1. Variables already set in the process environment are **not** overwritten.
/// 2. If `OPENHUMAN_DOTENV_PATH` is set, that file is loaded.
/// 3. Otherwise, it searches for `.env` in the current working directory.
fn load_dotenv_for_cli() -> Result<()> {
pub(crate) fn load_dotenv_for_cli() -> Result<()> {
match std::env::var("OPENHUMAN_DOTENV_PATH") {
Ok(path) if !path.trim().is_empty() => {
dotenvy::from_path(&path).map_err(|e| {
+1
View File
@@ -26,6 +26,7 @@ pub use openhuman::memory_store::{MemoryClient, MemoryState};
///
/// Returns an error if command execution fails.
pub fn run_core_from_args(args: &[String]) -> anyhow::Result<()> {
core::cli::load_dotenv_for_cli()?;
openhuman::service::apply_startup_restart_delay_from_env();
openhuman::keyring::init_master_key();
core::cli::run_from_cli_args(args)
@@ -18,6 +18,7 @@ use parking_lot::Mutex;
use crate::openhuman::keyring::backend::KeyringBackend;
use crate::openhuman::keyring::crypto::{self, KEY_LEN};
use crate::openhuman::keyring::error::KeyringError;
use crate::openhuman::keyring::store::BackendKind;
const KEYCHAIN_SERVICE: &str = "openhuman";
const KEYCHAIN_MASTER_KEY_USERNAME: &str = "app:master_key";
@@ -42,8 +43,11 @@ pub fn init_master_key() {
crate::openhuman::keyring::init_workspace(&dir);
MASTER_KEY.get_or_init(|| {
if !is_staging_or_production() {
log::debug!("[keyring:encrypted_file] skipping master key init (dev environment)");
let backend_kind = crate::openhuman::keyring::store::effective_backend_kind();
if backend_kind != BackendKind::EncryptedFile {
log::debug!(
"[keyring:encrypted_file] skipping master key init backend={backend_kind:?}"
);
return None;
}
@@ -68,13 +72,6 @@ pub fn init_master_key() {
});
}
fn is_staging_or_production() -> bool {
matches!(
std::env::var("OPENHUMAN_APP_ENV").as_deref(),
Ok("staging") | Ok("production")
)
}
/// Returns `true` if the master key has been successfully loaded.
pub fn is_master_key_available() -> bool {
MASTER_KEY.get().and_then(|k| k.as_ref()).is_some()
+90 -9
View File
@@ -9,6 +9,13 @@ use std::sync::OnceLock;
use crate::openhuman::keyring::backend::{self, KeyringBackend};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum BackendKind {
Os,
File,
EncryptedFile,
}
// ── Global state ─────────────────────────────────────────────────────────────
/// The workspace directory provided by the caller at startup.
@@ -42,12 +49,12 @@ pub(super) fn backend() -> &'static dyn KeyringBackend {
pub(super) fn build_backend() -> Box<dyn KeyringBackend> {
// Priority 1: explicit env var override.
if let Ok(env_val) = std::env::var("OPENHUMAN_KEYRING_BACKEND") {
match env_val.trim() {
"os" => {
match backend_kind_from_env_value(&env_val) {
Some(BackendKind::Os) => {
log::info!("[keyring] backend=os (OPENHUMAN_KEYRING_BACKEND override)");
return Box::new(backend::OsBackend);
}
"file" => {
Some(BackendKind::File) => {
let path = workspace_dir_for_file_backend();
log::info!(
"[keyring] backend=file path={} (OPENHUMAN_KEYRING_BACKEND override)",
@@ -55,7 +62,7 @@ pub(super) fn build_backend() -> Box<dyn KeyringBackend> {
);
return Box::new(backend::FileBackend::new(&path));
}
"encrypted_file" => {
Some(BackendKind::EncryptedFile) => {
let path = workspace_dir_for_file_backend();
log::info!(
"[keyring] backend=encrypted_file path={} (OPENHUMAN_KEYRING_BACKEND override)",
@@ -65,9 +72,10 @@ pub(super) fn build_backend() -> Box<dyn KeyringBackend> {
&path,
));
}
other => {
None => {
log::warn!(
"[keyring] unknown OPENHUMAN_KEYRING_BACKEND={other:?}; falling through to defaults"
"[keyring] unknown OPENHUMAN_KEYRING_BACKEND={:?}; falling through to defaults",
env_val.trim()
);
}
}
@@ -98,12 +106,48 @@ pub(super) fn build_backend() -> Box<dyn KeyringBackend> {
}
fn is_staging_or_production() -> bool {
matches!(
std::env::var("OPENHUMAN_APP_ENV").as_deref(),
Ok("staging") | Ok("production")
is_staging_or_production_value(std::env::var("OPENHUMAN_APP_ENV").as_deref().ok())
}
pub(super) fn effective_backend_kind() -> BackendKind {
effective_backend_kind_for(
std::env::var("OPENHUMAN_APP_ENV").as_deref().ok(),
std::env::var("OPENHUMAN_KEYRING_BACKEND").as_deref().ok(),
cfg!(test),
)
}
fn effective_backend_kind_for(
app_env: Option<&str>,
backend_override: Option<&str>,
cfg_test: bool,
) -> BackendKind {
if let Some(kind) = backend_override.and_then(backend_kind_from_env_value) {
return kind;
}
if cfg_test {
return BackendKind::File;
}
if is_staging_or_production_value(app_env) {
BackendKind::EncryptedFile
} else {
BackendKind::File
}
}
fn backend_kind_from_env_value(value: &str) -> Option<BackendKind> {
match value.trim().to_ascii_lowercase().as_str() {
"os" => Some(BackendKind::Os),
"file" => Some(BackendKind::File),
"encrypted_file" => Some(BackendKind::EncryptedFile),
_ => None,
}
}
fn is_staging_or_production_value(app_env: Option<&str>) -> bool {
matches!(app_env.map(str::trim), Some("staging") | Some("production"))
}
/// Derive the directory for keyring files (`secrets.enc`, `dev-keychain.json`).
///
/// Uses the registered value from [`init_workspace`] if set; otherwise falls
@@ -127,3 +171,40 @@ pub fn workspace_dir_for_file_backend() -> PathBuf {
};
openhuman_dir
}
#[cfg(test)]
mod tests {
use super::{effective_backend_kind_for, BackendKind};
#[test]
fn explicit_file_backend_wins_over_staging_environment() {
assert_eq!(
effective_backend_kind_for(Some("staging"), Some("file"), false),
BackendKind::File
);
}
#[test]
fn explicit_encrypted_file_backend_wins_in_dev_environment() {
assert_eq!(
effective_backend_kind_for(Some("development"), Some("encrypted_file"), false),
BackendKind::EncryptedFile
);
}
#[test]
fn staging_defaults_to_encrypted_file_without_override() {
assert_eq!(
effective_backend_kind_for(Some(" staging "), None, false),
BackendKind::EncryptedFile
);
}
#[test]
fn unknown_backend_override_falls_back_to_environment_default() {
assert_eq!(
effective_backend_kind_for(Some("production"), Some("bogus"), false),
BackendKind::EncryptedFile
);
}
}