[{"content":"Voici quelques éclaircissements sur ma façon d\u0026rsquo;utiliser les modèles génératifs.\nRédaction des articles # Je n\u0026rsquo;utilise pas de LLMs pour rédiger mes articles. Ils garderont toujours des tournures de phrases qui me sont propres, quelques erreurs typographiques.\nSi toutefois du contenu généré par IA serait présent, il serait alors explicité d\u0026rsquo;une mention dédiée.\nDifférents contextes # Dans le contexte professionnel, j\u0026rsquo;utilise les LLMs pour déléguer certaines tâches ; notamment les étapes de recherche d\u0026rsquo;information (codebase, projets,\u0026hellip;) et de génération de code. Les LLMs sont ici un outil parmi d\u0026rsquo;autres en vu d\u0026rsquo;un objectif précis.\nLes LLMs restent sinon un outil peu utile dans mon quotidien qui n\u0026rsquo;apporte pas de valeur ajoutée significative.\n","date":"7 juin 2026","externalUrl":null,"permalink":"/articles/fr/ai_manifesto/","section":"Articles","summary":"Éclaircissements sur mon utilisation des “LLMs”.","title":"Manifeste de l'IA","type":"articles"},{"content":"Le terme \u0026ldquo;Battle of the mesh\u0026rdquo; reprend la dénomination d\u0026rsquo;une convention annuelle1 dédiée à l\u0026rsquo;étude des réseaux maillés et leurs performances. Ce terme est repris ici pour centraliser les différentes informations et actualités liées aux réseau maillés numériques émergents.\nNote Le terme Battle laisse supposer une concurrence ouverte entre les différents projets suivis ici. Il n\u0026rsquo;en est rien, car même si ils semblent au demeurant concurrents, leur fonctionnalités et particularités les rendent complémentaires voire même complètement différents.\nArticles Meshcore Meshtastic Reticulum 2026, EN, Lien, Liyan Gong , MeshCore Repeater Hardware Recommendation and Build Guide 2026. 2026, EN, Lien, Bryan Cockfield, Meshtastic Does More Than Simple Communication. 2026, EN, Lien, Danny Davis , MeshCore vs Meshtastic A Practical Comparison. 2025, EN, Lien, Josh, MeshCore vs Meshtastic Comparison. 2025, EN, Lien, Donald Papp, Lessons Learned After Trying MeshCore For Off-grid Text Messaging. 2025, FR, Lien, bastou65, Détection des mangeurs de poules. 2025, EN, Lien, Tom Nardi, Meshtastic: A Tale Of Two Cities. 2025, EN, Lien, Meshtastic Team, ...That one time at DEF CON!. 2025, EN, Lien, Henri Bergius, Off-grid Boat Communications with Meshtastic. 2025, EN, Lien, @nullagent@partyon.xyz, Mastodon discussion about Meshtastic and privacy. 2025, EN, Lien, Austin Mesh, Meshtastic vs. MeshCore on Austin Mesh . 2024, EN, Lien, Linux in a bit, The Reticulum Network and How it Works. Lien Second site web de Meshcore, porté par la majorité de l\u0026rsquo;équipe originale. Lien Scission dans l\u0026rsquo;équipe de Meshcore. Lien Documentation de Meshcore. Lien Nouvelle carte internationale des noeuds Meshcore. Lien Ancienne carte internationale des noeuds Meshcore. Lien Carte française des noeuds Meshcore. Lien Liste d\u0026rsquo;initiatives open source d\u0026rsquo;outils dédiés à Meshcore. Lien Documentation de Meshtastic. Lien Carte internationale des noeuds Meshtastic. Lien Carte française des noeuds Meshtastic. Lien Liste d\u0026rsquo;initiatives open source d\u0026rsquo;outils dédiés à Meshtastic Lien Logiciel de communication Nomadnet. Lien Logiciel de communication Reticulum-meshchat. Lien Logiciel de communication Columba. PDF Manuel de Reticulum. Lien Carte des noeuds Reticulum. https://battlemesh.org\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"16 mai 2026","externalUrl":null,"permalink":"/articles/fr/battle_of_the_mesh/","section":"Articles","summary":"Des ressources et actualités consultées dans le cadre des réseaux maillés.","title":"Battle of the mesh","type":"articles"},{"content":"Lors d\u0026rsquo;un diagnostic d\u0026rsquo;un lien réseau, cURL propose des options très informatives qu\u0026rsquo;il serait dommage de ne pas profiter. Grâce à elles, on va pouvoir mesurer les différentes étapes d\u0026rsquo;établissement de la connexion. La version utilisée de cURL est curl 8.5.0.\n$ curl -o /dev/null -s -w \u0026#39;Time lookup: %{time_namelookup}s\\nTime redirect: %{time_redirect}s\\nTime connect: %{time_connect}s\\nTime appconnect: %{time_appconnect}\\nTime pretransfer: %{time_pretransfer}\\nTTFB: %{time_starttransfer}s\\nTotal: %{time_total}s\\n\u0026#39; HOSTNAME Étape Variable Description 1 time_namelookup Résolution DNS du nom de domaine 2 time_redirect Temps passé sur les redirections éventuelles 3 time_connect Établissement du handshake TCP en 3 étapes (SYN → SYN-ACK → ACK) 4 time_appconnect Handshake TLS/SSL terminée (échange de certificats, négociation du chiffrement). 0 pour HTTP simple 5 time_pretransfer Toute la configuration au niveau protocolaire est terminée ; cURL est prêt à envoyer le premier octet de la requête 6 time_starttransfer (TTFB) Le premier octet de la réponse est reçu 7 time_total Le transfert entier est terminé, connexion fermée Documentation (man curl) time_appconnect The time, in seconds, it took from the start until the SSL/SSH/etc connect/handshake to the remote host was completed. time_connect The time, in seconds, it took from the start until the TCP connect to the remote host (or proxy) was completed. time_namelookup The time, in seconds, it took from the start until the name resolving was completed. time_pretransfer The time, in seconds, it took from the start until the file transfer was just about to begin. This includes all pre-transfer commands and negotiations that are specific to the particular protocol(s) involved. time_redirect The time, in seconds, it took for all redirection steps including name lookup, connect, pretransfer and transfer before the final transaction was started. time_redirect shows the complete execution time for multiple redirections. time_starttransfer The time, in seconds, it took from the start until the first byte is received. This includes time_pretransfer and also the time the server needed to calculate the result. ","date":"19 avril 2026","externalUrl":null,"permalink":"/articles/fr/memo_curl/","section":"Articles","summary":"Mémo : De très utiles options d’utilisation de cURL pour diagnostiquer un lien réseau.","title":"cURL : options avancées","type":"articles"},{"content":"Changer le User-Agent d\u0026rsquo;APT (Advanced Package Tool) peut s\u0026rsquo;avérer utile, notamment lorsqu\u0026rsquo;il est invoqué derrière un proxy, et que la connexion s\u0026rsquo;en retrouve bloquée.\nUne solution à tester est de le configurer en créant un nouveau fichier, par exemple /etc/apt/apt.conf.d/99custom-user-agent avec le contenu suivant.\nAcquire { http { User-Agent \u0026#34;curl/7.68.0\u0026#34;; }; }; En ayant au préalable testé que les requêtes curl sont autorisées à traverser le proxy en question.\n","date":"5 avril 2026","externalUrl":null,"permalink":"/articles/fr/apt_ua/","section":"Articles","summary":"Changer le User-Agent d’APT peut s’avérer utile.","title":"APT et User-Agent","type":"articles"},{"content":" Note L\u0026rsquo;application développée a beaucoup évolué depuis cet article, accessible ici antlas0/meshtastic_visualizer Qt desktop application to send messages, inspect packets, metrics from your Meshtastic node. Python 18 0 Introduction # Le projet Meshtastic est un réseau maillé, en voici la description tirée du site gaulix.fr 1.\nMeshtastic est un projet qui permet d\u0026rsquo;utiliser des radios LoRa bon marché pour établir un système de communication hors réseau à longue portée, sans dépendre des infrastructures de communication existantes. Il fonctionne sur les bandes ISM (bande industrielle, scientifique et médicale) sur 433Mhz, 868Mhz et 2,4Ghz. Ce projet met en place un réseau maillé. Meshtastic est entièrement soutenu par la communauté et open source.\nLes émetteurs-récepteurs sont communément appelés \u0026ldquo;noeuds\u0026rdquo; et peuvent être administrés avec un smartphone ou un ordinateur.\nIl est possible de dialoguer avec un noeud via une communication série. Sous Linux, un chemin vers la ressource série apparait au branchement du noeud, dans mon cas j\u0026rsquo;obtiens /dev/ttyACM0. Un utilitaire Python permet de gérer la configuration du noeud pour tous ses aspects (configuration du module, de la radio, des intervalles de communications,\u0026hellip;) 2. Un client web est également disponible à cette fin 3.\nObjectif # Dans l\u0026rsquo;optique de bénéficier d\u0026rsquo;un client de visualisation local, sans pour autant sortir l\u0026rsquo;artillerie lourde et Docker (utile pour lancer le client web en local), j\u0026rsquo;ai opté pour le développement d\u0026rsquo;une application desktop. C\u0026rsquo;est aussi pour une compréhension du protocole réseau et des fonctionnalités de Meshtastic que cette petite application a vu le jour. Je souhaite principalement pouvoir superviser l\u0026rsquo;état du réseau maillé, envoyer des messages, lancer des traceroutes, et bien sûr exporter les données et métriques.\nDéveloppement # Utilisant Python3 et PyQt6, on peut proposer une application graphique. Son architecture repose sur un \u0026ldquo;data store\u0026rdquo; backend, et certains évènements vont mettre à jour le front-end. Le data store se présente comme une dataclass.\n@dataclass class MeshtasticDataStore: device_path: Optional[str] = None channels: Optional[List[Channel]] = None local_node_config: Optional[MeshtasticNode] = None connected: bool = False nodes: Dict[str, MeshtasticNode] = field( default_factory=dict) # Dict[node_id, Node object] messages: Dict[str, MeshtasticMessage] = field( default_factory=dict) # Dict[message_id, Message object] [...] Le thread backend alimente ce store et celui du front-end vient à son tour en afficher le contenu. Des méthodes sont présentes pour gérer la mise à jour des attributs. La base de données native du noeud Meshtastic nodeDB ne permet pas un rafraichissement en temps-réel et c\u0026rsquo;est l\u0026rsquo;une des raisons pour lesquelles j\u0026rsquo;ai défini une classe dédiée pour décrire un noeud, et également pour enregistrer les séries temporelles radio (SNR, RSSI,\u0026hellip;).\n@dataclass class MeshtasticNode: long_name: Optional[str] = None short_name: Optional[str] = None id: Optional[str] = None role: Optional[str] = None hardware: Optional[str] = None lat: Optional[str] = None lon: Optional[str] = None alt: Optional[str] = None batterylevel: Optional[int] = None chutil: Optional[str] = None txairutil: Optional[str] = None rssi: Optional[str] = None snr: Optional[str] = None neighbors: Optional[List[str]] = None hopsaway: Optional[str] = None firstseen: Optional[str] = None lastseen: Optional[str] = None uptime: Optional[int] = None is_local: bool = False La mise à jour globale de l\u0026rsquo;application se base sur une méthode qui est déclenchée par souscription. Lorsque de nouveaux paquets entrants sont détectés, le store est mis à jour avec leurs propres informations : position, consommation énergétique, identité, télémétrie\u0026hellip;\npub.subscribe(self.on_receive, \u0026#34;meshtastic.receive\u0026#34;) Ensuite des signaux Qt comme notify_data(...) ou notify_node(...) se chargent de déclencher une mise à jour de la partie du front-end concernée, et ainsi afficher les nouvelles informations.\nLe design de l\u0026rsquo;application s\u0026rsquo;est rapidement fait avec QtDesigner, qui comme son nom l\u0026rsquo;indique, est un outil de conception d\u0026rsquo;interface graphique.\nRemarques diverses # La gestion des ack est facilitée par le packet type ROUTING_APP, La configuration du noeud et de ses voisins est essentielle pour tester le développement correctement, la carte issue du package folium s\u0026rsquo;intègre bien dans une web view, mais la mise à jour de la carte sans sa recréation totale n\u0026rsquo;est pas directe. L\u0026rsquo;affichage temps-réel des informations nécessite des astuces d\u0026rsquo;affichage graphique. Conclusion # Après quelques découvertes sur le protocole, les paramètres des noeuds, le cycle de vie des informations (sur l\u0026rsquo;air et en mémoire), je dispose d\u0026rsquo;une petite application rapide à lancer qui informe sur l\u0026rsquo;écosystème local LoRa et Meshastic. Voici un aperçu de l\u0026rsquo;application, et pour le code, c\u0026rsquo;est ici 4. Encore beaucoup d\u0026rsquo;améliorations sont possibles, que ce soit sur la cosmétique ou le fonctionnement interne\u0026hellip; A faire évoluer !\nhttps://gaulix.fr\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/meshtastic/python\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://client.meshtastic.org\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/antlas0/meshtastic_visualizer\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"22 février 2026","externalUrl":null,"permalink":"/articles/fr/project_meshtastic_visualizer/","section":"Articles","summary":"Description d’une application Linux avec PyQt6 pour suivre les métriques d’un noeud Meshtastic.","title":"Supervision d'un noeud Meshtastic","type":"articles"},{"content":"Voici quelques liens intéressants, triés par date de lecture.\n2026, EN, calpaterson.com, https://calpaterson.com/disregard.html 2025, EN, hackaday.com, https://hackaday.com/2025/10/10/quic-jump-to-user-space/ 2025, EN, www.seangoedecke.com, https://www.seangoedecke.com/good-api-design/ 2025, EN, wirelesspi.com, https://wirelesspi.com/two-birds-with-one-tone-i-q-signals-and-fourier-transform-part-1/ 2025, FR, slash-root.fr, https://slash-root.fr/debian-configuration-complete-dunattended-upgrades-pour-une-securite-sans-effort/ 2025, EN, tenthousandmeters.com, https://tenthousandmeters.com/blog/python-behind-the-scenes-12-how-asyncawait-works-in-python/ 2025, EN, blog.edward-li.com, https://blog.edward-li.com/tech/advanced-python-features/, 2025, EN, crocidb.com, https://crocidb.com/post/kernel-adventures/demystifying-the-shebang/ 2025, FR, ploum.net, https://ploum.net/2025-04-10-smartphone_ado.html 2024, EN, blog.christophetd.fr, https://blog.christophetd.fr/stop-worrying-about-allowprivilegeescalation/ 2025, EN, hackaday.com, https://hackaday.com/2025/02/27/linux-fu-usb-everywhere/ 2025, EN, www.bigbookofr.com, https://www.bigbookofr.com/ 2025, EN, www.bitecode.dev, https://www.bitecode.dev/p/a-year-of-uv-pros-cons-and-should 2025, EN, endler.dev, https://endler.dev/2025/best-programmers/ 2025, EN, meshtastic.org, https://meshtastic.org/blog/meshtastic-2-6-preview/ 2022, EN, martinheinz.dev, https://martinheinz.dev/blog/74 ","date":"23 novembre 2025","externalUrl":null,"permalink":"/articles/fr/links_review/","section":"Articles","summary":"Des articles d’internet parcourus dans mon quotidien, stockés ici à des fins d’archivage.","title":"Au fil du temps...","type":"articles"},{"content":" Introduction # Dans le cadre d\u0026rsquo;utilisation du VR-N76, les applications smartphone proposent déjà une belle panoplie de fonctionnalités via la connexion bluetooth, centrale dans toute l\u0026rsquo;utilisation de cette radio.\nMais disposer d\u0026rsquo;une socket TCP de commande peut s\u0026rsquo;avérer utile, notamment pour les différents logiciels présents sur PC\u0026hellip; qu\u0026rsquo;en est-il sur Linux ? Regardons cela avec un PC Ubuntu 22.04.\n[ VR-N76 ] --\u0026gt; [ Bluetooth as serial device ] --\u0026gt; [ bridging to TCP ] Voyons comment rendre disponible le VR-N76, doté du mode KISS TNC, via une connexion TCP.\nConnexion bluetooth # Tout d\u0026rsquo;abord, l\u0026rsquo;appairage de l\u0026rsquo;appareil est nécessaire. Démarrons une écoute des périphériques bluetooth environnants.\n$ bluetoothctl scan on Puis appairons le VR-N76 à notre PC.\n$ bluetoothctl pair 38:D2:00:AA:EE:FF Attempting to pair with 38:D2:00:AA:EE:FF [CHG] Device 38:D2:00:AA:EE:FF Connected: yes [NEW] Primary Service (Handle 0x0000) [...] [CHG] Device 38:D2:00:AA:EE:FF UUIDs: 00000001-ba2a-46c9-ae49-01b0961f68bb [CHG] Device 38:D2:00:AA:EE:FF UUIDs: 00001100-d102-11e1-9b23-00025b00a5a5 [CHG] Device 38:D2:00:AA:EE:FF UUIDs: 00001101-0000-1000-8000-00805f9b34fb [CHG] Device 38:D2:00:AA:EE:FF UUIDs: 0000111f-0000-1000-8000-00805f9b34fb [CHG] Device 38:D2:00:AA:EE:FF UUIDs: 00001800-0000-1000-8000-00805f9b34fb [CHG] Device 38:D2:00:AA:EE:FF UUIDs: 00001801-0000-1000-8000-00805f9b34fb [CHG] Device 38:D2:00:AA:EE:FF UUIDs: 39144315-32fa-40db-85ed-fbfeba2d86e6 [CHG] Device 38:D2:00:AA:EE:FF ServicesResolved: yes Pairing successful L\u0026rsquo;apparaige du VR-N76 propose deux types de conenxions : comme périphérique audio et comme port série. C\u0026rsquo;est ce deuxième type de périphérique qui va nous intéresser.\nSi le périphérique série n\u0026rsquo;apparait pas, il peut être intéressant de préciser les profiles bluetooth dans la configuration globale :\n$ sudo vim /etc/bluetooth/main.conf Enable=Source,Sink,Media,Socket,Network,Input,Serial On portera attention à la globalité de cette configuration, qui concerne la machine dans son entièreté et donc tous les périphériques qui s\u0026rsquo;y connectent. Le profil souhaité ici est Serial. Les autres sont présents pour garder la compatibilité.\nLa vérification des fonctionnalités proposées par le VR-N76 sont consultables en affichant les données envoyées par bluetooth. $ bluetoothctl info 38:D2:00:AA:BB:FF Device 38:D2:00:AA:EE:FF (public) Name: VR-N76 Alias: VR-N76 Class: 0x00200404 Icon: audio-headset Paired: yes Trusted: no Blocked: no Connected: yes LegacyPairing: no UUID: Vendor specific (00000001-ba2a-46c9-ae49-01b0961f68bb) UUID: Vendor specific (00001100-d102-11e1-9b23-00025b00a5a5) UUID: Serial Port (00001101-0000-1000-8000-00805f9b34fb) UUID: Handsfree Audio Gateway (0000111f-0000-1000-8000-00805f9b34fb) UUID: Generic Access Profile (00001800-0000-1000-8000-00805f9b34fb) UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb) UUID: Vendor specific (39144315-32fa-40db-85ed-fbfeba2d86e6) On note bien Serial Port.\nAprès connexion au profil série du périphérique, un nouveau device Linux devrait apparaitre lors de la connection au VR-N76 en tant que port série.\n$ ls /dev/rfcom* /dev/rfcomm0 Il peut être nécessaire de changer le Baudrate via la commande suivante :\n$ stty -F /dev/rfcomm0 115200 Conversion vers TCP # Après analyse du protocole KISS TNC1, l\u0026rsquo;encapsulation en TCP ne relève pas d\u0026rsquo;une complexité importante. Il s\u0026rsquo;agit de transférer les paquets réseau TCP vers le port série et inversement. C\u0026rsquo;est un protocole asynchrone, évitant notamment la gestion du temps entre les échanges. Il n\u0026rsquo;y a pas de vérification de protocole (notamment des délimiteurs de trames FEND, l\u0026rsquo;envoi en série 8-bits, 1 bit de stop sans parité); les deux systèmes communiquants doivent se comprendre. On peut dans ce cas lancer la commande socat pour répondre à ce besoin simple de transfert de données bidirectionnel.\n$ socat -dddd TCP-LISTEN:8001,fork,reuseaddr /dev/rfcomm0,raw,echo=0 Commande qui lancera une écoute TCP sur le port 8001, pour plusieurs clients à la fois, transférant les données telles quelles, vers et du port série rfcomm0.\nConclusion # Une fois l\u0026rsquo;appairage et la connexion bluetooth réussis et après analyse du protocole KISS TNC, ce pont vers TCP reste somme toute assez rapide et direct à mettre en place. C\u0026rsquo;est très pratique pour les applications réseaux nécessitant un serveur TCP pour commander un appareil TNC, tel qu\u0026rsquo;il est le cas pour certains outils du réseau reticulum.\nhttps://www.ax25.net/kiss.aspx\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"22 novembre 2025","externalUrl":null,"permalink":"/articles/fr/kisstnc_totcp/","section":"Articles","summary":"Bref tutoriel pour rendre disponible un appareil KISS TNC via un serveur TCP.","title":"Conversion KISS TNC vers TCP","type":"articles"},{"content":" Introduction # Dans la continuité d\u0026rsquo;évaluer des solutions de communications respectueuses de la vie privée, le projet Reticulum 1 a attiré mon attention.\nReticulum est une suite logicielle permettant d\u0026rsquo;interconnecter divers systèmes utilisant divers moyens de connexion. Basée sur le langage Python, elle redéfinit de bout en bout un protocole de communication point-à-point chiffré. Voici décrite ici la mise en place d\u0026rsquo;un relai TCP auquel divers clients TCP peuvent se connecter et échanger des messages. On utilisera les logiciels reticulum-meshchat2 et nomadnet 3 pour discuter au travers de ce relai. Les versions respectives sont ci-dessous.\nLogiciel Version Reticulum-meshchat v1.21.0 NomadNet 0.6.2 RNS (sur le serveur) 0.9.4 Mise en place du relai # Se basant sur Docker, avec le Dockerfile suivant on lance directement l\u0026rsquo;outil rnsd.\nFROM python:3.11-bookworm ENV TCP_SERVER_LISTEN_IP=\u0026#34;0.0.0.0\u0026#34; ENV TCP_SERVER_LISTEN_PORT=64532 ENV CONFIG_FILE_PATH=\u0026#34;.reticulum/config\u0026#34; RUN apt-get update \\ \u0026amp;\u0026amp; apt-get install -y sudo pipx build-essential \\ \u0026amp;\u0026amp; apt-get clean \\ \u0026amp;\u0026amp; rm -rf /var/lib/apt/lists/* RUN useradd -ms /bin/bash runner RUN usermod -aG sudo runner COPY --chmod=0755 entrypoint.sh /entrypoint.sh USER runner WORKDIR /home/runner RUN mkdir -p .reticulum/ COPY config $CONFIG_FILE_PATH RUN pipx install rns \\ \u0026amp;\u0026amp; pipx ensurepath ENTRYPOINT [\u0026#34;/entrypoint.sh\u0026#34;] CMD [\u0026#34;rnsd\u0026#34;, \u0026#34;-v\u0026#34;] La configuration de l\u0026rsquo;interface paramètrera l\u0026rsquo;instanciation du server tcp sur le relai4 : via une interface et un port. Par défaut sur toutes les interfaces et le port 64532.\n[[TCPServer Interface]] type = TCPServerInterface enabled = yes mode = gateway listen_ip = 0.0.0.0 listen_port = 64532 Dans la mesure où rnsd est lancé dans un conteneur Docker, on pensera bien à ouvrir un port externe de la machine hôte. On supposera le relai accessible à l\u0026rsquo;adresse factice my-relai.org.\nConnexion clients # Maintenant que le relai est lancé, on peut configurer les clients et s\u0026rsquo;y connecter.\nMeshchat # On ajoutera facilement le relai dans la liste des interfaces.\nNomadNet # Puis il faudra également adapter la configuration de NomadNet, qui lui sera lancé en ligne de commande.\n# This is the default Reticulum config file. # You should probably edit it to include any additional, # interfaces and settings you might need. # Only the most basic options are included in this default # configuration. To see a more verbose, and much longer, # configuration example, you can run the command: # rnsd --exampleconfig [reticulum] # If you enable Transport, your system will route traffic # for other peers, pass announces and serve path requests. # This should only be done for systems that are suited to # act as transport nodes, ie. if they are stationary and # always-on. This directive is optional and can be removed # for brevity. enable_transport = False # By default, the first program to launch the Reticulum # Network Stack will create a shared instance, that other # programs can communicate with. Only the shared instance # opens all the configured interfaces directly, and other # local programs communicate with the shared instance over # a local socket. This is completely transparent to the # user, and should generally be turned on. This directive # is optional and can be removed for brevity. share_instance = No # If you want to run multiple *different* shared instances # on the same system, you will need to specify different # shared instance ports for each. The defaults are given # below, and again, these options can be left out if you # don\u0026#39;t need them. shared_instance_port = 37428 instance_control_port = 37429 # You can configure Reticulum to panic and forcibly close # if an unrecoverable interface error occurs, such as the # hardware device for an interface disappearing. This is # an optional directive, and can be left out for brevity. # This behaviour is disabled by default. panic_on_interface_error = No [logging] # Valid log levels are 0 through 7: # 0: Log only critical information # 1: Log errors and lower log levels # 2: Log warnings and lower log levels # 3: Log notices and lower log levels # 4: Log info and lower (this is the default) # 5: Verbose logging # 6: Debug logging # 7: Extreme logging loglevel = 4 # The interfaces section defines the physical and virtual # interfaces Reticulum will use to communicate on. This # section will contain examples for a variety of interface # types. You can modify these or use them as a basis for # your own config, or simply remove the unused ones. [interfaces] # This interface enables communication with other # link-local Reticulum nodes over UDP. It does not # need any functional IP infrastructure like routers # or DHCP servers, but will require that at least link- # local IPv6 is enabled in your operating system, which # should be enabled by default in almost any OS. See # the Reticulum Manual for more configuration options. [[Default Interface]] type = AutoInterface enabled = false # [[RNS Testnet Dublin]] # type = TCPClientInterface # enabled = yes # target_host = dublin.connect.reticulum.network # target_port = 4965 [[TCP Client Interface]] type = TCPClientInterface enabled = yes target_host = my-relai.org target_port = 64532 Ayant placé ce fichier dans un répertoire arbitraire (ici ~/nomadnet), on pourra invoquer la ligne de commande suivante pour lancer NomadNet.\n$ docker run -it -v $HOME/nomadnet/:/root/.reticulum/ --network host ghcr.io/markqvist/nomadnet:master --textui NomadNet sera lancé dans un conteneur Docker, en mode texte via l\u0026rsquo;image Docker publiquement accessible sur ghcr.io.\nVisualisation # Les deux noeuds Meshchat (nommé Meshchat_antlas2) et NomadNet (nommé NomadNet_antlas1) se voient après avoir envoyé leurs announces respectives.\nVoici ce que NomadNet liste comme peer, comme noeuds identifiés. On remarque bien le nom Meshchat_antlas2 dans l\u0026rsquo; Announce Data. De son côté l\u0026rsquo;application Meshchat propose un graphe réseau — on notera l\u0026rsquo;insertion du nom de domaine factice dans l\u0026rsquo;image. Le noeud NomadNet_antlas1 est bien découvert via le relai TCP my-relai.org. Discussion # S\u0026rsquo;étant mutuellement détectés, et les deux logiciels étant compatibles avec le protocole LXMF 5 notamment dédié à l\u0026rsquo;envoi de messages, tentons de communiquer par texte. Les adresses LXMF sont les suivantes.\nNoeud Identity hash Adresse LXMF NomadNet_antlas1 5a1c6... 109f2... Meshchat_antlas2 0a9b5... e344a... Avec Meshchat on initie la conversation, en retrouvant l\u0026rsquo;adresse LXMF de NomadNet_antlas1: 109f2.... Puis on répond via NomadNet à l\u0026rsquo;adresse LXMF de Meshchat_antlas2 : e344a....\nConclusion # Malgré une complexité significative des outils et la nécessité d\u0026rsquo;une configuration minutieuse, ce modèle de communication permet d\u0026rsquo;échanger des informations chiffrées via une infrastructure totalement maitrisée. On a passé sous silence quelques vrais atouts de cette solution, notamment le routage pair-à-pair et le fonctionnement avec des connexions bas-débit\u0026hellip; L\u0026rsquo;encapsulation Docker est disponible ci-dessous 6. antlas0/rns_tools Experimentations based on Reticulum and LXMF. Python 0 1 https://reticulum.network/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/liamcottle/reticulum-meshchat\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/markqvist/NomadNet\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://reticulum.network/manual/interfaces.html#tcp-server-interface\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/markqvist/lxmf\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/antlas0/rns_tools/tree/4c4aca1df67e631ccc1f03c64cb6da66602bed88/etc/rnsd-docker\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"24 avril 2025","externalUrl":null,"permalink":"/articles/fr/reticulum_server/","section":"Articles","summary":"Mise en place d’un relai Reticulum avec Docker.","title":"Relai TCP via Reticulum","type":"articles"},{"content":" Introduction # Dans le cadre professionnel, la plupart de nos connexions se trouvent filtrées voire analysées par des composants tels que des proxies. Filtrage particulièrement efficace pour limiter certaines surfaces d\u0026rsquo;attaques de nos équipements, mais ne facilitant pas l\u0026rsquo;intégration de nouvelles plateformes qui doivent se conformer aux contraintes de connectivité.\nConfigurations # Voici un rapide tour d\u0026rsquo;horizon de quelques logiciels et de leur configuration de connexion proxy. Travaillant quotidiennement avec des systèmes Linux basés sur le système d\u0026rsquo;exploitation Debian, je me limiterai ici à ces systèmes et aux logiciels couramment utilisés dans ce contexte.\nDebian # Pour commencer, la machine elle-même change son comportement de connexion si certaines variables d\u0026rsquo;environnement sont présentes. Mais ce n\u0026rsquo;est pas un changement global unitaire d\u0026rsquo;un coup d\u0026rsquo;un seul ; c\u0026rsquo;est un changement au demeurant global émergeant de la somme des changements de configuration de chaque logiciel ou bibliothèque utilisée en coulisses. Il s\u0026rsquo;agit des variables HTTP_PROXY, HTTPS_PROXY, NO_PROXY et leur équivalent en minuscule. Ces variables sont lues et prises en charge par de nombreux logiciels, tels que: curl, python requests. On pourra exécuter les commandes suivantes :\n$ export HTTP_PROXY=http://example.proxy.com:80 $ export HTTPS_PROXY=http://example.proxy.com:80 $ export NO_PROXY=127.0.0.1,localhost,domain.towhitelist.org $ export http_proxy=http://example.proxy.com:80 $ export https_proxy=http://example.proxy.com:80 $ export no_proxy=127.0.0.1,localhost,domain.towhitelist.org Curl # Concernant Curl, la documentation est claire à ce sujet 1, plusieurs types de proxies sont paramétrables via les variables d\u0026rsquo;environnement en minuscules ou majuscules: ftp, http,https,\u0026hellip;\nThe proxy example above is for HTTP, but can of course also set ftp_proxy, https_proxy, and so on for the specific protocols you want to proxy. All these proxy environment variable names except http_proxy can also be specified in uppercase, like HTTPS_PROXY.\nIl peut alors être intéressant d\u0026rsquo;utiliser la variable ALL_PROXY à des fins de factorisation, notamment comme valeur par défaut d\u0026rsquo;un environnement. Cette variable sera ignorée pour le protocole pour lequel on lui a défini un proxy.\nTo set a single variable that controls all protocols, the ALL_PROXY exists. If a specific protocol variable one exists, such a one takes precedence.\nAPT (Advanced Package Tool) # apt nécessite l\u0026rsquo;édition spécifique d\u0026rsquo;un fichier de configuration ou l\u0026rsquo;injection en ligne de commande les paramètres de proxy.\nEn configurant le fichier /etc/apt/apt.conf : Acquire::http::Proxy \u0026#34;http://example.proxy.com:80\u0026#34;; Acquire::https::Proxy \u0026#34;http://example.proxy.com:80\u0026#34;; Acquire:http:Proxy::domain.towhitelist.org DIRECT; Acquire:https:Proxy::domain.towhitelist.org DIRECT; En injectant en ligne de commande les options suivantes : $ apt-get -o Acquire::http::Proxy=\u0026#34;http://example.proxy.com:80\u0026#34; update Docker # Client # La configuration proxy devient moins intuitive pour les étapes de construction (build) d\u0026rsquo;images.\nUn première solution consiste à passer dans le Dockerfile les paramètres proxy, tout en se souciant de leur cycle de vie. ARG http_proxy FROM ... [...] RUN export \\ http_proxy=$http_proxy \\ https_proxy=$http_proxy \\ \\ \u0026amp;\u0026amp; curl .... \\ \u0026amp;\u0026amp; unset http_proxy https_proxy [...] Et lancer la construction d\u0026rsquo;image avec\n$ docker build --build-arg http_proxy=\u0026#34;http://example.proxy.com:80\u0026#34; Attention à bien supprimer cette variable dans la même commande RUN pour que cette variable ne se retrouve pas disponible 2.\nUne seconde solution fait appel à la configuration générale du client Docker utilisé. Il inspecte le fichier ~/.docker/config.json à la recherche de paramètres. Son contenu est du JSON de la forme : { \u0026#34;proxies\u0026#34;: { \u0026#34;default\u0026#34;: { \u0026#34;httpProxy\u0026#34;: \u0026#34;http://example.proxy.com:80\u0026#34;, \u0026#34;httpsProxy\u0026#34;: \u0026#34;http://example.proxy.com:80\u0026#34;, \u0026#34;noProxy\u0026#34;: \u0026#34;*.test.example.com,.example.org,127.0.0.0/8\u0026#34; } } } On prêtera également attention à l\u0026rsquo;activation de cette configuration, qui ne sera appliquée que pour les futurs conteneurs construits et déployés 3.\nThe configuration becomes active after saving the file, you don\u0026rsquo;t need to restart Docker. However, the configuration only applies to new containers and builds, and doesn\u0026rsquo;t affect existing containers.\nLe stockage de la configuration est cependant en clair sur le système.\nServer # Le daemon Docker suit lui aussi la pratique standarde de se configurer automatiquement en réponse à la présence des variables d\u0026rsquo;environnement système 4.\nIl est également possible d\u0026rsquo;éditer le fichier de configuration global.\n{ \u0026#34;proxies\u0026#34;: { \u0026#34;http-proxy\u0026#34;: \u0026#34;http://example.proxy.com:80\u0026#34;, \u0026#34;https-proxy\u0026#34;: \u0026#34;http://example.proxy.com:80\u0026#34;, \u0026#34;no-proxy\u0026#34;: \u0026#34;*.test.example.com,.example.org,127.0.0.0/8\u0026#34; } } Plus d\u0026rsquo;informations pour d\u0026rsquo;autres situations (systemd) sur la documentation officielle 5.\nGit # On peut directement éditer la configuration proxy via la ligne de commande suivante 6.\n$ git config --global http.proxy http://example.proxy.com:80 Et pour vérifier que la configuration a bien été changée :\n$ git config --global --get-regexp http.* La suppression de cette configuration peut se faire en ligne de commande également.\n$ git config --global --unset http.proxy $ git config --global --unset https.proxy https://everything.curl.dev/transfers/conn/proxies.html#proxy-environment-variables\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://docs.docker.com/build/building/best-practices/#env\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://docs.docker.com/engine/cli/proxy/#configure-the-docker-client\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://docs.docker.com/engine/daemon/proxy/#environment-variables\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://docs.docker.com/engine/daemon/proxy/#systemd-unit-file\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://gist.github.com/evantoli/f8c23a37eb3558ab8765\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"16 février 2025","externalUrl":null,"permalink":"/articles/fr/working_with_proxies/","section":"Articles","summary":"Rapide tour d’horizon de quelques configurations de connexion proxies, à des fins d’archivage.","title":"Connexions et proxies","type":"articles"},{"content":" Introduction # En lien avec mon prédécent article 1 et de certaines expérimentations, il m\u0026rsquo;arrive de vouloir superviser l\u0026rsquo;activité d\u0026rsquo;applications conteneurisées. Il se trouve que les métriques des conteneurs sont à prendre avec précautions. Dans cette optique, on va mettre en place un pod pourvu d\u0026rsquo;un shell, et comprendre pourquoi les utilitaires tels que top ne rendent pas correctement compte de la situation réelle.\nPréparation de l\u0026rsquo;environnement # On se base sur Minikube, en suivant une partie du tutoriel présenté ici 2. Le pod qui exécute un process a une limite de consommation RAM et CPU. Un seul conteneur le composera, basé sur l\u0026rsquo;image progrium/stress 3.\nVoici le manifest K8s qui définit ce petit déploiement.\napiVersion: v1 kind: Pod metadata: name: stress-demo spec: containers: - name: stress1 image: progrium/stress resources: limits: cpu: \u0026#34;1.5\u0026#34; memory: \u0026#34;600M\u0026#34; requests: cpu: \u0026#34;1\u0026#34; memory: \u0026#34;500M\u0026#34; command: [\u0026#34;stress\u0026#34;] args: [\u0026#34;--vm\u0026#34;, \u0026#34;2\u0026#34;, \u0026#34;--vm-bytes\u0026#34;, \u0026#34;256M\u0026#34;, \u0026#34;--verbose\u0026#34;] $ kubectl apply -f pod.yml On veillera à gérer la demande en RAM au regard des limits pour ne pas que le kernel stoppe le processus du conteneur qui en demande trop.\n➡ On applique ce manifest pour mettre en place l\u0026rsquo;environnement, on a un pod qui s\u0026rsquo;exécute dans le cluster Minikube.\nObservations # On déroule les outils classiques de diagnostic, et on note plusieurs différences importantes:\nEn faisant appel aux métriques Kubernetes $ kubectl top pods NAME CPU(cores) MEMORY(bytes) stress-demo 1501m 152Mi En se plaçant dans le pod pour exécuter des commandes shell $ kubectl exec -ti stress-demo -- free -h total used free shared buffers cached Mem: 7.5G 6.9G 584M 750M 79M 2.8G $ kubectl exec -ti stress-demo -- top Tasks: 4 total, 3 running, 1 sleeping, 0 stopped, 0 zombie %Cpu(s): 7.1 us, 35.6 sy, 0.0 ni, 57.2 id, 0.0 wa, 0.0 hi, 0.1 si, 0.0 st KiB Mem: 7861644 total, 7327232 used, 534412 free, 82008 buffers KiB Swap: 2097148 total, 311296 used, 1785852 free. 2895988 cached Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 8 root 20 0 269468 30276 896 R 75.3 0.4 1:31.23 stress 9 root 20 0 269468 206988 896 R 75.0 2.6 1:31.04 stress 1 root 20 0 7320 1536 1536 S 0.0 0.0 0:00.01 stress 17 root 20 0 19864 2176 1920 R 0.0 0.0 0:00.00 top Les informations rendues par kubectl et top attestent de la limite CPU (1.5 CPU utilisé au maximum qui est la somme de 2 fois 75%, sur 2 demandés par le programme de stress). La liste des processus de top se limite à ceux du conteneur. Cependant on note une différence majeure entre kubectl et free concernant la mémoire utilisée.\n➡ Autant l\u0026rsquo;utilisation CPU semble correspondre entre un diagnostic fait sur le pod et avec les API Kubernetes, autant l\u0026rsquo;utilisation mémoire ne semble pas alignée.\nSolution # Les proc entries font partie de la hiérarchie de dossiers procfs qui sont partiellement altérés dans les environnements de conteneurs. Par exemple, l\u0026rsquo;utilitaire top n\u0026rsquo;a qu\u0026rsquo;une visibilité partielle sur les processus, tandis que l\u0026rsquo;utilitaire free lit le fichier /proc/meminfo, qui n\u0026rsquo;est pas transformé et contient les informations de la machine hôte.\n# dans le container stress-demo $ strace free 2\u0026gt;\u0026amp;1 | grep open [...] openat(AT_FDCWD, \u0026#34;/proc/meminfo\u0026#34;, O_RDONLY) = 3 [...] Il récupère l\u0026rsquo;utilisation mémoire non pas pour le conteneur dans lequel on lance la commande, mais pour la machine hôte. Cette commande ne rend donc pas compte de la réalité du conteneur.\nLa liste de toutes les entrées transformées dans le conteneur dépend de la version de la plateforme de conteneur, des politiques d\u0026rsquo;accès,\u0026hellip; Il n\u0026rsquo;y a donc pas de liste exhaustive ! La vigilance est donc de vigueur.\n➡ Il faudra donc rester critique sur les utilitaires en ligne de commande lancés dans un conteneur, car toutes les sources d\u0026rsquo;informations ne sont pas forcément intégrées au fonctionnement conteneurisé. On accordera notre confiance aux outils de gestion de conteneurs, tels que kubectl top pods, ou docker stats,\u0026hellip;\nPour aller plus loin # D\u0026rsquo;après la documentation de Kubernetes, le concept de cgroups (pour control groups) est utilisé4 pour gérer la distribution des ressources des pods. Les cgroups sont décrits comme un mécanisme d\u0026rsquo;organisation et de hiérarchie des processus, notamment pour leur assignation des ressources système. Les configurations sont stockées dans des dossiers et des fichiers \u0026ldquo;d\u0026rsquo;interfaces\u0026rdquo;5.\nPlongeons dans les cgroups # Succintement, on peut mentionner que :\nIl y a à ce jour deux versions majeures, la v1 et la v2 6, L\u0026rsquo;implémentation des cgroups v2 est native dans le kernel Linux \u0026gt;= 5.8, Pour fonctionner avec K8s7, doit être installée la bibliothèque de gestion de conteneur (container runtime) containerd \u0026gt;= v1.4 ou cri-o \u0026gt;= v1.20. Et que pour la version v2,\nChaque processus appartient à un seul cgroup, Tous les threads d\u0026rsquo;un même processus appartiennent au même cgroup, Lors de la création, les processus sont placés dans le même cgroup que leur processus parent. Ils peuvent être migrés ensuite. Premièrement, déterminons quelle version notre système utilise :\n$ stat -fc %T /sys/fs/cgroup/ cgroup2fs C\u0026rsquo;est actuellement la version v2.\nMaintenant créons un pod dans un namespace dédié stressed-ns.\n$ kubectl create namespace stressed-ns $ kubectl --namespace stressed-ns apply -f pod.yml Récupérons l\u0026rsquo;identifiant du processus et inspectons la configuration du système. On filtre la liste des processus en ne gardant que ceux qui contiennent \u0026ldquo;stress\u0026rdquo;.\n$ ps aux | grep stress root 37140 0.0 0.0 7320 1664 ? Ss 10:17 0:00 stress --vm 2 --vm-bytes 256M --verbose root 37153 74.7 2.0 269468 162136 ? R 10:17 4:18 stress --vm 2 --vm-bytes 256M --verbose root 37154 75.1 3.0 269468 241684 ? R 10:17 4:20 stress --vm 2 --vm-bytes 256M --verbose antoine 42883 0.0 0.0 11780 2560 pts/0 S+ 10:22 0:00 grep --color=auto stress Vérifions quand même que ces processus fassent bien partie du pod que l\u0026rsquo;on configure. Sélectionnons le pid du processus dont le status est sleeping.\n$ nsenter -t 37140 -u hostname stress-demo Parfait, on travaille bien sur un processus conteneurisé.\nLien entre namespace K8s et cgroup # Un des moyens est de récupérer le nom du cgroup associé au pid:\n$ cat /proc/37140/cgroup 0::/system.slice/docker-0fef63ebdbef160d586f3b6e4a271aeb3329030xxxx.scope/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podxxxx.slice/docker-xxxx.scope On note un chemin, qui est à parcourir dans la hiérarchie /sys/fs/cgroup/. Allons voir ce qu\u0026rsquo;il s\u0026rsquo;y trouve !\nInformations d\u0026rsquo;un cgroup # $ cd /sys/fs/cgroup/system.slice/docker-0fef63ebdbef160d586f3b6e4a271aeb3329030xxxx.scope/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podxxxx.slice/docker-xxxx.scope/ $ ls cgroup.controllers cgroup.type cpu.uclamp.max hugetlb.2MB.current io.weight memory.pressure misc.events cgroup.events cpu.idle cpu.uclamp.min hugetlb.2MB.events memory.current memory.reclaim misc.max cgroup.freeze cpu.max cpu.weight hugetlb.2MB.events.local memory.events memory.stat pids.current cgroup.kill cpu.max.burst cpu.weight.nice hugetlb.2MB.max memory.events.local memory.swap.current pids.events cgroup.max.depth cpu.pressure hugetlb.1GB.current hugetlb.2MB.numa_stat memory.high memory.swap.events pids.max cgroup.max.descendants cpuset.cpus hugetlb.1GB.events hugetlb.2MB.rsvd.current memory.low memory.swap.high pids.peak cgroup.pressure cpuset.cpus.effective hugetlb.1GB.events.local hugetlb.2MB.rsvd.max memory.max memory.swap.max rdma.current cgroup.procs cpuset.cpus.partition hugetlb.1GB.max io.max memory.min memory.swap.peak rdma.max cgroup.stat cpuset.mems hugetlb.1GB.numa_stat io.pressure memory.numa_stat memory.zswap.current cgroup.subtree_control cpuset.mems.effective hugetlb.1GB.rsvd.current io.prio.class memory.oom.group memory.zswap.max cgroup.threads cpu.stat hugetlb.1GB.rsvd.max io.stat memory.peak misc.current ➡ On retrouve les fichiers d\u0026rsquo;interface décrits dans la documentation des cgroups.\nOn pourra par exemple afficher les informations relatives au CPU et à la mémoire.\nL\u0026rsquo;interprétation des fichiers sera grandement aidée par la consultation de la documentation. C\u0026rsquo;est ainsi que l\u0026rsquo;on pourra retrouver la limite CPU:\nInspectons par exemple le fichier cpu.max, qui d\u0026rsquo;après la documentation contient la limite d\u0026rsquo;allocation CPU.\ncpu.max: A read-write two value file which exists on non-root cgroups. The default is “max 100000”. The maximum bandwidth limit. It’s in the following format: $MAX $PERIOD which indicates that the group may consume up to $MAX in each $PERIOD duration. “max” for $MAX indicates no limit. If only one number is written, $MAX is updated.\n$ cat cpu.max 150000 100000 Pour une période unitaire d\u0026rsquo;une seconde, ce cgroup peut consommer 1,5 CPU. Mettons à jour la définition du pod pour autoriser 1,8 CPU. On devra supprimer, recréer un pod et réitérer ces dernières étapes pour voir la nouvelle valeur.\n$ ps aux | grep stress $ nsenter ... $ cd /sys/fs/cgroup/system.slice/... $ cat cpu.max 180000 100000 ➡ On constate que la configuration utilisateur dans K8s se répercute sur la configuration du cgroup.\ncgroups et namespaces linux # La distribution des ressources système via les cgroups n\u0026rsquo;est pas le seul procédé mis en place. Les cgroups sont un type, un sous-ensemble de restrictions, d\u0026rsquo;isolations concrètes réalisées par les namespaces linux. Et d\u0026rsquo;autres types sont effectivement appliqués.\nOn note le double sens du mot namespace, qui définit ici deux concepts différents : l\u0026rsquo;isolation des ressources dans le contexte de Kubernetes et l\u0026rsquo;isolation des processus dans le contexte du système Linux.\nVu depuis l\u0026rsquo;extérieur, on peut lister les namespaces linux actuels, notamment celui contenant le container stress-demo. On invoque la commande suivante sur le node choisi.\n$ lsns | grep stress 4026533745 mnt 3 79723 root stress --vm 2 --vm-bytes 256M --verbose 4026533746 uts 3 79723 root stress --vm 2 --vm-bytes 256M --verbose 4026533747 pid 3 79723 root stress --vm 2 --vm-bytes 256M --verbose 4026533748 cgroup 3 79723 root stress --vm 2 --vm-bytes 256M --verbose Les processus enfants du parent stress --vm 2 --vm-bytes 256M --verbose vivront dans le même namespace, tel que mentionné pour la version cgroup v2. À cette échelle, il n\u0026rsquo;y a plus de notion de namespace Kubernetes, on ne parle que de cgroup et namespace linux.\nOn note les différents types de namespace linux: mnt, uts, pid, cgroup. On peut résumer chaque fonctionnalité de type via la table suivante8.\nNamespace Isolation cgroup Informations relatives à chaque cgroup ipc Messages et ressources IPC net Ressources réseau mnt Points de montage de fichiers pid Identification des processus time Fonctionnalité de démarrage et accès aux horloges système user Permissions utilisateurs uts Hostname et nom de domaine NIS C\u0026rsquo;est pour cela que lancer la commande top dans le conteneur stress-demo ne montre que les processus du pod. L\u0026rsquo;appel aux APIs de namespaces telles que unshare9 remplit ce rôle en créant ce namespace linux dont la visibilité est limitée. Plus d\u0026rsquo;info avec man unshare.\nConclusion # Nous ne descendrons pas jusqu\u0026rsquo;au kernel, même si une description de l\u0026rsquo;implémentation est disponible 10, et que cette vidéo 11 reste à ce jour encore très informative.\n➡ Le concept de namespace Kubernetes est très puissant pour isoler les pods sur un même système ; cependant leur supervision peut s\u0026rsquo;avérer délicate si l\u0026rsquo;on n\u0026rsquo;a pas conscience des différentes couches de bas en haut : les namespaces linux dont les cgroups sont un sous-ensemble, puis les namespaces Kubernetes.\nhttps://antlas.art/articles/consumption/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://antlas.art/articles/minikube/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://hub.docker.com/r/progrium/stress/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://man7.org/linux/man-pages/man7/cgroups.7.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html#core-interface-files\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://kubernetes.io/docs/concepts/architecture/cgroups/#requirements\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://man7.org/linux/man-pages/man7/namespaces.7.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://man7.org/linux/man-pages/man2/unshare.2.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/cgroups.html#how-are-cgroups-implemented\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.youtube.com/watch?v=sK5i-N34im8\u0026t=2485s\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"21 avril 2024","externalUrl":null,"permalink":"/articles/fr/consumption_container/","section":"Articles","summary":"Remarque sur les métriques d’activité des conteneurs Docker, et bref panorama des coulisses.","title":"Activité des conteneurs","type":"articles"},{"content":" Introduction # Confronté au sujet de contrôle parental, je me suis demandé s\u0026rsquo;il était possible de limiter l\u0026rsquo;accès réseau d\u0026rsquo;un utilisateur. La configuration doit être persistante, active dès le démarrage, et accessible en tant que super utilisateur seulement.\nFirewall # C\u0026rsquo;est au niveau du firewall que la solution est la plus intuitive, mes recherches en ligne proposent presque toutes le même raisonnement :\nAjouter une règle spécifique pour bloquer les flux, Spécifier le nom d\u0026rsquo;utilisateur, DROP toutes les connexions sauf pour l\u0026rsquo;interface loopback lo. Ce qui donne, pour un système basé sur Debian équipé d\u0026rsquo; iptables :\n$ iptables -A OUTPUT ! -o lo -m owner --uid-owner $USER -j DROP La partie ! -o lo exclut l\u0026rsquo;interface lo. Le résultat est le suivant :\n$ iptables -vL [...] Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 0 0 DROP all -- any !lo anywhere anywhere owner UID match antoine [...] Pour revenir à la normale, on supprime simplement la règle. Attention, dans mon cas cette règle était la seule de la chain OUTPUT.\n$ iptables -D OUTPUT 1 Persistence # Pour que la persistence soit effective sur une machine Debian, une solution consiste en l\u0026rsquo;installation du package iptables-persistent, et de copier les règles souhaitées dans le répertoire /etc/iptables/, en suivant le nommage rules.v4 et rules.v6 pour les règles ipv4 et ipv6. On lancera donc, pour sauvegarder les règles actuelles, les commandes suivantes :\n# save ipv4 rules $ iptables-save \u0026gt; /etc/iptables/rules.v4 # save ipv6 rules $ ip6tables-save \u0026gt; /etc/iptables/rules.v6 Conclusion # La solution proposée semble binaire, mais on bénéficie toutefois de la granularité du firewall pour définir finement les accès réseaux, et donc les accès extérieurs vers internet.\nEt pour aller un peu plus loin, on pourrait se conformer à l\u0026rsquo;utilisation de nftables, successeur d\u0026rsquo;iptables.\n","date":"24 mars 2024","externalUrl":null,"permalink":"/articles/fr/snippet_network_control/","section":"Articles","summary":"Comment restreindre l’accès réseau d’un utilisateur.","title":"Contrôle d'accès réseau","type":"articles"},{"content":" Introduction # Suite à cet article 1 où je mets en place un relai Nostr accessible publiquement, examinons plus en détails la sécurité de l\u0026rsquo;application.\nNote Je ne suis pas un expert en cybersécurité et ce qui est décrit ici ne peut être considéré exhaustif dans la quête d\u0026rsquo;une sécurisation complète d\u0026rsquo;un système informatique.\nLe raisonnement que je propose est d\u0026rsquo;avoir une vision globale en décrivant l\u0026rsquo;architecture du logiciel, puis d\u0026rsquo;utiliser des outils qui facilitent son suivi. Ensuite vient une analyse qui se divise en deux : une partie statique appliquée lors de la conception de l\u0026rsquo;application, puis une partie dynamique, appliquée lorsque l\u0026rsquo;application est en cours d\u0026rsquo;utilisation.\nL\u0026rsquo;écosystème # Commençons par bien définir l\u0026rsquo;écosystème technique. Plus le périmètre technique sera défini et identifié, plus on réduira les incompréhensions ou omissions et les surfaces d\u0026rsquo;attaques.\nReprenons le schéma proposé dans l\u0026rsquo;article référé 1.\nCe qui nous donne la matrice de flux suivante :\nNom Source Destination Détails Commentaires Flux entrant Internet Reverse Proxy, port 80 HTTP Gestion des connexions HTTP Flux entrant sécurisé Internet Reverse Proxy, port 443 HTTPS Gestion des connexions HTTPs, avec certificat Flux sortant sécurisé Reverse Proxy, port 443 Internet HTTPS Gestion des connexions HTTPs, avec certificat Redirection vers l\u0026rsquo;application Reverse Proxy Relai Nostr, port 8001 HTTP Transition en clair dans l\u0026rsquo;écosystème local Les connexions réseaux sont chiffrées via le reverse proxy, et assurent l\u0026rsquo;authenticité des interlocuteurs, ainsi que la confidentialité et l\u0026rsquo;intégrité des échanges.\nL\u0026rsquo;application est administrée via la plateforme Docker engine en mode swarm 2, sur un système Debian. La version de Docker engine est 24.0.7, celle de containerd 1.6.26 tandis que l\u0026rsquo;actuelle de la runtime bas niveau runc est 1.1.10. Attention à cette faille récemment découverte3, il faut passer à la version \u0026gt;= 1.1.12 !\n$docker version Server: Docker Engine - Community Engine: Version: 24.0.7 OS/Arch: linux/amd64 containerd: Version: 1.6.26 runc: Version: 1.1.10 docker-init: Version: 0.19.0 L\u0026rsquo;application est lancée via un service Docker, et tout cette stack réside sur une distribution GNU/Linux Debian 11. Point important : pas de CAPS Docker spécifiques à déclarer.\n$ lsb_release -a No LSB modules are available. Distributor ID:\tDebian Description:\tDebian GNU/Linux 11 (bullseye) Release:\t11 Codename:\tbullseye Les informations plus bas-niveau de la machine hôte sont les suivantes :\n$ uname -a Linux machine.antoine.local 5.10.X #1 SMP Debian 5.10.X x86_64 GNU/Linux ➡ On dispose d\u0026rsquo;une vue globale des différentes couches que composent le système. Ces informations restent nécessaires à la compréhension globale même si elles restent en dehors de la partie applicative.\nL\u0026rsquo;application # Deux modules composent le logiciel final : le relai et le reverse proxy.\nLe relai Nostr # Il est basé sur Python 3.11. Le gestionnaire de packages pip installe les dépendances. On utilise Docker pour packager le logiciel, dont voici le Dockerfile.\nFROM python:3.11.1-slim WORKDIR /app/ RUN apt-get update RUN apt-get install build-essential -y RUN apt-get install pkg-config -y --no-install-recommends COPY pyproject.toml requirements.txt /app/ RUN pip install -r requirements.txt COPY pyrelay /app/pyrelay ENV PYTHONPATH=/app La liste des dépendances est la suivante 4:\nattr==0.3.2 attrs==22.2.0 pydantic==1.10.4 secp256k1==0.14.0 SQLAlchemy==1.4.46 websockets==10.4 alembic==1.9.1 aiosqlite==0.18.0 Le relai Nostr stocke les informations des différents clients dans une base de données locale SQLite, utilisant le module asyncio. Les connexions clients - relais sont réalisées via des websockets.\nLe reverse proxy # Il est basé sur l\u0026rsquo;image Docker nginx-certbot:5.0.0. On n\u0026rsquo;a pas besoin de construire l\u0026rsquo;image nous-même, elle est présente en ligne. Il suffira de la télécharger pour l\u0026rsquo;analyser. C\u0026rsquo;est une image construite à partir d\u0026rsquo;une autre image Docker : nginx:1.25.3 5.\n$ docker pull jonasal/nginx-certbot:5.0.0 ➡ De même pour l\u0026rsquo;application, on dispose maintenant d\u0026rsquo;une vue globale des différents modules qui la composent.\nLa recherche de vulnérabilités # J\u0026rsquo;ai précédemment mentionné 6 l\u0026rsquo;utilitaire Grype qui se limite à l\u0026rsquo;analyse de vulnérabilités d\u0026rsquo;images Docker et filesystems. Pour cet article je propose d\u0026rsquo;utiliser Trivy 7 qui embarque d\u0026rsquo;autres analyses telles que les SBOM, les configurations Docker et Kubernetes.\nProposant des analyses assez complètes allant des packages systèmes jusqu\u0026rsquo;aux licences logicielles, voici son périmètre d\u0026rsquo;action 8 :\nOS Packages Language-specific Packages IaC files Kubernetes clusters Concernant les vulnérabilités, Trivy maintient une base de données des vulnérabilités connues, puis fait un matching sur les composants analysés. Le téléchargement de cette base de données est possible via la commande 9:\n$ trivy image --download-db-only Voici les différentes sources d\u0026rsquo;informations :\nType de détection Source OS Packages Multiples sources depending of target, see here Languages Packages Multiples sources depending of target, see here Kubernetes Kubernetes official CVE feed Attention à la notation de sévérité : Trivy donne la sévérité affichée par les \u0026ldquo;Vendors\u0026rdquo; (les distributeurs). Exemple : The severity is taken from the selected data source since the severity from vendors is more accurate. Using CVE-2023-0464 as an example, while it is rated as \u0026ldquo;HIGH\u0026rdquo; in NVD, Red Hat has marked its \u0026lsquo;Impact\u0026rsquo; as \u0026ldquo;Low\u0026rdquo;. As a result, Trivy will display it as \u0026ldquo;Low\u0026rdquo;. The severity depends on the compile option, the default configuration, etc. NVD doesn\u0026rsquo;t know how the vendor distributes the software. Red Hat evaluates the severity more accurately. That\u0026rsquo;s why Trivy prefers vendor scores over NVD.\nInstallons Trivy et voyons les informations que l\u0026rsquo;on peut en tirer.\nSur Debian, on exécute le code suivant.\n$ wget https://github.com/aquasecurity/trivy/releases/download/v0.48.3/trivy_0.48.3_Linux-64bit.deb On vérifie le checksum\n$ wget https://github.com/aquasecurity/trivy/releases/download/v0.48.3/trivy_0.48.3_checksums.txt \u0026amp;\u0026amp; grep \u0026#34;trivy_0.48.3_Linux-64bit.deb\u0026#34; trivy_0.48.3_checksums.txt 17a1721063c0af9eb51a49f65f4814512b22ed8e00995a73de6379e0a9480449 trivy_0.48.3_Linux-64bit.deb $ sha256sum trivy_0.48.3_Linux-64bit.deb 17a1721063c0af9eb51a49f65f4814512b22ed8e00995a73de6379e0a9480449 trivy_0.48.3_Linux-64bit.deb Et on finit par installer.\n$ sudo dpkg -i trivy_0.48.3_Linux-64bit.deb Analyse du relai Nostr # Analyse du Dockerfile # De nombreux conseils sont fournis par l\u0026rsquo;analyse du Dockerfile.\n$ trivy config Dockerfile Tests: 26 (SUCCESSES: 22, FAILURES: 4, EXCEPTIONS: 0) Failures: 4 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 3, CRITICAL: 0) HIGH: Specify at least 1 USER command in Dockerfile with non-root user as argument ════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════ Running containers with \u0026#39;root\u0026#39; user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a \u0026#39;USER\u0026#39; statement to the Dockerfile. See https://avd.aquasec.com/misconfig/ds002 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── HIGH: The instruction \u0026#39;RUN \u0026lt;package-manager\u0026gt; update\u0026#39; should always be followed by \u0026#39;\u0026lt;package-manager\u0026gt; install\u0026#39; in the same RUN statement. ════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════ The instruction \u0026#39;RUN \u0026lt;package-manager\u0026gt; update\u0026#39; should always be followed by \u0026#39;\u0026lt;package-manager\u0026gt; install\u0026#39; in the same RUN statement. See https://avd.aquasec.com/misconfig/ds017 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Dockerfile:5 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 5 [ RUN apt-get update ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── LOW: Add HEALTHCHECK instruction in your Dockerfile ════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════ You should add HEALTHCHECK instruction in your docker container images to perform the health check on running containers. See https://avd.aquasec.com/misconfig/ds026 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── HIGH: \u0026#39;--no-install-recommends\u0026#39; flag is missed: \u0026#39;apt-get install build-essential -y\u0026#39; ════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════ \u0026#39;apt-get\u0026#39; install should use \u0026#39;--no-install-recommends\u0026#39; to minimize image size. See https://avd.aquasec.com/misconfig/ds029 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Dockerfile:6 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 6 [ RUN apt-get install build-essential -y ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ➡ On peut facilement appliquer ces recommandations qui tendent à sécuriser l\u0026rsquo;exécution des processus, limiter la taille de l\u0026rsquo;image générée et améliorer la conformité du conteneur instancié.\nAnalyse statique # Dans la mesure où cette application est développée ici, on a la main sur son cycle de vie et sur la gestion granulaire des dépendances. Comptons déjà toutes les vulnérabilités. Pour cela, mettons en place un template Trivy10. Nous formatons facilement les résultats. Voici son contenu 11.\n{{- $critical := 0 }} {{- $high := 0 }} {{- $medium := 0 }} {{- $low := 0 }} {{- range . }} {{- range .Vulnerabilities }} {{- if eq .Severity \u0026#34;CRITICAL\u0026#34; }}{{- $critical = add $critical 1 }}{{- end }} {{- if eq .Severity \u0026#34;HIGH\u0026#34; }}{{- $high = add $high 1 }}{{- end }} {{- if eq .Severity \u0026#34;MEDIUM\u0026#34; }}{{- $medium = add $medium 1 }}{{- end }} {{- if eq .Severity \u0026#34;LOW\u0026#34; }}{{- $low = add $low 1 }}{{- end }} {{- end }} {{- end }} critical, high, medium, low {{ $critical }}, {{ $high }}, {{ $medium }}, {{ $low }} Et le résultat est donné par la commande suivante.\n$ trivy image pyrelay --format template --template \u0026#34;@count_vulnerabilities.tpl\u0026#34; 2\u0026gt;/dev/null critical, high, medium, low 1, 63, 115, 442 Le nombre de vulnérabilités est assez important. Mais cette information n\u0026rsquo;est pas assez précise pour statuer complètement. Voyons lesquelles ont été corrigées et que nous pouvons peut-être appliquer.\nOn lance une nouvelle recherche, on ne retiendra que le statut 12 fixed, pour savoir si l\u0026rsquo;on peut mettre à jour le composant concerné. Filtrons également par criticité et concentrons-nous seulement sur les failles les plus critiques.\n$ trivy image pyrelay --severity HIGH --ignore-status unknown,not_affected,under_investigation,will_not_fix,fix_deferred,end_of_life,affected Total: 18 (HIGH: 18, CRITICAL: 0) ┌──────────────┬────────────────┬──────────┬────────┬───────────────────┬────────────────────────┬──────────────────────────────────────────────────────────────┐ │ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ ├──────────────┼────────────────┼──────────┼────────┼───────────────────┼────────────────────────┼──────────────────────────────────────────────────────────────┤ │ libc-bin │ CVE-2023-4911 │ HIGH │ fixed │ 2.31-13+deb11u5 │ 2.31-13+deb11u7 │ glibc: buffer overflow in ld.so leading to privilege │ │ │ │ │ │ │ │ escalation │ │ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-4911 │ ├──────────────┼────────────────┤ │ ├───────────────────┼────────────────────────┼──────────────────────────────────────────────────────────────┤ │ libgnutls30 │ CVE-2023-0361 │ │ │ 3.7.1-5+deb11u2 │ 3.7.1-5+deb11u3 │ gnutls: timing side-channel in the TLS RSA key exchange code │ │ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-0361 │ ├──────────────┼────────────────┤ │ ├───────────────────┼────────────────────────┼──────────────────────────────────────────────────────────────┤ │ libncursesw6 │ CVE-2022-29458 │ │ │ 6.2+20201114-2 │ 6.2+20201114-2+deb11u1 │ segfaulting OOB read │ │ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2022-29458 │ │ ├────────────────┤ │ │ ├────────────────────────┼──────────────────────────────────────────────────────────────┤ │ │ CVE-2023-29491 │ │ │ │ 6.2+20201114-2+deb11u2 │ ncurses: Local users can trigger security-relevant memory │ │ │ │ │ │ │ │ corruption via malformed data │ │ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-29491 │ ├──────────────┼────────────────┤ │ ├───────────────────┼────────────────────────┼──────────────────────────────────────────────────────────────┤ │ libssl1.1 │ CVE-2022-4450 │ │ │ 1.1.1n-0+deb11u3 │ 1.1.1n-0+deb11u4 │ openssl: double free after calling PEM_read_bio_ex │ │ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2022-4450 │ │ ├────────────────┤ │ │ │ ├──────────────────────────────────────────────────────────────┤ │ │ CVE-2023-0215 │ │ │ │ │ openssl: use-after-free following BIO_new_NDEF │ │ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-0215 │ │ ├────────────────┤ │ │ │ ├──────────────────────────────────────────────────────────────┤ │ │ CVE-2023-0286 │ │ │ │ │ openssl: X.400 address type confusion in X.509 GeneralName │ │ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-0286 │ │ ├────────────────┤ │ │ ├────────────────────────┼──────────────────────────────────────────────────────────────┤ │ │ CVE-2023-0464 │ │ │ │ 1.1.1n-0+deb11u5 │ openssl: Denial of service by excessive resource usage in │ │ │ │ │ │ │ │ verifying X509 policy... │ │ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-0464 │ ├──────────────┼────────────────┤ │ ├───────────────────┼────────────────────────┼──────────────────────────────────────────────────────────────┤ │ libtinfo6 │ CVE-2022-29458 │ │ │ 6.2+20201114-2 │ 6.2+20201114-2+deb11u1 │ segfaulting OOB read │ │ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2022-29458 │ │ ├────────────────┤ │ │ ├────────────────────────┼──────────────────────────────────────────────────────────────┤ │ │ CVE-2023-29491 │ │ │ │ 6.2+20201114-2+deb11u2 │ ncurses: Local users can trigger security-relevant memory │ │ │ │ │ │ │ │ corruption via malformed data │ │ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-29491 │ ├──────────────┼────────────────┤ │ │ ├────────────────────────┼──────────────────────────────────────────────────────────────┤ │ ncurses-base │ CVE-2022-29458 │ │ │ │ 6.2+20201114-2+deb11u1 │ segfaulting OOB read │ │ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2022-29458 │ │ ├────────────────┤ │ │ ├────────────────────────┼──────────────────────────────────────────────────────────────┤ │ │ CVE-2023-29491 │ │ │ │ 6.2+20201114-2+deb11u2 │ ncurses: Local users can trigger security-relevant memory │ │ │ │ │ │ │ │ corruption via malformed data │ │ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-29491 │ ├──────────────┼────────────────┤ │ │ ├────────────────────────┼──────────────────────────────────────────────────────────────┤ │ ncurses-bin │ CVE-2022-29458 │ │ │ │ 6.2+20201114-2+deb11u1 │ segfaulting OOB read │ │ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2022-29458 │ │ ├────────────────┤ │ │ ├────────────────────────┼──────────────────────────────────────────────────────────────┤ │ │ CVE-2023-29491 │ │ │ │ 6.2+20201114-2+deb11u2 │ ncurses: Local users can trigger security-relevant memory │ │ │ │ │ │ │ │ corruption via malformed data │ │ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-29491 │ ├──────────────┼────────────────┤ │ ├───────────────────┼────────────────────────┼──────────────────────────────────────────────────────────────┤ │ openssl │ CVE-2022-4450 │ │ │ 1.1.1n-0+deb11u3 │ 1.1.1n-0+deb11u4 │ openssl: double free after calling PEM_read_bio_ex │ │ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2022-4450 │ │ ├────────────────┤ │ │ │ ├──────────────────────────────────────────────────────────────┤ │ │ CVE-2023-0215 │ │ │ │ │ openssl: use-after-free following BIO_new_NDEF │ │ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-0215 │ │ ├────────────────┤ │ │ │ ├──────────────────────────────────────────────────────────────┤ │ │ CVE-2023-0286 │ │ │ │ │ openssl: X.400 address type confusion in X.509 GeneralName │ │ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-0286 │ │ ├────────────────┤ │ │ ├────────────────────────┼──────────────────────────────────────────────────────────────┤ │ │ CVE-2023-0464 │ │ │ │ 1.1.1n-0+deb11u5 │ openssl: Denial of service by excessive resource usage in │ │ │ │ │ │ │ │ verifying X509 policy... │ │ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-0464 │ └──────────────┴────────────────┴──────────┴────────┴───────────────────┴────────────────────────┴──────────────────────────────────────────────────────────────┘ 18 des 63 failles HIGH de notre image pyrelay peuvent-être corrigées. Penchons-nous sur la plus haute criticité : la faille CRITICAL. Aucun correctif n\u0026rsquo;est présent. Mais impacte-elle notre application ? En recherchant plus d\u0026rsquo;informations sur son rôle, on apprend qu\u0026rsquo;elle est liée à la gestion de base de données Berkeley13.\n$ trivy image pyrelay --severity CRITICAL --ignore-status unknown,not_affected,under_investigation,will_not_fix,fix_deferred,end_of_life Total: 1 (CRITICAL: 1) ┌──────────┬───────────────┬──────────┬──────────┬───────────────────┬───────────────┬────────────────────────────────────────────────┐ │ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ ├──────────┼───────────────┼──────────┼──────────┼───────────────────┼───────────────┼────────────────────────────────────────────────┤ │ libdb5.3 │ CVE-2019-8457 │ CRITICAL │ affected │ 5.3.28+dfsg1-0.8 │ │ heap out-of-bound read in function rtreenode() │ │ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2019-8457 │ └──────────┴───────────────┴──────────┴──────────┴───────────────────┴───────────────┴────────────────────────────────────────────────┘ Analyse dynamique # Recherchons plus d\u0026rsquo;informations sur cette bibliothèque. N\u0026rsquo;oublions pas que d\u0026rsquo;un point de vue ensembliste, la totalité de l\u0026rsquo;environnement n\u0026rsquo;est pas dédiée à l\u0026rsquo;application. Par exemple il embarque les binaires apt-get, ls, \u0026hellip; qui sont inutiles à l\u0026rsquo;exécution du relai Nostr.\nUne simple commande ldd nous informerait des bibliothèques dynamiques attachées au programme. Mais nous utilisons l\u0026rsquo;interpréteur Python et elle n\u0026rsquo;est pas applicable. C\u0026rsquo;est l\u0026rsquo;ajout de la variable d\u0026rsquo;environnement LD_DEBUG qui va nous montrer les appels aux bibliothèques dynamiques.\nAttention cependant, on ne verra que les bibliothèques appelées lorsque le programme tourne. Cela signifie utiliser l\u0026rsquo;application entièrement pour être sûr que l\u0026rsquo;intégralité des fonctionnalités applicatives appellent ou n\u0026rsquo;appellent pas une bibliothèque spécifique.\nVoici un rappel des valeurs possibles de la variable d\u0026rsquo;environnement LD_DEBUG.\nLD_DEBUG=help ls Valid options for the LD_DEBUG environment variable are: libs display library search paths reloc display relocation processing files display progress for input file symbols display symbol table processing bindings display information about symbol binding versions display version dependencies all all previous options combined statistics display relocation statistics unused determined unused DSOs help display this help message and exit To direct the debugging output into a file instead of standard output a filename can be specified using the LD_DEBUG_OUTPUT environment variable. Relançons le service nostr_pyrelay et exécutons la commande suivante, en passant minutieusement sur toutes les fonctionnalités :\n$ docker exec -it nostr_pyrelay.1.43f44ozc4dhpuctpeylt8x6cw bash root@nostr_pyrelay:/app$ LD_DEBUG=libs python pyrelay/relay/server.py 2\u0026gt;\u0026amp;1 | grep -iv python | grep \u0026#34;/lib\u0026#34; 9140:\ttrying file=/usr/local/bin/../lib/libc.so.6 9140:\ttrying file=/lib/x86_64-linux-gnu/libc.so.6 9140:\ttrying file=/lib/x86_64-linux-gnu/libpthread.so.0 9140:\ttrying file=/lib/x86_64-linux-gnu/libdl.so.2 9140:\ttrying file=/lib/x86_64-linux-gnu/libutil.so.1 9140:\ttrying file=/lib/x86_64-linux-gnu/libm.so.6 9140:\tcalling init: /lib/x86_64-linux-gnu/libpthread.so.0 9140:\tcalling init: /lib/x86_64-linux-gnu/libc.so.6 9140:\tcalling init: /lib/x86_64-linux-gnu/libm.so.6 9140:\tcalling init: /lib/x86_64-linux-gnu/libutil.so.1 9140:\tcalling init: /lib/x86_64-linux-gnu/libdl.so.2 9140:\ttrying file=/usr/lib/x86_64-linux-gnu/libssl.so.1.1 9140:\ttrying file=/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 9140:\tcalling init: /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 9140:\tcalling init: /usr/lib/x86_64-linux-gnu/libssl.so.1.1 9140:\ttrying file=/lib/x86_64-linux-gnu/libz.so.1 9140:\tcalling init: /lib/x86_64-linux-gnu/libz.so.1 9140:\ttrying file=/lib/x86_64-linux-gnu/libbz2.so.1.0 9140:\tcalling init: /lib/x86_64-linux-gnu/libbz2.so.1.0 9140:\ttrying file=/lib/x86_64-linux-gnu/liblzma.so.5 9140:\tcalling init: /lib/x86_64-linux-gnu/liblzma.so.5 9140:\ttrying file=/usr/lib/x86_64-linux-gnu/libstdc++.so.6 9140:\ttrying file=/lib/x86_64-linux-gnu/libgcc_s.so.1 9140:\tcalling init: /lib/x86_64-linux-gnu/libgcc_s.so.1 9140:\tcalling init: /usr/lib/x86_64-linux-gnu/libstdc++.so.6 Si la bibliothèque libdb n\u0026rsquo;apparaît pas dans ces lignes, et qu\u0026rsquo;on est sûrs que l\u0026rsquo;intégralité des fonctionnalités de l\u0026rsquo;application a été testée, c\u0026rsquo;est qu\u0026rsquo;aucun appel à la bibliothèque libdb n\u0026rsquo;est réalisé. Cette faille de sécurité peut être jugée comme mineure sur notre système. On peut réitérer cette analyse avec les autres CVE qui sont affected.\n➡ Cette méthode reste expérimentale, et nécessite de plonger dans les couches les plus basses du système.\nMitigation des vulnérabilités # Voyons maintenant comment corriger une des failles de sécurité, en mettant à jour une dépendance. On ajoute une ligne d\u0026rsquo;installation dans le Dockerfile :\nFROM python:3.11.1-slim WORKDIR /app/ RUN apt-get update RUN apt-get install build-essential -y RUN apt-get install pkg-config -y --no-install-recommends # on ajoute la mise à jour de la bibliothèque libc-bin RUN apt-get install libc-bin=2.31-13+deb11u7 COPY pyproject.toml requirements.txt /app/ RUN pip install -r requirements.txt COPY pyrelay /app/pyrelay ENV PYTHONPATH=/app On rebuilde le tout et on relance l\u0026rsquo;analyse, qui ne montre plus cette faille. L\u0026rsquo;application a été patchée, on peut dérouler la suite de tests, augmenter son numéro de version, et l\u0026rsquo;utiliser.\n➡ Je décris ici qu\u0026rsquo;une partie minime de l\u0026rsquo;analyse de vulnérabilités et de leur correction. L\u0026rsquo;idée est de montrer comment l\u0026rsquo;outil Trivy nous informe sur la sécurité de notre application, et comment mettre en place des vérifications. Bien sûr, les points d\u0026rsquo;attentions varient selon la nature de l\u0026rsquo;application packagée, et des métriques à rendre visibles. En effet, tracer par exemple l\u0026rsquo;évolution des vulnérabilités semble une bonne pratique pour toujours rester à l\u0026rsquo;écoute des potentielles failles de sécurité qui pourraient compromettre le service, et in fine, les utilisateurs.\nAnalyse du reverse proxy # Nous ne maintenons pas l\u0026rsquo;image Docker du reverse proxy. Il est donc plus difficile de modifier son cycle de vie, son développement, son ajout de dépendances,\u0026hellip; Même si l\u0026rsquo;image Docker est open source, elle peut être récupérée à notre compte et être intégrée à nos développements.\nNote Mon parti pris sera de privilégier le moins de customisation possible. Plus on utilise les logiciels tels quels, moins ils nécessitent d\u0026rsquo;attention, hormis leur intégration au sein de notre écosystème. Customiser, adapter un logiciel à nos besoins signifie développer, donc maintenir un service, et devient un point d\u0026rsquo;attention supplémentaire. Un compromis est à atteindre entre la complexité d\u0026rsquo;une nouvelle intégration logicielle et celle inhérente à un nouveau développement.\nOn peut répéter l\u0026rsquo;analyse statique des vulnérabilités d\u0026rsquo;une part, et tester dynamiquement l\u0026rsquo;accès au reverse proxy. Pour la première option, on notera que le nombre de vulnérabilités augmente significativement avec l\u0026rsquo;ajout de Python et certbot.\n# vulnerabilities in nginx image $ trivy image nginx --format template --template \u0026#34;@tp.tpl\u0026#34; 2\u0026gt;/dev/null critical, high, medium, low 2, 18, 33, 85 # vulnerabilities in nginx-certbot image $ trivy image jonasal/nginx-certbot:5.0.0 --format template --template \u0026#34;@tp.tpl\u0026#34; 2\u0026gt;/dev/null critical, high, medium, low 2, 29, 50, 89 Les modules nginx peuvent embarquer des failles de sécurité, et il est toujours intéressant d\u0026rsquo;en connaitre la liste :\n$ docker run -it jonasal/nginx-certbot:5.0.0 bash root@e8b3baf03248:/$ nginx -V 2\u0026gt;\u0026amp;1 | tr \u0026#39; \u0026#39; \u0026#39;\\n\u0026#39; | grep module --modules-path=/usr/lib/nginx/modules --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-http_v3_module --with-mail_ssl_module --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module De ce fait on pourra, en plus de Trivy, en consultant cette page14, savoir si l\u0026rsquo;un des modules installé embarque une faille de sécurité.\nPour la seconde option, tournons-nous vers l\u0026rsquo;utilitaire Whatweb15.\nWeb scanning # J\u0026rsquo;ai mis en place une image Docker, facilitant l\u0026rsquo;utilisation de ce logiciel 16. On lance tout ça en visant notre endpoint à l\u0026rsquo;adresse antoine.local (cf article référé 1).\n$ wget https://gist.githubusercontent.com/antlas0/a63c5f08ef9d6bc4b3252a4b394c9f95/raw/21a6711e56665429e267defc94a190893ae597c1/Dockerfile $ docker build . -t ww:0.0.1 [...] $ docker run ww:0.0.1 whatweb antoine.local -a 3 https://antoine.local [426 Upgrade Required] Country[XXXX][XX], HTTPServer[nginx], IP[xx.xx.xx.xx], UncommonHeaders[upgrade], WebSocket, nginx Le reverse proxy et l\u0026rsquo;application répondent présents et divulguent certaines informations sur leur identité et fonctionnement. On voit que seules les propriétés applicatives sont accessibles, et on ne sait pas que la plateforme Docker est utilisée en coulisse.\n➡ N\u0026rsquo;utilisant pas de framework webs connus, le nombre de failles de sécurité diminue, mais au dépit de reporter la responsabilité sur le relai Nostr, dont le développement devient crucial.\nLa composition des modules # Pour connaître précisément tous les composants logiciels embarqués dans chaque module, Trivy vient encore à la rescousse. Une liste dotée d\u0026rsquo;un format spécifique sera alors générée et pourra servir de référence. On connaitra alors les compositions exactes de chaque module, que l\u0026rsquo;on nomme SBOM (Software Bill of Materials).\nTrivy supporte la reconnaissance des packages systèmes et de développement 17.\nDeux formats de description sont possibles18, le SPDX et le CDX. Tournons-nous vers le plus récent qui se veut un peu plus léger.\nTentons de retrouver la composition du relai. Le scan commence par l\u0026rsquo;image Docker, le système d\u0026rsquo;exploitation et les packages systèmes.\n$ trivy image --format cyclonedx --output result.json pyrelay:0.0.1 $ cat result.json { \u0026#34;$schema\u0026#34;: \u0026#34;http://cyclonedx.org/schema/bom-1.5.schema.json\u0026#34;, \u0026#34;bomFormat\u0026#34;: \u0026#34;CycloneDX\u0026#34;, \u0026#34;specVersion\u0026#34;: \u0026#34;1.5\u0026#34;, \u0026#34;serialNumber\u0026#34;: \u0026#34;urn:uuid:98a177ce-b5b8-4642-a586-e6ddfce63ca6\u0026#34;, \u0026#34;version\u0026#34;: 1, \u0026#34;metadata\u0026#34;: { \u0026#34;timestamp\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;tools\u0026#34;: [ { \u0026#34;vendor\u0026#34;: \u0026#34;aquasecurity\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;trivy\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;0.48.3\u0026#34; } ], \u0026#34;component\u0026#34;: { \u0026#34;bom-ref\u0026#34;: \u0026#34;8d8fe4ce-1e41-4283-9bf0-f3face7ae57d\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;container\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;pyrelay:1.0.0\u0026#34;, \u0026#34;properties\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;aquasecurity:trivy:DiffID\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;sha256:bd2fe8b74db65d82ea10db97368d35b92998d4ea0e7e7dc819481fe4a68f64cf\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;aquasecurity:trivy:ImageID\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;sha256:af71e4e9289ba9f42609a6f6fb4f91df30168d42459992a896194363cd7c9871\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;aquasecurity:trivy:RepoTag\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;pyrelay:1.0.0\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;aquasecurity:trivy:SchemaVersion\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;2\u0026#34; } ] } }, \u0026#34;components\u0026#34;: [ { \u0026#34;bom-ref\u0026#34;: \u0026#34;c8938c83-ea3e-4ab0-8ae6-d8a5ef1ebcb7\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;operating-system\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;debian\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;11.6\u0026#34;, \u0026#34;properties\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;aquasecurity:trivy:Class\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;os-pkgs\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;aquasecurity:trivy:Type\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;debian\u0026#34; } ] }, { \u0026#34;bom-ref\u0026#34;: \u0026#34;pkg:deb/debian/adduser@3.118?arch=all\u0026amp;distro=debian-11.6\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;library\u0026#34;, \u0026#34;supplier\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;Debian Adduser Developers \u0026lt;adduser@packages.debian.org\u0026gt;\u0026#34; }, \u0026#34;name\u0026#34;: \u0026#34;adduser\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;3.118\u0026#34;, \u0026#34;licenses\u0026#34;: [ { \u0026#34;license\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;GPL-2.0\u0026#34; } } ], \u0026#34;purl\u0026#34;: \u0026#34;pkg:deb/debian/adduser@3.118?arch=all\u0026amp;distro=debian-11.6\u0026#34;, \u0026#34;properties\u0026#34;: [ [...] Puis les packages python sont reconnus, exemple ici avec le package attr.\n[...] { \u0026#34;bom-ref\u0026#34;: \u0026#34;pkg:pypi/attr@0.3.2?file_path=usr%2Flocal%2Flib%2Fpython3.11%2Fsite-packages%2Fattr-0.3.2.dist-info%2FMETADATA\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;library\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;attr\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;0.3.2\u0026#34;, \u0026#34;licenses\u0026#34;: [ { \u0026#34;license\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;MIT\u0026#34; } } ], [...] On peut, grâce à ce fichier, par exemple suivre les évolutions de licences au fur et à mesure du développement et des versions réalisées.\n➡ La SBOM est un outil très intéressant, elle donne une vue précise de tout le logiciel étudié. En comparant plusieurs fois la SBOM de plusieurs versions de son logiciel, on sera en mesure de déterminer l\u0026rsquo;évolution des dépendances, de sa complexité et des changements de licence.\nWeb application firewall # On repart sur une analyse dynamique, dont l\u0026rsquo;un des objectifs est de se protéger des intrusions. Lors du fonctionnement de l\u0026rsquo;application, mettre en place un WAF19 adresse ce problème. En surveillant les connexions HTTP établies en temps-réel, le firewall sera en mesure de stopper celles jugées malveillantes. Un module 20 dédié au reverse proxy Nginx peut être installé, contenant une base de références d\u0026rsquo;attaques web, identifiées par l\u0026rsquo;OWASP 21.\nAdaptation de l\u0026rsquo;architecture # On peut aussi le mettre en place de façon modulaire comme un intermédiaire supplémentaire dans l\u0026rsquo;écosystème, à travers lequel toutes les connexions vont passer. Le reverse proxy Nginx peut, à la place de rediriger directement les connexions vers le relai, les router vers le WAF, qui les enverra vers le relai après une analyse de sécurité.\nLe service de reverse proxy utilise une image déjà customisée, nginx-certbot:5.0.0. Pour des raisons de simplicité, le meilleur compromis semble d\u0026rsquo;ajouter un service Docker de plus pour le WAF22, plutôt que d\u0026rsquo;intégrer le module Nginx Modsecurity dans l\u0026rsquo;image nginx-certbot, nécessitant un ajout et une recompilation complète du module Nginx et de l\u0026rsquo;image Docker. Les inconvénients se trouveront alors dans la complexité de gestion de deux services Docker en amont du relai, et de l\u0026rsquo;augmentation des temps de réponse HTTP. Ceci dit, tout reste modulaire : un service pour la gestion SSL/TLS, et un autre pour l\u0026rsquo;analyse de flux.\nInstallation # On récupère l\u0026rsquo;image\n$ docker pull owasp/modsecurity-crs:3.3.5-nginx-202401080101 On dispose le WAF entre le reverse proxy et le relai, ce qui nécessite la configuration suivante :\nLe reverse proxy pointe maintenant vers le WAF, le WAF pointe vers le relai. Le point d\u0026rsquo;attention sur cette image est la présence des variables d\u0026rsquo;environnement et des templates. La combinaison des deux permet d\u0026rsquo;arriver à ses fins. Dans notre cas, on montera le fichier server default.conf dans le dossier templates23, en précisant le resolver Docker avec la variable DNS_SERVER=127.0.0.11. Pour plus d\u0026rsquo;informations sur le niveau paranoia, c\u0026rsquo;est par ici24.\nLancement # On lance les services Docker. Comme précisé dans l\u0026rsquo;article référé1, j\u0026rsquo;utilise un outil homemade pour lancer les services Docker. Voici la configuration de services, on retrouve les paramètres standards Docker.\n{ \u0026#34;spec\u0026#34;: { \u0026#34;image\u0026#34;: \u0026#34;jonasal/nginx-certbot\u0026#34;, \u0026#34;tag\u0026#34;:\u0026#34;5.0.0\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;nginx_reverse_proxy\u0026#34;, \u0026#34;env\u0026#34;:[\u0026#34;CERTBOT_EMAIL=user@example.com\u0026#34;], \u0026#34;hostname\u0026#34;:\u0026#34;nginx_reverse_proxy\u0026#34;, \u0026#34;mounts\u0026#34;: [\u0026#34;nginx_system.conf:/etc/nginx/nginx.conf/:ro\u0026#34;, \u0026#34;./user/:/etc/nginx/user_conf.d/:ro\u0026#34;, \u0026#34;access.log:/var/log/nginx/access.log:rw\u0026#34;, \u0026#34;./letsencrypt/:/etc/letsencrypt/:rw\u0026#34;], \u0026#34;endpoints\u0026#34;:{ \u0026#34;mode\u0026#34;:\u0026#34;vip\u0026#34;, \u0026#34;ports\u0026#34;:{\u0026#34;443\u0026#34;: 443, \u0026#34;80\u0026#34;: 80} } } }, { \u0026#34;spec\u0026#34;: { \u0026#34;image\u0026#34;: \u0026#34;owasp/modsecurity-crs\u0026#34;, \u0026#34;tag\u0026#34;:\u0026#34;3.3.5-nginx-202401080101\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;nostr_waf\u0026#34;, \u0026#34;hostname\u0026#34;:\u0026#34;nostr_waf\u0026#34;, \u0026#34;mounts\u0026#34;: [\u0026#34;nginx_user.conf:/etc/nginx/templates/conf.d/default.conf.template\u0026#34;], \u0026#34;env\u0026#34;:[\u0026#34;PARANOIA=1\u0026#34;, \u0026#34;DNS_SERVER=127.0.0.11\u0026#34;], \u0026#34;endpoints\u0026#34;:{ \u0026#34;mode\u0026#34;:\u0026#34;dnsrr\u0026#34; } } }, { \u0026#34;spec\u0026#34;: { \u0026#34;image\u0026#34;: \u0026#34;pyrelay\u0026#34;, \u0026#34;tag\u0026#34;:\u0026#34;latest\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;nostr_pyrelay\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;python pyrelay/relay/server.py\u0026#34;, \u0026#34;env\u0026#34;:[\u0026#34;SERVER_NAME=antoine.local\u0026#34;], \u0026#34;hostname\u0026#34;:\u0026#34;nostr_pyrelay\u0026#34;, \u0026#34;endpoints\u0026#34;:{ \u0026#34;mode\u0026#34;:\u0026#34;dnsrr\u0026#34; } } } On vérifie que le service WAF redirige son flux vers le relai nostr_pyrelay :\n$ docker exec -it nostr_waf.1.f39bwqlrl21a9tsnjv1gtf0aa cat /etc/nginx/nginx.conf load_module modules/ngx_http_modsecurity_module.so; worker_processes auto; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; keepalive_timeout 60s; sendfile on; resolver 127.0.0.11 valid=5s; include /etc/nginx/conf.d/*.conf; } Le module modsecurity est bien chargé, le resolver Docker est bien renseigné.\nOn jette un coup d\u0026rsquo;oeil au server HTTP configuré :\n$ docker exec -it nostr_waf.1.f39bwqlrl21a9tsnjv1gtf0aa cat /etc/nginx/conf.d/default.conf map $http_upgrade $connection_upgrade { default upgrade; \u0026#39;\u0026#39; close; } server { listen 80 default_server; location / { proxy_pass http://nostr_pyrelay:8001; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_read_timeout 86400; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } La redirection est bien faite du WAF vers le relai !\nMaintenant, le reverse proxy doit faire passer les requêtes HTTP par le WAF.\n$ docker exec -it nginx_reverse_proxy.1.ytfy0s2l7vjbzk9viwjnraly0 cat /etc/nginx/conf.d/nginx.conf server { # Listen to port 443 on both IPv4 and IPv6. listen 443 ssl default_server reuseport; listen [::]:443 ssl default_server reuseport; # Domain names this server should respond to. server_name antoine.local; [...] location / { proxy_pass http://nostr_waf:80; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_read_timeout 86400; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } Tout est bon, les aiguillages sont établis.\nPlace au test de connexion, on vérifie que l\u0026rsquo;on atteint bien le relai à partir d\u0026rsquo;internet.\n$ docker service logs nostr_pyrelay nostr_pyrelay.1.w5mxphrcoyu4@host | __main__ - INFO - New message from connection conn_uid=452a18dc-9edb-4734-b921-ee463af78c37 nostr_pyrelay.1.w5mxphrcoyu4@host | pyrelay.relay.dispatcher - INFO - Got request=NostrRequest(subscription_id=\u0026#39;Profiles\u0026#39;, filters=[NostrFilter(ids=None, authors=[\u0026#39;\u0026#39;, \u0026#39;\u0026#39;], kinds=[], since=1704276504, until=None, limit=None, generic_tags=None)]) conn_uid=452a18dc-9edb-4734-b921-ee463af78c37 nostr_pyrelay.1.w5mxphrcoyu4@host | __main__ - INFO - New message from connection conn_uid=452a18dc-9edb-4734-b921-ee463af78c37 nostr_pyrelay.1.w5mxphrcoyu4@host | pyrelay.relay.dispatcher - INFO - Got request=NostrRequest(subscription_id=\u0026#39;RESUME-Following1706762439\u0026#39;, filters=[NostrFilter(ids=None, authors=[\u0026#39;\u0026#39;, \u0026#39;\u0026#39;], kinds=[], since=1706762439, until=None, limit=5000, generic_tags=None)]) conn_uid=452a18dc-9edb-4734-b921-ee463af78c37 Les traces du relai sont parlantes, on observe les requêtes du client Nostur, que l\u0026rsquo;on a configuré sur ce relai.\nDans le container nostr_waf, on observe les transferts de connexions en provenance du reverse proxy, dont l\u0026rsquo;IP est 192.168.122.243. Le user-agent du client Nostur est visible.\nnostr_waf.1.f39bwqlrl21a@host | [notice] 1#1: ModSecurity-nginx v1.0.3 (rules loaded inline/local/remote: 0/923/0) nostr_waf.1.f39bwqlrl21a@host | 192.168.122.243 - - [xx/xxx/2024:xx:xx:xx +0000] \u0026#34;GET / HTTP/1.1\u0026#34; 101 36 \u0026#34;-\u0026#34; \u0026#34;Nostur/259 CFNetwork/1490.0.4 Darwin/23.2.0\u0026#34; \u0026#34;10.10.0.4\u0026#34; ➡ L\u0026rsquo;architecture est adaptée, fonctionnelle. Maintenant tentons des requêtes malveillantes, et observons le résultat.\nVérification du fonctionnement # Premièrement, le scan du site web via l\u0026rsquo;utilitaire Whatweb est déjà détecté et stoppé, le firewall web renvoi un code d\u0026rsquo;erreur 403.\n$ docker run ww:0.0.1 whatweb antoine.local -a 3 https://antoine.local [403 Forbidden] Country[XXXX][XX], HTTPServer[nginx], IP[xx.xx.xx.xx], Title[403 Forbidden], nginx Testons ensuite l\u0026rsquo;injection SQL via l\u0026rsquo;outil SQLMap 25. Pour attester du fonctionnement, on lancera les mêmes commandes à partir du reverse proxy, qui a un accès direct au relai, et à partir d\u0026rsquo;une machine extérieure au swarm.\nSur le reverse proxy, on récupère l\u0026rsquo;IP du relai\nroot@nginx_reverse_proxy:~$ nslookup nostr_pyrelay Server:\t127.0.0.11 Address:\t127.0.0.11#53 Non-authoritative answer: Name:\tnostr_pyrelay Address: 192.168.122.239 et on lance l\u0026rsquo;outil SQLMap\nroot@nginx_reverse_proxy:/$ git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev root@nginx_reverse_proxy:/$ cd sqlmap-dev root@nginx_reverse_proxy:/$ python3 sqlmap.py -u 192.168.122.239:8001 [*] starting [10:31:48] [INFO] testing connection to the target URL [10:31:48] [WARNING] the web server responded with an HTTP error code (426) which could interfere with the results of the tests [10:31:48] [INFO] testing if the target URL content is stable [10:31:49] [INFO] target URL content is stable [10:31:49] [CRITICAL] no parameter(s) found for testing in the provided data (e.g. GET parameter \u0026#39;id\u0026#39; in \u0026#39;www.site.com/index.php?id=1\u0026#39;) [10:31:49] [WARNING] HTTP error codes detected during run: 426 (Upgrade Required) - 2 times [*] ending Pas d\u0026rsquo;information glanée, mais en tous cas l\u0026rsquo;analyse s\u0026rsquo;est déroulée correctement, ce qui n\u0026rsquo;est pas le cas à partir d\u0026rsquo;une machine extérieure au swarm. On obtient un 403 Forbidden :\n# on an internet machine $ python sqlmap.py -u https://antoine.local [*] starting [11:26:06] [INFO] testing connection to the target URL [11:26:06] [WARNING] the web server responded with an HTTP error code (403) which could interfere with the results of the tests [11:26:06] [INFO] checking if the target is protected by some kind of WAF/IPS [11:26:06] [INFO] testing if the target URL content is stable [11:26:07] [INFO] target URL content is stable [11:26:07] [CRITICAL] no parameter(s) found for testing in the provided data (e.g. GET parameter \u0026#39;id\u0026#39; in \u0026#39;www.site.com/index.php?id=1\u0026#39;) [11:26:07] [WARNING] HTTP error codes detected during run: 403 (Forbidden) - 3 times [*] ending Les logs du WAF sont explicites :\n{ \u0026#34;transaction\u0026#34;: { \u0026#34;client_ip\u0026#34;: \u0026#34;192.168.122.243\u0026#34;, \u0026#34;time_stamp\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;server_id\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;client_port\u0026#34;: 50872, \u0026#34;host_ip\u0026#34;: \u0026#34;192.168.122.241\u0026#34;, \u0026#34;host_port\u0026#34;: 80, \u0026#34;unique_id\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;request\u0026#34;: { \u0026#34;method\u0026#34;: \u0026#34;GET\u0026#34;, \u0026#34;http_version\u0026#34;: 1.1, \u0026#34;uri\u0026#34;: \u0026#34;/\u0026#34;, \u0026#34;headers\u0026#34;: { \u0026#34;Connection\u0026#34;: \u0026#34;close\u0026#34;, \u0026#34;Cache-Control\u0026#34;: \u0026#34;no-cache\u0026#34;, \u0026#34;X-Real-IP\u0026#34;: \u0026#34;10.10.0.4\u0026#34;, \u0026#34;Host\u0026#34;: \u0026#34;antoine.local\u0026#34;, \u0026#34;X-Forwarded-For\u0026#34;: \u0026#34;10.10.0.4\u0026#34;, \u0026#34;User-Agent\u0026#34;: \u0026#34;sqlmap/1.8.1.7#dev (https://sqlmap.org)\u0026#34;, \u0026#34;Accept\u0026#34;: \u0026#34;*/*\u0026#34;, \u0026#34;Accept-Encoding\u0026#34;: \u0026#34;gzip,deflate\u0026#34; } }, \u0026#34;response\u0026#34;: { \u0026#34;body\u0026#34;: \u0026#34;\u0026lt;html\u0026gt;\\r\\n\u0026lt;head\u0026gt;\u0026lt;title\u0026gt;403 Forbidden\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt;\\r\\n\u0026lt;body\u0026gt;\\r\\n\u0026lt;center\u0026gt;\u0026lt;h1\u0026gt;403 Forbidden\u0026lt;/h1\u0026gt;\u0026lt;/center\u0026gt;\\r\\n\u0026lt;hr\u0026gt;\u0026lt;center\u0026gt;nginx/1.25.3\u0026lt;/center\u0026gt;\\r\\n\u0026lt;/body\u0026gt;\\r\\n\u0026lt;/html\u0026gt;\\r\\n\u0026#34;, \u0026#34;http_code\u0026#34;: 403, \u0026#34;headers\u0026#34;: { \u0026#34;Server\u0026#34;: \u0026#34;nginx/1.25.3\u0026#34;, \u0026#34;Date\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;Content-Length\u0026#34;: \u0026#34;153\u0026#34;, \u0026#34;Content-Type\u0026#34;: \u0026#34;text/html\u0026#34;, \u0026#34;Connection\u0026#34;: \u0026#34;close\u0026#34; } }, [...] \u0026#34;messages\u0026#34;: [ { \u0026#34;message\u0026#34;: \u0026#34;Found User-Agent associated with security scanner\u0026#34;, \u0026#34;details\u0026#34;: { \u0026#34;match\u0026#34;: \u0026#34;Matched \\\u0026#34;Operator `PmFromFile\u0026#39; with parameter `scanners-user-agents.data\u0026#39; against variable `REQUEST_HEADERS:User-Agent\u0026#39; (Value: `sqlmap/1.8.1.7#dev (https://sqlmap.org)\u0026#39; )\u0026#34;, \u0026#34;reference\u0026#34;: \u0026#34;o0,6v133,39t:lowercase\u0026#34;, \u0026#34;ruleId\u0026#34;: \u0026#34;913100\u0026#34;, \u0026#34;file\u0026#34;: \u0026#34;/etc/modsecurity.d/owasp-crs/rules/REQUEST-913-SCANNER-DETECTION.conf\u0026#34;, \u0026#34;lineNumber\u0026#34;: \u0026#34;34\u0026#34;, \u0026#34;data\u0026#34;: \u0026#34;Matched Data: sqlmap found within REQUEST_HEADERS:User-Agent: sqlmap/1.8.1.7#dev (https://sqlmap.org)\u0026#34;, \u0026#34;severity\u0026#34;: \u0026#34;2\u0026#34;, \u0026#34;ver\u0026#34;: \u0026#34;OWASP_CRS/3.3.5\u0026#34;, \u0026#34;rev\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;tags\u0026#34;: [ \u0026#34;modsecurity\u0026#34;, \u0026#34;application-multi\u0026#34;, \u0026#34;language-multi\u0026#34;, \u0026#34;platform-multi\u0026#34;, \u0026#34;attack-reputation-scanner\u0026#34;, \u0026#34;paranoia-level/1\u0026#34;, \u0026#34;OWASP_CRS\u0026#34;, \u0026#34;capec/1000/118/224/541/310\u0026#34;, \u0026#34;PCI/6.5.10\u0026#34; ], \u0026#34;maturity\u0026#34;: \u0026#34;0\u0026#34;, \u0026#34;accuracy\u0026#34;: \u0026#34;0\u0026#34; } }, { \u0026#34;message\u0026#34;: \u0026#34;Inbound Anomaly Score Exceeded (Total Score: 5)\u0026#34;, \u0026#34;details\u0026#34;: { \u0026#34;match\u0026#34;: \u0026#34;Matched \\\u0026#34;Operator `Ge\u0026#39; with parameter `5\u0026#39; against variable `TX:ANOMALY_SCORE\u0026#39; (Value: `5\u0026#39; )\u0026#34;, \u0026#34;reference\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;ruleId\u0026#34;: \u0026#34;949110\u0026#34;, \u0026#34;file\u0026#34;: \u0026#34;/etc/modsecurity.d/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf\u0026#34;, \u0026#34;lineNumber\u0026#34;: \u0026#34;81\u0026#34;, \u0026#34;data\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;severity\u0026#34;: \u0026#34;2\u0026#34;, \u0026#34;ver\u0026#34;: \u0026#34;OWASP_CRS/3.3.5\u0026#34;, \u0026#34;rev\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;tags\u0026#34;: [ \u0026#34;modsecurity\u0026#34;, \u0026#34;application-multi\u0026#34;, \u0026#34;language-multi\u0026#34;, \u0026#34;platform-multi\u0026#34;, \u0026#34;attack-generic\u0026#34; ], \u0026#34;maturity\u0026#34;: \u0026#34;0\u0026#34;, \u0026#34;accuracy\u0026#34;: \u0026#34;0\u0026#34; } } ] } } Le scanner SQLMap a été détecté, et les réponses HTTP ont été modifiées pour renvoyer une page vide et le code d\u0026rsquo;erreur 403.\n➡ Le WAF est donc fonctionnel, notre architecture propose une sécurité accrue face aux tentatives d\u0026rsquo;intrusions via le flux HTTP.\nConclusion # Nous avons brièvement abordé la sécurité applicative. La sécurité n\u0026rsquo;est pas qu\u0026rsquo;une affaire de conception ou de supervision. Les deux aspects doivent cohabiter dans une vision globale.\nUn outil comme Trivy amène de la clarté sur la stack logicielle construite au moment de son développement. Même si son analyse technique propose des réponses certaines sur les périmètres d\u0026rsquo;impacts, c\u0026rsquo;est bien la vision humaine qui reste centrale pour orienter les choix de conception. Des procédés automatisés au moment de la production logicielle peuvent adresser cette vision durable, en suivant par exemple l\u0026rsquo;évolution des vulnérabilités.\nCette vision humaine se base sur\nUne connaissance globale du système (intgration des modules, flux réseaux,\u0026hellip;), Une connaissance précise de chaque module applicatif, Un suivi continu des développements et des plateformes utilisées (Docker,\u0026hellip;). Mais une analyse en temps-réel lorsque le produit est utilisé reste inévitable pour superviser les évènements, contrôler les accès, et répondre aux attaques.\nhttps://antlas.art/articles/nostr_experimentations/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://docs.docker.com/engine/swarm/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/antlas0/pyrelay/blob/master/requirements.txt\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/JonasAlfredsson/docker-nginx-certbot/blob/master/src/Dockerfile#L1C1-L1C18\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://antlas.art/articles/memo_docker_vulnerabilities/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/aquasecurity/trivy\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://aquasecurity.github.io/trivy/v0.48/docs/coverage/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://aquasecurity.github.io/trivy/v0.48/docs/configuration/db/#vulnerability-database\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://aquasecurity.github.io/trivy/v0.18.3/examples/report/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://gist.github.com/antlas0/1e30b228d3e5f21a60eb5e8b098a723c#file-count_vulnerabilities-tpl\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://aquasecurity.github.io/trivy/v0.48/docs/configuration/filtering/#by-status\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://libdb.org/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://nginx.org/en/security_advisories.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/urbanadventurer/WhatWeb\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://gist.github.com/antlas0/a63c5f08ef9d6bc4b3252a4b394c9f95\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://aquasecurity.github.io/trivy/v0.37/docs/vulnerability/detection/language/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://scribesecurity.com/blog/spdx-vs-cyclonedx-sbom-formats-compared/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://fr.wikipedia.org/wiki/Web_application_firewall\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.nginx.com/blog/compiling-and-installing-modsecurity-for-open-source-nginx/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://fr.wikipedia.org/wiki/Open_Web_Application_Security_Project\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://hub.docker.com/r/owasp/modsecurity-crs\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/coreruleset/modsecurity-crs-docker/tree/develop?tab=readme-ov-file#nginx-based-images-breaking-change\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://coreruleset.org/20211028/working-with-paranoia-levels/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/sqlmapproject/sqlmap.git\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"10 février 2024","externalUrl":null,"permalink":"/articles/fr/article_docker_images_analysis/","section":"Articles","summary":"Tour d’horizon des analyses d’une application web administrée avec Docker.","title":"Revue de sécurité d'une application Docker","type":"articles"},{"content":" Introduction # Toujours dans l\u0026rsquo;optique de maîtriser ses communications et sa vie privée, le projet Nostr 1 a attiré mon attention. Voici un petit résumé du projet, extrait du web.\n\u0026ldquo;Nostr (Notes and Other Stuff Transmitted by Relays) est un nouveau protocole simple, ouvert et résistant à la censure. Actuellement, les developpeur-euse-s sont concentrés sur la construction d’un réseau social décentralisé basé principalement sur des clés publiques et privées2. Les spécifications sont faites au niveau protocolaire ; avec des parties obligatoires, et optionnelles. On parle alors de NIPs (Nostr Implementation Possibilities) 3.\nPour atteindre cela, les clients vont se connecter à différents relais et vont y publier des messages appelés événements. Nostr utilise la notion de relai simple et client intelligent qui vont se charger de signer les événements à l’aide d’une paire de clés pour authentifier la personne 2.\u0026rdquo;\nLes relais peuvent être utilisés pour récupérer les évènements (read) ou en publier (write). Un évènement peut autant être une note (similaire à un tweet ou un toot) qu\u0026rsquo;un message privé. ➡ Pourrait-on mettre en place un relai et communiquer de manière sécurisée en étant sûr que chaque élément soit connu et maîtrisé ? Mettre en place une infrastructure légère à installer semble intéressant, et diffère des écosystèmes complexes à maintenir tels que Matrix. Voyons plus en détails les étapes à mettre en oeuvre pour ajouter un relai public et s\u0026rsquo;y connecter.\nChoix du relai # Il existe de nombreux logiciels relais4, développés en différents langages. Les relais n\u0026rsquo;implémentent pas tous toutes les mêmes NIPs, mais généralement les NIPs obligatoires sont disponibles.\nMon choix se portera sur le relai PyRelay 5 à jour du commit 79dca44, assez récent au moment de l\u0026rsquo;écriture et développé en Python.\nTest du relai # Après avoir corrigé quelques détails dans le code6 — lié au manque de maintenance du logiciel et des fonctionnalités grandissantes des clients, le relai peut être lancé.\nOn construit une dernière fois l\u0026rsquo;image Docker :\n$ docker build . -t pyrelay:0.0.1\nFROM python:3.11.1-slim WORKDIR /app/ RUN apt-get update RUN apt-get install build-essential -y RUN apt-get install pkg-config -y --no-install-recommends COPY pyproject.toml requirements.txt /app/ RUN pip install -r requirements.txt COPY pyrelay /app/pyrelay ENV PYTHONPATH=/app Et on lance le fichier docker-compose.yml suivant avec la commande $ docker-compose up.\nversion: \u0026#34;2\u0026#34; services: relay: image: pyrelay:0.0.1 ports: - \u0026#34;80:8001\u0026#34; command: - bash - -c - | set -e python pyrelay/relay/server.py Le relai démarre et attend les connexions client:\n$ docker-compose up Recreating pyrelay_relay_1 ... done Attaching to pyrelay_relay_1 relay_1 | alembic.runtime.migration - INFO - Context impl SQLiteImpl. relay_1 | alembic.runtime.migration - INFO - Will assume non-transactional DDL. relay_1 | alembic.runtime.migration - INFO - Running upgrade -\u0026gt; 9ef618136f49, Add kind relay_1 | alembic.runtime.migration - INFO - Running upgrade 9ef618136f49 -\u0026gt; 9b2e8a3aee6b, Add tag extras relay_1 | alembic.runtime.migration - INFO - Running upgrade 9b2e8a3aee6b -\u0026gt; 5c571f8007b7, Add deleted at relay_1 | /app/pyrelay/relay/server.py:33: DeprecationWarning: There is no current event loop relay_1 | event_loop = asyncio.get_event_loop() relay_1 | websockets.server - INFO - server listening on [::]:8001 relay_1 | websockets.server - INFO - server listening on 0.0.0.0:8001 Côté client, on va tenter de se connecter avec l\u0026rsquo;application Nostur 7. On peut configurer les relais via les settings. Le relai à ajouter n\u0026rsquo;est autre que notre machine personnelle.\n$ ip a [...] 42: xxxxx: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff inet 192.168.1.78/24 brd 192.168.1.255 scope global dynamic noprefixroute antoine.localx [...] L\u0026rsquo;URI à ajouter dans Nostur sera donc ws://192.168.1.78, grâce à l\u0026rsquo;exposition du port 80 du conteneur.\nLa connexion est non-chiffrée, et dans ce contexte cela n\u0026rsquo;est pas critique : on teste la faisabilité de l\u0026rsquo;écosystème. Pour la suite, chiffrer les connexions peut s\u0026rsquo;envisager avec un composant dédié tel qu\u0026rsquo;un proxy.\nUne fois le relai renseigné, on observe les requêtes du client:\nrelay_1 | __main__ - INFO - New message from connection conn_uid=366a6ada-bd6b-4cd8-86cf-1120db1dedc5 relay_1 | __main__ - INFO - [\u0026#39;REQ\u0026#39;, \u0026#39;Notifications\u0026#39;, {\u0026#39;#p\u0026#39;: [\u0026#39;KEY\u0026#39;], \u0026#39;since\u0026#39;: 1704289599, \u0026#39;kinds\u0026#39;: [7, 9735, 1, 4, 6, 9802, 30023], \u0026#39;limit\u0026#39;: 500}] relay_1 | pyrelay.relay.dispatcher - INFO - Got request=NostrRequest(subscription_id=\u0026#39;Notifications\u0026#39;, filters=[NostrFilter(ids=None, authors=None, kinds=[], since=1704719625, until=None, limit=500, generic_tags={\u0026#39;p\u0026#39;: [\u0026#39;KEY\u0026#39;]})]) conn_uid=366a6ada-bd6b-4cd8-86cf-1120db1dedc5 relay_1 | __main__ - INFO - New message from connection conn_uid=366a6ada-bd6b-4cd8-86cf-1120db1dedc5 relay_1 | __main__ - INFO - [\u0026#39;REQ\u0026#39;, \u0026#39;Notifications-CATCHUP\u0026#39;, {\u0026#39;#p\u0026#39;: [\u0026#39;KEY\u0026#39;], \u0026#39;since\u0026#39;: 1704289599, \u0026#39;kinds\u0026#39;: [7, 9735, 1, 4, 6, 9802, 30023], \u0026#39;limit\u0026#39;: 500}] relay_1 | pyrelay.relay.dispatcher - INFO - Got request=NostrRequest(subscription_id=\u0026#39;Notifications-CATCHUP\u0026#39;, filters=[NostrFilt On voit ici les requêtes du client. La clé KEY demande les informations de type kinds == [7, 9735, 1, 4, 6, 9802, 30023] depuis la date du 03 janvier 2024. Voici le tableau des kinds Nostr. Notons qu\u0026rsquo;il n\u0026rsquo;y a rien à récupérer, le relai n\u0026rsquo;a pas pu stocker quelconque information au préalable.\n➡ La faisabilité est validée, on peut passer au déploiement !\nArchitecture cible # Les ports utilisés sont les ports standards 80 et 443. Pour la gestion du cycle de vie du conteneur, on utilise le mode swarm 8 de Docker Engine.\nPour la gestion des certificats et les points d\u0026rsquo;entrées, place au reverse proxy. Je propose ici l\u0026rsquo;utilisation d\u0026rsquo; Nginx. Il permettra d\u0026rsquo;également sécuriser les accès. En partageant le fichier de logs avec la machine hôte, un autre processus tel que fail2ban pourra détecter les tentatives d\u0026rsquo;intrusions9.\nTout le flux entre par le reverse proxy Nginx, et est redirigé vers le relai. Nginx gère le chiffrement des communications entre les clients et le swarm. La connexion interne jusqu\u0026rsquo;au relai passe alors en clair.\nLe fichier access.log sera analysé en temps réel et des protections au niveau IP seront déclenchées si nécessaire. Pour protéger le système, il est nécessaire de manipuler le firewall et fail2ban est déjà en cours d\u0026rsquo;utilisation sur la machine hôte. On lui configurera quelques jails supplémentaires, qui ajouteront leurs rules dans la chain DOCKER-USER10.\n➡ Avec cette architecture, on répond aux besoins de confidentialité et de chiffrement, puis aux nécessités de protection face aux attaques extérieures classiques.\nLe reverse proxy # L\u0026rsquo;image utilisée sera Nginx:1.25.3 11. On montera plusieurs fichiers dans le conteneur associé:\nDescription Nom de la ressource Chemin dans le conteneur Commentaires Fichier de configuration Nginx global nginx_system.conf /etc/nginx/nginx.conf Fichier global de Nginx séparé du reste. Fichier de configuration Nginx dédié au relai nginx_user.conf /etc/nginx/conf.d/user.conf Ne contient que la partie dédiée au relai. Dossier des certificats ./certs/ /etc/nginx/certs/ C\u0026rsquo;est dans ce dossier que l\u0026rsquo;on génère manuellement les certificats. Le conteneur les utilisera. Fichier de logs ./log/access.log /var/log/nginx.access.log/ Avoir accès au fichier de logs est utile pour détecter les intrusions. On peut tout à fait joindre les deux fichiers de configuration. Cependant pour la suite, on va devoir les garder séparés.\nRésolution de conteneurs dans le swarm # La configuration d\u0026rsquo;Nginx en tant que proxy se fait de la manière suivante, via le fichier nginx_user.conf :\nserver { [...] location / { proxy_pass http://nostr_pyrelay:8001; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; # for websockets proxy_set_header Connection $connection_upgrade; # for websockets proxy_read_timeout 86400; # for websockets proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } Le nom de domaine interne au swarm nostr_pyrelay, pointant vers le service du même nom, est résolu par le resolver Docker 127.0.0.11. On peut le renseigner dans le fichier de configuration système nginx_system.conf.\nhttp { [...] resolver 127.0.0.11 ipv6=off valid=10s; [...] } Une courte durée d\u0026rsquo;invalidité de résolution permet d\u0026rsquo;être plus réactif sur les mises à jour des IP des conteneurs, notamment lors des redémarrages fréquents. Pratique pour du prototypage.\nGestion des certificats # Dans un premier temps, partons sur la création d\u0026rsquo;un certificat auto-signé. La connexion sera alors chiffrée ; même si l\u0026rsquo;identité du relai ne sera pas vérifiée. On exécute le code suivant.\n#!/bin/bash export DOMAIN_NAME=\u0026#34;antoine.local\u0026#34; export DAYS_VALID=720 openssl \\ req -x509 \\ -nodes \\ -subj \u0026#34;/CN=${DOMAIN_NAME}}\u0026#34; \\ -addext \u0026#34;subjectAltName=DNS:${DOMAIN_NAME}\u0026#34; \\ -days ${DAYS_VALID} \\ -newkey rsa:2048 -keyout ./self-signed.key \\ -out ./self-signed.crt On placera ce certificat ainsi généré dans le dossier ./certs/.\nLancement des services # J\u0026rsquo;utilise un outil homemade pour déployer les services Docker. Le fichier de description ne se conforme donc pas à un docker-compose.yml. Néanmoins, l\u0026rsquo;idée reste la même. Une précision sur la configuration réseau du conteneur Nginx en mode \u0026ldquo;Virtual IP\u0026rdquo; (vip).\n{ \u0026#34;image\u0026#34;: \u0026#34;nginx\u0026#34;, \u0026#34;tag\u0026#34;:\u0026#34;1.25.3\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;nginx_reverse_proxy\u0026#34;, \u0026#34;hostname\u0026#34;:\u0026#34;nginx_reverse_proxy\u0026#34;, \u0026#34;mounts\u0026#34;: [\u0026#34;./nginx/nginx_system.conf:/etc/nginx/nginx.conf/:ro\u0026#34;, \u0026#34;./nginx/nginx_user.conf/:/etc/nginx/conf.d/:ro\u0026#34;, \u0026#34;./nginx/certs:/etc/nginx/certs/:ro\u0026#34;, \u0026#34;./nginx/log/access.log:/var/log/nginx/access.log:rw\u0026#34;], \u0026#34;endpoints\u0026#34;:{ \u0026#34;mode\u0026#34;:\u0026#34;vip\u0026#34;, \u0026#34;ports\u0026#34;: {\u0026#34;443\u0026#34;:443} }, { \u0026#34;image\u0026#34;: \u0026#34;pyrelay\u0026#34;, \u0026#34;tag\u0026#34;:\u0026#34;latest\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;nostr_pyrelay\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;python pyrelay/relay/server.py\u0026#34;, \u0026#34;env\u0026#34;:[\u0026#34;SERVER_NAME=antoine.local\u0026#34;], \u0026#34;hostname\u0026#34;:\u0026#34;nostr_pyrelay\u0026#34;, \u0026#34;endpoints\u0026#34;:{ \u0026#34;mode\u0026#34;:\u0026#34;dnsrr\u0026#34; }, \u0026#34;network\u0026#34;:{ \u0026#34;name\u0026#34;:\u0026#34;network_swarm\u0026#34;, \u0026#34;aliases\u0026#34;:[\u0026#34;nostr_pyrelay\u0026#34;] } } J\u0026rsquo;utilise Portainer12 pour visualiser graphiquement les ressources Docker mises en place. On expose le port 443 du reverse proxy. Le relai quant à lui n\u0026rsquo;est pas visible de l\u0026rsquo;extérieur.\nOn peut se connecter sur le relai et voir l\u0026rsquo;établissement de la connexion sur le serveur\n# sur ma machine personnelle $ websocat wss://antoine.local --insecure Sur le serveur: On retrouve le certificat auto-signé, que l\u0026rsquo;on peut récupérer à partir d\u0026rsquo;une machine tierce. Le champ antoine.local correspond au nom de domaine public de la seule machine hébergeant le swarm.\nNote Le nom de domaine utilisé dans cet article antoine.local n\u0026rsquo;est pas officiel et en réalité j\u0026rsquo;en utilise un autre pour la réalisation de ces étapes. Néanmoins pour la compréhension du déroulé, on supposera que le nom de domaine réel est bien antoine.local.\n$ openssl s_client -showcerts -servername antoine.local antoine.local:443 \u0026lt;/dev/null CONNECTED(00000003) depth=0 CN = antoine.local} verify error:num=18:self-signed certificate verify return:1 depth=0 CN = antoine.local} verify return:1 --- Certificate chain 0 s:CN = antoine.local} i:CN = antoine.local} a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 v:NotBefore: Jan 3 15:18:53 2024 GMT; NotAfter: Dec 26 15:18:53 2025 GMT -----BEGIN CERTIFICATE----- [...] -----END CERTIFICATE----- --- Server certificate subject=CN = antoine.local} issuer=CN = antoine.local} --- No client certificate CA names sent Peer signing digest: SHA256 Peer signature type: RSA-PSS Server Temp Key: X25519, 253 bits --- SSL handshake has read 1380 bytes and written 397 bytes Verification error: self-signed certificate --- New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384 Server public key is 2048 bit Secure Renegotiation IS NOT supported Compression: NONE Expansion: NONE No ALPN negotiated Early data was not sent Verify return code: 18 (self-signed certificate) --- DONE ➡ Cependant la vérification des certificats est obligatoire; la plupart des clients Nostr ne supportent pas les certificats auto-signés. On va devoir faire valider notre certificat par une autorité reconnue.\nLet\u0026rsquo;s encrypt # On se tourne vers le service bien connu Let\u0026rsquo;s Encrypt pour facilement mettre en place une certification. Pour cela, on va utiliser l\u0026rsquo;image Docker nginx-certbot:5.0.013 qui automatise la gestion de certificats14.\nNginx-certbot docker image # On lance le conteneur avec quelques modifications et configurations :\nMontage des bons volumes avec les bons modes (read-only pour certains15). Description Nom de la ressource Chemin dans le conteneur Commentaires Fichier de configuration Nginx global nginx_system.conf /etc/nginx/nginx.conf Fichier global de Nginx séparé du reste. Dossier de configuration Nginx dédié au relai ./user_conf.d/ /etc/nginx/user_conf.d/ Ne contient que les fichiers dédiés au relai. Doit être monté en lecture seule. C\u0026rsquo;est un répertoire spécial pour cette image. Dossier des certificats ./letsencrypt/ /etc/letsencrypt/ C\u0026rsquo;est dans ce dossier que certbot va générer les certificats. Fichier de logs ./log/access.log /var/log/nginx.access.log/ Avoir accès au fichier de logs est utile pour détecter les intrusions. Changement du nom de domaine par défaut dans le fichier nginx_user.conf présent dans le dossier ./user_conf.d/. server { # Listen to port 443 on both IPv4 and IPv6. listen 443 ssl default_server reuseport; listen [::]:443 ssl default_server reuseport; # Domain names this server should respond to. server_name antoine.local; # Load the certificate files. ssl_certificate /etc/letsencrypt/live/antoine.local/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/antoine.local/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/antoine.local/chain.pem; # Load the Diffie-Hellman parameter. ssl_dhparam /etc/letsencrypt/dhparams/dhparam.pem; return 200 \u0026#39;Let\\\u0026#39;s Encrypt certificate successfully installed!\u0026#39;; add_header Content-Type text/plain; } Note Notons l\u0026rsquo;absence de configuration proxy : en effet la première étape avec cette image est de passer la procédure via l\u0026rsquo;utilitaire certbot pour obtenir un certificat vérifié, que l\u0026rsquo;on retrouvera dans le répertoire persistent ./letsencrypt/.\nIl démarre et demande un certificat via l\u0026rsquo;utilitaire certbot.\n[...] %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \u0026gt;\u0026gt;\u0026gt;\u0026gt;\u0026gt; Diffie-Hellman parameter creation done! \u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt; % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2024/01/08 15:50:24 [info] Starting certificate renewal process 2024/01/08 15:50:24 [info] Requesting an ECDSA certificate for \u0026#39;antoine.local\u0026#39; (http-01 through webroot) Saving debug log to /var/log/letsencrypt/letsencrypt.log Account registered. Requesting a certificate for antoine.local Successfully received certificate. Certificate is saved at: /etc/letsencrypt/live/antoine.local/fullchain.pem Key is saved at: /etc/letsencrypt/live/antoine.local/privkey.pem This certificate expires on 2024-04-07. [...] Une fois le certificat obtenu, on relance le conteneur avec la configuration finale du reverse proxy. On édite une dernière fois le fichier présent dans le répertoire ./user_conf.d/, pour y configurer la redirection de connexion, et la gestion des websocket.\nserver { # Listen to port 443 on both IPv4 and IPv6. listen 443 ssl default_server reuseport; listen [::]:443 ssl default_server reuseport; # Domain names this server should respond to. server_name antoine.local; # Load the certificate files. ssl_certificate /etc/letsencrypt/live/antoine.local/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/antoine.local/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/antoine.local/chain.pem; # Load the Diffie-Hellman parameter. ssl_dhparam /etc/letsencrypt/dhparams/dhparam.pem; location / { proxy_pass http://nostr_pyrelay:8001; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; # for websockets proxy_set_header Connection $connection_upgrade; # for websockets proxy_read_timeout 86400; # for websockets proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } Cerbot staging mode # On peut passer la variable STAGING=1 au conteneur nginx-certbot pour obtenir un certificat de test et ainsi valider le fonctionnement de l\u0026rsquo;image. Voici le certificat résultant.\n$ openssl s_client -showcerts -servername antoine.local antoine.local:443 \u0026lt;/dev/null CONNECTED(00000003) depth=2 C = US, O = (STAGING) Internet Security Research Group, CN = (STAGING) Bogus Broccoli X2 verify error:num=20:unable to get local issuer certificate verify return:1 depth=1 C = US, O = (STAGING) Let\u0026#39;s Encrypt, CN = (STAGING) Ersatz Edamame E1 verify return:1 depth=0 CN = antoine.local verify return:1 --- Certificate chain 0 s:CN = antoine.local i:C = US, O = (STAGING) Let\u0026#39;s Encrypt, CN = (STAGING) Ersatz Edamame E1 a:PKEY: id-ecPublicKey, 256 (bit); sigalg: ecdsa-with-SHA384 v:NotBefore: Jan 10 07:21:37 2024 GMT; NotAfter: Apr 9 07:21:36 2024 GMT -----BEGIN CERTIFICATE----- [...] Lancement final # On lance le tout, et après quelques secondes, le relai est accessible !\nOn peut le configurer dans le client mobile Nostur.\nUne requête curl montre bien la disponibilité du relai.\n$ curl https://antoine.local Failed to open a WebSocket connection: invalid Connection header: close. You cannot access a WebSocket server directly with a browser. You need a WebSocket client. Le certificat est bien vérifié :\n$ openssl s_client -showcerts -servername antoine.local antoine.local:443 \u0026lt;/dev/null CONNECTED(00000003) depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1 verify return:1 depth=1 C = US, O = Let\u0026#39;s Encrypt, CN = R3 verify return:1 depth=0 CN = antoine.local verify return:1 --- Certificate chain 0 s:CN = antoine.local i:C = US, O = Let\u0026#39;s Encrypt, CN = R3 a:PKEY: id-ecPublicKey, 256 (bit); sigalg: RSA-SHA256 v:NotBefore: Jan 10 07:37:07 2024 GMT; NotAfter: Apr 9 07:37:06 2024 GMT -----BEGIN CERTIFICATE----- [...] -----END CERTIFICATE----- --- Server certificate subject=CN = antoine.local issuer=C = US, O = Let\u0026#39;s Encrypt, CN = R3 --- No client certificate CA names sent Peer signing digest: SHA256 Peer signature type: ECDSA Server Temp Key: X25519, 253 bits --- SSL handshake has read 4118 bytes and written 391 bytes Verification: OK En configurant un client tel que Gossip16 sur notre machine personnelle, on peut débugguer les paquets réseaux avec Wireshark17.\nOn peut voir l\u0026rsquo;établissement de la connexion chiffrée avec le relai:\nid Timestamp SRC DST Protocol Details 60 11.442065691 192.168.1.78 antoine.local TLSv1.3 Client Hello 61 11.460031100 antoine.local 192.168.1.78 TCP 443 → 58538 [ACK] Seq=1 Ack=241 Win=64128 Len=0 TSval=4147355995 TSecr=3356805992 62 11.460031506 antoine.local 192.168.1.78 TLSv1.3 Server Hello, Change Cipher Spec, Application Data ➡ La mise en place du relai public est terminée ! On peut s\u0026rsquo;en servir pour communiquer au sein du réseau Nostr. Ce relai est accessible et n\u0026rsquo;importe quel client Nostr peut l\u0026rsquo;inclure dans sa liste de relais.\nProtection des intrusions # Après quelques dizaines de minutes, on peut déjà voir des requêtes non souhaitées sur les ports 80 et 443, visibles dans les logs du conteneur Nginx:\n$ tail -f ./log/access.log -n 100 [...] 10.10.0.4 - - [Jan/2024:09:21:41 +0000] \u0026#34;GET / HTTP/1.1\u0026#34; 101 4 \u0026#34;-\u0026#34; \u0026#34;-\u0026#34; 10.10.0.4 - - [Jan/2024:09:21:41 +0000] 101 \u0026#34;GET / HTTP/1.1\u0026#34; 4 \u0026#34;-\u0026#34; \u0026#34;-\u0026#34; \u0026#34;-\u0026#34; 10.10.0.4 - - [Jan/2024:09:21:46 +0000] \u0026#34;GET / HTTP/1.1\u0026#34; 426 165 \u0026#34;-\u0026#34; \u0026#34;curl/7.81.0\u0026#34; 10.10.0.4 - - [Jan/2024:09:21:46 +0000] 426 \u0026#34;GET / HTTP/1.1\u0026#34; 165 \u0026#34;-\u0026#34; \u0026#34;curl/7.81.0\u0026#34; \u0026#34;-\u0026#34; 10.10.0.4 - - [Jan/2024:09:31:14 +0000] \u0026#34;\\x03\\x00\\x00/*\\xE0\\x00\\x00\\x00\\x00\\x00Cookie: mstshash=Administr\u0026#34; 400 150 \u0026#34;-\u0026#34; \u0026#34;-\u0026#34; 10.10.0.4 - - [Jan/2024:09:31:14 +0000] 400 \u0026#34;\\x03\\x00\\x00/*\\xE0\\x00\\x00\\x00\\x00\\x00Cookie: mstshash=Administr\u0026#34; 150 \u0026#34;-\u0026#34; \u0026#34;-\u0026#34; \u0026#34;-\u0026#34; 10.10.0.4 - - [Jan/2024:09:45:32 +0000] \u0026#34;GET / HTTP/1.1\u0026#34; 301 162 \u0026#34;-\u0026#34; \u0026#34;Hello World\u0026#34; 10.10.0.4 - - [Jan/2024:09:45:32 +0000] 301 \u0026#34;GET / HTTP/1.1\u0026#34; 162 \u0026#34;-\u0026#34; \u0026#34;Hello World\u0026#34; \u0026#34;-\u0026#34; 10.10.0.4 - - [Jan/2024:09:45:53 +0000] \u0026#34;H\\x00\\x00\\x00tj\\xA8\\x9E#D\\x98+\\xCA\\xF0\\xA7\\xBBl\\xC5\\x19\\xD7\\x8D\\xB6\\x18\\xEDJ\\x1En\\xC1\\xF9xu[l\\xF0E\\x1D-j\\xEC\\xD4xL\\xC9r\\xC9\\x15\\x10u\\xE0%\\x86Rtg\\x05fv\\x86]%\\xCC\\x80\\x0C\\xE8\\xCF\\xAE\\x00\\xB5\\xC0f\\xC8\\x8DD\\xC5\\x09\\xF4\u0026#34; 400 150 \u0026#34;-\u0026#34; \u0026#34;-\u0026#34; 10.10.0.4 - - [Jan/2024:09:45:53 +0000] 400 \u0026#34;H\\x00\\x00\\x00tj\\xA8\\x9E#D\\x98+\\xCA\\xF0\\xA7\\xBBl\\xC5\\x19\\xD7\\x8D\\xB6\\x18\\xEDJ\\x1En\\xC1\\xF9xu[l\\xF0E\\x1D-j\\xEC\\xD4xL\\xC9r\\xC9\\x15\\x10u\\xE0%\\x86Rtg\\x05fv\\x86]%\\xCC\\x80\\x0C\\xE8\\xCF\\xAE\\x00\\xB5\\xC0f\\xC8\\x8DD\\xC5\\x09\\xF4\u0026#34; 150 \u0026#34;-\u0026#34; \u0026#34;-\u0026#34; \u0026#34;-\u0026#34; 10.10.0.4 - - [Jan/2024:09:46:18 +0000] \u0026#34;GET / HTTP/1.1\u0026#34; 426 165 \u0026#34;-\u0026#34; \u0026#34;-\u0026#34; On peut à ce moment détecter quelles requêtes sont issues d\u0026rsquo;outils de scanning et bannir au niveau firewall les IPs correspondantes. Pour autant, on note que toutes les IPs sont identiques et viennent du réseau ingress de Docker swarm.\nNginx permet de récupérer l\u0026rsquo;adresse IP source du client Nostr avec la directive proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;. Cependant cela ne fonctionne pas avec Docker swarm actuellement, et plusieurs tickets ont été ouverts à ce sujet 18 19 20.\nVoici la version de Docker Engine avec laquelle ce problème s\u0026rsquo;observe.\n$ docker version Server: Engine: Version: 24.0.7 ➡ Une solution de secours serait alors de mettre en place le reverse proxy sur la machine hôte et gérer les certificats à cet endroit, puis de rediriger les flux 80 et 443 vers le reverse proxy du swarm sur des ports non-standards. De toute façon, si cette machine devait héberger plusieurs applications dialoguant en HTTP/HTTPS, un reverse proxy au niveau du host serait nécessaire.\nConclusion # À travers cet article, on a mis en place un relai Nostr public, avec des fonctionnalités de chiffrement et de gestion de cycle de vie. La mise en place de protection impliquant fail2ban reste à finaliser suite aux investigations techniques sur Docker swarm.\nMême si la mise en place et la maintenance opérationnelle du relai reste légère (pas de base de données à gérer par exemple), un développement logiciel de fond est nécessaire pour profiter des fonctionnalités grandissantes que les NIPs proposent (transfert d\u0026rsquo;images, de vidéo, groupes de discussions,\u0026hellip;).\nDe plus, il serait intéressant de qualifier les communications via les relais Nostr, notamment de déterminer les temps d\u0026rsquo;attentes liés aux récupérations des messages.\nhttps://github.com/nostr-protocol/nostr\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://medium.com/notrustverify/nostr-x-nym-b1602320ecf0\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/nostr-protocol/nips\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/aljazceru/awesome-nostr#relays\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/johnny423/pyrelay\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/antlas0/pyrelay\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://nostur.com/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://docs.docker.com/engine/swarm/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://web.archive.org/web/20230325103248/https://www.linuxcapable.com/nginx-custom-fail2ban-filters-and-jails-10-examples/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://docs.docker.com/network/packet-filtering-firewalls/#add-iptables-policies-before-dockers-rules\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://hub.docker.com/_/nginx/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.portainer.io/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://hub.docker.com/r/jonasal/nginx-certbot\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/JonasAlfredsson/docker-nginx-certbot\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/JonasAlfredsson/docker-nginx-certbot/blob/master/docs/good_to_know.md#good-to-know\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/mikedilger/gossip\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.wireshark.org/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/moby/moby/issues/25526\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/docker/roadmap/issues/157\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/imbolc/axum-client-ip/issues/22\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"6 janvier 2024","externalUrl":null,"permalink":"/articles/fr/nostr_experimentations/","section":"Articles","summary":"Mise en place d’un relai Nostr.","title":"Expérimentation du réseau Nostr","type":"articles"},{"content":" Introduction # Vivant parmi les animaux dans une campagne somme toute classique, il arrive quelquefois que des visiteurs imprévus tournent autour de mon domicile. La nuit ne les rend pas plus polis, et malgré un réveil inopportun, le temps de s\u0026rsquo;éjecter hors de son lit reste apparemment trop long pour espérer les voir. Est donc venue l\u0026rsquo;idée d\u0026rsquo;inlassablement poster quelqu\u0026rsquo;un sur le balcon, de nuit. Pour des raisons encore obscures, le choix de remplacer cette personne par un détecteur de mouvement fût rapidement adoptée. Le déclenchement de la photo ne doit se faire que lorsque du mouvement est détecté, et que c\u0026rsquo;est bien un animal qui passe. Bien sûr, se baser sur une image de nuit nécessite une caméra infrarouge.\nEn alliant une détection de mouvement basique par seuil (basée sur l\u0026rsquo;intensité des pixels) et une reconnaissance de formes via le projet You Only Look Once1, un petit logiciel pourra voir le jour. La configuration sera bien importante pour apprécier la quantité de données générées : temps d\u0026rsquo;un évènement, génération en vidéo pour n\u0026rsquo;avoir qu\u0026rsquo;un seul fichier,\u0026hellip;\nVoici donc la présentation de ce petit logiciel que j\u0026rsquo;ai développé, avec quelques remarques, notamment sur son écosystème global.\nFonctionnement # Ce programme développé en Python détecte de l\u0026rsquo;activité vidéo et analyse son contenu selon la configuration pour en reconnaitre des formes. Il est également capable de détecter de l\u0026rsquo;activité audio – sans reconnaissance cette fois.\nIl implémente le module argparse pour proposer des options de lancement en ligne de commande. Deux subparsers sont mis en place pour dissocier les options inhérentes au traitement d\u0026rsquo;image et au traitement audio que le programme propose.\n$ python3 -m ir-visualiser -h usage: __main__.py [-h] [-e EXPORTER] [-E EVENT_DURATION] [-o OUTPUT_DIR] {video,audio} ... IR visualiser positional arguments: {video,audio} Input kind (video or audio) options: -h, --help show this help message and exit -e EXPORTER, --exporter EXPORTER Where to export detected images (signal, local), default local. -E EVENT_DURATION, --event-duration EVENT_DURATION Event duration in seconds, default 60 -o OUTPUT_DIR, --output-dir OUTPUT_DIR Specify an output directory, default ./output. On appelle le programme sous forme de module Python. Ceci est rendu possible par la présence du fichier __main__.py dans l\u0026rsquo;arborescence du projet. Cela signifie que chaque développeur a la possibilité de redéfinir sa propre interaction avec la bibliothèque, en faisant un simple import du package (voir section Packaging). L\u0026rsquo;interaction avec la bibliothèque se fera alors par la classe ir-visualiser.worker et un dict de configuration à lui donner.\nLe programme est divisé en plusieurs parties qui vivent chacunes dans un thread dédié. Chaque module implémente une fonctionnalité unitaire :\nLecture du flux entrant (d\u0026rsquo;un périphérique ou d\u0026rsquo;un flux HTTP), Détection de mouvement et reconnaissance des classes d\u0026rsquo;intérêt (on aurait pu scinder ces deux étapes), Traitement après reconnaissance : envoi vers une cible réseau (utilisant Signal ou Telegram) ou vers un dossier local. C\u0026rsquo;est ce cas d\u0026rsquo;usage que je décris dans cet article. Chaque queue permet une communication thread-safe entre les threads. Chaque queue d\u0026rsquo;envoi de données (output_queue) est également la queue d\u0026rsquo;entrée du module suivant (input_queue).\nLecture de flux # La lecture de flux repose sur la bibliothèque OpenCV. La classe dédiée à la lecture du flux vidéo s\u0026rsquo;instancie avec une URI pointant vers un périphérique local ou un flux distant.\nclass ImageReader(InterfaceReader): \u0026#34;\u0026#34;\u0026#34; Class dealing with input flow reading \u0026#34;\u0026#34;\u0026#34; def __init__(self, video_capture_uri:str=\u0026#34;/dev/video0\u0026#34;, video_frequency:Optional[int]=None): super().__init__() self._capture = None [...] def setup(self) -\u0026gt; bool: \u0026#34;\u0026#34;\u0026#34; Setup the hardware or remote endpoint \u0026#34;\u0026#34;\u0026#34; try: self._capture = cv2.VideoCapture(self._video_capture_uri) except Exception as e: logger.error(str(e)) return False [...] L\u0026rsquo;envoi vers le module de reconnaissance d\u0026rsquo;image se fait par l\u0026rsquo;ajout de l\u0026rsquo;image en cours de lecture dans sa queue d\u0026rsquo;envoi (output_queue).\ndef run(self) -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34; Continuously read the incoming video stream and send frames to other thread \u0026#34;\u0026#34;\u0026#34; while self._running: if self._read_mode == \u0026#34;fast\u0026#34;: [...] self._publish(DetectedData(image=frame)) [...] L\u0026rsquo;objet DetectedData est une dataclass qui stocke les données d\u0026rsquo;entrées. À ce stade, son utilisation symbolise une première étape de structuration de données tout au long du process. En effet, le Python reste assez permissif sur l\u0026rsquo;adjonction d\u0026rsquo;attributs aux objets, et on pourra même rendre read-only une instance de cette classe en lui appliquant le decorator @dataclass(frozen=True).\n@dataclass class DetectedData: image: Optional[bytes]=None video: Optional[byterray]=None audio: Optional[bytes]=None metrics: Optional[dict]=None Reconnaissance de classes d\u0026rsquo;intérêt # Les classes d\u0026rsquo;intérêt sont définies par le paramètre en ligne de commande --classes-of-interest. La classe par défaut est person – une personne humaine :\nvideo_parser.add_argument(\u0026#34;-C\u0026#34;, \u0026#34;--classes-of-interest\u0026#34;, help=\u0026#34;Specify a class of interest to detect with CNN, default person.\u0026#34;, default=[\u0026#34;person\u0026#34;], action=\u0026#34;store\u0026#34;, nargs=\u0026#34;+\u0026#34;) Ces classes d\u0026rsquo;intérêt font partie d\u0026rsquo;une liste prédéfinie de classes sur lesquelles le modèle YOLO a été entrainé. Cette liste est fixe et contient environ 80 éléments.\nLe modèle mathématique YOLO est chargé au démarrage, via la méthode setup.\ndef setup(self) -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34; Setup the modalities of image recognition \u0026#34;\u0026#34;\u0026#34; [...] # derive the paths to the YOLO weights and model configuration weightsPath = os.path.sep.join([yolo_path, \u0026#34;yolov3.weights\u0026#34;]) configPath = os.path.sep.join([yolo_path, \u0026#34;yolov3.cfg\u0026#34;]) logger.info(\u0026#34;Loading YOLO from disk...\u0026#34;) self._net = cv2.dnn.readNetFromDarknet(configPath, weightsPath) [...] Le module de reconnaissance d\u0026rsquo;image bloque alors son exécution sur sa queue de lecture (input_queue) puis le modèle est appelé pour la prédiction.\nclass ImageRecognition(InterfaceDetection): \u0026#34;\u0026#34;\u0026#34; Class dealing with image recognition using deep learning When specific target is detected, send the image \u0026#34;\u0026#34;\u0026#34; def __init__(self, capture:str, movement_threshold:int): super().__init__() [...] def run(self) -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34; Runs the image recognition based on the YOLO weigths \u0026#34;\u0026#34;\u0026#34; [...] while self._running: boxes = [] current_date = datetime.now() frame = self._input_queue.get()[\u0026#34;image\u0026#34;] # FIXME : unsafe frame = cv2.resize(frame, (640, 360)) blob = cv2.dnn.blobFromImage( frame, 1 / 255.0, (416, 416), swapRB=True, crop=False ) self._net.setInput(blob) start = time.time() layerOutputs = self._net.forward(ln) end = time.time() [...] Si du mouvement est détecté, et qu\u0026rsquo;une classe d\u0026rsquo;intérêt est reconnue dans le contenu de l\u0026rsquo;image, alors l\u0026rsquo;image est envoyée au module d\u0026rsquo;exportation – de stockage. On pourrra choisir d\u0026rsquo;incorporer dans l\u0026rsquo;image résultat le tracé des zones détectées.\nSauvegarde des résultats # Sur le même principe, ce thread déclenche une action sur l \u0026lsquo;arrivée de données dans sa queue de lecture. Si le module récupère une image seule dans sa queue de lecture, alors il écrira sur le disque cette image, en png. Dans le cas où il récupère une vidéo, il la sauvegardera alors en mp4.\nclass WriterModule(threading.Thread): def __init__(self): super().__init__() [...] def _write_image(self, frame) -\u0026gt; bool: \u0026#34;\u0026#34;\u0026#34; Write image to disk \u0026#34;\u0026#34;\u0026#34; res = True image = None output_dir = self._config.get_param(\u0026#34;general\u0026#34;, \u0026#34;output_dir\u0026#34;) suffix = datetime.now().strftime(\u0026#34;%Y_%m_%d__%H_%M_%S\u0026#34;) filepath = os.path.join(output_dir, f\u0026#34;image_{suffix}.png\u0026#34;) try: image = open(filepath, \u0026#39;wb\u0026#39;) except Exception as e: logger.warning(str(e)) res = False [...] def run(self) -\u0026gt; None: while self._running: to_write = self._input_queue.get() self._process_queue_data(to_write) Assemblage des modules # La connexion des modules entre eux nécessite leur instanciation préalable et la création de deux queues qui vont être configurées en entrée et sortie.\nC\u0026rsquo;est la classe Worker qui organise cette structure. Cette classe est la classe centrale, véritable colonne vertébrale du programme. Toutes les instanciations annexes des différents composants se font dans cette classe, et le lancement de chaque thread s\u0026rsquo;y produit.\nclass Worker(metaclass=Singleton): def __init__(self): signal.signal(signal.SIGINT, signal_custom_teardown) self._config = Configuration() [...] self._messaging_queue = Queue(maxsize=100) # création de la queue de lecture du module d\u0026#39;exportation [...] def setup(self) -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34; Setup the objects and queues \u0026#34;\u0026#34;\u0026#34; [...] # connexion entre le reader de flux et le traitement d\u0026#39;image q = Queue(maxsize=100) # création de la queue reader = ImageReader(video_captures[stream_number], video_frequency) reader.set_output_queue(q) # le reader de flux configure cette queue comme sa sortie reader.setup() recognizer = ImageRecognition(video_captures[stream_number], int(thresholds[stream_number])) recognizer.set_input_queue(q) # le module de traitement d\u0026#39;image configure cette queue comme sa source, son entrée de données recognizer.set_output_queue(self._messaging_queue) # configuration de la queue de sortie vers le module d\u0026#39;exportation recognizer.setup() [...] Maintenant que le logiciel a été conçu et développé, attardons-nous plus en détails sur ce qui arrive ensuite : son installation, sa distribution et son lancement.\nChaine de production logicielle # Produire un logiciel signifie le rendre disponible à des tiers. Cela suit plusieurs étapes, dont la gestion de package, l\u0026rsquo;application de tests et vérifications, la génération de documentation – ou manuel utilisateur, la mise à disposition voire même le déploiement. L\u0026rsquo;objectif est d\u0026rsquo;automatiser le plus possible ces étapes qui, une fois opérées séquentiellement, vont fournir un logiciel fiable, documenté et utilisable.\nGestion de package # Packager son logiciel permet de le distribuer facilement. C\u0026rsquo;est l\u0026rsquo;objectif ; réduire l\u0026rsquo;effort à produire pour tester, lancer et utiliser ce logiciel. On parlera de build – de construction de package.\nEn langage Python, on peut utiliser setuptools 2 pour définir un package et ses attributs. Les paramètres à donner sont nombreux ; dont les principaux sont :\nle nom du package : name, le chemin local du package : packages, le numéro de version : version, l\u0026rsquo;auteur : author et author_email, la liste de dépendances : install_requires, les éventuels fichiers statiques à incorporer dans le package : include_package_data et package_data, from setuptools import setup import git def get_latest_tag() -\u0026gt; str: repo = git.Repo(\u0026#34;.\u0026#34;) tags = sorted(repo.tags, key=lambda t: t.commit.committed_datetime) latest_tag = \u0026#34;0.0.0\u0026#34; try: latest_tag = tags[-1] except Exception as e: pass finally: return latest_tag with open(\u0026#34;README.md\u0026#34;, \u0026#34;r\u0026#34;) as readme_fp: readme_fp = readme_fp.read() required = [] with open(\u0026#39;requirements.txt\u0026#39;) as f: required = f.read().splitlines() setup( name=\u0026#34;ir-visualiser\u0026#34;, author=\u0026#34;antlas\u0026#34;, author_email=\u0026#34;\u0026#34;, version=get_latest_tag(), description=\u0026#34;Image detection\u0026#34;, long_description=readme_fp, include_package_data=True, packages=[\u0026#34;ir-visualiser\u0026#34;], package_data={\u0026#34;ir-visualiser\u0026#34; : [\u0026#34;yolo-coco/*\u0026#34;]}, python_requires=\u0026#34;\u0026gt;=3.6\u0026#34;, classifiers=[ \u0026#34;Programming Language :: Python :: 3.6\u0026#34;, \u0026#34;Programming Language :: Python :: 3.7\u0026#34;, \u0026#34;Programming Language :: Python :: 3 :: Only\u0026#34;, \u0026#34;License :: OSI Approved :: MIT License\u0026#34;, \u0026#34;Operating System :: OS Independent\u0026#34;, \u0026#34;Intended Audience :: Education\u0026#34; ], install_requires=required, ) Il est nécessaire dans notre cas d\u0026rsquo;embarquer le modèle YOLO dans le package lui-même. Il sera statiquement chargé lors des prédictions de reconnaissance d\u0026rsquo;image.\nIl faut porter attention à l\u0026rsquo;embarquement du modèle dans le package. En effet, cela signifie changer le numéro de version du package à chaque changement du modèle, qui est externe au développement. Pour autant, les cycles de vie des deux éléments sont différents et un changement de version du package devrait répondre à un changement dans son code source. Dans le cadre du MLOps 3, il serait plus approprié de charger à distance le modèle mathématique, voire dans les situations les plus évoluées, d\u0026rsquo;envoyer les données d\u0026rsquo;entrée de prédiction aux APIs du serveur de stockage du modèle, et d\u0026rsquo;en récupérer en réponse les résultats de prédiction.\nLe numéro de version est fixé à 0.0.1, suivant la convention major.minor.patch issue du semantic versioning 4. Le numéro patch est incrémenté lorsque des bugs sont résolus, le numéro minor est incrémenté lorsqu\u0026rsquo;une ou plusieurs fonctionnalités significatives sont implémentées, et le numéro major est incrémenté lorsqu\u0026rsquo;il y a un changement important, non rétro-compatible par exemple.\nIci, on utilise le module git pour récupérer le numéro de version. Dans l\u0026rsquo;optique d\u0026rsquo;avoir une production automatisée, on peut déclencher le packaging automatiquement et ainsi tester le logiciel annoté du bon numéro de version.\nGestion des dépendances # Elle est obligatoire pour proposer une installation automatisée, qui installera toutes les nécessités pour que le package fonctionne. La gestion des dépendances du logiciel peut être assurée par l\u0026rsquo;utilitaire pipreqs.\n$ pipreqs ./ir-visualiser\nLa liste des dépendances est un bon indicateur structurel ; notamment si elles sont de natures très différentes. Faire par exemple coexister au sein d\u0026rsquo;un même logiciel ou bibliothèque des dépendances de data science, web et d\u0026rsquo;opérations bas-niveau témoigne d\u0026rsquo;un logiciel aux semblants complexes.\nD\u0026rsquo;un point de vue architectural, il est préférable de développer un logiciel dont le domaine d\u0026rsquo;application est unitaire. Dans notre cas, on aurait pu mettre en place un collecteur de flux, qui met à disposition les flux entrants et vers lequel le module de reconnaissance d\u0026rsquo;image se connecterait. De ce fait, si la nature de la récupération des flux change, elle n\u0026rsquo;impacte pas toute la chaine comme c\u0026rsquo;est le cas ici. Cependant la latence serait plus importante, nécessitant un transfert réseau.\nDocumentation # On ne répètera jamais assez que la documentation reste un atout central pour la distribution et le partage de logiciel. À notre échelle ici, on peut mettre en place la même procédure de génération évoquée dans ce précédent article.\nDocker # Une manière commune de proposer un environnement complet prêt à l\u0026rsquo;emploi est de compter sur la très répandue plateforme Docker et de mettre à disposition un Dockerfile et un docker-compose.yml. Ces deux fichiers décrivent respectivement le contenu du logiciel déployé (le programme initial et ce qu\u0026rsquo;il lui faut avec) et la manière de le lancer. Les utilisateurs n\u0026rsquo;auront plus qu\u0026rsquo;à intégrer ces fichiers-là et :\ncréer l\u0026rsquo;image Docker issue du Dockerfile lancer cette image dans un container Docker comme le propose le fichier docker-compose.yml. Ici, on se base sur une image Debian. On définit des variables d\u0026rsquo;environnement par défaut, qui seront passées au programme en argument de ligne de commande. De ce fait, on pourra donner au container Docker une configuration détaillée en lui ajustant ses variables d\u0026rsquo;environnement.\nFROM python:3.9-bullseye ENV EVENT_DURATION 60 ENV OUTPUT_DIR /var/lib/ir-visualiser ENV VIDEO_CLASSES_OF_INTEREST \u0026#34;person bird\u0026#34; ENV VIDEO_CAPTURE \u0026#34;/dev/video0\u0026#34; ENV VIDEO_FREQUENCY 1 ENV VIDEO_OBJECT_BOXES 1 ENV VIDEO_TO_MOVIE 1 [...] CMD python -m ir-visualiser --event-duration ${EVENT_DURATION} --output-dir ${OUTPUT_DIR} video --video-cap ${VIDEO_CAPTURE} --video-frequency ${VIDEO_FREQUENCY} --classes-of-interest ${VIDEO_CLASSES_OF_INTEREST} ${VIDEO_OBJECT_BOXES:+--recognition-boxes} ${VIDEO_TO_MOVIE:+--to-video} Et c\u0026rsquo;est ce que fait le fichier docker-compose.yml : la section environment permet d\u0026rsquo;ajuster les variables d\u0026rsquo;environnement du container, et en coulisse les paramètres du programme. L\u0026rsquo;interaction est ici simplifiée : on ne se soucie que du container, sans pour autant plonger dans les détails du logiciel en dessous.\nversion: \u0026#39;2\u0026#39; services: ir-visualiser: image: ir-visualiser:latest container_name: ir-visualiser restart: always cap_add: - SYS_ADMIN devices: - \u0026#34;/dev/video0:/dev/video0\u0026#34; volumes: - \u0026#34;./data/:/var/lib/ir-visualiser/:rw\u0026#34; environment: - VIDEO_CAPTURE=/dev/video0 - VIDEO_FREQUENCY=1 - VIDEO_TO_MOVIE= - VIDEO_CLASSES_OF_INTEREST=cat bird En lançant la commande bash $ docker-compose up dans le répertoire du docker-compose.yml, on pourra lancer le programme de reconnaissance d\u0026rsquo;image dans un container Docker.\nOn devra aussi versionner les fichiers Dockerfile et docker-compose.yml. Cela signifie que le projet de développement devra également inscrire dans son cycle de vie ces deux produits qui peuvent parfois évoluer sans changement de code source. Exemple : mettre à jour la version d\u0026rsquo;une dépendance à installer dans le Dockerfile ne change pas le code source du package Python mais doit être pris en compte dans le projet et peut couvrir une correction de bug.\nTesting # Les tests sont une partie importante de la chaine de production logicielle. Ils couvrent de manière non-exhaustive plusieurs aspects :\nla fiabilité du code source (tests unitaires) la fiabilité de l\u0026rsquo;exécution du programme (tests d\u0026rsquo;intégration, tests end-to-end) la recherche de vulnérabilités les rapports de tests. Sans que cela s\u0026rsquo;applique complètement à notre cas, on pourra noter la différence entre les tests unitaires et ceux d\u0026rsquo;intégration. Les tests unitaires se concentrent sur les parties atomiques du code, tandis que les tests d\u0026rsquo;intégration se concentrent sur la compatibilité d\u0026rsquo;intégration du programme avec d\u0026rsquo;autres entités : autres programmes, système d\u0026rsquo;exploitation,\u0026hellip; On teste l\u0026rsquo;ensemble du produit logiciel, qui peut contenir plusieurs composants. Les tests end-to-end quant à eux vérifient le bon fonctionnement du produit en simulant une utilisation client.\nOn peut se tourner vers Python pour certains types de tests, pytest pour les tests unitaires, ou safety pour la détection de vulnérabilités. Son rôle est de scanner l\u0026rsquo;environnement Python et d\u0026rsquo;en trouver des failles de sécurité.\n$ python -m pip install safety\nVoici un extrait de la sortie standard :\n$ safety check +==================================================================================+ /$$$$$$ /$$ /$$__ $$ | $$ /$$$$$$$ /$$$$$$ | $$ \\__//$$$$$$ /$$$$$$ /$$ /$$ /$$_____/ |____ $$| $$$$ /$$__ $$|_ $$_/ | $$ | $$ | $$$$$$ /$$$$$$$| $$_/ | $$$$$$$$ | $$ | $$ | $$ \\____ $$ /$$__ $$| $$ | $$_____/ | $$ /$$| $$ | $$ /$$$$$$$/| $$$$$$$| $$ | $$$$$$$ | $$$$/| $$$$$$$ |_______/ \\_______/|__/ \\_______/ \\___/ \\____ $$ /$$ | $$ | $$$$$$/ by pyup.io \\______/ +==================================================================================+ REPORT Safety is using PyUp\u0026#39;s free open-source vulnerability database. This data is 30 days old and limited. For real-time enhanced vulnerability data, fix recommendations, severity reporting, cybersecurity support, team and project policy management and more sign up at https://pyup.io or email sales@pyup.io Safety v2.3.5 is scanning for Vulnerabilities... Scanning dependencies in your environment: [...] Using non-commercial database Found and scanned 76 packages Timestamp 2023-10-09 11:16:43 6 vulnerabilities found 0 vulnerabilities ignored +==================================================================================+ VULNERABILITIES FOUND +==================================================================================+ -\u0026gt; Vulnerability found in certifi version 2022.12.7 Vulnerability ID: 59956 Affected spec: \u0026gt;=2015.04.28,\u0026lt;2023.07.22 ADVISORY: Certifi 2023.07.22 includes a fix for CVE-2023-37920: Certifi prior to version 2023.07.22 recognizes \u0026#34;e-Tugra\u0026#34; root certificates. e-Tugra\u0026#39;s... CVE-2023-37920 For more information, please visit https://pyup.io/v/59956/f17 [...] À la suite de tous les tests, on pourra choisir lesquels sont critiques et demandent toujours un résultat positif, et ceux que l\u0026rsquo;on peut ignorer ponctuellement selon le contexte.\nAutomatisation # On sort du périmètre du logiciel initial pour se tourner vers la plateforme de production logicielle. On peut concevoir notre chaine de production, en commençant par déclencher des procédures relatives aux évolutions du code source. Par exemple, si le code source est tagué, on peut lancer toute la chaine de production logicielle.\nEn utilisant Gitlab, on peut créer un fichier .gitlab-ci.yml qui contiendra toutes les étapes de production : testing, reporting, etc. Voici un exemple reprenant les tests de sécurité décrits ici. Pour plus de clarté, on pourrait séparer en deux stages le build et le test.\nbuild_and_test: image: python:3.9-bullseye stage: build_and_test interruptible: true script: - apt-get update \u0026amp;\u0026amp; apt-get upgrade -y - apt-get install -y build-essential gcc - apt-get install -y portaudio19-dev python3-pyaudio python3-opencv - python -m pip install safety - python setup.py install --user - safety check || true On commence par installer les prérequis, puis le logiciel, puis on scanne ses dépendances. Notons la fin de ligne safety check || true pour que même si des vulnérabilités sont trouvées, la commande retourne 0 (succès) pour ne pas arrêter le stage. C\u0026rsquo;est pour cela qu\u0026rsquo;un stage dédié avec l\u0026rsquo;option allow_failure: true peut être préférée : on voit que le stage ne réussit pas, mais le pipeline ne s\u0026rsquo;arrête pas pour autant.\nCette étape de test décrite ici est invoquée si l\u0026rsquo;une des règles suivantes correspond :\nrules: - if: \u0026#39;$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH\u0026#39; - if: \u0026#39;$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\u0026#39; - if: \u0026#39;$CI_COMMIT_TAG\u0026#39; - if: \u0026#39;$CI_PIPELINE_SOURCE == \u0026#34;web\u0026#34;\u0026#39; si on merge une branche sur la master si on commit une branche sur la master si on tag une branche si on lance le pipeline par le portail web. On peut donc commencer par automatiser son travail et se diriger vers une gestion beaucoup plus rapide.\nConclusion # Après ce tour d\u0026rsquo;horizon, et à la suite des remarques, de nombreuses pistes d\u0026rsquo;évolutions sont à suivre, autant pour le développement que pour la gestion du \u0026ldquo;produit\u0026rdquo;. Et bien d\u0026rsquo;autres sujets d\u0026rsquo;améliorations sont à explorer !\nEn tous cas, il ne reste plus qu\u0026rsquo;à disposer la caméra au bon endroit et lancer le container Docker ! Et pour le code, c\u0026rsquo;est par ici !\nhttps://opencv-tutorial.readthedocs.io/en/latest/yolo/yolo.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://packaging.python.org/en/latest/guides/tool-recommendations/#packaging-tool-recommendations\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://en.wikipedia.org/wiki/MLOps\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://en.wikipedia.org/wiki/Software_versioning\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"8 octobre 2023","externalUrl":null,"permalink":"/articles/fr/image_processing/","section":"Articles","summary":"Description d’un programme de détection d’image, de son packaging Python, et des problématiques liées.","title":"Reconnaissance d'image","type":"articles"},{"content":" Introduction # Lors de choix architecturaux, prendre en compte les cycle de vie des logiciels impactés oriente toujours les choix. Est-il par exemple justifié de migrer vers une version de logiciel dont la fin de support est proche ? Combien de temps avons-nous avant de devoir se mettre à jour, et quel en sera l\u0026rsquo;impact ? Ce questionnement reste central dans l\u0026rsquo;établissement de l\u0026rsquo;effort à fournir pour répondre aux besoins finaux.\nDans cette situation, je me réfère au site \u0026ldquo;endoflife.date\u0026rdquo; 1 qui centralise les cycles de vie de nombreux logiciels connus.\n➡ Et à des fins de rapidité d\u0026rsquo;utilisation et d\u0026rsquo;automatisation, j\u0026rsquo;ai mis en place ce petit script qui facilite la récupération de l\u0026rsquo;information.\nFonctionnement # Le script fait une requête HTTP GET sur la ressource https://endoflife.date/api/PRODUCT.json. Il traite ensuite les différentes versions et les affiche via la méthode Python __repr__.\nInstallation # Lancer le script Bash suivant :\nSCRIPT_1=\u0026#34;https://gitlab.com/-/snippets/2592975/raw/main/software_endoflife.py\u0026#34; TEMP_DIR=$(mktemp -d) echo \u0026#34;Downloading in local directory: $TEMP_DIR\u0026#34; curl $SCRIPT_1 \u0026gt; \u0026#34;$TEMP_DIR/software_endoflife.py\u0026#34; 2\u0026gt; /dev/null python3 \u0026#34;$TEMP_DIR/software_endoflife.py\u0026#34; -h Quelques dépendances sont à prévoir : requests, dataclasses.\nUtilisation # Lancer l\u0026rsquo;interpréteur Python3 pour exécuter le script.\n$ python3 \u0026#34;$TEMP_DIR/software_endoflife.py\u0026#34; -h Voici ce que cela donne (sur le site asciinema2).\nhttps://endoflife.date\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://asciinema.org/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2 septembre 2023","externalUrl":null,"permalink":"/articles/fr/snippet_software_endoflife/","section":"Articles","summary":"Brève présentation d’un outil en ligne de commande donnant les dates de fin de support de logiciels.","title":"Fin de support logiciel","type":"articles"},{"content":" Introduction # Je suis souvent amené à faire du support système assez général pour des utilisateurs de GNU/Linux, notamment sur Ubuntu. Pour automatiser la collecte d\u0026rsquo;informations, je leur demande de lancer un script sur leur machine, qui collecte les informations système suivantes :\nIdentité de la machine, Journaux de logs, Configuration réseau, Activité de processus,\u0026hellip; ➡ Cela me permet de faciliter les analyses des problèmes rencontrés.\nFonctionnement # L\u0026rsquo;outil propose une fenêtre graphique qui guide l\u0026rsquo;utilisateur dans la génération d\u0026rsquo;une archive contenant les informations collectées.\nIl indique ensuite le chemin de l\u0026rsquo;archive générée. Puis l\u0026rsquo;utilisateur a plusieurs choix de traitement de cette archive. Installation # Copier les deux fichiers dans un répertoire, ou lancer le script Bash suivant :\nSCRIPT_1=\u0026#34;https://gitlab.com/-/snippets/2567527/raw/main/antlas_report.sh\u0026#34; SCRIPT_2=\u0026#34;https://gitlab.com/-/snippets/2567527/raw/main/antlas_report_utils.sh\u0026#34; TEMP_DIR=$(mktemp -d) echo \u0026#34;Downloading in local directory: $TEMP_DIR\u0026#34; curl $SCRIPT_1 \u0026gt; \u0026#34;$TEMP_DIR/antlas_report.sh\u0026#34; 2\u0026gt; /dev/null curl $SCRIPT_2 \u0026gt; \u0026#34;$TEMP_DIR/antlas_report_utils.sh\u0026#34; 2\u0026gt; /dev/null chmod u+x \u0026#34;$TEMP_DIR/antlas_report.sh\u0026#34; ls -lh $TEMP_DIR Quelques dépendences sont à prévoir : xclip, zenity et thunderbird pour l\u0026rsquo;envoi par email si souhaité.\nUtilisation # Simplement lancer un shell et la commande $ bash antlas_report.sh.\n","date":"17 juillet 2023","externalUrl":null,"permalink":"/articles/fr/snippet_system_reporter/","section":"Articles","summary":"Brève présentation d’un collecteur d’information système Debian, dans le cadre de support utilisateur.","title":"Collecteur d'information","type":"articles"},{"content":" Introduction # Abordons ici, un peu plus en détails, la consommation énergétique d’un système informatique GNU/Linux. C\u0026rsquo;est un sujet qui me semble assez important compte tenu des problématiques énergétiques qui se trouvaient souvent écartées voire délaissées des raisonnements décisionnaires. Mais maintenant, les bilans carbones des infrastructures informatiques deviennent la norme ( à l\u0026rsquo;image des hébergeurs qui commencent à justifier leurs bilans 1) ; et c\u0026rsquo;est là que ce sujet prend son sens : et s\u0026rsquo;il était possible, à l\u0026rsquo;image des systèmes embarqués, de réduire périodiquement la consommation des infrastructures ?\nL\u0026rsquo;infrastructure # Dans la suite de cet article, on définit une infrastructure comme le matériel qui compose une machine et la somme des logiciels qui s\u0026rsquo;exécutent en temps réel dessus. Certains logiciels sont essentiels au fonctionnement global (tels que le kernel et les plateformes de production comme Docker ou Kubernetes) et d\u0026rsquo;autres non (les autres programmes ou applications d\u0026rsquo;utilisateur final).\nLa consommation globale d\u0026rsquo;un système dépend donc des besoins énergétiques :\nde fonctionnement de chaque composant matériel (CPU, mémoire, écran, carte Wi-Fi,\u0026hellip;) de la sollicitation plus ou moins intense de ces composants par les différents programmes. D\u0026rsquo;un point de vue logiciel, le système est divisé en deux mondes distincts, l\u0026rsquo;espace kernel et l\u0026rsquo;espace user. La partie d\u0026rsquo;exploitation, avec le kernel, est là pour gérer les périphériques, charger les drivers nécessaires, organiser l\u0026rsquo;ordonnancement des tâches,\u0026hellip; Tandis que l\u0026rsquo;espace dit user contient tous les logiciels spécifiques, les bibliothèques dédiées, etc, qui ne tournent pas dans le kernel.\n➡ On portera attention au scheduler 2, appartenant au kernel qui est responsable de l\u0026rsquo;ordonnancement des tâches. Son rôle est central car c\u0026rsquo;est lui qui alloue aux programmes des intervalles de temps insécables pendant lesquels ils peuvent opérer une partie de leurs instructions. On n\u0026rsquo;interagira pas directement avec le scheduler, mais connaitre son existence et les différents types de schedulers reste nécessaire pour comprendre les principes de priorités et d\u0026rsquo;exécution.\nActivité # J\u0026rsquo;ai déjà discuté du lien entre activité et sollicitation énergétique du CPU dans cet article. Les résultats étaient concluants : les temps de calculs alloués aux conteneurs Docker étaient maîtrisés, et leur activité réduite. Mais voyons plus profondément les mécanismes en jeu.\n➡ La question est de savoir ici, s’il est possible d\u0026rsquo;être actif dans la gestion énergétique d’un système, avec une granularité au programme ; dans la mesure où l\u0026rsquo;humain sait quel ensemble de programmes il lance et pour quels objectifs.\nLeviers # À utilisation identique, un programme génère un jeu d’instructions déterminé ; et son empreinte énergétique reste identique dans le même contexte d\u0026rsquo;utilisation. Pour réduire l’empreinte énergétique, il faut réduire le jeu d’instructions sur la période d\u0026rsquo;observation, et pour cela deux pistes peuvent être suivies.\nPenser l’architecture du logiciel en incluant des modes de fonctionnement dégradés afin de réduire le code sous-jacent à exécuter. Cela signifie incorporer, et ce dès l\u0026rsquo;étape de spécification d\u0026rsquo;un produit, ces différents modes. Brider l’exécution du programme au niveau système ou plateforme : limiter au programme sa possibilité d\u0026rsquo;exécuter ses instructions. Ce procédé peut facilement se généraliser, même si l\u0026rsquo;expérience utilisateur s\u0026rsquo;en trouvera impactée. ➡ L’étude de la consommation est très liée à une période d’observation, et pour une même période donnée, réduire la consommation signifie réduire le temps d\u0026rsquo;activité du programme visé ; par quelconque moyen.\nDétermination de l\u0026rsquo;activité # Commençons d\u0026rsquo;abord par mesurer l\u0026rsquo;activité d\u0026rsquo;un système, puis d\u0026rsquo;un programme spécifique. Les temps d\u0026rsquo;activité sont donnés en jiffy, dont la valeur sur mon système est de 100 Hz, soit 10 millisecondes.\n$ python3 -c \u0026#34;import os;print(os.sysconf(os.sysconf_names[\\\u0026#34;SC_CLK_TCK\\\u0026#34;]))\u0026#34; 100 Activité globale # Pour déterminer l\u0026rsquo;activité globale — le temps que passe le CPU à exécuter des instructions, le kernel propose des statistiques toutes prêtes dans les proc entries, dans un fichier intitulé /proc/stat. Récupérer ces informations et les interpréter sont la base de fonctionnement du logiciel top. Le manuel décrit bien la structure et les données consultables.\n$ man 5 proc [...] /proc/stat kernel/system statistics. Varies with architecture. Common entries include: The amount of time, measured in units of USER_HZ (1/100ths of a second on most architectures, use sysconf(_SC_CLK_TCK) to obtain the right value), that the system (\u0026#34;cpu\u0026#34; line) or the specific CPU (\u0026#34;cpuN\u0026#34; line) spent in various states: user (1) Time spent in user mode. nice (2) Time spent in user mode with low priority (nice). system (3) Time spent in system mode. idle (4) Time spent in the idle task. This value should be USER_HZ times the second entry in the /proc/uptime pseudo-file. iowait (since Linux 2.5.41) (5) Time waiting for I/O to complete. This value is not reliable, for the following reasons: irq (since Linux 2.6.0) (6) Time servicing interrupts. softirq (since Linux 2.6.0) (7) Time servicing softirqs. steal (since Linux 2.6.11) (8) Stolen time, which is the time spent in other operating systems when running in a virtualized environment guest (since Linux 2.6.24) (9) Time spent running a virtual CPU for guest operating systems under the control of the Linux kernel. guest_nice (since Linux 2.6.33) (10) Time spent running a niced guest (virtual CPU for guest operating systems under the control of the Linux kernel). [...] Il suffit de calculer le temps que le système a passé à fonctionner3 4, basé sur le nombre de jiffies écoulé:\nTotal activité = user + nice + system + idle + iowait + irq + softirq + steal Ce qui donnera le code Python suivant :\ndef get_total_jiffies() -\u0026gt; int: \u0026#34;\u0026#34;\u0026#34; get the absolute total jiffies elapsed \u0026#34;\u0026#34;\u0026#34; res = 0 with open(os.path.join(\u0026#34;/\u0026#34;, \u0026#34;proc\u0026#34;, \u0026#34;stat\u0026#34;), \u0026#34;r\u0026#34;) as stats: line = stats.readline().strip().replace(\u0026#34; \u0026#34;, \u0026#34; \u0026#34;).split(\u0026#34; \u0026#34;) # remove guest del line[9] del line[8] jiffies = [ int(i) for i in line[1:] ] res = sum(jiffies) return res Et dont le résultat sera une quantité de jiffies, par exemple 634434, 636475,\u0026hellip;\nActivité d\u0026rsquo;un programme # Et pour le cas d\u0026rsquo;un programme spécifique, on ira consulter le fichier intitulé par le numéro de son pid.\n$ man 5 proc [...] /proc/[pid]/stat Status information about the process. This is used by ps(1). It is defined in the kernel source file fs/proc/array.c. (1) pid %d The process ID. (2) comm %s The filename of the executable, in parentheses [...] (14) utime %lu Amount of time that this process has been scheduled in user mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). This includes guest time, guest_time (time spent run‐ ning a virtual CPU, see below), so that applications that are not aware of the guest time field do not lose that time from their calculations. (15) stime %lu Amount of time that this process has been scheduled in kernel mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). (16) cutime %ld Amount of time that this process waited-for children have been scheduled in user mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). (See also times(2).) This in‐ cludes guest time, cguest_time (time spent running a virtual CPU, see below). (17) cstime %ld Amount of time that this process waited-for children have been scheduled in kernel mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). (18) priority %ld [...] On sera intéressé ici par les valeurs de utime, stime, et cutime, cutime selon si l\u0026rsquo;on veut également inclure les statistiques des processus enfants.\nTotal activité processus = utime + stime Total activité processus avec enfants = utime + stime + cutime + cstime Ce qui donnera le code Python suivant :\ndef read_process_jiffies(pid:str, with_children:bool=False) -\u0026gt; int: \u0026#34;\u0026#34;\u0026#34; get the total process jiffies (utime, stime, cutime, cstime) \u0026#34;\u0026#34;\u0026#34; res = 0 with open(os.path.join(\u0026#34;/\u0026#34;, \u0026#34;proc\u0026#34;, pid, \u0026#34;stat\u0026#34;), \u0026#34;r\u0026#34;) as stats: line = stats.readline().split(\u0026#34; \u0026#34;) res = int(line[13]) + int(line[14]) if with_children: res = res + int(line[15]) + int(line[16]) return res Les valeurs calculées seront toujours inférieures à celles du système. Elles sont globales, pour tous les CPU.\n➡ On est maintenant capable de calculer le pourcentage d\u0026rsquo;activité d\u0026rsquo;un programme : il suffit, sur un intervalle donné de faire le ratio entre temps d\u0026rsquo;activité du système et le temps d\u0026rsquo;activité du programme.\nDétermination de la consommation # Sur la machine d\u0026rsquo;étude, le kernel propose des proc entries dédiées à l\u0026rsquo;utilisation de la batterie : le courant consommé et sa tension. Ces proc entries sont accessibles à ces deux endroits : /sys/class/power_supply/BAT0/current_now et /sys/class/power_supply/BAT0/voltage_now. La puissance est calculée par le produit du courant et de la tension.\nMais regardons la signification de ces valeurs, qui sont mesurées instantanément. En tenant compte de la période d\u0026rsquo;observation, on pourra pour une meilleure précision des mesures, les lire de manière répétée et en calculer la moyenne.\nConsommation globale # La détermination de la consommation globale sera alors établie par l\u0026rsquo;exemple de script suivant :\ndef get_total_power(interval:int, sampling:int) -\u0026gt; tuple: \u0026#34;\u0026#34;\u0026#34; get the average total power of the system based on battery proc entries returns: (current, voltage, power) \u0026#34;\u0026#34;\u0026#34; res = (0, 0, 0) current_path = \u0026#34;/sys/class/power_supply/BAT0/current_now\u0026#34; voltage_path = \u0026#34;/sys/class/power_supply/BAT0/voltage_now\u0026#34; current_values = [0] voltage_values = [0] power_values = [0] delay = float(interval / sampling) for k in range(sampling): with open(current_path, \u0026#34;r\u0026#34;) as current: with open(voltage_path, \u0026#34;r\u0026#34;) as voltage: current = int(current.readline())/1000000 voltage = int(voltage.readline())/1000000 current_values.append(current) voltage_values.append(voltage) power_values.append(current * voltage, 2) time.sleep(delay) res = (round(mean(current_values), 2), round(mean(voltage_values), 2), round(mean(power_values),2)) return res Cette consommation illustre la puissance nécessaire à toute l\u0026rsquo;infrastructure sur l\u0026rsquo;intervalle d\u0026rsquo;observation.\nConsommation d\u0026rsquo;un programme # C\u0026rsquo;est là qu\u0026rsquo;une approximation supplémentaire est nécessaire. Pour estimer la consommation d\u0026rsquo;un programme, on supposera que dans une certaine mesure la consommation globale de l\u0026rsquo;infrastructure est équivalente à celle de l\u0026rsquo;activité CPU globale du système.\nConsommation du programme = consommation globale * ratio d activité du programme C\u0026rsquo;est bien sûr une estimation car on récupère le niveau de batterie global, sans pour autant utiliser les outils dédiés tels que le fait Scaphandre5 avec RAPL6.\nThanks to this technology it is possible to get the total energy consumption of the CPU, of the consumption per CPU socket, plus in some cases, the consumption of the DRAM controller. In most cases it represents the vast majority of the energy consumption of the machine (except when running GPU intensive workloads, for example).\nCe ne sera pas le cas dans cet article.\n➡ On peut dorénavant estimer la consommation d\u0026rsquo;un programme, estimation qui sera toujours plus juste sur une machine qui a le minimum de périphériques en cours d\u0026rsquo;utilisation.\nOutils de réduction d\u0026rsquo;activité # Maintenant que l\u0026rsquo;on est capable de mesurer l\u0026rsquo;activité d\u0026rsquo;un programme, d\u0026rsquo;en estimer son empreinte énergétique, tournons nous sur les moyens de réduction d\u0026rsquo;activité, et indirectement de consommation.\nPriorisation # Il est possible de changer la priorité des processus et les reléguer en fin de liste : le scheduler leur allouera alors moins de temps pour tourner, et donc consommer5. Cela est réalisé avec la commande nice, pour les processus qui sont en scheduling policy SCHED_OTHER (ou SCHED_BATCH). On peut lister les scheduling policies des processus via la commande chrt.\nThe nice value is an attribute that can be used to influence the CPU scheduler to favor or disfavor a process in scheduling decisions. It affects the scheduling of SCHED_OTHER and SCHED_BATCH (see below) processes. The nice value can be modified using nice(2), setpriority(2), or sched_setattr(2).\n$ for pid in $(ps aux | grep -iv pid | tr -s \u0026#34; \u0026#34; | cut -d \u0026#34; \u0026#34; -f2); do chrt -p $pid; done [...] stratégie d’ordonnancement actuelle pour le PID 29662 : SCHED_OTHER priorité de planification actuelle pour le PID 29662 : 0 [...] Cela peut dans un premier abord être une solution, mais que se passe-t-il si on veut maîtriser de manière déterministe l\u0026rsquo;allocation énergétique ? Cela n\u0026rsquo;est pas possible car si les processus de plus haute priorité terminent, le processus étudié se verra utiliser plus de temps de calcul et donc plus de ressources.\n➡ La notion de priorité est relative à l\u0026rsquo;activité instantanée de l\u0026rsquo;infrastructure, et ne propose pas de limite objective et déterministe.\nUtilitaires # On peut se tourner vers un outil Linux très pratique : cpulimit pour réduire l\u0026rsquo;activité d\u0026rsquo;un processus, à partir de son numéro de pid. Pour limiter à 50% de CPU un processus, on peut appeler la commande suivante.\n$ cpulimit -l 50 -p $PID Le principe en coulisse est de créer un groupe de processus et d\u0026rsquo;aller en changer les limites d\u0026rsquo;exécution 7.\n➡ Même si très pratique, il ne gère qu\u0026rsquo;un processus seulement, et la configuration n\u0026rsquo;est pas persistante; dès que cpulimit est stoppé, le processus pid reprend son rythme de croisière.\nDocker # Dans le monde de la production de logiciels, l\u0026rsquo;intégration dans Docker est un plus, car elle permet de maîtriser un sous ensemble déterminé de logiciels : ceux qui constituent l\u0026rsquo;image Docker instanciée. Avec la bibliothèque Python pour Docker8, on peut programmatiquement altérer la configuration système de chaque conteneur qui s\u0026rsquo;exécute. L\u0026rsquo;extrait de la documentation mentionne plusieurs paramètres à moduler.\nupdate(**kwargs) Update resource configuration of the containers. Parameters blkio_weight (int) – Block IO (relative weight), between 10 and 1000 cpu_period (int) – Limit CPU CFS (Completely Fair Scheduler) period cpu_quota (int) – Limit CPU CFS (Completely Fair Scheduler) quota cpu_shares (int) – CPU shares (relative weight) cpuset_cpus (str) – CPUs in which to allow execution cpuset_mems (str) – MEMs in which to allow execution mem_limit (int or str) – Memory limit mem_reservation (int or str) – Memory soft limit memswap_limit (int or str) – Total memory (memory + swap), -1 to disable swap kernel_memory (int or str) – Kernel memory limit restart_policy (dict) – Restart policy dictionary Returns Dictionary containing a Warnings key. Return type (dict) Raises docker.errors.APIError – If the server returns an error. On configurera les paramètres\ncpuset_cpus (str) – CPUs in which to allow execution cpu_quota (int) – Limit CPU CFS (Completely Fair Scheduler) quota cpu_period (int) – Limit CPU CFS (Completely Fair Scheduler) period ➡ Avec cette bibliothèque, on va pouvoir étudier la limitation de ressources de conteneurs Docker, qui sont en policy SCHED_OTHER et qui de ce fait sont gérés par le CFS.\nÉtude # Pour étudier les effets de la réduction, on va lancer un conteneur Docker incorporant l\u0026rsquo;utilitaire stress, puis on limitera au fur et à mesure son quota de ressources.\ndef run_stress_container(name:str, cpus:int=1) -\u0026gt; Optional[list]: \u0026#34;\u0026#34;\u0026#34; run container \u0026#34;\u0026#34;\u0026#34; client = get_docker_client() try: container = client.containers.run( image=\u0026#34;progrium/stress\u0026#34;, command=f\u0026#34;--cpu {cpus}\u0026#34;, name=name, detach=True, remove=True, ) except docker.errors.NotFound: return None except docker.errors.ContainerError: return None else: cmd = \u0026#34;pidof stress\u0026#34; res = subprocess.check_output(cmd, shell=True) res = str(res.decode(\u0026#34;utf-8\u0026#34;)).replace(\u0026#34;\\n\u0026#34;, \u0026#34;\u0026#34;).split(\u0026#34; \u0026#34;) return res En récupérant les différents pid du conteneur, on pourra mesurer leur consommation. Et on stockera dans un fichier CSV les valeurs suivantes :\nPID Jiffies globales Jiffies du processus Ratio d\u0026rsquo;activité du processus Courant moyenné sur l\u0026rsquo;intervalle de mesure Tension moyennée sur l\u0026rsquo;intervalle de mesure Puissance moyennée sur l\u0026rsquo;intervalle de mesure Nombre total de processus sur la machine Nombre de processus en scheduling policy SCHED_FIFO Nombre de processus en scheduling policy SCHED_RR Nombre de processus en scheduling policy SCHED_OTHER Le script qui génère ce fichier CSV est accessible ici.\nOn lance le script avec la commande suivante :\n$ python consumption.py -i 2 -N 15 -T 1 -S 1 Avec un intervalle -i de 2 secondes entre deux mesures, 15 boucles -N de mesures avant de limiter l\u0026rsquo;activité (de 100% à 5%, et de 5% à 100%), avec sollicitation -T d\u0026rsquo;un CPU sur le CPU -S numéro 1.\nRésultats # Voici à quoi ressemble le contenu du fichier CSV. Deux secondes séparent donc deux lignes.\ncpu_quota;cpuset_cpus;current;number_of_processes;percentage_process;pid;power;process_jiffies;process_power;processes_fifo;processes_other;processes_rr;total_jiffies;voltage 1.0;1;0.74;314;0.00172;79969;11.77;220;0.0;23;288;0;12780308;15.96 1.0;1;0.74;314;2e-05;79937;11.77;2;0.0;23;288;0;12780337;15.96 1.0;1;0.96;298;25.31646;79969;15.14;260;3.83;23;272;0;1027;15.84 1.0;1;0.96;298;0.0;79937;15.14;0;0.0;23;272;0;1023;15.84 [...] Activité # Simple thread # On constate, sur ce graphe, que le nombre de jiffies pour ce processus diminue et augmente en fonction du quota que l\u0026rsquo;on fixe. On commence à 100% de quota, pour diminuer jusqu\u0026rsquo;à 5%, et remonter à 100%.\nMais n\u0026rsquo;oublions pas comment se comporte le scheduler : si le nombre de processus total augmente sur la machine, le nombre de jiffies alloué pour chaque processus va diminuer, pour laisser du temps à chacun d\u0026rsquo;exécuter ses instructions. Vérifions le nombre de processus sur la période d\u0026rsquo;étude.\nAu premier abord, le nombre semble stable. En comparant également le nombre de processus dans chaque policy, on constate que l\u0026rsquo;état de la machine est stable et qu\u0026rsquo;aucun processus en SCHED_FIFO ou SCHED_RR ne vient prendre plus de temps et diminuer l\u0026rsquo;allocation de jiffies des processus en SCHED_OTHER.\nMultiples CPU # On lance maintenant le conteneur de stress simulant 100% d\u0026rsquo;activité d\u0026rsquo;un CPU sur 3 cores, avec la commande suivante.\n$ python consumption.py -i 2 -N 15 -T 3 -S 0-2 Regardons le résultat. On note bien 4 processus ; le premier est le processus parent, dont l\u0026rsquo;activité est proche de zéro. La somme des jiffies écoulées par processus respecte bien le rapport 3 par rapport à un seul CPU. Et on vérifie bien que l\u0026rsquo;état du système est stable pendant le temps de l\u0026rsquo;observation.\n➡ On peut donc conclure que c\u0026rsquo;est bien nous qui limitons le programme de stress et que ce n\u0026rsquo;est pas une fortuite coincidence.\nConsommation # Simple thread # En reprenant la formule que nous avons proposée, on va pouvoir calculer la consommation du programme de stress.\nConsommation du programme = consommation globale * ratio d activité du programme Il est important de noter certaines aberrations possibles, notamment que certains pics de consommation puissent être conséquents à des activités diverses (communication réseau, écriture sur le disque,\u0026hellip;) et dans ces cas-là, le réel pourcentage de consommation de ce processus n\u0026rsquo;est pas juste. On note quand même une tendance qui suit le quota d\u0026rsquo;activité — cohérent dans la mesure où le pourcentage d\u0026rsquo;activité CPU diminue.\nLa consommation globale ne montre pas pour autant de diminution sur l\u0026rsquo;intervalle d\u0026rsquo;observation.\nUne raison à cela est que les autres processus de la machine aient momentanément besoin de plus ; ou bien que la limitation d\u0026rsquo;un seul processus sur les environs 300 sur la machine ne soit pas significative à cette échelle.\nMultiples CPU # On retrouve logiquement le rapport 3 qui montre une consommation trois fois moins importante par processus. Et de même que la consommation globale avec un simple thread, avec l\u0026rsquo;occupation de trois cores elle semble rester indépendante du quota de CPU autorisé.\n➡ Dans le cadre des suppositions faites pour la consommation, on observe bien une diminution de l\u0026rsquo;empreinte énergétique du processus, en cohérence avec le calcul lui-même basé sur le ratio d\u0026rsquo;activité. La demande énergétique globale de l\u0026rsquo;infrastructure reste quant à elle stable ; mais sans étudier l\u0026rsquo;intégralité des autes processus et leur nature, il n\u0026rsquo;est pas possible de statuer et d\u0026rsquo;en trouver la raison.\nConclusion # On peut déjà commenter sur l\u0026rsquo;intrication du scheduling et de la consommation du CPU, et que le scheduler reste essentiel pour proposer une porte d\u0026rsquo;entrée à la gestion d\u0026rsquo;activité. Le raisonnement derrière le scheduling montre qu\u0026rsquo;il est très impactant sur l\u0026rsquo;allocation des ressources et donc sur la consommation. On peut se poser la question suivante : le principe derrière le scheduler standard de Linux (CFS) est-il le meilleur pour appréhender cette thématique de gestion d\u0026rsquo;énergie ?\nCependant, une constatation semble claire : il est possible de maîtriser les activités des programmes, soit en allant directement changer leurs attributs (via cpulimit par exemple), soit en passant par une plateforme d\u0026rsquo;administration de programmes (telle que Docker), qui permet surtout une adaptation de la granularité : parfois on voudra maîtriser une application complète, qui est en réalité un sous ensemble de programmes, et non traiter chaque sous programme indépendamment. Pour autant, les mécanismes feront appel in fine au scheduler.\nMais la réponse à cette gestion énergétique ne réside-t-elle pas dans le développement d\u0026rsquo;une méthode de scheduling adaptée à la gestion énergétique, comme on peut le voir pour les systèmes temps-réel ? Pourrait-on intégrer des modes de fonctionnement dégradés directement dans le kernel et la gestion des périphériques ? Qu\u0026rsquo;existe-t-il déjà dans le kernel à ce sujet ?\nhttps://corporate.ovhcloud.com/fr/sustainability/environment/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.kernel.org/doc/html/latest/scheduler/sched-design-CFS.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://stackoverflow.com/questions/5514119/accurately-calculating-cpu-utilization-in-linux-using-proc-stat\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/brgl/busybox/blob/master/procps/top.c#L276\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/hubblo-org/scaphandre\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://hubblo-org.github.io/scaphandre-documentation/explanations/how-scaph-computes-per-process-power-consumption.html#some-details-about-rapl\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/opsengine/cpulimit/blob/f4d2682804931e7aea02a869137344bb5452a3cd/src/cpulimit.c#L189\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://docker-py.readthedocs.io/en/stable/containers.html#docker.models.containers.Container.update\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"12 mai 2023","externalUrl":null,"permalink":"/articles/fr/consumption/","section":"Articles","summary":"Comment réduire l’empreinte énergétique logicielle ? Plongeons plus en détails dans le fonctionnement technique.","title":"Consommation énergétique","type":"articles"},{"content":" Introduction # Souhaitant me tourner un peu vers la domotique, je me suis procuré un relai électrique commandable via une communication série. Avec quelques expériences en électricité, démarrer automatiquement certains équipements de son domicile devient possible. Les luminaires dès que l\u0026rsquo;on rentre chez soi, la fameuse machine à café à l\u0026rsquo;aube d\u0026rsquo;une belle journée,\u0026hellip; Voire les guirlandes de Noël. J\u0026rsquo;ai donc développé une petite bibliothèque dédiée à l\u0026rsquo;utilisation de ce relai. Je décrirai dans un futur article ce qu\u0026rsquo;il adviendra de lui. Pour le moment, voyons plus en détails cette petite bibliothèque.\nAu cas où Petite note au préalable sur les dangers de l\u0026rsquo;électricité, même à de faibles tensions. Dans le doute, pas de doute : on ne fait pas.\nDescription du relai # Le relai USB-RLY02 de Devantech dispose de deux contacteurs et d’un micro contrôleur avec un port série pour communiquer, à l’écoute de commandes qui lui sont envoyées. Elles contrôlent les états de chaque contacteur. L’ensemble des commandes est défini sur des valeurs entières1 d’un octet. Chaque valeur correspond donc à une commande, par exemple la valeur 0x66 à mettre en position active le relai numéro 2. On pourra envoyer six commandes différentes pour activer ou désactiver les contacteurs. Une commande particulière reste celle de demande d\u0026rsquo;état qui reste très pratique pour savoir comment sont configurés les contacteurs. N\u0026rsquo;oublions pas non plus la commande 0x5A qui informe sur l\u0026rsquo;identifiant du relai et sa version logicielle.\nDu point de vue électrique, suivre les abaques2 fournies détermine la plage d’utilisation du relai – qui sera ici jusqu’à 16 A à 230 VAC.\nPrise en main # Branchement # Au branchement USB sur un PC GNU/Linux — suis basé sur Debian, le kernel 5.19.0 fait son travail et identifie le composant, diffuse un uevent capté par udev3 — via une socket NETLINK_ KOBJECT_UEVENT — et son service systemd associé.\n[595331.721754] usb 3-1: new full-speed USB device number 5 using xhci_hcd [595331.872896] usb 3-1: New USB device found, idVendor=04d8, idProduct=ffee, bcdDevice= 1.00 [595331.872908] usb 3-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 [595331.872911] usb 3-1: Product: USB-RLY02. [595331.872914] usb 3-1: Manufacturer: Devantech Ltd. [595331.872917] usb 3-1: SerialNumber: 00008749 [595331.872919] cdc_acm 3-4:1.0: ttyACM0: USB ACM device C\u0026rsquo;est le module kernel cdc_adm4 qui a la tâche de créer le device ttyACM0. Et c\u0026rsquo;est via le service systemd-udevd.service que le relai devient accessible. Le device /dev/ttyACM0 apparait alors dans le filesystem, conformément aux règles préétablies dans /etc/udev/rules.d/, ou dans le répertoire par défaut /lib/udev/rules.d/. La règle udev exécutée est celle dédiée aux appareils série.\nVoyons ce qu\u0026rsquo;il se passe dans les logs d\u0026rsquo;udev. Changeons temporairement le niveau de logging :\n$ sudo udevadm control --log-priority=debug Et voici ce qu\u0026rsquo;il nous raconte :\n[...] systemd-udevd[178638]: ttyACM0: /usr/lib/udev/rules.d/60-serial.rules:8 Importing properties from results of builtin command \u0026#39;hwdb --subsystem=usb\u0026#39; systemd-udevd[178638]: ttyACM0: hwdb modalias key: \u0026#34;usb:v04D8pFFEEd0100dc02dsc00dp00ic02isc02ip01in00\u0026#34; systemd-udevd[178638]: ttyACM0: /usr/lib/udev/rules.d/60-serial.rules:15 Importing properties from results of builtin command \u0026#39;path_id\u0026#39; systemd-udevd[178638]: ttyACM0: /usr/lib/udev/rules.d/60-serial.rules:16 LINK \u0026#39;serial/by-path/pci-0000:00:14.0-usb-0:4:1.0\u0026#39; systemd-udevd[178638]: ttyACM0: /usr/lib/udev/rules.d/60-serial.rules:19 Skipping builtin \u0026#39;usb_id\u0026#39; in IMPORT key systemd-udevd[178638]: ttyACM0: /usr/lib/udev/rules.d/60-serial.rules:23 LINK \u0026#39;serial/by-id/usb-Devantech_Ltd._USB-RLY02._00008749-if00\u0026#39; systemd-udevd[178637]: 3-4:1.1: sd-device: Created db file \u0026#39;/run/udev/data/+usb:3-4:1.1\u0026#39; for \u0026#39;/devices/pci0000:00/0000:00:14.0/usb3/3-4/3-4:1.1\u0026#39; systemd-udevd[178637]: 3-4:1.1: Device processed (SEQNUM=9933, ACTION=bind) systemd-udevd[178638]: ttyACM0: Setting permissions /dev/ttyACM0, uid=0, gid=20, mode=0660 systemd-udevd[178638]: ttyACM0: Handling device node \u0026#39;/dev/ttyACM0\u0026#39;, devnum=c166:0 systemd-udevd[178638]: ttyACM0: /run/udev/links/serial\\x2fby-path\\x2fpci-0000:00:14.0-usb-0:4:1.0 is modified, but its timestamp is not changed, updating timestamp after 10ms. systemd-udevd[178637]: 3-4:1.1: sd-device-monitor: Passed 523 byte to netlink monitor systemd-udevd[178638]: ttyACM0: /run/udev/links/serial\\x2fby-id\\x2fusb-Devantech_Ltd._USB-RLY02._00008749-if00 is modified, but its timestamp is not changed, updating timestamp after 10ms. systemd-udevd[178638]: ttyACM0: sd-device: Created db file \u0026#39;/run/udev/data/c166:0\u0026#39; for \u0026#39;/devices/pci0000:00/0000:00:14.0/usb3/3-4/3-4:1.0/tty/ttyACM0\u0026#39; systemd-udevd[178638]: ttyACM0: Failed to get watch handle, ignoring: No such file or directory systemd-udevd[178638]: ttyACM0: Device processed (SEQNUM=9930, ACTION=add) systemd-udevd[178638]: ttyACM0: sd-device-monitor: Passed 1081 byte to netlink monitor [...] Et on retrouve bien le chemin d\u0026rsquo;accès /dev/ttyACM0 qu\u0026rsquo;udev nous donne, via la règle 60-serial.rules.\n$ cat /lib/udev/rules.d/60-serial.rules # do not edit this file, it will be overwritten on update ACTION==\u0026#34;remove\u0026#34;, GOTO=\u0026#34;serial_end\u0026#34; SUBSYSTEM!=\u0026#34;tty\u0026#34;, GOTO=\u0026#34;serial_end\u0026#34; SUBSYSTEMS==\u0026#34;pci\u0026#34;, ENV{ID_BUS}=\u0026#34;pci\u0026#34;, ENV{ID_VENDOR_ID}=\u0026#34;$attr{vendor}\u0026#34;, ENV{ID_MODEL_ID}=\u0026#34;$attr{device}\u0026#34; SUBSYSTEMS==\u0026#34;pci\u0026#34;, IMPORT{builtin}=\u0026#34;hwdb --subsystem=pci\u0026#34; SUBSYSTEMS==\u0026#34;usb\u0026#34;, IMPORT{builtin}=\u0026#34;usb_id\u0026#34;, IMPORT{builtin}=\u0026#34;hwdb --subsystem=usb\u0026#34; # /dev/serial/by-path/, /dev/serial/by-id/ for USB devices KERNEL!=\u0026#34;ttyUSB[0-9]*|ttyACM[0-9]*\u0026#34;, GOTO=\u0026#34;serial_end\u0026#34; SUBSYSTEMS==\u0026#34;usb-serial\u0026#34;, ENV{.ID_PORT}=\u0026#34;$attr{port_number}\u0026#34; IMPORT{builtin}=\u0026#34;path_id\u0026#34; ENV{ID_PATH}==\u0026#34;?*\u0026#34;, ENV{.ID_PORT}==\u0026#34;\u0026#34;, SYMLINK+=\u0026#34;serial/by-path/$env{ID_PATH}\u0026#34; ENV{ID_PATH}==\u0026#34;?*\u0026#34;, ENV{.ID_PORT}==\u0026#34;?*\u0026#34;, SYMLINK+=\u0026#34;serial/by-path/$env{ID_PATH}-port$env{.ID_PORT}\u0026#34; IMPORT{builtin}=\u0026#34;usb_id\u0026#34; ENV{ID_SERIAL}==\u0026#34;\u0026#34;, GOTO=\u0026#34;serial_end\u0026#34; SUBSYSTEMS==\u0026#34;usb\u0026#34;, ENV{ID_USB_INTERFACE_NUM}=\u0026#34;$attr{bInterfaceNumber}\u0026#34; ENV{ID_USB_INTERFACE_NUM}==\u0026#34;\u0026#34;, GOTO=\u0026#34;serial_end\u0026#34; ENV{.ID_PORT}==\u0026#34;\u0026#34;, SYMLINK+=\u0026#34;serial/by-id/$env{ID_BUS}-$env{ID_SERIAL}-if$env{ID_USB_INTERFACE_NUM}\u0026#34; ENV{.ID_PORT}==\u0026#34;?*\u0026#34;, SYMLINK+=\u0026#34;serial/by-id/$env{ID_BUS}-$env{ID_SERIAL}-if$env{ID_USB_INTERFACE_NUM}-port$env{.ID_PORT}\u0026#34; LABEL=\u0026#34;serial_end\u0026#34; On peut toutefois directement appeler l\u0026rsquo;utilitaire udevadm pour demander les informations du périphérique en question.\n$ udevadm info --name /dev/ttyACM0 --query all P: /devices/pci0000:00/0000:00:14.0/usb3/3-4/3-4:1.0/tty/ttyACM0 N: ttyACM0 L: 0 S: serial/by-path/pci-0000:00:14.0-usb-0:4:1.0 S: serial/by-id/usb-Devantech_Ltd._USB-RLY02._00008749-if00 E: DEVPATH=/devices/pci0000:00/0000:00:14.0/usb3/3-4/3-4:1.0/tty/ttyACM0 E: DEVNAME=/dev/ttyACM0 E: MAJOR=166 E: MINOR=0 E: SUBSYSTEM=tty E: USEC_INITIALIZED=186532534818 E: ID_BUS=usb E: ID_VENDOR_ID=04d8 E: ID_MODEL_ID=ffee E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller E: ID_PCI_INTERFACE_FROM_DATABASE=XHCI E: ID_VENDOR_FROM_DATABASE=Microchip Technology, Inc. E: ID_AUTOSUSPEND=1 E: ID_MODEL_FROM_DATABASE=Devantech USB-ISS E: ID_VENDOR=Devantech_Ltd. E: ID_VENDOR_ENC=Devantech\\x20Ltd. E: ID_MODEL=USB-RLY02. E: ID_MODEL_ENC=USB-RLY02. E: ID_REVISION=0100 E: ID_SERIAL=Devantech_Ltd._USB-RLY02._00008749 E: ID_SERIAL_SHORT=00008749 E: ID_TYPE=generic E: ID_USB_INTERFACES=:020201:0a0000: E: ID_USB_INTERFACE_NUM=00 E: ID_USB_DRIVER=cdc_acm E: ID_USB_CLASS_FROM_DATABASE=Communications E: ID_PATH=pci-0000:00:14.0-usb-0:4:1.0 E: ID_PATH_TAG=pci-0000_00_14_0-usb-0_4_1_0 E: ID_MM_CANDIDATE=1 E: DEVLINKS=/dev/serial/by-path/pci-0000:00:14.0-usb-0:4:1.0 /dev/serial/by-id/usb-Devantech_Ltd._USB-RLY02._00008749-if00 E: TAGS=:systemd: E: CURRENT_TAGS=:systemd: ➡ La reconnaissance de ce périphérique est automatique sur cette machine, et on peut d\u0026rsquo;ores et déjà communiquer avec le relai via le chemin /dev/ttyACM0.\nCommunication série # Les données de communication série transitent sous forme de séquences, dont les structures sont prédéterminées5. Dans notre cas, le nombre de bits de données est de 8, il n’y aura pas de bit de parité, et on terminera avec un stop bit à 1. La vitesse de transmission est de 115200 bauds.\nSpécifications logicielles # Voici une liste très biaisée et personnelle de ce que cette bibliothèque doit fournir comme fonctionnalités.\nLangage : pour développer, nous nous tournerons vers le langage Python. Communication série : les quatre fonctionnalités primordiales de communication série pour nous sont les suivantes : ouverture et fermeture de port série, transfert et réception de données. On souhaitera s\u0026rsquo;assurer de la bonne réception des commandes série au fur et à mesure. Pouvoir exécuter ces opérations dans un thread dédié. Commander les fonctionnalités de la bibliothèque de manière thread safe : que depuis un autre thread, l\u0026rsquo;on puisse envoyer des données et en récupérer. Proposer une configuration par défaut. Fournir une documentation. Tester un minimum. Développement # Solutions # La bibliothèque PySerial6 offre des fonctionnalités d’interfaçage série. Les commandes de version et d\u0026rsquo;état nécessitent une lecture supplémentaire pour récupérer la réponse du micro contrôleur. Cela signifie un envoi de commande et une lecture de données à la suite. Pour s’assurer de la bonne activation des contacteurs, on peut vérifier que l\u0026rsquo;envoi vers le port série s\u0026rsquo;est bien passé en analysant le retour de la fonction write()7. Et si nécessaire, vérifier l\u0026rsquo;état du contacteur en interrogeant le relai avec la commande d\u0026rsquo;état.\nExécuter les opérations dans un thread sera intégré par la bibliothèque Threading8 du langage Python, tandis que la communication thread safe sera assurée par des Queues de la bibliothèque du même nom9. On utilisera les docstrings 10 pour documenter le code avec Pdoc 11.\nRésultat # Rappelons-nous qu\u0026rsquo;il s\u0026rsquo;agit d\u0026rsquo;un petit projet, l\u0026rsquo;objectif est bien d\u0026rsquo;avoir une stabilité minimale et de pouvoir facilement, d\u0026rsquo;ici quelques mois, se replonger dans le code. Les sources de cette bibliotèque ne seront pas proposées dans l\u0026rsquo;article, mais plutôt ici. La documentation est consultable une fois le dépôt téléchargé, ou à cet endroit.\nConclusion # Cette bibliothèque n\u0026rsquo;est pas en soi très complexe, mais cherche à respecter les étapes minimales de stabilité et de partage sur le long terme,\u0026hellip; Pour pouvoir l\u0026rsquo;intégrer dans de futurs projets de domotique !\nhttps://www.robot-electronics.co.uk/htm/usb_rly02tech.htm\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.robot-electronics.co.uk/files/hf115f.pdf\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.reactivated.net/writing_udev_rules.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.hcc-embedded.com/products/usb-overview/host-class-drivers/cdc-acm#:~:text=The%20CDC%2DACM%20class%20provides,as%20a%20remote%20serial%20port.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://fr.wikipedia.org/wiki/UART\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://pyserial.readthedocs.io/en/latest/pyserial_api.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://pyserial.readthedocs.io/en/latest/pyserial_api.html#serial.Serial.write\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://docs.python.org/3/library/threading.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://docs.python.org/3/library/queue.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://peps.python.org/pep-0257/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://pdoc3.github.io/pdoc/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"1 mai 2023","externalUrl":null,"permalink":"/articles/fr/relai/","section":"Articles","summary":"Présentation du développement d’une bibliothèque de contrôle de relai électrique.","title":"Relai programmable","type":"articles"},{"content":"Besoin de prototyper pour concevoir son écosystème de production, en appréhender les différentes problématiques et éviter les embuches ? Tournons-nous pour cela vers Minikube 1 ! Écosystème local, facile à maîtriser, paramétrer, la plupart des sujets pourront y être abordés : services, ingress, monitoring, pv,\u0026hellip;\nMinikube # Pour une distribution basée sur Debian, on peut directement installer le .deb à télécharger :\n$ curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube_latest_amd64.deb $ sudo dpkg -i minikube_latest_amd64.deb Une fois l\u0026rsquo;installation terminée, le cluster pourra être démarré. Une multitude d\u0026rsquo;options sont disponibles, dont une assez pratique qui vous évitera de tourner en bourrique.\n$ minikube start --disk-size 5g 😄 minikube v1.29.0 on Ubuntu 20.04 ✨ Automatically selected the docker driver. Other choices: qemu2, ssh 📌 Using Docker driver with root privileges 👍 Starting control plane node minikube in cluster minikube 🚜 Pulling base image ... 💾 Downloading Kubernetes v1.26.1 preload ... \u0026gt; preloaded-images-k8s-v18-v1...: 397.05 MiB / 397.05 MiB 100.00% 2.95 Mi 🔥 Creating docker container (CPUs=X, Memory=XXXXMB) ... 🐳 Preparing Kubernetes v1.26.1 on Docker 20.10.23 ... ▪️ Generating certificates and keys ... ▪️ Booting up control plane ... ▪️ Configuring RBAC rules ... 🔗 Configuring bridge CNI (Container Networking Interface) ... ▪️ Using image gcr.io/k8s-minikube/storage-provisioner:v5 🔎 Verifying Kubernetes components... 🌟 Enabled addons: storage-provisioner, default-storageclass ❗️ /usr/local/bin/kubectl is version 1.24.2, which may have incompatibilities with Kubernetes 1.26.1. ▪️ Want kubectl v1.26.1? Try \u0026#39;minikube kubectl -- get pods -A\u0026#39; 🏄 Done! kubectl is now configured to use \u0026#34;minikube\u0026#34; cluster and \u0026#34;default\u0026#34; namespace by default disk-size ici spécifie l\u0026rsquo;espace mémoire disque alloué à la machine virtuelle Minikube, et ne pas l\u0026rsquo;autoriser à vous en réquisitionner davantage.\nSi la rigueur vous empoigne fortement, on peut alors conseiller de supprimer toutes les configurations en cours d\u0026rsquo;exploitation, de faire place nette et ainsi (re)partir sur une page blanche.\n$ minikube delete --all --purge 🔥 Successfully deleted all profiles 💀 Successfully purged minikube directory located at - [/home/antoine/.minikube] 📌 Kicbase images have not been deleted. To delete images run: ▪️ docker rmi gcr.io/k8s-minikube/kicbase:v0.0.37 gcr.io/k8s-minikube/kicbase:v0.0.27 Et pour vérifer le tout, regardons le statut !\n$ minikube status minikube type: Control Plane host: Running kubelet: Running apiserver: Running kubeconfig: Configured Addons # Certaines fonctionnalités sont à activer une fois le démarrage amorcé. C\u0026rsquo;est le cas de l\u0026rsquo;ingress controller par exemple, pour déployer un service HTTP. Listons les tous, pour avoir une vue d\u0026rsquo;ensemble.\n$ minikube addons list |-----------------------------|----------|--------------|--------------------------------| | ADDON NAME | PROFILE | STATUS | MAINTAINER | |-----------------------------|----------|--------------|--------------------------------| [...] | helm-tiller | minikube | disabled | 3rd party (Helm) | | inaccel | minikube | disabled | 3rd party (InAccel | | | | | [info@inaccel.com]) | | ingress | minikube | disabled | Kubernetes | | ingress-dns | minikube | disabled | Google | [...] | registry-creds | minikube | disabled | 3rd party (UPMC Enterprises) | | storage-provisioner | minikube | disabled | Google | | storage-provisioner-gluster | minikube | disabled | 3rd party (Gluster) | | volumesnapshots | minikube | disabled | Kubernetes | |-----------------------------|----------|--------------|--------------------------------| On peut désormais activer l\u0026rsquo;addon à partir de son nom : ingress.\n$ minikube addons enable ingress 💡 ingress is an addon maintained by Kubernetes. For any concerns contact minikube on GitHub. You can view the list of minikube maintainers at: https://github.com/kubernetes/minikube/blob/master/OWNERS ▪️ Using image registry.k8s.io/ingress-nginx/controller:v1.5.1 ▪️ Using image registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20220916-gd32f8c343 ▪️ Using image registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20220916-gd32f8c343 🔎 Verifying ingress addon... 🌟 The \u0026#39;ingress\u0026#39; addon is enabled Les messages d\u0026rsquo;activation de l\u0026rsquo;ingress controller mentionnent l\u0026rsquo;image qui va être utilisée : registry.k8s.io/ingress-nginx/controller. On pourra alors utiliser les annotations nginx pour paramétrer le flux HTTP entrant.\napiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-name namespace: NAMESPACE annotations: nginx.ingress.kubernetes.io/enable-cors: \u0026#34;true\u0026#34; nginx.ingress.kubernetes.io/force-ssl-redirect: \u0026#34;true\u0026#34; nginx.ingress.kubernetes.io/cors-allow-methods: \u0026#34;PUT, GET, POST, OPTIONS, DELETE\u0026#34; nginx.ingress.kubernetes.io/cors-allow-origin: \u0026#34;https://example1.com, https://example2.com\u0026#34; nginx.ingress.kubernetes.io/session-cookie-conditional-samesite-none: \u0026#34;true\u0026#34; spec: rules: - host: example.com http: paths: - path: / pathType: Prefix backend: service: name: SERVICE port: number: PORT Dashboard # La ligne de commande est très pratique pour automatiser, mais pouvoir compter sur un dashboard et toutes ses informations en une seule place est tout autant important. On peut alors penser au dashboard intégré proposé par Minikube. Pour le charger, on peut lancer la commande suivante.\n$ minikube dashboard Il en existe d\u0026rsquo;autres, dont Portainer 2, qui propose un interface graphique de ce type. Pour installer un dashboard tiers, on génère un manifest à donner à Minikube. Concernant Portainer, l\u0026rsquo;installation se fait en deux temps : la mise en place de l\u0026rsquo;agent dans le cluster, et de l\u0026rsquo;interface graphique ensuite. On viendra configurer l\u0026rsquo;interface graphique pour qu\u0026rsquo;elle puisse dialoguer avec l\u0026rsquo;agent et ainsi afficher les données. L\u0026rsquo;équipe développant Portainer propose les lignes de commandes suivantes pour installer l\u0026rsquo;agent.\n$ curl -L https://downloads.portainer.io/portainer-agent-k8s-nodeport.yaml -o portainer-agent-k8s.yaml $ kubectl apply -f portainer-agent-k8s.yaml En inspectant le manifest ainsi téléchargé, on notera la mise en place d\u0026rsquo;un service nommé portainer-agent-headless en NodePort sur le port 30778.\nL\u0026rsquo;interface graphique peut se mettre en place via un conteneur Docker, partageant la connectivité du host. ⚠️ Note : ce mode opératoire reste valable pour du prototypage.\nversion: \u0026#39;2.4\u0026#39; services: portainer: image: portainer/portainer-ce:2.9.0-alpine container_name: portainer restart: unless-stopped volumes: - \u0026#34;./data:/data\u0026#34; environment: - \u0026#34;GODEBUG=x509ignoreCN=0\u0026#34; network_mode: \u0026#34;host\u0026#34; Après l\u0026rsquo;étape de configuration, on peut accéder au dahboard de notre cluster. Pour plus d\u0026rsquo;informations, voici l\u0026rsquo;architecture logicielle de Portainer3.\nRésolution DNS des services # Pour configurer les services Kubernetes et leur intercommunication — qui reste interne au cluster, on doit souvent passer en paramètres des URI, qui peuvent contenir les nom de domaines locaux au cluster. Une requête nslookup du host ne suffit pas, car le cluster est isolé. Une solution est de lancer un pod dédié à cette tâche dans le cluster et son namespace dédié.\napiVersion: v1 kind: Pod metadata: name: dnsutils namespace: NAMESPACE spec: containers: - name: dnsutils image: gcr.io/kubernetes-e2e-test-images/dnsutils:1.3 command: - sleep - \u0026#34;86400\u0026#34; imagePullPolicy: IfNotPresent restartPolicy: Always On pourra placer ce contenu dans un fichier nommé pod_dnsutils.yml. Et ensuite l\u0026rsquo;instancier avec kubectl.\n$ kubectl apply -f pod_dnsutils.yml Puis ensuite faire la requête nslookup à partir de ce pod.\n$ kubectl exec -it --namespace $NAMESPACE dnsutils -- nslookup $SERVICE Conclusion # Ces quelques étapes décrites ici permettent de rapidement mettre en place un cluster, commencer à prototyper et prendre en main les fonctionnalités de Kubernetes. J\u0026rsquo;espère qu\u0026rsquo;elles vous seront fortes utiles !\nhttps://minikube.sigs.k8s.io/docs/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.portainer.io/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://docs.portainer.io/start/architecture\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"8 avril 2023","externalUrl":null,"permalink":"/articles/fr/minikube/","section":"Articles","summary":"Rapide tutoriel sur la mise en place d’un petit cluster Kubernetes avec Minikube.","title":"Prototypage sur K8s","type":"articles"},{"content":" Introduction # Le monde numérique et la notion d\u0026rsquo;identité sont toujours très intriqués. On se rend compte que d\u0026rsquo;une part, en tant qu\u0026rsquo;utilisateurs de services numériques on fournit une quantité d\u0026rsquo;informations personnelles importante. Puis, en creusant un peu, on découvre que l\u0026rsquo;écosystème économique du monde numérique se base sur nos identités en ligne, et qu\u0026rsquo;il est difficile d\u0026rsquo;appréhender ce que nous dévoilons de nous-même. De quelle identité parle-t-on, que cela implique-t-il sur notre présence en ligne ? Tentons de répondre à ces questions.\nTout au long de cet article, on parlera d\u0026rsquo;identité réelle ou numérique, que nous définirons de la manière suivante :\nIdentité réelle : L\u0026rsquo;identité de l\u0026rsquo;individu est la reconnaissance de ce qu\u0026rsquo;il est, par lui-même ou par les autres. Nom, âge, lieu, cercle social, profession, activités,\u0026hellip; Identité numérique : Somme d\u0026rsquo;informations qui permet d\u0026rsquo;identifier de manière unique un utilisateur d\u0026rsquo;internet. Pour cela, voyons d\u0026rsquo;abord la nature de ces données : celles qui sont consciemment partagées de notre part, et les autres. Nous nous intéresserons à leur cycle de vie, leur but et à leur protection. Nous pourrons ensuite statuer sur ce que l\u0026rsquo;on laisse en ligne après notre passage.\nCataloguer les informations en partage conscient et inconscient permet dans cet argumentaire, de mettre en lumière deux aspects du monde numérique : une divulgation active de notre identité réelle d\u0026rsquo;une part, et une perméabilité d\u0026rsquo;accès à notre identité numérique.\nLe partage conscient # Nous partageons d\u0026rsquo;abord des données qui nous concernent en tant qu\u0026rsquo;individu. Qu\u0026rsquo;elles soient des documents officiels ou des photos de familles, nous avons conscience de leur existence, et nous les partageons de notre plein gré. Certaines recommandations peuvent être faites sur la manière de partager son propre contenu, mais ce n\u0026rsquo;est pas le but de cet article.\nFichage d\u0026rsquo;identité # En tant que citoyen, une partie de notre identité réelle est enregistrée pour des raisons administratives. Par exemple, ayant un passeport biométrique, nos identités sont stockées dans la base de données TES (pour Titres Électroniques Sécurisés). Voici une liste des principales bases de données concernant les citoyens Français, en y ajoutant pour l\u0026rsquo;exemple un fournisseur d\u0026rsquo;énergie et un fournisseur d\u0026rsquo;accès internet.\nBase de données Description Contenu Durée de rétention des données Titres Électroniques Sécurisés (TES) 1 Recensement citoyen Données biométriques, coordonnées et filiation Adultes : 20 ans. Mineurs : 15 ans Agence nationale des Titres Sécurisés (ANTS) 2 Réaliser des démarches administratives en ligne Selon démarche Conformément à la CNIL 3 Fichier Automatisé des Empreintes Digitales (FAED) 4 Faciliter notamment la recherche et l\u0026rsquo;identification des auteurs de crimes et de délits. Données biométriques, coordonnées et filiation 25 ans Ameli 5 Service d\u0026rsquo;assurance maladie Identité réelle et historique médical Conformément à la CNIL 6 Total Énergies 7 Fournisseur d\u0026rsquo;énergie, base de données clients Identités des clients Conformément à la CNIL 6, détails 8 Orange 9 Fournisseur d\u0026rsquo;accès internet, base de données clients Identités des clients Conformément à la CNIL 6, détails 10: en fonction de la catégorie des données. Note : Un petit mot sur France Connect11, qui n\u0026rsquo;est pas une base de données, mais un service numérique permettant de prouver notre identité. Il est soumis à la conformité de la CNIL3.\n➡ C\u0026rsquo;est consciemment que l\u0026rsquo;on laisse les agents administratifs faire leur travail et gérer nos données citoyennes, avec les outils numériques et les bases de données qui sont officiellement utilisés.\nRéseaux sociaux # Partager volontairement son quotidien sur les réseaux sociaux facilite l\u0026rsquo;accès à notre identité réelle. En fonction des paramètres de partage, il est possible que ces données se retrouvent accessibles publiquement via les moteurs de recherche. En tant que citoyens Européens, la réglementation de la CNIL 3 s\u0026rsquo;applique également aux entreprises derrière les réseaux sociaux.\n➡ Sans se concentrer sur ce sujet, une bonne pratique est de partager nos informations en les considérant comme publiques, comme si tout le monde allait les consulter. Car les réseaux sociaux sont en effet le meilleur outil numérique pour informer sur l\u0026rsquo;identité réelle des utilisateurs !\nLe partage inconscient # Vient alors un autre type de données, lié à la mécanique de l\u0026rsquo;outil numérique lui-même qui, par le principe même de connexion, génère des données. Ces données sont intrinsèques à nos équipements et elles sont partagées sans notre aval — ou presque — sans qu\u0026rsquo;on en ait conscience.\nMise en situation # Prenons pour la suite, l\u0026rsquo;exemple d\u0026rsquo;une consultation d\u0026rsquo;un site de prévisions météo, à partir d\u0026rsquo;un ordinateur à notre domicile. Nous sommes utilisateurs de l\u0026rsquo;ordinateur (1), souscrivons un abonnement internet et une box (2), et nous nous connectons à internet (3) pour accéder au site web de prévisions météorologiques (4).\nNote Par volonté de simplification, certains aspects techniques sont passés sous silence, ou sont rendus quelque peu inexacts en vue d\u0026rsquo;une compréhension générale.\nLes données de connexion # Regardons plus en détails la nature de ces données qui, à la vitesse de la lumière, s\u0026rsquo;activent pour faire fonctionner le réseau internet. Sommairement, pour récupérer les prévisions météorologiques, notre ordinateur (1) va se connecter à notre box internet (2) de notre domicile qui va prendre en main la connexion, et se connecter au site consulté (4) à travers internet (3).\nLa connexion se réalise via l\u0026rsquo;identification par adresses des équipements qui constituent internet. Par définition, la box internet (2) fait partie d\u0026rsquo;internet, mais pas notre ordinateur, qui reste en aval. Du point de vue d\u0026rsquo;internet donc, l\u0026rsquo;adresse de l\u0026rsquo;ordinateur n\u0026rsquo;est pas accessible, seule l\u0026rsquo;adresse de la box l\u0026rsquo;est car c\u0026rsquo;est elle qui prend en main cette connexion, qui l\u0026rsquo;impersonne (voir Notes).\nC\u0026rsquo;est en assignant des adresses numériques (adresses IP) aux équipements qu\u0026rsquo;ils sont capables de communiquer entre eux. Les conditions d\u0026rsquo;assignations sont différentes en fonction du type de réseau 12. Dans notre exemple irréel, la connexion sera établie entre l\u0026rsquo;ordinateur (1) à l\u0026rsquo;adresse 192.168.1.20 et le site consulté (4) à l\u0026rsquo;adresse 137.129.43.129, dont la connexion aura été impersonnée par la box dont l\u0026rsquo;adresse est 1.2.3.4. Les équipements d\u0026rsquo;aiguillage (voir Notes) qui constituent internet (3) sont là pour physiquement orienter la connexion vers le site consulté, ils agissent comme des relais.\nEn réalité, la connexion est relayée par de multiples équipements, eux aussi pourvus d\u0026rsquo;adresse IP. Dans le schéma, ils ne sont pas visibles mais sont bien présents dans l\u0026rsquo;écosystème internet (3). Si l\u0026rsquo;on trace la route de la connexion en utilisant le logiciel traceroute permettant de lister les trente premiers relais vers le site web (4), on obtient le résultat suivant :\n$ traceroute meteo.fr traceroute to meteo.fr (137.129.43.129), 30 hops max, 60 byte packets 1 box.domestique (192.168.1.1) 3.548 ms 3.477 ms 3.458 ms 2 10.169.243.5 (10.169.243.5) 28.335 ms 28.316 ms 28.297 ms 3 10.125.115.83 (10.125.115.83) 37.196 ms 37.179 ms 37.162 ms 4 10.125.120.50 (10.125.120.50) 37.198 ms 37.183 ms 37.167 ms 5 * * * 6 212.194.170.0 (212.194.170.0) 46.863 ms 38.567 ms 44.280 ms 7 be5.cbr01-cro.net.bbox.fr (212.194.171.141) 50.060 ms 27.983 ms 34.350 ms 8 * * * 9 * * * 10 206.96.106.212.in-addr.arpa.celeste.fr (212.106.96.206) 56.064 ms 56.553 ms 56.032 ms 11 7.96.106.212.in-addr.arpa.celeste.fr (212.106.96.7) 56.542 ms 56.526 ms 56.480 ms 12 149.96.106.212.in-addr.arpa.celeste.fr (212.106.96.149) 58.730 ms * * 13 * * * 14 * * * [...] 30 * * * Il se trouve que le nom de domaine meteo.fr existe et pointe vers le site web de https://meteofrance.com. Les * * * indiquent que l\u0026rsquo;information n\u0026rsquo;a pas pu être récupérée.\nOn note bien les différents relais, qui sont déjà assez nombreux. Les temps en millisecondes informent sur le temps de réponse de chaque relai.\nParmi toutes ces informations et adresses IP, c\u0026rsquo;est grâce à celle de notre box (2) que nous partageons, sans le vouloir, notre position géographique globale. En effet, en inspectant les détails avec des sites web d\u0026rsquo;analyse 13 on peut remonter à notre position. En voici un exemple.\nLes informations consultables sont :\nISP : le fournisseur d\u0026rsquo;accès internet, City : la ville, Region : la région locale, Country : le pays. ➡ Une première étape d\u0026rsquo;identification, même peu précise, devient accessible. Dans cette situation, nous n\u0026rsquo;avons pas de contrôle sur la diffusion de cette information ; c\u0026rsquo;est l\u0026rsquo;infrastructure elle-même qui semble trop bavarde. Mais en même temps, est-ce anormal ? Il est nécessaire d\u0026rsquo;adresser les équipements pour qu\u0026rsquo;ils communiquent entre eux et sans la possibilité de connaitre leur place au sein du réseau qu\u0026rsquo;est internet, la détection d\u0026rsquo;erreur et la maintenance en seraient difficiles voire impossibles.\nLes noms de domaines # Un second principe régit notre interaction à internet et ses nombreux sites web dont il est constitué. Les noms de domaines établissent une correspondance entre les adresses numériques et un nom facile à lire et se remémorer : c\u0026rsquo;est bien utile pour nos mémoires. Il est plus simple de se souvenir du nom de domaine d\u0026rsquo;un site tel que www.meteo.fr que son adresse numérique 137.129.43.129.\nDes bases de données (5) sont donc tenues, en temps réel, de tenir à jour ces correspondances. Elles sont appelées base de données DNS (pour Domain Name System) ou encore serveurs DNS. Notre ordinateur (1) va donc leur demander la correspondance numérique du site à consulter www.meteo.fr. Elles vont lui renvoyer son adresse numérique 137.129.43.129, et à ce moment-là, la connexion pourra s\u0026rsquo;initier.\nEn quoi cela informe sur notre identité d\u0026rsquo;utilisateurs ? Et bien en enregistrant sur la durée les demandes de correspondances, la base de données DNS sera capable d\u0026rsquo;observer celles émanant de la box (2) dont l\u0026rsquo;adresse IP est 1.2.3.4 et en analyser le contenu. Elle pourra forger un profil théorique de l\u0026rsquo;adresse IP 1.2.3.4 : visites quotidiennes sur un site de sport, consultations de journaux d\u0026rsquo;actualités français,\u0026hellip; et en tirer des conclusions statistiques quant à l\u0026rsquo;identité réelle de 1.2.3.4.\nDes sites web sont accessibles pour consulter ces correspondances 14. Nous pouvons écrire un nom de domaine et voir son adresse IP correspondante.\n➡ C\u0026rsquo;est encore via l\u0026rsquo;infrastructure elle-même que les utilisateurs peuvent être suivis. Un acteur malveillant peut grâce à ces informations, établir un profil utilisateur. On désigne cette pratique d\u0026rsquo;identification via l\u0026rsquo;expression anglaise profiling 15.\nLe User Agent HTTP # Lors d\u0026rsquo;une visite sur un site web, de multiples informations sont échangées. Les données de connexion déjà, puis les données liées à l\u0026rsquo;écosystème web.\nOn en revient au mécanisme de fonctionnement des sites web, qui se construit sur celui d\u0026rsquo;internet. Le mécanisme d\u0026rsquo;échange de données est tributaire d\u0026rsquo;un protocole connu : le protocole HTTP (pour Hypertext Transfer Protocol). Ce protocole, par définition définit les informations échangées pour naviguer sur les sites web.\nL\u0026rsquo;équipement utilisé — notre ordinateur dans notre exemple – va communiquer naturellement une partie de son identité logicielle en établissant la connexion HTTP. Cette partie est appelé l\u0026rsquo;agent utilisateur ou User Agent en anglais. Elle informe sur le navigateur utilisé et l\u0026rsquo;ordinateur en lui-même. En accédant à des services en ligne 16, on peut consulter notre propre User Agent.\nEn voici un exemple que nous allons décortiquer.\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; rv:110.0) Gecko/20100101 Firefox/110.0 Mozilla/5.0 informe de la compatibilité avec Mozilla, Windows NT 10.0 est la plateforme sur laquelle le navigateur tourne : Windows dans ce cas, rv:110.0 correspond à la version utilisée de Gecko, Gecko/20100101 indique que le navigateur se base sur Gecko, Firefox/110.0 est le nom du navigateur et sa version. Ces informations sont très pratiques pour adapter un site web à sa consultation sur smartphone ou ordinateur de bureau. Un site web peut donc prendre plusieurs agencements visuels selon l\u0026rsquo;équipement qui y accède.\n➡ La question en vue d\u0026rsquo;une identification est donc : est-ce suffisant pour identifier de manière unique cet équipement ? Si oui, on peut alors recouper ses différentes visites sur les sites web et ainsi établir un profil. C\u0026rsquo;est souvent insuffisant, et des techniques supplémentaires sont nécessaires.\nLes données générées # Même si les situations précédentes semblent s\u0026rsquo;apparenter à une sorte de perméabilité d\u0026rsquo;accès à des données nous concernant, il existe des techniques agressives dont leur seul but est de nous identifier. Et ce qui compte, c\u0026rsquo;est d\u0026rsquo;abord d\u0026rsquo;identifier de manière unique l\u0026rsquo;équipement que nous utilisons, pour ensuite en faire correspondre un profil d\u0026rsquo;activité.\nCes techniques sont utilisées pour la vente de publicité ciblée, qui est tributaire des informations humaines telles que le sexe, la localisation, l\u0026rsquo;âge, le travail, les médias utilisés (réseaux sociaux,\u0026hellip;), les centres d\u0026rsquo;intérêts, les comportements d\u0026rsquo;achats,\u0026hellip;\nCes informations peuvent être statistiquement devinées au long cours, après une analyse statistique de notre activité en ligne sur une durée minimale.\n➡ Les méthodes de génération de ces données ne dépendent plus de l\u0026rsquo;infrastructure réseau ou des protocoles utilisés, mais bien des éditeurs de sites web. La volonté d\u0026rsquo;identification numérique est le but et ces méthodes les moyens. Cependant tout n\u0026rsquo;est pas si tranché ; ces méthodes utilisent des principes techniques déjà mis en place dont certaines parties sont utiles voir nécessaires à la navigation.\nLes Cookies # Ce sont des fichiers stockés dans le navigateur de l\u0026rsquo;utilisateur pendant la navigation. Ils existent par site web. Ils peuvent être utilisés pour des fonctions critiques, comme la rétention d\u0026rsquo;informations d\u0026rsquo;authentification, la personnalisation du contenu visuel (langue de l\u0026rsquo;utilisateur,\u0026hellip;) ou pour stocker des données non fonctionnelles, simplement liées à l\u0026rsquo;utilisateur. Ils sont des vecteurs importants du pistage, dans la mesure où beaucoup d\u0026rsquo;informations de différentes natures peuvent être stockées. Les cookies sont globaux au navigateur ; de ce fait leur contenu est partagé entre tous les onglets.\nIls sont à diviser en deux catégories : les cookies du site sur lequel nous naviguons, et les cookies d\u0026rsquo;autres sites web – appelés cookies tiers, très souvent publicitaires. À l\u0026rsquo;heure où cet article est rédigé, les cookies tiers sont voués à disparaitre 17.\nÀ la première visite d\u0026rsquo;un site web, ce dernier va demander au navigateur de stocker en mémoire certaines informations. Si auparavant nous avons déjà visité ce même site web, alors les données déjà présentes dans ce cookie seront traitées et mises à jour.\nChaque site web qui utilise des cookies est juridiquement censé nous en avertir et nous laisser le choix du pistage, nous laisser le choix ou non de stocker ces informations que les sites nous font enregistrer. C\u0026rsquo;est pour cela que nous voyons apparaitre ce type de bandeau lors de nos navigations web.\nÀ titre d\u0026rsquo;exemple, en navigant sur un site de sport, je pourrais consulter la page sportive de ski, puis consulter le site de prévisions météo. Si les deux sites web intègrent un mécanisme de publicité ciblée, je pourrais voir apparaitre des publicités pour l\u0026rsquo;achat de skis sur le site de prévisions météo. Mais tout dépend de la nature des données enregistrées à mon insu qui révèlent mon comportement en ligne, et de la manière dont elles sont traitées.\n➡ En tant qu\u0026rsquo;utilisateur on ne maîtrise pas le rôle des cookies : authentification, tracking,\u0026hellip; Seuls les développeurs des sites web activent les cookies en fonction de leurs besoins. C\u0026rsquo;est bien sûr sans noter qu\u0026rsquo;un flou semble volontairement laissé de la part des éditeurs de sites sur leur utilisation. En tant qu\u0026rsquo;utilisateur, savoir comment sont utilisées nos données demande un effort conséquent et n\u0026rsquo;est pas direct. Il est toutefois possible avec des extensions de navigateur telles que Privacy Badger 18 de désactiver a minima les cookies tiers, qui sont principalement responsables de la publicité en ligne.\nLe Fingerprinting # L\u0026rsquo;idée est de détecter toute information de l\u0026rsquo;équipement qui peut le rendre unique. On parle alors de fingerprinting. Comme l\u0026rsquo;explique la CNIL 19, « Le fingerprinting, ou « prise d’empreinte » est une technique probabiliste visant à identifier un utilisateur de façon unique sur un site web ou une application mobile en utilisant les caractéristiques techniques de son navigateur ».\nLe site https://www.amiunique.org/ propose une vérification de l\u0026rsquo;unicité des navigateurs. Voici le résultat pour mon propre navigateur. Une multitude de paramètres logiciels et matériels sont testés. L\u0026#39;utilisation de certaines extensions de navigateurs, la résolution de l\u0026#39;écran, la version du navigateur,... Parmi toutes ces informations disponibles, leur concurrence rend mon navigateur unique et donc identifiable. Si je consulte différents sites web qui pratiquent cette technique d\u0026#39;identification, chacun d\u0026#39;eux pourra noter ma visite. Cependant, même si chaque site note indépendamment mon unicité et enregistre mon comportement associé (achats, visite de réseaux sociaux,...) ces informations d\u0026#39;utilisation restent isolées dans ces sites web, jusqu\u0026#39;à ce qu\u0026#39;elles soient remontées à des entreprises qui vont mettre en commun toutes ces informations à des fins publicitaires. On s\u0026#39;égare alors dans le monde de la publicité ciblée. ➡ L\u0026rsquo;énorme quantité de sites web utilisant ces techniques ne nous permet pas d\u0026rsquo;éviter le suivi et les analyses dont nous sommes la cible. Même si l\u0026rsquo;utilisation de certaines extensions nous rend unique, certaines sont garantes de notre sécurité et notre confort de navigation. C\u0026rsquo;est un compromis à faire entre les deux. De manière générale, utiliser le navigateur Firefox suivi des recommandations 20 de Privacy Guides semble un bon point de départ.\nLe Single Sign On # Le Single Sign On (SSO) est une méthode permettant l\u0026rsquo;accès à divers services numérique avec les mêmes identifiants. Cela facilite la navigation de l\u0026rsquo;utilisateur. On peut par exemple, en utilisant son compte Google, se connecter au site lequipe.fr.\nDe multiples sites web proposent cette fonctionnalité, qui n\u0026rsquo;est initialement pas prévue pour du ciblage publicitaire mais pour une simplification de l\u0026rsquo;authentification en ligne. Dans quelle mesure ces informations d\u0026rsquo;abonnements sont remontées à l\u0026rsquo;entreprise Google – dans cet exemple – et sont analysées pour établir un profil utilisateur ?\n➡ Le SSO est une méthode qui vise à faciliter la navigation utilisateur sur le web. Par définition, elle empêche les utilisateurs d\u0026rsquo;avoir de multiples identités sur différents services en ligne. Elle permet d\u0026rsquo;avoir une vision globale de la navigation utilisateur, et encore une fois, tout dépend du traitement de nos données et de l\u0026rsquo;écosystème associé.\nRésumé # Pour résumer, on peut lister les différents points d\u0026rsquo;attention argumentés ici et les informations personnelles qui leurs sont liées. Cette liste est non-exhaustive, il existe d\u0026rsquo;autres moyens de tracer les utilisateurs, notamment en se tournant vers les écosystèmes présents sur les smartphones.\nComposant technique / Méthode Informations liées Connexion IP Identité réelle, la position géographique Connexion HTTP Identité numérique, suivi des noms de domaines : profiling Connexion HTTP Identité numérique, informations du navigateur et du système d\u0026rsquo;exploitation Cookies Identité numérique, suivi du comportement des utilisateurs Fingerprinting Identitification unique du navigateur Single Sign On Identité numérique, suivi du comportement des utilisateurs Si un service en ligne nécessitant vos informations personnelles vous piste, le lien entre identité réelle et numérique peut être établi.\nSe protéger # Protection juridique # Depuis le mois de mai 2018 21, une législation est entrée en vigueur au sein de la juridiction européenne. Plus connu sous le nom du Réglèment Général de la Protection des Données (RGPD) ce texte apporte certains droits de regard à l\u0026rsquo;utilisateur de services numériques, si ce service utilise ou enregistre des données à caractère personnel 22.\nLe règlement s’applique à tous les organismes établis sur le territoire de l’Union Européenne, mais aussi à tout organisme implanté hors de l’UE mais dont l’activité cible directement des résidents européens.\n➡ On peut donc faire valoir ses droits au nom de la réglementation RGPD, et contacter les entreprises pour récupérer les données qu\u0026rsquo;elles ont nous concernant, voire même en supprimer l\u0026rsquo;intégralité.\nProtection technique # Des solutions permettent de diminuer la perméabilité d\u0026rsquo;accès à nos données. Même si l\u0026rsquo;anonymat complet n\u0026rsquo;est pas atteignable et que la différence doit être faite entre sécurité et confidentialité, un champ des possibles reste ouvert.\nL\u0026rsquo;anonymat est atteint lorsque l\u0026rsquo;identité n\u0026rsquo;est pas divulguée au cours d\u0026rsquo;un échange d\u0026rsquo;information et qu\u0026rsquo;on ne peut remonter à une personne. La sécurité s\u0026rsquo;attarde sur la véracité des interlocuteurs et l\u0026rsquo;intégrité des informations échangées, tandis que la confidentialité assure que seuls les interlocuteurs aient accès aux informations.\nConnexion IP # Pour obfusquer notre position géographique, on pourra citer les réseaux privés virtuels (ou VPN). L\u0026rsquo;ordinateur (1) se connecte à une machine distante (6) et y redirige sa navigation. C\u0026rsquo;est un relai qui se connecte au site web consulté (4). Il peut se situer à l\u0026rsquo;autre bout de la planète, ou dans un pays spécifique, pour éviter la censure par exemple.\nLes échanges se veulent chiffrés entre l\u0026rsquo;ordinateur (1) et le relai (6), puis les connexions entre le relai et le site web consulté (4) seront chiffrées si le site web propose un mécanisme de chiffrement.\nDu point de vue du site web, c\u0026rsquo;est le relai qui se connecte et donc ce sont ses informations de connexion qui sont analysées : fournisseur d\u0026rsquo;accès, pays, région, ville\u0026hellip; Les demandes de correspondance DNS sont généralement aussi relayées.\n➡ On ajoute un relai entre le site web et notre ordinateur (1) : nous ne sommes pas la cible du pistage par adresse\u0026hellip; Mais nous faisons confiance à ce relai, qui lui sait qui nous sommes et à quel site web nous souhaitons accéder.\nNoms de domaines # Rediriger sa connexion par un serveur VPN (6) permet de ne pas lier les demandes de correspondances (les requêtes DNS dans le jargon) avec l\u0026rsquo;adresse IP de notre box (2). Cependant, il n\u0026rsquo;est parfois pas possible de passer par un service VPN. Dans ce cas, on peut proposer de configurer son ordinateur (1) ou sa box (2) de manière à ce que les demandes de correspondance soient faites à des serveurs DNS de confiance, en évitant par exemple ceux de nos fournisseurs d\u0026rsquo;accès internet. Voici un post de blog 23 qui propose des adresses de serveurs DNS que l\u0026rsquo;on peut considérer de confiance.\n➡ Nous sommes obligatoirement tributaires du fonctionnement DNS. Pour ne pas être profilé, on peut espérer faire ces requêtes DNS à des serveurs qui ne font pas de profiling.\nSécurité # Le chiffrement est l\u0026rsquo;un des piliers de la sécurité d\u0026rsquo;échanges d\u0026rsquo;informations. Le chiffrement des échanges est primordial pour assurer que les données nous concernant soient acheminées convenablement – sans fuite — au bon interlocuteur. Les mécanismes SSL / TLS en sont garants.\nUn certificat est la preuve de l\u0026rsquo;identité d\u0026rsquo;un site web, qui est lui-même vérifié par une autorité de certification publiquement reconnue. Un certificat est intriqué avec un nom de domaine. Cette vérification est réalisée naturellement par le navigateur lors de la navigation et il saura nous alerter si elle ne peut se faire.\nPour ce site web, c\u0026rsquo;est Let\u0026rsquo;s Encrypt 24 qui assure la vérification du site. L\u0026rsquo;utilisateur peut alors être sûr qu\u0026rsquo;il échange des données avec seulement antlas.art. En pratique, aucun échange d\u0026rsquo;informations personnelle n\u0026rsquo;est demandé par mon site.\n➡ il est primordial de s\u0026rsquo;assurer de la sécurité de la connexion pour les échanges de documents officiels ou documents sensibles. De ce fait, on sait que l\u0026rsquo;on dialogue avec les bons interlocuteurs et que personne d\u0026rsquo;autre ne peut accéder aux données en cours d\u0026rsquo;échange.\nUser Agent # Il est possible de créer des User Agent factices, en utilisant l\u0026rsquo;extension Chameleon 25. Les user agents sont donc changés périodiquement et les sites web qui pistent nous voient comme de nous utilisateurs.\n➡ On peut mimiquer des navigateurs, en changer régulièrement. Cela permet de limiter le pistage, qui lui est basé sur ces informations. Cela peut altérer le visuel d\u0026rsquo;un site web si le user agent simulé est celui d\u0026rsquo;un smartphone par exemple.\nFingerprinting # Certaines options activables dans les navigateurs permettent de limiter le fingerprinting, mais il est reconnu que le plus d\u0026rsquo;extensions nous installons, plus notre identité numérique est unique et identifiable.\nFirefox 26 tente de bloquer au mieux le fingerprinting.\nConcernant la protection contre le fingerprinting, voici les options qui sont disponibles dans Firefox, via about:config accessibles via la barre de recherche. Il faut s\u0026rsquo;assurer que l\u0026rsquo;option privacy.resistFingerprinting est mise à true. Elle permet de simuler, bloquer les paramètres testés par les tentatives de fingerprinting.\n➡ C\u0026rsquo;est un bras de fer entre utilisateurs et éditeurs qui ne cesse d\u0026rsquo;évoluer avec les techniques du moment.\nGestion des cookies # Les cookies seront toujours réécrits lors de la consultation d\u0026rsquo;un site web. On peut les effacer fréquemment, pour ne pas laisser trop d\u0026rsquo;informations consultables sur notre comportement. N\u0026rsquo;oublions pas que certains cookies sont nécessaires aux fonctionnalités des sites web. Les supprimer peut rendre la navigation plus lourde, en nous demandant par exemple de re-configurer les paramètres de langue plus souvent.\nUne extension permet de supprimer les cookies de tout onglet du navigateur fermé : Cookies auto delete 27. Elle est très efficace, mais demande de se ré-authentifier assez souvent si l\u0026rsquo;on a l\u0026rsquo;habitude de fermer les onglets.\n➡ De même, c\u0026rsquo;est un compromis à faire entre protection et confort de navigation.\nConclusion # ➡ Cet article ne fait qu\u0026rsquo;effleurer le sujet de l\u0026rsquo;identité numérique, qui mèle partage d\u0026rsquo;informations voulues et protection du bavardage incessant de nos équipements. De nombreuses ressources sont disponibles sur internet pour se prémunir de cette diffusion constante qui permet de nous identifier personnellement. Un bref panorama a été présenté en décrivant les aspect techniques les plus classiques. Cet article ne fait pas foi, les techniques évoluent et les moyens de protection aussi.\nCe que l\u0026rsquo;on peut conclure, c\u0026rsquo;est qu\u0026rsquo;il existe des moyens de limiter le pistage pendant notre navigation et de rendre difficile l\u0026rsquo;accès à notre identité numérique. Nous sommes d\u0026rsquo;une manière ou d\u0026rsquo;une autre toujours suivis, et la somme de ces moyens peut s\u0026rsquo;avérer efficace pour la préserver en partie.\nMaitriser son identité réelle, c\u0026rsquo;est-à-dire partager consciemment ses informations personnelles avec attention, reste la première manière de se protéger contre les acteurs malveillants et l\u0026rsquo;exposition de soi en ligne. Préserver son identité réelle c\u0026rsquo;est également s\u0026rsquo;assurer que les échanges de données nous concernant soient sécurisés !\nMais le plus important est de déterminer envers qui nous souhaitons rester discrets, quelles informations on ne souhaite pas dévoiler pour garder au chaud notre identité réelle\u0026hellip; et cela passe par lister les outils que nous utilisons, et dans quel but. Les entités ayant accès à notre identité numérique ne sont pas forcément les mêmes que celles qui peuvent accéder à notre identité réelle.\nPour plus d\u0026rsquo;information sur la vie privée, je vous propose un lien vers Privacy guides !\nNotes # Quelques notes au long de cet article :\nL\u0026rsquo;exemple se concentre sur le web, et donc d\u0026rsquo;une sous-partie seulement de ce qu\u0026rsquo;est réellement internet. La thématique d\u0026rsquo;adressage Ethernet (niveau 2 du modèle OSI) a été passée sous silence, en se concentrant sur l\u0026rsquo;adressage IP (niveau 3). Les équipements d\u0026rsquo;aiguillage sont les routeurs. Oui la box, en jouant le rôle de relai entre le réseau local et internet, dispose en vérité de deux adresses, une pour chaque réseau. Souvent accessible à 192.168.1.254 dans le réseau domestique. Le terme « impersonner » signifie faire du NAT 28 dans le contexte de l\u0026rsquo;exemple. Même si la box (2) et les routeurs d\u0026rsquo;internet (3) relayent la connexion, les routeurs ne vont pas impersonner la connexion. Les routeurs sont là pour aiguiller la connexion, lui faire prendre les bons rails, mais le train part bien de la box. Le serveur VPN proposé ici ne joue le rôle que de proxy. https://www.interieur.gouv.fr/Archives/Archives-des-dossiers/2016-Dossiers/Le-systeme-des-titres-electroniques-securises/Qu-est-ce-que-le-systeme-TES\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://ants.gouv.fr/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.cnil.fr/fr/les-durees-de-conservation-des-donnees\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.service-public.fr/particuliers/vosdroits/F34835\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.ameli.fr/assure/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.ameli.fr/assure/protection-donnees-personnelles\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://totalenergies.com/fr\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://totalenergies.com/fr/donnees-personnelles\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.orange.fr/portail\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://c.orange.fr/pages-juridiques/donnees-personnelles.html#durees_conservation\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://franceconnect.gouv.fr/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://fr.wikipedia.org/wiki/Adresse_IP#IPv4\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://whatismyipaddress.com/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://toolbox.googleapps.com/apps/dig/#A/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://fr.wikipedia.org/wiki/Profilage_de_personnes\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://duckduckgo.com/?q=user+agent\u0026ia=answer\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.cnil.fr/fr/cookies-et-autres-traceurs/regles/alternatives-aux-cookies-tiers\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://privacybadger.org/fr/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.cnil.fr/fr/definition/fingerprinting\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.privacyguides.org/fr/desktop-browsers/#firefox\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.cnil.fr/fr/reglement-europeen-protection-donnees\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.economie.gouv.fr/entreprises/reglement-general-sur-protection-des-donnees-rgpd\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.justgeek.fr/liste-des-serveurs-dns-les-plus-rapides-et-securises-44705/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://letsencrypt.org/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://addons.mozilla.org/en-US/firefox/addon/chameleon-ext/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.mozilla.org/fr/firefox/features/block-fingerprinting/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://addons.mozilla.org/en-US/firefox/addon/cookie-autodelete/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://culture-informatique.net/cest-quoi-le-nat-cest-quoi-le-pat/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"31 mars 2023","externalUrl":null,"permalink":"/articles/fr/identitenum/","section":"Articles","summary":"Quelles informations partageons-nous en ligne, de gré ou de force ? Panorama technique des forces en présence.","title":"Identités numériques","type":"articles"},{"content":"Voici le contenu des aliases et fonction Bash que j\u0026rsquo;utilise couramment.\nSystemd alias sc=\u0026#34;sudo systemctl\u0026#34; alias sclf=\u0026#34;sudo systemctl list-unit-files\u0026#34; alias sclu=\u0026#34;sudo systemctl list-units\u0026#34; alias scs=\u0026#34;sudo systemctl status\u0026#34; alias scstart=\u0026#34;sudo systemctl start\u0026#34; alias scstop=\u0026#34;sudo systemctl stop\u0026#34; alias scres=\u0026#34;sudo systemctl restart\u0026#34; alias screl=\u0026#34;sudo systemctl reload\u0026#34; alias scenable=\u0026#34;sudo systemctl enable\u0026#34; alias scdisable=\u0026#34;sudo systemctl disable\u0026#34; alias scie=\u0026#34;sudo systemctl is-enabled\u0026#34; Iptables alias ipsi=\u0026#34;sudo iptables -L INPUT --line-n -v\u0026#34; alias ipsf=\u0026#34;sudo iptables -L FORWARD --line-n -v\u0026#34; alias ipso=\u0026#34;sudo iptables -L OUTPUT --line-n -v\u0026#34; alias ipspre=\u0026#34;sudo iptables -t nat -L PREROUTING --line-n -v\u0026#34; alias ipspost=\u0026#34;sudo iptables -t nat -L POSTROUTING --line-n -v\u0026#34; Docker alias dc=\u0026#34;docker-compose\u0026#34; alias db=\u0026#34;docker build\u0026#34; alias dv=\u0026#34;docker version\u0026#34; alias dims=\u0026#34;docker images\u0026#34; alias dim=\u0026#34;docker image\u0026#34; alias dimsd=\u0026#34;docker images -f dangling=true\u0026#34; alias dport=\u0026#34;docker port\u0026#34; alias dops=\u0026#39;docker ps -a --format \u0026#34;table {{.ID}}\\t{{.Names}}\\t{{.Ports}}\\t{{.Status}}\u0026#34; | (read -r; printf \u0026#34;%s\\n\u0026#34; \u0026#34;$REPLY\u0026#34;; sort -k 2)|grep -iv exited\u0026#39; alias dtop=\u0026#34;docker top\u0026#34; alias dstats=\u0026#34;docker stats\u0026#34; alias ddiff=\u0026#34;docker diff\u0026#34; alias dhistory=\u0026#34;docker history\u0026#34; function dallports() { for i in $(dops | tr -s \u0026#34; \u0026#34;| cut -d \u0026#34; \u0026#34; -f2|tail -n +2); do echo \u0026#34;$i :\u0026#34; docker port \u0026#34;$i\u0026#34; done } function dips() { for i in $(dops | tr -s \u0026#34; \u0026#34;| cut -d \u0026#34; \u0026#34; -f2|tail -n +2); do aa=$(docker inspect -f \u0026#39;{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}\u0026#39; $i) \u0026amp;\u0026amp; echo \u0026#34;$i: $aa\u0026#34;; done } function dipn() { for i in $(docker network ls | tr -s \u0026#34; \u0026#34; | cut -d \u0026#34; \u0026#34; -f2| tail -n +2);do bb=$(docker network inspect -f \u0026#39;{{json .Containers}}\u0026#39; $i | jq \u0026#39;.[] | .Name + \u0026#34;:\u0026#34; + .IPv4Address\u0026#39;) \u0026amp;\u0026amp; echo \u0026#34;$i: $bb\u0026#34; done } alias dcs=\u0026#34;docker ps -as\u0026#34; # docker container size alias dsdf=\u0026#34;docker system df\u0026#34; function dbash() { [[ -z \u0026#34;$1\u0026#34; ]]\\ \u0026amp;\u0026amp; echo \u0026#34;usage: dbash \u0026lt;container name\u0026gt;\u0026#34;\\ \u0026amp;\u0026amp; return 1 local container=\u0026#34;$1\u0026#34; docker exec -it $container bash } Prompt command function __prompt_command() { local EXIT=$? local pythonpath=$(which python3) local RCol=\u0026#39;\\[\\e[0m\\]\u0026#39; local BRed=\u0026#39;\\[\\e[1;41m\\]\u0026#39; local BGre=\u0026#39;\\[\\e[1;42m\\]\u0026#39; local BYel=\u0026#39;\\[\\e[1;43m\\]\u0026#39; local BBlu=\u0026#39;\\[\\e[1;44m\\]\u0026#39; local BPur=\u0026#39;\\[\\e[1;45m\\]\u0026#39; PS1=\u0026#34;${BGre}\\u${RCol}${RCol}@${BBlu}\\h:${BRed}\\W${Rcol}\u0026#34; local CodeCol=${BGre} [[ $EXIT -ne 0 ]]\\ \u0026amp;\u0026amp; CodeCol=${BRed} PS1+=\u0026#34;${CodeCol}[?=$EXIT]\\$${RCol} \u0026#34; } export PROMPT_COMMAND=__prompt_command RSync function pullsudorsync(){ [[ -z \u0026#34;$1\u0026#34; ]]\\ \u0026amp;\u0026amp; echo \u0026#34;usage: pullsudorsync \u0026lt;src\u0026gt; \u0026lt;port\u0026gt;\u0026#34;\\ \u0026amp;\u0026amp; return 255 local SOURCE=\u0026#34;$1\u0026#34; local PORT=\u0026#34;22\u0026#34; [[ -n \u0026#34;$2\u0026#34; ]]\\ \u0026amp;\u0026amp; PORT=\u0026#34;$2\u0026#34; rsync -vrlHpEAXogtzD --delete -e \u0026#34;ssh -p $PORT\u0026#34; --rsync-path=\u0026#34;sudo rsync\u0026#34; --progress \u0026#34;$SOURCE\u0026#34; ./backup } function pushsudorsync(){ [[ -z \u0026#34;$3\u0026#34; ]]\\ \u0026amp;\u0026amp; echo \u0026#34;usage: pushsudorsync \u0026lt;src\u0026gt; \u0026lt;dst\u0026gt; \u0026lt;port\u0026gt;\u0026#34;\\ \u0026amp;\u0026amp; return 255 local SRC=\u0026#34;$1\u0026#34; local DEST=\u0026#34;$2\u0026#34; local PORT=\u0026#34;22\u0026#34; [[ -n \u0026#34;$3\u0026#34; ]]\\ \u0026amp;\u0026amp; PORT=\u0026#34;$3\u0026#34; rsync -vrlHpEAXogtzD --delete -e \u0026#34;ssh -p $PORT\u0026#34; --rsync-path=\u0026#34;sudo rsync\u0026#34; --progress \u0026#34;$SRC\u0026#34; \u0026#34;$DEST\u0026#34; } Git alias gs=\u0026#34;git status .\u0026#34; # get current branch in git repo function parse_git_branch() { BRANCH=`git branch 2\u0026gt; /dev/null | sed -e \u0026#39;/^[^*]/d\u0026#39; -e \u0026#39;s/* \\(.*\\)/\\1/\u0026#39;` if [ ! \u0026#34;${BRANCH}\u0026#34; == \u0026#34;\u0026#34; ] then STAT=`parse_git_dirty` echo \u0026#34;[${BRANCH}${STAT}]\u0026#34; else echo \u0026#34;\u0026#34; fi } # get current status of git repo function parse_git_dirty { status=`git status 2\u0026gt;\u0026amp;1 | tee` dirty=`echo -n \u0026#34;${status}\u0026#34; 2\u0026gt; /dev/null | grep \u0026#34;modified:\u0026#34; \u0026amp;\u0026gt; /dev/null; echo \u0026#34;$?\u0026#34;` untracked=`echo -n \u0026#34;${status}\u0026#34; 2\u0026gt; /dev/null | grep \u0026#34;Untracked files\u0026#34; \u0026amp;\u0026gt; /dev/null; echo \u0026#34;$?\u0026#34;` ahead=`echo -n \u0026#34;${status}\u0026#34; 2\u0026gt; /dev/null | grep \u0026#34;Your branch is ahead of\u0026#34; \u0026amp;\u0026gt; /dev/null; echo \u0026#34;$?\u0026#34;` newfile=`echo -n \u0026#34;${status}\u0026#34; 2\u0026gt; /dev/null | grep \u0026#34;new file:\u0026#34; \u0026amp;\u0026gt; /dev/null; echo \u0026#34;$?\u0026#34;` renamed=`echo -n \u0026#34;${status}\u0026#34; 2\u0026gt; /dev/null | grep \u0026#34;renamed:\u0026#34; \u0026amp;\u0026gt; /dev/null; echo \u0026#34;$?\u0026#34;` deleted=`echo -n \u0026#34;${status}\u0026#34; 2\u0026gt; /dev/null | grep \u0026#34;deleted:\u0026#34; \u0026amp;\u0026gt; /dev/null; echo \u0026#34;$?\u0026#34;` bits=\u0026#39;\u0026#39; if [ \u0026#34;${renamed}\u0026#34; == \u0026#34;0\u0026#34; ]; then bits=\u0026#34;\u0026gt;${bits}\u0026#34; fi if [ \u0026#34;${ahead}\u0026#34; == \u0026#34;0\u0026#34; ]; then bits=\u0026#34;*${bits}\u0026#34; fi if [ \u0026#34;${newfile}\u0026#34; == \u0026#34;0\u0026#34; ]; then bits=\u0026#34;+${bits}\u0026#34; fi if [ \u0026#34;${untracked}\u0026#34; == \u0026#34;0\u0026#34; ]; then bits=\u0026#34;?${bits}\u0026#34; fi if [ \u0026#34;${deleted}\u0026#34; == \u0026#34;0\u0026#34; ]; then bits=\u0026#34;x${bits}\u0026#34; fi if [ \u0026#34;${dirty}\u0026#34; == \u0026#34;0\u0026#34; ]; then bits=\u0026#34;!${bits}\u0026#34; fi if [ ! \u0026#34;${bits}\u0026#34; == \u0026#34;\u0026#34; ]; then echo \u0026#34; ${bits}\u0026#34; else echo \u0026#34;\u0026#34; fi } ","date":"25 mars 2023","externalUrl":null,"permalink":"/articles/fr/memo_bash_aliases/","section":"Articles","summary":"Mémo : aliases Bash.","title":"Bash aliases","type":"articles"},{"content":" Principe # L\u0026rsquo;envoi de notifications peut être perçu comme un envoi unidirectionnel d\u0026rsquo;information d\u0026rsquo;une source vers une destination. Pratique pour alerter d\u0026rsquo;une situation ou prévenir de l\u0026rsquo;état d\u0026rsquo;un système. L\u0026rsquo;envoi d\u0026rsquo;image en plus de texte est souvent le bienvenu ; l\u0026rsquo;information peut alors prendre des formats différents.\nLogiciels # L\u0026rsquo;objectif est d\u0026rsquo;intégrer un système de notifications à un smartphone, pourvu d\u0026rsquo;un échange sécurisé et dont le contenu n\u0026rsquo;est pas stocké sur un serveur tiers. Que de contraintes à première vue, dans la mesure où l\u0026rsquo;on doit passer par le principe clients-serveur dont le serveur a le rôle de relai. Mais en dressant un rapide état des lieux, on note l\u0026rsquo;existence de différents logiciels prévus pour cela, tels que ntfy 1, gotify 2, et d\u0026rsquo;autres avec lesquels on peut construire un système de notification, tels que Telegram 3, Signal 4,\u0026hellip; Qui peut le plus peut le moins : si deux interlocuteurs peuvent communiquer entre eux, il est alors possible d\u0026rsquo;en définir un comme la source de l\u0026rsquo;information et le second comme la destination.\nCe qui compte ici sont bien les fonctionnalités et caractéristiques du serveur ; et si le protocole d\u0026rsquo;échange est standard on pourra alors utiliser n\u0026rsquo;importe quel logiciel client standard qui parle ce protocole – typiquement curl qui dialogue en HTTP.\nVoici un bref comparatif des différentes propositions. Il n\u0026rsquo;est pas exhaustif, et ne prend pas en compte toutes les solutions de messagerie instantanée dont le détail est proposé par Mark Williams sur ce site web 5.\nLogiciel Échange sécurisé Envoi d\u0026rsquo;image attachée Stockage du contenu Disponibilité Open source Activité Ntfy Oui Oui Local Android, iOS Oui Actif Gotify Oui Non, image distante Distant Android, Fdroid Oui Actif Telegram Oui Oui Distant Android, iOS Apps oui, Serveur non Pas de bilan d\u0026rsquo;activité du serveur Signal Oui Oui Local Android, iOS Oui Actif Choix d\u0026rsquo;une solution # Pour plusieurs raisons, je me tourne vers l\u0026rsquo;utilisation de Signal pour envoyer des notifications :\nOpen source, Échange sécurisé de bout-en-bout, Pas de serveur à héberger moi-même, le rendre accessible et tout ce que cela implique, Facilité d\u0026rsquo;intégration : beaucoup de gens autour de moi utilisent Signal, cela peut être intéressant d\u0026rsquo;intégrer un système de notification dans un de mes groupes. Mise en place # La mise en place de l\u0026rsquo;écosystème Signal nécessite :\nLa création d\u0026rsquo;un compte Signal, uniquement par numéro de téléphone, L\u0026rsquo;enregistrement de ce numéro, La persistence de l\u0026rsquo;environnement Signal ainsi obtenu. On peut se tourner vers le package Debian 6 embarquant l\u0026rsquo;utilitaire signal-cli 7. Une image Docker est disponible, proposant un environnement avec ce package. On pourra de ce fait travailler de manière isolée dans un conteneur tout le long de cette mise en place. Commençons par récupérer l\u0026rsquo;image.\n$ docker pull registry.gitlab.com/packaging/signal-cli/signal-cli-jre Enregistrement du numéro # La difficulté réside surtout dans l\u0026rsquo;appropriation d\u0026rsquo;un nouveau numéro de téléphone\u0026hellip; Que ce soit via une carte prépayée ou via des offres en ligne de fournisseur d\u0026rsquo;accès internet français, on en revient au même constat : il faut sortir le portefeuille.\nDès qu\u0026rsquo;un numéro – désigné NUMBER pour la suite — peut être enregistré, on peut lancer la commande register de signal-cli, via un conteneur Docker. On monte un volume pour faire persister la configuration Signal et les détails d\u0026rsquo;enregistrement.\n$ docker run -v ${PWD}/data:/root/.local/share/signal-cli:rw -it registry.gitlab.com/packaging/signal-cli/signal-cli-jre:latest -a \u0026#34;$NUMBER\u0026#34; register À l\u0026rsquo;issue de cette étape, un lien vers la résolution d\u0026rsquo;un captcha est affiché sur stdout. On doit résoudre le captcha et récupérer le token via les developer tools du navigateur, comme le précise la documentation de signal-cli 8.\nTo get the token, go to https://signalcaptchas.org/registration/generate.html For the staging environment, use: https://signalcaptchas.org/staging/registration/generate.html Check the developer tools for a redirect starting with signalcaptcha:// Everything after signalcaptcha:// is the captcha token.\nLorsque le captcha est copié dans le presse-papier, on peut le coller dans la ligne de commande suivante.\n$ docker run -v ${PWD}/data:/root/.local/share/signal-cli:rw -it registry.gitlab.com/packaging/signal-cli/signal-cli-jre:latest -a \u0026#34;$NUMBER\u0026#34; register --captcha \u0026#34;$CAPTCHA\u0026#34; On terminera la procédure d\u0026rsquo;enregistrement avec la vérification par le code SMS automatiquement reçu sur le numéro NUMBER.\n$ docker run -v ${PWD}/data:/root/.local/share/signal-cli:rw -it registry.gitlab.com/packaging/signal-cli/signal-cli-jre:latest -a \u0026#34;$NUMBER\u0026#34; verify \u0026#34;$CODE\u0026#34; Persistence des données # Les différents appels Docker montent toujours le même volume ${PWD}/data:/root/.local/share/signal-cli:rw . C\u0026rsquo;est ce répertoire que l\u0026rsquo;on devra monter dans les prochains conteneurs pour envoyer des messages.\nL\u0026rsquo;envoi de messages # On peut d\u0026rsquo;ores et déjà envoyer des messages à un second numéro TARGET préalablement enregistré sur Signal :\n$ docker run -v ${PWD}/data:/root/.local/share/signal-cli:rw -it registry.gitlab.com/packaging/signal-cli/signal-cli-jre:latest -a \u0026#34;$NUMBER\u0026#34; send -m \u0026#34;Hello, World !\u0026#34; \u0026#34;$TARGET\u0026#34; WARN ManagerImpl - No profile name set. When sending a message it\u0026#39;s recommended to set a profile name with the updateProfile command. This may become mandatory in the future. 1234567890 Et voilà, en se contentant de bash, l\u0026rsquo;envoi de notifications sécurisé de bout-en-bout vers un utilisateur ou un groupe Signal est possible.\nAutomatisation et APIs # Mais se contenter de bash n\u0026rsquo;est parfois pas une simple tâche. Si l\u0026rsquo;on veut automatiser l\u0026rsquo;envoi de certains contenus avec des règles un peu mieux conçues, on va vite se retrouver perdu. C\u0026rsquo;est à ce moment que je me tourne vers le langage Python et cherche des bibliothèques existantes, proposées par la communautée. En prenant partie de la bibliothèque signalbot 9, je vais pouvoir intégrer l\u0026rsquo;envoi de messages à un programme Python. Le prérequis principal est d\u0026rsquo;avoir accès à une API de type JSON-RPC qui wrap signal-cli.\nQui parle de serveur, parle d\u0026rsquo;accès et d\u0026rsquo;ouverture de port\u0026hellip; Cependant il n\u0026rsquo;y a pas de soucis ici : le serveur peut être mis en place localement sans accès extérieur. On va pouvoir lancer un conteneur Docker avec les détails d\u0026rsquo;enregistrement, en mode JSON-RPC, avec le docker-compose.yml suivant. Attention, on change d\u0026rsquo;image Docker 10, conformément à la documentation de la bibliothèque signalbot 11. On la récupère dans un premier temps.\n$ docker pull bbernhard/signal-cli-rest-api:latest Et on peut lancer la commande docker-compose up -d dans le même répertoire que le ficher suivant.\nversion: \u0026#34;3\u0026#34; services: signal-cli: image: bbernhard/signal-cli-rest-api:latest volumes: - /home/antoine/signal_bot/data:/home/.local/share/signal-cli:rw # chemin absolu vers le dossier persistent d\u0026#39;enregistrement environment: - MODE=json-rpc # supported modes: json-rpc, native, normal On peut créer un réseau Docker dédié pour maîtriser plus sereinement l\u0026rsquo;adressage du conteneur des APIs. Ce sera pratique lorsque l\u0026rsquo;on donnera cette adresse à notre programme Python qui s\u0026rsquo;y connectera.\nVoici un exemple de classe Python pour se connecter aux APIs et de ce fait envoyer des messages.\n#!/usr/bin/env python3 from signalbot import SignalBot [...] class SignalBotManager(Thread): def __init__(self): super().__init__() self._bot = None self._recipient = None self._config = None def set_bot_config(self, service:str, phone_number:str, recipient:str) -\u0026gt; None: self._config = { \u0026#34;signal_service\u0026#34;: service, \u0026#34;phone_number\u0026#34;: phone_number, \u0026#34;storage\u0026#34;: None, } self._recipient = recipient def get_bot(self) -\u0026gt; Optional[SignalBot]: return self._bot def get_recipient(self) -\u0026gt; Optional[str]: return self._recipient def setup(self) -\u0026gt; bool: try: self._bot = SignalBot(self._config) except Exception as e: logging.warning(str(e)) return False else: return True async def send_images(self, message:str, images:list) -\u0026gt; bool: try: await self._bot.start_typing(self._recipient) await self._bot.send( self._recipient, message, base64_attachments=images, ) except Exception as e: logging.warning(str(e)) return False else: return True finally: await self._bot.stop_typing(self._recipient) def run(self) -\u0026gt; None: self._bot.listen(self._recipient) self._bot.start() Le programme Python peut quant à lui instancier cette classe et ensuite envoyer une image :\n# create the bot manager in the main thread bm = SignalBotManager() bm.set_bot_config( service=\u0026#34;API_IP:PORT\u0026#34;, # l\u0026#39;adresse IP du serveur JSON-RPC phone_number=\u0026#34;+33123456789\u0026#34;, # NUMBER recipient=\u0026#34;+33987654321\u0026#34;, # TARGET ) bm.setup() bm.run() [...] # in another thread loop = asyncio.new_event_loop() loop.run_until_complete(bm.send_images( message=message, base64_attachments=[encoded_image.decode(\u0026#34;utf-8\u0026#34;)] ) ) loop.close() Une attention particulière sera apportée au package Python asyncio qui est utilisé par la bibliothèque signalbot. On devra s\u0026rsquo;adapter à ce comportement.\nConclusion # Au terme de cette procédure, on dispose d\u0026rsquo;un moyen de notification basé sur des logiciels open source. Le contenu de l\u0026rsquo;information est maîtrisé, autant pour son transfert que pour son stockage.\nhttps://ntfy.sh/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://gotify.net/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://telegram.org/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.signal.org/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.securemessagingapps.com/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://packaging.gitlab.io/signal-cli/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/AsamK/signal-cli/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/AsamK/signal-cli/blob/master/man/signal-cli.1.adoc#register\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://pypi.org/project/signalbot/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://hub.docker.com/r/bbernhard/signal-cli-rest-api/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/filipre/signalbot-example\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"3 mars 2023","externalUrl":null,"permalink":"/articles/fr/signalbot/","section":"Articles","summary":"Réalisation d’un bot dans l’écosystème Signal.","title":"Notifications avec Signal Messenger","type":"articles"},{"content":" Intelligence Artificielle # OpenAI est une entreprise de 2015 dédiée à la recherche en intelligence artificielle. Une intelligence artificielle a été conçue à partir de ChatGPT basé sur GPT-3.5, un modèle d\u0026rsquo;apprentissage de conversation qui génère du texte en temps réel1. Elle est entrainée sur les données accessibles d\u0026rsquo;internet. Elle a donc accès – supposons-le – à l\u0026rsquo;entièreté des données et actualités qui ont été publiées jusqu\u0026rsquo;à 2021 (voir la FAQ).\nUne interface web2 et une API sont disponibles pour interagir avec cette intelligence. Les informations personnelles collectées sont précisées dans la section « Vie privée ». Voici un résumé de mes échanges, où je dialogue en français, en anglais, sur des sujets généraux et dédiés à l\u0026rsquo;informatique pour comprendre l\u0026rsquo;apport potentiel d\u0026rsquo;une telle technologie dans la vie professionnelle quotidienne.\nIntroduction # Commencons ce tour d\u0026rsquo;horizon par des questions classiques. Laissons-là d\u0026rsquo;abord se présenter elle-même. Quel est le rôle d\u0026rsquo;une intelligence artificielle ? En particulier, pourquoi est-elle elle-même présente aujourd\u0026rsquo;hui ?\nContinuons par lui demander son avis sur l\u0026rsquo;importance de la présence de l\u0026rsquo;intelligence artificielle.\nAvec quelques précisions supplémentaires sur les possibles domaines impactés.\nOn note que les réponses sont neutres, d\u0026rsquo;abord dé-contextualisées pour décrire généralement le sujet. Ensuite l\u0026rsquo;intelligence artificielle répond à la question.\nPour terminer cette introduction, tentons une question avec une difficulté supplémentaire. La difficulté ici réside dans l\u0026rsquo;identification des évènements et de les relier entre eux, par exemple, quel âge avait Jacques Chirac lors de la sortie du film « Harry Potter et la chambre des secrets » ?\nOn ne note pas de problème à identifier les évènements, et les corréler entre eux.\nInformatique # Certaines problématiques d\u0026rsquo;un travail informatique semblent assez universelles : la génération de contenu (notamment du code), la recherche de documentation ou de solutions et l\u0026rsquo;analyse de problèmes. Voyons comment l\u0026rsquo;intelligence artificielle, que nous appellerons par abus de langage « ChatGPT », peut nous aider à optimiser notre temps passé sur ces tâches, temps pas forcément intéressant et souvent répétitif.\nGénération de contenu # Dans les tâches fastidieuses et sources d\u0026rsquo;erreur, attelons-nous à la rédaction de procédures. Très souvent, les scripts ou fichiers de configuration contiennent des erreurs minimes, non décelables au moment de leur rédaction. On travaille rapidement, et parfois les petits détails nous échappent.\nEt si on déléguait ce travail à ChatGPT, pour ensuite seulement vérifier le résultat ? Voyons ce que cela peut donner pour le déploiement dans un cluster Kubernetes d\u0026rsquo;un pod Nginx. Par réflèxe professionnel, dialoguons à partir de maintenant en anglais. Il est envisageable que les mêmes résultats soient accessibles en français.\nLe constat semble clair : la génération est rapide et cohérente au vu de la spécification. Cependant, souhaitant toujours automatiser mon travail, je me tourne presque toujours vers Ansible. Voyons ce que ChatGPT peut fournir comme playbook associé à une nouvelle demande de configuration.\nC\u0026rsquo;est encore un bon résultat, on notera toutefois qu\u0026rsquo;il est possible de générer des configurations erronées, sans que ChatGPT n\u0026rsquo;avertisse quoi que ce soit. C\u0026rsquo;est le cas si on lui demande par exemple de changer le port exposé à une valeur indisponible.\nDocumentation # La propension à ChatGPT d\u0026rsquo;expliquer ses réponses tend à faire penser qu\u0026rsquo;il serait très pratique pour l\u0026rsquo;accès aux documentations des outils open source.\nC\u0026rsquo;est en effet le cas, le temps de recherche de documentation est diminué ; on accède beaucoup plus rapidement à ce que l\u0026rsquo;on souhaite. De plus, il nous décrit les informations importantes à savoir et propose même des exemples. C\u0026rsquo;est le cas pour cette demande de résumé de documentation du module k8s d\u0026rsquo;Ansible.\nEt voici l\u0026rsquo;exemple associé :\nLe temps de recherche devient vraiment réduit, surtout pour la prise en main de nouvelles documentations.\nAnalyse technique # L\u0026rsquo;analyse de code, de fichiers logs est une tâche assez chronophage. Même avec notre meilleur ami grep, ou des stacks de logging telles que ELK, on peut passer du temps à comprendre les messages d\u0026rsquo;erreur. Voyons si ChatGPT est capable d\u0026rsquo;identifier un problème à partir d\u0026rsquo;un fichier log et de le formuler.\nQuelle réponse aurons-nous avec un problème de résolution DNS ?\nL\u0026rsquo;identification est faite, la formulation aussi, et les propositions de résolution sont très pertinentes. Il en va de même pour la demande de résolution d\u0026rsquo;une commande bash manquante.\nD\u0026rsquo;un autre ordre, il est capable d\u0026rsquo;expliquer certaines requêtes bloquées lors d\u0026rsquo;une navigation web.\nOn constate donc que pour des problématiques standardes, ChatGPT est capable d\u0026rsquo;identifier le problème, le formuler et proposer une solution.\nRespect de la vie privée # Petit statut rapide sur les conditions d\u0026rsquo;utilisation de ChatGPT. La description des données collectées est accessible sur une page dédiée 3.\nLes conversations avec ChatGPT sont potentiellements revues par l\u0026rsquo;entreprise à plusieurs fins :\nRespect des conditions d\u0026rsquo;utilisation Entrainement du moteur Voici la liste des données collectées par l\u0026rsquo;entreprise OpenAI lors des échanges avec ChatGPT :\nDonnées personnelles\nDonnées de compte Données de communication avec l\u0026rsquo;entreprise Données accessibles via les pages des réseaux sociaux (FB, Twitter,\u0026hellip;) Données d\u0026rsquo;utilisation\nLogging : fingerprint du navigateur (User-Agent, IP,\u0026hellip;) et système d\u0026rsquo;exploitation Les utilisations de ces données se bornent quant à elles à :\nMonitoring des services R\u0026amp;D interne Prévention de fraudes et de mauvaise utilisation des services Conformité juridique Les données peuvent être partagées à des partenaires associés, pour des raisons business ou légales.\n⚠️ De manière générale, il importe d\u0026rsquo;éviter de partager des données sensibles : données personnelles ou professionnelles. Pas de RIB personnel, pas de mot de passe, pas d\u0026rsquo;adresses IP publiques de votre infrastructure, etc.\nConclusion # Ce tour d\u0026rsquo;horizon montre des capacités indéniables à remplacer une partie du travail informatique. Notons que la prise en main de ChatGPT ici se réfère à une utilisation basique de génération de contenu, une recherche classique de documentation et une analyse de problème simpliste. On peut cependant déjà constater qu\u0026rsquo;une partie de notre temps peut-être optimisée. Et cela semble déjà un progrès significatif. ChatGPT est aussi capable de générer du code – sujet non couvert dans ce post – et les résultats sont eux aussi très prometteurs. ChatGPT est un chatbot, et nous répond par du contenu lisible. Il est donc nécessaire d\u0026rsquo;ajouter une brique de conversion de ses réponses pour réellement appliquer ses propositions. Même s\u0026rsquo;il est toutefois possible d\u0026rsquo;automatiser ce procédé, une attention particulière à la véracité du contenu sera nécessaire.\nPlus d\u0026rsquo;information sur la FAQ4.\nhttps://help.openai.com/en/articles/6825453-chatgpt-release-notes\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://chat.openai.com/chat\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://openai.com/privacy/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://help.openai.com/en/articles/6783457-chatgpt-faq\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"7 janvier 2023","externalUrl":null,"permalink":"/articles/fr/openai/","section":"Articles","summary":"Revue de ChatGPT sous l’angle du logiciel.","title":"Tour d'horizon de ChatGPT","type":"articles"},{"content":"Filtrer les requêtes DNS apporte plusieurs avantages :\nSécurité : maîtriser via des listes, l\u0026rsquo;ensemble des noms de domaines à éviter Confidentialité : ne pas passer par le(s) serveur(s) DNS du fournisseur d\u0026rsquo;accès, qui enregistrent notre activité Rapidité : ne pas charger les publicités (~20% du flux DNS total) et se concentrer sur le contenu utile. Instanciation d\u0026rsquo;un DNS localement # En se basant sur un fichier compose du projet PiHole1, on dispose d\u0026rsquo;une interface graphique pour paramétrer le filtrage des requêtes DNS.\nversion: \u0026#34;3\u0026#34; services: pihole: container_name: pihole image: pihole/pihole:latest # ports: # - \u0026#34;53:53/tcp\u0026#34; # - \u0026#34;53:53/udp\u0026#34; # - \u0026#34;67:67/udp\u0026#34; # - \u0026#34;80:80/tcp\u0026#34; environment: TZ: \u0026#34;Europe/Paris\u0026#34; WEBPASSWORD: \u0026#34;password\u0026#34; INTERFACE: \u0026#34;en0\u0026#34; volumes: - \u0026#34;./etc-pihole/:/etc/pihole/\u0026#34; - \u0026#34;./etc-dnsmasq.d/:/etc/dnsmasq.d/\u0026#34; # Recommended but not required (DHCP needs NET_ADMIN) # https://github.com/pi-hole/docker-pi-hole#note-on-capabilities # as I do not use the dhcp server feature, I comment it # cap_add: # - NET_ADMIN restart: unless-stopped Et on appelle $ docker-compose up pour lancer le tout.\nhttps://github.com/pi-hole/docker-pi-hole/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"28 décembre 2022","externalUrl":null,"permalink":"/articles/fr/pihole/","section":"Articles","summary":"Brève description d’un utilitaire de résolution DNS.","title":"DNS Local","type":"articles"},{"content":"Transférer des fichiers de manière sécurisée n\u0026rsquo;est pas forcément facile si l\u0026rsquo;on souhaite rester complètement maître des ses données. Une solution ? Se tourner – encore – vers l\u0026rsquo;open source. Voici le projet « Lufi »1 qui propose un outil tout en un pour des transferts sécurisés, chiffrés de bout en bout, ou « E2E encrypted ».\nPlateforme # C\u0026rsquo;est via Docker que son instanciation est proposée, et que je décris ici.\nBuild de l\u0026rsquo;image # Le build reste assez manuel : on va compiler à partir des sources les logiciels à embarquer dans une image docker Debian. Voici le docker-compose.yml. On n\u0026rsquo;oubliera pas le HEALTHCHECK.\nFROM debian:11 RUN apt update \\ \u0026amp;\u0026amp; apt install -y \\ curl \\ libmojo-sqlite-perl \\ libmojo-pg-perl \\ libpq-dev \\ git \\ build-essential \\ libssl-dev \\ libio-socket-ssl-perl \\ liblwp-protocol-https-perl \\ \u0026amp;\u0026amp; apt-get clean -y \\ \u0026amp;\u0026amp; rm -rf /var/lib/{apt,dpkg,cache,log,tmp}/* RUN cpan Carton RUN git clone https://framagit.org/fiat-tux/hat-softwares/lufi.git /usr/lufi WORKDIR /usr/lufi RUN carton install --deployment --without=test --without=mysql --without=sqlite VOLUME /usr/lufi/data /usr/lufi/files HEALTHCHECK CMD curl --fail http://localhost:8081/ || exit 1 CMD [\u0026#34;carton\u0026#34;, \u0026#34;exec\u0026#34;, \u0026#34;hypnotoad\u0026#34;, \u0026#34;-f\u0026#34;, \u0026#34;/usr/lufi/script/lufi\u0026#34;] Instanciation du container # Configuration # Son instanciation est tributaire de fichiers de configuration de la base de données – le backend, du front-end. Ces deux fichiers seront montés de manière persistante.\nNote Pour un régime de production, préférer une configuration stateless, et embarquer les fichiers de configuration directement dans le Dockerfile.\nConcernant la gestion de compte, on peut se connecter à un LDAP en coulisse, très pratique pour intégrer ce service dans un écosystème déjà mis en place.\nLe fichier de configuration du backend n\u0026rsquo;est qu\u0026rsquo;un simple descripteur de variables d\u0026rsquo;environnement pour la base de données. POSTGRES_USER=lufi POSTGRES_PASSWORD=password POSTGRES_DB=defaultdb Le fichier de configuration du service front-end quant à lui est un peu plus fourni. En voici un extrait avec les parties intéressantes, notamment de gestion de comptes. Une attention particulière sera portée au paramètre proxy, si ce service se trouve derrière un reverse proxy. Un exemple de ce fichier de configuration se trouve ici.\n# vim:set sw=4 ts=4 sts=4 ft=perl expandtab: { hypnotoad =\u0026gt; { # array of IP addresses and ports you want to listen to # you can specify a unix socket too, like \u0026#39;http+unix://%2Ftmp%2Flufi.sock\u0026#39; listen =\u0026gt; [\u0026#39;http://0.0.0.0:8081\u0026#39;], # if you use Lufi behind a reverse proxy like Nginx, you want to set proxy to 1 # if you use Lufi directly, let it commented proxy =\u0026gt; 1, }, [...] ############# # DB settings ############# # Choose what database you want to use # Valid choices are sqlite, postgresql and mysql (all lowercase) # optional, default is sqlite dbtype =\u0026gt; \u0026#39;postgresql\u0026#39;, # SQLite ONLY - only used if dbtype is set to sqlite # Define a path to the SQLite database # You can define it relative to lufi directory or set an absolute path # Remember that it has to be in a directory writable by Lufi user # optional, default is lufi.db #db_path =\u0026gt; \u0026#39;data/lufi.db\u0026#39;, # PostgreSQL ONLY - only used if dbtype is set to postgresql # These are the credentials to access the PostgreSQL database # mandatory if you choosed postgresql as dbtype pgdb =\u0026gt; { database =\u0026gt; \u0026#39;defaultdb\u0026#39;, host =\u0026gt; \u0026#39;files_transferer_database\u0026#39;, # optional, default is 5432 #port =\u0026gt; 5432, user =\u0026gt; \u0026#39;lufi\u0026#39;, pwd =\u0026gt; \u0026#39;password\u0026#39;, # https://mojolicious.org/perldoc/Mojo/Pg#max_connections # optional, default is 1 #max_connections =\u0026gt; 1, ############################################# # LDAP settings (authentication and features) ############################################# # Set `ldap` if you want that only authenticated users can upload files # Please note that everybody can still download files # optional, no default ldap =\u0026gt; { uri =\u0026gt; \u0026#39;ldap://ldap_hostname\u0026#39;, # server URI user_tree =\u0026gt; \u0026#39;dc=example,dc=com\u0026#39;, # search base DN bind_dn =\u0026gt; \u0026#39;cn=readonly,dc=example,dc=com\u0026#39;, # search bind DN bind_pwd =\u0026gt; \u0026#39;readonly\u0026#39;, # search bind password user_attr =\u0026gt; \u0026#39;uid\u0026#39;, # user attribute (uid, mail, sAMAccountName, etc.) # user_filter =\u0026gt; \u0026#39;(!(uid=ldap_user))\u0026#39;, # user filter (to exclude some users, etc.) # # optional start_tls configuration. See https://metacpan.org/pod/distribution/perl-ldap/lib/Net/LDAP.pod#start_tls # # don\u0026#39;t set or uncomment if you don\u0026#39;t want to configure it # start_tls =\u0026gt; { # verify =\u0026gt; \u0026#39;optional\u0026#39;, # clientcert =\u0026gt; \u0026#39;/etc/ssl/certs/ca-bundle.pem\u0026#39; # } }, [...] }; Lancement # Et voici le docker-compose.yml final permettant de lancer les deux containers : la base de données et le front-end.\nversion: \u0026#39;2.4\u0026#39; services: files_transferer: build: build container_name: files_transferer restart: always mem_limit: 16gb memswap_limit: 16gb volumes: - ./conf/lufi.conf:/usr/lufi/lufi.conf:ro files_transferer_database: container_name: files_transferer_database image: postgres:14 restart: always mem_limit: 16gb memswap_limit: 16gb env_file: ./conf/postgres.env volumes: - ./dbdata:/var/lib/postgresql/data Conclusion # Très pratique et implémentant les fonctionnalités classiques de transfert de fichiers (protection par mot de passe, stockage de la liste de fichiers téléchargés, date d\u0026rsquo;expiration,\u0026hellip;) ce service semble répondre aux besoins de maîtrise de ses propres informations. Pour en savoir plus, c\u0026rsquo;est sur le wiki 2.\nhttps://framagit.org/fiat-tux/hat-softwares/lufi.git\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://framagit.org/fiat-tux/hat-softwares/lufi/-/wikis/home\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"27 décembre 2022","externalUrl":null,"permalink":"/articles/fr/transfert_fichiers/","section":"Articles","summary":"Brève description d’un utilitaire de partage sécurisé de fichiers.","title":"Transfert de fichiers","type":"articles"},{"content":"Voici une liste d\u0026rsquo;aliases Bash que j\u0026rsquo;utilise plus ou moins fréquement pour administrer un cluster Kubernetes.\nfunction current_k8s_namespace(){ [[ -n \u0026#34;$CURRENT_K8S_NAMESPACE\u0026#34; ]]\\ \u0026amp;\u0026amp; echo \u0026#34;[$CURRENT_K8S_NAMESPACE]\u0026#34; } function kgetall (){ local namespace=\u0026#34;\u0026#34; [[ -n \u0026#34;$CURRENT_K8S_NAMESPACE\u0026#34; ]]\\ \u0026amp;\u0026amp; namespace=\u0026#34;$CURRENT_K8S_NAMESPACE\u0026#34; [[ -n \u0026#34;$1\u0026#34; ]]\\ \u0026amp;\u0026amp; namespace=\u0026#34;$1\u0026#34; echo -e \u0026#34;\\e[1;31mNamespace:\\e[0m $namespace\u0026#34; for i in $(kubectl api-resources --verbs=list --namespaced -o name | grep -v \u0026#34;events.events.k8s.io\u0026#34; | grep -v \u0026#34;events\u0026#34; | sort | uniq); do echo -e \u0026#34;\\e[1;34mResource kind:\\e[0m $i\u0026#34; if [ -n \u0026#34;$namespace\u0026#34; ]; then kubectl -n \u0026#34;$namespace\u0026#34; get --ignore-not-found ${i} else kubectl get --ignore-not-found ${i} fi done } function k_set_namespace(){ [[ -z \u0026#34;$1\u0026#34; ]]\\ \u0026amp;\u0026amp; return 1 export CURRENT_K8S_NAMESPACE=\u0026#34;$1\u0026#34; } function k_get_namespace(){ echo \u0026#34;$CURRENT_K8S_NAMESPACE\u0026#34; } function k(){ local namespace=\u0026#34;\u0026#34; [[ -n \u0026#34;$CURRENT_K8S_NAMESPACE\u0026#34; ]]\\ \u0026amp;\u0026amp; namespace=\u0026#34;$CURRENT_K8S_NAMESPACE\u0026#34; if [ -n \u0026#34;$namespace\u0026#34; ];then echo -e \u0026#34;\\e[1;34mNamespace:\\e[0m $namespace\u0026#34; kubectl --namespace \u0026#34;$namespace\u0026#34; $@ else kubectl $@ fi } alias kg=\u0026#34;k get\u0026#34; alias kd=\u0026#34;k describe\u0026#34; # config alias kcfg=\u0026#34;k config view\u0026#34; # namespaces alias kgn=\u0026#34;kubectl get namespaces\u0026#34; alias kdn=\u0026#34;kubectl describe namespaces\u0026#34; alias kcn=\u0026#34;kubectl create namespace\u0026#34; alias kdeln=\u0026#34;kubectl delete namespaces\u0026#34; # pods alias kgp=\u0026#34;k get pods --output=wide\u0026#34; alias kdp=\u0026#34;k describe pod\u0026#34; alias kdelp=\u0026#34;k delete pod\u0026#34; # ingress alias kgi=\u0026#34;k get ingress --all-namespaces --output=wide\u0026#34; alias kdi=\u0026#34;k describe ingress\u0026#34; alias kdeli=\u0026#34;k delete ingress\u0026#34; # nodes alias kgnodes=\u0026#34;k get nodes --output wide\u0026#34; alias kdnodes=\u0026#34;k describe nodes\u0026#34; # deployments alias ked=\u0026#34;k expose deployment\u0026#34; alias kgd=\u0026#34;k get deployment\u0026#34; alias kdd=\u0026#34;k describe deployment\u0026#34; alias kdeld=\u0026#34;k delete deployments\u0026#34; function klogd(){ local deployment=\u0026#34;$1\u0026#34; [[ -z \u0026#34;$deployment\u0026#34; ]] \\ \u0026amp;\u0026amp; echo \u0026#34;usage: klogd \u0026lt;deployment-name\u0026gt;\u0026#34; \\ \u0026amp;\u0026amp; return 255 kubectl logs \u0026#34;deployments/$deployment\u0026#34; } # services alias kgs=\u0026#34;k get services\u0026#34; alias kds=\u0026#34;k describe service\u0026#34; alias kdels=\u0026#34;k delete service\u0026#34; # storage alias kgpv=\u0026#34;k get pv\u0026#34; alias kgpvc=\u0026#34;k get pvc\u0026#34; alias kdelpv=\u0026#34;k delete pv\u0026#34; alias kdelpvc=\u0026#34;k delete pvc\u0026#34; # apply from file alias kap=\u0026#34;kubectl apply -f\u0026#34; alias kac=\u0026#34;kubectl create -f\u0026#34; # get shell to container function kpshell(){ local namespace=\u0026#34;$1\u0026#34; local pod=\u0026#34;$2\u0026#34; [[ -z \u0026#34;$pod\u0026#34; ]] \\ \u0026amp;\u0026amp; echo \u0026#34;usage: kpshell \u0026lt;namespace\u0026gt; \u0026lt;pod-name\u0026gt;\u0026#34; \\ \u0026amp;\u0026amp; return 255 kubectl -n \u0026#34;$namespace\u0026#34; exec --stdin --tty \u0026#34;$pod\u0026#34; -- /bin/bash } ","date":"10 décembre 2022","externalUrl":null,"permalink":"/articles/fr/memo_k8s_aliases/","section":"Articles","summary":"Mémo : aliases Kubernetes.","title":"K8s aliases","type":"articles"},{"content":"Proposer des services connectés fait souvent partie du coeur de métier de notre travail. Nécessitant parfois d\u0026rsquo;importantes ressources de calcul, de présentation, de communication, on se retrouve rapidement à instancier des systèmes informatiques. Un ajout de service pour l\u0026rsquo;intranet, une interface web, des APIs et des bases de données,\u0026hellip; et ce très facilement, que ce soit via des machines virtuelles ou des conteneurs.\nEt ce même à infrastructure constante, les logiciels ont une empreinte énergétique ⚡ que dans certains contextes nous devons maitriser. D\u0026#39;un point de vue plus global et à titre d\u0026#39;exemple, l\u0026#39;hiver 2022-2023, selon sa rudesse nécessitera les utilisateurs – particuliers et professionnels – d\u0026#39;adapter leur consommation électrique pendant certaines plages horaires[^0]. RTE, responsable du transport de l\u0026rsquo;électricité sur le territoire Français, propose une ressource en ligne indiquant la criticité du réseau. Elle est estimée plusieurs jours à l\u0026rsquo;avance. Si le réseau se trouve très contraint par une demande supérieure à la production, des coupures temporaires peuvent être réalisées. L\u0026rsquo;entreprise recommande déjà de diminuer sa propre consommation de 5 voire 15 points lors de situations météorologiques extrèmes. À des fins informatives et pour assumer au mieux la réduction de consommation, RTE a mis en place \u0026ldquo;le signal EcoWatt\u0026rdquo;.\nMon EcoWatt # Ce signal est global à toute la France métropolitaine, sur trois niveaux (raisonnable, moyen et critique), consultable en ligne. Le site web[^1] montre l\u0026#39;état actuel du réseau électrique français, ainsi que les estimations jusqu\u0026#39;à J\u0026#43;4. Récupération du signal # Une API1 est disponible pour récupérer programmatiquement le statut du réseau et en connaître l\u0026rsquo;état instantané. Après une création de compte, on retrouvera une documentation2, qui indiquera les modalités de connexion. Une authentification OAuth2 avec une limite de requête toutes les 15 minutes. Deux requêtes sont donc à réaliser :\nHTTP/POST avec des headers Content-Type et Authorization Basic pour récupérer le token d\u0026rsquo;authentification, HTTP/GET avec des headers Content-Type et Authorization Bearer pour accéder au signal. Dans la mesure où l\u0026rsquo;on ne maintient pas de connexion, on doit établir une requête à chaque fois que l\u0026rsquo;on souhaite prendre connaissance du signal. Rappelons que l\u0026rsquo;estimation est quotidienne ; une requête par jour peut s\u0026rsquo;avérer suffisante. Voici un exemple de script.\nActions conditionnelles # Ayant une fois déterminé la criticité énergétique, on peut déclencher des actions conditionnelles ; activer des tâches énergiques lorsque que le signal est au plus haut, passer en mode dégradé lorsqu\u0026#39;il est au plus bas. La domotique a le champ libre pour des automatisations de ce genre, avec les micro-ordinateurs. Consommation logicielle # Mais c\u0026rsquo;est bien la maitrise de la consommation logicielle qui est intéressante dans notre cas. Déterminer l\u0026rsquo;énergie utilisée par les applications logicielles est un vaste sujet, dont seules les approximations nous permettent de faire des choix. Dans le secteur des systèmes embarqués, on peut mesurer directement en tête de système la consommation instantanée et précisément observer les changements d\u0026rsquo;état – de power mode, mais pour un système plus complexe tel qu\u0026rsquo;un serveur GNU/Linux, l\u0026rsquo;identification de processus spécifiques reste non triviale. Quelques logiciels proposent des calculs réels pour approximer la consommation de chaque processus d\u0026rsquo;une machine, tels que Scaphandre3, qui se base sur l\u0026rsquo;allocation des jiffies.\nOn note que quelque soit la méthode de calcul, une corrélation semble inévitable : plus le temps CPU est utilisé, plus la consommation augmente.\nDesign logiciel # Une réponse technique réside dans le design des logiciels, dans leur capacité à utiliser efficacement les ressources matérielles. En optimisant l\u0026rsquo;empreinte mémoire et les besoins de calcul – avec par exemple asyncio, une meilleure gestion des threads, un accès à la mémoire moins gourmand\u0026hellip;\nMais l\u0026rsquo;approche ici est quelque peu différente car on pourrait souhaiter limiter la consommation énergétique, et ce temporairement. Cette contrainte impacte également la manière de scaler, de passer à l\u0026rsquo;échelle et de déployer des services pour dix ou cent fois leur capacité initiale.\nAsservissement système # Dans mon écosystème de micro-services quotidien – Docker, la configuration des services donne le contrôle sur le temps CPU disponible. Avec la bibliothèque Python pour Docker4, on peut donc programmatiquement altérer la configuration système de chaque conteneur qui s\u0026rsquo;exécute. L\u0026rsquo;extrait de la documentation mentionne plusieurs paramètres à moduler.\nupdate(**kwargs) Update resource configuration of the containers. Parameters blkio_weight (int) – Block IO (relative weight), between 10 and 1000 cpu_period (int) – Limit CPU CFS (Completely Fair Scheduler) period cpu_quota (int) – Limit CPU CFS (Completely Fair Scheduler) quota cpu_shares (int) – CPU shares (relative weight) cpuset_cpus (str) – CPUs in which to allow execution cpuset_mems (str) – MEMs in which to allow execution mem_limit (int or str) – Memory limit mem_reservation (int or str) – Memory soft limit memswap_limit (int or str) – Total memory (memory + swap), -1 to disable swap kernel_memory (int or str) – Kernel memory limit restart_policy (dict) – Restart policy dictionary Returns Dictionary containing a Warnings key. Return type (dict) Raises docker.errors.APIError – If the server returns an error. Tous les paramètres ne nous sont pas nécessaires. En se concentrant sur period, quota et cpus, on peut limiter l\u0026rsquo;exécution de conteneurs de deux manières.\ncpu_period et cpu_quota : en spécifiant un quota par rapport à la period, on fournit un ratio d\u0026rsquo;utilisation de CPU.\ncpuset_cpus : il est sinon possible de spécifier exactement quels CPUs utiliser, le premier et le troisième par exemple 0,2, comme les quatre premiers 0-3, dont voici un exemple de script.\nBranchement # Une fois que nous pouvons modifier volontairement notre consommation énergétique, le branchement entre le signal EcoWatt déclencheur et le script opérationnel peut se réaliser. Il ne sera pas décrit ici. Il s\u0026rsquo;agit-là de mettre en forme un connecteur entre la sortie du signal EcoWatt et la mise à jour de la configuration CPU. Les limites CPU en fonction du signal seront donc à établir, et dans un premier temps à qualifier : pour diminuer mon impact de 5%, quelles valeurs limites sont à spécifier ?\nConclusion # Cette procédure décrite ici propose plusieurs raisonnements coexistants dans l\u0026#39;écosystème informatique. L\u0026rsquo;impact de nos développements et déploiements et leur utilisation des ressources matérielles. Comment limiter la consommation logicielle ? L\u0026rsquo;idée ici est de compter sur la plateforme Docker qui va ensuite déléguer ces contraintes au kernel Linux. Notons que d\u0026rsquo;autres plateformes permettent cette configuration, comme Kubernetes5. Établir une hiérarchie des services à conserver selon les niveaux de criticité : vaut-il mieux limiter tous les services ou en arrêter certains au profit d\u0026rsquo;autres ? Dans le cadre d\u0026rsquo;un produit logiciel en SaaS, qu\u0026rsquo;est-il possible de faire contractuellement ? Note Note : ce mode opératoire n’est pas dédié pour un environnement de production.\nhttps://data.rte-france.com/catalog/-/api/consumption/Ecowatt/v4.0\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://data.rte-france.com/catalog/-/api/doc/user-guide/Ecowatt/4.0\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://hubblo-org.github.io/scaphandre-documentation/explanations/how-scaph-computes-per-process-power-consumption.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://docker-py.readthedocs.io/en/stable/containers.html#docker.models.containers.Container.update\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://kubernetes.io/docs/tasks/configure-pod-container/assign-cpu-resource/#specify-a-cpu-request-and-a-cpu-limit\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"17 octobre 2022","externalUrl":null,"permalink":"/articles/fr/asservissement_energetique/","section":"Articles","summary":"Intégration du signal ÉcoWatt pour une réduction d’activité de conteneurs Docker.","title":"Asservissement énergétique","type":"articles"},{"content":"Make a Docker environment accessible over TCP. Always be aware about security threats and how to protect the docker socket.\nWe need to generate certificates for the server and the client, then alter the Docker /etc/docker/daemon.json configuration:\n{ \u0026#34;tlsverify\u0026#34;: true, \u0026#34;tlscacert\u0026#34;: \u0026#34;/etc/docker/certs/ca-cert.pem\u0026#34;, \u0026#34;tlscert\u0026#34;: \u0026#34;/etc/docker/certs/server-cert.pem\u0026#34;, \u0026#34;tlskey\u0026#34;: \u0026#34;/etc/docker/certs/server-key.pem\u0026#34;, \u0026#34;host\u0026#34;: \u0026#34;tcp://X.X.X.X:PORT\u0026#34; } However this is not sufficient: adjusting the systemd unit is needed, by patching the corresponding line :\nExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock -H tcp://X.X.X.X:PORT ","date":"10 septembre 2022","externalUrl":null,"permalink":"/articles/fr/memo_docker/","section":"Articles","summary":"Mémo : rendre Docker accessible en TCP (anglais).","title":"Remote docker server","type":"articles"},{"content":" Étude de la végétation # L\u0026rsquo;index de végétation est une valeur calculée, déterminant l\u0026rsquo;état d\u0026rsquo;une plante dans son cycle de vie. Ce ratio résulte de l\u0026rsquo;observation de longueurs d\u0026rsquo;ondes émises par celle-ci. En effet, les arbres, le sol, les forêts émettent des longueurs d\u0026rsquo;ondes (des couleurs) que nos yeux peuvent capter ou non. L\u0026rsquo;index de végétation, couramment nommé « NDVI » — pour « Normalised Difference Vegetation Index », désigne la différence d\u0026rsquo;émission entre les couleurs rouge et le proche infra rouge, en anglais « NIR ».\nPlus le végétal en question émettra du rouge par rapport à l\u0026#39;infrarouge, plus son état sera considéré dégradé. Pourquoi ? Car la chlorophylle, responsable de la photosynthèse, absorbe le bleu et le rouge: le vert est alors réémis et capté par nos yeux. Moins de photosynthèse signifiera donc moins d\u0026#39;absorption et une réémission plus importante de couleur rouge. Le ratio sera donc le suivant, similaire à un calcul de contraste.\nNIR - Rouge NDVI = ------------ NIR + Rouge On parle bien ici d\u0026rsquo;intensité de couleur. Les valeurs de ce ratio déterminent, en pratique, les types de sols suivants:\nNDVI = [0.6, 1] : végétation dense NDVI = [0.2, 0.5] : champs agricoles NDVI ~ 0 : rochers, neige, sable NDVI ~ -1 : eau Dans la mesure où l\u0026rsquo;on peut donc détecter les différents types de végétation, les chercheurs peuvent donc étudier l\u0026rsquo;évolution des forêts, des champs,\u0026hellip; Mais comment récupérer ces intensités de couleurs ?\nLes satellites artificiels en orbites font des photographies par zones de différentes longueurs d\u0026rsquo;ondes. Et entre autres, les longueurs d\u0026rsquo;ondes rouge et infrarouge, qui nous intéressent. Pour calculer le NDVI il faut donc récupérer les images satellites traitant d\u0026rsquo;une zone donnée, en extraire les bandes NIR et rouge et appliquer le calcul de contraste pixel par pixel. Une nouvelle image sera créée, comme une carte, comportant une seule valeur par pixel.\nCertaines organisations proposent des cartes NDVI précalculées. Il est alors aisé pour le scientifique de les télécharger, et ensuite de les projeter sur une carte, ou une image satellite réelle.\nTélécharger les données # L\u0026rsquo;entreprise Londonienne AgroMonitoring propose des APIs de récupération de cartes NDVI. Elle propose une souscription gratuite ou payante selon la localité ou la résolution.\nOn doit définir une région d\u0026#39;intérêt, de manière programmatique ou graphique. Dans ce contexte, l\u0026#39;approche graphique est plus directe, on ne cible qu\u0026#39;une seule zone. Pour récupérer les cartes NDVI chronologiques, on va devoir automatiser le procédé pour générer des images et ensuite les calquer sur une image satellite du lieu. Cet extrait de code Python décrit l\u0026rsquo;appel à l\u0026rsquo;API en prenant en paramètre la profondeur temporelle, puis télécharge l\u0026rsquo;image si elle n\u0026rsquo;est pas sur le disque local. Il appelle en dernier lieu un script R qui a la tâche de superposer une image satellite et l\u0026rsquo;image NDVI pour la lisibilité.\ndef get_final_images(self, polygon_id:str, start_date:datetime.datetime, end_date:datetim.datetime) -\u0026gt; bool: \u0026#34;\u0026#34;\u0026#34;get NDVI rasters of a polygon id during the interval between two dates, and map them on a satellite image \u0026#34;\u0026#34;\u0026#34; # call a dedicated method to retrieve upper and lower bounds ullng, ullat, lrlng, lrlat = self._getPolygonCoords(polygon_id) start_ts = round(start_date.timestamp()) end_ts = round(end_date.timestamp()) d1 = start.strftime(\u0026#34;%d-%m-%Y\u0026#34;) d2 = end.strftime(\u0026#34;%d-%m-%Y\u0026#34;) print(f\u0026#34;Retrieving NDVI images from {d1} to {d2}\u0026#34;) # generate the query params = { \u0026#34;start\u0026#34;: start_ts, \u0026#34;end\u0026#34;: end_ts, \u0026#34;polygon_id\u0026#34;: polygon_id, \u0026#34;appid\u0026#34;: self._apitoken, \u0026#34;resolution_min\u0026#34;: 10 } url = f\u0026#34;https://api.agromonitoring.com/agro/1.0/image/search\u0026#34; try: result = requests.get(url, params=params).json() except Exception as e: print(f\u0026#34;Could not retrieve NDVI image: {e}\u0026#34;) return False if not result: print(\u0026#34;No NDVI image found...\u0026#34;) return False rlen = len(result) print(f\u0026#34;Found {rlen} NDVI images\u0026#34;) for res in result: acq_ts = int(res[\u0026#34;dt\u0026#34;]) acq_time = datetime.datetime.utcfromtimestamp(acq_ts).strftime(\u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;) sat_type = res[\u0026#34;type\u0026#34;].replace(\u0026#34; \u0026#34;,\u0026#34;_\u0026#34;) if \u0026#34;landsat\u0026#34; in sat_type.lower(): # skip it continue ndvi_stored_path = f\u0026#34;image_{polygon_id}_{acq_ts}_{sat_type}_ndvi.png\u0026#34; if not os.path.isfile(ndvi_stored_path): print(f\u0026#34;NDVI image {ndvi_stored_path} not found on disk, downloading\u0026#34;) res = requests.get(res[\u0026#34;image\u0026#34;][\u0026#34;ndvi\u0026#34;]) try: f = open(ndvi_stored_path, \u0026#34;wb\u0026#34;) f.write(res.content) except Exception as e: print(f\u0026#34;Could not write image to disk {ndvi_stored_path}: {e}\u0026#34;) return False finally: f.close() # now call a R script that maps the NDVI image on top of a satellite image ndvi_projected_path = f\u0026#34;image_{polygon_id}_{acq_ts}_{sat_type}_ndvi_projected.png\u0026#34; try: self.call_r_routine(ndvi_stored_path, ndvi_projected_path, ullng, ullat, lrlng, lrlat) except Exception as e: print(f\u0026#34;Could not execute the images mapping: {e}\u0026#34;) return False else: return True Le lien entre Python et R # L\u0026rsquo;utilisation de bibliothèques telles que Reticulate sont intéressantes pour l\u0026rsquo;appel de script Python dans la session R. Dans le sens inverse qui nous intéresse présentement, le module rpy2 répond à cette problématique d\u0026rsquo;appeler un script R dans la session Python. Mais dans ce contexte d\u0026rsquo;expérimentation, les exigences techniques ne sont pas les mêmes que pour un logiciel final à utiliser fréquemment. On se limitera ici à forker un processus sur une machine GNU/Linux.\ndef call_r_routine(self, infilename:str, outfilename:str, ullng:float, ullat:float, lrlng:float, lrlat:float): \u0026#34;\u0026#34;\u0026#34;call a R script\u0026#34;\u0026#34;\u0026#34; command = f\u0026#34;./r_process_images.R {infilename} {outfilename} {ullng} {ullat} {lrlng} {lrlat}\u0026#34; try: process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) except Exception as e: raise e output = process.communicate()[0] exitCode = process.returncode if exitCode == 0: return output else: raise RuntimeError Superposer les images # Voici le script R, qui gère le système de coordonnées, télécharge l\u0026rsquo;image satellite et calque les deux images entre elles.\n#!/usr/bin/Rscript library(raster) library(RColorBrewer) library(OpenStreetMap) library(png) args = commandArgs(trailingOnly=TRUE) if (length(args) \u0026lt; 6){ stop() } infilename \u0026lt;- args[1] outfilename \u0026lt;- args[2] ullng \u0026lt;- as.numeric(args[3]) ullat \u0026lt;- as.numeric(args[4]) lrlng \u0026lt;- as.numeric(args[5]) lrlat \u0026lt;- as.numeric(args[6]) # load the image as a raster r \u0026lt;- raster(infilename) # set the colors palette cuts \u0026lt;- seq(-1, 1, 0.1) #set breaks create_linear_palette \u0026lt;- function(x){ pal \u0026lt;- colorRampPalette(c(\u0026#34;#0000FF\u0026#34;, \u0026#34;#FF0000\u0026#34;, \u0026#34;#FFFF00\u0026#34;, \u0026#34;#00FF00\u0026#34;)) return(pal(x)) } # polygon coordinates ul \u0026lt;- c(ullat, ullng) lr \u0026lt;- c(lrlat, lrlng) ROI \u0026lt;- c(ullng, lrlng, lrlat, ullat) # set extent ext \u0026lt;- extent(ROI) extent(r) \u0026lt;- ext values(r) \u0026lt;- (values(r)/125)-1 # get map mmap \u0026lt;- openmap(ul, lr, zoom=19) crs(r) \u0026lt;- \u0026#34;+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0\u0026#34; # project to map CRS \u0026lt;- crs(raster(mmap)) r.proj \u0026lt;- projectRaster(r, raster(mmap), crs(CRS)) # plot png(file = outfilename, width = 800, height = 700) plot(mmap) plot(r.proj, alpha=0.5, add=T, col=create_linear_palette(20), breaks=cuts) dev.off() Résultat # Regardons d\u0026rsquo;abord le lieu que nous souhaitons analyser. Il s\u0026rsquo;agit d\u0026rsquo;un village de montagne. Voici l\u0026rsquo;image satellite. Et voici la carte open source.\nPour terminer on superpose la carte NDVI sur la carte open source, avec une image du NDVI d\u0026rsquo;une résolution d\u0026rsquo;un pixel pour 10 mètres. On note bien les différences de niveau de l\u0026rsquo;indicateur NDVI ; notamment sur la partie de gauche où se situent les habitations. ","date":"1 septembre 2022","externalUrl":null,"permalink":"/articles/fr/vegetation/","section":"Articles","summary":"Introduction à l’index de végétation et cartographie de données.","title":"Analyse géographique","type":"articles"},{"content":"On debian based distributions,apt is the go-to way to perform all packages operations. dpkg is as well very useful, especially when you build your own .deb packages.\nAdvanced Packaging Tool # # list installed packages apt-cache pkgnames # including missing deps apt-cache --all-names pkgnames # show information on a package apt-cache showpkg $pkg # search available packages to install apt-cache search $regexp # with full info apt-cache search --full $regexp # show a package record apt-cache show $pkg # show package sources apt-cache showsrc $pkg # show dependencies apt-cache depends $pkg # --recurse to make them recursive # show reverse dependencies # to filter with the following options # --no-pre-depends # --no-depends # --no-recommends # --no-suggests # --no-conflicts # --no-breaks # --no-replaces # --no-enhances apt-cache rdepends $pkg # show policy apt-cache policy $pkg # cache stats apt-cache stats # show every package cached apt-cache dump # more info apt-cache dumpavail # show unmet dependencies apt-cache unmet # filter with --important ","date":"1 septembre 2022","externalUrl":null,"permalink":"/articles/fr/memo_installation/","section":"Articles","summary":"Mémo : utilitaire d’installation Debian (anglais).","title":"Installation with APT","type":"articles"},{"content":" Notify when ssh-ing on a debian system # Configure the Pluggable Authentication Modules in the /etc/pam.d/ directory, specifically in the ssh file.\nAppend the following line to the file.\nsession optional pam_exec.so seteuid /home/user/notify_login.sh The script /home/user/notify_login.sh with execution rights, can do whaetever you need to.\nHere is an example of the script, which sends a notification on a Telegram chatroom.\n#!/bin/bash # notify_login.sh # sends a message on a telegram chat TOKEN=\u0026#34;\u0026#34; CHAT_ID=\u0026#34;\u0026#34; URL=\u0026#34;https://api.telegram.org/bot$TOKEN/sendMessage\u0026#34; username=${PAM_USER} ddate=$(date) MESSAGE=\u0026#34; **New Login detected** $(hostname) from : $PAM_RHOST user : $PAM_USER date : $ddate \u0026#34; function verify_login_ip() { local ip=\u0026#34;$1\u0026#34; local found=1 # not found local known_ips=$(last | tr -s \u0026#34; \u0026#34;| cut -d \u0026#34; \u0026#34; -f3|sort|uniq|grep -E \u0026#34;^[0-9]\u0026#34;) local netname=\u0026#34;\u0026#34; local warn=\u0026#34;\u0026#34; for i in $known_ips;do if [ \u0026#34;$ip\u0026#34; == \u0026#34;$i\u0026#34; ];then found=0 fi done if [ $found -eq 1 ];then netname=$(whois $ip|grep -i netname|tr -s \u0026#34; \u0026#34;| cut -d \u0026#34; \u0026#34; -f2) warn=\u0026#34;**WARNING** Logging detected from unknown ip address $ip, net-name $netname, please check.\u0026#34; curl -s -X POST \u0026#34;$URL\u0026#34; -d chat_id=\u0026#34;$CHAT_ID\u0026#34; -d text=\u0026#34;$warn\u0026#34; fi } if [ \u0026#34;${PAM_TYPE}\u0026#34; == \u0026#34;open_session\u0026#34; ]; then verify_login_ip \u0026#34;$PAM_RHOST\u0026#34; curl -s -X POST \u0026#34;$URL\u0026#34; -d chat_id=\u0026#34;$CHAT_ID\u0026#34; -d text=\u0026#34;$MESSAGE\u0026#34; fi ","date":"1 septembre 2022","externalUrl":null,"permalink":"/articles/fr/memo_login/","section":"Articles","summary":"Mémo : Notifications de connexions SSH (anglais).","title":"Login","type":"articles"},{"content":" Print certificate # openssl x509 -in cert.pem -noout -text Print information # #list all available ciphers openssl ciphers -v # list only TLSv1 ciphers openssl ciphers -v -tls1 # list only high encryption ciphers (keys larger than 128 bits) openssl ciphers -v \u0026#39;HIGH\u0026#39; # list only high encryption ciphers using the AES algorithm openssl ciphers -v \u0026#39;AES+HIGH\u0026#39; Benchmark system performance # # global test openssl speed # test rsa speeds openssl speed rsa # do the same test on a two-way SMP system openssl speed rsa -multi 2 Benchmark remote connections # # retrieve remote test.html page using only new sessions openssl s_time -connect remote.host:443 -www /test.html -new # similar, using only SSL v3 and high encryption (see # ciphers(1) man page for cipher strings) openssl s_time \\ -connect remote.host:443 -www /test.html -new \\ -ssl3 -cipher HIGH # compare relative performance of various ciphers in # 10-second tests IFS=\u0026#34;:\u0026#34; for c in $(openssl ciphers -ssl3 RSA); do echo $c openssl s_time -connect remote.host:443 \\ -www / -new -time 10 -cipher $c 2\u0026gt;\u0026amp;1 | \\ grep bytes echo done Generate a self-signed certificate # openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365 Get server certificate # openssl s_client -showcerts -servername www.example.com -connect google.com:443 \u0026lt;/dev/null ","date":"1 septembre 2022","externalUrl":null,"permalink":"/articles/fr/memo_openssl/","section":"Articles","summary":"Mémo : commandes OpenSSL récurrentes (anglais).","title":"OpenSSL reminder","type":"articles"},{"content":" Manually reporting on the system # Here is a non exhaustive list of useful command to get the global status of a system.\nlsb_release -a uname -a whoami who w df -h uptime free -h ps -eo user,comm,pid,ppid,%mem --sort -%mem journalctl -x systemctl list-units systemctl list-timers systemctl list-sockets --all systemctl list-unit-files hcitool dev cat /etc/hosts nmcli d ip a ip l ip route iptables -L ss -tlpn avahi-browse -arlt lsof -iTCP -sTCP:LISTEN ss -tu lsof -iTCP -sTCP:ESTABLISHED systemctl status fail2ban fail2ban-client status ssh fail2ban-client status ssh |grep \u0026#34;Currently banned\u0026#34; | cut -d \u0026#34;:\u0026#34; -f 2 lsmod Automatically reporting on the system # Use a systemd service.\nsystem-status.service [Unit] Description=System status [Service] Type=simple Environment=\u0026#34;NS_PASSWORD=FOO\u0026#34; ExecStart=/home/user/secu/notify_stats.py [Install] WantedBy=multi-user.target system-status.timer [Unit] Description=Daily send system status [Timer] OnCalendar=*-*-* 18:00:00 Persistent=true [Install] WantedBy=timers.target the /home/user/secu/notify_stats.py script #!/usr/bin/env python3 # sends server status by email import os import re import subprocess import smtplib import datetime from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from subprocess import PIPE, run sender = \u0026#34;reporter@example.com\u0026#34; recipients = [\u0026#34;user@example.com\u0026#34;] smtp_config = \u0026#34;stmp.example.com:587\u0026#34; if __name__ == \u0026#34;__main__\u0026#34;: cmds = { # Add or remove commands \u0026#34;Users\u0026#34;: \u0026#34;w\u0026#34;, \u0026#34;Logins\u0026#34;: \u0026#34;last | head -10\u0026#34;, \u0026#34;Memory\u0026#34;: \u0026#34;free -h\u0026#34;, \u0026#34;Disk\u0026#34;: \u0026#34;df -h\u0026#34;, \u0026#34;Fail2ban status\u0026#34;: \u0026#34;fail2ban-client status\u0026#34;, \u0026#34;Fail2ban status SSH\u0026#34;: \u0026#34;fail2ban-client status sshd\u0026#34;, \u0026#34;Fail2ban status VPN\u0026#34;: \u0026#34;fail2ban-client status openvpn\u0026#34;, \u0026#34;Fail2ban status Nginx 403\u0026#34;: \u0026#34;fail2ban-client status nginx-403\u0026#34;, \u0026#34;Fail2ban status Nginx 444\u0026#34;: \u0026#34;fail2ban-client status nginx-444\u0026#34;, \u0026#34;Fail2ban status Nginx nowordpress\u0026#34;: \u0026#34;fail2ban-client status nginx-nowordpress\u0026#34;, \u0026#34;Fail2ban status Nginx x00\u0026#34;: \u0026#34;fail2ban-client status nginx-x00\u0026#34;, \u0026#34;Fail2ban status Nginx noproxy\u0026#34;: \u0026#34;fail2ban-client status nginx-noproxy\u0026#34;, \u0026#34;Fail2ban status Nginx noscript\u0026#34;: \u0026#34;fail2ban-client status nginx-noscript\u0026#34;, \u0026#34;Connections\u0026#34;: \u0026#34;ss -tupn\u0026#34;, \u0026#34;Containers connections\u0026#34;: \u0026#34;for i in $(docker ps -a --format \\\u0026#34;table {{.ID}}\\t{{.Names}}\\t{{.Ports}}\\t{{.Status}}\\\u0026#34; | grep -iv \\\u0026#34;Exited\\\u0026#34; | awk \u0026#39;{print $2}\u0026#39;|grep -v ID);do echo \\\u0026#34;__ $i __\\\u0026#34;;nsenter -t $(docker inspect -f \u0026#39;{{.State.Pid}}\u0026#39; $i) -n ss state established -tu | tr -s \\\u0026#34; \\\u0026#34; ;done\u0026#34;, \u0026#34;Services\u0026#34;: \u0026#34;systemctl list-units | grep \u0026#39;\\.service\u0026#39; | grep running | sort\u0026#34;, \u0026#34;Services failed\u0026#34;: \u0026#34;systemctl list-units --failed\u0026#34;, } debug = { \u0026#34;Listening\u0026#34;: \u0026#34;ss -tulpn\u0026#34;, \u0026#34;Processes\u0026#34;: \u0026#34;ps -eo user,comm,pid,ppid,%mem --sort -%mem\u0026#34;, } res = {} date = datetime.datetime.now().strftime(\u0026#34;%Y-%m-%d %H:%M:%S%z\u0026#34;) for name, cmd in cmds.items(): output = subprocess.getoutput(cmd) res[name] = output debug_data = [] for name, cmd in debug.items(): output = subprocess.getoutput(cmd) debug_data.append(\u0026#34;======================================\u0026#34;) debug_data.append(output) debug_data = \u0026#34;\\n\u0026#34;.join(debug_data) lines = [] lines.append(\u0026#34;*** Status of system ***\u0026#34;) lines.append(f\u0026#34;date: {date}\\n\u0026#34;) for name, result in res.items(): lines.append(f\u0026#34;============================\\n{name}:\\n{result}\u0026#34;) message = \u0026#34;\\n\\n\\n\u0026#34;.join(lines) password = os.environ[\u0026#34;NS_PASSWORD\u0026#34;] email_subject_line = f\u0026#34;[System] status\u0026#34; for recipient in recipients: msg = MIMEMultipart() msg[\u0026#39;From\u0026#39;] = sender msg[\u0026#39;To\u0026#39;] = recipient msg[\u0026#39;Subject\u0026#39;] = email_subject_line email_body = message msg.attach(MIMEText(email_body, \u0026#39;plain\u0026#39;)) email_content = msg.as_string() server = smtplib.SMTP(smtp_config) server.starttls() server.login(sender, password) server.sendmail(sender, recipient, email_content) server.quit() ","date":"1 septembre 2022","externalUrl":null,"permalink":"/articles/fr/memo_reporting/","section":"Articles","summary":"Mémo : script de reporting d’utilisation système (anglais).","title":"System reporting","type":"articles"},{"content":"","date":"7 juin 2026","externalUrl":null,"permalink":"/tags/ai/","section":"Tags","summary":"","title":"AI","type":"tags"},{"content":"","date":"7 juin 2026","externalUrl":null,"permalink":"/","section":"Antoine L.","summary":"","title":"Antoine L.","type":"page"},{"content":"","date":"7 juin 2026","externalUrl":null,"permalink":"/articles/","section":"Articles","summary":"","title":"Articles","type":"articles"},{"content":"","date":"7 juin 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"16 mai 2026","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"16 mai 2026","externalUrl":null,"permalink":"/tags/radio/","section":"Tags","summary":"","title":"Radio","type":"tags"},{"content":"","date":"16 mai 2026","externalUrl":null,"permalink":"/categories/veille/","section":"Categories","summary":"","title":"Veille","type":"categories"},{"content":"","date":"19 avril 2026","externalUrl":null,"permalink":"/tags/curl/","section":"Tags","summary":"","title":"CURL","type":"tags"},{"content":"","date":"19 avril 2026","externalUrl":null,"permalink":"/series/memos/","section":"Series","summary":"","title":"Memos","type":"series"},{"content":"","date":"19 avril 2026","externalUrl":null,"permalink":"/tags/r%C3%A9seau/","section":"Tags","summary":"","title":"Réseau","type":"tags"},{"content":"","date":"19 avril 2026","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","date":"19 avril 2026","externalUrl":null,"permalink":"/categories/technotes/","section":"Categories","summary":"","title":"Technotes","type":"categories"},{"content":"","date":"5 avril 2026","externalUrl":null,"permalink":"/tags/linux/","section":"Tags","summary":"","title":"Linux","type":"tags"},{"content":"","date":"5 avril 2026","externalUrl":null,"permalink":"/tags/tcp/","section":"Tags","summary":"","title":"TCP","type":"tags"},{"content":"","date":"22 février 2026","externalUrl":null,"permalink":"/categories/articles/","section":"Categories","summary":"","title":"Articles","type":"categories"},{"content":"","date":"22 février 2026","externalUrl":null,"permalink":"/tags/interface-graphique/","section":"Tags","summary":"","title":"Interface Graphique","type":"tags"},{"content":"","date":"22 février 2026","externalUrl":null,"permalink":"/tags/python/","section":"Tags","summary":"","title":"Python","type":"tags"},{"content":"","date":"22 février 2026","externalUrl":null,"permalink":"/tags/qt/","section":"Tags","summary":"","title":"Qt","type":"tags"},{"content":"","date":"23 novembre 2025","externalUrl":null,"permalink":"/tags/informatique/","section":"Tags","summary":"","title":"Informatique","type":"tags"},{"content":"","date":"24 avril 2025","externalUrl":null,"permalink":"/series/communications-ma%C3%AEtris%C3%A9es/","section":"Series","summary":"","title":"Communications Maîtrisées","type":"series"},{"content":"","date":"24 avril 2025","externalUrl":null,"permalink":"/tags/docker/","section":"Tags","summary":"","title":"Docker","type":"tags"},{"content":"","date":"16 février 2025","externalUrl":null,"permalink":"/tags/proxy/","section":"Tags","summary":"","title":"Proxy","type":"tags"},{"content":"","date":"21 avril 2024","externalUrl":null,"permalink":"/tags/kubernetes/","section":"Tags","summary":"","title":"Kubernetes","type":"tags"},{"content":"","date":"24 mars 2024","externalUrl":null,"permalink":"/tags/s%C3%A9curit%C3%A9/","section":"Tags","summary":"","title":"Sécurité","type":"tags"},{"content":"","date":"10 février 2024","externalUrl":null,"permalink":"/tags/architecture/","section":"Tags","summary":"","title":"Architecture","type":"tags"},{"content":"","date":"10 février 2024","externalUrl":null,"permalink":"/tags/projet/","section":"Tags","summary":"","title":"Projet","type":"tags"},{"content":"","date":"10 février 2024","externalUrl":null,"permalink":"/tags/web/","section":"Tags","summary":"","title":"Web","type":"tags"},{"content":"","date":"6 janvier 2024","externalUrl":null,"permalink":"/tags/nostr/","section":"Tags","summary":"","title":"Nostr","type":"tags"},{"content":"","date":"8 octobre 2023","externalUrl":null,"permalink":"/tags/ci/cd/","section":"Tags","summary":"","title":"CI/CD","type":"tags"},{"content":"","date":"17 juillet 2023","externalUrl":null,"permalink":"/tags/bash/","section":"Tags","summary":"","title":"Bash","type":"tags"},{"content":"","date":"17 juillet 2023","externalUrl":null,"permalink":"/tags/debian/","section":"Tags","summary":"","title":"Debian","type":"tags"},{"content":"","date":"12 mai 2023","externalUrl":null,"permalink":"/tags/%C3%A9nergie/","section":"Tags","summary":"","title":"Énergie","type":"tags"},{"content":"","date":"1 mai 2023","externalUrl":null,"permalink":"/tags/domotique/","section":"Tags","summary":"","title":"Domotique","type":"tags"},{"content":"","date":"8 avril 2023","externalUrl":null,"permalink":"/tags/minikube/","section":"Tags","summary":"","title":"Minikube","type":"tags"},{"content":"","date":"31 mars 2023","externalUrl":null,"permalink":"/tags/num%C3%A9rique/","section":"Tags","summary":"","title":"Numérique","type":"tags"},{"content":"","date":"31 mars 2023","externalUrl":null,"permalink":"/tags/vie-priv%C3%A9e/","section":"Tags","summary":"","title":"Vie Privée","type":"tags"},{"content":"","date":"3 mars 2023","externalUrl":null,"permalink":"/tags/messaging/","section":"Tags","summary":"","title":"Messaging","type":"tags"},{"content":"","date":"28 décembre 2022","externalUrl":null,"permalink":"/tags/autoh%C3%A9bergement/","section":"Tags","summary":"","title":"Autohébergement","type":"tags"},{"content":"","date":"28 décembre 2022","externalUrl":null,"permalink":"/tags/dns/","section":"Tags","summary":"","title":"DNS","type":"tags"},{"content":"","date":"27 décembre 2022","externalUrl":null,"permalink":"/tags/http/","section":"Tags","summary":"","title":"HTTP","type":"tags"},{"content":"","date":"17 octobre 2022","externalUrl":null,"permalink":"/tags/api/","section":"Tags","summary":"","title":"API","type":"tags"},{"content":"","date":"17 octobre 2022","externalUrl":null,"permalink":"/tags/consommation/","section":"Tags","summary":"","title":"Consommation","type":"tags"},{"content":"J’explore et partage ici des expérimentations logicielles variées, alliant développement, systèmes open source, radio,\u0026hellip; Voici quelques-uns des thèmes que j’aborde :\nLangages \u0026amp; Outils : Python, Bash, Docker, Kubernetes, et Linux, pour automatiser, conteneuriser, et optimiser. Réseaux \u0026amp; DevOps : Automatisation, Protocoles (TCP, HTTP, DNS),\u0026hellip; Curiosités tech : Protocoles décentralisés, réflexions sur l’impact du numérique,\u0026hellip; J’y partage aussi quelques-unes de mes créations photographiques.\nPour en savoir plus sur mon utilisation des LLMs, voici mon manifeste de l\u0026rsquo;IA.\nBonne exploration !\n","date":"1 septembre 2022","externalUrl":null,"permalink":"/about/","section":"Articles","summary":"À Propos","title":"À Propos","type":"articles"},{"content":"","date":"1 septembre 2022","externalUrl":null,"permalink":"/tags/r/","section":"Tags","summary":"","title":"R","type":"tags"},{"content":"","date":"1 septembre 2022","externalUrl":null,"permalink":"/tags/remote-sensing/","section":"Tags","summary":"","title":"Remote Sensing","type":"tags"},{"content":"","date":"1 septembre 2022","externalUrl":null,"permalink":"/tags/reporting/","section":"Tags","summary":"","title":"Reporting","type":"tags"},{"content":"","date":"1 septembre 2022","externalUrl":null,"permalink":"/tags/ssh/","section":"Tags","summary":"","title":"SSH","type":"tags"},{"content":"","date":"1 septembre 2022","externalUrl":null,"permalink":"/tags/ssl/","section":"Tags","summary":"","title":"SSL","type":"tags"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"}]