diff --git a/Cargo.lock b/Cargo.lock index 57a2945..318c317 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + [[package]] name = "anyhow" version = "1.0.68" @@ -121,8 +130,10 @@ name = "command_gateway" version = "0.1.0" dependencies = [ "prost", + "regex", "serde", "serde_json", + "serde_yaml", "tokio", "tokio-stream", "tonic", @@ -613,6 +624,8 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ + "aho-corasick", + "memchr", "regex-syntax", ] @@ -674,6 +687,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb06d4b6cdaef0e0c51fa881acb721bed3c924cfaa71d9c94a3b771dfdf6567" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "slab" version = "0.4.7" @@ -939,6 +965,12 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +[[package]] +name = "unsafe-libyaml" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2" + [[package]] name = "uuid" version = "1.2.2" diff --git a/Cargo.toml b/Cargo.toml index ce26a6c..87c3163 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,8 @@ path = "src/interpreter/main.rs" [dependencies] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +serde_yaml = "0.9" +regex = "1.7" uuid = { version = "1.2.2", features = [ "v4", # Lets you generate random UUIDs "fast-rng", # Use a faster (but still sufficiently random) RNG diff --git a/proto/interpreter.proto b/proto/interpreter.proto index dd49f7f..020687a 100644 --- a/proto/interpreter.proto +++ b/proto/interpreter.proto @@ -10,18 +10,16 @@ service Unix { } message AuthorizeRequest { - // identifier of the project - string identifier = 1; - // ssh_keys from ssh agent - string token = 2; - // command like /bin/bash - string command = 3; + // json like argument + string command_arg = 1; // pid - uint32 pid = 4; + uint32 pid = 2; } message AuthorizeResponse { - string session_id = 2; + string session_id = 1; + // json like arguments changed by daemon + string command_arg = 2; } message TerminateRequest { diff --git a/src/command.rs b/src/command.rs index 6c09cd3..e2716ce 100644 --- a/src/command.rs +++ b/src/command.rs @@ -3,11 +3,10 @@ use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize, Debug)] pub struct Command { - pub identifier: String, - pub token: String, pub command: String, pub envs: HashMap, - pub args: Vec + pub args: Vec, + pub metadata: HashMap, } impl Into for Command { @@ -19,4 +18,21 @@ impl Into for Command { command.envs(self.envs); command } +} + +impl From<&str> for Command { + fn from(value: &str) -> Self { + serde_json::from_str(value).unwrap_or_default() + } +} + +impl Default for Command { + fn default() -> Self { + Self { + command: "/bin/nologin".into(), + envs: HashMap::default(), + args: Vec::default(), + metadata: HashMap::default(), + } + } } \ No newline at end of file diff --git a/src/daemon/configuration/mod.rs b/src/daemon/configuration/mod.rs new file mode 100644 index 0000000..ee1412e --- /dev/null +++ b/src/daemon/configuration/mod.rs @@ -0,0 +1,14 @@ +mod whitelist; + +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Default)] +pub struct Configuration { + whitelist: whitelist::Whitelist, +} + +impl Configuration { + pub fn command_allowed(&self, command: &str) -> bool { + self.whitelist == command + } +} \ No newline at end of file diff --git a/src/daemon/configuration/whitelist.rs b/src/daemon/configuration/whitelist.rs new file mode 100644 index 0000000..3e68051 --- /dev/null +++ b/src/daemon/configuration/whitelist.rs @@ -0,0 +1,45 @@ +use regex::Regex; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Default, Debug)] +pub struct Whitelist(Vec); + +impl PartialEq<&str> for Whitelist { + fn eq(&self, other: &&str) -> bool { + self.0.iter().any(|w| { + match Regex::new(w) { + Ok(regex) => regex.is_match(other), + Err(_) => w == other + } + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_should_return_true_if_exact_value() { + let w = Whitelist(vec!["/usr/bin/bash".into()]); + assert_eq!(w, "/usr/bin/bash"); + } + + #[test] + fn test_should_return_false_if_no_exact_value() { + let w = Whitelist(vec!["/usr/bin/bash".into()]); + assert_ne!(w, "/bin/bash"); + } + + #[test] + fn test_should_return_true_if_regex_match() { + let w = Whitelist(vec![r".*?_allow$".into()]); + assert_eq!(w, "/usr/bin/bash_allow"); + } + + #[test] + fn test_should_return_false_if_regex_not_match() { + let w = Whitelist(vec![r".*?_allow$".into()]); + assert_ne!(w, "/usr/bin/bash_not_allowed"); + } +} diff --git a/src/daemon/main.rs b/src/daemon/main.rs index 55d25e9..258d494 100644 --- a/src/daemon/main.rs +++ b/src/daemon/main.rs @@ -3,6 +3,7 @@ use std::sync::Mutex; mod server; mod sessions; +mod configuration; pub(self) static SESSIONS : Mutex> = Mutex::new(Vec::new()); diff --git a/src/daemon/server.rs b/src/daemon/server.rs index 14e9a97..343c9f2 100644 --- a/src/daemon/server.rs +++ b/src/daemon/server.rs @@ -22,6 +22,7 @@ impl Unix for DaemonServer { super::SESSIONS.lock().unwrap().push(session); Ok(Response::new(AuthorizeResponse { + command_arg: request.get_ref().command_arg.clone(), session_id })) } diff --git a/src/interpreter/main.rs b/src/interpreter/main.rs index 799a111..1b2c144 100644 --- a/src/interpreter/main.rs +++ b/src/interpreter/main.rs @@ -11,20 +11,18 @@ async fn main() -> Result<(), Box> { let arg = std::env::args() .skip(1) .last().unwrap(); - let command_arg : libcommand::Command = serde_json::from_str::(&arg) - .unwrap(); let mut client = client::connect().await?; let request = tonic::Request::new(AuthorizeRequest { - identifier: command_arg.identifier.clone(), - token: command_arg.token.clone(), - command: command_arg.command.clone(), + command_arg: arg, pid: std::process::id() }); let response : Response = client.authorize(request).await?; + let command_arg = libcommand::Command::from(response.get_ref().command_arg.as_ref()); + let mut command : std::process::Command = command_arg.into(); let mut child = command.spawn().unwrap(); child.wait().unwrap();