Skip to content

feat: add AD drift reconciliation for passwords and group membership - #398

Merged
mwendigg merged 1 commit into
dreadnode:mainfrom
l50:upstream/ad-drift-reconciliation
Aug 3, 2026
Merged

feat: add AD drift reconciliation for passwords and group membership#398
mwendigg merged 1 commit into
dreadnode:mainfrom
l50:upstream/ad-drift-reconciliation

Conversation

@l50

@l50 l50 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Key Changes:

  • Introduced password and group-membership drift reconciliation so lab reset converges on the declared baseline instead of a superset, since both operations were previously add-only and left attack-run changes in place
  • Added a repeatable -E/--extra-vars CLI flag across provision, ad-users, and lab reset, enabling ad_reconcile_check_only=true dry-runs to report drift without correcting it
  • Reconciliation deliberately never touches cross-domain members, machine accounts, protected management accounts, or built-in principals (RID < 1000) to avoid over-correcting state not owned by the lab config
  • Added comprehensive unit tests covering extra-vars parsing, precedence layering, and malformed-input rejection

Added:

  • Group membership reconciliation - New reconcile_group_membership.yml diffs each lab-managed group against config and removes unexpected members via PowerShell, filtering foreign SIDs, computer accounts, built-in RIDs, and protected accounts
  • Password reconciliation - New reconcile_passwords.yml authenticates each user with its configured password via LogonUser and resets only accounts that fail, including a credential probe that aborts if the host authenticates a nonexistent principal, plus lockout clearing and Protected Users handling
  • Role defaults - New defaults/main.yml for the ad role defining ad_reconcile_passwords, ad_reconcile_group_membership, ad_reconcile_check_only, ad_reconcile_protected_accounts, and ad_multi_domain_groups_member
  • Extra-vars support - Added parseExtraVars, applyExtraVars, and sortedPairs helpers in provision.go, wired the shared extraVarsUsage flag into provision, ad-users, and lab reset, and layered user vars over SOCKS tunnel connection vars
  • Test coverage - New provision_test.go validating extra-vars parsing edge cases, precedence rules, caller-map immutability, and stable rendering
  • Documentation - Documented the new role defaults and reconciliation tasks in the ad role README

Changed:

  • Reconciliation task ordering - Wired the two reconcile passes into the ad role's main.yml after all add-member tasks, guarded by their enable flags, so they reconcile against a fully built baseline
  • Multi-domain group data - Threaded ad_multi_domain_groups_member through ad-data.yml so the membership reconciler recognizes same-domain principals applied by the later groups_domains play as managed
  • Provisioning signature - Extended provisionPlaybooks to accept an extraVars map, merged into the runner's ExtraVars, and added structured success/failure logging around playbook execution
  • Reset command guidance - Updated lab reset help text and examples to explain that Stage 2 now reconciles rather than only re-applies, and how to rehearse with -E ad_reconcile_check_only=true

**Added:**

- Password reconciliation task that authenticates each user with the configured
  password and resets only accounts that fail, handling lockouts and skipping
  Protected Users members - `roles/ad/tasks/reconcile_passwords.yml`
- Credential probe safety check that aborts if a nonexistent principal
  authenticates, preventing the role from silently becoming a no-op
- Group membership reconciliation task that diffs lab-managed groups against
  config and removes unexpected members, skipping cross-domain members, machine
  accounts, built-in principals (RID < 1000), and protected accounts -
  `roles/ad/tasks/reconcile_group_membership.yml`
- Role defaults declaring `ad_reconcile_passwords`, `ad_reconcile_group_membership`,
  `ad_reconcile_check_only`, `ad_reconcile_protected_accounts`, and
  `ad_multi_domain_groups_member` - `roles/ad/defaults/main.yml`
- Repeatable `--extra-vars`/`-E` flag on `provision`, `ad-users`, and `lab reset`
  to pass Ansible variables from the command line, primarily to enable
  `ad_reconcile_check_only=true` dry runs - `cli/cmd/provision.go`, `cli/cmd/lab_reset.go`
- `parseExtraVars`, `applyExtraVars`, and `sortedPairs` helpers, with user vars
  layered over SOCKS tunnel vars so explicit `-e` always wins - `cli/cmd/provision.go`
- Test coverage for extra-vars parsing, malformed rejection, precedence layering,
  and stable rendering - `cli/cmd/provision_test.go`
- Documented default variables and new reconciliation tasks in role docs -
  `roles/ad/README.md`

**Changed:**

- Wired `ad_multi_domain_groups_member` into the ad-data play so the membership
  reconciler recognises same-domain principals declared via cross-domain groups -
  `playbooks/ad-data.yml`
- Ordered reconciliation tasks after all add-member steps in the ad role so they
  diff against a fully built baseline - `roles/ad/tasks/main.yml`
- Threaded `extraVars` through `provisionPlaybooks` and added structured logging
  on provisioning success and failure - `cli/cmd/provision.go`
- Expanded `lab reset` help and examples to document that Stage 2 reconciles
  drift and how to rehearse with `ad_reconcile_check_only=true` - `cli/cmd/lab_reset.go`
@dreadnode-renovate-bot dreadnode-renovate-bot Bot added area/playbooks Changes made to playbooks directory area/roles Changes made to Ansible roles labels Aug 3, 2026
@mwendigg
mwendigg added this pull request to the merge queue Aug 3, 2026
Merged via the queue into dreadnode:main with commit f74d865 Aug 3, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/playbooks Changes made to playbooks directory area/roles Changes made to Ansible roles

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants