Merge pull request #33900 from jtojnar/nginx-acme

nixos/nginx: allow using existing ACME certificate

Changed files
+30 -5
nixos
modules
services
web-servers
+15 -4
nixos/modules/services/web-servers/nginx/default.nix
···
} // (optionalAttrs vhostConfig.enableACME {
sslCertificate = "/var/lib/acme/${serverName}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/${serverName}/key.pem";
})
) cfg.virtualHosts;
enableIPv6 = config.networking.enableIPv6;
···
redirectListen = filter (x: !x.ssl) defaultListen;
-
acmeLocation = ''
location /.well-known/acme-challenge {
${optionalString (vhost.acmeFallbackHost != null) "try_files $uri @acme-fallback;"}
root ${vhost.acmeRoot};
···
${concatMapStringsSep "\n" listenString redirectListen}
server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases};
-
${optionalString vhost.enableACME acmeLocation}
location / {
return 301 https://$host$request_uri;
}
···
server {
${concatMapStringsSep "\n" listenString hostListen}
server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases};
-
${optionalString vhost.enableACME acmeLocation}
${optionalString (vhost.root != null) "root ${vhost.root};"}
${optionalString (vhost.globalRedirect != null) ''
return 301 http${optionalString hasSSL "s"}://${vhost.globalRedirect}$request_uri;
···
are mutually exclusive.
'';
}
];
systemd.services.nginx = {
···
security.acme.certs = filterAttrs (n: v: v != {}) (
let
vhostsConfigs = mapAttrsToList (vhostName: vhostConfig: vhostConfig) virtualHosts;
-
acmeEnabledVhosts = filter (vhostConfig: vhostConfig.enableACME) vhostsConfigs;
acmePairs = map (vhostConfig: { name = vhostConfig.serverName; value = {
user = cfg.user;
group = lib.mkDefault cfg.group;
···
} // (optionalAttrs vhostConfig.enableACME {
sslCertificate = "/var/lib/acme/${serverName}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/${serverName}/key.pem";
+
}) // (optionalAttrs (vhostConfig.useACMEHost != null) {
+
sslCertificate = "/var/lib/acme/${vhostConfig.useACMEHost}/fullchain.pem";
+
sslCertificateKey = "/var/lib/acme/${vhostConfig.useACMEHost}/key.pem";
})
) cfg.virtualHosts;
enableIPv6 = config.networking.enableIPv6;
···
redirectListen = filter (x: !x.ssl) defaultListen;
+
acmeLocation = optionalString (vhost.enableACME || vhost.useACMEHost != null) ''
location /.well-known/acme-challenge {
${optionalString (vhost.acmeFallbackHost != null) "try_files $uri @acme-fallback;"}
root ${vhost.acmeRoot};
···
${concatMapStringsSep "\n" listenString redirectListen}
server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases};
+
${acmeLocation}
location / {
return 301 https://$host$request_uri;
}
···
server {
${concatMapStringsSep "\n" listenString hostListen}
server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases};
+
${acmeLocation}
${optionalString (vhost.root != null) "root ${vhost.root};"}
${optionalString (vhost.globalRedirect != null) ''
return 301 http${optionalString hasSSL "s"}://${vhost.globalRedirect}$request_uri;
···
are mutually exclusive.
'';
}
+
+
{
+
assertion = all (conf: !(conf.enableACME && conf.useACMEHost != null)) (attrValues virtualHosts);
+
message = ''
+
Options services.nginx.service.virtualHosts.<name>.enableACME and
+
services.nginx.virtualHosts.<name>.useACMEHost are mutually exclusive.
+
'';
+
}
];
systemd.services.nginx = {
···
security.acme.certs = filterAttrs (n: v: v != {}) (
let
vhostsConfigs = mapAttrsToList (vhostName: vhostConfig: vhostConfig) virtualHosts;
+
acmeEnabledVhosts = filter (vhostConfig: vhostConfig.enableACME && vhostConfig.useACMEHost == null) vhostsConfigs;
acmePairs = map (vhostConfig: { name = vhostConfig.serverName; value = {
user = cfg.user;
group = lib.mkDefault cfg.group;
+15 -1
nixos/modules/services/web-servers/nginx/vhost-options.nix
···
enableACME = mkOption {
type = types.bool;
default = false;
-
description = "Whether to ask Let's Encrypt to sign a certificate for this vhost.";
};
acmeRoot = mkOption {
···
enableACME = mkOption {
type = types.bool;
default = false;
+
description = ''
+
Whether to ask Let's Encrypt to sign a certificate for this vhost.
+
Alternately, you can use an existing certificate through <option>useACMEHost</option>.
+
'';
+
};
+
+
useACMEHost = mkOption {
+
type = types.nullOr types.str;
+
default = null;
+
description = ''
+
A host of an existing Let's Encrypt certificate to use.
+
This is useful if you have many subdomains and want to avoid hitting the
+
<link xlink:href="https://letsencrypt.org/docs/rate-limits/">rate limit</link>.
+
Alternately, you can generate a certificate through <option>enableACME</option>.
+
'';
};
acmeRoot = mkOption {