Merge branch 'mayflower-feature/simp_le-service'

Closes #11506

Changed files
+278 -4
nixos
doc
manual
modules
pkgs
tools
admin
simp_le
+1
nixos/doc/manual/configuration/configuration.xml
···
<!-- FIXME: auto-include NixOS module docs -->
<xi:include href="postgresql.xml" />
+
<xi:include href="acme.xml" />
<xi:include href="nixos.xml" />
<!-- Apache; libvirtd virtualisation -->
+1
nixos/doc/manual/default.nix
···
cp -prd $sources/* . # */
chmod -R u+w .
cp ${../../modules/services/databases/postgresql.xml} configuration/postgresql.xml
+
cp ${../../modules/security/acme.xml} configuration/acme.xml
cp ${../../modules/misc/nixos.xml} configuration/nixos.xml
ln -s ${optionsDocBook} options-db.xml
echo "${version}" > version
+1
nixos/modules/module-list.nix
···
./programs/xfs_quota.nix
./programs/zsh/zsh.nix
./rename.nix
+
./security/acme.nix
./security/apparmor.nix
./security/apparmor-suid.nix
./security/ca.nix
+202
nixos/modules/security/acme.nix
···
+
{ config, lib, pkgs, ... }:
+
+
with lib;
+
+
let
+
+
cfg = config.security.acme;
+
+
certOpts = { ... }: {
+
options = {
+
webroot = mkOption {
+
type = types.str;
+
description = ''
+
Where the webroot of the HTTP vhost is located.
+
<filename>.well-known/acme-challenge/</filename> directory
+
will be created automatically if it doesn't exist.
+
<literal>http://example.org/.well-known/acme-challenge/</literal> must also
+
be available (notice unencrypted HTTP).
+
'';
+
};
+
+
email = mkOption {
+
type = types.nullOr types.str;
+
default = null;
+
description = "Contact email address for the CA to be able to reach you.";
+
};
+
+
user = mkOption {
+
type = types.str;
+
default = "root";
+
description = "User running the ACME client.";
+
};
+
+
group = mkOption {
+
type = types.str;
+
default = "root";
+
description = "Group running the ACME client.";
+
};
+
+
postRun = mkOption {
+
type = types.lines;
+
default = "";
+
example = "systemctl reload nginx.service";
+
description = ''
+
Commands to run after certificates are re-issued. Typically
+
the web server and other servers using certificates need to
+
be reloaded.
+
'';
+
};
+
+
plugins = mkOption {
+
type = types.listOf (types.enum [
+
"cert.der" "cert.pem" "chain.der" "chain.pem" "external_pem.sh"
+
"fullchain.der" "fullchain.pem" "key.der" "key.pem" "account_key.json"
+
]);
+
default = [ "fullchain.pem" "key.pem" "account_key.json" ];
+
description = ''
+
Plugins to enable. With default settings simp_le will
+
store public certificate bundle in <filename>fullchain.pem</filename>
+
and private key in <filename>key.pem</filename> in its state directory.
+
'';
+
};
+
+
extraDomains = mkOption {
+
type = types.attrsOf (types.nullOr types.str);
+
default = {};
+
example = {
+
"example.org" = "/srv/http/nginx";
+
"mydomain.org" = null;
+
};
+
description = ''
+
Extra domain names for which certificates are to be issued, with their
+
own server roots if needed.
+
'';
+
};
+
};
+
};
+
+
in
+
+
{
+
+
###### interface
+
+
options = {
+
security.acme = {
+
directory = mkOption {
+
default = "/var/lib/acme";
+
type = types.str;
+
description = ''
+
Directory where certs and other state will be stored by default.
+
'';
+
};
+
+
validMin = mkOption {
+
type = types.int;
+
default = 30 * 24 * 3600;
+
description = "Minimum remaining validity before renewal in seconds.";
+
};
+
+
renewInterval = mkOption {
+
type = types.str;
+
default = "weekly";
+
description = ''
+
Systemd calendar expression when to check for renewal. See
+
<citerefentry><refentrytitle>systemd.time</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry>.
+
'';
+
};
+
+
certs = mkOption {
+
default = { };
+
type = types.loaOf types.optionSet;
+
description = ''
+
Attribute set of certificates to get signed and renewed.
+
'';
+
options = [ certOpts ];
+
example = {
+
"example.com" = {
+
webroot = "/var/www/challenges/";
+
email = "foo@example.com";
+
extraDomains = { "www.example.com" = null; "foo.example.com" = "/var/www/foo/"; };
+
};
+
"bar.example.com" = {
+
webroot = "/var/www/challenges/";
+
email = "bar@example.com";
+
};
+
};
+
};
+
};
+
};
+
+
###### implementation
+
config = mkMerge [
+
(mkIf (cfg.certs != { }) {
+
+
systemd.services = flip mapAttrs' cfg.certs (cert: data:
+
let
+
cpath = "${cfg.directory}/${cert}";
+
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);
+
+
in nameValuePair
+
("acme-${cert}")
+
({
+
description = "ACME cert renewal for ${cert} using simp_le";
+
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 -m 700 '${cpath}'
+
chown '${data.user}:${data.group}' '${cpath}'
+
fi
+
'';
+
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
+
'';
+
})
+
);
+
+
systemd.timers = flip mapAttrs' cfg.certs (cert: data: nameValuePair
+
("acme-${cert}")
+
({
+
description = "timer for ACME cert renewal of ${cert}";
+
wantedBy = [ "timers.target" ];
+
timerConfig = {
+
OnCalendar = cfg.renewInterval;
+
Unit = "acme-${cert}.service";
+
};
+
})
+
);
+
})
+
+
{ meta.maintainers = with lib.maintainers; [ abbradar fpletz globin ];
+
meta.doc = ./acme.xml;
+
}
+
];
+
+
}
+69
nixos/modules/security/acme.xml
···
+
<chapter xmlns="http://docbook.org/ns/docbook"
+
xmlns:xlink="http://www.w3.org/1999/xlink"
+
xmlns:xi="http://www.w3.org/2001/XInclude"
+
version="5.0"
+
xml:id="module-security-acme">
+
+
<title>SSL/TLS Certificates with ACME</title>
+
+
<para>NixOS supports automatic domain validation &amp; certificate
+
retrieval and renewal using the ACME protocol. This is currently only
+
implemented by and for Let's Encrypt. The alternative ACME client
+
<literal>simp_le</literal> is used under the hood.</para>
+
+
<section><title>Prerequisites</title>
+
+
<para>You need to have a running HTTP server for verification. The server must
+
have a webroot defined that can serve
+
<filename>.well-known/acme-challenge</filename>. This directory must be
+
writeable by the user that will run the ACME client.</para>
+
+
<para>For instance, this generic snippet could be used for Nginx:
+
+
<programlisting>
+
http {
+
server {
+
server_name _;
+
listen 80;
+
listen [::]:80;
+
+
location /.well-known/acme-challenge {
+
root /var/www/challenges;
+
}
+
+
location / {
+
return 301 https://$host$request_uri;
+
}
+
}
+
}
+
</programlisting>
+
</para>
+
+
</section>
+
+
<section><title>Configuring</title>
+
+
<para>To enable ACME certificate retrieval &amp; renewal for a certificate for
+
<literal>foo.example.com</literal>, add the following in your
+
<filename>configuration.nix</filename>:
+
+
<programlisting>
+
security.acme.certs."foo.example.com" = {
+
webroot = "/var/www/challenges";
+
email = "foo@example.com";
+
};
+
</programlisting>
+
</para>
+
+
<para>The private key <filename>key.pem</filename> and certificate
+
<filename>fullchain.pem</filename> will be put into
+
<filename>/var/lib/acme/foo.example.com</filename>. The target directory can
+
be configured with the option <literal>security.acme.directory</literal>.
+
</para>
+
+
<para>Refer to <xref linkend="ch-options" /> for all available configuration
+
options for the <literal>security.acme</literal> module.</para>
+
+
</section>
+
+
</chapter>
+4 -4
pkgs/tools/admin/simp_le/default.nix
···
{ stdenv, fetchFromGitHub, pythonPackages }:
pythonPackages.buildPythonPackage rec {
-
name = "simp_le-20151205";
+
name = "simp_le-20151207";
src = fetchFromGitHub {
owner = "kuba";
repo = "simp_le";
-
rev = "976a33830759e66610970f92f6ec1a656a2c8335";
-
sha256 = "0bfa5081rmjjg9sii6pn2dskd1wh0dgrf9ic9hpisawrk0y0739i";
+
rev = "ac836bc0af988cb14dc0a83dc2039e7fa541b677";
+
sha256 = "0r07mlis81n0pmj74wjcvjpi6i3lkzs6hz8iighhk8yymn1a8rbn";
};
-
propagatedBuildInputs = with pythonPackages; [ acme cryptography pytz requests2 ];
+
propagatedBuildInputs = with pythonPackages; [ acme ];
meta = with stdenv.lib; {
homepage = https://github.com/kuba/simp_le;