0
# Active Directory Integration
1
2
Comprehensive Windows Active Directory support including Security Identifiers (SID), Access Control Lists (ACL), Security Descriptors, and User Account Control flags. These components enable working with Windows-specific LDAP attributes and security structures.
3
4
## Capabilities
5
6
### Security Identifiers (SID)
7
8
Windows Security Identifier handling for users, groups, and computer accounts with string and binary format conversion.
9
10
```python { .api }
11
from bonsai.active_directory import SID
12
13
class SID:
14
def __init__(
15
self,
16
str_rep: Optional[str] = None,
17
bytes_le: Optional[bytes] = None
18
) -> None:
19
"""
20
Initialize SID from string or binary representation.
21
22
Parameters:
23
- str_rep: String SID format (e.g., "S-1-5-21-1234567890-987654321-1111111111-1001")
24
- bytes_le: Binary SID in little-endian byte order
25
26
Note: Exactly one parameter must be provided
27
"""
28
29
def __str__(self) -> str:
30
"""Convert SID to standard string format."""
31
32
def __eq__(self, other) -> bool:
33
"""Compare SIDs for equality."""
34
35
def __hash__(self) -> int:
36
"""Hash SID for use in sets and dictionaries."""
37
38
@property
39
def revision(self) -> int:
40
"""SID revision number (usually 1)."""
41
42
@property
43
def identifier_authority(self) -> int:
44
"""Top-level authority that issued the SID."""
45
46
@property
47
def subauthorities(self) -> Tuple[int, ...]:
48
"""Tuple of subauthority values."""
49
50
@property
51
def bytes_le(self) -> bytes:
52
"""Binary representation in little-endian byte order."""
53
54
def is_well_known(self) -> bool:
55
"""Check if SID is a well-known Windows SID."""
56
57
def get_account_domain_sid(self) -> Optional["SID"]:
58
"""
59
Get the domain SID portion by removing the RID.
60
61
Returns:
62
Domain SID if this is an account SID, None otherwise
63
"""
64
65
def get_rid(self) -> Optional[int]:
66
"""
67
Get Relative Identifier (RID) - the last subauthority.
68
69
Returns:
70
RID value if this is an account SID, None otherwise
71
"""
72
73
@property
74
def sddl_alias(self) -> Optional[str]:
75
"""SDDL alias for well-known SIDs (e.g., 'BA' for Administrators)."""
76
77
@property
78
def size(self) -> int:
79
"""Size of SID in bytes."""
80
```
81
82
### Access Control Lists (ACL)
83
84
Windows Access Control List implementation with support for Discretionary (DACL) and System (SACL) access control lists.
85
86
```python { .api }
87
from bonsai.active_directory import ACL, ACE, ACLRevision, ACEFlag, ACERight, ACEType
88
from enum import IntEnum
89
90
class ACLRevision(IntEnum):
91
ACL_REVISION = 2
92
ACL_REVISION_DS = 4
93
94
class ACEType(IntEnum):
95
ACCESS_ALLOWED = 0
96
ACCESS_DENIED = 1
97
SYSTEM_AUDIT = 2
98
SYSTEM_ALARM = 3
99
ACCESS_ALLOWED_COMPOUND = 4
100
ACCESS_ALLOWED_OBJECT = 5
101
ACCESS_DENIED_OBJECT = 6
102
SYSTEM_AUDIT_OBJECT = 7
103
SYSTEM_ALARM_OBJECT = 8
104
ACCESS_ALLOWED_CALLBACK = 9
105
ACCESS_DENIED_CALLBACK = 10
106
ACCESS_ALLOWED_CALLBACK_OBJECT = 11
107
ACCESS_DENIED_CALLBACK_OBJECT = 12
108
SYSTEM_AUDIT_CALLBACK = 13
109
SYSTEM_ALARM_CALLBACK = 14
110
SYSTEM_AUDIT_CALLBACK_OBJECT = 15
111
SYSTEM_ALARM_CALLBACK_OBJECT = 16
112
113
class ACEFlag(IntEnum):
114
OBJECT_INHERIT = 0x01
115
CONTAINER_INHERIT = 0x02
116
NO_PROPAGATE_INHERIT = 0x04
117
INHERIT_ONLY = 0x08
118
INHERITED = 0x10
119
SUCCESSFUL_ACCESS = 0x40
120
FAILED_ACCESS = 0x80
121
122
class ACERight(IntEnum):
123
GENERIC_READ = 0x80000000
124
GENERIC_WRITE = 0x40000000
125
GENERIC_EXECUTE = 0x20000000
126
GENERIC_ALL = 0x10000000
127
DELETE = 0x00010000
128
READ_CONTROL = 0x00020000
129
WRITE_DAC = 0x00040000
130
WRITE_OWNER = 0x00080000
131
SYNCHRONIZE = 0x00100000
132
133
class ACE:
134
def __init__(
135
self,
136
ace_type: ACEType,
137
ace_flags: int,
138
access_mask: int,
139
sid: SID,
140
object_type: Optional[str] = None,
141
inherited_object_type: Optional[str] = None
142
) -> None:
143
"""
144
Initialize Access Control Entry.
145
146
Parameters:
147
- ace_type: Type of ACE (allow, deny, audit, etc.)
148
- ace_flags: ACE flags (inheritance, audit success/failure)
149
- access_mask: Access rights mask
150
- sid: Security identifier for the principal
151
- object_type: GUID for object-specific ACE (optional)
152
- inherited_object_type: GUID for inheritance (optional)
153
"""
154
155
@classmethod
156
def from_binary(cls, data: bytes) -> "ACE":
157
"""
158
Create ACE from binary data.
159
160
Parameters:
161
- data: Binary ACE data
162
163
Returns:
164
ACE object
165
"""
166
167
def to_binary(self) -> bytes:
168
"""
169
Convert ACE to binary format.
170
171
Returns:
172
Binary representation of ACE
173
"""
174
175
@property
176
def ace_type(self) -> ACEType:
177
"""Type of access control entry."""
178
179
@property
180
def ace_flags(self) -> int:
181
"""ACE flags bitfield."""
182
183
@property
184
def access_mask(self) -> int:
185
"""Access rights mask."""
186
187
@property
188
def sid(self) -> SID:
189
"""Security identifier."""
190
191
@property
192
def object_type(self) -> Optional[str]:
193
"""Object type GUID for object-specific ACEs."""
194
195
@property
196
def inherited_object_type(self) -> Optional[str]:
197
"""Inherited object type GUID."""
198
199
@property
200
def short_name(self) -> str:
201
"""Short name for ACE type (for SDDL format)."""
202
203
@property
204
def is_object_type(self) -> bool:
205
"""Whether this ACE type supports object-specific permissions."""
206
207
class ACL:
208
def __init__(self, revision: ACLRevision = ACLRevision.ACL_REVISION) -> None:
209
"""
210
Initialize Access Control List.
211
212
Parameters:
213
- revision: ACL revision (ACL_REVISION or ACL_REVISION_DS)
214
"""
215
216
@classmethod
217
def from_binary(cls, data: bytes) -> "ACL":
218
"""
219
Create ACL from binary data.
220
221
Parameters:
222
- data: Binary ACL data
223
224
Returns:
225
ACL object
226
"""
227
228
def to_binary(self) -> bytes:
229
"""
230
Convert ACL to binary format.
231
232
Returns:
233
Binary representation of ACL
234
"""
235
236
def add_ace(self, ace: ACE, index: Optional[int] = None) -> None:
237
"""
238
Add ACE to the ACL.
239
240
Parameters:
241
- ace: ACE object to add
242
- index: Position to insert ACE (None to append)
243
"""
244
245
def remove_ace(self, index: int) -> None:
246
"""
247
Remove ACE from ACL by index.
248
249
Parameters:
250
- index: Index of ACE to remove
251
"""
252
253
def __len__(self) -> int:
254
"""Number of ACEs in the ACL."""
255
256
def __getitem__(self, index: int) -> ACE:
257
"""Get ACE by index."""
258
259
def __iter__(self):
260
"""Iterate over ACEs."""
261
262
@property
263
def revision(self) -> ACLRevision:
264
"""ACL revision."""
265
266
@property
267
def aces(self) -> List[ACE]:
268
"""List of Access Control Entries."""
269
```
270
271
### Security Descriptors
272
273
Windows Security Descriptor handling with support for owner, group, DACL, and SACL components.
274
275
```python { .api }
276
from bonsai.active_directory import SecurityDescriptor
277
278
class SecurityDescriptor:
279
def __init__(
280
self,
281
control: Dict[str, bool],
282
owner_sid: Optional[SID],
283
group_sid: Optional[SID],
284
sacl: Optional[ACL],
285
dacl: Optional[ACL],
286
revision: int = 1,
287
sbz1: int = 0,
288
) -> None:
289
"""
290
Initialize Security Descriptor.
291
292
Parameters:
293
- control: Control flags dictionary
294
- owner_sid: Owner security identifier
295
- group_sid: Group security identifier
296
- sacl: System Access Control List (for auditing)
297
- dacl: Discretionary Access Control List (for access control)
298
- revision: Security descriptor revision
299
- sbz1: Reserved field
300
"""
301
302
@classmethod
303
def from_binary(cls, data: bytes) -> "SecurityDescriptor":
304
"""
305
Create SecurityDescriptor from binary data.
306
307
Parameters:
308
- data: Binary security descriptor data in little-endian format
309
310
Returns:
311
SecurityDescriptor object
312
"""
313
314
def to_binary(self) -> bytes:
315
"""
316
Convert SecurityDescriptor to binary format.
317
318
Returns:
319
Binary representation in little-endian byte order
320
"""
321
322
def set_control(self, control: Union[Dict[str, bool], int]) -> None:
323
"""
324
Set control flags for the security descriptor.
325
326
Parameters:
327
- control: Control flags as dictionary or integer bitmask
328
"""
329
330
def set_owner_sid(self, owner_sid: SID) -> None:
331
"""Set owner SID."""
332
333
def set_group_sid(self, group_sid: SID) -> None:
334
"""Set group SID."""
335
336
def set_dacl(self, dacl: Optional[ACL]) -> None:
337
"""Set discretionary access control list."""
338
339
def set_sacl(self, sacl: Optional[ACL]) -> None:
340
"""Set system access control list."""
341
342
@property
343
def revision(self) -> int:
344
"""Security descriptor revision."""
345
346
@property
347
def control(self) -> Dict[str, bool]:
348
"""Control flags dictionary with keys like:
349
- owner_defaulted, group_defaulted
350
- dacl_present, dacl_defaulted, dacl_protected
351
- sacl_present, sacl_defaulted, sacl_protected
352
- self_relative, dacl_auto_inherited, sacl_auto_inherited
353
"""
354
355
@property
356
def owner_sid(self) -> Optional[SID]:
357
"""Owner security identifier."""
358
359
@property
360
def group_sid(self) -> Optional[SID]:
361
"""Group security identifier."""
362
363
@property
364
def dacl(self) -> Optional[ACL]:
365
"""Discretionary access control list."""
366
367
@property
368
def sacl(self) -> Optional[ACL]:
369
"""System access control list."""
370
```
371
372
### User Account Control
373
374
Parse and manipulate Windows User Account Control flags for user accounts.
375
376
```python { .api }
377
from bonsai.active_directory import UserAccountControl
378
379
class UserAccountControl:
380
def __init__(self, flags: int) -> None:
381
"""
382
Initialize User Account Control parser.
383
384
Parameters:
385
- flags: Integer value of userAccountControl attribute
386
"""
387
388
@property
389
def properties(self) -> Dict[str, bool]:
390
"""
391
Dictionary of user account properties including:
392
- script: Login script execution
393
- accountdisable: Account is disabled
394
- homedir_required: Home directory required
395
- lockout: Account is locked out
396
- passwd_notreqd: Password not required
397
- passwd_cant_change: User cannot change password
398
- encrypted_text_pwd_allowed: Encrypted text password allowed
399
- temp_duplicate_account: Temporary duplicate account
400
- normal_account: Normal user account
401
- interdomain_trust_account: Interdomain trust account
402
- workstation_trust_account: Workstation trust account
403
- server_trust_account: Server trust account
404
- dont_expire_password: Password doesn't expire
405
- mns_logon_account: MNS logon account
406
- smartcard_required: Smart card required for logon
407
- trusted_for_delegation: Account trusted for delegation
408
- not_delegated: Account cannot be delegated
409
- use_des_key_only: Use DES keys only
410
- dont_req_preauth: Kerberos pre-authentication not required
411
- password_expired: Password expired
412
- trusted_to_auth_for_delegation: Trusted to authenticate for delegation
413
- partial_secrets_account: Partial secrets account (RODC)
414
"""
415
416
@property
417
def value(self) -> int:
418
"""Integer value computed from properties."""
419
420
# Convenience properties for common flags
421
@property
422
def account_disabled(self) -> bool:
423
"""Account is disabled."""
424
425
@property
426
def password_not_required(self) -> bool:
427
"""Password not required."""
428
429
@property
430
def password_cant_change(self) -> bool:
431
"""User cannot change password."""
432
433
@property
434
def password_expired(self) -> bool:
435
"""Password has expired."""
436
437
@property
438
def account_locked_out(self) -> bool:
439
"""Account is locked out."""
440
441
@property
442
def normal_account(self) -> bool:
443
"""Normal user account."""
444
445
@property
446
def dont_expire_password(self) -> bool:
447
"""Password never expires."""
448
449
@property
450
def smartcard_required(self) -> bool:
451
"""Smart card required for interactive logon."""
452
```
453
454
## Usage Examples
455
456
### Working with Security Identifiers
457
458
```python
459
from bonsai.active_directory import SID
460
461
# Create SID from string representation
462
user_sid = SID(str_rep="S-1-5-21-1234567890-987654321-1111111111-1001")
463
print(f"User SID: {user_sid}")
464
print(f"Domain SID: {user_sid.get_account_domain_sid()}")
465
print(f"RID: {user_sid.get_rid()}")
466
467
# Create SID from binary data (from LDAP objectSid attribute)
468
binary_sid = entry['objectSid'][0] # Raw bytes from LDAP
469
sid_obj = SID(bytes_le=binary_sid)
470
print(f"SID from binary: {sid_obj}")
471
472
# Compare SIDs
473
admin_sid = SID(str_rep="S-1-5-32-544") # Built-in Administrators
474
if admin_sid.is_well_known():
475
print("This is a well-known SID")
476
```
477
478
### Parsing Security Descriptors
479
480
```python
481
from bonsai.active_directory import SecurityDescriptor, SID
482
from bonsai import LDAPClient
483
484
# Get security descriptor from Active Directory
485
client = LDAPClient("ldap://dc.example.com")
486
client.set_credentials("SIMPLE", user="admin@example.com", password="secret")
487
488
with client.connect() as conn:
489
# Search for user and get ntSecurityDescriptor
490
results = conn.search(
491
"cn=Users,dc=example,dc=com",
492
2,
493
"(sAMAccountName=jdoe)",
494
["ntSecurityDescriptor"]
495
)
496
497
if results:
498
# Parse security descriptor from binary attribute
499
sd_binary = results[0]['ntSecurityDescriptor'][0]
500
security_desc = SecurityDescriptor.from_binary(sd_binary)
501
502
print(f"Owner: {security_desc.owner_sid}")
503
print(f"Group: {security_desc.group_sid}")
504
print(f"Control flags: {security_desc.control}")
505
506
# Examine DACL
507
if security_desc.dacl:
508
print(f"DACL has {len(security_desc.dacl)} ACEs:")
509
for i, ace in enumerate(security_desc.dacl):
510
print(f" ACE {i}: {ace.ace_type.name} for {ace.sid}")
511
print(f" Access mask: 0x{ace.access_mask:08x}")
512
print(f" Flags: 0x{ace.ace_flags:02x}")
513
```
514
515
### Creating Access Control Lists
516
517
```python
518
from bonsai.active_directory import ACL, ACE, ACEType, ACERight, SID
519
520
# Create new DACL
521
dacl = ACL()
522
523
# Add Allow ACE for user
524
user_sid = SID(str_rep="S-1-5-21-1234567890-987654321-1111111111-1001")
525
allow_ace = ACE(
526
ace_type=ACEType.ACCESS_ALLOWED,
527
ace_flags=0,
528
access_mask=ACERight.GENERIC_READ | ACERight.GENERIC_WRITE,
529
sid=user_sid
530
)
531
dacl.add_ace(allow_ace)
532
533
# Add Deny ACE for guest account
534
guest_sid = SID(str_rep="S-1-5-21-1234567890-987654321-1111111111-501")
535
deny_ace = ACE(
536
ace_type=ACEType.ACCESS_DENIED,
537
ace_flags=0,
538
access_mask=ACERight.GENERIC_ALL,
539
sid=guest_sid
540
)
541
dacl.add_ace(deny_ace, index=0) # Insert at beginning for precedence
542
543
print(f"DACL has {len(dacl)} ACEs")
544
for i, ace in enumerate(dacl):
545
print(f"ACE {i}: {ace.ace_type.name} for {ace.sid}")
546
```
547
548
### Analyzing User Account Control
549
550
```python
551
from bonsai.active_directory import UserAccountControl
552
553
# Parse userAccountControl from Active Directory
554
with client.connect() as conn:
555
results = conn.search(
556
"cn=Users,dc=example,dc=com",
557
2,
558
"(sAMAccountName=jdoe)",
559
["userAccountControl"]
560
)
561
562
if results:
563
uac_value = int(results[0]['userAccountControl'][0])
564
uac = UserAccountControl(uac_value)
565
566
print(f"Account Control Value: {uac.value}")
567
print(f"Account Disabled: {uac.account_disabled}")
568
print(f"Password Required: {not uac.password_not_required}")
569
print(f"Password Expires: {not uac.dont_expire_password}")
570
print(f"Smart Card Required: {uac.smartcard_required}")
571
print(f"Account Locked: {uac.account_locked_out}")
572
573
# Check all properties
574
for prop, value in uac.properties.items():
575
if value:
576
print(f" {prop}: {value}")
577
```
578
579
### Modifying Security Descriptors
580
581
```python
582
from bonsai.active_directory import SecurityDescriptor, ACL, ACE, ACEType, ACERight, SID
583
584
# Create new security descriptor
585
control_flags = {
586
"dacl_present": True,
587
"dacl_defaulted": False,
588
"self_relative": True,
589
"dacl_protected": False
590
}
591
592
owner_sid = SID(str_rep="S-1-5-21-1234567890-987654321-1111111111-1001")
593
group_sid = SID(str_rep="S-1-5-21-1234567890-987654321-1111111111-513")
594
595
# Create DACL with full control for owner
596
dacl = ACL()
597
full_control_ace = ACE(
598
ace_type=ACEType.ACCESS_ALLOWED,
599
ace_flags=0,
600
access_mask=ACERight.GENERIC_ALL,
601
sid=owner_sid
602
)
603
dacl.add_ace(full_control_ace)
604
605
# Create security descriptor
606
security_desc = SecurityDescriptor(
607
control=control_flags,
608
owner_sid=owner_sid,
609
group_sid=group_sid,
610
dacl=dacl,
611
sacl=None
612
)
613
614
# Convert to binary for storing back to LDAP
615
sd_binary = security_desc.to_binary()
616
617
# Update LDAP entry (requires appropriate permissions)
618
with client.connect() as conn:
619
entry = conn.search("cn=test-object,dc=example,dc=com", 0)[0]
620
entry['ntSecurityDescriptor'] = sd_binary
621
conn.modify(entry)
622
```