diff --git a/.gitignore b/.gitignore index 855c68f..f05adcc 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ target/ # testing credentials ca.key ca.srl + +# environment variables +.env diff --git a/config/.gitignore b/config/.gitignore new file mode 100644 index 0000000..9aa3101 --- /dev/null +++ b/config/.gitignore @@ -0,0 +1 @@ +mesh.toml diff --git a/config/default.toml b/config/default.toml index f382fd7..aaf1f14 100644 --- a/config/default.toml +++ b/config/default.toml @@ -1,38 +1,42 @@ -[security] +[default] +address = "0.0.0.0" +port = 3030 +log_level = "info" + +[default.tls] key_rotation_days = 7 -ca_path = "certs/ca.crt" -tls_cert_path = "certs/node.crt" -tls_key_path = "certs/node.key" +ca = "certs/ca.crt" +certs = "certs/node.crt" +key = "certs/node.key" -[storage] +[default.storage] encrypted_logs_path = "data/logs" -[network] +[default.network] gossip_interval_seconds = 60 peers_per_round = 3 registry_url = "https://registry.dtim.example.com" -[[network.default_peers]] +[[default.network.init_peers]] id = "2" endpoint = "https://peer1.example.com:3030" public_key = "Mg==" -[[network.default_peers]] +[[default.network.init_peers]] id = "3" endpoint = "https://peer2.example.com:3030" public_key = "Mw==" -[privacy] +[default.privacy] level = "moderate" # Options: "strict", "moderate", "open" allow_custom_fields = true -[watchers] +[default.watchers] ufw_log_path = "/var/log/ufw.log" fail2ban_log_path = "/var/log/fail2ban.log" suricata_log_path = "/var/log/suricata/eve.json" zeek_log_dir = "/opt/zeek/logs/current" clamav_scan_dir = "/var/tmp/scan" -virustotal_api_key = "YOUR_API_KEY_HERE" nginx_access_log = "/var/log/nginx/access.log" nginx_error_log = "/var/log/nginx/error.log" apache_access_log = "/var/log/apache2/access.log" diff --git a/src/main.rs b/src/main.rs index b7bbe49..27ddb53 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,15 @@ mod api; -mod config; mod crypto; mod error; mod logging; mod models; mod node; +mod settings; mod uuid; use axum::body::Body; use base64::prelude::BASE64_STANDARD; use base64::Engine; -use config::Config; use http_body_util::BodyExt as _; use log::LevelFilter; use models::{IndicatorType, ThreatIndicator}; @@ -21,7 +20,9 @@ use rustls::pki_types::{CertificateDer, PrivateKeyDer}; use rustls::server::WebPkiClientVerifier; use rustls::RootCertStore; use serde_json::json; +use settings::Settings; use std::path::Path; +use std::str::FromStr; use std::sync::Arc; use tokio::sync::Mutex; @@ -43,8 +44,8 @@ fn load_private_key(filename: &Path) -> PrivateKeyDer<'static> { PrivateKeyDer::from_pem_file(filename).expect("cannot read private key file") } -fn make_server_config(config: &Config) -> Arc { - let client_auth = if let Some(auth) = &config.security.ca_path { +fn make_server_config(settings: &settings::Settings) -> Arc { + let client_auth = if let Some(auth) = &settings.tls.ca { let roots = load_certs(auth); let mut client_auth_roots = RootCertStore::empty(); for root in roots { @@ -57,8 +58,8 @@ fn make_server_config(config: &Config) -> Arc { WebPkiClientVerifier::no_client_auth() }; - let certs = load_certs(&config.security.tls_cert_path); - let privkey = load_private_key(&config.security.tls_key_path); + let certs = load_certs(&settings.tls.certs); + let privkey = load_private_key(&settings.tls.key); let config = rustls::ServerConfig::builder_with_provider(provider::default_provider().into()) .with_safe_default_protocol_versions() @@ -73,24 +74,31 @@ fn make_server_config(config: &Config) -> Arc { #[tokio::main] async fn main() -> Result<(), Box> { init_logging(); - let config = config::Config::load()?; + let settings = Settings::new()?; let mesh_identity = crypto::MeshIdentity::load_or_generate()?; - let mut key_mgr = crypto::SymmetricKeyManager::new(config.security.key_rotation_days); - - let tls_config = make_server_config(&config); + let mut key_mgr = crypto::SymmetricKeyManager::new(settings.tls.key_rotation_days); + let tls_config = make_server_config(&settings); let logger = logging::EncryptedLogger::new( - config.storage.encrypted_logs_path.clone(), + settings.storage.encrypted_logs_path.clone(), key_mgr.clone(), - LevelFilter::Debug, + LevelFilter::from_str(&settings.log_level).unwrap(), )?; - let node = node::Node::new(mesh_identity.clone(), logger, config.privacy); + let node = node::Node::new(mesh_identity.clone(), logger, settings.privacy); let id = node.get_id(); println!("Node ID: {:?}", id); - let data = NodePeer::new(id.to_string(), "127.0.0.1:3030".to_string()); + let base64_pubkey = BASE64_STANDARD.encode(mesh_identity.verifying_key().to_bytes()); + + let mut data = NodePeer { + id: id.to_string(), + endpoint: "127.0.0.1:3030".to_string(), + public_key: base64_pubkey, + signature: None, + }; + let body = Body::from(serde_json::to_string(&data).unwrap()); let bytes = body @@ -99,16 +107,12 @@ async fn main() -> Result<(), Box> { .expect("Failed to collect body") .to_bytes(); - let signature = crypto::MeshIdentity::sign(mesh_identity.signing_key().unwrap().clone(), &bytes); - let base64_pubkey = BASE64_STANDARD.encode(mesh_identity.verifying_key().to_bytes()); - println!( - "{}", - json!({ - "data": data, - "X-Mesh-Public-Key": base64_pubkey, - "X-Mesh-Signature": signature, - }) - ); + let signature = + crypto::MeshIdentity::sign(mesh_identity.signing_key().unwrap().clone(), &bytes); + + data.set_signature(signature); + + println!("{}", json!(data)); let indicator = ThreatIndicator::new( IndicatorType::Ipv4Address, @@ -130,7 +134,7 @@ async fn main() -> Result<(), Box> { { let mut node = node.lock().await; node.add_indicator(indicator.clone()); - node.bootstrap_peers(config.network.default_peers.clone()); + node.bootstrap_peers(settings.network.init_peers.clone()); } let server_handle = diff --git a/src/node.rs b/src/node.rs index c4aa903..c57aabe 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1,5 +1,5 @@ use crate::{ - config::PrivacyConfig, + settings::PrivacyConfig, crypto::MeshIdentity, logging::EncryptedLogger, models::{PrivacyLevel, ThreatIndicator, TlpLevel}, @@ -132,15 +132,13 @@ impl Node { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct NodePeer { - id: String, - endpoint: String, + pub id: String, + pub endpoint: String, + pub public_key: String, + pub signature: Option, } impl NodePeer { - pub fn new(id: String, endpoint: String) -> Self { - NodePeer { id, endpoint } - } - pub fn get_id(&self) -> &str { &self.id } @@ -148,4 +146,22 @@ impl NodePeer { pub fn get_endpoint(&self) -> &str { &self.endpoint } + + pub fn get_public_key(&self) -> &str { + &self.public_key + } + + pub fn get_signature(&self) -> Option<&str> { + self.signature.as_deref() + } + + pub fn set_signature(&mut self, signature: String) { + self.signature = Some(signature); + } +} + +impl PartialEq for NodePeer { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } } diff --git a/src/config.rs b/src/settings.rs similarity index 61% rename from src/config.rs rename to src/settings.rs index 44f0c25..d5b8787 100644 --- a/src/config.rs +++ b/src/settings.rs @@ -4,62 +4,81 @@ use std::path::PathBuf; use crate::node::NodePeer; #[derive(Debug, Deserialize)] -pub struct SecurityConfig { +#[allow(unused)] +pub struct TLSConfig { pub key_rotation_days: u64, - pub ca_path: Option, - pub tls_cert_path: PathBuf, - pub tls_key_path: PathBuf, + pub ca: Option, + pub certs: PathBuf, + pub key: PathBuf, } #[derive(Debug, Deserialize)] +#[allow(unused)] pub struct StorageConfig { pub encrypted_logs_path: PathBuf, + #[serde(skip_serializing)] + pub database_url: String, } #[derive(Debug, Deserialize)] +#[allow(unused)] pub struct NetworkConfig { pub gossip_interval_seconds: u64, pub peers_per_round: u32, - pub default_peers: Vec, + pub init_peers: Vec, pub registry_url: String, } #[derive(Debug, Deserialize)] +#[allow(unused)] pub struct PrivacyConfig { pub level: String, pub allow_custom_fields: bool, } #[derive(Debug, Deserialize)] +#[allow(unused)] pub struct WatchersConfig { pub ufw_log_path: PathBuf, pub fail2ban_log_path: PathBuf, pub suricata_log_path: PathBuf, pub zeek_log_dir: PathBuf, pub clamav_scan_dir: PathBuf, - pub virustotal_api_key: String, pub nginx_access_log: PathBuf, pub nginx_error_log: PathBuf, pub apache_access_log: PathBuf, pub apache_error_log: PathBuf, + #[serde(skip_serializing)] + pub virustotal_api_key: String, } #[derive(Debug, Deserialize)] -pub struct Config { - pub security: SecurityConfig, +#[allow(unused)] +pub(crate) struct Settings { + pub address: String, + pub port: u16, + pub log_level: String, + pub tls: TLSConfig, pub storage: StorageConfig, pub network: NetworkConfig, pub privacy: PrivacyConfig, pub watchers: WatchersConfig, } -impl Config { - pub fn load() -> Result { +#[derive(Debug, Deserialize)] +#[allow(unused)] +struct Root { + pub default: Settings, +} + +impl Settings { + pub(crate) fn new() -> Result { let config = config::Config::builder() - .add_source(config::File::with_name("config/default")) + .add_source(config::File::with_name("config/mesh")) .add_source(config::Environment::with_prefix("DTIM")) .build()?; - config.try_deserialize() + let root = config.try_deserialize::()?; + Ok(root.default) } }