diff --git a/CHANGELOG.md b/CHANGELOG.md index d6cc676..f0a1c89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- The names of both the certificate file and the associated private key can now be configured. + ### Fixed - Configuration files cannot be loaded more than one time, which prevents infinite recursion. diff --git a/acmed/src/config.rs b/acmed/src/config.rs index c681db3..5e3329c 100644 --- a/acmed/src/config.rs +++ b/acmed/src/config.rs @@ -182,6 +182,7 @@ pub struct GlobalOptions { pub certificates_directory: Option, #[serde(default)] pub env: HashMap, + pub file_name_format: Option, pub pk_file_group: Option, pub pk_file_mode: Option, pub pk_file_user: Option, @@ -196,11 +197,19 @@ impl GlobalOptions { None => Ok(Duration::new(crate::DEFAULT_CERT_RENEW_DELAY, 0)), } } + + pub fn get_crt_name_format(&self) -> String { + match &self.file_name_format { + Some(n) => n.to_string(), + None => crate::DEFAULT_CERT_FORMAT.to_string(), + } + } } #[derive(Clone, Deserialize)] #[serde(deny_unknown_fields)] pub struct Endpoint { + pub file_name_format: Option, pub name: String, #[serde(default)] pub rate_limits: Vec, @@ -221,6 +230,16 @@ impl Endpoint { } } + pub fn get_crt_name_format(&self, cnf: &Config) -> String { + match &self.file_name_format { + Some(n) => n.to_string(), + None => match &cnf.global { + Some(g) => g.get_crt_name_format(), + None => crate::DEFAULT_CERT_FORMAT.to_string(), + }, + } + } + fn to_generic( &self, cnf: &Config, @@ -468,10 +487,13 @@ impl Certificate { Ok(name) } - pub fn get_crt_name_format(&self) -> String { + pub fn get_crt_name_format(&self, cnf: &Config) -> Result { match &self.file_name_format { - Some(n) => n.to_string(), - None => crate::DEFAULT_CERT_FORMAT.to_string(), + Some(n) => Ok(n.to_string()), + None => { + let ep = self.do_get_endpoint(cnf)?; + Ok(ep.get_crt_name_format(cnf)) + } } } diff --git a/acmed/src/main.rs b/acmed/src/main.rs index 2bbbc73..a1276e5 100644 --- a/acmed/src/main.rs +++ b/acmed/src/main.rs @@ -27,7 +27,7 @@ pub const DEFAULT_PID_FILE: &str = "/var/run/acmed.pid"; pub const DEFAULT_CONFIG_FILE: &str = "/etc/acmed/acmed.toml"; pub const DEFAULT_ACCOUNTS_DIR: &str = "/etc/acmed/accounts"; pub const DEFAULT_CERT_DIR: &str = "/etc/acmed/certs"; -pub const DEFAULT_CERT_FORMAT: &str = "{{name}}_{{algo}}.{{file_type}}.{{ext}}"; +pub const DEFAULT_CERT_FORMAT: &str = "{{name}}_{{key_type}}.{{file_type}}.{{ext}}"; pub const DEFAULT_SLEEP_TIME: u64 = 3600; pub const DEFAULT_POOL_TIME: u64 = 5000; pub const DEFAULT_CSR_DIGEST: HashFunction = HashFunction::Sha256; diff --git a/acmed/src/main_event_loop.rs b/acmed/src/main_event_loop.rs index acb30fd..30c2c97 100644 --- a/acmed/src/main_event_loop.rs +++ b/acmed/src/main_event_loop.rs @@ -101,7 +101,7 @@ impl MainEventLoop { account_directory: cnf.get_account_dir(), account_name: crt.account.clone(), crt_name: crt_name.clone(), - crt_name_format: crt.get_crt_name_format(), + crt_name_format: crt.get_crt_name_format(&cnf)?, crt_directory: crt.get_crt_dir(&cnf), crt_key_type: key_type.to_string(), cert_file_mode: cnf.get_cert_file_mode(), diff --git a/acmed/src/storage.rs b/acmed/src/storage.rs index 63c6e2f..9a7e5ba 100644 --- a/acmed/src/storage.rs +++ b/acmed/src/storage.rs @@ -3,6 +3,8 @@ use crate::logs::HasLogger; use acme_common::b64_encode; use acme_common::crypto::{KeyPair, X509Certificate}; use acme_common::error::Error; +use handlebars::Handlebars; +use serde::Serialize; use std::collections::HashMap; use std::fmt; use std::fs::{File, OpenOptions}; @@ -77,6 +79,14 @@ impl fmt::Display for FileType { } } +#[derive(Clone, Serialize)] +pub struct CertFileFormat { + pub ext: String, + pub file_type: String, + pub key_type: String, + pub name: String, +} + fn get_file_full_path( fm: &FileManager, file_type: FileType, @@ -94,14 +104,14 @@ fn get_file_full_path( ext = "bin" ), FileType::PrivateKey | FileType::Certificate => { - // TODO: use fm.crt_name_format instead of a string literal - format!( - "{name}_{algo}.{file_type}.{ext}", - name = fm.crt_name, - algo = fm.crt_key_type, - file_type = file_type.to_string(), - ext = "pem" - ) + let fmt_data = CertFileFormat { + key_type: fm.crt_key_type.to_string(), + ext: "pem".into(), + file_type: file_type.to_string(), + name: fm.crt_name.to_owned(), + }; + let reg = Handlebars::new(); + reg.render_template(&fm.crt_name_format, &fmt_data)? } }; let mut path = PathBuf::from(&base_path); diff --git a/man/en/acmed.toml.5 b/man/en/acmed.toml.5 index 1ff67b9..3d3924c 100644 --- a/man/en/acmed.toml.5 +++ b/man/en/acmed.toml.5 @@ -27,7 +27,13 @@ Array of tables describing describing the account holder's contact information. .It Ic mailto Ar string A mailto URI as defined by .Em RFC 6068 . -This URI cannot contains neither "hfields" nor more than one "addr-spec" in the "to" component. +This URI cannot contains neither +.Dq hfields +nor more than one +.Dq addr-spec +in the +.Dq to +component. .El .It Ic env Ar table Table of environment variables that will be accessible from hooks. @@ -110,6 +116,36 @@ Path to the directory where certificates and their associated private keys are s Name of the endpoint to use. .It Ic env Ar table 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 Handlebars . +See the +.Sx STANDARDS +section for a link to the +.Em Handlebars +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}} . +Possible variables are: +.Bl -tag +.It Ic ext Ar string +File extension. Currently, only +.Dq pem +is supported. +.It Ic file_type Ar string +Contains +.Dq pk +for the private key file and +.Dq crt +for the certificate file. +.It Ic key_type Ar string +The certificate's private key type. +.It Ic name Ar string +The certificate's name. +.El .It Ic hooks Ar array Names of hooks that will be called when requesting a new certificate. The hooks are guaranteed to be called sequentially in the declaration order. .It Ic identifiers Ar array @@ -204,6 +240,12 @@ Array of table where each element defines a Certificate Authority .Pq CA which may be used to request certificates. .Bl -tag +.It Cm file_name_format Ar string +Template used to build the file's name. For detailed documentation, see the +.Em file_name_format +directive located in the +.Em certificate +element. .It Cm name Ar string The name the endpoint is registered under. Must be unique. .It Cm rate_limits Ar array @@ -241,6 +283,12 @@ for more details. Specify the directory where the certificates and their associated private keys are stored. .It Ic env Ar table 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. For detailed documentation, see the +.Em file_name_format +directive located in the +.Em certificate +element. .It Cm pk_file_group Ar group_name|group_id Ft string Specify the group who will own newly-created private-key files. See .Xr chown 2