Automating STIG Deployments with Ansible and Gitlab
If you are required to maintain compliance in a DoD environment or government contracting program, you are most likely already familiar with the concept of Security Technical Implementation Guides (STIGs). A STIG is a standardized set of configurations consisting of security requirements for a specific product, which can be anything from a Windows Server or Linux system to a network device. The main way to implement a STIG is to just configure the security controls manually. However, in this article we discuss how you can automate a STIG deployment on Linux systems using an Ansible playbook provided by DISA (STIGs Document Library – DoD Cyber Exchange) coupled with GitLab.
What is GitLab?
GitLab is a complete DevOps/DevSecOps platform. GitLab is feature packed for Continuous Integration/Continuous Delivery (CI/CD). It provides a wide range of integration features and complete toolset. Aside from CI/CD pipelines and source code management (“SCM”) GitLab also offers a host of other capabilities, including, but not limited to, issue tracking, vulnerability management, monitoring, and package and container registries. GitLab uses YAML for all its CI/CD pipelines, which makes for much cleaner pipeline configurations.
GitLab has both paid and free versions, allowing you to only pay for what you need; there are quite a few features included in the free version. You can also choose to host a GitLab instance in your own environment or use GitLab cloud. We prefer GitLab Ultimate, once you go Ultimate, it is hard to go back.
What is Ansible®?
Ansible is a tool that uses infrastructure as code to do a multitude of things, including, software provisioning, configuration management, and application deployment. For our purposes, we will be using it as a configuration management tool. We will be using it on Linux systems in this blog, but you can also use it on Windows. SSH is used for connecting to remote systems and can utilize any authentication method available to SSH; for simplicity, we’ll be using password authentication.
Why Automate STIG Deployments with GitLab?
The process for deploying STIGs can be cumbersome when it all needs to be done manually; especially if you have hundreds, or even thousands, of machines that you need to keep compliant. Most environments are likely Windows based, so deploying STIGs may not be so bad because DISA provides Group Policies Objects, which can be applied to entire Organizational Units (OU's), to keep systems compliant.
But what if you have Linux systems? That is where Ansible comes in. DISA provides a few Ansible playbooks for different Linux flavors, network devices, or even Docker. Now that you know what GitLab and Ansible are as well as why you would use them, let’s start talking about how this can all be implemented.
What You Will Need
Since the STIGs are provided as an Ansible playbook, all you must do is set up your GitLab repository file structure to make use of any, and all, playbooks you may need. There isn’t a lot that is needed to build this setup. The systems and software needed for each are listed below:
1 Linux system
Linux (Ubuntu or RHEL):
- Sshpass (only needed if using password authentication)
- GitLab Runner
As you can see, the setup is rather simple. The GitLab runner is used to run the pipeline on an internally hosted system instead of using GitLab’s provided shared runners. The reason this is needed is because the shared runners would not have access to the systems that need to be scanned in your environment.
Put it All Together
Now that we have some background on all the hardware and systems needed, let’s look at how it all works. To start, this is the structure of the GitLab repository.
NOTE: If you want to deploy STIGs using Ansible, you will likely need to add “become: true” to the site.yml files that are in the root of each downloaded DISA Ansible zip file.
As you can see, the naming conventions for most of the files and folders correlate to the operating system, or version, that is going to be scanned. This repository uses manually created host files, but these could also be created automatically depending on the tools used in the environment. There are host files for each version and flavor of Linux that needs a STIG deployed; In this instance, we are doing Ubuntu 18 and RHEL 8. The names of the files are used in the GitLab pipeline to determine which Ansible playbook needs to be run.
Before we look at the GitLab YAML, let’s look at the Project variables needed in GitLab. This part is only needed if you are doing username/password authentication with Ansible. This is not the most secure way, but we are using it here for simplicity. The variables needed for this setup would be RHEL_ROOT_PASS, RHEL_USERNAME, UBUNTU_ROOT_PASS, UBUNTU_USERNAME. These variables are used to login to the RHEL/Ubuntu machine(s) for remote command execution over SSH.
Now, it’s time to move on to the gitlab-ci.yml file. Starting from the top, we have the stages and the deployment template, identified by the period (‘.’) at the start of the name, that is used for all Linux systems. This template can be used on several Linux flavors so long as the correct variables are used. This template will first identify all files with the OS flavor in the name of the file; rhel_8.txt, for instance, in our repository. It will then iterate through each file and run the Ansible playbook against each host defined in the file.
stages: - stig-deployment .linux-stig-deployment: script: - > FILES=$(find $FOLDER -maxdepth 1 -name "*$OS*" -type f) if [[ $FILES ]]; then for FILE in $FILES; do VERSION=$(echo $FILE | cut -d'_' -f2 | cut -d'.' -f1) if [ -s $FILE ]; then ansible-playbook ./ansible/$OS/$VERSION/site.yml -i $FILE -u $USERNAME \ --extra-vars "ansible_sudo_pass=$ROOT_PASS ansible_ssh_pass=$ROOT_PASS" \ --ssh-common-args "-o StrictHostKeyChecking=no" fi done fi tags: - ansible
The last part of the gitlab-ci.yml file are the two jobs in the pipeline that execute the scans, as seen below. These jobs are very similar, with the only real change being the variables used for the username and password, that was discussed previously, and then the Linux flavor for the “OS” variable.
ubuntu-stig-deployment: stage: stig-deployment extends: .linux-stig-deployment variables: OS: ubuntu USERNAME: $UBUNTU_USERNAME ROOT_PASS: $UBUNTU_ROOT_PASS FOLDER: deployments rules: - if: $CI_PIPELINE_SOURCE == "push" when: never - if: $CI_PIPELINE_SOURCE == "schedule" rhel-stig-deployment: stage: stig-deployment extends: .linux-stig-deployment variables: OS: rhel USERNAME: $RHEL_USERNAME ROOT_PASS: $RHEL_ROOT_PASS FOLDER: deployments rules: - if: $CI_PIPELINE_SOURCE == "push" when: never - if: $CI_PIPELINE_SOURCE == "schedule"
If we focused on the RHEL STIG deployment job, when it starts, it runs the script block identified in the “linux-stig-deployment” template job; due to the extends property. This job would search in the deployments’ folder, defined by the “FOLDER” variable, for any files that match the value of the “OS” variable. It would iterate through each file, get the version that needs to be deployed by looking at the name of the file, and then run the appropriate Ansible playbook accordingly.
Automating STIG deployments for Linux systems can alleviate a lot of headaches for those who must do them manually. If you use an Ansible playbook, there is no possibility of fat fingering a configuration, or forgetting to apply something, it is mostly done for you. While these playbooks may not apply every single configuration needed, they do get a large majority of the job done. If you think this is something that would be useful to you or your organization, reach out to us, and we can further discuss deployment in your environment.