use std::{collections::HashMap, path::PathBuf};
use serde::{Deserialize, Serialize};
const DENY_CONFIG_KEY_SUFFIX_LIST: [&str; 5] = [
"password",
"secret",
"token",
"cookie",
"private_key",
];
fn is_sensitive_leaf_key(leaf_key: &str) -> bool {
let key = leaf_key.to_ascii_lowercase();
DENY_CONFIG_KEY_SUFFIX_LIST
.iter()
.any(|deny_suffix| key.ends_with(deny_suffix))
}
#[derive(Clone, Default, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(deny_unknown_fields, default)]
pub struct ZebradConfig {
pub consensus: zebra_consensus::config::Config,
pub metrics: crate::components::metrics::Config,
pub network: zebra_network::config::Config,
pub state: zebra_state::config::Config,
pub tracing: crate::components::tracing::Config,
pub sync: crate::components::sync::Config,
pub mempool: crate::components::mempool::Config,
pub rpc: zebra_rpc::config::rpc::Config,
pub mining: zebra_rpc::config::mining::Config,
pub health: crate::components::health::Config,
}
impl ZebradConfig {
pub fn load(config_path: Option<PathBuf>) -> Result<Self, config::ConfigError> {
Self::load_with_env(config_path, "ZEBRA")
}
pub fn load_with_env(
config_path: Option<PathBuf>,
env_prefix: &str,
) -> Result<Self, config::ConfigError> {
let mut builder = config::Config::builder();
if let Some(path) = config_path {
builder = builder.add_source(
config::File::from(path)
.format(config::FileFormat::Toml)
.required(true),
);
}
let mut filtered_env: HashMap<String, String> = HashMap::new();
let required_prefix = format!("{}_", env_prefix);
for (key, value) in std::env::vars() {
if let Some(without_prefix) = key.strip_prefix(&required_prefix) {
let parts: Vec<&str> = without_prefix.split("__").collect();
if let Some(leaf) = parts.last() {
if is_sensitive_leaf_key(leaf) {
return Err(config::ConfigError::Message(format!(
"Environment variable '{}' contains sensitive key '{}' which cannot be overridden via environment variables. \
Use the configuration file instead to prevent process table exposure.",
key, leaf
)));
}
}
filtered_env.insert(without_prefix.to_string(), value);
}
}
builder = builder.add_source(
config::Environment::default()
.separator("__")
.try_parsing(true)
.source(Some(filtered_env)),
);
let config = builder.build()?;
config.try_deserialize()
}
}