%
Puffy image/svg+xml Puffy 2019-06-14 Stéphane HUC OpenBSD Team Inkscape Puffy OpenBSD https://www.openbsd.org/art4.html English "Puffy", it's a symbol of OpenBSD

Vger : faire du multi-hosting (Gemini + nginx / OpenBSD)

Configurer les serveurs Vger et nginx, en tant que proxy, pour diffuser sur le protocol Gemini, de multiples noms de domaines

Détails relatifs à l'article

Article publié, le
et modifié le
6 minutes de lecture

Cet article contient 1150 mots.

Identifiant de l'article : tag:doc.huc.fr.eu.org,2026-05-16:/fr/sys/openbsd/vger-gemini-multihost/#6b83885d29a220d49c8031bc088c9caa4f3f12299a3e4806a569914d1fd5c5b9


Source brute de l'article :
Commit version : 9bf3d7b


Cet article est aussi disponible sur le protocole Gemini :
gemini://doc.huc.fr.eu.org/fr/sys/openbsd/vger-gemini-multihosts.gmi


Description

J’ai présenté, en Mai 2022, Vger sur cet autre article. Je ne vais pas revenir dessus.

Le propos de cet article est de démontrer la faisabilité du multi-hosting de capsule Gemini, diffusé par Vger, avec nginx servant de proxy de flux.

Info

Configuration

Reprenons rapidement les bases :

Utilisateur dédié _vger

Le but est de créer un utilisateur local, sans droits particulier si ce n’est d’exécuter le binaire vger et d’avoir les droits sur les répertoires de publication :

$ doas useradd -d /var/gemini -s /sbin/nologin _vger

ou tout autre nom d’utilisateur…

Modifications systèmes

Veillez à créer le répertoire /var/gemini, si ce n’est pas déjà fait du fait d’avoir déjà configuré Vger.

:$ doas mkdir /var/gemini/

Vérifiez les droits en lecture/écriture sur 0755 et utilisateur système root:wheel, ce qui devrait être le cas par défaut puisque vous avez créé le répertoire avec des droits administrateurs.

Ne donnez les droits à l’utilisateur _vger QUE sur les sous-répertoires dédiés à vos différents domaines, tel pour l’exemple :

  • /var/gemini/domaine1.net
  • /var/gemini/domaine2.com
:$ doas chown _vger /var/gemini/{domaine1.net,domaine2.com}

L’utilisateur _vger ne doit avoir aucun droit sur le répertoire parent /var/gemini.
Cela pourrait empêcher toute connexion SSH, selon la configuration de ce service. La configuration du service SSH n’est pas abordé dans cet article.

inetd

inetd écoute les flux entrant sur le protocole TCP sur les adresses de bouclage localhost, sur les protocoles IPv(4|6), avec les droits minimum de l’utilisateur _vger afin d’utiliser le binaire vger pour servir les données dans le répertoire /var/gemini ajoutant le support des hôtes virtuels, permettant ainsi à ce que chaque domaine puisse répondre, pour autant qu’un répertoire dédié ayant le même nom existe :

127.0.0.1:1965 stream tcp nowait _vger /usr/local/bin/vger vger -d /var/gemini -v
[::1]:1965 stream tcp6 nowait _vger /usr/local/bin/vger vger -d /var/gemini -v

On ne veut pas qu’inetd écoute l’extérieur, juste “localement” !

Certificats SSL

Là non plus, je n’aborderais pas la création des certificats SSL ; mais il est nécessaire de les générer si vous voulez utiliser le protocole HTTPS.

Nous admettrons que c’est le cas, alors veillez à les générer, puisque la configuration sous-jacente de nginx le fera, sinon à vous de vous adapter.

nginx

Abordons l’aspect le plus intéressant : la configuration de nginx pour qu’il gère le flux, tout en servant de proxy au serveur Vger. Ce dernier n’étant pas exposé directement à Internet.

Il faut toujours veiller à ce que le module stream de nginx soit chargé, avant la directive http dans le fichier de configuration relatif /etc/nginx/nginx.conf :

load_module "modules/ngx_stream_module.so";

ce qui signifie que le paquet nginx-stream soit installé !

Puis le fichier de configuration stream.conf aura les déclarations suivantes :

stream {
    log_format basic '$remote_addr $upstream_addr [$time_local] '
                     '$protocol $status $bytes_sent $bytes_received '
                     '$session_time';

    access_log logs/access.gemini.log basic;

    upstream backend_ipv4 {
        hash $remote_addr consistent;
        server 127.0.0.1:1965;
    }

    upstream backend_ipv6 {
        hash $remote_addr consistent;
        server [::1]:1965;
    }

    map $server_addr $backend {
        addresse_ipv4 backend_ipv4;
        addresse_ipv6 backend_ipv6;
    }

    server {

        listen addresse_ipv4:1965 ssl;
        listen [addresse_ipv6]:1965 ssl;

        server_name domaine1.net;

        access_log logs/domaine1.net/access.gemini.log basic;

        ssl_certificate /etc/letsencrypt/live/domaine1.net/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/domaine1.net/privkey.pem;
        ssl_trusted_certificate /etc/letsencrypt/live/domaine1.net/chain.pem;
        ssl_ciphers TLS-CHACHA20-POLY1305-SHA256:TLS-AES-256-GCM-SHA384:TLS-AES-128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
#       ssl_early_data on;  <= not allowed on stream context
        ssl_ecdh_curve X25519:P-521:P-384:P-256;
        ssl_prefer_server_ciphers on;
        ssl_protocols TLSv1.3 TLSv1.2;

        proxy_pass $backend;
#       proxy_set_header Early-Data $ssl_early_data;    <= not allowed on stream context
    }

    server {

        listen addresse_ipv4:1965 ssl;
        listen [addresse_ipv6]:1965 ssl;

        server_name domaine2.com;

        access_log logs/domaine2.com/access.gemini.log basic;

        ssl_certificate /etc/letsencrypt/live/domaine2.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/domaine2.com/privkey.pem;
        ssl_trusted_certificate /etc/letsencrypt/live/domaine2.com/chain.pem;
        ssl_ciphers TLS-CHACHA20-POLY1305-SHA256:TLS-AES-256-GCM-SHA384:TLS-AES-128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
        ssl_ecdh_curve X25519:P-521:P-384:P-256;
        ssl_prefer_server_ciphers on;
        ssl_protocols TLSv1.3 TLSv1.2;

        proxy_pass $backend;
    }
}

Lier ce fichier de configuration au fichier de configuration principal, par l’ajout d’une déclaration tel que :

include /etc/nginx/stream.conf;

Explications :

  • Un seul fichier de déclaration stream ; si vous essayez d’en créer plusieurs, tel un par nom de domaine à gérer, nginx refusera de valider la configuration !
  • toujours deux directives upstream pour gérer correctement les protocoles IPv(4|6) et mappées de manière à n’être appeler que par un seul proxy, celui correspondant au protocole IP concerné.
  • une directive server par nom de domaine à gérer, dans lesquelles configurer les directives d’écoutes, de ssl et de proxy nécessaires.
  • attention les logs seront bel et bien dans le répertoire /var/www/logs/ ou les sous-répertoires dédiés si c’est votre cas. Et oui, c’est le serveur nginx qui écoute frontalement sur les deux protocoles HTTP et Gemini.
  • pour finir, j’ai laissé deux lignes en commentaire pour ma mémoire, histoire de me rappeler que les définitions en questions ne sont pas gérées dans un flux ;)

Une dernière réflexion à-propos des logs, comme le dit la documentation de nginx, ils peuvent être déclarés à la fois dans le contexte des directives stream que les sous-jacentes server, maintenant il semble qu’ils ne soient générés que pour le premier domaine ; si quelqu’un a un avis, je suis preneur d’informations adéquates.

PF

Un petit rappel concernant une règle PF nécessaire :

host  = adresse_ipv4
host6 = adresse_ipv6

pass in quick on egress proto tcp from any to { $host $host6 } port 1965

Rechargez le jeu de règles : $ doas pfctl -f /etc/pf.conf.

Logs

Veillez à (ré)éditer le fichier de régénération des logs /etc/newsyslog.conf pour ajouter la rotation des logs, tel que pour l’exemple :

/var/www/logs/domaine1.net/access.gemini.log    664 4   *   $W0 Z   /var/run/nginx.pid SIGUSR1

C’est simple, prend quelques petites minutes à (re)configurer et fonctionnel normalement immédiatement.

Vérifications

En admettant que mon domaine principal huc.fr.eu.org et son sous-domaine doc. diffusent tous les deux leurs contenus respectifs :

inetd

⇒ Pour vérifier que le service inetd écoute en local :

:$ printf '%s\r\n' "gemini://huc.fr.eu.org" | vger -v -d /var/gemini
20 text/gemini;
# huc.fr.eu.org|gemini

(…)

:$  printf '%s\r\n' "gemini://doc.huc.fr.eu.org" | vger -v -d /var/gemini
20 text/gemini;
# Stéphane HUC :: IT Documentation (doc.huc.fr.eu.org)

(…)
  • Si la réponse est bien 20 text/gemini; dans chacune des deux interrogations, c’est que vger écoute et répond localement.
  • Si la réponse est du type telnet: Unable to connect to remote host: Connection refused, vérifiez le service inetd ; est-il actif et démarré ?!

à l’écoute ?

⇒ Vérifier que le port 1965 soit ouvert en écoute sur vos adresses IP :

:$ netstat -an | grep 1965
tcp          0      0  127.0.0.1.1965         *.*                    LISTEN
tcp          0      0  *.1965                 *.*                    LISTEN
tcp          0      0  46.23.90.29.1965       *.*                    LISTEN
tcp6         0      0  *.1965                 *.*                    LISTEN
tcp6         0      0  ::1.1965               *.*                    LISTEN
tcp6         0      0  2a03:6000:6e65:6.1965  *.*                    LISTEN

CLI

Depuis une station, avec un terminal, si vous avez les outils suivants, pour vérifiez que l’écoute sur le port 1965 à destination du serveur s’exécute bien :

⇒ avec openssl :

:$ domain=huc.fr.eu.org
:$ printf '%s%s\r\n' "gemini://" "${domain}" | openssl s_client -ign_eof -connect "${domain}":1965
Connecting to 46.23.90.29
CONNECTED(00000003)
(…)
Verify return code: 0 (ok)
---
20 text/gemini; lang=en,fr
(…)
closed

⇒ avec gnutls-cli :

:$ { printf '%s%s\r\n' "gemini://" "${domain}" ; cat -; } | gnutls-cli --no-ca-verification "${domain}":1965
Processed 144 CA certificate(s).
Resolving 'huc.fr.eu.org:1965'...
Connecting to '2a03:6000:6e65:619::29:1965'...
Connecting to '46.23.90.29:1965'...
(…)
- Handshake was completed

- Simple Client Mode:

20 text/gemini; lang=en,fr
(…)
- Peer has closed the GnuTLS connection
^C

Voilà !


Enjoy-ID!
Enjoy-IT!