nixos/acme: add compat for IP SANs to minica fallback (#479212)

This commit is contained in:
Franz Pletz
2026-03-22 11:38:37 +00:00
committed by GitHub
3 changed files with 30 additions and 5 deletions
+18 -4
View File
@@ -20,6 +20,16 @@ let
mkAccountHash = acmeServer: data: mkHash "${toString acmeServer} ${data.keyType} ${data.email}";
accountDirRoot = "/var/lib/acme/.lego/accounts/";
isIP =
address:
let
isIPv6 = lib.length (lib.splitString ":" address) > 2;
isIPv4 = (lib.match "^([0-9]+\\.){3}[0-9]+$" address) != null;
in
isIPv6 || isIPv4;
isDomainName = name: !isIP name;
# Lockdir is acme-setup.service's RuntimeDirectory.
# Since that service is a oneshot with RemainAfterExit,
# the folder will exist during all renewal services.
@@ -389,10 +399,14 @@ let
exit 0
fi
minica \
--ca-key ca/key.pem \
--ca-cert ca/cert.pem \
--domains ${lib.escapeShellArg (builtins.concatStringsSep "," ([ data.domain ] ++ extraDomains))}
minica ${
lib.cli.toCommandLineShellGNU { } {
ca-key = "ca/key.pem";
ca-cert = "ca/cert.pem";
domains = lib.concatStringsSep "," (lib.filter isDomainName ([ data.domain ] ++ extraDomains));
ip-addresses = lib.concatStringsSep "," (lib.filter isIP ([ data.domain ] ++ extraDomains));
}
}
# Create files to match directory layout for real certificates
(
+7 -1
View File
@@ -1,11 +1,11 @@
{
config,
lib,
pkgs,
...
}:
let
domain = "example.test";
ip = "192.168.1.2";
in
{
name = "http01-builtin";
@@ -34,6 +34,7 @@ in
environment.systemPackages = [ pkgs.openssl ];
security.acme.certs."${config.networking.fqdn}" = {
extraDomainNames = [ ip ];
listenHTTP = ":80";
};
@@ -124,6 +125,7 @@ in
[ alt_names ]
DNS.1 = ${config.networking.fqdn}
IP.1 = ${ip}
'';
csrData =
pkgs.runCommand "csr-and-key"
@@ -142,6 +144,7 @@ in
security.acme.certs."${config.networking.fqdn}" = {
csr = "${csrData}/request.csr";
csrKey = "${csrData}/key.pem";
extraDomainNames = lib.mkForce [ ];
};
};
};
@@ -158,6 +161,7 @@ in
${(import ./utils.nix).pythonUtils}
domain = "${domain}"
ip = "${ip}"
cert = "${certName}"
cert2 = "builtin-2." + domain
cert3 = "builtin-3." + domain
@@ -173,6 +177,7 @@ in
check_issuer(builtin, cert, "pebble")
check_domain(builtin, cert, cert)
check_ip(builtin, cert, ip)
with subtest("Validate permissions"):
check_permissions(builtin, cert, "acme")
@@ -300,6 +305,7 @@ in
builtin.wait_for_unit("renew-triggered.target")
check_issuer(builtin, cert, "pebble")
check_ip(builtin, cert, ip)
with subtest("Generate self-signed certs"):
acme.shutdown()
+5
View File
@@ -84,6 +84,11 @@ def check_domain(node, cert_name, domain, fail=False) -> None:
cmd = f"openssl x509 -noout -checkhost '{domain}' -in /var/lib/acme/{cert_name}/cert.pem"
run(node, cmd, fail=fail)
# Ensures the provided ip address matches with the given cert
def check_ip(node, cert_name, ip_address, fail=False) -> None:
cmd = f"openssl x509 -noout -checkip '{ip_address}' -in /var/lib/acme/{cert_name}/cert.pem"
run(node, cmd, fail=fail)
# Ensures the required values for OCSP stapling are present
# Pebble doesn't provide a full OCSP responder, so just checks the URL
def check_stapling(node, cert_name, ca_domain, fail=False):