mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-07-14 06:00:33 +03:00
{apache,caddy,nginx}: not "before" ACME certs using DNS validation (#336412)
This commit is contained in:
commit
0453fe2395
6 changed files with 63 additions and 40 deletions
|
@ -217,7 +217,7 @@ let
|
||||||
|
|
||||||
protocolOpts = if useDns then (
|
protocolOpts = if useDns then (
|
||||||
[ "--dns" data.dnsProvider ]
|
[ "--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 ]
|
++ lib.optionals (data.dnsResolver != null) [ "--dns.resolvers" data.dnsResolver ]
|
||||||
) else if data.s3Bucket != null then [ "--http" "--http.s3-bucket" data.s3Bucket ]
|
) else if data.s3Bucket != null then [ "--http" "--http.s3-bucket" data.s3Bucket ]
|
||||||
else if data.listenHTTP != null then [ "--http" "--http.port" data.listenHTTP ]
|
else if data.listenHTTP != null then [ "--http" "--http.port" data.listenHTTP ]
|
||||||
|
|
|
@ -1,4 +1,21 @@
|
||||||
{ cert, group, groups, user }: {
|
lib:
|
||||||
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}";
|
{ 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;
|
certName = if hostOpts.useACMEHost != null then hostOpts.useACMEHost else hostOpts.hostName;
|
||||||
}) (filter (hostOpts: hostOpts.enableACME || hostOpts.useACMEHost != null) vhosts);
|
}) (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:
|
mkListenInfo = hostOpts:
|
||||||
if hostOpts.listen != [] then
|
if hostOpts.listen != [] then
|
||||||
|
@ -371,7 +373,7 @@ let
|
||||||
echo "$options" >> $out
|
echo "$options" >> $out
|
||||||
'';
|
'';
|
||||||
|
|
||||||
mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix;
|
mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix lib;
|
||||||
in
|
in
|
||||||
|
|
||||||
|
|
||||||
|
@ -641,10 +643,10 @@ in
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
] ++ map (name: mkCertOwnershipAssertion {
|
] ++ map (name: mkCertOwnershipAssertion {
|
||||||
inherit (cfg) group user;
|
|
||||||
cert = config.security.acme.certs.${name};
|
cert = config.security.acme.certs.${name};
|
||||||
groups = config.users.groups;
|
groups = config.users.groups;
|
||||||
}) dependentCertNames;
|
services = [ config.systemd.services.httpd ] ++ lib.optional (vhostCertNames != []) config.systemd.services.httpd-config-reload;
|
||||||
|
}) vhostCertNames;
|
||||||
|
|
||||||
warnings =
|
warnings =
|
||||||
mapAttrsToList (name: hostOpts: ''
|
mapAttrsToList (name: hostOpts: ''
|
||||||
|
@ -747,8 +749,10 @@ in
|
||||||
systemd.services.httpd = {
|
systemd.services.httpd = {
|
||||||
description = "Apache HTTPD";
|
description = "Apache HTTPD";
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
wants = concatLists (map (certName: [ "acme-finished-${certName}.target" ]) dependentCertNames);
|
wants = concatLists (map (certName: [ "acme-finished-${certName}.target" ]) vhostCertNames);
|
||||||
after = [ "network.target" ] ++ map (certName: "acme-selfsigned-${certName}.service") dependentCertNames;
|
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;
|
before = map (certName: "acme-${certName}.service") dependentCertNames;
|
||||||
restartTriggers = [ cfg.configFile ];
|
restartTriggers = [ cfg.configFile ];
|
||||||
|
|
||||||
|
@ -789,9 +793,9 @@ in
|
||||||
# which allows the acme-finished-$cert.target to signify the successful updating
|
# which allows the acme-finished-$cert.target to signify the successful updating
|
||||||
# of certs end-to-end.
|
# of certs end-to-end.
|
||||||
systemd.services.httpd-config-reload = let
|
systemd.services.httpd-config-reload = let
|
||||||
sslServices = map (certName: "acme-${certName}.service") dependentCertNames;
|
sslServices = map (certName: "acme-${certName}.service") vhostCertNames;
|
||||||
sslTargets = map (certName: "acme-finished-${certName}.target") dependentCertNames;
|
sslTargets = map (certName: "acme-finished-${certName}.target") vhostCertNames;
|
||||||
in mkIf (sslServices != []) {
|
in mkIf (vhostCertNames != []) {
|
||||||
wantedBy = sslServices ++ [ "multi-user.target" ];
|
wantedBy = sslServices ++ [ "multi-user.target" ];
|
||||||
# Before the finished targets, after the renew services.
|
# Before the finished targets, after the renew services.
|
||||||
# This service might be needed for HTTP-01 challenges, but we only want to confirm
|
# This service might be needed for HTTP-01 challenges, but we only want to confirm
|
||||||
|
@ -801,7 +805,7 @@ in
|
||||||
restartTriggers = [ cfg.configFile ];
|
restartTriggers = [ cfg.configFile ];
|
||||||
# Block reloading if not all certs exist yet.
|
# Block reloading if not all certs exist yet.
|
||||||
# Happens when config changes add new vhosts/certs.
|
# 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 = {
|
serviceConfig = {
|
||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
TimeoutSec = 60;
|
TimeoutSec = 60;
|
||||||
|
|
|
@ -5,8 +5,12 @@ with lib;
|
||||||
let
|
let
|
||||||
cfg = config.services.caddy;
|
cfg = config.services.caddy;
|
||||||
|
|
||||||
|
certs = config.security.acme.certs;
|
||||||
virtualHosts = attrValues cfg.virtualHosts;
|
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:
|
mkVHostConf = hostOpts:
|
||||||
let
|
let
|
||||||
|
@ -51,9 +55,7 @@ let
|
||||||
|
|
||||||
configPath = "/etc/${etcConfigFile}";
|
configPath = "/etc/${etcConfigFile}";
|
||||||
|
|
||||||
acmeHosts = unique (catAttrs "useACMEHost" acmeVHosts);
|
mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix lib;
|
||||||
|
|
||||||
mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix;
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
|
@ -329,10 +331,10 @@ in
|
||||||
message = "To specify an adapter other than 'caddyfile' please provide your own configuration via `services.caddy.configFile`";
|
message = "To specify an adapter other than 'caddyfile' please provide your own configuration via `services.caddy.configFile`";
|
||||||
}
|
}
|
||||||
] ++ map (name: mkCertOwnershipAssertion {
|
] ++ map (name: mkCertOwnershipAssertion {
|
||||||
inherit (cfg) group user;
|
|
||||||
cert = config.security.acme.certs.${name};
|
cert = config.security.acme.certs.${name};
|
||||||
groups = config.users.groups;
|
groups = config.users.groups;
|
||||||
}) acmeHosts;
|
services = [ config.systemd.services.caddy ];
|
||||||
|
}) vhostCertNames;
|
||||||
|
|
||||||
services.caddy.globalConfig = ''
|
services.caddy.globalConfig = ''
|
||||||
${optionalString (cfg.email != null) "email ${cfg.email}"}
|
${optionalString (cfg.email != null) "email ${cfg.email}"}
|
||||||
|
@ -348,9 +350,10 @@ in
|
||||||
|
|
||||||
systemd.packages = [ cfg.package ];
|
systemd.packages = [ cfg.package ];
|
||||||
systemd.services.caddy = {
|
systemd.services.caddy = {
|
||||||
wants = map (hostOpts: "acme-finished-${hostOpts.useACMEHost}.target") acmeVHosts;
|
wants = map (certName: "acme-finished-${certName}.target") vhostCertNames;
|
||||||
after = map (hostOpts: "acme-selfsigned-${hostOpts.useACMEHost}.service") acmeVHosts;
|
after = map (certName: "acme-selfsigned-${certName}.service") vhostCertNames
|
||||||
before = map (hostOpts: "acme-${hostOpts.useACMEHost}.service") acmeVHosts;
|
++ 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" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
startLimitIntervalSec = 14400;
|
startLimitIntervalSec = 14400;
|
||||||
|
@ -397,10 +400,10 @@ in
|
||||||
|
|
||||||
security.acme.certs =
|
security.acme.certs =
|
||||||
let
|
let
|
||||||
certCfg = map (useACMEHost: nameValuePair useACMEHost {
|
certCfg = map (certName: nameValuePair certName {
|
||||||
group = mkDefault cfg.group;
|
group = mkDefault cfg.group;
|
||||||
reloadServices = [ "caddy.service" ];
|
reloadServices = [ "caddy.service" ];
|
||||||
}) acmeHosts;
|
}) vhostCertNames;
|
||||||
in
|
in
|
||||||
listToAttrs certCfg;
|
listToAttrs certCfg;
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,9 @@ let
|
||||||
inherit (config.security.acme) certs;
|
inherit (config.security.acme) certs;
|
||||||
vhostsConfigs = mapAttrsToList (vhostName: vhostConfig: vhostConfig) virtualHosts;
|
vhostsConfigs = mapAttrsToList (vhostName: vhostConfig: vhostConfig) virtualHosts;
|
||||||
acmeEnabledVhosts = filter (vhostConfig: vhostConfig.enableACME || vhostConfig.useACMEHost != null) vhostsConfigs;
|
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:
|
virtualHosts = mapAttrs (vhostName: vhostConfig:
|
||||||
let
|
let
|
||||||
serverName = if vhostConfig.serverName != null
|
serverName = if vhostConfig.serverName != null
|
||||||
|
@ -471,7 +473,7 @@ let
|
||||||
'') authDef)
|
'') 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"));
|
oldHTTP2 = (versionOlder cfg.package.version "1.25.1" && !(cfg.package.pname == "angie" || cfg.package.pname == "angieQuic"));
|
||||||
in
|
in
|
||||||
|
@ -1209,10 +1211,10 @@ in
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
] ++ map (name: mkCertOwnershipAssertion {
|
] ++ map (name: mkCertOwnershipAssertion {
|
||||||
inherit (cfg) group user;
|
|
||||||
cert = config.security.acme.certs.${name};
|
cert = config.security.acme.certs.${name};
|
||||||
groups = config.users.groups;
|
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
|
services.nginx.additionalModules = optional cfg.recommendedBrotliSettings pkgs.nginxModules.brotli
|
||||||
++ lib.optional cfg.recommendedZstdSettings pkgs.nginxModules.zstd;
|
++ lib.optional cfg.recommendedZstdSettings pkgs.nginxModules.zstd;
|
||||||
|
@ -1236,8 +1238,10 @@ in
|
||||||
systemd.services.nginx = {
|
systemd.services.nginx = {
|
||||||
description = "Nginx Web Server";
|
description = "Nginx Web Server";
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
wants = concatLists (map (certName: [ "acme-finished-${certName}.target" ]) dependentCertNames);
|
wants = concatLists (map (certName: [ "acme-finished-${certName}.target" ]) vhostCertNames);
|
||||||
after = [ "network.target" ] ++ map (certName: "acme-selfsigned-${certName}.service") dependentCertNames;
|
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
|
# Nginx needs to be started in order to be able to request certificates
|
||||||
# (it's hosting the acme challenge after all)
|
# (it's hosting the acme challenge after all)
|
||||||
# This fixes https://github.com/NixOS/nixpkgs/issues/81842
|
# 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
|
# which allows the acme-finished-$cert.target to signify the successful updating
|
||||||
# of certs end-to-end.
|
# of certs end-to-end.
|
||||||
systemd.services.nginx-config-reload = let
|
systemd.services.nginx-config-reload = let
|
||||||
sslServices = map (certName: "acme-${certName}.service") dependentCertNames;
|
sslServices = map (certName: "acme-${certName}.service") vhostCertNames;
|
||||||
sslTargets = map (certName: "acme-finished-${certName}.target") dependentCertNames;
|
sslTargets = map (certName: "acme-finished-${certName}.target") vhostCertNames;
|
||||||
in mkIf (cfg.enableReload || sslServices != []) {
|
in mkIf (cfg.enableReload || vhostCertNames != []) {
|
||||||
wants = optionals cfg.enableReload [ "nginx.service" ];
|
wants = optionals cfg.enableReload [ "nginx.service" ];
|
||||||
wantedBy = sslServices ++ [ "multi-user.target" ];
|
wantedBy = sslServices ++ [ "multi-user.target" ];
|
||||||
# Before the finished targets, after the renew services.
|
# Before the finished targets, after the renew services.
|
||||||
|
@ -1329,7 +1333,7 @@ in
|
||||||
restartTriggers = optionals cfg.enableReload [ configFile ];
|
restartTriggers = optionals cfg.enableReload [ configFile ];
|
||||||
# Block reloading if not all certs exist yet.
|
# Block reloading if not all certs exist yet.
|
||||||
# Happens when config changes add new vhosts/certs.
|
# 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 = {
|
serviceConfig = {
|
||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
TimeoutSec = 60;
|
TimeoutSec = 60;
|
||||||
|
|
|
@ -54,11 +54,6 @@ let
|
||||||
testCerts = import ./snakeoil-certs.nix;
|
testCerts = import ./snakeoil-certs.nix;
|
||||||
domain = testCerts.domain;
|
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 = {
|
pebbleConf.pebble = {
|
||||||
listenAddress = "0.0.0.0:443";
|
listenAddress = "0.0.0.0:443";
|
||||||
managementListenAddress = "0.0.0.0:15000";
|
managementListenAddress = "0.0.0.0:15000";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue