s4:dsdb: Add dsdb_update_gmsa_keys()
authorJo Sutton <josutton@catalyst.net.nz>
Tue, 13 Feb 2024 03:09:57 +0000 (16:09 +1300)
committerJo Sutton <jsutton@samba.org>
Sun, 21 Apr 2024 22:10:36 +0000 (22:10 +0000)
Signed-off-by: Jo Sutton <josutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
source4/dsdb/gmsa/util.c
source4/dsdb/gmsa/util.h

index 4397219eb78c57bf6ed20fda881ede4947fd26b2..b33626557a883d00103848770aeefb0cc657395b 100644 (file)
@@ -1448,6 +1448,286 @@ out:
        return ret;
 }
 
+static void gmsa_update_debug(const struct gmsa_update *gmsa_update)
+{
+       struct ldb_dn *dn = NULL;
+       const char *account_dn = "<unknown>";
+
+       if (!CHECK_DEBUGLVL(DBGLVL_NOTICE)) {
+               return;
+       }
+
+       dn = gmsa_update->dn;
+       if (dn != NULL) {
+               const char *dn_str = NULL;
+
+               dn_str = ldb_dn_get_linearized(dn);
+               if (dn_str != NULL) {
+                       account_dn = dn_str;
+               }
+       }
+
+       DBG_NOTICE("Updating keys for Group Managed Service Account %s\n",
+                  account_dn);
+}
+
+static int gmsa_perform_request(struct ldb_context *ldb,
+                               struct ldb_request *req)
+{
+       int ret = LDB_SUCCESS;
+
+       if (req == NULL) {
+               return LDB_SUCCESS;
+       }
+
+       ret = ldb_request(ldb, req);
+       if (ret) {
+               return ret;
+       }
+
+       return ldb_wait(req->handle, LDB_WAIT_ALL);
+}
+
+static bool dsdb_data_blobs_equal(const DATA_BLOB *d1, const DATA_BLOB *d2)
+{
+       if (d1 == NULL && d2 == NULL) {
+               return true;
+       }
+
+       if (d1 == NULL || d2 == NULL) {
+               return false;
+       }
+
+       {
+               const int cmp = data_blob_cmp(d1, d2);
+               return cmp == 0;
+       }
+}
+
+int dsdb_update_gmsa_entry_keys(struct ldb_context *ldb,
+                               TALLOC_CTX *mem_ctx,
+                               const struct gmsa_update *gmsa_update)
+{
+       TALLOC_CTX *tmp_ctx = NULL;
+       int ret = LDB_SUCCESS;
+       bool in_transaction = false;
+
+       if (gmsa_update == NULL) {
+               ret = ldb_operr(ldb);
+               goto out;
+       }
+
+       tmp_ctx = talloc_new(mem_ctx);
+       if (tmp_ctx == NULL) {
+               ret = ldb_oom(ldb);
+               goto out;
+       }
+
+       gmsa_update_debug(gmsa_update);
+
+       /* The following must take place in a single transaction. */
+       ret = ldb_transaction_start(ldb);
+       if (ret) {
+               goto out;
+       }
+       in_transaction = true;
+
+       {
+               /*
+                * Before performing the update, ensure that the managed
+                * password ID in the database has the value we expect.
+                */
+
+               struct ldb_result *res = NULL;
+               const struct ldb_val *pwd_id_blob = NULL;
+               static const char *const managed_pwd_id_attr[] = {
+                       "msDS-ManagedPasswordId",
+                       NULL,
+               };
+
+               if (gmsa_update->dn == NULL) {
+                       ret = ldb_operr(ldb);
+                       goto out;
+               }
+
+               ret = dsdb_search_dn(ldb,
+                                    tmp_ctx,
+                                    &res,
+                                    gmsa_update->dn,
+                                    managed_pwd_id_attr,
+                                    0);
+               if (ret) {
+                       goto out;
+               }
+
+               if (res->count != 1) {
+                       ret = ldb_error(
+                               ldb,
+                               LDB_ERR_NO_SUCH_OBJECT,
+                               "failed to find Group Managed Service Account "
+                               "to verify managed password ID");
+                       goto out;
+               }
+
+               pwd_id_blob = ldb_msg_find_ldb_val(res->msgs[0],
+                                                  "msDS-ManagedPasswordId");
+               if (!dsdb_data_blobs_equal(pwd_id_blob,
+                                          gmsa_update->found_pwd_id))
+               {
+                       /*
+                        * The account’s managed password ID doesn’t match what
+                        * we thought it was — cancel the update. If the caller
+                        * needs the latest values, it will retry the search,
+                        * performing the update again if necessary.
+                        */
+                       ret = LDB_SUCCESS;
+                       goto out;
+               }
+       }
+
+       /*
+        * First update the previous password (if the request is not NULL,
+        * indicating that the previous password already matches the password of
+        * the account).
+        */
+       ret = gmsa_perform_request(ldb, gmsa_update->old_pw_req);
+       if (ret) {
+               goto out;
+       }
+
+       /* Then update the current password. */
+       ret = gmsa_perform_request(ldb, gmsa_update->new_pw_req);
+       if (ret) {
+               goto out;
+       }
+
+       /* Finally, update the msDS-ManagedPasswordId attribute. */
+       ret = gmsa_perform_request(ldb, gmsa_update->pwd_id_req);
+       if (ret) {
+               goto out;
+       }
+
+       /* Commit the transaction. */
+       ret = ldb_transaction_commit(ldb);
+       in_transaction = false;
+       if (ret) {
+               goto out;
+       }
+
+out:
+       if (in_transaction) {
+               int ret2 = ldb_transaction_cancel(ldb);
+               if (ret2) {
+                       ret = ret2;
+               }
+       }
+       talloc_free(tmp_ctx);
+       return ret;
+}
+
+int dsdb_update_gmsa_keys(struct ldb_context *ldb,
+                         TALLOC_CTX *mem_ctx,
+                         const struct ldb_result *res,
+                         bool *retry_out)
+{
+       TALLOC_CTX *tmp_ctx = NULL;
+       int ret = LDB_SUCCESS;
+       bool retry = false;
+       unsigned i;
+       NTTIME current_time;
+       bool am_rodc = true;
+
+       {
+               /* Calculate the current time, as reckoned for gMSAs. */
+               bool ok = dsdb_gmsa_current_time(ldb, &current_time);
+               if (!ok) {
+                       ret = ldb_operr(ldb);
+                       goto out;
+               }
+       }
+
+       tmp_ctx = talloc_new(mem_ctx);
+       if (tmp_ctx == NULL) {
+               ret = ldb_oom(ldb);
+               goto out;
+       }
+
+       /* Are we operating as an RODC? */
+       ret = samdb_rodc(ldb, &am_rodc);
+       if (ret != LDB_SUCCESS) {
+               DBG_WARNING("unable to tell if we are an RODC\n");
+               goto out;
+       }
+
+       /* Loop through each entry in the results. */
+       for (i = 0; i < res->count; ++i) {
+               struct ldb_message *msg = res->msgs[i];
+               struct gmsa_update *gmsa_update = NULL;
+               const bool is_gmsa = dsdb_account_is_gmsa(ldb, msg);
+
+               /* Is the account a Group Managed Service Account? */
+               if (!is_gmsa) {
+                       /*
+                        * It’s not a gMSA, and there’s nothing more to do for
+                        * this result.
+                        */
+                       continue;
+               }
+
+               if (am_rodc) {
+                       static const char *const secret_attributes[] = {
+                               DSDB_SECRET_ATTRIBUTES};
+                       size_t j;
+
+                       /*
+                        * If we’re an RODC, we won’t be able to update the
+                        * database entry with the new gMSA keys. The simplest
+                        * thing to do is redact all the password attributes in
+                        * the message. If our caller is the KDC, it will
+                        * recognize the missing keys and dispatch a referral to
+                        * a writable DC. */
+                       for (j = 0; j < ARRAY_SIZE(secret_attributes); ++j) {
+                               ldb_msg_remove_attr(msg, secret_attributes[j]);
+                       }
+
+                       /* Proceed to the next search result. */
+                       continue;
+               }
+
+               /* Update any old gMSA state. */
+               ret = gmsa_recalculate_managed_pwd(
+                       tmp_ctx, ldb, msg, current_time, &gmsa_update, NULL);
+               if (ret) {
+                       goto out;
+               }
+
+               if (gmsa_update == NULL) {
+                       /*
+                        * The usual case; the keys are up‐to‐date, and there’s
+                        * nothing more to do for this result.
+                        */
+                       continue;
+               }
+
+               ret = dsdb_update_gmsa_entry_keys(ldb, tmp_ctx, gmsa_update);
+               if (ret) {
+                       goto out;
+               }
+
+               /*
+                * Since the database entry has been updated, the caller will
+                * need to perform the search again.
+                */
+               retry = true;
+       }
+
+       *retry_out = retry;
+
+out:
+       talloc_free(tmp_ctx);
+       return ret;
+}
+
 bool dsdb_gmsa_current_time(struct ldb_context *ldb, NTTIME *current_time_out)
 {
        const unsigned long long *gmsa_time = talloc_get_type(
index 371bdf2c5946bb1cdb7ff47f9ba0e91e17cd04ec..2fc1ee0d5711f24a9b56374c965feb141504decf 100644 (file)
@@ -115,6 +115,15 @@ int gmsa_recalculate_managed_pwd(TALLOC_CTX *mem_ctx,
                                 struct gmsa_update **update_out,
                                 struct gmsa_return_pwd *return_out);
 
+int dsdb_update_gmsa_entry_keys(struct ldb_context *ldb,
+                               TALLOC_CTX *mem_ctx,
+                               const struct gmsa_update *gmsa_update);
+
+int dsdb_update_gmsa_keys(struct ldb_context *ldb,
+                         TALLOC_CTX *mem_ctx,
+                         const struct ldb_result *res,
+                         bool *retry_out);
+
 #define DSDB_GMSA_TIME_OPAQUE ("dsdb_gmsa_time_opaque")
 
 bool dsdb_gmsa_current_time(struct ldb_context *ldb, NTTIME *current_time_out);