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.
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
upstreampour 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
serverpar 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 serviceinetd; 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à !