Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an example how to run ddupdate under an unprivileged systemd user #65

Open
ppetr opened this issue May 16, 2022 · 8 comments
Open

Comments

@ppetr
Copy link
Contributor

ppetr commented May 16, 2022

Recent versions support running services under DynamicUser, see https://0pointer.net/blog/dynamic-users-with-systemd.html

Below is my working example of such a set-up. I'm not sure if it makes sense to include it in the project as it is, or just keep it somewhere as an example.

# Runs ddupdate under an unprivileged, dynamic user.
# Expects:
# - Configuration in /etc/ddupdate/ddupdate.conf.
# - Credentials in /etc/ddupdate/netrc (possibly readable just by root).

[Unit]
Description=Update DNS  data for this host
Documentation=man:ddupdate.8 http://github.com/leamas/ddupdate
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/ddupdate
Environment=PATH=/bin:/usr/bin:/sbin:/usr/sbin
# Environment=http_proxy=my.proxy.domain:8888
# Environment=https_proxy=my.proxy.domain:8888
DynamicUser=yes
CacheDirectory=ddupdate
Environment="XDG_CACHE_HOME=%C/ddupdate"
ConfigurationDirectory=ddupdate
Environment="XDG_CONFIG_HOME=%E/ddupdate"
# Since /etc/ddupdate/netrc file is owned by root and is expected to be
# unreadable by unprivileged users, copy it to a runtime directory and make it
# owned by the service's user.
# See https://github.com/systemd/systemd/issues/16060
RuntimeDirectory=ddupdate
RuntimeDirectoryMode=0700
ExecStartPre=+/bin/sh -c "/usr/bin/install --verbose --owner=$(stat --format=%%u %t/ddupdate) --mode=0600 --no-target-directory %E/ddupdate/netrc %t/ddupdate/netrc"
Environment="NETRC=%t/ddupdate/netrc"

[Install]
WantedBy=multi-user.target

Since version 250 systemd also has explicit support for passing credentials (https://systemd.io/CREDENTIALS/), but I don't use recent enough version to be able to use it (and so I suppose many other users are in a similar situation).

@leamas
Copy link
Owner

leamas commented May 16, 2022

At a glance: is the temporary systemd user solving any problem here? I understand that this is extremely useful for typical "system" services which runs with all sort of privileges managing a central configuration typically in /etc. However, ddupdate is running as systemd user service. This means that that this not note in the link you gave me is applicable:

A service that need to write to files outside of /run/, /var/lib/, /var/cache/, /var/log/, /var/tmp, /tmp, /dev/shm are generally incompatible with this scheme. This rules out daemons that upgrade the system as one example, as that involves writing to /usr

ddupdate reads and writes files under invoking user's $HOME, so... OTOH, since we run as a regular user this solves many security problems. It also simplifies configuration, since nothing is done as root.

Unless, of course, we start using /etc/netrc. However, this is deprecated for the same reasons: it requires using root access. Also note that /etc/netrc is not part of the official setup (see the manpage).

OTOH, systemd credentials seems promising, it should be a fine replacement for current keyring storage for unattended use. Since I'm on Fedora I actually have 250 on my boxes. When there is time, I will look into this

@ppetr
Copy link
Contributor Author

ppetr commented May 16, 2022

The problem this is solving is reducing privileges ddupdate is running with when run as a system service. I run it as such on a few machines of mine. It doesn't make sense for me to run it under a specific user account (some even don't have one). And I also automate the configuration with Ansible centrally. So instead of running it under root, or setting up a dedicated role account I use DynamicUser so that systemd takes care of all this for me. I just set up a few environment variables as shown in the example so that ddupdate uses the right systemd locations.

But perhaps my use-case is an uncommon one.

@leamas
Copy link
Owner

leamas commented May 16, 2022

But perhaps my use-case is an uncommon one.

Probably, yes.

EDIT: But interesting!

@ppetr
Copy link
Contributor Author

ppetr commented May 16, 2022

FWIW I also have a NetworkManager hook in /etc/NetworkManager/dispatcher.d/99-ddupdate to run ddupdate immediately after an address change:

#!/bin/sh
set -e
IFACE="$1"
ACTION="$2"
case "$ACTION" in
    dhcp6-change|dhcp4-change)
        systemctl --no-ask-password start ddupdate.service
        ;;
    *) ;;
esac

@leamas
Copy link
Owner

leamas commented May 16, 2022

hm... that's a good one! Could you perhaps make it to a script in the dispatcher.d directory and make a PR? One or two lines of comments in the beginning should be documentation enough.

Also, if you have time and motivation: could you update the ddupdate.8 manpage with a section on the new NETRC environment variable? Missed that when I merged...

@ppetr
Copy link
Contributor Author

ppetr commented May 16, 2022

Gladly 😊

@leamas
Copy link
Owner

leamas commented May 16, 2022

If you do, you need to convert the script to use systemctl --user and add a line defining the user (see the other script there). This is the normal usecase, and needs to be taken care of somehow.

EDIT: Or make it two scripts, one the one you have and one using --user. Perhaps cleaner, dunno.

@ppetr
Copy link
Contributor Author

ppetr commented May 22, 2022

Now I'm trying out a slightly different approach:

  • I added RemainAfterExit=yes to ddupdate.service so that it's recognized as active or inactive.
  • I set up the NetworkManager hook to react to:
    • connectivity-change events - if full then the service is started, otherwise stopped.
    • dhcp[46]-change events invoke try-reload-or-restart on the service. So that if it's active, it's restarted, otherwise no-op.
  • I removed ddupdate.timer completely.

I'll let you know how it works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants