1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-11 20:58:27 +03:00

user-record: Introduce selfModifiable fields

Allows the system administrator to configure what fields the user is
allowed to edit about themself, along with hard-coded defaults.
This commit is contained in:
Adrian Vovk 2024-04-24 18:12:54 -04:00 committed by Luca Boccassi
parent 5310cf3354
commit ad03f2d5f0
4 changed files with 131 additions and 1 deletions

View File

@ -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

View File

@ -28,6 +28,25 @@ const char* user_record_state_color(const char *state) {
return NULL; return NULL;
} }
static void dump_self_modifiable(const char *heading, char **field, const char **value) {
assert(heading);
/* Helper function for printing the various self_modifiable_* fields from the user record */
if (strv_isempty((char**) value))
/* Case 1: the array is explicitly set to be empty by the administrator */
printf("%13s %sDisabled by Administrator%s\n", heading, ansi_highlight_red(), ansi_normal());
else if (!field)
/* Case 2: we have values, but the field is NULL. This means that we're using the defaults.
* We list them anyways, because they're security-sensitive to the administrator */
STRV_FOREACH(i, value)
printf("%13s %s%s%s\n", i == value ? heading : "", ansi_grey(), *i, ansi_normal());
else
/* Case 3: we have a list provided by the administrator */
STRV_FOREACH(i, value)
printf("%13s %s\n", i == value ? heading : "", *i);
}
void user_record_show(UserRecord *hr, bool show_full_group_info) { void user_record_show(UserRecord *hr, bool show_full_group_info) {
_cleanup_strv_free_ char **langs = NULL; _cleanup_strv_free_ char **langs = NULL;
const char *hd, *ip, *shell; const char *hd, *ip, *shell;
@ -585,6 +604,16 @@ 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);
dump_self_modifiable("Self Modify:",
hr->self_modifiable_fields,
user_record_self_modifiable_fields(hr));
dump_self_modifiable("(Blobs)",
hr->self_modifiable_blobs,
user_record_self_modifiable_blobs(hr));
dump_self_modifiable("(Privileged)",
hr->self_modifiable_privileged,
user_record_self_modifiable_privileged(hr));
} }
void group_record_show(GroupRecord *gr, bool show_full_user_info) { void group_record_show(GroupRecord *gr, bool show_full_user_info) {

View File

@ -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);
@ -1300,6 +1304,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 },
{}, {},
}; };
@ -1646,6 +1653,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 },
@ -2156,6 +2166,78 @@ 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);
/* Note that we intentionally distinguish between NULL and an empty array here */
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);
/* Note that we intentionally distinguish between NULL and an empty array here */
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);
/* Note that we intentionally distinguish between NULL and an empty array here */
return (const char**) h->self_modifiable_privileged ?: (const char**) default_fields;
}
uint64_t user_record_ratelimit_next_try(UserRecord *h) { uint64_t user_record_ratelimit_next_try(UserRecord *h) {
assert(h); assert(h);

View File

@ -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,10 @@ 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_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);