Gestion des entêtes avec relayd

Une gestion fine des entêtes (headers) peut vous intéresser. Cela peut notamment servir pour indiquer aux navigateurs de garder en cache plus longtemps les fichiers téléchargés et alléger la charge du serveur, ou encore régler des questions de sécurité.

httpd n'est pas capable de gérer les entêtes. Heureusement, tout est prévu : nous allons utiliser relayd et le placer avant httpd.

Inutile d'installer quoi que ce soit, relayd est déjà présent dans OpenBSD. Elle est pas belle la vie ? 😁

Pour tester les entêtes de votre site, vous voudrez peut-être visiter celui-ci :

https://securityheaders.com/

Configuration de relayd

La configuration de relayd est écrite dans le fichier /etc/relayd.conf que nous allons éditer.

À l'intérieur, et à titre d'exemple, nous allons mettre les lignes suivantes :

http protocol "http" {
  match request header remove "Proxy"
  match response header set "X-Xss-Protection" value "1; mode=block"

  return error
  pass
}

relay "www" {
  listen on 192.0.2.2 port 80
  protocol "http"
  forward to 127.0.0.1 port 80
}

Voici ce que ces lignes signifient :

Justement, afin que httpd prenne la suite de relayd, il doit maintenant écouter en local sur le port 80. On devra donc avoir cette ligne dans la configuration de httpd :

listen on localhost port 80

Si on résume, les choses se passent désormais ainsi :

1. Un visiteur demande à voir votre site web, il se présente sur le port 80 ou 443.

2. relayd redirige le tout à httpd qui retourne la page web demandée comme il le faisait d'habitude.

Après avoir réalisé vos modifications, n'oubliez pas d'activer relayd et de redémarrer les services, ainsi que recharger le parefeu :

# rcctl enable relayd
# rcctl restart httpd
# rcctl start relayd

Notez que cela va modifier l'apparence des journaux pour httpd.

Vous préférerez peut-être utiliser le format "forwarded":

log style forwarded

Vous pouvez consulter un exemple de configuration à la fin du document.

Exemple relayd.conf

TLS ou https

Si votre site propose un accès chiffré avec une adresse https://..., (c'est bien ! 😉), la configuration de relayd peut-être déroutante.

Ci-dessous, voici un exemple de configuration de relayd correspondante. Notez les mentions de tls :

http protocol "https" {
  match request header remove "Proxy"
  match response header set "X-Xss-Protection" value "1; mode=block"

  return error
  pass

  tls keypair chezmoi.tld
  tls keypair ici.tld
}

relay "tlsforward" {
  listen on 192.0.2.2 port 443 tls
  protocol "https"
  forward with tls to 127.0.0.1 port 443
}

Prêtez attention aux lignes tls keypair. Elles permettent de définir les certificats et clés à utiliser pour le chiffrement TLS. Dans l'exemple ci-dessus, on précise deux certificats possibles pour deux domaines différents. Vous pouvez ajouter une ligne pour chaque domaine géré par votre serveur.

Toutefois, il est indispensable que ces certificats soient placés comme on l'a présenté dans la partie sur acme-client. Autrement dit, vos certificats et clés seront les fichiers suivants :

/etc/ssl/private/chezmoi.tld.key
/etc/ssl/chezmoi.tld.crt

De plus, le fichier /etc/ssl/chezmoi.tld.crt doit être le certificat "full chain".

Le plus simple sera peut-être de modifier votre configuration pour acme en conséquence. Par exemple, dans æcme.conf:

domain chezmoi.tld {
	domain key "/etc/ssl/private/chezmoi.tld.key"
	domain certificate "/etc/ssl/chezmoi.tld-cert.crt"
	domain chain certificate "/etc/ssl/chezmoi.tld-chain.crt"
	domain full chain certificate "/etc/ssl/chezmoi.tld.crt"
	sign with letsencrypt
}

Inutile de préciser quoi que ce soit en plus dans la configuration de relayd ou httpd, tout fonctionne normalement comme prévu avec l'utilisation de vos certificats 😉

Renouvellement des certificats

Si vous renouvelez vos certificats avec acme-client, pensez à recharger relayd pour qu'il tienne compte des changements. Par exemple :

/usr/sbin/acme-client -v chezmoi.tld && \
/usr/sbin/rcctl reload relayd

Ajoutez la ligne suivante si vous n'avez pas modifié l'emplacement des certificats:

cp /etc/ssl/chezmoi.tld.crt /etc/ssl/chezmoi.tld.crt

IPv6

Si vous voulez proposer un accès IPv4 et IPv6 avec relayd, vous devrez configurer les 2.

Tout d'abord, assurez-vous d'avoir dans le fichier /etc/hosts:

127.0.0.1 localhost
::1 localhost

Vous avez ainsi l'ipv4 et l'ipv6 correspondant à "localhost", ce qui sera utile ensuite.

Maintenant, créer pour dans la configuration de relayd 2 entrées :

relay "http" {
        listen on $ext_ip4 port 80
        protocol "http"
        forward to 127.0.0.1 port 80
}

relay "http6" {
        listen on $ext_ip6 port 80
        protocol "http"
        forward to ::1 port 80
}

La configuration de httpd pourra être plus simple en profitant de "localhost" :

listen on localhost port 80

Gestion des entêtes

Httpoxy

Si votre site n'est accessible qu'en http (pas de chiffrement), alors je vous conseille vivement de vous prémunir contre une faille connue sous le doux nom de httpoxy. Cela peut permettre (en gros hein...) à un pirate de paraître venir de votre serveur lorsqu'il réalise une attaque.

httpoxy
Ou alors je mets tout en https, c'est plus simple non ?

Oui ça serait plus simple et mieux pour la sécurité de vos visiteurs.

Voici à quoi ressemblera le fichier relayd.conf minimal pour s'en protéger :

http protocol "http" {
    match request header remove "Proxy"
    return error
    pass
}

relay "www" {
    listen on 127.0.0.1 port 8080
    protocol "http"
    forward to destination
}

Autres entêtes de sécurité

En plus du précédent, vous pouvez ajouter d'autres règles pour améliorer la sécurité de votre serveur en modifiant ces entêtes :

match request header remove "Proxy"
match response header set "X-Xss-Protection" value "1; mode=block"
match response header set "Frame-Options" value "SAMEORIGIN"
match response header set "X-Frame-Options" value "SAMEORIGIN"
match response header set "X-Robots-Tag" value "index,nofollow"
match response header set "X-Permitted-Cross-Domain-Policies" value "none"
match response header set "X-Download-Options" value "noopen"
match response header set "X-Content-Type-Options" value "nosniff"
match response header set "Permissions-Policy" value "interest-cohort=()"

Si vous n'hébergez qu'un seul nom de domaine, vous devriez ajouter ceci :

match response header set "Access-Control-Allow-Origin" value "chezmoi.tld"

Plus d'informations :

https://developer.mozilla.orgdocs/Web/HTTP/Headers/Access-Control-Allow-Origin
https://blog.stephane-huc.net/web/http/index

Optimisation du cache et de la bande passante

Que vous ayez une bande passante performante ou non, je vous conseille vivement d'optimiser le nombre de requêtes qu'un visiteur réalisera lorsqu'il visitera votre site. Pour cela, vous pouvez indiquer à son navigateur de garder les ressources (images, feuilles de style...) en cache pendant un temps assez long (21 jours dans l'exemple : 21 jours = 1814400 secondes).

Voici les éléments à ajouter dans la section "protocol" juste avant le mot clé "pass" :

match request path "/*.css" tag "CACHE"
match request path "/*.js" tag "CACHE"
match request path "/*.atom" tag "CACHE"
match request path "/*.rss" tag "CACHE"
match request path "/*.xml" tag "CACHE"
match request path "/*.jpg" tag "CACHE"
match request path "/*.png" tag "CACHE"
match request path "/*.svg" tag "CACHE"
match request path "/*.gif" tag "CACHE"
match request path "/*.ico" tag "CACHE"

match response tagged "CACHE" header set "Cache-Control" value "max-age=1814400"

L'idée est très simple, à chaque fois qu'un visiteur demande un fichier se terminant par l'une des extensions ".css, .js, .atom, ... , .ico", on colle sur la requête une étiquette "CACHE". À la fin, lorsque relayd détecte une requête avec cette étiquette, il ajoute un entête "Cache-Control" avec une valeur assez grande pour que le navigateur ne tente pas de télécharger à nouveau ce fichier de sitôt.

Définir l'encodage par défaut

Pour être sûr qu'un texte avec accents s'affiche bien, on peut préciser l'encodage. Pour tous les fichiers html et les URL se terminant avec "/", on applique l'étiquette "UTF8" puis on ajoute un entête:

match request path "/*.html" tag "UTF8"
match request path "*/" tag "UTF8"
match response tagged "UTF8" header set "Content-Type" value "text/html;charset=UTF-8"

Note à propos des étiquette

Vous ne pouvez pas appliquer plusieurs étiquettes à la fois. Si vous voulez ajouter plusieurs entêtes, alors vous devrez le faire au fur et à mesure. Par exemple, si je veux augmenter le cache pour une page html et préciser l'encodage, alors j'applique d'abord le tag "CACHE", définit l'entête "Cache-Control", puis plus loin applique le tag "UTF8" et définit le "Content-Type" :

match request path "/*.html" tag "CACHE"
match response tagged "CACHE" header set "Cache-Control" value "max-age=1814400"

match request path "/*.html" tag "UTF8"
match response tagged "UTF8" header set "Content-Type" value "text/html;charset=UTF-8"