Since the beginning of the year, several vulnerabilities have been discovered in the Linux Kernel as well as in others important and widely-used packages. Among them, there was the famous CVE-2021-3156 affecting the sudo package and allowing any unprivileged user to gain root privileges. This one had a base score of 7.8, which is considered as high.
This kind of events demonstrate the importance of having a strong patching strategy to ensure up-to-date softwares and operating systems (even when it’s Linux 😎 ).
When your inventory is composed of few servers, you can easily and quickly patch all of them manually using “dnf/yum update” for RHEL family OS, “apt update” for Debian based OS or with “zypper update” for SUSE servers.
But when you have to update dozens of machines, doing it manually could be time consuming… and boring.
For Enterprise distributions, there are tools that can help you (Red Hat Satellite, SUSE Manager, aso.). But if you want to go with an open source solution, you can use Ansible to create playbooks and roles to patch your servers automatically.
The goal of this blog is not to explain what Ansible is and how does it work. If you have no knowledge on Ansible, it would be better to start by reading the official documentation, which is written in a very simple way and contains a lot of useful exemples.
Playbook
--- - name: OS update hosts: dev gather_facts: yes tasks: - name: OS update - all packages or security fixes only include_role: name: os_update ...
Role
--- - include_tasks: redhat.yml when: ansible_os_family == "RedHat" ...
Task
--- - name: Get packages that can be upgraded become: yes ansible.builtin.dnf: list: upgrades state: latest update_cache: yes register: reg_dnf_output_all when: ev_security_only == "no" - name: List packages that can be upgraded ansible.builtin.debug: msg: "{{ reg_dnf_output_all.results | map(attribute='name') | list }}" when: ev_security_only == "no" - name: Get packages that can be patched with security fixes become: yes ansible.builtin.dnf: security: yes list: updates state: latest update_cache: yes register: reg_dnf_output_secu when: ev_security_only == "yes" - name: List packages that can be patched with security fixes ansible.builtin.debug: msg: "{{ reg_dnf_output_secu.results | map(attribute='name') | list }}" when: ev_security_only == "yes" - name: Request user confirmation ansible.builtin.pause: prompt: | The packages listed above will be upgraded. Do you want to continue ? -> Press RETURN to continue. -> Press Ctrl+c and then "a" to abort. when: reg_dnf_output_all is defined or reg_dnf_output_secu is defined
- name: Upgrade packages become: yes ansible.builtin.dnf: name: '*' state: latest update_cache: yes update_only: no register: reg_upgrade_ok when: ev_security_only == "no" and reg_dnf_output_all is defined - name: Patch packages become: yes ansible.builtin.dnf: name: '*' security: yes state: latest update_cache: yes update_only: no register: reg_upgrade_ok when: ev_security_only == "yes" and reg_dnf_output_secu is defined - name: Print errors if upgrade failed ansible.builtin.debug: msg: "Packages upgrade failed" when: reg_upgrade_ok is not defined
- name: Install dnf-utils become: yes ansible.builtin.dnf: name: 'dnf-utils' state: latest update_cache: yes - name: Check if a reboot is required become: yes command: needs-restarting -r register: reg_reboot_required ignore_errors: yes failed_when: false changed_when: reg_reboot_required.rc != 0 notify: - Reboot server ...
Handler
--- - name : Reboot server ansible.builtin.reboot: msg: "Reboot initiated by Ansible after OS update" reboot_timeout: 3600 test_command: uptime ...
Execution
$ ansible-playbook playbooks/os_update.yml --extra-vars "ev_security_only=no" PLAY [OS update] ***************************************************************************************************************************************************************************************************************************** TASK [Gathering Facts] *********************************************************************************************************************************************************************************************************************** ok: [192.168.22.101] TASK [OS update - all packages or security fixes only] *************************************************************************************************************************************************************************************** TASK [os_update : include_tasks] ************************************************************************************************************************************************************************************************************* included: .../roles/os_update/tasks/redhat.yml for 192.168.22.101 TASK [os_update : Get packages that can be upgraded] ***************************************************************************************************************************************************************************************** ok: [192.168.22.101] TASK [os_update : List packages that can be upgraded] **************************************************************************************************************************************************************************************** ok: [192.168.22.101] => { "changed": false, "msg": [ "tuned", "hwdata", "libstdc++", "libgomp", "libgcc", "openssl-libs", "openssl", "tuned", "hwdata", "python36", "libstdc++-devel", "tuned" ] } TASK [os_update : Get packages that can be patched with security fixes] ***************************************************************************************************************************** skipping: [192.168.22.101] TASK [os_update : List packages that can be patched with security fixes] ********************************************************************************************************************************************************************* skipping: [192.168.22.101] TASK [os_update : Request user confirmation] ************************************************************************************************************************************************************************************************* [os_update : Request user confirmation] The packages listed above will be upgraded. Do you want to continue ? -> Press RETURN to continue. -> Press Ctrl+c and then "a" to abort. : ok: [192.168.22.101] TASK [os_update : Upgrade packages] ********************************************************************************************************************************************************************************************************** changed: [192.168.22.101] TASK [os_update : Patch packages] ************************************************************************************************************************************************************************************************************ skipping: [192.168.22.101] TASK [os_update : Print errors if upgrade failed] ******************************************************************************************************************************************************************************************** skipping: [192.168.22.101] TASK [os_update : Install dnf-utils] ********************************************************************************************************************************************************************************************************* ok: [192.168.22.101] TASK [os_update : Check if a reboot is required] ********************************************************************************************************************************************************************************************* ok: [192.168.22.101] PLAY RECAP *********************************************************************************************************************************************************************************************************************************** 192.168.22.101 : ok=8 changed=1 unreachable=0 failed=0 skipped=4 rescued=0 ignored=0 $
Possible improvements
At this stage, this role is rather basic. It could therefore benefit from some improvements, such as :
- Create tasks to patch Debian based and SUSE servers
- Add pre-task to send the list of packages by email before starting the patching
- Logging
- ….
Perhaps in a next blog…
Joël Cattin
16.02.2023Hi,
In your inventory, create a group containing your CentOS servers, and replace the 3rd line of the playbook by the name of the group
(hosts: group_name).
Joël