Tag: ansible

  • Assembling a STIG Checklist Automation Tool for RHEL 9 with Ansible

    … and Claude Code.

    AI Disclaimer: No chatbots were used in writing this blog post. The code in focus was heavily augmented by AI, though, and the commits and README indicate as such.

    Seriously, I didn’t use AI for this post, this is really just how I talk. πŸ™‚

    Recently I finished my first release of a command-line utility that uses Ansible playbooks to comprehensively validate the STIG compliance of Red Hat EL 9.x servers and produces 100% filled-out and ready-to-submit Checklists (CKLB files, a la STIG Viewer 3.x).

    The project is open source and is available for download on my Codeberg repo: https://codeberg.org/openaiken/rhel-stig-full/src/branch/v1.0/


    High Level Overview

    DISA, who authors STIGs, also releases an Ansible role that implements and validates approximately 2/3 of the rules for the Red Hat Enterprise Linux Operating System STIG. That role amateurishly produces an XCCDF (XML file with raw results), which can be imported in the STIG Viewer application so the System Owner can complete the checklist.

    This utility encompasses the DISA Ansible role unaltered, and accomplishes several other things:

    • Adds checks for all of the remaining 1/3 of the STIG rules
    • Facilitates the System Owner attesting to the compliance of certain controls with Ansible host/group variables
    • Merges the results from both sets of checks
    • Fully populates a proper STIG Checklist file
    • Wraps all of this up into a single bash utility script
    • Supports groups of multiple hosts

    At release 1.0 of rhel-stig-full, RHEL 9.7 hosts are validated against the RHEL 9 STIG V2R8, but the only supported remediations are from the original official role from DISA. Release 2.0 will include support for remediating as much as possible.


    Design & Development

    The project README details how to use and maintain the tool. This post elaborates on how I made it. I recognize the quandary of referring to AI-generated assets as something one “made”. I used the verb ‘assemble’ for the title but am otherwise claiming that I made it, for simplicity.

    The augmentation of this project with Claude Code is an essential part of the story. Before chatbots could work like this, writing and maintaining a STIG automation tool required at least one full time position on a program, which clients often aren’t willing to pay for, or would often be sold as an off-the-shelf product. I’m just a boy. With Claude Code I was done in a couple weeks.

    Development pattern

    The STIG benchmark is thousands of lines long, 446 rules at time of writing, and some lines are hundreds or thousands of characters. Fortunately the nested content is written for humans to understand. I instructed Claude to convert the benchmark file into a JSON document that it could index into efficiently. The JSON includes a boolean to indicate whether the rule was covered by the official DISA ansible role.

    STIG rules have several naming conventions for identifying them, for reasons only fully comprehended by the Lord. I used “STIG IDs” in this project, because they communicate what STIG the rule is for, what control category the rule addresses, which specific rule, and a sort of built-in way to indicate version control/succession. This made organizing the code simple and will simplify maintenance. This also simplified the artifact organization (directory layout).

    This design structure enabled me to instruct Claude to evaluate one category of rules at a time, in context-window-friendly batches (due to ability to index into the JSON instead of parsing all of it and staying ‘focused’ on one topic at a time), only addressing rules not covered by the existing code.

    Pipeline Structure

    The project uses 3 Ansible roles: the “formal” DISA role, the supplementary role, and the checklist rendering role.

    The formal role, in its original state, includes a poorly written Ansible Callback module for producing the XCCDF file. One of its problems was not supporting running the play on a group of multiple hosts. While this project leaves the formal role code unaltered, I did delete this callback and I rely on not enabling it in future revisions. However, I did have Claude write a new callback that stores the data in the authentic XCCDF format, with host data, on the controller executing the script. This is a useful compliance artifact because it is effectively the result of the original official DISA Ansible role. The formal role executes in its own playbook to benefit from the callback artifact. This led me to write separate playbooks for each of the 3 roles.

    The supplement role is similar to the formal role in that it uses the Ansible Facts runtime memory system to produce a JSON document with results from the role’s validation tasks.

    The checklist renderer ingests an empty copy of a RHEL 9 V2R8 STIG checklist file (CKLB, the JSON variant), and systematically populates all of the rules with the corresponding data from the latest available XCCDF and the latest available supplementary facts, and writes the resulting CKLB to the configured reports location.

    This means that all 3 playbooks can be run separately — which is good, because the STIG roles are very slow. It can take 10-15 minutes to execute on one host running as a VM locally, but in my work lab environment with older hardware, an enterprise network, and a dozen targets, it can be hours. And if you’re focused on STIG rule remediation from one role or the other, you can rerun the validation from just that role and re-use the old results from the other role in the checklist renderer.

    Human Attestation

    One of the primary complicating factors of simple automation of STIG compliance is just that many of the rules are vague, highly dependent on organizational and business/mission context, and require discretion. Fortunately, Claude did a good job interpreting which rules could be statically analyzed and which would require input from the System Owner to accurately represent the compliance status.

    For example, STIG ID 251035 requires that all open firewall ports/protocols comply with PPSM CAL for your program. This is a policy control, not a security control. Unless you want to build a database or web endpoint that your Ansible controller can query and validate your host firewall zone settings against, you will simply have to attest to whether the open ports are authorized.

    The Ansible host var rhel9_attest_ppsm_compliant can be set by host or host group to be true or false, and the supplement role uses the values of these variables in its logic for declaring the Rule status (Not a Finding, Open, N/A). Some of these include a free text variable that will be included as a comment automatically as well, so that organizational and operational context can be included in the checklist deliverables.

    Project Maintenance

    Each new STIG revision includes a changelog detailing what corrections and alterations have been made. When a new revision is released (e.g., V2R9 presumably), I will export a new empty checklist file using STIG Viewer 3.7 and the new STIG version, drop it in roles/rhel9STIG/files/ for the project, overwrite the whole official Ansible role if applicable, paste the changelog into Claude Code, and instruct it to implement the changes, to include updating the files/rules.json content, adding and removing STIGs by STIG ID (remember, a superseding rule to 251035 is likely 251036, or similar, so the old task file may need to be disabled and the new one added in), and retrofitting new policy.

    All of that can be done manually as well, without Claude Code. The changelogs for revisions are usually small. New versions are heftier.

    Future plans

    • Rule remediation support for all supplementary role tasks
    • Comprehensive replacement of the official DISA Ansible role with a new one matching the supplement role’s structure, to run more efficiently and facilitate more nuanced remediations
    • Application STIGs for common lightweight apps such as Apache/Nginx web reverse proxies and Podman containerization (Container Platform SRG)
    • RHEL 10 support and future major releases
    Fediverse Reactions