Privilege Escalation für Konfigurationsmanagement, Teil II
So, nachdem ich die letzte Woche mit "Voraussetzungen aufbauen" verbracht habe, und wegen Zeitmangel am Wochenende sogar einen vorbereiteten Füller-Artikel posten musste, kommen wir heute (endlich) zu dem, was ich eigentlich mit Euch diskutieren wollte. Zu diesem Artikel sind mir Eure Kommentare besonders wichtig; ich weiß aber, dass es nach dem Absenden eines Kommentars im Browser einen Timeout hagelt. Das ändert aber nichts dran, dass der Kommentar ordentlich gepostet wurde.
Heute soll es nun endlich darum gehen, wie man Konfigurationsarbeiten ermöglicht und unterstützt, bei denen sich derjenige, der die Arbeiten ausführt, aktiv auf den zu bearbeitenden Systemen einlogged. Dabei schließe ich ausdrücklich das manuelle Arbeiten mit ssh oder mehrfach-ssh wie mssh und cssh ein, meine aber auch semiautomatisierte Arbeiten wie xargs --max-procs
oder parallel
(1) bis hin zum vollautomatischen Prozess, z.B. mit Ansible.
Das Login ist einfach, da nimmt man ssh mit Key und fein. In meiner kleinen Umgebung mit unter hundert Servern kann ich sogar darüber nachdenken, einen Key mit Passphrase oder einen Key auf einem Crypto-Token zu nehmen, denn ich werde auch den Konfigurations-Vollautomaten stets von Hand starten, während ich an der Konsole sitze. Also kann ich meinen Standard-Key verwenden.
Aber auf den Zielsystemen eingelogged zu sein, bedeutet nicht, dass auch die notwendigen Rechte für die geplanten Arbeiten vorliegen. In den allermeisten Fällen wird man Root-Rechte benötigen, es sei denn, man möchte nur Anwendungen konfigurieren, die von root (manuell?) so vorbereitet wurden, dass man sie administrieren kann, ohne root zu sein. Hier kommt meist eine Kombination aus Dateirechten und sudo-Regeln zum Einsatz, die man sinngemäß mit denselben Methoden erschlagen kann wie das "root werden" zum Administrieren des Systems. Auch wenn die Rechteerhöhung auf root erfolgen soll, nimmt man in den allermeisten Fällen sudo, das wegen seiner Konfigurierbarkeit besonders dafür geeignet ist, für Bequemlichkeit und Automatisierung in Kauf genommene Sicherheitslöcher in nahezu beliebiger Größe aufzureißen.
Bisher habe ich auf meinen privaten Systemen (die Schuhe vom Schuster sind immer am heftigsten durchgelatscht) den manuellen Ansatz gewählt: Die mssh-Kommandozeile der passenden Rechnergruppe aus der Shell-History gefischt, aufgerufen, sudo -v und in jedem Teilfenster einzeln das zum Host passende Passwort getippt. Nach der nur wenige Minuten dauernden Einleitungsorgie konnten dann die Arbeiten beginnen.
Beim halbautomatischen Einsatz funktioniert das nur bei einfachen Aufgaben, die ohne Pipes und Redirection auskommen, und nur im seriellen Betrieb - denn sonst wird man von zehn Prozessen gleichzeitig nach einem sudo-Passwort gefragt und die Konsole weiß nicht, für wen die Eingaben sind.
Und bei Ansible funktioniert mein Ansatz schonmal gar nicht, da man ansible-playbook
zwar durch Angabe einer Kommandozeilenoption beim Aufruf zur Frage nach einem Sudo-Passwort motivieren kann, dieses Passwort dann aber bei -allen- Hosts identisch verwendet wird. Das fällt also bei meinem "jedem Host sein eigenes Passwort"-Luxus auf die Nase wegen "ist nicht". Für den Rest dieses Artikels möchte ich mich gerne auf den Ansible-Fall konzentrieren, denn das ist das aktuell für mich interessante.
Direkter root-Login
In dieser einfachsten Variante erfolgt der Login mit einem ssh-Key ohne Passwort direkt als root mit entsprechend (PermitRootLogin yes|without-password
) konfiguriertem sshd. Vorteil: Es funktioniert, es ist einfach. Nachteil: Die Security-Implikationen sind erheblich. Wer den Key unter seine Kontrolle bekommt, ist root auf allen gemanagten Systemen.
Man kann dieses Risiko ein bisschen eindämmen, indem man die Nutzung des Keys erschwert. Man kann sowohl den ssh-Server des Systems als auch den Key selbst so einstellen, dass man ihn nur von den zugelassenen Management-Systemen benutzen kann, die man wieder mit anderen Methoden absichern kann. Sprunghosts lassen sich mit den entsprechenden Methoden nahtlos und transparent einfügen.
Leider kann man den Key, der für die ganz allgemeinen Arbeiten gedacht ist, nicht auf einzelne Kommandos einschränken: Dass das für den manuellen und halbautomatischen Einsatz nicht taugt, liegt auf der Hand. Beim Vollautomaten (z.b. Ansible) könnte man zwar den Key vernageln, aber da Ansible zuerst ein Script nicht vorhersagbaren Inhalts hochlädt und das denn auf dem Zielsystem ausführt, ist nichts gewonnen: Derjenige mit Kontrolle über den Key kann ebenfalls ein script mit dem von ihm gewünschten Inhalt hochladen und ausführen und ist damit zwar nicht direkt, aber immerhin mal mit minimalem Umweg root.
Auch der Audit-Trail lässt Wünsche offen: Im Syslog landet in diesem Falle nur die Information, dass sich jemand von einer mitgeloggten IP-Adresse als root eingelogged hat, mit etwas Glück noch der Fingerprint des verwendeten Keys (nur bei Zertifikaten?), und das war's. Was root gemacht hat, bleibt ohne Schweinereien wie execve-Wrapper o.ä. im Dunkeln.
Alles ohne Passwort
Ein wenig besser ist meiner Meinung nach die Indirektion, bei der der Login mit einem ssh-Key ohne Passwort auf einen unprivilegierten Account und die Rechterhöhung mit einer zu diesem gehörenden NOPASSWD-sudo-Regel erfolgt. Das funktioniert ebenfalls und ist nur unwesentlich komplizierter als die erste Variante. Es bleiben die Nachteile, die mit denen der vorherigen Möglichkeit übereinstimmen: Wer den Key unter seine Kontrolle bekommt, ist root auf allen gemanagten Systemen. Der Audit Trail ist ein wenig besser, bei disziplinierter Nutzung sieht man die Kommandos, die mit erhöhten Rechten abgesetzt wurden, aber der Vollautomat kümmert sich nicht darum und liefert nur ein "sudo /tmp/foo-irgendwas" im Log ab, was den Audit-Trail unbrauchbar macht.
Diese Grundmuster kann man jetzt an verschiedenen Stellen verfeinern.
Absicherung des Keys
Wenn man auf die Vollautomatisierung verzichtet, kann man den Key absichern: Der liegt ganz sicher auf einem Crypto-Token, das nur bei Nutzung eingesteckt bzw. aufgeschlossen wird, oder ein wenig sicher mit einem Passphrase geschützt auf einem ordentlich gesicherten System. Will der lokale Admin nun einen Prozess auslösen, logged er sich auf dem System ein, das den Key hat und nutzt ihn zur Auslösung des Prozesses.
Vorteile und Nachteile sind klar: Es ist für den Angreifer schwieriger, den privaten Teil des ssh-Keys unter seine Kontrolle zu bekommen und ihn wirklich nutzen zu können. Der legitime Admin zahlt hierfür den Preis, dass er keine Vollautomatisierung mehr erreichen kann, da seine Mitwirkung notwendig ist, um den Key nutzbar zu machen.
Absicherung der Rechteerhöhung
Der gängige Ansatz zur Absicherung der Rechteerhöhung (das "root werden") ist ein Passwort. Bei der klassischen Methodik ist es das (systembezogene) root-Passwort bei su(1) bzw das (benutzerbezogene) User-Passwort bei sudo.
Der Vorteil des Passworts ist der Nachteil des Passworts: Soll es sicher sein, muss es komplex sein, darf nicht an zu vielen Stellen mehrfachbenutzt werden und es muss jemand da sein, der es eingibt. Besonders letzteres ist - erneut - der Feind der Vollautomatisierung. Möchte man Vollautomatisierung, muss man das Passwort irgendwo speichern, dann muss man den Speicherort des Passworts absichern, möchte aber Vollautomatisierung und so weiter. Was ist eigentlich Rekursion?
Kompromiss tut not
Es kommt also mal wieder zum klassischen Schluß: Möchte man skalierbare Vollautomatisierung, muss man sein Sicherheitsniveau senken. Möchte man ein höheres Sicherheitsniveau, skaliert man nicht mehr. Dabei ist dann auch noch zu berücksichtigen, dass sich ab einer gewissen Größe lohnt, gesonderte Maßnahmen zur Absicherung der Automatisierung zu implementieren, damit der durch sie entstehende Sicherheitsverlust nicht so hoch ist.
Abschließend ist dann auch klar, dass es keine Standardlösung geben kann, und dass jeder Systembetreiber seinen eigenen Kompromiss aus Aufwand, Sicherheitsniveau und Automatisierungsgrad finden und implementieren muss.
Mein Kompromiss
Manchmal hilft es ja, seine Gedanken niederzuschreiben und sie in Struktur zu bringen. So ist auch bei mir beim Schreiben dieser Artikel eine Lösung gewachsen, die auf meine spezielle Situation passen dürfte, und die ich Euch in diesem Abschnitt vorstellen möchte. Vielleicht sagt Ihr mir ja Eure Meinung dazu.
- für die klassische, manuelle Systemadministration bleibe ich bei meinem altbewährten Verfahren mit einem aus meinen verschiedenen Netzsegmenten erreichbarem ssh-Server, keybasiertem Login auf meinen persönlichen Account und sudo mit hostindividuellem Passwort.
- für Automatismen möchte ich ansible-Playbooks verwenden. Dabei ist das vorläufige Ziel, Dinge, die auf nahezu allen Systemen vorhanden sein müssen (z.B. syslog, ntp, Login) zu automatisieren. Alles weiter gehende ist zunächst mal optional.
- Ansible wird sich per ssh auf einen eigenen Account ("ansible", "automat", "cfgmgt" o.ä.) einloggen, der potenziell eingeschränkt sein kann (keine interaktive Shell oder ähnliche Maßnahmen). Dabei bin ich mir bewusst, dass ich mit solchen Maßnahmen einen entschlossenen Angreifer nicht werde aufhalten können, ihm aber wenigstens den Spaß an seiner Arbeit reduzieren kann.
- Vom Ansible-Account kann man mit sudo(1) und einem auf allen Systemen gleichen, dafür aber ekelhaften und vielleicht gar regelmäßig geändertem Passwort root werden. Dieses Passwort muss entweder in meinen Kopf oder ins Passwort-Safe, was das signifikante Sicherheitsrisiko dieses Setups sein dürfte.
- Ein Software-Passwortsafe ist angreifbar.
- Ein Passwortsafe auf dem Cryptotoken reduziert die Sicherheit: Man braucht nur noch das Cryptotoken und (eine oder zwei) PINs und ist "drin".
- Ein Passwortsafe auf einem getrennten Cryptotoken erschwert das Handling, da ich mit zwei Tokens hantieren muss, die ich obendrein immer "am Mann" haben muss. Die Wahrscheinlichkeit, dass beide Tokens gemeinsam abhanden kommen und in denselben Angreiferhänden landen, ist hoch.
- Ich sehe Mr. Schlaumeier Zugschlus bei manuellem Arbeiten kurzerhand den ansible-Account benutzen, um nicht für jeden einzelnen Host das Passwort raussuchen zu müssen. Da muss ich mir noch überlegen, wie ich mir selbst diese Hintertür so umständlich mache, dass ich freiwillig weiterhin mit meinem "eigenen" Account arbeite.
- ich verzichte bewusst auf Vollautomatisierung und möchte alle Aktionen manuell auslösen. Das erlaubt mir, den ssh-Key auf einem PIN-geschützten Cryptotoken abzulegen (das ich abziehe wenn ich es nicht benutze), und bei ansible-Läufen das sudo-Passwort einzugeben - diesmal in der von Ansible unterstützten Version, dass das Passwort auf allen Maschinen gleich ist.
- Eventuell spalte ich das auch nochmal auf: Meine Hosts sind in Hostgruppen eingeteilt, die teilweise nach Funktion, teilweise nach Standort und teilweise nach dem Besitzer (ich arbeite ja für eine Vielzahl von Kunden und administriere bei einigen dieser Kunden die von mir betreuten Systeme trotzdem nach meinen Standards). Dabei könnte ich mir vorstellen, dass das ansible-wird-root-Passwort nur auf den zusammengehörigen Systemen gleich ist, und ich halt dasselbe Playbook dann mit unterschiedlichen inventories und unterschiedlichen Passworten aufrufe.
Meine Wünsche an ansible
Bei der Erstellung dieses Artikels ist mir aufgefallen, wie wenig sich die Leute, die solche hippen Softwareprojekte wie ansible machen oder benutzen, für Sicherheit interessieren. Dass ansible davon ausgeht, dass alle angesprochenen Systeme dasselbe ansible-wird-root-Passwort benutzen, ist natürlich ein Dauerbrenner in den Foren. Das finde ich genau so unbefriedigend wie die Standardlösung, sich dann halt irgendwo eine Tabelle mit den ansible-wird-root-Passworten zu hinterlegen und diese bestenfalls zu verschlüsseln. Dass dabei die durch unterschiedliche ansible-wird-root-Passworte gewonnene Sicherheit wieder zunichte gemacht wird, weil man durch das Bekanntwerden des Schlüssels für die zentrale Passworttabelle auf einen Schlag in den Besitz aller Passworte kommt, wird entweder nicht wahrgenommen oder ignoriert.
mehr als ein ansible-wird-root-Passwort
Dabei wäre es doch vermutlich mit vertretbarem Aufwand möglich, die Hosts im ansible-Inventory in Gruppen einzuteilen und dann beim Start eines Playbooks für jede Passwortgruppe (die im Extremfall nur einen Host als Member hat) ein ansible-wird-root-Passwort abzufragen.
Und ehe jetzt jemand kommt mit "schreib's doch": ansible ist in python geschrieben, einer Programmiersprache der ich aus Gründen lieber aus dem Weg gehe als dass ich sie benutzen möchte, und außerdem habe ich keine Zeit, mir noch eine weitere Baustelle in meinem Leben zu öffnen. Ich habe davon schon genug.
Signierte Playbooks
Und nun noch eine Spinnerei meinerseits, die mir so offensichtlich erscheint, dass ich mich wundere, dass darüber noch niemand geschrieben hat. Das Problem, das ich mit dem ansible-Verfahren oder Push-Methoden im allgemeinen habe ist, dass ein Angreifer es auf diese Weise leicht hat, ein System dazu zu bringen, ihm eine Hintertür zu öffnen. Bei vollautomatisch betriebenen Verfahren kann diese Hintertür trivial auf einer Vielzahl von Systemen ausgerollt werden.
Ein möglicher Weg, um diese Gefahr abzumildern wäre, dass der Autor eines Playbooks dieses Playbook nach seiner Fertigstellung signiert und der Prozess, der von ansible auf dem Zielsystem ausgeführt wird, die Signatur auf dem Playbook prüft, bevor er sie ausführt. Auf diese Weise ist das eigentliche Login auf dem Zielsystem deutlich weniger gefährlich, weil er nutzlos ist, wenn man keine signierten Playbooks erstellen kann. Und ich traue einem als root laufenden, tausendfach angeschauten Programmcode halbwegs zu, eine Signatur zu prüfen. Das ist ein gelöstes Problem, modulo dummer Programmierfehler kann da nichts passieren.
Allerdings ist es hierzu notwendig, einen (vertrauenswüridgen) Agenten auf dem Zielsystem zu installieren und eine Liste der Public Keys zu hinterlegen, deren Playbooks ausgeführt werden dürfen. Damit rückt man ein ordentlich großes Stück von ansibles größtem Vorteil ab, dass auf dem Zielsystem außer python nichts installiert sein muss. Und wenn man länger darüber nachdenkt, ist man schnell wieder beim Pull-Modell, und dann kann man auch gleich Puppet nehmen. Puppet spricht das Problem des "beliebigen Code ausführens" immerhin so weit an, dass nur Kataloge ausgeführt werden, die über eine Verbindung reinkamen, die qua SSL-Zertifika geprüft vom Puppet-Server kommen musste. Von "Code, der von einem zertifizierten Devops-Engineer signiert wurde" ist das zwar immer noch weit entfernt, aber besser als das, was Ansible Stand heute liefert, ist selbst der aktuelle Stand von puppet allemal.
So, und nun dürft ihr kommentieren und ich freue mich auf die Diskussion.
Comments
Display comments as Linear | Threaded