add group and reloadServices options to ssl block
This commit is contained in:
parent
0bfa15fd3c
commit
e00a41b086
3 changed files with 119 additions and 3 deletions
|
@ -87,6 +87,15 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
group = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
Unix group owning this certificate.
|
||||
'';
|
||||
default = "root";
|
||||
example = "nginx";
|
||||
};
|
||||
|
||||
paths = lib.mkOption {
|
||||
description = ''
|
||||
Paths where certs will be located.
|
||||
|
@ -105,6 +114,15 @@ in
|
|||
type = lib.types.str;
|
||||
default = "shb-certs-cert-selfsigned-${config._module.args.name}.service";
|
||||
};
|
||||
|
||||
reloadServices = lib.mkOption {
|
||||
description = ''
|
||||
The list of systemd services to call `systemctl try-reload-or-restart` on.
|
||||
'';
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
example = [ "nginx.service" ];
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
|
@ -150,12 +168,30 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
group = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = ''
|
||||
Unix group owning this certificate.
|
||||
'';
|
||||
default = "acme";
|
||||
example = "nginx";
|
||||
};
|
||||
|
||||
systemdService = lib.mkOption {
|
||||
description = "Systemd oneshot service used to generate the certs.";
|
||||
type = lib.types.str;
|
||||
default = "shb-certs-cert-letsencrypt-${config._module.args.name}.service";
|
||||
};
|
||||
|
||||
reloadServices = lib.mkOption {
|
||||
description = ''
|
||||
The list of systemd services to call `systemctl try-reload-or-restart` on.
|
||||
'';
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
example = [ "nginx.service" ];
|
||||
};
|
||||
|
||||
dnsProvider = lib.mkOption {
|
||||
description = "DNS provider to use. See https://go-acme.github.io/lego/dns/ for the list of supported providers.";
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
|
@ -245,7 +281,6 @@ in
|
|||
before = [ config.shb.certs.systemdService ];
|
||||
serviceConfig.Type = "oneshot";
|
||||
serviceConfig.RuntimeDirectory = serviceName caCfg.systemdService;
|
||||
# serviceConfig.User = "nextcloud";
|
||||
# Taken from https://github.com/NixOS/nixpkgs/blob/7f311dd9226bbd568a43632c977f4992cfb2b5c8/nixos/tests/custom-ca.nix
|
||||
script = ''
|
||||
cd $RUNTIME_DIRECTORY
|
||||
|
@ -278,6 +313,7 @@ in
|
|||
}
|
||||
) cfg.cas.selfsigned;
|
||||
}
|
||||
# Config for self-signed CA bundle.
|
||||
{
|
||||
systemd.services.${serviceName config.shb.certs.systemdService} = (lib.mkIf (cfg.cas.selfsigned != {}) {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
@ -309,6 +345,11 @@ in
|
|||
script =
|
||||
let
|
||||
extraDnsNames = lib.strings.concatStringsSep "\n" (map (n: "dns_name = ${n}") certCfg.extraDomains);
|
||||
chmod = cert:
|
||||
''
|
||||
chown root:${certCfg.group} ${cert}
|
||||
chmod 640 ${cert}
|
||||
'';
|
||||
in
|
||||
''
|
||||
cd $RUNTIME_DIRECTORY
|
||||
|
@ -330,7 +371,7 @@ in
|
|||
--key-type rsa \
|
||||
--sec-param High \
|
||||
--outfile ${certCfg.paths.key}
|
||||
chmod 666 ${certCfg.paths.key}
|
||||
${chmod certCfg.paths.key}
|
||||
|
||||
mkdir -p "$(dirname -- "${certCfg.paths.cert}")"
|
||||
${pkgs.gnutls}/bin/certtool \
|
||||
|
@ -340,7 +381,11 @@ in
|
|||
--load-ca-certificate ${certCfg.ca.paths.cert} \
|
||||
--template server.template \
|
||||
--outfile ${certCfg.paths.cert}
|
||||
chmod 666 ${certCfg.paths.cert}
|
||||
${chmod certCfg.paths.cert}
|
||||
'';
|
||||
|
||||
postStart = lib.optionalString (certCfg.reloadServices != []) ''
|
||||
systemctl --no-block try-reload-or-restart ${lib.escapeShellArgs certCfg.reloadServices}
|
||||
'';
|
||||
|
||||
serviceConfig.Type = "oneshot";
|
||||
|
@ -363,6 +408,7 @@ in
|
|||
extraDomainNames = [ certCfg.domain ] ++ certCfg.extraDomains;
|
||||
email = certCfg.adminEmail;
|
||||
inherit (certCfg) dnsProvider dnsResolver;
|
||||
inherit (certCfg) group reloadServices;
|
||||
credentialsFile = certCfg.credentialsFile;
|
||||
enableDebugLogs = certCfg.debug;
|
||||
};
|
||||
|
|
|
@ -20,6 +20,10 @@ The contract for this block is defined in [`/modules/contracts/ssl.nix`](@REPO@/
|
|||
|
||||
Every module implementing this contract provides the following options:
|
||||
|
||||
- `domain`: Domain to generate the certificate for.
|
||||
- `extraDomains`: Other domains the certificate should be generated for.
|
||||
- `group`: The unix group owning this certificate.
|
||||
- `reloadServices`: Systemd services to reload when the certificate gets renewed.
|
||||
- `paths.cert`: Path to the cert file.
|
||||
- `paths.key`: Path to the key file.
|
||||
- `systemdService`: Systemd oneshot service used to generate the certificate.
|
||||
|
@ -53,15 +57,21 @@ shb.certs.certs.selfsigned = {
|
|||
ca = config.shb.certs.cas.selfsigned.myca;
|
||||
|
||||
domain = "example.com";
|
||||
group = "nginx";
|
||||
reloadServices = [ "nginx.service" ];
|
||||
};
|
||||
"www.example.com" = {
|
||||
ca = config.shb.certs.cas.selfsigned.myca;
|
||||
|
||||
domain = "www.example.com";
|
||||
group = "nginx";
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
The group has been chosen to be `nginx` to be consistent with the examples further down in this
|
||||
document.
|
||||
|
||||
### Let's Encrypt {#ssl-block-impl-lets-encrypt}
|
||||
|
||||
Defined in [`/modules/blocks/ssl.nix`](@REPO@/modules/blocks/ssl.nix).
|
||||
|
@ -71,6 +81,7 @@ We can ask Let's Encrypt to generate a certificate with:
|
|||
```nix
|
||||
shb.certs.certs.letsencrypt."example.com" = {
|
||||
domain = "example.com";
|
||||
group = "nginx";
|
||||
dnsProvider = "linode";
|
||||
adminEmail = "admin@example.com";
|
||||
credentialsFile = /path/to/secret/file;
|
||||
|
|
|
@ -8,6 +8,21 @@
|
|||
../../modules/blocks/ssl.nix
|
||||
];
|
||||
|
||||
users.users = {
|
||||
user1 = {
|
||||
group = "group1";
|
||||
isSystemUser = true;
|
||||
};
|
||||
user2 = {
|
||||
group = "group2";
|
||||
isSystemUser = true;
|
||||
};
|
||||
};
|
||||
users.groups = {
|
||||
group1 = {};
|
||||
group2 = {};
|
||||
};
|
||||
|
||||
shb.certs = {
|
||||
cas.selfsigned = {
|
||||
myca = {
|
||||
|
@ -22,17 +37,32 @@
|
|||
ca = config.shb.certs.cas.selfsigned.myca;
|
||||
|
||||
domain = "example.com";
|
||||
group = "nginx";
|
||||
};
|
||||
subdomain = {
|
||||
ca = config.shb.certs.cas.selfsigned.myca;
|
||||
|
||||
domain = "subdomain.example.com";
|
||||
group = "nginx";
|
||||
};
|
||||
multi = {
|
||||
ca = config.shb.certs.cas.selfsigned.myca;
|
||||
|
||||
domain = "multi1.example.com";
|
||||
extraDomains = [ "multi2.example.com" "multi3.example.com" ];
|
||||
group = "nginx";
|
||||
};
|
||||
|
||||
cert1 = {
|
||||
ca = config.shb.certs.cas.selfsigned.myca;
|
||||
|
||||
domain = "cert1.example.com";
|
||||
};
|
||||
cert2 = {
|
||||
ca = config.shb.certs.cas.selfsigned.myca;
|
||||
|
||||
domain = "cert2.example.com";
|
||||
group = "group2";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -81,6 +111,9 @@
|
|||
top = nodes.server.shb.certs.certs.selfsigned.top;
|
||||
subdomain = nodes.server.shb.certs.certs.selfsigned.subdomain;
|
||||
multi = nodes.server.shb.certs.certs.selfsigned.multi;
|
||||
cert1 = nodes.server.shb.certs.certs.selfsigned.cert1;
|
||||
cert2 = nodes.server.shb.certs.certs.selfsigned.cert2;
|
||||
cert3 = nodes.server.shb.certs.certs.selfsigned.cert3;
|
||||
in
|
||||
''
|
||||
start_all()
|
||||
|
@ -96,12 +129,27 @@
|
|||
server.wait_for_file("${subdomain.paths.cert}")
|
||||
server.wait_for_file("${multi.paths.key}")
|
||||
server.wait_for_file("${multi.paths.cert}")
|
||||
server.wait_for_file("${cert1.paths.key}")
|
||||
server.wait_for_file("${cert1.paths.cert}")
|
||||
server.wait_for_file("${cert2.paths.key}")
|
||||
server.wait_for_file("${cert2.paths.cert}")
|
||||
|
||||
server.require_unit_state("${nodes.server.shb.certs.systemdService}", "inactive")
|
||||
|
||||
server.wait_for_unit("nginx")
|
||||
server.wait_for_open_port(443)
|
||||
|
||||
def assert_owner(path, user, group):
|
||||
owner = server.succeed("stat --format '%U:%G' {}".format(path)).strip();
|
||||
want_owner = user + ":" + group
|
||||
if owner != want_owner:
|
||||
raise Exception('Unexpected owner for {}: wanted "{}", got: "{}"'.format(path, want_owner, owner))
|
||||
|
||||
def assert_perm(path, want_perm):
|
||||
perm = server.succeed("stat --format '%a' {}".format(path)).strip();
|
||||
if perm != want_perm:
|
||||
raise Exception('Unexpected perm for {}: wanted "{}", got: "{}"'.format(path, want_perm, perm))
|
||||
|
||||
with subtest("Certificate is trusted in curl"):
|
||||
resp = server.succeed("curl --fail-with-body -v https://example.com")
|
||||
if resp != "Top domain":
|
||||
|
@ -123,6 +171,17 @@
|
|||
if resp != "multi3":
|
||||
raise Exception('Unexpected response, got: {}'.format(resp))
|
||||
|
||||
with subtest("Certificate has correct permission"):
|
||||
assert_owner("${cert1.paths.key}", "root", "root")
|
||||
assert_owner("${cert1.paths.cert}", "root", "root")
|
||||
assert_perm("${cert1.paths.key}", "640")
|
||||
assert_perm("${cert1.paths.cert}", "640")
|
||||
|
||||
assert_owner("${cert2.paths.key}", "root", "group2")
|
||||
assert_owner("${cert2.paths.cert}", "root", "group2")
|
||||
assert_perm("${cert2.paths.key}", "640")
|
||||
assert_perm("${cert2.paths.cert}", "640")
|
||||
|
||||
with subtest("Fail if certificate is not in CA bundle"):
|
||||
server.fail("curl --cacert /etc/static/ssl/certs/ca-bundle.crt --fail-with-body -v https://example.com")
|
||||
server.fail("curl --cacert /etc/static/ssl/certs/ca-bundle.crt --fail-with-body -v https://subdomain.example.com")
|
||||
|
|
Loading…
Reference in a new issue