diff options
author | Lennart Poettering <lennart@poettering.net> | 2021-11-23 13:13:06 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-23 13:13:06 +0100 |
commit | 244cab4a651d4a5b5d9f0275ef59b8273418ee51 (patch) | |
tree | 09edb26aaae9ba82368d738dcd41548e0156b2fd | |
parent | 7d34f26a32b0960db17ff9e619b174eb1d4276e1 (diff) | |
parent | 2f09e2eea1257bcf19e7d57b6f8c7b3466743d77 (diff) | |
download | systemd-244cab4a651d4a5b5d9f0275ef59b8273418ee51.tar.gz |
Merge pull request #21443 from poettering/homed-grow-shrink-on-login-logout
homed: add ability to auto-grow home dir in login and auto-shrink on logout (if luks2+btrfs is used)
-rw-r--r-- | docs/USER_RECORD.md | 6 | ||||
-rw-r--r-- | man/homectl.xml | 20 | ||||
-rw-r--r-- | src/home/homectl.c | 22 | ||||
-rw-r--r-- | src/home/homework-luks.c | 191 | ||||
-rw-r--r-- | src/home/homework-luks.h | 4 | ||||
-rw-r--r-- | src/home/homework-password-cache.c | 45 | ||||
-rw-r--r-- | src/home/homework-password-cache.h | 9 | ||||
-rw-r--r-- | src/home/homework.c | 45 | ||||
-rw-r--r-- | src/home/homework.h | 7 | ||||
-rw-r--r-- | src/shared/user-record-show.c | 3 | ||||
-rw-r--r-- | src/shared/user-record.c | 46 | ||||
-rw-r--r-- | src/shared/user-record.h | 13 |
12 files changed, 388 insertions, 23 deletions
diff --git a/docs/USER_RECORD.md b/docs/USER_RECORD.md index 9263d2db1d..9634f32b70 100644 --- a/docs/USER_RECORD.md +++ b/docs/USER_RECORD.md @@ -501,6 +501,12 @@ memory cost for the PBKDF operation, when LUKS storage is used, in bytes. `luksPbkdfParallelThreads` → An unsigned 64bit integer, indicating the intended required parallel threads for the PBKDF operation, when LUKS storage is used. +`autoResizeMode` → A string, one of `off`, `grow`, `shrink-and-grow`. Unless +set to `off`, controls whether the home area shall be grown automatically to +the size configured in `diskSize` automatically at login time. If set to +`shrink-and-grown` the home area is also shrunk to the minimal size possible +(as dictated by used disk space and file system constraints) on logout. + `service` → A string declaring the service that defines or manages this user record. It is recommended to use reverse domain name notation for this. For example, if `systemd-homed` manages a user a string of `io.systemd.Home` is diff --git a/man/homectl.xml b/man/homectl.xml index 89af3d77c7..b94d52b094 100644 --- a/man/homectl.xml +++ b/man/homectl.xml @@ -684,6 +684,26 @@ </varlistentry> <varlistentry> + <term><option>--auto-resize-mode=</option></term> + + <listitem><para>Configures whether to automatically grow and/or shrink the backing file system on + login and logout. Takes one of the strings <literal>off</literal>, <literal>grow</literal>, + <literal>shrink-and-grow</literal>. Only applies to the LUKS2 backend currently, and if the btrfs + file system is used inside it (since only then online growing/shrinking of the file system is + supported). Defaults to <literal>shrink-and-grow</literal>, if LUKS2/btrfs is used, otherwise is + off. If set to <literal>off</literal> no automatic shrinking/growing during login or logout is + done. If set to <literal>grow</literal> the home area is grown to the size configured via + <option>--disk-size=</option> should it currently be smaller. If it already matches the configured + size or is larger no operation is executed. If set to <literal>shrink-and-grow</literal> the home + area is also resized to the minimal size used disk space and file system constraints permit, during + logout. This mode thus ensures that while a home area is activated it is sized to the configured + size, but while deactivated it is compacted taking up only the minimal space possible. Note that if + the system is powered off abnormally or if the user otherwise not logged out cleanly the shrinking + operation will not take place, and the user has to re-login/logout again before it is executed + again.</para></listitem> + </varlistentry> + + <varlistentry> <term><option>--nosuid=</option><replaceable>BOOL</replaceable></term> <term><option>--nodev=</option><replaceable>BOOL</replaceable></term> <term><option>--noexec=</option><replaceable>BOOL</replaceable></term> diff --git a/src/home/homectl.c b/src/home/homectl.c index 648c275aec..cc2b9c8f31 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -2260,6 +2260,7 @@ static int help(int argc, char *argv[], void *userdata) { " Number of parallel threads for PKBDF\n" " --luks-extra-mount-options=OPTIONS\n" " LUKS extra mount options\n" + " --auto-resize-mode=MODE Automatically grow/shrink home on login/logout\n" "\n%4$sMounting User Record Properties:%5$s\n" " --nosuid=BOOL Control the 'nosuid' flag of the home mount\n" " --nodev=BOOL Control the 'nodev' flag of the home mount\n" @@ -2359,6 +2360,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_AND_CHANGE_PASSWORD, ARG_DROP_CACHES, ARG_LUKS_EXTRA_MOUNT_OPTIONS, + ARG_AUTO_RESIZE_MODE, }; static const struct option options[] = { @@ -2444,6 +2446,7 @@ static int parse_argv(int argc, char *argv[]) { { "and-change-password", required_argument, NULL, ARG_AND_CHANGE_PASSWORD }, { "drop-caches", required_argument, NULL, ARG_DROP_CACHES }, { "luks-extra-mount-options", required_argument, NULL, ARG_LUKS_EXTRA_MOUNT_OPTIONS }, + { "auto-resize-mode", required_argument, NULL, ARG_AUTO_RESIZE_MODE }, {} }; @@ -3540,6 +3543,25 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_AUTO_RESIZE_MODE: + if (isempty(optarg)) { + r = drop_from_identity("autoResizeMode"); + if (r < 0) + return r; + + break; + } + + r = auto_resize_mode_from_string(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --auto-resize-mode= argument: %s", optarg); + + r = json_variant_set_field_string(&arg_identity_extra, "autoResizeMode", auto_resize_mode_to_string(r)); + if (r < 0) + return log_error_errno(r, "Failed to set autoResizeMode field: %m"); + + break; + case 'j': arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO; break; diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 57b41d26b8..fd8f252dac 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -33,6 +33,7 @@ #include "homework-mount.h" #include "id128-util.h" #include "io-util.h" +#include "keyring-util.h" #include "memory-util.h" #include "missing_magic.h" #include "mkdir.h" @@ -247,15 +248,60 @@ static int run_fsck(const char *node, const char *fstype) { return 1; } +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(key_serial_t, keyring_unlink, -1); + +static int upload_to_keyring( + UserRecord *h, + const char *password, + key_serial_t *ret_key_serial) { + + _cleanup_free_ char *name = NULL; + key_serial_t serial; + + assert(h); + assert(password); + + /* If auto-shrink-on-logout is turned on, we need to keep the key we used to unlock the LUKS volume + * around, since we'll need it when automatically resizing (since we can't ask the user there + * again). We do this by uploading it into the kernel keyring, specifically the "session" one. This + * is done under the assumption systemd-homed gets its private per-session keyring (i.e. default + * service behaviour, given that KeyringMode=private is the default). It will survive between our + * systemd-homework invocations that way. + * + * If auto-shrink-on-logout is disabled we'll skip this step, to be frugal with sensitive data. */ + + if (user_record_auto_resize_mode(h) != AUTO_RESIZE_SHRINK_AND_GROW) { /* Won't need it */ + if (ret_key_serial) + *ret_key_serial = -1; + return 0; + } + + name = strjoin("homework-user-", h->user_name); + if (!name) + return -ENOMEM; + + serial = add_key("user", name, password, strlen(password), KEY_SPEC_SESSION_KEYRING); + if (serial == -1) + return -errno; + + if (ret_key_serial) + *ret_key_serial = serial; + + return 1; +} + static int luks_try_passwords( + UserRecord *h, struct crypt_device *cd, char **passwords, void *volume_key, - size_t *volume_key_size) { + size_t *volume_key_size, + key_serial_t *ret_key_serial) { char **pp; int r; + assert(h); assert(cd); STRV_FOREACH(pp, passwords) { @@ -269,6 +315,16 @@ static int luks_try_passwords( *pp, strlen(*pp)); if (r >= 0) { + if (ret_key_serial) { + /* If ret_key_serial is non-NULL, let's try to upload the password that + * worked, and return its serial. */ + r = upload_to_keyring(h, *pp, ret_key_serial); + if (r < 0) { + log_debug_errno(r, "Failed to upload LUKS password to kernel keyring, ignoring: %m"); + *ret_key_serial = -1; + } + } + *volume_key_size = vks; return 0; } @@ -280,6 +336,7 @@ static int luks_try_passwords( } static int luks_setup( + UserRecord *h, const char *node, const char *dm_name, sd_id128_t uuid, @@ -292,8 +349,10 @@ static int luks_setup( struct crypt_device **ret, sd_id128_t *ret_found_uuid, void **ret_volume_key, - size_t *ret_volume_key_size) { + size_t *ret_volume_key_size, + key_serial_t *ret_key_serial) { + _cleanup_(keyring_unlinkp) key_serial_t key_serial = -1; _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; _cleanup_(erase_and_freep) void *vk = NULL; sd_id128_t p; @@ -301,6 +360,7 @@ static int luks_setup( char **list; int r; + assert(h); assert(node); assert(dm_name); assert(ret); @@ -352,10 +412,11 @@ static int luks_setup( r = -ENOKEY; FOREACH_POINTER(list, + cache ? cache->keyring_passswords : NULL, cache ? cache->pkcs11_passwords : NULL, cache ? cache->fido2_passwords : NULL, passwords) { - r = luks_try_passwords(cd, list, vk, &vks); + r = luks_try_passwords(h, cd, list, vk, &vks, ret_key_serial ? &key_serial : NULL); if (r != -ENOKEY) break; } @@ -382,6 +443,8 @@ static int luks_setup( *ret_volume_key = TAKE_PTR(vk); if (ret_volume_key_size) *ret_volume_key_size = vks; + if (ret_key_serial) + *ret_key_serial = TAKE_KEY_SERIAL(key_serial); return 0; } @@ -490,10 +553,11 @@ static int luks_open( r = -ENOKEY; FOREACH_POINTER(list, + cache ? cache->keyring_passswords : NULL, cache ? cache->pkcs11_passwords : NULL, cache ? cache->fido2_passwords : NULL, h->password) { - r = luks_try_passwords(setup->crypt_device, list, vk, &vks); + r = luks_try_passwords(h, setup->crypt_device, list, vk, &vks, NULL); if (r != -ENOKEY) break; } @@ -1322,7 +1386,8 @@ int home_setup_luks( log_info("Setting up loopback device %s completed.", setup->loop->node ?: ip); - r = luks_setup(setup->loop->node ?: ip, + r = luks_setup(h, + setup->loop->node ?: ip, setup->dm_name, h->luks_uuid, h->luks_cipher, @@ -1334,7 +1399,8 @@ int home_setup_luks( &setup->crypt_device, &found_luks_uuid, &volume_key, - &volume_key_size); + &volume_key_size, + &setup->key_serial); if (r < 0) return r; @@ -1402,6 +1468,42 @@ static void print_size_summary(uint64_t host_size, uint64_t encrypted_size, cons FORMAT_BYTES((uint64_t) sfs->f_bfree * (uint64_t) sfs->f_frsize)); } +static int home_auto_grow_luks( + UserRecord *h, + HomeSetup *setup, + PasswordCache *cache) { + + struct statfs sfs; + + assert(h); + assert(setup); + + if (!IN_SET(user_record_auto_resize_mode(h), AUTO_RESIZE_GROW, AUTO_RESIZE_SHRINK_AND_GROW)) + return 0; + + assert(setup->root_fd >= 0); + + if (fstatfs(setup->root_fd, &sfs) < 0) + return log_error_errno(errno, "Failed to statfs home directory: %m"); + + if (!fs_can_online_shrink_and_grow(sfs.f_type)) { + log_debug("Not auto-grow file system, since selected file system cannot do both online shrink and grow."); + return 0; + } + + log_debug("Initiating auto-grow..."); + + return home_resize_luks( + h, + HOME_SETUP_ALREADY_ACTIVATED| + HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES| + HOME_SETUP_RESIZE_DONT_SHRINK| + HOME_SETUP_RESIZE_DONT_UNDO, + setup, + cache, + NULL); +} + int home_activate_luks( UserRecord *h, HomeSetup *setup, @@ -1442,6 +1544,10 @@ int home_activate_luks( if (r < 0) return r; + r = home_auto_grow_luks(h, setup, cache); + if (r < 0) + return r; + r = block_get_size_by_fd(setup->loop->fd, &host_size); if (r < 0) return log_error_errno(r, "Failed to get loopback block device size: %m"); @@ -1483,8 +1589,9 @@ int home_activate_luks( setup->do_offline_fallocate = false; setup->do_mark_clean = false; setup->do_drop_caches = false; + TAKE_KEY_SERIAL(setup->key_serial); /* Leave key in kernel keyring */ - log_info("Everything completed."); + log_info("Activation completed."); print_size_summary(host_size, encrypted_size, &sfs); @@ -1498,7 +1605,6 @@ int home_deactivate_luks(UserRecord *h, HomeSetup *setup) { assert(h); assert(setup); - assert(!setup->crypt_device); /* Note that the DM device and loopback device are set to auto-detach, hence strictly speaking we * don't have to explicitly have to detach them. However, we do that nonetheless (in case of the DM @@ -1506,13 +1612,17 @@ int home_deactivate_luks(UserRecord *h, HomeSetup *setup) { * don't bother about the loopback device because unlike the DM device it doesn't have a fixed * name. */ - r = acquire_open_luks_device(h, setup, /* graceful= */ true); - if (r < 0) - return log_error_errno(r, "Failed to initialize cryptsetup context for %s: %m", setup->dm_name); - if (r == 0) { - log_debug("LUKS device %s has already been detached.", setup->dm_name); - we_detached = false; - } else { + if (!setup->crypt_device) { + r = acquire_open_luks_device(h, setup, /* graceful= */ true); + if (r < 0) + return log_error_errno(r, "Failed to initialize cryptsetup context for %s: %m", setup->dm_name); + if (r == 0) { + log_debug("LUKS device %s has already been detached.", setup->dm_name); + we_detached = false; + } + } + + if (setup->crypt_device) { log_info("Discovered used LUKS device %s.", setup->dm_node); cryptsetup_enable_logging(setup->crypt_device); @@ -3396,7 +3506,7 @@ int home_resize_luks( return r; } - log_info("Everything completed."); + log_info("Resizing completed."); print_size_summary(new_image_size, new_fs_size, &sfs); @@ -3408,9 +3518,10 @@ int home_resize_luks( int home_passwd_luks( UserRecord *h, + HomeSetupFlags flags, HomeSetup *setup, - const PasswordCache *cache, /* the passwords acquired via PKCS#11/FIDO2 security tokens */ - char **effective_passwords /* new passwords */) { + const PasswordCache *cache, /* the passwords acquired via PKCS#11/FIDO2 security tokens */ + char **effective_passwords /* new passwords */) { size_t volume_key_size, max_key_slots, n_effective; _cleanup_(erase_and_freep) void *volume_key = NULL; @@ -3447,11 +3558,12 @@ int home_passwd_luks( r = -ENOKEY; FOREACH_POINTER(list, + cache ? cache->keyring_passswords : NULL, cache ? cache->pkcs11_passwords : NULL, cache ? cache->fido2_passwords : NULL, h->password) { - r = luks_try_passwords(setup->crypt_device, list, volume_key, &volume_key_size); + r = luks_try_passwords(h, setup->crypt_device, list, volume_key, &volume_key_size, NULL); if (r != -ENOKEY) break; } @@ -3497,6 +3609,11 @@ int home_passwd_luks( return log_error_errno(r, "Failed to set up LUKS password: %m"); log_info("Updated LUKS key slot %zu.", i); + + /* If we changed the password, then make sure to update the copy in the keyring, so that + * auto-rebalance continues to work. We only do this if we operate on an active home dir. */ + if (i == 0 && FLAGS_SET(flags, HOME_SETUP_ALREADY_ACTIVATED)) + upload_to_keyring(h, effective_passwords[i], NULL); } return 1; @@ -3718,3 +3835,39 @@ int wait_for_block_device_gone(HomeSetup *setup, usec_t timeout_usec) { log_debug("Successfully waited until device %s disappeared.", setup->dm_node); return 0; } + +int home_auto_shrink_luks(UserRecord *h, HomeSetup *setup, PasswordCache *cache) { + struct statfs sfs; + int r; + + assert(h); + assert(user_record_storage(h) == USER_LUKS); + assert(setup); + assert(setup->root_fd >= 0); + + if (user_record_auto_resize_mode(h) != AUTO_RESIZE_SHRINK_AND_GROW) + return 0; + + if (fstatfs(setup->root_fd, &sfs) < 0) + return log_error_errno(errno, "Failed to statfs home directory: %m"); + + if (!fs_can_online_shrink_and_grow(sfs.f_type)) { + log_debug("Not auto-shrinking file system, since selected file system cannot do both online shrink and grow."); + return 0; + } + + r = home_resize_luks( + h, + HOME_SETUP_ALREADY_ACTIVATED| + HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES| + HOME_SETUP_RESIZE_MINIMIZE| + HOME_SETUP_RESIZE_DONT_GROW| + HOME_SETUP_RESIZE_DONT_UNDO, + setup, + cache, + NULL); + if (r < 0) + return r; + + return 1; +} diff --git a/src/home/homework-luks.h b/src/home/homework-luks.h index 796a883831..f0f29b78f7 100644 --- a/src/home/homework-luks.h +++ b/src/home/homework-luks.h @@ -19,11 +19,13 @@ int home_get_state_luks(UserRecord *h, HomeSetup *setup); int home_resize_luks(UserRecord *h, HomeSetupFlags flags, HomeSetup *setup, PasswordCache *cache, UserRecord **ret_home); -int home_passwd_luks(UserRecord *h, HomeSetup *setup, const PasswordCache *cache, char **effective_passwords); +int home_passwd_luks(UserRecord *h, HomeSetupFlags flags, HomeSetup *setup, const PasswordCache *cache, char **effective_passwords); int home_lock_luks(UserRecord *h, HomeSetup *setup); int home_unlock_luks(UserRecord *h, HomeSetup *setup, const PasswordCache *cache); +int home_auto_shrink_luks(UserRecord *h, HomeSetup *setup, PasswordCache *cache); + static inline uint64_t luks_volume_key_size_convert(struct crypt_device *cd) { int k; diff --git a/src/home/homework-password-cache.c b/src/home/homework-password-cache.c index 87d90c9184..c1606c5f5f 100644 --- a/src/home/homework-password-cache.c +++ b/src/home/homework-password-cache.c @@ -1,6 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "homework-password-cache.h" +#include "keyring-util.h" +#include "missing_syscall.h" +#include "user-record.h" void password_cache_free(PasswordCache *cache) { if (!cache) @@ -9,3 +12,45 @@ void password_cache_free(PasswordCache *cache) { cache->pkcs11_passwords = strv_free_erase(cache->pkcs11_passwords); cache->fido2_passwords = strv_free_erase(cache->fido2_passwords); } + +void password_cache_load_keyring(UserRecord *h, PasswordCache *cache) { + _cleanup_(erase_and_freep) void *p = NULL; + _cleanup_free_ char *name = NULL; + char **strv = NULL; + key_serial_t serial; + size_t sz; + int r; + + assert(h); + assert(cache); + + /* Loads the password we need to for automatic resizing from the kernel keyring */ + + name = strjoin("homework-user-", h->user_name); + if (!name) + return (void) log_oom(); + + serial = request_key("user", name, NULL, 0); + if (serial == -1) + return (void) log_debug_errno(errno, "Failed to request key '%s', ignoring: %m", name); + + r = keyring_read(serial, &p, &sz); + if (r < 0) + return (void) log_debug_errno(r, "Failed to read keyring key '%s', ignoring: %m", name); + + if (memchr(p, 0, sz)) + return (void) log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Cached password contains embedded NUL byte, ignoring."); + + strv = new(char*, 2); + if (!strv) + return (void) log_oom(); + + strv[0] = TAKE_PTR(p); /* Note that keyring_read() will NUL terminate implicitly, hence we don't have + * to NUL terminate manually here: it's a valid string. */ + strv[1] = NULL; + + strv_free_erase(cache->keyring_passswords); + cache->keyring_passswords = strv; + + log_debug("Successfully acquired home key from kernel keyring."); +} diff --git a/src/home/homework-password-cache.h b/src/home/homework-password-cache.h index 6b4de15724..fdfbcfe4e0 100644 --- a/src/home/homework-password-cache.h +++ b/src/home/homework-password-cache.h @@ -5,6 +5,9 @@ #include "user-record.h" typedef struct PasswordCache { + /* Passwords acquired from the kernel keyring */ + char **keyring_passswords; + /* Decoding passwords from security tokens is expensive and typically requires user interaction, * hence cache any we already figured out. */ char **pkcs11_passwords; @@ -17,5 +20,9 @@ static inline bool password_cache_contains(const PasswordCache *cache, const cha if (!cache) return false; - return strv_contains(cache->pkcs11_passwords, p) || strv_contains(cache->fido2_passwords, p); + return strv_contains(cache->pkcs11_passwords, p) || + strv_contains(cache->fido2_passwords, p) || + strv_contains(cache->keyring_passswords, p); } + +void password_cache_load_keyring(UserRecord *h, PasswordCache *cache); diff --git a/src/home/homework.c b/src/home/homework.c index 5907015e2b..ac52a011ed 100644 --- a/src/home/homework.c +++ b/src/home/homework.c @@ -343,6 +343,34 @@ int home_setup_undo_dm(HomeSetup *setup, int level) { return ret; } +int keyring_unlink(key_serial_t k) { + + if (k == -1) /* already invalidated? */ + return -1; + + if (keyctl(KEYCTL_UNLINK, k, KEY_SPEC_SESSION_KEYRING, 0, 0) < 0) + log_debug_errno(errno, "Failed to unlink key from session kernel keyring, ignoring: %m"); + + return -1; /* Always return the key_serial_t value for "invalid" */ +} + +static int keyring_flush(UserRecord *h) { + _cleanup_free_ char *name = NULL; + long serial; + + assert(h); + + name = strjoin("homework-user-", h->user_name); + if (!name) + return log_oom(); + + serial = keyctl(KEYCTL_SEARCH, (unsigned long) KEY_SPEC_SESSION_KEYRING, (unsigned long) "user", (unsigned long) name, 0); + if (serial == -1) + return log_debug_errno(errno, "Failed to find kernel keyring entry for user, ignoring: %m"); + + return keyring_unlink(serial); +} + int home_setup_done(HomeSetup *setup) { int r = 0, q; @@ -393,6 +421,8 @@ int home_setup_done(HomeSetup *setup) { setup->temporary_image_path = mfree(setup->temporary_image_path); } + setup->key_serial = keyring_unlink(setup->key_serial); + setup->undo_mount = false; setup->undo_dm = false; setup->do_offline_fitrim = false; @@ -908,6 +938,7 @@ static int home_activate(UserRecord *h, UserRecord **ret_home) { static int home_deactivate(UserRecord *h, bool force) { _cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT; + _cleanup_(password_cache_free) PasswordCache cache = {}; bool done = false; int r; @@ -943,8 +974,12 @@ static int home_deactivate(UserRecord *h, bool force) { if (r < 0) return r; - if (user_record_storage(h) == USER_LUKS) + if (user_record_storage(h) == USER_LUKS) { + /* Automatically shrink on logout if that's enabled. To be able to shrink we need the + * keys to the device. */ + password_cache_load_keyring(h, &cache); (void) home_trim_luks(h, &setup); + } /* Sync explicitly, so that the drop caches logic below can work as documented */ if (syncfs(setup.root_fd) < 0) @@ -952,6 +987,9 @@ static int home_deactivate(UserRecord *h, bool force) { else log_info("Syncing completed."); + if (user_record_storage(h) == USER_LUKS) + (void) home_auto_shrink_luks(h, &setup, &cache); + setup.root_fd = safe_close(setup.root_fd); /* Now get rid of the bind mount, too */ @@ -977,6 +1015,9 @@ static int home_deactivate(UserRecord *h, bool force) { done = true; } + /* Explicitly flush any per-user key from the keyring */ + (void) keyring_flush(h); + if (!done) return log_error_errno(SYNTHETIC_ERRNO(ENOEXEC), "Home is not active."); @@ -1656,7 +1697,7 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) { switch (user_record_storage(h)) { case USER_LUKS: - r = home_passwd_luks(h, &setup, &cache, effective_passwords); + r = home_passwd_luks(h, flags, &setup, &cache, effective_passwords); if (r < 0) return r; break; diff --git a/src/home/homework.h b/src/home/homework.h index be77764d8e..750ad331c8 100644 --- a/src/home/homework.h +++ b/src/home/homework.h @@ -8,6 +8,8 @@ #include "homework-password-cache.h" #include "loop-util.h" +#include "missing_keyctl.h" +#include "missing_syscall.h" #include "user-record.h" #include "user-record-util.h" @@ -28,6 +30,8 @@ typedef struct HomeSetup { void *volume_key; size_t volume_key_size; + key_serial_t key_serial; + bool undo_dm:1; bool undo_mount:1; /* Whether to unmount /run/systemd/user-home-mount */ bool do_offline_fitrim:1; @@ -49,6 +53,7 @@ typedef struct HomeSetup { .image_fd = -1, \ .partition_offset = UINT64_MAX, \ .partition_size = UINT64_MAX, \ + .key_serial = -1, \ } /* Various flags for the operation of setting up a home directory */ @@ -71,6 +76,8 @@ int home_setup_done(HomeSetup *setup); int home_setup_undo_mount(HomeSetup *setup, int level); int home_setup_undo_dm(HomeSetup *setup, int level); +int keyring_unlink(key_serial_t k); + int home_setup(UserRecord *h, HomeSetupFlags flags, HomeSetup *setup, PasswordCache *cache, UserRecord **ret_header_home); int home_refresh(UserRecord *h, HomeSetup *setup, UserRecord *header_home, PasswordCache *cache, struct statfs *ret_statfs, UserRecord **ret_new_home); diff --git a/src/shared/user-record-show.c b/src/shared/user-record-show.c index c733654b20..17502845af 100644 --- a/src/shared/user-record-show.c +++ b/src/shared/user-record-show.c @@ -444,6 +444,9 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) { if (hr->drop_caches >= 0 || user_record_drop_caches(hr)) printf(" Drop Caches: %s\n", yes_no(user_record_drop_caches(hr))); + if (hr->auto_resize_mode >= 0) + printf(" Auto Resize: %s\n", auto_resize_mode_to_string(user_record_auto_resize_mode(hr))); + if (!strv_isempty(hr->ssh_authorized_keys)) printf("SSH Pub. Key: %zu\n", strv_length(hr->ssh_authorized_keys)); diff --git a/src/shared/user-record.c b/src/shared/user-record.c index 268f8a3998..fa677adb2c 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -84,6 +84,7 @@ UserRecord* user_record_new(void) { .fido2_user_presence_permitted = -1, .fido2_user_verification_permitted = -1, .drop_caches = -1, + .auto_resize_mode = _AUTO_RESIZE_MODE_INVALID, }; return h; @@ -957,6 +958,32 @@ static int dispatch_recovery_key(const char *name, JsonVariant *variant, JsonDis return 0; } +static int dispatch_auto_resize_mode(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + AutoResizeMode *mode = userdata, m; + + assert_se(mode); + + if (json_variant_is_null(variant)) { + *mode = _AUTO_RESIZE_MODE_INVALID; + return 0; + } + + if (json_variant_is_boolean(variant)) { + *mode = json_variant_boolean(variant) ? AUTO_RESIZE_SHRINK_AND_GROW : AUTO_RESIZE_OFF; + return 0; + } + + if (!json_variant_is_string(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string, boolean or null.", strna(name)); + + m = auto_resize_mode_from_string(json_variant_string(variant)); + if (m < 0) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid automatic resize mode.", strna(name)); + + *mode = m; + return 0; +} + static int dispatch_privileged(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { static const JsonDispatch privileged_dispatch_table[] = { @@ -1149,6 +1176,7 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp { "luksPbkdfParallelThreads", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_parallel_threads), 0 }, { "luksExtraMountOptions", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_extra_mount_options), 0 }, { "dropCaches", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, drop_caches), 0 }, + { "autoResizeMode", _JSON_VARIANT_TYPE_INVALID, dispatch_auto_resize_mode, offsetof(UserRecord, auto_resize_mode), 0 }, { "rateLimitIntervalUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_interval_usec), 0 }, { "rateLimitBurst", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 }, { "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 }, @@ -1499,6 +1527,7 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla { "luksPbkdfParallelThreads", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_parallel_threads), 0 }, { "luksExtraMountOptions", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_extra_mount_options), 0 }, { "dropCaches", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, drop_caches), 0 }, + { "autoResizeMode", _JSON_VARIANT_TYPE_INVALID, dispatch_auto_resize_mode, offsetof(UserRecord, auto_resize_mode), 0 }, { "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE }, { "rateLimitIntervalUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_interval_usec), 0 }, { "rateLimitBurst", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 }, @@ -1901,6 +1930,15 @@ bool user_record_drop_caches(UserRecord *h) { return user_record_storage(h) == USER_FSCRYPT; } +AutoResizeMode user_record_auto_resize_mode(UserRecord *h) { + assert(h); + + if (h->auto_resize_mode >= 0) + return h->auto_resize_mode; + + return user_record_storage(h) == USER_LUKS ? AUTO_RESIZE_SHRINK_AND_GROW : AUTO_RESIZE_OFF; +} + uint64_t user_record_ratelimit_next_try(UserRecord *h) { assert(h); @@ -2148,3 +2186,11 @@ static const char* const user_disposition_table[_USER_DISPOSITION_MAX] = { }; DEFINE_STRING_TABLE_LOOKUP(user_disposition, UserDisposition); + +static const char* const auto_resize_mode_table[_AUTO_RESIZE_MODE_MAX] = { + [AUTO_RESIZE_OFF] = "off", + [AUTO_RESIZE_GROW] = "grow", + [AUTO_RESIZE_SHRINK_AND_GROW] = "shrink-and-grow", +}; + +DEFINE_STRING_TABLE_LOOKUP(auto_resize_mode, AutoResizeMode); diff --git a/src/shared/user-record.h b/src/shared/user-record.h index 22a6bb28f4..c5424eef82 100644 --- a/src/shared/user-record.h +++ b/src/shared/user-record.h @@ -213,6 +213,14 @@ typedef struct RecoveryKey { char *hashed_password; } RecoveryKey; +typedef enum AutoResizeMode { + AUTO_RESIZE_OFF, /* no automatic grow/shrink */ + AUTO_RESIZE_GROW, /* grow at login */ + AUTO_RESIZE_SHRINK_AND_GROW, /* shrink at logout + grow at login */ + _AUTO_RESIZE_MODE_MAX, + _AUTO_RESIZE_MODE_INVALID = -EINVAL, +} AutoResizeMode; + typedef struct UserRecord { /* The following three fields are not part of the JSON record */ unsigned n_ref; @@ -249,6 +257,7 @@ typedef struct UserRecord { uint64_t disk_size_relative; /* Disk size, relative to the free bytes of the medium, normalized to UINT32_MAX = 100% */ char *skeleton_directory; mode_t access_mode; + AutoResizeMode auto_resize_mode; uint64_t tasks_max; uint64_t memory_high; @@ -387,6 +396,7 @@ usec_t user_record_ratelimit_interval_usec(UserRecord *h); uint64_t user_record_ratelimit_burst(UserRecord *h); bool user_record_can_authenticate(UserRecord *h); bool user_record_drop_caches(UserRecord *h); +AutoResizeMode user_record_auto_resize_mode(UserRecord *h); int user_record_build_image_path(UserStorage storage, const char *user_name_and_realm, char **ret); @@ -417,3 +427,6 @@ UserStorage user_storage_from_string(const char *s) _pure_; const char* user_disposition_to_string(UserDisposition t) _const_; UserDisposition user_disposition_from_string(const char *s) _pure_; + +const char* auto_resize_mode_to_string(AutoResizeMode m) _const_; +AutoResizeMode auto_resize_mode_from_string(const char *s) _pure_; |