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)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -683,6 +683,26 @@
|
||||
for details on the specific attributes.</para></listitem>
|
||||
</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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
+172
-19
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
+43
-2
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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_;
|
||||
|
||||
Reference in New Issue
Block a user