]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
user-record: Introduce selfModifiable fields
authorAdrian Vovk <adrianvovk@gmail.com>
Wed, 24 Apr 2024 22:12:54 +0000 (18:12 -0400)
committerLuca Boccassi <luca.boccassi@gmail.com>
Fri, 1 Nov 2024 10:41:46 +0000 (10:41 +0000)
Allows the system administrator to configure what fields the user is
allowed to edit about themself, along with hard-coded defaults.

docs/USER_RECORD.md
src/shared/user-record-show.c
src/shared/user-record.c
src/shared/user-record.h

index 0268cc1230bc1f6acf8d747daea504a799ccabad..911fceb03f59303041e71e9ebe840f51fc75f509 100644 (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.
 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
 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`,
 `passwordChangeMinUSec`, `passwordChangeMaxUSec`, `passwordChangeWarnUSec`,
 `passwordChangeInactiveUSec`, `passwordChangeNow`, `pkcs11TokenUri`,
-`fido2HmacCredential`.
+`fido2HmacCredential`, `selfModifiableFields`, `selfModifiableBlobs`, `selfModifiablePrivileged`.
 
 ## Fields in the `binding` section
 
index 45ef48bab6703d1e4087942c6f7f4c1a3e210d47..e6de0cd002f4db48325fa491bc90cc7b3a725ff2 100644 (file)
@@ -28,6 +28,25 @@ const char* user_record_state_color(const char *state) {
         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) {
         _cleanup_strv_free_ char **langs = NULL;
         const char *hd, *ip, *shell;
@@ -585,6 +604,16 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) {
 
         if (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) {
index b03a38eb18a540ec6defef72af335a16f65e8280..35512cbf51a47f14ce4674038dcce520273672fb 100644 (file)
@@ -207,6 +207,10 @@ static UserRecord* user_record_free(UserRecord *h) {
         for (size_t i = 0; i < h->n_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);
 
         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              },
                 { "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              },
+                { "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              },
                 { "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              },
+                { "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              },
                 { "privileged",                 SD_JSON_VARIANT_OBJECT,        dispatch_privileged,                  0,                                                   0              },
@@ -2156,6 +2166,78 @@ int user_record_languages(UserRecord *h, char ***ret) {
         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) {
         assert(h);
 
index 0443820890ca79c2e3b61977a441cdff08b73710..acbb8eca735db97daf819a4acce441ea1888d40f 100644 (file)
@@ -383,6 +383,10 @@ typedef struct UserRecord {
         char **capability_bounding_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;
 } UserRecord;
 
@@ -431,6 +435,10 @@ uint64_t user_record_capability_bounding_set(UserRecord *h);
 uint64_t user_record_capability_ambient_set(UserRecord *h);
 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);
 
 bool user_record_equal(UserRecord *a, UserRecord *b);