{"id":232,"date":"2021-06-09T19:56:00","date_gmt":"2021-06-09T17:56:00","guid":{"rendered":"https:\/\/pascal-korz.de\/blog\/?p=232"},"modified":"2021-04-18T22:34:23","modified_gmt":"2021-04-18T20:34:23","slug":"ansible-playbook-ssh-zugriff-beschraenken","status":"publish","type":"post","link":"https:\/\/pascal-korz.de\/blog\/2021\/06\/09\/ansible-playbook-ssh-zugriff-beschraenken\/","title":{"rendered":"Ansible-Playbook: SSH-Zugriff beschr\u00e4nken"},"content":{"rendered":"\n<p>SSH ist ein m\u00e4chtiges Protokoll und eine der Grundlagen von Ansible. In einem Arbeitsumfeld m\u00f6chte man aber nicht unbedingt jedermann den Zugriff auf fremde Rechner erm\u00f6glichen, sobald ein Passwort einmal bekannt wird, erst recht nicht, wenn es sich um das root-Passwort eines Rechners handelt.<\/p>\n\n\n\n<div class=\"wp-block-group has-green-background-color has-background\"><div class=\"wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow\">\n<p>Diese Anleitung steht f\u00fcr sich allein, kn\u00fcpft aber an die folgenden Beitr\u00e4ge an:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><a rel=\"noreferrer noopener\" href=\"https:\/\/pascal-korz.de\/blog\/2018\/12\/08\/freeipa-installation-eines-domain-controllers\/\" data-type=\"post\" data-id=\"130\" target=\"_blank\">FreeIPA: Installation eines Domain Controllers<\/a><\/li><li><a rel=\"noreferrer noopener\" href=\"https:\/\/pascal-korz.de\/blog\/2021\/05\/12\/ansible-in-einer-freeipa-domaene-einrichten\/\" target=\"_blank\">Ansible in einer FreeIPA-Dom\u00e4ne einrichten<\/a><\/li><li><a rel=\"noreferrer noopener\" href=\"sudo-Regel f\u00fcr den Ansible-Benutzer\" data-type=\"URL\" data-id=\"FreeIPA: sudo-Regel f\u00fcr den Ansible-Benutzer\" target=\"_blank\">FreeIPA: sudo-Regel f\u00fcr den Ansible-Benutzer<\/a><\/li><li><a href=\"Host-Based Access Control\" data-type=\"URL\" data-id=\"FreeIPA: Host-Based Access Control\" target=\"_blank\" rel=\"noreferrer noopener\">FreeIPA: Host-Based Access Control<\/a><\/li><\/ul>\n<\/div><\/div>\n\n\n\n<p>In einem fr\u00fcheren Beitrag habe ich bereits den SSH-Zugang generell mittels Host-Based-Access-Control-Regeln von FreeIPA eingeschr\u00e4nkt und nur wenigen Benutzern erlaubt. Hier gehe ich einen Schritt weiter und schr\u00e4nke die konkreten M\u00f6glichkeiten der Authentifizierung f\u00fcr den SSH-Verbindungsaufbau ein, indem ich die leicht skriptbare Passwort-Authentifizierung (<code>PasswordAuthentication<\/code>) und die Verbindung als root-Benutzer (<code>PermitRootLogin<\/code>) ausschalte, aber die interaktive Passwort-Authentifizierung (<code>ChallengeResponseAuthentication<\/code>) erlaube, die dank HBAC-Regeln aber nur wenigen erlaubt ist.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. Ansible-Playbook erstellen<\/h3>\n\n\n\n<p>Das folgende Ansible-Playbook, das im Wesentlichen dem sehr empfehlenswerten Beitrag <a href=\"https:\/\/ryaneschinger.com\/blog\/securing-a-server-with-ansible\/\">Securing a Server with Ansible | ryaneschinger.com<\/a> entnommen ist, wird der SSH-Zugriff auf allen Rechnern schnell auf Public-Key-Authentication eingeschr\u00e4nkt, und die entfernte Anmeldung als root-Benutzer wird gleich ganz unterbunden:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>---\n- hosts: \"{{ targets }}\"\n  tasks:\n  - name: Disable root SSH access.\n    lineinfile:\n      path=\/etc\/ssh\/sshd_config\n      regexp=\"^PermitRootLogin\"\n      line=\"PermitRootLogin no\"\n      state=present\n    notify: restart sshd\n  - name: Disable SSH password authentication.\n    lineinfile:\n      path=\/etc\/ssh\/sshd_config\n      regexp=\"^PasswordAuthentication\"\n      line=\"PasswordAuthentication no\"\n      state=present\n  - name: Enable SSH keyboard-interactive authentication.\n    lineinfile:\n      path=\/etc\/ssh\/sshd_config\n      regexp=\"^ChallengeResponseAuthentication\"\n      line=\"ChallengeResponseAuthentication yes\"\n      state=present\n    notify: restart sshd\n  handlers:\n  - name: restart sshd\n    service: name=sshd state=restarted\n...\n<\/code><\/pre>\n\n\n\n<p>Das YAML-Format der Ansible-Playbooks ist gew\u00f6hnungsbed\u00fcrftig, aber es gibt alternative Formate, auf die ich hier allerdings nicht eingehe. Die meisten Playbooks, die man online findet, sind im YAML-Format geschrieben.<\/p>\n\n\n\n<p>Ich speichere dieses Playbook auf dem Ansible-Server unter <code>\/opt\/playbooks\/restrict_ssh_access.yml<\/code>. Dieses Playbook besteht aus zwei sogenannten Tasks, die zusammen ein Play bilden. Beide Tasks suchen (mithilfe von <code>regexp<\/code>, also einem regul\u00e4ren Ausdruck) und ersetzen eine bestimmte Zeile in der unter <code>path<\/code> angegebenen Datei durch die unter <code>line<\/code> angegebene Zeile. Danach verlangen Sie einen Neustart des SSH-Dienstes, der von einem entsprechenden Handler, welcher auf der Hierarchieebene der <code>hosts<\/code>-Zeile definiert ist, am Ende des Plays ausgef\u00fchrt wird.<\/p>\n\n\n\n<p>Im Playbook gebe ich ganz bewusst nicht an, mit wessen Benutzerrechten diese Aufgaben ausgef\u00fchrt werden sollen, obwohl ich das problemlos sowohl f\u00fcr das ganze Playbook, f\u00fcr ein einzelnes Play oder f\u00fcr einzelne Tasks tun k\u00f6nnte. Letztendlich ist mir egal, wer diese Aufgaben ausf\u00fchrt, solange er blo\u00df dazu berechtigt ist. Darum gebe ich die Anmedeldeinformationen erst beim Ausf\u00fchren des Playbooks an.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2. Playbook ausf\u00fchren<\/h3>\n\n\n\n<p>Das Playbook l\u00e4sst sich mit der Option <code>--check<\/code> testen, ohne es auszuf\u00fchren. Ansible meldet dann blo\u00dfe Prognosen \u00fcber den Ausgang. Da ich im Playbook in der <code>hosts<\/code>-Zeile keine Rechner oder Rechnergruppen aus dem Ansible-Inventory bestimme, sondern die Variable <code>targets<\/code> angebe, kann ich das Playbook f\u00fcr beliebige Rechner einsetzen, indem ich erst bei der Ausf\u00fchrung in der Option <code>--extra-vars<\/code> die Variable <code>targets<\/code> definiere (im unteren Beispiel schlicht <code>all<\/code>, also alle Rechener im Inventory).<\/p>\n\n\n\n<p>Wenn ich bereits mit meinem Ansible-Benutzer angemeldet bin und keinen Benutzer f\u00fcr 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\u00e4ne hat, kann Ansible sich problemlos \u00fcberallhin verbinden. Wenn der Ansible-Benutzer aber einmal verbunden ist, muss er die Befehle aktiv mit sudo-Rechten ausf\u00fchren, weil das Playbook Systemdateien ver\u00e4ndert. Darum f\u00fcge ich die Optionen <code>--become<\/code> (f\u00fcr die Ausf\u00fchrung als anderer Benutzer, standardm\u00e4\u00dfig root) und <code>--become-method<\/code> (obwohl bereits standardm\u00e4\u00dfig sudo) sowie <code>--ask-become-pass<\/code> (zur Eingabe des Passworts des sudo-berechtigten Benutzers) hinzu.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ansible-playbook \/opt\/playbooks\/restrict_ssh_access.yml --extra-vars=\"targets=all\" --become --ask-become-pass --check<\/code><\/pre>\n\n\n\n<p>Damit andere Benutzer das Playbook ausf\u00fchren k\u00f6nnen, m\u00fcssten sie den Befehl um die Optionen <code>--user<\/code> und <code>--ask-pass<\/code> erweitern, da die FreeIPA-Richtlinien ihnen selber momentan keinen SSH-Zugriff gestatten. Die Option <code>--ask-pass<\/code> ist au\u00dferdem n\u00f6tig, weil sie selber keine Public-Key-Authentication zu den Rechnern eingerichtet haben und sich darum mit dem Passwort des Ansible-Benutzers autorisieren m\u00fcssen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ansible-playbook \/opt\/playbooks\/restrict_ssh_access.yml --extra-vars=\"targets=all\" --user=ansible.ssh --ask-pass --become --ask-become-pass --check<\/code><\/pre>\n\n\n\n<p>So oder so k\u00f6nnte die Ausgabe des Befehls etwa folgenderma\u00dfen aussehen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PLAY &#91;all] *******************************************************************************\n\nTASK &#91;Gathering Facts] *******************************************************************\nok: &#91;cgn-ansible01.ipa.animentor.de]\nok: &#91;cgn-ipa01.ipa.animentor.de]\nok: &#91;cgn-ipa02.ipa.animentor.de]\n\nTASK &#91;Disable root SSH access.] **********************************************************\nchanged: &#91;cgn-ipa02.ipa.animentor.de]\nchanged: &#91;cgn-ansible01.ipa.animentor.de]\nchanged: &#91;cgn-ipa01.ipa.animentor.de]\n\nTASK &#91;Disable SSH password authentication.] **********************************************\nchanged: &#91;cgn-ipa01.ipa.animentor.de]\nchanged: &#91;cgn-ipa02.ipa.animentor.de]\nchanged: &#91;cgn-ansible01.ipa.animentor.de]\n\nTASK &#91;Enable SSH keyboard-interactive authentication.] ***********************************\nchanged: &#91;cgn-ipa02.ipa.animentor.de]\nchanged: &#91;cgn-ipa01.ipa.animentor.de]\nchanged: &#91;cgn-ansible01.ipa.animentor.de]\n\nRUNNING HANDLER &#91;restart sshd] ***********************************************************\nchanged: &#91;cgn-ipa01.ipa.animentor.de]\nchanged: &#91;cgn-ipa02.ipa.animentor.de]\nchanged: &#91;cgn-ansible01.ipa.animentor.de]\n\nPLAY RECAP *******************************************************************************\ncgn-ansible01.ipa.animentor.de : ok=5    changed=4    unreachable=0    failed=0   \ncgn-ipa01.ipa.animentor.de : ok=5    changed=4    unreachable=0    failed=0   \ncgn-ipa02.ipa.animentor.de : ok=5    changed=4    unreachable=0    failed=0 <\/code><\/pre>\n\n\n\n<p>Wenn unerwartete Probleme auftreten, helfen \u00fcbrigens die Optionen <code>--verbose<\/code> oder (noch wesentlich ausf\u00fchrlicher) <code>-vvv<\/code> mit Details zum Verbindungsaufbau, den eingesetzten Ansible-Modulen und dem Playbook-Ablauf weiter.<\/p>\n\n\n\n<p class=\"has-green-background-color has-background\">Warum benutzen wir nicht die Option <code>--become-user<\/code>, wenn wir doch den Ansible-Benutzer mit sudo-Rechten verwenden wollen? Dazu muss man das sudo-Konzept grunds\u00e4tzlich verstehen: Tats\u00e4chlich wollen wir gerade <strong>nicht<\/strong>, dass die Aktionen mit den Rechten vom Ansible-Benutzer ausgef\u00fchrt werden, sondern wir wollen seine sudo-Rechte nutzen, um die Aktionen mit <strong>root<\/strong>-Rechten auszuf\u00fchren. Die Option <code>--become-user ansible.ssh<\/code> w\u00e4re also gleichbedeutend mit <code>sudo -u ansible.ssh<\/code> und gerade nicht mit <code>sudo<\/code>. 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 \u2013 und die werden ihm auch gew\u00e4hrt, weil sudo auf dem entfernten Rechner dank der FreeIPA-Richtlinie f\u00fcr diesen Benutzer konfiguriert wurde.[<\/p>\n\n\n\n<p>Nicht nur simuliert, sondern ausgef\u00fchrt wird das Playbook ohne die Option <code>--check<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ansible-playbook \/opt\/playbooks\/restrict_ssh_access.yml --extra-vars=\"targets=all\" --become --ask-become-pass<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">3. Ergebnisse pr\u00fcfen<\/h3>\n\n\n\n<p>Ob das Playbook wie gew\u00fcnscht den SSH-Zugriff restriktiver gemacht hat, kann man einmal \u00fcber die Anzeige der SSHD-Konfiguration auf den entfernten Rechnern und dann nat\u00fcrlich \u00fcber entsprechende Verbindungsversuche pr\u00fcfen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo grep ^PasswordAuthentication \/etc\/ssh\/sshd_config\nsudo grep ^ChallengeResponseAuthentication \/etc\/ssh\/sshd_config\nsudo grep ^PermitRootLogin \/etc\/ssh\/sshd_config<\/code><\/pre>\n\n\n\n<p>Die Ausgaben sollten jeweils lauten:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PasswordAuthentication no<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>ChallengeResponseAuthentication yes<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>PermitRootLogin no<\/code><\/pre>\n\n\n\n<p>Damit ist das Ziel erreicht und geichzeitig Ansibles Funktionalit\u00e4t unter Beweis gestellt.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>SSH ist ein m\u00e4chtiges Protokoll und eine der Grundlagen von Ansible. In einem Arbeitsumfeld m\u00f6chte man aber nicht unbedingt jedermann den Zugriff auf fremde Rechner erm\u00f6glichen, sobald ein Passwort einmal bekannt wird, erst recht nicht, wenn es sich um das root-Passwort eines Rechners handelt. Diese Anleitung steht f\u00fcr sich allein, kn\u00fcpft aber an die folgenden&hellip; <a class=\"more-link\" href=\"https:\/\/pascal-korz.de\/blog\/2021\/06\/09\/ansible-playbook-ssh-zugriff-beschraenken\/\"><span class=\"screen-reader-text\">Ansible-Playbook: SSH-Zugriff beschr\u00e4nken<\/span> weiterlesen<\/a><\/p>\n","protected":false},"author":6,"featured_media":468,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[15,12,8],"tags":[102],"class_list":["post-232","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ansible","category-freeipa","category-linux","tag-artikelreihe-testumgebung","entry"],"_links":{"self":[{"href":"https:\/\/pascal-korz.de\/blog\/wp-json\/wp\/v2\/posts\/232","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/pascal-korz.de\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/pascal-korz.de\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/pascal-korz.de\/blog\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/pascal-korz.de\/blog\/wp-json\/wp\/v2\/comments?post=232"}],"version-history":[{"count":20,"href":"https:\/\/pascal-korz.de\/blog\/wp-json\/wp\/v2\/posts\/232\/revisions"}],"predecessor-version":[{"id":481,"href":"https:\/\/pascal-korz.de\/blog\/wp-json\/wp\/v2\/posts\/232\/revisions\/481"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/pascal-korz.de\/blog\/wp-json\/wp\/v2\/media\/468"}],"wp:attachment":[{"href":"https:\/\/pascal-korz.de\/blog\/wp-json\/wp\/v2\/media?parent=232"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/pascal-korz.de\/blog\/wp-json\/wp\/v2\/categories?post=232"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/pascal-korz.de\/blog\/wp-json\/wp\/v2\/tags?post=232"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}