Allow using existing ACME certificates

Add a certificate scheme for using an existing ACME certificate without
setting up Nginx.

Also use names instead of magic numbers for certificate schemes.
This commit is contained in:
Naïm Favier 2023-02-15 13:15:09 +01:00 committed by lewo
parent 42c5564791
commit a948c49ca7
7 changed files with 49 additions and 35 deletions

View file

@ -48,7 +48,11 @@ in
type = types.listOf types.str;
example = [ "imap.example.com" "pop3.example.com" ];
default = [];
description = "Secondary domains and subdomains for which it is necessary to generate a certificate.";
description = ''
({option}`mailserver.certificateScheme` == `acme-nginx`)
Secondary domains and subdomains for which it is necessary to generate a certificate.
'';
};
messageSizeLimit = mkOption {
@ -448,19 +452,26 @@ in
};
};
certificateScheme = mkOption {
type = types.enum [ 1 2 3 ];
default = 2;
certificateScheme = let
schemes = [ "manual" "selfsigned" "acme-nginx" "acme" ];
translate = i: warn "setting mailserver.certificateScheme by number is deprecated, please use names instead"
(builtins.elemAt schemes (i - 1));
in mkOption {
type = with types; coercedTo (enum [ 1 2 3 ]) translate (enum schemes);
default = "selfsigned";
description = ''
Certificate Files. There are three options for these.
The scheme to use for managing TLS certificates:
1) You specify locations and manually copy certificates there.
2) You let the server create new (self signed) certificates on the fly.
3) You let the server create a certificate via `Let's Encrypt`. Note that
this implies that a stripped down webserver has to be started. This also
implies that the FQDN must be set as an `A` record to point to the IP of
the server. In particular port 80 on the server will be opened. For details
on how to set up the domain records, see the guide in the readme.
1. `manual`: you specify locations via {option}`mailserver.certificateFile` and
{option}`mailserver.keyFile` and manually copy certificates there.
2. `selfsigned`: you let the server create new (self-signed) certificates on the fly.
3. `acme-nginx`: you let the server request certificates from [Let's Encrypt](https://letsencrypt.org)
via NixOS' ACME module. By default, this will set up a stripped-down Nginx server for
{option}`mailserver.fqdn` and open port 80. For this to work, the FQDN must be properly
configured to point to your server (see the [setup guide](setup-guide.rst) for more information).
4. `acme`: you already have an ACME certificate set up (for example, you're already running a TLS-enabled
Nginx server on the FQDN). This is better than `manual` because the appropriate services will be reloaded
when the certificate is renewed.
'';
};
@ -468,8 +479,9 @@ in
type = types.path;
example = "/root/mail-server.crt";
description = ''
Scheme 1)
Location of the certificate
({option}`mailserver.certificateScheme` == `manual`)
Location of the certificate.
'';
};
@ -477,8 +489,9 @@ in
type = types.path;
example = "/root/mail-server.key";
description = ''
Scheme 1)
Location of the key file
({option}`mailserver.certificateScheme` == `manual`)
Location of the key file.
'';
};
@ -486,8 +499,9 @@ in
type = types.path;
default = "/var/certs";
description = ''
Scheme 2)
This is the folder where the certificate will be created. The name is
({option}`mailserver.certificateScheme` == `selfsigned`)
This is the folder where the self-signed certificate will be created. The name is
hardcoded to "cert-DOMAIN.pem" and "key-DOMAIN.pem" and the
certificate is valid for 10 years.
'';

View file

@ -81,7 +81,7 @@ these should be the most common ones.
# Use Let's Encrypt certificates. Note that this needs to set up a stripped
# down nginx and opens port 80.
certificateScheme = 3;
certificateScheme = "acme-nginx";
};
}

View file

@ -21,22 +21,22 @@ let
in
{
# cert :: PATH
certificatePath = if cfg.certificateScheme == 1
certificatePath = if cfg.certificateScheme == "manual"
then cfg.certificateFile
else if cfg.certificateScheme == 2
else if cfg.certificateScheme == "selfsigned"
then "${cfg.certificateDirectory}/cert-${cfg.fqdn}.pem"
else if cfg.certificateScheme == 3
else if cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx"
then "${config.security.acme.certs.${cfg.fqdn}.directory}/fullchain.pem"
else throw "Error: Certificate Scheme must be in { 1, 2, 3 }";
else throw "unknown certificate scheme";
# key :: PATH
keyPath = if cfg.certificateScheme == 1
keyPath = if cfg.certificateScheme == "manual"
then cfg.keyFile
else if cfg.certificateScheme == 2
else if cfg.certificateScheme == "selfsigned"
then "${cfg.certificateDirectory}/key-${cfg.fqdn}.pem"
else if cfg.certificateScheme == 3
else if cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx"
then "${config.security.acme.certs.${cfg.fqdn}.directory}/key.pem"
else throw "Error: Certificate Scheme must be in { 1, 2, 3 }";
else throw "unknown certificate scheme";
passwordFiles = let
mkHashFile = name: hash: pkgs.writeText "${builtins.hashString "sha256" name}-password-hash" hash;

View file

@ -23,6 +23,6 @@ in
config = with cfg; lib.mkIf enable {
environment.systemPackages = with pkgs; [
dovecot opendkim openssh postfix rspamd
] ++ (if certificateScheme == 2 then [ openssl ] else []);
] ++ (if certificateScheme == "selfsigned" then [ openssl ] else []);
};
}

View file

@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
{ config, pkgs, lib, ... }:
{ config, lib, ... }:
let
cfg = config.mailserver;
@ -31,7 +31,7 @@ in
++ lib.optional enablePop3 110
++ lib.optional enablePop3Ssl 995
++ lib.optional enableManageSieve 4190
++ lib.optional (certificateScheme == 3) 80;
++ lib.optional (certificateScheme == "acme-nginx") 80;
};
};
}

View file

@ -24,8 +24,8 @@ let
acmeRoot = "/var/lib/acme/acme-challenge";
in
{
config = lib.mkIf (cfg.enable && cfg.certificateScheme == 3) {
services.nginx = {
config = lib.mkIf (cfg.enable && (cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx")) {
services.nginx = lib.mkIf (cfg.certificateScheme == "acme-nginx") {
enable = true;
virtualHosts."${cfg.fqdn}" = {
serverName = cfg.fqdn;

View file

@ -19,9 +19,9 @@
let
cfg = config.mailserver;
certificatesDeps =
if cfg.certificateScheme == 1 then
if cfg.certificateScheme == "manual" then
[]
else if cfg.certificateScheme == 2 then
else if cfg.certificateScheme == "selfsigned" then
[ "mailserver-selfsigned-certificate.service" ]
else
[ "acme-finished-${cfg.fqdn}.target" ];
@ -29,7 +29,7 @@ in
{
config = with cfg; lib.mkIf enable {
# Create self signed certificate
systemd.services.mailserver-selfsigned-certificate = lib.mkIf (cfg.certificateScheme == 2) {
systemd.services.mailserver-selfsigned-certificate = lib.mkIf (cfg.certificateScheme == "selfsigned") {
after = [ "local-fs.target" ];
script = ''
# Create certificates if they do not exist yet