Oft brauchen Anwendungen Passwörter, etwa um sich Servern gegenüber auszuweisen. Diese geheimen Daten möchte man nicht im Klartext speichern, aber auch nicht jedes Mal neu eintippen. Was tun?
Das Problem stellt sich gerade auch beim Entwickeln in Teams: Hier müssen alle Teammitglieder bestimmte Passwörter kennen, um sich gegenüber Servern auszuweisen. Im einfachsten Fall ist das beispielsweise die Datenbank einer Webapplikation, die eine Authentifizierung verlangt. Die Herausforderung besteht nun darin, diese geheimen Informationen auf effiziente, aber auch sichere Weise zu teilen und später auch automatisch zu übermitteln. Wie stellt man das am besten an?
Drei Ansätze
Traditionell gibt es mehrere Ansätze, um Geheimnisse zu verwalten, die mit einer Applikation verbunden sind. Die wohl älteste Methode sind Konstanten im Quellcode:
var PASSWORD = 'sn4k3oil'
Mit dieser Methode wird die Herausforderung, geheime Informationen zwischen allen Teammitgliedern synchron zu halten, auf einfache, allerdings nicht auf sichere Weise gelöst.
Denn zum einen ist so jeder, der Zugriff auf den Quellcode hat, auch automatisch im Besitz der geheimen Informationen – unabhängig davon, ob das nötig oder wünschenswert ist. Zum anderen kann sich das Austauschen von Schlüsseln mit diesem Verfahren als mühsam entpuppen, weil jedes Mal der Quellcode zu ändern, in die Versionsverwaltung einzuchecken und neu zu verteilen ist. Auch ist es damit schwierig, den gleichen Code in mehreren Umgebungen zu nutzen, etwa in einer Entwicklungsumgebung und im produktiven Umfeld.
Diese Nachteile will eine ausgereiftere Alternative zu diesem Verfahren vermeiden. Sie speichert die Geheimnisse in Konfigurationsdateien, die nicht in die Versionskontrolle eingecheckt sind. Wer diesen Ansatz nutzt, kann die Konstanten aus dem Quellcode entfernen, nimmt aber andere Nachteile in Kauf. Ohne Versionskontrolle fällt auch die Synchronisierung zwischen den Teammitgliedern weg, die zuvor inhärent gegeben war. Will ein Teammitglied das Passwort ändern, muss es alle anderen darüber informieren und dafür einen sicheren Weg finden. Darüber hinaus führt dieses Vorgehen dazu, dass unverschlüsselte Dateien auf Clients und Servern liegen, die Nutzer dort vergessen können.
In der Folge hat sich eine dritte Methode etabliert, um geheime Informationen sowohl vom lokalen Dateisystem als auch vom Quellcode fernzuhalten. Dieses Verfahren verwendet Umgebungsvariablen, die dann wie in diesem Javascript-Beispiel auszulesen sind:
var PASSWORD = process.env.PASSWORD
Diese Vorgehensweise sorgt dafür, dass weder im Quellcode noch im Dateisystem geheime Informationen im Klartext vorzufinden sind. Hinzu kommt, dass der Code durch die einfache Konfigurierbarkeit problemlos in unterschiedlichen Umgebungen parallel laufen kann. Allerdings wird die Applikation komplizierter zu verwenden – um sie starten zu können, muss der Nutzer sicherstellen, dass die durch Umgebungsvariablen konfigurierten Werte in der Laufzeitumgebung verfügbar sind.
Alle drei bislang vorgestellten Ansätze beschreiben jeweils einen Kompromiss zwischen der Fähigkeit, die Geheimnisse zu synchronisieren, und der sicheren Speicherung. Optimal wäre aber ein Verfahren, das einen synchronisier- und teilbaren, aber trotzdem verschlüsselten Speicherplatz zur Verfügung stellt.
Jedes Teammitglied müsste dabei in der Lage sein, Informationen aus dem Speicher zu entschlüsseln. Dafür soll aber kein Passwort nötig sein, das sich alle Teammitglieder teilen, denn das führt wieder zu Problemen beim Verteilen und Ändern dieses Passworts. Es ist aber auch eine passwortlose Lösung möglich, und sie lässt sich mit ein paar Open-Source-Tools sogar selber bauen.
Ein Abstecher zu GPG
Eines der verwendeten Tools ist GPG (GNU Privacy Guard), die Implementierung einer PKI (Public-Key-Infrastruktur). Bekannt ist GPG vor allem durch das Verschlüsseln und Signieren von E-Mails. Wer ein GPG-Zertifikat erstellt, erzeugt dabei einen öffentlichen und einen privaten Schlüssel. Mit diesen Schlüsseln sind verschiedene Operationen der asymmetrischen Kryptographie möglich. Darüber hinaus umfasst ein solches Zertifikat eine eindeutige Kennung, die so genannte GPG-ID. Sie dient der sicheren Referenzierung eines Zertifikats, was später noch eine wichtige Rolle spielt.
Informationen, die jemand mit dem öffentlichen Schlüssel eines GPG-Zertifikates verschlüsselt, kann ein anderer ausschließlich mit dem zugehörigen Privatschlüssel wieder entschlüsseln. Teilt ein Zertifikatsinhaber den öffentlichen Schlüssel mit anderen, ist es denen möglich, ihm vertrauliche Informationen verschlüsselt zukommen zu lassen. Solange der Empfänger den Privatschlüssel nicht teilt, kann ausschließlich er alleine seine Informationen lesen.
Der Passwortmanager Pass
Das zweite Tool, das für die avisierte Methode zum sicheren Teilen von Geheimnissen gebraucht wird, ist der Passwortmanager Pass [1]. Im Wesentlichen stellt er einen Wrapper um GPG dar. Mit Pass lassen sich Dateien in einem Verzeichnis mit mehreren öffentlichen GPG-Schlüsseln gleichzeitig verschlüsseln. Dadurch können mehrere Personen simultan auf die verschlüsselten Dateien in diesem Verzeichnis zugreifen und sie entschlüsseln.
Pass nutzt dazu die Fähigkeiten von GPG, einzelne Dateien für beliebig viele Empfänger simultan zu verschlüsseln. Hierzu verschlüsselt GPG die Datei selber zunächst mit einem ad hoc generierten symmetrischen Schlüssel. Dieser wird dann für jeden Empfänger jeweils mit dessen öffentlichem Schlüssel verschlüsselt und in dieser verschlüsselten Form dem Header der Datei hinzugefügt. Dadurch enthält der Dateiheader nach der Verschlüsselung mit GPG die Informationen zum Entschlüsseln für jeden Empfänger.
Das Verzeichnis mit den verschlüsselten Files lässt sich bequem über ein Versionskontrollsystem verwalten, versionieren und verteilen. Mit Hilfe von Pass wird im Rahmen der folgenden Beispielanwendung ein verschlüsselter Pool an Informationen erstellt, ohne auf die Verwendung eines geteilten Geheimnisses, etwa eines Passworts, zurückzugreifen. Die Installation von Pass gelingt denkbar einfach mittels der jeweiligen Paketverwaltung der verwendeten Distribution.
Als Beispiel dient in diesem Beitrag eine Statusseite, die einige Statistiken über einen bestimmten Github-Benutzer interaktiv anzeigt. Dazu sind Github-Benutzername sowie ein API-Token notwendig. Dies sind die geheimen Informationen, die es zu schützen gilt.
Die Anwendung selbst ist in Javascript geschrieben und auf dem Platform-as-a-Service-Hoster Heroku ([2], Abbildung 1) gehostet. Weil das Beispiel bestehende Infrastruktur nutzt, kann es sich auf die Geheimnisverwaltung konzentrieren.
Heroku als PaaS-Hoster
Das Beispiel setzt auf Platform as a Service (PaaS), weil der Nutzer dabei weder Bare-Metal-Server noch virtuelle Maschinen verwalten muss. Alles spielt sich auf Applikationsebene ab. Heroku ist eines der Urgesteine des PaaS-Markts und bietet sich besonders für einfache Beispiele – wie in diesem Falle – an.
Um ein bestehendes Projekt auf Heroku zu starten, reicht es, einen Account auf der Heroku-Webseite zu erstellen, das Heroku-CLI-Tool herunterzuladen und sich einzuloggen (Listing 1).
Listing 1
Heroku-Login
01 $ heroku login 02 Enter your Heroku credentials: 03 Email: reader@linuxmagazin.de 04 Password: ************************* 05 Logged in as reader@linuxmagazin.de
Bevor der Nutzer eine Anwendung in der Heroku-Cloud betreiben kann, braucht er ein lokales Git-Repository. Mit Hilfe dieses Repository lässt sich dann die Applikation bei Heroku registrieren (Listing 2). Danach ist die neue Applikation unter dem Namen »linux-magazin-secrets«) online verfügbar.
Listing 2
Applikation erstellen
01 $ cd ~/code/app/linux-magazin-secrets 02 $ heroku create linux-magazin-secrets 03 Creating linux-magazin-secrets... done 04 https://linux-magazin-secrets.herokuapp.com/ | 05 https://git.heroku.com/linux-magazin-secrets.git
Die Applikation hat in dieser Rohversion noch keine Funktionalität. Die folgenden Schritte fügen die nötigen Dateien hinzu, um die Applikation lauffähig zu machen. Zuerst muss der Anwender eine Prozessdefinition anlegen, die beschreibt, welche Prozesse Heroku starten und verwalten soll. Für eine Javascript-Applikation ist das denkbar einfach. Der Dateiname muss »Procfile« lauten und im Wurzelverzeichnis des Repository liegen.
Die Datei weist die Plattform an, einen Service mit dem Namen »web« zu verwalten. Der Service besteht aus einem Node.js-Prozess, der die Datei »index.js« ausführt.
web: node index.js
Damit die Javascript-Anwendung auf Heroku lauffähig ist, benötigt diese noch eine Paketdefinition. Sie ist in erster Linie wichtig, um Javascript-Paketabhängigkeiten zu definieren, die die Plattform automatisch installiert.
Da die Applikation einen HTTP-Endpunkt zur Verfügung stellt, benötigt sie ein Web-Framework. Die Javascript-Bibliothek »express.js« bietet sich dafür an und ist deshalb in der Paketdefinition als Abhängigkeit definiert. Abzulegen ist diese Datei ebenfalls im Wurzelverzeichnis, aber unter dem Namen »package.json« (Listing 3).
Listing 3
package.json
01 {
02 "name": "linux-magazin-secrets",
03 "version": "1.0.0",
04 "main": "index.js",
05 "license": "ISC",
06 "dependencies": {
07 "express": "^4.14.0"
08 }
09 }
Der eigentliche Server-Endpunkt befindet sich in der Datei »index.js«. Aufbauend auf dem Web-Framework »express.js« implementiert er einen HTTP-API-Endpunkt, der die Informationen über den Github-Benutzer ausliefert, die das Frontend braucht (Listing 4).
Listing 4
index.js
01 var express = require('express')
02 var app = express()
03
04 var user = process.env.GITHUB_USER
05 var apiToken = process.env.GITHUB_API_TOKEN
06
07 var port = process.env.PORT || 5000
08
09 app.get('/', function (req, res) {
10 // Implement Github API call
11 })
12
13 app.listen(port, function () {
14 console.log('App listening on port ' + port)
15 })
Die Anwendung wird über drei Umgebungsvariablen konfiguriert. Die Variable »PORT« stellt die Laufzeitumgebung der Plattform automatisch zur Verfügung. Die Konvention ist, dass ein Service auf diesem Port lauschen muss, um auf Heroku lauffähig zu sein. Die beiden anderen Variablen, »GITHUB_USER« und »GITHUB_API_TOKEN« stellen die geheimen Informationen dar, die der Applikation zur Verfügung zu stellen sind.
Um nun den aktuellen Stand auf Heroku zu deployen, kann ein einfaches »git push« zum Einsatz kommen. Das passende Git Remote Repository mit dem Namen »heroku« wurde bereits durch den vorherigen Aufruf von »heroku create« automatisch konfiguriert:
$ git push heroku master:master
Im Prinzip ist dies bereits alles, um die Anwendung zum Laufen zu bringen. Da aber die beiden Github-Umgebungsvariablen nicht initialisiert sind, wird die Applikation allerdings mit einem Fehler abbrechen.
Das Heroku-CLI erlaubt es, Konfigurationen einzurichten, die die Applikation in Form von Umgebungsvariablen zur Verfügung gestellt bekommt. Änderungen an dieser Konfiguration bewirken automatisch einen Neustart aller Services. So lassen sich die beiden fehlenden Variablen mit dem folgendem Kommando einrichten:
$ heroku config:set GITHUB_USER="holderbaum" GITHUB_API_TOKEN="sn4k3oil"
Damit ist das Ziel erreicht, keine geheime Information im Quellcode oder in anderen Dateien abzulegen. Doch müssen für die Ausführung des vorherigen Konfigurationsbefehls das API-Token und der Benutzername bekannt sein.
Es ist nicht empfehlenswert, geheime Informationen einfach so in die Shell zu tippen, weil sie in der Shell-Historie für lange Zeit verbleiben. Daher gilt es, einen sicheren Weg zu finden, die geheimen Informationen zu speichern und an die Personen zu verteilen, die die Applikation konfigurieren sollen. An dieser Stelle kommt der Passwortmanager ins Spiel.
Das Verzeichnis als Geheimnis-Pool
Wie bereits erwähnt, wird Pass auf ein einzelnes Verzeichnis angewendet. Zur Initialisierung eines solchen Speichers legt der Anwender den Verzeichnispfad in einer Umgebungsvariablen ab. Das gelingt ihm zum Beispiel mit:
$ export PASSWORD_STORE_DIR=~/code/app/secrets $ export MY_ID=5244D411CD7CBA95 $ pass init $MY_ID
Das für »pass init« verwendete Argument ist die GPG-ID des Autors. Es kann aber auch eine beliebige Liste von IDs übergeben werden. Um das Beispiel einfach zu halten, verwendet es aber zunächst eine einzelne ID.
Wer dem Speicher Werte hinzufügen will, nutzt »pass add«. Es erwartet als Argument den Namen des Geheimnisses, das dem Speicher hinzuzufügen ist. Via Standardeingabe fragt es nach dem eigentlichen Geheimnis, also beispielsweise dem Passwort oder API-Token, das es verschlüsselt zu speichern gilt:
$ export PASSWORD_STORE_DIR=~/code/app/secrets $ pass add production/user $ pass add production/api_token
Um nun ein Geheimnis wieder auszugeben, gibt es den Befehl »pass show«. Sofern der aufrufende Nutzer im Besitz des Privatschlüssels aus einem der bei »init« angegebenen Zertifikate ist, entschlüsselt der Aufruf des Befehls das gespeicherte Geheimnis und gibt es auf der Standardausgabe aus:
$ export PASSWORD_STORE_DIR=~/code/app/secrets $ pass show production/api_token "sn4k3oil"
Bisher wurde ein Passwortspeicher erstellt, der sich in einem einzelnen Verzeichnis befindet und auf separaten Dateien basiert. Daher lässt er sich hervorragend mit Hilfe einer Versionskontrolle im Team verteilen.
Das Verzeichnis »~/code/myapp/secrets« sieht nun wie folgt aus:
$ find ~/code/app/secrets ~/code/app/secrets/.gpg_id ~/code/app/secrets/production/user.gpg ~/code/app/secrets/production/api_token.gpg
Die »*.gpg«-Dateien enthalten die zuvor hinzugefügten verschlüsselten Geheimnisse. Die Datei ».gpg_id« beinhaltet die Liste der zum Verschlüsseln des Speicherinhalts verwendeten GPG-IDs.
Es ist darüber hinaus möglich, weitere ».gpg_id«-Dateien in Unterverzeichnissen zu platzieren. Somit lassen sich jeweils unterschiedliche Zugriffsberechtigungen für verschiedene Unterverzeichnisse realisieren. Natürlich ist kein Tool wie »find« zwingend, um den Inhalt eines solchen Speichers aufzulisten.
Das Kommando »pass ls« gibt die Baumstruktur des Speichers auf der Standardausgabe aus:
$ export PASSWORD_STORE_DIR=~/code/app/secrets
$ pass ls
+-- production
|-- api_token
+-- user
Durch die Kombination des auf Pass basierenden Speichers mit dem Heroku-CLI lässt sich die Applikation konfigurieren, ohne dass die Eingabe expliziter Geheimnisse notwendig wird (Listing 5).
Listing 5
Variablenkonfiguration
01 export PASSWORD_STORE_DIR=~/code/app/secrets 02 export USER=`pass show production/user` 03 export TOKEN=`pass show production/api_token` 04 heroku config:set GITHUB_USER=$USER \ 05 GITHUB_API_TOKEN=$TOKEN
Damit ist ein Speicher für Geheimnisse entstanden, der allerdings nur einer einzelnen Person Zugriff auf die gespeicherten Geheimnisse gewährt. Das Ziel besteht jedoch nach wie vor darin, die Geheimnisse für ein ganzes Team zu verwalten.
Ein Speicher für Geheimnisse, der nur einer einzelnen Person das Entschlüsseln erlaubt, ist nützlich für persönliche Projekte, bei denen kein Team involviert ist. Mehr ist allerdings zu beachten, sobald sich ein Projekt auf mehrere Personen ausweitet.
Teamwork
Zwei Gelegenheiten sind für die Sicherheit besonders wichtig: Roll-on und Roll-off. Bei einem Roll-on wird der vertrauenswürdige Kreis von Teammitgliedern um eine Person erweitert. Das bedeutet im Regelfall, dass das neue Teammitglied bestimmte Zugriffsberechtigungen bekommt, etwa den Zugriff auf Projektgeheimnisse oder Infrastruktur.
Mit Pass ist dies mühelos zu bewerkstelligen: Dazu ist nur die GPG-ID des neuen Mitglieds und der zugehörige öffentliche Schlüssel nötig. Damit ruft man dann erneut »pass init« mit allen IDs auf, einschließlich der neuen. Pass verschlüsselt alle Informationen im Speicher mit den öffentlichen Schlüsseln der angegebenen IDs neu (Listing 6).
Listing 6
Neuverschlüsselung
01 export PASSWORD_STORE_DIR=~/code/app/secrets 02 export MY_ID=5244D411CD7CBA95 03 export ADAS_ID=44A7B1E354AF81E2 04 export ALANS_ID=BA29EE533AF39B21 05 pass init $MY_ID $ADAS_ID $ALANS_ID
Für den Zugriff auf den geheimen Speicher reicht das. Dieser Vorgang lässt sich natürlich für jedes neue Teammitglied analog wiederholen. Jeder, der dem Speicher vertrauliche Daten hinzufügen möchte, muss im Besitz aller öffentlichen Schlüssel sein, die beim Initialisieren anzugeben sind. Nur so lassen sich die Daten beim Hinzufügen korrekt verschlüsseln.
Beim Roll-off eines Teammitglieds scheidet die fragliche Person aus und darf danach keinen Zugriff auf die vertraulichen Geheimnisse mehr haben. Hierzu ruft man wieder »pass init« auf, lässt die entsprechende GPG-ID aber weg.
Auch hier verschlüsselt Pass alle Geheimnisse innerhalb des Speichers neu, jedoch diesmal ohne die ID, die weggelassen wurde. Zu beachten ist dabei, dass die Neuverschlüsselung nur bewirkt, dass der ehemalige Mitarbeiter von jetzt an keine Geheimnisse mehr entschlüsseln kann. Behält er aber eine Kopie des Repository vor der Neuverschlüsselung, hat er auch weiterhin Zugriff auf die Geheimnisse.
Aus diesem Grund muss die Änderung aller geheimen Informationen ein fester Bestandteil des Roll-off sein. Die Rede ist dann von der so genannten Rotation von Geheimnissen. Da Pass die Geheimnisse an einem zentralen Ort aufbewahrt – dem angelegten Speicher –, lässt sich deren Rotation nach einem Roll-off deutlich leichter bewältigen, als wenn sie wahllos über das Projekt verteilt wären.
Fazit
Das Beispiel zeigt einen verschlüsselten Speicher für geheime Informationen in einem gewöhnlichen Verzeichnis. Dieses Verzeichnis lässt sich wie Quellcode verteilen, etwa durch das Einchecken in die Versionskontrolle.
Dank Pass lassen sich Zu- und Abgänge von Teammitgliedern weitgehend automatisch behandeln, ohne dabei die Sicherheit zu beeinträchtigen. Darüber hinaus ist der Zugriff leicht automatisierbar, da der Anwender über eine einfache Kommandozeile ein beliebiges Geheimnis direkt auf der Standardausgabe ausgegeben kann.
Infos
- Pass: https://www.passwordstore.org
- Heroku: https://www.heroku.com








