diff --git a/CHANGELOG.md b/CHANGELOG.md index 00d3718..27c2e22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Support for HTTP-01 and TLS-ALPN-01 challenge solvers (standalone server + webroot modes) - IP address certificates (IPv4 and IPv6 SANs) +- ACME profile selection per certificate (e.g., `shortlived` for IP certs on Let's Encrypt) - Per-domain solver assignment via `solvers = [...]` or single `solver = "..."` - Named DNS client connections reusable by solvers and DANE blocks diff --git a/README.md b/README.md index e95f1fc..8c32236 100644 --- a/README.md +++ b/README.md @@ -170,15 +170,21 @@ name = "web" domains = ["example.com", "www.example.com"] solver = "dns" -# Per-domain solver (e.g., mixed domain + IP certificate) +# IP address certificate (requires shortlived profile, ~6 day validity) [[certificate]] name = "dns-server" domains = ["dns.example.com", "203.0.113.1", "2001:db8::1"] solvers = ["dns", "http", "http"] +profile = "shortlived" +renew_before_days = 3 ``` If neither `solver` nor `solvers` is set, the global `default_solver` is used. +### ACME profiles + +Some ACME providers require a specific profile for certain certificate types. Set `profile` on a certificate to select one. For example, Let's Encrypt requires the `shortlived` profile for IP address certificates (see [Let's Encrypt profiles](https://letsencrypt.org/docs/profiles/)). + ### Credential storage Every secret (ACME account key, TSIG keys, TLS private keys) supports two modes: diff --git a/examples/config.toml b/examples/config.toml index 8a92799..0e7ce5d 100644 --- a/examples/config.toml +++ b/examples/config.toml @@ -81,13 +81,15 @@ dns = "main" # references [dns.main] # type = "systemd-reload" # unit = "postfix.service" # -# # Certificate with per-domain solver assignment +# # IP address certificate (requires shortlived profile, ~6 day validity) # [[certificate]] -# name = "mixed" -# domains = ["example.com", "203.0.113.1", "2001:db8::1"] +# name = "dns-server" +# domains = ["dns.example.com", "203.0.113.1", "2001:db8::1"] # solvers = ["dns", "http", "http"] -# key_path = "/etc/certforge/keys/mixed.key" -# cert_path = "/etc/certforge/certs/mixed.pem" +# profile = "shortlived" +# renew_before_days = 3 +# key_path = "/etc/certforge/keys/dns-server.key" +# cert_path = "/etc/certforge/certs/dns-server.pem" # # # Simple certificate using default_solver # [[certificate]] diff --git a/src/acme.rs b/src/acme.rs index 94dbedb..c738465 100644 --- a/src/acme.rs +++ b/src/acme.rs @@ -99,7 +99,7 @@ impl AcmeClient { } /// Create a new ACME order for the given domains/IPs. - pub async fn create_order(&self, domains: &[String]) -> Result { + pub async fn create_order(&self, domains: &[String], profile: Option<&str>) -> Result { let identifiers: Vec = domains .iter() .map(|d| { @@ -111,10 +111,12 @@ impl AcmeClient { }) .collect(); - let order = self - .account - .new_order(&NewOrder::new(&identifiers)) - .await?; + let mut new_order = NewOrder::new(&identifiers); + if let Some(profile) = profile { + new_order = new_order.profile(profile); + } + + let order = self.account.new_order(&new_order).await?; Ok(order) } diff --git a/src/config.rs b/src/config.rs index 3302774..f0954d1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -123,6 +123,11 @@ pub struct CertificateConfig { pub cert_path: PathBuf, + /// ACME profile to use for the order. + /// Some providers require a specific profile for certain certificate types + /// (e.g., Let's Encrypt requires "shortlived" for IP address certificates). + pub profile: Option, + #[serde(default = "default_renew_before_days")] pub renew_before_days: u32, @@ -493,6 +498,7 @@ name = "mixed" domains = ["example.com", "203.0.113.1"] key_path = "/etc/certforge/keys/mixed.key" cert_path = "/etc/certforge/certs/mixed.pem" +profile = "shortlived" solvers = ["dns", "http"] "#; @@ -640,6 +646,7 @@ name = "ip" domains = ["203.0.113.1"] key_path = "/etc/certforge/keys/ip.key" cert_path = "/etc/certforge/certs/ip.pem" +profile = "shortlived" solver = "dns" "#; diff --git a/src/renew.rs b/src/renew.rs index d3b123f..96fe440 100644 --- a/src/renew.rs +++ b/src/renew.rs @@ -193,7 +193,7 @@ async fn renew_certificate( // Create ACME order tracing::debug!(name = %cert_config.name, domains = ?cert_config.domains, "creating ACME order"); - let mut order = acme.create_order(&cert_config.domains).await?; + let mut order = acme.create_order(&cert_config.domains, cert_config.profile.as_deref()).await?; // Collect challenges let challenges = AcmeClient::collect_challenges(&mut order, &desired_types).await?;