summaryrefslogtreecommitdiffstats
path: root/nixos/modules
diff options
context:
space:
mode:
authorBob van der Linden <bobvanderlinden@gmail.com>2016-06-01 12:39:46 +0200
committerDomen Kožar <domen@dev.si>2016-06-01 11:39:46 +0100
commit4e6697dcb6baba9a96de5d60451bcd025833a1e8 (patch)
tree4ffb100a5f7553646fb0e603179a4079dcf8d233 /nixos/modules
parent164ead312e6c1c7eb455cbcd251cb7b603eef298 (diff)
acme: added option `security.acme.preliminarySelfsigned` (#15562)
Diffstat (limited to 'nixos/modules')
-rw-r--r--nixos/modules/security/acme.nix180
-rw-r--r--nixos/modules/security/acme.xml28
2 files changed, 162 insertions, 46 deletions
diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix
index cb5410a5f15d..ef6da788e619 100644
--- a/nixos/modules/security/acme.nix
+++ b/nixos/modules/security/acme.nix
@@ -114,6 +114,19 @@ in
'';
};
+ preliminarySelfsigned = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether a preliminary self-signed certificate should be generated before
+ doing ACME requests. This can be useful when certificates are required in
+ a webserver, but ACME needs the webserver to make its requests.
+
+ With preliminary self-signed certificate the webserver can be started and
+ can later reload the correct ACME certificates.
+ '';
+ };
+
certs = mkOption {
default = { };
type = types.loaOf types.optionSet;
@@ -140,54 +153,126 @@ in
config = mkMerge [
(mkIf (cfg.certs != { }) {
- systemd.services = flip mapAttrs' cfg.certs (cert: data:
- let
- cpath = "${cfg.directory}/${cert}";
- rights = if data.allowKeysForGroup then "750" else "700";
- cmdline = [ "-v" "-d" cert "--default_root" data.webroot "--valid_min" cfg.validMin ]
- ++ optionals (data.email != null) [ "--email" data.email ]
- ++ concatMap (p: [ "-f" p ]) data.plugins
- ++ concatLists (mapAttrsToList (name: root: [ "-d" (if root == null then name else "${name}:${root}")]) data.extraDomains);
+ systemd.services = let
+ services = concatLists servicesLists;
+ servicesLists = mapAttrsToList certToServices cfg.certs;
+ certToServices = cert: data:
+ let
+ cpath = "${cfg.directory}/${cert}";
+ rights = if data.allowKeysForGroup then "750" else "700";
+ cmdline = [ "-v" "-d" cert "--default_root" data.webroot "--valid_min" cfg.validMin ]
+ ++ optionals (data.email != null) [ "--email" data.email ]
+ ++ concatMap (p: [ "-f" p ]) data.plugins
+ ++ concatLists (mapAttrsToList (name: root: [ "-d" (if root == null then name else "${name}:${root}")]) data.extraDomains);
+ acmeService = {
+ description = "Renew ACME Certificate for ${cert}";
+ after = [ "network.target" ];
+ serviceConfig = {
+ Type = "oneshot";
+ SuccessExitStatus = [ "0" "1" ];
+ PermissionsStartOnly = true;
+ User = data.user;
+ Group = data.group;
+ PrivateTmp = true;
+ };
+ path = [ pkgs.simp_le ];
+ preStart = ''
+ mkdir -p '${cfg.directory}'
+ if [ ! -d '${cpath}' ]; then
+ mkdir '${cpath}'
+ fi
+ chmod ${rights} '${cpath}'
+ chown -R '${data.user}:${data.group}' '${cpath}'
+ '';
+ script = ''
+ cd '${cpath}'
+ set +e
+ simp_le ${concatMapStringsSep " " (arg: escapeShellArg (toString arg)) cmdline}
+ EXITCODE=$?
+ set -e
+ echo "$EXITCODE" > /tmp/lastExitCode
+ exit "$EXITCODE"
+ '';
+ postStop = ''
+ if [ -e /tmp/lastExitCode ] && [ "$(cat /tmp/lastExitCode)" = "0" ]; then
+ echo "Executing postRun hook..."
+ ${data.postRun}
+ fi
+ '';
- in nameValuePair
- ("acme-${cert}")
- ({
- description = "Renew ACME Certificate for ${cert}";
- after = [ "network.target" ];
- serviceConfig = {
- Type = "oneshot";
- SuccessExitStatus = [ "0" "1" ];
- PermissionsStartOnly = true;
- User = data.user;
- Group = data.group;
- PrivateTmp = true;
+ before = [ "acme-certificates.target" ];
+ wantedBy = [ "acme-certificates.target" ];
+ };
+ selfsignedService = {
+ description = "Create preliminary self-signed certificate for ${cert}";
+ preStart = ''
+ if [ ! -d '${cpath}' ]
+ then
+ mkdir -p '${cpath}'
+ chmod ${rights} '${cpath}'
+ chown '${data.user}:${data.group}' '${cpath}'
+ fi
+ '';
+ script =
+ ''
+ # Create self-signed key
+ workdir="/run/acme-selfsigned-${cert}"
+ ${pkgs.openssl.bin}/bin/openssl genrsa -des3 -passout pass:x -out $workdir/server.pass.key 2048
+ ${pkgs.openssl.bin}/bin/openssl rsa -passin pass:x -in $workdir/server.pass.key -out $workdir/server.key
+ ${pkgs.openssl.bin}/bin/openssl req -new -key $workdir/server.key -out $workdir/server.csr \
+ -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=IT Department/CN=example.com"
+ ${pkgs.openssl.bin}/bin/openssl x509 -req -days 1 -in $workdir/server.csr -signkey $workdir/server.key -out $workdir/server.crt
+
+ # Move key to destination
+ mv $workdir/server.key ${cpath}/key.pem
+ mv $workdir/server.crt ${cpath}/fullchain.pem
+
+ # Clean up working directory
+ rm $workdir/server.csr
+ rm $workdir/server.pass.key
+
+ # Give key acme permissions
+ chmod ${rights} '${cpath}/key.pem'
+ chown '${data.user}:${data.group}' '${cpath}/key.pem'
+ chmod ${rights} '${cpath}/fullchain.pem'
+ chown '${data.user}:${data.group}' '${cpath}/fullchain.pem'
+ '';
+ serviceConfig = {
+ Type = "oneshot";
+ RuntimeDirectory = "acme-selfsigned-${cert}";
+ PermissionsStartOnly = true;
+ User = data.user;
+ Group = data.group;
+ };
+ unitConfig = {
+ # Do not create self-signed key when key already exists
+ ConditionPathExists = "!${cpath}/key.pem";
+ };
+ before = [
+ "acme-selfsigned-certificates.target"
+ ];
+ wantedBy = [
+ "acme-selfsigned-certificates.target"
+ ];
+ };
+ in (
+ [ { name = "acme-${cert}"; value = acmeService; } ]
+ ++
+ (if cfg.preliminarySelfsigned
+ then [ { name = "acme-selfsigned-${cert}"; value = selfsignedService; } ]
+ else []
+ )
+ );
+ servicesAttr = listToAttrs services;
+ nginxAttr = {
+ nginx = {
+ after = [ "acme-selfsigned-certificates.target" ];
+ wants = [ "acme-selfsigned-certificates.target" "acme-certificates.target" ];
+ };
};
- path = [ pkgs.simp_le ];
- preStart = ''
- mkdir -p '${cfg.directory}'
- if [ ! -d '${cpath}' ]; then
- mkdir '${cpath}'
- fi
- chmod ${rights} '${cpath}'
- chown -R '${data.user}:${data.group}' '${cpath}'
- '';
- script = ''
- cd '${cpath}'
- set +e
- simp_le ${concatMapStringsSep " " (arg: escapeShellArg (toString arg)) cmdline}
- EXITCODE=$?
- set -e
- echo "$EXITCODE" > /tmp/lastExitCode
- exit "$EXITCODE"
- '';
- postStop = ''
- if [ -e /tmp/lastExitCode ] && [ "$(cat /tmp/lastExitCode)" = "0" ]; then
- echo "Executing postRun hook..."
- ${data.postRun}
- fi
- '';
- })
- );
+ in
+ servicesAttr //
+ (if config.services.nginx.enable then nginxAttr else {});
systemd.timers = flip mapAttrs' cfg.certs (cert: data: nameValuePair
("acme-${cert}")
@@ -200,6 +285,9 @@ in
};
})
);
+
+ systemd.targets."acme-selfsigned-certificates" = mkIf cfg.preliminarySelfsigned {};
+ systemd.targets."acme-certificates" = {};
})
{ meta.maintainers = with lib.maintainers; [ abbradar fpletz globin ];
diff --git a/nixos/modules/security/acme.xml b/nixos/modules/security/acme.xml
index e32fa72c9393..15ed4c04a23d 100644
--- a/nixos/modules/security/acme.xml
+++ b/nixos/modules/security/acme.xml
@@ -66,4 +66,32 @@ options for the <literal>security.acme</literal> module.</para>
</section>
+<section><title>Using ACME certificates in Nginx</title>
+<para>In practice ACME is mostly used for retrieval and renewal of
+ certificates that will be used in a webserver like Nginx. A configuration for
+ Nginx that uses the certificates from ACME for
+ <literal>foo.example.com</literal> will look similar to:
+</para>
+
+<programlisting>
+services.nginx.httpConfig = ''
+ server {
+ server_name foo.example.com;
+ listen 443 ssl;
+ ssl_certificate ${config.security.acme.directory}/foo.example.com/fullchain.pem;
+ ssl_certificate_key ${config.security.acme.directory}/foo.example.com/key.pem;
+ root /var/www/foo.example.com/;
+ }
+'';
+</programlisting>
+
+<para>Now Nginx will try to use the certificates that will be retrieved by ACME.
+ ACME needs Nginx (or any other webserver) to function and Nginx needs
+ the certificates to actually start. For this reason the ACME module
+ automatically generates self-signed certificates that will be used by Nginx to
+ start. After that Nginx is used by ACME to retrieve the actual ACME
+ certificates. <literal>security.acme.preliminarySelfsigned</literal> can be
+ used to control whether to generate the self-signed certificates.
+</para>
+</section>
</chapter>