]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
s4-libnet: Add export of gMSA keys to "samba-tool domain exportkeytab"
authorAndrew Bartlett <abartlet@samba.org>
Wed, 20 Dec 2023 05:10:45 +0000 (18:10 +1300)
committerAndrew Bartlett <abartlet@samba.org>
Thu, 14 Mar 2024 22:06:39 +0000 (22:06 +0000)
Signed-off-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Jo Sutton <josutton@catalyst.net.nz>
source4/auth/kerberos/srv_keytab.c
source4/libnet/libnet_export_keytab.c
source4/libnet/wscript_build

index 024fab547296f503af4cc08d467b2b1dcc9eaa0a..2af3f15c47bc35aef0e3db0f2c949159c65e6dec 100644 (file)
 #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.
  *
index 2c40440725bd298509ad29a1d63d06c9b03722ee..c8e094ef1d9162c410d91dbfc9d89bcfcc192440 100644 (file)
@@ -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);
 
index 5bd3b04c072c3918167e83bcfd08be32f202728b..2dce60246c2cc9ffee37489e9e0d123ba134ea43 100644 (file)
@@ -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')
         )