user-record: add fields for a fallback home dir + shell

This adds fields to the user record logic to allow a "fallback" home
directory and shell to be set as part of the "status" section of the
user record, i.e. supplied by the manager of the user record.

The idea is that if the fallback homedir/shell is set it will take
precedence over the real one in most ways.

Usecase: let's try to make ssh logins into homed directories work.
systemd-homed would set a fallback shell/homedir for inactive home dirs.
Thus, when ssh logins take place via key auth, we can allow them, and
these fallback session params would be used because the real home cannot
be activated just yet becasue we cannot acquire any password for it from
the user.
This commit is contained in:
Lennart Poettering 2022-05-13 18:21:01 +02:00
parent 6e9bf0ad29
commit 46c60f7216
3 changed files with 72 additions and 20 deletions

View file

@ -914,6 +914,20 @@ itself.
`fileSystemType` → The file system type backing the home directory: a short
string, such as "btrfs", "ext4", "xfs".
`fallbackShell`, `fallbackHomeDirectory` → These fields have the same contents
and format as the `shell` and `homeDirectory` fields (see above). When the
`useFallback` field (see below) is set to true, the data from these fields
should override the fields of the same name without the `fallback` prefix.
`useFallback` → A boolean that allows choosing between the regular `shell` and
`homeDirectory` fields or the fallback fields of the same name (see above). If
`true` the fallback fields should be used in place of the regular fields, if
`false` or unset the regular fields should be used. This mechanism is used for
enable subsystems such as SSH to allow logins into user accounts, whose homed
directories need further unlocking (because the SSH native authentication
cannot release a suitabable disk encryption key), which the fallback shell
provides.
## Fields in the `signature` section
As mentioned, the `signature` section of the user record may contain one or

View file

@ -167,6 +167,9 @@ static UserRecord* user_record_free(UserRecord *h) {
free(h->home_directory);
free(h->home_directory_auto);
free(h->fallback_shell);
free(h->fallback_home_directory);
strv_free(h->member_of);
strv_free(h->capability_bounding_set);
strv_free(h->capability_ambient_set);
@ -1325,23 +1328,26 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp
static int dispatch_status(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
static const JsonDispatch status_dispatch_table[] = {
{ "diskUsage", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_usage), 0 },
{ "diskFree", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_free), 0 },
{ "diskSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 },
{ "diskCeiling", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_ceiling), 0 },
{ "diskFloor", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_floor), 0 },
{ "state", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, state), JSON_SAFE },
{ "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE },
{ "signedLocally", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, signed_locally), 0 },
{ "goodAuthenticationCounter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, good_authentication_counter), 0 },
{ "badAuthenticationCounter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, bad_authentication_counter), 0 },
{ "lastGoodAuthenticationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, last_good_authentication_usec), 0 },
{ "lastBadAuthenticationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, last_bad_authentication_usec), 0 },
{ "rateLimitBeginUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_begin_usec), 0 },
{ "rateLimitCount", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_count), 0 },
{ "removable", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, removable), 0 },
{ "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 },
{ "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
{ "diskUsage", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_usage), 0 },
{ "diskFree", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_free), 0 },
{ "diskSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 },
{ "diskCeiling", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_ceiling), 0 },
{ "diskFloor", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_floor), 0 },
{ "state", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, state), JSON_SAFE },
{ "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE },
{ "signedLocally", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, signed_locally), 0 },
{ "goodAuthenticationCounter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, good_authentication_counter), 0 },
{ "badAuthenticationCounter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, bad_authentication_counter), 0 },
{ "lastGoodAuthenticationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, last_good_authentication_usec), 0 },
{ "lastBadAuthenticationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, last_bad_authentication_usec), 0 },
{ "rateLimitBeginUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_begin_usec), 0 },
{ "rateLimitCount", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_count), 0 },
{ "removable", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, removable), 0 },
{ "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 },
{ "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
{ "fallbackShell", JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, fallback_shell), 0 },
{ "fallbackHomeDirectory", JSON_VARIANT_STRING, json_dispatch_home_directory, offsetof(UserRecord, fallback_home_directory), 0 },
{ "useFallback", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, use_fallback), 0 },
{},
};
@ -1752,7 +1758,7 @@ mode_t user_record_access_mode(UserRecord *h) {
return h->access_mode != MODE_INVALID ? h->access_mode : 0700;
}
const char* user_record_home_directory(UserRecord *h) {
static const char *user_record_home_directory_real(UserRecord *h) {
assert(h);
if (h->home_directory)
@ -1767,6 +1773,15 @@ const char* user_record_home_directory(UserRecord *h) {
return "/";
}
const char* user_record_home_directory(UserRecord *h) {
assert(h);
if (h->use_fallback && h->fallback_home_directory)
return h->fallback_home_directory;
return user_record_home_directory_real(h);
}
const char *user_record_image_path(UserRecord *h) {
assert(h);
@ -1775,7 +1790,9 @@ const char *user_record_image_path(UserRecord *h) {
if (h->image_path_auto)
return h->image_path_auto;
return IN_SET(user_record_storage(h), USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT) ? user_record_home_directory(h) : NULL;
/* For some storage types the image is the home directory itself. (But let's ignore the fallback logic for it) */
return IN_SET(user_record_storage(h), USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT) ?
user_record_home_directory_real(h) : NULL;
}
const char *user_record_cifs_user_name(UserRecord *h) {
@ -1792,7 +1809,7 @@ unsigned long user_record_mount_flags(UserRecord *h) {
(h->nodev ? MS_NODEV : 0);
}
const char *user_record_shell(UserRecord *h) {
static const char *user_record_shell_real(UserRecord *h) {
assert(h);
if (h->shell)
@ -1807,6 +1824,21 @@ const char *user_record_shell(UserRecord *h) {
return NOLOGIN;
}
const char *user_record_shell(UserRecord *h) {
const char *shell;
assert(h);
shell = user_record_shell_real(h);
/* Return fallback shall if we are told so — except if the primary shell is already a nologin shell,
* then let's not risk anything. */
if (h->use_fallback && h->fallback_shell)
return is_nologin_shell(shell) ? NOLOGIN : h->fallback_shell;
return shell;
}
const char *user_record_real_name(UserRecord *h) {
assert(h);

View file

@ -293,6 +293,10 @@ typedef struct UserRecord {
char *home_directory;
char *home_directory_auto; /* when none is set explicitly, this is where we place the implicit home directory */
/* fallback shell and home dir */
char *fallback_shell;
char *fallback_home_directory;
uid_t uid;
gid_t gid;
@ -322,6 +326,8 @@ typedef struct UserRecord {
uint64_t disk_ceiling;
uint64_t disk_floor;
bool use_fallback; /* if true → use fallback_shell + fallback_home_directory instead of the regular ones */
char *state;
char *service;
int signed_locally;