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:

committed by
Luke Howard

parent
be708ca3cf
commit
d833ce4cbc
@@ -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
|
||||
|
198
lib/hdb/common.c
198
lib/hdb/common.c
@@ -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;
|
||||
}
|
||||
|
@@ -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}
|
||||
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user