summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2021-11-23 13:13:06 +0100
committerGitHub <noreply@github.com>2021-11-23 13:13:06 +0100
commit244cab4a651d4a5b5d9f0275ef59b8273418ee51 (patch)
tree09edb26aaae9ba82368d738dcd41548e0156b2fd
parent7d34f26a32b0960db17ff9e619b174eb1d4276e1 (diff)
parent2f09e2eea1257bcf19e7d57b6f8c7b3466743d77 (diff)
downloadsystemd-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.md6
-rw-r--r--man/homectl.xml20
-rw-r--r--src/home/homectl.c22
-rw-r--r--src/home/homework-luks.c191
-rw-r--r--src/home/homework-luks.h4
-rw-r--r--src/home/homework-password-cache.c45
-rw-r--r--src/home/homework-password-cache.h9
-rw-r--r--src/home/homework.c45
-rw-r--r--src/home/homework.h7
-rw-r--r--src/shared/user-record-show.c3
-rw-r--r--src/shared/user-record.c46
-rw-r--r--src/shared/user-record.h13
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_;