Sicherheit

Wir haben bereits erwähnt, dass CouchDB noch in der Entwicklung ist und das seit dem Erscheinen dieses Buchs weitere Funktionen hinzugekommen sein können. Das gilt insbesondere für die Sicherheitsmechanismen in CouchDB. In der aktuellen Version 0.10.0 gibt es rudimentäre Sicherheitsfunktionen, doch während wir dies schreiben werden Erweiterungen bereits diskutiert.

In diesem Kapitel wollen wir uns die grundlegenden Sicherheitsmechanismen in CouchDB ansehen: die Admin Party, Basic Authentication, Cookie Authentication und OAuth.

Die Admin Party

Wenn man CouchDB gerade installiert hat darf jeder Request von jedem ausgeführt werden. Eine Datenbank anlegen? Kein Problem. Ein paar Dokumente löschen? Schon weg. In CouchDB nennt man das die Admin Party. Jeder darf alles. Nette Sache.

Während es unglaublich einfach ist, mit CouchDB so zu starten, so sollte es ebenfalls klar sein, das eine solche Installation nicht ins Internet gehört. Jeder böswillige oder schlecht geschriebene Client kann vorbeikommen und mal eben eine Datenbank löschen.

Immerhin nutzt CouchDB standardmäßig nur das Loopback Interface (127.0.0.1 oder localhost). Somit kann man nur selber mit CouchDB sprechen und niemand sonst. Doch irgendwann wird die CouchDB Instanz an das offene Internet angeschlossen (z.B. indem CouchDB an der öffentlichen IP Adresse lauscht) und spätestens dann möchte man den Zugriff beschränken, damit die Admin Party nicht mutwillig zerstört wird.

In den vorangegangen Kapiteln haben wir immer mal wieder erwähnt, wie man CouchDB ohne Admin Party betreibt. Zunächst gibt es den admin, der eine Art von Superuser beschreibt. Dann gibt es Privilegien. Schauen wir uns das genauer an.

CouchDB kennt das Konzept eines Administrator (oder auch Admin User, Super User bzw. root), der innerhalb einer CouchDB Installation alles darf. In der Standardeinstellung ist jeder Benutzer ein Administrator. Falls das nicht gewünscht ist, kann man bestimmte Administratorkonten mit Benutzername und Passwort anlegen.

Des weiteren gibt es eine Gruppe von Requests, die ausschließlich Administratoren vorbehalten sind. Wird ein solcher Request durchgeführt und Administratorkonten sind angelegt, so fragt CouchDB nach der Authentifizierung.

Neue Administratoren anlegen

Gehen wir noch einmal mit curl durch das API und schauen, wie sich CouchDB verhält, wenn Administratorkonten angelegt wurden.

> HOST="http://127.0.0.1:5984"
> curl -X PUT $HOST/database
{"ok":true}

Legen wir zuerst eine neue Datenbank an. Nichts Unerwartetes passiert. Anschließend legen wir ein Administratorkonto an. Wir geben ihm den Namen anna und ihr Passwort ist secret. Achtung nochmal bei den doppelten Anführungszeichen im folgenden Beispiel. Wie wir bereits gesehen haben, brauchen wir diese für die Übergaben von String Werten beim Konfigurations API.

curl -X PUT $HOST/_config/admins/anna -d '"secret"'
""

Gemäß dem Verhalten des _config APIs erhalten wir den vorherigen Wert des geänderten Parameters als Ergebnis zurück. Da unser Administrator noch nicht existiert hat, bekommen wir einen leeren String zurück.

Wenn wir kurz in die CouchDB Log Dateien schauen, finden wir die folgenden beiden Einträge:

[debug] [<0.43.0>] saving to file '/Users/jan/Work/couchdb-git/etc/couchdb/local_dev.ini', Config: '{{"admins","anna"},"secret"}'

[debug] [<0.43.0>] saving to file '/Users/jan/Work/couchdb-git/etc/couchdb/local_dev.ini', Config:'{{"admins","anna"}, "-hashed-6a1cc3760b4d09c150d44edf302ff40606221526,a69a9e4f0047be899ebfe09a40b2f52c"}'

Der erste Eintrag ist unser Request. Wir sehen das unser Administrator in die Konfigurationsdateien übernommen wird. Dazu haben wir den Log Level unseres CouchDB Servers auf debug gesetzt, um genau sehen zu können was vor sich geht. Dabei sehen wir zuerst den Request mit dem Passwort im Klartext und dann noch einmal mit einem Hashwert.

Passwort Hashing

Das Passwort im Klartext zu sehen macht ein wenig nervös, oder? Doch keine Sorge. Wenn der Log Level nicht auf debug steht, tauchen Passwörter nirgendwo im Klartext auf, sondern werden gleich gehasht. Der Hash ist die lange unhandliche Zeichenkette, die mit -hashed- beginnt. Doch wie wird die angelegt?

  1. Erzeuge eine neue 128-bit UUID. Das ist unser Salt.
  2. Erzeuge einen SHA-1 Hash vom Klartext-Passwort und dem Salt (sha1(password + salt)).
  3. Stelle dem Ergebnis -hashed- voran und hänge ,Salt hinten an.

Um ein Klartext-Passwort während der Authentifizierung mit dem gespeicherten Hashwert zu vergleichen, wird die gleiche Prozedur noch einmal durchgeführt und der errechnete Hashwert mit dem gespeicherten Wert verglichen. Die Wahrscheinlichkeit, dass zwei Hashwerte kollidieren kann man vernachlässigen (vgl. auch Bruce Schneier). Sollte der gespeicherte Hashwert einem Angreifer in die Hände fallen, so ist es nach heutigem Stand der Technik zu aufwändig, das dazugehörige Klartext-Passwort zu finden (d.h. es ist zu teuer und es dauert zu lange).

Doch was hat es mit dem -hashed- Präfix auf sich? Erinnern wir uns, wie das Configuration API funktioniert. Wenn CouchDB startet, werden eine Gruppe von .ini Dateien mit Konfigurationseinstellungen gelesen. Diese werden in eine interne Datenstruktur – keine Datenbank – geladen. Mit dem Configuration API kann man die aktuelle Konfiguration auslesen, ändern und neue Werte anlegen. CouchDB schreibt die Änderungen dann zurück in die .ini Dateien.

Die .ini Dateien können auch von Hand geändert werden, wenn CouchDB nicht läuft. Um ein Administrator Konto anzulegen, können wir auch CouchDB stoppen, die local.ini Datei öffnen, einen Eintrag anna = secret unter [admins] hinzufügen und CouchDB wieder starten. Nach dem Neustart liest CouchDB den neuen Eintrag der local.ini Datei, berechnet den Hashwert des Passworts und schreibt die Änderung zurück in die local.ini Datei. Dabei wird der ursprüngliche Eintrag ersetzt. Damit CouchDB Passwörter nur einmal hasht, bekommen bereits gehashete Passwörter den -hashed- Präfix. So kann CouchDB sie von Klartext-Passwörtern unterscheiden. Das bedeutet zwar, dass ein Passwort nicht mit -hashed- beginnen kann, doch das ist sowieso eher unwahrscheinlich.

Basic Authentication

Nachdem wir einen Administrator angelegt haben, erlaubt es uns CouchDB nicht länger neue Datenbanken anzulegen ohne uns korrekt zu authentifizieren. Überprüfen wir das einmal:

> curl -X PUT $HOST/somedatabase
{"error":"unauthorized","reason":"You are not a server admin."}

Das sieht soweit gut aus. Jetzt noch einmal mit Benutzername und Passwort:

> HOST="http://anna:secret@127.0.0.1:5984"
> curl -X PUT $HOST/somedatabase
{"ok":true}

Falls Sie schon einmal auf einer passwortgeschützten Webseite oder FTP Server waren, sollte der username:password@ Teil in der URL ihnen bekannt vorkommen.

Ist Sicherheit wichtig, sollte das Fehlen von HTTPS Nervosität erzeugen. Immerhin wird das Passwort im Klartext an CouchDB übertragen. Das ist in der Regel nicht so gut. Im aktuellen Fall ist es jedoch nicht ganz so schlimm, weil CouchDB auf 127.0.0.1 auf unserer eigenen Maschine lauscht. Wer sollte dort das Passwort mithören können?

In einer Produktionsumgebung sieht das anders aus. Soll CouchDB über das öffentliche Internet oder über ein internes LAN erreichbar sein? Auch eine Instanz, die auf einem Server liegt, den man sich mit anderen teilt ist öffentlich. Es gibt mehrere Wege die Kommunikation zwischen ihrer Anwendung und CouchDB zu sichern, die jedoch den Rahmen dieses Buchs sprengen. Wir empfehlen, sich mit VPNs vertraut zu machen und nachzulesen wie man CouchDB hinter einem SSL-Proxy einrichtet (beispielsweise mit Apaches mod_proxy, nginx oder varnish). Auf das CouchDB API kann man zur Zeit nicht direkt per SSL zugreifen. Allerdings kann CouchDB auf andere CouchDB Instanzen replizieren, die hinter einem SSL Proxy liegen.

Nocheinmal Update Funktionen

Erinnern Sie sich noch an Kapitel 7: Validierungsfunktionen? Dort haben wir eine Update Validierungsfunktion geschrieben, welche überprüft hat, dass der Autor eines Dokuments mit dem aktuell authentifizierten Benutzernamen übereinstimmt.

function(newDoc, oldDoc, userCtx) {
  if (newDoc.author) {
    if(newDoc.author != userCtx.name) {
      throw("forbidden": "You may only update documents with author " +
        userCtx.name});
    }
  }
}

Was genau ist der userCtx? Es enthält Daten über den Benutzer des aktuellen Requests. Schauen wir mal rein. Dabei zeigen wir auch gleich einen netten Trick mit dem man in sämtliches JavaScript das wir schreiben genauer reinschauen kann.

> curl -X PUT $HOST/somedatabase/_design/log -d '{"validate_doc_update":"function(newDoc, oldDoc, userCtx) { log(userCtx); }"}'
{"ok":true,"id":"_design/log","rev":"1-498bd568e17e93d247ca48439a368718"}

Schauen wir auf die validate_doc_update Funktion:

function(newDoc, oldDoc, userCtx) {
  log(userCtx);
}

Diese Funktion wird für jedes zukünftige Update jedes Dokuments aufgerufen. Sie macht nichts weiter als einen Log Eintrag in das CouchDB Log zu schreiben. Legen wir mal ein neues Dokument an und schauen was passiert:

> curl -X POST $HOST/somedatabase/ -d '{"a":1}'
{"ok":true,"id":"36174efe5d455bd45fa1d51efbcff986","rev":"1-23202479633c2b380f79507a776743d5"}

Wir sollten nun Folgendes in der couch.log Datei sehen:

[info] [<0.9973.0>] OS Process :: {"db": "somedatabase","name": "anna","roles":["_admin"]}

Formatiert sieht es wie folgt aus:

{
  "db": "somedatabase",
  "name": "anna",
  "roles": ["_admin"]
}

Wir sehen die aktuelle Datenbank, den Namen des authentifizierten Benutzers und ein Array von Rollen (roles), welches eine Rolle "_admin" enthält. Daraus können wir schließen, dass Administratoren in CouchDB normale Benutzerkonten sind, welche die _admin Rolle haben.

Indem man die Benutzer und Rollen voneinander trennt, kann man das Authentifizierungssystem flexibel gestalten. Für den Moment konzentrieren wir uns jedoch auf die Administratoren.

Cookie Authentifizierung

Basic Authentication mit Klartext-Passwörtern ist zwar einfach zu handhaben, ohne zusätzliche Sicherheitsmassnahmen jedoch nicht besonders sicher. Zudem ist die User Experience nicht besonders elegant, da der Browser ein einfaches Dialogfeld ohne jeden Schmuck einblendet auf das man als Entwickler keinen Einfluss hat. Damit zeigt man auch das hier nicht gerade Profis am Werk sind.

Um die Situation zu verbessern unterstützt CouchDB Cookie Authentifizierung. Mit Cookie Authentifizierung muss ihre Anwendung nicht den hässlichen Login Dialog des Browsers nutzen, sondern man nutzt stattdessen ein normales HTML Formular um die Login Daten an CouchDB zu übertragen. Stimmen die Daten erzeugt CouchDB ein eindeutiges Token, welches der Client bei den nächsten Requests anstelle des Benutzernamens und Passwort nutzen kann. Das Token ist dann standardmäßig für 10 Minuten gültig.

Um das erste Token zu erhalten und damit den Benutzer das erste Mal zu authentifizieren, schickt man den Benutzernamen und das Passwort an das _session API. Das API versteht Daten, die von einem HTML Form kommen automatisch, sodass man in der Anwendung nichts Besonderes machen muss.

Nutzt man keine HTML Formulare muss man lediglich einen HTTP Request schicken, der so aussieht als käme er von einem HTML Formular. Glücklicherweise ist das kein großer Aufwand:

> HOST="http://127.0.0.1:5984"
> curl -vX POST $HOST/_session -H 'application/x-www-form-urlencoded' -d 'name=anna&password=secret'

Schauen wir mal genauer auf die Antwort die CouchDB zurückschickt:

< HTTP/1.1 200 OK
< Set-Cookie: AuthSession=YW5uYTo0QUIzOTdFQjrC4ipN-D-53hw1sJepVzcVxnriEw;
< Version=1; Path=/; HttpOnly
> ...
<
{"ok":true}

Ein 200 Response Code zeigt an das der Request in Ordnung war. Der Set-Cookie Header enthält das Token welches wir für die kommenden Requests benutzen können. Die Standard JSON Antwort sagt uns noch einmal das der Request OK war.

Nun können wir mit diesem Token einen weiteren Request machen und müssen dabei nicht den Benutzernamen und das Passwort mitschicken:

> curl -vX PUT $HOST/mydatabase --cookie AuthSession=YW5uYTo0QUIzOTdFQjrC4ipN-D-53hw1sJepVzcVxnriEw -H "X-CouchDB-WWW-Authenticate: Cookie" -H "Content-Type: application/x-www-form-urlencoded"
{"ok":true}

Wir können dieses Token standardmäßig für 10 Minuten nutzen. Nach Ablauf der Zeit muss sich der Benutzer erneut bei CouchDB authentifizieren. Die Gültigkeitsdauer des Tokens kann man im timeout Parameter des couch_httpd_auth Bereichs ändern. Der Timeout wird in Sekunden angegeben.

Damit Cookie Authentifizierung funktioniert, muss in der local.ini Datei der cookie_authentication_handler aktiviert sein.

[httpd]
authentication_handlers = {couch_httpd_auth, cookie_authentication_handler}, {couch_httpd_oauth, oauth_authentication_handler}, {couch_httpd_auth, default_authentication_handler}

Ausserdem muss noch ein Server Secret eingetragen werden:

[couch_httpd_auth]
secret = yours3cr37pr4s3

Netzwerk Sicherheit

CouchDB ist ein Server, der in de Regel in einem Netzwerk betrieben wird. Für solche Installationen gibt es "Best Practices", die den Rahmen dieses Buchs sprengen würden. Anhang D: Installation von den Sourcen enthält einige der Best Practices. Setzen Sie sich damit auseinander und versuchen Sie die Konsequenzen für ihre Anwendung zu verstehen.