Browse Source

Allow to give a file path in a hook's stdin

There is use-cases where a command's standard input should be filled
with a file's content. In order to stay consistent with the names of the
other fields, `stdin` is now the field which accepts such a path.
`stdin_str` has been created in order to also support the use of a raw
string.
pull/5/head
Rodolphe Breard 6 years ago
parent
commit
4303ed61d7
  1. 2
      CHANGELOG.md
  2. 22
      acmed/src/config.rs
  3. 38
      acmed/src/hooks.rs
  4. 9
      man/en/acmed.toml.5

2
CHANGELOG.md

@ -17,9 +17,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Hooks now have the optional `allow_failure` field.
- In hooks, the `stdin_str` has been added in replacement of the previous `stdin` behavior.
### Changed
- Hooks are now cleaned right after the current challenge has been validated instead of after the certificate's retrieval.
- In hooks, the `stdin` field now refers to the path of the file that should be written into the hook's standard input.
### Fixed
- The http-01-echo hook now correctly sets the file's access rights

22
acmed/src/config.rs

@ -17,6 +17,25 @@ macro_rules! set_cfg_attr {
};
}
fn get_stdin(hook: &Hook) -> Result<hooks::HookStdin, Error> {
match &hook.stdin {
Some(file) => match &hook.stdin_str {
Some(_) => {
let msg = format!(
"{}: A hook cannot have both stdin and stdin_str",
&hook.name
);
Err(msg.into())
}
None => Ok(hooks::HookStdin::File(file.to_string())),
},
None => match &hook.stdin_str {
Some(s) => Ok(hooks::HookStdin::Str(s.to_string())),
None => Ok(hooks::HookStdin::None),
},
}
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
@ -55,7 +74,7 @@ impl Config {
hook_type: hook.hook_type.to_owned(),
cmd: hook.cmd.to_owned(),
args: hook.args.to_owned(),
stdin: hook.stdin.to_owned(),
stdin: get_stdin(&hook)?,
stdout: hook.stdout.to_owned(),
stderr: hook.stderr.to_owned(),
allow_failure: hook
@ -159,6 +178,7 @@ pub struct Hook {
pub cmd: String,
pub args: Option<Vec<String>>,
pub stdin: Option<String>,
pub stdin_str: Option<String>,
pub stdout: Option<String>,
pub stderr: Option<String>,
pub allow_failure: Option<bool>,

38
acmed/src/hooks.rs

@ -8,6 +8,7 @@ use std::collections::hash_map::Iter;
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::io::BufReader;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::{env, fmt};
@ -75,13 +76,20 @@ pub struct FileStorageHookData {
imple_hook_data_env!(FileStorageHookData);
#[derive(Clone, Debug)]
pub enum HookStdin {
File(String),
Str(String),
None,
}
#[derive(Clone, Debug)]
pub struct Hook {
pub name: String,
pub hook_type: Vec<HookType>,
pub cmd: String,
pub args: Option<Vec<String>>,
pub stdin: Option<String>,
pub stdin: HookStdin,
pub stdout: Option<String>,
pub stderr: Option<String>,
pub allow_failure: bool,
@ -131,15 +139,29 @@ where
.stdout(get_hook_output!(&hook.stdout, reg, &data))
.stderr(get_hook_output!(&hook.stderr, reg, &data))
.stdin(match &hook.stdin {
Some(_) => Stdio::piped(),
None => Stdio::null(),
HookStdin::Str(_) | HookStdin::File(_) => Stdio::piped(),
HookStdin::None => Stdio::null(),
})
.spawn()?;
if hook.stdin.is_some() {
let data_in = reg.render_template(&hook.stdin.to_owned().unwrap(), &data)?;
debug!("Hook {}: stdin: {}", hook.name, data_in);
let stdin = cmd.stdin.as_mut().ok_or("stdin not found")?;
stdin.write_all(data_in.as_bytes())?;
match &hook.stdin {
HookStdin::Str(s) => {
let data_in = reg.render_template(&s, &data)?;
debug!("Hook {}: string stdin: {}", hook.name, &data_in);
let stdin = cmd.stdin.as_mut().ok_or("stdin not found")?;
stdin.write_all(data_in.as_bytes())?;
}
HookStdin::File(f) => {
let file_name = reg.render_template(&f, &data)?;
debug!("Hook {}: file stdin: {}", hook.name, &file_name);
let stdin = cmd.stdin.as_mut().ok_or("stdin not found")?;
let file = File::open(&file_name)?;
let buf_reader = BufReader::new(file);
for line in buf_reader.lines() {
let line = format!("{}\n", line?);
stdin.write_all(line.as_bytes())?;
}
}
HookStdin::None => {}
}
// TODO: add a timeout
let status = cmd.wait()?;

9
man/en/acmed.toml.5

@ -116,7 +116,11 @@ The name of the command that will be launched.
.It Ic args Ar array
Array of strings representing the command's arguments.
.It Ic stdin Ar string
String that will be written into the command's standard input.
Path to the file that will be written into the command's standard intput. Mutually exclusive with
.Em stdin_str .
.It Ic stdin_str Ar string
String that will be written into the command's standard input. Mutually exclusive with
.Em stdin .
.It Ic stdout Ar string
Path to the file where the command's standard output if written.
.It Ic stderr Ar string
@ -199,6 +203,7 @@ A hook have a type that will influence both the moment it is called and the avai
When writing a hook, the values of
.Em args ,
.Em stdin ,
.Em stdin_str ,
.Em stdout
and
.Em stderr
@ -549,7 +554,7 @@ args = [
"-f", "noreply.certs@example.net",
"contact@example.net"
]
stdin = """Subject: Certificate renewal {{#if is_success}}succeeded{{else}}failed{{/if}} for {{domains.[0]}}
stdin_str = """Subject: Certificate renewal {{#if is_success}}succeeded{{else}}failed{{/if}} for {{domains.[0]}}
The following certificate has {{#unless is_success}}*not* {{/unless}}been renewed.
domains: {{#each domains}}{{#if @index}}, {{/if}}{{this}}{{/each}}

Loading…
Cancel
Save