Carnets en ligne - Technologiehttps://blog.notmyidea.org/2016-02-11T00:00:00+01:00Let's Encrypt + HAProxy2016-02-11T00:00:00+01:002016-02-11T00:00:00+01:00tag:blog.notmyidea.org,2016-02-11:/lets-encrypt-haproxy.html<p><em>Note : Cet article n'est plus à jour. Il est maintenant (2018) possible d'installer des certificats SSL Let's Encrypt d'une manière beaucoup plus simple, en utilisant certbot (et le plugin nginx <code>certbot --nginx</code>).</em></p>
<blockquote>
<p>It’s time for the Web to take a big step forward in terms of security
and privacy …</p></blockquote><p><em>Note : Cet article n'est plus à jour. Il est maintenant (2018) possible d'installer des certificats SSL Let's Encrypt d'une manière beaucoup plus simple, en utilisant certbot (et le plugin nginx <code>certbot --nginx</code>).</em></p>
<blockquote>
<p>It’s time for the Web to take a big step forward in terms of security
and privacy. We want to see HTTPS become the default. Let’s Encrypt
was built to enable that by making it as easy as possible to get and
manage certificates.</p>
<p>-- <a href="https://letsencrypt.org/">Let's Encrypt</a></p>
</blockquote>
<p>Depuis début Décembre, la nouvelle <em>autorité de certification</em> Let's
Encrypt est passée en version <em>Beta</em>. Les certificats SSL sont un moyen
de 1. chiffrer la communication entre votre navigateur et le serveur et
2. un moyen d'être sur que le site Web auquel vous accédez est celui
auquel vous pensez vous connecter (pour éviter des <a href="https://fr.wikipedia.org/wiki/Attaque_de_l'homme_du_milieu">attaques de l'homme
du milieu</a>).</p>
<p>Jusqu'à maintenant, il était nécessaire de payer une entreprise pour
faire en sorte d'avoir des certificats qui évitent d'avoir ce genre
d'erreurs dans vos navigateurs:</p>
<p><img alt="Message de firefox lorsque une connexion n'est pas
sécurisée." src="%7Bfilename%7D/static/unsecure-connection.png"></p>
<p>Maintenant, grâce à Let's Encrypt il est possible d'avoir des
certificats SSL <strong>gratuits</strong>, ce qui représente un grand pas en avant
pour la sécurité de nos communications.</p>
<p>Je viens de mettre en place un procédé (assez simple) qui permet de
configurer votre serveur pour générer des certificats SSL valides avec
Let's Encrypt et le répartiteur de charge
<a href="http://www.haproxy.org/">HAProxy</a>.</p>
<p>Je me suis basé pour cet article sur
d'<a href="https://blog.infomee.fr/p/letsencrypt-haproxy">autres</a>
<a href="http://blog.victor-hery.com/article22/utiliser-let-s-encrypt-avec-haproxy">articles</a>,
dont je vous recommande la lecture pour un complément d'information.</p>
<h2>Validation des domaines par Let's Encrypt</h2>
<p>Je vous passe les détails d'installation du client de Let's Encrypt, qui
sont <a href="https://github.com/letsencrypt/letsencrypt#installation">très bien expliqués sur leur
documentation</a>.</p>
<p>Une fois installé, vous allez taper une commande qui va ressembler à:</p>
<div class="highlight"><pre><span></span><span class="err">letsencrypt-auto certonly --renew-by-default</span>
<span class="err">--webroot -w /home/www/letsencrypt-requests/ \</span>
<span class="err">-d hurl.kinto-storage.org \</span>
<span class="err">-d forums.kinto-storage.org</span>
</pre></div>
<p>Le <em>webroot</em> est l'endroit ou les preuves de détention du domaine vont
être déposées.</p>
<p>Lorsque les serveurs de Let's Encrypt vont vouloir vérifier que vous
êtes bien à l'origine des demandes de certificats, ils vont envoyer une
requête HTTP sur <code>http://domaine.org/.well-known/acme-challenge</code>, ou il
voudra trouver des informations qu'il aura généré via la commande
<code>letsencrypt-auto</code>.</p>
<p>J'ai choisi de faire une règle dans haproxy pour diriger toutes les
requêtes avec le chemin <code>.well-known/acme-challenge</code> vers un <em>backend</em>
nginx qui sert des fichiers statiques (ceux contenus dans
<code>/home/www/letsencrypt-requests/</code>).</p>
<p>Voici la section de la configuration de HAProxy (et <a href="https://github.com/almet/infra/blob/master/haproxy/haproxy.cfg#L63-L72">la configuration
complete</a>
si ça peut être utile):</p>
<div class="highlight"><pre><span></span><span class="n">frontend</span> <span class="n">http</span>
<span class="n">bind</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">:</span><span class="mi">80</span>
<span class="k">mode</span> <span class="n">http</span>
<span class="n">default_backend</span> <span class="n">nginx_server</span>
<span class="n">acl</span> <span class="n">letsencrypt_check</span> <span class="n">path_beg</span> <span class="o">/</span><span class="p">.</span><span class="n">well</span><span class="o">-</span><span class="n">known</span><span class="o">/</span><span class="n">acme</span><span class="o">-</span><span class="n">challenge</span>
<span class="n">use_backend</span> <span class="n">letsencrypt_backend</span> <span class="k">if</span> <span class="n">letsencrypt_check</span>
<span class="n">redirect</span> <span class="n">scheme</span> <span class="n">https</span> <span class="n">code</span> <span class="mi">301</span> <span class="k">if</span> <span class="o">!</span><span class="err">{</span> <span class="n">ssl_fc</span> <span class="err">}</span> <span class="o">!</span><span class="n">letsencrypt_check</span>
<span class="n">backend</span> <span class="n">letsencrypt_backend</span>
<span class="n">http</span><span class="o">-</span><span class="n">request</span> <span class="k">set</span><span class="o">-</span><span class="n">header</span> <span class="k">Host</span> <span class="n">letsencrypt</span><span class="p">.</span><span class="n">requests</span>
<span class="k">dispatch</span> <span class="mi">127</span><span class="p">.</span><span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">.</span><span class="mi">1</span><span class="p">:</span><span class="mi">8000</span>
</pre></div>
<p>Et celle de NGINX:</p>
<div class="highlight"><pre><span></span><span class="err">server {</span>
<span class="err"> listen 8000;</span>
<span class="err"> server_name letsencrypt.requests;</span>
<span class="err"> root /home/www/letsencrypt-requests;</span>
<span class="err">}</span>
</pre></div>
<h2>Installation des certificats dans HAProxy</h2>
<p>Vos certificats SSL devraient être générés dans <code>/etc/letsencrypt/live</code>,
mais ils ne sont pas au format attendu par haproxy. Rien de grave, la
commande suivant convertit l'ensemble des certificats en une version
compatible avec
HAProxy:</p>
<div class="highlight"><pre><span></span><span class="err">cat /etc/letsencrypt/live/domaine.org/privkey.pem /etc/letsencrypt/live/domaine.org/fullchain.pem > /etc/ssl/letsencrypt/domaine.org.pem</span>
</pre></div>
<p>Et ensuite dans la configuration de haproxy, pour le (nouveau)
<em>frontend</em> https:</p>
<div class="highlight"><pre><span></span><span class="err">bind 0.0.0.0:443 ssl no-sslv3 crt /etc/ssl/letsencrypt</span>
</pre></div>
<p>Faites bien attention à avoir un <em>frontend</em> https pour tous vos sites en
HTTPS. <a href="https://github.com/almet/infra/blob/master/haproxy/haproxy.cfg#L38-L60">Pour moi cela ressemble à
ça</a>.</p>
<p>Une fois tout ceci fait, redémarrez votre service haproxy et zou !</p>
<h2>Automatisation</h2>
<p>Pour automatiser un peu tout ça, j'ai choisi de faire ça comme suit:</p>
<ul>
<li>Un fichier domaine dans <code>letsencrypt/domains/domain.org</code> qui
contient le script <code>letsencrypt</code>.</li>
<li>Un fichier d'installation de certificats dans
<code>letsencrypt/install-certs.sh</code> qui s'occupe d'installer les
certificats déjà générés.</li>
</ul>
<p>Et voila ! <a href="https://github.com/almet/infra/">Le tout est dans un dépot
github</a>, si jamais ça peut vous servir,
tant mieux !</p>Ateliers d'autodéfense numérique2016-01-14T00:00:00+01:002016-01-14T00:00:00+01:00tag:blog.notmyidea.org,2016-01-14:/ateliers-dautodefense-numerique.html<p>Il y a huit mois, je me rendais compte de l'importance du choix des
outils pour faire face à la surveillance généralisée, et notamment en
rapport au chiffrement des données. Une de mes envies de l'époque était
l'animation d'ateliers.</p>
<blockquote>
<p>Je compte donc:</p>
<ul>
<li>Organiser des ateliers de sensibilisation aux outils de …</li></ul></blockquote><p>Il y a huit mois, je me rendais compte de l'importance du choix des
outils pour faire face à la surveillance généralisée, et notamment en
rapport au chiffrement des données. Une de mes envies de l'époque était
l'animation d'ateliers.</p>
<blockquote>
<p>Je compte donc:</p>
<ul>
<li>Organiser des ateliers de sensibilisation aux outils de
communication, envers mes proches;</li>
<li>Utiliser la communication chiffrée le plus souvent possible, au
moins pour rendre le déchiffrement des messages plus longue,
"noyer le poisson".</li>
</ul>
<p>-- <a href="http://blog.notmyidea.org/chiffrement.html">Chiffrement</a></p>
</blockquote>
<p>J'ai mis un peu de temps à mettre le pied à l'étrier, mais je ressors
finalement du premier atelier que j'ai co-animé avec geb, auprès d'un
public de journalistes.</p>
<p>Pour cette première édition l'idée était à la fois d'aller à la
rencontre d'un public que je connais mal, de leur donner des outils pour
solutionner les problèmes auxquels ils font parfois face, et de me faire
une idée de ce que pouvait être un atelier sur l'autodéfense numérique.</p>
<p>L'objectif pour ce premier atelier était de:</p>
<ol>
<li>Échanger autour des besoins et <strong>faire ressortir des histoires</strong> ou
le manque d'outillage / connaissances à posé problème, dans des
situations concrètes;</li>
<li>Se rendre compte des "conduites à risque", <strong>faire peur</strong> aux
personnes formées pour qu'elles se rendent compte de l'état actuel
des choses;</li>
<li><strong>Proposer des solutions concrètes</strong> aux problèmes soulevés, ainsi
que le minimum de connaissance théorique pour les appréhender.</li>
</ol>
<h2>1. Faire ressortir les problèmes</h2>
<p>Afin de faire ressortir les problèmes, nous avons choisi de constituer
des petits groupes de discussion, afin de faire des "Groupes d'Interview
Mutuels", ou "GIM":</p>
<blockquote>
<p>l’animateur invite les participants à se regrouper par trois, avec des
personnes qu’on connaît moins puis invite chacun à livrer une
expérience vécue en lien avec le thème de la réunion et les deux
autres à poser des questions leur permettant de bien saisir ce qui a
été vécu.</p>
<p>-- «<a href="http://www.scoplepave.org/pour-s-ecouter">Pour s'écouter</a>», SCOP
Le Pavé.</p>
</blockquote>
<p>De ces <em>GIMs</em> nous avons pu ressortir quelques histoires, gravitant
autour de:</p>
<ul>
<li><strong>La protection des sources (d'information)</strong>: Comment faire pour
aider quelqu'un à faire "fuiter" des données depuis l'intérieur
d'une entreprise ?</li>
<li><strong>Le chiffrement de ses données</strong>: Comment éviter de faire "fuiter"
des données importantes lors d'une perquisition de matériel ?</li>
</ul>
<h2>2. Faire peur</h2>
<p>Un des premiers objectifs est de faire peur, afin que tout le monde se
rende compte à quel point il est facile d'accéder à certaines données.
<a href="http://blog.barbayellow.com/">Grégoire</a> m'avait conseillé quelques
petites accroches qui ont ma foi bien marché:</p>
<p>J'ai demandé aux présent.e.s de:</p>
<ul>
<li>donner leur mot de passe à voix haute devant les autres: a priori
personne ne le fera;</li>
<li>venir se connecter à leur compte email depuis mon ordinateur. J'ai
piégé une personne, qui est venu pour taper son mot de passe.</li>
</ul>
<p>Cela à été un bon moyen de parler de l'importance des traces que l'on
peut laisser sur un ordinateur, et de la confiance qu'il faut avoir dans
le matériel que l'on utilise, à fortiori si ce ne sont pas les vôtres.</p>
<p>Pour continuer à leur faire peur, après une brève explication de ce
qu'est SSL nous avons montré comment il était facile de scruter le
réseau à la recherche de mots de passe en clair.</p>
<h2>3. Proposer des solutions concrêtes</h2>
<p>Une fois que tout le monde avait pleinement pris sonscience des
problématiques et n'osait plus utiliser son ordinateur ou son
téléphone, on à commencé à parler de quelques solutions. Plusieurs
approches étaient possibles ici, nous avons choisi de présenter quelques
outils qui nous semblaient répondre aux attentes:</p>
<ul>
<li>On a expliqué ce qu'était <a href="https://tails.boum.org">Tails</a>, et
comment l'utiliser et le dupliquer.</li>
<li>On a pu faire un tour des outils existants sur Tails, notamment
autour de l'<em>anonymisation</em> de fichiers et la suppression effective
de contenus.</li>
<li>Certaines personnes ont pu créer une clé tails avec la persistance
de configurée.</li>
<li>Nous nous sommes connectés au réseau
<a href="https://www.torproject.org">Tor</a> et testé que nos adresses IP
changeaient bien à la demande.</li>
<li>Nous avons utilisé <a href="https://crypto.cat">CryptoCat</a> par dessus Tor,
afin de voir comment avoir une conversation confidentielle dans
laquelle il est possible d'échanger des fichiers.</li>
</ul>
<h2>Retours</h2>
<p>D'une manière générale, pour une formation de trois heures et demi, je
suis assez content de l'exercice, et de l'ensemble des sujets que nous
avons pu couvrir. Il y a beaucoup de place pour l'amélioration,
notamment en amont (j'avais par exemple oublié d'amener avec moi
suffisamment de clés USB pour utiliser Tails).</p>
<p>La plupart des retours qu'on a pu avoir jusqu'à maintenant sont
positifs, et il y a l'envie d'aller plus loin sur l'ensemble de ces
sujets.</p>
<h2>La suite</h2>
<p>Il y a beaucoup de sujets que nous n'avons pas abordés, ou uniquement
survolés, à cause du manque de temps disponible. Idéalement, il faudrait
au moins une journée entière pour couvrir quelques sujets plus en détail
(on peut imaginer avoir une partie théorique le matin et une partie
pratique l'après-midi par exemple).</p>
<p>J'ai choisi volontairement de ne pas aborder le chiffrement des messages
via PGP parce que <a href="%7Bfilename%7D2015.05.pgp-problemes.rst">je pense que la protection que ce média propose n'est
pas suffisante</a>, mais je suis
en train de revenir sur ma décision: il pourrait être utile de présenter
l'outil, à minima, en insistant sur certaines de ses faiblesses.</p>
<p>Un compte twitter à été créé recemment autour des crypto-party à Rennes,
si vous êtes interessés, <a href="https://twitter.com/CryptoPartyRNS">allez jeter un coup
d'œil</a>!</p>
<p>Je n'ai pas trouvé de ressources disponibles par rapport à des plans de
formation sur le sujet, j'ai donc décidé de publier les nôtres, afin de
co-construire avec d'autres des plans de formation.</p>
<p>Ils sont pour l'instant disponibles <a href="http://autodefense-numerique.readthedocs.org/en/latest/">sur Read The
Docs</a>. Tous les
retours sont évidemment les bienvenus !</p>Le mail doit-il mourir ?2015-11-24T00:00:00+01:002015-11-24T00:00:00+01:00tag:blog.notmyidea.org,2015-11-24:/le-mail-doit-il-mourir.html<p>J'utilise quotidiennement le protocole email, tant bien que mal, tout en sachant que l'ensemble de mes messages passent en clair sur le réseau pour la plupart de mes conversations, puisque trop peu de monde utilise le chiffrement des messages.</p>
<p>Et même si j'arrive à convaincre certains de mes proches à …</p><p>J'utilise quotidiennement le protocole email, tant bien que mal, tout en sachant que l'ensemble de mes messages passent en clair sur le réseau pour la plupart de mes conversations, puisque trop peu de monde utilise le chiffrement des messages.</p>
<p>Et même si j'arrive à convaincre certains de mes proches à installer PGP, je ne suis pas satisfait du résultat: les méta-données (qui contacte qui à quel
moment, et pour lui dire quoi) transitent de toute manière, elles, en clair, à la vue de tous.</p>
<p>Ce problème est lié directement au protocole email: il est <em>necessaire</em> de faire fuiter ces meta-données (au moins le destinataire) pour avoir un protocole
mail fonctionnel.</p>
<p>Le mail répond à un besoin de communication asynchrone qui permet des conversations plus réfléchies qu'un simple chat (miaou). Il est tout à fait possible d'utiliser certaines technologies existantes afin de construire le futur de l'email, pour lequel:</p>
<ul>
<li>Les méta-données seraient chiffrées — Il n'est pas possible de savoir qui
communique avec qui, et quand;</li>
<li>Le chiffrement serait fort (et protégé d'une phrase de passe ?);</li>
<li>La fuite d'une clé de chiffrement utilisée dans un échange ne permette pas de
déchiffrer l'ensemble des échanges (forward secrecy);</li>
<li>Il ne soit pas possible de réutiliser les données comme preuve pour
incriminer l'emmeteur du message (deniability);</li>
</ul>
<p>Avec au moins ces besoins en tête, il semble qu'une revue de l'ensemble des projets existants pointe du doigt vers <a href="https://github.com/agl/pond">pond</a>, ou vers <a href="https://www.whispersystems.org">Signal</a>.</p>
<p>Malheureusement, Pond est le projet d'une seule personne, qui veut plutôt utiliser ce code comme démonstration du concept en question.</p>Web distribution signing2015-10-12T00:00:00+02:002015-10-12T00:00:00+02:00tag:blog.notmyidea.org,2015-10-12:/web-distribution-signing.html<p><em>I'm not a crypto expert, nor pretend to be one. These are thoughts I
want to share with the crypto community to actually see if any solution
exists to solve this particular problem.</em></p>
<p>One <a href="http://www.tonyarcieri.com/whats-wrong-with-webcrypto">often pointed</a> flaw in
web-based cryptographic applications is the fact that there is no way to …</p><p><em>I'm not a crypto expert, nor pretend to be one. These are thoughts I
want to share with the crypto community to actually see if any solution
exists to solve this particular problem.</em></p>
<p>One <a href="http://www.tonyarcieri.com/whats-wrong-with-webcrypto">often pointed</a> flaw in
web-based cryptographic applications is the fact that there is no way to
trust online software distributions. Put differently, you don't actually
trust the software authors but are rather trusting the software
distributors and certificate authorities (CAs).</p>
<p>I've been talking with a few folks in the past months about that and
they suggested me to publish something to discuss the matter. So here I
come!</p>
<h2>The problem (Attack vectors)</h2>
<p>Let's try to describe a few potential attacks:</p>
<p><em>Application Authors</em> just released a new version of their open source
web crypto messaging application. An <em>Indie Hoster</em> installs it on their
servers so a wide audience can actually use it.</p>
<p>Someone alters the files on <em>Indie Hoster</em> servers, effectively
replacing them with other <em>altered files</em> with less security properties
/ a backdoor. This someone could either be an <em>Evil Attacker</em> which
found its way trough, the <em>Indie Hoster</em> or a CDN which delivers the
files,</p>
<p>Trusted <em>Certificate Authorities</em> ("governments" or "hacking team") can
also trick the User Agents (i.e. Firefox) into thinking they're talking
to <em>Indie Hoster</em> even though they're actually talking to a different
server.</p>
<p><strong>Altered files</strong> are then being served to the User Agents, and <em>Evil
Attacker</em> now has a way to actually attack the end users.</p>
<h2>Problem Mitigation</h2>
<p>Part of the problem is solved by the recently introduced <a href="https://w3c.github.io/webappsec/specs/subresourceintegrity/">Sub Resource
Integrity</a>
(SRI). To quote them: "[it] defines a mechanism by which user agents
may verify that a fetched resource has been delivered without unexpected
manipulation.".</p>
<p>SRI is a good start, but isn't enough: it ensures the assets (JavaScript
files, mainly) loaded from a specific HTML page are the ones the author
of the HTML page intends. However, SRI doesn't allow the User Agent to
ensure the HTML page is the one he wants.</p>
<p>In other words, we miss a way to create trust between <em>Application
Authors</em> and <em>User Agents</em>. The User-Agent currently has to trust the
<em>Certificate Authorities</em> and the delivery (<em>Indie Hoster</em>).</p>
<p>For desktop software distribution: <em>Crypto Experts</em> audit the software,
sign it somehow and then this signature can be checked locally during
installation or runtime. It's not automated, but at least it's possible.</p>
<p>For web applications, we don't have such a mechanism, but it should be
possible. Consider the following:</p>
<ul>
<li><em>App Authors</em> publish a new version of their software; They provide
a hash of each of their distributed files (including the HTML
files);</li>
<li><em>Crypto Experts</em> audit these files and sign the hashes somehow;</li>
<li><em>User Agents</em> can chose to trust some specific <em>Crypto Experts</em>;</li>
<li>When a <em>User Agent</em> downloads files, it checks if they're signed by
a trusted party.</li>
</ul>
<h2>Chosing who you trust</h2>
<p>In terms of user experience, handling certificates is hard, and that's
where the community matters. Distributions such as
<a href="https://tails.boom.org">Tails</a> could chose who they trust to verify the
files, and issue warnings / refuse to run the application in case files
aren't verified.</p>
<p>But, as highligted earlier, CAs are hard to trust. A new instance of the
same CA system wouldn't make that much differences, expect the fact that
distributions could ship with a set of trusted authorities (for which
revocation would still need to be taken care of).</p>
<blockquote>
<p>[...] users are vulnerable to MitM attacks by the authority, which
can vouch for, or be coerced to vouch for, false keys. This weakness
has been highlighted by recent CA scandals. Both schemes can also be
attacked if the authority does not verify keys before vouching for
them.</p>
<p>-- <a href="http://cacr.uwaterloo.ca/techreports/2015/cacr2015-02.pdf">SoK : Secure
Messaging</a>;</p>
</blockquote>
<p>It seems that some other systems could allow for something more
reliable:</p>
<blockquote>
<p>Melara et al proposed CONIKS, using a series of chained commitments to
Merkle prefix trees to build a key directory [...] for which
individual users can efficiently verify the consistency of their own
entry in the directory without relying on a third party.</p>
<p>This “self- auditing log” approach makes the system partially have no
auditing required (as general auditing of non-equivocation is still
required) and also enables the system to be privacy preserving as the
entries in the directory need not be made public. This comes at a mild
bandwidth cost not reflected in our table, estimated to be about 10
kilobytes per client per day for self-auditing.</p>
<p>-- <a href="http://cacr.uwaterloo.ca/techreports/2015/cacr2015-02.pdf">SoK : Secure
Messaging</a>;</p>
</blockquote>
<p>Now, I honestly have no idea if this thing solves the whole problem, and
I'm pretty sure this design has many security problems attached to it.</p>
<p>However, that's a problem I would really like to see solved one day, so
here the start of the discussion, don't hesitate to <a href="/pages/about.html">get in
touch</a>!</p>
<h2>Addendum</h2>
<p>It seems possible to increase the level a user has in a Web Application
by adding indicators in the User-Agent. For instance, when using an
application that's actually signed by someone considered trustful by the
User-Agent (or the distributor of the User-Agent), a little green icon
could be presented to the User, so they know that they can be confident
about this.</p>
<p>A bit like User-Agents do for SSL, but for the actual signature of the
files being viewed.</p>Service de nuages : Pourquoi avons-nous fait Cliquet ?2015-07-14T00:00:00+02:002015-07-14T00:00:00+02:00tag:blog.notmyidea.org,2015-07-14:/pourquoi-cliquet<p class="first last">Basé sur Pyramid, Cliquet est un projet qui permet de se concentrer sur l'essentiel
lors de la conception d'APIs.</p>
<p><em>Cet article est repris depuis le blog « Service de Nuages » de mon équipe à Mozilla</em></p>
<p><strong>tldr; Cliquet est un toolkit Python pour construire des APIs, qui implémente
les bonnes pratiques en terme de mise en production et de protocole HTTP.</strong></p>
<div class="section" id="les-origines">
<h2>Les origines</h2>
<p>L'objectif pour le premier trimestre 2015 était de construire un service de
stockage et de <a class="reference external" href="{filename}2015.04.service-de-nuages.rst">synchronisation de listes de lecture</a>.</p>
<p>Au démarrage du projet, nous avons tenté de rassembler toutes les bonnes pratiques
et recommandations, venant de différentes équipes et surtout des derniers projets déployés.</p>
<p>De même, nous voulions tirer parti du protocole de <em>Firefox Sync</em>, robuste et éprouvé,
pour la synchronisation des données «offline».</p>
<p>Plutôt qu'écrire un <a class="reference external" href="http://blog.octo.com/en/design-a-rest-api/">énième</a>
<a class="reference external" href="http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api">article</a> de blog,
nous avons préféré les rassembler dans ce qu'on a appellé «un protocole».</p>
<p>Comme pour l'architecture envisagée nous avions deux projets à construire, qui
devaient obéir globalement à ces mêmes règles, nous avons décidé de mettre en
commun l'implémentation de ce protocole et de ces bonnes pratiques dans un
«toolkit».</p>
<p><em>Cliquet</em> est né.</p>
<img alt="Cliquet logo" class="align-center" src="{filename}/images/cliquet-logo.png" />
<div class="section" id="les-intentions">
<h3>Les intentions</h3>
<blockquote class="epigraph">
Quelle structure JSON pour mon API ? Quelle syntaxe pour filtrer la liste
via la querystring ? Comment gérer les écritures concurrentes ?
Et synchroniser les données dans mon application cliente ?</blockquote>
<p>Désormais, quand un projet souhaite bénéficier d'une API REST pour stocker et consommer
des données, il est possible d'utiliser le <strong>protocole HTTP</strong> proposé
et de se concentrer sur l'essentiel. Cela vaut aussi pour les clients, où
la majorité du code d'interaction avec le serveur est réutilisable.</p>
<blockquote class="epigraph">
Comment pouvons-nous vérifier que le service est opérationnel ? Quels indicateurs StatsD ?
Est-ce que Sentry est bien configuré ? Comment déployer une nouvelle version
sans casser les applications clientes ?</blockquote>
<p>Comme <em>Cliquet</em> fournit tout ce qui est nécessaire pour être conforme avec les
exigences de la <strong>mise en production</strong>, le passage du prototype au service opérationnel
est très rapide ! De base le service répondra aux attentes en terme supervision, configuration,
déploiement et dépréciation de version. Et si celles-ci évoluent, il suffira
de faire évoluer le toolkit.</p>
<blockquote class="epigraph">
Quel backend de stockage pour des documents JSON ? Comment faire si l'équipe
de production impose PostgreSQL ? Et si on voulait passer à Redis ou en
mémoire pour lancer les tests ?</blockquote>
<p>En terme d'implémentation, nous avons choisi de <strong>fournir des abstractions</strong>.
En effet, nous avions deux services dont le coeur consistait
à exposer un <em>CRUD</em> en <em>REST</em>, persistant des données JSON dans un backend.
Comme <em>Pyramid</em> et <em>Cornice</em> ne fournissent rien de tout prêt pour ça,
nous avons voulu introduire des classes de bases pour abstraire les notions
de resource REST et de backend de stockage.</p>
<p>Dans le but de tout rendre optionnel et «pluggable», <strong>tout est configurable</strong>
depuis le fichier <tt class="docutils literal">.ini</tt> de l'application. Ainsi tous les projets qui utilisent
le toolkit se déploieront de la même manière : seuls quelques éléments de configuration
les distingueront.</p>
<img alt="Une réunion à Paris..." class="align-center" src="{filename}/images/cliquet-notes-whiteboard.jpg" />
</div>
</div>
<div class="section" id="le-protocole">
<h2>Le protocole</h2>
<blockquote class="epigraph">
Est-ce suffisant de parler d'«API REST» ? Est-ce bien nécessaire de
relire la spec HTTP à chaque fois ? Pourquoi réinventer un protocole complet
à chaque fois ?</blockquote>
<p>Quand nous développons un (micro)service Web, nous dépensons généralement beaucoup
trop d'énergie à (re)faire des choix (arbitraires).</p>
<p>Nul besoin de lister ici tout ce qui concerne la dimension
de la spécification HTTP pure, qui nous impose le format des headers,
le support de CORS, la négocation de contenus (types mime), la différence entre
authentification et autorisation, la cohérence des code status...</p>
<p>Les choix principaux du protocole concernent surtout :</p>
<ul class="simple">
<li><strong>Les resources REST</strong> : Les deux URLs d'une resource (pour la collection
et les enregistrements) acceptent des verbes et des headers précis.</li>
<li><strong>Les formats</strong> : le format et la structure JSON des réponses est imposé, ainsi
que la <a class="reference external" href="{filename}/2015.05.continuation-token.rst">pagination des listes</a>
ou la syntaxe pour filtrer/trier les resources via la <a class="reference external" href="https://en.wikipedia.org/wiki/Query_string">querystring</a>.</li>
<li><strong>Les timestamps</strong> : un numéro de révision qui s'incrémente à chaque opération
d'écriture sur une collection d'enregistrements.</li>
<li><strong>La synchronisation</strong> : une série de leviers pour récupérer et renvoyer des
changements sur les données, sans perte ni collision, en utilisant les timestamps.</li>
<li><strong>Les permissions</strong> : les droits d'un utilisateur sur une collection ou un enregistrement
(<em>encore frais et sur le point d'être documenté</em>) <a class="footnote-reference" href="#id3" id="id1">[1]</a>.</li>
<li><strong>Opérations par lot</strong>: une URL qui permet d'envoyer une série de requêtes
décrites en JSON et d'obtenir les réponses respectives.</li>
</ul>
<p>Dans la dimension opérationnelle du protocole, on trouve :</p>
<ul class="simple">
<li><strong>La gestion de version</strong> : cohabitation de plusieurs versions en production,
avec alertes dans les entêtes pour la fin de vie des anciennes versions.</li>
<li><strong>Le report des requêtes</strong> : entêtes interprétées par les clients, activées en cas de
maintenance ou de surchage, pour ménager le serveur.</li>
<li><strong>Le canal d'erreurs</strong> : toutes les erreurs renvoyées par le serveur ont le même
format JSON et ont un numéro précis.</li>
<li><strong>Les utilitaires</strong> : URLs diverses pour répondre aux besoins exprimés par
l'équipe d'administrateurs (monitoring, metadonnées, paramètres publiques).</li>
</ul>
<p>Ce protocole est une compilation des bonnes pratiques pour les APIs HTTP (<em>c'est notre métier !</em>),
des conseils des administrateurs système dont c'est le métier de mettre à disposition des services
pour des millions d'utilisateurs et des retours d'expérience de l'équipe
de <em>Firefox Sync</em> pour la gestion de la concurrence et de l'«offline-first».</p>
<p>Il est <a class="reference external" href="http://cliquet.readthedocs.org/en/latest/api/index.html">documenté en détail</a>.</p>
<p>Dans un monde idéal, ce protocole serait versionné, et formalisé dans une RFC.
En rêve, il existerait même plusieurs implémentations avec des technologies différentes
(Python, Go, Node, etc.). <a class="footnote-reference" href="#id4" id="id2">[2]</a></p>
<table class="docutils footnote" frame="void" id="id3" rules="none">
<colgroup><col class="label" /><col /></colgroup>
<tbody valign="top">
<tr><td class="label"><a class="fn-backref" href="#id1">[1]</a></td><td>Voir notre <a class="reference external" href="{filename}/2015.05.cliquet-permissions.rst">article dédié sur les permissions</a></td></tr>
</tbody>
</table>
<table class="docutils footnote" frame="void" id="id4" rules="none">
<colgroup><col class="label" /><col /></colgroup>
<tbody valign="top">
<tr><td class="label"><a class="fn-backref" href="#id2">[2]</a></td><td>Rappel: nous sommes une toute petite équipe !</td></tr>
</tbody>
</table>
</div>
<div class="section" id="le-toolkit">
<h2>Le toolkit</h2>
<div class="section" id="choix-techniques">
<h3>Choix techniques</h3>
<p><em>Cliquet</em> implémente le protocole en Python (<em>2.7, 3.4+, pypy</em>), avec <a class="reference external" href="http://trypyramid.com/">Pyramid</a> <a class="footnote-reference" href="#id6" id="id5">[3]</a>.</p>
<p><strong>Pyramid</strong> est un framework Web qui va prendre en charge tout la partie HTTP,
et qui s'avère pertinent aussi bien pour des petits projets que des plus
ambitieux.</p>
<p><strong>Cornice</strong> est une extension de <em>Pyramid</em>, écrite en partie par Alexis et Tarek,
qui permet d'éviter d'écrire tout le code <em>boilerplate</em> quand on construit une
API REST avec Pyramid.</p>
<p>Avec <em>Cornice</em>, on évite de réécrire à chaque fois le code qui va
cabler les verbes HTTP aux méthodes, valider les entêtes, choisir le sérialiseur
en fonction des entêtes de négociation de contenus, renvoyer les codes HTTP
rigoureux, gérer les entêtes CORS, fournir la validation JSON à partir de schémas...</p>
<p><strong>Cliquet</strong> utilise les deux précédents pour implémenter le protocole et fournir
des abstractions, mais on a toujours <em>Pyramid</em> et <em>Cornice</em> sous la main pour
aller au delà de ce qui est proposé !</p>
<table class="docutils footnote" frame="void" id="id6" rules="none">
<colgroup><col class="label" /><col /></colgroup>
<tbody valign="top">
<tr><td class="label"><a class="fn-backref" href="#id5">[3]</a></td><td>Au tout début nous avons commencé une implémentation avec <em>Python-Eve</em>
(Flask), mais n'étions pas satisfaits de l'approche pour la configuration
de l'API. En particulier du côté magique.</td></tr>
</tbody>
</table>
</div>
<div class="section" id="concepts">
<h3>Concepts</h3>
<p>Bien évidemment, les concepts du toolkit reflètent ceux du protocole mais il y
a des éléments supplémentaires:</p>
<ul class="simple">
<li><strong>Les backends</strong> : abstractions pour le stockage, le cache et les permissions
(<em>ex. PostgreSQL, Redis, en-mémoire, ...</em>)</li>
<li><strong>La supervision</strong> : logging JSON et indicateurs temps-réel (<em>StatsD</em>) pour suivre les
performances et la santé du service.</li>
<li><strong>La configuration</strong> : chargement de la configuration depuis les variables
d'environnement et le fichier <tt class="docutils literal">.ini</tt></li>
<li><strong>La flexibilité</strong> : dés/activation ou substitution de la majorité des composants
depuis la configuration.</li>
<li><strong>Le profiling</strong> : utilitaires de développement pour trouver les <a class="reference external" href="https://fr.wiktionary.org/wiki/goulet_d%E2%80%99%C3%A9tranglement">goulets
d'étranglement</a>.</li>
</ul>
<img alt="Cliquet concepts" class="align-center" src="{filename}/images/cliquet-concepts.png" />
<p>Proportionnellement, l'implémentation du protocole pour les resources REST est
la plus volumineuse dans le code source de <em>Cliquet</em>.
Cependant, comme nous l'avons décrit plus haut, <em>Cliquet</em> fournit tout un
ensemble d'outillage et de bonnes pratiques, et reste
donc tout à fait pertinent pour n'importe quel type d'API, même sans
manipulation de données !</p>
<p>L'objectif de la boîte à outils est de faire en sorte qu'un développeur puisse constuire
une application simplement, en étant sûr qu'elle réponde aux exigeances de la
mise en production, tout en ayant la possibilité de remplacer certaines parties
au fur et à mesure que ses besoins se précisent.</p>
<p>Par exemple, la persistence fournie par défault est <em>schemaless</em> (e.g <em>JSONB</em>),
mais rien n'empêcherait d'implémenter le stockage dans un modèle relationnel.</p>
<p>Comme les composants peuvent être remplacés depuis la configuration, il est
tout à fait possible d'étendre <em>Cliquet</em> avec des notions métiers ou des
technologies exotiques ! Nous avons posé quelques idées dans <a class="reference external" href="http://cliquet.readthedocs.org/en/latest/ecosystem.html">la documentation
de l'éco-système</a>.</p>
<p>Dans les prochaines semaines, nous allons introduire la notion d'«évènements» (ou signaux),
qui permettraient aux extensions de s'interfacer beaucoup plus proprement.</p>
<p>Nous attachons beaucoup d'importance à la clareté du code, la pertinence des
<em>patterns</em>, des tests et de la documentation. Si vous avez des commentaires,
des critiques ou des interrogations, n'hésitez pas à <a class="reference external" href="https://github.com/mozilla-services/cliquet/issues">nous en faire part</a> !</p>
</div>
</div>
<div class="section" id="cliquet-a-l-action">
<h2>Cliquet, à l'action.</h2>
<p>Nous avons écrit un <a class="reference external" href="http://cliquet.readthedocs.org/en/latest/quickstart.html">guide de démarrage</a>,
qui n'exige pas de connaître <em>Pyramid</em>.</p>
<p>Pour illustrer la simplicité et les concepts, voici quelques extraits !</p>
<div class="section" id="etape-1">
<h3>Étape 1</h3>
<p>Activer <em>Cliquet</em>:</p>
<div class="highlight"><pre><span></span><span class="hll"><span class="kn">import</span> <span class="nn">cliquet</span>
</span><span class="kn">from</span> <span class="nn">pyramid.config</span> <span class="kn">import</span> <span class="n">Configurator</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">(</span><span class="n">global_config</span><span class="p">,</span> <span class="o">**</span><span class="n">settings</span><span class="p">):</span>
<span class="n">config</span> <span class="o">=</span> <span class="n">Configurator</span><span class="p">(</span><span class="n">settings</span><span class="o">=</span><span class="n">settings</span><span class="p">)</span>
<span class="hll"> <span class="n">cliquet</span><span class="o">.</span><span class="n">initialize</span><span class="p">(</span><span class="n">config</span><span class="p">,</span> <span class="s1">'1.0'</span><span class="p">)</span>
</span> <span class="k">return</span> <span class="n">config</span><span class="o">.</span><span class="n">make_wsgi_app</span><span class="p">()</span>
</pre></div>
<p>À partir de là, la plupart des outils de <em>Cliquet</em> sont activés et accessibles.</p>
<p>Par exemple, les URLs <em>hello</em> (<tt class="docutils literal">/v1/</tt>) ou <em>supervision</em> (<tt class="docutils literal">/v1/__heartbeat__</tt>).
Mais aussi les backends de stockage, de cache, etc.
qu'il est possible d'utiliser dans des vues classiques <em>Pyramid</em> ou <em>Cornice</em>.</p>
</div>
<div class="section" id="etape-2">
<h3>Étape 2</h3>
<p>Ajouter des vues:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">main</span><span class="p">(</span><span class="n">global_config</span><span class="p">,</span> <span class="o">**</span><span class="n">settings</span><span class="p">):</span>
<span class="n">config</span> <span class="o">=</span> <span class="n">Configurator</span><span class="p">(</span><span class="n">settings</span><span class="o">=</span><span class="n">settings</span><span class="p">)</span>
<span class="n">cliquet</span><span class="o">.</span><span class="n">initialize</span><span class="p">(</span><span class="n">config</span><span class="p">,</span> <span class="s1">'1.0'</span><span class="p">)</span>
<span class="hll"> <span class="n">config</span><span class="o">.</span><span class="n">scan</span><span class="p">(</span><span class="s2">"myproject.views"</span><span class="p">)</span>
</span> <span class="k">return</span> <span class="n">config</span><span class="o">.</span><span class="n">make_wsgi_app</span><span class="p">()</span>
</pre></div>
<p>Pour définir des resources CRUD, il faut commencer par définir un schéma,
avec <em>Colander</em>, et ensuite déclarer une resource:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">cliquet</span> <span class="kn">import</span> <span class="n">resource</span><span class="p">,</span> <span class="n">schema</span>
<span class="k">class</span> <span class="nc">BookmarkSchema</span><span class="p">(</span><span class="n">schema</span><span class="o">.</span><span class="n">ResourceSchema</span><span class="p">):</span>
<span class="n">url</span> <span class="o">=</span> <span class="n">schema</span><span class="o">.</span><span class="n">URL</span><span class="p">()</span>
<span class="hll"><span class="nd">@resource</span><span class="o">.</span><span class="n">register</span><span class="p">()</span>
</span><span class="hll"><span class="k">class</span> <span class="nc">Bookmark</span><span class="p">(</span><span class="n">resource</span><span class="o">.</span><span class="n">BaseResource</span><span class="p">):</span>
</span><span class="hll"> <span class="n">mapping</span> <span class="o">=</span> <span class="n">BookmarkSchema</span><span class="p">()</span>
</span></pre></div>
<p>Désormais, la resource CRUD est disponible sur <tt class="docutils literal">/v1/bookmarks</tt>, avec toutes
les fonctionnalités de synchronisation, filtrage, tri, pagination, timestamp, etc.
De base les enregistrements sont privés, par utilisateur.</p>
<div class="highlight"><pre><span></span><span class="err">$</span> <span class="err">http</span> <span class="err">GET</span> <span class="s2">"http://localhost:8000/v1/bookmarks"</span>
<span class="err">HTTP/</span><span class="mf">1.1</span> <span class="mi">200</span> <span class="err">OK</span>
<span class="err">...</span>
<span class="p">{</span>
<span class="nt">"data"</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="nt">"url"</span><span class="p">:</span> <span class="s2">"http://cliquet.readthedocs.org"</span><span class="p">,</span>
<span class="nt">"id"</span><span class="p">:</span> <span class="s2">"cc103eb5-0c80-40ec-b6f5-dad12e7d975e"</span><span class="p">,</span>
<span class="nt">"last_modified"</span><span class="p">:</span> <span class="mi">1437034418940</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="p">}</span>
</pre></div>
</div>
<div class="section" id="etape-3">
<h3>Étape 3</h3>
<p>Évidemment, il est possible choisir les URLS, les verbes HTTP supportés, de modifier
des champs avant l'enregistrement, etc.</p>
<div class="highlight"><pre><span></span><span class="hll"><span class="nd">@resource</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">collection_path</span><span class="o">=</span><span class="s1">'/user/bookmarks'</span><span class="p">,</span>
</span><span class="hll"> <span class="n">record_path</span><span class="o">=</span><span class="s1">'/user/bookmarks/{{id}}'</span><span class="p">,</span>
</span><span class="hll"> <span class="n">collection_methods</span><span class="o">=</span><span class="p">(</span><span class="s1">'GET'</span><span class="p">,))</span>
</span><span class="k">class</span> <span class="nc">Bookmark</span><span class="p">(</span><span class="n">resource</span><span class="o">.</span><span class="n">BaseResource</span><span class="p">):</span>
<span class="n">mapping</span> <span class="o">=</span> <span class="n">BookmarkSchema</span><span class="p">()</span>
<span class="hll"> <span class="k">def</span> <span class="nf">process_record</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">new</span><span class="p">,</span> <span class="n">old</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
</span><span class="hll"> <span class="k">if</span> <span class="n">old</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">new</span><span class="p">[</span><span class="s1">'device'</span><span class="p">]</span> <span class="o">!=</span> <span class="n">old</span><span class="p">[</span><span class="s1">'device'</span><span class="p">]:</span>
</span><span class="hll"> <span class="n">device</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">headers</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'User-Agent'</span><span class="p">)</span>
</span><span class="hll"> <span class="n">new</span><span class="p">[</span><span class="s1">'device'</span><span class="p">]</span> <span class="o">=</span> <span class="n">device</span>
</span><span class="hll"> <span class="k">return</span> <span class="n">new</span>
</span></pre></div>
<p><a class="reference external" href="http://cliquet.readthedocs.org/en/latest/reference/resource.html">Plus d'infos dans la documentation dédiée</a> !</p>
<div class="admonition note">
<p class="first admonition-title">Note</p>
<p class="last">Il est possible de définir des resources sans validation de schema.
<a class="reference external" href="https://github.com/mozilla-services/kinto/blob/master/kinto/views/records.py">Voir le code source de Kinto</a>.</p>
</div>
</div>
<div class="section" id="etape-4-optionelle">
<h3>Étape 4 (optionelle)</h3>
<p>Utiliser les abstractions de <em>Cliquet</em> dans une vue <em>Cornice</em>.</p>
<p>Par exemple, une vue qui utilise le backend de stockage:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">cliquet</span> <span class="kn">import</span> <span class="n">Service</span>
<span class="n">score</span> <span class="o">=</span> <span class="n">Service</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s2">"score"</span><span class="p">,</span>
<span class="n">path</span><span class="o">=</span><span class="s1">'/score/</span><span class="si">{game}</span><span class="s1">'</span><span class="p">,</span>
<span class="n">description</span><span class="o">=</span><span class="s2">"Store game score"</span><span class="p">)</span>
<span class="nd">@score</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">schema</span><span class="o">=</span><span class="n">ScoreSchema</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">post_score</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="n">collection_id</span> <span class="o">=</span> <span class="s1">'scores-'</span> <span class="o">+</span> <span class="n">request</span><span class="o">.</span><span class="n">match_dict</span><span class="p">[</span><span class="s1">'game'</span><span class="p">]</span>
<span class="n">user_id</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">authenticated_userid</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">validated</span> <span class="c1"># c.f. Cornice.</span>
<span class="hll"> <span class="n">storage</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">registry</span><span class="o">.</span><span class="n">storage</span>
</span><span class="hll"> <span class="n">record</span> <span class="o">=</span> <span class="n">storage</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="n">collection_id</span><span class="p">,</span> <span class="n">user_id</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span> <span class="k">return</span> <span class="n">record</span>
</pre></div>
</div>
</div>
<div class="section" id="vos-retours">
<h2>Vos retours</h2>
<p>N'hésitez pas à nous faire part de vos retours ! Cela vous a donné envie
d'essayer ? Vous connaissez un outil similaire ?
Y-a-t-il des points qui ne sont pas clairs ? Manque de cas d'utilisation concrets ?
Certains aspects mal pensés ? Trop contraignants ? Trop de magie ? Overkill ?</p>
<p>Nous prenons tout.</p>
<div class="section" id="points-faibles">
<h3>Points faibles</h3>
<p>Nous sommes très fiers de ce que nous avons construit, en relativement peu
de temps. Et comme nous l'exposions dans <a class="reference external" href="{filename/2015.07.whistler-use-cases.rst}">l'article précédent</a>, il y a du potentiel !</p>
<p>Cependant, nous sommes conscients d'un certain nombre de points
qui peuvent être vus comme des faiblesses.</p>
<ul class="simple">
<li><strong>La documentation d'API</strong> : actuellement, nous n'avons pas de solution pour qu'un
projet qui utilise <em>Cliquet</em> puisse intégrer facilement toute
<a class="reference external" href="http://cliquet.readthedocs.org/en/latest/api/index.html">la documentation de l'API</a>
obtenue.</li>
<li><strong>La documentation</strong> : il est très difficile d'organiser la documentation, surtout
quand le public visé est aussi bien débutant qu'expérimenté. Nous sommes probablement
victimes du «<a class="reference external" href="https://en.wikipedia.org/wiki/Curse_of_knowledge">curse of knowledge</a>».</li>
<li><strong>Le protocole</strong> : on sent bien qu'on va devoir versionner le protocole. Au
moins pour le désolidariser des versions de <em>Cliquet</em>, si on veut aller au
bout de la philosophie et de l'éco-système.</li>
<li><strong>Le conservatisme</strong> : Nous aimons la stabilité et la robustesse. Mais surtout
nous ne sommes pas tout seuls et devons nous plier aux contraintes de la mise
en production ! Cependant, nous avons très envie de faire de l'async avec Python 3 !</li>
<li><strong>Publication de versions</strong> : le revers de la médaille de la factorisation. Il
arrive qu'on préfère faire évoluer le toolkit (e.g. ajouter une option) pour
un point précis d'un projet. En conséquence, on doit souvent releaser les
projets en cascade.</li>
</ul>
</div>
<div class="section" id="quelques-questions-courantes">
<h3>Quelques questions courantes</h3>
<blockquote>
Pourquoi Python ?</blockquote>
<p>On prend beaucoup de plaisir à écrire du Python, et le calendrier annoncé
initialement était très serré: pas question de tituber avec une technologie
mal maitrisée !</p>
<p>Et puis, après avoir passé près d'un an sur un projet Node.js, l'équipe avait
bien envie de refaire du Python.</p>
<blockquote>
Pourquoi pas Django ?</blockquote>
<p>On y a pensé, surtout parce qu'il y a plusieurs fans de <em>Django REST Framework</em>
dans l'équipe.</p>
<p>On l'a écarté principalement au profit de la légèreté et la modularité de
<em>Pyramid</em>.</p>
<blockquote>
Pourquoi pas avec un framework asynchrone en Python 3+ ?</blockquote>
<p>Pour l'instant nos administrateurs système nous imposent des déploiements en
Python 2.7, à notre grand désarroi /o\</p>
<p>Pour <em>Reading List</em>, nous <a class="reference external" href="https://github.com/mozilla-services/readinglist/blob/1.7.0/readinglist/__init__.py#L19-L26">avions activé</a>
<em>gevent</em>.</p>
<p>Puisque l'approche consiste à implémenter un protocole bien déterminé, nous n'excluons
pas un jour d'écrire un <em>Cliquet</em> en <em>aiohttp</em> ou <em>Go</em> si cela s'avèrerait pertinent.</p>
<blockquote>
Pourquoi pas JSON-API ?</blockquote>
<p>Comme nous l'expliquions <a class="reference external" href="{filename}/2015.05.retour-apidays.rst">au retour des APIdays</a>,
JSON-API est une spécification qui rejoint plusieurs de nos intentions.</p>
<p>Quand nous avons commencé le protocole, nous ne connaissions pas JSON-API.
Pour l'instant, comme notre proposition est beaucoup plus minimaliste, le
rapprochement n'a <a class="reference external" href="https://github.com/mozilla-services/cliquet/issues/254">pas dépassé le stade de la discussion</a>.</p>
<blockquote>
Est-ce que Cliquet est un framework REST pour Pyramid ?</blockquote>
<p>Non.</p>
<p>Au delà des classes de resources CRUD de Cliquet, qui implémentent un
protocole bien précis, il faut utiliser Cornice ou Pyramid directement.</p>
<blockquote>
Est-ce que Cliquet est suffisamment générique pour des projets hors Mozilla ?</blockquote>
<p>Premièrement, nous faisons en sorte que tout soit contrôlable depuis la
configuration <tt class="docutils literal">.ini</tt> pour permettre la dés/activation ou substitution des
composants.</p>
<p>Si le protocole HTTP/JSON des resources CRUD vous satisfait,
alors Cliquet est probablement le plus court chemin pour construire une
application qui tient la route.</p>
<p>Mais l'utilisation des resources CRUD est facultative, donc Cliquet reste pertinent
si les bonnes pratiques en terme de mise en production ou les abstractions fournies
vous paraissent valables !</p>
<p>Cliquet reste un moyen simple d'aller très vite pour mettre sur pied
une application Pyramid/Cornice.</p>
<blockquote>
Est-ce que les resources JSON supporte les modèles relationnels complexes ?</blockquote>
<p>La couche de persistence fournie est très simple, et devrait
répondre à la majorité des cas d'utilisation où les données n'ont pas de
relations.</p>
<p>En revanche, il est tout à fait possible de bénéficier de tous les aspects
du protocole en utilisant une classe <tt class="docutils literal">Collection</tt> maison, qui se chargerait
elle de manipuler les relations.</p>
<p>Le besoin de relations pourrait être un bon prétexte pour implémenter le
protocole avec Django REST Framework :)</p>
<blockquote>
Est-il possible de faire ci ou ça avec Cliquet ?</blockquote>
<p>Nous aimerions collecter des besoins pour écrire un ensemble de «recettes/tutoriels». Mais
pour ne pas travailler dans le vide, nous aimerions <a class="reference external" href="https://github.com/mozilla-services/cliquet/issues">connaitre vos idées</a> !
(<em>ex. brancher l'authentification Github, changer le format du logging JSON, stocker des
données cartographiques, ...</em>)</p>
<blockquote>
Est-ce que Cliquet peut manipuler des fichiers ?</blockquote>
<p><a class="reference external" href="https://github.com/mozilla-services/cliquet/issues/236">Nous l'envisageons</a>,
mais pour l'instant nous attendons que le besoin survienne en interne pour se
lancer.</p>
<p>Si c'est le cas, le protocole utilisé sera <a class="reference external" href="http://remotestorage.io/">Remote Storage</a>,
afin notamment de s'intégrer dans l'éco-système grandissant.</p>
<blockquote>
Est-ce que la fonctionnalité X va être implémentée ?</blockquote>
<p><em>Cliquet</em> est déjà bien garni. Plutôt qu'implémenter la fonctionnalité X,
il y a de grandes chances que nous agissions pour s'assurer que les abstractions
et les mécanismes d'extension fournis permettent de l'implémenter sous forme
d'extension.</p>
</div>
</div>
Service de nuages : Perspectives pour l'été2015-07-07T00:00:00+02:002015-07-07T00:00:00+02:00tag:blog.notmyidea.org,2015-07-07:/service-de-nuages-perspectives-pour-lete-fr.html<p class="first last">Le travail en cours et les fonctionnalités à venir pour les prochains mois.</p>
<p><em>Cet article est repris depuis le blog « Service de Nuages » de mon équipe à Mozilla</em></p>
<p>Mozilla a pour coutume d'organiser régulièrement des semaines de travail où tous les employés
sont réunis physiquement. Pour cette dernière édition, nous avons pu retrouver
nos collègues du monde entier à <a class="reference external" href="http://www.openstreetmap.org/node/268148288#map=4/50.12/-122.95">Whistler, en Colombie Britannique au Canada</a> !</p>
<img alt="«All Hands» talk about Lego, by @davidcrob - CC0" class="align-center" src="{filename}/images/whistler-talks.jpg" />
<p>Ce fût l'occasion pour notre équipe de se retrouver, et surtout de partager notre
vision et nos idées dans le domaine du stockage, afin de collecter des cas d'utilisation pour
notre solution <a class="reference external" href="https://kinto.readthedocs.org">Kinto</a>.</p>
<p>Dans cet article, nous passons en revue les pistes que nous avons pour
les prochains mois.</p>
<div class="section" id="ateliers-et-promotion">
<h2>Ateliers et promotion</h2>
<p>Nicolas a présenté <a class="reference external" href="https://github.com/mozilla-services/kinto.js">Kinto.js</a> dans un atelier dédié, avec comme support de
présentation le <a class="reference external" href="http://kintojs.readthedocs.org/en/latest/tutorial/">tutorial d'introduction</a>.</p>
<p>L'application résultante, pourtant toute simple, permet d'appréhender les
concepts de synchronisation de Kinto. Le tout sans installation prélable,
puisque Rémy a mis en place un <a class="reference external" href="https://kinto.dev.mozaws.net/v1/">serveur de dev effacé tous les jours</a>.</p>
<p>Nous avions mis un point d'honneur à faire du Vanilla.JS, déjà pour éviter les
combats de clochers autour des frameworks, mais aussi pour mettre en évidence qu'avec
HTML5 et ES6, on n'était plus aussi démunis qu'il y a quelques années.</p>
<p>Ce petit atelier nous a permis de nous rendre compte qu'on avait encore de
grosses lacunes en terme de documentation, surtout en ce qui concerne
l'éco-système et la vision globale des projets (Kinto, Kinto.js, Cliquet, ...).
Nous allons donc faire de notre mieux pour combler ce manque.</p>
<img alt="Kinto.js workshop - CC0" class="align-center" src="{filename}/images/whistler-workshop.jpg" />
</div>
<div class="section" id="mozilla-payments">
<h2>Mozilla Payments</h2>
<p>Comme <a class="reference external" href="http://www.servicedenuages.fr/la-gestion-des-permissions">décrit précédemment</a>, nous avons mis en place un système de permissions pour répondre aux besoins de suivi des paiements et abonnements.</p>
<p>Pour ce projet, Kinto sera utilisé depuis une application Django, via un client Python.</p>
<p>Maintenant que les développements ont été livrés, il faut transformer l'essai, réussir l'intégration, l'hébergement et la montée en puissance. La solution doit être livrée à la fin de l'année.</p>
<div class="section" id="a-venir">
<h3>À venir</h3>
<p>Nous aimerions en profiter pour implémenter une fonctionnalité qui nous tient à coeur : la construction de la liste des enregistrements accessibles en lecture sur une collection partagée.</p>
<img alt="Whistler Alta Lake - CC0" class="align-center" src="{filename}/images/whistler-lake.jpg" />
</div>
</div>
<div class="section" id="firefox-os-et-stockage">
<h2>Firefox OS et stockage</h2>
<p>Nous avons eu beaucoup d'échanges avec l'équipe de Firefox OS, avec qui nous avions
déjà eu l'occasion de collaborer, pour le <a class="reference external" href="https://github.com/mozilla-services/msisdn-gateway">serveur d'identification BrowserID par SMS</a> et pour <a class="reference external" href="https://github.com/mozilla-services/loop-server">Firefox Hello</a>.</p>
<div class="section" id="in-app-sync">
<h3>In-App sync</h3>
<p>Kinto, la solution simple promue pour la synchronisation de données dans les applications
Firefox OS ? La classe ! C'est ce qu'on avait en tête depuis longtemps, déjà à
l'époque avec <a class="reference external" href="http://daybed.readthedocs.org/">Daybed</a>. Voici donc une belle opportunité à saisir !</p>
<p>Il va falloir expliciter les limitations et hypothèses simplificatrices de notre
solution, surtout en termes de gestion de la concurrence. Nous sommes persuadés
que ça colle avec la plupart des besoins, mais il ne faudrait pas décevoir :)</p>
<p>Le fait que <a class="reference external" href="https://github.com/daleharvey">Dale</a>, un des auteurs de <a class="reference external" href="http://pouchdb.com/">PouchDB</a> et <a class="reference external" href="https://github.com/michielbdejong">Michiel de Jong</a>, un des auteurs de <a class="reference external" href="http://remotestorage.io/">Remote Storage</a>, nous aient encouragés sur nos premiers pas nous a bien motivé !</p>
</div>
<div class="section" id="cut-the-rope">
<h3>Cut the Rope</h3>
<p>Kinto devrait être mis à profit pour synchroniser les paramètres et les scores
du <a class="reference external" href="http://mozilla.cuttherope.net/">jeu</a>. Un premier exercice et une première vitrine sympas !</p>
</div>
<div class="section" id="syncto">
<h3>« SyncTo »</h3>
<p><a class="reference external" href="https://docs.services.mozilla.com/storage/apis-1.5.html">Firefox Sync</a> est la solution qui permet de synchroniser les données de Firefox (favoris, extensions, historique, complétion des formulaires, mots de passe, ...) entre plusieurs périphériques, de manière chiffrée.</p>
<p>L'implémentation du client en JavaScript est relativement complexe et date un peu maintenant.
Le code existant n'est pas vraiment portable dans <em>Firefox OS</em> et les tentatives de réécriture
n'ont pas abouti.</p>
<p>Nous souhaitons implémenter un pont entre <em>Kinto</em> et <em>Firefox Sync</em>, de manière
à pouvoir utiliser le client <em>Kinto.js</em>, plus simple et plus moderne, pour récupérer
les contenus et les stocker dans IndexedDB. Le delta à implémenter côté serveur est faible car nous nous étions
inspirés du protocole déjà éprouvé de Sync. Côté client, il s'agira surtout de
câbler l'authentification BrowserId et la Crypto.</p>
<p>Alexis a sauté sur l'occasion pour commencer l'écriture d'<a class="reference external" href="https://github.com/mozilla-services/syncclient">un client python pour Firefox Sync</a>, qui servira de brique de base pour l'écriture du service.</p>
</div>
<div class="section" id="cloud-storage">
<h3>Cloud Storage</h3>
<p>Eden Chuang et Sean Lee ont présenté les avancées sur l'intégration de services de stockages
distants (<em>DropBox, Baidu Yun</em>) dans <em>Firefox OS</em>. Actuellement, leur preuve de
concept repose sur <a class="reference external" href="https://fr.wikipedia.org/wiki/Filesystem_in_Userspace">FUSE</a>.</p>
<p>Nous avons évidemment en tête d'introduire la notion de fichiers attachés dans
<em>Kinto</em>, en implémentant la specification
<a class="reference external" href="https://tools.ietf.org/html/draft-dejong-remotestorage-05">*Remote Storage*</a>,
mais pour l'instant les cas d'utilisations ne se sont pas encore présentés officiellement.</p>
</div>
<div class="section" id="id2">
<h3>À venir</h3>
<p>Nous serons probablement amenés à introduire la gestion de la concurrence dans
le client JS, en complément de ce qui a été fait sur le serveur, pour permettre
les écritures simultanées et synchronisation en tâche de fond.</p>
<p>Nous sommes par ailleurs perpétuellement preneurs de vos retours — et bien
entendu de vos contributions — tant sur le code <a class="reference external" href="https://github.com/mozilla-services/kinto/">serveur</a>
que <a class="reference external" href="https://github.com/mozilla-services/kinto.js/">client</a> !</p>
<img alt="Firefox OS Cloud Storage Presentation - CC0" class="align-center" src="{filename}/images/whistler-cloud-storage.jpg" />
</div>
</div>
<div class="section" id="contenus-applicatifs-de-firefox">
<h2>Contenus applicatifs de Firefox</h2>
<p>Aujourd'hui Firefox a un cycle de release de six semaines. Un des objectifs
consiste à désolidariser certains contenus applicatifs de ces cycles
relativement longs (ex. <em>règles de securité, dictionnaires, traductions, ...</em>) <a class="footnote-reference" href="#id4" id="id3">[1]</a>.</p>
<p>Il s'agit de données JSON et binaire qui doivent être versionnées et synchronisées par
les navigateurs (<em>lecture seule</em>).</p>
<p>Il y a plusieurs outils officiels qui existent pour gérer ça (<em>Balrog</em>, <em>Shavar</em>, ...),
et pour l'instant, aucun choix n'a été fait. Mais lors des conversations avec
l'équipe en charge du projet, ce fût vraiment motivant de voir que même pour
ce genre de besoins internes, <em>Kinto</em> est tout aussi pertinent !</p>
<table class="docutils footnote" frame="void" id="id4" rules="none">
<colgroup><col class="label" /><col /></colgroup>
<tbody valign="top">
<tr><td class="label"><a class="fn-backref" href="#id3">[1]</a></td><td>La bonne nouvelle c'est que toutes les fonctionnalités <em>third-party</em> qui ont
été intégrées récemment vont redevenir des <em>add-ons</em> \o/.</td></tr>
</tbody>
</table>
<img alt="Landscape - CC0" class="align-center" src="{filename}/images/whistler-landscape.jpg" />
</div>
<div class="section" id="awesome-bar">
<h2>Awesome bar</h2>
<p>L'équipe <em>Firefox Labs</em>, le laboratoire qui élève des pandas roux en éprouvette,
serait vraiment intéressé par notre solution, notamment pour abreuver en données
un prototype pour améliorer <em>Awesome bar</em>, qui fusionnerait URL, historique et recherche.</p>
<p>Nous ne pouvons pas en dire beaucoup plus pour l'instant, mais les fonctionnalités
de collections d'enregistrements partagées entre utilisateurs de <em>Kinto</em>
correspondent parfaitement à ce qui est envisagé pour le futur du navigateur :)</p>
<div class="section" id="id5">
<h3>À venir</h3>
<p>Nous serons donc probablement amenés, avant de la fin de l'année, à introduire des
fonctionnalités d'indexation et de recherche <em>full-text</em> (comprendre <em>ElasticSearch</em>).
Cela rejoint nos plans précédents, puisque c'est quelque chose que nous avions dans
<em>Daybed</em>, et qui figurait sur notre feuille de route !</p>
<img alt="Firefox Labs Meeting - CC0" class="align-center" src="{filename}/images/whistler-labs.jpg" />
</div>
</div>
<div class="section" id="browser-html">
<h2>Browser.html</h2>
<p>L'équipe <em>Recherche</em> explore les notions de plateforme, et travaille notamment
sur l'implémentation d'un navigateur en JS/HTML avec <em>React</em>:
<a class="reference external" href="https://github.com/mozilla/browser.html">browser.html</a></p>
<p><em>Kinto</em> correspond parfaitement aux attentes
de l'équipe pour synchroniser les données associées à un utilisateur.</p>
<p>Il pourrait s'agir de données de navigation (comme Sync), mais aussi de collections
d'enregistrements diverses, comme par exemple les préférences du navigateur
ou un équivalent à <em>Alexa.com Top 500</em> pour fournir la complétion d'URL sans
interroger le moteur de recherche.</p>
<p>L'exercice pourrait être poussé jusqu'à la synchronisation d'états <em>React</em>
entre périphériques (par exemple pour les onglets).</p>
<div class="section" id="id7">
<h3>À venir</h3>
<p>Si <em>browser.html</em> doit stocker des données de navigation, il faudra ajouter
des fonctionnalités de chiffrement sur le client JS. Ça tombe bien, c'est un
sujet passionant, et <a class="reference external" href="http://www.w3.org/TR/WebCryptoAPI/">il y a plusieurs standards</a> !</p>
<p>Pour éviter d'interroger le serveur à intervalle régulier afin de synchroniser les
changements, l'introduction des <a class="reference external" href="https://w3c.github.io/push-api/">*push notifications*</a> semble assez naturelle.
Il s'agirait alors de la dernière pierre qui manque à l'édifice pour obtenir
un «<em>Mobile/Web backend as a service</em>» complet.</p>
<img alt="Roadmap - CC0" class="align-center" src="{filename}/images/whistler-roadmap.jpg" />
</div>
</div>
<div class="section" id="conclusion">
<h2>Conclusion</h2>
<p>Nous sommes dans une situation idéale, puisque ce que nous avions imaginé
sur <a class="reference external" href="https://github.com/mozilla-services/kinto/wiki/Roadmap">notre feuille de route</a> correspond à ce qui nous est demandé par les
différentes équipes.</p>
<p>L'enjeu consiste maintenant à se coordonner avec tout le monde, ne pas décevoir,
tenir la charge, continuer à améliorer et à faire la promotion du produit, se concentrer
sur les prochaines étapes et embarquer quelques contributeurs à nos cotés pour
construire une solution libre, générique, simple et auto-hébergeable pour le stockage
de données sur le Web :)</p>
<img alt="Friday Night Party - CC0" class="align-center" src="{filename}/images/whistler-top-roof.jpg" />
</div>
Service de nuages : Stocker et interroger les permissions avec Kinto2015-05-26T00:00:00+02:002015-05-26T00:00:00+02:00tag:blog.notmyidea.org,2015-05-26:/service-de-nuages-stocker-et-interroger-les-permissions-avec-kinto-fr.html<p class="first last">Comment faire pour stocker et interroger la base de données au sujet des permissions avec Kinto ?</p>
<p><em>Cet article est repris depuis le blog « Service de Nuages » de mon équipe à Mozilla</em></p>
<p><strong>tl;dr: On a maintenant un super système de permission mais comment faire pour stocker et interroger ces permissions de manière efficace ?</strong></p>
<div class="section" id="la-problematique">
<h2>La problématique</h2>
<p>Maintenant que nous avons défini un modèle de gestion des permissions
sur les objets qui nous satisfait, le problème est de stocker ces
permissions de manière efficace afin de pouvoir autoriser ou interdire
l'accès à un objet pour la personne qui fait la requête.</p>
<p>Chaque requête sur notre API va générer une ou plusieurs demandes
d'accès, il faut donc que la réponse soit très rapide sous peine
d'impacter la vélocité du service.</p>
</div>
<div class="section" id="obtenir-la-liste-des-principals-d-un-utilisateur">
<h2>Obtenir la liste des "principals" d'un utilisateur</h2>
<p>Les <em>principals</em> de l'utilisateur correspondent à son <tt class="docutils literal">user_id</tt>
ainsi qu'à la liste des identifiants des groupes dans lesquels il a
été ajouté.</p>
<p>Pour éviter de recalculer les <em>principals</em> de l'utilisateur à chaque
requête, le mieux reste de maintenir une liste des <em>principals</em> par
utilisateur.</p>
<p>Ainsi lorsqu'on ajoute un utilisateur à un groupe, il faut bien penser
à ajouter le groupe à la liste des <em>principals</em> de l'utilisateur.</p>
<p>Ça se complexifie lorsqu'on ajoute un groupe à un groupe.</p>
<p>Dans un premier temps interdire l'ajout d'un groupe à un groupe est
une limitation qu'on est prêts à accepter pour simplifier le
modèle.</p>
<p>L'avantage de maintenir la liste des <em>principals</em> d'un utilisateur
lors de la modification de cette liste c'est qu'elle est déjà
construite lors des lectures, qui sont dans notre cas plus fréquentes
que les écritures.</p>
<p>Cela nécessite de donner un identifiant unique aux groupes pour tous
les <em>buckets</em>.</p>
<p>Nous proposons de de les nommer avec leur URI:
<tt class="docutils literal">/buckets/blog/groups/moderators</tt></p>
</div>
<div class="section" id="obtenir-la-liste-des-principals-d-un-ace">
<h2>Obtenir la liste des "principals" d'un ACE</h2>
<blockquote>
Rappel, un "ACE" est un <em>Access Control Entry</em>, un des éléments
d'une ACL (e.g. <em>modifier un enregistrement</em>).</blockquote>
<p>Avec le <a class="reference external" href="{filename}/2015.05.cliquet-permissions.rst">système de permissions choisi</a>, les permissions d'un
objet héritent de celle de l'objet parent.</p>
<p>Par exemple, avoir le droit d'écriture sur un <em>bucket</em> permet la
création des permissions et la modification de tous ses records.</p>
<p>Ce qui veut dire que pour obtenir la liste complète des <em>principals</em>
ayant une permission sur un objet, il faut regarder à plusieurs
endroits.</p>
<p>Rémy a <a class="reference external" href="https://gist.github.com/Natim/77c8f61c1d42e476cef8#file-permission-py-L9-L52">décrit dans un gist la liste d'héritage de chaque permission</a>.</p>
<p>Prenons l'exemple de l'ajout d'un record dans une collection.</p>
<p>Le droit <tt class="docutils literal">records:create</tt> est obtenu si l'on a l'un des droits suivants:</p>
<ul class="simple">
<li><tt class="docutils literal">bucket:write</tt></li>
<li><tt class="docutils literal">collection:write</tt></li>
<li><tt class="docutils literal">records:create</tt></li>
</ul>
<p>Notre première idée était de stocker les permissions sur chaque objet
et de maintenir la liste exhaustive des permissions lors d'une
modification d'ACL. Cependant cela nécessitait de construire cette
liste lors de l'ajout d'un objet et de mettre à jour tout l'arbre lors
de sa suppression. (<em>Je vous laisse imaginer le nombre d'opérations
nécessaires pour ajouter un administrateur sur un *bucket</em> contenant
1000 collections avec 100000 records chacune.*)</p>
<p>La solution que nous avons désormais adoptée consiste à stocker les
<em>principals</em> de chaque <em>ACE</em> (<em>qui</em> a le droit de faire telle action
sur l'objet), et de faire l'union des <em>ACE</em> hérités, afin de les
croiser avec les <em>principals</em> de l'utilisateur :</p>
<blockquote>
(ACE(object, permission) ∪ inherited_ACE) ∩ PRINCIPALS(user)</blockquote>
<p>Par exemple l'ACE: <tt class="docutils literal">/buckets/blog/collections/article:records:create</tt> hérite de
l'ACE <tt class="docutils literal">/buckets/blog/collections/article:write</tt> et de <tt class="docutils literal">/buckets/blog:write</tt> :</p>
<blockquote>
(ACE(/buckets/blog/collections/article:records:create) ∪ ACE(/buckets/blog/collections/article:write) ∪ ACE(/buckets/blog:write)) ∩ PRINCIPALS('fxa:alexis')</blockquote>
</div>
<div class="section" id="recuperer-les-donnees-de-l-utilisateur">
<h2>Récupérer les données de l'utilisateur</h2>
<p>La situation se corse lorsqu'on souhaite limiter la liste des
<em>records</em> d'une collection à ceux accessibles pour l'utilisateur, car
on doit faire cette intersection pour tous les <em>records</em>.</p>
<p>Une première solution est de regarder si l'utilisateur est mentionné
dans les <em>ACL*s du *bucket</em> ou de la <em>collection</em>:</p>
<p>Ensuite, si ce n'est pas le cas, alors on filtre les <em>records</em> pour
lesquels les <em>principals</em> correspondent à ceux de l'utilisateur.</p>
<div class="highlight"><pre><span></span><span class="n">principals</span> <span class="o">=</span> <span class="n">get_user_principals</span><span class="p">(</span><span class="n">user_id</span><span class="p">)</span>
<span class="n">can_read_all</span> <span class="o">=</span> <span class="n">has_read_perms</span><span class="p">(</span><span class="n">bucket_id</span><span class="p">,</span> <span class="n">collection_id</span><span class="p">,</span>
<span class="n">principals</span><span class="p">)</span>
<span class="k">if</span> <span class="n">can_read_all</span><span class="p">:</span>
<span class="n">records</span> <span class="o">=</span> <span class="n">get_all_records</span><span class="p">(</span><span class="n">bucket_id</span><span class="p">,</span> <span class="n">collection_id</span><span class="p">,</span>
<span class="n">filters</span><span class="o">=</span><span class="p">[</span><span class="o">...</span><span class="p">])</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">records</span> <span class="o">=</span> <span class="n">filter_read_records</span><span class="p">(</span><span class="n">bucket_id</span><span class="p">,</span> <span class="n">collection_id</span><span class="p">,</span>
<span class="n">principals</span><span class="o">=</span><span class="n">principals</span><span class="p">,</span>
<span class="n">filters</span><span class="o">=</span><span class="p">[</span><span class="o">...</span><span class="p">])</span>
</pre></div>
<p>Il faudra faire quelque chose de similaire pour la suppression
multiple, lorsqu'un utilisateur souhaitera supprimer des
enregistrements sur lesquels il a les droits de lecture mais pas
d'écriture.</p>
</div>
<div class="section" id="le-modele-de-donnees">
<h2>Le modèle de données</h2>
<p>Pour avoir une idée des requêtes dans un backend SQL, voyons un peu ce
que donnerait le modèle de données.</p>
<div class="section" id="le-format-des-id">
<h3>Le format des ID</h3>
<p>Utiliser des URI comme identifiant des objets présente de nombreux
avantages (lisibilité, unicité, cohérence avec les URLs)</p>
<ul class="simple">
<li>bucket: <tt class="docutils literal">/buckets/blog</tt></li>
<li>groupe: <tt class="docutils literal">/buckets/blog/group/moderators</tt></li>
<li>collection: <tt class="docutils literal">/buckets/blog/collections/articles</tt></li>
<li>record: <tt class="docutils literal"><span class="pre">/buckets/blog/collections/articles/records/02f3f76f-7059-4ae4-888f-2ac9824e9200</span></tt></li>
</ul>
</div>
<div class="section" id="les-tables">
<h3>Les tables</h3>
<p>Pour le stockage des principals et des permissions:</p>
<div class="highlight"><pre><span></span><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="k">user</span><span class="p">(</span><span class="n">id</span> <span class="nb">TEXT</span><span class="p">,</span> <span class="n">principals</span> <span class="nb">TEXT</span><span class="p">[]);</span>
<span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">perms</span><span class="p">(</span><span class="n">ace</span> <span class="nb">TEXT</span><span class="p">,</span> <span class="n">principals</span> <span class="nb">TEXT</span><span class="p">[]);</span>
</pre></div>
<p>La table <em>perms</em> va associer des <em>principals</em> à chaque <em>ACE</em>
(e.g.``/buckets/blog:write``).</p>
<p>Pour le stockage des données:</p>
<div class="highlight"><pre><span></span><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="k">object</span><span class="p">(</span><span class="n">id</span> <span class="nb">TEXT</span><span class="p">,</span> <span class="k">type</span> <span class="nb">TEXT</span><span class="p">,</span> <span class="n">parent_id</span> <span class="nb">TEXT</span><span class="p">,</span> <span class="k">data</span> <span class="n">JSONB</span><span class="p">,</span>
<span class="n">write_principals</span> <span class="nb">TEXT</span><span class="p">[],</span> <span class="n">read_principals</span> <span class="nb">TEXT</span><span class="p">[]);</span>
</pre></div>
<p>La colonne <em>parent_id</em> permet de savoir à qui appartient l'objet
(e.g. groupe d'un <em>bucket</em>, collection d'un <em>bucket</em>, <em>record</em> d'une
collection, ...).</p>
</div>
<div class="section" id="exemple-d-utilisateur">
<h3>Exemple d'utilisateur</h3>
<div class="highlight"><pre><span></span><span class="k">INSERT</span> <span class="k">INTO</span> <span class="k">user</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">principals</span><span class="p">)</span>
<span class="k">VALUES</span> <span class="p">(</span><span class="s1">'fxa:alexis'</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">);</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="k">user</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">principals</span><span class="p">)</span>
<span class="k">VALUES</span> <span class="p">(</span><span class="s1">'fxa:natim'</span><span class="p">,</span>
<span class="s1">'{"/buckets/blog/groups/moderators"}'</span><span class="p">);</span>
</pre></div>
</div>
<div class="section" id="exemple-d-objets">
<h3>Exemple d'objets</h3>
<div class="section" id="bucket">
<h4>Bucket</h4>
<div class="highlight"><pre><span></span><span class="k">INSERT</span> <span class="k">INTO</span> <span class="k">object</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="k">type</span><span class="p">,</span> <span class="n">parent_id</span><span class="p">,</span> <span class="k">data</span><span class="p">,</span>
<span class="n">read_principals</span><span class="p">,</span> <span class="n">write_principals</span><span class="p">)</span>
<span class="k">VALUES</span> <span class="p">(</span>
<span class="s1">'/buckets/blog'</span><span class="p">,</span>
<span class="s1">'bucket'</span><span class="p">,</span>
<span class="k">NULL</span><span class="p">,</span>
<span class="s1">'{"name": "blog"}'</span><span class="p">::</span><span class="n">JSONB</span><span class="p">,</span>
<span class="s1">'{}'</span><span class="p">,</span> <span class="s1">'{"fxa:alexis"}'</span><span class="p">);</span>
</pre></div>
</div>
<div class="section" id="group">
<h4>Group</h4>
<div class="highlight"><pre><span></span><span class="k">INSERT</span> <span class="k">INTO</span> <span class="k">object</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="k">type</span><span class="p">,</span> <span class="n">parent_id</span><span class="p">,</span> <span class="k">data</span><span class="p">,</span>
<span class="n">read_principals</span><span class="p">,</span> <span class="n">write_principals</span><span class="p">)</span>
<span class="k">VALUES</span> <span class="p">(</span>
<span class="s1">'/buckets/blog/groups/moderators'</span><span class="p">,</span>
<span class="s1">'group'</span><span class="p">,</span>
<span class="s1">'/buckets/blog'</span><span class="p">,</span>
<span class="s1">'{"name": "moderators", "members": ['</span><span class="n">fxa</span><span class="p">:</span><span class="n">natim</span><span class="s1">']}'</span><span class="p">::</span><span class="n">JSONB</span><span class="p">,</span>
<span class="s1">'{}'</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">);</span>
</pre></div>
<p>Ce groupe peut être gére par <tt class="docutils literal">fxa:alexis</tt> puisqu'il a la permission
<tt class="docutils literal">write</tt> dans le <em>bucket</em> parent.</p>
</div>
<div class="section" id="collection">
<h4>Collection</h4>
<div class="highlight"><pre><span></span><span class="k">INSERT</span> <span class="k">INTO</span> <span class="k">object</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="k">type</span><span class="p">,</span> <span class="n">parent_id</span><span class="p">,</span> <span class="k">data</span><span class="p">,</span>
<span class="n">read_principals</span><span class="p">,</span> <span class="n">write_principals</span><span class="p">)</span>
<span class="k">VALUES</span> <span class="p">(</span>
<span class="s1">'/buckets/blog/collections/articles'</span><span class="p">,</span>
<span class="s1">'collection'</span><span class="p">,</span>
<span class="s1">'/buckets/blog'</span><span class="p">,</span>
<span class="s1">'{"name": "article"}'</span><span class="p">::</span><span class="n">JSONB</span><span class="p">,</span>
<span class="s1">'{"system.Everyone"}'</span><span class="p">,</span>
<span class="s1">'{"/buckets/blog/groups/moderators"}'</span><span class="p">);</span>
</pre></div>
<p>Cette collection d'articles peut être lue par tout le monde,
et gérée par les membres du groupe <tt class="docutils literal">moderators</tt>, ainsi que
<tt class="docutils literal">fxa:alexis</tt>, via le <em>bucket</em>.</p>
</div>
<div class="section" id="records">
<h4>Records</h4>
<div class="highlight"><pre><span></span><span class="k">INSERT</span> <span class="k">INTO</span> <span class="k">object</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="k">type</span><span class="p">,</span> <span class="n">parent_id</span><span class="p">,</span> <span class="k">data</span><span class="p">,</span>
<span class="n">read_principals</span><span class="p">,</span> <span class="n">write_principals</span><span class="p">)</span>
<span class="k">VALUES</span> <span class="p">(</span>
<span class="s1">'/buckets/blog/collections/articles/records/02f3f76f-7059-4ae4-888f-2ac9824e9200'</span><span class="p">,</span>
<span class="s1">'record'</span><span class="p">,</span>
<span class="s1">'/buckets/blog/collections/articles'</span><span class="p">,</span>
<span class="s1">'{"name": "02f3f76f-7059-4ae4-888f-2ac9824e9200",</span>
<span class="s1"> "title": "Stocker les permissions", ...}'</span><span class="p">::</span><span class="n">JSONB</span><span class="p">,</span>
<span class="s1">'{}'</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">);</span>
</pre></div>
</div>
</div>
<div class="section" id="interroger-les-permissions">
<h3>Interroger les permissions</h3>
<div class="section" id="id1">
<h4>Obtenir la liste des "principals" d'un ACE</h4>
<p>Comme vu plus haut, pour vérifier une permission, on fait l'union des
<em>principals</em> requis par les objets hérités, et on teste leur
intersection avec ceux de l'utilisateur:</p>
<div class="highlight"><pre><span></span><span class="k">WITH</span> <span class="n">required_principals</span> <span class="k">AS</span> <span class="p">(</span>
<span class="k">SELECT</span> <span class="k">unnest</span><span class="p">(</span><span class="n">principals</span><span class="p">)</span> <span class="k">AS</span> <span class="n">p</span>
<span class="k">FROM</span> <span class="n">perms</span>
<span class="k">WHERE</span> <span class="n">ace</span> <span class="k">IN</span> <span class="p">(</span>
<span class="s1">'/buckets/blog:write'</span><span class="p">,</span>
<span class="s1">'/buckets/blog:read'</span><span class="p">,</span>
<span class="s1">'/buckets/blog/collections/article:write'</span><span class="p">,</span>
<span class="s1">'/buckets/blog/collections/article:read'</span><span class="p">)</span>
<span class="p">),</span>
<span class="n">user_principals</span> <span class="k">AS</span> <span class="p">(</span>
<span class="k">SELECT</span> <span class="k">unnest</span><span class="p">(</span><span class="n">principals</span><span class="p">)</span>
<span class="k">FROM</span> <span class="k">user</span>
<span class="k">WHERE</span> <span class="n">id</span> <span class="o">=</span> <span class="s1">'fxa:natim'</span>
<span class="p">)</span>
<span class="k">SELECT</span> <span class="k">COUNT</span><span class="p">(</span><span class="o">*</span><span class="p">)</span>
<span class="k">FROM</span> <span class="n">user_principals</span> <span class="n">a</span>
<span class="k">INNER</span> <span class="k">JOIN</span> <span class="n">required_principals</span> <span class="n">b</span>
<span class="k">ON</span> <span class="n">a</span><span class="p">.</span><span class="n">p</span> <span class="o">=</span> <span class="n">b</span><span class="p">.</span><span class="n">p</span><span class="p">;</span>
</pre></div>
</div>
<div class="section" id="filtrer-les-objets-en-fonction-des-permissions">
<h4>Filtrer les objets en fonction des permissions</h4>
<p>Pour filtrer les objets, on fait une simple intersection de liste
(<em>merci PostgreSQL</em>):</p>
<div class="highlight"><pre><span></span><span class="k">SELECT</span> <span class="k">data</span>
<span class="k">FROM</span> <span class="k">object</span> <span class="n">o</span><span class="p">,</span> <span class="k">user</span> <span class="n">u</span>
<span class="k">WHERE</span> <span class="n">o</span><span class="p">.</span><span class="k">type</span> <span class="o">=</span> <span class="s1">'record'</span>
<span class="k">AND</span> <span class="n">o</span><span class="p">.</span><span class="n">parent_id</span> <span class="o">=</span> <span class="s1">'/buckets/blog/collections/article'</span>
<span class="k">AND</span> <span class="p">(</span><span class="n">o</span><span class="p">.</span><span class="n">read_principals</span> <span class="o">&&</span> <span class="n">u</span><span class="p">.</span><span class="n">principals</span> <span class="k">OR</span>
<span class="n">o</span><span class="p">.</span><span class="n">write_principals</span> <span class="o">&&</span> <span class="n">u</span><span class="p">.</span><span class="n">principals</span><span class="p">)</span>
<span class="k">AND</span> <span class="n">u</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="s1">'fxa:natim'</span><span class="p">;</span>
</pre></div>
<p>Les listes s'indexent bien, notamment grâce aux <a class="reference external" href="http://www.postgresql.org/docs/current/static/indexes-types.html">index GIN</a>.</p>
</div>
</div>
<div class="section" id="avec-redis">
<h3>Avec Redis</h3>
<p><em>Redis</em> présente plusieurs avantages pour ce genre de
problématiques. Notamment, il gère les <em>set</em> nativement (listes de
valeurs uniques), ainsi que les opérations d'intersection et d'union.</p>
<p>Avec <em>Redis</em> on peut écrire l'obtention des <em>principals</em> pour un <em>ACE</em>
comme cela :</p>
<div class="highlight"><pre><span></span>SUNIONSTORE temp_perm:/buckets/blog/collections/articles:write permission:/buckets/blog:write permission:/buckets/blog/collections/articles:write
SINTER temp_perm:/buckets/blog/collections/articles:write principals:fxa:alexis
</pre></div>
<ul class="simple">
<li><tt class="docutils literal">SUNIONSTORE</tt> permet de créer un set contenant les éléments de
l'union de tous les set suivants. Dans notre cas on le nomme
<tt class="docutils literal"><span class="pre">temp_perm:/buckets/blog/collections/articles:write</span></tt> et il contient
l'union des sets d'ACLs suivants:
- <tt class="docutils literal"><span class="pre">permission:/buckets/blog:write</span></tt>
- <tt class="docutils literal"><span class="pre">permission:/buckets/blog/collections/articles:write</span></tt></li>
<li><tt class="docutils literal">SINTER</tt> retourne l'intersection de tous les sets passés en paramètres dans notre cas :
- <tt class="docutils literal"><span class="pre">temp_perm:/buckets/blog/collections/articles:write</span></tt>
- <tt class="docutils literal">principals:fxa:alexis</tt></li>
</ul>
<p>Plus d'informations sur :
- <a class="reference external" href="http://redis.io/commands/sinter">http://redis.io/commands/sinter</a>
- <a class="reference external" href="http://redis.io/commands/sunionstore">http://redis.io/commands/sunionstore</a></p>
<p>Si le set résultant de la commande <tt class="docutils literal">SINTER</tt> n'est pas vide, alors
l'utilisateur possède la permission.</p>
<p>On peut ensuite supprimer la clé temporaire <tt class="docutils literal">temp_perm</tt>.</p>
<p>En utilisant <tt class="docutils literal">MULTI</tt> on peut <a class="reference external" href="https://gist.github.com/Natim/77c8f61c1d42e476cef8#file-permission-py-L117-L124">même faire tout cela au sein d'une
transaction</a>
et garantir ainsi l'intégrité de la requête.</p>
</div>
</div>
<div class="section" id="conclusion">
<h2>Conclusion</h2>
<p>La solution a l'air simple mais nous a demandé beaucoup de réflexion
en passant par plusieurs propositions.</p>
<p>L'idée finale est d'avoir :</p>
<ul class="simple">
<li>Un backend spécifique permettant de stocker les <em>principals</em> des
utilisateurs et des <em>ACE</em> (e.g. avec les sets Redis) ;</li>
<li>La liste des principals read et write sur la table des objets.</li>
</ul>
<p>C'est dommage d'avoir le concept de permissions à deux endroits, mais
cela permet de connaître rapidement la permission d'un utilisateur sur
un objet et également de pouvoir récupérer tous les objets d'une
collection pour un utilisateur si celui-ci n'a pas accès à tous les
records de la collection, ou toutes les collections du bucket.</p>
</div>
Les problèmes de PGP2015-05-25T00:00:00+02:002015-05-25T00:00:00+02:00tag:blog.notmyidea.org,2015-05-25:/les-problemes-de-pgp.html<blockquote>
<p>Flip a bit in the communication between sender and recipient and they
will experience decryption or verification errors. How high are the
chances they will start to exchange the data in the clear rather than
trying to hunt down the man in the middle?</p>
<p>-- <a href="http://secushare.org/PGP">http://secushare.org/PGP</a></p>
</blockquote>
<p>Une fois …</p><blockquote>
<p>Flip a bit in the communication between sender and recipient and they
will experience decryption or verification errors. How high are the
chances they will start to exchange the data in the clear rather than
trying to hunt down the man in the middle?</p>
<p>-- <a href="http://secushare.org/PGP">http://secushare.org/PGP</a></p>
</blockquote>
<p>Une fois passé l'euphorie du "il faut utiliser PGP pour l'ensemble de
nos communications", j'ai réalisé lors de discussions que PGP avait
plusieurs problèmes, parmi ceux-ci:</p>
<ul>
<li>Les <em>meta données</em> (y compris le champ "sujet" de la conversation)
sont quand même échangées en clair (il est possible de savoir qu'un
message à été échangé entre telle et telle personne, a telle date);</li>
<li>PGP se base sur un protocole de communication qui est lui non
chiffré, et il est donc facile de soit se tromper, soit dégrader le
mode de conversation vers une méthode non chiffrée;</li>
<li>Il est facile de connaître votre réseau social avec PGP, puisque
tout le principe est de signer les clés des personnes dont vous
validez l'identité;</li>
<li>En cas de fuite de votre clé privée, tous les messages que vous avez
chiffrés avec elle sont compromis. On dit que PGP ne fournit pas de
<em>forward secrecy</em>;</li>
<li>La découverte de la clé de pairs se passe souvent <em>en clair</em>, sans
utiliser une connexion "sécurisée" (HTTPS). Tout le monde peut donc
voir ces échanges et savoir de qui vous cherchez la clé;</li>
<li>Les discussions de groupes sont très difficiles: il faut chiffrer
pour chacun des destinataires (ou que ceux-ci partagent une paire de
clés).</li>
</ul>
<p>Je suis en train de creuser à propos les alternatives à PGP, par exemple
<a href="https://pond.imperialviolet.org/">Pond</a>, qui lui ne construit pas par
dessus un standard déjà établi, et donc n'hérite pas de ses défauts
(mais pas non plus de son réseau déjà établi).</p>
<p>En attendant, quelques bonnes pratiques sur PGP ;)</p>
<h2>Bonnes pratiques</h2>
<p>Il est en fait assez facile d'utiliser PGP de travers. Riseup à fait <a href="https://help.riseup.net/en/security/message-security/openpgp/best-practices">un
excellent
guide</a>
qui explique comment configurer son installation correctement.</p>
<ul>
<li>J'en ai déjà parlé, mais il faut absolument choisir des phrases de
passes suffisamment longues. Pas facile de les retenir, mais
indispensable. Vous pouvez aussi avoir un document chiffré avec une
clé que vous ne mettez jamais en ligne, qui contiens ces phrases de
passe, au cas ou vous les oubliez.</li>
<li>Générez des clés RSA de 4096 bits, en utilisant sha512;</li>
<li>Il faut utiliser une date d'expiration de nos clés suffisamment
proche (2 ans). Il est possible de repousser cette date si
nécessaire, par la suite.</li>
</ul>
<p>Parmi les choses les plus frappantes que j'ai rencontrées:</p>
<ul>
<li>Utiliser le <em>flag</em> –hidden-recipient avec PGP pour ne pas dévoiler
qui est le destinataire du message;</li>
<li>Ne pas envoyer les messages de brouillons sur votre serveur, ils le
seraient en clair !;</li>
<li>Utilisez HPKS pour communiquer avec les serveurs de clés, sinon tout
le trafic est en clair.</li>
</ul>
<p>Le <a href="https://bitmask.net/">projet Bitmask</a> vise lui à rendre les outils
de chiffrement d'échanges de messages et de VPN simples à utiliser,
encore quelque chose à regarder.</p>
<p>Enfin bref, y'a du taf.</p>