ECT

Etoile Cercle Triangle - Blog de Damien

How to configure Docker DNS on Ubuntu in a corporate environment?

| Comments

When you are in a corporate environment, network is often configured to restrict outgoing requests, such as DNS resolution requests. By default, Docker uses Google DNS (8.8.8.8 and 8.8.4.4) to resolve domain names:

1
2
3
4
$ docker run busybox nslookup google.com
Server:    8.8.8.8
Address 1: 8.8.8.8
nslookup: can't resolve 'google.com'

If you are in a corporate environment, resolution fails because you have to use your internal DNS server.

You can find many, many documentation about how to configure Docker DNS on Ubuntu (such as official Docker doc), but none of them answer these requirements all together:

1/ Configuration must be portable: works at home or at work or anywhere else

2/ Configuration must be written in files not provided by a deb package to avoid conflicts after package updates

Portable configuration

Ubuntu provides Dnsmasq, a local DNS server configured to use DNS server of your network through DHCP. Docker can’t use it because it doesn’t allow to use a local DNS server if its IP address is a local configuration, such as 127.0.0.1.

So we’ll configure Dnsmasq to listen to another available IP address, such as the one provided by docker0 interface, to solve this issue.

Edit the new file /etc/NetworkManager/dnsmasq.d/docker.conf (as sudo):

1
interface=docker0

Then restart NetworkManager service:

1
sudo service network-manager restart

Configure Docker DNS

Extract the IP address of the docker0 interface:

1
2
$ docker network inspect bridge | grep Gateway
                    "Gateway": "172.17.0.1"

Then edit /etc/docker/daemon.json (as sudo):

1
2
3
{
    "dns": ["172.17.0.1"]
}

Restart Docker:

1
sudo service docker restart

Finally, check DNS resolution works again:

1
2
3
4
5
6
$ docker run busybox nslookup google.com
Server:    172.17.0.1
Address 1: 172.17.0.1
Name:      google.com
Address 1: 2a00:1450:4009:811::200e lhr26s02-in-x200e.1e100.net
Address 2: 216.58.198.174 lhr25s10-in-f14.1e100.net

Le Web sécurisé par défaut grâce à DevOps et Let’s Encrypt ?

| Comments

Initialement publié sur le Journal Du Net.

L’accès à de nombreux sites Web n’est pas sécurisé pour plusieurs raisons : c’est compliqué et cher. Voyons comment les principes du DevOps et un coup de pouce de Let’s Encrypt pourraient nous conduire au Web sécurisé par défaut.

Les principes du DevOps insistent fortement sur l’automatisation des processus de construction ou de déploiement des systèmes informatiques. De nombreuses entreprises se sont emparées de ces nouveaux outils et les mettent en œuvre progressivement afin de livrer plus vite, au meilleur coût, avec une qualité élevée, des fonctionnalités à forte valeur ajoutée à leurs clients et leurs utilisateurs.

La plupart des étapes du processus de construction des systèmes informatique sont en effet automatisables : construction et paramétrage des serveurs, construction des applications (serveur ou mobiles), exécution des tests, livraison, déploiement, gestion et prise en compte des pannes… La liste est chaque jour plus longue. Il reste pourtant un domaine où l’automatisation est faible voire inexistante : la sécurité de l’accès aux sites Internet. Or, le contexte actuel d’(in)sécurité du Web en général montre que la sécurité ne doit plus être sacrifiée sur l’autel de la complexité ou du prix.

La sécurité du Web

Cette sécurité repose sur des protocoles comme HTTPS, TLS, des algorithmes de chiffrement ou de hashage et au cœur, des certificats X.509. La combinaison de ces éléments nous permet d’accéder à des sites Internet de manière sécurisée (httpS://…), nous garantissant des échanges de données chiffrées et donc illisibles pour tout tiers qui tenterait d’intercepter les données. Du moins, en théorie car les failles de sécurités des systèmes informatiques et le talent des pirates leur permettent quelquefois d’accéder à des données confidentielles.

En pratique, la sécurité repose sur d’autres concepts qui permettent de renforcer la sécurité et de limiter les risques de piratage mais l’essentiel du processus repose sur les certificats de la norme X.509.

Normés certes, mais compliqués et chers

X.509 est une norme qui définit entre autres les fameux certificats sur lesquels reposent HTTPS et TLS. Une chaine de confiance hiérarchique est établie, dans laquelle des certificats particuliers -dits “racine”- signent et valident d’autres certificats. Les certificats racines sont gérés par des autorités de certification qui sont responsables de l’émission de nouveaux certificats.

Les certificats racines sont directement embarqués dans les navigateurs web, ce qui permet à ces derniers d’ouvrir des connexions sécurisées avec des sites web lorsqu’ils présentent un certificat délivré par une autorités de certification reconnue.

Les certificats X.509 sont de bons concepts cryptographiques, qui présentent cependant des inconvénients majeurs.

1/ Ils sont compliqués à gérer. Un certificat doit être d’abord généré, ce qui requiert plusieurs étapes et fait intervenir plusieurs acteurs. L’utilisateur émetteur doit générer une demande de signature de certificat (CSR) lors de laquelle il choisit un certain nombre de paramètres, tels que la taille de la clé du certificat (il faut se tenir au courant des valeurs assurant un niveau de sécurité adéquat, ce qui évolue avec le temps) et un mot de passe optionnel qui sera requit à chaque usage du certificat. Puis l’autorité de certification doit signer la demande et renvoyer le certificat à l’émetteur de la demande. Enfin, il faut configurer le site web avec le certificat reçu de l’autorité et d’autres paramètres, ce qui est tout sauf évident : les serveurs web ne proposant que rarement une configuration simplifiée (des dizaines de paramètres sont ajustables).

Un certificat a en outre une durée de vie limitée -généralement entre 1 et 3 ans- et doit donc être renouvelé avant son expiration par un processus proche de sa génération initiale. Il faut alors de nouveau mettre à jour la configuration du serveur.

Corollaire direct, gérer les certificats est long et lent. Comme le processus fait intervenir plusieurs acteurs, avec une complexité élevée et d’occurrence rare, pourquoi automatiser quelque chose qui se produit très rarement ? Les certificat sont donc dans la plupart des organisations générés et gérés manuellement et la difficulté est élevée à chaque fois qu’il faut les générer ou renouveler.

2/ Ils sont chers. Demander un certificat à une autorité de certification publique est la plupart du temps payant (jusqu’à plusieurs centaine d’euros par certificat).

Compte tenu de ces inconvénients, le choix des concepteurs de site web est donc radical : ils ne sécurisent pas ou très peu quand ils ne peuvent pas faire autrement. Pourtant, l’accès sécurisé à un site web est désormais essentiel ! Google favorise et privilégie les sites Internet accessibles au travers de HTTPS en leur offrant un meilleur référencement sur son moteur de recherche. De nouveaux protocoles qui vont apporter de meilleures performances dans les usages mobiles comme HTTP/2, requièrent, aussi, obligatoirement HTTPS, donc TLS, donc des certificats.

Des nouvelles autorités de certification

Revenons à nos pratiques DevOps, et en particulier l’automatisation. Certains acteurs majeurs du Cloud ont bien compris la problématique et proposent depuis quelques temps la gestion complète des certificats, de leur cycle de vie et de leur configuration dans les serveurs web, sans (presque) aucune opération manuelle ! (comme AWS certificate Manager) Une raison de plus pour basculer dans le Cloud !

Pour les autres, une nouvelle autorité de certification répond aux trois problématiques exposées précédemment : Let’s Encrypt. Let’s Encrypt est une autorité de certification officiellement opérationnelle depuis le 12 avril 2016, avec comme principes clés la gratuité, l’automatisation et la sécurité. Le tout garanti par la transparence et l’ouverture de ses processus et de ses codes sources.

Comme toute autorité de certification, Let’s Encrypt délivre des certificats, les renouvelle mais aussi les configure sur les serveurs web, et ce automatiquement, sans autre intervention humaine que de fournir un paramétrage initial. Ensuite, les outils s’occupent de tout ! Et comme un bon processus est un processus récurrent fréquent, Let’s Encrypt force à l’automatisation en générant des certificats à durée de vie extrêmement courte : seulement trois mois, avec préconisation de renouvellement au bout de deux mois. Impossible à utiliser sans automatisation donc.

Le Web sécurisé par défaut grâce aux principes DevOps ?

De nombreuses entreprises et entités soutiennent Let’s Encrypt, financièrement et moralement, comme Mozilla, Akamai, Cisco, OVH, Google, Facebook, Gemalto, Gandi, Free, HP… et l’usage des certificats émis par Let’s Encrypt se répand comme une trainée de poudre : le cap du million de certificats générés a été franchi en mars 2016 et la progression continue sur le même rythme ; de plus en plus de services Internet proposent ou vont proposer très prochainement de sécuriser les sites web de leurs utilisateurs au travers de Let’s Encrypt : Gandi, Free/Online, Wordpress, OVH, sans compter divers CDN (KeyCDN, Kloudsec, CDNSun …).

L’usage simplifié promu par Let’s Encrypt n’est pas étranger à cette adoption, qui sera sans nul doute, massive dans les années à venir. En effet, tout le monde y gagne :

  • Les concepteurs de site : activation de la sécurité de manière simple, voire transparente, voire même par défaut ; meilleur référencement auprès de Google,
  • Les utilisateurs : garantie de confidentialité de leurs activités sur de nombreux sites web ; en ce temps d’espionnage massif, c’est essentiel pour garantir la confiance,
  • Les services Internet d’hébergement de sites ou les CDN : c’est un service gratuit de plus à offrir à leur client et qui peut en attirer de nouveaux.

Les autorités de certification traditionnelles sont pour le moment relativement épargnées puisque que ce sont principalement des sites web qui n’étaient pas auparavant sécurisés qui basculent vers Let’s Encrypt. De plus, certains types de certificats ne sont actuellement pas pris en compte par Let’s Encrypt, ce qui continue de garantir leur monopole. Mais pour combien de temps ?

Certains considèrent cependant que les choix effectués par Let’s Encrypt vont à l’encontre de l’élévation du niveau de sécurité des sites web. Mais ce sont justement ces choix qui permettent et conduisent à l’automatisation. Le web sécurisé par défaut n’a jamais été aussi proche grâce aux principes DevOps et à Let’s Encrypt.

Redeemable promises avec Play Framework

| Comments

J’ai eu à intervenir récemment sur un programme écrit en Play Framework v2.3, dont le rôle est assez simple : faire passe-plat entre un client et un serveur et effectuant notamment des transformations protocolaires (comme REST vers TCP par exemple). Ce qui m’a donné l’occasion d’utiliser les RedeemablePromise de Play Framework, pas du tout documentées à ce jour.

Client asynchrone

Les différents échanges de messages entre les systèmes peuvent être représentés à l’aide du diagramme de séquence suivant :

Le programme en Play Framework est nommé “PassePlat” dans ce diagramme.

Les messages montants sont implémentés sous forme d’une API RESTful ; tandis que le retour du “Serveur”, asynchrone, est implémentée avec des callbacks et une requête REST vers le client d’origine. Voici un exemple de pseudo-code Java sur le traitement du message de retour :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
/**
 * Receive responses from "server"
 */
public class ServerReceiver {

  public void onServerResponse(Response response) {
      // Utilisation de la librairie play.libs.ws.WS
      // pour effectuer des appels REST
      WS.url("http://client_url/api/response")
          .setContentType("application/json")
          .post(response.asJson());
  }

  public void onServerError(Throwable error) {
      WS.url("http://client_url/api/response")
          .setContentType("application/json")
          // Serialize exception as JSON
          .post(Helper.asJson(error);
  }

}
...

Le système complet fonctionne bien, mais n’est pas très satisfaisant :

  • le Client et le PassePlat sont très couplés. En effet, le Client doit connaître l’adresse du PassePlat pour lui envoyer les messages montants et le PassePlat doit connaitre l’adresse du Client pour lui renvoyer le message de retour. Bref, une dépendance cyclique ;

  • Le fonctionnement du Serveur est asynchrone (messages montants et de retours sont décorrélés) et cette implémentation a déporté l’asynchronisme jusqu’au Client, alors que le client (l’humain cette fois-ci :-) voulait plutôt un fonctionnement synchrone du Client, ce qui était plus facile à appréhender pour lui.

Client synchrone

Nous avons donc travaillés sur une implémentation du système plutôt comme ceci :

L’appel du Client vers le PassePlat est donc bloqué tant que l’acquittement et la réponse du Serveur ne sont pas parvenus au PassePlat.

Comment ?

On peut faire cela très simplement avec les RedeemablePromise de Play Framework.

Les RedeemablePromise sont une implémentation du design pattern promise, désormais répandu dans l’informatique pour résoudre le callback hell -l’enfer des callbacks-, problème très fréquent avec la programmation asynchrone. En effet, votre code est exécuté au sein de callbacks en réponse à des évènements : fin de traitement, lecture d’un fichier, arrivée d’un message par Web Socket, …

On trouve des implémentations de ce pattern naturellement en Javascript (plusieurs même), mais aussi en Scala, en Java, …

Voyons comment nous pouvons les utiliser pour répondre à notre nouveau besoin.

Adaptons la classe qui réceptionne les évènements venant du Serveur :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
/**
 * Receive responses from "server"
 */
public class ServerReceiver {

  // Initialisation de la promesse, à vide
  private RedeemablePromise<Response> promise = RedeemablePromise.empty();

  // get/set

  public void onServerResponse(Response response) {
      // La promesse est résolue ou complétée avec succès ici
      promise.success(response);
  }

  public void onServerError(Throwable error) {
      // La promesse est résolue ou complétée en erreur avec l'exception
      promise.failure(error);
  }

}
...

L’élément clé ici est le fait de “résoudre” ou “compléter” la promesse en succès ou en erreur selon le cas. Attention à n’oublier aucun cas de fin de traitement du Serveur, sinon le Client restera bloqué dans ces cas non prévus.

Jetons maintenant un coup d’oeil au contrôleur REST sur le PassePlat qui reçoit les requêtes HTTP depuis le Client et qui “bloque” tant que la réponse du Serveur n’est pas parvenue :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
public static Promise<Result> sendMessage(...) {

  // Envoi du message entrant vers le serveur
  // server.send(message)

  ServerReceiver serverReceiver = new ServerReceiver();

  // Ecoute des messages retour
  server.listen(serverReceiver);

  return
      // Promesse de type Promise<Response>
      serverReceiver.getPromise()
      // Conversion 'success' en Promise<Result>
      .map(response ->
          ok(JSON.toJson(response));
      );
}
...

Dans le cas nominal, la Response sera convertie en Result au sein de la méthode map (programmation fonctionnelle).

Et dans le cas où la promesse a été résolue en erreur ? Par défaut, Play va générer une réponse HTTP avec un code retour 500 et une sérialisation de l’exception renvoyée. Si vous souhaitez définir vous-même votre propre retour, il faut que le traitement de l’erreur génère un Result standard.

Voici comment adapter la transformation de la promesse pour renvoyer une erreur 500 et une sérialisation de l’exception en cas de promesse résolue en erreur :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
return
  // Promesse de type Promise<Response>
  promise
  // Conversion 'success' en Promise<Result>
  .map(response ->
      ok(Json.toJson(response));
  )
  // Conversion 'failure' en Promise<Result>
  .recover(error ->
      // Serialize exception as JSON
      // and send HTTP 500 status
      internalServerError(Json.toJson(error));
  );

Au final, nous avons pu rendre l’appel du Client synchrone en à peine quelques lignes de code grâce à l’API riche de Play Framework. Enfin, vous remarquerez que Java 8 améliore significativement la lisibilité du code. Cependant, on reste loin de Scala, de Groovy ou tout simplement de Javascript.