From 8bec643c8f8374b6608c7c3dadc145937bd80b96 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 29 Oct 2021 09:45:17 +0200 Subject: [PATCH 1/5] user-record: add auto-resize property --- src/shared/user-record-show.c | 3 +++ src/shared/user-record.c | 46 +++++++++++++++++++++++++++++++++++ src/shared/user-record.h | 13 ++++++++++ 3 files changed, 62 insertions(+) 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_; From ab3b6fcb4016afbc40d11844b3467a3579705e14 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 4 Nov 2021 18:05:49 +0100 Subject: [PATCH 2/5] homectl: expose new autoResizeMode JSON user record property --- src/home/homectl.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) 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; From 26191000383d79dd27c95748f668c4e27a37c799 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 29 Oct 2021 10:15:00 +0200 Subject: [PATCH 3/5] homework: add auto-shrink/auto-grow --- src/home/homework-luks.c | 99 ++++++++++++++++++++++++++++++++++++---- src/home/homework-luks.h | 2 + src/home/homework.c | 4 ++ 3 files changed, 95 insertions(+), 10 deletions(-) diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 57b41d26b8..134df7b945 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -1402,6 +1402,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 +1478,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"); @@ -1484,7 +1524,7 @@ int home_activate_luks( setup->do_mark_clean = false; setup->do_drop_caches = false; - log_info("Everything completed."); + log_info("Activation completed."); print_size_summary(host_size, encrypted_size, &sfs); @@ -1498,7 +1538,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 +1545,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 +3439,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); @@ -3718,3 +3761,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..afbe8d4909 100644 --- a/src/home/homework-luks.h +++ b/src/home/homework-luks.h @@ -24,6 +24,8 @@ int home_passwd_luks(UserRecord *h, HomeSetup *setup, const PasswordCache *cache 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.c b/src/home/homework.c index 5907015e2b..351c31b73e 100644 --- a/src/home/homework.c +++ b/src/home/homework.c @@ -908,6 +908,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; @@ -952,6 +953,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 */ From d26cdde3d45ed8cd418afd7fa71697359b528c2c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 2 Nov 2021 18:24:02 +0100 Subject: [PATCH 4/5] homework: upload home password into kernel keyring if needed If we do automatic disk space rebalancing, we must be able to unlock the encrypted volume for that in the background, thus we need to decryption key around in userspace. Let's do this via the kernel keyring. This allows us to do this in a relatively secure way, so that it sticks around between homework invocations, but still is destroyed automatically when homed goes down. --- src/home/homework-luks.c | 92 +++++++++++++++++++++++++++--- src/home/homework-luks.h | 2 +- src/home/homework-password-cache.c | 45 +++++++++++++++ src/home/homework-password-cache.h | 9 ++- src/home/homework.c | 41 ++++++++++++- src/home/homework.h | 7 +++ 6 files changed, 183 insertions(+), 13 deletions(-) diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 134df7b945..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; @@ -1523,6 +1589,7 @@ 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("Activation completed."); @@ -3451,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; @@ -3490,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; } @@ -3540,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; diff --git a/src/home/homework-luks.h b/src/home/homework-luks.h index afbe8d4909..f0f29b78f7 100644 --- a/src/home/homework-luks.h +++ b/src/home/homework-luks.h @@ -19,7 +19,7 @@ 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); 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 351c31b73e..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; @@ -944,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) @@ -981,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."); @@ -1660,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); From 2f09e2eea1257bcf19e7d57b6f8c7b3466743d77 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 19 Nov 2021 10:55:57 +0100 Subject: [PATCH 5/5] man/doc: document auto resize modes --- docs/USER_RECORD.md | 6 ++++++ man/homectl.xml | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+) 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 @@ -683,6 +683,26 @@ for details on the specific attributes. + + + + Configures whether to automatically grow and/or shrink the backing file system on + login and logout. Takes one of the strings off, grow, + shrink-and-grow. 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 shrink-and-grow, if LUKS2/btrfs is used, otherwise is + off. If set to off no automatic shrinking/growing during login or logout is + done. If set to grow the home area is grown to the size configured via + should it currently be smaller. If it already matches the configured + size or is larger no operation is executed. If set to shrink-and-grow 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. + + BOOL BOOL