Inhaltsverzeichnis

Ansible - erweiterte Konfigurationsbeispiele: Ansible mit Hilfe von Ansible einrichten

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.

Aufgabenstellung 1 - "vereinfachte" Grund-/Basis-Installation

Für die Grundkonfiguration in der Basisausführung der Ansible-Umgebung, wie wir Sie im Kapitel Erste Schritte Rund um Ansible - (Grund-)Konfiguration Eingangs kennengelernt hatten, benötigen wir eine vordefinierte Grundinstallation und Konfiguration. Damit diese Erstkonfiguration unserer Ansible-Umgebung nicht von Hand erfolgen muss, greifen wir auf das Playbook ansible_grundconfig_v1.yml zurück.

Mit Hilfe dieses Playbooks können alle erforderlichen Konfigurationsschritte reproduzierbar und beliebig oft abgesetzt werden. Bei Bedarf ist als also jederzeit ohne grossen Aufwand möglich den Ansible-Controll-Node neu aufzusetzen und das System für spätere Playbook-Rollouts vorzubereiten, den Host also auch initial frisch zu versorgen.

Folgende Schritte sollen von dem playbook abgearbeitet werden:

  1. Kopieren der Ansible-Konfigurationsdatei /etc/ansible/ansible.cfg in das $HOME-Verzeichnis des Admin-Users.
  2. Anpassen, sprich konfigurieren der individuellen Ansible Umgebung.
  3. Ansible Directory Layout anlegen und mit Dummy-Inhalten versorgen.

Schliesslich wollen wir unsere Zeit als Admin ja auch sinnvoll nutzen und mit möglichst geringen Aufwand zu Ziel kommen.

Lösung

Der ungeduldigen Leser kann auch direkt zur Tat schreiten und das manuelle Anlegen des Verzeichnisses und des Ansible-Scripts überspringen. Mit Folgendem Befehl erledigt man dies sozusagen auf einem Rutsch:

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

Anschliessend kann man direkt zur Ausführung schreiten.

Script anlegen

Zunächst müssen wir manuell einmal das Verzeichnis ~/ansible/playbooks in dem wir unsere Ansible-Playbooks ablegen werden auf unserer Admin-Workstation bzw. dem Ansible-Controll-Node anlegen.

 $ mkdir -p ~/ansible/playbooks

Hier legen wir nun unser Ansible-Playbook/-Script ansible_grundconfig_v1.yml ab, mit dessen Hilfe wir unsere Ansible-Umgebung individuell einrichten und konfigurieren wollen.

 $ vim ~/ansible/playbooks/ansible_grundconfig_v1.yml
~/ansible/playbooks/ansible_grundconfig_v1.yml
--- # YAML Start
    # Ansible Playbook zum initialen Einrichten der Ansible-Umgebung
    #
    # Aufruf aus dem entsprechenden Arbeits-Verzeichnis via:
    # ansible-playbook playbooks/ansible_grundconfig_v1.yml -K
 
- name: ansible_grundconfig_v1.yml
  gather_facts: true
  hosts: localhost
  become: true
 
  vars:
    ansible_working_dir: ansible
    ansible_config: /etc/ansible/ansible.cfg
    admin_user: "{{ lookup('env','USER') }}"
 
  tasks:
    - name: "Ansible Konfigurationsdatei {{ ansible_config }} vorhanden?"
      ansible.builtin.stat:
        path: '{{ ansible_config }}'
      register: check_ansible_config
 
    - name: "Fehlerhinweis im Fehlerfall ausgeben"
      ansible.builtin.fail:
        msg: "Ansible Konfigurationsdatei {{ ansible_config }} NICHT gefunden!"
      when: check_ansible_config.stat.exists != 1
 
    - name: "Ansible Konfigurationsverzeichnis in das User/Admin-Verzeichnis kopieren"
      ansible.builtin.copy:
        dest: '/home/{{ admin_user }}/.ansible.cfg'
        group: '{{ admin_user }}'
        owner: '{{ admin_user }}'
        src: '{{ ansible_config }}'
        mode: '0640'
 
    - name: "Ansible Konfiguration anpassen"
      ansible.builtin.lineinfile:
        line: "{{ item.line }}"
        path: "/home/{{ admin_user }}/.ansible.cfg"
        regexp: "{{ item.regexp }}"
        state: present
      with_items:
        - {
          regexp: "^\\[defaults\\]",
          line: "[defaults]\n# Generated by Ansible on {{ ansible_date_time.date }}, do not edit manually!\n\
          # default:\ninterpreter_python = auto_silent"
        }
        - {
          regexp: "^\\#inventory\ \ \ \ \ \ =\ /etc/ansible/hosts",
          line: "# Generated by Ansible on {{ ansible_date_time.date }}, do not edit manually!\n\
          # default: #inventory      = /etc/ansible/hosts\n\
          inventory       = /home/{{ admin_user }}/ansible/inventories/production"
        }
        - {
          regexp: "^\\#become=True",
          line: "# Generated by Ansible on {{ ansible_date_time.date }}, do not edit manually!\n\
          # default: #become=True\n\
          become=True"
        }
        - {
          regexp: "^\\#become_method=sudo",
          line: "# Generated by Ansible on {{ ansible_date_time.date }}, do not edit manually!\n\
          # default: #become_method=sudo\n\
          become_method=sudo"
        }
        - {
          regexp: "^\\#become_user=root",
          line: "# Generated by Ansible on {{ ansible_date_time.date }}, do not edit manually!\n\
          # default: #become_user=root\n\
          become_user=root"
        }
        - {
          regexp: "^\\#become_ask_pass=False",
          line: "# Generated by Ansible on {{ ansible_date_time.date }}, do not edit manually!\n\
          # default: #become_ask_pass=False\n\
          become_ask_pass=True"
        }
        - {
          regexp: "^\\#roles_path\ \ \ \ =\ /etc/ansible/roles",
          line: "# Generated by Ansible on {{ ansible_date_time.date }}, do not edit manually!\n\
          # default: #roles_path    = /etc/ansible/roles\n\
          roles_path    = ~/ansible/roles"
        }
 
    - name: "Ansible Directory Layout anlegen"
      ansible.builtin.file:
        path: '/home/{{ admin_user }}/{{ ansible_working_dir }}/{{ item.directory }}'
        state: directory
        owner: '{{ admin_user }}'
        group: '{{ admin_user }}'
        mode: '0755'
      with_items:
        - {directory: "filter_plugins/"}
        - {directory: "library/"}
        - {directory: "module_utils/"}
        - {directory: "playbooks/"}
        - {directory: "inventories/production/group_vars/"}
        - {directory: "inventories/production/host_vars/"}
        - {directory: "inventories/staging/group_vars/"}
        - {directory: "inventories/staging/host_vars/"}
        - {directory: "roles/common/defaults/"}
        - {directory: "roles/common/files/"}
        - {directory: "roles/common/handlers/"}
        - {directory: "roles/common/library/"}
        - {directory: "roles/common/lookup_plugins/"}
        - {directory: "roles/common/meta/"}
        - {directory: "roles/common/module_utils/"}
        - {directory: "roles/common/tasks/"}
        - {directory: "roles/common/templates/"}
        - {directory: "roles/common/vars/"}
 
    - name: "Ansible Directory Layout mit dummy-files main.yml befüllen"
      ansible.builtin.file:
        path: '/home/{{ admin_user }}/{{ ansible_working_dir }}/{{ item.file }}'
        state: touch
        owner: '{{ admin_user }}'
        group: '{{ admin_user }}'
        mode: '0640'
      with_items:
        - {file: "filter_plugins/main.yml"}
        - {file: "library/main.yml"}
        - {file: "module_utils/main.yml"}
        - {file: "inventories/production/hosts"}
        - {file: "inventories/production/group_vars/main.yml"}
        - {file: "inventories/production/host_vars/main.yml"}
        - {file: "inventories/staging/hosts"}
        - {file: "inventories/staging/group_vars/main.yml"}
        - {file: "inventories/staging/host_vars/main.yml"}
        - {file: "roles/common/defaults/main.yml"}
        - {file: "roles/common/files/main.yml"}
        - {file: "roles/common/handlers/main.yml"}
        - {file: "roles/common/library/main.yml"}
        - {file: "roles/common/lookup_plugins/main.yml"}
        - {file: "roles/common/meta/main.yml"}
        - {file: "roles/common/module_utils/main.yml"}
        - {file: "roles/common/tasks/main.yml"}
        - {file: "roles/common/templates/main.yml"}
        - {file: "roles/common/vars/main.yml"}
 
... # YML Ende

Script Beschreibung

Mit Hilfe des Playbooks werden alle wesentlichen Konfigurationsoptionen definiert, die im Kapitel (Grund-)Konfiguration - (Grund-)Konfiguration detailliert beschrieben und besprochen wurden. Nacheinander werden folgende Punkte abgearbeitet:

  1. Ermitteln des angemeldeten (Admin-)Usernamens admin_user, der für die weitere Programmlauf benötigt wird.
  2. Prüfen, ob die Konfigurationsdatei /etc/ansible.cfg vorhanden ist und ggf. das Playbook geordnet abbrechen falls dies nicht der Fall sein sollte.
  3. Kopieren der Ansible Konfigurationsdatei /etc/ansible.cfg nach ~/.ansible.cfg.
  4. Setzen der Ansible-Konfigurationsoptionen
    • interpreter_python = auto_silent
    • inventory = /home/{{ admin_user }}/ansible/inventories/production
    • become=True
    • become_method=sudo
    • become_user=root
    • become_ask_pass=True
    • roles_path = ~/ansible/roles
  5. Ansible Directory Layout anlegen und anschliessend
  6. Ansible Directory Layout mit dummy-files main.yml befüllen.

Script starten

Das Ansible-Playbook lässt sich wie folgt auf dem Ansible-Controll-Host bzw. der Admin-Workstation aufrufen:

 $ ansible-playbook ~/ansible/playbooks/ansible_grundconfig_v1.yml -K

BECOME password: 
[WARNING]: Unable to parse /home/django/ansible/inventories/production as an inventory source
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [ansible_grundconfig_v1.yml] *************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************
ok: [localhost]

TASK [Ansible Konfigurationsdatei /etc/ansible/ansible.cfg vorhanden?] ************************************************************
ok: [localhost]

TASK [Fehlerhinweis im Fehlerfall ausgeben] ***************************************************************************************
skipping: [localhost]

TASK [Ansible Konfigurationsverzeichnis in das User/Admin-Verzeichnis kopieren] ***************************************************
changed: [localhost]

TASK [Ansible Konfiguration anpassen] *********************************************************************************************
changed: [localhost] => (item={'regexp': '^\\[defaults\\]', 'line': '[defaults]\n# Generated by Ansible on 2022-09-22, do not edit manually!\n# default:\ninterpreter_python = auto_silent'})
changed: [localhost] => (item={'regexp': '^\\#inventory      = /etc/ansible/hosts', 'line': '# Generated by Ansible on 2022-09-22, do not edit manually!\n# default: #inventory      = /etc/ansible/hosts\ninventory       = /home/django/ansible/inventories/production'})
changed: [localhost] => (item={'regexp': '^\\#become=True', 'line': '# Generated by Ansible on 2022-09-22, do not edit manually!\n# default: #become=True\nbecome=True'})changed: [localhost] => (item={'regexp': '^\\#become_method=sudo', 'line': '# Generated by Ansible on 2022-09-22, do not edit manually!\n# default: #become_method=sudo\n
become_method=sudo'})
changed: [localhost] => (item={'regexp': '^\\#become_user=root', 'line': '# Generated by Ansible on 2022-09-22, do not edit manually!\n# default: #become_user=root\nbecome_user=root'})
changed: [localhost] => (item={'regexp': '^\\#become_ask_pass=False', 'line': '# Generated by Ansible on 2022-09-22, do not edit manually!\n# default: #become_ask_pass=False\nbecome_ask_pass=True'})
changed: [localhost] => (item={'regexp': '^\\#roles_path    = /etc/ansible/roles', 'line': '# Generated by Ansible on 2022-09-22, do not edit manually!\n# default: #roles_path    = /etc/ansible/roles\nroles_path    = ~/ansible/roles'})

TASK [Ansible Directory Layout anlegen] *******************************************************************************************
ok: [localhost] => (item={'directory': 'filter_plugins/'})
ok: [localhost] => (item={'directory': 'library/'})
ok: [localhost] => (item={'directory': 'module_utils/'})
ok: [localhost] => (item={'directory': 'playbooks/'})
ok: [localhost] => (item={'directory': 'inventories/production/group_vars/'})
ok: [localhost] => (item={'directory': 'inventories/production/host_vars/'})
ok: [localhost] => (item={'directory': 'inventories/staging/group_vars/'})
ok: [localhost] => (item={'directory': 'inventories/staging/host_vars/'})
ok: [localhost] => (item={'directory': 'roles/common/defaults/'})
ok: [localhost] => (item={'directory': 'roles/common/files/'})
ok: [localhost] => (item={'directory': 'roles/common/handlers/'})
ok: [localhost] => (item={'directory': 'roles/common/library/'})
ok: [localhost] => (item={'directory': 'roles/common/lookup_plugins/'})
ok: [localhost] => (item={'directory': 'roles/common/meta/'})
ok: [localhost] => (item={'directory': 'roles/common/module_utils/'})
ok: [localhost] => (item={'directory': 'roles/common/tasks/'})
ok: [localhost] => (item={'directory': 'roles/common/templates/'})
ok: [localhost] => (item={'directory': 'roles/common/vars/'})

TASK [Ansible Directory Layout mit dummy-files main.yml befüllen] ***************************************************************
changed: [localhost] => (item={'file': 'filter_plugins/main.yml'})
changed: [localhost] => (item={'file': 'library/main.yml'})
changed: [localhost] => (item={'file': 'module_utils/main.yml'})
changed: [localhost] => (item={'file': 'inventories/production/hosts'})
changed: [localhost] => (item={'file': 'inventories/production/group_vars/main.yml'})
changed: [localhost] => (item={'file': 'inventories/production/host_vars/main.yml'})
changed: [localhost] => (item={'file': 'inventories/staging/hosts'})
changed: [localhost] => (item={'file': 'inventories/staging/group_vars/main.yml'})
changed: [localhost] => (item={'file': 'inventories/staging/host_vars/main.yml'})
changed: [localhost] => (item={'file': 'roles/common/defaults/main.yml'})
changed: [localhost] => (item={'file': 'roles/common/files/main.yml'})
changed: [localhost] => (item={'file': 'roles/common/handlers/main.yml'})
changed: [localhost] => (item={'file': 'roles/common/library/main.yml'})
changed: [localhost] => (item={'file': 'roles/common/lookup_plugins/main.yml'})
changed: [localhost] => (item={'file': 'roles/common/meta/main.yml'})
changed: [localhost] => (item={'file': 'roles/common/module_utils/main.yml'})
changed: [localhost] => (item={'file': 'roles/common/tasks/main.yml'})
changed: [localhost] => (item={'file': 'roles/common/templates/main.yml'})
changed: [localhost] => (item={'file': 'roles/common/vars/main.yml'})

PLAY RECAP ********************************************************************************************************************
localhost                  : ok=6  changed=3    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

Für eine einfache Ansible Umgebung haben wir nun erfolgreich die Basis geschaffen. Künftig können wir nun einfach unsere Playboooks starten, ohne zusätzliche Optionen angeben zu müssen, wie z.B.:

 $ ansible-playbook ~/ansible/playbooks/01_create-user.yml

Wir werden dann automatisch aufgefordert das become_password einzugeben.

Da wir aber später vertrauliche Informationen wie Passworte und Schlüssel in unseren Playbooks und im Inventory verwenden wollen, werden wir uns nicht mit dieser Basis-Konfiguration zufrieden geben, sondern eher auf Sicherheit bedacht sein, und daher lieber das nachfolgende Beispiel verwenden wollen!

Aufgabenstellung 2 - "erweiterte" Grund-/Basis-Installation für Ansible-Vault unter Arch Linux

Mit Hinblick auf das all umspannende Thema Sicherheit in der IT ist eines unserer Hauptaugenmerk, dass schützenswerte Informationen nicht mehr als plain-text in unserer Ansible-Entwicklungsumgebung ungeschützt herumliegen. Folgende Sicherheitsvorkehrungen wollen wir unseren Admins für die Arbeit mit Ansible unter Arch Linux an die Hand geben:

  1. Die Anmeldung an remote Hosts erfolgt mit Hilfe der SSH ausschliesslich mit Schlüsseln, auf keinen Fall über Passworte. Anmeldung als root sind grundsätzlich unterbunden, auch die Anmeldung mit Hilfe eines User-Keys als root ist ebenso wenig gestattet! Der Admin muss den SSH-Private-Key besitzen und von dessen Passphrase Kenntnis haben. Wird ein Nitrokey Start USB-Schlüssel verwendet, kann bei Bedarf auch der SSH-Key bei Verwendung der SSH benutzt werden. Die Zusätzliche Eingabe einer Passphrase erübrigt sich dadurch auch hier, wenn der SSH-Key auf dem Kryptostick verwendet wird!
  2. Der Administrator soll beim Aufruf der Playbooks nur noch den PGP-Schlüssel durch Eingabe der zugehörigen Passphrase entsperren. Das Passwort für die Rechteerweiterung wird von Ansible aus dem Vault gelesen, genau so wie das become_password zur Rechteerweiterung.

Wir wollen vertrauliche Informationen in unseren Playbooks bzw. im Inventory ausschliesslich via Ansible-Vault vorhalten, da diese als krypted AES256 Daten abgelegt und somit auch jederzeit in einem verteiltem Versionskontrollsystem wie git vorgehalten werden können. Durch Nutzung des Passwort-Manager pass wird die Handhabung soweit vereinfacht, so dass der Admin auch ohne grosse Not mehrmals hintereinander Ansible-Playbooks ausführen kann, ohne sich durch zigfache Eingabe von Passworten sich selbst das Leben allzu schwer zu machen!

Aus Sicht von IT-Security haben wir somit einen erheblicher Zugewinn an Sicherheit. Die Akzeptanzschwelle ist durch Minimierung von mehrfachen Eingaben diverser Passworte durchaus niedrig, so dass für den Admin durchaus ein Mehrwert bei der täglichen administrativen Tätigkeit ausgemacht werden kann.

Damit wir nun diesen Vorüberlegungen gerecht werden können, benötigen wir eine vordefinierte Grundinstallation und Konfiguration der Ansible-Umgebung für unsere(n) Admin(s) unter Arch Linux. Damit diese Grund-Konfiguration unserer Ansible-Umgebung nicht von Hand erfolgen muss, greifen wir auf das Playbook ansible_grundconfig_v2.yml zurück.

Mit Hilfe dieses Playbooks können alle erforderlichen Konfigurationsschritte reproduzierbar und beliebig oft abgesetzt werden. Bei Bedarf ist als also jederzeit ohne grossen Aufwand möglich den Ansible-Controll-Node unter Arch Linux neu aufzusetzen und das System für spätere Playbook-Rollouts vorzubereiten, den Host also auch initial frisch zu versorgen.

Folgende Schritte sollen von dem playbook abgearbeitet werden:

  1. Abfrage des Passwortes für den Password-Store und des ansible_become_password.
  2. Kopieren der Ansible-Konfigurationsdatei /etc/ansible/ansible.cfg in das $HOME-Verzeichnis des Admin-Users.
  3. Anpassen, sprich konfigurieren der individuellen Ansible Umgebung.
  4. Ansible Directory Layout anlegen und mit Dummy-Inhalten versorgen.
  5. Installation und Konfiguration des Passwortmanagers pass.
  6. Hinterlegen des Ansible Become Passwortes in einem Ansible-Vault.
  7. Für die kompakte Ausgabe der Ansible Rückmeldungen soll der Ansible Stdout Compact Logger genutzt werden.

Somit erreichen wir später bequem unser gestecktes Ziel unsere Zeit als Admin effizient zu nutzen und in einer sicheren Umgebung uns zu bewegen.

Lösung

Der ungeduldigen Leser kann auch direkt zur Tat schreiten und das manuelle Anlegen des Verzeichnisses und des Ansible-Scripts überspringen. Mit Folgendem Befehl erledigt man dies sozusagen auf einem Rutsch:

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

Anschliessend kann man, nachdem man die Variablen wie z.B. admin_mail an die individuellen Bedürfnisse angepasst hat, dann auch gleich direkt zur Ausführung schreiten.

Script anlegen

Haben wir durch erste Gehversuche oder aus einer vorangegangenen Basisinstallation bereits eine ~/.ansible.cfg in unserem $HOME-Verzeichnis, löschen wir diese da wir diese nun neu erzeugen und eine ggf. vorhandene Konfigurationsdatei beim Lauf dieses Playbooks eher hinderlich als hilfreich wäre!

 $ rm ~/.ansible.cfg

Als erstes legen wir manuell einmal das Verzeichnis ~/devel/ansible/playbooks, in dem wir unsere Ansible-Playbooks ablegen werden auf unserer Admin-Workstation bzw. dem Ansible-Controll-Node anlegen, sofern wir das noch nicht erstellt hatten.

 $ mkdir -p ~/devel/ansible/playbooks

Hier legen wir nun unser Ansible-Playbook/-Script ansible_grundconfig_v2.yml ab, mit dessen Hilfe wir unsere Ansible-Umgebung individuell einrichten und konfigurieren wollen.

 $ vim ~/ansible/playbooks/ansible_grundconfig_v2.yml
playbooks/ansible_grundconfig_v2.yml
--- # YAML Start
    # Ansible Playbook zum initialen Einrichten der Ansible-Umgebung unter Arch Linux
    #
    # Aufruf aus dem entsprechenden Arbeits-Verzeichnis via:
    # ansible-playbook playbooks/ansible_grundconfig_v2.yml -K

- name: "Playbookname: ansible_grundconfig_v2.yml"
  gather_facts: true
  hosts: localhost
  become: true
  vars:
    ansible_working_dir: devel/ansible
    ansible_config: '/home/{{ admin_user }}/.ansible.cfg'
    admin_user: "{{ lookup('env','USER') }}"
    admin_mail: '{{ admin_user }}@nausch.org'
  vars_prompt:
    - name: pass_secret
      prompt: "Enter password for vault-password-store?"
    - name: pass_secret_2nd
      prompt: "Retype password for vault-password-store?"
    - name: become_secret
      prompt: "Enter become-password for sudo?"
    - name: become_secret_2nd
      prompt: "Retype become-password for sudo?"

  tasks:
    - name: "Playbooklauf abbrechen sofern die beiden eingegebenen Vault-Passwörter nicht übereinstimmen!"
      ansible.builtin.fail:
        msg: "Error: the entered password-store passwords do not match."
      when:
        - pass_secret != pass_secret_2nd

    - name: "Playbooklauf abbrechen sofern die beiden eingegebenen sudo-Passwörter nicht übereinstimmen!"
      ansible.builtin.fail:
        msg: "Error: the entered become-passwords for sudo do not match."
      when:
        - become_secret != become_secret_2nd

    - name: "Erzeugen der Ansible Konfigurationsdatei '{{ ansible_config }}'"
      ansible.builtin.shell:
        cmd: ansible-config init --disabled > '{{ ansible_config }}'
      register: configuration
      changed_when: false

    - name: "Dateirechte der Ansible Konfigurationsdatei dem Admin zuweisen."
      ansible.builtin.file:
        path: '{{ ansible_config }}'
        owner: '{{ admin_user }}'
        group: '{{ admin_user }}'
        mode: '0644'

    - name: "Ansible Konfiguration anpassen"
      ansible.builtin.lineinfile:
        line: "{{ item.line }}"
        path: "/home/{{ admin_user }}/.ansible.cfg"
        regexp: "{{ item.regexp }}"
        state: present
      with_items:
        - {
          regexp: "^\\;interpreter_python=auto",
          line: "# Generated by Ansible on {{ ansible_date_time.date }}, do not edit manually!\n\
          # default: ;interpreter_python=auto\n\
          interpreter_python = auto_silent"
        }
        - {
          regexp: "^\\;inventory=/etc/ansible/hosts",
          line: "# Generated by Ansible on {{ ansible_date_time.date }}, do not edit manually!\n\
          # default: ;inventory=/etc/ansible/hosts\n\
          inventory = /home/{{ admin_user }}/{{ ansible_working_dir }}/inventories/production"
        }
        - {
          regexp: "^\\;roles_path=.*",
          line: "# Generated by Ansible on {{ ansible_date_time.date }}, do not edit manually!\n\
          # default: ;roles_path=/home/{{ admin_user }}/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles\n\
          roles_path = ~/{{ ansible_working_dir }}/roles"
        }
        - {
          regexp: "^\\;vault_password_file.*",
          line: "# Generated by Ansible on {{ ansible_date_time.date }}, do not edit manually!\n\
          # default: ;vault_password_file=\n\
          vault_password_file = ~/bin/ansible_vault_password"
        }

    - name: "Ansible Directory Layout anlegen"
      ansible.builtin.file:
        path: '/home/{{ admin_user }}/{{ ansible_working_dir }}/{{ item.directory }}'
        state: directory
        owner: '{{ admin_user }}'
        group: '{{ admin_user }}'
        mode: '0755'
      with_items:
        - {directory: "filter_plugins/"}
        - {directory: "library/"}
        - {directory: "module_utils/"}
        - {directory: "playbooks/"}
        - {directory: "inventories/production/group_vars/"}
        - {directory: "inventories/production/host_vars/"}
        - {directory: "inventories/staging/group_vars/"}
        - {directory: "inventories/staging/host_vars/"}
        - {directory: "roles/common/defaults/"}
        - {directory: "roles/common/files/"}
        - {directory: "roles/common/handlers/"}
        - {directory: "roles/common/library/"}
        - {directory: "roles/common/lookup_plugins/"}
        - {directory: "roles/common/meta/"}
        - {directory: "roles/common/module_utils/"}
        - {directory: "roles/common/tasks/"}
        - {directory: "roles/common/templates/"}
        - {directory: "roles/common/vars/"}
        - {directory: "roles/common/vars/"}

    - name: "Ansible Directory Layout mit dummy-files main.yml befüllen"
      ansible.builtin.file:
        path: '/home/{{ admin_user }}/{{ ansible_working_dir }}/{{ item.file }}'
        state: touch
        owner: '{{ admin_user }}'
        group: '{{ admin_user }}'
        mode: '0640'
      with_items:
        - {file: "filter_plugins/.gitkeep"}
        - {file: "library/.gitkeep"}
        - {file: "module_utils/.gitkeep.yml"}
        - {file: "inventories/production/hosts"}
        - {file: "inventories/staging/hosts"}
        - {file: "roles/common/defaults/.gitkeep"}
        - {file: "roles/common/files/.gitkeep"}
        - {file: "roles/common/handlers/.gitkeep"}
        - {file: "roles/common/library/.gitkeep"}
        - {file: "roles/common/lookup_plugins/.gitkeep"}
        - {file: "roles/common/meta/.gitkeep"}
        - {file: "roles/common/module_utils/.gitkeep"}
        - {file: "roles/common/tasks/main.yml"}
        - {file: "roles/common/templates/.gitkeep"}
        - {file: "roles/common/vars/.gitkeep"}

    - name: "Passwort-Manager pass installieren"
      ansible.builtin.package:
        name: pass
        update_cache: true
        state: present

    - name: "Verzeichnis ~/bin anlegen"
      ansible.builtin.file:
        path: '/home/{{ admin_user }}/bin'
        group: '{{ admin_user }}'
        owner: '{{ admin_user }}'
        state: directory
        mode: '0750'

    - name: "Wrapperscript für den Passort-Safe vault anlegen"
      ansible.builtin.copy:
        dest: '/home/{{ admin_user }}/bin/ansible_vault_password'
        content: |
          #!/bin/bash
          # Ansible generated, do not edit manually!
          pass show ansible-vault-password
        owner: '{{ admin_user }}'
        group: '{{ admin_user }}'
        mode: '0750'

    - name: "Sicherstellen dass das pass Store-Verzeichnis nicht existiert"
      ansible.builtin.file:
        path: '/home/{{ admin_user }}/.password-store'
        state: absent

    - name: "Pass-Store Passwort ablegen"
      become_user: django
      become: true
      ansible.builtin.shell: |
        set -o pipefail && pass init '{{ admin_mail }}' && /usr/bin/echo '{{ pass_secret }}' | pass insert -ef ansible-vault-password
      changed_when: false
      args:
        executable: /bin/bash

    - name: "Erstellen des Verzeichnisses für den Ansible Vault"
      ansible.builtin.file:
        path: '/home/{{ admin_user }}/ansible/inventories/production/group_vars/all/'
        state: directory
        owner: '{{ admin_user }}'
        group: '{{ admin_user }}'
        mode: '0755'

    - name: "Sicherstellen dass das File für das verschlüsselte become-password noch nicht existiert"
      ansible.builtin.file:
        path: '/home/{{ admin_user }}/ansible/inventories/production/group_vars/all/vault'
        state: absent

    - name: "Ansible Become Password für sudo Rechteerweiterung anlegen"
      ansible.builtin.copy:
        dest: '/home/{{ admin_user }}/ansible/inventories/production/group_vars/all/vault'
        content: |
          # Generated by Ansible on {{ ansible_date_time.date }}, do not edit manually!
          ansible_become_pass: {{ become_secret }}
        owner: '{{ admin_user }}'
        group: '{{ admin_user }}'
        mode: '0640'

    - name: "Ansible Become Password mit ansible-vault verschlüsseln"
      become_user: django
      become: true
      ansible.builtin.shell: |
        ansible-vault encrypt /home/{{ admin_user }}/ansible/inventories/production/group_vars/all/vault
      changed_when: false

    - name: "Sicherstellen dass das File mit der Ansible-Konfiguration nicht existiert"
      ansible.builtin.file:
        path: '/home/{{ admin_user }}/ansible/inventories/production/group_vars/all/ansible_environment'
        state: absent

    - name: "Ansible Konfigurationsdatei mit den Definitionen zu privilege_escalation anlegen"
      ansible.builtin.copy:
        dest: '/home/{{ admin_user }}/ansible/inventories/production/group_vars/all/ansible_environment'
        content: |
          # Generated by Ansible on {{ ansible_date_time.date }}, do not edit manually!
          ansible_become: True
          ansible_become_method: sudo
          ansible_become_user: root
          ansible_become_ask_pass: False
        owner: '{{ admin_user }}'
        group: '{{ admin_user }}'
        mode: '0644'

    - name: "Erstellen des Verzeichnisses für Ansible Stdout Compact Logger"
      ansible.builtin.file:
        path: '/home/{{ admin_user }}/.ansible/plugins/callback/'
        state: directory
        owner: '{{ admin_user }}'
        group: '{{ admin_user }}'
        mode: '0755'

    - name: "Download des anstomlog Python scripts"
      ansible.builtin.get_url:
        url: https://raw.githubusercontent.com/octplane/ansible_stdout_compact_logger/main/callbacks/anstomlog.py
        dest: '/home/{{ admin_user }}/.ansible/plugins/callback/anstomlog.py'
        mode: '0664'

    - name: "Ansible Konfiguration für Ansible Stdout Compact Logger anpassen"
      ansible.builtin.lineinfile:
        line: "{{ item.line }}"
        path: "/home/{{ admin_user }}/.ansible.cfg"
        regexp: "{{ item.regexp }}"
        state: present
      with_items:
        - {
          regexp: "^\\;stdout_callback=default",
          line: "# Generated by Ansible on {{ ansible_date_time.date }}, do not edit manually!\n\
          # default: ;stdout_callback=default\n\
          stdout_callback=anstomlog"
        }
        - {
          regexp: "^\\;callback_plugins=",
          line: "# Generated by Ansible on {{ ansible_date_time.date }}, do not edit manually!\n\
          # default: ;callback_plugins=/home/{{ admin_user }}/.ansible/plugins/callback:/usr/share/ansible/plugins/callback\n\
          callback_plugins   = ~/.ansible/plugins/callback"
        }
 
... # YML Ende

Wichtig:
Die Variablen wie z.B. admin_mail passen wir natürlich noch unseren Gegebenheiten nach an, denn das Beispiel im Script wird sicherlich nicht für jedermann|frau zutreffen! 8-)

Script Beschreibung

Mit Hilfe des Playbooks werden für unseren Admin-User auf unserem Arch-Linux Host alle wesentlichen Konfigurationsoptionen definiert, die im Kapitel (Grund-)Konfiguration - (Grund-)Konfiguration detailliert beschrieben und besprochen wurden. Die Details zur spezifischen Ansible-Vault Konfiguration finden sich im Kapitel Ansible - erweitertes Konfigurationsbeispiel 7: Ansible Vault - ansible-vault in Verbindung mit ansible-playbook.

Nacheinander werden folgende Punkte abgearbeitet:

  1. Ermitteln des angemeldeten (Admin-)Usernamens admin_user, der für die weitere Programmlauf benötigt wird.
  2. Abfrage des Passwortes für den Password-Store und des ansible_become_password, damit dies bei der weiteren Abarbeitung des Ansible-Playbooks entsprechend verschlüsselt in einer vault-Datei sicher abgelegt werden kann.
  3. Generieren der Ansible Konfigurationsdatei /etc/ansible.cfg mit Hilfe von ansible-config init –disabled > ~/.ansible.cfg
  4. Setzen der Ansible-Konfigurationsoptionen
    • interpreter_python = auto_silent
    • inventory = /home/{{ admin_user }}/ansible/inventories/production
    • roles_path = ~/ansible/roles
    • vault_password_file = ~/bin/ansible_vault_password
  5. Ansible Directory Layout anlegen und anschliessend
  6. Ansible Directory Layout mit dummy-files .gitkeep befüllen.
  7. Installation des Passwort-Managers pass
  8. vault-Wrapperscript im bin Verzeichnis des Admins ablegen und ggf. das zugehörige Verzeichnis anlegen.
  9. Store-Passwort für pass im zugehörigen Verzeichnis ablegen, dabei ggf. ein bereits existierendes pass-Verzeichnis löschen.
  10. Im Inventory das become_password unter group_vars/all ablegen und dabei eine bereits existierende vault-Datei vorher entfernen.
  11. Ansible-Vault Datei, die zuvor angelegt wurde, mit dem Ansible-Vault-Passwort sicher verschlüsseln.
  12. Im Inventory die Definitionen zu privilege_escalation anlegen und auch hier ggf. ein bereits existierende Konfigurationsdatei vorher löschen.
  13. Installation und Konfiguration des Ansible Stdout Compact Logger unter Arch Linux
    • stdout_callback=anstomlog
    • callback_plugins = ~/.ansible/plugins/callback„

Script starten

Das Ansible-Playbook lässt sich wie folgt auf dem Ansible-Controll-Host bzw. der Admin-Workstation aufrufen:

 $ ansible-playbook ~/devel/ansible/playbooks/ansible_grundconfig_v2.yml -K

Bild: Ansible NitroKey StartBeim Aufruf eines Ansible-Playbooks liest das Programm ansible-playbook alle benötigten Dateien ein. Zum Abarbeiten benötigt Ansible natürlich die temporär entschlüsselten Informationen. Damit Ansible nun die verschlüsselten Informationen herankommt, benötigt Ansible natürlich vorübergehend das Vault-Passwort/-Passphrase, welches sich nun selbst verschlüsselt in einem Ansible-Vault befindet. Durch den Passwortmanager pass und unserem PGP-Schlüssel kommen wir nun direkt zu dem Ansible Vault Passwort. Wir brauchen also nur einmal das für den PGP zugehörige Passwort oder im Falle der Verwendung eines Security-Hardware-Devices wie eines Nitrokey Start.

Der Aufruf des Scripts 01_create-user.ym aus Beispiel 1 würde dann lauten:

 $ ansible-playbook -v 01_create-user.yml --limit=demo

Nach der Abfrage des PGP-Passwortes bzw. der PIN bei Verwendung eines Nitrokey Start wird dann sofort das Playbook ausgeführt. Eine zusätzliche Eingabe eines become_password ist nunmehr nicht mehr nötig.

Fazit und Ausblick

Wir haben nun Dank der beiden gezeigten Ansible-Playbooks zur Konfiguration unserer Ansible-Umgebung die Möglichkeit, jederzeit bei Bedarf einen weiteren Admin zu befähigen bzw. eine bestehende Umgebung erneut auszurollen. Somit können wir nun mit Ansible-Vault vertrauliche Informationen in unseren Playbooks bzw. im Inventory ablegen. Diese werden als krypted AES256 Daten abgelegt und können dadurch auch jederzeit in einem verteiltem Versionskontrollsystem wie git vorgehalten werden. Durch Nutzung des Passwort-Manager pass wird die Handhabung soweit vereinfacht, so dass der Admin auch ohne grosse Not mehrmals hintereinander Ansible-Playbooks ausführen kann, ohne sich durch zigfache Eingabe von Passworten sich selbst das Leben allzu schwer zu machen!

Links