docs: use collections to structure the data

This commit is contained in:
hulkoba
2024-02-22 10:11:54 +01:00
parent 4049d92fda
commit 5e8ff010a1
83 changed files with 144 additions and 107 deletions
+136
View File
@@ -0,0 +1,136 @@
---
title: Converting Existing Users to systemd-homed
category: Users, Groups and Home Directories
layout: default
SPDX-License-Identifier: LGPL-2.1-or-later
---
# Converting Existing Users to systemd-homed managed Users
Traditionally on most Linux distributions, regular (human) users are managed
via entries in `/etc/passwd`, `/etc/shadow`, `/etc/group` and
`/etc/gshadow`. With the advent of
[`systemd-homed`](https://www.freedesktop.org/software/systemd/man/systemd-homed.service.html)
it might be desirable to convert an existing, traditional user account to a
`systemd-homed` managed one. Below is a brief guide how to do that.
Before continuing, please read up on these basic concepts:
* [Home Directories](HOME_DIRECTORY)
* [JSON User Records](USER_RECORD)
* [JSON Group Records](GROUP_RECORD)
* [User/Group Record Lookup API via Varlink](USER_GROUP_API)
## Caveat
This is a manual process, and possibly a bit fragile. Hence, do this at your
own risk, read up beforehand, and make a backup first. You know what's at
stake: your own home directory, i.e. all your personal data.
## Step-By-Step
Here's the step-by-step guide:
0. Preparations: make sure you run a distribution that has `systemd-homed`
enabled and properly set up, including the necessary PAM and NSS
configuration updates. Make sure you have enough disk space in `/home/` for
a (temporary) second copy of your home directory. Make sure to backup your
home directory. Make sure to log out of your user account fully. Then log in
as root on the console.
1. Rename your existing home directory to something safe. Let's say your user
ID is `foobar`. Then do:
```
mv /home/foobar /home/foobar.saved
```
2. Have a look at your existing user record, as stored in `/etc/passwd` and
related files. We want to use the same data for the new record, hence it's good
looking at the old data. Use commands such as:
```
getent passwd foobar
getent shadow foobar
```
This will tell you the `/etc/passwd` and `/etc/shadow` entries for your
user. For details about the fields, see the respective man pages
[passwd(5)](https://man7.org/linux/man-pages/man5/passwd.5.html) and
[shadow(5)](https://man7.org/linux/man-pages/man5/shadow.5.html).
The fourth field in the `getent passwd foobar` output tells you the GID of
your user's main group. Depending on your distribution it's a group private
to the user, or a group shared by most local, regular users. Let's say the
GID reported is 1000, let's then query its details:
```
getent group 1000
```
This will tell you the name of that group. If the name is the same as your
user name your distribution apparently provided you with a private group for
your user. If it doesn't match (and is something like `users`) it apparently
didn't. Note that `systemd-homed` will always manage a private group for
each user under the same name, hence if your distribution is one of the
latter kind, then there's a (minor) mismatch in structure when converting.
Save the information reported by these three commands somewhere, for later
reference.
3. Now edit your `/etc/passwd` file and remove your existing record
(i.e. delete a single line, the one of your user's account, leaving all
other lines unmodified). Similar for `/etc/shadow`, `/etc/group` (in case
you have a private group for your user) and `/etc/gshadow`. Most
distributions provide you with a tool for that, that adds safe
synchronization for these changes: `vipw`, `vipw -s`, `vigr` and `vigr -s`.
4. At this point the old user account vanished, while the home directory still
exists safely under the `/home/foobar.saved` name. Let's now create a new
account with `systemd-homed`, using the same username and UID as before:
```
homectl create foobar --uid=$UID --real-name=$GECOS
```
In this command line, replace `$UID` by the UID you previously used,
i.e. the third field of the `getent passwd foobar` output above. Similar,
replace `$GECOS` by the GECOS field of your old account, i.e the fifth field
of the old output. If your distribution traditionally does not assign a
private group to regular user groups, then consider adding `--member-of=`
with the group name to get a modicum of compatibility with the status quo
ante: this way your new user account will still not have the old primary
group as new primary group, but will have it as auxiliary group.
Consider reading through the
[homectl(1)](https://www.freedesktop.org/software/systemd/man/homectl.html)
manual page at this point, maybe there are a couple of other settings you
want to set for your new account. In particular, look at `--storage=` and
`--disk-size=`, in order to change how your home directory shall be stored
(the default `luks` storage is recommended).
5. Your new user account exists now, but it has an empty home directory. Let's
now migrate your old home directory into it. For that let's mount the new
home directory temporarily and copy the data in.
```
homectl with foobar -- rsync -aHANUXv --remove-source-files /home/foobar.saved/ .
```
This mounts the home directory of the user, and then runs the specified
`rsync` command which copies the contents of the old home directory into the
new. The new home directory is the working directory of the invoked `rsync`
process. We are invoking this command as root, hence the `rsync` runs as
root too. When the `rsync` command completes the home directory is
automatically unmounted again. Since we used `--remove-source-files` all files
copied are removed from the old home directory as the copy progresses. After
the command completes the old home directory should be empty. Let's remove
it hence:
```
rmdir /home/foobar.saved
```
And that's it, we are done already. You can log out now and should be able to
log in under your user account as usual, but now with `systemd-homed` managing
your home directory.
+163
View File
@@ -0,0 +1,163 @@
---
title: JSON Group Records
category: Users, Groups and Home Directories
layout: default
SPDX-License-Identifier: LGPL-2.1-or-later
---
# JSON Group Records
Long story short: JSON Group Records are to `struct group` what
[JSON User Records](USER_RECORD) are to `struct passwd`.
Conceptually, much of what applies to JSON user records also applies to JSON
group records. They also consist of seven sections, with similar properties and
they carry some identical (or at least very similar) fields.
## Fields in the `regular` section
`groupName` → A string with the UNIX group name. Matches the `gr_name` field of
UNIX/glibc NSS `struct group`, or the shadow structure `struct sgrp`'s
`sg_namp` field.
`realm` → The "realm" the group belongs to, conceptually identical to the same
field of user records. A string in DNS domain name syntax.
`description` → A descriptive string for the group. This is similar to the
`realName` field of user records, and accepts arbitrary strings, as long as
they follow the same GECOS syntax requirements as `realName`.
`disposition` → The disposition of the group, conceptually identical to the
same field of user records. A string.
`service` → A string, an identifier for the service managing this group record
(this field is typically in reverse domain name syntax.)
`lastChangeUSec` → An unsigned 64-bit integer, a timestamp (in µs since the UNIX
epoch 1970) of the last time the group record has been modified. (Covers only
the `regular`, `perMachine` and `privileged` sections).
`gid` → An unsigned integer in the range 0…4294967295: the numeric UNIX group
ID (GID) to use for the group. This corresponds to the `gr_gid` field of
`struct group`.
`members` → An array of strings, listing user names that are members of this
group. Note that JSON user records also contain a `memberOf` field, or in other
words a group membership can either be denoted in the JSON user record or in
the JSON group record, or in both. The list of memberships should be determined
as the combination of both lists (plus optionally others). If a user is listed
as member of a group and doesn't exist it should be ignored. This field
corresponds to the `gr_mem` field of `struct group` and the `sg_mem` field of
`struct sgrp`.
`administrators` → Similarly, an array of strings, listing user names that
shall be considered "administrators" of this group. This field corresponds to
the `sg_adm` field of `struct sgrp`.
`privileged`/`perMachine`/`binding`/`status`/`signature`/`secret` → The
objects/arrays for the other six group record sections. These are organized the
same way as for the JSON user records, and have the same semantics.
## Fields in the `privileged` section
The following fields are defined:
`hashedPassword` → An array of strings with UNIX hashed passwords; see the
matching field for user records for details. This field corresponds to the
`sg_passwd` field of `struct sgrp` (and `gr_passwd` of `struct group` in a
way).
## Fields in the `perMachine` section
`matchMachineId`/`matchHostname` → Strings, match expressions similar as for
user records, see the user record documentation for details.
The following fields are defined for the `perMachine` section and are defined
equivalent to the fields of the same name in the `regular` section, and
override those:
`gid`, `members`, `administrators`
## Fields in the `binding` section
The following fields are defined for the `binding` section, and are equivalent
to the fields of the same name in the `regular` and `perMachine` sections:
`gid`
## Fields in the `status` section
The following fields are defined in the `status` section, and are mostly
equivalent to the fields of the same name in the `regular` section, though with
slightly different conceptual semantics, see the same fields in the user record
documentation:
`service`
## Fields in the `signature` section
The fields in this section are defined identically to those in the matching
section in the user record.
## Fields in the `secret` section
Currently no fields are defined in this section for group records.
## Mapping to `struct group` and `struct sgrp`
When mapping classic UNIX group records (i.e. `struct group` and `struct sgrp`)
to JSON group records the following mappings should be applied:
| Structure | Field | Section | Field | Condition |
|----------------|-------------|--------------|------------------|----------------------------|
| `struct group` | `gr_name` | `regular` | `groupName` | |
| `struct group` | `gr_passwd` | `privileged` | `password` | (See notes below) |
| `struct group` | `gr_gid` | `regular` | `gid` | |
| `struct group` | `gr_mem` | `regular` | `members` | |
| `struct sgrp` | `sg_namp` | `regular` | `groupName` | |
| `struct sgrp` | `sg_passwd` | `privileged` | `password` | (See notes below) |
| `struct sgrp` | `sg_adm` | `regular` | `administrators` | |
| `struct sgrp` | `sg_mem` | `regular` | `members` | |
At this time almost all Linux machines employ shadow passwords, thus the
`gr_passwd` field in `struct group` is set to `"x"`, and the actual password
is stored in the shadow entry `struct sgrp`'s field `sg_passwd`.
## Extending These Records
The same logic and recommendations apply as for JSON user records.
## Examples
A reasonable group record for a system group might look like this:
```json
{
"groupName" : "systemd-resolve",
"gid" : 193,
"status" : {
"6b18704270e94aa896b003b4340978f1" : {
"service" : "io.systemd.NameServiceSwitch"
}
}
}
```
And here's a more complete one for a regular group:
```json
{
"groupName" : "grobie",
"binding" : {
"6b18704270e94aa896b003b4340978f1" : {
"gid" : 60232
}
},
"disposition" : "regular",
"status" : {
"6b18704270e94aa896b003b4340978f1" : {
"service" : "io.systemd.Home"
}
}
}
```
+178
View File
@@ -0,0 +1,178 @@
---
title: Home Directories
category: Users, Groups and Home Directories
layout: default
SPDX-License-Identifier: LGPL-2.1-or-later
---
# Home Directories
[`systemd-homed.service(8)`](https://www.freedesktop.org/software/systemd/man/systemd-homed.service.html)
manages home directories of regular ("human") users. Each directory it manages
encapsulates both the data store and the user record of the user, so that it
comprehensively describes the user account, and is thus naturally portable
between systems without any further, external metadata. This document describes
the format used by these home directories, in the context of the storage
mechanism used.
## General Structure
Inside of the home directory a file `~/.identity` contains the JSON formatted
user record of the user. It follows the format defined in
[`JSON User Records`](USER_RECORD). It is recommended to bring the
record into 'normalized' form (i.e. all objects should contain their fields
sorted alphabetically by their key) before storing it there, though this is not
required nor enforced. Since the user record is cryptographically signed, the
user cannot make modifications to the file on their own (at least not without
corrupting it, or knowing the private key used for signing the record). Note
that user records are stored here without their `binding`, `status` and
`secret` sections, i.e. only with the sections included in the signature plus
the signature section itself.
## Storage Mechanism: Plain Directory/`btrfs` Subvolume
If the plain directory or `btrfs` subvolume storage mechanism of
`systemd-homed` is used (i.e. `--storage=directory` or `--storage=subvolume` on
the
[`homectl(1)`](https://www.freedesktop.org/software/systemd/man/homectl.html)
command line) the home directory requires no special setup besides including
the user record in the `~/.identity` file.
It is recommended to name home directories managed this way by
`systemd-homed.service` by the user name, suffixed with `.homedir` (example:
`lennart.homedir` for a user `lennart`) but this is not enforced. When the user
is logged in, the directory is generally mounted to `/home/$USER` (in our
example: `/home/lennart`), thus dropping the suffix while the home directory is
active. `systemd-homed` will automatically discover home directories named this
way in `/home/*.homedir` and synthesize NSS user records for them as they show
up.
## Storage Mechanism: `fscrypt` Directories
This storage mechanism is mostly identical to the plain directory storage
mechanism, except that the home directory is encrypted using `fscrypt`. (Use
`--storage=fscrypt` on the `homectl` command line.) Key management is
implemented via extended attributes on the directory itself: for each password
an extended attribute `trusted.fscrypt_slot0`, `trusted.fscrypt_slot1`,
`trusted.fscrypt_slot2`, … is maintained. Its value contains a colon-separated
pair of Base64 encoded data fields. The first field contains a salt value, the
second field the encrypted volume key. The latter is encrypted using AES256 in
counter mode, using a key derived from the password via PBKDF2-HMAC-SHA512,
together with the salt value. The construction is similar to what LUKS does for
`dm-crypt` encrypted volumes. Note that extended attributes are not encrypted
by `fscrypt` and hence are suitable for carrying the key slots. Moreover, by
using extended attributes, the slots are directly attached to the directory and
an independent sidecar key database is not required.
## Storage Mechanism: `cifs` Home Directories
In this storage mechanism, the home directory is mounted from a CIFS server and
service at login, configured inside the user record. (Use `--storage=cifs` on
the `homectl` command line.) The local password of the user is used to log into
the CIFS service. The directory share needs to contain the user record in
`~/.identity` as well. Note that this means that the user record needs to be
registered locally before it can be mounted for the first time, since CIFS
domain and server information needs to be known *before* the mount. Note that
for all other storage mechanisms it is entirely sufficient if the directories
or storage artifacts are placed at the right locations — all information to
activate them can be derived automatically from their mere availability.
## Storage Mechanism: `luks` Home Directories
This is the most advanced and most secure storage mechanism and consists of a
Linux file system inside a LUKS2 volume inside a loopback file (or on removable
media). (Use `--storage=luks` on the `homectl` command line.) Specifically:
* The image contains a GPT partition table. For now it should only contain a
single partition, and that partition must have the type UUID
`773f91ef-66d4-49b5-bd83-d683bf40ad16`. Its partition label must be the
user name.
* This partition must contain a LUKS2 volume, whose label must be the user
name. The LUKS2 volume must contain a LUKS2 token field of type
`systemd-homed`. The JSON data of this token must have a `record` field,
containing a string with base64-encoded data. This data is the JSON user
record, in the same serialization as in `~/.identity`, though encrypted. The
JSON data of this token must also have an `iv` field, which contains a
base64-encoded binary initialization vector for the encryption. The
encryption used is the same as the LUKS2 volume itself uses, unlocked by the
same volume key, but based on its own IV.
* Inside of this LUKS2 volume must be a Linux file system, one of `ext4`,
`btrfs` and `xfs`. The file system label must be the user name.
* This file system should contain a single directory named after the user. This
directory will become the home directory of the user when activated. It
contains a second copy of the user record in the `~/.identity` file, like in
the other storage mechanisms.
The image file should reside in a directory `/home/` on the system,
named after the user, suffixed with `.home`. When activated, the container home
directory is mounted to the same path, though with the `.home` suffix dropped —
unless a different mount point is defined in the user record. (e.g.: the
loopback file `/home/waldo.home` is mounted to `/home/waldo` while activated.)
When the image is stored on removable media (such as a USB stick), the image
file can be directly `dd`'ed onto it; the format is unchanged. The GPT envelope
should ensure the image is properly recognizable as a home directory both when
used in a loopback file and on a removable USB stick. (Note that when mounting
a home directory from a USB stick, it too defaults to a directory in `/home/`,
named after the username, with no further suffix.)
Rationale for the GPT partition table envelope: this way the image is nicely
discoverable and recognizable already by partition managers as a home
directory. Moreover, when copied onto a USB stick the GPT envelope makes sure
the stick is properly recognizable as a portable home directory
medium. (Moreover, it allows embedding additional partitions later on, for
example on a multi-purpose USB stick that contains both a home
directory and a generic storage volume.)
Rationale for including the encrypted user record in the LUKS2 header:
Linux kernel file system implementations are generally not robust towards
maliciously formatted file systems; there's a good chance that file system
images can be used as attack vectors, exploiting the kernel. Thus it is
necessary to validate the home directory image *before* mounting it and
establishing a minimal level of trust. Since the user record data is
cryptographically signed and user records not signed with a recognized private
key are not accepted, a minimal level of trust between the system and the home
directory image is established.
Rationale for storing the home directory one level below to root directory of
the contained file system: this way special directories such as `lost+found/`
do not show up in the user's home directory.
## Algorithm
Regardless of the storage mechanism used, an activated home directory
necessarily involves a mount point to be established. In case of the
directory-based storage mechanisms (`directory`, `subvolume` and `fscrypt`)
this is a bind mount. In case of `cifs` this is a CIFS network mount, and in
case of the LUKS2 backend a regular block device mount of the file system
contained in the LUKS2 image. By requiring a mount for all cases (even for
those that already are a directory), a clear logic is defined to distinguish
active and inactive home directories, so that the directories become
inaccessible under their regular path the instant they are
deactivated. Moreover, the `nosuid`, `nodev` and `noexec` flags configured in
the user record are applied when the bind mount is established.
During activation, the user records retained on the host, the user record
stored in the LUKS2 header (in case of the LUKS2 storage mechanism) and the
user record stored inside the home directory in `~/.identity` are
compared. Activation is only permitted if they match the same user and are
signed by a recognized key. When the three instances differ in `lastChangeUSec`
field, the newest record wins, and is propagated to the other two locations.
During activation, the file system checker (`fsck`) appropriate for the
selected file system is automatically invoked, ensuring the file system is in a
healthy state before it is mounted.
If the UID assigned to a user does not match the owner of the home directory in
the file system, the home directory is automatically and recursively `chown()`ed
to the correct UID.
Depending on the `luksDiscard` setting of the user record, either the backing
loopback file is `fallocate()`ed during activation, or the mounted file system
is `FITRIM`ed after mounting, to ensure the setting is correctly enforced.
When deactivating a home directory, the file system or block device is trimmed
or extended as configured in the `luksOfflineDiscard` setting of the user
record.
+326
View File
@@ -0,0 +1,326 @@
---
title: Users, Groups, UIDs and GIDs on systemd Systems
category: Users, Groups and Home Directories
layout: default
SPDX-License-Identifier: LGPL-2.1-or-later
---
# Users, Groups, UIDs and GIDs on systemd Systems
Here's a summary of the requirements `systemd` (and Linux) make on UID/GID
assignments and their ranges.
Note that while in theory UIDs and GIDs are orthogonal concepts they really
aren't IRL. With that in mind, when we discuss UIDs below it should be assumed
that whatever we say about UIDs applies to GIDs in mostly the same way, and all
the special assignments and ranges for UIDs always have mostly the same
validity for GIDs too.
## Special Linux UIDs
In theory, the range of the C type `uid_t` is 32-bit wide on Linux,
i.e. 0…4294967295. However, four UIDs are special on Linux:
1. 0 → The `root` super-user.
2. 65534 → The `nobody` UID, also called the "overflow" UID or similar. It's
where various subsystems map unmappable users to, for example file systems
only supporting 16-bit UIDs, NFS or user namespacing. (The latter can be
changed with a sysctl during runtime, but that's not supported on
`systemd`. If you do change it you void your warranty.) Because Fedora is a
bit confused the `nobody` user is called `nfsnobody` there (and they have a
different `nobody` user at UID 99). I hope this will be corrected eventually
though. (Also, some distributions call the `nobody` group `nogroup`. I wish
they didn't.)
3. 4294967295, aka "32-bit `(uid_t) -1`" → This UID is not a valid user ID, as
`setresuid()`, `chown()` and friends treat -1 as a special request to not
change the UID of the process/file. This UID is hence not available for
assignment to users in the user database.
4. 65535, aka "16-bit `(uid_t) -1`" → Before Linux kernel 2.4 `uid_t` used to be
16-bit, and programs compiled for that would hence assume that `(uid_t) -1`
is 65535. This UID is hence not usable either.
The `nss-systemd` glibc NSS module will synthesize user database records for
the UIDs 0 and 65534 if the system user database doesn't list them. This means
that any system where this module is enabled works to some minimal level
without `/etc/passwd`.
## Special Distribution UID ranges
Distributions generally split the available UID range in two:
1. 1…999 → System users. These are users that do not map to actual "human"
users, but are used as security identities for system daemons, to implement
privilege separation and run system daemons with minimal privileges.
2. 1000…65533 and 65536…4294967294 → Everything else, i.e. regular (human) users.
Some older systems placed the boundary at 499/500, or even 99/100,
and some distributions allow the boundary between system and regular users to be changed
via local configuration.
In `systemd`, the boundary is configurable during compilation time
and is also queried from `/etc/login.defs` at runtime,
if the `-Dcompat-mutable-uid-boundaries=true` compile-time setting is used.
We strongly discourage downstreams from changing the boundary from the upstream default of 999/1000.
Also note that programs such as `adduser` tend to allocate from a subset of the
available regular user range only, usually 1000..60000.
This range can also be configured using `/etc/login.defs`.
Note that systemd requires that system users and groups are resolvable without
network — a requirement that is not made for regular users. This
means regular users may be stored in remote LDAP or NIS databases, but system
users may not (except when there's a consistent local cache kept, that is
available during earliest boot, including in the initrd).
## Special `systemd` GIDs
`systemd` defines no special UIDs beyond what Linux already defines (see
above). However, it does define some special group/GID assignments, which are
primarily used for `systemd-udevd`'s device management. The precise list of the
currently defined groups is found in this `sysusers.d` snippet:
[basic.conf](https://raw.githubusercontent.com/systemd/systemd/main/sysusers.d/basic.conf.in)
It's strongly recommended that downstream distributions include these groups in
their default group databases.
Note that the actual GID numbers assigned to these groups do not have to be
constant beyond a specific system. There's one exception however: the `tty`
group must have the GID 5. That's because it must be encoded in the `devpts`
mount parameters during earliest boot, at a time where NSS lookups are not
possible. (Note that the actual GID can be changed during `systemd` build time,
but downstreams are strongly advised against doing that.)
## Special `systemd` UID ranges
`systemd` defines a number of special UID ranges:
1. 60001…60513 → UIDs for home directories managed by
[`systemd-homed.service(8)`](https://www.freedesktop.org/software/systemd/man/systemd-homed.service.html). UIDs
from this range are automatically assigned to any home directory discovered,
and persisted locally on first login. On different systems the same user
might get different UIDs assigned in case of conflict, though it is
attempted to make UID assignments stable, by deriving them from a hash of
the user name.
2. 61184…65519 → UIDs for dynamic users are allocated from this range (see the
`DynamicUser=` documentation in
[`systemd.exec(5)`](https://www.freedesktop.org/software/systemd/man/systemd.exec.html)). This
range has been chosen so that it is below the 16-bit boundary (i.e. below
65535), in order to provide compatibility with container environments that
assign a 64K range of UIDs to containers using user namespacing. This range
is above the 60000 boundary, so that its allocations are unlikely to be
affected by `adduser` allocations (see above). And we leave some room
upwards for other purposes. (And if you wonder why precisely these numbers:
if you write them in hexadecimal, they might make more sense: 0xEF00 and
0xFFEF). The `nss-systemd` module will synthesize user records implicitly
for all currently allocated dynamic users from this range. Thus, NSS-based
user record resolving works correctly without those users being in
`/etc/passwd`.
3. 524288…1879048191 → UID range for `systemd-nspawn`'s automatic allocation of
per-container UID ranges. When the `--private-users=pick` switch is used (or
`-U`) then it will automatically find a so far unused 16-bit subrange of this
range and assign it to the container. The range is picked so that the upper
16-bit of the 32-bit UIDs are constant for all users of the container, while
the lower 16-bit directly encode the 65536 UIDs assigned to the
container. This mode of allocation means that the upper 16-bit of any UID
assigned to a container are kind of a "container ID", while the lower 16-bit
directly expose the container's own UID numbers. If you wonder why precisely
these numbers, consider them in hexadecimal: 0x00080000…0x6FFFFFFF. This
range is above the 16-bit boundary. Moreover it's below the 31-bit boundary,
as some broken code (specifically: the kernel's `devpts` file system)
erroneously considers UIDs signed integers, and hence can't deal with values
above 2^31. The `systemd-machined.service` service will synthesize user
database records for all UIDs assigned to a running container from this
range.
Note for both allocation ranges: when a UID allocation takes place NSS is
checked for collisions first, and a different UID is picked if an entry is
found. Thus, the user database is used as synchronization mechanism to ensure
exclusive ownership of UIDs and UID ranges. To ensure compatibility with other
subsystems allocating from the same ranges it is hence essential that they
ensure that whatever they pick shows up in the user/group databases, either by
providing an NSS module, or by adding entries directly to `/etc/passwd` and
`/etc/group`. For performance reasons, do note that `systemd-nspawn` will only
do an NSS check for the first UID of the range it allocates, not all 65536 of
them. Also note that while the allocation logic is operating, the glibc
`lckpwdf()` user database lock is taken, in order to make this logic race-free.
## Figuring out the system's UID boundaries
The most important boundaries of the local system may be queried with
`pkg-config`:
```
$ pkg-config --variable=system_uid_max systemd
999
$ pkg-config --variable=dynamic_uid_min systemd
61184
$ pkg-config --variable=dynamic_uid_max systemd
65519
$ pkg-config --variable=container_uid_base_min systemd
524288
$ pkg-config --variable=container_uid_base_max systemd
1878982656
```
(Note that the latter encodes the maximum UID *base* `systemd-nspawn` might
pick — given that 64K UIDs are assigned to each container according to this
allocation logic, the maximum UID used for this range is hence
1878982656+65535=1879048191.)
Systemd has compile-time default for these boundaries. Using those defaults is
recommended. It will nevertheless query `/etc/login.defs` at runtime, when
compiled with `-Dcompat-mutable-uid-boundaries=true` and that file is present.
Support for this is considered only a compatibility feature and should not be
used except when upgrading systems which were created with different defaults.
## Considerations for container managers
If you hack on a container manager, and wonder how and how many UIDs best to
assign to your containers, here are a few recommendations:
1. Definitely, don't assign less than 65536 UIDs/GIDs. After all the `nobody`
user has magic properties, and hence should be available in your container, and
given that it's assigned the UID 65534, you should really cover the full 16-bit
range in your container. Note that systemd will — as mentioned — synthesize
user records for the `nobody` user, and assumes its availability in various
other parts of its codebase, too, hence assigning fewer users means you lose
compatibility with running systemd code inside your container. And most likely
other packages make similar restrictions.
2. While it's fine to assign more than 65536 UIDs/GIDs to a container, there's
most likely not much value in doing so, as Linux distributions won't use the
higher ranges by default (as mentioned neither `adduser` nor `systemd`'s
dynamic user concept allocate from above the 16-bit range). Unless you actively
care for nested containers, it's hence probably a good idea to allocate exactly
65536 UIDs per container, and neither less nor more. A pretty side-effect is
that by doing so, you expose the same number of UIDs per container as Linux 2.2
supported for the whole system, back in the days.
3. Consider allocating UID ranges for containers so that the first UID you
assign has the lower 16-bits all set to zero. That way, the upper 16-bits become
a container ID of some kind, while the lower 16-bits directly encode the
internal container UID. This is the way `systemd-nspawn` allocates UID ranges
(see above). Following this allocation logic ensures best compatibility with
`systemd-nspawn` and all other container managers following the scheme, as it
is sufficient then to check NSS for the first UID you pick regarding conflicts,
as that's what they do, too. Moreover, it makes `chown()`ing container file
system trees nicely robust to interruptions: as the external UID encodes the
internal UID in a fixed way, it's very easy to adjust the container's base UID
without the need to know the original base UID: to change the container base,
just mask away the upper 16-bit, and insert the upper 16-bit of the new container
base instead. Here are the easy conversions to derive the internal UID, the
external UID, and the container base UID from each other:
```
INTERNAL_UID = EXTERNAL_UID & 0x0000FFFF
CONTAINER_BASE_UID = EXTERNAL_UID & 0xFFFF0000
EXTERNAL_UID = INTERNAL_UID | CONTAINER_BASE_UID
```
4. When picking a UID range for containers, make sure to check NSS first, with
a simple `getpwuid()` call: if there's already a user record for the first UID
you want to pick, then it's already in use: pick a different one. Wrap that
call in a `lckpwdf()` + `ulckpwdf()` pair, to make allocation
race-free. Provide an NSS module that makes all UIDs you end up taking show up
in the user database, and make sure that the NSS module returns up-to-date
information before you release the lock, so that other system components can
safely use the NSS user database as allocation check, too. Note that if you
follow this scheme no changes to `/etc/passwd` need to be made, thus minimizing
the artifacts the container manager persistently leaves in the system.
5. `systemd-homed` by default mounts the home directories it manages with UID
mapping applied. It will map four UID ranges into that uidmap, and leave
everything else unmapped: the range from 0…60000, the user's own UID, the range
60514…65534, and the container range 524288…1879048191. This means
files/directories in home directories managed by `systemd-homed` cannot be
owned by UIDs/GIDs outside of these four ranges (attempts to `chown()` files to
UIDs outside of these ranges will fail). Thus, if container trees are to be
placed within a home directory managed by `systemd-homed` they should take
these ranges into consideration and either place the trees at base UID 0 (and
then map them to a higher UID range for use in user namespacing via another
level of UID mapped mounts, at *runtime*) or at a base UID from the container
UID range. That said, placing container trees (and in fact any
files/directories not owned by the home directory's user) in home directories
is generally a questionable idea (regardless of whether `systemd-homed` is used
or not), given this typically breaks quota assumptions, makes it impossible for
users to properly manage all files in their own home directory due to
permission problems, introduces security issues around SETUID and severely
restricts compatibility with networked home directories. Typically, it's a much
better idea to place container images outside of the home directory,
i.e. somewhere below `/var/` or similar.
## Summary
| UID/GID | Purpose | Defined By | Listed in |
|-----------------------|-----------------------|---------------|-------------------------------|
| 0 | `root` user | Linux | `/etc/passwd` + `nss-systemd` |
| 1…4 | System users | Distributions | `/etc/passwd` |
| 5 | `tty` group | `systemd` | `/etc/passwd` |
| 6…999 | System users | Distributions | `/etc/passwd` |
| 1000…60000 | Regular users | Distributions | `/etc/passwd` + LDAP/NIS/… |
| 60001…60513 | Human users (homed) | `systemd` | `nss-systemd` |
| 60514…60577 | Host users mapped into containers | `systemd` | `systemd-nspawn` |
| 60578…61183 | Unused | | |
| 61184…65519 | Dynamic service users | `systemd` | `nss-systemd` |
| 65520…65533 | Unused | | |
| 65534 | `nobody` user | Linux | `/etc/passwd` + `nss-systemd` |
| 65535 | 16-bit `(uid_t) -1` | Linux | |
| 65536…524287 | Unused | | |
| 524288…1879048191 | Container UID ranges | `systemd` | `nss-systemd` |
| 1879048192…2147483647 | Unused | | |
| 2147483648…4294967294 | HIC SVNT LEONES | | |
| 4294967295 | 32-bit `(uid_t) -1` | Linux | |
Note that "Unused" in the table above doesn't mean that these ranges are
really unused. It just means that these ranges have no well-established
pre-defined purposes between Linux, generic low-level distributions and
`systemd`. There might very well be other packages that allocate from these
ranges.
Note that the range 2147483648…4294967294 (i.e. 2^31…2^32-2) should be handled
with care. Various programs (including kernel file systems — see `devpts` — or
even kernel syscalls – see `setfsuid()`) have trouble with UIDs outside of the
signed 32-bit range, i.e any UIDs equal to or above 2147483648. It is thus
strongly recommended to stay away from this range in order to avoid
complications. This range should be considered reserved for future, special
purposes.
## Notes on resolvability of user and group names
User names, UIDs, group names and GIDs don't have to be resolvable using NSS
(i.e. getpwuid() and getpwnam() and friends) all the time. However, systemd
makes the following requirements:
System users generally have to be resolvable during early boot already. This
means they should not be provided by any networked service (as those usually
become available during late boot only), except if a local cache is kept that
makes them available during early boot too (i.e. before networking is
up). Specifically, system users need to be resolvable at least before
`systemd-udevd.service` and `systemd-tmpfiles-setup.service` are started, as both
need to resolve system users — but note that there might be more services
requiring full resolvability of system users than just these two.
Regular users do not need to be resolvable during early boot, it is sufficient
if they become resolvable during late boot. Specifically, regular users need to
be resolvable at the point in time the `nss-user-lookup.target` unit is
reached. This target unit is generally used as synchronization point between
providers of the user database and consumers of it. Services that require that
the user database is fully available (for example, the login service
`systemd-logind.service`) are ordered *after* it, while services that provide
parts of the user database (for example an LDAP user database client) are
ordered *before* it. Note that `nss-user-lookup.target` is a *passive* unit: in
order to minimize synchronization points on systems that don't need it the unit
is pulled into the initial transaction only if there's at least one service
that really needs it, and that means only if there's a service providing the
local user database somehow through IPC or suchlike. Or in other words: if you
hack on some networked user database project, then make sure you order your
service `Before=nss-user-lookup.target` and that you pull it in with
`Wants=nss-user-lookup.target`. However, if you hack on some project that needs
the user database to be up in full, then order your service
`After=nss-user-lookup.target`, but do *not* pull it in via a `Wants=`
dependency.
+169
View File
@@ -0,0 +1,169 @@
---
title: systemd-homed and JSON User/Group Record Support in Desktop Environments
category: Users, Groups and Home Directories
layout: default
SPDX-License-Identifier: LGPL-2.1-or-later
---
# `systemd-homed` and JSON User/Group Record Support in Desktop Environments
Starting with version 245, systemd supports a new subsystem
[`systemd-homed.service`](https://www.freedesktop.org/software/systemd/man/systemd-homed.service.html)
for managing regular ("human") users and their home directories. Along with it
a new concept `userdb` got merged that brings rich, extensible JSON user/group
records, extending the classic UNIX/glibc NSS `struct passwd`/`struct group`
structures. Both additions are added in a fully backwards compatible way,
accessible through `getpwnam()`/`getgrnam()`/… (i.e. libc NSS) and PAM as
usual, meaning that for basic support no changes in the upper layers of the
stack (in particular desktop environments, such as GNOME or KDE) have to be
made. However, for better support a number of changes to desktop environments
are recommended. A few areas where that applies are discussed below.
Before reading on, please read up on the basic concepts, specifically:
* [Home Directories](HOME_DIRECTORY)
* [JSON User Records](USER_RECORD)
* [JSON Group Records](GROUP_RECORD)
* [User/Group Record Lookup API via Varlink](USER_GROUP_API)
## Support for Suspending Home Directory Access during System Suspend
One key feature of `systemd-homed` managed encrypted home directories is the
ability that access to them can be suspended automatically during system sleep,
removing any cryptographic key material from memory while doing so. This is
important in a world where most laptop users seldom shut down their computers
but most of the time just suspend them instead. Previously, the encryption keys
for the home directories remained in memory during system suspend, so that
sufficiently equipped attackers could read them from there and gain full access
to the device. By removing the key material from memory before suspend, and
re-requesting it on resume this attack vector can be closed down effectively.
Supporting this mechanism requires support in the desktop environment, since
the encryption keys (i.e. the user's login password) need to be reacquired on
system resume, from a lock screen or similar. This lock screen must run in
system context, and cannot run in the user's own context, since otherwise it
might end up accessing the home directory of the user even though access to it
is temporarily suspended and thus will hang if attempted.
It is suggested that desktop environments that implement lock screens run them
from system context, for example by switching back to the display manager, and
only revert back to the session after re-authentication via this system lock
screen (re-authentication in this case refers to passing the user's login
credentials to the usual PAM authentication hooks). Or in other words, when
going into system suspend it is recommended that GNOME Shell switches back to
the GNOME Display Manager login screen which now should double as screen lock,
and only switches back to the shell's UI after the user re-authenticated there.
Note that this change in behavior is a good idea in any case, and does not
create any dependencies on `systemd-homed` or systemd-specific APIs. It's
simply a change of behavior regarding use of existing APIs, not a suggested
hook-up to any new APIs.
A display manager which supports this kind of out-of-context screen lock
operation needs to inform systemd-homed about this so that systemd-homed knows
that it is safe to suspend the user's home directory on suspend. This is done
via the `suspend=` argument to the
[`pam_systemd_home`](https://www.freedesktop.org/software/systemd/man/pam_systemd_home.html)
PAM module. A display manager should hence change its PAM stack configuration
to set this parameter to on. `systemd-homed` will not suspend home directories
if there's at least one active session of the user that does not support
suspending, as communicated via this parameter.
## User Management UIs
The rich user/group records `userdb` and `systemd-homed` support carry various
fields of relevance to UIs that manage the local user database or parts
thereof. In particular, most of the metadata `accounts-daemon` (also see below)
supports is directly available in these JSON records. Hence it makes sense for
any user management UI to expose them directly.
`systemd-homed` exposes APIs to add, remove and make changes to local users via
D-Bus, with full [polkit](https://www.freedesktop.org/software/polkit/docs/latest/)
hook-up. On the command line this is exposed via the
`homectl` command. A graphical UI that exposes similar functionality would be
very useful, exposing the various new account settings, and in particular
providing a stream-lined UI for enrolling new-style authentication tokens such
as PKCS#11/YubiKey-style devices. (Ideally, if the user plugs in an
uninitialized YubiKey during operation it might be nice if the Desktop would
automatically ask if a key pair shall be written to it and the local account be
bound to it, `systemd-homed` provides enough YubiKey/PKCS#11 support to make
this a reality today; except that it will not take care of token
initialization).
A strong point of `systemd-homed` is per-user resource management. In
particular disk space assignments are something that most likely should be
exposed in a user management UI. Various metadata fields are supplied allowing
exposure of disk space assignment "slider" UI. Note however that the file system
back-ends of `systemd-homed.service` have different feature sets. Specifically,
only btrfs has online file system shrinking support, ext4 only offline file
system shrinking support, and xfs no shrinking support at all (all three file
systems support online file system growing however). This means if the LUKS
back-end is used, disk space assignment cannot be instant for logged in users,
unless btrfs is used.
Note that only `systemd-homed` provides an API for modifying/creating/deleting
users. The generic `userdb` subsystem (which might have other back-ends, besides
`systemd-homed`, for example LDAP or Windows) exclusively provides a read-only
interface. (This is unlikely to change, as the other back-ends might have very
different concepts of adding or modifying users, i.e. might not even have any
local concept for that at all). This means any user management UI that intends
to change (and not just view) user accounts should talk directly to
`systemd-homed` to make use of its features; there's no abstraction available
to support other back-ends under the same API.
Unfortunately there's currently no documentation for the `systemd-homed` D-Bus
API. Consider using the `homectl` sources as guidelines for implementing a user
management UI. The JSON user/records are well documented however, see above,
and the D-Bus API provides limited introspection.
## Relationship to `accounts-daemon`
For a long time `accounts-daemon` has been included in Linux distributions
providing richer user accounts. The functionality of this daemon overlaps in
many areas with the functionality of `systemd-homed` or `userdb`, but there are
systematic differences, which means that `systemd-homed` cannot replace
`accounts-daemon` fully. Most importantly: `accounts-daemon` provides
"side-car" metadata for *any* type of user account, while `systemd-homed` only
provides additional metadata for the users it defines itself. In other words:
`accounts-daemon` will augment foreign accounts; `systemd-homed` cannot be used
to augment users defined elsewhere, for example in LDAP or as classic
`/etc/passwd` records.
This probably means that for the time being, a user management UI (or other UI)
that wants to support rich user records with compatibility with the status quo
ante should probably talk to both `systemd-homed` and `accounts-daemon` at the
same time, and ignore `accounts-daemon`'s records if `systemd-homed` defines
them. While I (Lennart) personally believe in the long run `systemd-homed` is
the way to go for rich user records, any UI that wants to manage and support
rich records for classic records has to support `accounts-daemon` in parallel
for the time being.
In the short term, it might make sense to also expose the `userdb` provided
records via `accounts-daemon`, so that clients of the latter can consume them
without changes. However, I think in the long run `accounts-daemon` should
probably be removed from the general stack, hence this sounds like a temporary
solution only.
In case you wonder, there's no automatic mechanism for converting existing
users registered in `/etc/passwd` or LDAP to users managed by
`systemd-homed`. There's documentation for doing this manually though, see
[Converting Existing Users to systemd-homed managed Users](CONVERTING_TO_HOMED).
## Future Additions
JSON user/group records are extensible, hence we can easily add any additional
fields desktop environments require. For example, pattern-based authentication
is likely very useful on touch-based devices, and the user records should hence
learn them natively. Fields for other authentication mechanisms, such as
fingerprint authentication should be provided as well, eventually.
It is planned to extend the `userdb` Varlink API to support look-ups by partial
user name and real name (GECOS) data, so that log-in screens can optionally
implement simple complete-as-you-type login screens.
It is planned to extend the `systemd-homed` D-Bus API to instantly inform clients
about hardware associated with a specific user being plugged in, to which login
screens can listen in order to initiate authentication. Specifically, any
YubiKey-like security token plugged in that is associated with a local user
record should initiate authentication for that user, making typing in of the
username unnecessary.
+285
View File
@@ -0,0 +1,285 @@
---
title: User/Group Record Lookup API via Varlink
category: Users, Groups and Home Directories
layout: default
SPDX-License-Identifier: LGPL-2.1-or-later
---
# User/Group Record Lookup API via Varlink
JSON User/Group Records (as described in the [JSON User Records](USER_RECORD)
and [JSON Group Records](GROUP_RECORD) documents) that are defined on the
local system may be queried with a [Varlink](https://varlink.org/) API. This
API takes both the role of what
[`getpwnam(3)`](https://man7.org/linux/man-pages/man3/getpwnam.3.html) and
related calls are for `struct passwd`, as well as the interfaces modules
implementing the [glibc Name Service Switch
(NSS)](https://www.gnu.org/software/libc/manual/html_node/Name-Service-Switch.html)
expose. Or in other words, it both allows applications to efficiently query
user/group records from local services, and allows local subsystems to provide
user/group records efficiently to local applications.
The concepts described here define an IPC interface. Alternatively, user/group
records may be dropped in number of drop-in directories as files where they are
picked up in addition to the users/groups defined by this IPC logic. See
[`nss-systemd(8)`](https://www.freedesktop.org/software/systemd/man/nss-systemd.html)
for details.
This simple API only exposes only three method calls, and requires only a small
subset of the Varlink functionality.
## Why Varlink?
The API described in this document is based on a simple subset of the
mechanisms described by [Varlink](https://varlink.org/). The choice of
preferring Varlink over D-Bus and other IPCs in this context was made for three
reasons:
1. User/Group record resolution should work during early boot and late shutdown
without special handling. This is very hard to do with D-Bus, as the broker
service for D-Bus generally runs as regular system daemon and is hence only
available at the latest boot stage.
2. The JSON user/group records are native JSON data, hence picking an IPC
system that natively operates with JSON data is natural and clean.
3. IPC systems such as D-Bus do not provide flow control and are thus unusable
for streaming data. They are useful to pass around short control messages,
but as soon as potentially many and large objects shall be transferred,
D-Bus is not suitable, as any such streaming of messages would be considered
flooding in D-Bus' logic, and thus possibly result in termination of
communication. Since the APIs defined in this document need to support
enumerating potentially large numbers of users and groups, D-Bus is simply
not an appropriate option.
## Concepts
Each subsystem that needs to define users and groups on the local system is
supposed to implement this API, and offer its interfaces on a Varlink
`AF_UNIX`/`SOCK_STREAM` file system socket bound into the
`/run/systemd/userdb/` directory. When a client wants to look up a user or
group record, it contacts all sockets bound in this directory in parallel, and
enqueues the same query to each. The first positive reply is then returned to
the application, or if all fail the last seen error is returned
instead. (Alternatively a special Varlink service is available,
`io.systemd.Multiplexer` which acts as frontend and will do the parallel
queries on behalf of the client, drastically simplifying client
development. This service is not available during earliest boot and final
shutdown phases.)
Unlike with glibc NSS there's no order or programmatic expression language
defined in which queries are issued to the various services. Instead, all
queries are always enqueued in parallel to all defined services, in order to
make look-ups efficient, and the simple rule of "first successful lookup wins"
is unconditionally followed for user and group look-ups (though not for
membership lookups, see below).
This simple scheme only works safely as long as every service providing
user/group records carefully makes sure not to answer with conflicting
records. This API does not define any mechanisms for dealing with user/group
name/ID collisions during look-up nor during record registration. It assumes
the various subsystems that want to offer user and group records to the rest of
the system have made sufficiently sure in advance that their definitions do not
collide with those of other services. Clients are not expected to merge
multiple definitions for the same user or group, and will also not be able to
detect conflicts and suppress such conflicting records.
It is recommended to name the sockets in the directory in reverse domain name
notation, but this is neither required nor enforced.
## Well-Known Services
Any subsystem that wants to provide user/group records can do so, simply by
binding a socket in the aforementioned directory. By default two
services are listening there, that have special relevance:
1. `io.systemd.NameServiceSwitch` → This service makes the classic UNIX/glibc
NSS user/group records available as JSON User/Group records. Any such
records are automatically converted as needed, and possibly augmented with
information from the shadow databases.
2. `io.systemd.Multiplexer` → This service multiplexes client queries to all
other running services. It's supposed to simplify client development: in
order to look up or enumerate user/group records it's sufficient to talk to
one service instead of all of them in parallel. Note that it is not available
during earliest boot and final shutdown phases, hence for programs running
in that context it is preferable to implement the parallel lookup
themselves.
Both these services are implemented by the same daemon
`systemd-userdbd.service`.
Note that these services currently implement a subset of Varlink only. For
example, introspection is not available, and the resolver logic is not used.
## Other Services
The `systemd` project provides three other services implementing this
interface. Specifically:
1. `io.systemd.DynamicUser` → This service is implemented by the service
manager itself, and provides records for the users and groups synthesized
via `DynamicUser=` in unit files.
2. `io.systemd.Home` → This service is implemented by `systemd-homed.service`
and provides records for the users and groups defined by the home
directories it manages.
3. `io.systemd.Machine` → This service is implemented by
`systemd-machined.service` and provides records for the users and groups used
by local containers that use user namespacing.
Other projects are invited to implement these services too. For example, it
would make sense for LDAP/ActiveDirectory projects to implement these
interfaces, which would provide them a way to do per-user resource management
enforced by systemd and defined directly in LDAP directories.
## Compatibility with NSS
Two-way compatibility with classic UNIX/glibc NSS user/group records is
provided. When using the Varlink API, lookups into databases provided only via
NSS (and not natively via Varlink) are handled by the
`io.systemd.NameServiceSwitch` service (see above). When using the NSS API
(i.e. `getpwnam()` and friends) the `nss-systemd` module will automatically
synthesize NSS records for users/groups natively defined via a Varlink
API. Special care is taken to avoid recursion between these two compatibility
mechanisms.
Subsystems that shall provide user/group records to the system may choose
between offering them via an NSS module or via a this Varlink API, either way
all records are accessible via both APIs, due to the bidirectional
forwarding. It is also possible to provide the same records via both APIs
directly, but in that case the compatibility logic must be turned off. There
are mechanisms in place for this, please contact the systemd project for
details, as these are currently not documented.
## Caching of User Records
This API defines no concepts for caching records. If caching is desired it
should be implemented in the subsystems that provide the user records, not in
the clients consuming them.
## Method Calls
```
interface io.systemd.UserDatabase
method GetUserRecord(
uid : ?int,
userName : ?string,
service : string
) -> (
record : object,
incomplete : bool
)
method GetGroupRecord(
gid : ?int,
groupName : ?string,
service : string
) -> (
record : object,
incomplete : bool
)
method GetMemberships(
userName : ?string,
groupName : ?string,
service : string
) -> (
userName : string,
groupName : string
)
error NoRecordFound()
error BadService()
error ServiceNotAvailable()
error ConflictingRecordFound()
error EnumerationNotSupported()
```
The `GetUserRecord` method looks up or enumerates a user record. If the `uid`
parameter is set it specifies the numeric UNIX UID to search for. If the
`userName` parameter is set it specifies the name of the user to search
for. Typically, only one of the two parameters are set, depending whether a
look-up by UID or by name is desired. However, clients may also specify both
parameters, in which case a record matching both will be returned, and if only
one exists that matches one of the two parameters but not the other an error of
`ConflictingRecordFound` is returned. If neither of the two parameters are set
the whole user database is enumerated. In this case the method call needs to be
made with `more` set, so that multiple method call replies may be generated as
effect, each carrying one user record.
The `service` parameter is mandatory and should be set to the service name
being talked to (i.e. to the same name as the `AF_UNIX` socket path, with the
`/run/systemd/userdb/` prefix removed). This is useful to allow implementation
of multiple services on the same socket (which is used by
`systemd-userdbd.service`).
The method call returns one or more user records, depending which type of query is
used (see above). The record is returned in the `record` field. The
`incomplete` field indicates whether the record is complete. Services providing
user record lookup should only pass the `privileged` section of user records to
clients that either match the user the record is about or to sufficiently
privileged clients, for all others the section must be removed so that no
sensitive data is leaked this way. The `incomplete` parameter should indicate
whether the record has been modified like this or not (i.e. it is `true` if a
`privileged` section existed in the user record and was removed, and `false` if
no `privileged` section existed or one existed but hasn't been removed).
If no user record matching the specified UID or name is known the error
`NoRecordFound` is returned (this is also returned if neither UID nor name are
specified, and hence enumeration requested but the subsystem currently has no
users defined).
If a method call with an incorrectly set `service` field is received
(i.e. either not set at all, or not to the service's own name) a `BadService`
error is generated. Finally, `ServiceNotAvailable` should be returned when the
backing subsystem is not operational for some reason and hence no information
about existence or non-existence of a record can be returned nor any user
record at all. (The `service` field is defined in order to allow implementation
of daemons that provide multiple distinct user/group services over the same
`AF_UNIX` socket: in order to correctly determine which service a client wants
to talk to, the client needs to provide the name in each request.)
The `GetGroupRecord` method call works analogously but for groups.
The `GetMemberships` method call may be used to inquire about group
memberships. The `userName` and `groupName` arguments take what the name
suggests. If one of the two is specified all matching memberships are returned,
if neither is specified all known memberships of any user and any group are
returned. The return value is a pair of user name and group name, where the
user is a member of the group. If both arguments are specified the specified
membership will be tested for, but no others, and the pair is returned if it is
defined. Unless both arguments are specified the method call needs to be made
with `more` set, so that multiple replies can be returned (since typically
there are multiple members per group and also multiple groups a user is
member of). As with `GetUserRecord` and `GetGroupRecord` the `service`
parameter needs to contain the name of the service being talked to, in order to
allow implementation of multiple services within the same IPC socket. In case no
matching membership is known `NoRecordFound` is returned. The other two errors
are also generated in the same cases as for `GetUserRecord` and
`GetGroupRecord`.
Unlike with `GetUserRecord` and `GetGroupRecord` the lists of memberships
returned by services are always combined. Thus unlike the other two calls a
membership lookup query has to wait for the last simultaneous query to complete
before the complete list is acquired.
Note that only the `GetMemberships` call is authoritative about memberships of
users in groups. i.e. it should not be considered sufficient to check the
`memberOf` field of user records and the `members` field of group records to
acquire the full list of memberships. The full list can only be determined by
`GetMemberships`, and as mentioned requires merging of these lists of all local
services. Result of this is that it can be one service that defines a user A,
and another service that defines a group B, and a third service that declares
that A is a member of B.
Looking up explicit users/groups by their name or UID/GID, or querying
user/group memberships must be supported by all services implementing these
interfaces. However, supporting enumeration (i.e. user/group lookups that may
result in more than one reply, because neither UID/GID nor name is specified)
is optional. Services which are asked for enumeration may return the
`EnumerationNotSupported` error in this case.
And that's really all there is to it.
+170
View File
@@ -0,0 +1,170 @@
---
title: User/Group Name Syntax
category: Users, Groups and Home Directories
layout: default
SPDX-License-Identifier: LGPL-2.1-or-later
---
# User/Group Name Syntax
The precise set of allowed user and group names on Linux systems is weakly
defined. Depending on the distribution a different set of requirements and
restrictions on the syntax of user/group names are enforced — on some
distributions the accepted syntax is even configurable by the administrator. In
the interest of interoperability systemd enforces different rules when
processing users/group defined by other subsystems and when defining users/groups
itself, following the principle of "Be conservative in what you send, be
liberal in what you accept". Also in the interest of interoperability systemd
will enforce the same rules everywhere and not make them configurable or
distribution dependent. The precise rules are described below.
Generally, the same rules apply for user as for group names.
## Other Systems
* On POSIX the set of [valid user
names](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_437)
is defined as [lower and upper case ASCII letters, digits, period,
underscore, and
hyphen](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_282),
with the restriction that hyphen is not allowed as first character of the
user name. Interestingly no size limit is declared, i.e. in neither
direction, meaning that strictly speaking, according to POSIX, both the empty
string is a valid user name as well as a string of gigabytes in length.
* Debian/Ubuntu based systems enforce the regular expression
`^[a-z][-a-z0-9]*$`, i.e. only lower case ASCII letters, digits and
hyphens. As first character only lowercase ASCII letters are allowed. This
regular expression is configurable by the administrator at runtime
though. This rule enforces a minimum length of one character but no maximum
length.
* Upstream shadow-utils enforces the regular expression
`^[a-z_][a-z0-9_-]*[$]$`, i.e. is similar to the Debian/Ubuntu rule, but
allows underscores and hyphens, but the latter not as first character. Also,
an optional trailing dollar character is permitted.
* Fedora/Red Hat based systems enforce the regular expression of
`^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,30}[a-zA-Z0-9_.$-]?$`, i.e. a size limit of
32 characters, with upper and lower case letters, digits, underscores,
hyphens and periods. No hyphen as first character though, and the last
character may be a dollar character. On top of that, `.` and `..` are not
allowed as user/group names.
* sssd is known to generate user names with embedded `@` and white-space
characters, as well as non-ASCII (i.e. UTF-8) user/group names.
* winbindd is known to generate user/group names with embedded `\` and
white-space characters, as well as non-ASCII (i.e. UTF-8) user/group names.
Other operating systems enforce different rules; in this documentation we'll
focus on Linux systems only however, hence those are out of scope. That said,
software like Samba is frequently deployed on Linux for providing compatibility
with Windows systems; on such systems it might be wise to stick to user/group
names also valid according to Windows rules.
## Rules systemd enforces
Distilled from the above, below are the rules systemd enforces on user/group
names. An additional, common rule between both modes listed below is that empty
strings are not valid user/group names.
Philosophically, the strict mode described below enforces an allow list of
what's allowed and prohibits everything else, while the relaxed mode described
below implements a deny list of what's not allowed and permits everything else.
### Strict mode
Strict user/group name syntax is enforced whenever a systemd component is used
to register a user or group in the system, for example a system user/group
using
[`systemd-sysusers.service`](https://www.freedesktop.org/software/systemd/man/systemd-sysusers.html)
or a regular user with
[`systemd-homed.service`](https://www.freedesktop.org/software/systemd/man/systemd-homed.html).
In strict mode, only uppercase and lowercase characters are allowed, as well as
digits, underscores and hyphens. The first character may not be a digit or
hyphen. A size limit is enforced: the minimum of `sysconf(_SC_LOGIN_NAME_MAX)`
(typically 256 on Linux; rationale: this is how POSIX suggests to detect the
limit), `UT_NAMESIZE-1` (typically 31 on Linux; rationale: names longer than
this cannot correctly appear in `utmp`/`wtmp` and create ambiguity with login
accounting) and `NAME_MAX` (255 on Linux; rationale: user names typically
appear in directory names, i.e. the home directory), thus MIN(256, 31, 255) =
31.
Note that these rules are both more strict and more relaxed than all of the
rules enforced by other systems listed above. A user/group name conforming to
systemd's strict rules will not necessarily pass a test by the rules enforced
by these other subsystems.
Written as regular expression the above is: `^[a-zA-Z_][a-zA-Z0-9_-]{0,30}$`
### Relaxed mode
Relaxed user/group name syntax is enforced whenever a systemd component accepts
and makes use of user/group names registered by other (non-systemd)
components of the system, for example in
[`systemd-logind.service`](https://www.freedesktop.org/software/systemd/man/systemd-logind.html).
Relaxed syntax is also enforced by the `User=` setting in service unit files,
i.e. for system services used for running services. Since these users may be
registered by a variety of tools relaxed mode is used, but since the primary
purpose of these users is to run a system service and thus a job for systemd a
warning is shown if the specified user name does not qualify by the strict
rules above.
* No embedded NUL bytes (rationale: handling in C must be possible and
straightforward)
* No names consisting fully of digits (rationale: avoid confusion with numeric
UID/GID specifications)
* Similar, no names consisting of an initial hyphen and otherwise entirely made
up of digits (rationale: avoid confusion with negative, numeric UID/GID
specifications, e.g. `-1`)
* No strings that do not qualify as valid UTF-8 (rationale: we want to be able
to embed these strings in JSON, with permits only valid UTF-8 in its strings;
user names using other character sets, such as JIS/Shift-JIS will cause
validation errors)
* No control characters (i.e. characters in ASCII range 1…31; rationale: they
tend to have special meaning when output on a terminal in other contexts,
moreover the newline character — as a specific control character — is used as
record separator in `/etc/passwd`, and hence it's crucial to avoid
ambiguities here)
* No colon characters (rationale: it is used as field separator in `/etc/passwd`)
* The two strings `.` and `..` are not permitted, as these have special meaning
in file system paths, and user names are frequently included in file system
paths, in particular for the purpose of home directories.
* Similar, no slashes, as these have special meaning in file system paths
* No leading or trailing white-space is permitted; and hence no user/group names
consisting of white-space only either (rationale: this typically indicates
parsing errors, and creates confusion since not visible on screen)
Note that these relaxed rules are implied by the strict rules above, i.e. all
user/group names accepted by the strict rules are also accepted by the relaxed
rules, but not vice versa.
Note that this relaxed mode does not refuse a couple of very questionable
syntaxes. For example, it permits a leading or embedded period. A leading period
is problematic because the matching home directory would typically be hidden
from the user's/administrator's view. An embedded period is problematic since
it creates ambiguity in traditional `chown` syntax (which is still accepted
today) that uses it to separate user and group names in the command's
parameter: without consulting the user/group databases it is not possible to
determine if a `chown` invocation would change just the owning user or both the
owning user and group. It also allows embedding `@` (which is confusing to
MTAs).
## Common Core
Combining all rules listed above, user/group names that shall be considered
valid in all systemd contexts and on all Linux systems should match the
following regular expression (at least according to our understanding):
`^[a-z][a-z0-9-]{0,30}$`
File diff suppressed because it is too large Load Diff