Browse Source

Change the template engine to MiniJinja

pull/99/head
Jan Christian Grünhage 11 months ago
parent
commit
7567e7566c
No known key found for this signature in database GPG Key ID: EEC1170CE56FA2ED
  1. 1
      CHANGELOG.md
  2. 23
      Cargo.lock
  3. 2
      acme_common/Cargo.toml
  4. 4
      acme_common/src/error.rs
  5. 2
      acmed/Cargo.toml
  6. 2
      acmed/build.rs
  7. 38
      acmed/config/default_hooks.toml
  8. 42
      acmed/src/template.rs
  9. 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
- The minimum supported Rust version (MSRV) is now 1.64.
- Manual (and badly designed) threads have been replaced by async.
- Randomized early delay, for spacing out renewals when dealing with a lot of certificates.
- Replaced the template engine TinyTemplate with MiniJinja.
## [0.21.0] - 2022-12-19

23
Cargo.lock

@ -11,6 +11,7 @@ dependencies = [
"env_logger",
"glob",
"log",
"minijinja",
"native-tls",
"nix",
"openssl",
@ -19,7 +20,6 @@ dependencies = [
"reqwest",
"serde_json",
"syslog",
"tinytemplate",
"toml",
]
@ -35,13 +35,13 @@ dependencies = [
"futures",
"glob",
"log",
"minijinja",
"nix",
"nom",
"rand",
"reqwest",
"serde",
"serde_json",
"tinytemplate",
"tokio",
"toml",
]
@ -797,6 +797,15 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minijinja"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40870a194358132836de5c67e5038c279de3bff7a05f5da201ed13f6064b979"
dependencies = [
"serde",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@ -1387,16 +1396,6 @@ dependencies = [
"time-core",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "tinyvec"
version = "1.6.0"

2
acme_common/Cargo.toml

@ -24,6 +24,7 @@ daemonize = "0.5"
env_logger = "0.10"
glob = "0.3"
log = "0.4"
minijinja = "1.0.3"
native-tls = "0.2"
openssl = { version = "0.10", optional = true }
openssl-sys = { version = "0.9", optional = true }
@ -31,7 +32,6 @@ punycode = "0.4"
reqwest = { version = "0.11.16", default-features = false }
serde_json = "1.0"
syslog = "6.0"
tinytemplate = "1.2"
toml = "0.7"
[target.'cfg(unix)'.dependencies]

4
acme_common/src/error.rs

@ -105,8 +105,8 @@ impl From<glob::PatternError> for Error {
}
}
impl From<tinytemplate::error::Error> for Error {
fn from(error: tinytemplate::error::Error) -> Self {
impl From<minijinja::Error> for Error {
fn from(error: minijinja::Error) -> Self {
format!("template error: {error}").into()
}
}

2
acmed/Cargo.toml

@ -31,11 +31,11 @@ log = "0.4"
nom = { version = "7.0", default-features = false, features = [] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tinytemplate = "1.2"
toml = "0.7"
tokio = { version = "1", features = ["full"] }
rand = "0.8.5"
reqwest = "0.11.16"
minijinja = "1.0.3"
[target.'cfg(unix)'.dependencies]
nix = "0.26"

2
acmed/build.rs

@ -117,7 +117,7 @@ fn set_default_values() {
set_data_path_if_absent!("ACMED_DEFAULT_CERT_DIR", "certs");
set_env_var_if_absent!(
"ACMED_DEFAULT_CERT_FORMAT",
"{ name }_{ key_type }.{ file_type }.{ ext }"
"{{ name }}_{{ key_type }}.{{ file_type }}.{{ ext }}"
);
set_cfg_path_if_absent!("ACMED_DEFAULT_CONFIG_FILE", "acmed.toml");
set_runstate_path_if_absent!("ACMED_DEFAULT_PID_FILE", "acmed.pid");

38
acmed/config/default_hooks.toml

@ -12,7 +12,7 @@
#
# http-01 challenge in "/var/www/{ identifier }/"
# http-01 challenge in "/var/www/{{ identifier }}/"
#
[[hook]]
@ -21,7 +21,7 @@ type = ["challenge-http-01"]
cmd = "mkdir"
args = [
"-m", "0755",
"-p", "{{ if env.HTTP_ROOT }}{ env.HTTP_ROOT }{{ else }}/var/www{{ endif }}/{ identifier }/.well-known/acme-challenge"
"-p", "{{ env.HTTP_ROOT | default('/var/www') }}/{{ identifier }}/.well-known/acme-challenge"
]
allow_failure = true
@ -30,7 +30,7 @@ name = "http-01-echo-echo"
type = ["challenge-http-01"]
cmd = "echo"
args = ["{ proof }"]
stdout = "{{ if env.HTTP_ROOT }}{ env.HTTP_ROOT }{{ else }}/var/www{{ endif }}/{ identifier }/.well-known/acme-challenge/{ file_name }"
stdout = "{{ env.HTTP_ROOT | default('/var/www') }}/{{ identifier }}/.well-known/acme-challenge/{{ file_name }}"
[[hook]]
name = "http-01-echo-chmod"
@ -38,7 +38,7 @@ type = ["challenge-http-01"]
cmd = "chmod"
args = [
"a+r",
"{{ if env.HTTP_ROOT }}{ env.HTTP_ROOT }{{ else }}/var/www{{ endif }}/{ identifier }/.well-known/acme-challenge/{ file_name }"
"{{ env.HTTP_ROOT | default('/var/www') }}/{{ identifier }}/.well-known/acme-challenge/{{ file_name }}"
]
allow_failure = true
@ -48,7 +48,7 @@ type = ["challenge-http-01-clean"]
cmd = "rm"
args = [
"-f",
"{{ if env.HTTP_ROOT }}{ env.HTTP_ROOT }{{ else }}/var/www{{ endif }}/{ identifier }/.well-known/acme-challenge/{ file_name }"
"{{ env.HTTP_ROOT | default('/var/www') }}/{{ identifier }}/.well-known/acme-challenge/{{ file_name }}"
]
allow_failure = true
@ -71,10 +71,10 @@ name = "tls-alpn-01-tacd-start-tcp"
type = ["challenge-tls-alpn-01"]
cmd = "tacd"
args = [
"--pid-file", "{{ if env.TACD_PID_ROOT }}{ env.TACD_PID_ROOT }{{ else }}/run{{ endif }}/tacd_{ identifier }.pid",
"--pid-file", "{{ env.TACD_PID_ROOT | default('/run') }}/tacd_{{ identifier }}.pid",
"--domain", "{ identifier_tls_alpn }",
"--acme-ext", "{ proof }",
"--listen", "{{ if env.TACD_HOST }}{ env.TACD_HOST }{{ else }}{ identifier }{{ endif }}:{{ if env.TACD_PORT }}{ env.TACD_PORT }{{ else }}5001{{ endif }}"
"--listen", "{{ env.TACD_PORT | default('5001') }}"
]
[[hook]]
@ -82,10 +82,10 @@ name = "tls-alpn-01-tacd-start-unix"
type = ["challenge-tls-alpn-01"]
cmd = "tacd"
args = [
"--pid-file", "{{ if env.TACD_PID_ROOT }}{ env.TACD_PID_ROOT }{{ else }}/run{{ endif }}/tacd_{ identifier }.pid",
"--pid-file", "{{ env.TACD_PID_ROOT | default('/run') }}/tacd_{{ identifier }}.pid",
"--domain", "{ identifier_tls_alpn }",
"--acme-ext", "{ proof }",
"--listen", "unix:{{ if env.TACD_SOCK_ROOT }}{ env.TACD_SOCK_ROOT }{{ else }}/run{{ endif }}/tacd_{ identifier }.sock"
"--listen", "unix:{{ env.TACD_SOCK_ROOT | default('/run') }}/tacd_{{ identifier }}.sock"
]
[[hook]]
@ -93,7 +93,7 @@ name = "tls-alpn-01-tacd-kill"
type = ["challenge-tls-alpn-01-clean"]
cmd = "pkill"
args = [
"-F", "{{ if env.TACD_PID_ROOT }}{ env.TACD_PID_ROOT }{{ else }}/run{{ endif }}/tacd_{ identifier }.pid",
"-F", "{{ env.TACD_PID_ROOT | default('/run') }}/tacd_{{ identifier }}.pid",
]
allow_failure = true
@ -102,7 +102,7 @@ name = "tls-alpn-01-tacd-rm"
type = ["challenge-tls-alpn-01-clean"]
cmd = "rm"
args = [
"-f", "{{ if env.TACD_PID_ROOT }}{ env.TACD_PID_ROOT }{{ else }}/run{{ endif }}/tacd_{ identifier }.pid",
"-f", "{{ env.TACD_PID_ROOT | default('/run') }}/tacd_{{ identifier }}.pid",
]
allow_failure = true
@ -125,7 +125,7 @@ type = ["file-pre-create", "file-pre-edit"]
cmd = "git"
args = [
"init",
"{ file_directory }"
"{{ file_directory }}"
]
[[hook]]
@ -133,8 +133,8 @@ name = "git-add"
type = ["file-post-create", "file-post-edit"]
cmd = "git"
args = [
"-C", "{ file_directory }",
"add", "{ file_name }"
"-C", "{{ file_directory }}",
"add", "{{ file_name }}"
]
allow_failure = true
@ -143,12 +143,12 @@ name = "git-commit"
type = ["file-post-create", "file-post-edit"]
cmd = "git"
args = [
"-C", "{ file_directory }",
"-c", "user.name='{{ if env.GIT_USERNAME }}{ env.GIT_USERNAME }{{ else }}ACMEd{{ endif }}'",
"-c", "user.email='{{ if env.GIT_EMAIL }}{ env.GIT_EMAIL }{{ else }}acmed@localhost{{ endif }}'",
"-C", "{{ file_directory }}",
"-c", "user.name='{{ env.GIT_USERNAME | default('ACMEd') }}'",
"-c", "user.email='{{ env.GIT_EMAIL | default('acmed@localhost') }}'",
"commit",
"-m", "{ file_name }",
"--only", "{ file_name }"
"-m", "{{ file_name }}",
"--only", "{{ file_name }}"
]
allow_failure = true

42
acmed/src/template.rs

@ -1,37 +1,23 @@
use acme_common::error::Error;
use minijinja::{value::Value, Environment};
use serde::Serialize;
use serde_json::Value;
use tinytemplate::TinyTemplate;
macro_rules! default_format {
($value: ident, $output: ident) => {{
$output.push_str(&$value.to_string());
Ok(())
}};
}
fn formatter_rev_labels(value: &Value, output: &mut String) -> tinytemplate::error::Result<()> {
match value {
Value::Null => Ok(()),
Value::Bool(v) => default_format!(v, output),
Value::Number(v) => default_format!(v, output),
Value::String(v) => {
let s = v.rsplit('.').collect::<Vec<&str>>().join(".");
output.push_str(&s);
Ok(())
}
_ => Ok(()),
fn formatter_rev_labels(value: Value) -> Result<Value, minijinja::Error> {
if let Some(value) = value.as_str() {
Ok(value.rsplit('.').collect::<Vec<&str>>().join(".").into())
} else {
Ok(value)
}
}
pub fn render_template<T>(template: &str, data: &T) -> Result<String, Error>
pub fn render_template<T>(template: &str, data: &T) -> Result<String, minijinja::Error>
where
T: Serialize,
{
let mut reg = TinyTemplate::new();
reg.add_formatter("rev_labels", formatter_rev_labels);
reg.add_template("reg", template)?;
Ok(reg.render("reg", data)?)
let mut environment = Environment::new();
environment.add_filter("rev_labels", formatter_rev_labels);
environment.add_template("template", template)?;
let template = environment.get_template("template")?;
Ok(template.render(data)?)
}
#[cfg(test)]
@ -51,7 +37,7 @@ mod tests {
foo: String::from("test"),
bar: 42,
};
let tpl = "This is { foo } { bar -} !";
let tpl = "This is {{ foo }} {{ bar -}} !";
let rendered = render_template(tpl, &c);
assert!(rendered.is_ok());
let rendered = rendered.unwrap();
@ -64,7 +50,7 @@ mod tests {
foo: String::from("mx1.example.org"),
bar: 42,
};
let tpl = "{ foo } - { foo | rev_labels }";
let tpl = "{{ foo }} - {{ foo | rev_labels }}";
let rendered = render_template(tpl, &c);
assert!(rendered.is_ok());
let rendered = rendered.unwrap();

48
man/en/acmed.toml.5

@ -122,17 +122,17 @@ Name of the endpoint to use.
Table of environment variables that will be accessible from hooks.
.It Ic file_name_format Ar string
Template used to build the file's name. The template syntax is
.Em TinyTemplate .
.Em MiniJinja .
See the
.Sx STANDARDS
section for a link to the
.Em TinyTemplate
.Em MiniJinja
specifications. If not specified, the value defined in the
.Em endpoint
element, and then the
.Em global
element, is used. Default is
.Dq { name }_{ key_type }.{ file_type }.{ ext } .
.Dq {{ name }}_{{ key_type }}.{{ file_type }}.{{ ext }} .
Possible variables are:
.Bl -tag
.It Ic ext Ar string
@ -424,11 +424,11 @@ and
are considered as template strings whereas
.Em cmd
is not. The template syntax is
.Em TinyTemplate .
.Em MiniJinja .
See the
.Sx STANDARDS
section for a link to the
.Em TinyTemplate
.Em MiniJinja
specifications.
.Pp
The available types and the associated template variable are described below.
@ -604,10 +604,10 @@ and
environment variables.
.It Pa http-01-echo
This hook is designed to solve the http-01 challenge. For this purpose, it will write the proof into
.Pa { env.HTTP_ROOT }/{ identifier }/.well-known/acme-challenge/{ file_name } .
.Pa {{ env.HTTP_ROOT }}/{{ identifier }}/.well-known/acme-challenge/{{ file_name }} .
.Pp
The web server must be configured so the file
.Pa http://{ identifier }/.well-known/acme-challenge/{ file_name }
.Pa http://{{ identifier }}/.well-known/acme-challenge/{{ file_name }}
can be accessed from the CA.
.Pp
If
@ -632,7 +632,7 @@ environment variable (default is 5001).
.Pp
.Xr tacd 8
will store its pid into
.Pa { TACD_PID_ROOT }/tacd_{ identifier }.pid .
.Pa {{ TACD_PID_ROOT }}/tacd_{{ identifier }}.pid .
If
.Ev TACD_PID_ROOT
is not specified, it will be set to
@ -648,7 +648,7 @@ option.
.Pp
.Xr tacd 8
will listen on the unix socket
.Pa { env.TACD_SOCK_ROOT }/tacd_{ identifier }.sock .
.Pa {{ env.TACD_SOCK_ROOT }}/tacd_{{ identifier }}.sock .
If
.Ev TACD_SOCK_ROOT
is not specified, it will be set to
@ -656,7 +656,7 @@ is not specified, it will be set to
.Pp
.Xr tacd 8
will store its pid into
.Pa { TACD_PID_ROOT }/tacd_{ identifier }.pid .
.Pa {{ TACD_PID_ROOT }}/tacd_{{ identifier }}.pid .
If
.Ev TACD_PID_ROOT
is not specified, it will be set to
@ -696,8 +696,8 @@ For example,
and
.Dq 40s20h4h2s
both represents a period of one day and forty-two seconds.
.Sh TEMPLATE FORMATTERS
In addition the the formatters provided by default by TinyTemplate, ACMEd provides the following formatters:
.Sh TEMPLATE FILTERS
In addition the the filters provided by default by MiniJinja, ACMEd provides the following filters:
.Bl -tag
.It Pa rev_labels
Reverts the labels of a domain name (eg:
@ -763,7 +763,7 @@ type = ["challenge-http-01"]
cmd = "mkdir"
args = [
"-m", "0755",
"-p", "{{ if env.HTTP_ROOT }}{ env.HTTP_ROOT }{{ else }}/var/www{{ endif }}/{ identifier }/.well-known/acme-challenge"
"-p", "{{ env.HTTP_ROOT | default('/var/www') }}/{{ identifier }}/.well-known/acme-challenge"
]
[[hook]]
@ -771,7 +771,7 @@ name = "http-01-echo-echo"
type = ["challenge-http-01"]
cmd = "echo"
args = ["{ proof }"]
stdout = "{{ if env.HTTP_ROOT }}{ env.HTTP_ROOT }{{ else }}/var/www{{ endif }}/{ identifier }/.well-known/acme-challenge/{ file_name }"
stdout = "{{ env.HTTP_ROOT | default('/var/www') }}/{{ identifier }}/.well-known/acme-challenge/{{ file_name }}"
[[hook]]
name = "http-01-echo-chmod"
@ -779,7 +779,7 @@ type = ["challenge-http-01-clean"]
cmd = "chmod"
args = [
"a+r",
"{{ if env.HTTP_ROOT }}{ env.HTTP_ROOT }{{ else }}/var/www{{ endif }}/{ identifier }/.well-known/acme-challenge/{ file_name }"
"{{ env.HTTP_ROOT | default('/var/www') }}/{{ identifier }}/.well-known/acme-challenge/{{ file_name }}"
]
[[hook]]
@ -788,7 +788,7 @@ type = ["challenge-http-01-clean"]
cmd = "rm"
args = [
"-f",
"{{ if env.HTTP_ROOT }}{ env.HTTP_ROOT }{{ else }}/var/www{{ endif }}/{ identifier }/.well-known/acme-challenge/{ file_name }"
"{{ env.HTTP_ROOT | default('/var/www') }}/{{ identifier }}/.well-known/acme-challenge/{{ file_name }}"
]
.Ed
.Pp
@ -821,12 +821,12 @@ args = [
"-f", "noreply.certs@example.net",
"contact@example.net"
]
stdin_str = """Subject: Certificate renewal {{ if is_success }}succeeded{{ else }}failed{{ endif }} for { identifiers.0 }
stdin_str = """Subject: Certificate renewal {{ 'succeeded' if is_success else 'failed' }} for {{ identifiers.0 }}
The following certificate has {{ if not is_success }}*not* {{ endif }}been renewed.
identifiers: {{ for ident in identifiers }}{{ if not @first }}, {{ endif }}{ ident }{{ endfor }}
key type: { key_type }
status: { status }"""
The following certificate has {{ '' if is_success else '*not* ' }}been renewed.
identifiers: {% for ident in identifiers %}{% if not loop.first %}, {% endif %}{{ ident }}{% endfor %}
key type: {{ key_type }}
status: {{ status }}"""
.Ed
.Sh SEE ALSO
.Xr acmed 8 ,
@ -842,9 +842,9 @@ status: { status }"""
.Re
.It
.Rs
.%A Brook Heisler
.%T TinyTemplate
.%U https://docs.rs/tinytemplate/latest/tinytemplate/syntax/index.html
.%A Armin Ronacher
.%T MiniJinja
.%U https://docs.rs/minijinja/latest/minijinja/syntax/index.html
.Re
.It
.Rs

Loading…
Cancel
Save