Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ target/
# testing credentials
ca.key
ca.srl

# environment variables
.env
1 change: 1 addition & 0 deletions config/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mesh.toml
26 changes: 15 additions & 11 deletions config/default.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
54 changes: 29 additions & 25 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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;

Expand All @@ -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<rustls::ServerConfig> {
let client_auth = if let Some(auth) = &config.security.ca_path {
fn make_server_config(settings: &settings::Settings) -> Arc<rustls::ServerConfig> {
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 {
Expand All @@ -57,8 +58,8 @@ fn make_server_config(config: &Config) -> Arc<rustls::ServerConfig> {
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()
Expand All @@ -73,24 +74,31 @@ fn make_server_config(config: &Config) -> Arc<rustls::ServerConfig> {
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
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(),
)?;
Comment on lines +83 to 86

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Handle potential log level parsing failure.

The code uses unwrap() on the log level parsing, which could cause a panic if the configuration contains an invalid log level string.

let logger = logging::EncryptedLogger::new(
    settings.storage.encrypted_logs_path.clone(),
    key_mgr.clone(),
-   LevelFilter::from_str(&settings.log_level).unwrap(),
+   LevelFilter::from_str(&settings.log_level).unwrap_or_else(|_| {
+       log::warn!("Invalid log level: {}, defaulting to Info", settings.log_level);
+       LevelFilter::Info
+   }),
)?;
🤖 Prompt for AI Agents
In src/main.rs around lines 83 to 86, the code uses unwrap() on the log level
parsing which can cause a panic if the log level string is invalid. Replace
unwrap() with proper error handling by matching or using a method like expect
with a clear error message, or returning a Result with an error if parsing
fails. This will prevent the program from panicking on invalid log level
configurations.


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
Expand All @@ -99,16 +107,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
.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,
Expand All @@ -130,7 +134,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
{
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 =
Expand Down
30 changes: 23 additions & 7 deletions src/node.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
config::PrivacyConfig,
settings::PrivacyConfig,
crypto::MeshIdentity,
logging::EncryptedLogger,
models::{PrivacyLevel, ThreatIndicator, TlpLevel},
Expand Down Expand Up @@ -132,20 +132,36 @@ 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<String>,
}

impl NodePeer {
pub fn new(id: String, endpoint: String) -> Self {
NodePeer { id, endpoint }
}

pub fn get_id(&self) -> &str {
&self.id
}

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
}
}
43 changes: 31 additions & 12 deletions src/config.rs → src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PathBuf>,
pub tls_cert_path: PathBuf,
pub tls_key_path: PathBuf,
pub ca: Option<PathBuf>,
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<NodePeer>,
pub init_peers: Vec<NodePeer>,
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<Self, config::ConfigError> {
#[derive(Debug, Deserialize)]
#[allow(unused)]
struct Root {
pub default: Settings,
}

impl Settings {
pub(crate) fn new() -> Result<Settings, config::ConfigError> {
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::<Root>()?;
Ok(root.default)
}
}
Loading