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.

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Nach oben scrollen