= Lab Exercise 4: Using Ansible in ComplianceAsCode
:toc2:
:linkattrs:
:experimental:
:imagesdir: images

== Introduction

Red Hat^(R)^ Ansible^(R)^ Automation is a powerful tool for automating the configuration of systems.
By running a predefined file called an Ansible Playbook, you can quickly configure the system according to your needs.

Ansible Automation can easily be used for security compliance automation, because it allows you to keep your system hardened, and it can operate on a large scale.
Using Ansible Automation, you can set everything you need, including minimal password lengths, firewall rules, package installation, or the disabling of services.

`ComplianceAsCode` works great with Ansible Automation.
`ComplianceAsCode` connects a human-readable security policy with Ansible tasks that implement the settings required by the security policy.
The rules in `ComplianceAsCode` profiles contain Ansible tasks that configure the system to conform to the rule.
From these Ansible tasks and rule metadata, `ComplianceAsCode` generates an Ansible Playbook that you can use to harden your system.
After running the playbook, your system meets the requirements of the security policy.

`ComplianceAsCode` generates a playbook for each profile that conforms to the profile definition.
Moreover, it generates separate playbooks for every rule.


.*Goals*

* Learn how to add an Ansible task for a rule
* Learn how to leverage `ComplianceAsCode` structure when creating Ansible content
* Learn how to generate Ansible Playbooks from a `ComplianceAsCode` repository
* Learn how to use the Ansible Playbooks generated by `ComplianceAsCode`

IMPORTANT: Content used in this lab has been altered to increase its educative potential, and is therefore different from the content in ComplianceAsCode upstream repository or the content in the scap-security-guide package shipped in Red Hat^(R)^ products.


.*Preconfigured Lab Environment*

* The `ComplianceAsCode` repository was already cloned.
* Ansible Automation was installed.
* The following dependencies required for the `ComplianceAsCode` content build were installed using `yum install`:
** Generic build utilities: `cmake` and `make`,
** Utilities for generating SCAP content: `openscap-scanner`
** Python dependencies for putting content together: `python3-pyyaml` and `python3-jinja2`

== Hands-on Lab

The `ComplianceAsCode` project consists of human-readable files that are compiled into standard-compliant files that are difficult to read and edit directly.

For your convenience, the environment is already set up, so the content is built and ready to be used.
No worries, though--you get to rebuild it later in the exercise.

To start the hands-on section, take one of the following steps:

=== Github Codespaces
. Go to: link:https://github.com/codespaces/new?hide_repo_select=true&ref=master&repo=19279458&skip_quickstart=true[Github Codespaces]
. Select `ComplianceAsCode Workshop Exercise 4` in the `Dev container configuration` option.

=== Gitpod
. Go to: link:https://gitpod.io/#WORKSHOP=lab4_ansible/https://github.com/ComplianceAsCode/content[Lab 4 Environment]
. Wait until all the steps being executed in the terminal are complete.


== Adding an Ansible Task for a Rule

Ansible tasks can be attached to every rule in a `ComplianceAsCode` project.
A lot of rules already contain Ansible tasks.

As an example, you add a new Ansible task for the `accounts_tmout` rule.
You have already encountered this rule in `Lab Exercise 1`.
This rule is about the interactive session timeout for inactive users.
Terminating an idle session within a short time period reduces the risk of unauthorized operations.
The Ansible task that you add configures the session timeout in the respective configuration file.

You work in the context of Red Hat^(R)^ Enterprise Linux^(R)^ 8 (RHEL 8) product and the OSPP profile for Red Hat^(R)^ Enterprise Linux^(R)^ 8,
but the structure is the same for all rules in `ComplianceAsCode`.

As you already know from Lab Exercise 1, source code for the `accounts_tmout` rule is located in the `linux_os/guide/system/accounts/accounts-session/accounts_tmout` directory.

. Switch to the rule directory and examine its content.
+
----
[... ]$ cd linux_os/guide/system/accounts/accounts-session/accounts_tmout
[... accounts_tmout]$ ls
bash  oval  rule.yml  tests
----
+
As you learned in previous lab exercises, the `rule.yml` file contains the rule description, rationale, and metadata.
+
[NOTE]
====
Apart from `rule.yml`, there are also three subdirectories in the `accounts_tmout` directory:

* `bash` contains source code for a Bash script that can fix the timeout settings, also called "Bash remediation."
* `oval` contains source code for an OVAL check that checks if the timeout is set.
* `tests` contains test scenarios which are used to test checks and remediations of a rule.
====

. To add Ansible tasks in this rule, you first create a new directory called `ansible` in the rule directory, at the same level as the `bash` and `oval` directories.
Create a new `ansible` directory and change into it:
+
----
[... accounts_tmout]$ mkdir ansible
[... ansible]$ cd ansible
----

. Next, you create a new file in this directory that contains your Ansible task.
+
[NOTE]
====
In `ComplianceAsCode`, the file needs to have a specific name.
You have two options:

* `shared.yml` is an universal name, the "shared" in this context means that the task can be applied to any product--that is, any Linux distribution.
* `product_id.yml`, (for example, `fedora.yml`), can be used if the task is specific to a single Linux distribution and cannot be extended to other Linux distributions.
====

. Because the interactive session timeout is not a specific feature of RHEL 8, but is handled the same way in most Linux distributions, you can name the file `shared.yml`.
Create a new `shared.yml` file in the `ansible` directory and open it in the text editor.
+
----
[... ansible]$ open shared.yml
----

. Next, you start to write the Ansible content in this file.
It is not in the format of an Ansible Playbook--instead, it uses a special format.
It is a simple YAML file.
+
The first part of this file must be a header that helps the build system integrate the Ansible tasks with the SCAP content and also with the rule metadata.
+
Add the following content to the top of the `shared.yml` file, including the `#` characters.
If you want to copy and paste the text, you have to use `Ctrl+V` to paste it:
+
----
# platform = multi_platform_all
# reboot = false
# strategy = restrict
# complexity = low
# disruption = low
----
+
Do not close the file yet.
+
[NOTE]
====
The header contains optional metadata.
The `platform` and `reboot` fields have well-defined meanings:

* `platform` is a comma-separated list of *products* that the Ansible tasks are applicable to.
It can be an operating system name such as `Red Hat Enterprise Linux 8`, or a wildcard string that matches multiple products--for example,
`multi_platform_rhel`.
Here we use the wildcard string, `multi_platform_all`, that matches all of the possible platforms.
* `reboot` specifies if a reboot is needed to activate the settings.
This can be either `true` or `false`.
Here, we signal that a reboot is not needed.
This value is purely informational and setting it to `true` does not cause Ansible Automation to reboot the system.

The other fields are optional, and their meanings are fuzzier:

* `strategy` is the method or approach for making the described fix.
It is typically one of the following: `configure`, `disable`, `enable`, `patch`, `restrict`, or `unknown`.
* `complexity` is the estimated complexity or difficulty of applying the fix to the target.
It can be `unknown`, `low`, `medium`, or `high`.
* `disruption` is an estimate of the potential for disruption or operational degradation that the application of this fix imposes on the target.
It can be `unknown`, `low`, `medium`, or `high`.
====

. Now, you add an Ansible task or tasks for this rule below the header in `shared.yml`.
Add the following content at the end of the `shared.yml` file.
Again, do not close the file just yet.
+
----
- name: configure timeout
  lineinfile:
    create: yes
    dest: /etc/profile
    regexp: "^#?TMOUT"
    line: "TMOUT=600"
----
+
At this point, expect the entire file to look like this:
+
----
# platform = multi_platform_all
# reboot = false
# strategy = restrict
# complexity = low
# disruption = low

- name: configure timeout
  lineinfile:
    create: yes
    dest: /etc/profile
    regexp: "^#?TMOUT"
    line: "TMOUT=600"
----
+
[NOTE]
====
If you are familiar with Ansible Automation, you probably know that you just wrote an link:https://docs.ansible.com/ansible/latest/user_guide/playbooks_intro.html#tasks-list[Ansible task^].
Normally, Ansible tasks are low-level components of Ansible Playbooks.
The `ComplianceAsCode` project allows content contributors to focus on tasks, and the playbook that aggregates them is generated by the project.
When writing tasks, you can use the standard Ansible syntax and write the Ansible tasks the exact same way as you write in Ansible Playbooks.
You can use link:https://docs.ansible.com/ansible/latest/modules/modules_by_category.html[any Ansible module^].

Using Ansible language, you have defined a new Ansible task with the name "configure timeout".
It uses the link:https://docs.ansible.com/ansible/latest/modules/lineinfile_module.html[lineinfile^] Ansible module, which can add, modify, and remove lines in configuration files.
Using the `lineinfile` module, you insert the line `TMOUT=600` to `/etc/profile`.

Note that the `regexp` line defines a regular expression that determines what Ansible Automation is going to do.
If the regular expression matches a line, it is substituted with `line`, so the lines `TMOUT=1800` and `#TMOUT=600` are replaced by `TMOUT=600`.
If no line matches the regular expression, contents of `line` are simply appended to `dest`, which in this case is `/etc/profile`.
====
+
In this rule, you add only a single Ansible task.
If your goals need to be achieved by multiple Ansible tasks, they all go into the same file.
+
In `ComplianceAsCode`, the general rule is that the Ansible tasks must conform to the rule description in `rule.yml` for the given rule.
Tasks must not do anything different than what the `rule.yml` description requires.
Think of the rule description as a natural language specification of what needs to be implemented in Ansible Automation.

// Now, it is a good time to build the playbook, open it, and run it in a check mode.

==  Using Variables in Ansible Tasks

At this point, your task does not fully conform to the rule description in `rule.yml`.
The difference is that `rule.yml` does not define a specific value for the timeout.

. Check that `rule.yml` does not specify whether the timeout should be 600 seconds or a different amount of time.
In fact, the rule is parameterized by a variable, `var_accounts_tmout`.
The specific value for a timeout variable is set by setting `var_accounts_tmout` in the profile definition.
This way, every profile can define a different timeout but still reuse the same source code.
+
You need to fix the Ansible task to use the `var_accounts_tmout` variable instead of explicitly setting 600 seconds in the task.
The general format for binding a variable from `ComplianceAsCode` profiles is `{{{ ansible_instantiate_variables("var_accounts_tmout") }}}`.

. Add the following line right after the `# disruption = low` line in the `shared.yml` file:
+
----
{{{ ansible_instantiate_variables("var_accounts_tmout") }}}
----
+
Now, you can use the bound variable in the `configure timeout` Ansible task as an Ansible variable using the standard Ansible syntax.
When the `shared.yml` file is processed by the `ComplianceAsCode` build system, this variable binding is resolved automatically and a new Ansible variable is created in the `vars` list in the generated playbook.

. Replace `line: "TMOUT=600"` with `line: "TMOUT={{ var_accounts_tmout }}"` to use the variable in the task.
+
At this point you have completed adding Ansible tasks for the `accounts_tmout` rule.
Expect the contents of the `shared.yml` file to look like this:
+
----
# platform = multi_platform_all
# reboot = false
# strategy = restrict
# complexity = low
# disruption = low
{{{ ansible_instantiate_variables("var_accounts_tmout") }}}

- name: configure timeout
  lineinfile:
    create: yes
    dest: /etc/profile
    regexp: ^#?TMOUT
    line: "TMOUT={{ var_accounts_tmout }}"
----

. You can now save the file by pressing `Ctrl+S` to save the file.

== Generating and Using Ansible Playbooks for a Rule

You now generate a playbook for the `accounts_tmout` rule you modified.
You do this in the context of the Red Hat^(R)^ Enterprise Linux^(R)^ 8 product and the OSPP profile for Red Hat^(R)^ Enterprise Linux^(R)^ 8.

To generate Ansible Playbooks, a complete build of the content for the product needs to be performed.
That means that all of the other playbooks for all of the other rules are generated as well.
Moreover, the SCAP content is also generated.

. Go back to the project root directory and run the following command to build the RHEL 8 product:
+
----
[... ansible]$ cd /workspace/content
[... ]$ ADDITIONAL_CMAKE_OPTIONS="-DSSG_ANSIBLE_PLAYBOOKS_PER_RULE_ENABLED=ON" ./build_product rhel8
----

. The Playbooks are generated in the `build/rhel8/playbooks` directory.
Check the contents of this directory:
+
----
[... ]$ ls build/rhel8/playbooks
all                  anssi_bp28_high          anssi_bp28_minimal  cis_server_l1       cis_workstation_l2  cui  hipaa  ospp     rht-ccp   stig
anssi_bp28_enhanced  anssi_bp28_intermediary  cis                 cis_workstation_l1  cjis                e8   ism_o  pci-dss  standard  stig_gui
----
+
Note that there is a directory for each profile in the RHEL8 product.
That is because each profile consists of a different set of rules and the rules are parameterized by variables which can have different values in each profile.

. The `accounts_tmout` rule is, for example, a part of the OSPP profile, so take a peek into the `ospp` directory:
+
----
[... ]$ ls build/rhel8/playbooks/ospp
----
+
There are many playbook files in the `ospp` directory.
One of them is the `accounts_tmout.yml` file, which is the Ansible Playbook that contains the Ansible tasks you added in the `accounts_tmout` rule.

. Open it in the text editor:
+
----
[... ]$ open build/rhel8/playbooks/ospp/accounts_tmout.yml
----
+
The contents of the `build/rhel8/playbooks/ospp/accounts_tmout.yml` file look like this:
+
----
# platform = multi_platform_all
# reboot = false
# strategy = restrict
# complexity = low
# disruption = low
- name: Set Interactive Session Timeout
  hosts: '@@HOSTS@@'
  become: true
  vars:
    var_accounts_tmout: '600'
  tags:
  - CCE-80673-7
  - NIST-800-171-3.1.11
  - NIST-800-53-AC-12
  - NIST-800-53-AC-2(5)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-10
  - accounts_tmout
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  tasks:

  - name: configure timeout
    lineinfile:
      create: true
      dest: /etc/profile
      regexp: ^#?TMOUT
      line: TMOUT={{ var_accounts_tmout }}
----
+
[TIP]
====
If you see a typo in the YAML file, edit the source again and rebuild.
====
+
This is a normal Ansible Playbook that Ansible users are familiar with.
The name of the playbook is the same as the title of the rule, which is defined in `rule.yml`.

. The `hosts` section contains only a placeholder string, `'@@HOSTS@@'`, which needs to be replaced by a list of IP addresses or hosts that the playbook applies to.
You have to edit this in order to check the playbook.
To use your playbook on your environment (on a local host), replace `'@@HOSTS@@'` with `'localhost'` and press `Ctrl+S` to save the file.
+
----
[... ]$ open build/rhel8/playbooks/ospp/accounts_tmout.yml
...
- name: Set Interactive Session Timeout
  hosts: 'localhost'
  become: true
...
----
+
Note that the timeout value supplied by the `var_accounts_tmout` variable was set to a specific value (600 seconds) during the build process, and the variable was added to the `vars` section of the playbook.
+
Note also that the playbook has tags in the `tags` section that were added based on metadata in `rule.yml`.
At the beginning, it contains the CCE (Common Configuration Enumeration) identifier.
Finally, the `tasks:` section contains the Ansible task that you created.

. Run the playbook:
+
----
[... ]$ ansible-playbook build/rhel8/playbooks/ospp/accounts_tmout.yml
----

. Check if it has any effect:
+
----
[... ]$ cat /etc/profile
----
+
Note that `TMOUT=600` is at the end of the file!
+
The biggest advantage of using Ansible tasks in `ComplianceAsCode` is that it gets integrated with the SCAP content, the HTML report, and the HTML guide as well.

. Navigate to the `build/guides` folder.:
. Right click the `ssg-rhel8-guide-ospp.html` file and select `Open with Live Server` to preview the file. Note: Your browser may block the pop-up. You must allow it when asked.
. A new tab opens and you can see your `OSPP` profile, which contains two rules.
. Check the "Set Interactive Session Timeout" rule.
Click the blue `(show)` link to the right of the green "Remediation Ansible snippet" label and you see your recently added Ansible content.
+
.The "Set Interactive Session Timeout" rule displayed in an HTML guide and including the expanded Ansible content
image::4-01-guide.png[]

== Using the Profile Ansible Playbooks

In the previous section, you learned about using a playbook for the `accounts_tmout` rule.
However, security policies are usually complex, which in turn means that profiles consist of many rules.
It is not convenient to have a separate Ansible Playbook for each rule, because that means you need to apply many Ansible Playbooks to the system.
Fortunately, `ComplianceAsCode` also generates Ansible Playbooks that contain all of the tasks for a given profile in a single playbook.

The playbooks are located in the `build/ansible` directory.
This directory contains Ansible Playbooks for each profile.
The Playbooks files have `.yml` extension.

----
[... ]$ ls build/ansible
all-profile-playbooks-rhel8                 rhel8-playbook-anssi_bp28_minimal.yml  rhel8-playbook-cis.yml   rhel8-playbook-hipaa.yml    rhel8-playbook-rht-ccp.yml
rhel8-playbook-anssi_bp28_enhanced.yml      rhel8-playbook-cis_server_l1.yml       rhel8-playbook-cjis.yml  rhel8-playbook-ism_o.yml    rhel8-playbook-standard.yml
rhel8-playbook-anssi_bp28_high.yml          rhel8-playbook-cis_workstation_l1.yml  rhel8-playbook-cui.yml   rhel8-playbook-ospp.yml     rhel8-playbook-stig_gui.yml
rhel8-playbook-anssi_bp28_intermediary.yml  rhel8-playbook-cis_workstation_l2.yml  rhel8-playbook-e8.yml    rhel8-playbook-pci-dss.yml  rhel8-playbook-stig.yml
----

. Check the contents of the OSPP profile playbook in your editor and verify that a task for the `accounts_tmout` rule is there among all the other tasks.
+
----
[... ]$ open build/ansible/rhel8-playbook-ospp.yml
----
+
At this point, you have per-rule Ansible Playbooks available, as well as per-profile ones.
You can integrate these into your CI/CD pipelines and infrastructure management as needed.

<<top>>

link:README.adoc#table-of-contents[ Table of Contents ] | link:lab5_oval.adoc[Lab Exercise 5: The Art of OVAL Checks]
