From: Jennifer Sutton Date: Thu, 5 Jun 2025 00:28:20 +0000 (+1200) Subject: s4:dsdb: Implement msDS-KeyCredentialLink attribute X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=a9c6e1ac37065d0b7a4c459c3b2933321ec074c3;p=thirdparty%2Fsamba.git s4:dsdb: Implement msDS-KeyCredentialLink attribute Signed-off-by: Jennifer Sutton Reviewed-by: Douglas Bagnall --- diff --git a/libds/common/flags.h b/libds/common/flags.h index 5e9a2364535..f970a4cb65d 100644 --- a/libds/common/flags.h +++ b/libds/common/flags.h @@ -242,6 +242,7 @@ #define DS_GUID_SCHEMA_ATTR_DEPARTMENT "bf96794f-0de6-11d0-a285-00aa003049e2" #define DS_GUID_SCHEMA_ATTR_DNS_HOST_NAME "72e39547-7b18-11d1-adef-00c04fd8d5cd" #define DS_GUID_SCHEMA_ATTR_INSTANCE_TYPE "bf96798c-0de6-11d0-a285-00aa003049e2" +#define DS_GUID_SCHEMA_ATTR_MS_DS_KEY_CREDENTIAL_LINK "5b47d60f-6090-40b2-9f37-2a4de88f3063" #define DS_GUID_SCHEMA_ATTR_MS_SFU_30 "16c5d1d3-35c2-4061-a870-a5cefda804f0" #define DS_GUID_SCHEMA_ATTR_NT_SECURITY_DESCRIPTOR "bf9679e3-0de6-11d0-a285-00aa003049e2" #define DS_GUID_SCHEMA_ATTR_PRIMARY_GROUP_ID "bf967a00-0de6-11d0-a285-00aa003049e2" diff --git a/librpc/idl/security.idl b/librpc/idl/security.idl index 9f02ee10d92..6c07bff6eae 100644 --- a/librpc/idl/security.idl +++ b/librpc/idl/security.idl @@ -963,6 +963,7 @@ interface security const string GUID_DRS_DNS_HOST_NAME = "72e39547-7b18-11d1-adef-00c04fd8d5cd"; const string GUID_DRS_ADD_DNS_HOST_NAME = "80863791-dbe9-4eb8-837e-7f0ab55d9ac7"; const string GUID_DRS_BEHAVIOR_VERSION = "d31a8757-2447-4545-8081-3bb610cacbf2"; + const string GUID_DRS_DS_VALIDATED_WRITE_COMPUTER = "9b026da6-0d3c-465c-8bee-5199d7165cba"; /* A type to describe the mapping of generic access rights to object specific access rights. */ diff --git a/source4/dsdb/pydsdb.c b/source4/dsdb/pydsdb.c index 3c010afdd4e..df46b288e4f 100644 --- a/source4/dsdb/pydsdb.c +++ b/source4/dsdb/pydsdb.c @@ -2069,6 +2069,7 @@ MODULE_INIT_FUNC(dsdb) ADD_DSDB_STRING(DS_GUID_SCHEMA_ATTR_DEPARTMENT); ADD_DSDB_STRING(DS_GUID_SCHEMA_ATTR_DNS_HOST_NAME); ADD_DSDB_STRING(DS_GUID_SCHEMA_ATTR_INSTANCE_TYPE); + ADD_DSDB_STRING(DS_GUID_SCHEMA_ATTR_MS_DS_KEY_CREDENTIAL_LINK); ADD_DSDB_STRING(DS_GUID_SCHEMA_ATTR_MS_SFU_30); ADD_DSDB_STRING(DS_GUID_SCHEMA_ATTR_NT_SECURITY_DESCRIPTOR); ADD_DSDB_STRING(DS_GUID_SCHEMA_ATTR_PRIMARY_GROUP_ID); diff --git a/source4/dsdb/samdb/ldb_modules/acl.c b/source4/dsdb/samdb/ldb_modules/acl.c index cf33ee64d76..8432dbfe25b 100644 --- a/source4/dsdb/samdb/ldb_modules/acl.c +++ b/source4/dsdb/samdb/ldb_modules/acl.c @@ -36,6 +36,8 @@ #include "auth/auth.h" #include "libcli/security/security.h" #include "dsdb/samdb/samdb.h" +#include "librpc/gen_ndr/keycredlink.h" +#include "librpc/gen_ndr/ndr_keycredlink.h" #include "librpc/gen_ndr/ndr_security.h" #include "param/param.h" #include "dsdb/samdb/ldb_modules/util.h" @@ -1122,6 +1124,216 @@ fail: return LDB_ERR_CONSTRAINT_VIOLATION; } +static int acl_check_ms_ds_key_credential_link( + TALLOC_CTX *mem_ctx, + struct ldb_module *module, + struct ldb_request *req, + const struct ldb_message_element *el, + struct security_descriptor *sd, + struct dom_sid *sid, + const struct dsdb_attribute *attr, + const struct dsdb_class *objectclass) +{ + int ret; + TALLOC_CTX *tmp_ctx = NULL; + struct ldb_context *ldb = ldb_module_get_ctx(module); + const struct dsdb_schema *schema = NULL; + const struct ldb_message *msg = NULL; + const struct dsdb_class *computer_objectclass = NULL; + bool is_subclass; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ldb_oom(ldb); + } + + if (req->operation == LDB_MODIFY) { + msg = req->op.mod.message; + } else if (req->operation == LDB_ADD) { + msg = req->op.add.message; + } + + /* The attribute only accepts single values */ + if (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_ADD && el->num_values == 0) { + talloc_free(tmp_ctx); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + /* if we have Write Property, we can do whatever we like */ + ret = acl_check_access_on_attribute(module, + tmp_ctx, + sd, + sid, + SEC_ADS_WRITE_PROP, + attr, + objectclass); + if (ret == LDB_SUCCESS) { + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + + ret = acl_check_extended_right(tmp_ctx, + module, + req, + objectclass, + sd, + acl_user_token(module), + GUID_DRS_DS_VALIDATED_WRITE_COMPUTER, + SEC_ADS_SELF_WRITE, + sid); + if (ret != LDB_SUCCESS) { + dsdb_acl_debug(sd, + acl_user_token(module), + msg->dn, + true, + DBGLVL_DEBUG); + talloc_free(tmp_ctx); + return ret; + } + + /* + * If we have “validated write msDS-KeyCredentialLink”, allow delete of + * any existing value (this keeps constrained delete to the same rules + * as unconstrained). + */ + if (req->operation == LDB_MODIFY) { + struct ldb_result *acl_res = NULL; + static const char *acl_attrs[] = {"msDS-KeyCredentialLink", + "objectSid", + NULL}; + bool existing_value_present = false; + struct dom_sid object_sid; + struct dom_sid *requestor_sid = NULL; + + /* If not add or replace (e.g. delete), return success */ + if (LDB_FLAG_MOD_TYPE(el->flags) != LDB_FLAG_MOD_ADD && + LDB_FLAG_MOD_TYPE(el->flags) != LDB_FLAG_MOD_REPLACE) + { + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + + /* Search for any existing values. */ + ret = dsdb_module_search_dn(module, + tmp_ctx, + &acl_res, + msg->dn, + acl_attrs, + DSDB_FLAG_NEXT_MODULE | + DSDB_FLAG_AS_SYSTEM | + DSDB_SEARCH_SHOW_RECYCLED, + req); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + + /* An existing value must not be present. */ + existing_value_present = ldb_msg_find_ldb_val( + acl_res->msgs[0], + "msDS-KeyCredentialLink") != + NULL; + if (existing_value_present) { + goto fail; + } + + ret = samdb_result_dom_sid_buf(acl_res->msgs[0], + "objectSid", + &object_sid); + if (ret) { + talloc_free(tmp_ctx); + return ldb_operr(ldb); + } + + /* The requestor must be SELF. */ + requestor_sid = &acl_user_token(module) + ->sids[PRIMARY_USER_SID_INDEX]; + if (!dom_sid_equal(requestor_sid, &object_sid)) { + goto fail; + } + } else if (req->operation != LDB_ADD) { + talloc_free(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + + /* Check if the account has objectclass 'computer'. */ + + schema = dsdb_get_schema(ldb, req); + if (schema == NULL) { + talloc_free(tmp_ctx); + return ldb_operr(ldb); + } + + computer_objectclass = dsdb_class_by_lDAPDisplayName(schema, + "computer"); + if (computer_objectclass == NULL) { + talloc_free(tmp_ctx); + return ldb_operr(ldb); + } + + is_subclass = dsdb_is_subclass_of(schema, + objectclass, + computer_objectclass); + if (!is_subclass) { + /* The account is not a computer. */ + goto fail; + } + + if (req->operation == LDB_MODIFY && + LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_REPLACE && + el->num_values == 0) + { + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + + /* The attribute only accepts single values */ + if (el->num_values != 1) { + goto fail; + } + + { + struct dsdb_dn *dsdb_dn = NULL; + struct KEYCREDENTIALLINK_BLOB key_credential_link; + enum ndr_err_code ndr_err; + + dsdb_dn = dsdb_dn_parse(tmp_ctx, + ldb, + &el->values[0], + DSDB_SYNTAX_BINARY_DN); + if (dsdb_dn == NULL) { + goto fail; + } + + /* + * Ensure that the binary portion of the attribute is a + * well‐formed KEYCREDENTIALLINK_BLOB value. + */ + + ndr_err = ndr_pull_struct_blob( + &dsdb_dn->extra_part, + tmp_ctx, + &key_credential_link, + (ndr_pull_flags_fn_t)ndr_pull_KEYCREDENTIALLINK_BLOB); + + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + goto fail; + } + } + + talloc_free(tmp_ctx); + return LDB_SUCCESS; + +fail: + ldb_debug_set( + ldb, + LDB_DEBUG_WARNING, + "acl: msDS-KeyCredentialLink validation failed for [%s]\n", + ldb_dn_get_linearized(msg->dn)); + talloc_free(tmp_ctx); + return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; +} + /* checks if modifications are allowed on "Member" attribute */ static int acl_check_self_membership(TALLOC_CTX *mem_ctx, struct ldb_module *module, @@ -1523,6 +1735,26 @@ static int acl_add(struct ldb_module *module, struct ldb_request *req) 10); return ret; } + } else if (ldb_attr_cmp("msDS-KeyCredentialLink", el->name) == 0) { + ret = acl_check_ms_ds_key_credential_link(req, + module, + req, + el, + control_sd->default_sd, + NULL, + attr, + objectclass); + if (ret != LDB_SUCCESS) { + ldb_asprintf_errstring(ldb_module_get_ctx(module), + "Object %s cannot be created with msDS-KeyCredentialLink", + ldb_dn_get_linearized(msg->dn)); + dsdb_acl_debug(control_sd->default_sd, + acl_user_token(module), + msg->dn, + true, + 10); + return ret; + } } else { ret = acl_check_access_on_attribute(module, req, @@ -2116,6 +2348,18 @@ static int acl_modify(struct ldb_module *module, struct ldb_request *req) if (ret != LDB_SUCCESS) { goto fail; } + } else if (ldb_attr_cmp("msDS-KeyCredentialLink", el->name) == 0) { + ret = acl_check_ms_ds_key_credential_link(tmp_ctx, + module, + req, + el, + sd, + sid, + attr, + objectclass); + if (ret != LDB_SUCCESS) { + goto fail; + } } else if (is_undelete != NULL && (ldb_attr_cmp("isDeleted", el->name) == 0)) { /* * in case of undelete op permissions on