systemd has a DynamicUser feature, see https://www.freedesktop.org/software/systemd/man/systemd.exec.html#DynamicUser=
i'm also using: PermissionsStartOnly=true
to change the permissions, since this requires root.
however, while the ExecStart
knows the correct UID of that dynamic user, ExecStartPost
and ExecStartPre
won't know it as they are run as root (UID=0) and so i don't know how to chown it.
wish: i'd like to use the dynamic user aspect of systemd but without knowing the UID i can't chown the documents so they can be accessed by the dynamic user.
is there something else in systemd which i can use to map the permissions of the files to this temporary user?
acmeSupplied = fold (identifier: con: if (config.nixcloud.TLS.certs.${identifier}.mode) == "ACME" then con ++ [
(nameValuePair "nixcloud.TLS-acmeSupplied-${identifier}" (let
c = config.nixcloud.TLS.certs.${identifier};
allDomains = concatMapStringsSep " " (x: "--domains=${x}") ( [ c.domain ] ++ c.extraDomains);
email = if (isString c.email) then c.email else "info@${c.domain}";
hash = hashIdentifierACMEOptions identifier;
in {
description = "nixcloud.TLS: create acmeSupplied certificate for ${identifier}";
preStart = ''
mkdir -p ${stateDir}/${identifier}/acmeSupplied/${hash}
chmod 0750 ${stateDir}/${identifier}/acmeSupplied -R
chown nc-lego:${filterIdentifier identifier} ${stateDir}/${identifier} -R
'';
script = ''
cd ${stateDir}/${identifier}/acmeSupplied
${pkgs.nixcloud.lego}/bin/lego ${allDomains} --email=${email} --exclude=dns-01 --exclude=tls-alpn-01 --webroot=/run/nixcloud/lego/${identifier}/challenges --path=${stateDir}/${identifier}/acmeSupplied/${hash} --accept-tos --server=${c.acmeApiEndpoint} run
${pkgs.nixcloud.lego}/bin/lego ${allDomains} --email=${email} --exclude=dns-01 --exclude=tls-alpn-01 --webroot=/run/nixcloud/lego/${identifier}/challenges --path=${stateDir}/${identifier}/acmeSupplied/${hash} --accept-tos --server=${c.acmeApiEndpoint} renew --days 15
'';
postStart = ''
chown nc-lego:${filterIdentifier identifier} ${stateDir}/${identifier} -R
chmod 0750 ${stateDir}/${identifier}/acmeSupplied -R
'';
serviceConfig = {
#DynamicUser = true;
User = "nc-lego";
ReadWritePaths = "-${stateDir}/${identifier}/acmeSupplied";
SupplementaryGroups = "${filterIdentifier identifier}";
PermissionsStartOnly = true;
Type = "oneshot";
RuntimeDirectory = "nixcloud/lego/${identifier}/challenges";
};
before = [ "nixcloud.TLS-acmeSupplied-certificates.target" ];
wantedBy = [ "nixcloud.TLS-acmeSupplied-certificates.target" ];
}))
] else con) [] (attrNames config.nixcloud.TLS.certs);
acmeSupplied = fold (identifier: con: if (config.nixcloud.TLS.certs.${identifier}.mode) == "ACME" then con ++ [
(nameValuePair "nixcloud.TLS-acmeSupplied-${identifier}" (let
c = config.nixcloud.TLS.certs.${identifier};
allDomains = concatMapStringsSep " " (x: "--domains=${x}") ( [ c.domain ] ++ c.extraDomains);
email = if (isString c.email) then c.email else "info@${c.domain}";
hash = hashIdentifierACMEOptions identifier;
in {
description = "nixcloud.TLS: create acmeSupplied certificate for ${identifier}";
preStart = ''
mkdir -p ${stateDir}/${identifier}/acmeSupplied/${hash}
chmod 0750 ${stateDir}/${identifier}/acmeSupplied -R
chown nixcloud.TLS-acmeSupplied-mail.nix.lt:${filterIdentifier identifier} ${stateDir}/${identifier} -R
'';
script = ''
cd ${stateDir}/${identifier}/acmeSupplied
${pkgs.nixcloud.lego}/bin/lego ${allDomains} --email=${email} --exclude=dns-01 --exclude=tls-alpn-01 --webroot=/run/nixcloud/lego/${identifier}/challenges --path=${stateDir}/${identifier}/acmeSupplied/${hash} --accept-tos --server=${c.acmeApiEndpoint} run
${pkgs.nixcloud.lego}/bin/lego ${allDomains} --email=${email} --exclude=dns-01 --exclude=tls-alpn-01 --webroot=/run/nixcloud/lego/${identifier}/challenges --path=${stateDir}/${identifier}/acmeSupplied/${hash} --accept-tos --server=${c.acmeApiEndpoint} renew --days 15
'';
postStart = ''
chown nixcloud.TLS-acmeSupplied-mail.nix.lt:${filterIdentifier identifier} ${stateDir}/${identifier} -R
chmod 0750 ${stateDir}/${identifier}/acmeSupplied -R
'';
serviceConfig = {
DynamicUser = true;
ReadWritePaths = "-${stateDir}/${identifier}/acmeSupplied";
SupplementaryGroups = "${filterIdentifier identifier}";
PermissionsStartOnly = true;
Type = "oneshot";
RuntimeDirectory = "nixcloud/lego/${identifier}/challenges";
};
before = [ "nixcloud.TLS-acmeSupplied-certificates.target" ];
wantedBy = [ "nixcloud.TLS-acmeSupplied-certificates.target" ];
}))
] else con) [] (attrNames config.nixcloud.TLS.certs);
the error message:
mailserver-nix-lt> updating GRUB 2 menu...
mailserver-nix-lt> activating the configuration...
mailserver-nix-lt> setting up /etc...
mailserver-nix-lt> setting up tmpfiles
mailserver-nix-lt> reloading the following units: dbus.service
mailserver-nix-lt> warning: the following units failed: nixcloud.TLS-acmeSupplied-mail.nix.lt.service
mailserver-nix-lt>
mailserver-nix-lt> ● nixcloud.TLS-acmeSupplied-mail.nix.lt.service - nixcloud.TLS: create acmeSupplied certificate for mail.nix.lt
mailserver-nix-lt> Loaded: loaded (/nix/store/i4nxkvw8bf9vs2brhj3lyjakxklq0rxg-unit-nixcloud.TLS-acmeSupplied-mail.nix.lt.service/nixcloud.TLS-acmeSupplied-mail.nix.lt.service; enabled; vendor preset: enabled)
mailserver-nix-lt> Active: failed (Result: exit-code) since Thu 2018-10-11 10:55:46 CEST; 22ms ago
mailserver-nix-lt> Process: 20613 ExecStartPre=/nix/store/c69ccjnc2n5y6g1p9q168kjlgf2w3l10-unit-script/bin/nixcloud.TLS-acmeSupplied-mail.nix.lt-pre-start (code=exited, status=1/FAILURE)
mailserver-nix-lt> Main PID: 20243 (code=exited, status=0/SUCCESS)
mailserver-nix-lt>
mailserver-nix-lt> Oct 11 10:55:46 mail.nix.lt systemd[1]: Starting nixcloud.TLS: create acmeSupplied certificate for mail.nix.lt...
mailserver-nix-lt> Oct 11 10:55:46 mail.nix.lt nixcloud.TLS-acmeSupplied-mail.nix.lt-pre-start[20613]: chown: invalid user: ‘nixcloud.TLS-acmeSupplied-mail.nix.lt:nc-mail-nix-lt’
mailserver-nix-lt> Oct 11 10:55:46 mail.nix.lt systemd[1]: nixcloud.TLS-acmeSupplied-mail.nix.lt.service: Control process exited, code=exited status=1
mailserver-nix-lt> Oct 11 10:55:46 mail.nix.lt systemd[1]: nixcloud.TLS-acmeSupplied-mail.nix.lt.service: Failed with result 'exit-code'.
mailserver-nix-lt> Oct 11 10:55:46 mail.nix.lt systemd[1]: Failed to start nixcloud.TLS: create acmeSupplied certificate for mail.nix.lt.
mailserver-nix-lt> error: Traceback (most recent call last):
File "/nix/store/ixarqxfbhr06fp8y8pj5dcm1rhar2j15-nixops-1.6/lib/python2.7/site-packages/nixops/deployment.py", line 731, in worker
raise Exception("unable to activate new configuration")
Exception: unable to activate new configuration
as the documentation states on DynamicUser
:
If these options are not used and
dynamic user/group allocation is enabled for a unit, the name of
the dynamic user/group is implicitly derived from the unit name.
If the unit name without the type suffix qualifies as valid user
name it is used directly, otherwise a name incorporating a hash
of it is used.
but what is this hash?
however, while the ExecStart knows the correct UID of that dynamic user, ExecStartPost and ExecStartPre won't know it as they are run as root (UID=0) and so i don't know how to chown it.
I don’t think that’s true – ExecStartPre=
and ExecStartPost=
run as the same User=
and Group=
(and with the same CapabilityBoundingSet=
, SystemCallFilter=
, etc. etc.) as the main executable, unless configured differently (using the +
, !
or !!
prefix – see man 5 systemd.service
). I tested it with this test service.
wish: i'd like to use the dynamic user aspect of systemd but without knowing the UID i can't chown the documents so they can be accessed by the dynamic user.
This is not supposed to be necessary. Your service should store state in its StateDirectory=
, runtime files in its RuntimeDirectory=
, cache data in its CacheDirectory=
, logs in its LogsDirectory=
(if it has any logging of its own – but consider using the journal), and read config from its ConfigurationDirectory=
(but that’s not writable to the service anyways). systemd will manage the ownership of those directories automatically for you, and they follow standard UNIX conventions (they’re subdirectories below /var/lib
, /run
, /var/cache
, /var/log
, /etc
– see man 5 systemd.exec
and this blog post for details).
If your daemon can’t be limited to these standard directories, then the simplest solution is probably to not use DynamicUser=
– there’s no shame in using a statically allocated user for more complicated setups. But consider creating the user via systemd-sysusers
(see man 5 sysusers.d
) instead of invoking adduser
/useradd
directly, especially if you’re writing a distribution package, so that it’s obvious which package installed the user (by checking which package owns the respective systemd-sysusers
configuration file).