2024-02-13 06:09:57 +03:00
/*
Unix SMB / CIFS implementation .
msDS - ManagedPassword attribute for Group Managed Service Accounts
Copyright ( C ) Catalyst . Net Ltd 2024
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program . If not , see < https : //www.gnu.org/licenses/>.
*/
# include "includes.h"
# include "ldb.h"
# include "ldb_module.h"
# include "ldb_errors.h"
# include "ldb_private.h"
# include "lib/crypto/gkdi.h"
# include "lib/crypto/gmsa.h"
# include "lib/util/data_blob.h"
# include "lib/util/fault.h"
# include "lib/util/time.h"
# include "libcli/security/access_check.h"
2024-08-22 19:25:06 +03:00
# include "libcli/security/session.h"
2024-02-13 06:09:57 +03:00
# include "librpc/gen_ndr/auth.h"
# include "librpc/gen_ndr/ndr_gkdi.h"
# include "librpc/gen_ndr/ndr_gmsa.h"
# include "librpc/gen_ndr/ndr_security.h"
# include "dsdb/common/util.h"
# include "dsdb/gmsa/gkdi.h"
# include "dsdb/gmsa/util.h"
# include "dsdb/samdb/samdb.h"
# undef strcasecmp
enum RootKeyType {
ROOT_KEY_NONE ,
ROOT_KEY_SPECIFIC ,
ROOT_KEY_NONSPECIFIC ,
ROOT_KEY_OBTAINED ,
} ;
struct RootKey {
TALLOC_CTX * mem_ctx ;
enum RootKeyType type ;
union {
struct KeyEnvelopeId specific ;
struct {
NTTIME key_start_time ;
} nonspecific ;
struct {
struct gmsa_update_pwd_part key ;
struct gmsa_null_terminated_password * password ;
} obtained ;
} u ;
} ;
static const struct RootKey empty_root_key = { . type = ROOT_KEY_NONE } ;
int gmsa_allowed_to_view_managed_password ( TALLOC_CTX * mem_ctx ,
struct ldb_context * ldb ,
const struct ldb_message * msg ,
const struct dom_sid * account_sid ,
bool * allowed_out )
{
TALLOC_CTX * tmp_ctx = NULL ;
struct security_descriptor group_msa_membership_sd = { } ;
const struct security_token * user_token = NULL ;
NTSTATUS status = NT_STATUS_OK ;
int ret = LDB_SUCCESS ;
if ( allowed_out = = NULL ) {
ret = ldb_operr ( ldb ) ;
goto out ;
}
* allowed_out = false ;
{
const struct auth_session_info * session_info = ldb_get_opaque (
ldb , DSDB_SESSION_INFO ) ;
const enum security_user_level level =
security_session_user_level ( session_info , NULL ) ;
if ( level = = SECURITY_SYSTEM ) {
* allowed_out = true ;
ret = LDB_SUCCESS ;
goto out ;
}
if ( session_info = = NULL ) {
ret = dsdb_werror ( ldb ,
LDB_ERR_OPERATIONS_ERROR ,
WERR_DS_CANT_RETRIEVE_ATTS ,
" no right to view attribute " ) ;
goto out ;
}
user_token = session_info - > security_token ;
}
tmp_ctx = talloc_new ( msg ) ;
if ( tmp_ctx = = NULL ) {
ret = ldb_oom ( ldb ) ;
goto out ;
}
{
const struct ldb_val * group_msa_membership = NULL ;
enum ndr_err_code err ;
/* [MS-ADTS] 3.1.1.4.4: Extended Access Checks. */
group_msa_membership = ldb_msg_find_ldb_val (
msg , " msDS-GroupMSAMembership " ) ;
if ( group_msa_membership = = NULL ) {
ret = dsdb_werror ( ldb ,
LDB_ERR_OPERATIONS_ERROR ,
WERR_DS_CANT_RETRIEVE_ATTS ,
" no right to view attribute " ) ;
goto out ;
}
err = ndr_pull_struct_blob_all (
group_msa_membership ,
tmp_ctx ,
& group_msa_membership_sd ,
( ndr_pull_flags_fn_t ) ndr_pull_security_descriptor ) ;
if ( ! NDR_ERR_CODE_IS_SUCCESS ( err ) ) {
status = ndr_map_error2ntstatus ( err ) ;
DBG_WARNING ( " msDS-GroupMSAMembership pull failed: %s \n " ,
nt_errstr ( status ) ) ;
ret = ldb_operr ( ldb ) ;
goto out ;
}
}
{
const uint32_t access_desired = SEC_ADS_READ_PROP ;
uint32_t access_granted = 0 ;
status = sec_access_check_ds ( & group_msa_membership_sd ,
user_token ,
access_desired ,
& access_granted ,
NULL ,
account_sid ) ;
if ( NT_STATUS_EQUAL ( status , NT_STATUS_ACCESS_DENIED ) ) {
/*
* The principal is not allowed to view the managed
* password .
*/
} else if ( ! NT_STATUS_IS_OK ( status ) ) {
DBG_WARNING ( " msDS-GroupMSAMembership: "
" sec_access_check_ds(access_desired=%#08x, "
" access_granted:%#08x) failed with: %s \n " ,
access_desired ,
access_granted ,
nt_errstr ( status ) ) ;
ret = dsdb_werror (
ldb ,
LDB_ERR_OPERATIONS_ERROR ,
WERR_DS_CANT_RETRIEVE_ATTS ,
" access check to view managed password failed " ) ;
goto out ;
} else {
/* Cool, the principal may view the password. */
* allowed_out = true ;
}
}
out :
TALLOC_FREE ( tmp_ctx ) ;
return ret ;
}
static NTSTATUS gmsa_managed_pwd_id ( struct ldb_context * ldb ,
TALLOC_CTX * mem_ctx ,
const struct ldb_val * pwd_id_blob ,
const struct ProvRootKey * root_key ,
struct KeyEnvelope * pwd_id_out )
{
if ( root_key = = NULL ) {
return NT_STATUS_INVALID_PARAMETER ;
}
if ( pwd_id_blob ! = NULL ) {
return gkdi_pull_KeyEnvelope ( mem_ctx , pwd_id_blob , pwd_id_out ) ;
}
{
const char * domain_name = NULL ;
const char * forest_name = NULL ;
domain_name = samdb_default_domain_name ( ldb , mem_ctx ) ;
if ( domain_name = = NULL ) {
return NT_STATUS_NO_MEMORY ;
}
forest_name = samdb_forest_name ( ldb , mem_ctx ) ;
if ( forest_name = = NULL ) {
/* We leak ‘ domain_name’ , but that can’ t be helped. */
return NT_STATUS_NO_MEMORY ;
}
* pwd_id_out = ( struct KeyEnvelope ) {
. version = root_key - > version ,
. flags = ENVELOPE_FLAG_KEY_MAY_ENCRYPT_NEW_DATA ,
. domain_name = domain_name ,
. forest_name = forest_name ,
} ;
}
return NT_STATUS_OK ;
}
void gmsa_update_managed_pwd_id ( struct KeyEnvelope * pwd_id ,
const struct gmsa_update_pwd_part * new_pwd )
{
pwd_id - > l0_index = new_pwd - > gkid . l0_idx ;
pwd_id - > l1_index = new_pwd - > gkid . l1_idx ;
pwd_id - > l2_index = new_pwd - > gkid . l2_idx ;
pwd_id - > root_key_id = new_pwd - > root_key - > id ;
}
NTSTATUS gmsa_pack_managed_pwd_id ( TALLOC_CTX * mem_ctx ,
const struct KeyEnvelope * pwd_id ,
DATA_BLOB * pwd_id_out )
{
NTSTATUS status = NT_STATUS_OK ;
enum ndr_err_code err ;
err = ndr_push_struct_blob ( pwd_id_out ,
mem_ctx ,
pwd_id ,
( ndr_push_flags_fn_t ) ndr_push_KeyEnvelope ) ;
status = ndr_map_error2ntstatus ( err ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
DBG_WARNING ( " KeyEnvelope push failed: %s \n " , nt_errstr ( status ) ) ;
}
return status ;
}
static int gmsa_specific_password ( TALLOC_CTX * mem_ctx ,
struct ldb_context * ldb ,
const struct KeyEnvelopeId pwd_id ,
struct gmsa_update_pwd_part * new_pwd_out )
{
TALLOC_CTX * tmp_ctx = NULL ;
NTSTATUS status = NT_STATUS_OK ;
int ret = LDB_SUCCESS ;
tmp_ctx = talloc_new ( mem_ctx ) ;
if ( tmp_ctx = = NULL ) {
ret = ldb_oom ( ldb ) ;
goto out ;
}
{
const struct ldb_message * root_key_msg = NULL ;
ret = gkdi_root_key_from_id ( tmp_ctx ,
ldb ,
& pwd_id . root_key_id ,
& root_key_msg ) ;
if ( ret ) {
goto out ;
}
status = gkdi_root_key_from_msg ( mem_ctx ,
pwd_id . root_key_id ,
root_key_msg ,
& new_pwd_out - > root_key ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
ret = ldb_operr ( ldb ) ;
goto out ;
}
}
new_pwd_out - > gkid = pwd_id . gkid ;
out :
talloc_free ( tmp_ctx ) ;
return ret ;
}
static int gmsa_nonspecific_password ( TALLOC_CTX * mem_ctx ,
struct ldb_context * ldb ,
const NTTIME key_start_time ,
const NTTIME current_time ,
struct gmsa_update_pwd_part * new_pwd_out )
{
TALLOC_CTX * tmp_ctx = NULL ;
int ret = LDB_SUCCESS ;
tmp_ctx = talloc_new ( mem_ctx ) ;
if ( tmp_ctx = = NULL ) {
ret = ldb_oom ( ldb ) ;
goto out ;
}
{
const struct ldb_message * root_key_msg = NULL ;
struct GUID root_key_id ;
NTSTATUS status = NT_STATUS_OK ;
ret = gkdi_most_recently_created_root_key ( tmp_ctx ,
ldb ,
current_time ,
key_start_time ,
& root_key_id ,
& root_key_msg ) ;
if ( ret ) {
goto out ;
}
status = gkdi_root_key_from_msg ( mem_ctx ,
root_key_id ,
root_key_msg ,
& new_pwd_out - > root_key ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
ret = ldb_operr ( ldb ) ;
goto out ;
}
}
new_pwd_out - > gkid = gkdi_get_interval_id ( key_start_time ) ;
out :
talloc_free ( tmp_ctx ) ;
return ret ;
}
static int gmsa_specifc_root_key ( TALLOC_CTX * mem_ctx ,
const struct KeyEnvelopeId pwd_id ,
struct RootKey * root_key_out )
{
if ( root_key_out = = NULL ) {
return LDB_ERR_OPERATIONS_ERROR ;
}
* root_key_out = ( struct RootKey ) { . mem_ctx = mem_ctx ,
. type = ROOT_KEY_SPECIFIC ,
. u . specific = pwd_id } ;
return LDB_SUCCESS ;
}
static int gmsa_nonspecifc_root_key ( TALLOC_CTX * mem_ctx ,
const NTTIME key_start_time ,
struct RootKey * root_key_out )
{
if ( root_key_out = = NULL ) {
return LDB_ERR_OPERATIONS_ERROR ;
}
* root_key_out = ( struct RootKey ) {
. mem_ctx = mem_ctx ,
. type = ROOT_KEY_NONSPECIFIC ,
. u . nonspecific . key_start_time = key_start_time } ;
return LDB_SUCCESS ;
}
static int gmsa_obtained_root_key_steal (
TALLOC_CTX * mem_ctx ,
const struct gmsa_update_pwd_part key ,
struct gmsa_null_terminated_password * password ,
struct RootKey * root_key_out )
{
if ( root_key_out = = NULL ) {
return LDB_ERR_OPERATIONS_ERROR ;
}
/* Steal the data on to the appropriate memory context. */
talloc_steal ( mem_ctx , key . root_key ) ;
talloc_steal ( mem_ctx , password ) ;
* root_key_out = ( struct RootKey ) { . mem_ctx = mem_ctx ,
. type = ROOT_KEY_OBTAINED ,
. u . obtained = { . key = key ,
. password = password } } ;
return LDB_SUCCESS ;
}
static int gmsa_fetch_root_key ( struct ldb_context * ldb ,
const NTTIME current_time ,
struct RootKey * root_key ,
const struct dom_sid * const account_sid )
{
TALLOC_CTX * tmp_ctx = NULL ;
NTSTATUS status = NT_STATUS_OK ;
int ret = LDB_SUCCESS ;
if ( root_key = = NULL ) {
ret = ldb_operr ( ldb ) ;
goto out ;
}
switch ( root_key - > type ) {
case ROOT_KEY_SPECIFIC :
case ROOT_KEY_NONSPECIFIC : {
struct gmsa_null_terminated_password * password = NULL ;
struct gmsa_update_pwd_part key ;
tmp_ctx = talloc_new ( NULL ) ;
if ( tmp_ctx = = NULL ) {
ret = ldb_oom ( ldb ) ;
goto out ;
}
if ( root_key - > type = = ROOT_KEY_SPECIFIC ) {
/* Search for a specific root key. */
ret = gmsa_specific_password ( tmp_ctx ,
ldb ,
root_key - > u . specific ,
& key ) ;
if ( ret ) {
/*
* We couldn ’ t find a specific key — treat this
* as an error .
*/
goto out ;
}
} else {
/*
* Search for the most recent root key meeting the start
* time requirement .
*/
ret = gmsa_nonspecific_password (
tmp_ctx ,
ldb ,
root_key - > u . nonspecific . key_start_time ,
current_time ,
& key ) ;
/* Handle errors below. */
}
if ( ret = = LDB_ERR_NO_SUCH_OBJECT ) {
/*
* We couldn ’ t find a key meeting the requirements —
* that ’ s OK , presumably . It ’ s not critical if we can ’ t
* find a key for deriving a previous gMSA password , for
* example .
*/
ret = LDB_SUCCESS ;
* root_key = empty_root_key ;
} else if ( ret ) {
goto out ;
} else {
/* Derive the password. */
status = gmsa_talloc_password_based_on_key_id (
tmp_ctx ,
key . gkid ,
current_time ,
key . root_key ,
account_sid ,
& password ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
ret = ldb_operr ( ldb ) ;
goto out ;
}
/*
* Initialize the obtained structure , and give it the
* appropriate memory context .
*/
ret = gmsa_obtained_root_key_steal ( root_key - > mem_ctx ,
key ,
password ,
root_key ) ;
if ( ret ) {
goto out ;
}
}
} break ;
case ROOT_KEY_NONE :
/* No key is available. */
break ;
case ROOT_KEY_OBTAINED :
/* The key has already been obtained. */
break ;
default :
ret = ldb_operr ( ldb ) ;
goto out ;
}
out :
TALLOC_FREE ( tmp_ctx ) ;
return ret ;
}
/*
* Get the password and update information associated with a root key . The
* caller * does not * own these structures ; the root key object retains
* ownership .
*/
static int gmsa_get_root_key (
struct ldb_context * ldb ,
const NTTIME current_time ,
const struct dom_sid * const account_sid ,
struct RootKey * root_key ,
struct gmsa_null_terminated_password * * password_out ,
struct gmsa_update_pwd_part * update_out )
{
int ret = LDB_SUCCESS ;
if ( password_out = = NULL ) {
ret = ldb_operr ( ldb ) ;
goto out ;
}
* password_out = NULL ;
if ( update_out ! = NULL ) {
* update_out = ( struct gmsa_update_pwd_part ) { } ;
}
/* Fetch the root key from the database and obtain the password. */
ret = gmsa_fetch_root_key ( ldb , current_time , root_key , account_sid ) ;
if ( ret ) {
goto out ;
}
switch ( root_key - > type ) {
case ROOT_KEY_NONE :
/* No key is available. */
break ;
case ROOT_KEY_OBTAINED :
* password_out = root_key - > u . obtained . password ;
if ( update_out ! = NULL ) {
* update_out = root_key - > u . obtained . key ;
}
break ;
default :
/* Unexpected. */
ret = ldb_operr ( ldb ) ;
goto out ;
}
out :
return ret ;
}
static int gmsa_system_update_password_id_req (
struct ldb_context * ldb ,
TALLOC_CTX * mem_ctx ,
const struct ldb_message * msg ,
const struct gmsa_update_pwd * new_pwd ,
2024-04-16 05:00:44 +03:00
const bool current_key_becomes_previous ,
2024-02-13 06:09:57 +03:00
struct ldb_request * * req_out )
{
TALLOC_CTX * tmp_ctx = NULL ;
const struct ldb_val * pwd_id_blob = ldb_msg_find_ldb_val (
msg , " msDS-ManagedPasswordId " ) ;
struct KeyEnvelope pwd_id ;
struct ldb_message * mod_msg = NULL ;
NTSTATUS status = NT_STATUS_OK ;
int ret = LDB_SUCCESS ;
tmp_ctx = talloc_new ( mem_ctx ) ;
if ( tmp_ctx = = NULL ) {
ret = ldb_oom ( ldb ) ;
goto out ;
}
/* Create a new ldb message. */
mod_msg = ldb_msg_new ( tmp_ctx ) ;
if ( mod_msg = = NULL ) {
ret = ldb_oom ( ldb ) ;
goto out ;
}
{
struct ldb_dn * dn = ldb_dn_copy ( mod_msg , msg - > dn ) ;
if ( dn = = NULL ) {
ret = ldb_oom ( ldb ) ;
goto out ;
}
mod_msg - > dn = dn ;
}
/* Get the Managed Password ID. */
status = gmsa_managed_pwd_id (
ldb , tmp_ctx , pwd_id_blob , new_pwd - > new_id . root_key , & pwd_id ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
ret = ldb_operr ( ldb ) ;
goto out ;
}
/* Update the password ID to contain the new GKID and root key ID. */
gmsa_update_managed_pwd_id ( & pwd_id , & new_pwd - > new_id ) ;
{
DATA_BLOB new_pwd_id_blob = { } ;
/* Pack the current password ID. */
status = gmsa_pack_managed_pwd_id ( tmp_ctx ,
& pwd_id ,
& new_pwd_id_blob ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
ret = ldb_operr ( ldb ) ;
goto out ;
}
/* Update the msDS-ManagedPasswordId attribute. */
ret = ldb_msg_append_steal_value ( mod_msg ,
" msDS-ManagedPasswordId " ,
& new_pwd_id_blob ,
LDB_FLAG_MOD_REPLACE ) ;
if ( ret ) {
goto out ;
}
}
{
DATA_BLOB * prev_pwd_id_blob = NULL ;
DATA_BLOB _prev_pwd_id_blob ;
DATA_BLOB prev_pwd_id = { } ;
if ( new_pwd - > prev_id . root_key ! = NULL ) {
/*
* Update the password ID to contain the previous GKID
* and root key ID .
*/
gmsa_update_managed_pwd_id ( & pwd_id , & new_pwd - > prev_id ) ;
/* Pack the previous password ID. */
status = gmsa_pack_managed_pwd_id ( tmp_ctx ,
& pwd_id ,
& prev_pwd_id ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
ret = ldb_operr ( ldb ) ;
goto out ;
}
prev_pwd_id_blob = & prev_pwd_id ;
2024-04-16 05:00:44 +03:00
} else if ( current_key_becomes_previous & & pwd_id_blob ! = NULL )
{
2024-02-13 06:09:57 +03:00
/* Copy the current password ID to the previous ID. */
_prev_pwd_id_blob = ldb_val_dup ( tmp_ctx , pwd_id_blob ) ;
if ( _prev_pwd_id_blob . length ! = pwd_id_blob - > length ) {
ret = ldb_oom ( ldb ) ;
goto out ;
}
prev_pwd_id_blob = & _prev_pwd_id_blob ;
}
if ( prev_pwd_id_blob ! = NULL ) {
/*
* Update the msDS - ManagedPasswordPreviousId attribute .
*/
ret = ldb_msg_append_steal_value (
mod_msg ,
" msDS-ManagedPasswordPreviousId " ,
prev_pwd_id_blob ,
LDB_FLAG_MOD_REPLACE ) ;
if ( ret ) {
goto out ;
}
}
}
{
struct ldb_request * req = NULL ;
/* Build the ldb request to return. */
ret = ldb_build_mod_req ( & req ,
ldb ,
tmp_ctx ,
mod_msg ,
NULL ,
NULL ,
ldb_op_default_callback ,
NULL ) ;
if ( ret ) {
goto out ;
}
/* Tie the lifetime of the message to that of the request. */
talloc_steal ( req , mod_msg ) ;
/* Make sure the password ID update happens as System. */
ret = dsdb_request_add_controls ( req , DSDB_FLAG_AS_SYSTEM ) ;
if ( ret ) {
goto out ;
}
* req_out = talloc_steal ( mem_ctx , req ) ;
}
out :
talloc_free ( tmp_ctx ) ;
return ret ;
}
int gmsa_generate_blobs ( struct ldb_context * ldb ,
TALLOC_CTX * mem_ctx ,
const NTTIME current_time ,
const struct dom_sid * const account_sid ,
DATA_BLOB * pwd_id_blob_out ,
struct gmsa_null_terminated_password * * password_out )
{
TALLOC_CTX * tmp_ctx = NULL ;
struct KeyEnvelope pwd_id ;
const struct ProvRootKey * root_key = NULL ;
NTSTATUS status = NT_STATUS_OK ;
int ret = LDB_SUCCESS ;
tmp_ctx = talloc_new ( mem_ctx ) ;
if ( tmp_ctx = = NULL ) {
ret = ldb_oom ( ldb ) ;
goto out ;
}
{
const struct ldb_message * root_key_msg = NULL ;
struct GUID root_key_id ;
const NTTIME one_interval = gkdi_key_cycle_duration +
gkdi_max_clock_skew ;
const NTTIME one_interval_ago = current_time -
MIN ( one_interval , current_time ) ;
ret = gkdi_most_recently_created_root_key ( tmp_ctx ,
ldb ,
current_time ,
one_interval_ago ,
& root_key_id ,
& root_key_msg ) ;
if ( ret ) {
goto out ;
}
status = gkdi_root_key_from_msg ( tmp_ctx ,
root_key_id ,
root_key_msg ,
& root_key ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
ret = ldb_operr ( ldb ) ;
goto out ;
}
}
/* Get the Managed Password ID. */
status = gmsa_managed_pwd_id ( ldb , tmp_ctx , NULL , root_key , & pwd_id ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
ret = ldb_operr ( ldb ) ;
goto out ;
}
{
const struct Gkid current_gkid = gkdi_get_interval_id (
current_time ) ;
/* Derive the password. */
status = gmsa_talloc_password_based_on_key_id ( tmp_ctx ,
current_gkid ,
current_time ,
root_key ,
account_sid ,
password_out ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
ret = ldb_operr ( ldb ) ;
goto out ;
}
{
const struct gmsa_update_pwd_part new_id = {
. root_key = root_key ,
. gkid = current_gkid ,
} ;
/*
* Update the password ID to contain the new GKID and
* root key ID .
*/
gmsa_update_managed_pwd_id ( & pwd_id , & new_id ) ;
}
}
/* Pack the current password ID. */
status = gmsa_pack_managed_pwd_id ( mem_ctx , & pwd_id , pwd_id_blob_out ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
ret = ldb_operr ( ldb ) ;
goto out ;
}
/* Transfer ownership of the password to the caller’ s memory context. */
talloc_steal ( mem_ctx , * password_out ) ;
out :
talloc_free ( tmp_ctx ) ;
return ret ;
}
static int gmsa_create_update ( TALLOC_CTX * mem_ctx ,
struct ldb_context * ldb ,
const struct ldb_message * msg ,
const NTTIME current_time ,
const struct dom_sid * account_sid ,
const bool current_key_becomes_previous ,
struct RootKey * current_key ,
struct RootKey * previous_key ,
struct gmsa_update * * update_out )
{
TALLOC_CTX * tmp_ctx = NULL ;
2024-04-16 05:03:36 +03:00
const DATA_BLOB * found_pwd_id = NULL ;
2024-02-13 06:09:57 +03:00
struct ldb_request * old_pw_req = NULL ;
struct ldb_request * new_pw_req = NULL ;
struct ldb_request * pwd_id_req = NULL ;
2024-04-16 05:03:05 +03:00
struct ldb_dn * account_dn = NULL ;
2024-02-13 06:09:57 +03:00
struct gmsa_update_pwd new_pwd = { } ;
struct gmsa_update * update = NULL ;
NTSTATUS status = NT_STATUS_OK ;
int ret = LDB_SUCCESS ;
if ( update_out = = NULL ) {
ret = ldb_operr ( ldb ) ;
goto out ;
}
* update_out = NULL ;
if ( current_key = = NULL ) {
ret = ldb_operr ( ldb ) ;
goto out ;
}
tmp_ctx = talloc_new ( mem_ctx ) ;
if ( tmp_ctx = = NULL ) {
ret = ldb_oom ( ldb ) ;
goto out ;
}
{
/*
* The password_hash module expects these passwords to be
* null ‐ terminated .
*/
struct gmsa_null_terminated_password * new_password = NULL ;
ret = gmsa_get_root_key ( ldb ,
current_time ,
account_sid ,
current_key ,
& new_password ,
& new_pwd . new_id ) ;
if ( ret ) {
goto out ;
}
if ( new_password = = NULL ) {
ret = ldb_operr ( ldb ) ;
goto out ;
}
status = gmsa_system_password_update_request (
ldb , tmp_ctx , msg - > dn , new_password - > buf , & new_pw_req ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
ret = ldb_operr ( ldb ) ;
goto out ;
}
}
/* Does the previous password need to be updated? */
if ( current_key_becomes_previous ) {
/*
* When we perform the password set , the now ‐ current password
* will become the previous password automatically . We don ’ t
* have to manage that ourselves .
*/
} else {
struct gmsa_null_terminated_password * old_password = NULL ;
/* The current key cannot be reused as the previous key. */
ret = gmsa_get_root_key ( ldb ,
current_time ,
account_sid ,
previous_key ,
& old_password ,
& new_pwd . prev_id ) ;
if ( ret ) {
goto out ;
}
if ( old_password ! = NULL ) {
status = gmsa_system_password_update_request (
ldb ,
tmp_ctx ,
msg - > dn ,
old_password - > buf ,
& old_pw_req ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
ret = ldb_operr ( ldb ) ;
goto out ;
}
}
}
/* Ready the update of the msDS-ManagedPasswordId attribute. */
2024-04-16 05:00:44 +03:00
ret = gmsa_system_update_password_id_req ( ldb ,
tmp_ctx ,
msg ,
& new_pwd ,
current_key_becomes_previous ,
& pwd_id_req ) ;
2024-02-13 06:09:57 +03:00
if ( ret ) {
goto out ;
}
2024-04-16 05:03:36 +03:00
{
/*
* Remember the original managed password ID so that we can
* confirm it hasn ’ t changed when we perform the update .
*/
const struct ldb_val * pwd_id_blob = ldb_msg_find_ldb_val (
msg , " msDS-ManagedPasswordId " ) ;
if ( pwd_id_blob ! = NULL ) {
DATA_BLOB found_pwd_id_data = { } ;
DATA_BLOB * found_pwd_id_blob = NULL ;
found_pwd_id_blob = talloc ( tmp_ctx , DATA_BLOB ) ;
if ( found_pwd_id_blob = = NULL ) {
ret = ldb_oom ( ldb ) ;
goto out ;
}
found_pwd_id_data = data_blob_dup_talloc (
found_pwd_id_blob , * pwd_id_blob ) ;
if ( found_pwd_id_data . length ! = pwd_id_blob - > length ) {
ret = ldb_oom ( ldb ) ;
goto out ;
}
* found_pwd_id_blob = found_pwd_id_data ;
found_pwd_id = found_pwd_id_blob ;
}
}
2024-04-16 05:03:05 +03:00
account_dn = ldb_dn_copy ( tmp_ctx , msg - > dn ) ;
if ( account_dn = = NULL ) {
ret = ldb_oom ( ldb ) ;
goto out ;
}
2024-02-13 06:09:57 +03:00
update = talloc ( tmp_ctx , struct gmsa_update ) ;
if ( update = = NULL ) {
ret = ldb_oom ( ldb ) ;
goto out ;
}
* update = ( struct gmsa_update ) {
2024-04-16 05:03:05 +03:00
. dn = talloc_steal ( update , account_dn ) ,
2024-04-16 05:03:36 +03:00
. found_pwd_id = talloc_steal ( update , found_pwd_id ) ,
2024-02-13 06:09:57 +03:00
. old_pw_req = talloc_steal ( update , old_pw_req ) ,
. new_pw_req = talloc_steal ( update , new_pw_req ) ,
. pwd_id_req = talloc_steal ( update , pwd_id_req ) } ;
* update_out = talloc_steal ( mem_ctx , update ) ;
out :
TALLOC_FREE ( tmp_ctx ) ;
return ret ;
}
NTSTATUS gmsa_pack_managed_pwd ( TALLOC_CTX * mem_ctx ,
const uint8_t * new_password ,
const uint8_t * old_password ,
uint64_t query_interval ,
uint64_t unchanged_interval ,
DATA_BLOB * managed_pwd_out )
{
const struct MANAGEDPASSWORD_BLOB managed_pwd = {
. passwords = { . current = new_password ,
. previous = old_password ,
. query_interval = & query_interval ,
. unchanged_interval = & unchanged_interval } } ;
NTSTATUS status = NT_STATUS_OK ;
enum ndr_err_code err ;
err = ndr_push_struct_blob ( managed_pwd_out ,
mem_ctx ,
& managed_pwd ,
( ndr_push_flags_fn_t )
ndr_push_MANAGEDPASSWORD_BLOB ) ;
status = ndr_map_error2ntstatus ( err ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
DBG_WARNING ( " MANAGEDPASSWORD_BLOB push failed: %s \n " ,
nt_errstr ( status ) ) ;
}
return status ;
}
bool dsdb_account_is_gmsa ( struct ldb_context * ldb ,
const struct ldb_message * msg )
{
/*
* Check if the account has objectClass
* ‘ msDS - GroupManagedServiceAccount ’ .
*/
return samdb_find_attribute ( ldb ,
msg ,
" objectclass " ,
" msDS-GroupManagedServiceAccount " ) ! = NULL ;
}
static struct new_key {
NTTIME start_time ;
bool immediately_follows_previous ;
} calculate_new_key ( const NTTIME current_time ,
const NTTIME current_key_expiration_time ,
const NTTIME rollover_interval )
{
NTTIME new_key_start_time = current_key_expiration_time ;
bool immediately_follows_previous = false ;
if ( new_key_start_time < current_time & & rollover_interval ) {
/*
* Advance the key start time by the rollover interval until it
* would be greater than the current time .
*/
const NTTIME time_to_advance_by = current_time + 1 -
new_key_start_time ;
const uint64_t stale_count = time_to_advance_by /
rollover_interval ;
new_key_start_time + = stale_count * rollover_interval ;
SMB_ASSERT ( new_key_start_time < = current_time ) ;
immediately_follows_previous = stale_count = = 0 ;
} else {
/*
* It is possible that new_key_start_time ≥ current_time ;
* specifically , if there is no password ID , and the creation
* time of the gMSA is in the future ( perhaps due to replication
* weirdness ) .
*/
}
return ( struct new_key ) {
. start_time = new_key_start_time ,
. immediately_follows_previous = immediately_follows_previous } ;
}
static bool gmsa_creation_time ( const struct ldb_message * msg ,
const NTTIME current_time ,
NTTIME * creation_time_out )
{
const struct ldb_val * when_created = NULL ;
time_t creation_unix_time ;
int ret ;
when_created = ldb_msg_find_ldb_val ( msg , " whenCreated " ) ;
ret = ldb_val_to_time ( when_created , & creation_unix_time ) ;
if ( ret ) {
/* Fail if we can’ t read the attribute or it isn’ t present. */
return false ;
}
unix_to_nt_time ( creation_time_out , creation_unix_time ) ;
return true ;
}
static const struct KeyEnvelopeId * gmsa_get_managed_pwd_id_attr_name (
const struct ldb_message * msg ,
const char * attr_name ,
struct KeyEnvelopeId * key_env_out )
{
const struct ldb_val * pwd_id_blob = ldb_msg_find_ldb_val ( msg ,
attr_name ) ;
if ( pwd_id_blob = = NULL ) {
return NULL ;
}
return gkdi_pull_KeyEnvelopeId ( * pwd_id_blob , key_env_out ) ;
}
const struct KeyEnvelopeId * gmsa_get_managed_pwd_id (
const struct ldb_message * msg ,
struct KeyEnvelopeId * key_env_out )
{
return gmsa_get_managed_pwd_id_attr_name ( msg ,
" msDS-ManagedPasswordId " ,
key_env_out ) ;
}
static const struct KeyEnvelopeId * gmsa_get_managed_pwd_prev_id (
const struct ldb_message * msg ,
struct KeyEnvelopeId * key_env_out )
{
return gmsa_get_managed_pwd_id_attr_name (
msg , " msDS-ManagedPasswordPreviousId " , key_env_out ) ;
}
static bool samdb_result_gkdi_rollover_interval ( const struct ldb_message * msg ,
NTTIME * rollover_interval_out )
{
int64_t managed_password_interval ;
managed_password_interval = ldb_msg_find_attr_as_int64 (
msg , " msDS-ManagedPasswordInterval " , 30 ) ;
return gkdi_rollover_interval ( managed_password_interval ,
rollover_interval_out ) ;
}
2024-04-15 05:46:47 +03:00
bool samdb_gmsa_key_is_recent ( const struct ldb_message * msg ,
const NTTIME current_time )
{
const struct KeyEnvelopeId * pwd_id = NULL ;
struct KeyEnvelopeId pwd_id_buf ;
NTTIME key_start_time ;
bool ok ;
pwd_id = gmsa_get_managed_pwd_id ( msg , & pwd_id_buf ) ;
if ( pwd_id = = NULL ) {
return false ;
}
ok = gkdi_get_key_start_time ( pwd_id - > gkid , & key_start_time ) ;
if ( ! ok ) {
return false ;
}
if ( current_time < key_start_time ) {
return false ;
}
return current_time - key_start_time < gkdi_max_clock_skew ;
}
2024-04-17 04:27:19 +03:00
/*
* Recalculate the managed password of an account . The account referred to by
* ‘ msg ’ should be a Group Managed Service Account .
*
* Updated passwords are returned in ‘ update_out ’ .
*
* Pass in a non ‐ NULL pointer for ‘ return_out ’ if you want the passwords as
* reflected by the msDS - ManagedPassword operational attribute .
*/
2024-02-13 06:09:57 +03:00
int gmsa_recalculate_managed_pwd ( TALLOC_CTX * mem_ctx ,
struct ldb_context * ldb ,
const struct ldb_message * msg ,
const NTTIME current_time ,
struct gmsa_update * * update_out ,
struct gmsa_return_pwd * return_out )
{
TALLOC_CTX * tmp_ctx = NULL ;
int ret = LDB_SUCCESS ;
NTTIME rollover_interval ;
NTTIME current_key_expiration_time ;
NTTIME key_expiration_time ;
struct dom_sid account_sid ;
struct KeyEnvelopeId pwd_id_buf ;
const struct KeyEnvelopeId * pwd_id = NULL ;
struct RootKey previous_key = empty_root_key ;
struct RootKey current_key = empty_root_key ;
struct gmsa_update * update = NULL ;
struct gmsa_null_terminated_password * previous_password = NULL ;
struct gmsa_null_terminated_password * current_password = NULL ;
NTTIME query_interval = 0 ;
NTTIME unchanged_interval = 0 ;
NTTIME creation_time = 0 ;
NTTIME account_age = 0 ;
NTTIME key_start_time = 0 ;
bool have_key_start_time = false ;
bool ok = true ;
bool current_key_is_valid = false ;
if ( update_out = = NULL ) {
ret = ldb_operr ( ldb ) ;
goto out ;
}
* update_out = NULL ;
/* Calculate the rollover interval. */
ok = samdb_result_gkdi_rollover_interval ( msg , & rollover_interval ) ;
if ( ! ok | | rollover_interval = = 0 ) {
/* We can’ t do anything if the rollover interval is zero. */
ret = ldb_operr ( ldb ) ;
goto out ;
}
ok = gmsa_creation_time ( msg , current_time , & creation_time ) ;
if ( ! ok ) {
return ldb_error ( ldb ,
LDB_ERR_OPERATIONS_ERROR ,
" unable to determine creation time of Group "
" Managed Service Account " ) ;
}
account_age = current_time - MIN ( creation_time , current_time ) ;
/* Calculate the expiration time of the current key. */
pwd_id = gmsa_get_managed_pwd_id ( msg , & pwd_id_buf ) ;
if ( pwd_id ! = NULL & &
gkdi_get_key_start_time ( pwd_id - > gkid , & key_start_time ) )
{
have_key_start_time = true ;
/* Check for overflow. */
if ( key_start_time > UINT64_MAX - rollover_interval ) {
ret = ldb_operr ( ldb ) ;
goto out ;
}
current_key_expiration_time = key_start_time +
rollover_interval ;
} else {
/*
* [ MS - ADTS ] does not say to use gkdi_get_interval_start_time ( ) ,
* but surely it makes no sense to have keys starting or ending
* at random times .
*/
current_key_expiration_time = gkdi_get_interval_start_time (
creation_time ) ;
}
/* Fetch the account’ s SID, necessary for deriving passwords. */
ret = samdb_result_dom_sid_buf ( msg , " objectSid " , & account_sid ) ;
if ( ret ) {
goto out ;
}
tmp_ctx = talloc_new ( mem_ctx ) ;
if ( tmp_ctx = = NULL ) {
ret = ldb_oom ( ldb ) ;
goto out ;
}
/*
* In determining whether the account ’ s passwords should be updated , we
* do not validate that the unicodePwd attribute is up ‐ to ‐ date , or even
* that it exists . We rely entirely on the fact that the managed
* password ID should be updated * only * as part of a successful gMSA
* password update . In any case , unicodePwd is optional in Samba — save
* for machine accounts ( which gMSAs are : ) ) — and we can ’ t always rely
* on its presence .
*
* All this means that an admin ( or a DC that doesn ’ t support gMSAs )
* could reset a gMSA ’ s password outside of the normal procedure , and
* the account would then have the wrong password until the key was due
* to roll over again . There ’ s nothing much we can do about this if we
* don ’ t want to re ‐ derive and verify the password every time we look up
* the keys .
*/
2024-04-16 04:58:15 +03:00
/*
* Administrators should be careful not to set a DC ’ s clock too far in
* the future , or a gMSA ’ s keys may be stuck at that future time and
* stop updating until said time rolls around for real .
*/
2024-02-13 06:09:57 +03:00
current_key_is_valid = pwd_id ! = NULL & &
current_time < current_key_expiration_time ;
if ( current_key_is_valid ) {
key_expiration_time = current_key_expiration_time ;
if ( return_out ! = NULL ) {
struct KeyEnvelopeId prev_pwd_id_buf ;
const struct KeyEnvelopeId * prev_pwd_id = NULL ;
ret = gmsa_specifc_root_key ( tmp_ctx ,
* pwd_id ,
& current_key ) ;
if ( ret ) {
goto out ;
}
if ( account_age > = rollover_interval ) {
prev_pwd_id = gmsa_get_managed_pwd_prev_id (
msg , & prev_pwd_id_buf ) ;
if ( prev_pwd_id ! = NULL ) {
ret = gmsa_specifc_root_key (
tmp_ctx ,
* prev_pwd_id ,
& previous_key ) ;
if ( ret ) {
goto out ;
}
} else if ( have_key_start_time & &
key_start_time > = rollover_interval )
{
/*
* The account ’ s old enough to have a
* previous password , but it doesn ’ t
* have a previous password ID for some
* reason . This can happen in our tests
* ( python / samba / krb5 / gmsa_tests . py )
* when we ’ re mucking about with times .
* Just produce what would have been the
* previous key .
*/
ret = gmsa_nonspecifc_root_key (
tmp_ctx ,
key_start_time -
rollover_interval ,
& previous_key ) ;
if ( ret ) {
goto out ;
}
}
} else {
/*
* The account is not old enough to have a
* previous password . The old password will not
* be returned .
*/
}
}
} else {
/* Calculate the start time of the new key. */
const struct new_key new_key = calculate_new_key (
current_time ,
current_key_expiration_time ,
rollover_interval ) ;
const bool current_key_becomes_previous =
pwd_id ! = NULL & & new_key . immediately_follows_previous ;
/* Check for overflow. */
if ( new_key . start_time > UINT64_MAX - rollover_interval ) {
ret = ldb_operr ( ldb ) ;
goto out ;
}
key_expiration_time = new_key . start_time + rollover_interval ;
ret = gmsa_nonspecifc_root_key ( tmp_ctx ,
new_key . start_time ,
& current_key ) ;
if ( ret ) {
goto out ;
}
if ( account_age > = rollover_interval ) {
/* Check for underflow. */
if ( new_key . start_time < rollover_interval ) {
ret = ldb_operr ( ldb ) ;
goto out ;
}
ret = gmsa_nonspecifc_root_key (
tmp_ctx ,
new_key . start_time - rollover_interval ,
& previous_key ) ;
if ( ret ) {
goto out ;
}
} else {
/*
* The account is not old enough to have a previous
* password . The old password will not be returned .
*/
}
/*
* The current GMSA key , according to the Managed Password ID ,
* is no longer valid . We should update the account ’ s Managed
* Password ID and keys in anticipation of their being needed in
* the near future .
*/
ret = gmsa_create_update ( tmp_ctx ,
ldb ,
msg ,
current_time ,
& account_sid ,
current_key_becomes_previous ,
& current_key ,
& previous_key ,
& update ) ;
if ( ret ) {
goto out ;
}
}
if ( return_out ! = NULL ) {
bool return_future_key ;
unchanged_interval = query_interval = key_expiration_time -
MIN ( current_time ,
key_expiration_time ) ;
/* Derive the current and previous passwords. */
return_future_key = query_interval < = gkdi_max_clock_skew ;
if ( return_future_key ) {
struct RootKey future_key = empty_root_key ;
/*
* The current key hasn ’ t expired yet , but it
* soon will . Return a new key that will be valid in the
* next epoch .
*/
ret = gmsa_nonspecifc_root_key ( tmp_ctx ,
key_expiration_time ,
& future_key ) ;
if ( ret ) {
goto out ;
}
ret = gmsa_get_root_key ( ldb ,
current_time ,
& account_sid ,
& future_key ,
& current_password ,
NULL ) ;
if ( ret ) {
goto out ;
}
ret = gmsa_get_root_key ( ldb ,
current_time ,
& account_sid ,
& current_key ,
& previous_password ,
NULL ) ;
if ( ret ) {
goto out ;
}
/* Check for overflow. */
if ( unchanged_interval > UINT64_MAX - rollover_interval )
{
ret = ldb_operr ( ldb ) ;
goto out ;
}
unchanged_interval + = rollover_interval ;
} else {
/*
* Note that a gMSA will become unusable ( at least until
* the next rollover ) if its associated root key is ever
* deleted .
*/
ret = gmsa_get_root_key ( ldb ,
current_time ,
& account_sid ,
& current_key ,
& current_password ,
NULL ) ;
if ( ret ) {
goto out ;
}
ret = gmsa_get_root_key ( ldb ,
current_time ,
& account_sid ,
& previous_key ,
& previous_password ,
NULL ) ;
if ( ret ) {
goto out ;
}
}
unchanged_interval - = MIN ( gkdi_max_clock_skew ,
unchanged_interval ) ;
}
* update_out = talloc_steal ( mem_ctx , update ) ;
if ( return_out ! = NULL ) {
* return_out = ( struct gmsa_return_pwd ) {
. prev_pwd = talloc_steal ( mem_ctx , previous_password ) ,
. new_pwd = talloc_steal ( mem_ctx , current_password ) ,
. query_interval = query_interval ,
. unchanged_interval = unchanged_interval ,
} ;
}
out :
TALLOC_FREE ( tmp_ctx ) ;
return ret ;
}
2024-02-13 06:09:57 +03:00
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 ;
}
}
2024-04-23 07:17:04 +03:00
int dsdb_update_gmsa_entry_keys ( TALLOC_CTX * mem_ctx ,
struct ldb_context * ldb ,
2024-02-13 06:09:57 +03:00
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 ;
}
2024-04-23 07:17:04 +03:00
int dsdb_update_gmsa_keys ( TALLOC_CTX * mem_ctx ,
struct ldb_context * ldb ,
2024-02-13 06:09:57 +03:00
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 ;
2024-03-05 06:18:34 +03:00
/*
* This is non - zero if we are local to the sam . ldb , this is an
* opaque set by the samba_dsdb module
*/
void * samba_dsdb_opaque = ldb_get_opaque (
ldb , DSDB_OPAQUE_PARTITION_MODULE_MSG_OPAQUE_NAME ) ;
if ( samba_dsdb_opaque = = NULL ) {
/*
* We are not connected locally , so no point trying to
* set passwords
*/
* retry_out = false ;
return LDB_SUCCESS ;
}
2024-02-13 06:09:57 +03:00
{
/* 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 ;
}
2024-04-23 07:17:04 +03:00
ret = dsdb_update_gmsa_entry_keys ( tmp_ctx ,
ldb ,
gmsa_update ) ;
2024-02-13 06:09:57 +03:00
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 ;
}
2024-02-13 06:09:57 +03:00
bool dsdb_gmsa_current_time ( struct ldb_context * ldb , NTTIME * current_time_out )
{
const unsigned long long * gmsa_time = talloc_get_type (
ldb_get_opaque ( ldb , DSDB_GMSA_TIME_OPAQUE ) , unsigned long long ) ;
if ( gmsa_time ! = NULL ) {
* current_time_out = * gmsa_time ;
return true ;
}
return gmsa_current_time ( current_time_out ) ;
}
2024-05-28 03:53:19 +03:00
/* Set the current time. Caller to supply valid unsigned long long talloc pointer and manage lifetime */
bool dsdb_gmsa_set_current_time ( struct ldb_context * ldb , unsigned long long * current_time_talloc )
{
int ret = ldb_set_opaque ( ldb , DSDB_GMSA_TIME_OPAQUE , current_time_talloc ) ;
if ( ret ! = LDB_SUCCESS ) {
return false ;
}
return true ;
}