1
0
mirror of https://github.com/systemd/systemd synced 2024-07-01 07:34:28 +00:00

homework: Implement offline updates

This makes it possible to update a home record (and blob directory) of a
home area that's either completely absent (i.e. on a USB stick that's
unplugged) or just inaccessible due to lack of authentication
This commit is contained in:
Adrian Vovk 2024-02-01 13:35:03 -05:00 committed by Luca Boccassi
parent 5ec87d577f
commit d94c7eef12
10 changed files with 131 additions and 27 deletions

View File

@ -169,6 +169,16 @@
<xi:include href="version-info.xml" xpointer="v245"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--offline</option></term>
<listitem><para>Do not attempt to update the copy of the user record and blob directory that is embedded inside
of the home area. This allows for operation on home areas that are absent, or without needing to authenticate as
the user being modified.</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<xi:include href="user-system-options.xml" xpointer="host" />
<xi:include href="user-system-options.xml" xpointer="machine" />

View File

@ -334,8 +334,16 @@ node /org/freedesktop/home1 {
Directories</ulink> for more info). The <varname>blobs</varname> argument works in the same way as
<function>CreateHomeEx()</function>, so check there for details. The new blob directory contents passed into
this method will completely replace the user's existing blob directory. The <varname>flags</varname> argument
may be used for future expansion, but for now pass 0. This method is equivalent to <function>UpdateEx()</function>
on the <classname>org.freedesktop.home1.Home</classname> interface.</para>
can be used to further customize the behavior of this method via flags defined as follows:</para>
<programlisting>
#define SD_HOMED_UPDATE_OFFLINE (UINT64_C(1) &lt;&lt; 0)
</programlisting>
<para>When <constant>SD_HOMED_UPDATE_OFFLINE</constant> (0x01) is set, no attempt is made to update the copies
of the user record and blob directory that are embedded into the home directory. Changes will be stored, however,
and may be propagated into the home directory the next time it is reconciled (most likely when the user next logs in).
Note that any changes made with this flag set may be lost if the home area has a newer record, which can happen
if the home area is updated on another machine after this method call. This method is equivalent to
<function>UpdateEx()</function> 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 optionally a user record consisting only of the <literal>secret</literal>

View File

@ -9,6 +9,13 @@
#include "time-util.h"
#include "user-record.h"
/* Flags supported by UpdateEx() */
#define SD_HOMED_UPDATE_OFFLINE (UINT64_C(1) << 0)
#define SD_HOMED_UPDATE_FLAGS_ALL (SD_HOMED_UPDATE_OFFLINE)
/* Flags supported by CreateHomeEx() */
#define SD_HOMED_CREATE_FLAGS_ALL (0)
/* Put some limits on disk sizes: not less than 5M, not more than 5T */
#define USER_DISK_SIZE_MIN (UINT64_C(5)*1024*1024)
#define USER_DISK_SIZE_MAX (UINT64_C(5)*1024*1024*1024*1024)

View File

@ -61,6 +61,7 @@ static bool arg_legend = true;
static bool arg_ask_password = true;
static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
static const char *arg_host = NULL;
static bool arg_offline = false;
static const char *arg_identity = NULL;
static JsonVariant *arg_identity_extra = NULL;
static JsonVariant *arg_identity_extra_privileged = NULL;
@ -1712,6 +1713,7 @@ static int update_home(int argc, char *argv[], void *userdata) {
_cleanup_free_ char *buffer = NULL;
_cleanup_hashmap_free_ Hashmap *blobs = NULL;
const char *username;
uint64_t flags = 0;
int r;
if (argc >= 2)
@ -1754,6 +1756,9 @@ static int update_home(int argc, char *argv[], void *userdata) {
if (arg_and_resize || arg_and_change_password)
log_info("Updating home directory.");
if (arg_offline)
flags |= SD_HOMED_UPDATE_OFFLINE;
for (;;) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
@ -1777,7 +1782,7 @@ static int update_home(int argc, char *argv[], void *userdata) {
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append(m, "t", UINT64_C(0));
r = sd_bus_message_append(m, "t", flags);
if (r < 0)
return bus_log_create_error(r);
@ -2564,6 +2569,7 @@ static int help(int argc, char *argv[], void *userdata) {
" --no-pager Do not pipe output into a pager\n"
" --no-legend Do not show the headers and footers\n"
" --no-ask-password Do not ask for system passwords\n"
" --offline Don't update record embedded in home directory\n"
" -H --host=[USER@]HOST Operate on remote host\n"
" -M --machine=CONTAINER Operate on local container\n"
" --identity=PATH Read JSON identity from file\n"
@ -2723,6 +2729,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_NO_PAGER,
ARG_NO_LEGEND,
ARG_NO_ASK_PASSWORD,
ARG_OFFLINE,
ARG_REALM,
ARG_EMAIL_ADDRESS,
ARG_DISK_SIZE,
@ -2808,6 +2815,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
{ "offline", no_argument, NULL, ARG_OFFLINE },
{ "host", required_argument, NULL, 'H' },
{ "machine", required_argument, NULL, 'M' },
{ "identity", required_argument, NULL, 'I' },
@ -2933,6 +2941,10 @@ static int parse_argv(int argc, char *argv[]) {
arg_ask_password = false;
break;
case ARG_OFFLINE:
arg_offline = true;
break;
case 'H':
arg_transport = BUS_TRANSPORT_REMOTE;
arg_host = optarg;

View File

@ -6,6 +6,7 @@
#include "bus-polkit.h"
#include "fd-util.h"
#include "format-util.h"
#include "home-util.h"
#include "homed-bus.h"
#include "homed-home-bus.h"
#include "homed-home.h"
@ -432,8 +433,8 @@ int bus_home_update_record(
if (r < 0)
return r;
if (flags != 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Provided flags are unsupported.");
if ((flags & ~SD_HOMED_UPDATE_FLAGS_ALL) != 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags provided.");
r = home_verify_polkit_async(
h,
@ -457,6 +458,8 @@ int bus_home_update_record(
if (r < 0)
return r;
h->current_operation->call_flags = flags;
return 1;
}

View File

@ -955,10 +955,13 @@ static void home_create_finish(Home *h, int ret, UserRecord *hr) {
static void home_change_finish(Home *h, int ret, UserRecord *hr) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
uint64_t flags;
int r;
assert(h);
flags = h->current_operation ? h->current_operation->call_flags : 0;
if (ret < 0) {
(void) home_count_bad_authentication(h, ret, /* save= */ true);
@ -969,17 +972,22 @@ static void home_change_finish(Home *h, int ret, UserRecord *hr) {
}
if (hr) {
r = home_set_record(h, hr);
if (r < 0)
log_warning_errno(r, "Failed to update home record, ignoring: %m");
else {
if (!FLAGS_SET(flags, SD_HOMED_UPDATE_OFFLINE)) {
r = user_record_good_authentication(h->record);
if (r < 0)
log_warning_errno(r, "Failed to increase good authentication counter, ignoring: %m");
}
r = home_set_record(h, hr);
if (r >= 0)
r = home_save_record(h);
if (r < 0)
log_warning_errno(r, "Failed to write home record to disk, ignoring: %m");
if (r < 0) {
if (FLAGS_SET(flags, SD_HOMED_UPDATE_OFFLINE)) {
log_error_errno(r, "Failed to update home record and write it to disk: %m");
sd_bus_error_set(&error, SD_BUS_ERROR_FAILED, "Failed to cache changes to home record");
goto finish;
} else
log_warning_errno(r, "Failed to update home record, ignoring: %m");
}
}
@ -1312,6 +1320,11 @@ static int home_start_work(
_exit(EXIT_FAILURE);
}
if (setenv("SYSTEMD_HOMEWORK_UPDATE_OFFLINE", one_zero(FLAGS_SET(flags, SD_HOMED_UPDATE_OFFLINE)), 1) < 0) {
log_error_errno(errno, "Failed to set $SYSTEMD_HOMEWORK_UPDATE_OFFLINE: %m");
_exit(EXIT_FAILURE);
}
r = setenv_systemd_exec_pid(true);
if (r < 0)
log_warning_errno(r, "Failed to update $SYSTEMD_EXEC_PID, ignoring: %m");
@ -1783,7 +1796,9 @@ int home_update(Home *h, UserRecord *hr, Hashmap *blobs, uint64_t flags, sd_bus_
case HOME_UNFIXATED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_UNFIXATED, "Home %s has not been fixated yet.", h->user_name);
case HOME_ABSENT:
return sd_bus_error_setf(error, BUS_ERROR_HOME_ABSENT, "Home %s is currently missing or not plugged in.", h->user_name);
if (!FLAGS_SET(flags, SD_HOMED_UPDATE_OFFLINE))
return sd_bus_error_setf(error, BUS_ERROR_HOME_ABSENT, "Home %s is currently missing or not plugged in.", h->user_name);
break; /* offline updates are compatible w/ an absent home area */
case HOME_LOCKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
case HOME_INACTIVE:

View File

@ -6,6 +6,7 @@
#include "bus-common-errors.h"
#include "bus-polkit.h"
#include "format-util.h"
#include "home-util.h"
#include "homed-bus.h"
#include "homed-home-bus.h"
#include "homed-manager-bus.h"
@ -507,8 +508,8 @@ static int method_create_home(sd_bus_message *message, void *userdata, sd_bus_er
r = sd_bus_message_read(message, "t", &flags);
if (r < 0)
return r;
if (flags != 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Provided flags are unsupported.");
if ((flags & ~SD_HOMED_CREATE_FLAGS_ALL) != 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags provided.");
}
r = bus_verify_polkit_async(
@ -538,6 +539,8 @@ static int method_create_home(sd_bus_message *message, void *userdata, sd_bus_er
if (r < 0)
return r;
h->current_operation->call_flags = flags;
return 1;
fail:

View File

@ -39,6 +39,7 @@ typedef struct Operation {
sd_bus_message *message;
UserRecord *secret;
uint64_t call_flags; /* flags passed into UpdateEx() or CreateHomeEx() */
int send_fd; /* pipe fd for AcquireHome() which is taken already when we start the operation */
int result; /* < 0 if not completed yet, == 0 on failure, > 0 on success */

View File

@ -1551,6 +1551,21 @@ static int home_remove(UserRecord *h) {
return 0;
}
static int home_basic_validate_update(UserRecord *h) {
assert(h);
if (!h->user_name)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks user name, refusing.");
if (!uid_is_valid(h->uid))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks UID, refusing.");
if (!IN_SET(user_record_storage(h), USER_LUKS, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT, USER_CIFS))
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Processing home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
return 0;
}
static int home_validate_update(UserRecord *h, HomeSetup *setup, HomeSetupFlags *flags) {
bool has_mount = false;
int r;
@ -1558,12 +1573,9 @@ static int home_validate_update(UserRecord *h, HomeSetup *setup, HomeSetupFlags
assert(h);
assert(setup);
if (!h->user_name)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks user name, refusing.");
if (!uid_is_valid(h->uid))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks UID, refusing.");
if (!IN_SET(user_record_storage(h), USER_LUKS, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT, USER_CIFS))
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Processing home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
r = home_basic_validate_update(h);
if (r < 0)
return r;
r = user_record_test_home_directory_and_warn(h);
if (r < 0)
@ -1610,19 +1622,31 @@ static int home_update(UserRecord *h, Hashmap *blobs, UserRecord **ret) {
_cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(password_cache_free) PasswordCache cache = {};
HomeSetupFlags flags = 0;
bool offline;
int r;
assert(h);
assert(ret);
password_cache_load_keyring(h, &cache);
offline = getenv_bool("SYSTEMD_HOMEWORK_UPDATE_OFFLINE") > 0;
r = user_record_authenticate(h, h, &cache, /* strict_verify= */ true);
if (r < 0)
return r;
assert(r > 0); /* Insist that a password was verified */
if (!offline) {
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 */
r = home_validate_update(h, &setup, &flags);
} else {
/* In offline mode we skip all authentication, since we're
* not propagating anything into the home area. The new home
* records's authentication will still be checked when the user
* next logs in, so this is fine */
r = home_basic_validate_update(h);
}
if (r < 0)
return r;
@ -1630,6 +1654,11 @@ static int home_update(UserRecord *h, Hashmap *blobs, UserRecord **ret) {
if (r < 0)
return r;
if (offline) {
log_info("Offline update requested. Not touching embedded records.");
return user_record_clone(h, USER_RECORD_LOAD_MASK_SECRET|USER_RECORD_PERMISSIVE, ret);
}
r = home_setup(h, flags, &setup, &cache, &header_home);
if (r < 0)
return r;

View File

@ -74,13 +74,19 @@ inspect test-user
homectl deactivate test-user
inspect test-user
homectl update test-user --real-name "Offline test" --offline
inspect test-user
PASSWORD=xEhErW0ndafV4s homectl activate test-user
inspect test-user
# Ensure that the offline changes were propagated in
grep "Offline test" /home/test-user/.identity
homectl deactivate test-user
inspect test-user
PASSWORD=xEhErW0ndafV4s homectl update test-user --real-name="Offline test"
PASSWORD=xEhErW0ndafV4s homectl update test-user --real-name="Inactive test"
inspect test-user
PASSWORD=xEhErW0ndafV4s homectl activate test-user
@ -326,6 +332,16 @@ checkblob barely-fits /tmp/external-barely-fits
(! PASSWORD=EMJuc3zQaMibJo homectl update blob-user -b файл=/tmp/external-test3 )
(! PASSWORD=EMJuc3zQaMibJo homectl update blob-user -b special@chars=/tmp/external-test3 )
# Make sure offline updates to blobs get propagated in
homectl deactivate blob-user
inspect blob-user
homectl update blob-user --offline -b barely-fits= -b propagated=/tmp/external-test3
inspect blob-user
PASSWORD=EMJuc3zQaMibJo homectl activate blob-user
inspect blob-user
(! checkblob barely-fits /tmp/external-barely-fits )
checkblob propagated /tmp/external-test3
homectl deactivate blob-user
wait_for_state blob-user inactive
homectl remove blob-user