Securing Linux Servers using Ansible
Managing a single Linux machine can already present substantial challenges, requiring a solid understanding of command-line interfaces, system configurations, and troubleshooting procedures.
But managing two or more such systems, as a system administrator or homelabber, multiplies the complexity. This task necessitates precise tracking and orchestration of different system states, configurations, and network settings.
Establishing consistent update schedules, managing inter-system communications, and ensuring data security across all devices becomes crucial, highlighting the importance of advanced system administration skills and tools.
Having a robust knowledge base and leveraging automation tools can substantially aid in efficiently managing multiple Linux machines.
Introducing Ansible
Ansible is an automation tool that can streamline the management of multiple systems. By leveraging a push-based model, Ansible allows administrators to automate tasks such as configuration management, and application deployment.
The cornerstone of automation with Ansible is the playbooks.
You use playbooks to define the desired system state and which hosts to apply it. Its usefulness lies in the fact that Ansible is idempotent, which means that repeating the same operation multiple times results in the same outcome. For example, if you create a playbook to install neofetch on a host of systems, it will first check if neofetch is already present and only make changes if the current state does not match the desired state.
Building blocks of Ansible
Inventory
The inventory file in Ansible serves as a blueprint for the hosts you aim to manage. It's essentially a text file that outlines the hosts and groups of hosts upon which commands, modules, and playbooks will operate. The inventory file enables Ansible to connect and interact with your nodes, making it a pivotal component of Ansible's configuration.
For instance, imagine you have two servers you want to manage: server1
and server2
. Your inventory file might look like this:
[webservers]
server1 ansible_host=192.168.0.1
server2 ansible_host=192.168.0.2
In this example, webservers
is a group containing two hosts, server1
and server2
. The ansible_host
setting is used to define the IP addresses of the hosts.
You can also specify other parameters like the ansible_user
and ansible_ssh_private_key_file
for each host.
Playbooks
Ansible Playbooks are the vehicle for bringing your system to a desired state.
Playbooks are written in the YAML format and provide a user-friendly way to script complex tasks.
They can be used for various tasks, such as provisioning servers, deploying applications, and orchestrating complex workflows. They are an excellent tool for managing repetitive tasks, ensuring that all tasks are executed in the same order and manner each time, reducing the potential for human error.
To install the packages net-tools
and neofetch
using an Ansible playbook, you could create a playbook file that looks like the following:
- name: Deploy Application
hosts: webservers
tasks:
- name: Copy application files
copy:
src: /path/to/app
dest: /var/www/app
owner: www-data
group: www-data
mode: 0755
- name: Restart Apache
service:
name: apache2
state: restarted
In this example, the playbook hosts: webservers
instructs Ansible to run this playbook on the webservers group defined in your inventory file.
The become: yes
directive is used to gain superuser privileges for installing packages.
The tasks:
list outlines the steps to be taken on the target hosts. Each task uses the apt
module to ensure that the specified package is installed.
The update_cache: yes
option ensures the package database is updated before the package is installed.
Templates
Ansible template files serve an essential role in configuration management and multi-machine deployment. They enable the customization of files for each system while maintaining a single source file. Built upon the Jinja2 templating engine, these files use the `.j2` extension and allow variables to be inserted, which Ansible will replace with actual values at runtime. This functionality makes them an invaluable tool for tasks where configuration files can change based on specific system properties or roles, providing a high degree of flexibility and reusability.
Here's an example template greeting.j2
.
Hello, {{ name }}! Welcome to Ansible. Your age is {{ age }}.
Vars
Ansible variables, often termed as vars
for short, provide a powerful way to control the behavior of playbooks, and accordingly, the systems being configured.
These variables can be defined in numerous places, including in a dedicated vars.yml
file, directly in the playbook, in an inventory file, or even as command-line arguments during playbook execution.
More importantly, the use of Ansible variables helps to enhance the reusability of playbooks, as they allow users to define and manipulate values that can change based on specific system properties or roles.
name: John
age: 30
Handlers
In Ansible, handlers are just like regular tasks in an Ansible playbook but are only run if notified by another task. They are triggered by a notifying directive and are run once, at the end of the block of tasks in a playbook, irrespective of how many tasks notify them.
For example, if we have a setup where we want to restart a service whenever a configuration file changes, we can use a handler for this. The task that modifies the configuration file will notify the handler to restart the service. But no matter how many tasks notify the handler, it will only run once, after all the tasks have been executed. Here's a basic example:
---
name: a simple playbook
hosts: localhost
tasks:
name: Install the software
command: echo "installing software"
notify: restart service
handlers:
name: restart service
command: echo "restarting service"
In this example, after the Install the software
the task is run, it notifies the restart service
handler.
After all the tasks are complete, the restart service
handler executes. Handlers are perfect for managing services related to a configuration file, ensuring that changes are correctly applied.
Roles
Ansible roles could be thought of as the Swiss army knife of Ansible playbooks. They are designed to be fully self-contained, reusable components containing variables, tasks, templates, files, and modules that can be seamlessly integrated into playbooks. Think of them as tiny playbooks themselves, each designed to perform a specific task.
Let's consider them in terms of setting up a server. You could have individual roles for each component of the server, for example, one for the web server setup, another for the database, and so on. When it's time to set up a new server, you just bring together all these roles in your playbook, like pieces of a jigsaw puzzle. This approach keeps your main playbook neat and tidy, and promotes the reuse of roles across different playbooks, leading to more efficient use of your time and resources.
Hardening Linux Servers
When it comes to server security, it's imperative to adopt a robust, proactive approach. One effective way to achieve this is through the automation capabilities provided by Ansible. It allows you to harden your Linux server security in a streamlined, efficient manner.
Moreover, Ansible's idempotent nature ensures that these security configurations are maintained over time. This means that no matter how many times a playbook is run, the result will always be the state defined in the playbook. This is especially helpful in maintaining the integrity of your security configurations, as any deviation from the desired state can potentially open up vulnerabilities.
In addition, Ansible's clear syntax and documentation make it a user-friendly choice for system administrators. It's also compatible with a wide range of Linux distributions, ensuring you can deploy consistent security configurations across diverse environments.
Installing public keys on hosts
After you have created your ssh keys, you need to configure ssh for all required hosts to allow Ansible to communicate and configure all machines.
install_public_keys.yml
The first task of this playbook is to install the ssh key into all your machines. The second task modifies the system config to allow `sudo` privileges without prompting the user for a password. This is necessary to set up Ansible as we don't want to enter passwords for every machine when we call on Playbooks.
- hosts: all
become: true
tasks:
- name: install public keys
ansible.posix.authorized_key:
user: mustafa
state: present
key: "{{ lookup('file', '/home/mustafa/.ssh/id_rsa.pub') }}"
- name: change sudoers file
lineinfile:
path: /etc/sudoers
state: present
regexp: '^%sudo'
line: '%sudo ALL=(ALL) NOPASSWD: ALL'
validate: /usr/sbin/visudo -cf %s
set_ssh_permissions.yml
Setting permissions for ~/.ssh
and ~/.ssh/authorized_keys
is a crucial step in securing your SSH setup.
The ~/.ssh
directory should have 0700
permissions, ensuring that only the owner has the rights to read, write, and execute files within the directory.
The ~/.ssh/authorized_keys
file should have 0600
permissions, allowing only the owner to read and write to the file. These permissions prevent unauthorized users from accessing or altering your SSH keys, protecting your system from potential security breaches.
- name: Change permissions for SSH directory and authorized_keys
hosts: all
become: true
tasks:
- name: Change permissions for ~/.ssh directory
file:
path: /home/mustafa/.ssh
mode: "0700"
- name: Change permissions for ~/.ssh/authorized_keys file
file:
path: /home/mustafa/.ssh/authorized_keys
mode: "0600"
configure_ssh_settings.yml
Another crucial step is disabling root login and password authentication.
---
- name: Secure SSH configuration
hosts: proxmox
become: yes
tasks:
- name: Update sshd_config to disable SSH password authentication and restrict root login
lineinfile:
path: /etc/ssh/sshd_config
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
loop:
- { regexp: '^(#)?PasswordAuthentication', line: 'PasswordAuthentication no' }
- { regexp: '^(#)?PermitRootLogin', line: 'PermitRootLogin no' }
- name: Update sshd_config to restrict SSH service to IPv4
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^(#)?AddressFamily'
line: 'AddressFamily inet'
- name: Restart SSH service
service:
name: sshd
state: restarted
setup fail2ban
Fail2Ban is an essential tool to consider in your cybersecurity arsenal. It operates by monitoring system logs for any malicious activity. Upon detection of repeated failed login attempts, which are often indicative of brute force attacks, Fail2Ban promptly blocks the offender's IP address. This significantly mitigates the risk of unauthorized system access. However, it's worth noting that while Fail2Ban provides an additional layer of security, it should not be the sole measure for system protection and needs to be incorporated into a more comprehensive security strategy.
Here's a quick guide on setting up fail2ban using Ansible:
Ansible fail2ban guide
Hardening a Linux server is a continuous and multilayered process, encompassing a wide array of security measures.
This list is by no means exhaustive of all the measures you might need to take for proper security. It's only a starting point to getting you started with thinking about all the doors you need to close to make sure that your server is protected from bad actors.
Here are some references that go into much more detail on the topic.
- https://opensource.com/article/19/10/linux-server-security
- https://medium.com/viithiisys/10-steps-to-secure-linux-server-for-production-environment-a135109a57c5
- https://securityboulevard.com/2020/08/linux-server-security-10-linux-hardening-security-best-practices/
- https://www.linode.com/docs/guides/securing-your-server/
- http://www.linuxandubuntu.com/home/10-steps-to-secure-linux-server
- https://www.servermania.com/kb/articles/top-5-ways-to-secure-your-linux-server/
- https://www.plesk.com/blog/various/linux-server-security-best-practices/
- https://www.youtube.com/watch?v=ZhMw53Ud2tY
- https://www.youtube.com/watch?v=X_VP10X2TEs
- https://linuxhint.com/linux_server_security_hardening/
- https://www.computerworld.com/article/3144985/linux-hardening-a-15-step-checklist-for-a-secure-linux-server.html
- https://www.tecmint.com/linux-server-hardening-security-tips/
- https://dev.to/vkolesov/how-to-protect-your-server-from-hackers-4j6l
- https://www.eurovps.com/blog/20-ways-to-secure-linux-vps/