From: Alexander Bokovoy Date: Tue, 2 Sep 2025 07:36:11 +0000 (+0300) Subject: krb5: handle GSS-Proxy credentials lifetime X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=c00b98ad840706cda222bb610d0d4860b98c72d4;p=thirdparty%2Fsamba.git krb5: handle GSS-Proxy credentials lifetime GSS-Proxy stores its credential in encrypted form in the Kerberos ccache with a start and end time of 0 and a server principal in the realm named 'X-GSSPROXY:'. This credential is accessed through GSS-Proxy interposer mechanism in MIT Kerberos and cannot be analysed with raw krb5 API. As MIT Kerberos has no krb5_cc_get_lifetime() implementation, add check for the GSS-Proxy credential to smb_krb5_cc_get_lifetime() wrapper to return KRB5_PLUGIN_NO_HANDLE. The two places where smb_krb5_cc_get_lifetime() is used then handle this return code to avoid deciding on the 'expired' lifetime to cause a kinit. This fixes FreeIPA use case where an IPA API endpoint uses Samba Python bindings with a GSS-Proxy-controlled credential cache. Bug: https://bugzilla.samba.org/show_bug.cgi?id=15902 Signed-off-by: Alexander Bokovoy Reviewed-by: Pavel Filipenský Autobuild-User(master): Pavel Filipensky Autobuild-Date(master): Wed Sep 3 10:15:50 UTC 2025 on atb-devel-224 --- diff --git a/auth/credentials/credentials_krb5.c b/auth/credentials/credentials_krb5.c index 4dc7e7be67e..8d289e26781 100644 --- a/auth/credentials/credentials_krb5.c +++ b/auth/credentials/credentials_krb5.c @@ -687,7 +687,14 @@ _PUBLIC_ int cli_credentials_get_named_ccache(struct cli_credentials *cred, bool kinit_required = false; ret = smb_krb5_cc_get_lifetime(cred->ccache->smb_krb5_context->krb5_context, cred->ccache->ccache, &lifetime); - if (ret == KRB5_CC_END || ret == ENOENT) { + if (ret == KRB5_PLUGIN_NO_HANDLE) { + /* + * KRB5_PLUGIN_NO_HANDLE is a special case of the encrypted + * GSSProxy credential. We don't know its lifetime but assume it + * is a valid one. Acquiring it will show the lifetime. + */ + kinit_required = false; + } else if (ret == KRB5_CC_END || ret == ENOENT) { kinit_required = true; } else if (ret == 0) { if (lifetime == 0) { @@ -800,18 +807,27 @@ _PUBLIC_ bool cli_credentials_get_ccache_name_obtained( if (ret == KRB5_CC_END || ret == ENOENT) { return false; } - if (ret != 0) { + + /* + * KRB5_PLUGIN_NO_HANDLE is a special case of the encrypted + * GSSProxy credential. We don't know its lifetime but assume it + * is a valid one. Acquiring it will show the lifetime. + * */ + if (ret != 0 && ret != KRB5_PLUGIN_NO_HANDLE) { return false; } - if (lifetime == 0) { - return false; - } else if (lifetime < 300) { - if (cred->password_obtained >= cred->ccache_obtained) { - /* - * we have a password to re-kinit - * so let the caller try that. - */ + + if (ret == 0) { + if (lifetime == 0) { return false; + } else if (lifetime < 300) { + if (cred->password_obtained >= cred->ccache_obtained) { + /* + * we have a password to re-kinit + * so let the caller try that. + */ + return false; + } } } diff --git a/lib/krb5_wrap/krb5_samba.c b/lib/krb5_wrap/krb5_samba.c index f9d91a4f0c1..dcf91348d1a 100644 --- a/lib/krb5_wrap/krb5_samba.c +++ b/lib/krb5_wrap/krb5_samba.c @@ -3025,6 +3025,8 @@ krb5_error_code smb_krb5_cc_get_lifetime(krb5_context context, krb5_creds cred; krb5_timestamp endtime = 0; krb5_timestamp now; + char *realm = NULL; + TALLOC_CTX *mem_ctx = NULL; *t = 0; @@ -3038,12 +3040,37 @@ krb5_error_code smb_krb5_cc_get_lifetime(krb5_context context, return kerr; } + mem_ctx = talloc_stackframe(); + if (mem_ctx == NULL) { + krb5_cc_end_seq_get(context, id, &cursor); + return ENOMEM; + } + while ((kerr = krb5_cc_next_cred(context, id, &cursor, &cred)) == 0) { if (krb5_is_config_principal(context, cred.server)) { krb5_free_cred_contents(context, &cred); continue; } + realm = smb_krb5_principal_get_realm(mem_ctx, context, cred.server); + if (realm == NULL) { + krb5_free_cred_contents(context, &cred); + kerr = ENOMEM; + break; + } + + /* + * 'X-GSSPROXY:' is the realm for an encrypted credential stored + * by the GSSProxy. There are no other creds in such ccache and + * we cannot see the actual lifetime (it is set to 0), + * indicate to the caller they need to handle this themselves. + */ + if (strcmp(realm, "X-GSSPROXY:") == 0) { + krb5_free_cred_contents(context, &cred); + kerr = KRB5_PLUGIN_NO_HANDLE; + break; + } + #ifndef HAVE_FLAGS_IN_KRB5_CREDS if (cred.ticket_flags & TKT_FLG_INITIAL) { #else @@ -3073,13 +3100,17 @@ krb5_error_code smb_krb5_cc_get_lifetime(krb5_context context, krb5_free_cred_contents(context, &cred); } + krb5_cc_end_seq_get(context, id, &cursor); + talloc_free(mem_ctx); + if (kerr == ENOMEM || kerr == KRB5_PLUGIN_NO_HANDLE) { + return kerr; + } + if (now < endtime) { *t = (time_t) (endtime - now); kerr = 0; } - krb5_cc_end_seq_get(context, id, &cursor); - return kerr; } #endif /* HAVE_KRB5_CC_GET_LIFETIME */