In his article 5 ways to harden a new system with Ansible, Enable Sysadmin Sudoer Anthony Critelli walks through developing an Ansible playbook to secure a new Linux server. He shows how to use Ansible to patch the system, lock remote access, disable unused software and services, and do other useful tasks.
In this article, I'll show you how to use Ansible to further govern your Linux server with additional policies and recommendations.
[ Cheat sheet: Get a list of Linux utilities and commands for managing servers and networks. ]
While these recommendations are helpful, keep in mind that they don't guarantee your server is perfectly safe. It's unlikely that any servers exposed on the internet are completely secure, but these additional policies help improve their security, making it harder for anyone to attack or exploit your server.
Finally, make sure that these recommendations are compatible with your server's purpose before applying them.
Encoding these recommendations and policies into an Ansible playbook and automating their application allows you to apply them consistently across your servers, verify compliance, and remediate servers periodically. I've included a complete sample playbook at the end of this article with all recommendations below.
1. Ensure your firewall is up and running
Ensuring your local firewall is up and running is one of the most basic things you can do to restrict access to your Linux server. Some Linux distributions, such as Red Hat Enterprise Linux (RHEL), enable the firewall by default during installation. However, it's a good idea to verify that the firewall is enabled in case you installed a server from, for instance, a corporate template that doesn't enable it by default.
To ensure the firewall is installed, up, and running using Ansible, use the ansible.builtin.dnf
and ansible.builtin.service
modules with the appropriate firewall service for your distribution. For Fedora-based distributions, the firewall service is firewalld. Enable firewalld
by adding both tasks like this:
- name: Ensure firewall package is installed
ansible.builtin.dnf:
name: firewalld
state: present
- name: Ensure firewall service is up and running
ansible.builtin.service:
name: firewalld
state: started
enabled: yes
Then, ensure the firewall allows access only to services you need, disabling other services. This task varies depending on your system's purpose. For example, disable services cockpit
and dhcpv6-client
if you don't use them by using the module ansible.posix.firewalld
with a loop:
- name: Block non-required services
ansible.posix.firewalld:
service: "{{ item }}"
state: disabled
permanent: yes
immediate: yes
loop:
- cockpit
- dhcpv6-client
Finally, enable required services using the same module. For example, ensure that you can access your server using SSH:
- name: Enable required services
ansible.posix.firewalld:
service: "ssh"
state: enabled
permanent: yes
immediate: yes
You can also use a loop if you need to enable more services or even configure both settings as a single task by looping over a dictionary and combining the services with the desired state.
Next, ensure SELinux is enabled.
2. Ensure SELinux is enabled and enforcing
Linux distributions in the Red Hat family, such as RHEL and Fedora, implement a mandatory access control (MAC) security solution known as SELinux (Security Enhanced Linux). SELinux is one of the best ways to improve your system's security by ensuring that processes only have access to specific resources, such as certain files or designated network ports.
I won't cover SELinux in detail here. It's a large topic, so I encourage you to learn more about it. For more information, consult the SELinux documentation.
[ Get the SELinux cheat sheet. ]
To enable SELinux and set it to enforcing mode to allow active system protection, use the ansible.posix.selinux
module:
- name: Ensure SELinux is enabled and enforcing
ansible.posix.selinux:
policy: targeted
state: enforcing
register: selinux_status
As with the firewall, SELinux should be enabled by default with RHEL and Fedora, but this is a good check and action in case it's not. When you change the SELinux status, a reboot is required. In this case, the selinux
module returns the variable reboot_required
set to true
. Check this value with the debug
module and use a changed_when
condition to notify a handler to reboot the machine at the end:
- name: Verify if reboot needed
ansible.builtin.debug:
msg: "Reboot needed: {{ selinux_status.reboot_required }}"
changed_when: "{{ selinux_status.reboot_required | bool }}"
notify: reboot_host
Then, at the end of your playbook, define the reboot_host
handle using the ansible.builtin.reboot
module to restart the machine:
handlers:
- name: reboot_host
ansible.builtin.reboot:
reboot_timeout: 360
Note that if you enable SELinux for an existing server with many files, the system relabels them when the machine restarts. This process can take a long time, so plan changes accordingly. The best option is to enable SELinux when deploying a new server to ensure it benefits from this protection from the start.
Next, enable some secure kernel parameters.
3. Enable kernel security parameters
The Linux kernel is highly flexible and customizable to meet diverse requirements, workloads, and uses. You can benefit from this flexibility to increase your server security by changing some kernel parameters. Linux exposes most of these parameters in the virtual filesystem /proc
. You can change them by setting the values directly into the corresponding /proc
key, but it's harder to manage. The command-line utility sysctl
makes this task pretty easy to implement and manage. You can also use the Ansible module ansible.posix.sysctl
to manage kernel parameters.
The Linux kernel makes several parameters available to change. These parameters impact the system's functionality and security in different ways. Ensure that you change the ones compatible with your server's purpose and utilization.
Start by updating basic system parameters such as:
- Randomize virtual address space, which makes it harder for an attacker to use known memory addresses for exploitation.
- Disable dmesg access to unprivileged users.
- Disable kernel profiling by unprivileged users.
Set these parameters using the ansible.posix.sysctl
module with a loop, like this:
- name: Harden kernel parameters
ansible.posix.sysctl:
name: "{{ item.name }}"
value: '{{ item.value }}'
sysctl_set: yes
state: present
reload: yes
sysctl_file: /etc/sysctl.d/90-kernel.conf
loop:
- name: kernel.randomize_va_space
value: 2
- name: kernel.dmesg_restrict
value: 1
- name: kernel.perf_event_paranoid
value: 2
Next, enable some basic network-related protections:
- Disable TCP SYN cookies to prevent SYN flood attacks.
- Log Martian packets to allow inspection of Internet Protocol (IP) packets from reserved addresses.
- Disable acceptance of source-routed packets on IPv4 and IPv6 to prevent accepting routing changes that could bypass network security.
- name: Harden network parameters
ansible.posix.sysctl:
name: "{{ item.name }}"
value: '{{ item.value }}'
sysctl_set: yes
state: present
reload: yes
sysctl_file: /etc/sysctl.d/90-net.conf
loop:
- name: net.ipv4.tcp_syncookies
value: 1
- name: net.ipv4.conf.default.log_martians
value: 1
- name: net.ipv4.conf.all.log_martians
value: 1
- name: net.ipv4.conf.all.accept_source_route
value: 0
- name: net.ipv4.conf.default.accept_source_route
value: 0
- name: net.ipv6.conf.all.accept_source_route
value: 0
- name: net.ipv6.conf.default.accept_source_route
value: 0
Finally, unless you use bridged network connections for virtual machines or containers, disable IP forwarding:
- name: Disable ip forwarding
ansible.posix.sysctl:
name: "{{ item.name }}"
value: '{{ item.value }}'
sysctl_set: yes
state: present
reload: yes
sysctl_file: /etc/sysctl.d/90-ip.conf
loop:
- name: net.ipv4.ip_forward
value: 0
- name: net.ipv6.conf.all.forwarding
value: 0
These options improve your server security by preventing attackers from using the network to circumvent other protections. Note that the default value for some of these parameters can be useful in other situations, such as network routers. They should not be needed in most servers.
Once you set some basic network security options, improve on them by blocking some ICMP requests.
[ Learn more about server and configuration management by downloading Ansible for DevOps. ]
4. Disable ICMP
Internet Control Message Protocol (ICMP) is a network protocol mainly used to manage and monitor networks. ICMP has many useful applications, but attackers can use some of its functionality to exploit your system.
Disabling ICMP may impact network monitoring tools that rely on it. Consult with your system's management team before making changes. If this is your private server or your server is exposed on the internet, you should disable some ICMP capabilities to prevent common attack vectors. These include:
- Ignore ICMP broadcast echo requests to make it harder to map your host.
- Ignore ICMP echo "ping" requests to make it harder to find your server.
- Disable ICMP redirects on IPv4/IPv6 interfaces to prevent "man-in-the-middle" attacks.
- Disable sending ICMP redirects to prevent leaking network routing information that could be further exploited.
Enable these options using the module ansible.posix.sysctl
with a loop:
- name: Disable ICMP echo and redirects
ansible.posix.sysctl:
name: "{{ item.name }}"
value: '{{ item.value }}'
sysctl_set: yes
state: present
reload: yes
sysctl_file: /etc/sysctl.d/90-icmp.conf
loop:
- name: net.ipv4.icmp_echo_ignore_broadcasts
value: 1
- name: net.ipv4.icmp_echo_ignore_all
value: 1
- name: net.ipv4.conf.default.accept_redirects
value: 0
- name: net.ipv4.conf.all.accept_redirects
value: 0
- name: net.ipv6.conf.all.accept_redirects
value: 0
- name: net.ipv6.conf.default.accept_redirects
value: 0
- name: net.ipv4.conf.default.send_redirects
value: 0
- name: net.ipv4.conf.all.send_redirects
value: 0
As with the network options, the default values of these parameters are required for routers but not servers. You can change them to improve security.
Next, make it easier to understand your system by enabling system auditing.
[ Boost security, flexibility, and scale at the edge with Red Hat Enterprise Linux. ]
5. Enable system auditing
The final step to improve security in your servers is enabling the audit services. Unlike the actions from previous sections in this article, enabling system auditing does not offer active system protection. Nonetheless, it improves your system security by capturing information you can use to track security-related incidents and better understand server activities to implement additional policies or enhance security measures.
Ensure system audit is working by using Ansible modules ansible.builtin.dnf
and ansible.builtin.service
:
- name: Ensure audit package is installed
ansible.builtin.dnf:
name: audit
state: present
- name: Ensure auditd service is up and running
ansible.builtin.service:
name: auditd
state: started
enabled: yes
Then, implement a basic set of rules that include, at minimum, the option -e 2
to make the rules configuration immutable. This setting requires a reboot to implement the changes, which prevents someone from intentionally or inadvertently stopping or tampering with the audit system:
$ vi audit.rules
## First rule - delete all
-D
## Increase the buffers to survive stress events.
## Make this bigger for busy systems
-b 8192
## This determine how long to wait in burst of events
--backlog_wait_time 60000
## Set failure mode to syslog
-f 1
## Make rules configuration immutable (requires reboot to change)
-e 2
Finally, use Ansible's ansible.builtin.copy
module to copy the rules file to its proper place. The audit service doesn't like to be restarted because it could not log potential issues. It's recommended to reboot the machine to make these changes effective. You can do that by notifying the same reboot_host
handler you added for the SELinux task:
- name: Add a basic audit config
ansible.builtin.copy:
src: audit.rules
dest: /etc/audit/rules.d/audit.rules
owner: root
group: root
mode: 0600
notify: reboot_host
The default audit rules provide a good foundation for system auditing, but you can add more rules to record and track other relevant events. For more information, consult the Auditing chapter in the RHEL security manual.
Security is a journey
The topics in this article are good starting points for improving your server's security. You may not make your server completely secure, but you're making it safer. Ensuring your firewall is running, SELinux is enforced, and network access is tightened are basic security measures. In many cases, taking care of the basics greatly protects your systems.
Don't stop here! After applying these policies and recommendations, continue to observe and monitor your system and adjust accordingly. Use the audit system to understand potential threats and utilization patterns to continuously improve your policies and update your systems.
Security is a journey, not a destination.
Complete playbook
For reference, this is the complete playbook using all the recommendations from this article:
- name: Linux hardening
hosts: linux_servers
gather_facts: yes
tasks:
- name: Ensure firewall package is installed
ansible.builtin.dnf:
name: firewalld
state: present
- name: Ensure firewall service is up and running
ansible.builtin.service:
name: firewalld
state: started
enabled: yes
- name: Block non-required services
ansible.posix.firewalld:
service: "{{ item }}"
state: disabled
permanent: yes
immediate: yes
loop:
- cockpit
- dhcpv6-client
- name: Enable required services
ansible.posix.firewalld:
service: "ssh"
state: enabled
permanent: yes
immediate: yes
- name: Ensure SELinux is enabled and enforcing
ansible.posix.selinux:
policy: targeted
state: enforcing
register: selinux_status
- name: Verify if reboot needed
ansible.builtin.debug:
msg: "Reboot needed: {{ selinux_status.reboot_required }}"
changed_when: "{{ selinux_status.reboot_required | bool }}"
notify: reboot_host
- name: Harden kernel parameters
ansible.posix.sysctl:
name: "{{ item.name }}"
value: '{{ item.value }}'
sysctl_set: yes
state: present
reload: yes
sysctl_file: /etc/sysctl.d/90-kernel.conf
loop:
- name: kernel.randomize_va_space
value: 2
- name: kernel.dmesg_restrict
value: 1
- name: kernel.perf_event_paranoid
value: 2
- name: Harden network parameters
ansible.posix.sysctl:
name: "{{ item.name }}"
value: '{{ item.value }}'
sysctl_set: yes
state: present
reload: yes
sysctl_file: /etc/sysctl.d/90-net.conf
loop:
- name: net.ipv4.tcp_syncookies
value: 1
- name: net.ipv4.conf.default.log_martians
value: 1
- name: net.ipv4.conf.all.log_martians
value: 1
- name: net.ipv4.conf.all.accept_source_route
value: 0
- name: net.ipv4.conf.default.accept_source_route
value: 0
- name: net.ipv6.conf.all.accept_source_route
value: 0
- name: net.ipv6.conf.default.accept_source_route
value: 0
- name: Disable ip forwarding
ansible.posix.sysctl:
name: "{{ item.name }}"
value: '{{ item.value }}'
sysctl_set: yes
state: present
reload: yes
sysctl_file: /etc/sysctl.d/90-ip.conf
loop:
- name: net.ipv4.ip_forward
value: 0
- name: net.ipv6.conf.all.forwarding
value: 0
- name: Disable ICMP echo and redirects
ansible.posix.sysctl:
name: "{{ item.name }}"
value: '{{ item.value }}'
sysctl_set: yes
state: present
reload: yes
sysctl_file: /etc/sysctl.d/90-icmp.conf
loop:
- name: net.ipv4.icmp_echo_ignore_broadcasts
value: 1
- name: net.ipv4.icmp_echo_ignore_all
value: 1
- name: net.ipv4.conf.default.accept_redirects
value: 0
- name: net.ipv4.conf.all.accept_redirects
value: 0
- name: net.ipv6.conf.all.accept_redirects
value: 0
- name: net.ipv6.conf.default.accept_redirects
value: 0
- name: net.ipv4.conf.default.send_redirects
value: 0
- name: net.ipv4.conf.all.send_redirects
value: 0
- name: Ensure audit package is installed
ansible.builtin.dnf:
name: audit
state: present
- name: Ensure auditd service is up and running
ansible.builtin.service:
name: auditd
state: started
enabled: yes
- name: Add a basic audit config
ansible.builtin.copy:
src: audit.rules
dest: /etc/audit/rules.d/audit.rules
owner: root
group: root
mode: 0600
notify: reboot_host
handlers:
- name: reboot_host
ansible.builtin.reboot:
reboot_timeout: 360
[ Check out this guide to boosting hybrid cloud security and protecting your business. ]
About the author
Ricardo Gerardi is Technical Community Advocate for Enable Sysadmin and Enable Architect. He was previously a senior consultant at Red Hat Canada, where he specialized in IT automation with Ansible and OpenShift.
He has been a Linux and open source enthusiast and contributor for over 20 years. He is currently interested in hacking stuff using the Go programming language, and he's the author of Powerful Command-Line Applications in Go: Build Fast and Maintainable Tools. Ricardo also writes regularly about Linux, Vim, and command line tools for Opensource.com and Enable Sysadmin community publications.
Ricardo enjoys spending time with his daughters, reading science fiction books, and playing video games.
Browse by channel
Automation
The latest on IT automation for tech, teams, and environments
Artificial intelligence
Updates on the platforms that free customers to run AI workloads anywhere
Open hybrid cloud
Explore how we build a more flexible future with hybrid cloud
Security
The latest on how we reduce risks across environments and technologies
Edge computing
Updates on the platforms that simplify operations at the edge
Infrastructure
The latest on the world’s leading enterprise Linux platform
Applications
Inside our solutions to the toughest application challenges
Original shows
Entertaining stories from the makers and leaders in enterprise tech
Products
- Red Hat Enterprise Linux
- Red Hat OpenShift
- Red Hat Ansible Automation Platform
- Cloud services
- See all products
Tools
- Training and certification
- My account
- Customer support
- Developer resources
- Find a partner
- Red Hat Ecosystem Catalog
- Red Hat value calculator
- Documentation
Try, buy, & sell
Communicate
About Red Hat
We’re the world’s leading provider of enterprise open source solutions—including Linux, cloud, container, and Kubernetes. We deliver hardened solutions that make it easier for enterprises to work across platforms and environments, from the core datacenter to the network edge.
Select a language
Red Hat legal and privacy links
- About Red Hat
- Jobs
- Events
- Locations
- Contact Red Hat
- Red Hat Blog
- Diversity, equity, and inclusion
- Cool Stuff Store
- Red Hat Summit