Bibliothèque Sonos

L'écosystème Sonos a bien évolué au fil des années, avec du positif comme du négatif ( 1 | 2 ), mais tout en intégrant une multitude de services musicaux, Sonos a toujours délaissé, le nombre de fichiers musicaux locaux explorables. En l'état cette limite est toujours fixée à 65.000 fichiers alors même que l'évolution Sonos 2 permettrait certainement de s'en affranchir.

J'ai longtemps utilisé Subsonic (ou ses forks) qui permet de contourner cette limite. Mais d'une part Subsonic est payant, et surtout n'est plus maintenu.

Aujourd'hui il existe un nouveau fork open source, Navidrome, qui est compatible avec les clients Subsonic, mais hélas ne propose pas de compatibilité Sonos. Fort heureusement un autre développeur de génie a eu la bonne idée de mettre à disposition une interface, Bonob, via les API Sonos, qui va permettre de faire le lien entre Sonos et les fors de Subsonic, en l'occurrence ici Navidrome.

Si ces programmes sont installables sous Linux, Windows ou MacOS, je vais choisir la facilité en passant par Docker. Pour y parvenir je commence par installer une petite VM Ubuntu Serveur avec Docker installé, et comme mes fichiers musicaux sont sur mon Nas je vais le lier en créant un volume NFS.

On commence par installer les paquets NFS :

administrator@vm:~# sudo apt install nfs-common

Ensuite on crée le répertoire idoine et on le lie au Nas :

administrator@vm:~# sudo mkdir -pv /nas/Music
administrator@vm:~# sudo mount 192.168.0.241:/volume1/Music /nas/music

Et pour terminer cette partie on fige le montage NFS en éditant le fichier /etc/fstab et en y ajoutant une ligne :

administrator@vm:~# sudo nano /etc/fstab
192.168.0.22:/volume1/Music /nas/music nfs auto,nofail,noatime,nolock,intr,tcp,actimeo=1800 0 0

Navidrome

On commence par créer un répertoire de travail ou sera installé le cache :

administrator@vm:~# sudo mkdir -pv /navidrome/data/

On installe le docker Navidrome avec la commande suivante : (voir les version Docker Compose à fin).

sudo docker run -d \
  --name navidrome \
  --restart=unless-stopped \
  --user $(id -u):$(id -g) \
  -v /nas/music:/music \
  -v /data/navidrome:/data \
  -p 4533:4533 \
  -e ND_LOGLEVEL=info \
  deluan/navidrome:latest

Il est possible de ne pas indiquer le user à des fin de test et debug, mais je ne le conseille pas de fonctionner en root. On retrouve ici nos deux répertoires, /data/navidrome pour le cache et /nas/music pour les fichiers musicaux qui pointe sur le NAS. Il y a pas mal d'autres options plus ou moins intéressantes que l'on pourra ajouter ici ou dans un fichier de configuration.

Il ne reste plus qu'à se connecter sur http://ip_serveur:4533 ... Il est bien sur possible de passer en SSL avec un reverse proxy (un petit Docker de plus...), mais également de créer des comptes secondaire pour d'autres utilisateurs qui pourront également télécharger fichiers ou albums.

Bonob

C'est ici que ça devient intéressant pour Sonos. Et ça se passe également sous Docker avec à minima :

sudo docker run -d \
  --name bonob \
  --restart=unless-stopped \
  -e BNB_PORT=4534 \
  -e BNB_URL=http://192.168.0.33:4534 \
  -e BNB_SONOS_SERVICE_NAME=Canaletto \
  -e BNB_SONOS_SEED_HOST=192.168.0.57 \
  -e BNB_SONOS_AUTO_REGISTER=true \
  -e BNB_SONOS_DEVICE_DISCOVERY=true \
  -e BNB_SUBSONIC_URL=http://172.17.0.2:4533 \
  -p 4534:4534 \
  simojenki/bonob

Il y a quelques options qui méritent explication :

  • BNB_URL= l'url du service Bonob afin de le faire savoir à Sonos
  • BNB_SONOS_SERVICE_NAME= Le nom du service qui apparaitra dans Sonos

  • BNB_SONOS_SEED_HOST= L'IP (fixe) d'un équipement Sonos permanent

  • BNB_SUBSONIC_URL= L'url interne à docker de Navidrome

Pour le reste je vous renvoi au GitHub afin d'adapter votre configuration.

A ce stade il suffit d'aller dans l'interface Sonos et d'ajouter le service que l'on viens de créer

Et de s'authentifier avec le compte précédemment créé dans Navidrome et de profiter de votre bibliothèque (MP3, FLAC, etc). Navidrome va indexer la bibliothèque et servir de cache. Je trouve les temps de recherche excellents au regard des 192 217 fichiers de ma bibliothèque répartis dans 15 753 répertoires. Il est possible dans Navidrome de créer des listes de lecture et des favoris que l'on retrouvera sous Sonos, par contre il n'est pas possible d'explorer l'arborescence des fichiers comme le permet Sonos dans son service de base.

Selon l'échantillonnage et le transcodage souhaité il faudra peut être modifier la configuration de Navi drome pour s'y adapter.

EDIT 26/08/2023

Suite à une mise à jour j'ai refait en Docker Compose :

version: "3"
services:
  navidrome:
    container_name: c-navidrome
    image: deluan/navidrome:latest
    # user: 1000:1000 # should be owner of volumes
    ports:
      - "4533:4533"
    restart: unless-stopped
    environment:
      # Optional: put your config options customization here. Examples:
      ND_SCANSCHEDULE: 1h
      ND_LOGLEVEL: info  
      ND_SESSIONTIMEOUT: 24h
      ND_BASEURL: ""
    volumes:
      - "/data/navidrome:/data"
      - "/nas/music:/music:ro"
  bonob:
    container_name: c-bonob
    image: simojenki/bonob:latest
    user: 1000:1000 # should be owner of volumes
    ports:
      - "4534:4534"
    restart: unless-stopped
    environment:
      BNB_PORT: 4534
      # ip address of your machine running bonob
      BNB_URL: http://192.168.10.33:4534  
      BNB_SECRET: password
      BNB_SONOS_AUTO_REGISTER: "true"
      BNB_SONOS_DEVICE_DISCOVERY: "true"
      BNB_SONOS_SERVICE_ID: 246
      BNB_SONOS_SERVICE_NAME: "Canaletto Music"
      # ip address of one of your sonos devices
      BNB_SONOS_SEED_HOST: 192.168.10.115
      BNB_SUBSONIC_URL: http://192.168.10.33:4533

EDIT 27/08/2023

On peut également choisi d'installer tout ça dans un VPS. Ca se complique un peu mais rien d'insurmontable. Par contre il faudra ensuite configurer les service à la main sur Sonos. La configuration comporte quelques différences, notamment au niveau de la reconnaissance automatique des équipements Sonos et de l'url externe. A noter que je fais pointer Bonob vers l'url Docker interne de Navidrome.

version: "3"
services:
  navidrome:
    container_name: c-navidrome
    image: deluan/navidrome:latest
    ports:
      - "4533:4533"
    restart: unless-stopped
    environment:
      ND_SCANSCHEDULE: 1h
      ND_LOGLEVEL: info  
      ND_SESSIONTIMEOUT: 24h
      ND_BASEURL: ""
      ND_SPOTIFY_ID: "c9d4gsdfghsfdgdfsfhsdfghdsfghsh1"
      ND_SPOTIFY_SECRET: "611sdfhgdfshshsghsfgh075f"
      ND_DEFAULTLANGUAGE: "fr"
    volumes:
      - "/data/navidrome:/data"
      - "/nfs/music:/music:ro"
  bonob:
    container_name: c-bonob
    image: simojenki/bonob:latest
    user: 1000:1000 # should be owner of volumes
    ports:
      - "4534:4534"
    restart: unless-stopped
    environment:
      BNB_PORT: 4534
      BNB_URL: https://bonob.domain.tls
      BNB_SECRET: wrtl
      BNB_SONOS_AUTO_REGISTER: "false"
      BNB_SONOS_DEVICE_DISCOVERY: "false"
      BNB_SONOS_SERVICE_ID: 246
      BNB_SONOS_SERVICE_NAME: "Online Music"
      BNB_SUBSONIC_URL: http://172.19.0.3:4533
      BNB_ICON_FOREGROUND_COLOR: "#1db954"
      BNB_ICON_BACKGROUND_COLOR: "#121212"
      TZ: "Europe/Paris"

Je sécurise le tout avec HAProxy sous pfsense (SSL). On teste les deux URL :

  • https://music.domain.tld : Ici on a l'interface de Navidrome et la première chose à faire est de créer un compte d'administration et de vérifier que tout fonctionne.
  • https://bonob.domain.tld : Ici on obtient l'interface de Bonob ou on va trouver les information pour configurer notre système Sonos.

Conseil : au niveau du firewall/reverse proxy vous pouvez appliquer rune restriction par IP afin de ne pas laisser tt ça ouvert aux quatre vents...

Une fois ceci configuré et fonctionnel, on passe à sonos en commençant par repérer l'adresse IP d'un des équipements et on lance :

http://192.168.210.115:1400/customsd.htm

Et là on tombe sur une archaïque interface ou l'on va saisir les informations recueillies sur la page https://bonob.domain.tld :

Il suffit ensuite s'ajoute le service dans l'interface Sonos (je l'ai fait sous Windows, mais c'est surement possible depuis un mobile) et de se connecter avec l'identifiant Navidrome pour voir apparaitre la bibliothèque Navidrome sous Sonos :

Vous trouvez ça trop compliqué, je comprends. Vous pouvez toujours vous abonner à un service de musique en ligne, c'est ce que je fais avec Spotify et Quobuz, mais je voulais également profiter de quelques morceaux introuvables que j'ai convertit moi même ! Une autre possibilité est de passer par un serveur Plex, mais l'interface est bien plus lente et je trouve pratique Navidrome pour construire des playlist...

EDIT 27/08/2023

J'ai essayé de faire passer Bonob via Cloudflared dans un container Docker. Tout se passe bien, sauf qu'il qu'au final il est impossible de lire sur Sonos. A suivre.

Freebox / Unifi UDM, DHCP & IPV6

De base l'UDM / UDM Pro en mode bridge sur une Freebox fonctionne en IPV4. Ca fait le job, mais parfois on peut avoir besoin d'un adressage en IPV6. J'ai lu pas mal de choses sur ce sujet, plus ou moins précises, notamment sur ce fil et sur ce site, j'ai eu quelques difficultés de mise en œuvre et je vais essayer de faire une synthèse simple.

EDIT 15/03/2024 : Lors de mon passage de la Freebox Delta à Ultra je me suis aperçu que l'on peut maintenant sur l'UDM utiliser SLAAC sur la config coté UDM. Cela simplifie grandement l'utilisation de l'IPv6 sur le LAN et évite tout ce qui suit, dès lors que l'on a pas besoin de fixer les IP (fonctionne très bien pour le player POP et OQEE).

Pour faire simple (Unifi Network 8.1.113)

La configuration IPv6 sur INTERNET/FREE :

La configuration IPv6 sur le LAN :

EDIT 02/04/2024 : Problème dans un réseau ActiveDirectory ou le routage site to site n'est pas effectif en IPv6 et ou les serveurs AD ne répondent pas en IPv6. Sur les clients le DNS IPv6 prends le dessus, et donc on n'a plus la résolution AD. Il est possible de forcer les clients à utiliser un DNS spécifique pour le domaine AD. Mais ce que la doc MS ne dit pas, c'est qu'il faut ajouter un point avant le nom de domaine pour que cela fonctionne :

Add-DnsClientNrptRule -Namespace ".domain.tld" -NameServers "192.168.x.x"

Ainsi la résolution du domaine AD passera par le DNS local et tout le reste par le DNS défini sur l'UDM. On doit pouvoir faire ça avec les policy du serveur AD.

La version plus complexe avec plusieurs réseaux en délégation

La première chose à faire est de récupérer l'IPV6 du port WAN actif de l'UDM en s'y connectant en SSH avec la commande ip addr | grep "global dynamic" -B2 -A3 :

# ip addr | grep "global dynamic" -B2 -A3
3: eth9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc htb state UP group default qlen 10000
    link/ether 74:ac:b9:14:2d:e2 brd ff:ff:ff:ff:ff:ff
    inet 86.66.125.69/24 scope global dynamic eth9
       valid_lft 602510sec preferred_lft 602510sec
    inet6 fe80::76ec:b9xx:fe15:2df2/64 scope link
       valid_lft forever preferred_lft forever

L'IPV6 de notre port WAN est donc : fe80::76ec:b9xx:fe15:2df2/64

On va ensuite sur la page de configuration de la Freebox, dans mon cas une Delta S connectée à l'UDM avec un câble SFP+ dans l'espoir (vain) d'obtenir les 8 Gbit/s promis en 10G EPON. Donc sur http://mafreebox.freebox.fr (Paramètres de la Freebox / Configuration IPV6) et on reporte cette adresse dans le second Next Hop (et pas le premier hein !) afin de déléguer un préfixe. Et on note le préfixe associé.

On configure le port WAN de l'UDM en client DHCPv6 avec une taille de délégation de 64 :

Maintenant on va aller configurer le LAN de l'UDM. On reporte le préfixe précédemment noté au niveau IPV6 Gateway/Subnet et si on souhaite activer le serveur DHCP on défini une IP de départ et une IP de fin. Je ne l'ai pas fait car je souhaitait utiliser le DHCP de mon contrôleur de domaine Windows (pourquoi faire simple... mais idée abandonnée.). Pensez à cocher l'option RA.

A partir de là en SSH sur l'UDM on doit pouvoir pinguer en IPV6 :

# ping6 www.ibm.com
PING www.ibm.com (2a02:26f0:2b00:383::1e89): 56 data bytes
64 bytes from 2a02:26f0:2b00:383::1e89: seq=0 ttl=52 time=11.222 ms
64 bytes from 2a02:26f0:2b00:383::1e89: seq=1 ttl=52 time=11.307 ms
64 bytes from 2a02:26f0:2b00:383::1e89: seq=2 ttl=52 time=11.511 ms

Si on a pas configuré le serveur DHCP, on peut également rapidement configurer un client Windows en statique :

PS C:\Users\Lionel.SUPTEL> ping www.google.com -6
Pinging www.google.com [2a00:1450:4007:819::2004] with 32 bytes of data:
Reply from 2a00:1450:4007:819::2004: time=11ms
Reply from 2a00:1450:4007:819::2004: time=11ms
Reply from 2a00:1450:4007:819::2004: time=11ms
Reply from 2a00:1450:4007:819::2004: time=12ms

DHCP et résilience

Il me reste à configurer le serveur DHCP v6 sous Windows Serveur. Enfin, là se pose une vrai question sur les avantages et inconvénients. Actuellement j'ai un serveur Windows (VM dans un ESXi) dans une infrastructure Active Directory répartie sur plusieurs sites. Donc ça se justifie, et c'est même obligatoire au niveau du DNS.

Par contre, imaginons que demain je ne soit plus là ou dans l'incapacité de maintenir tout ça (infra IP et IoT). Il faut que l'accès internet soit résilient et puisse se passer du host ESXi . Mon idée est donc de basculer le DHCP sur l'UDM, de faire pointer les deux premiers DNS vers des serveurs AD et les deux suivants vers des DNS publics.

Mais pour cela il faut que le DHCP de l'UDM fasse aussi bien que celui de Windows. Et là ce n'est pas gagné. La lacune principale est qu'il n'y a rient pour facilement lister les baux. Sérieux ! Un jour surement dans une nouvelle interface encore plus design. A faire du beau ils en oublient souvent le fonctionnel chez Ubiquiti.

Par contre sur le DHCP WIndows j'utilise l'option 121 (Classless Static Route Option, RFC3442) qui se configure très facilement sur Windows. Sur l'UDM c'est un peu plus compliqué (mais pas plus que sur OpenSense ou Mikrotik). Heureusement il y a ici un calculateur qui va nous permettre de créer la chaine hexa que l'on va va pouvoir entrer dans les options personnalisées dans un champ texte des paramètres DHCP de l'UDM. Pourquoi faire simple !

Bien sur il est impossible d'importer les réservations DHCP de Windows vers l'UDM. Il va donc falloir se les faire à la main en cliquant sur chaque objet. Dans cette interface on peut également figer l'AP utilisé.

Il est intéressant de noter la possibilité d'affecter une IP réservée en dehors de la plage déclarée dans le DHCP. Et ça c'est intéressant. Dans un premier temps je crée un DHCP sur l'UDM avec uniquement une IP disponible et je laisse le DHCP Windows actif qui continuera à affecter les baux. Je fige les IP sur l'UDM et quand c'est terminé je peux arrêter le DHCP Windows et élargir la plage sur l'UDM, et accessoirement faire marche arrière au besoin.

Par contre coté IPv6 il n'est pas possible à ce jour de figer les IP. Ca viendra, peut-être...

Sources

 

 

Home Assistant & IP's

Suite à une configuration un peu courte de la plage de mon serveur DHCP, j'ai eu des modules Shelly (et d'autres) qui ont un peu perdu les pédales... En IT je monitore avec des outils IP comme PRTG, mais l'idée m'est venue de monitorer les modules domotique dans Home Assistant.

Pour ça il y a l'intégration ping, une intégration qui doit dater des débuts de Home Assistant et qui ne remonte pas l'IP dans les attributs. Et on verra plus loin que ce serait bien !

La première solution

On commence donc par créer un fichier ping.yaml dans nos packages et d'ajouter nos sensors :

binary_sensor:
  - platform: ping
    host: 192.168.210.63
    name: "Ping : Shelly Plug S01 | Sonos SdB"
    count: 3
    scan_interval: 30
  - platform: ping
    host: 192.168.210.78
    name: "Ping : Shelly Plug S02 | Sonos Terrasse"
    count: 3
    scan_interval: 30
  - platform: ping
    host: 192.168.210.104
    name: "Ping : Shelly Plug S03 | Congellateurs"
    count: 3
    scan_interval: 30

Ensuite on crée un groupe, et comme j'en ai beaucoup j'ai cherché quelque chose pour créer un groupe en mode willcard, genre binary_sensor.ping_* . Mais il n'existe pas grand chose et j'ai juste trouvé Groups 2.0 sous AppDaemon (mais vous n'allez pas l'installer juste pour ça !). Enfin voici le code à ajouter pour ceux qui sont habitués à la chose :

###########################################################################################
#                                                                                         #
#  Rene Tode ( [email protected] )                                                            #
#  2017/11/29 Germany                                                                     #
#                                                                                         #
#  wildcard groups                                                                        #
#                                                                                         #
#  arguments:                                                                             #
#  name: your_name                                                                        #
#  device_type: sensor # or any devicetype                                                #
#  entity_part: "any_part"                                                                #
#  entities: # list of entities                                                           #
#    - sensor.any_entity                                                                  #
#  hidden: False # or True                                                                #
#  view: True # or False                                                                  #
#  assumed_state: False # or True                                                         #
#  friendly_name: Your Friendly Name                                                      #
#  nested_view: True # or False                                                           #
#                                                                                         #
###########################################################################################

import appdaemon.plugins.hass.hassapi as hass
class create_group(hass.Hass):

  def initialize(self):
    all_entities = self.get_state(self.args["device_type"])
    entitylist = []
    for entity in all_entities:
      if self.args["entity_part"] in entity:
        entitylist.append(entity.lower())
    if "entities" in self.args:
      for entity in self.args["entities"]:
        entitylist.append(entity.lower())
    hidden = self.args["hidden"]
    view = self.args["view"]
    assumed_state = self.args["assumed_state"]
    friendly_name = self.args["friendly_name"]
    name = "group." + self.args["name"]    
    if not self.args["nested_view"]:
      self.set_state(name,state="on",attributes={"view": view,"hidden": hidden,"assumed_state": assumed_state,"friendly_name": friendly_name,"entity_id": entitylist})
    else:
      self.set_state(name + "2",state="on",attributes={"view": False,"hidden": hidden,"assumed_state": assumed_state,"friendly_name": friendly_name,"entity_id": entitylist})
      self.set_state(name,state="on",attributes={"view": True,"hidden": hidden,"assumed_state": assumed_state,"friendly_name": friendly_name,"entity_id": [name + "2"]})

Et ensuite dans apps.yaml pour créer le groupe :

pings_ip:
  module: groups
  class: create_group
  name: ping_ip
  device_type: binary_sensor
  entity_part: "ping_"
  entities:
    - sensor.group_ping
  hidden: False
  view: True
  assumed_state: False
  friendly_name: Ping IP4
  nested_view: False #creates a second group inside a viewed group  

A partir de là il est possible de créer une auto_entity_card pour visualiser nos sensors :

Quant au groupe il va nous servir à envoyer des messages afin de signaler les objets inactifs. Pour ça on va créer (Merci @mathieu...) un sensor :

    - platform: template
      sensors:
        offline_ping_sensors:
          friendly_name: 'Ping Offline'
          value_template: >
            {% set unavailable_count = states
                                    | selectattr('state','in', ['off', 'disconnected'])
                                    | selectattr('entity_id','in',state_attr('group.ping_ip','entity_id'))
                                    | map(attribute='entity_id')
                                    | list
                                    | length
            %}
            {{ unavailable_count }}
          attribute_templates:
            IP Fail: >
              {% set unavailable_list = states
                                | selectattr('state','in', ['off', 'disconnected'])
                                | selectattr('entity_id','in',state_attr('group.ping_ip','entity_id'))
                                | map(attribute='entity_id')
                                | list
                                | join('\n') 
                                | replace("binary_sensor.ping", "")
                                | replace("_", " ")
                                
              %}
              {{ unavailable_list }}

Et une automation qui sera déclenchée par ce sensor et va nous envoyer une notification :

- alias: 'IP'
  id: fc48c309-63c5-4965-bd87-fbcabc026983
  initial_state: true
  # mode: single
  trigger:
    - platform: state
      entity_id: sensor.offline_ping_sensors
  condition:
    - condition: template
      value_template: "{{ trigger.to_state.state != trigger.from_state.state }}"
  action:
    - delay: 00:01:00
    - service: notify.slack_hass_canaletto 
      data_template: 
        title: "IP Fail" 
        message: "{{now().strftime('%d/%m/%Y, %H:%M')}} > IP FAIL{{ state_attr('sensor.offline_ping_sensors','IP Fail') }} Count: {{ states('sensor.offline_ping_sensors') }}"

Le résultat est basic et se borne à nous envoyer la liste des modules injoignables, c'est intéressant mais ça ne mets pas en évidence un module qui tombe à un instant t :

De plus j'aimerait bien que les notifications soient cliquables afin d'ouvrir la page web des modules Shelly. J'ai donc remis @mathieu à contribution et apres une bonne nuit de sommeil on est partit sur une autre solution complémentaire.

Le plan B

On conserve nos sensors ping, mais on les renomme afin d'y intégrer le dernier digit de l'IP dans le nom, ce qui va nous donner. On est obligé d'écrire cette IP dans le nom car on ne la retrouve pas dans les attributs du sensor :

  - platform: ping
    host: 192.168.210.58
    name: "Ping 058 : Yeelink 01 | Desk"
    count: 3
    scan_interval: 30

On remarque que l'IP 58 devient 058 afin de conserver un alignement correct dans notre auto_entity_card et on va traiter ça dans l'automation ou vois apprécierait le template du message. Cette option nous impose de lister tous nos sensors dans l'automation, en attendant de trouver une façon de faire appel à un groupe ou le fichier des sensors :

- alias: 'Ping Offline'
  id: '08b1556d-d816-4879-b911-bd83213dd150'
  initial_state: true
  mode: single
  trigger:
    - platform: state
      entity_id:
        - binary_sensor.ping_127_shelly_plug_s09_udm
        - binary_sensor.ping_058_yeelight_01_desk
  condition:
    - condition: template
      value_template: "{{ trigger.to_state.state != trigger.from_state.state }}"
  action:
    - service: notify.slack_hass_canaletto 
      data_template: 
        title: "IP Fail" 
        message: '{{now().strftime("%d/%m/%Y, %H:%M")}} > {{ trigger.to_state.state|replace("on", "IP UP")|replace("off", "IP DOWN") }} : {{ trigger.to_state.attributes.friendly_name }} : {{ trigger.entity_id | replace("_0","",1) | replace("_1","1",1) | replace("_2","2",1) | replace("binary_sensor.ping", "http://192.168.210.") | replace("_"," ",1) | truncate(24, True,"") }}'

Au passage une astuce pour récupérer une liste d'entités, vous collez ça dans Outils de développement / Templates, ça vous évitera de vous coller la liste à la main :

{% for state in states %}
{{ state.entity_id }}
{%- endfor -%}

Et le résultat est maintenant uniquement sur le module défaillant, avec son url :

Il reste quelques petites choses d'ordre cosmétique, mais ça correspond maintenant à ce que j'attendais. J'envoie ces messages dans un fil Slack qui me sert de log dynamique que je regarde quand j'ai quelque chose en panne, en opposition aux notification par SMS (urgentes) ou via l'application.

Encore merci à Mathieu qui m'a bien aidé afin de mettre en code mes idées, et à la communauté afin d'améliorer tout ça !

Utiliser un tracker NMAP

Comme me l'a rappelé un utilisateur sur HACF Il y a une autre solution qui pourrait consister à utiliser l'intégration NMAP Tracker, qui, bien que pas faite pour présente l'avantage de remonter l'adresse IP en attribut. Pour faire propre il faudra renommer les trackers ainsi crées tant au niveau de l'entity_id que du friendly_name qui de base reprennent ce que retourne le DNS, qui n'est pas toujours très lisible.

Ca donne ça et il n'est pas utile de bricoler l'IP pour créer un lien car celle ci est disponible en attribut, ainsi que d'autres informations potentielement utiles.

- alias: 'IP Track Offline'
  id: '96017908-d46d-40cc-8d95-6b7997f5a411'
  initial_state: true
  mode: single
  trigger:
    - platform: state
      entity_id:
        - device_tracker.nmap_shelly_1_01
        - binary_sensor.numero_3
        - binary_sensor.numero_4
        - binary_sensor.numero_5
  condition:
    - condition: template
      value_template: "{{ trigger.to_state.state != trigger.from_state.state }}"
  action:
    - service: notify.slack_hass_canaletto 
      data_template: 
        title: "IP Fail" 
        message: '{{now().strftime("%d/%m/%Y, %H:%M")}} > {{ trigger.to_state.state|replace("home", "IP UP")|replace("not_home", "IP DOWN") }} : {{ trigger.to_state.attributes.friendly_name }} : http://{{ trigger.to_state.attributes.ip }}'

Pour ce résultat :

Alternatives

Il est bien sur également possible d'utiliser des outils de monitirig IP de l'IT. Au delà du monde de l'IT pro, j'en ai découvert un de très sympa, Uptime Kuma, et dont l'auteur travaille à une intégration avec Home Assistant. On y reviendra.

Pour surveiller des équipement ou sites sur internet il existe UpTime Robot qui dispose d'une intégration officielle dans HA qui remonte des binary_sensor. C'est très pratique car il y a tout ce qu'il faut pour créer des notification sur mesure.

Conclusion

Aucune de ces solutions n'est parfaite et je continue à chercher... Mais pour trouver une solution au problème de base, je pense qu'il vaudrait mieux surveiller la disponibilité des entités. En effet j'ai remarqué que parfois un Shelly peut être disponible en IP et injoignable en CoIoT. Cela permettrait également de surveiller des équipements non IP, par exemple Zigbee...

Toutes les idées sont bienvenues et avec plaisir pour échanger, ici dans les commentaires ou sur le forum HACF..

 

Yealink, Teams Phone

Cet article va surprendre ceux qui me connaissent car je n'ai jamais été un fan de Teams. Et pour deux raisons, d'une part Microsoft a profité de la pandémie pour lancer un produit à l'arrache, et si maintenant Teams est utilisable, ça n'a pas toujours été le cas. D'autre part Microsoft en offrant Teams sans surcout dans le cadre d'une offre groupée Microsoft 365, anéantit toute forme de concurrence possible. Ce n'est pas sain pour le marché et il serait temps que les autorités de régulation se penchent sur le cas Microsoft. Ce coup de gueule étant posé, il n'en reste pas moins que Teams est devenu une évidence quasi incontournable, au point qu'aujourd'hui il n'est pas rare d'entendre l'expression "On se fait un Teams" comme jadis "On se fait un Skype", ou "On se fait un Facetime" pour les paumés de Cupertino...

C'est quoi Teams ?

Je serait réducteur en disant que ce n'est jamais que la version moderne (et moins fun) de MSN Messenger (voire des BBS ou ICQ pour les plus anciens). Donc du tchat et de la visio en groupe ou en one to one agrémenté de plusieurs fonctionnalités autour des fichiers plus ou moins utiles et utilisées. Dans la pratique Teams c'est surtout des réunions de groupe en visio, et ça fonctionne maintenant très bien. C'est ici qu'ont échoué les technologies du pharamineux rachat de Skype. Quant à la concurrence qui rame face à ce rouleau compresseur, c'est Slack, Zoom ou encore Meet chez Google.

Si Teams s'utilise naturellement sur un ordinateur personnel ou un smartphone, ce qui va m'intéresser ici c'est de l'utiliser sur un poste d'entreprise. De base Teams sait faire de la téléphonie entre utilisateurs Teams et Microsoft vend a prix d'or des options qui permettent de communiquer avec le réseau public. Ces options ne sont pas viables en terme de tarif quand on sait que l'équivalent en SIP coutera moins de 5 € / mois, par exemple chez OVH. Et c'est ici qu'intervient les postes Yealink qui savent fonctionner en mode hybride et ainsi supporter un compte Teams et plusieurs lignes SIP. Et un poste d'entreprise permet également d'accéder à toutes ces technologies pour ceux qui sont habitués à tenir un combiné en main, qui a dit que les habitudes ont la vie dure ?

Grand écran

Ce poste dispose d'un écran confortable et les deux modes évoluent dans deux vases clos sans aucune interaction, si ce n'est de passer de l'un à l'autre. On aurait pu imaginer que le SIP profite des contacts qui remontent dans Teams, mais non, le SIP ne semble être présent que pour assurer ne transition en douceur vers la téléphonie Teams.

La partie Team est très simple à configurer, il suffit de saisir une URL sur son PC pour que la configuration se fasse automatiquement. Ensuite on dispose de toutes les fonctionnalités habituelles, mais en dehors de la téléphonie on se rabattra sur le PC car si partager un écran est possible sur ce téléphone, ça teste petit. Quant à la visio il faudra un modèle plus haut de gamme intégrant une caméra. Il y a bien un port USB, mais il ne servira qu'à connecter un micro / casque de la marque, pas une webcam...

Pauvreté des fonctionnalités SIP

La partie SIP est assez pauvre en fonctionnalités si ce n'est de gérer 16 lignes qui fonctionnent très bien avec par exemple des comptes OVH ou Keyyo. Et d'ailleurs l'écran SIP avec son look à la Windows XP semble issu du siècle dernier et contraste avec l'écran Teams.

Ici pas de bouton d'accès direct, pas d'interactions possibles via des URL comme cela est possible chez Fanvil ou il est possible d'activer / programmer le DND via une une URL, d'ailleurs ici je n'ai même pas trouvé la fonction DND... Et la seule solution que j'ai trouvée pour que ce poste ne sonne pas la nuit est de programmer via Home Assistant le down du port POE sur le switch... Il n'y a pas non plus la possibilité de personnaliser la sonnerie, et donc encore mois d'avoir des sonneries différentes en fonction des correspondants (dispo sur le moindre mobile Android), pas plus que d'avoir une sonnerie différentes en Teams ou SIP. Ce téléphone tourne sous Android, mais cet Android est inaccessible, ne comptez donc pas en profiter et on se contrefout donc qu'il tourne sous Android ou un OS propriétaire...

Conclusion

Ce poste, qui existe également en version Zoom ainsi que dans d'autres versions plus ou moins évoluées, pourra convenir dans certaines situation, notamment pour des managers à l'ancienne... C'est cher (250 €) mais tout en restant dans la gamme de prix des postes d'entreprise (allez donc voir la gamme de prix chez Cisco...).

Cela nous démontre également comment la téléphonie est en train d'évoluer, avant il y avait Alcatel et France Télécom, puis Cisco s'es imposé dans les grands groupes, exit Alcatel ou Matra, et demain ce sont les GAFAM's qui prendrons le pas sur les opérateurs, exit Orange...

 

Redirection de port sous Linux : 2/2

Dans la partie précédente, en bon béotien, j'ai exploré pas mal de techniques de redirection de port, mais j'aurais du commencer par l'exploitation de ce qui est depuis un certain temps de base sans Linux, à savoir IPTables.

Pour cette manipulation on va utiliser un routeur Ubiquiti ER-X avec la dernière version de EdgeOS (2.0.9-hotfix.2). Et sur ce routeur on va commencer par y installer Wireguard (pour changer un peu, mais Zerotier ou les VPN disponibles de base auraient pu faire l'affaire).

On va se rendre compte que la philosophie de WireGuard est bien différente de Zerotier (ou même de TailScale). Ici il faut tout borner, c'est la version barbus et les ACL ne se gèrent pas sur une console d'admin, il faudra pour ça utiliser le firewall du point d'entrée, ce qui à mon gout est bien moins souple et rend impossible certaines restrictions plus granulaires au niveau de l'utilisateur client...

Je ne suis pas expert, mais contrairement à Zerotier ou le client génère un ID propre à la machine, sous WireGuard un simple copié collé suffit à exporter et utiliser la configuration ailleurs... On peut certes utiliser une clé supplémentaire (PresharedKey), mais ça ne changera rien. Un mot de passe qui se transmet vocalement aurait été préférable. Cela ne sera pas trop gênant dans une configuration fermée (la mienne, un serveur verrouillé vers un routeur qui sera tout autant), par contre dans le cadre de l'utilisation pour un utilisateur lambda itinérant et souvent inconscient cela pose question, tout comme le non support apparent d'une clé physique...

WireGuard coté serveur :

On se connecter en SSH et on installer la dernière version que l'on va trouver ici avec les commandes suivantes :

curl -OL https://github.com/WireGuard/wireguard-vyatta-ubnt/releases/download/1.0.20210606-1/e50-v2-v1.0.20210606-v1.0.20210424.deb
sudo dpkg -i e50-v2-v1.0.20210606-v1.0.20210424.deb

Attention à bien remplacer par la dernière version et faire attention car il existe des versions spécifiques à chaque modèle de routeur et pour chaque version de EdgeOS (v1 et v2).

Si on manque de place (df) on fait un show system image et on efface celle qui ne sert à rien après une mise à jour avec un delete system image. Et oui il y a du vécu.

Quand WireGuard est installé on va générer nos clés :

sudo wg genkey | tee /dev/tty | wg pubkey

Ce qui va nous donner deux clés, la première est la clé privée, la seconde la clé publique.

0GbmWkPkYB9y2s5aIaAxUrAPoSnsDFnuhHjRnujEsm8=
KsVzrtWPGWDbuCLPUyTsTL6pQOfiS+96VOXsMnPo+SI=

On sauvegarde ces clés au chaud et on passe à la configuration de l'interface CLI propre à EdgeOS (configure, commit, save et exit)

configure

On y associe un subnet privé, un port UDP et la clé privée :

set interfaces wireguard wg0 address 192.168.33.1/24 # Ici on choisit l'IP de notre serveur WireGuard
set interfaces wireguard wg0 listen-port 51833       # Ici le port UDP qu'il conviendra de redirigier si on est pas en Bridge ou DMZ...
set interfaces wireguard wg0 route-allowed-ips true  # Pour sortir de ce subnet....
set interfaces wireguard wg0 private-key 0GbmWkPkYB9y2s5aIaAxUrAPoSnsDFnuhHjRnujEsm8=

On va ensuite déclarer les pairs (peers) autorisés en associant notre interface à leur clé publique et une IP autorisée. 

set interfaces wireguard wg0 peer N4cuA0WPkMt+3hfidlemsI/VGcupv96NtrkwA/esf2E= allowed-ips 192.168.33.2/32
set interfaces wireguard wg0 peer u2w/+ZNI2RwbYTdft9yggPGnff8QexY9UjjvdvVf0gM= allowed-ips 192.168.33.3/32

On ajoute une règle sur le firewall (Il est également possible aussi de faire ça depuis l'interface du routeur)

set firewall name WAN_LOCAL rule 20 action accept
set firewall name WAN_LOCAL rule 20 protocol udp
set firewall name WAN_LOCAL rule 20 description 'WireGuard'
set firewall name WAN_LOCAL rule 20 destination port 51833

Et on termine par :

commit
save
exit

Il est bien sur possible de faire ça en plusieurs fois, mais dans ce cas là il ne faudra pas oublier de rentrer dans le CLI propre à EdgeOS (configure, commit, save et exit).

Si je fais un scan des ports coté WAN, je ne dois rien voir le seul port ouvert étant le 51833 en UDP.

WireGuard coté client

Le client peut être sous n'importe quel OS, dans mon cas ce sera Windows et on va faire la configuration manuellement (il y a moyen de préparer des fichiers de configuration à importer pour un déploiement conséquent). Plus haut on a ajouté la clé publique fournie par le client dans les pairs autorisés. Cette clé est propre à chaque tunnel défini coté client. Idem pour la clé privée du client que l'on reporte ci dessous, elle est crée lors de l'ajout d'un tunnel sur le client. Ensuite on renseigne le pair du client, c-a-d le serveur que l'on a créé plus haut, sa clé publique, les IP autorisées pour ce tunnel ainsi que l'IP ou le TLD du serveur.

[Interface]
PrivateKey = IIczTA5sdrdcg4+VQNnudslgnveoR5ZDD3ZyL0ZXonU=
ListenPort = 15092
Address = 192.168.33.2/32

[Peer]
PublicKey = KsVzrtWPGWDbuCLPUyTsTL6pQOfiS+96VOXsMnPo+SI=
AllowedIPs = 192.168.33.0/24
Endpoint = 69.69.69.69:51833
PersistentKeepalive = 25

Ensuite on active le tunnel et normalement à ce stade on doit pouvoir faire un ping sur l'IP LAN du routeur distant (notre serveur WireGuard) ainsi que les IP de son subnet pour peu que les routes inverses soient configurées.

Si sous Linux l'activation / désactivation d'un tunnel en CLI coule de source, il m'a fallut un peu chercher pour Windows. Mon but étant de permettre à une application de supervision d'éventuellement réactiver un tunnel cas de défaillance.

Donc pour activer un tunnel,  CMD en mode admin... (-h pour en savoir plus..) :

C:\>"C:\Program Files\WireGuard\wireguard.exe" /installtunnelservice "C:\Program Files\WireGuard\Data\Configurations\Nom du Tunnel.conf.dpapi"

Et pour le désactiver :

C:\>"C:\Program Files\WireGuard\wireguard.exe" /uninstalltunnelservice "Nom du Tunnel"

Pour information et pour les afficionados, on peut très bien installer un serveur WireGuard sous Windows, les explications sont ici, ce n'est pas officiellement supporté et ça a l'air bien plus compliqué.

Sauf que dans le cas qui me préoccupe je ne peux justement pas disposer des routes inverses pour une question de sécurité chez mon client. La seule IP autorisée sera l'IP LAN du routeur. Il faut donc que les services que je doit joindre me reconnaissent avec cette IP. Et c'ets ici qu'interviennent les IPTables.

IPTables

Pour utiliser les IPTables il n'y a rien à installer car cela fait partie de l'O/S. Ca tombe bien car sur ce routeur la place est limitée. Par contre il faut que l'IP Forwarding soit activé, on peut vérifier avec sysctl net.ipv4.ip_forward qui va nous répondre net.ipv4.ip_forward = 0 ou 1 si c'est activé.

Ensuite, toujours en SSH : 

sudo iptables -F
sudo iptables -F -t nat
sudo echo 1 >| /proc/sys/net/ipv4/ip_forward  # En cas de besoin....
sudo iptables -t nat -A  PREROUTING -p tcp -d  192.168.33.1 --dport 2525 -j DNAT --to 192.168.169.22:25
sudo iptables -t nat -A  PREROUTING -p tcp -d  192.168.33.1 --dport 8080 -j DNAT --to 192.168.150.20:80
sudo iptables -t nat  -A POSTROUTING -j MASQUERADE

Depuis le client on va faire pointer nos requetés sur la première IP de WireGuard à laquelle j'ai associé un serveur web sur 192.168.150.20 et un serveur SMTP sur 192.168.169.22. A noter que si le port de destination est bien sur le port sur lequel répond le service, le port source peut lui être identique ou défini différemment (surtout qu'en 80 on a l'interface du routeur...).

Maintenant, depuis mon client, le serveur web de destination répondra sur http://192.168.33.1:8080 et si je fais un telnet 192.168.33.1 25 j'obtiendrait la mire de mon serveur SMTP. Et ces deux derniers ne verront en IP source que l'IP LAN de mon routeur, donc une IP autorisée.

Mais on ne peut pas tout à fait aller dîner... En effet ces IPTables vont disparaitre  au premier redémarrage !

Persistance

Mon premier réflexe était d'utiliser le paquet iptables-persistent. Sauf que j'ai remarqué, que pour une raison que je n'explique pas, mais qui a certainement sa logique, il est impossible d'ajouter une règle au firewall (tout au moins depuis l'interface du routeur) dès lors que ce paquet est activé. Je vais donc créer un script qui se lancera au démarrage du routeur et que je pourrais désactiver au besoin... (si un barbu passe par la merci de me dire sil y a mieux à faire).

Sous EdgeOS on a un emplacement spécifique pour les scripts devant s'exécuter après le démarrage du routeur et de ses services : /config/scripts/post-config.d

sudo vi /config/scripts/post-config.d/myfwd

Et dans ce fichier on va insérer nos commande  :

#!/bin/bash
iptables -F
iptables -F -t nat
iptables -t nat -A PREROUTING -p tcp -d  192.168.33.1 --dport 2525 -j DNAT --to 192.168.169.22:25
iptables -t nat -A PREROUTING -p tcp -d  192.168.33.1 --dport 8080 -j DNAT --to 192.168.150.20:80
iptables -t nat -A POSTROUTING -j MASQUERADE

Ensuite on va rendre ce script exécutable :

chmod +x /config/scripts/post-config.d/myfwd

Mais, car il y a toujours un mais. Il se peut que la résolution DNS ne se fasse pas au lancement. Donc si vous devez obtenir l’adresse IP à partir d’un nom de domaine, vous devez utiliser dig que vous obtiendrez avec :

sudo apt-get install dnsutils

Ensuite il faudra modifier légèrement le script...

sudo IP_ADDR=$(dig +short smtp.mondomaine.com| awk 'NR==1') 
sudo iptables -t nat -A  PREROUTING -p tcp -d  192.168.33.1 --dport 2525 -j DNAT --to $IP_ADDR::25

Firewall

Attention : dès lors que l'on ajoute ces IPTables il ne sera plus possible de modifier les règles du firewall depuis l'interface, et comme l'interface qui ne fait que lancer des lignes de CLI, idem en CLI. Afin de pouvoir créer ou modifier des règles il faudra temporairement désactiver ce script et redémarrer. En ce qui me concerne je l'ai fait sauvagement en renommant le répertoire qui le contient :

cd /config/scripts
sudo mv post-config.d post-config.d.off

Pour le reste on laisse par défaut, mais on va tout de même éviter de laisser des ports ouverts. On commence par désactiver le service Ubiquiti Device Discovery qui expose en UDP/TCP le port 10001 :

configure
set service ubnt-discover-server disable
commit ; save 

On pourrait désactiver l'interface GUI et le SSH depuis le WAN. Pour ça il est possible de forcer sur l'IP LAN :

set service gui listen-address 192.168.0.1
set service ssh listen-address 192.168.0.1

Mais en fait ça ne m'intéresse pas car je veux que ce soit accessible également via WireGuad, et comme après avoir installé IPTables on ne peu plus utiliser le firewall intégré on va continuer à se servir d'IPTables pour blinder le port WAN, ici eth4 :

# On commence par tout fermer sur le port WAN
sudo iptables -A INPUT -i eth4 -j DROP
# On accepte le port UDP de Wireguard (à noter qu'il est possible de le restreindre à une IP ou un subnet avec -s)
sudo iptables -I INPUT -i eth4 -p udp --dport 51833 -j ACCEPT
# On une IP spécifique sur les ports 22/80/443
sudo iptables -I INPUT -i eth4 -p tcp -m tcp -s 82.65.19.160 --dports 22,80,443 -j ACCEPT

On ajoute au fichier de configuration cité plus haut et on fait un scan (avec ça par exemple) sur l'IP WAN afin de constater la fermeture effective. Il ne reste que l'ICMP et WireGuard en UDP. Si on souhaite encore renforcer la sécurité on peut aussi restreindre l'ICMP et le port UDP de Wireguard avec des IP sources... Le même scan depuis un client WireGuard continuera à présenter les mêmes ports que l'interface LAN.

Dans le fichier ci dessous j'ai donc mes règles DNAT, ensuite je ferme tout sur sur port WAN (eth4) et j'autorise depuis mon IP publique WG en UDP sur le port 51833, en TCP 22, 80, 443 et enfin l'ICMP pour pouvoir monitorer.

#!/bin/bash
iptables -F
iptables -F -t nat
iptables -t nat -A PREROUTING -p tcp -d  192.168.33.1 --dport 2525 -j DNAT --to 192.168.69.24:25
iptables -t nat -A PREROUTING -p tcp -d  192.168.33.1 --dport 1430 -j DNAT --to 192.168.69.24:143
iptables -t nat -A PREROUTING -p tcp -d  192.168.33.1 --dport 8080 -j DNAT --to 192.168.69.16:8000
iptables -t nat -A POSTROUTING -j MASQUERADE   

iptables -A INPUT -i eth4 -j DROP

iptables -I INPUT -i eth4 -p udp -s MyPublicIP --dport 51833 -j ACCEPT

iptables -I INPUT -i eth4 -p tcp -m multiport -s MyPublicIP --dports 22,80,443 -j ACCEPT

iptables -I INPUT -i eth4 -p icmp -s MyPublicIP -j ACCEPT

Et vu que je ne suis pas un grand fan de VI, le plus simple est de préparer le fichier en local, de l'uploader dans le répertoire utilisateur avec un SFTP graphique, et ensuite de le déplacer dans le répertoire idoine :

sudo mv myfwd /config/scripts/post-config.d/

Et de le rendre exécutable :

chmod +x /config/scripts/post-config.d/myfwd

Et pour vérifier les règles :

sudo iptables -L --line-numbers

Pour en savoir plus sur ces règles c'est ici, et vous allez trouver une petite bible dédiée aux EdgeRouter, et notamment pour tout ce qui concerne leur sécurité.

Voilà !

EDIT 25/09/2021: La bonne nouvelle c'es que Free a ajouté WireGuard dans ses Freebox et que ça fait tout le travail... Quand on a une Freebox bien sûr !

EDIT 04/11/2021: Ajustement du script pour EdgeOS.

EDIT 05/10/2022 : Ajustement IPTables pour le Firewall

Sources

 

Unifi UDM Pro, ZT, encore...

Longtemps j'ai disposé de deux lignes vDSL à 90 MB en étant proche du DSLAM. Il y avait un peu de bricolage, la Freebox en bridge sur le WAN1 de l'UDMP et Nerim en NAT entre le WAN2 (secours) de l'UDM Pro et vie le LAN sur un subnet différent sur MPTCP et OTB. Je sais, pas très propre, mais c'est juste un labo... Et c'est pourquoi on va optimiser un peu, d'autant plus qu'entre temps je me retrouve avec une fibre SFR et ma Freebox Révolution migrée elle aussi en fibre.

Versions utilisées, relativement stable par rapport à ces derniers mois :

  • UDMP : 1.10.0
  • Network : 6.4.47

Swap des ports WAN de l'UDM Pro

Avec l'idée migrer la Freebox Révolution vers une Delta S afin de profiter d'un lien à 8 GB, j'ai commencé par swapper la logique des deux ports Wan de l'UDMP. De base le port 9 en 1 GB (RJ45) correspond à WAN1 et le port 10 en 10 GB (SFP+) à WAN2. On peut se demander ou est leur logique tant il est évident que le lien principal sera plus performant que le lien de secours. Depuis je ne sais plus quelle beta il est possible d'inverser cette logique, mais attention il reste un bug de taille, le nom du provider (probablement lié à l'ASN) ne sera pas mis à jour et on reste avec ceux de base, et ça peut être déroutant. Comme on le voit ci dessous le port WAN1 avec la Freebox remonte OwCloud (anciennement Nerim racheté par BT), alors que le WAN2 est maintenant branché sur du SFR...). Ce bug est connu, mais comme d'autres il ne semble pas être dans les priorités des équipes Unifi qui préfèrent surement se concentrer sur une nouvelle version de l'interface...

Passer mon mélange de subnets en VLAN

C'est logique, il faut juste trouver un peu de temps. Et on va voir que du temps j'en ai perdu.

Pour créer un VLAN sur Unifi c'est dans l'absolu très simple. On crée un nouveau réseau et dans les paramètres avancés on choisit un VLAN ID et éventuellement le subnet et le mode DHCP, mais on peut faire sans et dans certains cas laisser faire l'Auto Scale Network. Ensuite on associe ce réseau à un port du switch. Sauf que j'ai passé des heures à chercher à faire compliqué car ça ne fonctionnait pas. Et finalement je me suis résolut à un reboot et là ça passe tout seul. Allez donc savoir...

Dans mon cas je voulait un VLAN sur un subnet particulier avec DHCP. Mon objectif étant d'y coller la box SFR qui sera branchée sur le WAN2 et sur ce VLAN et ainsi sera utilisable par mon fils pour ses jeux qui disposera ainsi d'une fibre rien que pour lui et sans aucune restrictions (il est grand !) et sans venir perturber la QoS du réseau principal, même si l'impact sera moindre via que fibre qu'il ne l'était en xDSL.

Sur le subnet de ce VLAN, la passerelle par défaut est donc la box SFR, comme c'est le cas sur le WAN2 de l'UDM. Les deux usages cohabitent, et la box de secours sert à quelque chose...

On peu ensuite créer un réseau WI-FI sur ce VLAN ou taguer des ports sur les switch (port profile) pour qu'ils l'utilisent.

On verra plus tard comment créer un VLAN dédié à l'IoT et surtout comment ouvrir ou bloquer le trafic entre les VLAN.

UPnP mon amour !

Toute cette partie fonctionnelle, je me suis aperçu que mon routeur VPN/SDN Zerotier (un Linux dans une VM) avait un peu de mal à sortir. Dans la configuration précédente il sortait en NAT via ma ligne de secours. Dans ma nouvelle configuration il doit sortir via l'UDMP, et le vas échéant profiter du secours. Curieusement j'ai remarqué que le passage en mode secours était lent et qu'au retour certaine destinations n'étaient pas joignables, bizzarement celles situées sur le réseau Free, Online Sacaleway pour être précis, alors que d'autres distants passaient. Et c'est critique.

On lance la commande :

root@zt:~# zerotier-cli peers

Et l'on obtient la liste des clients distants :

1d20dgr3f3 1.6.5  LEAF      -1 RELAY
2533gtdaef 1.6.5  LEAF      -1 RELAY
338fgte636 1.4.6  LEAF      13 DIRECT 7131     2944

Si DIRECT veut dire que tout va bien, RELAY indique que le distant est injoignable directement, que le trafic va transiter par les serveurs Zerotier. Et que quand le trafic passe, ce qui n'est pas toujours le cas, cela va induire une latence aléatoirement plus importante, au minimum 35 ms. là ou en direct on passera à 12 ms. Et justement, au delà de remplir les poches des Telcos, l'un des objectifs de la fibre est de réduire la latence (même si avec le l'IPV4 encapsulée dans de l'IPV6 on en perd un peu, mais c'est un autre problème et il faudra se pencher sur du full IPV6).

Après moultes recherches j'ai trouvé que 

  1. Que l'UDMP bloquait peut être certains ports en sortie, même si on désactive toute la partie sécurité et que tout est autorisé sur le firewall (pas clair !)
  2. Qu'il est possible de configurer des ports UDP secondaires sur un client (en plus du port UDP natif 9993.

On commence donc par configurer un port secondaire (on ne touche surtout pas au primary (voir ce fil vers la fin) sur le client qui me sert de routeur ZT en créant un fichier local.conf dans le bon répertoire (détails ici) :

{
  "settings": {
    "secondaryPort": 21234
  }
}

Mais visiblement ça ne suffit pas et il va falloir activer l'UPnP pour que tout passe en direct avec un minimum de latence :

Je ne suis pas fan de l'UPnP/NAT-PMP car ce protocole d'automatisation ouvre par définition les ports à la demandes des applications. Applications qui sur un réseau peuvent êtres malveillantes. Pourtant à la maison ce service est très utilisé et quasiment indispensable. Par contre hors de question d'utiliser cette méthode en entreprise ou une règle WAN_LOCAL avec la destination UDP 9993 devrait faire l'affaire (pas clair et à creuser). (ici pour pfsense avec ACL en prime, ce qui va aider en entreprise).

ZT Failover ?

Alors là on rentre dans le coté obscur. Tant qu'à avoir deux fibres autant qu'elles servent à quelque chose. Visiblement l'UPnP de l'UDM Pro est archi buggé. Je me suis donc dit que j'allais faire le failover directement dans le routeur Zerotier. J'ai donc ajouté une carte réseau virtuelle à ma VM ZT qui me sert au routage Lan to Lan (ici et pour monter ça) sur le VLAN 110 qui correspond à la box SFR. Les deux interfaces ont une passerelle par défaut avec le même poids. 

Ensuite on édite le fichier local.conf et on ajoute et on ajoute ces paramètres : 

{
  "settings":
  {
    "defaultBondingPolicy": "active-backup",
    "peerSpecificBonds":
    {
      "f6dd3a2db3":"active-backup",
      "1d20ff53f3":"balance-xor",
      "a92cbsd6fa":"broadcast"
    }
  }
}

Pour comprendre les différentes possibilités on ne trouve pas beaucoup d'information si ce ne sont deux docs officielles ici et ! A croire que ce sujet intéresse peu. Attention, j'ai eu pas mal de difficultés en éditant avec Nano ce fichier avec un copié / collé. Peut être une question de formatage qui m'aurait échappé. Par contre je n'ai pas réussit à associer dans ces paramètres le port secondaire.

Toujours est-il que si je débranche un interface l'autre prend le relais en mode active-backup avec une légère interruption que quelque secondes ou encore plus rapidement en mode broadcast. Mais ce dernier mode semble déconseillé car il génère pas mal de garbage...

Design

Si j'étais un cador en Visio ou d'autres outils en ligne comme draw.io ou autres, je pourrais vous faire un joli dessin, mais ce n'est pas le cas. Donc :

  • WAN1 : Freebox en mode bridge (je voulais tester la Freebox Pro mais elle n'a pas ce mode).
  • WAN2: Secours en NAT sur une box SFR. NAT également accessible sur les ports tagués en VLAN 110 sur un subnet dédié et isolé. Cela permet à mon fils de jouer et faire ses bricolages sans impact sur la sécurité du LAN principal.
  • VPN vers les sites distants accessible depuis le LAN principal. On verra plus tard comment profiter des fonctionnalités Multipath / Bonding de Zerotier qui vont nous permettre d'utiliser le lien de secours sans passer par celui de l'UDM.

Vieilleries...

Et dans tout ça que devient mon installation openMPTCP ? L'objectif de cette installation était d'agréger plusieurs lignes afin de fiabiliser et d'augmenter le débit disponible. Si la question du débit se pose en en xDSL elle ne se pose plus avec une fibre. Quant à la fiabilité il faudrait pour que ça ait du sens disposer d'un VPS puissant avec un lien 10 GB. Et encore on aurait malgré tout une latence dégradée. 

Mais je vais tout de même tenter la chose pour le sport. Pour ca il faut que je crée sur mon ESXi une nouvelle interface sur le VLAN 110 afin d'accéder au NAT de la ligne de secours et éventuellement le modem 4G. L'objectif n'est pas la performance, je ne toucherait donc rien coté VPS, mais juste d'éviter le lag qui se produit quand l'UDMP passe sur le lien de secours. Mais ça reste très rare et je ne sais pas si le jeu en vaut la chandelle. Peyt être pour l'IoT.

 

 

TailScale, VPN/SDN simplifié

Avant, il y avait les VPN (PPTP, IPSec, OpenVPN, etc...) qui ne sont pas toujours très simple à implémenter. Et puis arrivent les SDN, qui dans l'absolu sont des VPN, orientés réseaux étendus et très simples à mettre en œuvre. D'aucun le savent, je sus un fan de Zerotier que j'utilise au quotidien, tant pour interconnecter 4 sites à la place d'IPSec, que pour des machines distantes ou quand je suis en déplacement.

Entre temps les barbus nous ont beaucoup parlé de Wireguard, qui peu ou prou fait la même chose en un peu plus compliqué avec de soit disant meilleures performances. Et puis arrive TailScale qui lui est basé sur Wireguard et lui apporte la simplicité. Une sorte de Wireguard pour les nuls, enfin, pas que, car TailScale apporte à WireGuard la notion d'annuaire qui lui manque (WG-Dynamic en cours de dev.). Comme pour Zerotier, au delà d'un certain nombre de nodes il faudra passer à la caisse, mais les tarifs sont comparables, tout comme les possibilités offertes par la version gratuite suffisante pour un usage home.

Comme pour Zerotier, il est possible :

  1. D'accéder depuis internet à une machine particulière avec un client dédié (MacOS, Windows, Linux, IOS, Android).
  2. D'accéder depuis internet à un sous réseau dès lors que l'on installe un node Linux qui servira de routeur
  3. D'accéder depuis internet à internet de façon sécurisée en passant par un node Linux installé en mode Exit-Node, chez vous ou sur un VPS.
  4. D'accéder depuis chez vous à d'autres sous réseaux, bon là c'est plus compliqué et il faudra passer à la caisse et avantage à Zerotier (ou Wireguard, mais je n'ai pas testé).

Le gros avantage de Zerotier est que l'on travaille en Layer-2 sur un subnet dédié avec la possibilité de figer des IP là ou TailScale affecte des IP aléatoires et travaille en Layer-3. Mais les deux peuvent cohabiter, et ça peut être intéressant dans certains cas. Vous trouverez ici un comparatif des deux solutions.

Ce qui est certain, c'est que si moi j'y trouve des limitations, TailScale a pour lui une simplicité qui en fera un bon choix pour ceux qui débutent et veulent juste un usage limité. On va donc se consacrer aux trois premiers points d'usage.

Accéder depuis internet à une machine distante

Je ne vais pas vous expliquer comment créer un compte (il suffit d'aller sur leur site), ou comment se faire coucou entre deux machines clic clic (MacOS, Windows, IOS ou Android), il suffit de lancer l'installation et de faire un ping. Par contre je vais le faire pour la machine Linux qui nous servira dans les deux cas qui suivent.

On part du principe que la machine existe dans sa config minimale et qu'elle communique avec Internet. On en fait un client Tailsacle, ça se passe ici et c'est un peu différent selon les distributions. J'utilise Ubuntu et on commence par ajouter les clés et le repository :

curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.gpg | sudo apt-key add -
curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.list | sudo tee /etc/apt/sources.list.d/tailscale.list

On fait les mise à jour et on installe :

sudo apt-get update
sudo apt-get install tailscale

On lance le client et on copie l'url pour l'autoriser :

sudo tailscale up

Et voici notre IP :

ip addr show tailscale0

A ce stade si on a un autre client TailScale on peu faire un ping... A noter que sur la console d'admin on va pouvoir définir si la clé expire ou pas...

Si votre but est d'accéder à Home Assistant, il existe un addon qui fera le travail pour vous.

Accéder depuis internet à un sous réseau

Afin de ne pas devoir installer TailScale sur toutes vos machines, et surtout accéder à celle ou il n'est pas possible de l'installer (IoT par exemple), on va installer une machine en mode routeur. Pour l'instant ce n'est faisable que sous Linux, avec un petit RPI ou une VM par exemple... On continue sur la même machine.

Première chose on active l'IP Forwarding :

echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p /etc/sysctl.conf

Ensuite on active le routage du subnet :

sudo tailscale up --advertise-routes=192.168.210.0/24

Et pour terminer on va dans la console d'admin indiquer que cette machine servira de routeur pour ce subnet (sous réseau) (les ... au bout de la ligne) :

A partir de là un client TailScale pourra accéder à ce sous réseau.

Accéder depuis internet à internet de façon sécurisée

Quand vous vous connectez en WI-FI sur un hotspot public tout le monde vous dira que ce n'est pas très sécure et qu'il faut utiliser un VPN. Ici plutôt que de payer pour un VPN on va utiliser notre propre connexion, à la maison, ou encore dans un VPS. Pour ça on ajoute la fonction Exit-Node à notre machine :

sudo tailscale up --advertise-exit-node

Et on la configure de facon idoine au même endroit

Ensuite dans le client on choisit l'option Exit-Node afin que tout le trafic transite par notre nouveau point de sortie.

A noter que si l'in veut combiner les deux fonctions, subnet + Exit-Node la commande sera plutôt celle ci :

 sudo tailscale up --advertise-routes=192.168.210.0/24 --advertise-exit-node

Voilà, c'est pas plus compliqué, c'est gratuit et c'est sur. En parlant de gratuit sachez que Free a démarré une beta afin d'intégrer Wireguard dans les Freebox et que Wireguard tout comme Zerotier peut être intégré dans certains routeurs (Unifi, Asus, etc...) ce qui vous évitera la petite machine Linux. Mais ce n'est pas forcément aussi simple.

Sources :

 

NextDNS, une bonne alternative

Traditionnellement, et encore aujourd'hui, la grande majorité des utilisateurs se servent du DNS de leur fournisseur d'accès sans vraiment savoir de quoi il s'agit. Certains plus aguerris l'ont changé, enfin ceux qui peuvent car certains fournisseurs comme par Orange (lamentable sur ce point) rendent ce changement impossible sur les LiveBox. Mais il y a toujours moyen de contourner sur les appareils connectés ou mieux les navigateurs. Depuis peu les navigateurs modernes permettent de configurer un DNS sécurisé (DNS over HTTPS, DoH), et d'ailleurs dans les dernières version celui ci est déjà plus ou moins configuré.

Les choix :

  • Google : ça reste le premier choix alternatif que l'on retrouve. Donc au cas ou Google aurait loupé un de vos pas, il se rattrape en fournissant un DNS gratuit.
  • Cloudflare : on ne présente plus, certainement le plus rapide.
  • Quad9 : une autre initiative suisse, plus transparent et qui assure un filtrage de domaines malveillants...
  • La liste est longue et vous la retrouverez ici.

Pour aller un un peu plus loin on peut également utiliser un DNS filtrant à installer (et maintenir) sur son propre réseau. Il en existe principalement deux, Pi Hole et AdGuard Home. Je ne suis pas fan du premier, mais j'aime bien Adguard. L'inconvénient est qu'il faut une infrastructure minimale (VM, Docker, etc..) et que cela ne fonctionnera que sur un site. J'ai longtemps utilisé AdGuard, et comme tous les DNS filtrants on se retrouve parfois avec des faux positifs qui au final peuvent ralentir les flux.

L'intérêt d'utiliser un DNS filtrant réside principalement dans les points suivant :

  • Protection de la vie privée : confidentialité des requêtes, protection contre les trackers, suppression des publicités intrusives,
  • Sécurité : protection contre les menaces qui utilisent le DNS,
  • Contrôle parental,
  • Supervision.

NextDNS

Enfin, si je vous parle de tout ça c'est pour vous présenter NextDNS.

NextDNS est une offre hébergée qui combine les avantages d'un AdGuard Home et d'un DNS rapide accessible partout. L'avantage étant que je vais pouvoir l'utiliser sur plusieurs sites ou je me déplace, mais également en situation de mobilité.

Pour en profiter il est possible de changer (quand on peut) le DNS affecté au DHCP sur le routeur ou la box par ceux proposés par NextDNS. Mais pas que. Il est également possible de configurer DoH sur les navigateurs et les O/S Mobiles de façon très simple afin d'en profiter en situation de mobilité, sans pour autant avoir à modifier la configuration à chaque usage. Et pour ceux qui sont réfractaires à la technique, les service propose des applications pour tous les systèmes avec des guides très clairs proposés sur le panneau de contrôle de NextDNS.

Et oui car panneau de contrôle il y a, et c'est un gros plus offert par NextDNS. Outre les diverses configurations possibles on vas y retrouver des statistiques ainsi que la liste des requêtes que l'on a fait. Bien sur ces options sont facultatives, ces données ne sont présentes que pour vous et si on le souhaite NextDNS n'enregistrera simplement rien. Mais laissez les au moins un temps, histoire de vous faire peur en voyant le nombre de requêtes faites par vos appareils, ainsi que votre dépendance aux GAFAM's, c'est juste effarant ! (le graphique qui suit c'est juste 24 heures chez moi...).

Navigateurs

Si DNS over HTTPS fonctionne très bien sur Opera, Edge (Chromium MS) ou Firefox, la fonctionnalité est par contre bloquée sur ma machine avec Chrome et brave au motif que ces navigateurs seraient managés par une organisation. Ce n'est pas le cas, hormis le fait que ce PC fait partie d'un domaine Active Directory, sans toutefois de policies spécifiques à navigateurs. Pas de problème par contre sur MacOS ou sur d'autres PC hors domaine. Un point à creuser.

Aller plus loin en entreprise

Dans le cas d'un réseau d'entreprise ou d'utilisateur avancé on va donc aller changer le DNS sur le routeur ou le serveur DNS. Et si on est sur un domaine Active Directory, ce qui est mon cas, le DNS Windows est un passage obligatoire. Et là c'est le bug, car si Microsoft commence timidement à introduire DoH sur Windows 10, le DNS de Windows Server sur lequel repose Active Directory n'est toujours pas adapté à DNS over HTTPS ou DNS over TLS au niveau des redirecteurs (forwarders).

La seule solution est donc de passer en clair entre le DNS du serveur Windows et les IP de NextDNS. Et justement passer en clair est une des choses qu'on ne veut pas faire. Pour contourner ça NextDNS propose une sorte de proxy DNS sur lequel on pourrait faire pointer nos forwarders, une version graphique qui installe un driver TAP (en 2021 sérieux ?) et un client CLI qui ferait bien l'affaire mais ne fonctionne pas correctement. Clairement chez NextDNS Windows c'est pas leur truc et les explications et réponses aux questions que l'on trouve sur leur forum ou le GitHub en témoignent.

Pour l'expérience j'ai donc monté un AdGuard transparent dans un Docker qui pointe sur https://dns.nextdns.io/xxxxxx en DoH. Ca fonctionne en faisant pointer dessus le sforwarders de mon serveur DNS sous Windows Server. Je suppose que j'aurais pu faire la même chose avec un petit Linux et le client CLI, mais mon but était démontrer que ça tourne dans un environnement Windows d'entreprise. Après avoir perdu pas mal de temps à chercher des informations je me suis dit que ce CLI n'avait peut être jamais été testé sur Windows Serveur 2016/2019 et qu'il fonctionnerait peut être sur une vieille version de Windows, et banco ça fonctionne sur un Windows 2008R2 vintage... Sérieux ? Donc :

nextdns install -config 71fc72 -report-client-info -listen 0.0.0.0:53

Et ensuite on fait pointer du DNS Windows sur l'IP de ce serveur et c'est transparent pour les utilisateurs.

Une alternative pourrait consister à changer de DNS sur les clients (via le DHCP) et à les faire pointer sur le proxy DNS en précisent que les requêtes du domaine Active directory local pointeront bien sur le DNS AD. C'est possible, mais ce n'est pas ce que je conseillerais.

nextdns install -config 71fc72 -report-client-info -listen 0.0.0.0:53 -forwarder canaletto.fr=192.168.10.10

Dans l'absolu ce petit CLI est très bien fait, il est juste un peu bâclé en ce qui concerne l'environnement Windows. Par contre il s'installe en mode service et sera donc persistant.

Bonus

NextDNS permet de faire de la réécriture de DNS, une fonction qui sera intéressante quand on utilise un VPN/SDN comme Zerotier car cela de permettre de résoudre des nom de hosts internet sur des adresses IP privée, ce qui est théoriquement contraire aux RFC sur un DNS public (bien que certains come CloudFlare l'acceptent, ce n'est jamais une bonne idée de documenter ainsi une architecture interne).

Conclusion

Je ne vais pas détailler tout ce que les autres sites ont pu écrire, pas même insérer un lien d'affiliation qui pourtant existe.

Ce service est un peu jeune, mais il avance rapidement. il est le fruit de deux Français expatriés aux Etats Unis. Personnellement je suis séduit et je vous invite à le prendre en main.

NextDNS est un service qui peut être gratuit pour un usage Soho limité à 300.000 requêtes, ça peut être suffisant dans pas mal de cas, en ce qui me concerne j'en suis à 150.000 en 24 heures, mais il faut dire que j'ai un peu poussé la chose dans ses retranchements. La tarification est très accessible (2 € par mois ou 20 € par an pour l'utilisation que j'en ai) et il existe des offres destinées aux entreprises et à l'enseignement.

Enjoy ;-) (je mettrais à jour cet article au fil du temps... N'hésitez pas à revenir).

Sources

 

Installer Ghost sur aaPanel avec Docker

Il y a quelques semaines je vous avait vaguement expliqué comment installer aaPanel pour gérer un linux. Avec aaPanel on peut installer en quelques clics un site web, un Wordpress ou un autre CMS. Le hic c'est que celui que je veux tester n'est pas dans la liste, mais heureusement aaPanel gère les container Docker et justement Ghost est disponible préinstallé dans ce mode.

Je prends ici l'exemple de Ghost, mais ce qui suit s'applique à n'importe quel container d'application web.

Docker

Dans aaPanel vous pouvez ajouter Docker Manager depuis l'AppStore, celui-ci s'avèrera suffisant pour installer quelques containers, mais trop léger pour faire la majorité des manipulations. L'avantage à l'installer c'est que ça ajoutera au serveur tout ce qui est nécessaire de base pour Docker/

L'alternative consiste à installer Portainer qui lui offre une interface complète pour Docker, dans un container (il existe des alternatives).

docker volume create portainer_data
docker run -d -p 8000:8000 -p 9000:9000 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce

Mais au final le plus simple est encore Docker Compose ou d'y aller directement en SSH, ou plus simplement avec le terminal intégré à aaPanel.

Il y a plusieurs choses importantes à paramétrer pour utiliser Ghost dans un Docker :

  • Le port que l'on restituera ou pas sur le host (la machine qui supporte Docker). Si on ne le configure pas Ghost sera uniquement accessible en local via l'IP du container (http://172.17.0.2:2368 par exemple) et non sur le réseau local et non sur le réseau local qui héberge le serveur. Sur une machine isolée, un VPS par exemple, ce n'est pas gênant car on servira de NGINX pour accéder au site en reverse proxy au passage gérer le certificat Lets'Encrypt. Par contre si le point d'entrée passe par un firewall sur une autre IP du LAN, il sera important d'exposer un port sur le host afin d'éviter le passage par un NGINX local, inutile dans ce cas.
  • Le second point consiste à paramétrer le répertoire qui contiendra les données (db, images, etc...) de notre site. Si on ne le fait pas Docker va créer un répertoire arbitraire pour y stocker les données du container tel que cela aura été défini par celui qui a créé le Docker (dans l'absolu ces données pourraient très bien rester dans le container, mais ce ne serait pas du tout une bonne idée). Sur un VPS on peut par exemple faire en sorte que les données soient dans un sous répertoire de l'utilisateur.
  • Enfin, et si l'on veut éviter d'avoir à le faire plus tard, il faut définir l'url du site que l'on va créer. C'est un point important car comme dans Wordpress, Ghost à besoin de connaitre l'url sur lequel il tourne pour la navigation. Si on ne le précise pas et que l'on redirige le site via un reverse proxy (par exemple : https://www.domain.tld > http://172.17.0.2:2368) cela semblera fonctionner mais certains liens seront incorrect puisqu'ils pointeront sur l'IP du container.

Mise en pratique

Création du container minimaliste :

docker run -d --name ghost ghost

Création du container avec la gestion des ports et de l'url :

docker run -d --name ghost -e url=https://www.domain.tld -p 3001:2368 ghost

Création du container avec la gestion des ports, de l'url en précisant du répertoire contenant les données, une option de redémarrage et une image particulière (pour l'exemple car idéalement on utilise celle par défaut qui est la dernière :latest) :

docker run --name ghost -p 3001:2368 -e url=https://www.domain.tld -v /home/lionel/ghost/content:/var/lib/ghost/content --restart=always -d ghost:1.21.1-alpine

En fait cette dernière possibilité, bien que proposée ne fonctionne pas, ou plus, au niveau de l'url. Je vais donc créer mon docker sans l'url et en profiter pour mettre mes données dans un répertoire un peu plus parlant. Pour ça je vais au préalable créer un volume.

docker volume create canaletto_data
docker run --name canaletto -p 3001:2368 -v canaletto_data:/var/lib/ghost/content --restart=always -d ghost

Tout ça c'est bien beau si on a qu'un seul container et que l'on ne veut pas exposer les ports. Le problème dans cette configuration c'est que mes containers vont s'attribuer par défaut des IP par ordre de démarrage. Par défaut le host docker à 172.17.0.1 et le premier container à être lancé prendra la suivante et ainsi de suite... Sauf que mon reverse proxy aura besoin de connaitre une IP fixe.  Il me faut donc trouver la solution pour figer leur IP. Simple il dit lui ! J'ai fini par trouver une méthode simple qui semble faire le job :

On crée un nouveau réseau Docker que je vais appeler my_bridge :

docker network create --subnet=172.18.0.0/16 --gateway=172.18.0.1 my_bridge
docker network ls  (ls pour voir les réseaux, rm pour en supprimer un)

Ensuite on crée le container en spécifiant le réseau à utiliser et l'IP à lui affecter et cela permet également d'isoler les container entre eux (entre temps j'ai bien sur crée une image de mon précédent container pour ne pas le perdre) :

docker run --name Ghost-2 --net my_bridge --ip 172.18.0.10 -p 3010:2368 -v canaletto_data:/var/lib/ghost/content --restart=always -d ghost:2

Ensuite je vais aller éditer le fichier de configuration Ghost qui se trouve dans le docker.

docker ps pour repéréer l'ID
docker container exec -it b23f1490ccfb /bin/bash

Et une fois dans le docker

apt-get update
apt-get install nano
nano config.production.json

Et là je vais modifier l'url qui se présente ainsi "url": "http://localhost:2368", en "url": "https://ghost.canaletto.fr",. Attention à bien sortir avec un exit si vous souhaitez y revenir... (ce point sur l'url n'est pas clair dans ma tête).

Une fois notre docker installé on a plusieurs choix possibles pour y accéder

  • En direct si on a exposé le port 80 ou sur le port 2368 qui est le port par défaut de Ghost dans le container, et à condition d'ouvrir le port dans aaPanel. C'est clairement plus que déconseillé car d'une part il n'y aura pas de SSL et d'autre part la sécurité minimale impose un reverse proxy et votre site exposé directement risque de se faire rapidement descendre. De plus ça limiterait à un site par IP.
  • Via le reverse proxy d'un autre firewall sur le LAN. Dans mon cas j'utilise pfsense avec HAProxy et Acme pour gérer les certificats. Je vais donc faire pointer HAProxy sur l'IP et le port exposée sur mon host Docker (https://www.domain.tld > http://192.168.1.10:3001 (IP LAN de mon serveur Docker qui est dans une VM))
  • Enfin si on est par exemple dans un VPS on va y accéder via un site créé avec NGINX, dans lequel on va gérer le certificat via Let's Encrypt et configurer la partie reverse proxy qui va pointer sur l'IP et le port du container. C'ets ce que l'on va voir dans le chapitre suivant.

NIGNX

On va ici faire les choses via aaPanel qui nous sert d'interface. Mais ceux connaissent peuvent bien sur éditer les fichiers de configuration idoines. NIGNX s'installe via l'App Store de aaPanel en un clic. Ensuite on va aller créer un site via le menu Website avec le nom de domaine que l'on souhaite utiliser, ici ghost.canaletto.fr sur le port par defaut qui est le 80. On associe pas de FTP (on pourra le faire plus tard et ainsi le faire pointer sur le répertoire des données Ghost, pas de Database car elle est gérée par Ghost, pas de PHP car c'est ici inutile. Pour le SSL il faudra de toutes façons renseigner plus tard la méthode (vérification par fichier ou par DNS via l'API CloudFlare si le DNS du domaine est géré par eux).

Ensuite on va aller ajouter un reverse proxy dans NIGNX et ainsi rediriger les requêtes vers l'IP et le port du container qui contiens Ghost en n'oubliant pas de préciser le nom du domaine utilisé. Attention, pour je ne sais quelle raison il faut configurer le certificat avant de mettre en place le reverse proxy. C'est probablement une limitation (autre nom pour un bug) liée à aaPanel.

A ce stade Ghost est normalement accessible et on peut commencer à le configurer via l'url https://www.domain.tld/ghost. La suite coule de source et est expliquée dans la documentation Ghost par ailleurs très complète.

Bonus

Le gros avantage de Ghost est que l'on peut lui injecter du code. Soit dans l'admin pour des fonctionnalités globales, soit dans une page ou un article, par exemple pour y ajouter directement un bouton Paypal si l'article présente un produit que vous vendez, ou encore un formulaire ou une vidéo externe.

Au niveau global on peut injecter du code dans deux parties, Site Header et Site Footer.

Zoom

Imaginons que je veuille agrandir les images en cliquant dessus. Je vais alors utiliser ce composant (il en existe d'autres) et ajouter deux scripts dans la partie "footer". Le premier le chargera via un CDN tandis que le second permettra son exécution.

<script async src="https://cdn.jsdelivr.net/gh/coreysnyder04/fluidbox-ghost-blog-plugin@8b384f414e8f573fe56c93cc9d16be03d3d38a01/fluidbox-ghost-blog-plugin.min.js"></script>
<script>
    window.fluidboxGhostConfig = {
      theme: 'image-backdrop', // Options: light, dark, image-backdrop, hsla(262, 100%, 82%, 0.6)
      showCaption: true, // Sets whether to capture the caption and show it below the image when expanded
    }
</script>
Le code

Comme je publie régulièrement du code je vais ajouter Prism afin d'obtenir une présentation élégante des différents langages affichés. Je commence par le header et on termine par le footer. Au passage je vais également chercher une police chez Google et j'ajoute des modification de style pour la présentation du code (au début) mais également afin d'utiliser ma nouvelle police à la place de celle proposée par le thème par défaut. Et au passage dans le footer je vais également ajouter de quoi numéroter les lignes de mon code et ouvrir les liens dans une nouvelle fenêtre.

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/themes/prism-okaidia.min.css" integrity="sha256-Ykz0nNWK7w4QWJUYR7OraN4773aMB/11aMt1nZyrhuQ=" crossorigin="anonymous" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/plugins/line-numbers/prism-line-numbers.min.css">

<link href='https://fonts.googleapis.com/css?family=Encode+Sans:300&display=swap' rel='stylesheet'>

	<style type="text/css" media="screen">
        .post-full-content pre strong {
            color: white;
        }
        .post-full-content pre {
            line-height: 0.9;
        }
        .post-full-content pre code {
            white-space: pre-wrap;
            hyphens: auto;
            line-height: 1.6;
            font-size: 0.7em;
        }
        pre[class*=language-] {
        	margin: 1.75em 0;
    	}
        .post-template p {
 			text-align: justify;
		}
  
  		p { 
            font-family: 'Encode Sans', sans-serif; 
        }      
		li { 
            font-family: 'Encode Sans', sans-serif; 
        }      
        blockquote {
            font-family: 'Encode Sans', sans-serif; 
        }      

    </style>
<script>
    window.addEventListener('DOMContentLoaded', (event) => {
        document.querySelectorAll('pre[class*=language-]').forEach(function(node) {
            node.classList.add('line-numbers');
        });
        Prism.highlightAll();
    });
</script>
<script type='text/javascript'>
  $( document ).ready(function() {
  	$(".post-content a").attr("target","moredetail");
  });
</script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/prism.min.js" integrity="sha256-NFZVyNmS1YlmiklazBA+TALYJlJtZj/y/i/oADk6CVE=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-markup-templating.min.js" integrity="sha256-41PtHfb57czcvRtAYtUhYcSaLDZ3ahSDmVZarE0uWPo=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-javascript.min.js" integrity="sha256-KxieZ8/m0L2wDwOE1+F76U3TMFw4wc55EzHvzTC6Ej8=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-css.min.js" integrity="sha256-49Y45o2obU1Yv4zkYDpMDyAa+D9sgKNbNy4ZYGRl/ls=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-php.min.js" integrity="sha256-gJj4RKQeXyXlVFu2I8jQACQZsii/YzVMhcDT99lr45I=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-sql.min.js" integrity="sha256-zgHnuWPEbzVKrT72LUtMObJgbwkv0VESwRfz7jpdsq0=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-yaml.min.js" integrity="sha256-JoqiKM2GipZjbGjNyl62d6qjQY1F9QTLriWOe4N76wE=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-sass.min.js" integrity="sha256-3oigyyaPovKMS9Ktg4ahAD1R6fOSMGASuA03DT8IrvU=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-json.min.js" integrity="sha256-18m89UBQcWGjPHHo64UD+sQx4SpMxiRI1F0MbefKXWw=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-bash.min.js" integrity="sha256-0W9ddRPtgrjvZVUxGhU/ShLxFi3WGNV2T7A7bBTuDWo=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-python.min.js" integrity="sha256-zXSwQE9cCZ8HHjjOoy6sDGyl5/3i2VFAxU8XxJWfhC0=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-ruby.min.js" integrity="sha256-SGBXZakPP3Fv0P4U6jksuwZQU5FlC22ZAANstHSSp3k=" crossorigin="anonymous"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/plugins/line-numbers/prism-line-numbers.min.js"></script>
Edition

L'éditeur de Ghost fonctionne en Markdown qui est devenu un standard de fait. Mais il est également possible de créer ses articles avec un éditeur externe comme Zettlr! ou zNote (il en existe plein d'autres). Personnellement je préfère TinyMCE à Markdown, mais c'est surement parce que je suis vieux et que j'ai été lobotomisé par Word...

Traduction

Ghost est livré en anglais. Mais il est prévu pour être traduisible. Pour autant la chose n'est pas forcément simple et va demander un peu de patience. Cela se fait au niveau du thème, et donc chaque traduction est propre à chaque thème. C'est plutôt bien expliqué ici et on trouve des thèmes (payant) qui sont traduits. Dans l'absolu il est même possible de traduire le backoffice, de même qu'il sera possible de configurer Ghost en multi langage (ça c'est plus compliqué).

J'ai commencé à traduire le thème de base, Casper. Donc on commence par faire une copie du thème, on ouvre le .zip et on le renomme. Ensuite il va falloir ajouter une répertoire /locales dans le thème et y ajouter à minima un fichier fr.json (qui suit). Le plus fastidieux sera d'aller dans les templates ajouter {{t à gauche des tous les textes et de terminer par }} (donc Page suivante devient {{t page suivante }}). Fastidieux mais pas énorme non plus.

Explications à venir... (ajout des autres ficjiers)

{
    "Back": "Retour",
    "Newer Posts": "Articles plus récents",
    "Older Posts": "Articles plus anciens",
    "Page {page} of {pages}": "Page {page} sur {pages}",
    "Subscribe": "S’abonner",
    "Subscribe to {blogtitle}": "S’abonner à {blogtitle}",
    "Subscribed!": "Abonné !",
    "with the email address": "avec l’adresse e-mail",
    "Your email address": "Votre adresse e-mail",
    "You’ve successfully subscribed to": "Vous vous êtes abonné avec succès à",
    "A collection of posts": "Une catégorie d’articles",
    "A collection of 1 post": "Une catégorie avec un article",
    "A collection of % posts": "Une catégorie avec % articles",
    "Get the latest posts delivered right to your inbox": "Recevez les derniers articles directement dans votre boîte aux lettres.",
    "Go to the front page": "Aller sur la page d’accueil",
    "Latest Posts": "Derniers articles",
    "Message:": "Message :",
    "<a href='{url}'>More posts</a> by {name}": "<a href='{url}'>Plus d’articles</a> par {name}",
    "No posts": "Aucun article",
    " (Page %)": " (Page %)",
    "Read More": "En savoir plus",
    "Read <a href='{url}'>more posts</a> by this author": "Lire <a href='{url}'>plus d’articles</a> de cet auteur",
    "Ref:": "Réf. :",
    "See all % posts": "Voir les % articles",
    "Share this": "Partager",
    "Stay up to date! Get all the latest & greatest posts delivered straight to your inbox": "Restez à jour ! Recevez tous les derniers articles directement dans votre boîte aux lettres.",
    "This post was a collaboration between": "Cet article est une collaboration entre",
    "[email protected]": "[email protected]",
    "1 post": "Un article",
    "% posts": "% articles",
    "1 min read": "1 min de lecture",
    "% min read": "% min de lecture"
}
Les intégrations

Ghost supporte beaucoup d'intégrations de base et il est possible d'en ajouter simplement en modifiant le code. Par exemple si on souhaite ajouter des commentaires Disqus il suffit d'ajouter ce code dans le thème à l'endroit idoine :

<div id="disqus_thread"></div>
<script>
    var disqus_config = function () {
        this.page.url = "{{url absolute="true"}}";
        this.page.identifier = "ghost-{{comment_id}}"
    };
    (function() {
    var d = document, s = d.createElement('script');
    s.src = 'https://EXAMPLE.disqus.com/embed.js';
    s.setAttribute('data-timestamp', +new Date());
    (d.head || d.body).appendChild(s);
    })();
</script>

Mais si on préfère que les commentaires soient gérés par Telegram et être notifié par ce canal on configurera cette app et on collera le script au même endroit. Il existe d'autre solutions comme Cove ou mieux Commento qui lui devra être installé localement, dans un autre container Docker par exemple), mais qui présente l'avantage de l'autonomie et de ne pas partager les données des commentateurs.

Pour le reste il est bien sur possible de créer ses propres intégrations en codant un peu... La documentation et l'importante communauté via le forum seront des bonnes sources.

Epilogue

Voila, je cherchait à m'affranchir du mammouth (Wordpress que je déteste) et je crois que j'ai enfin trouvé avec Ghost la bonne solution alternative. En plus comme Ghost est multi auteurs on va peut être en faire un blog communautaire. L'exemple est ici https://ghost.canaletto.fr ! Pour l'instant j'utilise BlogEngine pour ce site et peut être qu'un jour je tenterait la migration, d'autant plus que certains l'ont fait et qu'il existe des convertisseurs.

Enjoy !

Sources

 

Windows Terminal & SSH

Après nous avoir vanté pendant des décennies l'avantage d'un O/S Windows ou tout se passe en mode clic clic, force est de constater ces dernières années que le CLI revient en force, et ce notamment avec PowerShell. Et puis ces dernières années Microsoft nous a pondu Windows Terminal (ou mieux celle-ci) qui supporte bien sur PowerShell, Dos, mais également le client SSH maintenant intégré (timidement) à Windows.

Et c'est celui ci qui m'a intéressé pour remplacer mes anciens clients SSH (j'utilisait Putty ou Bitvise SSH) avec des mots de passe. Sauf que ce client SSH calqué sur ceux que l'on trouve sur Linux (OpenSSH) ne permet bien sur pas de stocker les mots de passe. Et c'est normal. Il va donc falloir apprendre à générer et utiliser des paires de clés SSH, ce qui avec ma culture Windows n'a pas été une mince affaire.

Si ce n'est pas fait on installe le client SSH après avoir installé Windows Terminal (le client SSH peut aussi fonctionner seul, et tout ce qui se rapporte ici à SSH n'est bien sur pas lié à Windows...).

PS C:\> Add-WindowsCapability -Online -Name OpenSSH.Client*

On va donc commencer par générer une paire de clés. Idéalement on se place dans le répertoire .ssh qui se trouve dans le répertoire utilisateur. Cela nous évitera d'avoir à saisir un chemin, car au final les clés devront se trouver dans se répertoire pour simplifier la suite. Il est possible de protéger les clés par une phrase de passe, ou pas.

PS C:\Users\Lionel> cd .ssh
PS C:\Users\Lionel\.ssh> ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (C:\Users\Lionel/.ssh/id_rsa): test
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in test.
Your public key has been saved in test.pub.
The key fingerprint is:
SHA256:vAwnkX2iNyxyqFBVnNVHpzPbdvy6g1VoE/RU8o7+hJ0 lionel@canaletto
The key's randomart image is:
+---[RSA 2048]----+
|    .o.o.. ...+ +|
|   .  oo  . .o.= |
|  .   o o ..+  oo|
| .   . = o   =++.|
|.   o * S   ..+o+|
| . . o B o   o.+o|
|  .     o    ooE+|
|            . .+ |
|              oo.|
+----[SHA256]-----+

On dispose maintenant de notre paire de clés, test qui est la clé privée et test.pub qui est la clé publique. Si je ne l'avais pas nommée ainsi on se serait retrouvé avec id_rsa et id_rsa.pub qui est le nom de la clé par défaut. Peu importe le nommage, ce qui compte c'est de bien identifier ces deux fichiers.

A partir de là il va falloir installer la clé publique sur le serveur de destination.

A ce stade il est donc nécessaire que le service SSH soit activé sur le serveur distant avec la possibilité de se connecter avec un mot de passe, voire en root (je sais c'est pas bien). Pour cela on édite le fichier qui va bien avec sudo nano /etc/ssh/sshd_config et on redémare le service :

On remplace PermitRootLogin prohibit-password par PermitRootLogin yes 
On remplace PasswordAuthentication no par PasswordAuthentication yes

A noter que quand on a installé une surcouche comme aaPanel, celui ci se charge du SSH au dessus de l'O/S, et on peut télécharger la clé privée à utiliser coté client directement depuis l'interface, et le cas échéant désactiver l'authentification par mot de passe. Inconvénient, pour l'instant aaPanel ne gère que le root qu'il convient donc de sécuriser.

Et c'est là que ça se complique, car si sous Linux il existe le petit outil idoine pour faire ça :

ssh-copy-id username@remote_host

Rien de tel sous Windows et il va falloir faire avec ce sont on dispose, donc le client SSH et voici la méthode que j'ai trouvée pour installer la clé publique sur le serveur :

PS C:\> cat ~/.ssh/id_rsa.pub | ssh [email protected] "mkdir -p ~/.ssh && touch ~/.ssh/authorized_keys && chmod -R go= ~/.ssh && cat >> ~/.ssh/authorized_keys"

Une confirmation sera demandée (pour ajouter ce nouveau host à la liste locale) ainsi que le mot de passe SSH du serveur distant. 

A ce stade il est possible de se connecter avec notre clé et il sera possible de désactiver (sans se précipiter) l'authentification par mot de passe en éditant le fichier sshd_config comme vu précédemment.

PS C:\> ssh [email protected]

Et si j'ai plusieurs clés destinées à plusieurs serveurs ?

C'est possible, de deux façons, la première en cli :

PS C:\> ssh -i ~/.ssh/ma_cle_perso [email protected]

Ou plus simplement :

PS C:\> ssh MonServeur

A condition d'avoir créé un fichier config dans /.ssh

Host MonServeur
  User root
  HostName 192.168.20.20
    Port 22
  IdentityFile ~/.ssh/ma_cle_perso

Alternativement je préfère cette méthode (ici)

c:\cat ~/.ssh/id_rsa.pub | ssh username@remote_host "mkdir -p ~/.ssh && touch ~/.ssh/authorized_keys && chmod -R go= ~/.ssh && cat >> ~/.ssh/authorized_keys"

Intégration au menu Windows Terminal

Windows Terminal dispose d'un menu facilement paramétrable auquel on va pouvoir ajouter des raccourcis vers nos serveurs favoris. En cliquant sur paramètres on va directement ouvrir le fichier de paramètres et ajouter une entrée correspondant à un nouveau serveur SSH distant (attention le GUID doit être unique et on peut en générer sur plusieurs sites comme ici).

   {
     "guid":  "{232b7eac-3f44-4560-9250-eee6e97ca4e3}", // Random GUID
       "hidden":  false,
     "name":  "MonServeur",
  "icon":  "c:/users/lionel/pictures/terminal/ps.png", // facultatif
     "commandline":  "ssh 192.168.20.20"
   },

Ainsi il sera très facile d'accéder à un serveur. Dans ce fichier il est également possible de modifier tous les paramètres, mais pour ça je vous laisse lire les mes sources ci-dessous. Et comme il y a du Windows dans l'air, certains n'ont pas pu résister à ajouter des icones dans le terminal...

C'est bien sur anecdotique, mais je suis sur que ça va en amuser certains, voici la marche à suivre :

  1. On télécharge ces polices et on installe la police Caskaydia*
  2. Dans PowerShell on exécute Install-Module Terminal-Icons -Scope CurrentUser
  3. Toujours dans PS : code $profile, et on ajoute cette ligne Import-Module Terminal-Icons
  4. Avec RegEdit : Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Console\TrueTypeFont: 000=CaskaydiaCove Nerd Font
  5. Dans les paramètres de Windows Terminal on ajoute "fontFace": "CaskaydiaCove Nerd Font" dans profiles > defaults.

Bonus

Si vous êtes vraiment allergiques à Microsoft (que faites vous sous Windows), il y a une très bonne alternative, Fluent Terminal.

Voilà ! Je n'ai rien inventé et cet article a surtout pour but de me permettre de mémoriser tout ça. Et si ça peut vous aider.... Et si d'aucune ont quelque chose à y ajouter, n'hésitez pas !

Enjoy ;-)

Sources