Merge pull request #21333 from poettering/homed-report-fs-and-access-mode

homed: report actual home dir access mode and fs type in effect
This commit is contained in:
Lennart Poettering 2021-11-13 08:04:33 +01:00 committed by GitHub
commit 1ca5a6c76e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 192 additions and 64 deletions

5
TODO
View file

@ -81,6 +81,10 @@ Janitorial Clean-ups:
Features:
* extend src/basic/filesystems.[ch] so that it can be used to translate any fs
magic into a string. Then use that to replace fstype_magic_to_name() in homed
sources, and similar code.
* userdb: when synthesizing NSS records, pick "best" password from defined
passwords, not just the first. i.e. if there are multiple defined, prefer
unlocked over locked and prefer non-empty over empty.
@ -1290,7 +1294,6 @@ Features:
- make size of $XDG_RUNTIME_DIR configurable in user record
- query password from kernel keyring first
- update even if record is "absent"
- add a "access mode" + "fstype" field to the "status" section of json identity records reflecting the actually used access mode and fstype, even on non-luks backends
- move acct mgmt stuff from pam_systemd_home to pam_systemd?
- when "homectl --pkcs11-token-uri=" is used, synthesize ssh-authorized-keys records for all keys we have private keys on the stick for
- make slice for users configurable (requires logind rework)

View file

@ -866,6 +866,11 @@ on removable media the delay is selected very low to minimize the chance the
home directory remains in unclean state if the storage device is removed from
the system by the user).
`accessMode` → The access mode currently in effect for the home directory iself.
`fileSystemType` → The file system type backing the home directory: a short
string, such as "btrfs", "ext4", "xfs".
## Fields in the `signature` section
As mentioned, the `signature` section of the user record may contain one or

View file

@ -17,9 +17,11 @@
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "home-util.h"
#include "homed-home-bus.h"
#include "homed-home.h"
#include "missing_magic.h"
#include "missing_syscall.h"
#include "mkdir.h"
#include "path-util.h"
@ -32,9 +34,9 @@
#include "stat-util.h"
#include "string-table.h"
#include "strv.h"
#include "user-record-pwquality.h"
#include "user-record-sign.h"
#include "user-record-util.h"
#include "user-record-pwquality.h"
#include "user-record.h"
#include "user-util.h"
@ -2105,29 +2107,27 @@ static int home_get_disk_status_luks(
uint64_t *ret_disk_usage,
uint64_t *ret_disk_free,
uint64_t *ret_disk_ceiling,
uint64_t *ret_disk_floor) {
uint64_t *ret_disk_floor,
statfs_f_type_t *ret_fstype,
mode_t *ret_access_mode) {
uint64_t disk_size = UINT64_MAX, disk_usage = UINT64_MAX, disk_free = UINT64_MAX,
disk_ceiling = UINT64_MAX, disk_floor = UINT64_MAX,
stat_used = UINT64_MAX, fs_size = UINT64_MAX, header_size = 0;
mode_t access_mode = MODE_INVALID;
statfs_f_type_t fstype = 0;
struct statfs sfs;
struct stat st;
const char *hd;
int r;
assert(h);
assert(ret_disk_size);
assert(ret_disk_usage);
assert(ret_disk_free);
assert(ret_disk_ceiling);
if (state != HOME_ABSENT) {
const char *ip;
ip = user_record_image_path(h->record);
if (ip) {
struct stat st;
if (stat(ip, &st) < 0)
log_debug_errno(errno, "Failed to stat() %s, ignoring: %m", ip);
else if (S_ISREG(st.st_mode)) {
@ -2175,11 +2175,26 @@ static int home_get_disk_status_luks(
if (!hd)
goto finish;
if (stat(hd, &st) < 0) {
log_debug_errno(errno, "Failed to stat() %s, ignoring: %m", hd);
goto finish;
}
r = stat_verify_directory(&st);
if (r < 0) {
log_debug_errno(r, "Home directory %s is not a directory, ignoring: %m", hd);
goto finish;
}
access_mode = st.st_mode & 07777;
if (statfs(hd, &sfs) < 0) {
log_debug_errno(errno, "Failed to statfs() %s, ignoring: %m", hd);
goto finish;
}
fstype = sfs.f_type;
disk_free = sfs.f_bsize * sfs.f_bavail;
fs_size = sfs.f_bsize * sfs.f_blocks;
if (disk_size != UINT64_MAX && disk_size > fs_size)
@ -2209,11 +2224,20 @@ finish:
if (disk_floor == UINT64_MAX)
disk_floor = minimal_size_by_fs_name(user_record_file_system_type(h->record));
*ret_disk_size = disk_size;
*ret_disk_usage = disk_usage;
*ret_disk_free = disk_free;
*ret_disk_ceiling = disk_ceiling;
*ret_disk_floor = disk_floor;
if (ret_disk_size)
*ret_disk_size = disk_size;
if (ret_disk_usage)
*ret_disk_usage = disk_usage;
if (ret_disk_free)
*ret_disk_free = disk_free;
if (ret_disk_ceiling)
*ret_disk_ceiling = disk_ceiling;
if (ret_disk_floor)
*ret_disk_floor = disk_floor;
if (ret_fstype)
*ret_fstype = fstype;
if (ret_access_mode)
*ret_access_mode = access_mode;
return 0;
}
@ -2225,20 +2249,20 @@ static int home_get_disk_status_directory(
uint64_t *ret_disk_usage,
uint64_t *ret_disk_free,
uint64_t *ret_disk_ceiling,
uint64_t *ret_disk_floor) {
uint64_t *ret_disk_floor,
statfs_f_type_t *ret_fstype,
mode_t *ret_access_mode) {
uint64_t disk_size = UINT64_MAX, disk_usage = UINT64_MAX, disk_free = UINT64_MAX,
disk_ceiling = UINT64_MAX, disk_floor = UINT64_MAX;
mode_t access_mode = MODE_INVALID;
statfs_f_type_t fstype = 0;
struct statfs sfs;
struct dqblk req;
const char *path = NULL;
int r;
assert(ret_disk_size);
assert(ret_disk_usage);
assert(ret_disk_free);
assert(ret_disk_ceiling);
assert(ret_disk_floor);
assert(h);
if (HOME_STATE_IS_ACTIVE(state))
path = user_record_home_directory(h->record);
@ -2261,6 +2285,8 @@ static int home_get_disk_status_directory(
/* We don't initialize disk_usage from statfs() data here, since the device is likely not used
* by us alone, and disk_usage should only reflect our own use. */
fstype = sfs.f_type;
}
if (IN_SET(h->record->storage, USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME)) {
@ -2349,15 +2375,109 @@ static int home_get_disk_status_directory(
}
finish:
*ret_disk_size = disk_size;
*ret_disk_usage = disk_usage;
*ret_disk_free = disk_free;
*ret_disk_ceiling = disk_ceiling;
*ret_disk_floor = disk_floor;
if (ret_disk_size)
*ret_disk_size = disk_size;
if (ret_disk_usage)
*ret_disk_usage = disk_usage;
if (ret_disk_free)
*ret_disk_free = disk_free;
if (ret_disk_ceiling)
*ret_disk_ceiling = disk_ceiling;
if (ret_disk_floor)
*ret_disk_floor = disk_floor;
if (ret_fstype)
*ret_fstype = fstype;
if (ret_access_mode)
*ret_access_mode = access_mode;
return 0;
}
static int home_get_disk_status_internal(
Home *h,
HomeState state,
uint64_t *ret_disk_size,
uint64_t *ret_disk_usage,
uint64_t *ret_disk_free,
uint64_t *ret_disk_ceiling,
uint64_t *ret_disk_floor,
statfs_f_type_t *ret_fstype,
mode_t *ret_access_mode) {
assert(h);
assert(h->record);
switch (h->record->storage) {
case USER_LUKS:
return home_get_disk_status_luks(h, state, ret_disk_size, ret_disk_usage, ret_disk_free, ret_disk_ceiling, ret_disk_floor, ret_fstype, ret_access_mode);
case USER_CLASSIC:
case USER_DIRECTORY:
case USER_SUBVOLUME:
case USER_FSCRYPT:
case USER_CIFS:
return home_get_disk_status_directory(h, state, ret_disk_size, ret_disk_usage, ret_disk_free, ret_disk_ceiling, ret_disk_floor, ret_fstype, ret_access_mode);
default:
/* don't know */
if (ret_disk_size)
*ret_disk_size = UINT64_MAX;
if (ret_disk_usage)
*ret_disk_usage = UINT64_MAX;
if (ret_disk_free)
*ret_disk_free = UINT64_MAX;
if (ret_disk_ceiling)
*ret_disk_ceiling = UINT64_MAX;
if (ret_disk_floor)
*ret_disk_floor = UINT64_MAX;
if (ret_fstype)
*ret_fstype = 0;
if (ret_access_mode)
*ret_access_mode = MODE_INVALID;
return 0;
}
}
int home_get_disk_status(
Home *h,
uint64_t *ret_disk_size,
uint64_t *ret_disk_usage,
uint64_t *ret_disk_free,
uint64_t *ret_disk_ceiling,
uint64_t *ret_disk_floor,
statfs_f_type_t *ret_fstype,
mode_t *ret_access_mode) {
assert(h);
return home_get_disk_status_internal(
h,
home_get_state(h),
ret_disk_size,
ret_disk_usage,
ret_disk_free,
ret_disk_ceiling,
ret_disk_floor,
ret_fstype,
ret_access_mode);
}
static const char *fstype_magic_to_name(statfs_f_type_t magic) {
/* For now, let's only translate the magic values of the file systems we actually are able to manage */
if (F_TYPE_EQUAL(magic, EXT4_SUPER_MAGIC))
return "ext4";
if (F_TYPE_EQUAL(magic, XFS_SUPER_MAGIC))
return "xfs";
if (F_TYPE_EQUAL(magic, BTRFS_SUPER_MAGIC))
return "btrfs";
return NULL;
}
int home_augment_status(
Home *h,
UserRecordLoadFlags flags,
@ -2366,6 +2486,9 @@ int home_augment_status(
uint64_t disk_size = UINT64_MAX, disk_usage = UINT64_MAX, disk_free = UINT64_MAX, disk_ceiling = UINT64_MAX, disk_floor = UINT64_MAX;
_cleanup_(json_variant_unrefp) JsonVariant *j = NULL, *v = NULL, *m = NULL, *status = NULL;
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
statfs_f_type_t magic;
const char *fstype;
mode_t access_mode;
HomeState state;
sd_id128_t id;
int r;
@ -2382,29 +2505,19 @@ int home_augment_status(
state = home_get_state(h);
switch (h->record->storage) {
r = home_get_disk_status_internal(
h, state,
&disk_size,
&disk_usage,
&disk_free,
&disk_ceiling,
&disk_floor,
&magic,
&access_mode);
if (r < 0)
return r;
case USER_LUKS:
r = home_get_disk_status_luks(h, state, &disk_size, &disk_usage, &disk_free, &disk_ceiling, &disk_floor);
if (r < 0)
return r;
break;
case USER_CLASSIC:
case USER_DIRECTORY:
case USER_SUBVOLUME:
case USER_FSCRYPT:
case USER_CIFS:
r = home_get_disk_status_directory(h, state, &disk_size, &disk_usage, &disk_free, &disk_ceiling, &disk_floor);
if (r < 0)
return r;
break;
default:
; /* unset */
}
fstype = fstype_magic_to_name(magic);
if (disk_floor == UINT64_MAX || (disk_usage != UINT64_MAX && disk_floor < disk_usage))
disk_floor = disk_usage;
@ -2422,7 +2535,9 @@ int home_augment_status(
JSON_BUILD_PAIR_CONDITION(disk_free != UINT64_MAX, "diskFree", JSON_BUILD_UNSIGNED(disk_free)),
JSON_BUILD_PAIR_CONDITION(disk_ceiling != UINT64_MAX, "diskCeiling", JSON_BUILD_UNSIGNED(disk_ceiling)),
JSON_BUILD_PAIR_CONDITION(disk_floor != UINT64_MAX, "diskFloor", JSON_BUILD_UNSIGNED(disk_floor)),
JSON_BUILD_PAIR_CONDITION(h->signed_locally >= 0, "signedLocally", JSON_BUILD_BOOLEAN(h->signed_locally))
JSON_BUILD_PAIR_CONDITION(h->signed_locally >= 0, "signedLocally", JSON_BUILD_BOOLEAN(h->signed_locally)),
JSON_BUILD_PAIR_CONDITION(fstype, "fileSystemType", JSON_BUILD_STRING(fstype)),
JSON_BUILD_PAIR_CONDITION(access_mode != MODE_INVALID, "accessMode", JSON_BUILD_UNSIGNED(access_mode))
));
if (r < 0)
return r;

View file

@ -7,6 +7,7 @@ typedef struct Home Home;
#include "homed-operation.h"
#include "list.h"
#include "ordered-set.h"
#include "stat-util.h"
#include "user-record.h"
typedef enum HomeState {
@ -190,6 +191,8 @@ int home_unlock(Home *h, UserRecord *secret, sd_bus_error *error);
HomeState home_get_state(Home *h);
int home_get_disk_status(Home *h, uint64_t *ret_disk_size,uint64_t *ret_disk_usage, uint64_t *ret_disk_free, uint64_t *ret_disk_ceiling, uint64_t *ret_disk_floor, statfs_f_type_t *ret_fstype, mode_t *ret_access_mode);
void home_process_notify(Home *h, char **l, int fd);
int home_killall(Home *h);

View file

@ -1356,21 +1356,23 @@ 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_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_usage), 0 },
{ "diskFree", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_free), 0 },
{ "diskSize", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 },
{ "diskCeiling", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_ceiling), 0 },
{ "diskFloor", JSON_VARIANT_UNSIGNED, 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_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, good_authentication_counter), 0 },
{ "badAuthenticationCounter", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, bad_authentication_counter), 0 },
{ "lastGoodAuthenticationUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, last_good_authentication_usec), 0 },
{ "lastBadAuthenticationUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, last_bad_authentication_usec), 0 },
{ "rateLimitBeginUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_begin_usec), 0 },
{ "rateLimitCount", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_count), 0 },
{ "removable", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, removable), 0 },
{ "diskUsage", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_usage), 0 },
{ "diskFree", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_free), 0 },
{ "diskSize", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 },
{ "diskCeiling", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_ceiling), 0 },
{ "diskFloor", JSON_VARIANT_UNSIGNED, 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_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, good_authentication_counter), 0 },
{ "badAuthenticationCounter", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, bad_authentication_counter), 0 },
{ "lastGoodAuthenticationUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, last_good_authentication_usec), 0 },
{ "lastBadAuthenticationUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, last_bad_authentication_usec), 0 },
{ "rateLimitBeginUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_begin_usec), 0 },
{ "rateLimitCount", JSON_VARIANT_UNSIGNED, 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 },
{},
};