A guide on automating CIS Compliance for Ubuntu with Ansible Lockdown

EvanEvan
9 min read

Today I’m taking a look at MindPoint Group’s Ansible Lockdown project—a powerful, open-source tool for automating security compliance. Maintained as part of their broader Lockdown Enterprise suite, this project provides prebuilt Ansible roles aligned with industry-standard benchmarks like CIS and STIG. It’s a solid starting point for hardening systems across a range of platforms, including Ubuntu, RHEL, Amazon Linux, and Windows.

I will be demonstrating the CIS benchmark role with a newly installed Ubuntu 22.04 host, implementing the level 1 and some level 2 controls. If you’d like to dig deeper, I’ve included links to the project’s GitHub repository and official documentation.

This guide assumes you are familiar with Ansible and the CIS benchmarks, and that Ansible is already installed and working on your machine.

Why automate compliance?

So, why might you be looking to automate compliance in the first place?

As more organizations shift workloads to the cloud—especially in SaaS—security becomes a critical focus. Compliance requirements are increasingly strict, particularly in regulated industries like healthcare, finance, and government. To meet these demands, many teams are choosing to integrate preconfigured cloud system images in these environments since they come with software packages and/or security-focused configurations that provide a jump start to defining the base configuration for system resources.

In AWS, for example, you’ll find CIS Hardened Images—AMIs (Amazon Machine Images) that are purpose-built for secure, compliant environments. For example, you can find the CIS Hardened Image Level 1 for Ubuntu in the AWS marketplace. These images are incredibly convenient, but they come at a cost: higher hourly operating fees and annual license costs. For large-scale or cost-sensitive environments, this pricing model can quickly become a burden. This not a viable solution for hybrid environments where AMIs are simply not available to deploy for the on-premise machines.

System administrators who have tried it know that hardening a system by hand is very tedious work and is very time-consuming. Doing it manually across multiple systems? That is a tremendous headache.

Enter Ansible.

Ansible gives you the power to define and enforce your security baselines from a single control node, pushing configuration changes across hundreds—or thousands—of machines. This is why Ansible is now considered a necessary skill for DevSecOps engineers. Automation not only speeds up deployment but it makes compliance repeatable, auditable, and scalable.

Automating security hardening is a practical approach to compliance. Unlike using a pre-configured image, it allows for complete customization and greater control right from the beginning.

The Ansible Lockdown project takes this a step further by offering compliance-as-code.

What stood out to me is how accessible and transparent it is. As an engineer who likes to test and experiment in my home lab first, I appreciate being able to pull the roles directly from GitHub and get a clear overview from the documentation. It’s well-organized, actively maintained, and offers solid coverage for a range of operating systems.

This tool is particularly attractive if you're seeking an open-source alternative to prebuilt cloud images or costly compliance solutions and are already using Ansible. It's flexible for hybrid environments and, being based on Ansible, integrates smoothly with existing automation workflows.

This post is my first hands-on experience with the role, and I’m exploring it from the perspective of someone who’s considering it for use on future projects.

Let’s get started.

Setup

The Ansible Lockdown role works best when applied to a freshly installed system with a base configuration. Running it on a production server, or any system that already has applications in place, can and likely will cause services to break. This is because the role enforces strict security configurations that can interfere with existing setups.

Before running the benchmark, make sure your server is set up with the required disk partitions, as specified in the CIS Benchmark controls. For level 2, you’ll want the following mount points:

  • /var

  • /var/tmp

  • /var/log

  • /var/log/audit

  • /home

These partitions are required so the benchmark can enforce mount options like nodev, nosuid, and noexec, which play a role in preventing privilege escalation and unauthorized code execution.

Before running the playbook, make sure that root access is configured with a password on the target host and that SSH key-based authentication from your Ansible control node is working properly.

You might also consider setting a GRUB password to harden the bootloader. While the Lockdown role can configure this for you, it’s worth noting that this control (along with a few others) is disabled by default. That’s because improperly setting a GRUB password can lock you out of the system if it’s misconfigured.

Installation

You can install the Ansible Lockdown role directly from the project’s Git repository using the ansible-galaxy command. This tool is used to manage Ansible roles and collections, making it easy to pull in reusable automation code from external sources.

The Ansible Lockdown project is structured as a single repository that contains multiple roles for different operating systems. When you install a role using ansible-galaxy, it organizes the role’s tasks, handlers, and defaults into a structure that Ansible can manage.

Here’s how you can install the Ubuntu 22 role from GitHub:

ansible-galaxy install git+https://github.com/ansible-lockdown/UBUNTU22-CIS.git

This will place the role in the current user's ~/.ansible/roles/ directory.

Auditing

To evaluate the effectiveness of the role, I’ve used Wazuh to monitor my Ubuntu 22.04 instance. Wazuh is a free and open-source security platform used for threat prevention, detection, and response across various environments, including on-premises, virtualized, containerized, and cloud-based workloads. Wazuh includes its own CIS compliance assessment module, which runs checks based on the CIS benchmark.

Before applying the Lockdown role, the system scored a 48% compliance rating out of the box. I’ll be using Wazuh again after applying the playbook to see how much that score improves.

One of the most useful features of the Lockdown role is the ability to run it in audit-only mode. This allows you to check your system against the CIS benchmark without making any changes, which is great for testing.

You can configure audit mode either by passing variables directly on the command line with -e like this:

ansible-playbook -i /etc/ansible/hosts -e '{ "setup_audit":true,"run_audit":true, "audit_only":true, "grub_user_pass":  }' ./site.yml -K

Or, you can directly modify the role’s configuration file.

~/.ansible/roles/UBUNTU22-CIS/defaults/main.yml

Set the following variables to true:

setup_audit: true
run_audit: true
audit_only: true

Also, set the get_audit_binary_method to "download" so that the required GOSS binary (used for audit checks) can be fetched automatically from GitHub:

get_audit_binary_method: "download"

Once these settings are in place, you’ll be ready to perform your first audit without making any system modifications.

a peek at the main.yml for Ubuntu 22

One of the many configurable options in main.yml is whether to set a GRUB password.

However, I ran into an issue when I left the grub_user_pass variable unspecified. The playbook failed to run, which may be a bug, though it’s possible I misunderstood the intended behavior. I plan to report this on the project’s GitHub repository.

Fortunately, there’s a simple workaround:

  • If no GRUB password is configured on your system, you can pass an empty value for grub_user_pass when running the playbook:

      -e '{ "setup_audit":true,"run_audit":true, "audit_only":true, "grub_user_pass":  }'
    
  • If there is GRUB password set, pass it in like so:

      -e '{ "setup_audit":true,"run_audit":true, "audit_only":true, "grub_user_pass":your_secure_password  }'
    

This avoids the failure and allows the role to handle GRUB settings correctly based on your input.

Alternatively, you can run the GOSS audit from the host using the run_audit.sh script. This is done after running the playbook once to set up the audit files. By default, these files are placed in the host’s /opt directory, but you can change this in main.yml.

Here is an example of an audit summary after the playbook completes:

Remediation

To move from audit mode to actual remediation, you can run the playbook using a similar command as before—just make sure to set audit_only to false.

ansible-playbook -i /etc/ansible/hosts -e '{ "run_audit":true, "audit_only":false, "grub_user_pass":  }' ./site.yml -K

Alternatively, you can update the main.yml file directly.

As the playbook runs, there may be errors that come up such as this error regarding the auditd service:

My experience was that I encountered some errors whenever a control could not be enforced due to a conflicting system state or a missing dependency.

In these cases the options are to:

  • Fix the issue, and then re-run the playbook. On subsequent runs, Ansible will skip controls that have already been applied.

  • Temporarily disable problematic controls in main.yml, and circle back to resolve them manually later.

In my case, after just a couple of remediation runs (each taking about 3–4 minutes), the system's compliance score improved significantly. Wazuh now reports 75% compliance, up from the initial 48%.

We can now see the benefit of automation in the hardening process—what could have taken days to implement manually is handled in minutes with repeatable, testable code.

Audit again

To get a more comprehensive view of the system’s compliance, I will scan this instance using Lynis, another automated hardening and auditing tool. Lynis is a well-established, open-source project licensed under the GPL and first released back in 2007. It’s widely respected and actively maintained.

What makes Lynis especially useful is that it evaluates your system against multiple security standards—including CIS, NIST, NSA, and OpenSCAP—providing a broader, standards-based perspective beyond what a single benchmark offers.

In a real-world environment, it’s not enough to say a system is “probably secure”—you need proof. Demonstrating compliance is make-or-break for passing audits, especially in regulated sectors. That’s why tools like Wazuh and Lynis are part of my workflow here. They give you third-party visibility into how well the hardening actually aligns with the standards. Running both gives you a fuller picture of where your host stands and helps catch anything that might’ve slipped through. And Lynis is lightweight and fast to get going, which makes it a no-brainer.

Installing Lynis and running a quick scan is straightforward and lightweight. Here’s how to get started.

git clone https://github.com/CISOfy/lynis

cd ./lynis

./lynis audit system -Q

Lynis also reports similar results, highlighting the hardening index. This helps confirm that several controls are indeed implemented on this system.

Once the Ansible Lockdown playbook completes, it generates a .json report in the host’s /opt directory. This file includes the results of every control that was tested, giving you a clear view of what passed, what didn’t, and what still needs attention.

This is very helpful for fine-tuning your configuration, validating changes, and identifying anything you might want to address manually.

After just a few more minutes of manual tuning, I saw another bump in compliance—Wazuh is now reporting 81% against the CIS benchmark. That’s a significant jump from the original state, and it highlights just how effective this kind of automation and iterative testing can be in creating a new baseline.

Conclusion

The Ansible Lockdown project by Mindpoint Group is a solid way to jump-start the hardening process in Linux environments. It’s flexible, accessible, and fits naturally into an infrastructure-as-code approach.

But let’s be clear—compliance isn’t a one-size-fits-all checklist. It needs to be considered from the start of the system’s lifecycle and shaped around the organization’s unique business rules and regulatory obligations. Automation should support that strategy, not define it.

With a bit of planning and customization, this role can help enforce real-world compliance standards in a way that’s repeatable, scalable, and cost-effective.

0
Subscribe to my newsletter

Read articles from Evan directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Evan
Evan

Hi, I’m Evan — an engineer with a passion for automation, security, and building resilient cloud infrastructure. I spend a lot of time in the weeds solving real-world problems, whether that’s through client work or experiments in my homelab. This blog is where I document those lessons, not just to keep track of what I’ve learned, but to share practical insights that others in the field can apply too. My focus is on bridging the gap between security best practices and operational efficiency, using tools like Ansible, Docker, and AWS to make systems both secure and manageable. Whether you’re planning your infrastructure, hardening environments, or just learning the ropes, I hope these posts give you something useful to take with you. I’m always learning too, and this blog is as much about sharing the process as it is the results. Thanks for stopping by — let’s keep learning and building together.