gsskrb5: Check dst-TGT pokicy at store time

Our initiator supports configuration-driven delegation of destination
TGTs.

This commit adds acceptor-side handling of destination TGT policy to
reject storing of non-destination TGTs when destination TGTs are
desired.

Currently we use the same appdefault for this.

Background:

    A root TGT is one of the form krbtgt/REALM@SAME-REALM.

    A destination TGT is a root TGT for the same realm as the acceptor
    service's realm.

    Normally clients delegate a root TGT for the client's realm.

    In some deployments clients may want to delegate destination TGTs as
    a form of constrained delegation: so that the destination service
    cannot use the delegated credential to impersonate the client
    principal to services in its home realm (due to KDC lineage/transit
    checks).  In those deployments there may not even be a route back to
    the KDCs of the client's realm, and attempting to use a
    non-destination TGT might even lead to timeouts.
This commit is contained in:
Nicolas Williams
2020-06-21 16:34:11 -05:00
parent 73e54c4731
commit a684e001ba
9 changed files with 104 additions and 7 deletions

View File

@@ -149,12 +149,10 @@ _gsskrb5i_is_cfx(krb5_context context, gsskrb5_ctx ctx, int acceptor)
static OM_uint32
gsskrb5_accept_delegated_token
(OM_uint32 * minor_status,
gsskrb5_accept_delegated_token(OM_uint32 *minor_status,
gsskrb5_ctx ctx,
krb5_context context,
gss_cred_id_t * delegated_cred_handle
)
gss_cred_id_t *delegated_cred_handle)
{
krb5_ccache ccache = NULL;
krb5_error_code kret;
@@ -212,6 +210,40 @@ gsskrb5_accept_delegated_token
handle = (gsskrb5_cred) *delegated_cred_handle;
handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE;
/*
* A root TGT is one of the form krbtgt/REALM@SAME-REALM.
*
* A destination TGT is a root TGT for the same realm as the acceptor
* service's realm.
*
* Normally clients delegate a root TGT for the client's realm.
*
* In some deployments clients may want to delegate destination TGTs as
* a form of constrained delegation: so that the destination service
* cannot use the delegated credential to impersonate the client
* principal to services in its home realm (due to KDC lineage/transit
* checks). In those deployments there may not even be a route back to
* the KDCs of the client's realm, and attempting to use a
* non-destination TGT might even lead to timeouts.
*
* We could simply pretend not to have obtained a credential, except
* that a) we don't (yet) have an app name here for the appdefault we
* need to check, b) the application really wants to be able to log a
* message about the delegated credential being no good.
*
* Thus we leave it to _gsskrb5_store_cred_into2() to decide what to do
* with non-destination TGTs. To do that, it needs the realm of the
* acceptor service, which we record here.
*/
handle->destination_realm =
strdup(krb5_principal_get_realm(context, ctx->target));
if (handle->destination_realm == NULL) {
_gsskrb5_release_cred(minor_status, delegated_cred_handle);
*minor_status = krb5_enomem(context);
ret = GSS_S_FAILURE;
goto out;
}
}
out:

View File

@@ -603,6 +603,7 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred_from
return GSS_S_FAILURE;
}
handle->destination_realm = NULL;
HEIMDAL_MUTEX_init(&handle->cred_id_mutex);
if (desired_name != GSS_C_NO_NAME) {

View File

@@ -138,6 +138,7 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_add_cred_from (
handle->usage = cred_usage;
handle->endtime = cred->endtime;
handle->principal = NULL;
handle->destination_realm = NULL;
handle->keytab = NULL;
handle->ccache = NULL;
handle->mechanisms = NULL;

View File

@@ -96,6 +96,7 @@ _gsskrb5_krb5_import_cred(OM_uint32 *minor_status,
HEIMDAL_MUTEX_init(&handle->cred_id_mutex);
handle->usage = 0;
handle->destination_realm = NULL;
if (*id) {
time_t now;

View File

@@ -268,6 +268,7 @@ _gsskrb5_import_cred(OM_uint32 * minor_status,
}
handle->usage = GSS_C_INITIATE;
handle->destination_realm = NULL;
krb5_cc_get_principal(context, id, &handle->principal);
handle->ccache = id;
handle->cred_flags = flags;

View File

@@ -66,7 +66,7 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_duplicate_cred (
dup = calloc(1, sizeof(*dup));
if (dup == NULL) {
*minor_status = ENOMEM;
*minor_status = krb5_enomem(context);
return (GSS_S_FAILURE);
}
@@ -74,6 +74,14 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_duplicate_cred (
cred = (gsskrb5_cred)input_cred_handle;
HEIMDAL_MUTEX_lock(&cred->cred_id_mutex);
dup->destination_realm = NULL;
if (cred->destination_realm &&
(dup->destination_realm = strdup(cred->destination_realm)) == NULL) {
*minor_status = krb5_enomem(context);
free(dup);
return (GSS_S_FAILURE);
}
dup->usage = cred->usage;
dup->endtime = cred->endtime;
dup->principal = NULL;

View File

@@ -92,6 +92,7 @@ typedef struct gsskrb5_ctx {
typedef struct {
krb5_principal principal;
char *destination_realm; /* Realm of acceptor service, if delegated */
int cred_flags;
#define GSS_CF_DESTROY_CRED_ON_RELEASE 1
#define GSS_CF_NO_CI_FLAGS 2

View File

@@ -54,6 +54,7 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_release_cred
HEIMDAL_MUTEX_lock(&cred->cred_id_mutex);
free(cred->destination_realm);
if (cred->principal != NULL)
krb5_free_principal(context, cred->principal);
if (cred->keytab != NULL)

View File

@@ -119,6 +119,49 @@ principal_is_best_for_user(krb5_context context,
return ret;
}
static krb5_error_code
check_destination_tgt_policy(krb5_context context,
const char *appname,
gsskrb5_cred input_cred)
{
krb5_error_code ret;
krb5_boolean want_dst_tgt = 0;
krb5_data v;
if (input_cred->destination_realm == NULL)
/*
* Not a delegated credential, so we can't check the destination TGT
* policy for the realm of the service -- we don't know the realm of
* the service.
*/
return 0;
krb5_appdefault_boolean(context, appname, input_cred->destination_realm,
"require_delegate_destination_tgt", FALSE,
&want_dst_tgt);
if (!want_dst_tgt)
return 0;
krb5_data_zero(&v);
ret = krb5_cc_get_config(context, input_cred->ccache, NULL,
"start_realm", &v);
if (ret == 0 &&
v.length != strlen(input_cred->destination_realm))
ret = KRB5_CC_NOTFOUND;
if (ret == 0 &&
strncmp(input_cred->destination_realm, v.data, v.length) != 0)
ret = KRB5_CC_NOTFOUND;
if (ret)
krb5_set_error_message(context, ret,
"Delegated TGT is not a destination TGT for "
"realm \"%s\" but for \"%.*s\"",
input_cred->destination_realm,
(int)(v.length ? v.length : sizeof("<UNKNOWN>") - 1),
v.data ? (const char *)v.data : "<UNKNOWN>");
krb5_data_free(&v);
return ret;
}
OM_uint32 GSSAPI_CALLCONV
_gsskrb5_store_cred_into2(OM_uint32 *minor_status,
gss_const_cred_id_t input_cred_handle,
@@ -215,6 +258,14 @@ _gsskrb5_store_cred_into2(OM_uint32 *minor_status,
return GSS_S_NO_CRED;
}
ret = check_destination_tgt_policy(context, cs_app_name, input_cred);
if (ret) {
HEIMDAL_MUTEX_unlock(&input_cred->cred_id_mutex);
*minor_status = ret;
free(ccache_name);
return GSS_S_NO_CRED;
}
/*
* Find an appropriate ccache, which will be one of:
*