Browse Source

Add environment variables to hook templates

pull/5/head
Rodolphe Breard 6 years ago
parent
commit
03850d20e6
  1. 1
      CHANGELOG.md
  2. 4
      acmed/src/certificate.rs
  3. 54
      acmed/src/hooks.rs
  4. 2
      acmed/src/storage.rs
  5. 48
      man/en/acmed.toml.5

1
CHANGELOG.md

@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- ACMEd now displays a warning when the server indicates an error in an order or an authorization. - ACMEd now displays a warning when the server indicates an error in an order or an authorization.
- A configuration file can now include several other files. - A configuration file can now include several other files.
- Hooks have access to environment variables.
- tacd is now able to listen on a unix socket. - tacd is now able to listen on a unix socket.

4
acmed/src/certificate.rs

@ -5,7 +5,7 @@ use crate::storage::{certificate_files_exists, get_certificate};
use acme_common::error::Error; use acme_common::error::Error;
use log::{debug, trace}; use log::{debug, trace};
use openssl::x509::X509; use openssl::x509::X509;
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::fmt; use std::fmt;
use time::{strptime, Duration}; use time::{strptime, Duration};
@ -203,6 +203,7 @@ impl Certificate {
file_name: file_name.to_string(), file_name: file_name.to_string(),
proof: proof.to_string(), proof: proof.to_string(),
is_clean_hook: false, is_clean_hook: false,
env: HashMap::new(),
}; };
let hook_type = match challenge { let hook_type = match challenge {
Challenge::Http01 => (HookType::ChallengeHttp01, HookType::ChallengeHttp01Clean), Challenge::Http01 => (HookType::ChallengeHttp01, HookType::ChallengeHttp01Clean),
@ -235,6 +236,7 @@ impl Certificate {
algorithm: self.algo.to_string(), algorithm: self.algo.to_string(),
status: status.to_string(), status: status.to_string(),
is_success, is_success,
env: HashMap::new(),
}; };
hooks::call(&hook_data, &self.hooks, HookType::PostOperation)?; hooks::call(&hook_data, &self.hooks, HookType::PostOperation)?;
Ok(()) Ok(())

54
acmed/src/hooks.rs

@ -3,37 +3,63 @@ use acme_common::error::Error;
use handlebars::Handlebars; use handlebars::Handlebars;
use log::debug; use log::debug;
use serde::Serialize; use serde::Serialize;
use std::fmt;
use std::collections::HashMap;
use std::fs::File; use std::fs::File;
use std::io::prelude::*; use std::io::prelude::*;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use std::{env, fmt};
#[derive(Serialize)]
pub trait HookEnvData {
fn set_env(&mut self);
}
macro_rules! imple_hook_data_env {
($t: ty) => {
impl HookEnvData for $t {
fn set_env(&mut self) {
for (key, value) in env::vars() {
self.env.insert(key, value);
}
}
}
};
}
#[derive(Clone, Serialize)]
pub struct PostOperationHookData { pub struct PostOperationHookData {
pub domains: Vec<String>, pub domains: Vec<String>,
pub algorithm: String, pub algorithm: String,
pub status: String, pub status: String,
pub is_success: bool, pub is_success: bool,
pub env: HashMap<String, String>,
} }
#[derive(Serialize)]
imple_hook_data_env!(PostOperationHookData);
#[derive(Clone, Serialize)]
pub struct ChallengeHookData { pub struct ChallengeHookData {
pub domain: String, pub domain: String,
pub challenge: String, pub challenge: String,
pub file_name: String, pub file_name: String,
pub proof: String, pub proof: String,
pub is_clean_hook: bool, pub is_clean_hook: bool,
pub env: HashMap<String, String>,
} }
#[derive(Serialize)]
imple_hook_data_env!(ChallengeHookData);
#[derive(Clone, Serialize)]
pub struct FileStorageHookData { pub struct FileStorageHookData {
// TODO: add the current operation (create/edit) // TODO: add the current operation (create/edit)
pub file_name: String, pub file_name: String,
pub file_directory: String, pub file_directory: String,
pub file_path: PathBuf, pub file_path: PathBuf,
pub env: HashMap<String, String>,
} }
imple_hook_data_env!(FileStorageHookData);
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Hook { pub struct Hook {
pub name: String, pub name: String,
@ -64,14 +90,19 @@ macro_rules! get_hook_output {
}}; }};
} }
fn call_single<T: Serialize>(data: &T, hook: &Hook) -> Result<(), Error> {
fn call_single<T>(data: &T, hook: &Hook) -> Result<(), Error>
where
T: Clone + HookEnvData + Serialize,
{
debug!("Calling hook: {}", hook.name); debug!("Calling hook: {}", hook.name);
let mut data = (*data).clone();
data.set_env();
let reg = Handlebars::new(); let reg = Handlebars::new();
let mut v = vec![]; let mut v = vec![];
let args = match &hook.args { let args = match &hook.args {
Some(lst) => { Some(lst) => {
for fmt in lst.iter() { for fmt in lst.iter() {
let s = reg.render_template(fmt, data)?;
let s = reg.render_template(fmt, &data)?;
v.push(s); v.push(s);
} }
v.as_slice() v.as_slice()
@ -82,15 +113,15 @@ fn call_single<T: Serialize>(data: &T, hook: &Hook) -> Result<(), Error> {
debug!("Hook {}: args: {:?}", hook.name, args); debug!("Hook {}: args: {:?}", hook.name, args);
let mut cmd = Command::new(&hook.cmd) let mut cmd = Command::new(&hook.cmd)
.args(args) .args(args)
.stdout(get_hook_output!(&hook.stdout, reg, data))
.stderr(get_hook_output!(&hook.stderr, reg, data))
.stdout(get_hook_output!(&hook.stdout, reg, &data))
.stderr(get_hook_output!(&hook.stderr, reg, &data))
.stdin(match &hook.stdin { .stdin(match &hook.stdin {
Some(_) => Stdio::piped(), Some(_) => Stdio::piped(),
None => Stdio::null(), None => Stdio::null(),
}) })
.spawn()?; .spawn()?;
if hook.stdin.is_some() { if hook.stdin.is_some() {
let data_in = reg.render_template(&hook.stdin.to_owned().unwrap(), data)?;
let data_in = reg.render_template(&hook.stdin.to_owned().unwrap(), &data)?;
debug!("Hook {}: stdin: {}", hook.name, data_in); debug!("Hook {}: stdin: {}", hook.name, data_in);
let stdin = cmd.stdin.as_mut().ok_or("stdin not found")?; let stdin = cmd.stdin.as_mut().ok_or("stdin not found")?;
stdin.write_all(data_in.as_bytes())?; stdin.write_all(data_in.as_bytes())?;
@ -104,7 +135,10 @@ fn call_single<T: Serialize>(data: &T, hook: &Hook) -> Result<(), Error> {
Ok(()) Ok(())
} }
pub fn call<T: Serialize>(data: &T, hooks: &[Hook], hook_type: HookType) -> Result<(), Error> {
pub fn call<T>(data: &T, hooks: &[Hook], hook_type: HookType) -> Result<(), Error>
where
T: Clone + HookEnvData + Serialize,
{
for hook in hooks.iter().filter(|h| h.hook_type.contains(&hook_type)) { for hook in hooks.iter().filter(|h| h.hook_type.contains(&hook_type)) {
call_single(data, &hook)?; call_single(data, &hook)?;
} }

2
acmed/src/storage.rs

@ -6,6 +6,7 @@ use acme_common::error::Error;
use log::trace; use log::trace;
use openssl::pkey::{PKey, Private, Public}; use openssl::pkey::{PKey, Private, Public};
use openssl::x509::X509; use openssl::x509::X509;
use std::collections::HashMap;
use std::fmt; use std::fmt;
use std::fs::{File, OpenOptions}; use std::fs::{File, OpenOptions};
use std::io::{Read, Write}; use std::io::{Read, Write};
@ -138,6 +139,7 @@ fn write_file(cert: &Certificate, file_type: FileType, data: &[u8]) -> Result<()
file_name, file_name,
file_directory, file_directory,
file_path: path.to_path_buf(), file_path: path.to_path_buf(),
env: HashMap::new(),
}; };
let is_new = !path.is_file(); let is_new = !path.is_file();

48
man/en/acmed.toml.5

@ -211,21 +211,23 @@ Invoked when the ownership of a domain must be proved using the
.Em http-01 .Em http-01
challenge. The available template variables are: challenge. The available template variables are:
.Bl -tag -compact .Bl -tag -compact
.It Cm domain Ar string
The domain name whom ownership is currently being validated.
.It Cm challenge Ar string .It Cm challenge Ar string
The name of the challenge type The name of the challenge type
.Aq http-01 . .Aq http-01 .
Mostly used in hooks with multiple types. Mostly used in hooks with multiple types.
.It Cm domain Ar string
The domain name whom ownership is currently being validated.
.It Cm env Ar array
Array containing all the environment variables.
.It Cm file_name Ar string .It Cm file_name Ar string
Name of the file containing the proof. This is not a full path and does not include the Name of the file containing the proof. This is not a full path and does not include the
.Ql .well-known/acme-challenge/ .Ql .well-known/acme-challenge/
prefix. prefix.
.It Cm is_clean_hook Ar bool
False
.It Cm proof Ar string .It Cm proof Ar string
The content of the proof that must be written to The content of the proof that must be written to
.Em file_name . .Em file_name .
.It Cm is_clean_hook Ar bool
False
.El .El
.It Ic challenge-http-01-clean .It Ic challenge-http-01-clean
Invoked once a domain ownership has been proven using the Invoked once a domain ownership has been proven using the
@ -241,20 +243,22 @@ Invoked when the ownership of a domain must be proved using the
.Em dns-01 .Em dns-01
challenge. The available template variables are: challenge. The available template variables are:
.Bl -tag -compact .Bl -tag -compact
.It Cm domain Ar string
The domain name whom ownership is currently being validated.
.It Cm challenge Ar string .It Cm challenge Ar string
The name of the challenge type The name of the challenge type
.Aq dns-01 . .Aq dns-01 .
Mostly used in hooks with multiple types. Mostly used in hooks with multiple types.
.It Cm domain Ar string
The domain name whom ownership is currently being validated.
.It Cm env Ar array
Array containing all the environment variables.
.It Cm is_clean_hook Ar bool
False
.It Cm proof Ar string .It Cm proof Ar string
The content of the proof that must be written to a The content of the proof that must be written to a
.Ql TXT .Ql TXT
entry of the DNS zone for the entry of the DNS zone for the
.Ql _acme-challenge .Ql _acme-challenge
subdomain. subdomain.
.It Cm is_clean_hook Ar bool
False
.El .El
.It Ic challenge-dns-01-clean .It Ic challenge-dns-01-clean
Invoked once a domain ownership has been proven using the Invoked once a domain ownership has been proven using the
@ -270,12 +274,16 @@ Invoked when the ownership of a domain must be proved using the
.Em tls-alpn-01 .Em tls-alpn-01
challenge. The available template variables are: challenge. The available template variables are:
.Bl -tag -compact .Bl -tag -compact
.It Cm domain Ar string
The domain name whom ownership is currently being validated.
.It Cm challenge Ar string .It Cm challenge Ar string
The name of the challenge type The name of the challenge type
.Aq tls-alpn-01 . .Aq tls-alpn-01 .
Mostly used in hooks with multiple types. Mostly used in hooks with multiple types.
.It Cm domain Ar string
The domain name whom ownership is currently being validated.
.It Cm env Ar array
Array containing all the environment variables.
.It Cm is_clean_hook Ar bool
False
.It Cm proof Ar string .It Cm proof Ar string
Plain-text representation of the Plain-text representation of the
.Em acmeIdentifier .Em acmeIdentifier
@ -285,8 +293,6 @@ ALPN extension value.
.Xr acmed 8 .Xr acmed 8
will not generate the certificate itself since it can be done using will not generate the certificate itself since it can be done using
.Xr tacd 8 . .Xr tacd 8 .
.It Cm is_clean_hook Ar bool
False
.El .El
.It Ic challenge-tls-alpn-01-clean .It Ic challenge-tls-alpn-01-clean
Invoked once a domain ownership has been proven using the Invoked once a domain ownership has been proven using the
@ -304,10 +310,12 @@ a non-existent file
.Em created . .Em created .
The available template variables are: The available template variables are:
.Bl -tag -compact .Bl -tag -compact
.It Cm file_name Ar string
Name of the impacted file.
.It Cm env Ar array
Array containing all the environment variables.
.It Cm file_directory Ar string .It Cm file_directory Ar string
Name of the directory where the impacted file is located. Name of the directory where the impacted file is located.
.It Cm file_name Ar string
Name of the impacted file.
.It Cm file_path Ar string .It Cm file_path Ar string
Full path to the impacted file. Full path to the impacted file.
.El .El
@ -338,14 +346,16 @@ type.
.It Ic post-operation .It Ic post-operation
Invoked at the end of the certificate request process. The available template variables are: Invoked at the end of the certificate request process. The available template variables are:
.Bl -tag -compact .Bl -tag -compact
.It Cm domains Ar string
Array containing the domain names included in the requested certificate.
.It Cm algorithm Ar string .It Cm algorithm Ar string
Name of the algorithm used in the certificate. Name of the algorithm used in the certificate.
.It Cm status Ar string
Human-readable status. If the certificate request failed, it contains the error description.
.It Cm domains Ar string
Array containing the domain names included in the requested certificate.
.It Cm env Ar array
Array containing all the environment variables.
.It Cm is_success Ar boolean .It Cm is_success Ar boolean
True if the certificate request is successful. True if the certificate request is successful.
.It Cm status Ar string
Human-readable status. If the certificate request failed, it contains the error description.
.El .El
.El .El
.Sh FILES .Sh FILES
@ -460,7 +470,7 @@ args = [
] ]
stdin = """Subject: Certificate renewal {{#if is_success}}succeeded{{else}}failed{{/if}} for {{domains.[0]}} stdin = """Subject: Certificate renewal {{#if is_success}}succeeded{{else}}failed{{/if}} for {{domains.[0]}}
The following certificate has {{#unless is_success}}*not* {{/if}}been renewed.
The following certificate has {{#unless is_success}}*not* {{/unless}}been renewed.
domains: {{#each domains}}{{#if @index}}, {{/if}}{{this}}{{/each}} domains: {{#each domains}}{{#if @index}}, {{/if}}{{this}}{{/each}}
algorithm: {{algorithm}} algorithm: {{algorithm}}
status: {{status}}""" status: {{status}}"""

Loading…
Cancel
Save