<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Solona, Govtech by Theodo</title>
    <description>The latest articles on DEV Community by Solona, Govtech by Theodo (@solona).</description>
    <link>https://dev.to/solona</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F8682%2F6610cdcb-54d9-4ff2-9ac0-61d7c1e771ff.png</url>
      <title>DEV Community: Solona, Govtech by Theodo</title>
      <link>https://dev.to/solona</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/solona"/>
    <language>en</language>
    <item>
      <title>[Série Auth/Aut] Introduction : l'importance du contrôle d'accès</title>
      <dc:creator>Clément Di Domenico</dc:creator>
      <pubDate>Wed, 30 Oct 2024 10:51:39 +0000</pubDate>
      <link>https://dev.to/solona/serie-authaut-introduction-limportance-du-controle-dacces-17dl</link>
      <guid>https://dev.to/solona/serie-authaut-introduction-limportance-du-controle-dacces-17dl</guid>
      <description>&lt;h3&gt;
  
  
  Résumé
&lt;/h3&gt;

&lt;p&gt;Cette série d’articles illustre la mise en place de politiques de contrôle d’accès basées sur des rôles au sein d’une application web. Nous prendrons en exemple un service numérique composé un frontend en Angular et d’un backend en Spring Boot. La gestion des utilisateurs se ferra grâce à Keycloak.&lt;/p&gt;

&lt;p&gt;Dans ce premier article, nous allons présenter les enjeux de sécurité relatifs du contrôle d’accès. Par la suite, nous mettrons en places les différentes briques du système décrit en exemple.&lt;/p&gt;

&lt;h3&gt;
  
  
  Qu’est ce que le contrôle d’accès ?
&lt;/h3&gt;

&lt;p&gt;Dans le cadre du développement de services numériques, les applications produites doivent quasiment toujours être utilisées avec des d’utilisateurs aux besoins variées et qui ne nécessitent pas tous l’accès aux même ressources. Certaines ressources doivent aussi être protégées ou accessibles uniquement par certains utilisateurs. &lt;/p&gt;

&lt;p&gt;Prenons en exemple une application web permettant d’effectuer des démarches administratives en ligne. Tout utilisateur authentifié doit être en mesure de créer des demandes via des formulaires, et de consulter les demandes qu’il a effectué pour en connaître le status de traitement. Les agents de l’administration doivent quand à eux valider ou refuser des demandes. Ce mécanisme de validation doit être uniquement accessible aux agents et non simplement aux utilisateurs authentifiés. On a ici deux types d’utilisateurs avec deux modes d’accès différents aux ressources que composent les opérations sur les demandes.&lt;/p&gt;

&lt;p&gt;Le contrôle d’accès désigne ainsi les mécanismes permettant de &lt;strong&gt;réguler l’accès à des ressources&lt;/strong&gt;. Celles-ci peuvent être de nature diverses comme par exemple des bases de données, des espaces de stockages de fichiers ou encore des routes dans une API. Le rôle du contrôle d’accès est de s’assurer que seule une entité (en général un utilisateur) autorisée peut interagir avec les ressources dont elle fait la demande.&lt;/p&gt;

&lt;p&gt;On distingue usuellement deux parties dans le contrôle d’accès : &lt;strong&gt;l’authentification&lt;/strong&gt; et &lt;strong&gt;l’autorisation&lt;/strong&gt;. &lt;br&gt;
Dans la première, l’objectif est de vérifier l’identité d’un utilisateur. Il existe de nombreuses méthodes d’authentification comme l’utilisation d’un mot de passe, de jetons, de certificats ou même de clé physique.&lt;br&gt;
Dans la deuxième, on souhaite déterminer les permissions relatives à l’usage notre système dont l’utilisateur dispose. Les permissions sont en général associées à des opérations à réaliser sur des ressources (création, lecture, modification, suppression).&lt;/p&gt;

&lt;h3&gt;
  
  
  Pourquoi est-ce important ?
&lt;/h3&gt;

&lt;p&gt;En permettant la restriction de l’usage de ressources, le contrôle d’accès se trouve donc en première ligne de la stratégie de sécurité d’un service numérique. Ainsi, une mauvaise configuration de cette brique est souvent à l’origine de failles de sécurité qui peuvent donner lieu par la suite à des incidents en production et des attaques.&lt;/p&gt;

&lt;p&gt;Le rapport OWASP Top 10 est un document de référence produit par la fondation OWASP en agrégeant des rapports de tests de pénétrations et d’audits de cybersécurité. Il présente les 10 vulnérabilités les plus représentées. Dans sa version la plus récente datant de &lt;a href="https://owasp.org/Top10/A01_2021-Broken_Access_Control/" rel="noopener noreferrer"&gt;2021&lt;/a&gt;, on retrouve à la première place du classement les politiques de contrôles d’accès non fonctionnelles avec une présence (plus ou moins sévère) dans 94% du jeux de données. Cette vulnérabilité conduit même à des incidents dans environ 4% de cas.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;• &lt;a href="https://owasp.org/Top10/A01_2021-Broken_Access_Control/" rel="noopener noreferrer"&gt;&lt;strong&gt;A01:2021-Broken Access Control&lt;/strong&gt;&lt;/a&gt; moves up from the fifth position; 94% of applications were tested for some form of broken access control. The 34 Common Weakness Enumerations (CWEs) mapped to Broken Access Control had more occurrences in applications than any other category.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ue manière simple et efficace d’éviter ce type de vulnérabilité est de systématiquement refuser l’accès à n’importe quelle ressource non publique tant qu’aucune autre politique de permission n’a été mise en place : c’est le &lt;strong&gt;refus par défaut&lt;/strong&gt;. OWASP préconise aussi d’appliquer le &lt;strong&gt;principe de moindre privilège&lt;/strong&gt; qui consiste à donner à un utilisateur uniquement les permissions dont il a besoin pour effectuer les actions liées à son utilisation du service, plutôt que de lui attribuer un rôle avec des permissions très étendues au delà de ses besoins.&lt;/p&gt;

&lt;p&gt;Pour les applications backend, on retrouve dans la plupart des frameworks la possibilité de créer des middlewares ou des configurations de sécurité permettant la gestion globale de l’accès à toutes les routes exposées. La &lt;code&gt;SecurityFilterChain&lt;/code&gt; de Spring Security en est un exemple avec la possibilité de faire appel à la méthode &lt;code&gt;denyAll()&lt;/code&gt; qui rejète toutes les requêtes effectuées par défaut.&lt;/p&gt;

&lt;h3&gt;
  
  
  Application pratique et fil rouge
&lt;/h3&gt;

&lt;p&gt;Au cours de cette série d’articles, nous utiliserons en fil rouge l’exemple de l’application web permettant d’effectuer des démarches administratives. On définit les types d’utilisateurs :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;les citoyens : il s’agit d’utilisateurs authentifiés ayant la possibilité de créer une demande et de lister seulement les demandes qu’ils ont créées.&lt;/li&gt;
&lt;li&gt;les agents : des utilisateurs authentifiés qui peuvent lister toutes les demandes et les valider.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pour rester efficace mais représentatif d’un cas d’usage réel plus complexe, on mettra en place l’architecture suivante :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;un application frontend, réalisée avec le framework &lt;strong&gt;Angular,&lt;/strong&gt; servira d’interface utilisateur&lt;/li&gt;
&lt;li&gt;un backend réalisé en Java avec le framework &lt;strong&gt;Spring Boot,&lt;/strong&gt; exposera une API REST permettant des opérations sur les demandes&lt;/li&gt;
&lt;li&gt;une brique de gestion des utilisateurs via &lt;strong&gt;Keycloak&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;une base de données &lt;strong&gt;PostgreSQL&lt;/strong&gt; pour la persistence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;L’interface utilisateur disposera pour tout utilisateur authentifié d’un formulaire pour effectuer une demande et d’une page recensant les demandes effectuées. Les agents auront eux accès à la liste des demandes avec un bouton permettant de les valider.&lt;/p&gt;

&lt;p&gt;L’API REST exposée par le backend disposera des routes suivantes :&lt;/p&gt;

&lt;p&gt;Accès à tous les utilisateurs authentifiés :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;POST /demandes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /demandes/me&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Accès restreint aux agents :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET /demandes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PUT /demandes/:id&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn9lecqrbfowqemzvtnc6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn9lecqrbfowqemzvtnc6.png" alt="Schéma d’architecture de l’application web pour la réalisation de démarche en ligne" width="800" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Le contrôle d’accès est à la base de la stratégie de sécurité d’un service numérique et constitue la première barrière sur laquelle un attaquant potentiel pourra se retrouver bloquer.&lt;/p&gt;

&lt;p&gt;Nous avons défini un exemple d’application web avec des contraintes métier nécessitant la séparation d’utilisateurs en groupes aux permissions distinctes, et l’architecture technique qui nous permettra de développer une telle application. Cette application poursuivra son développement dans les futures articles de cette série.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>keycloak</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>-STORYTIME- Il tente de déployer sans Internet, ça tourne mal</title>
      <dc:creator>Clément Di Domenico</dc:creator>
      <pubDate>Mon, 29 Apr 2024 09:36:13 +0000</pubDate>
      <link>https://dev.to/solona/-storytime-il-tente-de-deployer-sans-internet-ca-tourne-mal-4931</link>
      <guid>https://dev.to/solona/-storytime-il-tente-de-deployer-sans-internet-ca-tourne-mal-4931</guid>
      <description>&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; on a tendance à oublier la valeur ajoutée gigantesque de tous les outils mis en place pour développer et administrer un service numérique, jusqu’au moment où l’on n’y a plus accès. Faire face aux problèmes que résolvent ces outils permet de pleinement comprendre pourquoi on peut effectivement en avoir besoin, plutôt que de les utiliser partout parce qu’ils sont à la mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le petit nid douillet du dev
&lt;/h2&gt;

&lt;p&gt;Le travail de dev de nos jours est un vrai régal.&lt;/p&gt;

&lt;p&gt;J’arrive au travail (ou je sors juste de mon lit), j’allume mon laptop dernier cri acheté reconditionné sur Backmarket, je lance mon IDE et hop, je suis prêt à coder. Tout le code est disponible sur un dépôt stocké en ligne sur une plateforme SaaS. À chaque push mon code est testé, analysé, linté, formaté, et je peux ouvrir des PRs pour intégrer mon travail en continu avec celui de mes collègues.&lt;/p&gt;

&lt;p&gt;Il me suffit en général d’un simple clic pour lancer un processus de déploiement en production. Les pipelines de Continuous Delivery se chargeront pour moi de build les artifacts et de les déployer via les ressources en place chez mon cloud provider préféré. Mon infrastructure prendra soin d’assurer un maintien du service pour mes utilisateurs, le tout pendant que je déguste tranquillement un thé au jasmin en attendant la notification de succès sur mon téléphone.&lt;/p&gt;

&lt;p&gt;En cas d’incident, je suis averti en temps réel et je peux lancer un roll back en un claquement de doigts. Je dispose de logs, de traces, de rapports d’analyse, de graphiques pour investiguer et produire des post-mortems qui permettront une amélioration continue de mon équipe et de mon architecture.&lt;/p&gt;

&lt;p&gt;Il n’y a rien à redire, c’est un bonheur absolu. Jusqu’au moment où je découvre les contraintes techniques imposées par le prochain projet sur lequel je vais devoir travailler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Petit oiseau si tu n’as pas d’ailes …
&lt;/h2&gt;

&lt;p&gt;Bien calé dans ma bouée voguant sur un océan d’insouciance (avec un thé au jasmin sur le porte gobelet), je découvre la multinationale &lt;strong&gt;&lt;em&gt;Cave Inc&lt;/em&gt;&lt;/strong&gt;. &lt;em&gt;(disclaimer : pour des raisons de confidentialité, les noms des entreprises et des personnes présents dans cet article sont fictifs.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;L’entreprise souhaite développer un outil custom pour faciliter des opérations métier. Il sera déployé sur une VM sur son réseau &lt;em&gt;interne&lt;/em&gt; et accessible par les employés. Je me redresse sur ma bouée. Le réseau de Cave Inc n’est pas relié à Internet pour des raisons de sécurité et la seule porte d’entrée numérique que l’entreprise nous met à disposition est une boucle de mail dont le poids ne doit pas dépasser 5Mo.&lt;/p&gt;

&lt;p&gt;Ma bouée se prend dans un récif et explose, m’envoyant m’échouer sur une île déserte. Je sauve in-extremis mon thé au jasmin.&lt;/p&gt;

&lt;p&gt;Il va nous falloir concevoir, coder et livrer un outil à un client qui n’aura pas accès à Internet pour le déployer sur son réseau. Pas question de se décourager, nous sommes des Ingénieurs Logiciel, on est là pour résoudre des problèmes même si on n’a pas forcément accès aux outils habituels pour nous simplifier la vie.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftywk0vzofbvngcm8qr64.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftywk0vzofbvngcm8qr64.png" alt="thunderstorm" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  L’envol
&lt;/h2&gt;

&lt;p&gt;Le projet sera constitué d’un frontend, d’un backend et d’une base de données. Il sera déployé sur une VM Linux par un ingénieur de Cave Inc que nous appellerons René. En un tour de boucle email nous apprenons que la VM de René dispose de Docker. Parfait, nous allons pouvoir livrer le code qui pourra être déployé sous forme de containers qui seront orchestrés par docker-compose. Ce n’est pas idéal comme solution mais c’est mieux que rien.&lt;/p&gt;

&lt;p&gt;Aïe premier accroc, René ne disposant pas d’Internet, il ne va pas être en mesure de build les images à partir du code et des Dockerfiles qu’on lui fournit. Nous allons donc build les images nous même et les lui liverons directement prêtes à tourner. On peut utiliser la commande &lt;code&gt;docker save&lt;/code&gt; pour sauvegarder dans une archive une liste d’images dans le registry local (préalablement buildée ou pull depuis un registry online). René pourra, lui, utiliser la commande &lt;code&gt;docker load&lt;/code&gt; pour charger les images que nous lui auront livrées dans le registry local de la VM et déployer l’outil.&lt;/p&gt;

&lt;p&gt;Aïe deuxième accroc. L’archive avec les 3 images pèse 600Mo, impossible de l’envoyer à René par email. Il nous faut nous résoudre à livrer PHYSIQUEMENT le produit à René, par l’intermédiaire d’une clé USB. Nos espoirs s’effondrent, adieu les déploiements en continu. La pluie se met à couler à grosses gouttes sur la plage et dilue le peu de thé au jasmin sauvé du naufrage encore dans ma tasse.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwrplmert3nbbvz4115sl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwrplmert3nbbvz4115sl.png" alt="process" width="800" height="128"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On se reprend, il faut composer avec les contraintes du projet. Si on n’a pas le droit à l’erreur, il va falloir bétonner la procédure et la rendre &lt;em&gt;error-proof&lt;/em&gt;. En temps normal on définirait des jobs dans la CI qui nous permettraient de produire l’artifact à livrer directement au client en pushant un tag par exemple. Malheureusement le code du projet est hosté sur une instance Gitlab mise à disposition par un provider qui ne permet pas d’utiliser docker-in-docker pour build les images nécessaires dans la CI. Mais après tout une plateforme de CI c’est juste un serveur qui execute des scripts en ayant accès au code. Nos machines de dev joueront le rôle de serveur de CI et on peut définir les mêmes scripts dans un Makefile directement au sein du repo.&lt;/p&gt;

&lt;p&gt;Le script qui génèrera un déploiement va :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cloner la branche main du repo&lt;/li&gt;
&lt;li&gt;copier les fichiers .env de production&lt;/li&gt;
&lt;li&gt;build les images Docker&lt;/li&gt;
&lt;li&gt;les sauvegarder&lt;/li&gt;
&lt;li&gt;créer une archive avec le repo et les images&lt;/li&gt;
&lt;li&gt;cleanup&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Plus dure sera la chute
&lt;/h2&gt;

&lt;p&gt;L’heure de la première livraison arrive. Le script marche comme sur des roulettes. On entrevoit un rayon de soleil entre deux nuages. Premier retour de René : “Erreur avec la commande &lt;code&gt;make deploy&lt;/code&gt;” suivi d’une stacktrace longue comme le bras. C’est parti pour une session de debugging par email. On est loin d’une solution SaaS d’observabilité avec des journaux d’événements et compagnie.&lt;/p&gt;

&lt;p&gt;Visiblement le backend n’arrive pas à joindre la DB. Ah non, en fait il ne parvient pas à démarrer du tout. Et la DB non plus. Et le voilà qui apparait, tel une pépite dans le tamis du chercheur d’or, le log qui allait tout expliquer.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Docker warning: The requested image's platform does not match the detected host platform (amd64)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Comment ça ? Qu’est ce que c’est que cette histoire d’architecture ? Docker n’est pas censé marcher partout quelle que soit la machine ???? Qu’est ce que c’est que cette histoire d’amd64 ??&lt;/p&gt;

&lt;p&gt;En réalité le fameux macbook reconditionné acheté sur Backmarket a le bon goût d’être équipé d’un processeur Apple Silicon M2 architecture ARM. En buildant les images sur cette machine, elles avaient donc &lt;code&gt;ARM&lt;/code&gt; comme architecture cible. La VM de René quant à elle fonctionnait sur une architecture &lt;code&gt;linux/amd64&lt;/code&gt;. Il me confirme avec un &lt;code&gt;docker inspect&lt;/code&gt; que les images livrées ont bien comme architecture &lt;code&gt;linux/arm64v8&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Heureusement il existe une solution à ce problème. Docker met à disposition buildx pour build des images avec une architecture différente de la machine sur laquelle il tourne. On peut l’activer et simplement exécuter :&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker buildx --plateform amd64 &amp;lt;IMAGE&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;On utilise des images de dev au quotidien et il serait possible de les livrer par inadvertance. Pour les identifier précisément, on ajoute le tag &lt;code&gt;delivery&lt;/code&gt; aux images buildées par le script et qui ont pour architecture &lt;code&gt;linux/amd64&lt;/code&gt;. Pour éviter toute forme de programmation par coïncidence, le fichier docker-compose de production est configuré pour utiliser les images avec ce tag.&lt;/p&gt;

&lt;p&gt;Pour aller plus loin sur le fonctionnement de buildx et des manifests Docker, je vous recommande ces deux articles :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.padok.fr/en/blog/docker-arm-architectures#Building_ARM_images_is_way_easier!"&gt;Docker build for arm64 and other architectures&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.padok.fr/en/blog/multi-architectures-docker-iot"&gt;Shipping containers: multi-architectural Docker builds&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;L’heure de la deuxième livraison arrive et c’est un succès ! Tout se passe pour le mieux. René peut déployer sur sa VM et nous pouvons avancer sur le développement de nouvelles features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Des hauts, puis des bas
&lt;/h2&gt;

&lt;p&gt;On arrive au moment d’implémenter l’internationalisation des contenus dans l’application frontend. Aucun problème, on setup i18n, on remplace les quelques textes existants par des clés de traduction et le tour est joué. Les valeurs associées aux clés sont populées avec des “lorem ipsum” en premier lieu et René pourra modifier après coup comme il le souhaite. Mais se pose un nouveau problème que l’on n’avait pas anticipé : changer les traductions dans le code est possible, mais René va devoir rebuild le frontend et l’image associée pour que ses modifications puissent être déployées et visibles par les utilisateurs. Sans internet ça complique le processus.&lt;/p&gt;

&lt;p&gt;Actuellement le Dockerfile qui permet de builder l’image du frontend contient les instructions suivantes :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;yarn &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;yarn build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:stable-alpine&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/build /usr/share/nginx/html&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["nginx", "-g", "daemon off;"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On utilise le multi-stage build proposé par Docker avec une première étape pour build le frontend en se basant sur une image node. On récupère dans une seconde étape le dossier &lt;code&gt;build&lt;/code&gt; produit dans la première étape pour pouvoir le servir avec nginx comme reverse-proxy.&lt;/p&gt;

&lt;p&gt;La commande &lt;code&gt;yarn install&lt;/code&gt; télécharge les dépendances et nécessite Internet. Idéalement, on aurait envie que René dispose déjà des dépendances qui ne vont pas changer entre deux livraisons et que la seule étape qui soit refaite dans le Dockerfile soit le build du frontend (&lt;code&gt;yarn build&lt;/code&gt;) avec les nouvelles valeurs des clés de traductions. Et c’est exactement ce qui va se passer grâce au cache Docker.&lt;/p&gt;

&lt;p&gt;Une image Docker est constituée d’un ensemble de layer (on les voit se télécharger au fur et à mesure lorsqu’on pull une image depuis un registry).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcq28djywu598c3z3vwws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcq28djywu598c3z3vwws.png" alt="layers-download" width="800" height="274"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Chaque layer est relié à une instruction du Dockerfile (plus ou moins, selon les instructions). Lorsque l’on build une image, Docker met en cache les différents layers qui pourront être réutilisés pour un prochain build. Chaque layer dispose d’un id qui est calculé à partir de l’id du layer précédent et d’un “diffId” représentant les changements dans le système de fichier par rapport au layer précédent. Si les ids ne changent pas, Docker peut utiliser le layer en cache quand il rebuild une image. Au contraire, si un id change, soit à cause de l’ordre des instructions du Dockerfile, soit à cause d’une mise à jour du système de fichier, pour toutes les étapes suivantes le cache sera invalidé et elles seront refaites.&lt;/p&gt;

&lt;p&gt;Pour aller plus loin sur le fonctionnement des layers dans Docker, je vous recommande cet article : &lt;a href="https://earthly.dev/blog/docker-image-storage-on-host/"&gt;How the Docker Image Is Stored on the Host Machine&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ainsi, on peut utiliser le cache Docker pour éviter que René ne doive re-télecharger les dépendances.&lt;/p&gt;

&lt;p&gt;On modifie le Dockerfile :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; yarn.lock ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;yarn &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;yarn build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:stable-alpine&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/build /usr/share/nginx/html&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["nginx", "-g", "daemon off;"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Une fois qu’il aura modifié les clés de traduction, rien n’aura changé du point de vue du système de fichier et dans l’ordre des commandes pour Docker par rapport au moment où l’image a été build par nos soins pour les étapes avant &lt;code&gt;COPY . ./&lt;/code&gt;. Ainsi pas besoin de d’exécuter de nouveau &lt;code&gt;yarn install&lt;/code&gt;. Et même pour le processus de livraison, pas besoin de télécharger de nouveaux les dépendances si seul le code change. Ça vaut bien une bonne gorgée de thé au jasmin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turbulences à l’horizon
&lt;/h2&gt;

&lt;p&gt;Nouvelle semaine, nouvelle livraison, on y croit cette fois-ci. RAS côté application, tout fonctionne comme prévu. Cependant René n’arrive pas à rebuild le frontend avec les clés de traductions mises à jour. Les nuages s’amoncellent de nouveau sur le ciel qui nous avait laissé entrevoir une lueur d’espoir. Le message de René inclut les logs du processus du rebuild qui sont cette fois-ci limpides : “failed to load image node:18-alpine” &amp;amp;  “failed to load image nginx:stable-alpine”. Les images en question sont exactement celles nécessaires pour build le frontend comme décrit dans le Dockerfile, et elles n’ont pas été livrées. Nous avions pourtant testé le livrable donné à René et fait devant lui la démonstration de rebuild pour changer les clés de traductions. Nouvel eurêka mêlé à la sensation étrange d’avoir failli : cette démonstration avait été faite sur notre cher macbook reconditionné bien aimé, qui avait été lui même utilisé pour produire l’archive de livraison. Le registry local contenait les images en question, mais elles n’ont pas été mises dans l’archive, et René ne pouvant pas les télécharger depuis un registry online, impossible de rebuild.&lt;/p&gt;

&lt;p&gt;Le gros problème dans notre script de livraison qui apparaît clairement avec ce bug est qu’il compte trop sur la machine sur lequel il est exécuté. Dans un monde idéal, c’est la CD, exécutée dans un container ou une VM chez un cloud-provider, qui devrait exécuter le script. Il serait tout à fait indépendant des environnement de dev et on pourrait détecter et prévenir beaucoup plus tôt ce genre d’erreur.&lt;/p&gt;

&lt;p&gt;Étant donné qu'on ne peut toujours pas build des images Docker via notre instance Gitlab, on va utiliser une machine dédiée en plus de revoir le script.&lt;/p&gt;

&lt;p&gt;On rajoute les étapes suivantes :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On pull toutes les images que l’on aura besoin de livrer peu importe si elles sont déjà présentes dans le registry local.&lt;/li&gt;
&lt;li&gt;Au moment de sauvegarder dans une archive les images buildées, on ajoute aussi celles qui permettront de rebuild le frontend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avec ça on garantie la reproductibilité du script et l’indépendance de l'artifact qui sera livré de l’environnement qui sert à le produire.&lt;/p&gt;

&lt;p&gt;De plus on va exécuter tout ça sur une machine dédiée Linux avec processeur sous architecture &lt;code&gt;amd64&lt;/code&gt; qui nous servira de “pc serveur”. Le script garde la commande de build avec buildx pour le multi-architecture toujours dans un but de reproductibilité.&lt;/p&gt;

&lt;p&gt;C’est justement le moment de mettre à exécution ce nouveau script. Le thé au jasmin mitige à merveille les effets du stress qui monte au fur et à mesure que les logs apparaissent dans le terminal. Aucune erreur. On utilise une nouvelle session indépendante sur la machine pour tester le build. Toute fonctionne, pas de bugs sur les parcours utilisateurs critiques, le soleil brille et la plage sur laquelle on avait échoué quelques semaines auparavant semble plus accueillante que jamais. Quelques jours plus tard, nous remettons la clé USB à René qui dans la foulé nous confirme que tout fonctionne pour lui. Il peut changer les clés de traductions à sa guise et est ravi.&lt;/p&gt;

&lt;h2&gt;
  
  
  Atterrissage en douceur
&lt;/h2&gt;

&lt;p&gt;Il faut cependant être réaliste, nous avons eu de la chance sur ce projet de pouvoir proposer une solution technique simple qui serait appropriée pour résoudre le problème de Cave Inc. Alors certes, cela peut paraître moins sexy de ne pas utiliser la dernière technologie à la mode (et qui sera dépréciée dans 6 mois), mais le rôle d’un ingénieur est de résoudre des problèmes plutôt que de pousser une complexité pas forcement pertinente qui transformera un projet en usine à gaz. A contrario, il ne faut pas du tout réduire l’impact phénoménal que peuvent avoir les outils IT modernes, mais peut-être se poser la question de leur pertinence face au contexte avec lequel on travaille et le problème à résoudre.&lt;/p&gt;

&lt;p&gt;Cette aventure m’aura quand même bien fait ressentir à quel point ces outils modernes comme Docker ont permis d’abstraire une bonne partie du travail et des problèmes rencontrés par les devs pour qu’ils puissent se concentrer sur le métier et la production de fonctionnalités. Avoir expérimenté les difficultés que ces outils permettent d’ignorer reste pour moi le meilleur moyen de juger de leur nécessité et de leur intérêt pour un projet.&lt;/p&gt;

&lt;p&gt;Et vous, si vous aviez dû construire un projet de la sorte et le déployer sans Internet, vous auriez fait comment ? N’hésitez pas à poster votre solution en commentaire, ou me contacter pour qu’on en discute autour d’un thé au jasmin.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>deployment</category>
      <category>offline</category>
    </item>
  </channel>
</rss>
