Wie man mithilfe von Ansible, Elastic Filebeat innerhalb eines Netzwerkes verteilt und konfiguriert

Maximilian Schneider
29. September 2021
Lesezeit: 11 min
Wie man mithilfe von Ansible, Elastic Filebeat innerhalb eines Netzwerkes verteilt und konfiguriert

Vorwort

In diesem Blogeintrag erfährst du, wie du mithilfe von Ansible, Elastic Filebeat innerhalb eines Netzwerkes verteilst und konfigurierst. Hier findest du außerdem eine Erklärung aller fundamentalen Dinge von Ansible. In diesem Beispiel habe ich als Zielhost einen Ubuntu-Server verwendet. Bei der Erstellung des Playbooks habe ich mich für den Ablauf an der Dokumentation von Elastic orientiert.

Was ist Ansible?

Ansible ist ein Open-Source Automatisierungs-Tool zur Orchestrierung und Konfiguration sowie Administration von verschiedenen Systemen. Dabei kombiniert Ansible Softwareverteilung, Ad-Hoc Commands sowie Konfigurationsmanagment. Der entscheidende Vorteil dabei ist, dass die Verbindung zwischen Orchestrierer und Zielhosts, über SSH erfolgt und deshalb keine zusätzliche Software oder Agents auf den Zielsystemen installiert werden müssen. Die einzelnen Skripts sowie Konfigurationen für Ansible, werden im YAML-Format geschrieben, mit der Absicht alles möglichst simpel zu halten und eine gute Lesbarkeit zu gewährleisten. Ansible gibt es in drei Versionen, Ansible selbst ist Open-Source und kostenlos. Dazu gibt es eine GUI-Lösung namens Ansible AWX, diese Version lässt sich auf einen Server installieren und enthält ein User-System. Dabei werden alle Ereignisse protokolliert. Zusätzlich gibt es auch den Ansible Tower. Dabei handelt es sich um eine Enterprise-Lösung von Redhat, sie ist Ansible AWX sehr ähnlich, jedoch gibt es dabei direkten Redhat Support.

Voraussetzungen

  • Ansible (Open-Source)
  • Ein Elasticsearch-Cluster
  • Ubuntu-Zielhosts
  • Eine funktionierende Filebeat-Konfigurationsdatei

Die Inventory Datei – Informationen der Hosts

Damit Ansible weiß, welche Hosts wir erreichen möchten und mit welchen User wir uns authentifizieren, muss eine sogenannte „Inventory“-Datei angelegt werden.

cd /etc/ansible
mkdir filebeat
cd filebeat
vim hosts

Unsere erstelle hosts-Datei wird nun mit folgendem gefüllt:

[ubuntu]
#Host-Addresse | User zur Authentifizierung | (Optional) Passwort für login
192.168.0.22 ansible_user=ansible ansible_ssh_pass="mysupersecretpassword" 

Konkret haben wir jetzt eine Gruppe angelegt namens „ubuntu“. In dieser Gruppe befindet sich der Host 192.168.0.22, als User zur Authentifizierung wird „ansible“ verwendet.
Unter ansible_ssh_pass haben wir das Passwort zur Authentifizierung festgelegt.
Hierbei sollte man bedenken, dass dieses Passwort im Klartext abgespeichert wird. Da dies sehr problematisch ist, hält Ansible eine Lösung bereit: den Ansible Vault. Was der Ansible Vault genau ist, wird im weiteren Verlauf näher beschrieben. Alternativ kann man aber auch SSH-Keys zur Authentifizierung verwenden.

Der Ansible Vault – Mit Secrets umgehen

Wie bereits bei der Erstellung der Inventory-Datei aufgefallen ist, werden Passwörter im Klartext abgespeichert. Hier kommt der Ansible Vault zum Einsatz. Im Ansible Vault können zusätzliche Variablen erstellt werden und in das Playbook sowie in die Inventory-Datei integriert werden. Diese werden dann anschließend mit einem Passwort verschlüsselt, das wir beim Erstellen der Ansible Vault-Datei festlegt haben.
So legt ihr eine Vault-Datei an:

ansible-vault create vault.yml
New Vault password: (Hier Passwort eingeben)
Confirm New Vault password: (Passwort nochmal eingeben)

Anschließend wird der Texteditor vim geöffnet und wir können nun unsere Variablen erstellen.
Als Beispiel erstellen wir eine Variable für ein Passwort, welches wir in der Inventory-Datei anwenden können:

secret_password: "mysupersecretpassword"

Diese Variable integrieren wir anschließend mit geschwungenen Klammern in unsere Inventory-Datei:

[ubuntu]
#Host-Addresse | User zur Authentifizierung | (Optional) Passwort mit Ansible-Vault Variable
192.168.0.22 ansible_user=root ansible_ssh_pass="{{ secret_password }}"

Vielleicht ist es dir schon aufgefallen, aber woher soll die Inventory-Datei beim Einsatz denn wissen, ob und welcher Vault verwendet werden muss? Um die Frage kurz und knapp zu beantworten: dies müsst ihr bei der Erstellung des Playbooks festlegen – dies wird aber im weiteren Verlauf noch einmal erklärt. Die Passworteingabe um die Vault-Datei zu entschlüsseln muss mit dem Flag „–ask-vault-pass“ beim Ausführen des Skripts anschließend „erzwungen“ werden.

Erstellung des Playbooks

Das Playbook ist einer der Hauptkomponenten von Ansible. Dort beschreiben wir, was die Zielhosts machen sollen und welchen Vault wir zum Beispiel verwenden.

Wir erstellen zunächst eine Datei mit einem beliebigen Namen und der Endung .yml

vim filebeat-deploy.yml

Jetzt beschreiben wir, welche Host-Gruppen dieses Playbook ausführen sollen. Außerdem sollen zusätzlich noch allgemeine Informationen über den Host gesammelt werden, dies wird später noch mal nützlich. Zusätzlich dazu soll auch eine Privelege-Escalation stattfinden, damit es nirgends an fehlenden Rechten scheitert. Zuletzt beschreiben wir, wo sich unsere Vault-Datei befindet.

--- 
- hosts: ubuntu
  gather_facts: yes
  become: yes
  vars_files:
    - ./vault.yml

Damit ist das wichtigste schon einmal abgeschlossen. Darauf folgen die Anweisungen, sogenannte Tasks, welche die Hosts dann ausführen werden. Damit man auch weiß, welcher Task was bezweckt, wird vor jedem Task ein Name gesetzt. Dies ist eine Beschreibung, was ein spezifischer Task macht, damit auch jeder versteht, was gerade passiert.

  tasks:
    - name: Lädt den Public Signing Key von Elastic herunter und installiert ihn.
      raw: "wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -"

    - name: Lädt das neuste apt-transport-https Paket über den apt-Package Manager herunter.
      apt: name=apt-transport-https state=latest

    - name: Speichert die Repository definition von Elastic.
      raw: echo "deb https://artifacts.elastic.co/packages/7.x/apt stable main" | sudo tee -a /etc/apt/sources.list.d/elastic-7.x.list

    - name: Macht einen apt-get update.
      apt: update_cache=yes

    - name: Lädt Filebeat herunter und installiert ihn über den apt-Package Manager.
      apt:
        name: filebeat
        state: latest

    - name: Aktiviere Filebeat beim starten des Systems.
      systemd:
        name: filebeat
        enabled: yes

    - name: Startet den Filebeat Service.
      systemd:
        name: filebeat
        state: started

Damit haben wir beschrieben, wie Filebeat selbst installiert werden soll. Nun müssen wir uns um die Konfiguration von Filebeat kümmern. Hierfür habe ich schon eine fertige Konfigurationsdatei angefertigt. Hier ein paar Parameter, die wichtig für die nachfolgende Konfiguration sein werden. Wie ihr Filebeat grundsätzlich konfiguriert, wird nicht weiter erklärt. Dafür verweise ich auf die Filebeat Dokumentation.

  api_key: ${ELK_API_KEY}
  ssl.certificate_authorities: ["/etc/filebeat/ca.pem"]

Wie ihr seht, werden wir im weiteren Verlauf mit API-Keys, Umgebungsvariablen und Zertifikaten arbeiten. Der Zweck dahinter ist, dass Filebeat sich beim Elasticsearch-Cluster damit verschlüsselt authentifizieren und verschlüsselt einen API-Key beantragen kann. Die fertige Konfigurationsdatei legen wir im Arbeitsordner ab.
Im Playbook schreiben wir folgendes, um die Datei an die Hosts weiterzugeben:

    - name: Kopiere die filebeat.yml auf den Ziel-Host unter /etc/filebeat/filebeat.yml
      copy:
        src: ./filebeat.yml
        dest: /etc/filebeat/filebeat.yml

Da wir in der Filebeat.yml mit Zertifikaten arbeiten, müssen wir das Zertifikat auch auf die Hosts verteilen. Das Zertifikat legen wir zusätzlich noch im Truststore des Betriebssystems ab. Wir schreiben Folgendes in unser Playbook:

    - name: Kopiere den Inhalt von der Variable ca_pem in den Filebeat-Ordner und nenne die Datei ca.pem
      copy:
        content: "{{ ca_pem }}"
        dest: '/etc/filebeat/ca.pem'

    - name: Kopiere den Inhalt von der Variable ca_pem in den Truststore vom Zielhost und nenne die Datei ca.pem
      copy:
        content: "{{ ca_pem }}"
        dest: '/usr/local/share/ca-certificates/ca.pem'
        
    - name: Aktualisiere den Trust-Store
      raw: update-ca-certificates

In diesem Beispiel haben wir eine Variable namens ca_pem verwendet. Diese Variable befindet sich im Ansible-Vault. Da Elastic Filebeat nur Zertifikate im PEM-Format unterstützt, müssen wir das Zertifikat in das richtige umformatieren, denn der Ansible-Vault unterstützt keine Zeilenumbrüche, die typischerweise im PEM-Format auftreten. Im Ansible-Vault müsst ihr beim Zertifikat sämtliche Zeilenumbrüche mit einem n ersetzen. Das Resulat sollte ein langer String sein wie im folgenden Beispiel:

ca_pem: "-----BEGIN CERTIFICATE-----nLoremipsumdolorsitametconsetetursadipscingelitrseddiamnLoremipsumdolorsitametconsetetursadipscingelitrseddiamLoremipsumdolorsitametconsetetursadipscingelitrseddiamLoremipsumdolorsitametconsetetursadipscingelitrseddiamnLoremipsumdolorsitametconsetetursadipscingelitrseddiamnLoremipsumdolorsitametconsetetursadipscingelitrseddiamn-----END CERTIFICATE-----"

Kommen wir nun zur API-Key Generierung. Hierbei halte ich mich größtenteils an die Dokumentation von Elastic. Heißt, wir machen über die Hosts einen Post-Request zu unserem Elasticsearch-Cluster und fragen einen API-Key an.

    - name: Mache einen Post-Request beim Elasticsearch-Cluster und frage einen API-Key an. Speichere die Antwort unter /etc/filebeat/output.json
      uri:
        url: https://192.168.0.3:9200/_security/api_key?pretty
        user: api_user
        password: "{{ api_user_secret }}"
        method: POST
        body: "{"name":"{{ ansible_hostname }}-{{ ansible_default_ipv4.address }}","role_descriptors": { "filebeat_writer": { "cluster": ["monitor", "read_ilm","manage_ilm","manage","all", "read_pipeline"], "index": [ { "names": ["filebeat-*"],"privileges": ["view_index_metadata", "create_doc","manage_ilm","manage","all"]}]}}}"
        body_format: json
        headers:
          Content-Type: application/json
        validate_certs: no
        force_basic_auth: yes
        dest: /etc/filebeat/output.json

Bei den Hosts sollten nun eine Datei unter /etc/filebeat/output.json mit folgendem Inhalt erstellt worden sein:

{
  "id" : "LOREM",
  "name" : "ansible-ubuntu-1-192.168.0.22",
  "api_key" : "ipsum"
}

Da dieses JSON-Format so weit noch unbrauchbar für uns ist, müssen wir noch einen JSON-Parser erstellen. Dies habe ich mit RegEx über ein Bash-Shell-Skript gelöst. Mein Skript sieht so aus:

#!/bin/bash
grep -Po '"id" : "*K"[^"]*"' /etc/filebeat/output.json | sed 's/"//' | sed 's/"//' > /etc/filebeat/tmp
echo : >> /etc/filebeat/tmp
grep -Po '"api_key" : "*K"[^"]*"' /etc/filebeat/output.json | sed 's/"//' | sed 's/"//' >> /etc/filebeat/tmp
tr -d 'n' < /etc/filebeat/tmp > /etc/filebeat/

Das Skript habe ich im Arbeitsverzeichnis abgelegt. Nach der Verteilung des Skripts müssen noch die Rechte angepasst werden und anschließend muss das Skript auf den Hosts ausgeführt werden.

    - name: Kopiere json_parser.sh zum Zielhost
      copy:
        src: ./json_parser.sh
        dest: /etc/filebeat/json_parser.sh

    - name: Modifiziere die Rechte von json_parser.sh
      raw: chmod 777 /etc/filebeat/json_parser.sh

    - name: Führe den json_parser.sh aus
      raw: /etc/filebeat/json_parser.sh

Als Resultat erhalten wir eine Datei mit dem API-Key als Inhalt. Dieser wurde in das Format geparsed, das von Elastic gewünscht ist. Die Datei befindet sich beim Host unter dem Pfad /etc/filebeat/api_key. Den Inhalt müssen wir nun nur noch als Umgebungsvariable anlegen. Anschließend muss Filebeat neu gestartet werden.

    - name: Lese die api_key Datei aus und Registriere den Inhalt in die Variable api_key
      raw: tail /etc/filebeat/api_key
      register: api_key

    - name: Registriere ob der /etc/systemd/system/filebeat.service.d Ordner existiert
      stat:
        path: /etc/systemd/system/filebeat.service.d
      register: folderinfo

    - name: Erstelle den /etc/systemd/system/filebeat.service.d folder Ordner wenn dieser NICHT Existiert.
      raw: mkdir /etc/systemd/system/filebeat.service.d
      when: not folderinfo.stat.exists

    - name: Erstelle override.conf für den Filebeat Service und schreibe die Umgebungsvariable hinein.
      copy:
        dest: /etc/systemd/system/filebeat.service.d/override.conf
        content: |
          [Service]
          Environment=ELK_API_KEY={{ api_key.stdout }}

    - name: Lade den Systemctl Daemon neu.
      systemd:
        daemon_reload: yes

    - name: Starte den Filebeat Service neu.
      systemd:
        name: filebeat
        state: restarted

Das Playbook ausführen

Damit wir das Playbook ausführen können, sollten wir in der ansible.cfg unter /etc/ansible das host_key_checking auf False setzen. Sollten wir uns nämlich zum ersten Mal auf einer der Zielhosts verbinden, kann es passieren, dass der Fingerprint vom Zielhost noch eingetragen werden muss. Den Fingerprint hinzuzufügen, macht Ansible von sich aus NICHT. Das heißt, dass Ansible von sich aus das Playbook für den jeweiligen Host beendet, bis ihr den Fingerprint hinzufügt oder die Überprüfung ganz abstellt.

Nachdem wir das erledigt haben, gehen wir zurück ins Arbeitsverzeichnis, wo unser Playbook liegt.
Um das Skript auszuführen, geben wir Folgendes ein:

ansible-playbook filebeat-deploy.yml -i hosts --ask-vault-pass

Mit „ansible-playbook“ sagen wir Ansible, dass wir ein Playbook ausführen wollen.
Dann geben wir den Dateinamen von dem Playbook an. Die Flag „-i“ steht für eine externe Inventory-Datei, die wir dann angeben müssen. In dem Fall „hosts“.
„–ask-vault-pass“ ist nötig, da wir einen Vault verwenden und wir damit die Passworteingabe erzwingen.

Damit sind wir mit dem Playbook fertig. Das vollständige Playbook findest du hier noch mal zum Download.

Fazit

In diesem Blogeintrag hast du gelernt, wie man Filebeat mithilfe von Ansible automatisiert auf Ubuntu-Hosts verteilt und konfiguriert. Damit hast du auch gelernt, wie man mit Ansible umgeht und hast einen Einblick auf alle fundamentalen Dinge von Ansible erhalten.