nixos/syncoid: automatically setup privilege delegation

This commit is contained in:
Ben Wolsieffer 2020-02-09 19:40:52 -05:00
parent fcc8660d35
commit 733acfa140
2 changed files with 53 additions and 8 deletions

View File

@ -4,6 +4,15 @@ with lib;
let
cfg = config.services.syncoid;
# Extract pool names of local datasets (ones that don't contain "@") that
# have the specified type (either "source" or "target")
getPools = type: unique (map (d: head (builtins.match "([^/]+).*" d)) (
# Filter local datasets
filter (d: !hasInfix "@" d)
# Get datasets of the specified type
(catAttrs type (attrValues cfg.commands))
));
in {
# Interface
@ -26,14 +35,25 @@ in {
user = mkOption {
type = types.str;
default = "root";
default = "syncoid";
example = "backup";
description = ''
The user for the service. Sudo or ZFS privilege delegation must be
configured to use a user other than root.
The user for the service. ZFS privilege delegation will be
automatically configured for any local pools used by syncoid if this
option is set to a user other than root. The user will be given the
"hold" and "send" privileges on any pool that has datasets being sent
and the "create", "mount", "receive", and "rollback" privileges on
any pool that has datasets being received.
'';
};
group = mkOption {
type = types.str;
default = "syncoid";
example = "backup";
description = "The group for the service.";
};
sshKey = mkOption {
type = types.nullOr types.path;
# Prevent key from being copied to store
@ -146,6 +166,18 @@ in {
# Implementation
config = mkIf cfg.enable {
users = {
users = mkIf (cfg.user == "syncoid") {
syncoid = {
group = cfg.group;
isSystemUser = true;
};
};
groups = mkIf (cfg.group == "syncoid") {
syncoid = {};
};
};
systemd.services.syncoid = {
description = "Syncoid ZFS synchronization service";
script = concatMapStringsSep "\n" (c: lib.escapeShellArgs
@ -156,10 +188,22 @@ in {
++ c.extraArgs
++ [ "--sendoptions" c.sendOptions
"--recvoptions" c.recvOptions
"--no-privilege-elevation"
c.source c.target
])) (attrValues cfg.commands);
after = [ "zfs.target" ];
serviceConfig.User = cfg.user;
serviceConfig = {
ExecStartPre = (map (pool: lib.escapeShellArgs [
"+/run/booted-system/sw/bin/zfs" "allow"
cfg.user "hold,send" pool
]) (getPools "source")) ++
(map (pool: lib.escapeShellArgs [
"+/run/booted-system/sw/bin/zfs" "allow"
cfg.user "create,mount,receive,rollback" pool
]) (getPools "target"));
User = cfg.user;
Group = cfg.group;
};
startAt = cfg.interval;
};
};

View File

@ -38,7 +38,7 @@ in {
services.syncoid = {
enable = true;
sshKey = "/root/.ssh/id_ecdsa";
sshKey = "/var/lib/syncoid/id_ecdsa";
commonArgs = [ "--no-sync-snap" ];
commands."pool/test".target = "root@target:pool/test";
};
@ -69,11 +69,12 @@ in {
"udevadm settle",
)
source.succeed("mkdir -m 700 /root/.ssh")
source.succeed(
"cat '${snakeOilPrivateKey}' > /root/.ssh/id_ecdsa"
"mkdir -m 700 -p /var/lib/syncoid",
"cat '${snakeOilPrivateKey}' > /var/lib/syncoid/id_ecdsa",
"chmod 600 /var/lib/syncoid/id_ecdsa",
"chown -R syncoid:syncoid /var/lib/syncoid/",
)
source.succeed("chmod 600 /root/.ssh/id_ecdsa")
source.succeed("touch /tmp/mnt/test.txt")
source.systemctl("start --wait sanoid.service")