Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions ad/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ Run on a DC, or on a host with RSAT, as `adm_mayonej`. The GPO step needs the Gr
.\converge.ps1 # Build
```

From the control node, Ansible can run Build on loom as `svc-ansible` (`../ansible/README.md`, tag `converge`):

```sh
cd ansible
ansible-playbook ad.yml -t converge # -WhatIf
ansible-playbook ad.yml -t converge -e ad_converge_apply=true # Build
```

Logs go to `C:\ProgramData\Patchwork\ad-converge\`. Each run ends with a count of changes, reports and errors. A REPORT line needs a person to look at it. The script changes nothing for it.

New accounts are disabled and must change the password at the next logon. The script never sets, reads or logs a password. To start an account, set a first password (or let Weave set it), then enable the account.
Expand Down
2 changes: 1 addition & 1 deletion ad/TIERING.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ Compensating controls for step 5a:
- The WinRM firewall rule allows only Jasper's VPN address (`Enable-AnsibleWinRM.ps1 -RemoteAddress`). Today the scope is the control node subnet. Make it one address. This needs the thimble NAT removed first: with NAT, every VPN client arrives as 10.100.92.20.
- A GPO denies `svc-ansible` local logon, RDP logon, batch and service logon on every host.
- Keep AES only and "not delegatable" (`-t svc-posture`, already live).
- Alert on any logon of `svc-ansible` (4624, 4776) from an address other than the VPN address (one address per person only after the NAT is removed). Alert on any group change that it makes outside an Ansible run.
- Alert on any logon of `svc-ansible` (4624, 4776) from an address other than the VPN address (one address per person only after the NAT is removed). Alert on any group change that it makes outside an Ansible run. `ad.yml -t converge` runs `converge.ps1` Build as `svc-ansible`, so a Build run is an Ansible run too. After the switch to Kerberos (5b), run `ad.yml -t converge` (`-WhatIf`) again and check that it still works.
- Rotate the password on a schedule and after any remus incident.

## 3. PAM and just-in-time admin
Expand Down
5 changes: 4 additions & 1 deletion ansible/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Ansible does not manage these hosts:

The Proxmox layer (VM creation, templates, the network gear) is not in this repo.

These jobs are done by hand and are not in Ansible: AD CS setup and the offline root signing, NPS policies, and the GPOs other than the two armoring GPOs and the password values of the `Default Domain Policy` (see "Tier 0"). The OUs, users and groups are in `../ad/` (`converge.ps1`).
These jobs are done by hand and are not in Ansible: AD CS setup and the offline root signing, NPS policies, and the GPOs other than the two armoring GPOs and the password values of the `Default Domain Policy` (see "Tier 0"). The OUs, users and groups are in `../ad/` (`converge.ps1`). `ad.yml -t converge` runs its Build phase. The Migrate and Prune phases are manual.

## Layout

Expand Down Expand Up @@ -151,6 +151,8 @@ ansible-playbook windows.yml # all Windows hosts (spool gets ne
ansible-playbook windows.yml -l shuttle # one host
ansible-playbook ad.yml -t audit # report only, changes nothing
ansible-playbook ad.yml -t host-groups # login groups for every NixOS host
ansible-playbook ad.yml -t converge # ../ad/converge.ps1 Build, -WhatIf
ansible-playbook ad.yml -t converge -e ad_converge_apply=true
ansible-playbook ad.yml -t dhcp,svc-posture
```

Expand All @@ -164,6 +166,7 @@ Every tag is opt-in. A run without `-t` does nothing. Directory changes run once
| `audit` | Reports join delegation, Protected Users and service-account posture. Changes nothing. Add `-e ad_audit_strict=true` to fail on a finding. |
| `join-delegation` | Gives `svc-lnx-join` the rights to join computers in both computer OUs. |
| `host-groups` | Creates `lnx-<host>-users` and `lnx-<host>-admins` for every NixOS host in the flake. |
| `converge` | Copies `../ad/converge.ps1` and `../ad/org.json` to loom and runs phase Build as `svc-ansible`, against loom. The run is `-WhatIf` unless you add `-e ad_converge_apply=true`. Read the `WOULD` lines before you apply. An apply refuses uncommitted changes under `../ad`. Only SYSTEM and Administrators can write to the script directory on loom. The report shows the commit, the `WOULD`, `CHANGE`, `REPORT`, `ERROR` and `END` lines, and stderr. Then the play fails if the script exited with an error. `--check` is not supported. Migrate and Prune stay manual. |
| `svc-posture` | Sets AES only and "not delegatable" on `svc-ansible`, `svc-lnx-join` and `svc-weave-ldap`. |
| `user-mail` | Sets the `mail` attribute from `ad_user_mail`. Does nothing while the map is empty. |
| `ldap-binds` | Creates the read-only LDAP bind accounts in `ad_ldap_bind_accounts` (`group_vars/patchwork.yml`). The list is empty until an app needs one; the comment there shows how to add one. |
Expand Down
108 changes: 108 additions & 0 deletions ansible/ad.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
# ad_ca_subject_match against them. REPORT ONLY
# -t ca-chain export the forest CA chain to files/pki/
# -t dns-log Microsoft-Windows-DNSServer/Analytical enabled
# -t converge ../ad/converge.ps1 phase Build on loom. -WhatIf
# unless -e ad_converge_apply=true
#
# Tier 0 (ad/TIERING.md). Run them in the order in ansible/README.md.
# -t authn-log Microsoft-Windows-Authentication/* logs and
Expand Down Expand Up @@ -80,6 +82,10 @@
# -t administrator-random: -e ad_admin_random_force=true sets a new
# password even when the last one is younger than the minimum age.
ad_admin_random_force: false
# -t converge: -e ad_converge_apply=true makes the changes. Without it,
# the run is -WhatIf.
ad_converge_apply: false
ad_converge_dir: 'C:\ProgramData\Patchwork\ad-converge\src'
tasks:

# ------------------------------------------------------------------ dhcp
Expand Down Expand Up @@ -402,6 +408,108 @@
when: ad_is_primary_dc
tags: [host-groups, host-dns, never]

# --------------------------------------------------------------- converge
# Runs ../ad/converge.ps1 (phase Build) on loom (ad_dcs[0]) as
# svc-ansible, against loom itself (-Server). A run is -WhatIf unless
# -e ad_converge_apply=true. Read the WOULD lines of a -WhatIf run before
# you apply. An apply refuses uncommitted changes under ../ad. Migrate and
# Prune stay manual (ad/README.md). The script writes its log to
# C:\ProgramData\Patchwork\ad-converge on loom, as a manual run does.
# --check is not supported: win_command cannot run in check mode.
- name: Converge | An apply uses only committed files under ../ad
ansible.builtin.command:
argv: [git, -C, "{{ ad_flake_dir }}", status, --porcelain, --, ad]
delegate_to: localhost
register: ad_converge_git
changed_when: false
failed_when: >-
ad_converge_git.rc != 0
or (ad_converge_apply | bool and ad_converge_git.stdout | length > 0)
when: ad_is_primary_dc
tags: [converge, never]

- name: Converge | The commit that this run uses
ansible.builtin.command:
argv: [git, -C, "{{ ad_flake_dir }}", rev-parse, HEAD]
delegate_to: localhost
register: ad_converge_rev
changed_when: false
when: ad_is_primary_dc
tags: [converge, never]

# Only SYSTEM and Administrators can write to the script directory. The
# inherited ProgramData ACL lets Users create files there, and the
# script runs as a Domain Admin. The task reports a change on every run,
# because it deletes the copies of the last run.
- name: Converge | Script directory exists, writable by admins only
ansible.windows.win_powershell:
parameters:
path: "{{ ad_converge_dir }}"
script: |
param([string]$path)
$Ansible.Changed = $false
New-Item -ItemType Directory -Force -Path $path | Out-Null
$want = 'O:BAG:SYD:PAI(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)'
$acl = Get-Acl -Path $path
if ($acl.Sddl -ne $want) {
$acl.SetSecurityDescriptorSddlForm($want)
Set-Acl -Path $path -AclObject $acl
$Ansible.Changed = $true
}
# A file can keep its own owner and ACEs. Delete the old copies, so
# the copy task writes new files that inherit only the ACL above.
Get-ChildItem -Path $path -Force | ForEach-Object {
Remove-Item -LiteralPath $_.FullName -Recurse -Force
$Ansible.Changed = $true
}
when: ad_is_primary_dc
tags: [converge, never]

- name: Converge | converge.ps1 and org.json match the repo
ansible.windows.win_copy:
src: "{{ ad_flake_dir }}/ad/{{ item }}"
dest: "{{ ad_converge_dir }}\\{{ item }}"
loop: [converge.ps1, org.json]
when: ad_is_primary_dc
tags: [converge, never]

- name: Converge | Run Build (-WhatIf unless ad_converge_apply)
ansible.windows.win_command:
argv: >-
{{ ['powershell.exe', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass',
'-File', ad_converge_dir ~ '\converge.ps1', '-Phase', 'Build',
'-DataFile', ad_converge_dir ~ '\org.json',
'-Server', ad_dcs[0].name]
+ ([] if ad_converge_apply | bool else ['-WhatIf']) }}
register: ad_converge
changed_when: ad_converge.stdout_lines | select('search', ' CHANGE ') | list | length > 0
# The Report task shows the lines first. The last task fails the play.
failed_when: false
when: ad_is_primary_dc
tags: [converge, never]

- name: Converge | Report
ansible.builtin.debug:
msg:
commit: "{{ ad_converge_rev.stdout }}"
uncommitted_under_ad: "{{ ad_converge_git.stdout_lines }}"
apply: "{{ ad_converge_apply | bool }}"
lines: >-
{{ ad_converge.stdout_lines
| select('search', ' (WOULD|CHANGE|REPORT|ERROR|END) ')
| list }}
stderr: "{{ ad_converge.stderr_lines }}"
when: ad_is_primary_dc and ad_converge.rc is defined
tags: [converge, never]

- name: Converge | The script finished with no error
ansible.builtin.assert:
that: ad_converge.rc == 0
fail_msg: "converge.ps1 exited {{ ad_converge.rc }}. Read the ERROR lines and stderr above."
quiet: true
when: ad_is_primary_dc and ad_converge.rc is defined
tags: [converge, never]

# ------------------------------------------------------------ svc-posture
# Enforce what -t audit checks on the service accounts: AES-only Kerberos
# keys (msDS-SupportedEncryptionTypes 24) and AccountNotDelegated. Neither
Expand Down
Loading