user-record-nss: preserve valid nss lookup aliases in records
NSS lookups may resolve an alias-like name to a canonical user or group name. For example, SSSD can resolve a userPrincipalName such as "testuser@example.test" while returning a passwd record whose pw_name is the canonical login name "testuser". The NSS userdb worker currently converts such records using only the canonical name. As a result, the originally requested lookup name is lost, and lookups through the varlink/multiplexer path may fail with ConflictingRecordFound even though direct NSS lookup succeeds. Preserve the originally requested lookup name as an alias when it differs from the canonical passwd/group name and passes normal alias validation. Add the alias both to the parsed record fields and to the JSON representation so serialized records remain self-consistent. Extend group record loading and matching to understand aliases too. When both a name and UID/GID are specified in a userdb lookup, prefer the by-name path so alias-aware records can satisfy the subsequent consistency checks. This allows canonicalized NSS results to match the original lookup name correctly and fixes session setup for users logging in with NSS-resolvable aliases such as UPNs.
This commit is contained in:
@@ -34,6 +34,7 @@ static GroupRecord *group_record_free(GroupRecord *g) {
|
||||
free(g->group_name);
|
||||
free(g->realm);
|
||||
free(g->group_name_and_realm_auto);
|
||||
strv_free(g->aliases);
|
||||
free(g->description);
|
||||
|
||||
strv_free(g->members);
|
||||
@@ -92,6 +93,7 @@ static int dispatch_per_machine(const char *name, sd_json_variant *variant, sd_j
|
||||
{ "matchMachineId", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 },
|
||||
{ "matchHostname", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 },
|
||||
{ "gid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(GroupRecord, gid), 0 },
|
||||
{ "aliases", SD_JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(GroupRecord, aliases), SD_JSON_RELAX },
|
||||
{ "members", SD_JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(GroupRecord, members), SD_JSON_RELAX },
|
||||
{ "administrators", SD_JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(GroupRecord, administrators), SD_JSON_RELAX },
|
||||
{},
|
||||
@@ -176,6 +178,7 @@ int group_record_load(
|
||||
|
||||
static const sd_json_dispatch_field group_dispatch_table[] = {
|
||||
{ "groupName", SD_JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(GroupRecord, group_name), SD_JSON_RELAX },
|
||||
{ "aliases", SD_JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(GroupRecord, aliases), SD_JSON_RELAX },
|
||||
{ "realm", SD_JSON_VARIANT_STRING, json_dispatch_realm, offsetof(GroupRecord, realm), 0 },
|
||||
{ "uuid", SD_JSON_VARIANT_STRING, sd_json_dispatch_id128, offsetof(GroupRecord, uuid), 0 },
|
||||
{ "description", SD_JSON_VARIANT_STRING, json_dispatch_gecos, offsetof(GroupRecord, description), 0 },
|
||||
@@ -345,6 +348,12 @@ bool group_record_matches_group_name(const GroupRecord *g, const char *group_nam
|
||||
if (streq_ptr(g->group_name_and_realm_auto, group_name))
|
||||
return true;
|
||||
|
||||
if (strv_contains(g->aliases, group_name))
|
||||
return true;
|
||||
|
||||
if (record_name_matches_alias_realm(group_name, (char * const*) g->aliases, g->realm))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -370,7 +379,8 @@ bool group_record_match(GroupRecord *h, const UserDBMatch *match) {
|
||||
h->description,
|
||||
};
|
||||
|
||||
if (!user_name_fuzzy_match(names, ELEMENTSOF(names), match->fuzzy_names))
|
||||
if (!user_name_fuzzy_match(names, ELEMENTSOF(names), match->fuzzy_names) &&
|
||||
!user_name_fuzzy_match((const char**) h->aliases, strv_length(h->aliases), match->fuzzy_names))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ typedef struct GroupRecord {
|
||||
char *group_name;
|
||||
char *realm;
|
||||
char *group_name_and_realm_auto;
|
||||
char **aliases;
|
||||
sd_id128_t uuid;
|
||||
|
||||
char *description;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "errno-util.h"
|
||||
#include "format-util.h"
|
||||
#include "group-record.h"
|
||||
#include "json-util.h"
|
||||
#include "libcrypt-util.h"
|
||||
#include "log.h"
|
||||
#include "string-util.h"
|
||||
@@ -45,9 +46,23 @@ static int strv_extend_strv_utf8_only(char ***dst, char **src, bool filter_dupli
|
||||
return strv_extend_strv(dst, t, filter_duplicates);
|
||||
}
|
||||
|
||||
static int nss_add_valid_alias(char ***aliases, const char *name, const char *alias_name) {
|
||||
assert(aliases);
|
||||
assert(name);
|
||||
|
||||
if (isempty(alias_name) || streq_ptr(alias_name, name))
|
||||
return 0;
|
||||
|
||||
if (!valid_user_group_name(alias_name, VALID_USER_RELAX))
|
||||
return 0;
|
||||
|
||||
return strv_extend(aliases, alias_name);
|
||||
}
|
||||
|
||||
int nss_passwd_to_user_record(
|
||||
const struct passwd *pwd,
|
||||
const struct spwd *spwd,
|
||||
const char *alias_name,
|
||||
UserRecord **ret) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
||||
@@ -69,6 +84,10 @@ int nss_passwd_to_user_record(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = nss_add_valid_alias(&hr->aliases, hr->user_name, alias_name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Some bad NSS modules synthesize GECOS fields with embedded ":" or "\n" characters, which are not
|
||||
* something we can output in /etc/passwd compatible format, since these are record separators
|
||||
* there. We normally refuse that, but we need to maintain compatibility with arbitrary NSS modules,
|
||||
@@ -150,6 +169,7 @@ int nss_passwd_to_user_record(
|
||||
r = sd_json_buildo(
|
||||
&hr->json,
|
||||
SD_JSON_BUILD_PAIR_STRING("userName", hr->user_name),
|
||||
JSON_BUILD_PAIR_STRV_NON_EMPTY("aliases", hr->aliases),
|
||||
SD_JSON_BUILD_PAIR_UNSIGNED("uid", hr->uid),
|
||||
SD_JSON_BUILD_PAIR_UNSIGNED("gid", user_record_gid(hr)),
|
||||
SD_JSON_BUILD_PAIR_CONDITION(!!hr->real_name, "realName", SD_JSON_BUILD_STRING(hr->real_name)),
|
||||
@@ -240,7 +260,7 @@ int nss_user_record_by_name(
|
||||
} else
|
||||
incomplete = true;
|
||||
|
||||
r = nss_passwd_to_user_record(result, sresult, ret);
|
||||
r = nss_passwd_to_user_record(result, sresult, name, ret);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -274,7 +294,7 @@ int nss_user_record_by_uid(
|
||||
} else
|
||||
incomplete = true;
|
||||
|
||||
r = nss_passwd_to_user_record(result, sresult, ret);
|
||||
r = nss_passwd_to_user_record(result, sresult, /* alias_name= */ NULL, ret);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -286,6 +306,7 @@ int nss_user_record_by_uid(
|
||||
int nss_group_to_group_record(
|
||||
const struct group *grp,
|
||||
const struct sgrp *sgrp,
|
||||
const char *alias_name,
|
||||
GroupRecord **ret) {
|
||||
|
||||
_cleanup_(group_record_unrefp) GroupRecord *g = NULL;
|
||||
@@ -307,6 +328,10 @@ int nss_group_to_group_record(
|
||||
if (!g->group_name)
|
||||
return -ENOMEM;
|
||||
|
||||
r = nss_add_valid_alias(&g->aliases, g->group_name, alias_name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = strv_extend_strv_utf8_only(&g->members, grp->gr_mem, false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -332,6 +357,7 @@ int nss_group_to_group_record(
|
||||
r = sd_json_buildo(
|
||||
&g->json,
|
||||
SD_JSON_BUILD_PAIR_STRING("groupName", g->group_name),
|
||||
JSON_BUILD_PAIR_STRV_NON_EMPTY("aliases", g->aliases),
|
||||
SD_JSON_BUILD_PAIR_UNSIGNED("gid", g->gid),
|
||||
SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(g->members), "members", SD_JSON_BUILD_STRV(g->members)),
|
||||
SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(g->hashed_password), "privileged", SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_STRV("hashedPassword", g->hashed_password))),
|
||||
@@ -412,7 +438,7 @@ int nss_group_record_by_name(
|
||||
} else
|
||||
incomplete = true;
|
||||
|
||||
r = nss_group_to_group_record(result, sresult, ret);
|
||||
r = nss_group_to_group_record(result, sresult, name, ret);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -446,7 +472,7 @@ int nss_group_record_by_gid(
|
||||
} else
|
||||
incomplete = true;
|
||||
|
||||
r = nss_group_to_group_record(result, sresult, ret);
|
||||
r = nss_group_to_group_record(result, sresult, /* alias_name= */ NULL, ret);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
|
||||
/* Synthesize UserRecord and GroupRecord objects from NSS data */
|
||||
|
||||
int nss_passwd_to_user_record(const struct passwd *pwd, const struct spwd *spwd, UserRecord **ret);
|
||||
int nss_passwd_to_user_record(const struct passwd *pwd, const struct spwd *spwd, const char *alias_name, UserRecord **ret);
|
||||
int nss_spwd_for_passwd(const struct passwd *pwd, struct spwd *ret_spwd, char **ret_buffer);
|
||||
|
||||
int nss_user_record_by_name(const char *name, bool with_shadow, UserRecord **ret);
|
||||
int nss_user_record_by_uid(uid_t uid, bool with_shadow, UserRecord **ret);
|
||||
|
||||
int nss_group_to_group_record(const struct group *grp, const struct sgrp *sgrp, GroupRecord **ret);
|
||||
int nss_group_to_group_record(const struct group *grp, const struct sgrp *sgrp, const char *alias_name, GroupRecord **ret);
|
||||
int nss_sgrp_for_group(const struct group *grp, struct sgrp *ret_sgrp, char **ret_buffer);
|
||||
|
||||
int nss_group_record_by_name(const char *name, bool with_shadow, GroupRecord **ret);
|
||||
|
||||
@@ -2775,11 +2775,24 @@ bool user_record_matches_user_name(const UserRecord *u, const char *user_name) {
|
||||
if (strv_contains(u->aliases, user_name))
|
||||
return true;
|
||||
|
||||
const char *realm = strrchr(user_name, '@');
|
||||
if (realm && streq_ptr(realm+1, u->realm))
|
||||
STRV_FOREACH(a, u->aliases)
|
||||
if (startswith(user_name, *a) == realm)
|
||||
return true;
|
||||
if (record_name_matches_alias_realm(user_name, (char * const*) u->aliases, u->realm))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool record_name_matches_alias_realm(const char *name, char * const *aliases, const char *realm) {
|
||||
const char *suffix;
|
||||
|
||||
assert(name);
|
||||
|
||||
suffix = strrchr(name, '@');
|
||||
if (!suffix || !streq_ptr(suffix + 1, realm))
|
||||
return false;
|
||||
|
||||
STRV_FOREACH(a, aliases)
|
||||
if (startswith(name, *a) == suffix)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -546,6 +546,7 @@ bool userdb_match_is_set(const UserDBMatch *match) _pure_;
|
||||
|
||||
void userdb_match_done(UserDBMatch *match);
|
||||
|
||||
bool record_name_matches_alias_realm(const char *name, char * const *aliases, const char *realm);
|
||||
bool user_name_fuzzy_match(const char *names[], size_t n_names, char **matches);
|
||||
bool user_record_match(UserRecord *u, const UserDBMatch *match);
|
||||
|
||||
|
||||
+2
-2
@@ -1124,7 +1124,7 @@ static int userdb_iterator_get_one(UserDBIterator *iterator, UserRecord **ret) {
|
||||
incomplete = true;
|
||||
}
|
||||
|
||||
r = nss_passwd_to_user_record(pw, r >= 0 ? &spwd : NULL, ret);
|
||||
r = nss_passwd_to_user_record(pw, r >= 0 ? &spwd : NULL, /* alias_name= */ NULL, ret);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -1570,7 +1570,7 @@ static int groupdb_iterator_get_one(UserDBIterator *iterator, GroupRecord **ret)
|
||||
incomplete = true;
|
||||
}
|
||||
|
||||
r = nss_group_to_group_record(gr, r >= 0 ? &sgrp : NULL, ret);
|
||||
r = nss_group_to_group_record(gr, r >= 0 ? &sgrp : NULL, /* alias_name= */ NULL, ret);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user