Paketversionen scannen mit Ansible
Immer mal wieder schwirren in IT-Landschaften Versionen von Paketen herum, die so in ihrer abgehangenen Akutalität besser nicht mehr installiert sein sollten. log4j und Polkit sind nur 2 dieser (in Zungenbrecherisch) „vulnerabiliy“ aus der jüngsten Vergangenheit. Schnell möchten wir nun diese Sicherheitslücken schließen. Doch neben dem eigentlichen Update, müssen wir erstmal herausfinden, welche Systeme überhaupt betroffen sind und welche eine nicht konforme Version installiert haben.
Um letztere Information zu beschaffen, möchte ich in folgendem eine Möglichkeit vorstellen, wie dies über Ansible erledigt werden kann.
Vorbereitungen
Um Ansible zu benutzen, benötigen wir selbstverständlich erst einmal eine Ansible Instanz die mit all unseren Zielhosts kommunizieren kann. An dieser Stelle verweise ich auf einen meiner früheren Beiträge, in dem es um die Installation einer solchen geht. —> Linux Orchestration mit Ansible / Teil 1 <—
Playbook zum scannen der Paketversionen
Wie wohl bereits bekannt ist, arbeitet Ansible mit sogenannten ‚Playbooks‘ in denen Befehlsanweisungen stehen, die auf den Zielsystemen ausgeführt werden sollen.
Im folgenden stelle ich ein Playbook vor, welches sich auf die Systeme verbindet, alle installierten Pakete erfasst und je nach Installationsstatus und installierter Paketversion den entsprechenden Eintrag in eine Log-Datei schreibt. Im Anschluß folgt eine kleine Erklärung.
---
- hosts: ubuntu
vars:
package_name: "policykit-1"
bionic_version: "0.105-20ubuntu0.18.04.6"
focal_version: "0.105-26ubuntu1.2"
log_path: "/tmp/package_status.txt"
package: ""
ignore_errors: yes
ignore_unreachable: yes
###########################################################
### Gather facts about installed software ###
###########################################################
tasks:
- name: Ubuntu bionic
set_fact:
software: "{{ package_name }}"
matching_version: "{{ bionic_version }}"
package: ""
when: ansible_distribution == "Ubuntu" and ansible_distribution_version == "18.04"
- name: Ubuntu focal
set_fact:
software: "{{ package_name }}"
matching_version: "{{ focal_version }}"
package: ""
when: ansible_distribution == "Ubuntu" and ansible_distribution_version == "20.04"
- name: Delete old log file
file:
path: "{{ log_path }}"
state: absent
delegate_to: localhost
- name: Gather the package facts
ansible.builtin.package_facts:
manager: auto
- name: Print the package facts
no_log: True
ansible.builtin.debug:
var: ansible_facts.packages
- name: Check if package is installed
set_fact: package="{{ ansible_facts.packages[software][0].version }}"
when: "software in ansible_facts.packages"
register: installation_status
####################################################################################
### Check if package is either installed, has matching version or is deprecated ###
####################################################################################
- name: Check if package matches the recent version
ansible.builtin.debug:
msg: "{{ software }} matches the recent version ( {{ matching_version }} ) on {{ ansible_ssh_host }} ( {{ ansible_distribution }} {{ ansible_distribution_version }} )"
when: "matching_version in package"
register: debug_output_matching_version
- name: Check if package is deprecated
ansible.builtin.debug:
msg: "{{ software }} seems to be deprecated ( {{ ansible_facts.packages[software][0].version }} ) on {{ ansible_ssh_host }} ( {{ ansible_distribution }} {{ ansible_distribution_version }} )"
when: "package != '' and matching_version not in package"
register: debug_output_deprecated
- name: Check if package is not installed
ansible.builtin.debug:
msg: "{{ software }} is not installed on {{ ansible_ssh_host }} ( {{ ansible_distribution }} {{ ansible_distribution_version }} )"
when: "package == ''"
register: debug_output_not_installed
###########################################################
### Write Output to log-file at /tmp/package_status.txt ###
###########################################################
- name: Copy output to a local file
ansible.builtin.lineinfile:
insertafter: EOF
line: "{{ debug_output_matching_version.msg }}"
dest: "{{ log_path }}"
create: yes
delegate_to: localhost
when: debug_output_matching_version.msg is defined
- name: Copy output to a local file
ansible.builtin.lineinfile:
insertafter: EOF
line: "{{ debug_output_deprecated.msg }}"
dest: "{{ log_path }}"
create: yes
delegate_to: localhost
when: debug_output_deprecated.msg is defined
- name: Copy output to a local file
ansible.builtin.lineinfile:
insertafter: EOF
line: "{{ debug_output_not_installed.msg }}"
dest: "{{ log_path }}"
create: yes
delegate_to: localhost
when: debug_output_not_installed.msg is defined
Essentiell zur Bedienung sind die drei Variablen ‚package_name‘, ‚bionic_version‘ und ‚focal_version‘ im oberen Bereich. ‚bionic_version‘ und ‚focal_version‘ sind nur beispielhaft gewählt um die gleichzeitige Suche nach Paketen unterschiedlicher Versionen zu ermöglichen. Diese können nämlich, je nach Ubuntu-Release, unterschiedliche Bezeichnungen tragen. Möchte nur nach einer einzelnen Paketversion gesucht werden, kann die zweite Variable freigelassen werden. Es sollte dann jedoch berücksichtigt werden, das richtige OS-release abzufragen (Zeile 23 bzw. 30) da sonst das Playbook ins leere läuft und die notwendige Variable nicht gesetzt wird.
Nach dem Paket, welches unter ‚package_name‘ definiert ist, wird also gesucht und festgestellt, ob es installiert ist. Ferner wird geschaut, ob es sich bei dem System um ein Ubuntu der Version 18.04 oder 20.04 handelt und die entsprechende Variable gesetzt. Diese Werte sind beliebig anpassbar.
Passt vor Ausführung des Playbooks außerdem die Gruppe der Zielsysteme an. In diesem Playbook würden nämlich lediglich Systeme der Gruppe ‚ubuntu‘ erfasst werden (Zeile 3).
Wichtig zu verstehen ist hierbei, dass mit ‚bionic_version‘ und ‚focal_version‘ lediglich eine Kontextsuche erfolgt und nicht abgeglichen wird, ob die gefundene Version tatsächlich neuer oder älter als die Zielversion ist. In diesem Playbook gehen wir davon aus, dass unsere zu findende Version die neueste ist, die installiert sein soll und sämtliche abweichende Versionen veraltet sind.
Nach erfolgreichem Abschluss des Playbooks wird eine Datei unter /tmp/package_status.txt erzeugt. Dieser Wert lässt sich ebenfalls nach belieben anpassen (Zeile 8). Ist diese Datei unter dem angegebenen Pfad noch nicht vorhanden, wird sie autoamtisch erzeugt. Der Inhalt der Log-Datei könnte in Abhängigkeit nach dem Installationsstatus und der gefundenen Version beispielsweise folgendermaßen aussehen:
policykit-1 matches the recent version ( 0.105-26ubuntu1.2 ) on Ubuntu-desktop-1 ( Ubuntu 20.04 )
policykit-1 matches the recent version ( 0.105-26ubuntu1.2 ) on Ubuntu-desktop-2 ( Ubuntu 20.04 )
policykit-1 seems to be deprecated ( 0.104-20ubuntu0.18.04.6 ) on ubuntu-desktop-02 ( Ubuntu 18.04 )
policykit-1 is not installed on ubuntu-server-01 ( Ubuntu 20.04 )
…
Aufgrund dieses Outputs lässt sich nun feststellen, welche Systeme nachholbedarf in Sachen Updates haben und welche bereits aktualisiert wurden. Der Output kann natürlich beliebig im Playbook angepasst bzw. die Datei nachträglich in der Shell bearbeitet werden.