Bookmark this page

Chapter 2.  Automating Configuration and Remediation with Ansible

Abstract

Goal

Remediate configuration and security issues automatically with Ansible Playbooks.

Sections
  • Configuring Ansible for Security Automation (and Guided Exercise)

  • Managing Playbooks with Automation Controller (and Guided Exercise)

Lab
  • Automating Configuration and Remediation with Ansible

Configuring Ansible for Security Automation

Objectives

  • Install and configure an Ansible control node, prepare managed hosts for Ansible access, and configure hosts by using a playbook with an Ansible inventory.

Ansible Concepts and Architecture

Ansible is an open source automation platform that uses an automation language to describe the intended configuration of servers and applications.

The Ansible architecture consists of two types of machines: control nodes and managed hosts. The control node runs Ansible, and it also has copies of your Ansible project files. Managed hosts are listed in an inventory, which also organizes those systems into groups for easier management. You can define the inventory statically in a text file, or dynamically by using scripts that obtain group and host information from external sources.

Instead of writing complex scripts, Ansible users write high-level plays that specify the intended state of particular managed hosts. A play performs a series of tasks on the hosts, in the order that the play specifies. These plays are expressed in YAML format in a text file. A playbook is a file that contains one or more plays.

Each task runs a module, which is a small piece of code (written in Python, PowerShell, or some other language) with specific arguments. Each module is essentially a tool in your toolkit. Ansible ships with hundreds of useful modules that can perform wide-ranging automation tasks. Modules can act on system files, install software, or make API calls.

Most modules are idempotent, which means that they can be run safely multiple times, and if the system is already in the correct state, they do nothing. Tasks, plays, and playbooks are also designed to be idempotent.

Ansible also uses plug-ins. Plug-ins extend Ansible to new uses and platforms.

The Ansible architecture is agentless. Typically, when an administrator runs an Ansible Playbook, the control node connects to the managed host by using SSH (by default) or WinRM. Therefore, an Ansible-specific agent does not need to be installed on managed hosts, and you do not need to allow any additional communication between the control node and managed hosts.

Security Automation

Managing security for many systems can be challenging. When new threats or issues are discovered, you might need to ensure that all the systems are configured or provisioned correctly.

Many administrators use automation to ensure that work is applied consistently and correctly to all machines under their care, and to speed up the correct deployment of security remediation.

A good automation solution must be able to deploy and provision systems from scratch. Administrators must be able to run automated tasks on a system that is already configured, and either confirm that it is already correctly configured and do nothing, or change that system to ensure that it is correctly configured.

You can use many tools for this purpose. In this course, you use Red Hat Ansible Automation Platform to address security issues. Using Ansible in a security context offers several advantages:

  • You can install Ansible with minimal requirements for managed systems, and it does not need an agent.

  • Working with Ansible does not require special coding skills.

  • Ansible provides human-readable automation instructions (Ansible Playbooks).

  • Ansible provides various tools that can identify and detect security-related issues that can help remediate the issues. This course reviews two tools in detail: the SCAP Security Guide for OpenSCAP and Red Hat Insights.

When your security policy is defined in Ansible, scanning and remediation of site-wide security policies can be integrated into other automated processes. Instead of being an afterthought, security is an integral part of everything that is deployed.

Installing Ansible

To use Ansible, you must install the Ansible software on a host (the control node) from which you run Ansible Playbooks. Ensure that your managed hosts are set up so that you can use Ansible to connect to them, become the root user, and run Ansible plays.

Installing Ansible on the Control Node

To run Ansible Playbooks, install the automation content navigator (ansible-navigator) on your control node and download an execution environment. Hosts that Ansible manages do not require the ansible-navigator tool to be installed; you need to install this tool only on the control node that you run Ansible Playbooks from.

Python 3.8 or later must be installed on the control node before you install the ansible-core package.

You must have a valid Red Hat Ansible Automation Platform subscription to install the automation content navigator on your control node.

If you activated Simple Content Access for your organization in the Red Hat Customer Portal, then you do not need to attach the subscription to your system.

Note

You do not need to run these exact steps in your classroom environment, because it is preconfigured to download the ee-supported-rhel9 execution environment.

  • Attach a valid Red Hat Ansible Automation Platform subscription:

[root@controlnode ~]# subscription-manager repos \
--enable ansible-automation-platform-2.4-for-rhel-9-x86_64-rpms
  • Install the automation content navigator on your control nodes:

[root@controlnode ~]# dnf install ansible-navigator
  • Verify that the automation content navigator is installed on the system:

[user@controlnode ~]$ ansible-navigator --version
ansible-navigator 3.4.1
  • Log in to the container registry that contains your automation execution environments (in this case, registry.redhat.io):

[user@controlnode ~]$ podman login registry.redhat.io
Username: your-registry-username
Password: your-registry-password
Login Succeeded!
  • Display the list of locally available container images. The automation content navigator automatically downloads the default execution environment when you run the ansible-navigator command:

[user@controlnode ~]$ ansible-navigator images
  Image                 Tag     Execution environment   Created         Size
0│ee-supported-rhel9    latest  True                    10 days ago     1.63 GB


^b/PgUp page up   ^f/PgDn page down   ↑↓ scroll   esc back   [0-9] goto   :help

Preparing Managed Hosts for Ansible Automation

Ansible is a cross-platform solution that can manage Linux hosts, Microsoft Windows systems, and even managed networking devices. This course focuses on configuring Linux managed hosts.

One benefit of Ansible is that a special agent does not need to be installed on managed hosts. The Ansible control node connects to managed hosts by using a standard network protocol to ensure that the systems are in the specified state.

Depending on how the control node connects to the managed hosts, and which modules are run on them, managed hosts might have the following requirements:

  • On Linux and UNIX managed hosts, Python 3.8 or later must be installed for most modules to work.

  • If SELinux is enabled on the managed hosts, then ensure that the python3-libselinux package is installed before using modules that are related to any copy, file, or template functions.

  • Ansible must connect to the managed machine by using SSH. When Ansible connects as a regular user, it must be able to use sudo to gain superuser access.

When configuring authentication and privilege escalation in Ansible, you have several options based on your policies and needs. A common method involves using SSH public key authentication to connect as a non-privileged user and using sudo to escalate privileges to the root user.

You can choose one of the following options to configure the Ansible user with sudo access. For the first option, configure sudo to require the Ansible user to enter their password to escalate privileges. The default configuration of the /etc/sudoers file permits sudo access for any member of the wheel group, so you might add the Ansible user to that group.

For the second option, add a rule to explicitly allow sudo access for a user. For example, by creating a file named /etc/sudoers.d/ansible-testuser that includes the following line, you allow the ansible-testuser user to use sudo to run any command as any user after they enter their password to authenticate.

ansible-testuser  ALL=(ALL) ALL

You can allow the Ansible user to run sudo without entering a password. This approach is less secure, but it might be more convenient than requiring a password. For example, you could set up /etc/sudoers.d/user to allow the ansible-testuser user to use sudo to run any command as any user without further authentication, in this way:

ansible-testuser  ALL=(ALL) NOPASSWD:ALL

Note

Various security trade-offs apply here. If you require the Ansible user to provide their password to escalate privileges, then the administrators who run Ansible might need to know that user's password and it must be the same everywhere. Automation controller protects the sudo password from Ansible administrators and allows privilege escalation.

An Ansible user with no password can be created, to help to ensure that the only way to access that account is through SSH public key authentication or the local root account.

Managing a Host Inventory

An inventory defines a collection of hosts that Ansible will manage. These hosts can also be assigned to groups, which can be managed collectively. Groups can contain child groups, and hosts can be members of multiple groups. The inventory can also set variables that apply to the hosts and groups that it defines.

An inventory can be of two types, static or dynamic. A static host inventory can be defined by an INI-like text file. A dynamic host inventory can be generated by a script or other program as needed, by using external information providers.

In its simplest form, a static inventory is a list of hostnames or IP addresses of the managed hosts, each on a single line:

server1.example.com
server2.example.com
server3.example.com

You can organize managed hosts into host groups. Host groups enable you to more effectively run Ansible against a collection of systems. In this example, each section starts with a host group name in square brackets ([]). This section is followed by the hostname or an IP address for each managed host in the group, each on a single line. Hosts can be and often are members of multiple host groups, which makes them easier to manage.

Host groups can also be nested. The :children suffix on a name in a square-bracketed section sets up a nested group, and each line is the name of another group. All hosts in those groups are also members of the parent group.

This example inventory file creates a webservers group that contains the server1.example.com and server2.example.com hosts. This inventory also creates another group named fileservers, which contains the server3.example.com and server4.example.com servers. Finally, the inventory creates a group named myservers that includes the webservers and fileservers groups as nested groups inside it. (The inventory could also have included a [myservers] section to add individual hosts to that group.)

[webservers]
server1.example.com
server2.example.com

[fileservers]
server3.example.com
server4.example.com

[myservers:children]
webservers
fileservers

Note

Two host groups always exist:

  • The all host group, which includes every host listed in the inventory.

  • The ungrouped host group, which contains every listed host in the inventory that is not a member of another group.

Configuring Ansible Operation

To configure the behavior of Ansible and the ansible-navigator command, you can create and edit two files in each of your Ansible project directories:

  • The ansible.cfg file, which configures the behavior of several Ansible tools.

  • The ansible-navigator.yml file, which changes the default options for the ansible-navigator command.

You can customize the behavior of an Ansible installation by modifying settings in the Ansible configuration file. Ansible chooses its configuration file from one of several possible locations on the control node.

The ansible package provides a default configuration file in the /etc/ansible/ansible.cfg file. However, the recommended practice is to create an ansible.cfg file in a directory from which you run Ansible commands. This directory would also contain any files that your Ansible project uses, such as an inventory and a playbook.

Note

The ansible --version command shows the location of the configuration file in use.

The Ansible configuration file consists of several sections, where each section contains settings that are defined as key-value pairs. Section titles are in square brackets. Two sections are essential for basic operations:

  • [defaults] sets defaults for Ansible operation.

  • [privilege_escalation] configures how Ansible escalates privileges on managed hosts.

The following content is a typical example of the ansible.cfg file:

[defaults]
inventory = ./inventory
remote_user = user
ask_pass = false

[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false

The directives in this file are explained in the following table:

DirectiveDescription
inventory Specifies the path to the inventory file.
remote_user The name of the user to log in as on the managed hosts. If not specified, the current user's name is used. (In a container-based automation execution environment that the ansible-navigator runs, the user is always root.)
ask_pass Whether to prompt for an SSH password. Can be false if using SSH public key authentication.
become Whether to automatically switch user on the managed host (typically to root) after connecting. A play can also specify this directive.
become_method How to switch user (typically sudo, which is the default, although su is an option).
become_user The user to switch to on the managed host (typically root, which is the default).
become_ask_pass Whether to prompt for a password for your become_method. Defaults to false.

The ansible-navigator config command displays the current Ansible configuration. The command displays the value that Ansible uses for each parameter and from which source it retrieves that value, configuration file, or environment variable.

The following output is from the ansible-navigator config command that is run from a /home/student/project/ directory that contains an ansible.cfg file. Each line describes an Ansible configuration parameter:

   Name                        Default Source                            Current
...output omitted...
44│Default ask pass            True    default                           False
45│Default ask vault pass      True    default                           False
46│Default become              False   /home/student/project/ansible.cfg True
47│Default become ask pass     False   /home/student/project/ansible.cfg True
...output omitted...
50│Default become method       False   /home/student/project/ansible.cfg sudo
51│Default become user         False   /home/student/project/ansible.cfg root
...output omitted...
  • The Default ask pass and Default ask vault pass parameters use their default values, which the True value in the Default column indicates.

  • The Default become and Default become ask pass parameters are manually configured to True in the /home/student/project/ansible.cfg configuration file. The Default column is False for these two parameters. The Source column provides the path to the configuration file that defines these parameters, and the Current column shows that the value for these two parameters is True.

  • The Default become method parameter has the current value of sudo, and the Default become user parameter has the current value of root.

Press Esc or type :q to exit the interactive mode of the ansible-navigator config command.

Note

If the project does not include an ansible.cfg file, then Ansible tries to use a ~/.ansible.cfg file in the home directory of the user that runs Ansible. If that file does not exist, then Ansible tries to use a /etc/ansible/ansible.cfg file.

However, the ansible-navigator command, if used, looks for these files inside the automation execution environment. On the current execution environments that Red Hat supports, these files do not exist or are empty.

If you are using the earlier ansible-playbook command or other Ansible commands that do not use automation execution environments, then Ansible looks for these files on your workstation.

Ansible Playbooks

The power of Ansible is that you can use playbooks to run multiple complex tasks against a set of targeted hosts in a repeatable manner.

You can use plays to change a lengthy, complex set of manual administrative tasks into a repeatable routine with predictable and successful outcomes. In a playbook, you can save the sequence of tasks in a play in a human-readable and immediately runnable form. The tasks themselves, because of the way in which they are written, document the steps to deploy your application or infrastructure.

Formatting an Ansible Playbook

The following example contains one play with a single task:

---
- name: Configure important user consistently
  hosts: server1.example.com
  tasks:
    - name: Newbie exists with UID 4000
      ansible.builtin.user:
        name: newbie
        uid: 4000
        state: present

A playbook is a text file in YAML format, and is normally saved with the .yml extension. The playbook uses indentation with space characters to indicate the structure of its data. Although YAML does not place strict requirements on how many spaces are used for the indentation, two main rules apply:

  • Data elements at the same level in the hierarchy (such as items in the same list) must have the same indentation.

  • Items that are children of another item must be indented more than their parents.

You can also add blank lines for readability.

Important

Use only space characters for indentation; do not use tab characters.

A playbook usually begins with a line that consists of three dashes (---) to indicate the start of the document. A playbook might end with three dots (...) to indicate the end of the document, although in practice this boundary marker is often omitted.

Between those markers, the playbook is defined as a list of plays. An item in a YAML list starts with a single dash followed by a space. For example, a YAML list might appear as follows:

- apple
- orange
- grape

The play itself is a collection of key-value pairs. Keys in the same play must have the same indentation. The following example shows a YAML snippet with three keys. The first two keys have single values. The third key has a list of three items as a value.

---
  name: just an example
  hosts: webservers
  tasks:
    - first
    - second
    - third

The initial example play has three keys: name, hosts, and tasks. These keys all have the same indentation.

The first line of the example play starts with a dash and a space (to indicate that the play is the first item of a list), and then the first key, name. The name key associates an arbitrary string with the play as a label that identifies the purpose of the play. The name key, though optional, is recommended because it helps to document your playbook. This name key is especially useful when a playbook contains multiple plays.

- name: Configure important user consistently

The second key in the play is a hosts key, which specifies the hosts that the play's tasks are run against. The hosts key takes a host pattern as a value, such as the names of managed hosts or groups in the inventory.

  hosts: server1.example.com

Finally, the last key in the play is tasks, whose value specifies a list of tasks to run for this play. This example has a single task, which runs the ansible.builtin.user module with specific arguments (to ensure that the newbie user exists and has UID 4000).

  tasks:
    - name: newbie exists with UID 4000
      ansible.builtin.user:
        name: newbie
        uid: 4000
        state: present

The tasks key is the part of the play that lists, in order, the tasks to be run on the managed hosts. Each task in the list is itself a collection of key-value pairs.

In this example, the only task in the play has two keys:

  • The name label optionally documents the purpose of the task. Red Hat recommends naming all your tasks to help to document the purpose of each step of the automation process.

  • The ansible.builtin.user module is run for this task. This module's arguments are passed as a collection of key-value pairs, which are children of the module (name, uid, and state).

The following output is another example of a tasks key with multiple tasks, where each task uses the ansible.builtin.service module to ensure that a service starts at boot:

  tasks:
    - name: Web server is enabled
      ansible.builtin.service:
        name: httpd
        enabled: true

    - name: NTP server is enabled
      ansible.builtin.service:
        name: chronyd
        enabled: true

    - name: Postfix is enabled
      ansible.builtin.service:
        name: postfix
        enabled: true

Important

The order in which the plays and tasks are listed in a playbook is important, because Ansible runs them in the same order.

Finding Modules for Tasks

Plays use modules to run tasks. Hundreds of modules are written for different purposes. You can usually find a tested, special-purpose module that does what you need, often as part of the default automation execution environment.

Ansible Content Collections distribute multiple types of Ansible content, such as playbooks, modules, documentation, and more.

The ansible-core package provides a single Ansible Content Collection named ansible.builtin. These modules are always available to you. Visit https://docs.ansible.com/ansible/latest/collections/ansible/builtin/ for a list of modules in the ansible.builtin collection.

In addition, the default automation execution environment that ansible-navigator uses in Red Hat Ansible Automation Platform 2.4, ee-rhel9-supported, includes several other Ansible Content Collections.

You can browse these collections by running the ansible-navigator collections command. In the interactive UI, you can type a colon (:) followed by the line number of a collection to get more information about it, including the list of modules and other Ansible content that it provides. You can do the same thing with the line number of a module to get documentation about that module. Press Esc to go back to the preceding list.

  Name               Version Shadowed Type	 Path
 0│amazon.aws         6.0.1   False    contained /usr/share/ansible/collections▒
 1│ansible.builtin    2.15.4  False    contained /usr/lib/python3.9/site-packag▒
 2│ansible.controller 4.4.2   False    contained /usr/share/ansible/collections▒
 3│ansible.netcommon  5.1.1   False    contained /usr/share/ansible/collections▒
 4│ansible.network    2.0.0   False    contained /usr/share/ansible/collections▒
 5│ansible.posix      1.5.4   False    contained /usr/share/ansible/collections▒

Modules are named by using fully qualified collection names (FQCNs). The same name can then be used for different modules in two Ansible Content Collections without causing conflicts. For example, the copy module from the ansible.builtin Ansible Content Collection has ansible.builtin.copy as its FQCN.

Note

Some modules might have their own additional requirements. For example, the ansible.builtin.dnf module, which can install packages on current Fedora systems, requires the python3-dnf package.

Running Playbooks

The ansible-navigator run command runs playbooks. The control node executes the command, and passes the name of the playbook to be run as an argument.

Running the ansible-navigator run command with the -m stdout option prints the output of the playbook to standard output. If the -m stdout option is not provided, then the ansible-navigator command runs in interactive mode. (Interactive mode is covered in the DO374: Developing Advanced Automation with Red Hat Ansible Automation Platform course.)

[user@controlnode ~]$ ansible-navigator run \
-m stdout site.yml

When you run the playbook, the output shows the play and the tasks that are executed. The output also reports the result of each task that is executed.

The following example shows the contents of a playbook:

[user@controlnode playdemo]$ cat webserver.yml
---
- name: Play to set up web server
  hosts: server1.example.com
  tasks:
  - name: Latest httpd version installed
    ansible.builtin.dnf:
      name: httpd
      state: latest
...output omitted...

When you run the playbook, the output shows that the latest version of the httpd package is installed:

[user@controlnode playdemo]$ ansible-navigator run \
-m stdout webserver.yml

PLAY [Play to set up web server] ************************************************

TASK [Gathering Facts] *********************************************************
ok: [server1.example.com]

TASK [Latest httpd version installed] ******************************************
changed: [server1.example.com]

PLAY RECAP *********************************************************************
server1.example.com    : ok=2    changed=1    unreachable=0    failed=0   skipped=0    rescued=0    ignored=0

The values of the name key for each play and task are displayed when the playbook is run. The ansible.builtin.setup module usually runs the Gathering Facts special task automatically at the start of a play. For playbooks with multiple plays and tasks, setting name attributes makes it easier to monitor the progress of a playbook's execution.

In the sample output, the Latest httpd version installed task is changed for server1.example.com. The task changed something on that host to ensure that its specification was met. In this case, it means that the httpd package was not previously installed or was not the latest version.

Increasing Output Verbosity

The default output from the ansible-navigator run command does not provide detailed task execution information. The -v option provides additional information, with up to four levels.

Table 2.1. Configuring the Output Verbosity of Playbook Execution

OptionDescription
-v Displays task results.
-vv Displays task results and task configuration.
-vvv Displays extra information about connections to managed hosts.
-vvvv Adds extra verbosity options to the connection plug-ins, including the users on managed hosts who executed scripts, and which scripts were executed.

Syntax Verification

Before executing a playbook, it is good practice to validate its syntax. You can use the ansible-navigator run --syntax-check command to validate the syntax of a playbook. The following example shows the successful syntax validation of a playbook:

[user@controlnode playdemo]$ ansible-navigator run \
-m stdout webserver.yml --syntax-check
playbook: /home/user/playdemo/webserver.yml

When syntax validation fails, a syntax error is reported. The output also includes the approximate location of the syntax issue in the playbook.

Executing Playbooks in Check Mode

You can use the --check option to run a playbook in check mode, which executes a playbook without altering your systems. In check mode, Ansible reports which changes would have occurred if the playbook was executed, but it does not make any changes to managed hosts.

The following example shows the check mode of a playbook that contains a single task for ensuring that the latest version of the httpd package is installed on a managed host. In this case, the dry run reports that the task would make a change on the managed host.

[user@controlnode playdemo]$ ansible-navigator run \
-m stdout webserver.yml --check

PLAY [Play to set up web server] ***********************************************

TASK [Gathering Facts] *********************************************************
ok: [server1.example.com]

TASK [Latest httpd version installed] ******************************************
changed: [server1.example.com]

PLAY RECAP *********************************************************************
server1.example.com    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Revision: rh415-9.2-a821299