Ansible-Tutorial: Setup von User-Accounts, SSH und Firewall mit Ansible - Feature-Bild

Ansible-Tutorial: Setup von User-Accounts, SSH und Firewall mit Ansible

Autor-Bild
von Sven Woltmann – 20. Oktober 2018

Artikelserie: Ansible-Tutorial

Teil 1: Setup eines Root-Servers mit Ansible

Teil 2: User-Accounts, SSH und Firewall

Teil 3: Docker, MySQL und WordPress

Teil 4: HAProxy + HTTPS von Let’s Encrypt

(Melde dich für den HappyCoders-Newsletter an, um sofort über neue Teile informiert zu werden.)

Im ersten Teil dieser Tutorial-Serie habe ich dir gezeigt, wie ich mit Ansible auf einem Root-Server von Hetzner das Betriebssystem-Image installiert habe. Im zweiten Teil des Ansible-Tutorials geht es um das „Bootstrapping“ des Servers. Unter dem Begriff „Bootstrapping“ fasse ich alle Aktionen zusammen, die für alle Server gleich sein werden, egal für welche Aufgabe ein Server eingesetzt werden wird. Im Einzelnen werde ich:

  • Patches einspielen,
  • den Hostnamen festlegen,
  • eine Hosts-Datei anlegen,
  • einen weiteren User anlegen, um mich nicht als root einloggen zu müssen,
  • die SSH-Konfiguration optimieren,
  • ein paar Bash-Aliase anlegen,
  • einige hilfreiche Kommandozeilen-Tools installieren und
  • die Firewall konfigurieren.

Den Code zum Artikel findest du in meinem GitHub-Repository unter https://gitlab.com/SvenWoltmann/happycoders-tutorial-server-setup.

Zusätzlich zum Artikel gibt es auch wieder ein Video-Tutorial: Setup von User-Accounts, SSH und Firewall mit Ansible

Bootstrap-Ansible-Playbook

Als erstes erstelle ich im Projektverzeichnis die Datei bootstrap.yml mit zunächst folgendem Inhalt:

--- - hosts: "{{ target }}" remote_user: root roles: - apt_upgrade - hostnames - users - ssh
Code-Sprache: YAML (yaml)

Der Unterschied zu den Playbooks des vorherigen Artikels ist, dass ich die Zeile gather_facts: false weglasse. Da der Default-Wert true ist, wird Ansible vor dem Ausführen der ersten Rolle die Fakten des Zielsystems (bspw. dessen Betriebssystem-Version) zusammentragen. Dies ist nur möglich, wenn auf dem Zielsystem bereits Python installiert ist. Das hatte ich im vorherigen Artikel als letzten Schritt erledigt. Die aufgelisteten Rollen erkläre ich im Folgenden Schritt für Schritt.

Patches einspielen

Als erstes möchte ich den Server auf den aktuellsten Stand bringen, in dem ich alle verfügbare Patches einspiele. Dazu erstelle ich die Rolle apt_upgrade, für die in der Datei roles/apt_upgrade/tasks/main.yml nur ein einzelner Task definiert ist:

--- - name: Update and upgrade apt packages apt: upgrade: yes update_cache: yes
Code-Sprache: YAML (yaml)

Ich verwende das Ansible-Modul apt, wobei update_cache: yes das Äquivalent ist für apt update und upgrade: yes für apt upgrade steht. Dieser Task ist das Äquivalent zur Ausführung von apt update && apt-upgrade auf der Kommandozeile.

Hostname festlegen

Nun möchte ich sicherstellen, dass die Datei /etc/hostname den korrekten Hostnamen enthält. Das sollte zwar das installimage-Skript erledigt haben, aber es schadet nichts, das an dieser Stelle noch einmal zu prüfen und ggf. zu korrigieren. Letztendlich sollte dieses Ansible-Playbook auch auf Servern anderer Hoster laufen, auf denen die hostname-Datei evtl. nicht bereits bei der Provisionierung gesetzt wird. Dazu lege ich die Rolle hostnames an in Form der Datei roles/hostnames/tasks/main.yml mit folgender Task-Definition:

--- - name: Set hostname hostname: name: "{{ inventory_hostname }}"
Code-Sprache: YAML (yaml)

Der Task verwendet das Ansible-Modul hostname, um zum einen das gleichnamige Kommando auszuführen, um den Hostnamen zu ändern, und zum anderen den Hostnamen in die Datei /etc/hostname zu schreiben, so dass dieser auch bei einem Serverneustart erhalten bleibt.

Hosts-Datei anlegen

Außerdem möchte ich alle relevanten Servernamen in die /etc/hosts-Datei schreiben. Im Moment ist das nur einer, doch sobald ich mehrere Server habe, wird das wichtig sein. Ich kopiere mir die Datei /etc/hosts des Servers in mein lokales Projektverzeichnis nach roles/hostnames/files/hosts und passe lediglich die Kommentare leicht an, so dass die Datei folgenden Inhalt hat:

# IPv4 127.0.0.1 localhost.localdomain localhost 46.4.99.9 happy1.happycoders.eu happy1 # IPv6 ::1 ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters ff02::3 ip6-allhosts 2a01:4f8:140:9090::2 happy1.happycoders.eu happy1
Code-Sprache: Klartext (plaintext)

Zum Kopieren der Datei auf den Server verwende ich das Ansible-Modul copy und füge entsprechend folgenden Eintrag in die zuvor angelegten Rollen-Datei roles/hostnames/tasks/main.yml ein:

- name: Copy "hosts" file copy: src: hosts dest: /etc/hosts owner: root group: root mode: 0644
Code-Sprache: YAML (yaml)

Mit src wird die Quelldatei relativ zum files/-Verzeichnis angegeben, mit dest der Zielpfad auf dem Server. Die restlichen drei Parameter legen den Besitzer und die Gruppe der Datei sowie die Bits für den Dateimodus fest.

User anlegen

Aus Sicherheitsgründen möchte ich mich in Zukunft nicht als root-User mit dem Server verbinden, daher lege ich einen User sven an und füge diesen zur Gruppe sudo hinzu. Während der Entwicklungsphase möchte ich allerdings nicht immer wieder für sudo mein Passwort eingeben müssen. Dafür erstelle ich eine zusätzliche Gruppe sudo-nopasswd, füge den User sven hinzu und erlaube allen Usern dieser Gruppe, sudo ohne Passwort auszuführen. Das alles mache ich wie immer mit Ansible, d. h. ich lege die Rolle users an und die Datei roles/users/tasks/main.yml mit folgenden Tasks:

Task 1 – sudo installieren:

--- - name: Install sudo apt: name: sudo
Code-Sprache: YAML (yaml)

Task 2 – User sven anlegen:

- name: Create "sven" user user: name: sven shell: /bin/bash uid: 1000 group: users groups: sudo append: true # --> user is not removed from any other group password: $6$2i/vlnXuBj/m8A$y9xNcMAJQstggFMSWYbVeJStvvDpk4s4jScPv074Fb94jSfCbxyWSPz.tmMPQ6Qiy6mnpn07SQRTe6Sex4Pfi/
Code-Sprache: YAML (yaml)

Den User erstelle ich mit dem Ansible-Modul user und verwende dabei folgende Parameter:

  • Mit name lege ich den Benutzernamen fest.
  • Die Benutzer-Shell wird mit shell festgelegt.
  • Ich lege mit uid eine fixe User-ID fest.
  • Mit group lege ich fest, welches die Hauptgruppe des Benutzers wird.
  • Mit groups gebe ich an, dass der User zusätzlich in der sudo-Gruppe enthalten sein soll.
  • Wichtig ist append: true anzugeben, da der User sonst aus Gruppen, denen er evtl. in anderen Playbooks hinzugefügt wurde, wieder entfernt werden würde.
  • Mit password gebe ich mein Passwort in verschlüsselter Form an. Dieses habe ich mit dem Kommando mkpasswd -m sha-512 generiert.

Task 3 – Gruppe sudo-nopasswd anlegen:

- name: Create "sudo-nopasswd" group group: name: sudo-nopasswd
Code-Sprache: YAML (yaml)

Hier verwende ich das Ansible-Modul group; dieses legt die unter name bezeichnete Gruppe an.

Task 4 – User sven zur Gruppe sudo-nopasswd hinzufügen:

- name: Add user "sven" to "sudo-nopasswd" group user: name: sven groups: sudo-nopasswd append: true # --> user is not removed from any other group when: passwordless_sudo is defined and passwordless_sudo == true
Code-Sprache: YAML (yaml)

Ich verwende hier erneut das user-Modul, welches ich weiter oben bereits erklärt habe. Mit when lege ich fest, dass die Gruppenzuordnung nur dann erfolgen soll, wenn die Variable passwordless_sudo vorhanden und auf true gesetzt ist. Somit kann ich nach der Entwicklungsphase sehr leicht die Passwort-Abfrage für sudo wieder aktivieren, indem ich die Variable entferne oder auf false setze. In diesem Fall wird Task 4 zwar nicht mehr ausgeführt, entfernt wird der User dadurch aber nicht aus der Gruppe. Dies erledige ich in Task 5.

Task 5 – User sven aus Gruppe sudo-nopasswd entfernen:

- name: Remove user "sven" from "sudo-nopasswd" group shell: /usr/sbin/delgroup sven sudo-nopasswd when: not (passwordless_sudo is defined and passwordless_sudo == true) ignore_errors: yes
Code-Sprache: YAML (yaml)

Dies ist leider mit dem user-Modul nicht möglich, daher verwende ich dafür das shell-Modul und das delgroup-Linux-Kommando. Wichtig ist hier die Zeile ignore_errors: yes anzugeben, da das Playbook ansonsten an dieser Stelle mit einer Fehlermeldung abbrechen würde, wenn der User nicht in der Gruppe ist, aus der er entfernt werden soll.

Task 6 – Festlegen, dass User der Gruppe sudo-nopasswd das Kommando sudo ohne Passwort ausführen dürfen:

- name: Add "sudo-nopasswd" group to "sudoers" file lineinfile: dest: /etc/sudoers line: '%sudo-nopasswd ALL=(ALL:ALL) NOPASSWD:ALL' regexp: '^%sudo-nopasswd'
Code-Sprache: YAML (yaml)

Dafür verwende ich das Ansible-Modul lineinfile mit folgenden Parametern:

  • Mit dest gebe ich die Datei an, die bearbeitet werden soll.
  • Mit line übergebe ich die Zeile, die in die Datei eingetragen werden soll.
  • Sollte es eine andere Zeile geben, auf die der mit regexp übergebene reguläre Ausdruck zutrifft (also eine, die mit %sudo-nopasswd beginnt), wird diese gelöscht.

In den letzten zwei Zeilen muss ich die Strings entsprechend YAML-Cookbook in Anführungszeichen setzen, da % ein Sonderzeichen ist.

Mit den restlichen vier Tasks kopiere ich die Public Keys für die User root und sven auf den Server. Der Public Key für den root-User wurde zwar bereits bei der Installation des Betriebssystems kopiert, doch möchte ich dieses Playbook auch nutzen können, um den Key ggf. zu ändern oder evtl. einen Key auf einem Server zu installieren, den ich nicht über das installimage-Skript von Hetzner eingerichtet habe.

Task 7 – .ssh-Verzeichnis für root-User anlegen

- name: "Create root user's .ssh directory" file: path: /root/.ssh state: directory owner: root group: root mode: 0700
Code-Sprache: YAML (yaml)

Hier wird mit Hilfe des Ansible-Moduls „file“ das Verzeichnis /root/.ssh/ erstellt und – wie beim copy-Modul – Eigentümer, Gruppe und Modus gesetzt.

Task 8 – Public Keys für root-User kopieren

- name: "Copy root user's authorized_keys" copy: src: authorized_keys_root dest: /root/.ssh/authorized_keys owner: root group: root mode: 0600
Code-Sprache: YAML (yaml)

Das copy-Modul habe ich bereits zuvor erklärt. Tasks 9 und 10 erstellen analog das Verzeichnis /home/sven/.ssh/ und kopieren die Quelldatei authorized_keys_sven nach /home/sven/.ssh/authorized_keys. Die Dateien authorized_keys_root und authorized_keys_sven liegen im Quellverzeichnis roles/users/files/ und enthalten die Public Keys derjenigen Personen, die als User root bzw. sven per SSH auf den Server Zugriff haben sollen.

Tasks auf mehrere Dateien verteilen

Die Datei roles/users/tasks/main.yml ist ziemlich lang und unübersichtlich geworden, was mir nicht gefällt. Ich werde daher einzelne Codeblöcke in andere Dateien extrahieren und diese mit import_tasks in die main.yml importieren. Ich teile die Datei in drei logische Blöcke auf:

  1. Installation und Konfiguration des sudo-Kommandos,
  2. Anlegen des Users und Hinzufügen zu/Entfernen aus Gruppen,
  3. Kopieren der Public Keys.

Die main.yml sieht nun wie folgt aus:

--- - import_tasks: setup_sudo.yml - import_tasks: setup_users.yml - import_tasks: upload_pubkeys.yml
Code-Sprache: YAML (yaml)

Die drei neuen Dateien liegen im selben Verzeichnis wie die main.yml, also in roles/users/tasks/ und sehen wie folgt aus:

Datei roles/users/tasks/setup_sudo.yml:

--- - name: Install sudo apt: name: sudo - name: Create "sudo-nopasswd" group group: name: sudo-nopasswd - name: Add "sudo-nopasswd" group to "sudoers" file lineinfile: dest: /etc/sudoers line: '%sudo-nopasswd ALL=(ALL:ALL) NOPASSWD:ALL' regexp: '^%sudo-nopasswd'
Code-Sprache: YAML (yaml)

Datei roles/users/tasks/setup_users.yml:

--- - name: Create "sven" user user: name: sven shell: /bin/bash uid: 1000 group: users groups: sudo append: true # --> user is not removed from any other group password: $6$2i/vlnXuBj/m8A$y9xNcMAJQstggFMSWYbVeJStvvDpk4s4jScPv074Fb94jSfCbxyWSPz.tmMPQ6Qiy6mnpn07SQRTe6Sex4Pfi/ - name: Add user "sven" to "sudo-nopasswd" group user: name: sven groups: sudo-nopasswd append: true # --> user is not removed from any other group when: passwordless_sudo is defined and passwordless_sudo == true - name: Remove user "sven" from "sudo-nopasswd" group shell: /usr/sbin/delgroup sven sudo-nopasswd when: not (passwordless_sudo is defined and passwordless_sudo == true) ignore_errors: yes
Code-Sprache: YAML (yaml)

Datei roles/users/tasks/upload_pubkeys.yml:

--- - name: "Create root user's .ssh directory" file: path: /root/.ssh state: directory owner: root group: root mode: 0700 - name: "Copy root user's authorized_keys" copy: src: authorized_keys_root dest: /root/.ssh/authorized_keys owner: root group: root mode: 0600 - name: "Create sven user's .ssh directory" file: path: /home/sven/.ssh state: directory owner: sven group: users mode: 0700 - name: "Copy sven user's authorized_keys" copy: src: authorized_keys_sven dest: /home/sven/.ssh/authorized_keys owner: sven group: users mode: 0600
Code-Sprache: YAML (yaml)

SSH-Konfiguration optimieren

Um meinen Server besser gegen Angriffe zu schützen, möchte ich gegenüber der Standard-SSH-Konfiguration einige Änderungen vornehmen. Ich bin kein Security-Experte und werde hier daher nicht auf die Details eingehen – dafür gibt es hinreichend Lektüre im Internet. Folgende Einstellungen möchte ich u. a. ändern:

  • Ausschließlich Public-Key-Authentifizierung zulassen mit modernsten Verschlüsselungsalgorithmen;
  • Verbindung als root-User unterbinden;
  • Verbindung über einen anderen als den Standard-Port 22, um für einen Großteil böswilliger Scans unsichtbar zu werden;
  • Agent-Forwarding deaktivieren;
  • TCP-Forwarding nur bei Bedarf aktivieren (per Variable);
  • Login- und Verbindungs-Timeouts minimieren.

Für die Konfiguration lege ich die Rolle ssh an und erstelle die unter diesem Absatz abgedruckte SSH-Konfigurationsdatei. Diese enthält u. a. die IP-Adresse des Servers als Variable. Um diese beim Ausführen des Ansible-Playbooks zu ersetzen, muss die Datei im templates/-Verzeichnis abgelegt werden und nicht im files/-Verzeichnis. Der Pfad der Datei lautet somit roles/ssh/templates/sshd_config.j2. Die Endung „.j2“ ist nicht zwingend, ich nutze sie aber, um in meiner IDE das Jinja2-Syntax-Highlighting zu aktivieren (in IntelliJ IDEA muss dazu das „Python“-Plugin installiert werden). Die Datei sieht wie folgt aus:

AcceptEnv LANG LC_* AllowAgentForwarding no {# using a boolean here and not a string, as a string might end up as 'True' if you set the variable to yes (without quote marks) #} AllowTcpForwarding {{ 'yes' if allow_tcp_forwarding is defined and allow_tcp_forwarding == true else 'no' }} AllowGroups sshers ChallengeResponseAuthentication no Ciphers [email protected],[email protected],aes256-ctr ClientAliveCountMax 3 ClientAliveInterval 15 Compression delayed GatewayPorts no HostbasedAuthentication no IgnoreRhosts yes KexAlgorithms [email protected] ListenAddress {{ ansible_default_ipv4.address }}:{{ ssh_port | default('22') }} LoginGraceTime 10 LogLevel INFO MaxAuthTries 3 PasswordAuthentication no PermitEmptyPasswords no PermitRootLogin no Protocol 2 PubkeyAuthentication yes StrictModes yes Subsystem sftp /usr/lib/openssh/sftp-server SyslogFacility AUTH UsePAM yes UsePrivilegeSeparation yes X11Forwarding no
Code-Sprache: Properties (properties)

Die Erklärung aller Parameter würde den Rahmen dieses Artikels sprengen. Ich verweise dazu auf das sshd_config-Handbuch. Ich beschränke mich auf die zwei Parameter, bei denen ich Variablen verwendet habe:

  • AllowTcpForwarding: dies möchte ich, wie in der Einführung erwähnt, nach Bedarf aktivieren können und führe dafür die Variable allow_tcp_forwarding ein, die ich fürs Erste in meiner Host-Konfigurationsdatei auf false setze.
  • ListenAddress: diese setze ich auf die IP-Adresse des Servers gefolgt von der Port-Nummer, die ich in der Variablen ssh_port definieren kann. Ist die Variable nicht definiert, wird der Standardport 22 verwendet. Ich wähle einen zufälligen Port – Port 5930 und trage dazu in meiner Host-Konfigurationsdatei host_vars/happy1.happycoders.eu folgendes ein: ssh_port: 5930.

Um die SSH-Konfigurationsdatei zu kopieren und den SSH-Service neuzustarten, lege ich die Task-Definitionsdatei roles/ssh/tasks/main.yml an, welche die folgenden drei Tasks enthält:

Task 1 – Gruppe sshers erstellen:

--- - name: Create "sshers" group group: name: sshers
Code-Sprache: YAML (yaml)

Aufgrund des Konfigurationseintrags AllowGroups sshers in der sshd_config dürfen sich nur User dieser Gruppe per SSH verbinden.

Task 2 – User sven zur Gruppe sshers hinzufügen:

- name: Add user "sven" to "sshers" group user: name: sven groups: sshers append: true # --> user is not removed from any other group
Code-Sprache: YAML (yaml)

Task 3 – SSH-Konfigurationsdatei kopieren:

- name: Configure SSH server template: src: sshd_config.j2 dest: /etc/ssh/sshd_config owner: root group: root mode: 0644 notify: - Restart SSH service
Code-Sprache: YAML (yaml)

Hier verwende ich das bis jetzt noch nicht eingesetze Ansible-Modul template, welches eine Template-Datei kopiert und dabei die darin enthaltenen Variablen ersetzt. Die Parameter sind die gleichen wie beim zuvor bereits genutzten copy-Modul.

Neu sind ebenfalls die letzten zwei Zeilen: Diese sorgen dafür, dass im Fall einer Änderung der sshd_config (und nur dann) der Handler Restart SSH service ausgeführt wird. Ein Handler ist ein Task, der als Reaktion auf ein bestimmtes Ereignis ausgeführt wird. Den Handler definiert man im Unterverzeichnis handlers/ der Rolle, wiederum in einer main.yml, also in roles/ssh/handlers/main.yml:

--- - name: Restart SSH service service: name: ssh state: restarted
Code-Sprache: YAML (yaml)

Ich verwende hier das Ansible-Modul service, um den SSH-Service neuzustarten. Wichtig ist zu wissen, dass der Handler nicht sofort nach dem Task Configure SSH server ausgeführt wird, sondern erst – gemeinsam mit allen anderen durch notify aktivierten Handlern – am Ende des Playbooks. Dies kann mit dem Task meta: flush_handlers auch vorgezogen werden, doch das ist an dieser Stelle nicht erforderlich.

Playbook ausführen

Das Bootstrap-Playbook ist noch nicht ganz fertig (es fehlen noch die Konfiguration der Aliase und die Installation einiger Tools), doch es ist nun an der Zeit es erstmals laufen zu lassen. Dies mache ich mit folgendem Kommando:

ansible-playbook --extra-vars "target=happy1.happycoders.eu" bootstrap.yml
Code-Sprache: Klartext (plaintext)
Ausführen des Bootstrap-Ansible-Playbooks
Ausführen des Bootstrap-Ansible-Playbooks

Das Playbook ist zu meiner Zufriedenheit erfolgreich durchgelaufen. Der Fehler beim Entfernen des Users sven aus der Gruppe sudo-nopasswd war erwartet und wird korrekterweise ignoriert. Da das Playbook die SSH-Einstellungen des Servers geändert hat, muss ich mich ab sofort über den neuen Port 5930 und als User sven mit dem Server verbinden:

ssh -p 5930 [email protected]
Code-Sprache: Klartext (plaintext)
Login als User "sven"
Login als User „sven“

Bash-Aliase anlegen

An der Standard-Debian-Installation stört mich, dass für den root-User und reguläre User unterschiedliche Bash-Aliase definiert sind. Für den root-User sind in der Datei /root/.bashrc standardmäßig folgende Aliase hinterlegt:

export LS_OPTIONS='--color=auto' [...] alias ls='ls $LS_OPTIONS' alias ll='ls $LS_OPTIONS -l' alias l='ls $LS_OPTIONS -lA'
Code-Sprache: Bash (bash)

Für reguläre User, also den User sven, ist hingegen nur ein einzelner Alias definiert, in der Datei /home/sven/.bashrc – der Rest ist auskommentiert und unterscheidet sich von denen des root-Users:

alias ls='ls --color=auto' #alias dir='dir --color=auto' #alias vdir='vdir --color=auto' #alias grep='grep --color=auto' #alias fgrep='fgrep --color=auto' #alias egrep='egrep --color=auto' [...] #alias ll='ls -l' #alias la='ls -A' #alias l='ls -CF'
Code-Sprache: Bash (bash)

Ich möchte für den sven-User die ls-Aliase des root-Users übernehmen und zusätzlich die auskommentierten Aliase für grep, fgrep, egrep aktivieren. Ich möchte jedoch nicht die /home/sven/.bashrc überschreiben, da mein Playbook auch mit zukünftigen Debian-Releases, welche evtl. eine andere .bashrc installieren könnten, kompatibel sein soll. Stattdessen werde ich die Aliase in die Datei /home/sven/.bash_aliases schreiben. Dazu erstelle ich eine Rolle aliases und lege darin die Datei roles/aliases/files/.bash_aliases mit folgendem Inhalt an:

# enable color support of ls and also add handy aliases if [ -x /usr/bin/dircolors ]; then test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)" alias ls='ls --color=auto' alias grep='grep --color=auto' alias fgrep='fgrep --color=auto' alias egrep='egrep --color=auto' fi # ls aliases from root user alias l='ls -lA' alias ll='ls -l'
Code-Sprache: Bash (bash)

Das Kopieren der Datei auf den Server geschieht mit folgender Task-Definition in der Datei roles/aliases/tasks/main.yml:

--- - name: Copy sven's .bash_aliases copy: src: .bash_aliases dest: /home/sven/.bash_aliases owner: sven group: users mode: 0644
Code-Sprache: YAML (yaml)

Tools installieren

Als letztes installiere ich einige Tools, die ich auf allen Servern benötigen werde. Ich erstelle dazu eine Rolle tools und definiere in der Tasks-Datei roles/tools/tasks/main.yml, welche Packages installiert werden sollen:

--- - name: Install Midnight Commander, NTP daemon apt: name: - mc - ntp cache_valid_time: 3600
Code-Sprache: YAML (yaml)

Das Modul „apt“ habe ich bereits weiter oben beschrieben. Neu ist der Parameter cache_valid_time: 3600. Damit lege ich fest, dass vor der Installation die Package-Liste mittels apt update aktualisiert werden soll, wenn diese älter als 3.600 Sekunden (1 Stunde) ist.

Playbook erneut ausführen

Ich füge die zwei neuen Rollen aliases und tools in das Bootstrap-Playbook bootstrap.yml ein, welches nun wie folgt aussieht:

--- - hosts: "{{ target }}" remote_user: root roles: - apt_upgrade - hostnames - users - ssh - aliases - tools
Code-Sprache: YAML (yaml)

Ich starte das Playbook erneut:

ansible-playbook --extra-vars "target=happy1.happycoders.eu" bootstrap.yml
Code-Sprache: Klartext (plaintext)

Doch dieses Mal bricht Ansible mit folgender Fehlermeldung ab: „Failed to connect to the host via ssh: ssh: connect to host happy1.happycoders.eu port 22: Connection refused“.

"Connection refused" beim erneuten Ausführen des Bootstrap-Playbooks
„Connection refused“ beim erneuten Ausführen des Bootstrap-Playbooks

Der Grund ist ganz einfach: Das Bootstrap-Playbook versucht sich als root-User auf den Standard-SSH-Port 22 zu verbinden. Durch die gehärtete SSH-Konfiguration muss die Verbindung allerdings als User sven auf Port 5930 erfolgen.

Happy Ansible Playbook

Möchte ich die Konfiguration ändern, kann ich das also nicht über das Bootstrap-Playbook erreichen. Nicht nur kann es sich mit der bisherigen Konfiguration nicht mehr mit dem Server verbinden – hinzu kommt, dass es alle Packages aktualisiert, was ich im Produktivbetrieb nicht unkontrolliert geschehen lassen möchte, wann immer ich etwas an der Serverkonfiguration ändere.

Die Lösung ist: Ich lege ein neues Playbook happy1.yml an, welches sich als User sven auf Port 5930 verbindet und alle Rollen ausführt, die auch das Bootstrap-Playbook ausführen würde – mit Ausnahme der Rolle apt_upgrade (aus o. g. Grund):

--- - hosts: happy1.happycoders.eu vars: ansible_port: "{{ ssh_port | default('22') }}" remote_user: sven become: yes roles: - hostnames - users - ssh - aliases - tools
Code-Sprache: YAML (yaml)

Folgendes unterscheidet dieses Playbook vom Bootstrap-Playbook:

  • Dieses Playbook ist kein generisches, sondern ein konkretes für meinen Server happy1.happycoders.eu. Somit kann ich diesen über den Parameter hosts direkt angeben und den Parameter --extra-vars "target=happy1.happycoders.eu" beim Ausführen des Playbooks weglassen.
  • Über vars setze ich die Variable ansible_port auf den SSH-Port, den ich in der Host-Konfigurationsdatei in der Variable ssh_port festgelegt habe. Somit verbindet sich Ansible über diesen Port mit dem Server.
  • Durch die Angabe von remote_user: sven verbindet sich Ansible als User sven.
  • Die Angabe become: yes führt dazu, dass Ansible alle Kommandos auf dem Server als root-User ausführt.

Beim Ausführen dieses Playbooks muss ich nun zusätzlich den Parameter -K angeben, damit Ansible mich nach dem sudo-Passwort fragt:

ansible-playbook -K happy1.yml
Code-Sprache: Klartext (plaintext)
Ausführen des Ansible-Playbooks "happy1.yml"
Ausführen des Ansible-Playbooks „happy1.yml“

Ihr seht, dass das Playbook komplett durchläuft und dabei bei fast allen Tasks „ok“ meldet, was bedeutet, dass keine Änderungen durchgeführt wurden. Lediglich für die zwei neuen Tasks meldet Ansible „changed“, was bedeutet: Die Bash-Aliase sowie die Tools wurden erfolgreich installiert.

sudo ohne Passwort

Wie du dich vielleicht erinnerst, habe ich im Bootstrap-Playbook in der users-Rolle die Möglichkeit geschaffen, während der Entwicklungsphase auf die Eingabe des Passworts für sudo verzichten zu können. Dies möchte ich nun aktivieren. Dazu muss ich in die Host-Konfigurationsdatei host_vars/happy1.happycoders.eu die Zeile passwordless_sudo: true eintragen und das Playbook noch einmal ausführen:

ansible-playbook -K happy1.yml
Code-Sprache: Klartext (plaintext)
User "sven" wird zur Gruppe "sudo-nopasswd" hinzugefügt
User „sven“ wird zur Gruppe „sudo-nopasswd“ hinzugefügt

Für den Task Add user "sven" to "sudo-nopasswd" group meldet Ansible „changed“, was bedeutet, dass der Task ausgeführt, also der User zu der Gruppe hinzugefügt wurde. Und den Task Remove user "sven" from "sudo-nopasswd" group quittiert Ansible mit „skipping“, d. h. dieser Schritt wurde übersprungen, der User also nicht wieder aus der Gruppe entfernt. Würde ich nun passwordless_sudo auf false setzen und das Playbook erneut ausführen, würde der erste Task übersprungen und der zweite ausgeführt werden.

Bei den zukünftigen Ausführungen des Playbooks kann der Parameter -K nun weggelassen werden (so lange bis ich passwordless_sudo wieder auf false setze):

ansible-playbook happy1.yml
Code-Sprache: Klartext (plaintext)

Firewall aus der Ansible Galaxy

Als letzten Schritt in diesem Artikel möchte ich nun noch eine Firewall installieren, die nur Anfragen auf den Ports 80, 443 und 5930 hineinlässt. Da das recht komplex werden könnte, schaue ich, ob sich nicht schon jemand anders die Mühe gemacht und eine Firewall-Rolle geschrieben hat. Die Ansible-Entwickler-Community veröffentlicht Rollen auf Ansible Galaxy. Dort suche ich nach dem Stichwort „Firewall“:

Suche nach "Firewall" in der Ansible Galaxy
Suche nach „Firewall“ in der Ansible Galaxy

Ich suche auch noch einmal bei Google und finde einige GitHub-Projekte, wovon jedoch nur die eben schon in der Ansible Galaxy gefundene Firewall-Rolle von geerlingguy mit mehr als 200 Sternen Relevanz hat; alle übrigen Ansible-Firewall-Projekte haben weniger als fünf Sterne.

Der Entwickler schreibt über seine Rolle: „This firewall aims for simplicity over complexity“, was sich im weiteren Verlauf bestätigt: Ich kann einzelne Ports freischalten, und wenn ich mehr in die Details gehen will, beliebig komplexe iptables-Kommandos hinzufügen.

Installiert wird die Rolle auf dem lokalen Rechner ganz einfach wie folgt:

ansible-galaxy install geerlingguy.firewall
Code-Sprache: MIPS Assembly (mipsasm)
Installation der Rolle "geerlingguy.firewall" aus der Ansible Galaxy
Installation der Rolle „geerlingguy.firewall“ aus der Ansible Galaxy

Ich füge die Rolle geerlingguy.firewall in das Playbook happy1.yml ein:

--- - hosts: happy1.happycoders.eu vars: ansible_port: "{{ ssh_port | default('22') }}" remote_user: sven become: yes roles: - hostnames - users - ssh - aliases - tools - geerlingguy.firewall
Code-Sprache: YAML (yaml)

Konfiguriert wird die Rolle über Variablen, die ich in die Host-Konfigurationsdatei host_vars/happy1.happycoders.eu einfüge. Ich muss hier lediglich die drei Ports angeben, die ich geöffnet haben möchte, den Rest erledigt die Rolle. Folgendes füge ich in die Host-Konfigurationsdatei ein:

firewall_allowed_tcp_ports: - 80 - 443 - 5930
Code-Sprache: YAML (yaml)

Ich führe das Playbook aus:

ansible-playbook happy1.yml
Code-Sprache: Klartext (plaintext)
Konfiguration der Firewall mit Ansible
Konfiguration der Firewall mit Ansible

Nach Anwendung des Playbooks prüfe ich, ob die Firewall-Settings aktiv sind:

ssh -p 5930 [email protected] sudo iptables -L sudo ip6tables -L
Code-Sprache: Klartext (plaintext)
Anzeige der Firewall-Konfiguration mit "iptables -L" und "ip6tables -L"
Anzeige der Firewall-Konfiguration mit „iptables -L“ und „ip6tables -L“

Ihr seht: Die Firewall wurde wie gewünscht konfiguriert.

Zusammenfassung und Ausblick

In diesem Teil der Serie habe ich dir das „Bootstrapping“ des Servers gezeigt, also all diejenigen Installationen und Konfigurationen, die ich auch für zukünftige Server mit möglicherweise anderen Anwendungsbereichen benötigen werde.

Im dritten Teil der Serie werde ich dann das happy1-Playbook erweitern und dir zeigen, wie ich mit Ansible Docker, MySQL und WordPress installiere.

Wenn dir dieser zweite Teil der Serie gefallen hat, hinterlasse mir gerne einen Kommentar oder teile den Artikel über einen der Share-Buttons am Ende.

Möchtest du informiert werden, wenn neue Artikel auf HappyCoders.eu veröffentlicht werden? Dann klick hier, um dich für den HappyCoders.eu Newsletter anzumelden.