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:
@@ -149,12 +149,10 @@ _gsskrb5i_is_cfx(krb5_context context, gsskrb5_ctx ctx, int acceptor)
|
|||||||
|
|
||||||
|
|
||||||
static OM_uint32
|
static OM_uint32
|
||||||
gsskrb5_accept_delegated_token
|
gsskrb5_accept_delegated_token(OM_uint32 *minor_status,
|
||||||
(OM_uint32 * minor_status,
|
|
||||||
gsskrb5_ctx ctx,
|
gsskrb5_ctx ctx,
|
||||||
krb5_context context,
|
krb5_context context,
|
||||||
gss_cred_id_t * delegated_cred_handle
|
gss_cred_id_t *delegated_cred_handle)
|
||||||
)
|
|
||||||
{
|
{
|
||||||
krb5_ccache ccache = NULL;
|
krb5_ccache ccache = NULL;
|
||||||
krb5_error_code kret;
|
krb5_error_code kret;
|
||||||
@@ -212,6 +210,40 @@ gsskrb5_accept_delegated_token
|
|||||||
|
|
||||||
handle = (gsskrb5_cred) *delegated_cred_handle;
|
handle = (gsskrb5_cred) *delegated_cred_handle;
|
||||||
handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE;
|
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:
|
out:
|
||||||
|
@@ -603,6 +603,7 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred_from
|
|||||||
return GSS_S_FAILURE;
|
return GSS_S_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handle->destination_realm = NULL;
|
||||||
HEIMDAL_MUTEX_init(&handle->cred_id_mutex);
|
HEIMDAL_MUTEX_init(&handle->cred_id_mutex);
|
||||||
|
|
||||||
if (desired_name != GSS_C_NO_NAME) {
|
if (desired_name != GSS_C_NO_NAME) {
|
||||||
|
@@ -138,6 +138,7 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_add_cred_from (
|
|||||||
handle->usage = cred_usage;
|
handle->usage = cred_usage;
|
||||||
handle->endtime = cred->endtime;
|
handle->endtime = cred->endtime;
|
||||||
handle->principal = NULL;
|
handle->principal = NULL;
|
||||||
|
handle->destination_realm = NULL;
|
||||||
handle->keytab = NULL;
|
handle->keytab = NULL;
|
||||||
handle->ccache = NULL;
|
handle->ccache = NULL;
|
||||||
handle->mechanisms = NULL;
|
handle->mechanisms = NULL;
|
||||||
|
@@ -96,6 +96,7 @@ _gsskrb5_krb5_import_cred(OM_uint32 *minor_status,
|
|||||||
HEIMDAL_MUTEX_init(&handle->cred_id_mutex);
|
HEIMDAL_MUTEX_init(&handle->cred_id_mutex);
|
||||||
|
|
||||||
handle->usage = 0;
|
handle->usage = 0;
|
||||||
|
handle->destination_realm = NULL;
|
||||||
|
|
||||||
if (*id) {
|
if (*id) {
|
||||||
time_t now;
|
time_t now;
|
||||||
|
@@ -268,6 +268,7 @@ _gsskrb5_import_cred(OM_uint32 * minor_status,
|
|||||||
}
|
}
|
||||||
|
|
||||||
handle->usage = GSS_C_INITIATE;
|
handle->usage = GSS_C_INITIATE;
|
||||||
|
handle->destination_realm = NULL;
|
||||||
krb5_cc_get_principal(context, id, &handle->principal);
|
krb5_cc_get_principal(context, id, &handle->principal);
|
||||||
handle->ccache = id;
|
handle->ccache = id;
|
||||||
handle->cred_flags = flags;
|
handle->cred_flags = flags;
|
||||||
|
@@ -66,7 +66,7 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_duplicate_cred (
|
|||||||
|
|
||||||
dup = calloc(1, sizeof(*dup));
|
dup = calloc(1, sizeof(*dup));
|
||||||
if (dup == NULL) {
|
if (dup == NULL) {
|
||||||
*minor_status = ENOMEM;
|
*minor_status = krb5_enomem(context);
|
||||||
return (GSS_S_FAILURE);
|
return (GSS_S_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +74,14 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_duplicate_cred (
|
|||||||
|
|
||||||
cred = (gsskrb5_cred)input_cred_handle;
|
cred = (gsskrb5_cred)input_cred_handle;
|
||||||
HEIMDAL_MUTEX_lock(&cred->cred_id_mutex);
|
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->usage = cred->usage;
|
||||||
dup->endtime = cred->endtime;
|
dup->endtime = cred->endtime;
|
||||||
dup->principal = NULL;
|
dup->principal = NULL;
|
||||||
|
@@ -92,6 +92,7 @@ typedef struct gsskrb5_ctx {
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
krb5_principal principal;
|
krb5_principal principal;
|
||||||
|
char *destination_realm; /* Realm of acceptor service, if delegated */
|
||||||
int cred_flags;
|
int cred_flags;
|
||||||
#define GSS_CF_DESTROY_CRED_ON_RELEASE 1
|
#define GSS_CF_DESTROY_CRED_ON_RELEASE 1
|
||||||
#define GSS_CF_NO_CI_FLAGS 2
|
#define GSS_CF_NO_CI_FLAGS 2
|
||||||
|
@@ -54,6 +54,7 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_release_cred
|
|||||||
|
|
||||||
HEIMDAL_MUTEX_lock(&cred->cred_id_mutex);
|
HEIMDAL_MUTEX_lock(&cred->cred_id_mutex);
|
||||||
|
|
||||||
|
free(cred->destination_realm);
|
||||||
if (cred->principal != NULL)
|
if (cred->principal != NULL)
|
||||||
krb5_free_principal(context, cred->principal);
|
krb5_free_principal(context, cred->principal);
|
||||||
if (cred->keytab != NULL)
|
if (cred->keytab != NULL)
|
||||||
|
@@ -119,6 +119,49 @@ principal_is_best_for_user(krb5_context context,
|
|||||||
return ret;
|
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
|
OM_uint32 GSSAPI_CALLCONV
|
||||||
_gsskrb5_store_cred_into2(OM_uint32 *minor_status,
|
_gsskrb5_store_cred_into2(OM_uint32 *minor_status,
|
||||||
gss_const_cred_id_t input_cred_handle,
|
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;
|
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:
|
* Find an appropriate ccache, which will be one of:
|
||||||
*
|
*
|
||||||
|
Reference in New Issue
Block a user