From: Andrew Bartlett Date: Wed, 20 Dec 2023 05:10:45 +0000 (+1300) Subject: s4-libnet: Add export of gMSA keys to "samba-tool domain exportkeytab" X-Git-Tag: tdb-1.4.11~1457 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bd2edecff06c2ce3393c5bfeb7906cce77eed702;p=thirdparty%2Fsamba.git s4-libnet: Add export of gMSA keys to "samba-tool domain exportkeytab" Signed-off-by: Andrew Bartlett Reviewed-by: Jo Sutton --- diff --git a/source4/auth/kerberos/srv_keytab.c b/source4/auth/kerberos/srv_keytab.c index 024fab54729..2af3f15c47b 100644 --- a/source4/auth/kerberos/srv_keytab.c +++ b/source4/auth/kerberos/srv_keytab.c @@ -30,9 +30,12 @@ #include "includes.h" #include "system/kerberos.h" #include "auth/credentials/credentials.h" +#include "auth/credentials/credentials_krb5.h" #include "auth/kerberos/kerberos.h" #include "auth/kerberos/kerberos_util.h" #include "auth/kerberos/kerberos_srv_keytab.h" +#include "librpc/gen_ndr/ndr_gmsa.h" +#include "dsdb/samdb/samdb.h" static void keytab_principals_free(krb5_context context, uint32_t num_principals, @@ -197,6 +200,194 @@ done: return ret; } +NTSTATUS smb_krb5_fill_keytab_gmsa_keys(TALLOC_CTX *mem_ctx, + struct smb_krb5_context *smb_krb5_context, + krb5_keytab keytab, + krb5_principal principal, + struct ldb_context *samdb, + struct ldb_dn *dn, + bool include_previous, + const char **error_string) +{ + const char *gmsa_attrs[] = { + "msDS-ManagedPassword", + "msDS-KeyVersionNumber", + "sAMAccountName", + "msDS-SupportedEncryptionTypes", + NULL + }; + + NTSTATUS status; + struct ldb_message *msg; + const struct ldb_val *managed_password_blob; + const char *managed_pw_utf8; + const char *previous_managed_pw_utf8; + const char *username; + const char *salt_principal; + uint32_t kvno = 0; + uint32_t supported_enctypes = 0; + krb5_context context = smb_krb5_context->krb5_context; + struct cli_credentials *cred = NULL; + const char *realm = NULL; + + /* + * Search for msDS-ManagedPassword (and other attributes to + * avoid a race) as this was not in the original search. + */ + int ret; + + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ret = dsdb_search_one(samdb, + tmp_ctx, + &msg, + dn, + LDB_SCOPE_BASE, + gmsa_attrs, 0, + "(objectClass=msDS-GroupManagedServiceAccount)"); + + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + /* + * Race condition, object has gone, or just wasn't a + * gMSA + */ + *error_string = talloc_asprintf(mem_ctx, + "Did not find gMSA at %s", + ldb_dn_get_linearized(dn)); + TALLOC_FREE(tmp_ctx); + return NT_STATUS_NO_SUCH_USER; + } + + if (ret != LDB_SUCCESS) { + *error_string = talloc_asprintf(mem_ctx, + "Error looking for gMSA at %s: %s", + ldb_dn_get_linearized(dn), ldb_errstring(samdb)); + TALLOC_FREE(tmp_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + /* Extract out passwords */ + managed_password_blob = ldb_msg_find_ldb_val(msg, "msDS-ManagedPassword"); + + if (managed_password_blob == NULL) { + /* + * No password set on this yet or not readable by this user + */ + *error_string = talloc_asprintf(mem_ctx, + "Did not find msDS-ManagedPassword at %s", + ldb_dn_get_extended_linearized(mem_ctx, msg->dn, 1)); + TALLOC_FREE(tmp_ctx); + return NT_STATUS_NO_USER_KEYS; + } + + cred = cli_credentials_init(tmp_ctx); + if (cred == NULL) { + *error_string = talloc_asprintf(mem_ctx, + "Could not allocate cli_credentials for %s", + ldb_dn_get_linearized(msg->dn)); + TALLOC_FREE(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + realm = smb_krb5_principal_get_realm(tmp_ctx, + context, + principal); + if (realm == NULL) { + *error_string = talloc_asprintf(mem_ctx, + "Could not allocate copy of realm for %s", + ldb_dn_get_linearized(msg->dn)); + TALLOC_FREE(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + cli_credentials_set_realm(cred, realm, CRED_SPECIFIED); + + username = ldb_msg_find_attr_as_string(msg, "sAMAccountName", NULL); + if (username == NULL) { + *error_string = talloc_asprintf(mem_ctx, + "No sAMAccountName on %s", + ldb_dn_get_linearized(msg->dn)); + TALLOC_FREE(tmp_ctx); + return NT_STATUS_INVALID_ACCOUNT_NAME; + } + + cli_credentials_set_username(cred, username, CRED_SPECIFIED); + + kvno = ldb_msg_find_attr_as_uint(msg, "msDS-KeyVersionNumber", 0); + + cli_credentials_set_kvno(cred, kvno); + + supported_enctypes = ldb_msg_find_attr_as_uint(msg, + "msDS-SupportedEncryptionTypes", + ENC_HMAC_SHA1_96_AES256); + /* + * We trim this down to just the salted AES types, as the + * passwords are now wrong for rc4-hmac due to the mapping of + * invalid sequences in UTF16_MUNGED -> UTF8 string conversion + * within cli_credentials_get_password(). Users using this new + * feature won't be using such weak crypto anyway. If + * required we could also set the NT Hash as a key directly, + * this is just a limitation of smb_krb5_fill_keytab() taking + * a simple string as input. + */ + supported_enctypes &= ENC_STRONG_SALTED_TYPES; + + /* Update the keytab */ + + status = cli_credentials_set_gmsa_passwords(cred, + managed_password_blob, + error_string); + + if (!NT_STATUS_IS_OK(status)) { + *error_string = talloc_asprintf(mem_ctx, + "Could not parse gMSA passwords on %s: %s", + ldb_dn_get_linearized(msg->dn), + *error_string); + TALLOC_FREE(tmp_ctx); + return status; + } + + managed_pw_utf8 = cli_credentials_get_password(cred); + + previous_managed_pw_utf8 = cli_credentials_get_old_password(cred); + + salt_principal = cli_credentials_get_salt_principal(cred, tmp_ctx); + if (salt_principal == NULL) { + *error_string = talloc_asprintf(mem_ctx, + "Failed to generated salt principal for %s", + ldb_dn_get_linearized(msg->dn)); + TALLOC_FREE(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + ret = smb_krb5_fill_keytab(tmp_ctx, + salt_principal, + kvno, + managed_pw_utf8, + previous_managed_pw_utf8, + supported_enctypes, + 1, + &principal, + context, + keytab, + include_previous, + error_string); + if (ret) { + *error_string = talloc_asprintf(mem_ctx, + "Failed to add keys from %s to keytab: %s", + ldb_dn_get_linearized(msg->dn), + *error_string); + TALLOC_FREE(tmp_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + TALLOC_FREE(tmp_ctx); + return NT_STATUS_OK; +} + /** * @brief Update a Kerberos keytab and removes any obsolete keytab entries. * diff --git a/source4/libnet/libnet_export_keytab.c b/source4/libnet/libnet_export_keytab.c index 2c40440725b..c8e094ef1d9 100644 --- a/source4/libnet/libnet_export_keytab.c +++ b/source4/libnet/libnet_export_keytab.c @@ -24,9 +24,9 @@ #include "auth/kerberos/kerberos.h" #include "auth/kerberos/kerberos_credentials.h" #include "auth/kerberos/kerberos_util.h" +#include "auth/kerberos/kerberos_srv_keytab.h" #include "kdc/samba_kdc.h" #include "libnet/libnet_export_keytab.h" - #include "kdc/db-glue.h" #include "kdc/sdb.h" @@ -46,6 +46,7 @@ static NTSTATUS sdb_kt_copy(TALLOC_CTX *mem_ctx, krb5_data password; bool keys_exported = false; krb5_context context = smb_krb5_context->krb5_context; + TALLOC_CTX *tmp_ctx = NULL; code = smb_krb5_kt_open_relative(context, keytab_name, @@ -83,7 +84,12 @@ static NTSTATUS sdb_kt_copy(TALLOC_CTX *mem_ctx, for (; code == 0; code = samba_kdc_nextkey(context, db_ctx, &sentry)) { int i; bool found_previous = false; - + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + code = krb5_unparse_name(context, sentry.principal, &entry_principal); @@ -112,47 +118,90 @@ static NTSTATUS sdb_kt_copy(TALLOC_CTX *mem_ctx, } } - if (sentry.keys.len == 0) { - SAFE_FREE(entry_principal); - sdb_entry_free(&sentry); - - continue; - } - - for (i = 0; i < sentry.keys.len; i++) { - struct sdb_key *s = &(sentry.keys.val[i]); - krb5_enctype enctype; - - enctype = KRB5_KEY_TYPE(&(s->key)); - password.length = KRB5_KEY_LENGTH(&s->key); - password.data = (char *)KRB5_KEY_DATA(&s->key); - - DBG_INFO("smb_krb5_kt_add_entry for enctype=0x%04x\n", - (int)enctype); - code = smb_krb5_kt_add_entry(context, - keytab, - sentry.kvno, - entry_principal, - NULL, - enctype, - &password, - true); /* no_salt */ - if (code != 0) { - status = NT_STATUS_UNSUCCESSFUL; - *error_string = smb_get_krb5_error_message(context, - code, - mem_ctx); - DEBUG(0, ("smb_krb5_kt_add_entry failed code=%d, error = %s\n", - code, *error_string)); + /* + * If this was a gMSA and we did not just read the + * keys directly, then generate them + */ + if (sentry.skdc_entry->group_managed_service_account + && sentry.keys.len == 0) { + struct ldb_dn *dn = sentry.skdc_entry->msg->dn; + /* + * for error message only, but we are about to + * destroy the string name, so write this out + * now + */ + const char *extended_dn = + ldb_dn_get_extended_linearized(mem_ctx, + dn, + 1); + + /* + * Modify the DN in the entry (not needed by + * the KDC code any longer) to be minimal, so + * we can search on it over LDAP. + */ + ldb_dn_minimise(dn); + + status = smb_krb5_fill_keytab_gmsa_keys(tmp_ctx, + smb_krb5_context, + keytab, + sentry.principal, + db_ctx->samdb, + dn, + found_previous ? false : true, + error_string); + if (NT_STATUS_IS_OK(status)) { + keys_exported = true; + } else if (copy_one_principal) { + *error_string = talloc_asprintf(mem_ctx, + "Failed to write gMSA password for %s to keytab: %s\n", + principal, + *error_string); goto done; + } else if (!NT_STATUS_EQUAL(status, NT_STATUS_NO_USER_KEYS)) { + *error_string = talloc_asprintf(mem_ctx, + "Failed to write gMSA password for %s to keytab: %s\n", + extended_dn, + *error_string); + goto done; + } + } else { + for (i = 0; i < sentry.keys.len; i++) { + struct sdb_key *s = &(sentry.keys.val[i]); + krb5_enctype enctype; + + enctype = KRB5_KEY_TYPE(&(s->key)); + password.length = KRB5_KEY_LENGTH(&s->key); + password.data = (char *)KRB5_KEY_DATA(&s->key); + + DBG_INFO("smb_krb5_kt_add_entry for enctype=0x%04x\n", + (int)enctype); + code = smb_krb5_kt_add_entry(context, + keytab, + sentry.kvno, + entry_principal, + NULL, + enctype, + &password, + true); /* no_salt */ + if (code != 0) { + status = NT_STATUS_UNSUCCESSFUL; + *error_string = smb_get_krb5_error_message(context, + code, + mem_ctx); + DEBUG(0, ("smb_krb5_kt_add_entry failed code=%d, error = %s\n", + code, *error_string)); + goto done; + } + keys_exported = true; } - keys_exported = true; } if (copy_one_principal) { break; } + TALLOC_FREE(tmp_ctx); SAFE_FREE(entry_principal); sdb_entry_free(&sentry); } @@ -178,6 +227,7 @@ static NTSTATUS sdb_kt_copy(TALLOC_CTX *mem_ctx, } done: + TALLOC_FREE(tmp_ctx); SAFE_FREE(entry_principal); sdb_entry_free(&sentry); diff --git a/source4/libnet/wscript_build b/source4/libnet/wscript_build index 5bd3b04c072..2dce60246c2 100644 --- a/source4/libnet/wscript_build +++ b/source4/libnet/wscript_build @@ -24,7 +24,7 @@ bld.SAMBA_PYTHON('python_net', bld.SAMBA_PYTHON('python_dckeytab', source='py_net_dckeytab.c libnet_export_keytab.c', - deps='%s %s db-glue krb5 com_err' % (pyrpc_util, pyldb_util), + deps='KERBEROS_SRV_KEYTAB KERBEROS_UTIL %s %s db-glue krb5 com_err' % (pyrpc_util, pyldb_util), realname='samba/dckeytab.so', enabled=bld.CONFIG_SET('AD_DC_BUILD_IS_ENABLED') )