docs: use collections to structure the data
This commit is contained in:
@@ -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.
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
Reference in New Issue
Block a user