{apache,caddy,nginx}: not "before" ACME certs using DNS validation (#336412)
This commit is contained in:
commit
0453fe2395
@ -217,7 +217,7 @@ let
|
||||
|
||||
protocolOpts = if useDns then (
|
||||
[ "--dns" data.dnsProvider ]
|
||||
++ lib.optionals (!data.dnsPropagationCheck) [ "--dns.disable-cp" ]
|
||||
++ lib.optionals (!data.dnsPropagationCheck) [ "--dns.propagation-disable-ans" ]
|
||||
++ lib.optionals (data.dnsResolver != null) [ "--dns.resolvers" data.dnsResolver ]
|
||||
) else if data.s3Bucket != null then [ "--http" "--http.s3-bucket" data.s3Bucket ]
|
||||
else if data.listenHTTP != null then [ "--http" "--http.port" data.listenHTTP ]
|
||||
|
@ -1,4 +1,21 @@
|
||||
{ cert, group, groups, user }: {
|
||||
assertion = cert.group == group || builtins.any (u: u == user) groups.${cert.group}.members;
|
||||
message = "Group for certificate ${cert.domain} must be ${group}, or user ${user} must be a member of group ${cert.group}";
|
||||
lib:
|
||||
|
||||
{ cert, groups, services }:
|
||||
let
|
||||
catSep = builtins.concatStringsSep;
|
||||
|
||||
svcGroups = svc:
|
||||
(lib.optional (svc.serviceConfig ? Group) svc.serviceConfig.Group)
|
||||
++ (svc.serviceConfig.SupplementaryGroups or [ ]);
|
||||
in
|
||||
{
|
||||
assertion = builtins.all (svc:
|
||||
svc.serviceConfig.User or "root" == "root"
|
||||
|| builtins.elem svc.serviceConfig.User groups.${cert.group}.members
|
||||
|| builtins.elem cert.group (svcGroups svc)
|
||||
) services;
|
||||
|
||||
message = "Certificate ${cert.domain} (group=${cert.group}) must be readable by service(s) ${
|
||||
catSep ", " (map (svc: "${svc.name} (user=${svc.serviceConfig.User} groups=${catSep " " (svcGroups svc)})") services)
|
||||
}";
|
||||
}
|
||||
|
@ -33,7 +33,9 @@ let
|
||||
certName = if hostOpts.useACMEHost != null then hostOpts.useACMEHost else hostOpts.hostName;
|
||||
}) (filter (hostOpts: hostOpts.enableACME || hostOpts.useACMEHost != null) vhosts);
|
||||
|
||||
dependentCertNames = unique (map (hostOpts: hostOpts.certName) acmeEnabledVhosts);
|
||||
vhostCertNames = unique (map (hostOpts: hostOpts.certName) acmeEnabledVhosts);
|
||||
dependentCertNames = filter (cert: certs.${cert}.dnsProvider == null) vhostCertNames; # those that might depend on the HTTP server
|
||||
independentCertNames = filter (cert: certs.${cert}.dnsProvider != null) vhostCertNames; # those that don't depend on the HTTP server
|
||||
|
||||
mkListenInfo = hostOpts:
|
||||
if hostOpts.listen != [] then
|
||||
@ -371,7 +373,7 @@ let
|
||||
echo "$options" >> $out
|
||||
'';
|
||||
|
||||
mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix;
|
||||
mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix lib;
|
||||
in
|
||||
|
||||
|
||||
@ -641,10 +643,10 @@ in
|
||||
'';
|
||||
}
|
||||
] ++ map (name: mkCertOwnershipAssertion {
|
||||
inherit (cfg) group user;
|
||||
cert = config.security.acme.certs.${name};
|
||||
groups = config.users.groups;
|
||||
}) dependentCertNames;
|
||||
services = [ config.systemd.services.httpd ] ++ lib.optional (vhostCertNames != []) config.systemd.services.httpd-config-reload;
|
||||
}) vhostCertNames;
|
||||
|
||||
warnings =
|
||||
mapAttrsToList (name: hostOpts: ''
|
||||
@ -747,8 +749,10 @@ in
|
||||
systemd.services.httpd = {
|
||||
description = "Apache HTTPD";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
wants = concatLists (map (certName: [ "acme-finished-${certName}.target" ]) dependentCertNames);
|
||||
after = [ "network.target" ] ++ map (certName: "acme-selfsigned-${certName}.service") dependentCertNames;
|
||||
wants = concatLists (map (certName: [ "acme-finished-${certName}.target" ]) vhostCertNames);
|
||||
after = [ "network.target" ]
|
||||
++ map (certName: "acme-selfsigned-${certName}.service") vhostCertNames
|
||||
++ map (certName: "acme-${certName}.service") independentCertNames; # avoid loading self-signed key w/ real cert, or vice-versa
|
||||
before = map (certName: "acme-${certName}.service") dependentCertNames;
|
||||
restartTriggers = [ cfg.configFile ];
|
||||
|
||||
@ -789,9 +793,9 @@ in
|
||||
# which allows the acme-finished-$cert.target to signify the successful updating
|
||||
# of certs end-to-end.
|
||||
systemd.services.httpd-config-reload = let
|
||||
sslServices = map (certName: "acme-${certName}.service") dependentCertNames;
|
||||
sslTargets = map (certName: "acme-finished-${certName}.target") dependentCertNames;
|
||||
in mkIf (sslServices != []) {
|
||||
sslServices = map (certName: "acme-${certName}.service") vhostCertNames;
|
||||
sslTargets = map (certName: "acme-finished-${certName}.target") vhostCertNames;
|
||||
in mkIf (vhostCertNames != []) {
|
||||
wantedBy = sslServices ++ [ "multi-user.target" ];
|
||||
# Before the finished targets, after the renew services.
|
||||
# This service might be needed for HTTP-01 challenges, but we only want to confirm
|
||||
@ -801,7 +805,7 @@ in
|
||||
restartTriggers = [ cfg.configFile ];
|
||||
# Block reloading if not all certs exist yet.
|
||||
# Happens when config changes add new vhosts/certs.
|
||||
unitConfig.ConditionPathExists = map (certName: certs.${certName}.directory + "/fullchain.pem") dependentCertNames;
|
||||
unitConfig.ConditionPathExists = map (certName: certs.${certName}.directory + "/fullchain.pem") vhostCertNames;
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
TimeoutSec = 60;
|
||||
|
@ -5,8 +5,12 @@ with lib;
|
||||
let
|
||||
cfg = config.services.caddy;
|
||||
|
||||
certs = config.security.acme.certs;
|
||||
virtualHosts = attrValues cfg.virtualHosts;
|
||||
acmeVHosts = filter (hostOpts: hostOpts.useACMEHost != null) virtualHosts;
|
||||
acmeEnabledVhosts = filter (hostOpts: hostOpts.useACMEHost != null) virtualHosts;
|
||||
vhostCertNames = unique (map (hostOpts: hostOpts.useACMEHost) acmeEnabledVhosts);
|
||||
dependentCertNames = filter (cert: certs.${cert}.dnsProvider == null) vhostCertNames; # those that might depend on the HTTP server
|
||||
independentCertNames = filter (cert: certs.${cert}.dnsProvider != null) vhostCertNames; # those that don't depend on the HTTP server
|
||||
|
||||
mkVHostConf = hostOpts:
|
||||
let
|
||||
@ -51,9 +55,7 @@ let
|
||||
|
||||
configPath = "/etc/${etcConfigFile}";
|
||||
|
||||
acmeHosts = unique (catAttrs "useACMEHost" acmeVHosts);
|
||||
|
||||
mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix;
|
||||
mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix lib;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
@ -329,10 +331,10 @@ in
|
||||
message = "To specify an adapter other than 'caddyfile' please provide your own configuration via `services.caddy.configFile`";
|
||||
}
|
||||
] ++ map (name: mkCertOwnershipAssertion {
|
||||
inherit (cfg) group user;
|
||||
cert = config.security.acme.certs.${name};
|
||||
groups = config.users.groups;
|
||||
}) acmeHosts;
|
||||
services = [ config.systemd.services.caddy ];
|
||||
}) vhostCertNames;
|
||||
|
||||
services.caddy.globalConfig = ''
|
||||
${optionalString (cfg.email != null) "email ${cfg.email}"}
|
||||
@ -348,9 +350,10 @@ in
|
||||
|
||||
systemd.packages = [ cfg.package ];
|
||||
systemd.services.caddy = {
|
||||
wants = map (hostOpts: "acme-finished-${hostOpts.useACMEHost}.target") acmeVHosts;
|
||||
after = map (hostOpts: "acme-selfsigned-${hostOpts.useACMEHost}.service") acmeVHosts;
|
||||
before = map (hostOpts: "acme-${hostOpts.useACMEHost}.service") acmeVHosts;
|
||||
wants = map (certName: "acme-finished-${certName}.target") vhostCertNames;
|
||||
after = map (certName: "acme-selfsigned-${certName}.service") vhostCertNames
|
||||
++ map (certName: "acme-${certName}.service") independentCertNames; # avoid loading self-signed key w/ real cert, or vice-versa
|
||||
before = map (certName: "acme-${certName}.service") dependentCertNames;
|
||||
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
startLimitIntervalSec = 14400;
|
||||
@ -397,10 +400,10 @@ in
|
||||
|
||||
security.acme.certs =
|
||||
let
|
||||
certCfg = map (useACMEHost: nameValuePair useACMEHost {
|
||||
certCfg = map (certName: nameValuePair certName {
|
||||
group = mkDefault cfg.group;
|
||||
reloadServices = [ "caddy.service" ];
|
||||
}) acmeHosts;
|
||||
}) vhostCertNames;
|
||||
in
|
||||
listToAttrs certCfg;
|
||||
|
||||
|
@ -7,7 +7,9 @@ let
|
||||
inherit (config.security.acme) certs;
|
||||
vhostsConfigs = mapAttrsToList (vhostName: vhostConfig: vhostConfig) virtualHosts;
|
||||
acmeEnabledVhosts = filter (vhostConfig: vhostConfig.enableACME || vhostConfig.useACMEHost != null) vhostsConfigs;
|
||||
dependentCertNames = unique (map (hostOpts: hostOpts.certName) acmeEnabledVhosts);
|
||||
vhostCertNames = unique (map (hostOpts: hostOpts.certName) acmeEnabledVhosts);
|
||||
dependentCertNames = filter (cert: certs.${cert}.dnsProvider == null) vhostCertNames; # those that might depend on the HTTP server
|
||||
independentCertNames = filter (cert: certs.${cert}.dnsProvider != null) vhostCertNames; # those that don't depend on the HTTP server
|
||||
virtualHosts = mapAttrs (vhostName: vhostConfig:
|
||||
let
|
||||
serverName = if vhostConfig.serverName != null
|
||||
@ -471,7 +473,7 @@ let
|
||||
'') authDef)
|
||||
);
|
||||
|
||||
mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix;
|
||||
mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix lib;
|
||||
|
||||
oldHTTP2 = (versionOlder cfg.package.version "1.25.1" && !(cfg.package.pname == "angie" || cfg.package.pname == "angieQuic"));
|
||||
in
|
||||
@ -1209,10 +1211,10 @@ in
|
||||
'';
|
||||
}
|
||||
] ++ map (name: mkCertOwnershipAssertion {
|
||||
inherit (cfg) group user;
|
||||
cert = config.security.acme.certs.${name};
|
||||
groups = config.users.groups;
|
||||
}) dependentCertNames;
|
||||
services = [ config.systemd.services.nginx ] ++ lib.optional (cfg.enableReload || vhostCertNames != []) config.systemd.services.nginx-config-reload;
|
||||
}) vhostCertNames;
|
||||
|
||||
services.nginx.additionalModules = optional cfg.recommendedBrotliSettings pkgs.nginxModules.brotli
|
||||
++ lib.optional cfg.recommendedZstdSettings pkgs.nginxModules.zstd;
|
||||
@ -1236,8 +1238,10 @@ in
|
||||
systemd.services.nginx = {
|
||||
description = "Nginx Web Server";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
wants = concatLists (map (certName: [ "acme-finished-${certName}.target" ]) dependentCertNames);
|
||||
after = [ "network.target" ] ++ map (certName: "acme-selfsigned-${certName}.service") dependentCertNames;
|
||||
wants = concatLists (map (certName: [ "acme-finished-${certName}.target" ]) vhostCertNames);
|
||||
after = [ "network.target" ]
|
||||
++ map (certName: "acme-selfsigned-${certName}.service") vhostCertNames
|
||||
++ map (certName: "acme-${certName}.service") independentCertNames; # avoid loading self-signed key w/ real cert, or vice-versa
|
||||
# Nginx needs to be started in order to be able to request certificates
|
||||
# (it's hosting the acme challenge after all)
|
||||
# This fixes https://github.com/NixOS/nixpkgs/issues/81842
|
||||
@ -1316,9 +1320,9 @@ in
|
||||
# which allows the acme-finished-$cert.target to signify the successful updating
|
||||
# of certs end-to-end.
|
||||
systemd.services.nginx-config-reload = let
|
||||
sslServices = map (certName: "acme-${certName}.service") dependentCertNames;
|
||||
sslTargets = map (certName: "acme-finished-${certName}.target") dependentCertNames;
|
||||
in mkIf (cfg.enableReload || sslServices != []) {
|
||||
sslServices = map (certName: "acme-${certName}.service") vhostCertNames;
|
||||
sslTargets = map (certName: "acme-finished-${certName}.target") vhostCertNames;
|
||||
in mkIf (cfg.enableReload || vhostCertNames != []) {
|
||||
wants = optionals cfg.enableReload [ "nginx.service" ];
|
||||
wantedBy = sslServices ++ [ "multi-user.target" ];
|
||||
# Before the finished targets, after the renew services.
|
||||
@ -1329,7 +1333,7 @@ in
|
||||
restartTriggers = optionals cfg.enableReload [ configFile ];
|
||||
# Block reloading if not all certs exist yet.
|
||||
# Happens when config changes add new vhosts/certs.
|
||||
unitConfig.ConditionPathExists = optionals (sslServices != []) (map (certName: certs.${certName}.directory + "/fullchain.pem") dependentCertNames);
|
||||
unitConfig.ConditionPathExists = optionals (sslServices != []) (map (certName: certs.${certName}.directory + "/fullchain.pem") vhostCertNames);
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
TimeoutSec = 60;
|
||||
|
@ -54,11 +54,6 @@ let
|
||||
testCerts = import ./snakeoil-certs.nix;
|
||||
domain = testCerts.domain;
|
||||
|
||||
resolver = let
|
||||
message = "You need to define a resolver for the acme test module.";
|
||||
firstNS = lib.head config.networking.nameservers;
|
||||
in if config.networking.nameservers == [] then throw message else firstNS;
|
||||
|
||||
pebbleConf.pebble = {
|
||||
listenAddress = "0.0.0.0:443";
|
||||
managementListenAddress = "0.0.0.0:15000";
|
||||
|
Loading…
Reference in New Issue
Block a user