interface.</para>
<para><function>UpdateHome()</function> updates a locally registered user record. Takes a fully
- specified JSON user record as argument (including the <literal>secret</literal> section). A user with a
- matching name and realm must be registered locally already, and the last change timestamp of the newly
- supplied record must be newer than the previously existing user record. Note this operation updates the
- user record only, it does not propagate passwords/authentication tokens from the user record to the
+ specified JSON user record as argument (possibly including the <literal>secret</literal> section). A user
+ with a matching name and realm must be registered locally already, and the last change timestamp of the
+ newly supplied record must be newer than the previously existing user record. Note this operation updates
+ the user record only, it does not propagate passwords/authentication tokens from the user record to the
storage back-end, or resizes the storage back-end. Typically a home directory is first updated, and then
the password of the underlying storage updated using <function>ChangePasswordHome()</function> as well
as the storage resized using <function>ResizeHome()</function>. This method is equivalent to
on the <classname>org.freedesktop.home1.Home</classname> interface.</para>
<para><function>ResizeHome()</function> resizes the storage associated with a user record. Takes a user
- name, a disk size in bytes and a user record consisting only of the <literal>secret</literal> section
- as argument. If the size is specified as <constant>UINT64_MAX</constant> the storage is resized to the
- size already specified in the user record. Typically, if the user record is updated using
+ name, a disk size in bytes, and optionally a user record consisting only of the <literal>secret</literal>
+ section as arguments. If the size is specified as <constant>UINT64_MAX</constant> the storage is resized to
+ the size already specified in the user record. Typically, if the user record is updated using
<function>UpdateHome()</function> above this is used to propagate the size configured there-in down to
- the underlying storage back-end. This method is equivalent to
- <function>Resize()</function> on the <classname>org.freedesktop.home1.Home</classname>
- interface.</para>
+ the underlying storage back-end. This method is equivalent to <function>Resize()</function> on the
+ <classname>org.freedesktop.home1.Home</classname> interface.</para>
<para><function>ChangePasswordHome()</function> changes the passwords/authentication tokens of a home
directory. Takes a user name, and two JSON user record objects, each consisting only of the
assert(message);
- r = bus_message_read_home_record(message, USER_RECORD_REQUIRE_REGULAR|USER_RECORD_REQUIRE_SECRET|USER_RECORD_ALLOW_PRIVILEGED|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_SIGNATURE|USER_RECORD_PERMISSIVE, &hr, error);
+ r = bus_message_read_home_record(message, USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_SECRET|USER_RECORD_ALLOW_PRIVILEGED|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_SIGNATURE|USER_RECORD_PERMISSIVE, &hr, error);
if (r < 0)
return r;
if (r == 0)
return 1; /* Will call us back */
- r = home_resize(h, sz, secret, /* automatic= */ false, error);
+ r = home_resize(h, sz, secret, error);
if (r < 0)
return r;
int home_resize(Home *h,
uint64_t disk_size,
UserRecord *secret,
- bool automatic,
sd_bus_error *error) {
_cleanup_(user_record_unrefp) UserRecord *c = NULL;
c = TAKE_PTR(signed_c);
}
- r = home_update_internal(h, automatic ? "resize-auto" : "resize", c, secret, NULL, 0, error);
+ r = home_update_internal(h, "resize", c, secret, NULL, 0, error);
if (r < 0)
return r;
int home_create(Home *h, UserRecord *secret, Hashmap *blobs, uint64_t flags, sd_bus_error *error);
int home_remove(Home *h, sd_bus_error *error);
int home_update(Home *h, UserRecord *new_record, Hashmap *blobs, uint64_t flags, sd_bus_error *error);
-int home_resize(Home *h, uint64_t disk_size, UserRecord *secret, bool automatic, sd_bus_error *error);
+int home_resize(Home *h, uint64_t disk_size, UserRecord *secret, sd_bus_error *error);
int home_passwd(Home *h, UserRecord *new_secret, UserRecord *old_secret, sd_bus_error *error);
int home_unregister(Home *h, sd_bus_error *error);
int home_lock(Home *h, sd_bus_error *error);
h->rebalance_pending = false;
- r = home_resize(h, h->rebalance_goal, /* secret= */ NULL, /* automatic= */ true, &error);
+ r = home_resize(h, h->rebalance_goal, /* secret= */ NULL, &error);
if (r < 0)
log_warning_errno(r, "Failed to resize home '%s' for rebalancing, ignoring: %s",
h->user_name, bus_error_message(&error, r));
* times over the course of an operation (think: on login we authenticate the host user record, the
* record embedded in the LUKS record and the one embedded in $HOME). Hence we keep a list of
* passwords we already decrypted, so that we don't have to do the (slow and potentially interactive)
- * PKCS#11/FIDO2 dance for the relevant token again and again. */
+ * PKCS#11/FIDO2 dance for the relevant token again and again.
+ *
+ * The 'cache' parameter might also contain the LUKS volume key, loaded from the kernel keyring.
+ * In this case, authentication becomes optional - if a secret section is provided it will be
+ * verified, but if missing then authentication is skipped entirely. Thus, callers should
+ * consider carefuly whether it is safe to load the volume key into 'cache' before doing so.
+ * Note that most of the time this is safe, because the home area must be active for the key
+ * to exist in the keyring, and the user would have had to authenticate when activating their
+ * home area; however, for some methods (i.e. ChangePassword, Authenticate) it makes more sense
+ * to force re-authentication. */
+
+ /* First, let's see if we already have a volume key from the keyring */
+ if (cache && cache->volume_key &&
+ json_variant_is_blank_object(json_variant_by_key(secret->json, "secret"))) {
+ log_info("LUKS volume key from keyring unlocks user record.");
+ return 1;
+ }
- /* First, let's see if the supplied plain-text passwords work? */
+ /* Next, let's see if the supplied plain-text passwords work? */
r = user_record_test_password(h, secret);
if (r == -ENOKEY)
need_password = true;
else
log_info("None of the supplied plaintext passwords unlock the user record's hashed recovery keys.");
- /* Second, test cached PKCS#11 passwords */
+ /* Next, test cached PKCS#11 passwords */
for (size_t n = 0; n < h->n_pkcs11_encrypted_key; n++)
STRV_FOREACH(pp, cache->pkcs11_passwords) {
r = test_password_one(h->pkcs11_encrypted_key[n].hashed_password, *pp);
}
}
- /* Third, test cached FIDO2 passwords */
+ /* Next, test cached FIDO2 passwords */
for (size_t n = 0; n < h->n_fido2_hmac_salt; n++)
/* See if any of the previously calculated passwords work */
STRV_FOREACH(pp, cache->fido2_passwords) {
}
}
- /* Fourth, let's see if any of the PKCS#11 security tokens are plugged in and help us */
+ /* Next, let's see if any of the PKCS#11 security tokens are plugged in and help us */
for (size_t n = 0; n < h->n_pkcs11_encrypted_key; n++) {
#if HAVE_P11KIT
_cleanup_(pkcs11_callback_data_release) struct pkcs11_callback_data data = {
#endif
}
- /* Fifth, let's see if any of the FIDO2 security tokens are plugged in and help us */
+ /* Next, let's see if any of the FIDO2 security tokens are plugged in and help us */
for (size_t n = 0; n < h->n_fido2_hmac_salt; n++) {
#if HAVE_LIBFIDO2
_cleanup_(erase_and_freep) char *decrypted_password = NULL;
assert(h);
assert(ret);
+ password_cache_load_keyring(h, &cache);
+
r = user_record_authenticate(h, h, &cache, /* strict_verify= */ true);
if (r < 0)
return r;
return 0;
}
-static int home_resize(UserRecord *h, bool automatic, UserRecord **ret) {
+static int home_resize(UserRecord *h, UserRecord **ret) {
_cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(password_cache_free) PasswordCache cache = {};
HomeSetupFlags flags = 0;
if (h->disk_size == UINT64_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No target size specified, refusing.");
- if (automatic)
- /* In automatic mode don't want to ask the user for the password, hence load it from the kernel keyring */
- password_cache_load_keyring(h, &cache);
- else {
- /* In manual mode let's ensure the user is fully authenticated */
- r = user_record_authenticate(h, h, &cache, /* strict_verify= */ true);
- if (r < 0)
- return r;
- assert(r > 0); /* Insist that a password was verified */
- }
+ password_cache_load_keyring(h, &cache);
- r = home_validate_update(h, &setup, &flags);
+ r = user_record_authenticate(h, h, &cache, /* strict_verify= */ true);
if (r < 0)
return r;
+ assert(r > 0); /* Insist that a password was verified */
- /* In automatic mode let's skip syncing identities, because we can't validate them, since we can't
- * ask the user for reauthentication */
- if (automatic)
- flags |= HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES;
+ r = home_validate_update(h, &setup, &flags);
+ if (r < 0)
+ return r;
switch (user_record_storage(h)) {
r = home_remove(home);
else if (streq(argv[1], "update"))
r = home_update(home, blobs, &new_home);
- else if (streq(argv[1], "resize")) /* Resize on user request */
- r = home_resize(home, false, &new_home);
- else if (streq(argv[1], "resize-auto")) /* Automatic resize */
- r = home_resize(home, true, &new_home);
+ else if (streq(argv[1], "resize"))
+ r = home_resize(home, &new_home);
else if (streq(argv[1], "passwd"))
r = home_passwd(home, &new_home);
else if (streq(argv[1], "inspect"))
homectl deactivate test-user
inspect test-user
+# Do some keyring tests, but only on real kernels, since keyring access inside of containers will fail
+# (See: https://github.com/systemd/systemd/issues/17606)
+if ! systemd-detect-virt -cq ; then
+ PASSWORD=xEhErW0ndafV4s homectl activate test-user
+ inspect test-user
+
+ # Key should now be in the keyring
+ homectl update test-user --real-name "Keyring Test"
+ inspect test-user
+
+ # These commands shouldn't use the keyring
+ (! timeout 5s homectl authenticate test-user )
+ (! NEWPASSWORD="foobar" timeout 5s homectl passwd test-user )
+
+ homectl lock test-user
+ inspect test-user
+
+ # Key should be gone from keyring
+ (! timeout 5s homectl update test-user --real-name "Keyring Test 2" )
+
+ PASSWORD=xEhErW0ndafV4s homectl unlock test-user
+ inspect test-user
+
+ # Key should have been re-instantiated into the keyring
+ homectl update test-user --real-name "Keyring Test 3"
+ inspect test-user
+
+ homectl deactivate test-user
+ inspect test-user
+fi
+
# Do some resize tests, but only if we run on real kernels, as quota inside of containers will fail
if ! systemd-detect-virt -cq ; then
# grow while inactive
homectl rebalance
inspect test-user
inspect test-user2
+
+ wait_for_state test-user2 active
+ homectl deactivate test-user2
+ wait_for_state test-user2 inactive
+ homectl remove test-user2
fi
PASSWORD=xEhErW0ndafV4s homectl with test-user -- test ! -f /home/test-user/xyz
wait_for_state test-user inactive
homectl remove test-user
-if ! systemd-detect-virt -cq ; then
- wait_for_state test-user2 active
- homectl deactivate test-user2
- wait_for_state test-user2 inactive
- homectl remove test-user2
-fi
-
# blob directory tests
# See docs/USER_RECORD_BLOB_DIRS.md
checkblob() {