Browse Source

Allow to configure environment variables for a given certificate

Sometimes, you want hooks to use different parameters depending on the
certificate. In order to achieve this, it is now now possible to
configure environment variables at certificate scope.
Thanks to this, the default hooks have been rewrote in order to use
cover more use cases.
pull/5/head
Rodolphe Breard 6 years ago
parent
commit
8d9ef17e1c
  1. 1
      CHANGELOG.md
  2. 26
      acmed/config/default_hooks.toml
  3. 7
      acmed/src/certificate.rs
  4. 3
      acmed/src/config.rs
  5. 30
      acmed/src/hooks.rs
  6. 1
      acmed/src/main_event_loop.rs
  7. 8
      acmed/src/storage.rs
  8. 13
      man/en/acmed.toml.5

1
CHANGELOG.md

@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 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.
- Hooks have access to environment variables.
- In the configuration, certificates can define environment variables for the hooks.
- tacd is now able to listen on a unix socket.

26
acmed/config/default_hooks.toml

@ -21,7 +21,7 @@ type = ["challenge-http-01"]
cmd = "mkdir"
args = [
"-m", "0755",
"-p", "/var/www/{{domain}}/.well-known/acme-challenge"
"-p", "{{#if env.HTTP_ROOT}}{{env.HTTP_ROOT}}{{else}}/var/www{{/if}}/{{domain}}/.well-known/acme-challenge"
]
[[hook]]
@ -29,7 +29,7 @@ name = "http-01-echo-echo"
type = ["challenge-http-01"]
cmd = "echo"
args = ["{{proof}}"]
stdout = "/var/www/{{domain}}/.well-known/acme-challenge/{{file_name}}"
stdout = "{{#if env.HTTP_ROOT}}{{env.HTTP_ROOT}}{{else}}/var/www{{/if}}/{{domain}}/.well-known/acme-challenge/{{file_name}}"
[[hook]]
name = "http-01-echo-chmod"
@ -37,7 +37,7 @@ type = ["challenge-http-01-clean"]
cmd = "chmod"
args = [
"a+r",
"/var/www/{{domain}}/.well-known/acme-challenge/{{file_name}}"
"{{#if env.HTTP_ROOT}}{{env.HTTP_ROOT}}{{else}}/var/www{{/if}}/{{domain}}/.well-known/acme-challenge/{{file_name}}"
]
[[hook]]
@ -46,11 +46,11 @@ type = ["challenge-http-01-clean"]
cmd = "rm"
args = [
"-f",
"/var/www/{{domain}}/.well-known/acme-challenge/{{file_name}}"
"{{#if env.HTTP_ROOT}}{{env.HTTP_ROOT}}{{else}}/var/www{{/if}}/{{domain}}/.well-known/acme-challenge/{{file_name}}"
]
[[group]]
name = "http-01-echo-var-www"
name = "http-01-echo"
hooks = [
"http-01-echo-mkdir",
"http-01-echo-echo",
@ -68,10 +68,10 @@ name = "tls-alpn-01-tacd-start-tcp"
type = ["challenge-tls-alpn-01"]
cmd = "tacd"
args = [
"--pid-file", "/tmp/tacd_{{domain}}.pid",
"--pid-file", "{{#if env.TACD_PID_ROOT}}{{env.TACD_PID_ROOT}}{{else}}/run{{/if}}/tacd_{{domain}}.pid",
"--domain", "{{domain}}",
"--acme-ext", "{{proof}}",
"--listen", "{{domain}}:5001"
"--listen", "{{#if env.TACD_HOST}}{{env.TACD_HOST}}{{else}}{{domain}}{{/if}}:{{#if env.TACD_PORT}}{{env.TACD_PORT}}{{else}}5001{{/if}}"
]
[[hook]]
@ -79,10 +79,10 @@ name = "tls-alpn-01-tacd-start-unix"
type = ["challenge-tls-alpn-01"]
cmd = "tacd"
args = [
"--pid-file", "/tmp/tacd_{{domain}}.pid",
"--pid-file", "{{#if env.TACD_PID_ROOT}}{{env.TACD_PID_ROOT}}{{else}}/run{{/if}}/tacd_{{domain}}.pid",
"--domain", "{{domain}}",
"--acme-ext", "{{proof}}",
"--listen", "unix:/tmp/tacd_{{domain}}.sock"
"--listen", "unix:{{#if env.TACD_SOCK_ROOT}}{{env.TACD_SOCK_ROOT}}{{else}}/run{{/if}}/tacd_{{domain}}.sock"
]
[[hook]]
@ -90,7 +90,7 @@ name = "tls-alpn-01-tacd-kill"
type = ["challenge-tls-alpn-01-clean"]
cmd = "pkill"
args = [
"-F", "/tmp/tacd_{{domain}}.pid"
"-F", "{{#if env.TACD_PID_ROOT}}{{env.TACD_PID_ROOT}}{{else}}/run{{/if}}/tacd_{{domain}}.pid",
]
[[hook]]
@ -98,7 +98,7 @@ name = "tls-alpn-01-tacd-rm"
type = ["challenge-tls-alpn-01-clean"]
cmd = "rm"
args = [
"-f", "/tmp/tacd_{{domain}}.pid"
"-f", "{{#if env.TACD_PID_ROOT}}{{env.TACD_PID_ROOT}}{{else}}/run{{/if}}/tacd_{{domain}}.pid",
]
[[group]]
@ -138,8 +138,8 @@ type = ["file-post-create", "file-post-edit"]
cmd = "git"
args = [
"-C", "{{file_directory}}",
"-c", "user.name=ACMEd",
"-c", "user.email=acmed@localhost",
"-c", "user.name='{{#if env.GIT_USERNAME}}{{env.GIT_USERNAME}}{{else}}ACMEd{{/if}}'",
"-c", "user.email='{{#if env.GIT_EMAIL}}{{env.GIT_EMAIL}}{{else}}acmed@localhost{{/if}}'",
"commit",
"-m", "{{file_name}}",
"--only", "{{file_name}}"

7
acmed/src/certificate.rs

@ -88,6 +88,7 @@ pub struct Certificate {
pub pk_file_mode: u32,
pub pk_file_owner: Option<String>,
pub pk_file_group: Option<String>,
pub env: HashMap<String, String>,
}
impl fmt::Display for Certificate {
@ -213,7 +214,7 @@ impl Certificate {
HookType::ChallengeTlsAlpn01Clean,
),
};
hooks::call(&hook_data, &self.hooks, hook_type.0)?;
hooks::call(self, &hook_data, hook_type.0)?;
Ok((hook_data, hook_type.1))
}
@ -222,7 +223,7 @@ impl Certificate {
data: &ChallengeHookData,
hook_type: HookType,
) -> Result<(), Error> {
hooks::call(data, &self.hooks, hook_type)
hooks::call(self, data, hook_type)
}
pub fn call_post_operation_hooks(&self, status: &str, is_success: bool) -> Result<(), Error> {
@ -238,7 +239,7 @@ impl Certificate {
is_success,
env: HashMap::new(),
};
hooks::call(&hook_data, &self.hooks, HookType::PostOperation)?;
hooks::call(self, &hook_data, HookType::PostOperation)?;
Ok(())
}
}

3
acmed/src/config.rs

@ -3,6 +3,7 @@ use crate::hooks;
use acme_common::error::Error;
use log::info;
use serde::Deserialize;
use std::collections::HashMap;
use std::fmt;
use std::fs::{self, File};
use std::io::prelude::*;
@ -206,6 +207,8 @@ pub struct Certificate {
pub name_format: Option<String>,
pub formats: Option<Vec<String>>,
pub hooks: Vec<String>,
#[serde(default)]
pub env: HashMap<String, String>,
}
impl Certificate {

30
acmed/src/hooks.rs

@ -1,3 +1,4 @@
use crate::certificate::Certificate;
use crate::config::HookType;
use acme_common::error::Error;
use handlebars::Handlebars;
@ -11,14 +12,22 @@ use std::process::{Command, Stdio};
use std::{env, fmt};
pub trait HookEnvData {
fn set_env(&mut self);
fn set_env(&mut self, cert: &Certificate);
}
fn deref<F, G>(t: (&F, &G)) -> (F, G)
where
F: Clone,
G: Clone,
{
((*(t.0)).to_owned(), (*(t.1)).to_owned())
}
macro_rules! imple_hook_data_env {
($t: ty) => {
impl HookEnvData for $t {
fn set_env(&mut self) {
for (key, value) in env::vars() {
fn set_env(&mut self, cert: &Certificate) {
for (key, value) in env::vars().chain(cert.env.iter().map(deref)) {
self.env.insert(key, value);
}
}
@ -90,13 +99,13 @@ macro_rules! get_hook_output {
}};
}
fn call_single<T>(data: &T, hook: &Hook) -> Result<(), Error>
fn call_single<T>(cert: &Certificate, data: &T, hook: &Hook) -> Result<(), Error>
where
T: Clone + HookEnvData + Serialize,
{
debug!("Calling hook: {}", hook.name);
let mut data = (*data).clone();
data.set_env();
data.set_env(cert);
let reg = Handlebars::new();
let mut v = vec![];
let args = match &hook.args {
@ -112,6 +121,7 @@ where
debug!("Hook {}: cmd: {}", hook.name, hook.cmd);
debug!("Hook {}: args: {:?}", hook.name, args);
let mut cmd = Command::new(&hook.cmd)
.envs(cert.env.iter())
.args(args)
.stdout(get_hook_output!(&hook.stdout, reg, &data))
.stderr(get_hook_output!(&hook.stderr, reg, &data))
@ -135,12 +145,16 @@ where
Ok(())
}
pub fn call<T>(data: &T, hooks: &[Hook], hook_type: HookType) -> Result<(), Error>
pub fn call<T>(cert: &Certificate, data: &T, hook_type: HookType) -> Result<(), Error>
where
T: Clone + HookEnvData + Serialize,
{
for hook in hooks.iter().filter(|h| h.hook_type.contains(&hook_type)) {
call_single(data, &hook)?;
for hook in cert
.hooks
.iter()
.filter(|h| h.hook_type.contains(&hook_type))
{
call_single(cert, data, &hook)?;
}
Ok(())
}

1
acmed/src/main_event_loop.rs

@ -40,6 +40,7 @@ impl MainEventLoop {
pk_file_mode: cnf.get_pk_file_mode(),
pk_file_owner: cnf.get_pk_file_user(),
pk_file_group: cnf.get_pk_file_group(),
env: crt.env.to_owned(),
};
certs.push(cert);
}

8
acmed/src/storage.rs

@ -144,9 +144,9 @@ fn write_file(cert: &Certificate, file_type: FileType, data: &[u8]) -> Result<()
let is_new = !path.is_file();
if is_new {
hooks::call(&hook_data, &cert.hooks, HookType::FilePreCreate)?;
hooks::call(cert, &hook_data, HookType::FilePreCreate)?;
} else {
hooks::call(&hook_data, &cert.hooks, HookType::FilePreEdit)?;
hooks::call(cert, &hook_data, HookType::FilePreEdit)?;
}
trace!("Writing file {:?}", path);
@ -168,9 +168,9 @@ fn write_file(cert: &Certificate, file_type: FileType, data: &[u8]) -> Result<()
}
if is_new {
hooks::call(&hook_data, &cert.hooks, HookType::FilePostCreate)?;
hooks::call(cert, &hook_data, HookType::FilePostCreate)?;
} else {
hooks::call(&hook_data, &cert.hooks, HookType::FilePostEdit)?;
hooks::call(cert, &hook_data, HookType::FilePostEdit)?;
}
Ok(())
}

13
man/en/acmed.toml.5

@ -141,6 +141,8 @@ Array of table representing a certificate that will be requested to a CA.
Name of the account to use.
.It Ic endpoint Ar string
Name of the endpoint to use.
.It Ic env Ar table
Table of environment variables that will be accessible from hooks.
.It Ic domains Ar array
Array of tables listing the domains that should be included in the certificate along with the challenge to use for each one.
.Bl -tag
@ -410,7 +412,7 @@ type = ["challenge-http-01"]
cmd = "mkdir"
args = [
"-m", "0755",
"-p", "/var/www/{{domain}}/.well-known/acme-challenge"
"-p", "{{%if env.HTTP_ROOT}}{{env.HTTP_ROOT}}{{else}}/var/www{{/if}}/{{domain}}/.well-known/acme-challenge"
]
[[hook]]
@ -418,7 +420,7 @@ name = "http-01-echo-echo"
type = ["challenge-http-01"]
cmd = "echo"
args = ["{{proof}}"]
stdout = "/var/www/{{domain}}/.well-known/acme-challenge/{{file_name}}"
stdout = "{{%if env.HTTP_ROOT}}{{env.HTTP_ROOT}}{{else}}/var/www{{/if}}/{{domain}}/.well-known/acme-challenge/{{file_name}}"
[[hook]]
name = "http-01-echo-chmod"
@ -426,7 +428,7 @@ type = ["challenge-http-01-clean"]
cmd = "chmod"
args = [
"a+r",
"/var/www/{{domain}}/.well-known/acme-challenge/{{file_name}}"
"{{%if env.HTTP_ROOT}}{{env.HTTP_ROOT}}{{else}}/var/www{{/if}}/{{domain}}/.well-known/acme-challenge/{{file_name}}"
]
[[hook]]
@ -435,7 +437,7 @@ type = ["challenge-http-01-clean"]
cmd = "rm"
args = [
"-f",
"/var/www/{{domain}}/.well-known/acme-challenge/{{file_name}}"
"{{%if env.HTTP_ROOT}}{{env.HTTP_ROOT}}{{else}}/var/www{{/if}}/{{domain}}/.well-known/acme-challenge/{{file_name}}"
]
.Ed
.Pp
@ -452,7 +454,8 @@ hooks = [
[[certificate]]
# Some fields omitted
hooks = ["http-01-echo-var-www"]
hooks = ["http-01-echo"]
env.HTTP_ROOT = "/srv/http"
.Ed
.Pp

Loading…
Cancel
Save