Published on
 // 7 min read

Automating application control

Authors

Implementing application control represents a significant uplift in managing the risk profile for systems - assuming you have teams that can support it. The IBM Security Cyber Resilient Organisation Report for 2020 found that 41% of organisations experienced a loss of skilled cyber security expertise, as well as an increase in the complexity of attacks. This means security teams need to do more with less - respond to and defend against increasingly complex attacks, with less security resources. Balancing these competing pressures is difficult, and getting it wrong means organisations may expose vulnerabilities which could lead to a significant breach.

Automation supports organisations to mitigate these risks by making consistent and repeatable changes, mimising human error, and allowing them to focus on higher-value activities, like risk management and response planning. This is the first article in a series and looks at setting up an Ansible environment to automate security workflows. In future articles, we'll expand on this concept to look at GitOps workflows and other ways to manage security workflows in hybrid cloud environments.

Application control and Ansible

In this article I'll look at using Ansible to automate fapolicyd on Red Hat Enterprise Linux. Ansible is an agentless automation framework, enabling you to create human-readable automation workflows across systems. It has a very active open source community, and is established in many organisations supporting automation.

I've covered fapolicyd and application control in a few articles previously. Specifically, how you can support application control across Red Hat Enterprise Linux and Kubernetes/OpenShift, and how you can add integrity checks to application control processes.

Reusable automation for security workflows

One of the key components of Ansible is a 'role'. A role is simply a reusable piece of Ansible automation, packaged in a way that it can be shared with the community and further enhanced.

Ansible roles have a defined directory structure with eight main standard directories. This makes it simple to include templates with roles, or separate handlers and variables from Ansible tasks. You can see this directory structure here for the roles common and webservers:

# playbooks
site.yml
webservers.yml
fooservers.yml
roles/
    common/
        tasks/
        handlers/
        library/
        files/
        templates/
        vars/
        defaults/
        meta/
    webservers/
        tasks/
        defaults/
        meta/

Roles can be shared with the Ansible community on Ansible Galaxy, and I've already created a role that you can use in your application control workflows. This role performs a number of tasks:

  • Ensures that fapolicyd is installed
  • Enables and starts the fapolicyd service
  • Templates out the /etc/fapolicyd/fapolicyd.trust file, and updates this with any custom files required

If you'd like to see how the role is created, you can find the code here on GitHub.

Using an Ansible role to automate application control

Setup and preparation

Let's see how you can use this role to automate application control configuration. Create a new Red Hat Enterprise Linux server (you can get access here) and pull down the latest copy of cowsay into a user's home directory.

target~$ curl -L https://github.com/Code-Hex/Neo-cowsay/releases/download/v1.0.3/cowsay_1.0.3_Linux_x86_64.tar.gz | tar -xz

If you try to run this command you shouldn't have any issues:

target~$ ./cowsay "moooo"
 _______
< moooo >
 -------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

Let's install and setup Ansible and Git on the control node. Create another Red Hat Enterprise Linux server and install Ansible.

You can read more about control nodes and managed nodes in the Ansible docs

control~$ sudo yum install python3-pip git -y
control~$ sudo pip3 install pip --upgrade
control~$ pip3 install ansible --user
control~$ ansible --version
ansible [core 2.11.6]

Let's create a directory structure that can be used to import roles. Create a new directory roles containing a file requirements.yml:

control~$ mkdir roles
control~$ touch roles/requirements.yml

Update this file to contain our role definition:

control~$ cat roles/requirements.yml
---
- src: shaneboulden.fapolicyd

We need a couple of basic files to get this playbook working. Create a new file ansible.cfg with the following content:

[defaults]
inventory=./inventory
roles_path=./roles
deprecation_warnings=False

[privilege_escalation]
become_method=sudo
become_ask_pass=yes

Let's also create an inventory. Here we've listed the IP for the target server - replace it with the hostname/IP of the target server for your environment:

[all]
targetserver

You can now install the role from Ansible Galaxy using the ansible-galaxy cli:

ansible-galaxy install -r roles/requirements.yml

You'll be able to see that the role is now installed and ready to use.

control~$ ansible-galaxy list
# /home/control/roles
- shaneboulden.fapolicyd, main

Using the Ansible playbooks and roles

Now that we have the role available and installed let's create a playbook. Create a file site.yml with the following content:

control~$ cat site.yml
---
- name: Update fapolicyd configuration
  hosts: all
  become: true
  roles:
    - { role: shaneboulden.fapolicyd }

This playbook is going to apply the fapolicyd role to all of the servers in our inventory. Before we move on, we need to ensure that the control node can connect to the target server.

control~$ ssh-copy-id user1@targetserver
user1@targetserver's password: 

Number of key(s) added: 1
...

You can test that Ansible is correctly configured with an ad hoc command:

control~$ ansible all -m ping -b
targetserver | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}

You can now run the playbook like so:

control~$ ansible-playbook site.yml
BECOME Password:

Once the playbook completes you'll be able to see a recap of the changes:

targetserver               : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

This looks good - the Ansible playbook has made a number of changes to the target server, including installing/enabling fapolicyd. You can test out the local script again on the target server:

target~$ ./cowsay
-bash: ./cowsay: Operation not permitted

You can verify that the application has been blocked by fapolicyd by checking the audit logs:

target~$ sudo ausearch --start today -m fanotify --raw | aureport --file -i

File Report
===============================================
# date time file syscall success exe auid event
===============================================
48. 25/11/21 23:01:24 /home/user1/./cowsay execve no /usr/bin/bash user1 1080

Success! We've now automated the deployment of a a simple application control daemon on Red Hat Enterprise Linux, using reusable automation logic from the Ansible community.

Closing out

In this article we laid the groundwork to support automating application control. We created an Ansible control node and used a role to automate fapolicyd setup and configuration on a target server.

In the next couple of articles we'll look at some other workflows for managing application control, like GitOps flows, or using host variables to store application state on a per-node basis. We'll also look at how to audit changes to application control state across multiple hosts.