use std::{collections::HashMap, path::PathBuf};
use serde::{Deserialize, Serialize};
use zakura_rpc::config::mining::{default_miner_address, MinerAddressType};
use crate::components::With;
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 ZakuradConfig {
pub consensus: zakura_consensus::config::Config,
pub metrics: crate::components::metrics::Config,
pub network: zakura_network::config::Config,
pub state: zakura_state::config::Config,
pub tracing: crate::components::tracing::Config,
pub sync: crate::components::sync::Config,
pub mempool: crate::components::mempool::Config,
pub rpc: zakura_rpc::config::rpc::Config,
pub mining: zakura_rpc::config::mining::Config,
pub health: crate::components::health::Config,
pub zcashd_compat: crate::components::zcashd_compat::Config,
}
impl ZakuradConfig {
pub fn load(config_path: Option<PathBuf>) -> Result<Self, config::ConfigError> {
Self::load_with_env_prefixes(config_path, &["ZEBRA", "ZAKURA"])
}
pub fn load_with_env(
config_path: Option<PathBuf>,
env_prefix: &str,
) -> Result<Self, config::ConfigError> {
if let Some(suffix) = env_prefix.strip_prefix("ZAKURA") {
let legacy_prefix = format!("ZEBRA{suffix}");
Self::load_with_env_prefixes(config_path, &[&legacy_prefix, env_prefix])
} else {
Self::load_with_env_prefixes(config_path, &[env_prefix])
}
}
pub(crate) fn load_with_env_prefixes(
config_path: Option<PathBuf>,
env_prefixes: &[&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),
);
}
for env_prefix in env_prefixes {
let filtered_env = filtered_env_vars(env_prefix)?;
if env_prefix.starts_with("ZEBRA") && !filtered_env.is_empty() {
tracing::warn!(
"ZEBRA_* config environment variables are deprecated; use ZAKURA_* instead"
);
}
builder = builder.add_source(
config::Environment::default()
.separator("__")
.try_parsing(true)
.source(Some(filtered_env)),
);
}
let config = builder.build()?;
config.try_deserialize()
}
}
fn filtered_env_vars(env_prefix: &str) -> Result<HashMap<String, String>, config::ConfigError> {
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);
}
}
Ok(filtered_env)
}
impl With<MinerAddressType> for ZakuradConfig {
fn with(mut self, miner_address_type: MinerAddressType) -> Self {
self.mining.miner_address = Some(
default_miner_address(self.network.network.kind(), &miner_address_type)
.parse()
.expect("valid hard-coded address"),
);
self
}
}