diff --git a/Cargo.toml b/Cargo.toml index 86de97b..658f0a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,4 @@ nonzero_ext = "0.3.0" [dev-dependencies] tempfile = "3" +mockall = "0.11.4" diff --git a/src/builder.rs b/src/builder.rs index 3230844..aa385bc 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -121,7 +121,7 @@ impl ImplementationBuilder> for DefaultBuilder { fn create( resource: &Cert, target: & as Resource>::Artifact, - (csr, root_cert, account_key, challenges_dir, user_name, _): ::Artifact, + (csr, root_cert, account_key, challenges_dir, (user_name, _), _): ::Artifact, ) -> Self::Implementation { CertSymbol::new( resource.0.clone(), @@ -552,13 +552,13 @@ impl ImplementationBuilder> for DefaultBuilder { type Prerequisites = (); fn prerequisites(_resource: &UserForDomain) -> Self::Prerequisites {} - type Implementation = UserSymbol, StdCommandRunner>; + type Implementation = UserSymbol, Rc, StdCommandRunner>; fn create( _resource: &UserForDomain, - (user_name, _home_path): & as Resource>::Artifact, + (user_name, home_path): & as Resource>::Artifact, (): ::Artifact, ) -> Self::Implementation { - UserSymbol::new(user_name.0.clone(), StdCommandRunner) + UserSymbol::new(user_name.0.clone(), home_path.into(), StdCommandRunner) } } @@ -566,13 +566,13 @@ impl ImplementationBuilder for DefaultBuilder { type Prerequisites = (); fn prerequisites(_resource: &User) -> Self::Prerequisites {} - type Implementation = UserSymbol, StdCommandRunner>; + type Implementation = UserSymbol, Rc, StdCommandRunner>; fn create( resource: &User, - (): &::Artifact, + home_path: &::Artifact, (): ::Artifact, ) -> Self::Implementation { - UserSymbol::new(resource.0.clone(), StdCommandRunner) + UserSymbol::new(resource.0.clone(), home_path.into(), StdCommandRunner) } } @@ -594,13 +594,13 @@ impl ImplementationBuilder for DefaultBuilder { type Prerequisites = (); fn prerequisites(_resource: &AcmeUser) -> Self::Prerequisites {} - type Implementation = UserSymbol, StdCommandRunner>; + type Implementation = UserSymbol, Rc, StdCommandRunner>; fn create( _resource: &AcmeUser, - user_name: &::Artifact, + (user_name, home_path): &::Artifact, (): ::Artifact, ) -> Self::Implementation { - UserSymbol::new(user_name.0.clone(), StdCommandRunner) + UserSymbol::new(user_name.0.clone(), home_path.into(), StdCommandRunner) } } @@ -617,7 +617,7 @@ impl ImplementationBuilder for DefaultBuilder { fn create( _resource: &AcmeChallengesDir, target: &::Artifact, - user_name: ::Artifact, + (user_name, _): ::Artifact, ) -> Self::Implementation { ( DirSymbol::new(target.clone_rc()), @@ -658,7 +658,7 @@ impl ImplementationBuilder for DefaultBuilder { fn create( _resource: &AcmeAccountKey, target: &::Artifact, - user_name: ::Artifact, + (user_name, _): ::Artifact, ) -> Self::Implementation { ( KeySymbol::new(StdCommandRunner, target.clone_rc()), @@ -781,7 +781,7 @@ impl ImplementationBuilder> for DefaultBuilder { UserForDomain(resource.0.clone()) } - type Implementation = CronSymbol<'static, Box, Rc, StdCommandRunner>; + type Implementation = CronSymbol<'static, Box<[u8]>, Rc, StdCommandRunner>; fn create( resource: &Cron, (): & as Resource>::Artifact, diff --git a/src/command_runner.rs b/src/command_runner.rs index ec10437..75a1d3b 100644 --- a/src/command_runner.rs +++ b/src/command_runner.rs @@ -1,4 +1,6 @@ use async_trait::async_trait; +#[cfg(test)] +use mockall::mock; use std::error::Error; use std::ffi::OsStr; use std::io::Result as IoResult; @@ -33,30 +35,57 @@ pub fn get_output(output: Output) -> Result, Box> { #[async_trait(?Send)] pub trait CommandRunner { - async fn run(&self, program: &str, args: &[&OsStr], stdin: &[u8]) -> IoResult; + async fn run<'a>(&self, program: &str, args: &'a [&'a OsStr], input: &[u8]) -> IoResult; - async fn run_with_args(&self, program: &str, args: &[&OsStr]) -> IoResult { + async fn run_with_args<'a>(&self, program: &str, args: &'a [&'a OsStr]) -> IoResult { self.run(program, args, b"").await } - async fn get_output(&self, program: &str, args: &[&OsStr]) -> Result, Box> { + async fn get_output<'a>( + &self, + program: &str, + args: &'a [&'a OsStr], + ) -> Result, Box> { let output = self.run_with_args(program, args).await?; get_output(output) } - async fn run_successfully(&self, program: &str, args: &[&OsStr]) -> Result<(), Box> { + async fn run_successfully<'a>( + &self, + program: &str, + args: &'a [&'a OsStr], + ) -> Result<(), Box> { is_success(self.run(program, args, b"").await)?; Ok(()) } - async fn get_stderr(&self, program: &str, args: &[&OsStr]) -> Result, Box> { + async fn get_stderr<'a>( + &self, + program: &str, + args: &'a [&'a OsStr], + ) -> Result, Box> { Ok(is_success(self.run_with_args(program, args).await)?.stderr) } } +#[cfg(test)] +mock! { + pub CommandRunner { + } + #[async_trait(?Send)] + impl CommandRunner for CommandRunner { + async fn run<'a>(&self, program: &str, args: &'a [&'a OsStr], input: &[u8]) -> IoResult; + + async fn run_with_args<'a>(&self, program: &str, args: &'a [&'a OsStr]) -> IoResult; + async fn get_output<'a>(&self, program: &str, args: &'a [&'a OsStr]) -> Result, Box>; + async fn run_successfully<'a>(&self, program: &str, args: &'a [&'a OsStr]) -> Result<(), Box>; + async fn get_stderr<'a>(&self, program: &str, args: &'a [&'a OsStr]) -> Result, Box>; + } +} + #[derive(Debug)] pub struct StdCommandRunner; #[async_trait(?Send)] impl CommandRunner for StdCommandRunner { - async fn run(&self, program: &str, args: &[&OsStr], input: &[u8]) -> IoResult { + async fn run<'a>(&self, program: &str, args: &'a [&'a OsStr], input: &[u8]) -> IoResult { //println!("{} {:?}", program, args); let mut child = Command::new(program) .args(args) @@ -114,7 +143,7 @@ impl Drop for TempSetEnv<'_> { #[async_trait(?Send)] impl> CommandRunner for SetuidCommandRunner { - async fn run(&self, program: &str, args: &[&OsStr], input: &[u8]) -> IoResult { + async fn run<'a>(&self, program: &str, args: &'a [&'a OsStr], input: &[u8]) -> IoResult { let uid = get_user_by_name(self.user_name.as_ref()) .expect("User does not exist") .uid(); diff --git a/src/locator.rs b/src/locator.rs index 2591253..01a6e41 100644 --- a/src/locator.rs +++ b/src/locator.rs @@ -202,8 +202,9 @@ impl ResourceLocator for DefaultLocator

{ impl ResourceLocator for DefaultLocator

{ type Prerequisites = (); fn locate(_resource: &AcmeUser) -> (::Artifact, Self::Prerequisites) { - let acme_user = P::acme_user(); - (UserNameArtifact(acme_user.into()), ()) + let user_name = P::acme_user(); + let home = P::user_home(&user_name); + ((UserNameArtifact(user_name.into()), PathArtifact::from(home)), ()) } } @@ -264,10 +265,11 @@ impl> ResourceLocator> for DefaultLoca } } -impl

ResourceLocator for DefaultLocator

{ +impl ResourceLocator for DefaultLocator

{ type Prerequisites = (); - fn locate(_resource: &User) -> (::Artifact, Self::Prerequisites) { - ((), ()) + fn locate(resource: &User) -> (::Artifact, Self::Prerequisites) { + let home = P::user_home(&resource.0); + ((PathArtifact::from(home)), ()) } } diff --git a/src/resources/mod.rs b/src/resources/mod.rs index 0d06c78..8c56a67 100644 --- a/src/resources/mod.rs +++ b/src/resources/mod.rs @@ -91,7 +91,7 @@ impl Resource for AcmeAccountKey { #[derive(Debug, Hash, PartialEq, Eq)] pub struct AcmeUser; impl Resource for AcmeUser { - type Artifact = UserNameArtifact; + type Artifact = (UserNameArtifact, PathArtifact); } #[derive(Debug, Hash, PartialEq, Eq)] @@ -138,7 +138,7 @@ pub fn get_saved_directory( #[derive(Debug, Hash, PartialEq, Eq)] pub struct User(pub Rc); impl Resource for User { - type Artifact = (); + type Artifact = PathArtifact; } #[derive(Debug, Hash, PartialEq, Eq)] diff --git a/src/setup/cache.rs b/src/setup/cache.rs index 1e28287..895b89b 100644 --- a/src/setup/cache.rs +++ b/src/setup/cache.rs @@ -1,5 +1,4 @@ use super::{Add, AddResult, AddableResource}; -use crate::async_utils::sleep; use crate::resources::{FromArtifact, FromResource}; use async_trait::async_trait; use futures_util::future::{FutureExt, Shared}; @@ -11,9 +10,10 @@ use std::future::Future; use std::hash::Hash; use std::pin::Pin; use std::rc::Rc; -use std::time::Duration; -type ResourceCache = HashMap>>>>; +// FIXME: Switch Error to Rc +type ResourceCache = + HashMap>>>>>; #[derive(Debug)] pub struct Cache { @@ -42,19 +42,17 @@ where async fn add(&self, logger: &Rc, resource: Rc, force_run: bool) -> AddResult { let (storable_resource, weak_resource) = Rs::from_resource(&resource); let mut resources = self.resources.borrow_mut(); - let result = if let Some(future) = resources.remove(&storable_resource) { + let future = if let Some(future) = resources.get(&storable_resource) { assert!( !force_run, "Forcing to run an already-added resource is a logical error" ); - resources.insert(storable_resource, future.clone()); - drop(resources); trace!(logger, "Resource already added"); - Ok(future.await) + future.clone() } else { let inner_weak = Rc::downgrade(&self.inner); let logger_weak = Rc::downgrade(logger); - let future = Box::pin(async move { + let future = (Box::pin(async move { let inner = inner_weak.upgrade().expect("Dangling!"); let logger = logger_weak.upgrade().expect("Dangling!"); let resource = weak_resource.upgrade().expect("Dangling!"); @@ -64,25 +62,92 @@ where result .map(|(t, did_run)| (As::from_artifact(t), did_run)) .map_err(|e| e.to_string()) - }) - .shared(); - let future_clone = future.clone(); - resources.insert( - storable_resource, - (Box::pin(async move { - let result = future_clone.await; - if result.is_err() { - // Step back to give the initial caller time to handle the error before unwrapping - sleep(Duration::from_millis(0)).await; - } - result.unwrap() - }) as Pin>>) - .shared(), - ); - drop(resources); - let result = future.await; - result.map_err(std::convert::Into::into) + }) as Pin>>>) + .shared(); + resources.insert(storable_resource, future.clone()); + future }; - result.map(|(t, did_run)| (t.into_artifact(), did_run)) + drop(resources); + future + .await + .map(|(t, did_run)| (t.into_artifact(), did_run)) + .map_err(std::convert::Into::into) + } +} + +#[cfg(test)] +mod test { + use super::{Add, AddResult, Cache, Logger}; + use crate::async_utils::{join, run}; + use crate::resources::{FromArtifact, FromResource, Resource}; + use async_trait::async_trait; + use std::fmt::Debug; + use std::rc::{Rc, Weak}; + + #[derive(Debug, PartialEq, Eq, Hash)] + struct TestResource(&'static str, T); + impl Resource for TestResource { + type Artifact = (); + } + + #[derive(Debug, Hash, PartialEq, Eq)] + enum Resources { + A(Rc>), + B(Rc>), + } + impl FromResource> for Resources { + fn from_resource( + inner: &Rc>, + ) -> (Self, Weak>) { + (Self::A(Rc::clone(&inner)), Rc::downgrade(&inner)) + } + } + impl FromResource> for Resources { + fn from_resource(inner: &Rc>) -> (Self, Weak>) { + (Self::B(Rc::clone(&inner)), Rc::downgrade(&inner)) + } + } + + #[derive(Clone)] + struct Artifacts; + impl FromArtifact> for Artifacts { + fn from_artifact(_from: ()) -> Self { + Self + } + #[allow(clippy::unused_unit)] + fn into_artifact(self) -> () { + #[allow(clippy::unused_unit)] + () + } + } + + struct Inner; + + #[async_trait(?Send)] + impl Add> for Inner { + async fn add( + &self, + logger: &Rc, + resource: Rc>, + force_run: bool, + ) -> AddResult> { + Ok(((), resource.0 != "reached")) + } + } + + #[test] + fn test() { + let log = Rc::new(slog::Logger::root(slog::Discard, slog::o!())); + + let cache: Cache<_, Resources, Artifacts> = Cache::new(Inner); + run(async { + let reached = cache.add(&log, Rc::new(TestResource("reached", ())), false); + let a = cache.add(&log, Rc::new(TestResource("a", ())), false); + let b = cache.add(&log, Rc::new(TestResource("b", ())), false); + let (reached, a, b) = join!(reached, a, b); + assert_eq!(((), false), reached.unwrap()); + assert_eq!(((), true), a.unwrap()); + assert_eq!(((), true), b.unwrap()); + }) } } diff --git a/src/setup/mod.rs b/src/setup/mod.rs index e90ebf0..e1f3722 100644 --- a/src/setup/mod.rs +++ b/src/setup/mod.rs @@ -191,19 +191,38 @@ mod test { #[derive(Debug, Hash, PartialEq, Eq)] enum Resources { - A(Rc>), - B(Rc>), + A(Rc>), + B(Rc>), + C(Rc>), + D(Rc>), + } + impl FromResource> for Resources { + fn from_resource(inner: &Rc>) -> (Self, Weak>) { + (Self::A(Rc::clone(&inner)), Rc::downgrade(&inner)) + } } impl FromResource> for Resources { fn from_resource( inner: &Rc>, ) -> (Self, Weak>) { - (Self::A(Rc::clone(&inner)), Rc::downgrade(&inner)) + (Self::B(Rc::clone(&inner)), Rc::downgrade(&inner)) } } - impl FromResource> for Resources { - fn from_resource(inner: &Rc>) -> (Self, Weak>) { - (Self::B(Rc::clone(&inner)), Rc::downgrade(&inner)) + impl FromResource> for Resources { + fn from_resource( + inner: &Rc>, + ) -> (Self, Weak>) { + (Self::C(Rc::clone(&inner)), Rc::downgrade(&inner)) + } + } + impl FromResource> for Resources { + fn from_resource( + inner: &Rc>, + ) -> ( + Self, + Weak>, + ) { + (Self::D(Rc::clone(&inner)), Rc::downgrade(&inner)) } } @@ -228,7 +247,78 @@ mod test { } } + #[derive(Debug)] + struct TestSymbol { + result: Result>, + } + impl TestSymbol { + fn new(def: &str) -> Self { + let first_char = def.chars().next().unwrap(); + Self { + result: if first_char == '!' { + Err(def.into()) + } else { + Ok(first_char.is_uppercase()) + }, + } + } + } + #[async_trait(?Send)] + impl Symbol for TestSymbol { + async fn target_reached(&self) -> Result> { + self.result.clone().map_err(|s| s.to_string().into()) + } + async fn execute(&self) -> Result<(), Box> { + Ok(()) + } + } + struct TestImplementationBuilder; + + impl ImplementationBuilder> + for TestImplementationBuilder + { + type Implementation = TestSymbol; + type Prerequisites = (TestResource<(&'static str, &'static str)>, TestResource<()>); + + fn prerequisites( + resource: &TestResource<((&'static str, &'static str), &'static str)>, + ) -> Self::Prerequisites { + ( + TestResource("complex_resource", (resource.1).0), // FIXME: Only one of these can exist + TestResource((resource.1).1, ()), + ) + } + fn create( + resource: &TestResource<((&'static str, &'static str), &'static str)>, + (): &(), + _inputs: ::Artifact, + ) -> Self::Implementation { + TestSymbol::new(resource.0) + } + } + + impl ImplementationBuilder> + for TestImplementationBuilder + { + type Implementation = TestSymbol; + type Prerequisites = (TestResource<()>, TestResource<()>); + + fn prerequisites(resource: &TestResource<(&'static str, &'static str)>) -> Self::Prerequisites { + ( + TestResource((resource.1).0, ()), + TestResource((resource.1).1, ()), + ) + } + fn create( + resource: &TestResource<(&'static str, &'static str)>, + (): &(), + _inputs: ::Artifact, + ) -> Self::Implementation { + TestSymbol::new(resource.0) + } + } + impl ImplementationBuilder> for TestImplementationBuilder { type Implementation = TestSymbol; type Prerequisites = TestResource<()>; @@ -241,35 +331,17 @@ mod test { (): &(), _inputs: ::Artifact, ) -> Self::Implementation { - TestSymbol { - reached: resource.0.chars().next().unwrap().is_uppercase(), - } + TestSymbol::new(resource.0) } } + impl ImplementationBuilder> for TestImplementationBuilder { type Implementation = TestSymbol; type Prerequisites = (); fn prerequisites(_resource: &TestResource<()>) -> Self::Prerequisites {} fn create(resource: &TestResource<()>, (): &(), (): ()) -> Self::Implementation { - TestSymbol { - reached: resource.0.chars().next().unwrap().is_uppercase(), - } - } - } - - #[derive(Debug)] - struct TestSymbol { - reached: bool, - } - - #[async_trait(?Send)] - impl Symbol for TestSymbol { - async fn target_reached(&self) -> Result> { - Ok(self.reached) - } - async fn execute(&self) -> Result<(), Box> { - Ok(()) + TestSymbol::new(resource.0) } } @@ -306,12 +378,28 @@ mod test { }); } + #[test] + fn failing_dependencies_deadlock() { + run(async { + let (count, setup, _) = get_setup(); + assert_eq!( + setup + .add(TestResource("a", (("b", "!x"), "!x"))) + .await + .unwrap_err() + .to_string(), + "!x" + ); + assert_eq!(*count.borrow(), 1); + }); + } + #[test] fn run_reached_symbol() { run(async { let (count, setup, log) = get_setup(); let did_run = setup - .run_symbol(TestSymbol { reached: true }, false) + .run_symbol(TestSymbol { result: Ok(true) }, false) .await .unwrap(); drop(setup); @@ -326,7 +414,7 @@ mod test { run(async { let (count, setup, log) = get_setup(); let did_run = setup - .run_symbol(TestSymbol { reached: false }, false) + .run_symbol(TestSymbol { result: Ok(false) }, false) .await .unwrap(); drop(setup); @@ -335,7 +423,8 @@ mod test { let log = log.release(); assert_eq!(log.len(), 1); assert_eq!(log[0].0, 3); - let re = Regex::new(r"^symbol: TestSymbol \{ reached: false \}\n \w+ \d{1,2} \d{2}:\d{2}:\d{2}.\d{3} INFO run\n$").unwrap(); + let re = Regex::new(r"^symbol: TestSymbol \{ result: Ok\(false\) \}\n \w+ \d{1,2} \d{2}:\d{2}:\d{2}.\d{3} INFO run\n$").unwrap(); + assert!(re.is_match(&log[0].1)); }); } diff --git a/src/symbols/cron.rs b/src/symbols/cron.rs index c0d7008..1152a30 100644 --- a/src/symbols/cron.rs +++ b/src/symbols/cron.rs @@ -10,11 +10,11 @@ pub struct Cron<'r, C, U, R> { command_runner: &'r R, } -impl<'r, U, R> Cron<'r, Box, U, R> { +impl<'r, U, R> Cron<'r, Box<[u8]>, U, R> { pub fn new>(user: U, content: C, command_runner: &'r R) -> Self { Self { user, - content: (String::from(content.as_ref()) + "\n").into(), + content: (String::from(content.as_ref()) + "\n").as_bytes().into(), command_runner, } } diff --git a/src/symbols/git/checkout.rs b/src/symbols/git/checkout.rs index 43be14b..b859425 100644 --- a/src/symbols/git/checkout.rs +++ b/src/symbols/git/checkout.rs @@ -92,63 +92,30 @@ impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef, S: AsRef, B: AsRef>>, - } - #[async_trait(?Send)] - impl CommandRunner for DummyCommandRunner { - async fn run(&self, program: &str, args: &[&OsStr], stdin: &[u8]) -> IoResult { - assert_eq!(program, "git"); - assert_eq!(stdin, b""); - sleep(Duration::from_millis(50)).await; - self - .args - .borrow_mut() - .push(args.iter().map(|a| a.to_os_string()).collect()); - Ok(Output { - status: ExitStatus::from_raw(0), - stdout: vec![], - stderr: vec![], - }) - } - } + use crate::command_runner::MockCommandRunner; + use mockall::Sequence; #[test] fn test() { - let c = DummyCommandRunner { - args: RefCell::new(vec![]), - }; - let checkout: Checkout = - Checkout::new(".", "source", "branch", &c); - let start = Instant::now(); + let mut seq = Sequence::new(); + let mut c = MockCommandRunner::new(); + c.expect_get_output() + .times(1) + .withf(|cmd, args| cmd == "git" && args == ["-C", ".", "rev-list", "-1", "HEAD"]) + .returning(|_, _| Ok("".into())); + c.expect_get_output() + .times(1) + .in_sequence(&mut seq) + .withf(|cmd, args| cmd == "git" && args == ["-C", ".", "fetch", "source", "branch"]) + .returning(|_, _| Ok("".into())); + c.expect_get_output() + .times(1) + .in_sequence(&mut seq) + .withf(|cmd, args| cmd == "git" && args == ["-C", ".", "rev-list", "-1", "FETCH_HEAD"]) + .returning(|_, _| Ok("".into())); + let checkout = Checkout::new(".", "source", "branch", c); assert!(run(checkout.target_reached()).unwrap()); - let end = Instant::now(); - let mut args = c.args.into_inner(); - let first_two_args = &mut args[0..2]; - first_two_args.sort_unstable(); - assert_eq!( - first_two_args, - [ - ["-C", ".", "fetch", "source", "branch"], - ["-C", ".", "rev-list", "-1", "HEAD"], - ] - ); - assert_eq!(args[2], ["-C", ".", "rev-list", "-1", "FETCH_HEAD"]); - - assert!((end - start).as_millis() >= 100); - assert!((end - start).as_millis() < 150); } } diff --git a/src/symbols/tls/csr.rs b/src/symbols/tls/csr.rs index 4af3683..a5894fd 100644 --- a/src/symbols/tls/csr.rs +++ b/src/symbols/tls/csr.rs @@ -2,6 +2,7 @@ use crate::command_runner::CommandRunner; use crate::symbols::Symbol; use async_trait::async_trait; use std::borrow::Borrow; +use std::convert::AsRef; use std::error::Error; use std::path::Path; @@ -25,11 +26,9 @@ impl Csr { } #[async_trait(?Send)] -impl, K: Borrow, P: Borrow> Symbol - for Csr -{ +impl, K: AsRef, P: AsRef> Symbol for Csr { async fn target_reached(&self) -> Result> { - if !self.csr_path.borrow().exists() { + if !self.csr_path.as_ref().exists() { return Ok(false); } @@ -37,10 +36,10 @@ impl, K: Borrow, P: Borrow> Symbol .command_runner .get_stderr( "openssl", - args!["req", "-in", self.csr_path.borrow(), "-noout", "-verify",], + args!["req", "-in", self.csr_path.as_ref(), "-noout", "-verify",], ) .await?; - Ok(output == b"verify OK\n") + Ok(output == b"verify OK\n" || output == b"Certificate request self-signature verify OK\n") } async fn execute(&self) -> Result<(), Box> { @@ -53,9 +52,9 @@ impl, K: Borrow, P: Borrow> Symbol "-new", "-sha256", "-key", - self.key_path.borrow(), + self.key_path.as_ref(), "-out", - self.csr_path.borrow(), + self.csr_path.as_ref(), "-subj", format!("/CN={}", self.domain.borrow()), ], @@ -65,4 +64,68 @@ impl, K: Borrow, P: Borrow> Symbol } #[cfg(test)] -mod test {} +mod test { + use super::{Csr, Symbol}; + use crate::async_utils::run; + use crate::command_runner::MockCommandRunner; + + #[test] + fn test_bookworm_success() { + let mut command_runner = MockCommandRunner::new(); + command_runner + .expect_get_stderr() + .times(1) + .returning(|_, _| Ok("Certificate request self-signature verify OK\n".into())); + let symbol = Csr::new(command_runner, "", "/nonexisting", "/"); // FIXME: Csr path has to be an existing file + run(async { + assert_eq!(symbol.target_reached().await.unwrap(), true); + }); + } + + #[test] + fn test_bookworm_invalid() { + let mut command_runner = MockCommandRunner::new(); + command_runner + .expect_get_stderr() + .times(1) + .returning(|_, _| { + Ok("Unable to load X509 request +40F746B61E7F0000:error:0480006C:PEM routines:get_name:no start line:../crypto/pem/pem_lib.c:763:Expecting: CERTIFICATE REQUEST +" .into()) + }); + let symbol = Csr::new(command_runner, "", "/nonexisting", "/"); // FIXME: Csr path has to be an existing file + run(async { + assert_eq!(symbol.target_reached().await.unwrap(), false); + }); + } + + #[test] + fn test_bullseye_success() { + let mut command_runner = MockCommandRunner::new(); + command_runner + .expect_get_stderr() + .times(1) + .returning(|_, _| Ok("verify OK\n".into())); + let symbol = Csr::new(command_runner, "", "/nonexisting", "/"); // FIXME: Csr path has to be an existing file + run(async { + assert_eq!(symbol.target_reached().await.unwrap(), true); + }); + } + + #[test] + fn test_bullseye_invalid() { + let mut command_runner = MockCommandRunner::new(); + command_runner + .expect_get_stderr() + .times(1) + .returning(|_, _| { + Ok("unable to load X509 request +140032085857600:error:0909006C:PEM routines:get_name:no start line:../crypto/pem/pem_lib.c:745:Expecting: CERTIFICATE REQUEST +" .into()) + }); + let symbol = Csr::new(command_runner, "", "/nonexisting", "/"); // FIXME: Csr path has to be an existing file + run(async { + assert_eq!(symbol.target_reached().await.unwrap(), false); + }); + } +} diff --git a/src/symbols/tls/key.rs b/src/symbols/tls/key.rs index acbb8cc..811d773 100644 --- a/src/symbols/tls/key.rs +++ b/src/symbols/tls/key.rs @@ -30,7 +30,7 @@ impl> Symbol for Key { return Ok(false); } - let stdout = self + let mut stdout = self .command_runner .get_output( "openssl", @@ -44,9 +44,12 @@ impl> Symbol for Key { ], ) .await?; + if stdout.starts_with("RSA ".as_ref()) { + stdout = stdout.split_off(4); + } Ok( stdout.ends_with(b"RSA key ok\n") - && stdout.starts_with(format!("RSA Private-Key: ({} bit, 2 primes)\n", self.bits).as_ref()), + && stdout.starts_with(format!("Private-Key: ({} bit, 2 primes)\n", self.bits).as_ref()), ) } @@ -67,4 +70,213 @@ impl> Symbol for Key { } #[cfg(test)] -mod test {} +mod test { + use super::{Key, Symbol}; + use crate::async_utils::run; + use crate::command_runner::MockCommandRunner; + + #[test] + fn test_bookworm_success() { + let mut command_runner = MockCommandRunner::new(); + command_runner + .expect_get_output() + .times(1) + .returning(|_, _| { + Ok( + "Private-Key: (4096 bit, 2 primes) +modulus: + 00:... +publicExponent: 65537 (0x10001) +privateExponent: + 57:... +prime1: + 00:... +prime2: + 00:... +exponent1: + 2b:... +exponent2: + 0e:... +coefficient: + 43:... +RSA key ok +" + .into(), + ) + }); + let symbol = Key::new(command_runner, "/"); // FIXME: Has to be an existing file + run(async { + assert_eq!(symbol.target_reached().await.unwrap(), true); + }); + } + + #[test] + fn test_bookworm_short_key() { + let mut command_runner = MockCommandRunner::new(); + command_runner + .expect_get_output() + .times(1) + .returning(|_, _| { + Ok( + "Private-Key: (2048 bit, 2 primes) +modulus: + 00:... +publicExponent: 65537 (0x10001) +privateExponent: + 57:... +prime1: + 00:... +prime2: + 00:... +exponent1: + 2b:... +exponent2: + 0e:... +coefficient: + 43:... +RSA key ok +" + .into(), + ) + }); + let symbol = Key::new(command_runner, "/"); // FIXME: Has to be an existing file + run(async { + assert_eq!(symbol.target_reached().await.unwrap(), false); + }); + } + + #[test] + fn test_bookworm_broken_key() { + let mut command_runner = MockCommandRunner::new(); + command_runner.expect_get_output() + .times(1) + .returning(|_, _| Ok("Private-Key: (4096 bit, 2 primes) +modulus: + 00:... +publicExponent: 65537 (0x10001) +privateExponent: + 57:... +prime1: + 00:... +prime2: + 00:... +exponent1: + 2b:... +exponent2: + 0e:... +coefficient: + 43:... +RSA key not ok +40C782E5E77F0000:error:0200007E:rsa routines:rsa_validate_keypair_multiprime:iqmp not inverse of q:../crypto/rsa/rsa_chk.c:196: +".into())); + let symbol = Key::new(command_runner, "/"); // FIXME: Has to be an existing file + run(async { + assert_eq!(symbol.target_reached().await.unwrap(), false); + }); + } + + #[test] + fn test_bullseye_success() { + let mut command_runner = MockCommandRunner::new(); + command_runner + .expect_get_output() + .times(1) + .returning(|_, _| { + Ok( + "RSA Private-Key: (4096 bit, 2 primes) +modulus: + 00:... +publicExponent: 65537 (0x10001) +privateExponent: + 57:... +prime1: + 00:... +prime2: + 00:... +exponent1: + 2b:... +exponent2: + 0e:... +coefficient: + 43:... +RSA key ok +" + .into(), + ) + }); + let symbol = Key::new(command_runner, "/"); // FIXME: Has to be an existing file + run(async { + assert_eq!(symbol.target_reached().await.unwrap(), true); + }); + } + + #[test] + fn test_bullseye_short_key() { + let mut command_runner = MockCommandRunner::new(); + command_runner + .expect_get_output() + .times(1) + .returning(|_, _| { + Ok( + "RSA Private-Key: (2048 bit, 2 primes) +modulus: + 00:... +publicExponent: 65537 (0x10001) +privateExponent: + 57:... +prime1: + 00:... +prime2: + 00:... +exponent1: + 2b:... +exponent2: + 0e:... +coefficient: + 43:... +RSA key ok +" + .into(), + ) + }); + let symbol = Key::new(command_runner, "/"); // FIXME: Has to be an existing file + run(async { + assert_eq!(symbol.target_reached().await.unwrap(), false); + }); + } + + #[test] + fn test_bullseye_broken_key() { + let mut command_runner = MockCommandRunner::new(); + command_runner + .expect_get_output() + .times(1) + .returning(|_, _| { + Ok( + "RSA Private-Key: (4096 bit, 2 primes) +modulus: + 00:... +publicExponent: 65537 (0x10001) +privateExponent: + 57:... +prime1: + 00:... +prime2: + 00:... +exponent1: + 2b:... +exponent2: + 0e:... +coefficient: + 43:... +RSA key error: iqmp not inverse of q +" + .into(), + ) + }); + let symbol = Key::new(command_runner, "/"); // FIXME: Has to be an existing file + run(async { + assert_eq!(symbol.target_reached().await.unwrap(), false); + }); + } +} diff --git a/src/symbols/user.rs b/src/symbols/user.rs index db784a7..c32bec3 100644 --- a/src/symbols/user.rs +++ b/src/symbols/user.rs @@ -4,27 +4,30 @@ use async_trait::async_trait; use once_cell::sync::Lazy; use std::error::Error; use tokio::sync::Semaphore; +use std::path::Path; pub type Wait = Lazy; static WAIT: Wait = Lazy::new(|| Semaphore::new(1)); #[derive(Debug)] -pub struct User { +pub struct User { user_name: U, + home_path: H, command_runner: C, } -impl User { - pub const fn new(user_name: U, command_runner: C) -> Self { +impl User { + pub const fn new(user_name: U, home_path: H, command_runner: C) -> Self { Self { user_name, + home_path, command_runner, } } } #[async_trait(?Send)] -impl, C: CommandRunner> Symbol for User { +impl, H: AsRef, C: CommandRunner> Symbol for User { async fn target_reached(&self) -> Result> { let output = self .command_runner @@ -48,6 +51,8 @@ impl, C: CommandRunner> Symbol for User { args![ // "-m", // Necessary for Fedora, not accepted in Debian "--system", + "--home", + self.home_path.as_ref(), self.user_name.as_ref(), ], )