Compare commits
8 Commits
cc921b0a6d
...
3099f0dcc3
Author | SHA1 | Date |
---|---|---|
Adrian Vovk | 3099f0dcc3 | |
Antonio Alvarez Feijoo | fb4c82b643 | |
Daan De Meyer | 4d9ccdc9ae | |
Antonio Alvarez Feijoo | bf39626d61 | |
Marius Hoch | ff831e7c50 | |
Adrian Vovk | 58117af9c0 | |
Adrian Vovk | ea9f50a0cd | |
Adrian Vovk | 1c97d3e141 |
|
@ -597,6 +597,17 @@ The salt to pass to the FIDO2 device is found in `fido2HmacSalt`.
|
||||||
The only supported recovery key type at the moment is `modhex64`, for details see the description of `recoveryKey` below.
|
The only supported recovery key type at the moment is `modhex64`, for details see the description of `recoveryKey` below.
|
||||||
An account may have any number of recovery keys defined, and the array should have one entry for each.
|
An account may have any number of recovery keys defined, and the array should have one entry for each.
|
||||||
|
|
||||||
|
`selfModifiableFields` → An array of strings, each corresponding to a field name that can appear
|
||||||
|
in the `regular` or `perMachine` sections. The user may be allowed to edit any field in this list
|
||||||
|
without authenticating as an administrator. Note that the user will only be allowed to edit fields
|
||||||
|
in `perMachine` sections that match the machine the user is performing the edit from.
|
||||||
|
|
||||||
|
`selfModifiableBlobs` → Similar to `selfModifiableFields`, but it lists blobs that the user
|
||||||
|
is allowed to edit.
|
||||||
|
|
||||||
|
`selfModifiablePrivileged` → Similar to `selfModifiableFields`, but it lists fields in
|
||||||
|
the `privileged` section that the user is allowed to edit.
|
||||||
|
|
||||||
`privileged` → An object, which contains the fields of the `privileged` section
|
`privileged` → An object, which contains the fields of the `privileged` section
|
||||||
of the user record, see below.
|
of the user record, see below.
|
||||||
|
|
||||||
|
@ -754,7 +765,7 @@ All other fields that may be used in this section are identical to the equally n
|
||||||
`autoLogin`, `preferredSessionType`, `preferredSessionLauncher`, `stopDelayUSec`, `killProcesses`,
|
`autoLogin`, `preferredSessionType`, `preferredSessionLauncher`, `stopDelayUSec`, `killProcesses`,
|
||||||
`passwordChangeMinUSec`, `passwordChangeMaxUSec`, `passwordChangeWarnUSec`,
|
`passwordChangeMinUSec`, `passwordChangeMaxUSec`, `passwordChangeWarnUSec`,
|
||||||
`passwordChangeInactiveUSec`, `passwordChangeNow`, `pkcs11TokenUri`,
|
`passwordChangeInactiveUSec`, `passwordChangeNow`, `pkcs11TokenUri`,
|
||||||
`fido2HmacCredential`.
|
`fido2HmacCredential`, `selfModifiableFields`, `selfModifiableBlobs`, `selfModifiablePrivileged`.
|
||||||
|
|
||||||
## Fields in the `binding` section
|
## Fields in the `binding` section
|
||||||
|
|
||||||
|
|
|
@ -760,8 +760,9 @@ sensor:modalias:i2c:bmc150_accel:dmi:*:svnLENOVO:*:pvrLenovoYoga300-11IBR:*
|
||||||
sensor:modalias:acpi:ACCL0001*:dmi:*:svnLENOVO:pn60072:pvr851*:*
|
sensor:modalias:acpi:ACCL0001*:dmi:*:svnLENOVO:pn60072:pvr851*:*
|
||||||
ACCEL_MOUNT_MATRIX=0, 1, 0; -1, 0, 0; 0, 0, 1
|
ACCEL_MOUNT_MATRIX=0, 1, 0; -1, 0, 0; 0, 0, 1
|
||||||
|
|
||||||
# IdeaPad Duet 3 10IGL5 (82AT)
|
# IdeaPad Duet 3 10IGL5 (82AT) and 10IGL5-LTE (82HK)
|
||||||
sensor:modalias:acpi:SMO8B30*:dmi:*:svnLENOVO*:pn82AT:*
|
sensor:modalias:acpi:SMO8B30*:dmi:*:svnLENOVO*:pn82AT:*
|
||||||
|
sensor:modalias:acpi:SMO8B30*:dmi:*:svnLENOVO*:pn82HK:*
|
||||||
ACCEL_MOUNT_MATRIX=0, 1, 0; -1, 0, 0; 0, 0, 1
|
ACCEL_MOUNT_MATRIX=0, 1, 0; -1, 0, 0; 0, 0, 1
|
||||||
|
|
||||||
#########################################
|
#########################################
|
||||||
|
|
|
@ -922,9 +922,9 @@
|
||||||
target for some other supplement definition. A target cannot have more than one supplement partition
|
target for some other supplement definition. A target cannot have more than one supplement partition
|
||||||
associated with it.</para>
|
associated with it.</para>
|
||||||
|
|
||||||
<para>For example, distributions can use this to implement <variable>$BOOT</variable> as defined in
|
<para>For example, distributions can use this to implement <varname>$BOOT</varname> as defined in
|
||||||
the <ulink url="https://uapi-group.org/specifications/specs/boot_loader_specification/">Boot Loader
|
the <ulink url="https://uapi-group.org/specifications/specs/boot_loader_specification/">Boot Loader
|
||||||
Specification</ulink>. Distributions may prefer to use the ESP as <variable>$BOOT</variable> whenever
|
Specification</ulink>. Distributions may prefer to use the ESP as <varname>$BOOT</varname> whenever
|
||||||
possible, but to adhere to the spec XBOOTLDR must sometimes be used instead. So, they should create
|
possible, but to adhere to the spec XBOOTLDR must sometimes be used instead. So, they should create
|
||||||
two definitions: the first defining an ESP big enough to hold just the bootloader, and a second for
|
two definitions: the first defining an ESP big enough to hold just the bootloader, and a second for
|
||||||
the XBOOTLDR that's sufficiently large to hold kernels and configured as a supplement for the ESP.
|
the XBOOTLDR that's sufficiently large to hold kernels and configured as a supplement for the ESP.
|
||||||
|
|
|
@ -55,7 +55,7 @@ static int property_get_state(
|
||||||
return sd_bus_message_append(reply, "s", home_state_to_string(home_get_state(h)));
|
return sd_bus_message_append(reply, "s", home_state_to_string(home_get_state(h)));
|
||||||
}
|
}
|
||||||
|
|
||||||
int bus_home_client_is_trusted(Home *h, sd_bus_message *message) {
|
static int bus_home_client_is_trusted(Home *h, sd_bus_message *message, bool strict) {
|
||||||
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
|
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
|
||||||
uid_t euid;
|
uid_t euid;
|
||||||
int r;
|
int r;
|
||||||
|
@ -73,7 +73,7 @@ int bus_home_client_is_trusted(Home *h, sd_bus_message *message) {
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
return euid == 0 || h->uid == euid;
|
return (!strict && euid == 0) || h->uid == euid;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int home_verify_polkit_async(
|
static int home_verify_polkit_async(
|
||||||
|
@ -117,7 +117,7 @@ int bus_home_get_record_json(
|
||||||
assert(h);
|
assert(h);
|
||||||
assert(ret);
|
assert(ret);
|
||||||
|
|
||||||
trusted = bus_home_client_is_trusted(h, message);
|
trusted = bus_home_client_is_trusted(h, message, /* strict= */ false);
|
||||||
if (trusted < 0) {
|
if (trusted < 0) {
|
||||||
log_warning_errno(trusted, "Failed to determine whether client is trusted, assuming untrusted.");
|
log_warning_errno(trusted, "Failed to determine whether client is trusted, assuming untrusted.");
|
||||||
trusted = false;
|
trusted = false;
|
||||||
|
@ -423,7 +423,7 @@ int bus_home_update_record(
|
||||||
Hashmap *blobs,
|
Hashmap *blobs,
|
||||||
uint64_t flags,
|
uint64_t flags,
|
||||||
sd_bus_error *error) {
|
sd_bus_error *error) {
|
||||||
int r;
|
int r, relax_access;
|
||||||
|
|
||||||
assert(h);
|
assert(h);
|
||||||
assert(message);
|
assert(message);
|
||||||
|
@ -436,10 +436,32 @@ int bus_home_update_record(
|
||||||
if ((flags & ~SD_HOMED_UPDATE_FLAGS_ALL) != 0)
|
if ((flags & ~SD_HOMED_UPDATE_FLAGS_ALL) != 0)
|
||||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags provided.");
|
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags provided.");
|
||||||
|
|
||||||
|
if (blobs) {
|
||||||
|
const char *failed = NULL;
|
||||||
|
r = user_record_ensure_blob_manifest(hr, blobs, &failed);
|
||||||
|
if (r == -EINVAL)
|
||||||
|
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Provided blob files do not correspond to blob manifest.");
|
||||||
|
if (r < 0)
|
||||||
|
return sd_bus_error_set_errnof(error, r, "Failed to generate hash for blob %s: %m", strnull(failed));
|
||||||
|
}
|
||||||
|
|
||||||
|
relax_access = user_record_self_changes_allowed(h->record, hr);
|
||||||
|
if (relax_access < 0) {
|
||||||
|
log_warning_errno(relax_access, "Failed to determine if changes to user record are permitted, assuming not: %m");
|
||||||
|
relax_access = false;
|
||||||
|
} else if (relax_access) {
|
||||||
|
relax_access = bus_home_client_is_trusted(h, message, true);
|
||||||
|
if (relax_access < 0) {
|
||||||
|
log_warning_errno(relax_access, "Failed to determine whether client is trusted, assuming not: %m");
|
||||||
|
relax_access = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
r = home_verify_polkit_async(
|
r = home_verify_polkit_async(
|
||||||
h,
|
h,
|
||||||
message,
|
message,
|
||||||
"org.freedesktop.home1.update-home",
|
relax_access ? "org.freedesktop.home1.update-home-by-owner"
|
||||||
|
: "org.freedesktop.home1.update-home",
|
||||||
UID_INVALID,
|
UID_INVALID,
|
||||||
error);
|
error);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
|
@ -561,7 +583,7 @@ int bus_home_method_change_password(
|
||||||
h,
|
h,
|
||||||
message,
|
message,
|
||||||
"org.freedesktop.home1.passwd-home",
|
"org.freedesktop.home1.passwd-home",
|
||||||
h->uid,
|
h->uid, /* Always let a user change their own password. Safe b/c homework will always re-check password */
|
||||||
error);
|
error);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
#include "bus-object.h"
|
#include "bus-object.h"
|
||||||
#include "homed-home.h"
|
#include "homed-home.h"
|
||||||
|
|
||||||
int bus_home_client_is_trusted(Home *h, sd_bus_message *message);
|
|
||||||
int bus_home_get_record_json(Home *h, sd_bus_message *message, char **ret, bool *ret_incomplete);
|
int bus_home_get_record_json(Home *h, sd_bus_message *message, char **ret, bool *ret_incomplete);
|
||||||
|
|
||||||
int bus_home_method_activate(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
int bus_home_method_activate(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||||
|
|
|
@ -1727,15 +1727,6 @@ static int home_update_internal(
|
||||||
secret = saved_secret;
|
secret = saved_secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blobs) {
|
|
||||||
const char *failed = NULL;
|
|
||||||
r = user_record_ensure_blob_manifest(hr, blobs, &failed);
|
|
||||||
if (r == -EINVAL)
|
|
||||||
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Provided blob files do not correspond to blob manifest.");
|
|
||||||
if (r < 0)
|
|
||||||
return sd_bus_error_set_errnof(error, r, "Failed to generate hash for blob %s: %m", strnull(failed));
|
|
||||||
}
|
|
||||||
|
|
||||||
r = manager_verify_user_record(h->manager, hr);
|
r = manager_verify_user_record(h->manager, hr);
|
||||||
switch (r) {
|
switch (r) {
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,16 @@
|
||||||
</defaults>
|
</defaults>
|
||||||
</action>
|
</action>
|
||||||
|
|
||||||
|
<action id="org.freedesktop.home1.update-home-by-owner">
|
||||||
|
<description gettext-domain="systemd">Update your home area</description>
|
||||||
|
<message gettext-domain="systemd">Authentication is required to update your home area.</message>
|
||||||
|
<defaults>
|
||||||
|
<allow_any>auth_admin_keep</allow_any>
|
||||||
|
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||||
|
<allow_active>yes</allow_active>
|
||||||
|
</defaults>
|
||||||
|
</action>
|
||||||
|
|
||||||
<action id="org.freedesktop.home1.resize-home">
|
<action id="org.freedesktop.home1.resize-home">
|
||||||
<description gettext-domain="systemd">Resize a home area</description>
|
<description gettext-domain="systemd">Resize a home area</description>
|
||||||
<message gettext-domain="systemd">Authentication is required to resize a user's home area.</message>
|
<message gettext-domain="systemd">Authentication is required to resize a user's home area.</message>
|
||||||
|
|
|
@ -6,7 +6,9 @@
|
||||||
#include "sd-daemon.h"
|
#include "sd-daemon.h"
|
||||||
|
|
||||||
#include "bpf-dlopen.h"
|
#include "bpf-dlopen.h"
|
||||||
|
#if HAVE_VMLINUX_H
|
||||||
#include "bpf-link.h"
|
#include "bpf-link.h"
|
||||||
|
#endif
|
||||||
#include "build-path.h"
|
#include "build-path.h"
|
||||||
#include "common-signal.h"
|
#include "common-signal.h"
|
||||||
#include "env-util.h"
|
#include "env-util.h"
|
||||||
|
|
|
@ -304,19 +304,6 @@ static SubvolumeFlags subvolume_flags_from_string(const char *s) {
|
||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
static char* subvolume_flags_to_string(SubvolumeFlags flags) {
|
|
||||||
const char *l[CONST_LOG2U(_SUBVOLUME_FLAGS_MASK + 1) + 1]; /* one string per known flag at most */
|
|
||||||
size_t m = 0;
|
|
||||||
|
|
||||||
if (FLAGS_SET(flags, SUBVOLUME_RO))
|
|
||||||
l[m++] = "ro";
|
|
||||||
|
|
||||||
assert(m < ELEMENTSOF(l));
|
|
||||||
l[m] = NULL;
|
|
||||||
|
|
||||||
return strv_join((char**) l, ",");
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct Subvolume {
|
typedef struct Subvolume {
|
||||||
char *path;
|
char *path;
|
||||||
SubvolumeFlags flags;
|
SubvolumeFlags flags;
|
||||||
|
@ -2438,6 +2425,14 @@ static int partition_read_definition(Partition *p, const char *path, const char
|
||||||
"SizeMinBytes=/SizeMaxBytes= cannot be used with Verity=%s.",
|
"SizeMinBytes=/SizeMaxBytes= cannot be used with Verity=%s.",
|
||||||
verity_mode_to_string(p->verity));
|
verity_mode_to_string(p->verity));
|
||||||
|
|
||||||
|
if (!ordered_hashmap_isempty(p->subvolumes) && arg_offline > 0)
|
||||||
|
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||||
|
"Subvolumes= cannot be used with --offline=yes.");
|
||||||
|
|
||||||
|
if (p->default_subvolume && arg_offline > 0)
|
||||||
|
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||||
|
"DefaultSubvolume= cannot be used with --offline=yes.");
|
||||||
|
|
||||||
if (p->default_subvolume && !ordered_hashmap_contains(p->subvolumes, p->default_subvolume))
|
if (p->default_subvolume && !ordered_hashmap_contains(p->subvolumes, p->default_subvolume))
|
||||||
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
|
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
|
||||||
"DefaultSubvolume= must be one of the paths in Subvolumes=.");
|
"DefaultSubvolume= must be one of the paths in Subvolumes=.");
|
||||||
|
@ -4286,7 +4281,7 @@ static int prepare_temporary_file(Context *context, PartitionTarget *t, uint64_t
|
||||||
|
|
||||||
static bool loop_device_error_is_fatal(const Partition *p, int r) {
|
static bool loop_device_error_is_fatal(const Partition *p, int r) {
|
||||||
assert(p);
|
assert(p);
|
||||||
return arg_offline == 0 || (r != -ENOENT && !ERRNO_IS_PRIVILEGE(r));
|
return arg_offline == 0 || (r != -ENOENT && !ERRNO_IS_PRIVILEGE(r)) || !ordered_hashmap_isempty(p->subvolumes) || p->default_subvolume;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int partition_target_prepare(
|
static int partition_target_prepare(
|
||||||
|
@ -5793,38 +5788,6 @@ static int partition_populate_filesystem(Context *context, Partition *p, const c
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int append_btrfs_subvols(char ***l, OrderedHashmap *subvolumes, const char *default_subvolume) {
|
|
||||||
Subvolume *subvolume;
|
|
||||||
int r;
|
|
||||||
|
|
||||||
assert(l);
|
|
||||||
|
|
||||||
ORDERED_HASHMAP_FOREACH(subvolume, subvolumes) {
|
|
||||||
_cleanup_free_ char *s = NULL, *f = NULL;
|
|
||||||
|
|
||||||
s = strdup(subvolume->path);
|
|
||||||
if (!s)
|
|
||||||
return log_oom();
|
|
||||||
|
|
||||||
f = subvolume_flags_to_string(subvolume->flags);
|
|
||||||
if (!f)
|
|
||||||
return log_oom();
|
|
||||||
|
|
||||||
if (streq_ptr(subvolume->path, default_subvolume) &&
|
|
||||||
!strextend_with_separator(&f, ",", "default"))
|
|
||||||
return log_oom();
|
|
||||||
|
|
||||||
if (!isempty(f) && !strextend_with_separator(&s, ":", f))
|
|
||||||
return log_oom();
|
|
||||||
|
|
||||||
r = strv_extend_many(l, "--subvol", s);
|
|
||||||
if (r < 0)
|
|
||||||
return log_oom();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int finalize_extra_mkfs_options(const Partition *p, const char *root, char ***ret) {
|
static int finalize_extra_mkfs_options(const Partition *p, const char *root, char ***ret) {
|
||||||
_cleanup_strv_free_ char **sv = NULL;
|
_cleanup_strv_free_ char **sv = NULL;
|
||||||
int r;
|
int r;
|
||||||
|
@ -5838,18 +5801,6 @@ static int finalize_extra_mkfs_options(const Partition *p, const char *root, cha
|
||||||
"Failed to determine mkfs command line options for '%s': %m",
|
"Failed to determine mkfs command line options for '%s': %m",
|
||||||
p->format);
|
p->format);
|
||||||
|
|
||||||
if (partition_needs_populate(p) && root && streq(p->format, "btrfs")) {
|
|
||||||
r = append_btrfs_subvols(&sv, p->subvolumes, p->default_subvolume);
|
|
||||||
if (r < 0)
|
|
||||||
return r;
|
|
||||||
|
|
||||||
if (p->suppressing) {
|
|
||||||
r = append_btrfs_subvols(&sv, p->suppressing->subvolumes, NULL);
|
|
||||||
if (r < 0)
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*ret = TAKE_PTR(sv);
|
*ret = TAKE_PTR(sv);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -585,6 +585,22 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) {
|
||||||
|
|
||||||
if (hr->service)
|
if (hr->service)
|
||||||
printf(" Service: %s\n", hr->service);
|
printf(" Service: %s\n", hr->service);
|
||||||
|
|
||||||
|
if (!strv_isempty(hr->self_modifiable_fields))
|
||||||
|
STRV_FOREACH(i, hr->self_modifiable_fields)
|
||||||
|
printf(i == hr->self_modifiable_fields ?
|
||||||
|
" Self Modify: %s\n" :
|
||||||
|
" %s\n", *i);
|
||||||
|
else if (!strv_isempty(hr->self_modifiable_blobs) || !strv_isempty(hr->self_modifiable_privileged))
|
||||||
|
printf(" Self Modify: %s(no fields)%s\n", ansi_grey(), ansi_normal());
|
||||||
|
STRV_FOREACH(i, hr->self_modifiable_blobs)
|
||||||
|
printf(i == hr->self_modifiable_blobs ?
|
||||||
|
" (Blobs) %s\n" :
|
||||||
|
" %s\n", *i);
|
||||||
|
STRV_FOREACH(i, hr->self_modifiable_privileged)
|
||||||
|
printf(i == hr->self_modifiable_privileged ?
|
||||||
|
" (Privileged) %s\n" :
|
||||||
|
" %s\n", *i);
|
||||||
}
|
}
|
||||||
|
|
||||||
void group_record_show(GroupRecord *gr, bool show_full_user_info) {
|
void group_record_show(GroupRecord *gr, bool show_full_user_info) {
|
||||||
|
|
|
@ -207,6 +207,10 @@ static UserRecord* user_record_free(UserRecord *h) {
|
||||||
for (size_t i = 0; i < h->n_recovery_key; i++)
|
for (size_t i = 0; i < h->n_recovery_key; i++)
|
||||||
recovery_key_done(h->recovery_key + i);
|
recovery_key_done(h->recovery_key + i);
|
||||||
|
|
||||||
|
strv_free(h->self_modifiable_fields);
|
||||||
|
strv_free(h->self_modifiable_blobs);
|
||||||
|
strv_free(h->self_modifiable_privileged);
|
||||||
|
|
||||||
sd_json_variant_unref(h->json);
|
sd_json_variant_unref(h->json);
|
||||||
|
|
||||||
return mfree(h);
|
return mfree(h);
|
||||||
|
@ -1317,6 +1321,9 @@ static int dispatch_per_machine(const char *name, sd_json_variant *variant, sd_j
|
||||||
{ "passwordChangeNow", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(UserRecord, password_change_now), 0 },
|
{ "passwordChangeNow", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(UserRecord, password_change_now), 0 },
|
||||||
{ "pkcs11TokenUri", SD_JSON_VARIANT_ARRAY, dispatch_pkcs11_uri_array, offsetof(UserRecord, pkcs11_token_uri), 0 },
|
{ "pkcs11TokenUri", SD_JSON_VARIANT_ARRAY, dispatch_pkcs11_uri_array, offsetof(UserRecord, pkcs11_token_uri), 0 },
|
||||||
{ "fido2HmacCredential", SD_JSON_VARIANT_ARRAY, dispatch_fido2_hmac_credential_array, 0, 0 },
|
{ "fido2HmacCredential", SD_JSON_VARIANT_ARRAY, dispatch_fido2_hmac_credential_array, 0, 0 },
|
||||||
|
{ "selfModifiableFields", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_fields), SD_JSON_STRICT },
|
||||||
|
{ "selfModifiableBlobs", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_blobs), SD_JSON_STRICT },
|
||||||
|
{ "selfModifiablePrivileged", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_privileged), SD_JSON_STRICT },
|
||||||
{},
|
{},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1485,7 +1492,6 @@ int user_group_record_mangle(
|
||||||
|
|
||||||
assert(v);
|
assert(v);
|
||||||
assert(ret_variant);
|
assert(ret_variant);
|
||||||
assert(ret_mask);
|
|
||||||
|
|
||||||
/* Note that this function is shared with the group record parser, hence we try to be generic in our
|
/* Note that this function is shared with the group record parser, hence we try to be generic in our
|
||||||
* log message wording here, to cover both cases. */
|
* log message wording here, to cover both cases. */
|
||||||
|
@ -1573,7 +1579,8 @@ int user_group_record_mangle(
|
||||||
else
|
else
|
||||||
*ret_variant = sd_json_variant_ref(v);
|
*ret_variant = sd_json_variant_ref(v);
|
||||||
|
|
||||||
*ret_mask = m;
|
if (ret_mask)
|
||||||
|
*ret_mask = m;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1663,6 +1670,9 @@ int user_record_load(UserRecord *h, sd_json_variant *v, UserRecordLoadFlags load
|
||||||
{ "pkcs11TokenUri", SD_JSON_VARIANT_ARRAY, dispatch_pkcs11_uri_array, offsetof(UserRecord, pkcs11_token_uri), 0 },
|
{ "pkcs11TokenUri", SD_JSON_VARIANT_ARRAY, dispatch_pkcs11_uri_array, offsetof(UserRecord, pkcs11_token_uri), 0 },
|
||||||
{ "fido2HmacCredential", SD_JSON_VARIANT_ARRAY, dispatch_fido2_hmac_credential_array, 0, 0 },
|
{ "fido2HmacCredential", SD_JSON_VARIANT_ARRAY, dispatch_fido2_hmac_credential_array, 0, 0 },
|
||||||
{ "recoveryKeyType", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, recovery_key_type), 0 },
|
{ "recoveryKeyType", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, recovery_key_type), 0 },
|
||||||
|
{ "selfModifiableFields", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_fields), SD_JSON_STRICT },
|
||||||
|
{ "selfModifiableBlobs", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_blobs), SD_JSON_STRICT },
|
||||||
|
{ "selfModifiablePrivileged", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_privileged), SD_JSON_STRICT },
|
||||||
|
|
||||||
{ "secret", SD_JSON_VARIANT_OBJECT, dispatch_secret, 0, 0 },
|
{ "secret", SD_JSON_VARIANT_OBJECT, dispatch_secret, 0, 0 },
|
||||||
{ "privileged", SD_JSON_VARIANT_OBJECT, dispatch_privileged, 0, 0 },
|
{ "privileged", SD_JSON_VARIANT_OBJECT, dispatch_privileged, 0, 0 },
|
||||||
|
@ -2173,6 +2183,228 @@ int user_record_languages(UserRecord *h, char ***ret) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char** user_record_self_modifiable_fields(UserRecord *h) {
|
||||||
|
/* As a rule of thumb: a setting is safe if it cannot be used by a
|
||||||
|
* user to give themselves some unfair advantage over other users on
|
||||||
|
* a given system. */
|
||||||
|
static const char *const default_fields[] = {
|
||||||
|
/* For display purposes */
|
||||||
|
"realName",
|
||||||
|
"emailAddress", /* Just the $EMAIL env var */
|
||||||
|
"iconName",
|
||||||
|
"location",
|
||||||
|
|
||||||
|
/* Basic account settings */
|
||||||
|
"shell",
|
||||||
|
"umask",
|
||||||
|
"environment",
|
||||||
|
"timeZone",
|
||||||
|
"preferredLanguage",
|
||||||
|
"additionalLanguages",
|
||||||
|
"preferredSessionLauncher",
|
||||||
|
"preferredSessionType",
|
||||||
|
|
||||||
|
/* Authentication methods */
|
||||||
|
"pkcs11TokenUri",
|
||||||
|
"fido2HmacCredential",
|
||||||
|
"recoveryKeyType",
|
||||||
|
|
||||||
|
"lastChangeUSec", /* Necessary to be able to change record at all */
|
||||||
|
"lastPasswordChangeUSec", /* Ditto, but for authentication methods */
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
assert(h);
|
||||||
|
|
||||||
|
return (const char**) h->self_modifiable_fields ?: (const char**) default_fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char** user_record_self_modifiable_blobs(UserRecord *h) {
|
||||||
|
static const char *const default_blobs[] = {
|
||||||
|
/* For display purposes */
|
||||||
|
"avatar",
|
||||||
|
"login-background",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
assert(h);
|
||||||
|
|
||||||
|
return (const char**) h->self_modifiable_blobs ?: (const char**) default_blobs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char** user_record_self_modifiable_privileged(UserRecord *h) {
|
||||||
|
static const char *const default_fields[] = {
|
||||||
|
/* For display purposes */
|
||||||
|
"passwordHint",
|
||||||
|
|
||||||
|
/* Authentication methods */
|
||||||
|
"hashedPassword"
|
||||||
|
"pkcs11EncryptedKey",
|
||||||
|
"fido2HmacSalt",
|
||||||
|
"recoveryKey",
|
||||||
|
|
||||||
|
"sshAuthorizedKeys", /* Basically just ~/.ssh/authorized_keys */
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
assert(h);
|
||||||
|
|
||||||
|
return (const char**) h->self_modifiable_privileged ?: (const char**) default_fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int remove_self_modifiable_json_fields_common(UserRecord *current, sd_json_variant **ret) {
|
||||||
|
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *blobs = NULL;
|
||||||
|
char **allowed;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(current);
|
||||||
|
assert(ret);
|
||||||
|
|
||||||
|
if (!sd_json_variant_is_object(*ret))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
v = sd_json_variant_ref(*ret);
|
||||||
|
|
||||||
|
/* Handle basic fields */
|
||||||
|
allowed = (char**) user_record_self_modifiable_fields(current);
|
||||||
|
r = sd_json_variant_filter(&v, allowed);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
/* Handle blobs */
|
||||||
|
blobs = sd_json_variant_ref(sd_json_variant_by_key(v, "blobManifest"));
|
||||||
|
if (blobs) {
|
||||||
|
/* The blobManifest contains the sha256 hashes of the blobs,
|
||||||
|
* which are enforced by the service managing the user. So, by
|
||||||
|
* comparing the blob manifests like this, we're actually comparing
|
||||||
|
* the contents of the blob directories & files */
|
||||||
|
|
||||||
|
allowed = (char**) user_record_self_modifiable_blobs(current);
|
||||||
|
r = sd_json_variant_filter(&blobs, allowed);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (sd_json_variant_is_blank_object(blobs))
|
||||||
|
r = sd_json_variant_filter(&v, STRV_MAKE("blobManifest"));
|
||||||
|
else
|
||||||
|
r = sd_json_variant_set_field(&v, "blobManifest", blobs);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSON_VARIANT_REPLACE(*ret, TAKE_PTR(v));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int remove_self_modifiable_json_fields(UserRecord *current, UserRecord *h, sd_json_variant **ret) {
|
||||||
|
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *privileged = NULL;
|
||||||
|
sd_json_variant *per_machine;
|
||||||
|
char **allowed;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(current);
|
||||||
|
assert(h);
|
||||||
|
assert(ret);
|
||||||
|
|
||||||
|
r = user_group_record_mangle(h->json, USER_RECORD_EXTRACT_SIGNABLE|USER_RECORD_PERMISSIVE, &v, NULL);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
/* Handle the regular section */
|
||||||
|
r = remove_self_modifiable_json_fields_common(current, &v);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
/* Handle the perMachine section */
|
||||||
|
per_machine = sd_json_variant_by_key(v, "perMachine");
|
||||||
|
if (per_machine) {
|
||||||
|
_cleanup_(sd_json_variant_unrefp) sd_json_variant *new_per_machine = NULL;
|
||||||
|
sd_json_variant *e;
|
||||||
|
|
||||||
|
if (!sd_json_variant_is_array(per_machine))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
JSON_VARIANT_ARRAY_FOREACH(e, per_machine) {
|
||||||
|
_cleanup_(sd_json_variant_unrefp) sd_json_variant *z = NULL;
|
||||||
|
|
||||||
|
if (!sd_json_variant_is_object(e))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
r = per_machine_match(e, 0);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
if (r == 0) {
|
||||||
|
/* It's only permissible to change anything inside of matching perMachine sections */
|
||||||
|
r = sd_json_variant_append_array(&new_per_machine, e);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
z = sd_json_variant_ref(e);
|
||||||
|
|
||||||
|
r = remove_self_modifiable_json_fields_common(current, &z);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (!sd_json_variant_is_blank_object(z)) {
|
||||||
|
r = sd_json_variant_append_array(&new_per_machine, z);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sd_json_variant_is_blank_array(new_per_machine))
|
||||||
|
r = sd_json_variant_filter(&v, STRV_MAKE("perMachine"));
|
||||||
|
else
|
||||||
|
r = sd_json_variant_set_field(&v, "perMachine", new_per_machine);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle the privileged section */
|
||||||
|
privileged = sd_json_variant_ref(sd_json_variant_by_key(v, "privileged"));
|
||||||
|
if (privileged) {
|
||||||
|
allowed = (char**) user_record_self_modifiable_privileged(current);
|
||||||
|
r = sd_json_variant_filter(&privileged, allowed);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (sd_json_variant_is_blank_object(privileged))
|
||||||
|
r = sd_json_variant_filter(&v, STRV_MAKE("privileged"));
|
||||||
|
else
|
||||||
|
r = sd_json_variant_set_field(&v, "privileged", privileged);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSON_VARIANT_REPLACE(*ret, TAKE_PTR(v));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int user_record_self_changes_allowed(UserRecord *current, UserRecord *new) {
|
||||||
|
_cleanup_(sd_json_variant_unrefp) sd_json_variant *vc = NULL, *vn = NULL;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(current);
|
||||||
|
assert(new);
|
||||||
|
|
||||||
|
/* We remove the fields that are allowed to change as the user,
|
||||||
|
* and then compare the resulting JSON records. If they are not equal,
|
||||||
|
* that means an unsafe field has been changed and thus we should not
|
||||||
|
* allow the user to apply the changes to themself. */
|
||||||
|
|
||||||
|
r = remove_self_modifiable_json_fields(current, current, &vc);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = remove_self_modifiable_json_fields(current, new, &vn);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
return sd_json_variant_equal(vc, vn);
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t user_record_ratelimit_next_try(UserRecord *h) {
|
uint64_t user_record_ratelimit_next_try(UserRecord *h) {
|
||||||
assert(h);
|
assert(h);
|
||||||
|
|
||||||
|
|
|
@ -383,6 +383,10 @@ typedef struct UserRecord {
|
||||||
char **capability_bounding_set;
|
char **capability_bounding_set;
|
||||||
char **capability_ambient_set;
|
char **capability_ambient_set;
|
||||||
|
|
||||||
|
char **self_modifiable_fields; /* fields a user can change about themself w/o auth */
|
||||||
|
char **self_modifiable_blobs;
|
||||||
|
char **self_modifiable_privileged;
|
||||||
|
|
||||||
sd_json_variant *json;
|
sd_json_variant *json;
|
||||||
} UserRecord;
|
} UserRecord;
|
||||||
|
|
||||||
|
@ -431,6 +435,11 @@ uint64_t user_record_capability_bounding_set(UserRecord *h);
|
||||||
uint64_t user_record_capability_ambient_set(UserRecord *h);
|
uint64_t user_record_capability_ambient_set(UserRecord *h);
|
||||||
int user_record_languages(UserRecord *h, char ***ret);
|
int user_record_languages(UserRecord *h, char ***ret);
|
||||||
|
|
||||||
|
const char **user_record_self_modifiable_fields(UserRecord *h);
|
||||||
|
const char **user_record_self_modifiable_blobs(UserRecord *h);
|
||||||
|
const char **user_record_self_modifiable_privileged(UserRecord *h);
|
||||||
|
int user_record_self_changes_allowed(UserRecord *current, UserRecord *new);
|
||||||
|
|
||||||
int user_record_build_image_path(UserStorage storage, const char *user_name_and_realm, char **ret);
|
int user_record_build_image_path(UserStorage storage, const char *user_name_and_realm, char **ret);
|
||||||
|
|
||||||
bool user_record_equal(UserRecord *a, UserRecord *b);
|
bool user_record_equal(UserRecord *a, UserRecord *b);
|
||||||
|
|
|
@ -182,6 +182,7 @@ simple_tests += files(
|
||||||
'test-umask-util.c',
|
'test-umask-util.c',
|
||||||
'test-unaligned.c',
|
'test-unaligned.c',
|
||||||
'test-unit-file.c',
|
'test-unit-file.c',
|
||||||
|
'test-user-record.c',
|
||||||
'test-user-util.c',
|
'test-user-util.c',
|
||||||
'test-utf8.c',
|
'test-utf8.c',
|
||||||
'test-verbs.c',
|
'test-verbs.c',
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
|
||||||
|
#include "json-util.h"
|
||||||
|
#include "macro.h"
|
||||||
|
#include "tests.h"
|
||||||
|
#include "user-record.h"
|
||||||
|
|
||||||
|
#define USER(ret, ...) \
|
||||||
|
({ \
|
||||||
|
typeof(ret) _r = (ret); \
|
||||||
|
user_record_unref(*_r); \
|
||||||
|
assert_se(user_record_build((ret), SD_JSON_BUILD_OBJECT(__VA_ARGS__)) >= 0); \
|
||||||
|
0; \
|
||||||
|
})
|
||||||
|
|
||||||
|
TEST(self_changes) {
|
||||||
|
_cleanup_(user_record_unrefp) UserRecord *curr = NULL, *new = NULL;
|
||||||
|
|
||||||
|
/* not allowlisted */
|
||||||
|
USER(&curr,
|
||||||
|
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
|
||||||
|
SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 11111));
|
||||||
|
USER(&new,
|
||||||
|
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
|
||||||
|
SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 99999));
|
||||||
|
assert_se(!user_record_self_changes_allowed(curr, new));
|
||||||
|
|
||||||
|
/* manually allowlisted */
|
||||||
|
USER(&curr,
|
||||||
|
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
|
||||||
|
SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 11111),
|
||||||
|
SD_JSON_BUILD_PAIR_ARRAY("selfModifiableFields", SD_JSON_BUILD_STRING("notInHardCodedList")));
|
||||||
|
USER(&new,
|
||||||
|
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
|
||||||
|
SD_JSON_BUILD_PAIR_ARRAY("selfModifiableFields", SD_JSON_BUILD_STRING("notInHardCodedList")),
|
||||||
|
/* change in order shouldn't affect things */
|
||||||
|
SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 99999));
|
||||||
|
assert_se(user_record_self_changes_allowed(curr, new));
|
||||||
|
|
||||||
|
/* default allowlisted */
|
||||||
|
USER(&curr,
|
||||||
|
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
|
||||||
|
SD_JSON_BUILD_PAIR_STRING("realName", "Old Name"));
|
||||||
|
USER(&new,
|
||||||
|
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
|
||||||
|
SD_JSON_BUILD_PAIR_STRING("realName", "New Name"));
|
||||||
|
assert_se(user_record_self_changes_allowed(curr, new));
|
||||||
|
|
||||||
|
/* introduced new default allowlisted */
|
||||||
|
USER(&curr,
|
||||||
|
SD_JSON_BUILD_PAIR_STRING("userName", "test"));
|
||||||
|
USER(&new,
|
||||||
|
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
|
||||||
|
SD_JSON_BUILD_PAIR_STRING("realName", "New Name"));
|
||||||
|
assert_se(user_record_self_changes_allowed(curr, new));
|
||||||
|
|
||||||
|
/* introduced new not allowlisted */
|
||||||
|
USER(&curr,
|
||||||
|
SD_JSON_BUILD_PAIR_STRING("userName", "test"));
|
||||||
|
USER(&new,
|
||||||
|
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
|
||||||
|
SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 99999));
|
||||||
|
assert_se(!user_record_self_changes_allowed(curr, new));
|
||||||
|
|
||||||
|
/* privileged section: default allowlisted */
|
||||||
|
USER(&curr,
|
||||||
|
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
|
||||||
|
SD_JSON_BUILD_PAIR_OBJECT("privileged",
|
||||||
|
SD_JSON_BUILD_PAIR_STRING("passwordHint", "Old Hint")));
|
||||||
|
USER(&new,
|
||||||
|
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
|
||||||
|
SD_JSON_BUILD_PAIR_OBJECT("privileged",
|
||||||
|
SD_JSON_BUILD_PAIR_STRING("passwordHint", "New Hint")));
|
||||||
|
assert_se(user_record_self_changes_allowed(curr, new));
|
||||||
|
|
||||||
|
/* privileged section: not allowlisted */
|
||||||
|
USER(&curr,
|
||||||
|
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
|
||||||
|
SD_JSON_BUILD_PAIR_OBJECT("privileged",
|
||||||
|
SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 11111)));
|
||||||
|
USER(&new,
|
||||||
|
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
|
||||||
|
SD_JSON_BUILD_PAIR_OBJECT("privileged",
|
||||||
|
SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 99999)));
|
||||||
|
assert_se(!user_record_self_changes_allowed(curr, new));
|
||||||
|
|
||||||
|
/* privileged section: manually allowlisted */
|
||||||
|
USER(&curr,
|
||||||
|
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
|
||||||
|
SD_JSON_BUILD_PAIR_ARRAY("selfModifiablePrivileged", SD_JSON_BUILD_STRING("notInHardCodedList")),
|
||||||
|
SD_JSON_BUILD_PAIR_OBJECT("privileged",
|
||||||
|
SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 11111)));
|
||||||
|
USER(&new,
|
||||||
|
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
|
||||||
|
SD_JSON_BUILD_PAIR_ARRAY("selfModifiablePrivileged", SD_JSON_BUILD_STRING("notInHardCodedList")),
|
||||||
|
SD_JSON_BUILD_PAIR_OBJECT("privileged",
|
||||||
|
SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 99999)));
|
||||||
|
assert_se(user_record_self_changes_allowed(curr, new));
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST_MAIN(LOG_INFO);
|
Loading…
Reference in New Issue