1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
|
---
title: systemd Dynamic Users
date: 2020-04-09
---
{% extends 'templates/base.html' %}
{% block body %}
<nav>
<a href='/projects'>> projects</a>
</nav>
<header>
<h1>{{ title }}</h1>
</header>
<article>
{% markdown %}
The Linux init system [systemd](https://en.wikipedia.org/wiki/Systemd) has its controversies, but it also has some pretty neat features, my favorite of which is _Dynamic Users_.
Lennart Poettering's blog has a [full rundown of the ins and outs](http://0pointer.net/blog/dynamic-users-with-systemd.html), but I'll summarize the key points below, and document a few tricks.
<details markdown='1'>
<summary>contents…</summary>
<div markdown='block'>
[TOC]
</div>
</details>
## Users and groups and Name Service Switch, oh my!
On Linux, when you create a user it is usually added to `/etc/passwd`, and groups it is in are added to `/etc/groups`.
However, these databases and others can actually come from multiple sources, controlled by the [Name Service Switch](https://en.wikipedia.org/wiki/Name_Service_Switch), and on modern Linux one of those sources is systemd.
When configured to, systemd will create a user for the lifetime of a Unit to run that Unit.
The user is never added to `/etc/passwd`, but exists only at runtime, and when the Unit finishes the user goes away.
Although they are very locked down by default, this user is a full Linux user, and can be added to groups, write to disk, or be given capabilities.
In particular, systemd provides a system for _State Directories_, which will be writable by the created user, persist after the Unit finishes, and will be fixed-up the next time the Unit runs to be writeable by the next dynamically created user, which can be used to store service data, or to simulate a home directory.
## Simple services
A very useful result of Dynamic Users is to simplify the packaging and running of services.
Instead of having to create a user to run a service when it is installed and remove it later when the service is uninstalled, the service package only needs to install a service file as the user is created at runtime.
For example, below is the configuration for a web service of mine:
```sh
$ cat /lib/systemd/system/log.eth.moe
[Unit]
Description=Backend server for https://log.eth.moe
[Service]
# Create a user at runtime.
# It will have a random name.
DynamicUser=yes
# Set the user's primary group to www-data.
Group=www-data
# Create a temporary runtime directory at /run/log-eth-moe/.
RuntimeDirectory=log-eth-moe
# Create a persistent state directory at /var/lib/log-eth-moe/.
StateDirectory=log-eth-moe
ExecStart=/usr/bin/log.eth.moe -socket /run/log-eth-moe/listen.sock -dir /var/lib/log-eth-moe/
[Install]
WantedBy=multi-user.target
```
It's also generally useful any time you want a non-human user to run something.
For example, below is [my configuration](https://github.com/ethulhu/kodi-systemd-service/) for running the [Kodi media center](https://kodi.tv/) on a Raspberry Pi:
```sh
$ cat /lib/systemd/system/kodi.service
[Unit]
Description=Kodi Media Center
After=systemd-user-sessions.service network.target sound.target
[Service]
# Create a user at runtime.
DynamicUser=yes
# Call that user "kodi".
User=kodi
# Add it to useful groups for a media center.
# Note that these are all supplementary groups,
# and the dynamic user has no primary group.
SupplementaryGroups=audio
SupplementaryGroups=input
SupplementaryGroups=plugdev
SupplementaryGroups=video
# Create a persistent state directory at /var/lib/kodi.
StateDirectory=kodi
# Set the home directory of the dynamic user to /var/lib/kodi.
Environment=HOME=/var/lib/kodi
ExecStart=/usr/bin/kodi-standalone
[Install]
WantedBy=multi-user.target
```
## Sandboxed Steam?
[Steam](https://store.steampowered.com/) is great, but it also requires running other people's code, so it's best to sandbox it a little.
While [some people](https://blog.jessfraz.com/post/docker-containers-on-the-desktop/) might go to the lengths of [running Steam in Docker](http://blog.drwahl.me/steam-running-in-docker-lxc/), I use systemd to achieve similar ends.
The steps of this approach are:
- Create a group to share your display with.
For myself, `eth`, I created `eth-x11`.
- Allow members of that group to access your display.
- Run Steam as a member of that group, directed to use your display.
### Implementation
After creating your group, grant that group permission to use your display, substituting `eth-x11` for your group:
```sh
$ xhost +si:localgroup:eth-x11
```
This will need to be done every X11 session, and should probably be added to your `.xinitrc` or `.xsession`.
Then create a script to run Steam under `systemd-run`:
- Run the command with a TTY so that Steam's log messages are output to your terminal for debugging.
- Create a user at runtime.
- Set the _primary_ group of the user to be `eth-x11`.
The `xhost` command above requires it to be the primary group.
- Add the user to the groups `audio`, `input`, and `video`.
- Create a `StateDirectory` called `steam`, which `systemd` will put at `/var/lib/steam/`.
- Set the `$DISPLAY` to be the current X11 display.
- Set the `$HOME` of the running process to `/var/lib/steam/`.
```sh
$ cat /usr/local/bin/steam
#!/bin/sh
set -eux
systemd-run \
--pty \
--property=DynamicUser=yes \
--property=Group=eth-x11 \
--property=SupplementaryGroups=audio \
--property=SupplementaryGroups=input \
--property=SupplementaryGroups=video \
--property=StateDirectory=steam \
--property=Environment=DISPLAY=${DISPLAY} \
--property=Environment=HOME=/var/lib/steam \
/usr/games/steam
```
Unfortunately, to use `systemd-run` you must either be root or authenticate yourself to systemd, but the experience can be improved with `sudoers` or setuid on the script.
{% endmarkdown %}
</article>
{% endblock %}
|