hdb: Namespace referrals

Add a new method for issuing referrals for entire namespaces of hostnames.

An alias of the form WELLKNOWN/HOSTBASED-NAMESPACE/service/namespace-fqdn@REALM
will cause all requests for host-based principals in the given namespace to be
referred to the given realm.
This commit is contained in:
Nicolas Williams
2021-11-14 16:24:48 -06:00
committed by Luke Howard
parent be708ca3cf
commit d833ce4cbc
4 changed files with 203 additions and 38 deletions

View File

@@ -166,13 +166,20 @@ and
sub-commands rather than having to edit the KDC's configuration
file and having to restart the KDC.
.Pp
However, there is currently no way to alias namespaces via HDB
entry aliases.
To issue referrals for entire namespaces use the
There are two methods for issuing referrals for entire namespaces
of hostnames.
An alias of the form
.Ar WELLKNOWN/HOSTBASED-NAMESPACE/service/namespace-fqdn@REALM
(see
.Nm add_namespace
below) will cause all requests for host-based principals in the
given namespace to be referred to the given realm.
Alternatively, the KDC will issue referrals for all host-based
service principals whose hostname component matches a
.Ar [domain_realm]
section of the KDC's
entry in the KDC's
.Ar krb5.conf
file.
file referring to a different realm.
.Ed
.Pp
.Nm add_namespace
@@ -182,18 +189,23 @@ file.
.Op Fl Fl max-ticket-life= Ns Ar lifetime
.Op Fl Fl max-renewable-life= Ns Ar lifetime
.Op Fl Fl attributes= Ns Ar attributes
.Ar principal...
.Ar host-based-principal...
.Bd -ragged -offset indent
Adds a new namespace of virtual host-based or domain-based
principals to the database, whose keys will be automatically
derived from base keys stored in the namespace record, and which
keys will be rotated automatically.
The namespace names should look like
.Ar hostname@REALM
The namespace names are of the same form as host-based principal
names:
.Ar service/hostname@REALM
and these will match all host-based or domain-based service names
where hostname component of such a principal ends in the labels
of the hostname in the namespace name.
.Pp
The service name component may be a wild-card (underscore,
.Ar _ ),
in which case it will match any service.
.Pp
For example,
.Ar bar.baz.example@BAZ.EXAMPLE
will match
@@ -223,6 +235,11 @@ The default enctypes is as for the
.Nm add
command.
.Pp
Note that namespaces are stored as principals whose names are of the form
.Ar WELLKNOWN/HOSTBASED-NAMESPACE/service/namespace.fqdn@REALM ,
with the
.Ar service
.Pp
This command has the following alias:
.Nm add_ns .
.Ed

View File

@@ -982,11 +982,6 @@ derive_keys(krb5_context context,
if (!h_is_namespace && !h->entry.flags.virtual_keys)
return 0;
h->entry.flags.virtual = 1;
if (h_is_namespace) {
/* Set the entry's principal name */
free_Principal(h->entry.principal);
ret = copy_Principal(princ, h->entry.principal);
}
kr.len = 0;
kr.val = 0;
@@ -1019,7 +1014,7 @@ derive_keys(krb5_context context,
}
/* The principal name will be used in key derivation and error messages */
if (ret == 0 && h_is_namespace)
if (ret == 0)
ret = krb5_unparse_name(context, princ, &p);
/* Sanity check key rotations, determine current & last kr */
@@ -1153,6 +1148,10 @@ derive_keys(krb5_context context,
}
/*
* Pick a best kvno for the given principal at the given time.
*
* Implements the [hdb] new_service_key_delay configuration parameter.
*
* In order for disparate keytab provisioning systems such as OSKT and our own
* kadmin ext_keytab and httpkadmind's get-keys to coexist, we need to be able
* to force keys set by the former to not become current keys until users of
@@ -1163,9 +1162,9 @@ derive_keys(krb5_context context,
* The context is that OSKT's krb5_keytab is very happy to change keys in a way
* that requires all members of a cluster to rekey together. If one also
* wishes to have cluster members that opt out of this and just fetch current,
* past, and future keys periodically, then the keys set by OSKT need to not
* come into effect until all the opt-out members have had a chance to fetch
* the new keys.
* past, and future keys periodically, then the keys set by OSKT must not come
* into effect until all the opt-out members have had a chance to fetch the new
* keys.
*
* The assumption is that services will fetch new keys periodically, say, every
* four hours. Then one can set `[hdb] new_service_key_delay = 8h' in the
@@ -1175,12 +1174,12 @@ derive_keys(krb5_context context,
* Naturally, this applies only to concrete principals with concrete keys.
*/
static krb5_error_code
fix_keys(krb5_context context,
HDB *db,
unsigned flags,
krb5_timestamp now,
krb5uint32 kvno,
hdb_entry_ex *h)
pick_kvno(krb5_context context,
HDB *db,
unsigned flags,
krb5_timestamp now,
krb5uint32 kvno,
hdb_entry_ex *h)
{
HDB_extension *ext;
HDB_Ext_KeySet keys;
@@ -1296,7 +1295,7 @@ make_namespace_princ(krb5_context context,
/* First go around, need a namespace princ. Make it! */
ret = krb5_build_principal(context, namespace, strlen(realm),
realm, "WELLKNOWN",
realm, KRB5_WELLKNOWN_NAME,
HDB_WK_NAMESPACE, comp0, NULL);
if (ret == 0)
ret = krb5_principal_set_comp_string(context, *namespace, 3, comp1);
@@ -1307,6 +1306,138 @@ make_namespace_princ(krb5_context context,
return ret;
}
static int
is_namespace_princ_p(krb5_context context,
krb5_const_principal princ)
{
return
krb5_principal_get_num_comp(context, princ) >= 4
&& strcmp(krb5_principal_get_comp_string(context, princ, 0),
KRB5_WELLKNOWN_NAME) == 0
&& strcmp(krb5_principal_get_comp_string(context, princ, 1),
HDB_WK_NAMESPACE) == 0;
}
/* See call site */
static krb5_error_code
rewrite_hostname(krb5_context context,
krb5_const_principal wanted_princ,
krb5_const_principal ns_princ,
krb5_const_principal found_ns_princ,
char **s)
{
const char *ns_host_part, *wanted_host_part, *found_host_part;
const char *p, *r;
size_t ns_host_part_len, wanted_host_part_len;
wanted_host_part = krb5_principal_get_comp_string(context, wanted_princ, 1);
wanted_host_part_len = strlen(wanted_host_part);
if (wanted_host_part_len > 256) {
krb5_set_error_message(context, HDB_ERR_NOENTRY,
"Aliases of host-based principals longer than "
"256 bytes not supported");
return HDB_ERR_NOENTRY;
}
ns_host_part = krb5_principal_get_comp_string(context, ns_princ, 3);
ns_host_part_len = strlen(ns_host_part);
/* Find `ns_host_part' as the tail of `wanted_host_part' */
for (r = p = strstr(wanted_host_part, ns_host_part);
r && strnlen(r, ns_host_part_len + 1) > ns_host_part_len;
p = (r = strstr(r, ns_host_part)) ? r : p)
;
if (!p || strnlen(p, ns_host_part_len + 1) != ns_host_part_len)
return HDB_ERR_NOENTRY; /* Can't happen */
if (p == wanted_host_part || p[-1] != '.')
return HDB_ERR_NOENTRY;
found_host_part =
krb5_principal_get_comp_string(context, found_ns_princ, 3);
return
asprintf(s, "%.*s%s", (int)(p - wanted_host_part), wanted_host_part,
found_host_part) < 0 ||
*s == NULL ? krb5_enomem(context) : 0;
}
/*
* Fix `h->entry.principal' to match the desired `princ' in the namespace
* `nsprinc' (which is either the same as `h->entry.principal' or an alias
* of it).
*/
static krb5_error_code
fix_princ_name(krb5_context context,
krb5_const_principal princ,
krb5_const_principal nsprinc,
hdb_entry_ex *h)
{
krb5_error_code ret = 0;
char *s = NULL;
if (!nsprinc)
return 0;
if (krb5_principal_get_num_comp(context, princ) < 2)
return HDB_ERR_NOENTRY;
/* `nsprinc' must be a namespace principal */
if (krb5_principal_compare(context, nsprinc, h->entry.principal)) {
/*
* `h' is the HDB entry for `nsprinc', and `nsprinc' is its canonical
* name.
*
* Set the entry's principal name to the desired name. The keys will
* be fixed next (upstairs, but don't forget to!).
*/
free_Principal(h->entry.principal);
return copy_Principal(princ, h->entry.principal);
}
if (!is_namespace_princ_p(context, h->entry.principal)) {
/*
* The alias is a namespace, but the canonical name is not. WAT.
*
* Well, the KDC will just issue a referral anyways, so we can leave
* `h->entry.principal' as is...
*
* Remove all of `h->entry's keys just in case, and leave
* `h->entry.principal' as-is.
*/
free_Keys(&h->entry.keys);
(void) hdb_entry_clear_password(context, &h->entry);
return hdb_clear_extension(context, &h->entry,
choice_HDB_extension_data_hist_keys);
}
/*
* A namespace alias of a namespace entry.
*
* We'll want to rewrite the original principal accordingly.
*
* E.g., if the caller wanted host/foo.ns.test.h5l.se and we
* found WELLKNOWN/HOSTBASED-NAMESPACE/ns.test.h5l.se is an
* alias of WELLKNOWN/HOSTBASED-NAMESPACE/ns.example.org, then
* we'll want to treat host/foo.ns.test.h5l.se as an alias of
* host/foo.ns.example.org.
*/
if (krb5_principal_get_num_comp(context, h->entry.principal) !=
2 + krb5_principal_get_num_comp(context, princ))
ret = HDB_ERR_NOENTRY; /* Only host-based services for now */
if (ret == 0)
ret = rewrite_hostname(context, princ, nsprinc, h->entry.principal, &s);
if (ret == 0) {
krb5_free_principal(context, h->entry.principal);
h->entry.principal = NULL;
ret = krb5_make_principal(context, &h->entry.principal,
krb5_principal_get_realm(context, princ),
krb5_principal_get_comp_string(context,
princ, 0),
s,
NULL);
}
return ret;
}
/* Wrapper around db->hdb_fetch_kvno() that implements virtual princs/keys */
static krb5_error_code
fetch_it(krb5_context context,
@@ -1319,7 +1450,7 @@ fetch_it(krb5_context context,
hdb_entry_ex *ent)
{
krb5_const_principal tmpprinc = princ;
krb5_principal baseprinc = NULL;
krb5_principal nsprinc = NULL;
krb5_error_code ret = 0;
const char *comp0 = krb5_principal_get_comp_string(context, princ, 0);
const char *comp1 = krb5_principal_get_comp_string(context, princ, 1);
@@ -1331,7 +1462,7 @@ fetch_it(krb5_context context,
int do_search = 0;
if (db->enable_virtual_hostbased_princs && comp1 &&
strcmp("krbtgt", comp0) != 0 && strcmp("WELLKNOWN", comp0) != 0) {
strcmp("krbtgt", comp0) != 0 && strcmp(KRB5_WELLKNOWN_NAME, comp0) != 0) {
char *htmp;
if ((host = strdup(comp1)) == NULL)
@@ -1358,7 +1489,7 @@ fetch_it(krb5_context context,
}
tmp = host ? host : comp1;
for (ret = HDB_ERR_NOENTRY; ret == HDB_ERR_NOENTRY; tmpprinc = baseprinc) {
for (ret = HDB_ERR_NOENTRY; ret == HDB_ERR_NOENTRY; tmpprinc = nsprinc) {
krb5_error_code ret2 = 0;
/*
@@ -1376,7 +1507,7 @@ fetch_it(krb5_context context,
ret = db->hdb_fetch_kvno(context, db, tmpprinc, flags, kvno, ent);
if (ret != HDB_ERR_NOENTRY || hdots == 0 || hdots < mindots || !tmp ||
!do_search)
break;
break;
/*
* Breadcrumb:
@@ -1403,12 +1534,13 @@ fetch_it(krb5_context context,
hdots--;
}
if (baseprinc == NULL)
if (nsprinc == NULL)
/* First go around, need a namespace princ. Make it! */
ret2 = make_namespace_princ(context, db, tmpprinc, &baseprinc);
/* Update the hostname component */
ret2 = make_namespace_princ(context, db, tmpprinc, &nsprinc);
/* Update the hostname component of the namespace principal */
if (ret2 == 0)
ret2 = krb5_principal_set_comp_string(context, baseprinc, 3, tmp);
ret2 = krb5_principal_set_comp_string(context, nsprinc, 3, tmp);
if (ret2)
ret = ret2;
@@ -1425,14 +1557,20 @@ fetch_it(krb5_context context,
* key derivation to do, but that's decided in derive_keys().
*/
if (ret == 0) {
ret = derive_keys(context, flags, princ, !!baseprinc, t, etype, kvno,
ent);
/* Fix the principal name if namespaced */
ret = fix_princ_name(context, princ, nsprinc, ent);
/* Derive keys if namespaced or virtual */
if (ret == 0)
ret = fix_keys(context, db, flags, t, kvno, ent);
if (ret)
hdb_free_entry(context, ent);
ret = derive_keys(context, flags, princ, !!nsprinc, t, etype, kvno,
ent);
/* Pick the best kvno for this principal at the given time */
if (ret == 0)
ret = pick_kvno(context, db, flags, t, kvno, ent);
}
krb5_free_principal(context, baseprinc);
if (ret)
hdb_free_entry(context, ent);
krb5_free_principal(context, nsprinc);
free(host);
return ret;
}

View File

@@ -78,6 +78,8 @@ server=host/datan.test.h5l.se
server2=host/computer.example.com
server3=host/refer-me-out.test.h5l.se
server4=host/no-auth-data-reqd.test.h5l.se
server5=host/a-host.refer-all-out.test.h5l.se
namespace=WELLKNOWN/HOSTBASED-NAMESPACE/_/refer-all-out.test.h5l.se
serverip=host/10.11.12.13
serveripname=host/ip.test.h5l.org
serveripname2=host/10.11.12.14
@@ -240,6 +242,9 @@ ${kadmin} add -p foo --use-defaults referral-placeholder@${R5} || exit 1
${kadmin} add_alias referral-placeholder@${R5} ${server3}@${R} || exit 1
${kadmin5} add -p kaka --use-defaults ${server3}@${R5} || exit 1
${kadmin5} ext -k ${keytab} ${server3}@${R5} || exit 1
${kadmin} add_alias referral-placeholder@${R5} ${namespace}@${R} || exit 1
${kadmin5} add -p kaka --use-defaults ${server5}@${R5} || exit 1
${kadmin5} ext -k ${keytab} ${server5}@${R5} || exit 1
${kadmin} add -p kaka --use-defaults ${serverip}@${R} || exit 1
${kadmin} ext -k ${keytab} ${serverip}@${R} || exit 1
${kadmin} add -p kaka --use-defaults ${serveripname}@${R} || exit 1
@@ -444,6 +449,8 @@ echo "Getting x-realm tickets with capaths for $R -> $R5"
${kgetcred} foo@${R5} || { ec=1 ; eval "${testfailed}"; }
echo "Testing HDB referral entry"
${kgetcred} --canonicalize ${server3}@${R} || { ec=1 ; eval "${testfailed}"; }
echo "Testing HDB namespace referral entry"
${kgetcred} --canonicalize ${server5}@${R} || { ec=1 ; eval "${testfailed}"; }
${klist}
${kdestroy}

View File

@@ -126,6 +126,9 @@
[hdb]
db-dir = @objdir@
enable_virtual_hostbased_princs = true
virtual_hostbased_princ_mindots = 1
virtual_hostbased_princ_maxdots = 3
[logging]
kdc = 0-/FILE:@objdir@/@messages@.log