Ansible-Tutorial: Setup von User-Accounts, SSH und Firewall mit Ansible
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 dersudo
-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 Kommandomkpasswd -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:
- Installation und Konfiguration des
sudo
-Kommandos, - Anlegen des Users und Hinzufügen zu/Entfernen aus Gruppen,
- 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 Variableallow_tcp_forwarding
ein, die ich fürs Erste in meiner Host-Konfigurationsdatei auffalse
setze.ListenAddress
: diese setze ich auf die IP-Adresse des Servers gefolgt von der Port-Nummer, die ich in der Variablenssh_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-Konfigurationsdateihost_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)
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)
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“.
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 Parameterhosts
direkt angeben und den Parameter--extra-vars "target=happy1.happycoders.eu"
beim Ausführen des Playbooks weglassen. - Über
vars
setze ich die Variableansible_port
auf den SSH-Port, den ich in der Host-Konfigurationsdatei in der Variablessh_port
festgelegt habe. Somit verbindet sich Ansible über diesen Port mit dem Server. - Durch die Angabe von
remote_user: sven
verbindet sich Ansible als Usersven
. - Die Angabe
become: yes
führt dazu, dass Ansible alle Kommandos auf dem Server alsroot
-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)
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)
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“:
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)
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)
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)
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.