Compare commits

...

6 Commits

Author SHA1 Message Date
Anita Zhang 96a4ce9f1d
Merge pull request #16690 from poettering/userdb-group-desc
description field for group records
2020-08-11 00:27:54 -07:00
Lennart Poettering 7e31e90e58 units: order volatile-root after repart
Let's make sure systemd-repart can still see the real device before we
replace its mount with an overlay mount, and thus order repart before
volatile-root.

See: https://lists.freedesktop.org/archives/systemd-devel/2020-July/044896.html
2020-08-11 09:12:56 +02:00
Lennart Poettering dcb9007162 update TODO 2020-08-07 08:40:00 +02:00
Lennart Poettering 072779f0bf docs: document new description field
Also, explain GECOS syntax requirements.
2020-08-07 08:39:56 +02:00
Lennart Poettering 0a388dfcc5 core,home,machined: generate description fields for all groups we synthesize 2020-08-07 08:39:52 +02:00
Lennart Poettering 0bb4308014 userdb: add "description" field to group records
User records have the realname/gecos fields, groups never had that, but
it would really be useful to have it, hence let's add it with similar
semantics.

We enforce the same syntax as for GECOS, since it's better to start with
strict rules and losen them later instead of the opposite.
2020-08-07 08:39:18 +02:00
13 changed files with 61 additions and 21 deletions

1
TODO
View File

@ -372,7 +372,6 @@ Features:
- in systemd's PAMName= logic: query passwords with ssh-askpassword, so that we can make "loginctl set-linger" mode work - in systemd's PAMName= logic: query passwords with ssh-askpassword, so that we can make "loginctl set-linger" mode work
- fingerprint authentication, pattern authentication, … - fingerprint authentication, pattern authentication, …
- make sure "classic" user records can also be managed by homed - make sure "classic" user records can also be managed by homed
- description field for groups
- make size of $XDG_RUNTIME_DIR configurable in user record - make size of $XDG_RUNTIME_DIR configurable in user record
- reuse pwquality magic in firstboot - reuse pwquality magic in firstboot
- query password from kernel keyring first - query password from kernel keyring first

View File

@ -22,6 +22,10 @@ UNIX/glibc NSS `struct group`, or the shadow structure `struct sgrp`'s
`realm` → The "realm" the group belongs to, conceptually identical to the same `realm` → The "realm" the group belongs to, conceptually identical to the same
field of user records. A string in DNS domain name syntax. field of user records. A string in DNS domain name syntax.
`description` → A descriptive string for the group. This is similar to the
`realName` field of user records, and accepts arbitrary strings, as long as
they follow the same GECOS syntax requirements as `realName`.
`disposition` → The disposition of the group, conceptually identical to the `disposition` → The disposition of the group, conceptually identical to the
same field of user records. A string. same field of user records. A string.

View File

@ -221,12 +221,14 @@ optional, when unset the user should not be considered part of any realm. A
user record with a realm set is never compatible (for the purpose of updates, user record with a realm set is never compatible (for the purpose of updates,
see above) with a user record without one set, even if the `userName` field matches. see above) with a user record without one set, even if the `userName` field matches.
`realName` → The real name of the user, a string. This should contain the user's `realName` → The real name of the user, a string. This should contain the
real ("human") name, and corresponds loosely to the GECOS field of classic UNIX user's real ("human") name, and corresponds loosely to the GECOS field of
user records. When converting a `struct passwd` to a JSON user record this classic UNIX user records. When converting a `struct passwd` to a JSON user
field is initialized from GECOS (i.e. the `pw_gecos` field), and vice versa record this field is initialized from GECOS (i.e. the `pw_gecos` field), and
when converting back. That said, unlike GECOS this field is supposed to contain vice versa when converting back. That said, unlike GECOS this field is supposed
only the real name and no other information. to contain only the real name and no other information. This field must not
contain control characters (such as `\n`) or colons (`:`), since those are used
as record separators in classic `/etc/passwd` files and similar formats.
`emailAddress` → The email address of the user, formatted as `emailAddress` → The email address of the user, formatted as
string. [`pam_systemd`](https://www.freedesktop.org/software/systemd/man/pam_systemd.html) string. [`pam_systemd`](https://www.freedesktop.org/software/systemd/man/pam_systemd.html)

View File

@ -136,6 +136,7 @@ static int build_group_json(const char *group_name, gid_t gid, JsonVariant **ret
return json_build(ret, JSON_BUILD_OBJECT( return json_build(ret, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("record", JSON_BUILD_OBJECT( JSON_BUILD_PAIR("record", JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(group_name)), JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(group_name)),
JSON_BUILD_PAIR("description", JSON_BUILD_STRING("Dynamic Group")),
JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(gid)), JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(gid)),
JSON_BUILD_PAIR("service", JSON_BUILD_STRING("io.systemd.DynamicUser")), JSON_BUILD_PAIR("service", JSON_BUILD_STRING("io.systemd.DynamicUser")),
JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING("dynamic")))))); JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING("dynamic"))))));

View File

@ -104,7 +104,7 @@ int user_record_synthesize(
} }
int group_record_synthesize(GroupRecord *g, UserRecord *h) { int group_record_synthesize(GroupRecord *g, UserRecord *h) {
_cleanup_free_ char *un = NULL, *rr = NULL, *group_name_and_realm = NULL; _cleanup_free_ char *un = NULL, *rr = NULL, *group_name_and_realm = NULL, *description = NULL;
char smid[SD_ID128_STRING_MAX]; char smid[SD_ID128_STRING_MAX];
sd_id128_t mid; sd_id128_t mid;
int r; int r;
@ -133,10 +133,15 @@ int group_record_synthesize(GroupRecord *g, UserRecord *h) {
return -ENOMEM; return -ENOMEM;
} }
description = strjoin("Primary Group of User ", un);
if (!description)
return -ENOMEM;
r = json_build(&g->json, r = json_build(&g->json,
JSON_BUILD_OBJECT( JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(un)), JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(un)),
JSON_BUILD_PAIR_CONDITION(!!rr, "realm", JSON_BUILD_STRING(rr)), JSON_BUILD_PAIR_CONDITION(!!rr, "realm", JSON_BUILD_STRING(rr)),
JSON_BUILD_PAIR("description", JSON_BUILD_STRING(description)),
JSON_BUILD_PAIR("binding", JSON_BUILD_OBJECT( JSON_BUILD_PAIR("binding", JSON_BUILD_OBJECT(
JSON_BUILD_PAIR(sd_id128_to_string(mid, smid), JSON_BUILD_OBJECT( JSON_BUILD_PAIR(sd_id128_to_string(mid, smid), JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(user_record_gid(h))))))), JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(user_record_gid(h))))))),

View File

@ -93,6 +93,8 @@ static int user_lookup_name(Manager *m, const char *name, uid_t *ret_uid, char *
int r; int r;
assert(m); assert(m);
assert(ret_uid);
assert(ret_real_name);
if (!valid_user_group_name(name, 0)) if (!valid_user_group_name(name, 0))
return -ESRCH; return -ESRCH;
@ -186,7 +188,7 @@ static int vl_method_get_user_record(Varlink *link, JsonVariant *parameters, Var
return varlink_reply(link, v); return varlink_reply(link, v);
} }
static int build_group_json(const char *group_name, gid_t gid, JsonVariant **ret) { static int build_group_json(const char *group_name, gid_t gid, const char *description, JsonVariant **ret) {
assert(group_name); assert(group_name);
assert(gid_is_valid(gid)); assert(gid_is_valid(gid));
assert(ret); assert(ret);
@ -195,6 +197,7 @@ static int build_group_json(const char *group_name, gid_t gid, JsonVariant **ret
JSON_BUILD_PAIR("record", JSON_BUILD_OBJECT( JSON_BUILD_PAIR("record", JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(group_name)), JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(group_name)),
JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(gid)), JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(gid)),
JSON_BUILD_PAIR_CONDITION(!isempty(description), "description", JSON_BUILD_STRING(description)),
JSON_BUILD_PAIR("service", JSON_BUILD_STRING("io.systemd.Machine")), JSON_BUILD_PAIR("service", JSON_BUILD_STRING("io.systemd.Machine")),
JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING("container")))))); JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING("container"))))));
} }
@ -211,8 +214,8 @@ static bool group_match_lookup_parameters(LookupParameters *p, const char *name,
return true; return true;
} }
static int group_lookup_gid(Manager *m, gid_t gid, char **ret_name) { static int group_lookup_gid(Manager *m, gid_t gid, char **ret_name, char **ret_description) {
_cleanup_free_ char *n = NULL; _cleanup_free_ char *n = NULL, *d = NULL;
gid_t converted_gid; gid_t converted_gid;
Machine *machine; Machine *machine;
int r; int r;
@ -220,6 +223,7 @@ static int group_lookup_gid(Manager *m, gid_t gid, char **ret_name) {
assert(m); assert(m);
assert(gid_is_valid(gid)); assert(gid_is_valid(gid));
assert(ret_name); assert(ret_name);
assert(ret_description);
if (gid < 0x10000) /* Host GID range */ if (gid < 0x10000) /* Host GID range */
return -ESRCH; return -ESRCH;
@ -236,18 +240,27 @@ static int group_lookup_gid(Manager *m, gid_t gid, char **ret_name) {
if (!valid_user_group_name(n, 0)) if (!valid_user_group_name(n, 0))
return -ESRCH; return -ESRCH;
if (asprintf(&d, "GID " GID_FMT " of Container %s", converted_gid, machine->name) < 0)
return -ENOMEM;
if (!valid_gecos(d))
d = mfree(d);
*ret_name = TAKE_PTR(n); *ret_name = TAKE_PTR(n);
*ret_description = TAKE_PTR(d);
return 0; return 0;
} }
static int group_lookup_name(Manager *m, const char *name, gid_t *ret_gid) { static int group_lookup_name(Manager *m, const char *name, gid_t *ret_gid, char **ret_description) {
_cleanup_free_ char *mn = NULL; _cleanup_free_ char *mn = NULL, *desc = NULL;
gid_t gid, converted_gid; gid_t gid, converted_gid;
Machine *machine; Machine *machine;
const char *e, *d; const char *e, *d;
int r; int r;
assert(m); assert(m);
assert(ret_gid);
assert(ret_description);
if (!valid_user_group_name(name, 0)) if (!valid_user_group_name(name, 0))
return -ESRCH; return -ESRCH;
@ -278,7 +291,13 @@ static int group_lookup_name(Manager *m, const char *name, gid_t *ret_gid) {
if (r < 0) if (r < 0)
return r; return r;
if (asprintf(&desc, "GID " GID_FMT " of Container %s", gid, machine->name) < 0)
return -ENOMEM;
if (!valid_gecos(desc))
desc = mfree(desc);
*ret_gid = converted_gid; *ret_gid = converted_gid;
*ret_description = desc;
return 0; return 0;
} }
@ -295,7 +314,7 @@ static int vl_method_get_group_record(Varlink *link, JsonVariant *parameters, Va
LookupParameters p = { LookupParameters p = {
.gid = GID_INVALID, .gid = GID_INVALID,
}; };
_cleanup_free_ char *found_name = NULL; _cleanup_free_ char *found_name = NULL, *found_description = NULL;
uid_t found_gid = GID_INVALID, gid; uid_t found_gid = GID_INVALID, gid;
Manager *m = userdata; Manager *m = userdata;
const char *gn; const char *gn;
@ -312,9 +331,9 @@ static int vl_method_get_group_record(Varlink *link, JsonVariant *parameters, Va
return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL);
if (gid_is_valid(p.gid)) if (gid_is_valid(p.gid))
r = group_lookup_gid(m, p.gid, &found_name); r = group_lookup_gid(m, p.gid, &found_name, &found_description);
else if (p.group_name) else if (p.group_name)
r = group_lookup_name(m, p.group_name, (uid_t*) &found_gid); r = group_lookup_name(m, p.group_name, (uid_t*) &found_gid, &found_description);
else else
return varlink_error(link, "io.systemd.UserDatabase.EnumerationNotSupported", NULL); return varlink_error(link, "io.systemd.UserDatabase.EnumerationNotSupported", NULL);
if (r == -ESRCH) if (r == -ESRCH)
@ -328,7 +347,7 @@ static int vl_method_get_group_record(Varlink *link, JsonVariant *parameters, Va
if (!group_match_lookup_parameters(&p, gn, gid)) if (!group_match_lookup_parameters(&p, gn, gid))
return varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL); return varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL);
r = build_group_json(gn, gid, &v); r = build_group_json(gn, gid, found_description, &v);
if (r < 0) if (r < 0)
return r; return r;

View File

@ -68,6 +68,9 @@ void group_record_show(GroupRecord *gr, bool show_full_user_info) {
} }
} }
if (gr->description && !streq(gr->description, gr->group_name))
printf(" Description: %s\n", gr->description);
if (!strv_isempty(gr->hashed_password)) if (!strv_isempty(gr->hashed_password))
printf(" Passwords: %zu\n", strv_length(gr->hashed_password)); printf(" Passwords: %zu\n", strv_length(gr->hashed_password));

View File

@ -28,6 +28,7 @@ static GroupRecord *group_record_free(GroupRecord *g) {
free(g->group_name); free(g->group_name);
free(g->realm); free(g->realm);
free(g->group_name_and_realm_auto); free(g->group_name_and_realm_auto);
free(g->description);
strv_free(g->members); strv_free(g->members);
free(g->service); free(g->service);
@ -192,6 +193,7 @@ int group_record_load(
static const JsonDispatch group_dispatch_table[] = { static const JsonDispatch group_dispatch_table[] = {
{ "groupName", JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(GroupRecord, group_name), JSON_RELAX}, { "groupName", JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(GroupRecord, group_name), JSON_RELAX},
{ "realm", JSON_VARIANT_STRING, json_dispatch_realm, offsetof(GroupRecord, realm), 0 }, { "realm", JSON_VARIANT_STRING, json_dispatch_realm, offsetof(GroupRecord, realm), 0 },
{ "description", JSON_VARIANT_STRING, json_dispatch_gecos, offsetof(GroupRecord, description), 0 },
{ "disposition", JSON_VARIANT_STRING, json_dispatch_user_disposition, offsetof(GroupRecord, disposition), 0 }, { "disposition", JSON_VARIANT_STRING, json_dispatch_user_disposition, offsetof(GroupRecord, disposition), 0 },
{ "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(GroupRecord, service), JSON_SAFE }, { "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(GroupRecord, service), JSON_SAFE },
{ "lastChangeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(GroupRecord, last_change_usec), 0 }, { "lastChangeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(GroupRecord, last_change_usec), 0 },

View File

@ -13,6 +13,8 @@ typedef struct GroupRecord {
char *realm; char *realm;
char *group_name_and_realm_auto; char *group_name_and_realm_auto;
char *description;
UserDisposition disposition; UserDisposition disposition;
uint64_t last_change_usec; uint64_t last_change_usec;

View File

@ -203,7 +203,7 @@ int json_dispatch_realm(const char *name, JsonVariant *variant, JsonDispatchFlag
return 0; return 0;
} }
static int json_dispatch_gecos(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { int json_dispatch_gecos(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
char **s = userdata; char **s = userdata;
const char *n; const char *n;

View File

@ -388,6 +388,7 @@ int user_record_test_password_change_required(UserRecord *h);
/* The following six are user by group-record.c, that's why we export them here */ /* The following six are user by group-record.c, that's why we export them here */
int json_dispatch_realm(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_realm(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_gecos(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_user_group_list(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_user_group_list(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_user_disposition(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_user_disposition(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);

View File

@ -232,6 +232,7 @@ static int show_group(GroupRecord *gr, Table *table) {
TABLE_STRING, gr->group_name, TABLE_STRING, gr->group_name,
TABLE_STRING, user_disposition_to_string(group_record_disposition(gr)), TABLE_STRING, user_disposition_to_string(group_record_disposition(gr)),
TABLE_GID, gr->gid, TABLE_GID, gr->gid,
TABLE_STRING, gr->description,
TABLE_INT, (int) group_record_disposition(gr)); TABLE_INT, (int) group_record_disposition(gr));
if (r < 0) if (r < 0)
return table_log_add_error(r); return table_log_add_error(r);
@ -255,13 +256,14 @@ static int display_group(int argc, char *argv[], void *userdata) {
arg_output = argc > 1 ? OUTPUT_FRIENDLY : OUTPUT_TABLE; arg_output = argc > 1 ? OUTPUT_FRIENDLY : OUTPUT_TABLE;
if (arg_output == OUTPUT_TABLE) { if (arg_output == OUTPUT_TABLE) {
table = table_new("name", "disposition", "gid", "disposition-numeric"); table = table_new("name", "disposition", "gid", "description", "disposition-numeric");
if (!table) if (!table)
return log_oom(); return log_oom();
(void) table_set_align_percent(table, table_get_cell(table, 0, 2), 100); (void) table_set_align_percent(table, table_get_cell(table, 0, 2), 100);
(void) table_set_empty_string(table, "-");
(void) table_set_sort(table, (size_t) 3, (size_t) 2, (size_t) -1); (void) table_set_sort(table, (size_t) 3, (size_t) 2, (size_t) -1);
(void) table_set_display(table, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) -1); (void) table_set_display(table, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) -1);
} }
if (argc > 1) { if (argc > 1) {

View File

@ -12,7 +12,7 @@ Description=Enforce Volatile Root File Systems
Documentation=man:systemd-volatile-root.service(8) Documentation=man:systemd-volatile-root.service(8)
DefaultDependencies=no DefaultDependencies=no
Conflicts=shutdown.target Conflicts=shutdown.target
After=sysroot.mount After=sysroot.mount systemd-repart.service
Before=initrd-root-fs.target shutdown.target Before=initrd-root-fs.target shutdown.target
AssertPathExists=/etc/initrd-release AssertPathExists=/etc/initrd-release