Quick Windows Hardening with Infrastructure-as-Code – Chef and Inspec

CIS, Center for Internet Security, publishes prescriptive system hardening documents which provide guidance for establishing a secure system configuration on platforms such as Windows. Usually, their Windows hardening documents are over a hundred pages long and would take a long time to perform hardening manually by one person. Thankfully, there is an Infrastructure-as-Code configuration management approach, e.g. the one introduced below leveraging Chef and Inspec, to achieve automation of the hardening process and validating the results.

Figure 1. Content of harden_winrm.rb, with references from CIS sections as an example of Chef recipes. (This one is from MattTunny/windows_hardening GitHub repository)

Instead of demonstrating the power of infrastructure-as-code fully, this quick post only aims at introducing the concept by leveraging Chef hardening recipes found on the Internet, showing the steps to perform Windows hardening on a single local machine quickly, which may suit one-time use cases. We will install Chef Development Kit to use chef-apply for applying Chef recipes to harden a local machine and inspec for verifying the hardening.

Figure 2. Installing Chef Development Kit

Tools Required

  • Chef cookbook or recipes for Windows OS hardening (e.g. ones from my GitHub repository, wandersick/windows_hardening, originally forked from MattTunny/windows_hardening GitHub repository, adjusted to fit the examples using the chef-apply approach in this article. You may use others found on the Internet, or craft ones that are catered to your needs)
    • Download Here: https://github.com/wandersick/windows_hardening
    • Chef recipes crafted according to CIS-published guidance
    • The example is for Windows Server 2012 and 2012 R2, which is similar for Windows Server 2016 and 2019
    • You should customize. Here are some ideas:
      1. You may not want to run some of the recipes which break functionalities such as harden_winrm.rb (WinRM)
      2. In core_hardening.rb, you may want UAC to be disabled (EnableLUA not set to 1), the warning text displayed at logon to be different (legalnoticecaption and legalnoticetext edited as desired). For domain controllers, you may want EnableAuthEpResolution to be removed in order to enable certain cross-forest/domain scenarios. For older printers/scanners/copiers not supporting NTLMv2 for scanning to shared folders, or you are encountering an issue in which Remote Desktop client keeps saying ‘the logon attempt failed’, you may want LmCompatibilityLevel to be removed. Similarly for RestrictNTLMInDomain if it is a domain controller.
      3. In localComputer.inf, you may prefer NewAdministratorName and NewGuestName are not set to _administrator and _guest, the maximum password age (MaximumPasswordAge) not set to 42 days, and the minimum password length (MinimumPasswordLength) not set to 14
      (All of the above are the default settings from the recipes)
    • Some recipes require specific variables to be defined such as cwd for the path to some configuration files. Be sure to refer to the README.md and comments within each recipe file.
  • RegShot (optional)
  • Chef Development Kit installed on a supported Windows machine
    • Download Here: https://downloads.chef.io/chefdk#windows
    • Although the Chef Development Kit provides a lot of features, we will only use chef-apply for applying recipes (.rb files) to a local machine and inspec for verification
    • The version used for this article is 4.4.27-1
  • Windows Command Prompt (run as administrator)
    • Commands are highlighted and shown in this font: Courier New
Figure 3. Ensure Command Prompt is run as administrator; otherwise, chef-apply would not run correctly later

Pre-Hardening Configuration

  • Install Chef Development Kit (on the target system to be hardened) with default settings
  • Add C:\opscode\chefdk\bin\ to the environment variable – %PATH% – if not already added
  • Store downloaded windows_hardening directory like our working directory in this example, c:\temp\windows_hardening instead of c:\temp\windows_hardening-master which may be inaccurately shown in a few screenshots

Pre-Hardening Verification and Backup

  • For safety, back up the OS drive or at least the system state (most of the settings are registry settings)
  • Perform 1st shot with Regshot 2.0
    • Save as .reg and .hive files (for an additional backup)
    • Do not close the Regshot application yet (a 2nd shot will be run at the end of hardening as a comparison)
  • Verify with inspec
    • cd C:\temp\windows_hardening\
    • inspec exec test/integration/default/default_spec.rb
    • See a report of what have been hardened and what have not, for comparison later
Figure 4. Performing inspec checking before hardening reports a lower number of successful items

Performing Hardening

Friendly reminder: Ensure this is not done to a system without thoughtfulness — it is normal for issues to emerge after hardening. Think twice. Still insist? OK. Here you go!
  • First, ensure we are at the directory containing recipies.
    • cd C:\temp\windows_hardening\recipes
    • dir
  • Run the listed OS hardening recipes as needed with one of the chef-apply methods below.
    • Either apply recipes one by one using chef-apply, e.g. on the main recipe file containing most of the settings by replacing <file_name>.rb with core_hardening.rb:
      chef-apply <file_name>.rb
    • Or apply multiple recipes in a go using chef-apply with a for loop:
      for /f "usebackq tokens=* delims=" %i in (`dir /b`) do chef-apply "%i"
Figure 5. Applying OS hardening Chef recipe with chef-apply. (Note: some errors may be inevitable)


  1. For the for-loop technique, please be reminded to carefully examine what will execute as all contents from all recipes will be applied. Some recipes may have a special purpose which are not meant to be run this way.
  2. Most hardening rules should work out of the box and do not require further modification if you choose the compatible Chef recipe named core_hardening.rb (where most of the hardening rules reside).

Side Talk – Ideas for Fixing Recipes Incompatible with chef-apply

Let's take a detour before verifying the results.
The use of chef-apply in this post (treating Chef recipes as scripts), while straight-forward for a quick demonstration or use, is not how Chef is run in production. If you use recipes not from my GitHub repository, you might encounter errors for those incompatible with chef-apply. As an example, in this repository, a majority of errors encountered in the incompatible recipes were eliminated by manually modifying .rb files.
For example, for the harden_winrm.rb recipe in Figure 1 (the top of this article), remove the first line after comment, and ifend outer wrapping from each block, leaving only inner registry_keyend parts.

Figure 6. An edited version of harden_winrm.rb (lower) in comparison with before hardening, i.e. Figure 1 (upper), where the outer if … end has been stripped off

Figure 7. After the edit, 'chef-apply harden_winrm.rb' command runs correctly

Post-Hardening Verification and Backup

  • Verify with inspec again after applying (Observe the increase in the count of passed items and the decrease in failed items.
    • cd ..
    • inspec exec test/integration/default/default_spec.rb

Figure 8. Running the inspec command – 1st run vs 2nd run (on an unsupported Windows 10 1709 testing machine for showing the difference only) where the second run has a lower number of failed items
The test run results in Figure 9 are available are available for viewing here

Figure 9. 213 successful and 0 failed items are reported in a case of mine on Windows Server 2016, where my chef-apply-adapted fork of the hardening recipes were used
  • Perform 2nd shot with Regshot 2.0 (to compare registry changes)
    • Save reg and hive files (for backup)
    • Back up C:\HIVE folder (the Regshot working folder) which contains UNDO and REDO reg files which are useful for rollback and for understanding what has changed. (Note that some keys are unrelated due to other concurrent Windows activities)
Figure 10. Regshot 2.0 produces RedoReg.txt and UndoReg.txt
For failed items reported by the second inspec run which should be much lower now (e.g. 25 failed items in a case of mine on Windows Server 2016, where some of the recipes were left out intentionally; 0 failed items in another case. In case of any issues, you can also always fix them according to the official CIS Benchmarks documents. For user rights and privileges, it would be useful to refer to Microsoft documentation as well.

Is It Good to Go?

Although this is a Windows hardening method that is not officially supported (use it at your own risk), when tested and implemented correctly by a DevOps engineer familiar with Infrastructure-as-Code (here are freelancing marketplaces to hire one), it is an elegant way to improve productivity and make sure security settings are correctly applied in a way that is less prone to human errors. For home servers or labs that do not require as much consideration, this is a godsend. Thanks!