mirror of
https://github.com/rkivalin/certforge.git
synced 2026-10-18 08:00:52 +00:00
Add ACME profile support for IP address certificates
- New per-certificate `profile` field for ACME profile selection - Let's Encrypt requires "shortlived" for IP address certs (~6 day validity) - Profile documented in README with link to LE profiles page Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
0726e829a9
commit
f36b2f8559
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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]]
|
||||
|
||||
+7
-5
@@ -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<Order> {
|
||||
pub async fn create_order(&self, domains: &[String], profile: Option<&str>) -> Result<Order> {
|
||||
let identifiers: Vec<Identifier> = 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)
|
||||
}
|
||||
|
||||
@@ -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<String>,
|
||||
|
||||
#[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"
|
||||
"#;
|
||||
|
||||
|
||||
+1
-1
@@ -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?;
|
||||
|
||||
Reference in New Issue
Block a user