From c0654ff2e8b11f39225b58c98d6dac87fa2c3117 Mon Sep 17 00:00:00 2001 From: beac0n Date: Mon, 16 Sep 2024 00:14:17 +0200 Subject: [PATCH 1/7] start implementing validating host --- src/client.rs | 27 ++++++++++++++++++++++----- src/data.rs | 17 ++++++++++++----- src/server.rs | 14 +++++++++++--- 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/src/client.rs b/src/client.rs index e36e352..a4ca73b 100644 --- a/src/client.rs +++ b/src/client.rs @@ -2,13 +2,15 @@ use std::fmt::{Debug, Display}; use std::fs; -use std::net::UdpSocket; +use std::net::{SocketAddr, UdpSocket}; use std::path::PathBuf; use openssl::pkey::Private; use openssl::rsa::Rsa; use openssl::version::version; +use std::net::ToSocketAddrs; + use crate::common::{info, PADDING_SIZE, RSA_PADDING}; use crate::data::ServerData; @@ -25,7 +27,7 @@ pub fn send( command: String, deadline: u16, strict: bool, - ip: Option, + source_ip: Option, now: u128, ) -> Result<(), String> { info(format!( @@ -33,8 +35,20 @@ pub fn send( version() )); + let destination_ips = address + .to_socket_addrs() + .map_err(|err| format!("Could not resolve hostname for {address}: {err}"))? + .filter(|a| a.is_ipv4()) + .collect::>(); + + let destination_ip = match destination_ips.first() { + Some(a) => a.ip().to_string(), + None => return Err(format!("Could not find any IPv4 address for {address}")), + }; + let rsa = get_rsa_private(&pem_path)?; - let data_to_encrypt = get_data_to_encrypt(&command, &rsa, deadline, strict, ip, now)?; + let data_to_encrypt = + get_data_to_encrypt(&command, &rsa, deadline, strict, source_ip, destination_ip, now)?; let encrypted_data = encrypt_data(&data_to_encrypt, &rsa)?; // create UDP socket and send the encrypted data to the specified address @@ -95,10 +109,13 @@ fn get_data_to_encrypt( rsa: &Rsa, deadline: u16, strict: bool, - ip: Option, + source_ip: Option, + destination_ip: String, now_ns: u128, ) -> Result, String> { - let data_to_encrypt = ServerData::create(command, deadline, strict, ip, now_ns).serialize()?; + let data_to_encrypt = + ServerData::create(command, deadline, strict, source_ip, destination_ip, now_ns) + .serialize()?; let data_to_encrypt_len = data_to_encrypt.len(); let rsa_size = rsa.size() as usize; if data_to_encrypt_len + PADDING_SIZE > rsa_size { diff --git a/src/data.rs b/src/data.rs index ad22627..5288e69 100644 --- a/src/data.rs +++ b/src/data.rs @@ -25,8 +25,9 @@ pub struct ServerData { pub c: String, // command name #[serde(serialize_with = "serialize", deserialize_with = "deserialize")] pub d: u128, // deadline in ns - pub s: u8, // strict - 0 == false, 1 == true - pub i: Option, // ip address + pub s: u8, // strict -> 0 == false, 1 == true + pub i: Option, // source ip address + pub h: String, // host ip address } impl ServerData { @@ -34,14 +35,16 @@ impl ServerData { command: &str, deadline: u16, strict: bool, - ip: Option, + source_ip: Option, + destination_ip: String, now_ns: u128, ) -> ServerData { ServerData { c: command.to_string(), d: now_ns + (u128::from(deadline) * 1_000_000_000), s: if strict { 1 } else { 0 }, - i: ip, + i: source_ip, + h: destination_ip, } } @@ -61,10 +64,14 @@ impl ServerData { self.s == 1 } - pub fn ip(&self) -> Option { + pub fn source_ip(&self) -> Option { self.i.clone() } + pub fn destination_ip(&self) -> String { + self.h.clone() + } + pub fn deadline(&self) -> u128 { self.d } diff --git a/src/server.rs b/src/server.rs index 1cd9fea..f1898af 100644 --- a/src/server.rs +++ b/src/server.rs @@ -180,17 +180,25 @@ impl Server { Ok((now_ns, data)) if now_ns > data.deadline() => { error(format!("Invalid deadline - now {now_ns} is after {}", data.deadline())) } + Ok((_, data)) if data.destination_ip() != "" => { + // TODO: get IP address from server -> define in config + error(format!("Invalid host IP - expected {}, actual ", data.destination_ip())) + } Ok((_, data)) if self.blocklist.is_blocked(data.deadline()) => { error(format!("Invalid deadline - {} is on blocklist", data.deadline())) } Ok((_, data)) - if data.is_strict() && data.ip().is_some_and(|ip_sent| ip_sent != ip_src) => + if data.is_strict() + && data.source_ip().is_some_and(|ip_sent| ip_sent != ip_src) => { - error(format!("Invalid IP - expected {:?}, actual {ip_src}", data.ip())) + error(format!( + "Invalid source IP - expected {:?}, actual {ip_src}", + data.source_ip() + )) } Ok((now_ns, data)) => { let command_name = String::from(&data.c); - let ip = data.ip().unwrap_or(ip_src); + let ip = data.source_ip().unwrap_or(ip_src); info(format!("Valid data - trying {command_name} with {ip}")); self.send_command(CommanderData { command_name, ip }); From 7daec0ae7868ed09e060a8e59c002709b7b425c1 Mon Sep 17 00:00:00 2001 From: beac0n Date: Mon, 16 Sep 2024 00:14:53 +0200 Subject: [PATCH 2/7] fix compile errors --- src/client.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client.rs b/src/client.rs index a4ca73b..dca4b2a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -178,6 +178,7 @@ mod tests { d: 1725821515000000000, s: 0, i: Some(String::from("192.168.178.123")), + h: String::from("192.168.178.124") } ); } From 160c3af1ae6d7d695ffab7f097f0104a813cb284 Mon Sep 17 00:00:00 2001 From: beac0n Date: Mon, 16 Sep 2024 00:15:34 +0200 Subject: [PATCH 3/7] fix compile errors --- src/client.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client.rs b/src/client.rs index dca4b2a..1ec8d31 100644 --- a/src/client.rs +++ b/src/client.rs @@ -164,6 +164,7 @@ mod tests { 5, false, Some(String::from("192.168.178.123")), + String::from("192.168.178.124"), 1725821510 * 1_000_000_000, ) .serialize() From 4a460a4ce14e39c366019272bcb23d9f073c83d0 Mon Sep 17 00:00:00 2001 From: beac0n Date: Mon, 16 Sep 2024 23:31:24 +0200 Subject: [PATCH 4/7] fix tests and validate host ip against sent host ip --- src/client.rs | 2 +- src/config_server.rs | 5 ++- src/server.rs | 70 +++++++++++++++++++----------- tests/client_send_test.rs | 8 ++-- tests/commander_test.rs | 1 + tests/files/config.toml | 1 + tests/files/config_end_to_end.toml | 1 + tests/integration_test.rs | 2 +- 8 files changed, 57 insertions(+), 33 deletions(-) diff --git a/src/client.rs b/src/client.rs index 1ec8d31..9068439 100644 --- a/src/client.rs +++ b/src/client.rs @@ -171,7 +171,7 @@ mod tests { .unwrap(); let server_data_str = String::from_utf8_lossy(&server_data).to_string(); - assert_eq!(server_data_str, "c=\"some_kind_of_long_but_not_really_that_long_command\"\nd=\"1725821515000000000\"\ns=0\ni=\"192.168.178.123\""); + assert_eq!(server_data_str, "c=\"some_kind_of_long_but_not_really_that_long_command\"\nd=\"1725821515000000000\"\ns=0\ni=\"192.168.178.123\"\nh=\"192.168.178.124\""); assert_eq!( ServerData::deserialize(&server_data).unwrap(), ServerData { diff --git a/src/config_server.rs b/src/config_server.rs index 79c829d..70df8bc 100644 --- a/src/config_server.rs +++ b/src/config_server.rs @@ -18,6 +18,7 @@ pub struct CliServer { #[derive(Debug, Deserialize, PartialEq)] pub struct ConfigServer { pub commands: HashMap, + pub ip: String, #[serde(default = "default_address")] pub address: String, #[serde(default = "default_config_path")] @@ -39,6 +40,7 @@ impl Default for ConfigServer { fn default() -> ConfigServer { ConfigServer { commands: HashMap::new(), + ip: String::from("127.0.0.1"), address: String::from(""), socket_user: String::from(""), socket_group: String::from(""), @@ -74,9 +76,10 @@ mod tests { #[test] fn test_create_deserialize() { assert_eq!( - ConfigServer::deserialize("[commands]").unwrap(), + ConfigServer::deserialize("ip = \"127.0.0.1\"\n[commands]").unwrap(), ConfigServer { commands: HashMap::new(), + ip: String::from("127.0.0.1"), address: default_address(), config_dir: default_config_path(), socket_user: default_socket_user(), diff --git a/src/server.rs b/src/server.rs index f1898af..b7f133f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -4,7 +4,7 @@ use openssl::rsa::Rsa; use openssl::version::version; use std::fs::ReadDir; use std::io::Write; -use std::net::{SocketAddr, UdpSocket}; +use std::net::{IpAddr, SocketAddr, UdpSocket}; use std::os::fd::{FromRawFd, RawFd}; use std::os::unix::net::UnixStream; use std::path::PathBuf; @@ -20,6 +20,7 @@ pub struct Server { rsa: Rsa, socket: UdpSocket, address: String, + ip: String, encrypted_data: Vec, decrypted_data: Vec, socket_path: PathBuf, @@ -50,6 +51,17 @@ impl Server { let address = config.address; let config_dir = resolve_path(&config.config_dir); + let ip_addr = config.ip.parse::().map_err(|e| { + format!("Could not parse configured host IP address {}: {e}", config.ip) + })?; + + if !ip_addr.is_ipv4() { + return Err(format!( + "Only IPv4 Addresses are currently supported, got IP config {}", + ip_addr + )); + } + let pem_path = Self::get_pem_path(&config_dir)?; info(format!( "Creating server, loading public PEM from {pem_path:?}, using {} ...", @@ -61,42 +73,47 @@ impl Server { let rsa = Rsa::public_key_from_pem(&pem_data) .map_err(|e| format!("Could not load public key from {pem_path:?}: {e}"))?; + let socket = Self::create_udp_socket(&address)?; + + let rsa_size = rsa.size() as usize; + let decrypted_data = vec![0; rsa_size]; + let encrypted_data = vec![0; rsa_size]; + + Ok(Server { + rsa, + address, + ip: ip_addr.to_string(), + socket, + decrypted_data, + encrypted_data, + socket_path: get_socket_path(&config_dir), + blocklist: Blocklist::create(&config_dir), + }) + } + + fn create_udp_socket(address: &str) -> Result { let pid = std::process::id().to_string(); - let socket = match env::var("LISTEN_PID") { + match env::var("LISTEN_PID") { Ok(listen_pid) if listen_pid == pid => { info(String::from( "env var LISTEN_PID was set to our PID, creating socket from raw fd ...", )); let fd: RawFd = 3; - unsafe { UdpSocket::from_raw_fd(fd) } + Ok(unsafe { UdpSocket::from_raw_fd(fd) }) } Ok(_) => { info(format!( "env var LISTEN_PID was set, but not to our PID, binding to {address}" )); - UdpSocket::bind(&address) - .map_err(|e| format!("Could not UdpSocket bind {address:?}: {e}"))? + UdpSocket::bind(address) + .map_err(|e| format!("Could not UdpSocket bind {address:?}: {e}")) } Err(_) => { info(format!("env var LISTEN_PID was not set, binding to {address}")); - UdpSocket::bind(&address) - .map_err(|e| format!("Could not UdpSocket bind {address:?}: {e}"))? + UdpSocket::bind(address) + .map_err(|e| format!("Could not UdpSocket bind {address:?}: {e}")) } - }; - - let rsa_size = rsa.size() as usize; - let decrypted_data = vec![0; rsa_size]; - let encrypted_data = vec![0; rsa_size]; - - Ok(Server { - rsa, - address, - socket, - decrypted_data, - encrypted_data, - socket_path: get_socket_path(&config_dir), - blocklist: Blocklist::create(&config_dir), - }) + } } fn get_pem_path(config_dir: &PathBuf) -> Result { @@ -180,10 +197,11 @@ impl Server { Ok((now_ns, data)) if now_ns > data.deadline() => { error(format!("Invalid deadline - now {now_ns} is after {}", data.deadline())) } - Ok((_, data)) if data.destination_ip() != "" => { - // TODO: get IP address from server -> define in config - error(format!("Invalid host IP - expected {}, actual ", data.destination_ip())) - } + Ok((_, data)) if self.ip != data.destination_ip() => error(format!( + "Invalid host IP - expected {}, actual {}", + self.ip, + data.destination_ip() + )), Ok((_, data)) if self.blocklist.is_blocked(data.deadline()) => { error(format!("Invalid deadline - {} is on blocklist", data.deadline())) } diff --git a/tests/client_send_test.rs b/tests/client_send_test.rs index 1633cb4..6ae89d7 100644 --- a/tests/client_send_test.rs +++ b/tests/client_send_test.rs @@ -87,7 +87,7 @@ mod tests { assert_eq!( result.unwrap_err().to_string(), - format!("Could not connect/send data to \"{address}\": invalid port value") + format!("Could not resolve hostname for {address}: invalid port value") ); } @@ -117,7 +117,7 @@ mod tests { assert_eq!( result.unwrap_err().to_string(), format!( - "Could not connect/send data to \"{address}\": \ + "Could not resolve hostname for {address}: \ failed to lookup address information: Name or service not known" ) ); @@ -148,8 +148,8 @@ mod tests { assert_eq!( result.unwrap_err().to_string(), String::from( - "Too much data, must be at most 117 bytes, but was 118 bytes. \ - Reduce command name length or create a bigger RSA key size." + "Too much data, must be at most 117 bytes, but was 132 bytes. \ + Reduce command name length or create a bigger RSA key size." ) ); } diff --git a/tests/commander_test.rs b/tests/commander_test.rs index c78278a..cfdcc07 100644 --- a/tests/commander_test.rs +++ b/tests/commander_test.rs @@ -59,6 +59,7 @@ mod tests { Commander::create_from_path(path), Ok(Commander::create(ConfigServer { address: String::from("127.0.0.1:8080"), + ip: String::from("127.0.0.1"), config_dir: PathBuf::from("tests/conf_dir"), socket_user: String::from("ruroco"), socket_group: String::from("ruroco"), diff --git a/tests/files/config.toml b/tests/files/config.toml index dec4ee8..c066327 100644 --- a/tests/files/config.toml +++ b/tests/files/config.toml @@ -1,4 +1,5 @@ address = "127.0.0.1:8080" +ip = "127.0.0.1" config_dir = "tests/conf_dir" [commands] diff --git a/tests/files/config_end_to_end.toml b/tests/files/config_end_to_end.toml index 84fae1a..79a855b 100644 --- a/tests/files/config_end_to_end.toml +++ b/tests/files/config_end_to_end.toml @@ -1,4 +1,5 @@ address = "127.0.0.1:8080" +ip = "127.0.0.1" config_dir = "/etc/ruroco/" [commands] diff --git a/tests/integration_test.rs b/tests/integration_test.rs index dd15061..6b1bbb8 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -113,7 +113,7 @@ mod tests { thread::spawn(move || { Server::create(ConfigServer { - address, + address: address, config_dir, ..Default::default() }) From da6b87dfcba2373a048fd92e3510f3288f7bb973 Mon Sep 17 00:00:00 2001 From: beac0n Date: Mon, 16 Sep 2024 23:31:56 +0200 Subject: [PATCH 5/7] reformat --- tests/integration_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 6b1bbb8..dd15061 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -113,7 +113,7 @@ mod tests { thread::spawn(move || { Server::create(ConfigServer { - address: address, + address, config_dir, ..Default::default() }) From 9cdb1787621cbf73e5b5d3a7486291a86e4eed10 Mon Sep 17 00:00:00 2001 From: beac0n Date: Wed, 25 Sep 2024 21:08:24 +0200 Subject: [PATCH 6/7] update config and add descriptions --- config/config.toml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/config/config.toml b/config/config.toml index ef2e449..87e2681 100644 --- a/config/config.toml +++ b/config/config.toml @@ -1,4 +1,7 @@ -address = "127.0.0.1:8080" -config_dir = "/etc/ruroco/" +ip = "" # public IP address of your server where this service runs on +address = "127.0.0.1:8080" # address where this service runs on +config_dir = "/etc/ruroco/" # directory where pem files are +socket_user = "ruroco" # user of socket, facilitating communication between server and commander +socket_group = "ruroco" # user group of socket, facilitating communication between server and commander -[commands] +[commands] # commands which are possible to execute From ad837b1ec968ebe42f3c34d7473cfa5aed4ec456 Mon Sep 17 00:00:00 2001 From: beac0n Date: Wed, 25 Sep 2024 21:08:35 +0200 Subject: [PATCH 7/7] remove default values for tests --- tests/files/config.toml | 1 - tests/files/config_end_to_end.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/files/config.toml b/tests/files/config.toml index c066327..c5fba2f 100644 --- a/tests/files/config.toml +++ b/tests/files/config.toml @@ -1,4 +1,3 @@ -address = "127.0.0.1:8080" ip = "127.0.0.1" config_dir = "tests/conf_dir" diff --git a/tests/files/config_end_to_end.toml b/tests/files/config_end_to_end.toml index 79a855b..efee11a 100644 --- a/tests/files/config_end_to_end.toml +++ b/tests/files/config_end_to_end.toml @@ -1,4 +1,3 @@ -address = "127.0.0.1:8080" ip = "127.0.0.1" config_dir = "/etc/ruroco/"