ansible-lockdown/Windows-2025-CIS
ansible-lockdown/Windows-2025-CIS has 28 GitHub stars and 18 forks. GitHot measured 0 stars in 24 hours. Last checked 2026-11-15 04:20 UTC. Last push 2026-11-04.
Measured star growth
- in 24h
- 0
- in 7 days
- Collecting history
- in 15 days
- Collecting history
- in 1 month
- Collecting history
Net star changes over overlapping periods, not daily totals. Missing measurements are left blank. Stars measure attention, not software quality.
About
Ansible CIS Benchmark Compliance Remediation for WINDOWS-2025
ProductivityREADME.md
OriginalWindows Server 2025 CIS
Configure a Microsoft Server 2025 machine to be CIS compliant
Based on CIS Microsoft Windows Server 2025 v1.0.0 - 03-19-2025
Public Repository
Lint & Pre-Commit Tools
Community Release Information
Subscriber Release Information
Looking for support?
Community
On our Discord Server to ask questions, discuss features, or just chat with other Ansible-Lockdown users
Contributing
Bug reports and feature requests are welcome from everyone, please raise an issue.
Pull requests are accepted from approved contributors only. To be onboarded, join the Discord Server and request contributor access. See CONTRIBUTING.md for the full process.
Caution(s)
This role will make changes to the system which may have unintended consequences. This is not an auditing tool but rather a remediation tool to be used after an audit has been conducted.
Check Mode is not supported! The role will complete in check mode without errors, but it is not supported and should be used with caution.
This role was developed against a clean install of the Windows 2025 Operating System. If you are implementing to an existing system please review this role for any site specific changes that are needed.
To use release version please point to main branch and relevant release for the cis benchmark you wish to work with.
Deviations from the benchmark
- 2.2.31 Generate security audits is set to
LOCAL SERVICE, NETWORK SERVICE, RESTRICTED SERVICES\PrintSpoolerService, not the v1.0.0 value ofLOCAL SERVICE, NETWORK SERVICE. The Print Spooler service grants this right to its own account every time it starts, so the v1.0.0 value only holds until the next service start or reboot. The role would report a change on every run and the audit would fail on any host where the spooler runs. CIS corrected the recommendation in v2.1.0 (control 2.2.30); this role applies that value. The audit asserts the same three accounts. On a host with IIS or ADFS installed, CIS notes 2 and 3 require an exception so that IIS application pools and the ADFS service accounts keep this right. The role detects both in prelim and skips 2.2.31 there with a warning, and the audit reports it as skipped. Setwin25cis_apply_generate_security_audits_on_iis_adfs: trueto apply the baseline on those hosts anyway.
Domain Members
Account Policy is domain scoped. On a domain joined host the Default Domain Policy
overwrites local [System Access] at every Group Policy refresh, so section 1 cannot
hold there. That is CIS's own position - on a member server, Account Policies belong
in the Default Domain Policy.
Measured on a member server after joining the domain:
| setting | applied by the role | after the domain refresh |
|---|---|---|
| MinimumPasswordLength | 14 | 7 |
| MaximumPasswordAge | 365 | 42 |
| LockoutBadCount | 5 | 0 (net accounts: Never) |
| LockoutDuration | 15 | removed |
| ResetLockoutCount | 15 | removed |
All 11 section 1 controls revert. Once LockoutBadCount is 0 Windows removes
LockoutDuration and ResetLockoutCount outright, which used to abort the play on a
second run. The role now detects a domain joined host and skips section 1 with a
warning instead.
Only the secedit backed controls are affected - registry backed controls hold. Set section 1 from a GPO on domain members.
Controls the domain overrides
On any host that is not standalone - a member server or a domain controller - these
are set by domain policy, so the role does not apply them and the audit reports them
as skipped, with the reason in meta.skip_reason. Both decide from the host itself:
the role from its prelim facts, the audit from run_audit.ps1, which reads
Win32_ComputerSystem.DomainRole and passes win25cis_system_role inline.
| Control | Setting | Why the domain wins | Role | Audit |
|---|---|---|---|---|
| 1.1.1 - 1.1.5, 1.1.7 | Password policy | Default Domain Policy sets [System Access] |
Skipped, with a warning | Skipped, with the reason |
| 1.2.1 - 1.2.4 | Account lockout policy (1.2.3 member server only) | Default Domain Policy sets [System Access] |
Skipped, with a warning | Skipped, with the reason |
| 2.3.11.6 | Force logoff when logon hours expire | Default Domain Policy sets ForceLogoffWhenHourExpire = 0 |
Skipped, with a warning | Skipped, with the reason |
| 2.3.5.4 | LDAP server signing (domain controller only) | Default Domain Controllers Policy sets LDAPServerIntegrity = 1, reverted at the next refresh |
Applied, with a warning | Expected to fail on a DC |
| 1.1.6 | Relax minimum password length limits (member server only) | Not overridden - a registry value, not account policy | Applied | Asserted |
Set the skipped controls in the Default Domain Policy, and 2.3.5.4 in the Default Domain Controllers Policy. On a standalone server every one of them is applied and asserted as normal.
Tags describe the CIS profile, not where the task runs
A control's level1-memberserver / level1-domaincontroller tags come straight from
its Profile Applicability in the benchmark, and they never change with the host. The
when decides whether the task can do anything on this host. The two can disagree,
and that is deliberate.
1.2.3 is the clearest case. CIS scopes it to Level 1 Member Server, so it carries
level1-memberserver and not level1-domaincontroller. It is also an
AllowAdministratorLockout value in [System Access], so it only holds on a
standalone server, and its when restricts it to one. A domain member server -
exactly the profile the tag names - therefore skips it and takes it from the Default
Domain Policy. Retagging it to match the when would misreport the benchmark, and
loosening the when to match the tag would write a value the domain reverts.
What the role enforces on each target type
The benchmark has 462 recommendations. The role holds a task for every one of them, but CIS scopes many to a single profile, and Windows scopes Account Policy to the domain. Counting only the host facts the prelim sets and the profile defaults - not the per rule toggles - a default run enforces:
| Target | Enforced by this role | Skipped: not applicable to this target | Skipped: NGWS profile off | Skipped: externally managed |
|---|---|---|---|---|
| Standalone server | 412 | 43 | 7 | 0 |
| Domain member server | 413 | 31 | 7 | 11 |
| Domain controller | 409 | 36 | 7 | 10 |
"Not applicable" means the benchmark does not apply the control to that target - the 31 skipped on a member server are the Level 1 Domain Controller controls, and the DC skips the Member Server ones. Those are not gaps. A standalone server also skips the Member Server controls that would cut off its remote administration or need a domain; see Stand-alone Servers.
"NGWS profile off" is the optional Next Generation Windows Security profile, which defaults off; see Next Generation Windows Security profile.
"Externally managed" is the set the role cannot make stick locally. On a member
server that is 1.1.1 - 1.1.5, 1.1.7, 1.2.1 - 1.2.4 and 2.3.11.6. A domain controller
is the same list less 1.2.3, which CIS scopes to member servers only. Every one of
these is a [System Access] value: a domain controller has no local account database
for domain accounts, and on a member server the Default Domain Policy overwrites the
local value at the next Group Policy refresh. A local secedit write is not a
partial fix on either - it is a no-op that reports changed.
There is one remediation path for these, and it is the Default Domain Policy - see Group Policy Objects. A clean run of this role against a domain controller is not evidence that those ten controls are enforced; it reports them as skipped, with the reason, and the audit does the same.
Matching A Security Level For CIS
It is possible to only run level 1 or level 2 controls for CIS as well as other profile definitions that are set in the CIS release. This is managed using tags:
- level1-domaincontroller
- level1-memberserver
- level2-domaincontroller
- level2-memberserver
- ngws-domaincontroller
- ngws-memberserver
The control found in defaults main also need to reflect this as this control the testing that takes place if you are using the audit component.
Next Generation Windows Security profile
Next Generation Windows Security (NGWS) is an optional CIS profile, not a third level.
Its eight controls - 18.9.5.x Device Guard and Credential Guard, and 18.9.26.2 LSA
protection - run only when win25cis_ngws is true (default false, in
defaults/main/main.yml), and the audit asserts them on the same switch, so the two
always agree. They need virtualization based security support on the host, so turn it
on deliberately. The ngws tags select within the profile; they do not enable it on
their own.
Stand-alone Servers
The benchmark is written for domain joined servers and says it is not intended for standalone or workgroup systems. This role still runs on one, and treats it as a member server wherever that is safe: 24 of the member server (MS only) controls apply to any server that is not a domain controller.
The rest stay member server only:
- 2.2.22 and 2.2.27 deny network and Remote Desktop logon for local accounts. On a standalone server every account is local, so applying them cuts off remote administration - the benchmark carries that caution.
- LAPS (18.9.25.x) and the Netlogon secure channel (2.3.6.1, 2.3.6.2) need a domain to mean anything.
Cached domain logons (2.3.7.6), domain controller unlock (2.3.7.8) and 18.6.21.2 and 18.9.28.4 also only act on a domain, but they are valid local policy, so they are set on a standalone server too and its local policy reads as compliant.
Section 1 account policy is applied on a standalone server only; on a domain member the Default Domain Policy owns it.
Coming From A Previous Release
CIS release always contains changes, it is highly recommended to review the new references and available variables. This have changed significantly since ansible-lockdown initial release. This is now compatible with python3 if it is found to be the default interpreter. This does come with pre-requisites which it configures the system accordingly.
Further details can be seen in the Changelog
Group Policy Objects
This role applies the benchmark directly to the host it runs against. It does not create Group Policy Objects.
GPOs are being addressed currently and will reside in a new repository once released.
Migrating from the in-role GPO path
GPO creation used to live in this role behind win25cis_create_gpos. It now lives in
Windows-2025-CIS-GPO (mindpointgroup.windows2025_cis_gpo), which runs against a
domain controller and writes the CIS GPOs, including the Default Domain Policy that
carries Account Policy.
The variables kept their names. Move them from this role's inventory to the GPO role's and they behave as before:
| Removed from this role | Now set on | Notes |
|---|---|---|
win25cis_create_gpos |
- | Dropped. The GPO role only creates GPOs, so there is nothing to switch on. |
win25cis_ansible_remediation |
- | Dropped. This role only remediates, so there is nothing to switch off. |
win25cis_create_domain, win25cis_domain, win25cis_safe_mode_administrator_password |
Windows-2025-CIS-GPO |
Test domain promotion. Unchanged names and defaults. Set the DSRM password from a vault, never in the playbook. |
win25cis_default_domain_policy_gpo, win25cis_l1_dc_gpo, win25cis_l1_ms_gpo, win25cis_l2_dc_gpo, win25cis_l2_ms_gpo, win25cis_l1_dm_gpo, win25cis_services_dc_gpo, win25cis_services_ms_gpo, win25cis_ngws_dc_gpo, win25cis_ngws_ms_gpo, win25cis_l1_user_gpo, win25cis_l2_user_gpo |
Windows-2025-CIS-GPO |
Which GPOs to create. Unchanged names and defaults. |
The matching *_gpo_name variables, win25cis_gpo_hardening_version, win25cis_gpo_hardening_os |
Windows-2025-CIS-GPO |
GPO naming. Unchanged. |
win25cis_copy_policy_definitions, win25cis_copy_cis_custom_admx_adml |
Windows-2025-CIS-GPO |
ADMX/ADML staging. Unchanged. |
win25cis_backup_custom_gpos, win25cis_backup_location_style |
Windows-2025-CIS-GPO |
GPO backup. Unchanged. |
Leaving any of these set on this role is harmless - they are simply unused - but nothing will create a GPO until you run the GPO role.
If you harden a domain, run both: this role against each member server and domain controller for the host scoped controls, and the GPO role once against a domain controller for the Account Policy controls this one reports as skipped.
Auditing (beta)
The audit component is in beta while we gather feedback. It is usable and its
results are meaningful, and we would like to hear how it behaves on your estate.
Please raise an issue, or come and talk to us on the Discord Server.
Remediation is unaffected - run_audit and setup_audit both default to false,
so none of this runs unless you ask for it.
This role is paired with Windows-2025-CIS-Audit, a set of syver specs generated
from this role by scripts/generate_windows_audit.py, so the audit asserts what
the role actually does rather than a separately maintained restatement of it.
Switches
| Variable | Default | Effect |
|---|---|---|
setup_audit |
false |
Place the syver binary and the audit content on the host |
run_audit |
false |
Run the audit before and after remediation |
audit_only |
false |
Run the pre-remediation audit, then stop without remediating |
fetch_audit_output |
false |
Collect the result files after the run |
# stage the binary and content, remediate nothing
ansible-playbook site.yml -e setup_audit=true
# audit the host and stop
ansible-playbook site.yml -e run_audit=true -e audit_only=true
# remediate with a before and after audit
ansible-playbook site.yml -e run_audit=true
Requirements
syver.exe is never committed to this repository. The default
get_audit_binary_method: download fetches it from the public release named in
audit_bin_version (v0.11.1) and verifies the published SHA256. Keep that digest
set: win_get_url checks it, and that check is what stands between a compromised
mirror and every audited host running someone else's binary elevated. No Windows
ARM64 asset is published for v0.11.1, so ARM64_checksum is deliberately empty
and a download on such a host fails the lookup rather than fetching an amd64
binary.
To supply your own build instead, set get_audit_binary_method: copy and point
audit_bin_copy_location at it; it defaults to syver-windows-amd64.exe in this
role directory.
The audit content comes from audit_conf_source, which defaults to the sibling
Windows-2025-CIS-Audit checkout on the controller.
Documentation
- Read The Docs
- Getting Started
- Customizing Roles
- Per-Host Configuration
- Getting the Most Out of the Role
Requirements
General:
Basic knowledge of Ansible, below are some links to the Ansible documentation to help get started if you are unfamiliar with Ansible
Functioning Ansible and/or Tower Installed, configured, and running. This includes all of the base Ansible/Tower configurations, needed packages installed, and infrastructure setup.
Please read through the tasks in this role to gain an understanding of what each control is doing. Some of the tasks are disruptive and can have unintended consequences in a live production system. Also, familiarize yourself with the variables in the defaults/main.yml file.
Technical Dependencies:
- Windows 2025 - Other versions are not supported
- Python3 Ansible run environment
- passlib
- xmltodict
- pywinrm or pypsrp
Role Variables
This role is designed so that the end user should not have to edit the tasks themselves. All customizing should be done via the defaults/main.yml file or with extra vars within the project, job, workflow, etc.
Tags
There are many tags available for added control precision. Each control has its own set of tags noting what level, what OS element it relates to, whether it's a patch or audit, and the rule number. Additionally, NIST references follow a specific conversion format for consistency and clarity.
Conversion Format for NIST References:
- Standard Prefix:
- All references are prefixed with "NIST".
- Standard Types:
- "800-53r5" references are formatted as NIST800-53R5 (with 'R' capitalized). Only Rev 5 is tagged.
- "800-171" references are formatted as NIST800-171.
- Details:
- Section and subsection numbers use periods (.) for numeric separators.
- Parenthetical elements are separated by underscores (_), e.g., IA-5(1)(d) becomes IA-5_1_d.
- Subsection letters (e.g., "b") are appended with an underscore.
Example of Tag Usage:
Below is an example of the tag section from a control within this role. Using this example, if you set your run to skip all controls with the tag smb, this task will be skipped. Conversely, you can choose to run only controls tagged with smb.
tags:
- level1-domaincontroller
- level1-memberserver
- rule_18.4.2
- patch
- smb
- NIST800-171_3.4.2
- NIST800-171_3.4.6
- NIST800-171_3.4.7
- NIST800-53R5_CM-6
- NIST800-53R5_CM-7
Conversion Examples in Use:
- 800-53r5 IA-5(1)(d) NIST800-53R5_IA-5_1_d
- 800-53r5 AC-17(2) NIST800-53R5_AC-17_2
- 800-53r5 CM-6b. NIST800-53R5_CM-6_b
- 800-171 3.5.2 NIST800-171_3.5.2
By maintaining this consistent tagging structure, it becomes easier to filter and manage tasks based on specific controls and compliance requirements.
Community Contribution
We encourage you (the community) to contribute to this role. Please read the rules below.
- Your work is done in your own individual branch. Make sure to Signed-off-by and GPG sign all commits you intend to merge.
- All community Pull Requests are pulled into the devel branch
- Pull Requests into devel will confirm your commits have a GPG signature, Signed-off-by, and a functional test before being approved
- Once your changes are merged and a more detailed review is complete, an authorized member will merge your changes into the main branch for a new release
Pipeline Testing
uses:
- ansible-core 2.16
- ansible collections - pulls in the latest version based on requirements file
- runs the audit using the devel branch
- This is an automated test that occurs on pull requests into devel
- self-hosted runners using OpenTofu
Local Testing
- Ansible
- ansible-core 2.21.01- python 3.13
Credits and Thanks
Massive thanks to the fantastic community and all its members.
This includes a huge thanks and credit to the original authors and maintainers.