Skip to content
Projects

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.

How these counts are measured

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

Productivity

README.md

Original

Windows 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

Org Stars Stars Forks Followers X URL Discord Badge

License

Lint & Pre-Commit Tools

Pre-Commit.ci YamlLint Ansible-Lint

Community Release Information

Release Branch Release Tag Main Release Date Benchmark Version Main Benchmark Version Devel

Main Pipeline Status Devel Pipeline Status

Devel Commits Open Issues Closed Issues Pull Requests


Subscriber Release Information

Private Release Branch Private Benchmark Version

Private Remediate Pipeline

Private Pull Requests Private Closed Issues


Looking for support?

Lockdown Enterprise

Ansible 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 of LOCAL 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. Set win25cis_apply_generate_security_audits_on_iis_adfs: true to 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

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:

  1. Standard Prefix:
- All references are prefixed with "NIST".
  1. 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.
  1. 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.