Merge branch 'mayflower-feature/simp_le-service'

Closes #11506

+278 -4
+1
nixos/doc/manual/configuration/configuration.xml
··· 26 26 27 27 <!-- FIXME: auto-include NixOS module docs --> 28 28 <xi:include href="postgresql.xml" /> 29 + <xi:include href="acme.xml" /> 29 30 <xi:include href="nixos.xml" /> 30 31 31 32 <!-- Apache; libvirtd virtualisation -->
+1
nixos/doc/manual/default.nix
··· 55 55 cp -prd $sources/* . # */ 56 56 chmod -R u+w . 57 57 cp ${../../modules/services/databases/postgresql.xml} configuration/postgresql.xml 58 + cp ${../../modules/security/acme.xml} configuration/acme.xml 58 59 cp ${../../modules/misc/nixos.xml} configuration/nixos.xml 59 60 ln -s ${optionsDocBook} options-db.xml 60 61 echo "${version}" > version
+1
nixos/modules/module-list.nix
··· 80 80 ./programs/xfs_quota.nix 81 81 ./programs/zsh/zsh.nix 82 82 ./rename.nix 83 + ./security/acme.nix 83 84 ./security/apparmor.nix 84 85 ./security/apparmor-suid.nix 85 86 ./security/ca.nix
+202
nixos/modules/security/acme.nix
··· 1 + { config, lib, pkgs, ... }: 2 + 3 + with lib; 4 + 5 + let 6 + 7 + cfg = config.security.acme; 8 + 9 + certOpts = { ... }: { 10 + options = { 11 + webroot = mkOption { 12 + type = types.str; 13 + description = '' 14 + Where the webroot of the HTTP vhost is located. 15 + <filename>.well-known/acme-challenge/</filename> directory 16 + will be created automatically if it doesn't exist. 17 + <literal>http://example.org/.well-known/acme-challenge/</literal> must also 18 + be available (notice unencrypted HTTP). 19 + ''; 20 + }; 21 + 22 + email = mkOption { 23 + type = types.nullOr types.str; 24 + default = null; 25 + description = "Contact email address for the CA to be able to reach you."; 26 + }; 27 + 28 + user = mkOption { 29 + type = types.str; 30 + default = "root"; 31 + description = "User running the ACME client."; 32 + }; 33 + 34 + group = mkOption { 35 + type = types.str; 36 + default = "root"; 37 + description = "Group running the ACME client."; 38 + }; 39 + 40 + postRun = mkOption { 41 + type = types.lines; 42 + default = ""; 43 + example = "systemctl reload nginx.service"; 44 + description = '' 45 + Commands to run after certificates are re-issued. Typically 46 + the web server and other servers using certificates need to 47 + be reloaded. 48 + ''; 49 + }; 50 + 51 + plugins = mkOption { 52 + type = types.listOf (types.enum [ 53 + "cert.der" "cert.pem" "chain.der" "chain.pem" "external_pem.sh" 54 + "fullchain.der" "fullchain.pem" "key.der" "key.pem" "account_key.json" 55 + ]); 56 + default = [ "fullchain.pem" "key.pem" "account_key.json" ]; 57 + description = '' 58 + Plugins to enable. With default settings simp_le will 59 + store public certificate bundle in <filename>fullchain.pem</filename> 60 + and private key in <filename>key.pem</filename> in its state directory. 61 + ''; 62 + }; 63 + 64 + extraDomains = mkOption { 65 + type = types.attrsOf (types.nullOr types.str); 66 + default = {}; 67 + example = { 68 + "example.org" = "/srv/http/nginx"; 69 + "mydomain.org" = null; 70 + }; 71 + description = '' 72 + Extra domain names for which certificates are to be issued, with their 73 + own server roots if needed. 74 + ''; 75 + }; 76 + }; 77 + }; 78 + 79 + in 80 + 81 + { 82 + 83 + ###### interface 84 + 85 + options = { 86 + security.acme = { 87 + directory = mkOption { 88 + default = "/var/lib/acme"; 89 + type = types.str; 90 + description = '' 91 + Directory where certs and other state will be stored by default. 92 + ''; 93 + }; 94 + 95 + validMin = mkOption { 96 + type = types.int; 97 + default = 30 * 24 * 3600; 98 + description = "Minimum remaining validity before renewal in seconds."; 99 + }; 100 + 101 + renewInterval = mkOption { 102 + type = types.str; 103 + default = "weekly"; 104 + description = '' 105 + Systemd calendar expression when to check for renewal. See 106 + <citerefentry><refentrytitle>systemd.time</refentrytitle> 107 + <manvolnum>5</manvolnum></citerefentry>. 108 + ''; 109 + }; 110 + 111 + certs = mkOption { 112 + default = { }; 113 + type = types.loaOf types.optionSet; 114 + description = '' 115 + Attribute set of certificates to get signed and renewed. 116 + ''; 117 + options = [ certOpts ]; 118 + example = { 119 + "example.com" = { 120 + webroot = "/var/www/challenges/"; 121 + email = "foo@example.com"; 122 + extraDomains = { "www.example.com" = null; "foo.example.com" = "/var/www/foo/"; }; 123 + }; 124 + "bar.example.com" = { 125 + webroot = "/var/www/challenges/"; 126 + email = "bar@example.com"; 127 + }; 128 + }; 129 + }; 130 + }; 131 + }; 132 + 133 + ###### implementation 134 + config = mkMerge [ 135 + (mkIf (cfg.certs != { }) { 136 + 137 + systemd.services = flip mapAttrs' cfg.certs (cert: data: 138 + let 139 + cpath = "${cfg.directory}/${cert}"; 140 + cmdline = [ "-v" "-d" cert "--default_root" data.webroot "--valid_min" cfg.validMin ] 141 + ++ optionals (data.email != null) [ "--email" data.email ] 142 + ++ concatMap (p: [ "-f" p ]) data.plugins 143 + ++ concatLists (mapAttrsToList (name: root: [ "-d" (if root == null then name else "${name}:${root}")]) data.extraDomains); 144 + 145 + in nameValuePair 146 + ("acme-${cert}") 147 + ({ 148 + description = "ACME cert renewal for ${cert} using simp_le"; 149 + after = [ "network.target" ]; 150 + serviceConfig = { 151 + Type = "oneshot"; 152 + SuccessExitStatus = [ "0" "1" ]; 153 + PermissionsStartOnly = true; 154 + User = data.user; 155 + Group = data.group; 156 + PrivateTmp = true; 157 + }; 158 + path = [ pkgs.simp_le ]; 159 + preStart = '' 160 + mkdir -p '${cfg.directory}' 161 + if [ ! -d '${cpath}' ]; then 162 + mkdir -m 700 '${cpath}' 163 + chown '${data.user}:${data.group}' '${cpath}' 164 + fi 165 + ''; 166 + script = '' 167 + cd '${cpath}' 168 + set +e 169 + simp_le ${concatMapStringsSep " " (arg: escapeShellArg (toString arg)) cmdline} 170 + EXITCODE=$? 171 + set -e 172 + echo "$EXITCODE" > /tmp/lastExitCode 173 + exit "$EXITCODE" 174 + ''; 175 + postStop = '' 176 + if [ -e /tmp/lastExitCode ] && [ "$(cat /tmp/lastExitCode)" = "0" ]; then 177 + echo "Executing postRun hook..." 178 + ${data.postRun} 179 + fi 180 + ''; 181 + }) 182 + ); 183 + 184 + systemd.timers = flip mapAttrs' cfg.certs (cert: data: nameValuePair 185 + ("acme-${cert}") 186 + ({ 187 + description = "timer for ACME cert renewal of ${cert}"; 188 + wantedBy = [ "timers.target" ]; 189 + timerConfig = { 190 + OnCalendar = cfg.renewInterval; 191 + Unit = "acme-${cert}.service"; 192 + }; 193 + }) 194 + ); 195 + }) 196 + 197 + { meta.maintainers = with lib.maintainers; [ abbradar fpletz globin ]; 198 + meta.doc = ./acme.xml; 199 + } 200 + ]; 201 + 202 + }
+69
nixos/modules/security/acme.xml
··· 1 + <chapter xmlns="http://docbook.org/ns/docbook" 2 + xmlns:xlink="http://www.w3.org/1999/xlink" 3 + xmlns:xi="http://www.w3.org/2001/XInclude" 4 + version="5.0" 5 + xml:id="module-security-acme"> 6 + 7 + <title>SSL/TLS Certificates with ACME</title> 8 + 9 + <para>NixOS supports automatic domain validation &amp; certificate 10 + retrieval and renewal using the ACME protocol. This is currently only 11 + implemented by and for Let's Encrypt. The alternative ACME client 12 + <literal>simp_le</literal> is used under the hood.</para> 13 + 14 + <section><title>Prerequisites</title> 15 + 16 + <para>You need to have a running HTTP server for verification. The server must 17 + have a webroot defined that can serve 18 + <filename>.well-known/acme-challenge</filename>. This directory must be 19 + writeable by the user that will run the ACME client.</para> 20 + 21 + <para>For instance, this generic snippet could be used for Nginx: 22 + 23 + <programlisting> 24 + http { 25 + server { 26 + server_name _; 27 + listen 80; 28 + listen [::]:80; 29 + 30 + location /.well-known/acme-challenge { 31 + root /var/www/challenges; 32 + } 33 + 34 + location / { 35 + return 301 https://$host$request_uri; 36 + } 37 + } 38 + } 39 + </programlisting> 40 + </para> 41 + 42 + </section> 43 + 44 + <section><title>Configuring</title> 45 + 46 + <para>To enable ACME certificate retrieval &amp; renewal for a certificate for 47 + <literal>foo.example.com</literal>, add the following in your 48 + <filename>configuration.nix</filename>: 49 + 50 + <programlisting> 51 + security.acme.certs."foo.example.com" = { 52 + webroot = "/var/www/challenges"; 53 + email = "foo@example.com"; 54 + }; 55 + </programlisting> 56 + </para> 57 + 58 + <para>The private key <filename>key.pem</filename> and certificate 59 + <filename>fullchain.pem</filename> will be put into 60 + <filename>/var/lib/acme/foo.example.com</filename>. The target directory can 61 + be configured with the option <literal>security.acme.directory</literal>. 62 + </para> 63 + 64 + <para>Refer to <xref linkend="ch-options" /> for all available configuration 65 + options for the <literal>security.acme</literal> module.</para> 66 + 67 + </section> 68 + 69 + </chapter>
+4 -4
pkgs/tools/admin/simp_le/default.nix
··· 1 1 { stdenv, fetchFromGitHub, pythonPackages }: 2 2 3 3 pythonPackages.buildPythonPackage rec { 4 - name = "simp_le-20151205"; 4 + name = "simp_le-20151207"; 5 5 6 6 src = fetchFromGitHub { 7 7 owner = "kuba"; 8 8 repo = "simp_le"; 9 - rev = "976a33830759e66610970f92f6ec1a656a2c8335"; 10 - sha256 = "0bfa5081rmjjg9sii6pn2dskd1wh0dgrf9ic9hpisawrk0y0739i"; 9 + rev = "ac836bc0af988cb14dc0a83dc2039e7fa541b677"; 10 + sha256 = "0r07mlis81n0pmj74wjcvjpi6i3lkzs6hz8iighhk8yymn1a8rbn"; 11 11 }; 12 12 13 - propagatedBuildInputs = with pythonPackages; [ acme cryptography pytz requests2 ]; 13 + propagatedBuildInputs = with pythonPackages; [ acme ]; 14 14 15 15 meta = with stdenv.lib; { 16 16 homepage = https://github.com/kuba/simp_le;