Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .lgtm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ extraction:
- python3-pip
- python3-setuptools
- python3-wheel
- libpwquality-dev
- libfdisk-dev
- libp11-kit-dev
- libssl-dev
Expand Down
3 changes: 3 additions & 0 deletions .mkosi/mkosi.fedora
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ BuildPackages=
libidn2-devel
libmicrohttpd-devel
libmount-devel
libpwquality-devel
libseccomp-devel
libselinux-devel
libxkbcommon-devel
Expand All @@ -52,6 +53,7 @@ BuildPackages=
m4
meson
openssl-devel
p11-kit-devel
pam-devel
pcre2-devel
pkgconfig
Expand All @@ -66,6 +68,7 @@ Packages=
coreutils
cryptsetup-libs
kmod-libs
e2fsprogs
libidn2
libseccomp
procps-ng
Expand Down
44 changes: 35 additions & 9 deletions TODO
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ Features:
* honour specifiers in unit files that resolve to some very basic
/etc/os-release data, such as ID, VERSION_ID, BUILD_ID, VARIANT_ID.

* cryptsetup: allow encoding key directly in /etc/crypttab, maybe with a
"base64:" prefix. Useful in particular for pkcs11 mode.

* socket units: allow creating a udev monitor socket with ListenDevices= or so,
with matches, then actviate app thorugh that passing socket oveer

Expand Down Expand Up @@ -158,6 +161,38 @@ Features:
user@.service, which returns the XDG_RUNTIME_DIR value, and make this
behaviour selectable via pam module option.

* homed:
- when user tries to log into record signed by unrecognized key, automatically add key to our chain after polkit auth
- hook up machined/nspawn users with a varlink user query interface
- rollback when resize fails mid-operation
- GNOME's side for forget key on suspend (requires rework so that lock screen runs outside of uid)
- resize on login?
- fstrim on logout?
- shrink fs on logout?
- update LUKS password on login if we find there's a password that unlocks the JSON record but not the LUKS device.
- create on activate?
- properties: icon url?, preferred session type?, administrator bool (which translates to 'wheel' membership)?, address?, telephone?, vcard?, samba stuff?, parental controls?
- communicate clearly when usb stick is safe to remove. probably involves
beefing up logind to make pam session close hook synchronous and wait until
systemd --user is shut down.
- logind: maybe keep a "busy fd" as long as there's a non-released session around or the user@.service
- maybe make automatic, read-only, time-based reflink-copies of LUKS disk images (think: time machine)
- distuingish destroy / remove (i.e. currently we can unregister a user, unregister+remove their home directory, but not just remove their home directory)
- in systemd's PAMName= logic: query passwords with ssh-askpassword, so that we can make "loginctl set-linger" mode work
- fingerprint authentication, pattern authentication, …
- make sure "classic" user records can also be managed by homed
- description field for groups
- make size of $XDG_RUNTIME_DIR configurable in user record
- reuse pwquality magic in firstboot
- query password from kernel keyring first
- update even if record is "absent"
- add a "access mode" + "fstype" field to the "status" section of json identity records reflecting the actually used access mode and fstype, even on non-luks backends
- move acct mgmt stuff from pam_systemd_home to pam_systemd?
- when "homectl --pkcs11-token-uri=" is used, synthesize ssh-authorized-keys records for all keys we have private keys on the stick for
- make slice for users configurable (requires logind rework)
- logind: populate auto-login list bus property from PKCS#11 token
- when determining state of a LUKS home directory, check DM suspended sysfs file

* introduce a new per-process uuid, similar to the boot id, the machine id, the
invocation id, that is derived from process creds, specifically a hashed
combination of AT_RANDOM + getpid() + the starttime from
Expand Down Expand Up @@ -459,15 +494,6 @@ Features:
"systemd-gdb" for attaching to the start-up of any system service in its
natural habitat.

* maybe add gpt-partition-based user management: each user gets his own
LUKS-encrypted GPT partition with a new GPT type. A small nss module
enumerates users via udev partition enumeration. UIDs are assigned in a fixed
way: the partition index is added as offset to some fixed base uid. User name
is stored in GPT partition name. A PAM module authenticates the user via the
LUKS partition password. Benefits: strong per-user security, compatibility
with stateless/read-only/verity-enabled root. (other idea: do this based on
loopback files in /home, without GPT involvement)

* gpt-auto logic: related to the above, maybe support a "secondary" root
partition, that is mounted to / and is writable, and where the actual root's
/usr is mounted into.
Expand Down
173 changes: 173 additions & 0 deletions docs/HOME_DIRECTORY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
---
title: Home Directories
category: Concepts
layout: default
---

# 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 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`](https://systemd.io/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 set-up 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. It's 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 carry 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`. It's 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 either 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 an 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 to embed additional partitions later on, for
example for allowing 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 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 distuingish
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 `discard` 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.
15 changes: 12 additions & 3 deletions docs/UIDS-GIDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,15 @@ but downstreams are strongly advised against doing that.)

`systemd` defines a number of special UID ranges:

1. 61184…65519 → UIDs for dynamic users are allocated from this range (see the
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 16bit boundary (i.e. below
Expand All @@ -111,7 +119,7 @@ but downstreams are strongly advised against doing that.)
user record resolving works correctly without those users being in
`/etc/passwd`.

2. 524288…1879048191 → UID range for `systemd-nspawn`'s automatic allocation of
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 16bit subrange of this
range and assign it to the container. The range is picked so that the upper
Expand Down Expand Up @@ -232,7 +240,8 @@ the artifacts the container manager persistently leaves in the system.
| 5 | `tty` group | `systemd` | `/etc/passwd` |
| 6…999 | System users | Distributions | `/etc/passwd` |
| 1000…60000 | Regular users | Distributions | `/etc/passwd` + LDAP/NIS/… |
| 60001…61183 | Unused | | |
| 60001…60513 | Human Users (homed) | `systemd` | `nss-systemd`
| 60514…61183 | Unused | | |
| 61184…65519 | Dynamic service users | `systemd` | `nss-systemd` |
| 65520…65533 | Unused | | |
| 65534 | `nobody` user | Linux | `/etc/passwd` + `nss-systemd` |
Expand Down
26 changes: 15 additions & 11 deletions factory/etc/pam.d/system-auth
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@
# You really want to adjust this to your local distribution. If you use this
# unmodified you are not building systems safely and securely.

auth sufficient pam_unix.so nullok try_first_pass
auth required pam_deny.so
auth sufficient pam_unix.so
-auth sufficient pam_systemd_home.so
auth required pam_deny.so

account required pam_nologin.so
account sufficient pam_unix.so
account required pam_permit.so
account required pam_nologin.so
-account sufficient pam_systemd_home.so
account sufficient pam_unix.so
account required pam_permit.so

password sufficient pam_unix.so nullok sha512 shadow try_first_pass try_authtok
password required pam_deny.so
-password sufficient pam_systemd_home.so
password sufficient pam_unix.so sha512 shadow try_first_pass try_authtok
password required pam_deny.so

-session optional pam_keyinit.so revoke
-session optional pam_loginuid.so
-session optional pam_systemd.so
session sufficient pam_unix.so
-session optional pam_keyinit.so revoke
-session optional pam_loginuid.so
-session optional pam_systemd_home.so
-session optional pam_systemd.so
session required pam_unix.so
2 changes: 1 addition & 1 deletion fuzzbuzz.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ setup:
- sudo apt-get update -y
- sudo apt-get build-dep -y systemd
- sudo apt-get install -y python3-pip
- sudo apt-get install -y libfdisk-dev libp11-kit-dev libssl-dev
- sudo apt-get install -y libfdisk-dev libp11-kit-dev libssl-dev libpwquality-dev
# FIXME: temporarily pin the meson version as 0.53 doesn't work with older
# python 3.5
# # See: https://github.com/mesonbuild/meson/issues/6427
Expand Down
Loading