Inhaltsverzeichnis

Ansible - weitere Beispiele: Admin Benutzer verwalten (v2)

Bild: Ansible Logo

Im Eingangskapitel Grundlagen haben wir uns mit der Installation bereits befasst. Mit den Hintergrundinformationen haben wir uns auch schon in den beiden Kapiteln Playbooks und YAML - was ist das? eingehend beschäftigt, sowie erste Erfahrungen mit Playbooks gesammelt.

Mit einer erste grundlegende Version unseres Ansible-Playbooks zum Anlegen, Modifizieren und Löschen unserer Admin-Konten haben wir im letzten Kapitel bereits betrachtet. Dieses Playbook werden wir nun noch etwas verändern und ein paar Prüfroutinen einbauen.

Aufgabenstellung

Oft steht man vor der Herausforderung, Admin-Konten auf unzähligen Zielsystemen anzulegen und wieder zu löschen. So sind im Zuge eines onboarding von neuen Admins jeweils Konten anzulegen und mit Passworten und Schlüsselmaterial zu versorgen. Beim Ausscheiden eines Admins, müssen dessen|ihre Konten samt Passworten und Schlüssel wieder gelöscht werden.

Es soll ja zuweilen Unternehmungen geben, bei denen sich das Personal mehr oder weniger in sehr kurzen Zeitabständen die Klinge in die Hand geben, was das Prozedere dann zusätzlich vom Aufwand her nach oben treibt. :-x

Möchte ein bestehender Admin sein|ihr Passwort oder Schlüssel ändern, kann dies auch zu einer zeitraubenden Tätigkeit ausarten. Was liegt also näher das Anlegen und Löschen der Konten samt Passwörter und Schlüsselmaterial sowie die Pflege dieser Daten mit Hilfe von Ansible zu automatisieren?

Die Beschreibung kennen wir schon aus dem ersten Beispiel Admin Benutzer verwalten - so weit, so gut. Zusätzlich zu dieser Ausgangssituation wollen wir nun noch sicherstellen, dass die Adminspezifischen Definitionen aus dem Inventory valide sind. Folgende Rahmenbedingungen bei den Eigenschaften eines Admin-Kontos sollen gelten:  

Zur Prüfung dieser Rahmenbedingungen greifen wir auf das Ansible Modul ansible.builtin.assert zurück und legen uns hierzu eine einen eigenen Task in der Rolle an.

Lösung

Der ungeduldigen Leser kann auch direkt zur Tat schreiten und das manuelle Anlegen der Inventory-Hülle, des Playbooks und der zugehörigen Rollen überspringen und diese Aufgaben mit folgendem Befehl sozusagen auf einem Rutsch erledigen:

 $ mkdir ~/ansible ; wget https://gitlab.nausch.org/django/example_14/-/archive/master/example_14-main.tar.gz -O - | tar -xz --strip-components=1 -C ~/ansible

Wichtig: Aber nicht vergessen die Admin-Inventory-Daten in ein Ansible-Vault verpacken!

 $ ansible-vault encrypt ~/ansible/inventories/production/group_vars/all/admins

Anschliessend kann man direkt zur Ausführung schreiten.

Vorbereitung - Admindaten im Inventory

Dreh- und Angelpunkt unserer Admin-Verwaltung ist natürlich unser Inventory. Dank unserer Vorbereitungen ins Sachen Ansible Vault können wir natürlich gehashte Passwörter und SSH-keys im Inventory vorhalten, da diese dort ja sicher verwahrt vorliegen.

Inventory-Hülle

Wir werden uns also erst einmal unsere Admins im Inventory eine zugehörige Datei anlegen.

 $ ansible-vault create ~/ansible/inventories/production/group_vars/all/admins

Dort legen wir als erstes mal eine Hülle für die weitere Bearbeitung an.

admins
linux_admins:
  - user   : Vorname Nachname
    name   : User-Name
    groups : wheel
    ids    : 1000
    shell  : /bin/bash
    state  : present                              # present bzw. absent 
    pwd    : <-- Ergebnis: openssl passwd -6  -->
    key    : ssh-key
 

gehashtes Passwort

Damit wir keine PLAIN-Text Passworte sondern nun gehashte Passwörter im Inventory stehen haben wollen, denn aus Sicherheitsgründen dürfen nur die Admins selbst deren Passwort kennen und sonst niemand, bitten wir diese uns das gehashte Passwort mit openssl zu generieren.

 $  openssl passwd -6
Password: 
Verifying - Password: 
$6$n9UE0JVV7T.nzFJOdSY1dHDEsbfY3$0SPNKmewfaQ0z5thaRMrrrI9Uig.nzFJOdSY1erIZbw5yzDqeCg4S2oXa8zn2jEf9KDfjg31

Ferner benötigen wir noch den SSH-Publickey, den wir uns ebenso wie das gerade erstellte gehashte Passwort von unseren Admins auf einem sicheren Kommunikationsweg zukommen lassen.

Inventory Daten für unsere Admins

Diese Daten übernehmen wir dann in unser Inventory:

 $ ansible-vault edit inventories/production/group_vars/all/admins
admins
linux_admins:
  - user   : Michael Nausch
    name   : django
    groups : wheel
    ids    : 1000
    shell  : /bin/bash
    state  : present
    pwd    : $6$QYCbUeY2/EecXmL4$iA7Q.M457er0F0354573RoPyKcbbPMozx8uFYKMpZLJnz2JIoAlcMxi0o0b1zJywJLECw1fSB2OCdfzc9vOX1
    key    : ssh-ed25519 AAAAC3N1NTE5A0aTHP001zaC1lZDI1NTE5AAAAINPs/cN40aTu2HTGeHhV7IV1EngqT5 openpgp:0xDAED833F
  - user   : Christoph Leichi
    name   : rookie
    groups : wheel
    ids    : 1001
    shell  : /bin/bash
    state  : present
    pwd    : $6$n9UE0JVV7T.nzFJOdSY1dHDEsbfY3$0SPNKmewfaQ0z5thaRMrrrI9Uig.nzFJOdSY1erIZbw5yzDqeCg4S2oXa8zn2jEf9KDfjg31
    key    : ssh-ed25519 AAAAC3NzaqK6Pb38bv0oM9fw0C1lZDI1NTE5AAAAIDo46Pb38bv0oM9fmgM6byylc0815 rookie@nausch.org
  - user   : Wänä Marschel
    name   : waenae
    groups : wheel
    ids    : 1002
    shell  : /bin/bash
    state  : present
    pwd    : $6$nJVSYV9J17.SY1v0oM9fow8Do46d04m354u3$0SPNKmewfaQ0z5tsafZi3haRMrrrI9Uig.OdSY1e6dHDEsbfY3$rI51ewfaQ0z5th
    key    : ssh-ed25519 AAAAK6Pb38bv0oM9fw8DoOdSY1er4b38bNzaqK6Pb38bv0oM9fw01erIZbw5yzDqeCC5 waennae@nausch.org

In diesem Beispiel haben wir also drei Admins mit den zugehörigen Daten.

Playbook mit zugehörigen Rollen

Hatten wir das Gitlab-Paket wie unter dem Abschnitt Lösung beschrieben können wir die nächsten Schritte zum Anlegen des Playbooks und der Rolle mit den zugehörigen Tasks, getrost überspringen und gleich zur Ausführung schreiten.

Playbook

Das Playbook an sich ist relativ unspektakulär, wird doch nur die zugehörige Rolle eingebunden, wie wir hier sehen.

 $ vim ~/ansible/playbooks/admin_updates.yml
playbooks/admin_updates.yml
---
# Ansible Playbook zum Anlegen, Aktualisieren und Löschen der Administratoren,
# inkl. der zugehörigen Passwörter und SSH-Schlüssel, basierend auf den Angaben
# im Inventory.
# Admins mit dem state "present" werden angelegt bzw. deren Passworte
# und Schlüssel aktualisiert. Admins mit dem state "absent" werden auf den Hosts
# gelöscht und können anschliessend aus dem Inventory entfernt werden.
#
# Aufruf zum Anlegen, Ändern oder Löschen auf allen Hosts, die im Inventory
# definiert sind:
#
#         $ ansible-playbook playbooks/admin_updates.yml
#
# bzw. für einzelne Hosts:
#         $ ansible-playbook playbooks/admin_updates.yml --limit <-hostnames->

- name: "Playbook-Name: admin_updates.yml"  # Name des Playbooks
  hosts: DMZ,intranet,localhost             # Hostgruppe für den das Playbook gelten soll

  roles:
    - role: admins                          # Admins anlegen, ändern oder löschen
      tags: admins                          # Tag-Kennzeichnung der definierten Rolle
...

Rolle und Tasks

Bevor wir unsere Rolle admins anlegen, kopieren wir noch kurz das Vorlagenverzeichnis common, welches wir bei der Erstkonfiguration von Ansible, wie im Kapitel Ansible mit Hilfe von Ansible einrichten beschrieben, bereits angelegt hatten.

 $ cp -avr ~/ansible/roles/common/ ~/ansible/roles/admins

Nun legen wir unseren Main-Task an.

 $ vim ~/ansible/roles/admins/tasks/main.yml
roles/admins/tasks/main.yml
---
- name: "Admin-Gruppe wheel anlegen."
  ansible.builtin.include_tasks:
    file: admingroup.yml
    apply:
      tags: admingroup

- name: "Variablen überprüfen."
  ansible.builtin.include_tasks:
    file: variablencheck.yml
    apply:
      tags: varcheck

- name: "Admin-Gruppe und -User pflegen."
  ansible.builtin.include_tasks:
    file: useranlage.yml
    apply:
      tags: linux_admins

- name: "Admins der Gruppe wheel sudoers zuweisen."
  ansible.builtin.include_tasks:
    file: sudoers.yml
    apply:
      tags: sudoers
...

Was nun noch fehlt sind die jeweiligen Tasks mit den Teilaufgaben. Zunächst definieren wir einen Task mit Hilfe dessen sichergestellt wird, dass die Gruppe wheel existiert, die später den Admins zugewiesen wird.

 $ vim ~/ansible/roles/admins/tasks/admingroup.yml
roles/admins/tasks/admingroup.yml
---
- name: "Sicherstellen dass die Gruppe wheel existiert."
  ansible.builtin.group:
    name: wheel
    state: present
...

  Bevor wir nun wie im ersten Beispiel mit dem Task useranlage die jeweilige(n) Admin-Gruppe(n) und User pflegen, werden wir erst einmal die Variablen aus dem Inventory einer Prüfung unterziehen und stellen somit sicher, ob die zuvor definierten Rahmenbedingungen auch zutreffen. Hierzu Nutzen wir das ansible.builtin.assert Modul.

 $ vim ~/ansible/roles/admins/tasks/variablencheck.yml
roles/admins/tasks/variablencheck.yml
---
- name: "Prüfen ob die Variable user gesetzt wurde."
  ansible.builtin.assert:
    that:
      - item.user is defined and item.user != ''
      - (item.user is defined) and (item.user != none)
    fail_msg: "Die Variable item.user ist nicht vorhanden bzw. leer!"
    quiet: true
  with_items: "{{ linux_admins }}"

- name: "Prüfen ob die Variable name gesetzt wurde."
  ansible.builtin.assert:
    that:
      - item.name is defined and item.name != ''
      - (item.name is defined) and (item.name != none)
    fail_msg: "Die Variable item.name ist nicht vorhanden bzw. leer!"
    quiet: true
  with_items: '{{ linux_admins }}'

- name: "Prüfen ob die Variable group gleich wheels gesetzt wurde."
  ansible.builtin.assert:
    that:
      - item.groups is defined and item.groups == 'wheel'
    fail_msg: "Die Variable item.groups ist nicht vorhanden bzw. nicht auf 'wheel' gesetzt!"
    quiet: true
  with_items: '{{ linux_admins }}'

- name: "Prüfen ob die UID richtig gesetzt wurde."
  ansible.builtin.assert:
    that:
      - item.ids is defined and item.ids > 999
      - item.ids is defined and item.ids < 5001
    fail_msg: "Die Variable item.ids ist nicht vorhanden bzw. falsch gesetzt!"
    quiet: true
  with_items: '{{ linux_admins }}'

- name: "Prüfen ob die Variable shell gesetzt wurde."
  ansible.builtin.assert:
    that:
      - item.shell is defined and item.shell != ''
      - (item.shell is defined) and (item.shell != none)
    fail_msg: "Die Variable item.shell ist nicht vorhanden bzw. leer!"
    quiet: true
  with_items: '{{ linux_admins }}'

- name: "Prüfen ob die Variable state richtig gesetzt wurde."
  ansible.builtin.assert:
    that:
      - item.state is defined and (item.state == 'present' or item.state == 'absent')
    fail_msg: "Die Variable item.state ist nicht vorhanden bzw. leer!"
    quiet: true
  with_items: '{{ linux_admins }}'
...

Anschliessend definieren wir den Task an, mit Hilfe dessen die jeweilige(n) Admin-Gruppe(n) und User gepflegt werden.

 $ vim ~/ansible/roles/admins/tasks/useranlage.yml
roles/admins/tasks/useranlage.yml
---
- name: "Sicherstellen dass die Gruppen für Admin-User existieren."
  ansible.builtin.group:
    gid: '{{ item.ids }}'
    name: '{{ item.name }}'
    state: present
  with_items: '{{ linux_admins }}'

- name: "Sicherstellen dass die Admin-User existieren."
  ansible.builtin.user:
    append: true
    comment: '{{ item.user }}'
    create_home: true
    force: true
    state: '{{ item.state }}'
    group: '{{ item.name }}'
    groups: '{{ item.groups }}'
    name: '{{ item.name }}'
    password: '{{ item.pwd }}'
    shell: '{{ item.shell }}'
    uid: '{{ item.ids }}'
    remove: true
  with_items: '{{ linux_admins }}'

- name: "Gruppe entfernen, sofern der User zum Löschen gekennzeichnet ist mit absent im Inventory/Vault."
  ansible.builtin.group:
    gid: '{{ item.ids }}'
    name: '{{ item.name }}'
    state: '{{ item.state }}'
  with_items: '{{ linux_admins }}'

- name: "SSH-Client-Verzeichnis anlegen."
  ansible.builtin.file:
    path: /home/{{ item.name }}/.ssh
    state: directory
    owner: '{{ item.name }}'
    group: '{{ item.name }}'
    mode: '0700'
  when: ' item.state == "present"'
  with_items: '{{ linux_admins }}'

- name: "SSH-Key des Admins hinterlegen."
  ansible.builtin.copy:
    dest: /home/{{ item.name }}/.ssh/authorized_keys
    content: |
      {{ item.key }}
    owner: '{{ item.name }}'
    group: '{{ item.name }}'
    mode: '0600'
  when: ' item.state == "present"'
  with_items: '{{ linux_admins }}'

- name: "SSH-Client-Verzeichnis entfernen, sofern der User zum Löschen gekennzeichnet ist mit absent im Inventory/Vault."
  ansible.builtin.file:
    path: /home/{{ item.name }}/.ssh
    state: absent
  when: ' item.state == "absent" '
  with_items: '{{ linux_admins }}'
...

Zu guter Letzt legen wir noch den Task an, damit die Admins, die Mitglied der Gruppe wheels sind, auch sudo-Rechte erlangen können.

 $ vim ~/ansible/roles/admins/tasks/sudoers.yml
roles/admins/tasks/sudoers.yml
---
- name: "Der Gruppe wheel sudo Rechte zuweisen."
  ansible.builtin.copy:
    content: "# Generated by Ansible, do not edit manually!\n# Allows people in group wheel to run all command\n%wheel    ALL=(ALL)       ALL\n"
    dest: /etc/sudoers.d/10_passwd_sudo_wheel
    owner: root
    group: root
    mode: '0440'
    validate: visudo -cf %s
...

Ausführung

Mit Hilfe dieses Playbooks können alle erforderlichen Konfigurationsschritte reproduzierbar und beliebig oft abgesetzt werden. Somit können neue Admins hinzugefügt, SSH-Keys und Passwörter ausgerollt bzw. aktualisiert werden und natürlich bestehende Admin-Konten auch bei Bedarf wieder gelöscht werden.

Folgende Schritte werden also mit Hilfe des Playbooks abgearbeitet:

  1. Sicherstellen dass die Gruppe wheel existiert.
  2. Überprüfen, ob die Variablen aus dem Inventory entsprechend der Vorgaben gesetzt und gültig sind. Falls nicht wird ein expliziter Fehlerhinweis ausgegeben und die Ausführung des Playbooks beendet.
    Beispiel:
    TASK [admins : Prüfen ob die UID richtig gesetzt wurde.] *******************************************************************************************************************************************
    fatal: [vml000137]: FAILED! => {"assertion": "item.ids is defined and item.ids < 5001", "changed": false, "evaluated_to": false, "msg": "Die Variable item.ids ist nicht vorhanden bzw. falsch gesetzt!"} 
    
    PLAY RECAP *****************************************************************************************************************************************************************************************
    vml000137                  : ok=13   changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

    Oder ein weiteres Beispiel:

    TASK [admins : Prüfen ob die Variable state richtig gesetzt wurde.] ********************************************************************************************************************************
    fatal: [vml000137]: FAILED! => {"assertion": "item.state is defined and (item.state == 'present' or item.state == 'absent')", "changed": false, "evaluated_to": false, "msg": "Die Variable item.state ist nicht vorhanden bzw. leer!"}
    
    PLAY RECAP *****************************************************************************************************************************************************************************************
    vml000137                  : ok=15   changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0
  3. Sicherstellen dass die Gruppen für Admin-User existieren.
  4. Sicherstellen dass die Admin-User existieren.
  5. Gruppe entfernen, sofern der User zum Löschen gekennzeichnet ist mit absent im Inventory/Vault.
  6. SSH-Client-Verzeichnis anlegen
  7. SSH-Key des Admins hinterlegen
  8. SSH-Client-Verzeichnis entfernen, sofern der User zum Löschen gekennzeichnet ist mit absent im Inventory/Vault.
  9. Der Gruppe wheel sudo Rechte zuweisen.

Entscheidend für das Anlegen bzw. Löschen eines Admins ist die Array-Variable state; ist ihr der Wert present zugewiesen wir der Admin neu angelegt und dessen Schlüssel angelegt. Ist der Wert der Variable aber absent wir dessen Gruppe und User auf den Zielsystemen entfernt. Nach einem erfolgreichen Playbooklauf können wir dann anschliessend den Admin wieder aus dem Inventory löschen oder eben solange dort stehen lassen, bis dieser wieder z.B. nach einem Sabbatical seinen Dienst antritt.

Haben wir die Daten in unserem Vault entsprechend aktualisiert können wir, wie im Playbook vermerkt, das Playbook wie gewohnt aufrufen. Aufruf via für alles Hosts:

 $ ansible-playbook playbooks/admin_updates.yml

bzw. für einzelne Hosts:

 $ ansible-playbook playbooks/admin_updates.yml --limit <-hostnames->

Interessiert uns lediglich die erfolgreiche Abarbeitung unseres Playbook-Aufrufs können wir dies z.B. beim Host mit dem Namen vml000137 auch wie folgt erreichen:

 $ ansible-playbook playbooks/admin_updates.yml --limit vml000137 | sed -n '/PLAY RECAP/,$p'
PLAY RECAP ********************************************************************************************************
vml000137                  : ok=29   changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0

Links