use ipnet::{IpNet, Ipv4Net}; use std::collections::HashMap; use std::error::Error; use std::fs; use std::net::{Ipv4Addr, SocketAddrV4}; use crate::dirs::{config_dir, tfclient_toml, tfdata_toml}; use dnapi_rs::client_blocking::EnrollMeta; use dnapi_rs::credentials::Credentials; use log::{debug, info}; use serde::{Deserialize, Serialize}; pub const DEFAULT_PORT: u16 = 8157; fn default_port() -> u16 { DEFAULT_PORT } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct TFClientConfig { #[serde(default = "default_port")] pub listen_port: u16, #[serde(default = "bool_false")] pub disable_automatic_config_updates: bool, } #[derive(Serialize, Deserialize, Clone)] pub struct TFClientData { pub dh_privkey: Option>, pub creds: Option, pub meta: Option, } pub fn create_config(instance: &str) -> Result<(), Box> { info!("Creating config directory..."); fs::create_dir_all(config_dir(instance))?; info!("Copying default config file to config directory..."); let config = TFClientConfig { listen_port: DEFAULT_PORT, disable_automatic_config_updates: false, }; let config_str = toml::to_string(&config)?; fs::write(tfclient_toml(instance), config_str)?; Ok(()) } pub fn load_config(instance: &str) -> Result> { info!("Loading config..."); let config_file = tfclient_toml(instance); if !config_file.exists() { create_config(instance)?; } debug!("opening {}", config_file.as_path().display()); let config_str = fs::read_to_string(config_file)?; debug!("parsing config file"); let config: TFClientConfig = toml::from_str(&config_str)?; info!("Loaded config successfully"); Ok(config) } pub fn create_cdata(instance: &str) -> Result<(), Box> { info!("Creating data directory..."); fs::create_dir_all(config_dir(instance))?; info!("Copying default data file to config directory..."); let config = TFClientData { dh_privkey: None, creds: None, meta: None, }; let config_str = toml::to_string(&config)?; fs::write(tfdata_toml(instance), config_str)?; Ok(()) } pub fn load_cdata(instance: &str) -> Result> { info!("Loading cdata..."); let config_file = tfdata_toml(instance); if !config_file.exists() { create_cdata(instance)?; } debug!("opening {}", config_file.as_path().display()); let config_str = fs::read_to_string(config_file)?; debug!("parsing cdata file"); let config: TFClientData = toml::from_str(&config_str)?; info!("Loaded cdata successfully"); Ok(config) } pub fn save_cdata(instance: &str, data: TFClientData) -> Result<(), Box> { info!("Saving cdata..."); let config_file = tfdata_toml(instance); if !config_file.exists() { create_cdata(instance)?; } debug!("serializing cdata file"); let config: String = toml::to_string(&data)?; debug!("writing to {}", config_file.as_path().display()); fs::write(config_file, config)?; info!("Saved cdata successfully"); Ok(()) } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfig { pub pki: NebulaConfigPki, #[serde(default = "empty_hashmap")] #[serde(skip_serializing_if = "is_empty_hashmap")] pub static_host_map: HashMap>, #[serde(skip_serializing_if = "is_none")] pub lighthouse: Option, #[serde(skip_serializing_if = "is_none")] pub listen: Option, #[serde(skip_serializing_if = "is_none")] pub punchy: Option, #[serde(default = "cipher_aes")] #[serde(skip_serializing_if = "is_cipher_aes")] pub cipher: NebulaConfigCipher, #[serde(default = "empty_vec")] #[serde(skip_serializing_if = "is_empty_vec")] pub preferred_ranges: Vec, #[serde(skip_serializing_if = "is_none")] pub relay: Option, #[serde(skip_serializing_if = "is_none")] pub tun: Option, #[serde(skip_serializing_if = "is_none")] pub logging: Option, #[serde(skip_serializing_if = "is_none")] pub sshd: Option, #[serde(skip_serializing_if = "is_none")] pub firewall: Option, #[serde(default = "u64_1")] #[serde(skip_serializing_if = "is_u64_1")] pub routines: u64, #[serde(default = "none")] #[serde(skip_serializing_if = "is_none")] pub stats: Option, #[serde(default = "none")] #[serde(skip_serializing_if = "is_none")] pub local_range: Option, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigPki { pub ca: String, pub cert: String, #[serde(default = "none")] #[serde(skip_serializing_if = "is_none")] pub key: Option, #[serde(default = "empty_vec")] #[serde(skip_serializing_if = "is_empty_vec")] pub blocklist: Vec, #[serde(default = "bool_false")] #[serde(skip_serializing_if = "is_bool_false")] pub disconnect_invalid: bool, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigLighthouse { #[serde(default = "bool_false")] #[serde(skip_serializing_if = "is_bool_false")] pub am_lighthouse: bool, #[serde(default = "bool_false")] #[serde(skip_serializing_if = "is_bool_false")] pub serve_dns: bool, #[serde(skip_serializing_if = "is_none")] pub dns: Option, #[serde(default = "u32_10")] #[serde(skip_serializing_if = "is_u32_10")] pub interval: u32, #[serde(default = "empty_vec")] #[serde(skip_serializing_if = "is_empty_vec")] pub hosts: Vec, #[serde(default = "empty_hashmap")] #[serde(skip_serializing_if = "is_empty_hashmap")] pub remote_allow_list: HashMap, #[serde(default = "empty_hashmap")] #[serde(skip_serializing_if = "is_empty_hashmap")] pub local_allow_list: HashMap, // `interfaces` is not supported } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigLighthouseDns { #[serde(default = "string_empty")] #[serde(skip_serializing_if = "is_string_empty")] pub host: String, #[serde(default = "u16_53")] #[serde(skip_serializing_if = "is_u16_53")] pub port: u16, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigListen { #[serde(default = "string_empty")] #[serde(skip_serializing_if = "is_string_empty")] pub host: String, #[serde(default = "u16_0")] #[serde(skip_serializing_if = "is_u16_0")] pub port: u16, #[serde(default = "u32_64")] #[serde(skip_serializing_if = "is_u32_64")] pub batch: u32, #[serde(skip_serializing_if = "is_none")] pub read_buffer: Option, #[serde(skip_serializing_if = "is_none")] pub write_buffer: Option, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigPunchy { #[serde(default = "bool_false")] #[serde(skip_serializing_if = "is_bool_false")] pub punch: bool, #[serde(default = "bool_false")] #[serde(skip_serializing_if = "is_bool_false")] pub respond: bool, #[serde(default = "string_1s")] #[serde(skip_serializing_if = "is_string_1s")] pub delay: String, } #[derive(Serialize, Deserialize, Clone, Debug)] pub enum NebulaConfigCipher { #[serde(rename = "aes")] Aes, #[serde(rename = "chachapoly")] ChaChaPoly, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigRelay { #[serde(default = "empty_vec")] #[serde(skip_serializing_if = "is_empty_vec")] pub relays: Vec, #[serde(default = "bool_false")] #[serde(skip_serializing_if = "is_bool_false")] pub am_relay: bool, #[serde(default = "bool_true")] #[serde(skip_serializing_if = "is_bool_true")] pub use_relays: bool, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigTun { #[serde(default = "bool_false")] #[serde(skip_serializing_if = "is_bool_false")] pub disabled: bool, #[serde(skip_serializing_if = "is_none")] pub dev: Option, #[serde(default = "bool_false")] #[serde(skip_serializing_if = "is_bool_false")] pub drop_local_broadcast: bool, #[serde(default = "bool_false")] #[serde(skip_serializing_if = "is_bool_false")] pub drop_multicast: bool, #[serde(default = "u64_500")] #[serde(skip_serializing_if = "is_u64_500")] pub tx_queue: u64, #[serde(default = "u64_1300")] #[serde(skip_serializing_if = "is_u64_1300")] pub mtu: u64, #[serde(default = "empty_vec")] #[serde(skip_serializing_if = "is_empty_vec")] pub routes: Vec, #[serde(default = "empty_vec")] #[serde(skip_serializing_if = "is_empty_vec")] pub unsafe_routes: Vec, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigTunRouteOverride { pub mtu: u64, pub route: Ipv4Net, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigTunUnsafeRoute { pub route: Ipv4Net, pub via: Ipv4Addr, #[serde(default = "u64_1300")] #[serde(skip_serializing_if = "is_u64_1300")] pub mtu: u64, #[serde(default = "i64_100")] #[serde(skip_serializing_if = "is_i64_100")] pub metric: i64, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigLogging { #[serde(default = "loglevel_info")] #[serde(skip_serializing_if = "is_loglevel_info")] pub level: NebulaConfigLoggingLevel, #[serde(default = "format_text")] #[serde(skip_serializing_if = "is_format_text")] pub format: NebulaConfigLoggingFormat, #[serde(default = "bool_false")] #[serde(skip_serializing_if = "is_bool_false")] pub disable_timestamp: bool, #[serde(default = "timestamp")] #[serde(skip_serializing_if = "is_timestamp")] pub timestamp_format: String, } #[derive(Serialize, Deserialize, Clone, Debug)] pub enum NebulaConfigLoggingLevel { #[serde(rename = "panic")] Panic, #[serde(rename = "fatal")] Fatal, #[serde(rename = "error")] Error, #[serde(rename = "warning")] Warning, #[serde(rename = "info")] Info, #[serde(rename = "debug")] Debug, } #[derive(Serialize, Deserialize, Clone, Debug)] pub enum NebulaConfigLoggingFormat { #[serde(rename = "json")] Json, #[serde(rename = "text")] Text, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigSshd { #[serde(default = "bool_false")] #[serde(skip_serializing_if = "is_bool_false")] pub enabled: bool, pub listen: SocketAddrV4, pub host_key: String, #[serde(default = "empty_vec")] #[serde(skip_serializing_if = "is_empty_vec")] pub authorized_users: Vec, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigSshdAuthorizedUser { pub user: String, #[serde(default = "empty_vec")] #[serde(skip_serializing_if = "is_empty_vec")] pub keys: Vec, } #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(tag = "type")] pub enum NebulaConfigStats { #[serde(rename = "graphite")] Graphite(NebulaConfigStatsGraphite), #[serde(rename = "prometheus")] Prometheus(NebulaConfigStatsPrometheus), } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigStatsGraphite { #[serde(default = "string_nebula")] #[serde(skip_serializing_if = "is_string_nebula")] pub prefix: String, #[serde(default = "protocol_tcp")] #[serde(skip_serializing_if = "is_protocol_tcp")] pub protocol: NebulaConfigStatsGraphiteProtocol, pub host: SocketAddrV4, pub interval: String, #[serde(default = "bool_false")] #[serde(skip_serializing_if = "is_bool_false")] pub message_metrics: bool, #[serde(default = "bool_false")] #[serde(skip_serializing_if = "is_bool_false")] pub lighthouse_metrics: bool, } #[derive(Serialize, Deserialize, Clone, Debug)] pub enum NebulaConfigStatsGraphiteProtocol { #[serde(rename = "tcp")] Tcp, #[serde(rename = "udp")] Udp, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigStatsPrometheus { pub listen: String, pub path: String, #[serde(default = "string_nebula")] #[serde(skip_serializing_if = "is_string_nebula")] pub namespace: String, #[serde(default = "string_nebula")] #[serde(skip_serializing_if = "is_string_nebula")] pub subsystem: String, pub interval: String, #[serde(default = "bool_false")] #[serde(skip_serializing_if = "is_bool_false")] pub message_metrics: bool, #[serde(default = "bool_false")] #[serde(skip_serializing_if = "is_bool_false")] pub lighthouse_metrics: bool, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigFirewall { #[serde(default = "none")] #[serde(skip_serializing_if = "is_none")] pub conntrack: Option, #[serde(default = "none")] #[serde(skip_serializing_if = "is_none")] pub inbound: Option>, #[serde(default = "none")] #[serde(skip_serializing_if = "is_none")] pub outbound: Option>, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigFirewallConntrack { #[serde(default = "string_12m")] #[serde(skip_serializing_if = "is_string_12m")] pub tcp_timeout: String, #[serde(default = "string_3m")] #[serde(skip_serializing_if = "is_string_3m")] pub udp_timeout: String, #[serde(default = "string_10m")] #[serde(skip_serializing_if = "is_string_10m")] pub default_timeout: String, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigFirewallRule { #[serde(default = "none")] #[serde(skip_serializing_if = "is_none")] pub port: Option, #[serde(default = "none")] #[serde(skip_serializing_if = "is_none")] pub proto: Option, #[serde(default = "none")] #[serde(skip_serializing_if = "is_none")] pub ca_name: Option, #[serde(default = "none")] #[serde(skip_serializing_if = "is_none")] pub ca_sha: Option, #[serde(default = "none")] #[serde(skip_serializing_if = "is_none")] pub host: Option, #[serde(default = "none")] #[serde(skip_serializing_if = "is_none")] pub group: Option, #[serde(default = "none")] #[serde(skip_serializing_if = "is_none")] pub groups: Option>, #[serde(default = "none")] #[serde(skip_serializing_if = "is_none")] pub cidr: Option, } // Default values for serde fn string_12m() -> String { "12m".to_string() } fn is_string_12m(s: &str) -> bool { s == "12m" } fn string_3m() -> String { "3m".to_string() } fn is_string_3m(s: &str) -> bool { s == "3m" } fn string_10m() -> String { "10m".to_string() } fn is_string_10m(s: &str) -> bool { s == "10m" } fn empty_vec() -> Vec { vec![] } fn is_empty_vec(v: &Vec) -> bool { v.is_empty() } fn empty_hashmap() -> HashMap { HashMap::new() } fn is_empty_hashmap(h: &HashMap) -> bool { h.is_empty() } fn bool_false() -> bool { false } fn is_bool_false(b: &bool) -> bool { !*b } fn bool_true() -> bool { true } fn is_bool_true(b: &bool) -> bool { *b } fn u16_53() -> u16 { 53 } fn is_u16_53(u: &u16) -> bool { *u == 53 } fn u32_10() -> u32 { 10 } fn is_u32_10(u: &u32) -> bool { *u == 10 } fn u16_0() -> u16 { 0 } fn is_u16_0(u: &u16) -> bool { *u == 0 } fn u32_64() -> u32 { 64 } fn is_u32_64(u: &u32) -> bool { *u == 64 } fn string_1s() -> String { "1s".to_string() } fn is_string_1s(s: &str) -> bool { s == "1s" } fn cipher_aes() -> NebulaConfigCipher { NebulaConfigCipher::Aes } fn is_cipher_aes(c: &NebulaConfigCipher) -> bool { matches!(c, NebulaConfigCipher::Aes) } fn u64_500() -> u64 { 500 } fn is_u64_500(u: &u64) -> bool { *u == 500 } fn u64_1300() -> u64 { 1300 } fn is_u64_1300(u: &u64) -> bool { *u == 1300 } fn i64_100() -> i64 { 100 } fn is_i64_100(i: &i64) -> bool { *i == 100 } fn loglevel_info() -> NebulaConfigLoggingLevel { NebulaConfigLoggingLevel::Info } fn is_loglevel_info(l: &NebulaConfigLoggingLevel) -> bool { matches!(l, NebulaConfigLoggingLevel::Info) } fn format_text() -> NebulaConfigLoggingFormat { NebulaConfigLoggingFormat::Text } fn is_format_text(f: &NebulaConfigLoggingFormat) -> bool { matches!(f, NebulaConfigLoggingFormat::Text) } fn timestamp() -> String { "2006-01-02T15:04:05Z07:00".to_string() } fn is_timestamp(s: &str) -> bool { s == "2006-01-02T15:04:05Z07:00" } fn u64_1() -> u64 { 1 } fn is_u64_1(u: &u64) -> bool { *u == 1 } fn string_nebula() -> String { "nebula".to_string() } fn is_string_nebula(s: &str) -> bool { s == "nebula" } fn string_empty() -> String { String::new() } fn is_string_empty(s: &str) -> bool { s.is_empty() } fn protocol_tcp() -> NebulaConfigStatsGraphiteProtocol { NebulaConfigStatsGraphiteProtocol::Tcp } fn is_protocol_tcp(p: &NebulaConfigStatsGraphiteProtocol) -> bool { matches!(p, NebulaConfigStatsGraphiteProtocol::Tcp) } fn none() -> Option { None } fn is_none(o: &Option) -> bool { o.is_none() }