hdb: Derive pw_end for virtual services

We derive keysets for virtual host-based service principals, and that
includes the `set_time` field of keys.  But applications using the kadm5
API lose that information.  Our httpkadmind wants to set a Cache-Control
header with an appropriate max-age so that clients know when to re-fetch
keytabs.

We could extract some of the lib/hdb/common.c functions so that
httpkadmind could re-create an HDB_entry from a kadm5 entry then compute
the desired time, but ultimately we already have an appropriate field in
the HDB_entry and kadm5_principal_ent_rec types: "password expiration".

So let's set the `pw_end` of a virtual host-based service's HDB entry to
the time when a client should next fetch the principal's keys, and we'll
use that in httpkadmind as the `pw_expiration` field of the kadm5 entry
type.
This commit is contained in:
Nicolas Williams
2022-04-25 17:21:34 -05:00
parent a51708c356
commit cd2e423d10

View File

@@ -856,6 +856,10 @@ derive_keys_for_kr(krb5_context context,
* (t - krp->epoch < 0) is better than (krp->epoch < t), making us more
* tolerant of signed 32-bit time_t here near 2038. Of course, we have
* signed 32-bit time_t dragons elsewhere.
*
* We don't need to check for n == 0 && rotation_period_offset < 0 because
* only derive_keys_for_current_kr() calls us with non-zero rotation period
* offsets, and it will never call us in that case.
*/
if (t - krp->epoch < 0)
return 0; /* This KR is not relevant yet */
@@ -864,6 +868,37 @@ derive_keys_for_kr(krb5_context context,
set_time = krp->epoch + krp->period * n;
kvno = krp->base_kvno + n;
/*
* Since this principal is virtual, or has virtual keys, we're going to
* derive a "password expiration time" for it in order to help httpkadmind
* and other tools figure out when to request keys again.
*
* The kadm5 representation of principals does not include the set_time of
* keys/keysets, so we can't have httpkadmind derive a Cache-Control from
* that without adding yet another "TL data". Since adding TL data is a
* huge pain, we'll just use the `pw_end' field of `HDB_entry' to
* communicate when this principal's keys will change next.
*/
if (h->pw_end[0] == 0) {
KerberosTime used = (t - krp->epoch) % krp->period;
KerberosTime left = krp->period - used;
/*
* If `h->pw_end[0]' == 0 then this must be the current period of the
* current KR we're deriving keys for. See upstairs.
*
* If there's more than a quarter of this time period left, then we'll
* set `h->pw_end[0]' to one quarter before the end of this time
* period. Else we'll set it to 1/4 after (we'll be including the next
* set of derived keys, so there's no harm in waiting that long to
* refetch).
*/
if (left > krp->period >> 2)
h->pw_end[0] = set_time + krp->period - (krp->period >> 2);
else
h->pw_end[0] = set_time + krp->period + (krp->period >> 2);
}
/*
* Do not waste cycles computing keys not wanted or needed.
@@ -1198,6 +1233,11 @@ derive_keys(krb5_context context,
"because last key rotation period "
"marks deletion", p);
/* See `derive_keys_for_kr()' */
if (h->pw_end == NULL &&
(h->pw_end = calloc(1, sizeof(h->pw_end[0]))) == NULL)
ret = krb5_enomem(context);
/*
* Derive and set in `h' its current kvno and current keys.
*
@@ -1246,6 +1286,12 @@ derive_keys(krb5_context context,
if (ret == 0 && *h->max_life > kr.val[current_kr].period >> 1)
*h->max_life = kr.val[current_kr].period >> 1;
if (ret == 0 && h->pw_end[0] == 0)
/* Shouldn't happen */
h->pw_end[0] = kr.val[current_kr].epoch +
kr.val[current_kr].period *
(1 + (t - kr.val[current_kr].epoch) / kr.val[current_kr].period);
free_HDB_Ext_KeyRotation(&kr);
free_HDB_Ext_KeySet(&base_keys);
free(p);
@@ -1730,11 +1776,22 @@ hdb_fetch_kvno(krb5_context context,
hdb_entry *h)
{
krb5_error_code ret;
krb5_timestamp now;
krb5_timeofday(context, &now);
flags |= kvno ? HDB_F_KVNO_SPECIFIED : 0; /* XXX is this needed */
if (t == 0)
krb5_timeofday(context, &t);
ret = fetch_it(context, db, principal, flags, t, etype, kvno, h);
ret = fetch_it(context, db, principal, flags, t ? t : now, etype, kvno, h);
if (ret == 0 && t == 0 && h->flags.virtual &&
h->pw_end && h->pw_end[0] < now) {
/*
* This shouldn't happen!
*
* Do not allow h->pw_end[0] to be in the past for virtual principals
* outside testing. This is just to prevent the AS/TGS from failing.
*/
h->pw_end[0] = now + 3600;
}
if (ret == HDB_ERR_NOENTRY)
krb5_set_error_message(context, ret, "no such entry found in hdb");
return ret;