SSH ist ein mächtiges Protokoll und eine der Grundlagen von Ansible. In einem Arbeitsumfeld möchte man aber nicht unbedingt jedermann den Zugriff auf fremde Rechner ermöglichen, sobald ein Passwort einmal bekannt wird, erst recht nicht, wenn es sich um das root-Passwort eines Rechners handelt.
Diese Anleitung steht für sich allein, knüpft aber an die folgenden Beiträge an:
In einem früheren Beitrag habe ich bereits den SSH-Zugang generell mittels Host-Based-Access-Control-Regeln von FreeIPA eingeschränkt und nur wenigen Benutzern erlaubt. Hier gehe ich einen Schritt weiter und schränke die konkreten Möglichkeiten der Authentifizierung für den SSH-Verbindungsaufbau ein, indem ich die leicht skriptbare Passwort-Authentifizierung (PasswordAuthentication
) und die Verbindung als root-Benutzer (PermitRootLogin
) ausschalte, aber die interaktive Passwort-Authentifizierung (ChallengeResponseAuthentication
) erlaube, die dank HBAC-Regeln aber nur wenigen erlaubt ist.
1. Ansible-Playbook erstellen
Das folgende Ansible-Playbook, das im Wesentlichen dem sehr empfehlenswerten Beitrag Securing a Server with Ansible | ryaneschinger.com entnommen ist, wird der SSH-Zugriff auf allen Rechnern schnell auf Public-Key-Authentication eingeschränkt, und die entfernte Anmeldung als root-Benutzer wird gleich ganz unterbunden:
---
- hosts: "{{ targets }}"
tasks:
- name: Disable root SSH access.
lineinfile:
path=/etc/ssh/sshd_config
regexp="^PermitRootLogin"
line="PermitRootLogin no"
state=present
notify: restart sshd
- name: Disable SSH password authentication.
lineinfile:
path=/etc/ssh/sshd_config
regexp="^PasswordAuthentication"
line="PasswordAuthentication no"
state=present
- name: Enable SSH keyboard-interactive authentication.
lineinfile:
path=/etc/ssh/sshd_config
regexp="^ChallengeResponseAuthentication"
line="ChallengeResponseAuthentication yes"
state=present
notify: restart sshd
handlers:
- name: restart sshd
service: name=sshd state=restarted
...
Das YAML-Format der Ansible-Playbooks ist gewöhnungsbedürftig, aber es gibt alternative Formate, auf die ich hier allerdings nicht eingehe. Die meisten Playbooks, die man online findet, sind im YAML-Format geschrieben.
Ich speichere dieses Playbook auf dem Ansible-Server unter /opt/playbooks/restrict_ssh_access.yml
. Dieses Playbook besteht aus zwei sogenannten Tasks, die zusammen ein Play bilden. Beide Tasks suchen (mithilfe von regexp
, also einem regulären Ausdruck) und ersetzen eine bestimmte Zeile in der unter path
angegebenen Datei durch die unter line
angegebene Zeile. Danach verlangen Sie einen Neustart des SSH-Dienstes, der von einem entsprechenden Handler, welcher auf der Hierarchieebene der hosts
-Zeile definiert ist, am Ende des Plays ausgeführt wird.
Im Playbook gebe ich ganz bewusst nicht an, mit wessen Benutzerrechten diese Aufgaben ausgeführt werden sollen, obwohl ich das problemlos sowohl für das ganze Playbook, für ein einzelnes Play oder für einzelne Tasks tun könnte. Letztendlich ist mir egal, wer diese Aufgaben ausführt, solange er bloß dazu berechtigt ist. Darum gebe ich die Anmedeldeinformationen erst beim Ausführen des Playbooks an.
2. Playbook ausführen
Das Playbook lässt sich mit der Option --check
testen, ohne es auszuführen. Ansible meldet dann bloße Prognosen über den Ausgang. Da ich im Playbook in der hosts
-Zeile keine Rechner oder Rechnergruppen aus dem Ansible-Inventory bestimme, sondern die Variable targets
angebe, kann ich das Playbook für beliebige Rechner einsetzen, indem ich erst bei der Ausführung in der Option --extra-vars
die Variable targets
definiere (im unteren Beispiel schlicht all
, also alle Rechener im Inventory).
Wenn ich bereits mit meinem Ansible-Benutzer angemeldet bin und keinen Benutzer für die Remote-Verbindung mit den entfernten Rechnern angebe, verbindet Ansible sich automatisch mit dem angemeldeten Benutzer, und da mein Ansible-Benutzer bereits SSH-Zugriff mit Public-Key-Authentication auf alle Rechner der Domäne hat, kann Ansible sich problemlos überallhin verbinden. Wenn der Ansible-Benutzer aber einmal verbunden ist, muss er die Befehle aktiv mit sudo-Rechten ausführen, weil das Playbook Systemdateien verändert. Darum füge ich die Optionen --become
(für die Ausführung als anderer Benutzer, standardmäßig root) und --become-method
(obwohl bereits standardmäßig sudo) sowie --ask-become-pass
(zur Eingabe des Passworts des sudo-berechtigten Benutzers) hinzu.
ansible-playbook /opt/playbooks/restrict_ssh_access.yml --extra-vars="targets=all" --become --ask-become-pass --check
Damit andere Benutzer das Playbook ausführen können, müssten sie den Befehl um die Optionen --user
und --ask-pass
erweitern, da die FreeIPA-Richtlinien ihnen selber momentan keinen SSH-Zugriff gestatten. Die Option --ask-pass
ist außerdem nötig, weil sie selber keine Public-Key-Authentication zu den Rechnern eingerichtet haben und sich darum mit dem Passwort des Ansible-Benutzers autorisieren müssen:
ansible-playbook /opt/playbooks/restrict_ssh_access.yml --extra-vars="targets=all" --user=ansible.ssh --ask-pass --become --ask-become-pass --check
So oder so könnte die Ausgabe des Befehls etwa folgendermaßen aussehen:
PLAY [all] *******************************************************************************
TASK [Gathering Facts] *******************************************************************
ok: [cgn-ansible01.ipa.animentor.de]
ok: [cgn-ipa01.ipa.animentor.de]
ok: [cgn-ipa02.ipa.animentor.de]
TASK [Disable root SSH access.] **********************************************************
changed: [cgn-ipa02.ipa.animentor.de]
changed: [cgn-ansible01.ipa.animentor.de]
changed: [cgn-ipa01.ipa.animentor.de]
TASK [Disable SSH password authentication.] **********************************************
changed: [cgn-ipa01.ipa.animentor.de]
changed: [cgn-ipa02.ipa.animentor.de]
changed: [cgn-ansible01.ipa.animentor.de]
TASK [Enable SSH keyboard-interactive authentication.] ***********************************
changed: [cgn-ipa02.ipa.animentor.de]
changed: [cgn-ipa01.ipa.animentor.de]
changed: [cgn-ansible01.ipa.animentor.de]
RUNNING HANDLER [restart sshd] ***********************************************************
changed: [cgn-ipa01.ipa.animentor.de]
changed: [cgn-ipa02.ipa.animentor.de]
changed: [cgn-ansible01.ipa.animentor.de]
PLAY RECAP *******************************************************************************
cgn-ansible01.ipa.animentor.de : ok=5 changed=4 unreachable=0 failed=0
cgn-ipa01.ipa.animentor.de : ok=5 changed=4 unreachable=0 failed=0
cgn-ipa02.ipa.animentor.de : ok=5 changed=4 unreachable=0 failed=0
Wenn unerwartete Probleme auftreten, helfen übrigens die Optionen --verbose
oder (noch wesentlich ausführlicher) -vvv
mit Details zum Verbindungsaufbau, den eingesetzten Ansible-Modulen und dem Playbook-Ablauf weiter.
Warum benutzen wir nicht die Option --become-user
, wenn wir doch den Ansible-Benutzer mit sudo-Rechten verwenden wollen? Dazu muss man das sudo-Konzept grundsätzlich verstehen: Tatsächlich wollen wir gerade nicht, dass die Aktionen mit den Rechten vom Ansible-Benutzer ausgeführt werden, sondern wir wollen seine sudo-Rechte nutzen, um die Aktionen mit root-Rechten auszuführen. Die Option --become-user ansible.ssh
wäre also gleichbedeutend mit sudo -u ansible.ssh
und gerade nicht mit sudo
. Da der SSH-Zugriff bereits mit dem Ansible-Benutzer hergestellt wird, bezieht sich der sudo-Befehl auf diesen Benutzer und verlangt, da keine weiteren sudo-Optionen verwendet werden, root-Rechte – und die werden ihm auch gewährt, weil sudo auf dem entfernten Rechner dank der FreeIPA-Richtlinie für diesen Benutzer konfiguriert wurde.[
Nicht nur simuliert, sondern ausgeführt wird das Playbook ohne die Option --check
:
ansible-playbook /opt/playbooks/restrict_ssh_access.yml --extra-vars="targets=all" --become --ask-become-pass
3. Ergebnisse prüfen
Ob das Playbook wie gewünscht den SSH-Zugriff restriktiver gemacht hat, kann man einmal über die Anzeige der SSHD-Konfiguration auf den entfernten Rechnern und dann natürlich über entsprechende Verbindungsversuche prüfen:
sudo grep ^PasswordAuthentication /etc/ssh/sshd_config
sudo grep ^ChallengeResponseAuthentication /etc/ssh/sshd_config
sudo grep ^PermitRootLogin /etc/ssh/sshd_config
Die Ausgaben sollten jeweils lauten:
PasswordAuthentication no
ChallengeResponseAuthentication yes
PermitRootLogin no
Damit ist das Ziel erreicht und geichzeitig Ansibles Funktionalität unter Beweis gestellt.