5 Commits

  1. 8
      src/builder.rs
  2. 13
      src/command_runner.rs
  3. 13
      src/locator.rs
  4. 4
      src/setup/util.rs
  5. 16
      src/symbols/acme/cert.rs
  6. 2
      src/symbols/cron.rs
  7. 7
      src/symbols/tls/csr.rs
  8. 4
      src/symbols/user.rs
  9. 4
      tests/setup.rs

8
src/builder.rs

@ -1,9 +1,9 @@
use crate::command_runner::{SetuidCommandRunner, StdCommandRunner}; use crate::command_runner::{SetuidCommandRunner, StdCommandRunner};
use crate::resources::{ use crate::resources::{
AcmeAccountKey, AcmeChallengesDir, AcmeChallengesNginxSnippet, AcmeUser, Cert,
CertChain, Cron, Csr, DefaultServer, Dir, File, GitCheckout, Key, KeyAndCertBundle,
LoadedDirectory, MariaDbDatabase, MariaDbUser, NpmInstall, Owner, PhpFpmPool, PostgresqlDatabase,
Resource, ServeCustom, ServePhp, ServeRedir, ServeService, ServeStatic, StoredDirectory,
AcmeAccountKey, AcmeChallengesDir, AcmeChallengesNginxSnippet, AcmeUser, Cert, CertChain, Cron,
Csr, DefaultServer, Dir, File, GitCheckout, Key, KeyAndCertBundle, LoadedDirectory,
MariaDbDatabase, MariaDbUser, NpmInstall, Owner, PhpFpmPool, PostgresqlDatabase, Resource,
ServeCustom, ServePhp, ServeRedir, ServeService, ServeStatic, StoredDirectory,
SystemdSocketService, User, UserForDomain, WordpressPlugin, WordpressTranslation, SystemdSocketService, User, UserForDomain, WordpressPlugin, WordpressTranslation,
}; };
use crate::storage::SimpleStorage; use crate::storage::SimpleStorage;

13
src/command_runner.rs

@ -29,10 +29,19 @@ pub fn is_success(res: Result<Output, impl Error + 'static>) -> Result<Output, B
check_success(res?) check_success(res?)
} }
pub fn get_output(output: Output) -> Result<Vec<u8>, Box<dyn Error>> {
pub fn get_stdout(output: Output) -> Result<Vec<u8>, Box<dyn Error>> {
Ok(check_success(output)?.stdout) Ok(check_success(output)?.stdout)
} }
pub fn get_stderr_or_stdout(output: Output) -> Result<Vec<u8>, Box<dyn Error>> {
let output = check_success(output)?;
Ok(if output.stderr.is_empty() {
output.stdout
} else {
output.stderr
})
}
#[async_trait(?Send)] #[async_trait(?Send)]
pub trait CommandRunner { pub trait CommandRunner {
async fn run<'a>(&self, program: &str, args: &'a [&'a OsStr], input: &[u8]) -> IoResult<Output>; async fn run<'a>(&self, program: &str, args: &'a [&'a OsStr], input: &[u8]) -> IoResult<Output>;
@ -46,7 +55,7 @@ pub trait CommandRunner {
args: &'a [&'a OsStr], args: &'a [&'a OsStr],
) -> Result<Vec<u8>, Box<dyn Error>> { ) -> Result<Vec<u8>, Box<dyn Error>> {
let output = self.run_with_args(program, args).await?; let output = self.run_with_args(program, args).await?;
get_output(output)
get_stdout(output)
} }
async fn run_successfully<'a>( async fn run_successfully<'a>(
&self, &self,

13
src/locator.rs

@ -3,10 +3,10 @@ use crate::artifacts::{
UserName as UserNameArtifact, UserName as UserNameArtifact,
}; };
use crate::resources::{ use crate::resources::{
AcmeAccountKey, AcmeChallengesDir, AcmeChallengesNginxSnippet, AcmeUser, Cert,
CertChain, Cron, Csr, DefaultServer, Dir, File, GitCheckout, Key, KeyAndCertBundle,
LoadedDirectory, MariaDbDatabase, MariaDbUser, NpmInstall, Owner, PhpFpmPool, PostgresqlDatabase,
Resource, ServeCustom, ServePhp, ServeRedir, ServeService, ServeStatic, StoredDirectory,
AcmeAccountKey, AcmeChallengesDir, AcmeChallengesNginxSnippet, AcmeUser, Cert, CertChain, Cron,
Csr, DefaultServer, Dir, File, GitCheckout, Key, KeyAndCertBundle, LoadedDirectory,
MariaDbDatabase, MariaDbUser, NpmInstall, Owner, PhpFpmPool, PostgresqlDatabase, Resource,
ServeCustom, ServePhp, ServeRedir, ServeService, ServeStatic, StoredDirectory,
SystemdSocketService, User, UserForDomain, WordpressPlugin, WordpressTranslation, SystemdSocketService, User, UserForDomain, WordpressPlugin, WordpressTranslation,
}; };
use crate::to_artifact::ToArtifact; use crate::to_artifact::ToArtifact;
@ -198,7 +198,10 @@ impl<P: Policy> ResourceLocator<AcmeUser> for DefaultLocator<P> {
fn locate(_resource: &AcmeUser) -> (<AcmeUser as Resource>::Artifact, Self::Prerequisites) { fn locate(_resource: &AcmeUser) -> (<AcmeUser as Resource>::Artifact, Self::Prerequisites) {
let user_name = P::acme_user(); let user_name = P::acme_user();
let home = P::user_home(&user_name); let home = P::user_home(&user_name);
((UserNameArtifact(user_name.into()), PathArtifact::from(home)), ())
(
(UserNameArtifact(user_name.into()), PathArtifact::from(home)),
(),
)
} }
} }

4
src/setup/util.rs

@ -108,7 +108,9 @@ impl Recorder {
slog_term::CompactFormat::new(decorator).build(), slog_term::CompactFormat::new(decorator).build(),
move |record| record.level().is_at_least(filter_level), move |record| record.level().is_at_least(filter_level),
); );
let Ok(mutex) = Arc::try_unwrap(self.0) else { panic!("cannot unwrap Arc") }; // AsyncRecord does not implement Debug, so we cannot unwrap
let Ok(mutex) = Arc::try_unwrap(self.0) else {
panic!("cannot unwrap Arc")
}; // AsyncRecord does not implement Debug, so we cannot unwrap
for record in mutex.into_inner().unwrap() { for record in mutex.into_inner().unwrap() {
record.log_to(&drain).unwrap(); record.log_to(&drain).unwrap();
} }

16
src/symbols/acme/cert.rs

@ -66,12 +66,18 @@ impl<_C: CommandRunner, C: Borrow<_C>, D: AsRef<str>, P: AsRef<Path>> Symbol for
) )
.await?; .await?;
if output.status.success() if output.status.success()
&& output.stdout
&& (output.stdout
== format!( == format!(
"subject=CN = {}\nCertificate will not expire\n", "subject=CN = {}\nCertificate will not expire\n",
self.domain.as_ref() self.domain.as_ref()
) )
.as_bytes() .as_bytes()
|| output.stdout
== format!(
"subject=CN={}\nCertificate will not expire\n",
self.domain.as_ref()
)
.as_bytes())
{ {
Ok( Ok(
self self
@ -94,12 +100,18 @@ impl<_C: CommandRunner, C: Borrow<_C>, D: AsRef<str>, P: AsRef<Path>> Symbol for
.is_ok(), .is_ok(),
) )
} else if output.status.code() == Some(1) } else if output.status.code() == Some(1)
&& output.stdout
&& (output.stdout
== format!( == format!(
"subject=CN = {}\nCertificate will expire\n", "subject=CN = {}\nCertificate will expire\n",
self.domain.as_ref() self.domain.as_ref()
) )
.as_bytes() .as_bytes()
|| output.stdout
== format!(
"subject=CN={}\nCertificate will expire\n",
self.domain.as_ref()
)
.as_bytes())
{ {
Ok(false) Ok(false)
} else { } else {

2
src/symbols/cron.rs

@ -37,7 +37,7 @@ impl<C: AsRef<[u8]>, U: AsRef<str>, R: CommandRunner> Symbol for Cron<'_, C, U,
.run( .run(
"crontab", "crontab",
args!["-u", self.user.as_ref(), "-",], args!["-u", self.user.as_ref(), "-",],
self.content.as_ref(),
self.content.as_ref(), // input
) )
.await, .await,
)?; )?;

7
src/symbols/tls/csr.rs

@ -1,4 +1,4 @@
use crate::command_runner::CommandRunner;
use crate::command_runner::{get_stderr_or_stdout, CommandRunner};
use crate::symbols::Symbol; use crate::symbols::Symbol;
use async_trait::async_trait; use async_trait::async_trait;
use std::borrow::Borrow; use std::borrow::Borrow;
@ -32,13 +32,14 @@ impl<C: CommandRunner, D: Borrow<str>, K: AsRef<Path>, P: AsRef<Path>> Symbol fo
return Ok(false); return Ok(false);
} }
let output = self
let result = self
.command_runner .command_runner
.get_stderr(
.run_with_args(
"openssl", "openssl",
args!["req", "-in", self.csr_path.as_ref(), "-noout", "-verify",], args!["req", "-in", self.csr_path.as_ref(), "-noout", "-verify",],
) )
.await?; .await?;
let output = get_stderr_or_stdout(result)?;
Ok(output == b"verify OK\n" || output == b"Certificate request self-signature verify OK\n") Ok(output == b"verify OK\n" || output == b"Certificate request self-signature verify OK\n")
} }

4
src/symbols/user.rs

@ -3,8 +3,8 @@ use crate::symbols::Symbol;
use async_trait::async_trait; use async_trait::async_trait;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::error::Error; use std::error::Error;
use tokio::sync::Semaphore;
use std::path::Path; use std::path::Path;
use tokio::sync::Semaphore;
pub type Wait = Lazy<Semaphore>; pub type Wait = Lazy<Semaphore>;
static WAIT: Wait = Lazy::new(|| Semaphore::new(1)); static WAIT: Wait = Lazy::new(|| Semaphore::new(1));
@ -74,6 +74,7 @@ mod test {
let symbol = User { let symbol = User {
user_name: "nonexisting", user_name: "nonexisting",
command_runner: StdCommandRunner, command_runner: StdCommandRunner,
home_path: "/home/nonexisting",
}; };
assert_eq!(run(symbol.target_reached()).unwrap(), false); assert_eq!(run(symbol.target_reached()).unwrap(), false);
} }
@ -83,6 +84,7 @@ mod test {
let symbol = User { let symbol = User {
user_name: "root", user_name: "root",
command_runner: StdCommandRunner, command_runner: StdCommandRunner,
home_path: "/root",
}; };
assert_eq!(run(symbol.target_reached()).unwrap(), true); assert_eq!(run(symbol.target_reached()).unwrap(), true);
} }

4
tests/setup.rs

@ -61,7 +61,7 @@ fn test(
#[test] #[test]
fn can_create_an_acme_user() { fn can_create_an_acme_user() {
let mut result = test(1, |setup| { let mut result = test(1, |setup| {
assert_eq!(&*(run(setup.add(AcmeUser)).unwrap().0).0, "acme");
assert_eq!(((run(setup.add(AcmeUser)).unwrap().0).0).0.as_ref(), "acme");
}); });
let entry = result let entry = result
.pop() .pop()
@ -127,7 +127,7 @@ fn can_create_an_acme_cert() {
.pop() .pop()
.expect("log is empty but should contain one entry"); .expect("log is empty but should contain one entry");
assert_eq!(entry.0, 3, "log entry has wrong level"); assert_eq!(entry.0, 3, "log entry has wrong level");
assert_eq!(entry.1.matches("run_symbol").count(), 19);
assert_eq!(entry.1.matches("run_symbol").count(), 18);
assert_eq!(result.len(), 0, "log has more than one entry"); assert_eq!(result.len(), 0, "log has more than one entry");
} }

Loading…
Cancel
Save