Headline
Better securing the frontlines: Leveraging Ansible Automation Platform and AIDE for DoD file integrity
A lot of system administrators within the Department of Defense already use the Advanced Intrusion Detection Environment (AIDE). This is mainly because of a Security Technical Implementation Guide (STIG) that states that a file integrity checker must be configured to verify extended file attributes. There are a lot of features to AIDE, and the combination of using AIDE with Red Hat Ansible Automation Platform gives you the ability to automate important corrections to your system configuration.
Telling AIDE what to check
AIDE can be configured to check multiple file and folder attributes.
A lot of system administrators within the Department of Defense already use the Advanced Intrusion Detection Environment (AIDE). This is mainly because of a Security Technical Implementation Guide (STIG) that states that a file integrity checker must be configured to verify extended file attributes. There are a lot of features to AIDE, and the combination of using AIDE with Red Hat Ansible Automation Platform gives you the ability to automate important corrections to your system configuration.
Telling AIDE what to check
AIDE can be configured to check multiple file and folder attributes. For this article, I concentrate on the permissions §, user (u), group (g), SELinux context (selinux) and SHA512 checksum (sha512). To create a rule that applies those attribute checks, define a name for that rule, then set it equal to each of the attributes separated by a plus sign (+). For example:
MYRULE = p+u+g+selinux+sha512
Then set different files or folders to the newly created file, separated by a dollar sign ($) and a space. For example:
/some/file/or/folder$ MYRULE
Of course, this isn’t all that’s needed to create a working configuration file for AIDE, but the rest can be found in the aide.conf man pages.
Here’s an example of an aide.conf file that locks down the attributes, as seen above, to a file named other.txt in the /tmp directory:
@@define DBDIR /var/lib/aide @@define LOGDIR /var/log/aide
The location of the database to be read.
database=file:@@{DBDIR}/exampleaide.db.gz
The location of the database to be written.
database_out=file:@@{DBDIR}/exampleaide.db.new.gz
#Whether to gzip the output to database gzip_dbout=yes
Default.
verbose=5
report_url=file:@@{LOGDIR}/exampleaide.log report_url=stdout
MYRULE = p+u+g+selinux+sha512
/tmp/other.txt$ MYRULE
Running AIDE
Between STIG and your custom rules, aide.conf can get unwieldy. AIDE, by default, uses the /etc/aide.conf file for its rules. But you can pass in the --config (-c for short) option to specify an alternate configuration file. For instance, you could run aide --check --config=/etc/alternateAIDE.conf to check the state of our other.txt file.
AIDE tells you when there’s an error in a file:
Start timestamp: 1970-01-01 00:00:00 +0000 (AIDE 0.16) AIDE found differences between database and filesystemvi aide.conf
Summary: Total number of entries: 3 Added entries: 0 Removed entries: 0 Changed entries: 1
Changed entries:
f pug C S : /tmp//other.txt
Detailed information about changes:
File: /tmp/other.txt Perm : -rw-r–r– | —Sr-xrwt Uid : 0 | 2 Gid : 0 | 2 SHA512 : dKZ3fe187ScU81Guyni0jGCsf | ziTsUMaYngwi6fafR5G1mCMm1 mX9/T9EZau2unU1+Nk7B5JKad | hr/Hi6LTHY8Pcd5esWdwCyu3jK YIKUi5kV5605/YhvRYDGWJGkx | eU2JAYR6qiDWpeIjsP7o7Yb3UE MHNTZp91sQ== | mnCzeUVtw== SELinux : unconfined_u:object_r:admin_ | unconfined_u:object_r:mnt_t:s0 home_t:s0
The attributes of the (uncompressed) database(s):
/var/lib/aide/exampleaide.db.gz SHA1 : KCr1BWRHAuZRkD4TgxR9RadnBto= SHA256 : pbAUuhUnpyqfHt/OVAytX37hco1QuBC1 keg107hsWEk= SHA512 : ur3bt+wGZAizlbtIdINVNmdYN4U/EzZq 50Ryzj8kUutkCVIBEPoQ3apOCBOYMY2p eGfeqK3QtmdjFHsy1cUKuw==
End timestamp: 1970-01-01 00:00:00 +0000 (run time: 0m 0s)
Understanding the AIDE output
A summary of incorrect attributes to the file is in the Changed entries section:
f pug C S : /tmp/other.txt
- f: a file
- pug: permissions, user, and group are incorrect
- C: checksum mismatch
- S: SELinux context is incorrect
If one of the attributes is correct, a dot (.) is displayed instead of the character.
The Detailed information about changes section provides a full explanation of differences in the file.
AIDE maintains a database, specified in aide.conf, so that it knows what the attributes are supposed to be. In my example, you can look at the contents of the database with the zcat command.
@@begin_db
This file was generated by Aide, version 0.16
Time of generation was 2023-06-29 00:27:36
@@db_spec name name attr perm inode uid gid sha512 selinux /tmp/other.txt 0 6442450973 100644 138 0 0 2a0d941f74139a069df6e1df2533159b46875e1eb4cc815f600ea22e1526552556e03adb4b3ae668398f1b5fb60471ebd1c80c6686fa3507e5561adcfcb87==
All the information that the aide.conf could want is stored within the database file.
Making use of the AIDE output and database
Now you’ve gotten the output of running the aide command and viewed the contents of the database that AIDE utilizes. So what can you do with this information? You can fix these files, when AIDE shows them as being incorrect, and you can do all of this with Ansible. All you have to do is write a playbook to run your aide command, interpret the errors, get the corrections from the AIDE database file, and then apply these fixes to the files. All of this is IF the checksums aren’t different. If the checksum ends up being different than expected, well then you have to replace the file with the correct one. But Ansible can handle replacing files with ease.
First step is to check for errors. You must use the cmd module to run the aide command and collect the output as seen here:
- name: Check aide for Errors ansible.builtin.shell: cmd: “/usr/sbin/aide -V5 -C -c {{ aideconf }} | egrep '^d|^f’” register: values when: true failed_when: values.rc > 1
Now that you can detect errors, you need to get the proper attributes prior to fixing the attributes to what they are. Once again, you have to defer to the cmd module to run the zcat command to view the contents of the database file.
- name: Get UID Index
ansible.builtin.shell:
cmd: AIDEDB=$(/bin/zcat {{ aidedb }} | grep @@db\_spec); echo ${AIDEDB\[@\]/uid//} | cut -d/ -f1 | wc -w
register: uidpos
when: true
- name: Set UID
ansible.builtin.shell:
cmd: /usr/bin/id -n -u $(/bin/zcat {{ aidedb }} | grep "{{ afile }} " | cut -d' ' -f"{{ uidpos.stdout }}")
register: auid
when: true
Because the database stores all possible attributes that AIDE can handle, you must set the index for the position that the attribute is in, then get that value from the database. In the above example, you can see how to do this for the UID. This must be done for each of the attributes that are checked.
Once the correct attribute is found, you can ensure that the file gets that correct attribute. First, ensure that the file needs to be fixed:
- name: Fix the File(s) ansible.builtin.include_tasks: file: fix.yml vars: afile: “{{ item.split(‘:’)[1].split(' ')[1] }}” loop: “{{ values.stdout_lines }}” when: (item is defined) and (not “C” in item.split(‘:’)[0]) and (item.split()[0] == ‘f’) and (‘p’ or ‘u’ or ‘g’ or ‘S’ in item.split(‘:’)[0])
Then make this change:
- name: Fix {{ afile }} ansible.builtin.file: path: “{{ afile }}” owner: “{{ auid.stdout }}”
When the checksum is different
Earlier, I said you could fix the file as long as the checksum is the same. But what do you do when the checksum has changed? In this situation, you replace the incorrect file with the proper one. This can be done with the Ansible modules copy or template. You can still use all the other attributes that were pulled from the database with the module. But the whole file needs to be replaced if the checksum differs.
Automate using Red Hat Ansible Automation Platform
Now that you have some working Ansible playbooks established, it would be best to utilize Ansible Automation Platform to execute them. The platform also enables you to use the REST (REpresentational State Transfer) API. But before you can do this, you must make a few changes to how the playbook operates.
First, ensure that there are credentials that the playbook can use to access the host system. This allows the playbook to run unassisted, meaning the REST API kicks it off and no other interactions are needed. Next, require the variables Prompt at launch. Here is where you need to allow changes to the inventory because you don’t want to run this playbook against all systems just because one system is out of configuration.
Once this is done, you can craft your REST API call to Ansible Automation Platform:
$ curl -k -X POST -u USERNAME:PASSWORD \ https://{ AAP }/api/v2/job_templates/{ Template Number }/launch/ \ -H “Content-Type: application/json” \ -d '{"extra_vars": "{host: '$(hostname)‘}"}’
This command sends a POST to the platform website to execute the REST API you want to call. To execute a playbook, curl must be able to log in to Ansible Automation Platform. This is where the -u USERNAME:PASSWORD comes into play. For this to work, you must set up a user account within the platform that only has access to execute this one playbook.
Also, you can obfuscate the password to comply compliant to the STIG against displaying clear text passwords. First, create a password hash:
$ echo mypassword | base64 > /root/passFile.txt
Decode that password in your curl command with $(cat /root/passFile.txt | base64 --decode) in place of PASSWORD.
The Template Number is a number assigned to the job template based on when it was put into the Ansible platform. To get this number, go to Red Hat Ansible Automation Platform > Templates, then click on the job template name. Afterwards, the Job Template ID appears within the URL of your site. For example, if a Job Template is 9, then your URL might be https://myaap/#/templates/job_template/9/details.
The Content-Type section is where you tell the API, through your POST action, that you have more applicable content to add to the call. Specify that you are inserting the arguments as JSON, and then input your code. In this example, you’re saying that the variable you want to reassign is called host and that you want to reassign it to $(hostname). The $(hostname) parameter tells the system to run the hostname command. If this isn’t the same as what you have in your Ansible Automation Platform inventory, then you must make them match. For example, if you have the condensed hostname in Ansible Automation Platform, but on the host you have the FQDN, then you would pass in something like $(hostname | cut -d. -f1).
Now that you have the REST API command, you can run this on the system to start your playbook automatically. Better yet, you can insert this command as a cron job to automatically return your system to the desired configuration.
AIDE and Ansible Automation Platform
AIDE is a useful and flexible tool that helps you stay compliant with STIG requirements. Combined with Red Hat Ansible Automation Platform, the most important configuration for your systems is automatic.