Sécurité

Nous avons signalé précédemment que CouchDB suit son développement et que des fonctionnalités pourraient avoir été ajoutées depuis la publication de ce livre. C’est tout particulièrement vrai en ce qui concerne les mécanismes de sécurité. Il y a, à l’heure actuelle (version 0.10.0) une implantation rudimentaire de la sécurité et, au jour où nous écrivons ces lignes, des améliorations sont en discussion.

Dans ce chapitre, nous décrirons les mécanismes de sécurité de base de CouchDB : l’Admin Party (en français vulgaire : fête du slip), la Basic Authentication, Cookie Authentication, et OAuth.

La fête du slip (Admin Party)

Quand vous démarrez CouchDB juste après l’avoir installé, n’importe qui peut exécuter n’importe quelle requête. Créer une base de données ? Aucun problème ! Supprimer quelques comptes ? Pas de souci, allez-y ! CouchDB nomme cela l’Admin Party ; les Français connaissant l’heureuse expression de la fête du slip. Chacun peut faire ce qui lui plaît.

Si cette approche permet de faire ses premiers pas facilement, il semble évident que lâcher une telle installation de CouchDB dans la nature est pour le moins téméraire. En effet, tout utilisateur malicieux pourrait s’immiscer et supprimer une base de données.

Si cela peut vous rassurer, sachez que, par défaut, CouchDB n’écoute que votre interface de loopback (127.0.0.1 ou localhost). Ainsi, vous seul pouvez soumettre des requêtes à CouchDB. Toutefois, quand vous ouvrirez votre serveur au public (c’est-à-dire en ordonnant à CouchDB d’écouter votre adresse IP publique), vous devrez penser à restreindre les accès afin d’éviter que cela tourne en une fête du slip.

Lors de nos discussions antérieures, nous avons distillé quelques mots-clés sur la façon dont les choses se passent en dehors de la fête du slip. En premier lieu, il y a un admin, c’est-à-dire une sorte de super utilisateur. Ensuite, il y a des privilèges. Voyons cela plus en détail.

CouchDB conçoit un administrateur comme étant autorisé à tout faire sur l’installation de CouchDB. Par défaut, tout le monde est administrateur. Si vous ne le souhaitez pas, vous pouvez créer des comptes d’administrateurs avec un nom d’utilisateur et un mot de passe.

CouchDB définit aussi un ensemble de requêtes que seuls les administrateurs peuvent exécuter. Si vous avez créé des comptes utilisateurs, CouchDB vous demandera de vous authentifier pour les opérations suivantes :

Créer de nouveaux administrateurs

Examinons le fonctionnement de l’API (avec curl) lorsque nous ajoutons un compte administrateur.

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

À l’origine, nous pouvons ajouter une base de données. Rien d’inattendu. Maintenant, créons un compte d’administrateur. Nous l’appellerons anna et son mot de passe sera secret. Prêtez attention aux doubles guillemets droits dans le code suivant ; ils sont nécessaires pour délimiter une chaîne de caractère dans l’API de configuration (comme nous l’avons vu plus tôt) :

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

L’API _config nous renvoie la valeur précédente de l’objet modifié. Puisqu’il n’existait pas, nous obtenons une chaîne vide.

Si nous consultons les journaux de CouchDB, nous y trouvons les deux lignes suivantes :

[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"}'

La première ligne correspond à notre requête. Vous voyez que notre compte administrateur est écrit dans les fichiers de configuration de CouchDB. Nous avons défini le niveau de journalisation à debug pour voir précisément ce qui est réalisé. C’est pourquoi nous voyons déjà notre requête arriver avec un mot de passe en clair, puis l’apercevons à nouveau avec l’empreinte du mot de passe.

Chiffrer les mots de passe

Voir ici le mot de passe en clair vous inquiète ? Rassurez-vous, lorsque vous ôtez le mode de débogage, vous ne le verrez nulle part, car il est condensé immédiatement. L’empreinte correspond à cette longue chaîne de caractère toute moche qui commence par -hashed-. Comment cela fonctionne-t-il ?

  1. Créer un nouveau UUID de 128 bits. C’est notre sel (salt en anglais).
  2. Créer un condensé SHA1 de la concaténation des octets composant le mot de passe en clair et le sel (sha1(password + salt)).
  3. Préfixer le résultat par -hashed- et ajouter ,salt.

Lors de l’authentification, la comparaison du mot de passe avec l’empreinte stockée se fait en suivant la même procédure. La probabilité de collision (deux condensés identiques pour des mots de passe différents) est trop improbable pour être mentionné (voir Bruce Schneier). En outre, si la forme condensée des mots de passe tombait dans de mauvaises mains, il est, à ce jour, considéré que retrouver le mot de passe initial serait malcommode (c.-à-d.. que cela prendrait du temps et coûterait cher).

Certes, mais qu’en est-il du préfixe -hashed- ? Tout d’abord, rappelons la manière dont fonctionne l’API de configuration. Lorsque CouchDB démarre, il lit les fichiers de configuration .ini et stocke les paramètres dans une zone de stockage interne (lequel n’est pas une base de données). L’API vous permet de lire et de modifier ces paramètres. En ce dernier cas, les modifications sont sitôt écrites dans les fichiers .ini.

Les fichiers .ini peuvent aussi être édités à la main lorsque CouchDB est arrêté. Ainsi, plutôt que de créer l’utilisateur par requête, vous pourriez avoir arrêté CouchDB, édité votre fichier local.ini, ajouté anna = secret à la section [admins] et redémarré CouchDB. Lorsqu’il aurait lu le fichier local.ini, il aurait remplacé le mot de passe en clair par son empreinte. Afin de s’assurer que CouchDB condense uniquement les mots de passe en clair, les condensés sont préfixés par -hashed-. Subséquemment, vos mots de passe ne peuvent pas débuter par -hashed-, ce qui ne devrait guère vous gêner.

Basic Authentication

Puisque nous avons à présent un administrateur, CouchDB ne nous permettra pas de créer de nouvelles bases de données à moins que nous lui communiquions des paramètres d’identification valides. Vérifions :

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

Ça à l’air bon. Essayons voir avec le bon compte :

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

Si vous avez déjà accédé à un site web ou un serveur FTP protégé par mot de passe, l’allure de l’URL username:password@ devrait vous paraître familière.

Si vous êtes sensible à la sécurité, le s manquant dans http:// vous rend nerveux. Nous envoyons nos identifiants à CouchDB en clair. C’est une mauvaise idée, n’est-ce pas ? Certainement, mais souvenez-vous de notre scénario : CouchDB écoute sur le port 127.0.0.1 d’un PC de développement dont nous sommes les seuls utilisateurs. Qui pourrait récupérer notre mot de passe ?

Si vous travaillez dans un environnement de production, vous devrez bien entendu reconsidérer la chose. Votre instance de CouchDB va-t-elle communiquer à travers un réseau public ? Même un LAN partagé avec d’autres clients est public. Il existe plusieurs moyens de sécuriser une connexion entre vous ou votre application et CouchDB, lesquels sortent du cadre de ce livre. Nous vous suggérons de vous renseigner sur les VPNs et de placer CouchDB derrière un serveur mandataire HTTP (comme le module Apache mod_pryx, nginx ou varnish) qui gèrera SSL à votre place. Pour l’heure, CouchDB ne sait pas exposer son API par SSL. Elle peut, en revanche, répliquer avec une base de données qui se trouve derrière un serveur mandataire SSL.

Validations lors d’une mise à jour, le retour !

Vous souvenez-vous du chapitre 7, Fonctions de validation ? Nous avions une fonction de validation des mises à jour qui nous permettait de vérifier que l’utilisateur qui souhaitait modifier un document correspondait à celui qui l’avait créé.

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

Qu’est-ce que ce userCtx ? C’est un objet contenant les informations relatives à l’identification de la requête courante. Regardons ce qui s’y trouve. Nous en profitons pour vous donner une astuce pour inspecter ce qui se passe dans le code JavaScript que vous écrivez.

> 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"}

Regardons la fonction validate_doc_update :

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

Elle sera appelée lors de toute mise à jour des documents et ne fera rien d’autre que de créer une entrée dans le journal de CouchDB. Si nous créons un document :

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

nous devrions voir apparaître cela dans notre fichier couch.log :

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

Reformattons :

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

Nous y trouvons le nom de la base de données courante, le nom de l’utilisateur authentifié ainsi qu’un tableau de rôles contenant le rôle "_admin". Nous pouvons en déduire que les administrateurs sont vus par CouchDB comme des utilisateurs classiques qui possèdent le rôle d’administration.

En séparant les utilisateurs des rôles, le système d’authentification peut être étendu avec flexibilité. Pour lors, nous nous occuperons seulement des utilisateurs administrateurs.

Cookie Authentication

La Basic authentication qui recourt à des mots de passe en clair est pratique, à défaut d’être sûre. Mais elle donne aussi une mauvaise expérience à l’utilisateur. Si vous utilisez la Basic authentication pour identifier vos admins, vous devrez compter avec une fenêtre de demande de mot de passe souvent moche et inaltérable dont la seule apparition indique surtout un boulot de gougnafier.

CouchDB propose donc une alternative : l’authentification par cookie. Avec cette approche, votre application peut s’affranchir de la fenêtre standard du navigateur internet. Vous pouvez exploiter un formulaire HTML classique et le soumettre à CouchDB. À sa réception, CouchDB génèrera un jeton unique dont le client pourra se servir pour ses prochaines requêtes. Lorsque CouchDB aperçoit le jeton dans une requête, il en déduit l’identité de l’utilisateur sans avoir besoin de réclamer à nouveau le mot de passe. Par défaut, un jeton est valide 10 minutes.

Pour obtenir le premier jeton, et ainsi authentifier l’utilisateur pour la première fois, le nom d’utilisateur et le mot de passe doivent être soumis à l’API _session. Cette API est suffisamment intelligente pour décoder les formulaires HTML qui lui sont soumis, ce qui vous évite d’implanter une quelconque logique de votre côté.

Dans le cas où vous n’utilisez pas de formulaires HTML pour vous connecter, vous devez envoyer une requête HTTP similaire à la soumission d’un formulaire. Heureusement, c’est très simple :

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

CouchDB répond avec quelques détails :

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

Un code de réponse 200 nous indique que tout s’est bien passé, un en-tête Set-Cookie donne le jeton que nous pouvons utiliser dans la prochaine requête, et la réponse standard JSON nous indique que la requête s’est exécutée avec succès.

Dès lors, nous pouvons utiliser le jeton pour soumettre une autre requête, avec l’identité que nous avons communiquée précédemment, sans avoir besoin de préciser à nouveau le mot de passe, ni le nom de l’utilisateur :

> 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}

Vous pouvez utiliser ce jeton pendant 10 minutes. Passé ce délai, vous devrez vous authentifier à nouveau. La durée de vie du jeton peut être ajustée avec le paramètre timeout (exprimé en secondes) dans la section de configuration couch_httpd_auth.

Veuillez noter que l’authentification par cookie ne fonctionne que si vous activez la directive cookie_authentication_handler dans votre fichier local.ini :

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

En outre, vous devez définir le secret du serveur :

[couch_httpd_auth]
secret = yours3cr37pr4s3

Sécurité d’un serveur sur le réseau

CouchDB est un serveur ayant accès au réseau. Il existe de bonnes pratiques pour sécuriser ces services, mais cela va au-delà du ressort de ce livre. L’Annexe D, installation depuis les sources inclut quelques-unes des bonnes pratiques. Assurez-vous d’en comprendre les implications.