diff --git a/easytier/src/common/config.rs b/easytier/src/common/config.rs index 8eb9f957..fc1977c2 100644 --- a/easytier/src/common/config.rs +++ b/easytier/src/common/config.rs @@ -597,16 +597,33 @@ impl TomlConfigLoader { Self::normalize_config_source(&mut config); config.flags_struct = Some(Self::gen_flags(config.flags.clone().unwrap_or_default())); + let has_network_identity = config.network_identity.is_some(); let config = TomlConfigLoader { config: Arc::new(Mutex::new(config)), }; let old_ns = config.get_network_identity(); - config.set_network_identity(NetworkIdentity::new( - old_ns.network_name, - old_ns.network_secret.unwrap_or_default(), - )); + + // Detect credential mode: secure_mode enabled + no network_secret in TOML + let is_credential = has_network_identity + && config + .get_secure_mode() + .map(|sm| sm.enabled) + .unwrap_or(false) + && old_ns + .network_secret + .as_deref() + .is_none_or(|s| s.is_empty()); + + if is_credential { + config.set_network_identity(NetworkIdentity::new_credential(old_ns.network_name)); + } else { + config.set_network_identity(NetworkIdentity::new( + old_ns.network_name, + old_ns.network_secret.unwrap_or_default(), + )); + } Ok(config) } @@ -1359,6 +1376,45 @@ stun_servers = [ assert_eq!(loaded.get_network_config_source(), ConfigSource::Webhook); } + #[test] + fn test_toml_credential_mode_omits_network_secret() { + for network_secret in ["", r#"network_secret = """#] { + let config = TomlConfigLoader::new_from_str(&format!( + r#" +[network_identity] +network_name = "credential-network" +{network_secret} + +[secure_mode] +enabled = true +"# + )) + .unwrap(); + + let identity = config.get_network_identity(); + assert_eq!(identity.network_name, "credential-network"); + assert_eq!(identity.network_secret, None); + assert_eq!(identity.network_secret_digest, None); + assert!(!config.dump().contains("network_secret")); + } + } + + #[test] + fn test_toml_secure_mode_without_network_identity_uses_default_secret() { + let config = TomlConfigLoader::new_from_str( + r#" +[secure_mode] +enabled = true +"#, + ) + .unwrap(); + + let identity = config.get_network_identity(); + assert_eq!(identity.network_name, "default"); + assert_eq!(identity.network_secret.as_deref(), Some("")); + assert!(identity.network_secret_digest.is_some()); + } + #[test] fn test_parse_mapped_listener_urls_allows_ws_without_port() { let parsed = parse_mapped_listener_urls(&[ diff --git a/easytier/src/core.rs b/easytier/src/core.rs index afde3dea..ed014faa 100644 --- a/easytier/src/core.rs +++ b/easytier/src/core.rs @@ -891,17 +891,20 @@ impl NetworkOptions { } let old_ns = cfg.get_network_identity(); - let network_name = self.network_name.clone().unwrap_or(old_ns.network_name); + let network_name = self + .network_name + .clone() + .unwrap_or_else(|| old_ns.network_name.clone()); if self.credential.is_some() { // Credential mode: no network_secret, authenticate via credential keypair cfg.set_network_identity(NetworkIdentity::new_credential(network_name)); - } else { - let network_secret = self - .network_secret - .clone() - .unwrap_or(old_ns.network_secret.unwrap_or_default()); + } else if let Some(network_secret) = &self.network_secret { + cfg.set_network_identity(NetworkIdentity::new(network_name, network_secret.clone())); + } else if let Some(network_secret) = old_ns.network_secret { cfg.set_network_identity(NetworkIdentity::new(network_name, network_secret)); + } else { + cfg.set_network_identity(NetworkIdentity::new_credential(network_name)); } if let Some(dhcp) = self.dhcp { @@ -1739,4 +1742,33 @@ mod tests { ); } } + + #[test] + fn test_network_options_merge_preserves_credential_identity() { + let cfg = TomlConfigLoader::new_from_str( + r#" +[network_identity] +network_name = "credential-network" +network_secret = "" + +[secure_mode] +enabled = true +"#, + ) + .unwrap(); + assert_eq!(cfg.get_network_identity().network_secret, None); + + NetworkOptions { + hostname: Some("override-host".to_string()), + ..Default::default() + } + .merge_into(&cfg) + .unwrap(); + + let identity = cfg.get_network_identity(); + assert_eq!(identity.network_name, "credential-network"); + assert_eq!(identity.network_secret, None); + assert_eq!(identity.network_secret_digest, None); + assert_eq!(cfg.get_hostname(), "override-host"); + } }