Als Linux-Distribution für den Betrieb im Rechenzentrum fiel unsere Wahl vor einiger Zeit auf Red Hat Enterprise Linux. Zu meinen Aufgaben gehört es, ein Patch-Management für diese Systeme zu entwickeln, welches die folgenden Anforderungen erfüllt:
- Die zu aktualisierenden Hosts werden in Gruppen im Ansible Inventory verwaltet. Eine Gruppe entspricht dabei einer Stage/Phase für das Patch-Management.
- An definierten Stichtagen sollen automatisiert fehlende Sicherheitsaktualisierungen auf den Systemen in den verschiedenen Stages/Phasen eingespielt werden können.
- Dabei sollen ausschließlich Pakete aktualisiert werden, für die Red Hat Security Advisory veröffentlicht wurden.
- Wenn Pakete aktualisiert wurden, soll der Host anschließend neugestartet werden.
Ich habe mich entschieden, diese Anforderungen durch die Verwendung von Ansible zu erfüllen. Wem Ansible noch kein Begriff ist, möge einen Blick in die Quellen am Ende des Artikels werfen.[1. Ansible – IT-Automation für Jedermann] [2. Ansible Documentation {en}] [3. Linux-Benutzerkonten mit Ansible verwalten] [4. Das Modul yum_repository] [5. Die Module copy und cron]
Mit den Anregungen meines Arbeitskollegen habe ich dazu das folgende Playbook erstellt, welches die Rolle rhel-patchmanagement auf allen Hosts mit einem Red Hat Betriebssystem ausführt:
--- - hosts: all tasks: - name: Group by OS group_by: key=os_{{ ansible_distribution }} changed_when: False - hosts: os_RedHat roles: - rhel-patchmanagement
Die Rolle rhel-patchmanagement besteht aus den beiden folgenden Dateien:
- roles/patch_rhel/tasks/main.yml
- roles/patch_rhel/vars/main.yml
roles/patch_rhel/vars/main.yml:
--- rhsa_to_install: RHSA-2016:1626,RHSA-2016:1628,RHSA-2016:1633
Obiges Listing gibt ein kurzes Beispiel, wie die Red Hat Security Advisory (RHSA) Nummern einer Variablen zugewiesen werden können. Die Variable rhsa_to_install wird dann in der Task-Datei verwendet.
roles/patch_rhel/tasks/main.yml:
--- - name: Install Red Hat Security Advisory (RHSA) command: yum -y update-minimal --advisory {{ rhsa_to_install }} register: yum_output - debug: var=yum_output - name: Reboot Host if packages were updated shell: sleep 2 && shutdown -r now "Ansible updates triggered" async: 1 poll: 0 ignore_errors: true when: ('"Complete!" in "{{ yum_output.stdout_lines[-1] }}"') or ('"Komplett!" in "{{ yum_output.stdout_lines[-1] }}"') - name: waiting for access server local_action: wait_for host={{ inventory_hostname }} state=started port=22 delay=30 timeout=300 connect_timeout=15
Zuerst wird das Kommando zur Aktualisierung der zu den angegebenen RHSA gehörenden Pakete auf den Hosts ausgeführt. Dabei wird die Ausgabe des Kommandos yum
auf den Hosts der Variable yum_output zugewiesen.[6. Registered Variables] Diese Variable wird im nächsten Schritt ausgewertet, um zu ermitteln, ob Pakete aktualisiert wurden. Ist dies der Fall, wird der Host neugestartet.
Erklärung: Wurden Pakete aktualisiert, steht in der letzten Zeile der YUM-Ausgabe der Ausdruck „Complete!“. "{{ yum_output.stdout_lines[-1] }}"
dereferenziert die Variable und liefert die Ausgabe des YUM-Kommandos als Liste zurück. Dabei wird in diesem Fall auf das letzte Element der Liste zugegriffen. Enthält diese Zeile den genannten Ausdruck, wird der Host neugestartet. Hinweis: Die zweite Zeile der when-Bedingung aus dem oberen Listing dient der Behandlung einer deutschsprachigen Ausgabe. In diesem Fall wird das Schlüsselwort „Komplett!“ ausgegeben und in der Variable gespeichert.
Der letzte Task wartet 30 Sekunden ab, um dem Host Zeit für den Neustart zu geben und prüft, ob eine Verbindung hergestellt werden kann. Damit soll sichergestellt werden, dass der Host nach dem Neustart wieder korrekt hochfährt.
Damit sind die Anforderungen aus Punkt 2 und 3 erfüllt. Um auch noch die Anforderung aus Punkt 1 zu erfüllen, setze ich folgendes Wrapper-Skript ein, welches das Playbook zu den definierten Stichtagen ausführen soll:
#!/bin/sh DOM=`date +%d` DOW=`date +%w` LOG="/var/log/ansible/patch_run_`date +%Y-%m-%d`.log" SETUP_LOG="/var/log/ansible/setup_patch_run_`date +%Y-%m-%d`.log" SSH_KEY="/pfad/zum/ssh-key" PLAYBOOK="/data/ansible/patch_rhel.yml" CREATEVARS="/pfad/zu/roles/rhel-patchmanagement/create_vars.sh" # Run Patch-Management ad-hoc in the specified phase. # Example: './run_rhel_patch_mgmt.sh NOW rhel-patch-phase1' if [ "${1}" = "NOW" ] && [ -n "${2}" ] then ansible-playbook $PLAYBOOK --private-key=$SSH_KEY --limit="${2}" >>$LOG 2>&1 exit fi if [ "${1}" = "NOW" ] && [ -z "${2}" ] then echo "ERROR: Second argument is missing." echo "Example: './run_rhel_patch_mgmt.sh NOW rhel-patch-phase1'" exit fi # Setup the next patchcycle if [ "$DOW" = "2" ] && [ "$DOM" -gt 0 ] && [ "$DOM" -lt 8 ] then $CREATEVARS > $SETUP_LOG 2>&1 fi # Patchcycle of the rhel-patch-phase1 on the second Tuesday of a month if [ "$DOW" = "2" ] && [ "$DOM" -gt 7 ] && [ "$DOM" -lt 15 ] then ansible-playbook $PLAYBOOK --private-key=$SSH_KEY --limit=rhel-patch-phase1 > $LOG 2>&1 fi # Patchcycle of the rhel-patch-phase2 on the third Tuesday of a month if [ "$DOW" = "2" ] && [ "$DOM" -gt 14 ] && [ "$DOM" -lt 22 ] then ansible-playbook $PLAYBOOK --private-key=$SSH_KEY --limit=rhel-patch-phase2 > $LOG 2>&1 fi # Patchcycle of the rhel-patch-phase3 on the fourth Tuesday of a month if [ "$DOW" = "2" ] && [ "$DOM" -gt 21 ] && [ "$DOM" -lt 29 ] then ansible-playbook $PLAYBOOK --private-key=$SSH_KEY --limit=rhel-patch-phase3 > $LOG 2>&1 fi # Patchcycle of the rhel-patch-phase4 on the fourth Wednesday of a month if [ "$DOW" = "3" ] && [ "$DOM" -gt 21 ] && [ "$DOM" -lt 30 ] then ansible-playbook $PLAYBOOK --private-key=$SSH_KEY --limit=rhel-patch-phase4 > $LOG 2>&1 fi
Mit diesem Wrapper-Skript möchte ich dafür sorgen, dass an jedem 2. Dienstag im Monat die RSHA-Aktualisierungen auf den Hosts in der rhel-patch-phase1, jeden 3. Dienstag in der rhel-patch-phase2, jeden 4. Dienstag in der rhel-patch-phase3 und jeden 4. Mittwoch in rhel-patch-phase4 installiert werden.
So kann sichergestellt werden, dass die Informationen aus den RHSA alle Hosts erreichen. Gleichzeitig wird den Betreibern der Hosts die Gelegegnheit gegeben, die Aktualisierungen bis zu den genannten Stichtagen selbst einzuspielen. Damit sollte ich auch an Punkt 1 einen Haken dran machen können.
Die Ansible-Rolle und dazugehörende Skripte und Beispiel-Dateien findet ihr auf:
- Ansible Galaxy: https://galaxy.ansible.com/tronde/rhel-patchmanagement
- GitHub: https://github.com/Tronde/ansible-role-rhel-patchmanagement
Fragen zur Anwendung, Konfiguration und Nutzung des RHEL-Patchmanagements könnt ihr gerne auf GitHub oder hier in den Kommentaren stellen.
Aktualisiert am: 07.09.2018
Hallo,
interessanter Artikel. Insbesondere die Verwendung von dynamischen Gruppen hatte ich bisher immer links liegen lassen.
Ein kleiner Hinweis zu der Direktive `group_by`. Ich persönlich stolpere immer über die Groß- Kleinschreibung bei distribution und einigen anderen Facts.
Daher würde ich folgendes verwenden:
group_by: key=os_{{ ansible_distribution.lower() }}
Somit steht die Distribution in Kleinschreibweise zur Verfügung, was IMO besser für Conditions geeignet ist und sauberer aussieht.
Pingback: Die Rolle von Ansible in unserem Linux-Betriebskonzept | My-IT-Brain
Pingback: CVE — Ist mein RHEL-System betroffen? Was kann ich tun? | My-IT-Brain
Pingback: Red Hat Insights – Patch and Drift | My-IT-Brain
Pingback: Ansible – Was ich am Ad-hoc-Modus schätze | My-IT-Brain
Pingback: Ein Blick auf AlmaLinux, RHEL und Rocky Linux | My-IT-Brain