Aus Linux-Magazin 03/2021

PKI-Workshop, Teil 2: PKI mit Automatisierung und Infrastructure as Code

© wrightstudio, 123RF

Im letzten Artikel wurde erklärt, was eine PKI ist und wofür man sie verwendet. Jetzt geht es darum, wie man die richtigen Tools findet, um eine komplexe PKI möglichst automatisiert aufzubauen und zu betreiben.

Stellt man sich zu Anfang die Frage, welchen Aufwand man in Kauf nehmen will, braucht man zunächst ein Szenario für die Umsetzung. Für den vorliegenden Fall sieht es so aus: Eine Firma mit einer webbasierten Anwendung stellt einen Dienst zur Verfügung, der entweder direkt im Browser oder über eine REST-API von einem Drittanbieter (Reseller) per Smartphone konsumiert wird. Dabei benutzt die Anwendung ihrerseits Komponenten, die Dienstleister oder Zulieferer auf ihrer Infrastruktur betreiben und wiederum per REST-API anbieten.

Abbildung 1 stellt das schematisch dar. Die Pfeile (1) und (2) stehen für die Verbindungen, die ganz klassisch über HTTPS abgesichert sind. Das Zertifikat stammt beispielsweise von Let’s Encrypt. Die Kommunikation zwischen Front- und Backend (3) läuft über ein internes Netzwerk und soll über eine eigene, interne Zertifizierungsstelle abgesichert werden. Die Abfragen der Backends für die Smartphone-App laufen über einen öffentlich erreichbaren Endpunkt (4). Hier soll jedoch ein Mutual TLS eingesetzt werden, denn wenn die Abfragen von einem Reseller kommen, dann soll der dafür bezahlen. Dazu muss er eindeutig identifizierbar sein.

Abbildung 1: Der geplante Anwendungsfall in der schematischen Darstellung.

Abbildung 1: Der geplante Anwendungsfall in der schematischen Darstellung.

Einige spezielle Anforderungen implementiert die Kernanwendung nicht selbst. Stattdessen werden Firmen mit der Implementierung beauftragt, die sich in dem Spezialgebiet gut auskennen. Diese Teilkomponenten bieten ihrerseits wiederum eine REST-API an, die die Kernkomponente konsumiert (6). Auch dieser Endpunkt ist aus dem Internet erreichbar. Um sicherzustellen, dass es dabei keine unberechtigten Zugriffe gibt, kommt auch hier wieder Mutual TLS zum Einsatz. Um die Kernkomponente zu entlasten, wird dem Reseller der Smartphone-App gestattet, diese Teilkomponente auch seinerseits direkt zu verwenden (5). Zur Kontrolle und Steuerung des Reseller-Zugriffs auf die Teilkomponente dient hier ebenfalls Mutual TLS.

Wahl des richtigen PKI-Tools

Bevor wir das richtige Tool auswählen können, müssen wir unsere Anforderungen definieren. Die grundlegendste Anforderung lautet, dass das Werkzeug alle Funktionen beherrschen muss, die man für eine PKI braucht. Da wir den Anspruch haben, dem Ideal Infrastructure as Code zu folgen, brauchen wir etwas, das sich automatisieren lässt. Weil wir in Zukunft möglicherweise erhöhte Sicherheitsanforderungen haben, möchten wir eine Lösung, die sich mit einem Hardware Security Module (HSM) betreiben lässt. Der YubiHSM2 [1] hat ein sehr gutes Preis-Leistungs-Verhältnis und dient als Referenz.

Gehen wir die Optionen der Reihe nach durch: Die Tools von Let’s Encrypt sind zwar Open Source, aber nicht dafür gedacht, eine eigene CA zu betreiben oder Mutual-TLS-Setups zu realisieren. Let’s Encrypt stellt nur Zertifikate für Subjects aus, die sich unter einer DNS-Adresse erreichen lassen. Das hilft uns also nicht weiter.

Sieht man in die Paketliste einer Distribution, um nach PKI-Tools zu suchen, findet man recht schnell die beiden Tools XCA und Kleopatra. Das schon etwas betagte XCA hat eine Oberfläche, die etwas altbacken wirkt (Abbildung 2). Betrachtet man aber nur die Funktion, entspricht es dennoch unseren Anforderungen. Allerdings kennt XCA keinerlei Automatisierung und bietet auch keine APIs an, über die es sich in eine Automatisierung integrieren ließe. Es fehlt auch eine Unterstützung für ein HSM. Das Tool Kleopatra wirkt etwas moderner (Abbildung 3), hat aber dieselben Nachteile wie XCA.

Abbildung 2: Die etwas angestaubte Oberfläche von XCA.

Abbildung 2: Die etwas angestaubte Oberfläche von XCA.

Abbildung 3: Kleopatra wirkt moderner, aber auch ihm fehlen APIs für die Automatisierung.

Abbildung 3: Kleopatra wirkt moderner, aber auch ihm fehlen APIs für die Automatisierung.

EJBCA, eine Java-Application, läuft auf JBoss beziehungsweise WildFly und ist somit eine Webanwendung. Das Tool wird von namhaften Anwendern eingesetzt, etwa von Siemens oder der Regierung der Färöer-Inseln. EJBCA besitzt eine REST-API, die man zur Automatisierung nutzen kann, und es bietet Support für das YubiHSM2. Das klingt erst einmal vielversprechend, formal erfüllt EJBCA also alle Anforderungen.

EJBCA bietet selbst aber keine Automatisierung an, und die Konfigurationen liegen nicht so vor, dass man sie einfach versionieren, reproduzieren und parametrisieren kann. Durch die verwendete Technologie (Java plus Application-Server) ist das Tool zudem sehr ressourcenhungrig. Man kann davon ausgehen, dass pro CA-Instanz mindestens 1 GByte RAM und 1 GByte Storage im Leerlauf gebraucht werden. Dazu kommt noch die Datenbank, die EJBCA benötigt. Kurz: EJBCA ließe sich zwar verwenden, erscheint aber nicht besonders flexibel. Es bliebe schwierig, Änderungen zu reproduzieren und nachzuvollziehen.

Mit FreeIPA, genauer gesagt mit dessen Komponente DogTag, gibt es eine weitere CA als Open-Source-Software. Dank Red Hat kann man sogar Enterprise-Support dafür bekommen. FreeIPA, quasi das Open-Source-Pendant zu Active Directory, entstand bereits 1996 bei Netscape. Die Entwicklung von DogTag, der CA in FreeIPA, begann 2008. DogTag ist hauptsächlich in Java, Shell-Skripts und Python geschrieben. Gegen DogTag sprechen dieselben Argumente wie zuvor gegen EJBCA: zu schwergewichtig, schlechter Support für Automatisierung und obendrein keine Unterstützung für das YubiHSM2.

Damit kommen wir jetzt bei den Low-Level-Lösungen an: OpenSSL und CFSSL. Bei OpenSSL sollte man gar nicht erst um den heißen Brei reden oder versuchen, etwas zu beschönigen, seine Mängel sind allzu offensichtlich und allgemein bekannt: Usability und API sind inkonsistent und wenig intuitiv. Es gibt kein JSON/YAML-Format für die Konfiguration. States hält OpenSSL in einfachen Textdateien statt in Datenbanken, es ist auf Interaktion statt Automation ausgelegt. Es lässt sich nicht als (HTTPS)-Service mit REST-API betreiben und umfasst viele exotische und teils veraltete Funktionen und Optionen, die die Sicht auf das Wesentliche verstellen und die Bedienung erschweren. Die Code-Base ist uralt, mit allen dazugehörigen (Sicherheits-)Problemen.

Aber OpenSSL hat auch Vorteile: Es gibt eine Menge Literatur dazu sowie zahlreiche Leute, die sich damit auskennen und gegebenenfalls helfen können. Man kann OpenSSL mit ein paar Tricks auch in eine Automatisierung wie Ansible einbauen. Es ist sehr leichtgewichtig, und es gibt eine Anbindung für das YubiHSM2. Diese Gründe geben letztlich den Ausschlag für OpenSSL.

Bevor wir darauf eingehen, wie man mithilfe von OpenSSL zu einer hoch automatisierten PKI kommt, noch eine kurze Bemerkung zu dem als “CloudFlare’s PKI/TLS Swiss Army Knife” beworbenen CFSSL [2]. Das Tool ist ähnlich leichtgewichtig wie OpenSSL, macht aber einiges besser: Es ist in Go und Assembler programmiert, hat eine junge und schlanke Code-Base, das Interface und die Konfigurationen verwenden JSON, die Bedienung ist intuitiv und konsistent. Allerdings unterstützt CFSSL das YubiHSM2 nicht und scheidet damit aus.

Man kann immerhin hoffen, dass es stetig weiterentwickelt wird und weiter an Anhängerschaft gewinnt. Sicherlich unterstützt es derzeit nur einen Bruchteil der Funktionen, die OpenSSL zu bieten hat, aber es wäre kein Nachteil, wenn es sich auf die wirklich wichtigen Dinge konzentriert und der Code schlank und wartbar bleibt. Die Tabelle “Tools im Vergleich” stellt die einzelnen Werkzeuge noch einmal gegenüber.

Name

Funktionen

Automatisierung

YubiHSM2-Support

FreeIPA/DogTag

+

CFSSL

+

+/–

EJBCA

+

+/–

+

Kleopatra

+

Let’s Encrypt

+

OpenSSL

+

+/–

+

XCA

+

Automatisierung

Wir haben uns nun für OpenSSL entschieden, aber damit fehlt uns ein Framework, das uns bei der Automatisierung hilft. Lebten wir im Jahr 1995, würden wir wahrscheinlich zur Bash oder zu Perl greifen. Beide könnte man theoretisch nach wie vor nutzen, hätte dann aber bald dasselbe Problem wie mit den Systemen, die uns die Babyboomer zurücklassen, die jetzt nach und nach in Rente gehen: unwartbarer Code, den nur dessen Ersteller versteht. Das ist keine Option. Jeder, der DevOps betreibt, baut auf Systeme, die verschiedene Personen verstehen müssen.

Wer sich auf der Devel-Seite [3] zum YubiHSM2 umsieht, der entdeckt, dass es eine Python-Library für YubiHSM2 gibt und daneben auch ein OpenSSL-Binding. Also warum nicht Python verwenden? Für Entwickler wäre das eine naheliegende Frage. Kommen die Zertifikate ausschließlich innerhalb einer Applikation zum Einsatz, wie etwa in einem Kubernetes-Cluster, der seine eigene CA mitbringt, wäre die Entscheidung für Python wahrscheinlich valide.

In unserem Anwendungsfall gehen wir jedoch davon aus, dass wir eine PKI für die gesamte Firma einschließlich aller Zulieferer und Channel-Partner betreiben wollen. Das heißt, dass verschiedenste Personen in den unterschiedlichsten Umgebungen damit arbeiten können müssen, also sowohl Administratoren als auch Entwickler. Man kann nicht davon ausgehen, dass ein Administrator so tief in eine Programmiersprache eintaucht, dass er komplexen Code zu lesen und zu schreiben vermag. Es steht auch nicht zu erwarten, dass ein Entwickler sich in komplexe Netzwerktopologien und Pacht-Managements vertiefen will. Wir brauchen also eine Lösung, die eine Schnittmenge aus beiden Welten abbilden kann.

An dieser Stelle kommt Ansible ins Spiel. Es bringt schon ein klares Konzept und Gerüst mit, in dem sowohl der Administrator als auch der Entwickler seine Sicht auf die Welt wiederfindet. Einige Dinge sind dabei immer gleich: Um das Zielsystem administrieren zu können, benötigt man in der Regel eine SSH-Verbindung. Die verwalteten Systeme gleichen sich in manchen Eigenschaften, in anderen weichen sie voneinander ab. Entsprechend sind die Schritte, die zu einem bestimmten Zustand führen, größtenteils gleich, aber nicht immer. Zudem gibt es oft eine Wechselwirkung zwischen verwalteten Systemen.

Alle diese Punkte treffen auch auf unsere PKI zu. Deshalb kann uns Ansible helfen, das Thema strukturiert zu lösen. Bevor wir auf die konkrete Implementierung der PKI eingehen, seien kurz einige grundlegende Konzepte in Ansible erklärt. Zunächst ist da das Inventory-File: Es führt alle Systeme auf, die es zu verwalten gilt, entweder im INI-Stil oder im YAML-Stil. Da der komplette Rest des Ansible-Codes ausschließlich YAML verwendet, gehen wir hier nur auf die YAML-Variante ein.

Listing 1 zeigt ein Beispiel für ein Inventory. Das »all« in der ersten Zeile repräsentiert die Eigenschaften aller Systeme, die weiter unten folgen. Zum Beispiel könnten sie alle eine Zeitzone gemeinsam haben. Die Zeilen 3 und 12 teilen die Systeme in Webserver und Datenbanken auf. Die einen brauchen zum Beispiel PHP, die anderen MariaDB. In Zeile 5 und 8 wird weiter spezifiziert: Die eine Gruppe bekommt eine WordPress-Installation, die andere eine Joomla-Installation. Am Ende des Baums – quasi als Blatt – folgen die konkreten Hosts (Zeilen 7, 10, 11, 14 und 15). Es handelt sich hierbei um die Domain-Namen (FQDNs), die Ansible verwendet, um sich via SSH zu verbinden. Die Doppelpunkte nach dem FQDN sind kein Versehen, sondern zwingend notwendig.

Listing 1

Inventory-File

all:
  children:
    phpwebserver:
      children:
        wordpress:
          hosts:
            weblog.dum.my:
        joomla:
          hosts:
              products.dum.my:
              company.dum.my:
    mariadb:
      hosts:
        joomla-backend.dum.my:
        wordpress-backend.dum.my:

Das zweite wichtige Ansible-Konzept sind die Roles. Sie beschreiben die konkreten Schritte, die auf einem System auszuführen sind. Eine minimale Role könnte zum Beispiel aus dem Verzeichnis »phpwebserver/« bestehen, das wiederum ein Verzeichnis »tasks/« enthält, das seinerseits die Datei »main.yml« beinhaltet. Das Ganze liegt in einem Verzeichnis namens »/roles/« (Listing 2).

Listing 2

Roles

/roles
  /phpwebserver
    /tasks
      main.yaml

Ansible sucht in »tasks/« nach der Datei »main.yaml« und führt sie aus. Diese Datei enthält ihrerseits eine Beschreibung von Tasks, die in der Reihenfolge abzuarbeiten sind, in der sie aufgeschrieben wurden. Das Beispiel aus Listing 3 installiert PHP und den Webserver Apache. Zeile 2 ruft das Ansible-Module »package« auf; Zeile 3 spezifiziert über einen Parameter, um welches Package es sich handelt. Um PHP und Apache in einem Schritt zu installieren, enthält Zeile 5 noch eine Schleife über eine Liste mit den Namen der einzurichtenden Pakete. Die Installation wird so oft ausgeführt, wie sich Elemente in der Liste finden. Dabei wird »item« jedes Mal durch einen anderen Wert ersetzt.

Listing 3

Ansible-Tasks

- name: install webserver packages
  package:
    name: "{{ item }}"
    state: present
  loop:
    - "php"
    - "httpd"

Die Beispiele zeigen, wie Ansible komplexe Sachverhalte in Modulen abstrahiert und deren Verhalten über Parametrisierung steuert. Dabei soll der Anwender nur noch den logischen Fluss im Auge haben, ohne auf die konkrete Umsetzung achten zu müssen. Je nachdem, um was für ein Linux-System es sich handelt, kümmert sich Ansible selbstständig darum, den richtigen Paketmanager mit den korrekten Parametern aufzurufen. Ansible findet zuverlässig heraus, in welcher Umgebung es läuft – dieses Wissen lässt sich nutzen. Die Informationen, die Ansible über seine Umgebung sammelt, speichert es in der Variablen »Facts«. Daten, die darüber hinaus gebraucht werden und die Ansible nicht wissen kann, lassen sich in selbst definierten Facts speichern.

Jetzt ist es an der Zeit, in einem Playbook eine Hostgroup mit einer Rolle zu verknüpfen. Den Name für das Playbook kann man frei wählen, den Inhalt für unser Beispiel zeigt Listing 4. In Zeile 1 geben wir die Gruppe »phpwebserver« an. Gemäß Listing 1 gehören dazu die drei Server »company.dum.my«, »products.dum.my« und »weblog.dum.my«. Auf allen Servern meldet sich Ansible per SSH an und führt sämtliche Kommandos aus, die in der Role »phpwebserver« stehen. Damit wäre das Grundkonzept von Ansible umrissen.

Listing 4

Playbook

- hosts: phpwebserver
  roles:
    - phpwebserver

Die Website von Ansible [4] führt Hunderte von Modulen auf, die Dinge erleichtern sollen, darunter auch die Module »openssl_certificate3«, »openssl_csr4«, »community.crypto.openssl_privatekey5« und »community.crypto.x509_certificate6« für OpenSSL.

Durch die YAML-API wird dann die unschöne OpenSSL-API wegabstrahiert, wie es Listing 5 zeigt. Dieser Vorteil erweist sich aber gleichzeitig auch als Nachteil: Die meisten Anleitungen und Hilfestellungen im Internet finden sich für das originale OpenSSL-Tool. Man muss also doppelte Arbeit leisten: Zuerst gilt es zu verstehen, wie es OpenSSL macht; dann muss man antizipieren, wie die Ansible-Module davon abstrahieren. Im Fall eines Problems muss man prüfen, ob man sowohl das Konzept von OpenSSL als auch das jenes des Ansible-Moduls richtig verstanden und angewendet hat. Da bei einer PKI mehrere Schritte aufeinanderfolgen oder ineinandergreifen, hilft es bei der Fehlersuche, dass man die Zwischenschritte und -ergebnisse einzeln auf Richtigkeit prüfen kann.

Listing 5

OpenSSL-Beispiel

# Generate an OpenSSL Certificate Signing Request with Subject information
- openssl_csr:
  path: /etc/ssl/csr/www.ansible.com.csr
  privatekey_path: /etc/ssl/private/ansible.com.pem
  country_name: FR
  organization_name: Ansible
  email_address: jdoe@ansible.com
  common_name: www.ansible.com

Bei einem so komplexen Thema wie der PKI kommt es vor, dass man zu einem bestimmten Aspekt eine zweite Meinung einholen will, weil man nicht erwarten kann, dass die OpenSSL/PKI-Experten auch Ansible-Experten sind. Dazu müsste man für sein Anliegen den Ansible-Code wieder zurück auf das OpenSSL-Tool portieren.

Bei all dem ist keineswegs gewiss, dass es nicht Probleme gibt, die sich nur direkt mit dem OpenSSL-Tool lösen lassen, etwa bei der Anbindung des YubiHSM2. So habe ich mich im vorliegenden PKI-Projekt dazu entschlossen, das OpenSSL-Tool direkt aus Ansible heraus aufzurufen. Im Beispiel aus Listing 6 erstellt Zeile 2 einen neuen Schlüssel; die Zeilen 8 bis 12 prüfen das Ergebnis.

Listing 6

Low-Level-Aufruf von OpenSSL

- name:  Generate a private key with the defaults
  shell:    openssl genrsa -out {{ pki_priv_key }}
  args:
    chdir: "{{ pki_private_dir }}"
    creates:  "{{ pki_priv_key }}"
- name:  View the content of private key
  shell: |
    openssl rsa \
    -noout \
    -text \
    -in {{ pki_priv_key }}

Absicherung der CD/CI

Jetzt haben wir mit Ansible ein Werkzeug, das uns dazu befähigt, eine PKI nach dem Prinzip Infrastructure as Code zu erstellen. Damit fehlt nur noch die CI/CD-Komponente. Dafür taugt das sehr bekannte Hudson/Jenkins allerdings nicht. Als es Mitte der 2000er-Jahre entwickelt wurde, war das eine echte Pionierleistung. Heute, eineinhalb Jahrzehnte später, sieht die IT-Welt ganz anders aus.

Trotz aller Modernisierungsbemühungen merkt man Hudson/Jenkins an vielen Stellen an, dass es aus der Zeit gefallen ist. So lassen sich Jenkins-Jobs nur schwer oder gar nicht zu 100 Prozent als Code definieren. Somit würden wir das, was wir mit Ansible erreicht haben, mit Jenkins wieder zunichte machen. Stattdessen empfehle ich den Einsatz von Werkzeugen wie GitLab-CI, Buildbot [5] oder Concourse-CI [6].

Für welches CI/CD-Tool man sich auch immer entscheidet: Es stellt sich die Frage, wie man die Automatisierung einrichtet, ohne die Integrität der gesamten PKI zu gefährden. Wir brauchen also ein Konzept, das die Automatisierung absichert, damit die ganze Arbeit mit der PKI nicht umsonst war, weil die CI/CD-Pipeline gehackt wurde.

Dreh- und Angelpunkt ist hier das Git-Repo. Als erste Sicherheitsmaßnahme kommt ein Werkzeug wie GitLab oder Gitea zum Einsatz, um den Zugang zum Git-Repository zu beschränken. Der nächste Schritt ist, alle Commits vor dem Bearbeiten signieren zu lassen. Dann kann ein CI/CD-Tool wie Jenkins, Concourse-CI, AWX oder GitLab-CI beim Auschecken des Ansible-Playbooks den Fingerprint des Commits prüfen (Abbildung 4). Im Ansible-Playbook sieht der Schritt so aus, wie in Abbildung 5 gezeigt.

Abbildung 4: Ein Übersichtsdarstellung des Prozesses zum Signieren der Zertifikate.

Abbildung 4: Ein Übersichtsdarstellung des Prozesses zum Signieren der Zertifikate.

Abbildung 5: Die Prüfung des Fingerprints des Commits im Ansible Playbook.

Abbildung 5: Die Prüfung des Fingerprints des Commits im Ansible Playbook.

Den Certificate Signing Request (CSR) könnte man ebenfalls in Git einchecken. In konkreten Fall tauschen die Zertifikate die Nehmer- und Geber-CSR sowie deren Zertifikate über HTTP aus. Beide verwenden einen Nginx-Webserver, über den sie beides zum Download anbieten. Diese Lösung ist dynamischer.

Public Key, CSR und signiertes Zertifikat stellen kein Geheimnis dar, das geschützt werden muss, im Gegensatz zum Private Key, der seinen Eigentümer niemals verlassen sollte. Wichtig ist auch, dass der Zertifikatsaussteller (CA) überprüfen kann, dass die Identität im CSR zum darin enthaltenen Key passt, dass also ein Antragsteller sich nicht ein Zertifikat für eine fremde Identität ausstellen lassen kann. In unserem PoC vertrauen die CAs der IP-Adresse.

Kann man den IP-Adressen nicht trauen und setzt auf Domains, bräuchte man zur Absicherung HTTPS. Dann hätte man aber unter Umständen ein Henne-Ei-Problem und müsste sich einen anderen Weg überlegen, wie man die Glaubwürdigkeit des CSR absichert – etwa, indem man das CSR doch in Git eincheckt. Ungeachtet dessen können die CAs aber weiterhin ihre Zertifikate den Eigentümern über HTTP zum Download anbieten, sofern die CA prinzipiell für die Zertifikatseigentümer erreichbar ist. Davon gehen wir im Weiteren aus.

Ausblick

Nach der Auswahl der Tools und ihrer Vorstellung geht es im dritten Teil dieser Artikelserie in der nächsten Ausgabe noch detaillierter darum, wie die Verwirklichung des Beispielprojekts konkret vonstatten geht. Außerdem erklären wir, was ein Hardware Security Module (HSM) ist, und zeigen anhand von YubiHSM2, wie man eine solche kostengünstige Lösung in die PKI intrigiert. (jcb/jlu)

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 6 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben