use std::collections::{HashMap, HashSet};
use std::sync::RwLock;
use kdl::KdlDocument;
use thiserror::Error;
static CONFIG: RwLock<Option<SecurityConfig>> = RwLock::new(None);
fn lock_config() -> Result<std::sync::RwLockWriteGuard<'static, Option<SecurityConfig>>, SecurityError> {
CONFIG
.write()
.map_err(|_| SecurityError::Invalid("security config lock poisoned".into()))
}
fn loaded_config() -> Result<SecurityConfig, SecurityError> {
let guard = CONFIG
.read()
.map_err(|_| SecurityError::Invalid("security config lock poisoned".into()))?;
guard
.clone()
.ok_or(SecurityError::Invalid("security config not initialized".into()))
}
pub const BUS_MODULE_NAMES: &[&str] = &[
"filter",
"telemetry",
"tls",
"webtransport",
"aetherdb",
"aether-runtime",
"kwt-replay",
"image-trust",
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SecurityConfig {
pub bus_token: Option<String>,
pub bus_scoped_tokens: HashMap<String, HashSet<String>>,
pub admin_token: Option<String>,
pub require_admin_token_for_metrics: bool,
pub require_bus_token_for_wt_control: bool,
pub require_kwt_jti_replay: bool,
pub require_wt_cert_allowlists: bool,
pub require_wt_replicate_key: bool,
pub lock_wt_aether_hot_reload: bool,
}
#[derive(Debug, Error)]
pub enum SecurityError {
#[error("read {path}: {source}")]
Io {
path: String,
source: std::io::Error,
},
#[error("security KDL parse: {0}")]
Parse(String),
#[error("security KDL: {0}")]
Invalid(String),
#[error("security policy requires admin_token (set security.admin_token in KDL, NAUTALID_ADMIN_TOKEN, or NAUTALID_ADMIN_TOKEN_FILE)")]
MissingAdminToken,
#[error("security policy requires bus_token for WT control (set security.bus_token in KDL, NAUTALID_BUS_TOKEN, or NAUTALID_BUS_TOKEN_FILE)")]
MissingBusToken,
#[error("WT route `{route}` requires a client certificate SHA-256 allowlist (set NAUTALID_WEBTRANSPORT_AETHER_CERT_SHA256_* )")]
MissingWtCertAllowlist { route: String },
#[error("security policy requires replicate key for WT replicate route (set AETHERDB_HOST_REPLICATE_KEY_HEX or AETHERDB_HUB_REPLICATE_KEY_HEX)")]
MissingReplicateKey,
}
impl SecurityConfig {
pub fn load() -> Result<Self, SecurityError> {
let kdl = KdlSecurityOverrides::from_env()?;
let mut bus_scoped_tokens = kdl.bus_scoped_tokens.clone();
merge_bus_scopes_from_env(&mut bus_scoped_tokens);
let cfg = Self {
bus_token: resolve_secret(
"NAUTALID_BUS_TOKEN",
"NAUTALID_BUS_TOKEN_FILE",
kdl.bus_token.as_deref(),
)?,
bus_scoped_tokens,
admin_token: resolve_secret(
"NAUTALID_ADMIN_TOKEN",
"NAUTALID_ADMIN_TOKEN_FILE",
kdl.admin_token.as_deref(),
)?,
require_admin_token_for_metrics: kdl
.require_admin_token_for_metrics
.unwrap_or_else(require_admin_token_for_metrics_from_env),
require_bus_token_for_wt_control: kdl
.require_bus_token_for_wt_control
.unwrap_or_else(require_bus_token_for_wt_control_from_env),
require_kwt_jti_replay: kdl
.require_kwt_jti_replay
.unwrap_or_else(require_kwt_jti_replay_from_env),
require_wt_cert_allowlists: kdl
.require_wt_cert_allowlists
.unwrap_or_else(require_wt_cert_allowlists_from_env),
require_wt_replicate_key: kdl
.require_wt_replicate_key
.unwrap_or_else(require_wt_replicate_key_from_env),
lock_wt_aether_hot_reload: kdl
.lock_wt_aether_hot_reload
.unwrap_or_else(lock_wt_aether_hot_reload_from_env),
};
if cfg.require_admin_token_for_metrics && cfg.admin_token.is_none() {
return Err(SecurityError::MissingAdminToken);
}
Ok(cfg)
}
pub fn validate_wt_aether(
&self,
wt_control_route_enabled: bool,
wt: &crate::wt_aether::WtAetherConfig,
) -> Result<(), SecurityError> {
if self.require_bus_token_for_wt_control
&& wt_control_route_enabled
&& self.bus_token.is_none()
{
return Err(SecurityError::MissingBusToken);
}
if self.require_wt_replicate_key
&& wt.enabled
&& wt.allow_replicate_route
&& wt.routes.contains(&crate::wt_aether::WtAetherRoute::Replicate)
&& !replicate_key_configured()
{
return Err(SecurityError::MissingReplicateKey);
}
if !self.require_wt_cert_allowlists || !wt.enabled || !wt.restrict_client_certs {
return Ok(());
}
for route in [
crate::wt_aether::WtAetherRoute::Command,
crate::wt_aether::WtAetherRoute::Dma,
crate::wt_aether::WtAetherRoute::Control,
crate::wt_aether::WtAetherRoute::Replicate,
] {
if !wt.routes.contains(&route) {
continue;
}
let needs_allowlist = (wt.require_mtls_for_privileged
&& matches!(
route,
crate::wt_aether::WtAetherRoute::Control
| crate::wt_aether::WtAetherRoute::Replicate
))
|| (wt.require_mtls_for_data
&& matches!(
route,
crate::wt_aether::WtAetherRoute::Command | crate::wt_aether::WtAetherRoute::Dma
));
if needs_allowlist && wt.effective_cert_allowlist(route).is_empty() {
return Err(SecurityError::MissingWtCertAllowlist {
route: wt_route_label(route).into(),
});
}
}
Ok(())
}
}
fn wt_route_label(route: crate::wt_aether::WtAetherRoute) -> &'static str {
match route {
crate::wt_aether::WtAetherRoute::Command => "command",
crate::wt_aether::WtAetherRoute::Dma => "dma",
crate::wt_aether::WtAetherRoute::Control => "control",
crate::wt_aether::WtAetherRoute::Replicate => "replicate",
}
}
#[doc(hidden)]
pub fn reset_for_tests() {
if let Ok(mut guard) = CONFIG.write() {
*guard = None;
}
}
pub fn init() -> Result<(), SecurityError> {
let cfg = SecurityConfig::load()?;
let mut guard = lock_config()?;
if let Some(existing) = guard.as_ref() {
if existing != &cfg {
return Err(SecurityError::Invalid(
"security config already initialized with different policy".into(),
));
}
return Ok(());
}
*guard = Some(cfg);
Ok(())
}
#[must_use]
pub fn bus_token() -> Option<String> {
if let Ok(guard) = CONFIG.read() {
if let Some(cfg) = guard.as_ref() {
if let Some(token) = cfg.bus_token.clone() {
return Some(token);
}
}
}
resolve_secret("NAUTALID_BUS_TOKEN", "NAUTALID_BUS_TOKEN_FILE", None).ok().flatten()
}
#[must_use]
pub fn admin_token() -> Option<String> {
if let Ok(guard) = CONFIG.read() {
if let Some(cfg) = guard.as_ref() {
if let Some(token) = cfg.admin_token.clone() {
return Some(token);
}
}
}
resolve_secret("NAUTALID_ADMIN_TOKEN", "NAUTALID_ADMIN_TOKEN_FILE", None).ok().flatten()
}
#[must_use]
pub fn metrics_auth_required() -> bool {
if let Ok(guard) = CONFIG.read() {
if let Some(cfg) = guard.as_ref() {
return cfg.require_admin_token_for_metrics || cfg.admin_token.is_some();
}
}
let admin = admin_token();
require_admin_token_for_metrics_from_env() || admin.is_some()
}
#[must_use]
pub fn wt_control_token_required() -> bool {
if let Ok(guard) = CONFIG.read() {
if let Some(cfg) = guard.as_ref() {
return cfg.require_bus_token_for_wt_control;
}
}
require_bus_token_for_wt_control_from_env()
}
#[must_use]
pub fn bus_scoped_tokens() -> HashMap<String, HashSet<String>> {
if let Ok(guard) = CONFIG.read() {
if let Some(cfg) = guard.as_ref() {
if !cfg.bus_scoped_tokens.is_empty() {
return cfg.bus_scoped_tokens.clone();
}
}
}
let mut scopes = HashMap::new();
merge_bus_scopes_from_env(&mut scopes);
scopes
}
#[must_use]
pub fn bus_credentials_configured() -> bool {
bus_token().is_some() || !bus_scoped_tokens().is_empty()
}
#[must_use]
pub fn kwt_jti_replay_required() -> bool {
if let Ok(guard) = CONFIG.read() {
if let Some(cfg) = guard.as_ref() {
return cfg.require_kwt_jti_replay;
}
}
require_kwt_jti_replay_from_env()
}
#[must_use]
pub fn wt_aether_hot_reload_locked() -> bool {
if let Ok(guard) = CONFIG.read() {
if let Some(cfg) = guard.as_ref() {
return cfg.lock_wt_aether_hot_reload;
}
}
lock_wt_aether_hot_reload_from_env()
}
fn replicate_key_configured() -> bool {
for var in ["AETHERDB_HOST_REPLICATE_KEY_HEX", "AETHERDB_HUB_REPLICATE_KEY_HEX"] {
if let Ok(hex) = std::env::var(var) {
if !hex.trim().is_empty() {
return true;
}
}
}
false
}
pub fn validate_wt_aether_startup() -> Result<(), SecurityError> {
#[cfg(feature = "kwt")]
{
let cfg = loaded_config()?;
let wt = crate::wt_aether::config_snapshot();
let control_enabled =
wt.enabled && wt.routes.contains(&crate::wt_aether::WtAetherRoute::Control);
return cfg.validate_wt_aether(control_enabled, &wt);
}
#[cfg(not(feature = "kwt"))]
Ok(())
}
fn resolve_secret(
env_var: &str,
file_var: &str,
kdl_value: Option<&str>,
) -> Result<Option<String>, SecurityError> {
if let Ok(value) = std::env::var(env_var) {
if !value.is_empty() {
return Ok(Some(value));
}
}
if let Ok(path) = std::env::var(file_var) {
let path = path.trim();
if !path.is_empty() {
let bytes = std::fs::read(path).map_err(|source| SecurityError::Io {
path: path.to_string(),
source,
})?;
let value = String::from_utf8(bytes)
.map_err(|_| SecurityError::Invalid(format!("{file_var} must be UTF-8")))?
.trim()
.to_string();
if !value.is_empty() {
return Ok(Some(value));
}
}
}
if let Some(value) = kdl_value {
let value = value.trim();
if !value.is_empty() {
return Ok(Some(value.to_string()));
}
}
Ok(None)
}
fn require_admin_token_for_metrics_from_env() -> bool {
match std::env::var("NAUTALID_SECURITY_REQUIRE_ADMIN_TOKEN")
.ok()
.map(|v| v.trim().to_ascii_lowercase())
.as_deref()
{
Some("0") | Some("false") | Some("no") | Some("off") => false,
Some("1") | Some("true") | Some("yes") | Some("on") => true,
_ => security_profile_from_env() == SecurityProfile::Production,
}
}
fn require_bus_token_for_wt_control_from_env() -> bool {
match std::env::var("NAUTALID_SECURITY_REQUIRE_WT_CONTROL_TOKEN")
.ok()
.map(|v| v.trim().to_ascii_lowercase())
.as_deref()
{
Some("0") | Some("false") | Some("no") | Some("off") => false,
Some("1") | Some("true") | Some("yes") | Some("on") => true,
_ => security_profile_from_env() == SecurityProfile::Production,
}
}
fn require_kwt_jti_replay_from_env() -> bool {
match std::env::var("NAUTALID_SECURITY_REQUIRE_KWT_JTI_REPLAY")
.ok()
.map(|v| v.trim().to_ascii_lowercase())
.as_deref()
{
Some("0") | Some("false") | Some("no") | Some("off") => false,
Some("1") | Some("true") | Some("yes") | Some("on") => true,
_ => security_profile_from_env() == SecurityProfile::Production,
}
}
fn require_wt_cert_allowlists_from_env() -> bool {
match std::env::var("NAUTALID_SECURITY_REQUIRE_WT_CERT_ALLOWLISTS")
.ok()
.map(|v| v.trim().to_ascii_lowercase())
.as_deref()
{
Some("0") | Some("false") | Some("no") | Some("off") => false,
Some("1") | Some("true") | Some("yes") | Some("on") => true,
_ => security_profile_from_env() == SecurityProfile::Production,
}
}
fn require_wt_replicate_key_from_env() -> bool {
match std::env::var("NAUTALID_SECURITY_REQUIRE_WT_REPLICATE_KEY")
.ok()
.map(|v| v.trim().to_ascii_lowercase())
.as_deref()
{
Some("0") | Some("false") | Some("no") | Some("off") => false,
Some("1") | Some("true") | Some("yes") | Some("on") => true,
_ => security_profile_from_env() == SecurityProfile::Production,
}
}
fn lock_wt_aether_hot_reload_from_env() -> bool {
match std::env::var("NAUTALID_SECURITY_LOCK_WT_AETHER_HOT_RELOAD")
.ok()
.map(|v| v.trim().to_ascii_lowercase())
.as_deref()
{
Some("0") | Some("false") | Some("no") | Some("off") => false,
Some("1") | Some("true") | Some("yes") | Some("on") => true,
_ => false,
}
}
fn merge_bus_scopes_from_env(scopes: &mut HashMap<String, HashSet<String>>) {
for module in BUS_MODULE_NAMES {
let var = format!(
"NAUTALID_BUS_TOKEN_{}",
module.to_ascii_uppercase().replace('-', "_")
);
let Ok(token) = std::env::var(&var) else {
continue;
};
let token = token.trim();
if token.is_empty() {
continue;
}
scopes
.entry(token.to_string())
.or_default()
.insert((*module).to_string());
}
}
fn parse_bus_modules(raw: &str) -> HashSet<String> {
raw.split(',')
.map(str::trim)
.filter(|s| !s.is_empty())
.filter(|name| BUS_MODULE_NAMES.contains(name))
.map(str::to_string)
.collect()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SecurityProfile {
Development,
Production,
}
fn security_profile_from_env() -> SecurityProfile {
match std::env::var("NAUTALID_SECURITY_PROFILE")
.ok()
.map(|v| v.trim().to_ascii_lowercase())
.as_deref()
{
Some("production") | Some("prod") | Some("strict") => SecurityProfile::Production,
_ => SecurityProfile::Development,
}
}
#[derive(Debug, Default)]
struct KdlSecurityOverrides {
bus_token: Option<String>,
bus_scoped_tokens: HashMap<String, HashSet<String>>,
admin_token: Option<String>,
require_admin_token_for_metrics: Option<bool>,
require_bus_token_for_wt_control: Option<bool>,
require_kwt_jti_replay: Option<bool>,
require_wt_cert_allowlists: Option<bool>,
require_wt_replicate_key: Option<bool>,
lock_wt_aether_hot_reload: Option<bool>,
}
impl KdlSecurityOverrides {
fn from_env() -> Result<Self, SecurityError> {
let Some(path) = std::env::var("NAUTALID_SECURITY_KDL_PATH")
.ok()
.filter(|p| !p.trim().is_empty())
else {
return Ok(Self::default());
};
let text = std::fs::read_to_string(&path).map_err(|source| SecurityError::Io {
path: path.clone(),
source,
})?;
Self::parse(&text)
}
fn parse(text: &str) -> Result<Self, SecurityError> {
let doc: KdlDocument = text
.parse::<KdlDocument>()
.map_err(|e: kdl::KdlError| SecurityError::Parse(e.to_string()))?;
let node = doc
.get("security")
.ok_or_else(|| SecurityError::Invalid("missing `security` node".into()))?;
let mut out = Self::default();
if let Some(token) = kdl_string(node, "bus_token") {
out.bus_token = Some(token);
}
if let Some(token) = kdl_string(node, "admin_token") {
out.admin_token = Some(token);
}
if let Some(require) = kdl_bool(node, "require_metrics_token") {
out.require_admin_token_for_metrics = Some(require);
}
if let Some(require) = kdl_bool(node, "require_admin_token_for_metrics") {
out.require_admin_token_for_metrics = Some(require);
}
if let Some(require) = kdl_bool(node, "require_wt_control_token") {
out.require_bus_token_for_wt_control = Some(require);
}
if let Some(require) = kdl_bool(node, "require_bus_token_for_wt_control") {
out.require_bus_token_for_wt_control = Some(require);
}
if let Some(require) = kdl_bool(node, "require_kwt_jti_replay") {
out.require_kwt_jti_replay = Some(require);
}
if let Some(require) = kdl_bool(node, "require_wt_cert_allowlists") {
out.require_wt_cert_allowlists = Some(require);
}
if let Some(require) = kdl_bool(node, "require_wt_replicate_key") {
out.require_wt_replicate_key = Some(require);
}
if let Some(lock) = kdl_bool(node, "lock_wt_aether_hot_reload") {
out.lock_wt_aether_hot_reload = Some(lock);
}
if let Some(scopes) = node.children().and_then(|c| c.get("bus_scopes")) {
if let Some(children) = scopes.children() {
for child in children.nodes() {
if child.name().value() != "scope" {
continue;
}
let Some(token) = kdl_string(child, "token") else {
continue;
};
let modules = kdl_string(child, "modules")
.map(|raw| parse_bus_modules(&raw))
.filter(|set| !set.is_empty())
.unwrap_or_default();
if modules.is_empty() {
continue;
}
out.bus_scoped_tokens
.entry(token)
.or_default()
.extend(modules);
}
}
}
if let Some(profile) = kdl_string(node, "profile") {
let production = matches!(
profile.trim().to_ascii_lowercase().as_str(),
"production" | "prod" | "strict"
);
if out.require_admin_token_for_metrics.is_none() {
out.require_admin_token_for_metrics = Some(production);
}
if out.require_bus_token_for_wt_control.is_none() {
out.require_bus_token_for_wt_control = Some(production);
}
if out.require_kwt_jti_replay.is_none() {
out.require_kwt_jti_replay = Some(production);
}
if out.require_wt_cert_allowlists.is_none() {
out.require_wt_cert_allowlists = Some(production);
}
if out.require_wt_replicate_key.is_none() {
out.require_wt_replicate_key = Some(production);
}
}
Ok(out)
}
}
fn kdl_string(node: &kdl::KdlNode, key: &str) -> Option<String> {
if let Some(value) = node.get(key).and_then(|v| v.as_string()) {
return Some(value.to_string());
}
let child = node.children()?.get(key)?;
child
.entries()
.iter()
.find_map(|entry| entry.value().as_string().map(str::to_string))
}
fn kdl_bool(node: &kdl::KdlNode, key: &str) -> Option<bool> {
if let Some(value) = node.get(key).and_then(|v| v.as_bool()) {
return Some(value);
}
let child = node.children()?.get(key)?;
child
.entries()
.iter()
.find_map(|entry| entry.value().as_bool())
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
static ENV_TEST_LOCK: Mutex<()> = Mutex::new(());
#[test]
fn parses_security_kdl() {
let kdl = r#"security {
bus_token "bus-secret"
admin_token "admin-secret"
require_metrics_token #true
require_wt_control_token #true
}"#;
let parsed = KdlSecurityOverrides::parse(kdl).expect("parse");
assert_eq!(parsed.bus_token.as_deref(), Some("bus-secret"));
assert_eq!(parsed.admin_token.as_deref(), Some("admin-secret"));
assert_eq!(parsed.require_admin_token_for_metrics, Some(true));
assert_eq!(parsed.require_bus_token_for_wt_control, Some(true));
}
#[test]
fn production_profile_sets_require_flags() {
let kdl = r#"security { profile "production" }"#;
let parsed = KdlSecurityOverrides::parse(kdl).expect("parse");
assert_eq!(parsed.require_admin_token_for_metrics, Some(true));
assert_eq!(parsed.require_bus_token_for_wt_control, Some(true));
assert_eq!(parsed.require_wt_replicate_key, Some(true));
assert_eq!(parsed.lock_wt_aether_hot_reload, None);
}
#[test]
fn wt_control_validation_requires_token_when_configured() {
let cfg = SecurityConfig {
bus_token: None,
bus_scoped_tokens: HashMap::new(),
admin_token: Some("a".into()),
require_admin_token_for_metrics: false,
require_bus_token_for_wt_control: true,
require_kwt_jti_replay: false,
require_wt_cert_allowlists: false,
require_wt_replicate_key: false,
lock_wt_aether_hot_reload: false,
};
let wt = crate::wt_aether::WtAetherConfig::default();
assert!(cfg.validate_wt_aether(true, &wt).is_err());
let ok = SecurityConfig {
bus_token: Some("b".into()),
..cfg
};
assert!(ok.validate_wt_aether(true, &wt).is_ok());
}
#[test]
fn parses_bus_scopes_kdl() {
let kdl = r#"security {
bus_scopes {
scope token="filter-ops" modules="filter,telemetry"
scope token="tls-ops" modules="tls"
}
}"#;
let parsed = KdlSecurityOverrides::parse(kdl).expect("parse");
let filter_ops = parsed
.bus_scoped_tokens
.get("filter-ops")
.expect("filter-ops");
assert!(filter_ops.contains("filter"));
assert!(filter_ops.contains("telemetry"));
assert_eq!(
parsed.bus_scoped_tokens.get("tls-ops").unwrap().len(),
1
);
}
#[test]
fn development_profile_sets_require_flags_off() {
let kdl = r#"security { profile "development" }"#;
let parsed = KdlSecurityOverrides::parse(kdl).expect("parse");
assert_eq!(parsed.require_admin_token_for_metrics, Some(false));
assert_eq!(parsed.require_bus_token_for_wt_control, Some(false));
assert_eq!(parsed.require_kwt_jti_replay, Some(false));
assert_eq!(parsed.require_wt_cert_allowlists, Some(false));
assert_eq!(parsed.require_wt_replicate_key, Some(false));
assert_eq!(parsed.lock_wt_aether_hot_reload, None);
}
#[test]
fn module_env_var_merges_scoped_bus_token() {
unsafe {
std::env::set_var("NAUTALID_BUS_TOKEN_FILTER", "scoped-filter");
}
let mut scopes = HashMap::new();
merge_bus_scopes_from_env(&mut scopes);
let modules = scopes.get("scoped-filter").expect("scoped-filter token");
assert!(modules.contains("filter"));
unsafe {
std::env::remove_var("NAUTALID_BUS_TOKEN_FILTER");
}
}
#[test]
fn env_bus_token_takes_precedence_over_empty_kdl() {
unsafe {
std::env::remove_var("NAUTALID_SECURITY_KDL_PATH");
std::env::set_var("NAUTALID_BUS_TOKEN", "env-bus-secret");
}
let cfg = SecurityConfig::load().expect("load");
assert_eq!(cfg.bus_token.as_deref(), Some("env-bus-secret"));
unsafe {
std::env::remove_var("NAUTALID_BUS_TOKEN");
}
}
#[test]
fn wt_cert_allowlist_validation_fails_when_required_and_empty() {
let cfg = SecurityConfig {
bus_token: None,
bus_scoped_tokens: HashMap::new(),
admin_token: None,
require_admin_token_for_metrics: false,
require_bus_token_for_wt_control: false,
require_kwt_jti_replay: false,
require_wt_cert_allowlists: true,
require_wt_replicate_key: false,
lock_wt_aether_hot_reload: false,
};
let wt = crate::wt_aether::WtAetherConfig {
enabled: true,
restrict_client_certs: true,
require_mtls_for_data: true,
routes: HashSet::from([crate::wt_aether::WtAetherRoute::Command]),
..crate::wt_aether::WtAetherConfig::default()
};
let err = cfg.validate_wt_aether(false, &wt).expect_err("missing allowlist");
assert!(matches!(
err,
SecurityError::MissingWtCertAllowlist { route } if route == "command"
));
}
#[test]
fn production_profile_env_enables_kwt_jti_replay_requirement() {
unsafe {
std::env::set_var("NAUTALID_SECURITY_PROFILE", "production");
std::env::set_var("KWT_REPLAY", "0");
}
assert!(require_kwt_jti_replay_from_env());
unsafe {
std::env::remove_var("NAUTALID_SECURITY_PROFILE");
std::env::remove_var("KWT_REPLAY");
}
}
#[test]
fn init_idempotent_when_policy_unchanged() {
unsafe {
std::env::remove_var("NAUTALID_SECURITY_KDL_PATH");
std::env::remove_var("NAUTALID_ADMIN_TOKEN");
}
let _ = init();
assert!(init().is_ok());
}
#[test]
fn init_rejects_conflicting_policy() {
unsafe {
std::env::remove_var("NAUTALID_SECURITY_KDL_PATH");
std::env::remove_var("NAUTALID_ADMIN_TOKEN");
}
let _ = init();
unsafe {
std::env::set_var("NAUTALID_ADMIN_TOKEN", "different-admin");
}
let err = init().expect_err("conflicting policy");
assert!(matches!(err, SecurityError::Invalid(_)));
unsafe {
std::env::remove_var("NAUTALID_ADMIN_TOKEN");
}
}
#[test]
fn admin_token_in_load_implies_metrics_auth_policy() {
unsafe {
std::env::remove_var("NAUTALID_SECURITY_KDL_PATH");
std::env::set_var("NAUTALID_ADMIN_TOKEN", "metrics-admin");
}
let cfg = SecurityConfig::load().expect("load");
assert_eq!(cfg.admin_token.as_deref(), Some("metrics-admin"));
assert!(
cfg.require_admin_token_for_metrics || cfg.admin_token.is_some(),
"admin token should enable metrics auth policy"
);
unsafe {
std::env::remove_var("NAUTALID_ADMIN_TOKEN");
}
}
#[test]
fn replicate_key_required_when_policy_and_route_enabled() {
unsafe {
std::env::remove_var("AETHERDB_HOST_REPLICATE_KEY_HEX");
std::env::remove_var("AETHERDB_HUB_REPLICATE_KEY_HEX");
}
let cfg = SecurityConfig {
bus_token: None,
bus_scoped_tokens: HashMap::new(),
admin_token: None,
require_admin_token_for_metrics: false,
require_bus_token_for_wt_control: false,
require_kwt_jti_replay: false,
require_wt_cert_allowlists: false,
require_wt_replicate_key: true,
lock_wt_aether_hot_reload: false,
};
let wt = crate::wt_aether::WtAetherConfig {
enabled: true,
allow_replicate_route: true,
routes: HashSet::from([crate::wt_aether::WtAetherRoute::Replicate]),
..crate::wt_aether::WtAetherConfig::default()
};
assert!(matches!(
cfg.validate_wt_aether(false, &wt),
Err(SecurityError::MissingReplicateKey)
));
}
#[test]
fn production_profile_keeps_wt_hot_reload_unlocked_by_default() {
let _guard = ENV_TEST_LOCK.lock().expect("env test lock");
reset_for_tests();
unsafe {
std::env::remove_var("NAUTALID_SECURITY_LOCK_WT_AETHER_HOT_RELOAD");
std::env::set_var("NAUTALID_SECURITY_PROFILE", "production");
}
assert!(!lock_wt_aether_hot_reload_from_env());
unsafe {
std::env::remove_var("NAUTALID_SECURITY_PROFILE");
}
}
#[test]
fn wt_hot_reload_lock_is_explicit_opt_in() {
let _guard = ENV_TEST_LOCK.lock().expect("env test lock");
reset_for_tests();
unsafe {
std::env::remove_var("NAUTALID_SECURITY_PROFILE");
std::env::set_var("NAUTALID_SECURITY_LOCK_WT_AETHER_HOT_RELOAD", "1");
}
assert!(lock_wt_aether_hot_reload_from_env());
unsafe {
std::env::remove_var("NAUTALID_SECURITY_LOCK_WT_AETHER_HOT_RELOAD");
}
}
}