top of page

AwesomeOps Part 5: Ansible CIS Windows Images

Updated: Jun 27, 2023

More AwesomeOps? Yes please! Today we will cover one of the most overlooked topics within organizations. Hardened Windows images. Yes we know, securing Windows images is not as exciting, fun, and cutting edge as Artificial Intelligence(AI) and Large Language Models (LLMs), but our approach to automation platform design has always been that when you start to build a house worry about the foundation before the type of couch you want in your living room. With that said, let's get after it.

Windows CIS Context:

Center for Internet Security (CIS) is a global, security focused, non-profit organization dedicated to identifying and mitigating security risks. CIS Windows Server images are approved standards by DoD Cloud Computing Security Recommendation Guide (SRG), Payment Card Industry Data Security Standard (PCI DSS), Health Insurance Portability and Accountability Act (HIPAA), Federal Information Security Management Act (FISMA), Federal Risk and Authorization Management Program (FedRAMP), the National Institute of Standards and Technology (NIST), and Defense Information Systems Agency Security Technical Implementation Guide (DISA STIG). You get the point. If you run a company, and you are required to conform to industry security standards and compliance requirements, you need to bake Windows Server CIS base images.


Hardening your base has historically been the best way to defend against potential intruders. Good guys create systems to protect people and property and bad guys try to break those systems is a tale as old as time. In the thousands of years of recorded human history everything has changed, and yet nothing has really changed. During the siege of Dura in C.E. 256 the Romans built large walls and towers to defend, while the invading Persians built tunnels underneath the walls and towers in an attempt to compromise the structures. Medieval times saw defense engineers expand upon, and in some cases literally build on top of, the ancient defense systems of the Romans and others. Massive towers, moats, arrow slits, and gun-ports all provided incredible protection, but given enough time attackers could eventually break through most defenses(learn more here and here). And finally, all the way up to our current Information Age where digital castles built by the wealthiest companies to ever exist have been hacked and information stolen and used on the dark web(checkout just a few here and here ). All of this is to say, security is the most important aspect of every business, and the first place to get started securing your organization is all the way at the base/OS that enables all of your applications to run.


Now, maybe you and your team think security is time consuming and a hassle that is not worth it because it is not directly generating revenue. If that is the case, consider this. According to the Harvard Business Review (HBR), the cost of a single cyber incident in the US was an estimated $9.44 million in 2022! To add context to this, HBR goes on to explain that the healthcare sector alone in 2021 suffered approximately $7.8 billion due to Ransomware! Those are staggering numbers. Maybe instead of thinking about things in terms of revenue generating vs security, we can start thinking about security as a necessity to the continued ability to generate revenue.


While all of this may seem daunting, the best way to defend your organization is to just start securing your base in small incremental steps. Sun Tzu said it best: "Whoever is first in the field and awaits the coming of the enemy, will be fresh for the fight; whoever is second in the field and has to hasten to battle will arrive exhausted." Creating hardened images will make your organization first in the field, and set you up with tactical success. Creating an automated process to release hardened images will provide a strategic advantage and ensure your enemy arrives exhausted as they have to continuously adapt to your every shifting and hardening base.


Ok! We get it, hardened Windows Server 2022 CIS images are important. But how do we ensure hardened Windows Server CIS images in our organization? Great question to help us transition into the tech.


The Tech: Packer + Ansible

If you have NOT been following along with our AwesomeOps series, you can find the full series here and just hit ctl-f to search "Awesome". We will take a quick intermission while you read through the other posts, no seriously, we will wait.

If you have read through the the AwesomeOps series, we would like to say thank you and feel free to skip ahead past the long intermission. :)


To secure your base you will first need to have a Packer setup that can build identical Windows images across all of your compute platforms. Creating a single baseline Windows Server 2022 image that is distributed to all hosting environments is critical to the long term success of your hardening initiative, because one of the tricks we use to keep things simple is to reuse the same Ansible playbooks that create images to also run drift control pipelines on living infrastructure. You will want to open up your favorite text editor, clone your packer repo(s), and create a feature branch from develop to follow along here. Directly after your Packer code block here:

source "vsphere-iso" "windows-server-2022-cis" {}

You should have a section called build. If you do not have this section, go ahead and create it now. It should look similar to this:

build {
  sources = [
    "source.vsphere-iso.windows-server-2022-cis-dap"
  ]

  provisioner "windows-update" {
    pause_before    = "30s"
    search_criteria = "IsInstalled=0"
    filters = [
      "exclude:$_.Title -like '*VMware*'",
      "exclude:$_.Title -like '*Preview*'",
      "exclude:$_.Title -like '*Defender*'",
      "include:$true",
      "update_limit: 25"
    ]
    update_limit = 25
  }

  provisioner "ansible" {
    command       = "../../../scripts/windows/win-cis-ansible-wrapper.sh"
    playbook_file = "${var.ansible_cis_dir}/site.yml"
    user          = var.build_username
    use_proxy     = false
    extra_arguments = [
"--extra-vars", 
"become_method=runas 
ansible_windows_domain_role='Stand-alone server' ansible_winrm_server_cert_validation=ignore ansible_password=${var.build_password} win19cis_admin_username=${var.build_username} win19cis_guest_username=scrambledguest newadministratorname=${var.ansible_username} newguestname=scrambledguest 
run_audit=true 
long_running=true 
is_implemented=false 
win2016cis_is_standalone=true 
audit_time=post 
audit_capture_path=${var.ansible_cis_dir} 
section01_patch=true 
section02_patch=true 
section09_patch=true 
section17_patch=true 
section18_patch=true 
rule_1_2_1=false 
rule_2_3_1_1=false 
rule_9_1_1=false 
rule_9_2_1=false
# This control will have to be set to Enabled (1) to allow for continued remote management via Ansible following machine restart
rule_18_9_102_2_2=false
# This control will have to be set to Enabled (1) to allow for continued remote management via Ansible following machine restart
rule_18_9_103_1=false 
min_ansible_version=2.9"
]
  }

  provisioner "powershell" {
    script = "../../../scripts/windows/winrm-setup.ps1"
  }

  provisioner "windows-restart" {
    restart_check_command = "powershell -command \"& {Write-Output 'restarted.'}\""
  }
}

Cool. So there is a lot going on here. Let's go step by step. The first thing we do is run a Windows Server update (more on that here on github) with a few filters that are specific to our environment. To do this we added this block of code:

provisioner "windows-update" {
    pause_before    = "30s"
    search_criteria = "IsInstalled=0"
    filters = [
      "exclude:$_.Title -like '*VMware*'",
      "exclude:$_.Title -like '*Preview*'",
      "exclude:$_.Title -like '*Defender*'",
      "include:$true",
      "update_limit: 25"
    ]
    update_limit = 25
  }

This will ensure that your new image gets the latest and greatest updates from Microsoft. Next we use the Ansible provisioner (more on that here):

provisioner "ansible" {}

There are five basic parts to make this work.

  1. command = "../../../scripts/windows/win-cis-ansible-wrapper.sh"

  2. playbook_file = "${var.ansible_cis_dir}/site.yml"

  3. user = var.build_username

  4. use_proxy = false

  5. extra_arguments = []

The command is the script or command you want to execute, the playbook_file is the main yml file that you want to kick off, the user is the user that you would like to connect to the host as, use_proxy is a bool that specifies if this build should use a proxy(if you work in an enterprise you know the pain of proxies), and finally the extra_arguments is how you pass in all of your key value pairs to your Ansible playbook specified in the playbook_file. But what is going on with all those extra variables? Glad you asked. There is a lot going on here, so let's take a closer look:

extra_arguments = [
"--extra-vars", 
"become_method=runas 
ansible_windows_domain_role='Stand-alone server' ansible_winrm_server_cert_validation=ignore ansible_password=${var.build_password} win19cis_admin_username=${var.build_username} win19cis_guest_username=scrambledguest newadministratorname=${var.ansible_username} newguestname=scrambledguest 
run_audit=true 
long_running=true 
is_implemented=false 
win2016cis_is_standalone=true 
audit_time=post 
audit_capture_path=${var.ansible_cis_dir} 
section01_patch=true 
section02_patch=true 
section09_patch=true 
section17_patch=true 
section18_patch=true 
rule_1_2_1=false 
rule_2_3_1_1=false 
rule_9_1_1=false 
rule_9_2_1=false
# This control will have to be set to Enabled (1) to allow for continued remote management via Ansible following machine restart
rule_18_9_102_2_2=false
# This control will have to be set to Enabled (1) to allow for continued remote management via Ansible following machine restart
rule_18_9_103_1=false 
min_ansible_version=2.9"
]

Don't worry, it is not as bad as Homer is making it out to be. All we are really doing is flipping some light switches on and off. This playbook rocks by the way, and a big shout out to the MindPoint Group who developed it. Definitely take some time and review this playbook because it is really slick. To really understand how this playbook works, we need to dig into the tasks.

- name: "18.5.8.1 | PATCH | Ensure Enable insecure guest logons is set to Disabled"
  ansible.windows.win_regedit:
      path: HKLM:\Software\Policies\Microsoft\Windows\Lanmanworkstation
      name: AllowInsecureGuestAuth
      data: 0
      type: dword
  when:
      - rule_18_5_8_1
  tags:
      - level1-domaincontroller
      - level1-memberserver
      - rule_18.5.8.1
      - automated

The task are neatly organized into sections, each section has a series of tasks, and each task has a number and conditionals and tags. If you recall, this is a similar strategy we use in our baseline Ansible playbooks used in drift control (blog here). By combining multiple strategies into one playbook you can turn CIS checks on and off like a light switch depending on the compliance requirements your company is required to adhere to. As you can see above, we first turn on all sections by simply stating the name of the section and then true.

section01_patch=true

Then we can target individual tasks within each section by specifying the name and specifying false:

# This control will have to be set to Enabled (1) to allow for continued remote management via Ansible following machine restart
rule_18_9_102_2_2=false

Remember to always review the code! We found that we needed to disable the rule above so that we could continue connecting to hosts with WinRM. Let's take a look at this task and see what is going on:

- name: "18.9.102.2.2 | PATCH | Ensure Allow remote server         
  management through WinRM is set to Disabled"
  ansible.windows.win_regedit:
  path: HKLM:\Software\Policies\Microsoft\Windows\Winrm\Service
  name: AllowAutoConfig
  data: 1
  type: dword
  when:      
    - rule_18_9_102_2_2      
    - not win_skip_for_test
  tags:      
    - level2-domaincontroller      
    - level2-memberserver      
    - rule_18.9.102.2.2      
    - automated

Nice! This is a simple Ansible task that uses the Ansible Windows Collection Module found here. This is an incredibly helpful module that will pay dividends when modifying your Windows Server 2022 infrastructure. Remember when we first introduced this module here in the AwesomeOps series? There is a good reason for that! We use this module extensively in our engagements to rapidly reduce time to value with Ansible, because who honestly wants to write PowerShell all day (some among us were PS devs and fanboys for years, so we know the pain. hehehe). Instead of long scripts, you can declaratively tell Windows Servers to update registry entries with just: the path, name, data, and type. The icing on the cake of this task is that it is self documenting code. No more looking through long PowerShell scripts for randomly named functions, just read the code and you will be able to understand what is going on.


Once your Packer build completes you will have a hardened Windows Server 2022 image that you can deploy to all compute platforms supported by your company. Now if you really want to maintain compliance, you will want to take the same CIS playbook and put that into a scheduled pipeline so that all of the hosts in your golden inventory have the same playbook run against them on a daily basis. And the final coup de grâce to ensure drift is controlled is to make sure the output of all of your Ansible and Ansible Automation Platform (AAP) runs are sent to Elasticsearch so you can create compliance dashboards. Hrm, maybe that will be the topic of another AwesomeOps post.


Next Time on AwesomeOps

Well, that was fun! Hopefully this blog, and the blog series as a whole, helps you and your organization on the path of AwesomeOps.


After our last post on Ansible drift control we created a poll to see what automation topic people would like to read about next. And while Ansible CIS images came out on top, a deep dive on Windows drift control came in second place. So, next week we will have a lengthy blog on how to control your Windows Server drift.


Until next time, you stay classy world.


138 views0 comments
bottom of page