Journal Durcir nginx et PHP avec systemd

Posté par  (site web personnel) . Licence CC By‑SA.
55
3
fév.
2022

Sommaire

Dans une installation Linux-nginx-PHP classique, on a:

  • systemd qui doit orchestrer les services et s'exécute en root (inévitable)
  • nginx qui reçoit les les requêtes web et les répartit, notamment vers php-fpm. Il fonctionne avec un processus maître qui fonctionne en root pour se mettre en écoute sur le port 443 et des workers, non privilégiés, qui traitent les requêtes
  • php-fpm qui tourne sous root, reçoit les requêtes vers des scripts PHP de la part de nginx et les répartit vers des workers moins privilégiés (qui, sous Debian, s'exécutent sous l'utilisateur www-data).

Ça fait beaucoup trop de choses qui tournent avec les droits root. Systemd, c'est inévitable. Mais:

  • php-fpm il n'y a aucune raison
  • nginx a simplement besoin de pouvoir écouter sur le port 443 (port privilégié). On peut confier cette tâche à systemd et ainsi réduire les droits de nginx.

On peut donc configurer tout ça de façon :

  • à exécuter php-fpm et nginx sans les droits root grâce à l'activation par socket
  • à isoler les différentes applications PHP qui s'exécutent grâce aux template units de systemd
  • à bloquer certaines escalades de privilège vers root (Baron Samedit, PwnKit, PHP-FPM local root vulnerability) et empêcher l'exploitation de failles de type SSRF

Les fichiers de configuration

Je reprends ici la configuration correspondant à Debian bullseye (stable en février 2022). Il faudra sans doute adapter les chemins/exécutables ou versions à votre système.

PHP

  • /etc/systemd/system/php-fpm@.service :

    [Unit]
    Description=The PHP 7.4 FastCGI Process Manager for %i
    Documentation=man:php-fpm7.4(8)
    After=network.target php-fpm@%i.socket
    # On a besoin de php-fpm@%i.socket pour ouvrir les ports et les envoyer à PHP-FPM
    Requires=php-fpm@%i.socket
    
    [Service]
    Type=notify
    Environment="FPM_SOCKETS=/run/php/%i.socket=3"
    ExecStart=/usr/sbin/php-fpm7.4 --nodaemonize --fpm-config /etc/php/7.4/fpm/%i.conf
    ExecReload=/bin/kill -USR2 $MAINPID
    StateDirectory=%i
    RuntimeDirectory=%i
    LogsDirectory=%i
    SupplementaryGroups=%i
    
    # Options de durcissement
    DynamicUser=true
    PrivateUsers=true
    ProtectSystem=strict
    PrivateTmp=true
    PrivateNetwork=true
    NoNewPrivileges=true
    RestrictAddressFamilies=AF_UNIX
    IPAddressDeny=any
    SystemCallFilter=@system-service
    SystemCallFilter=~@resources @privileged
    CapabilityBoundingSet=
    MemoryDenyWriteExecute=true
    UMask=0077
    ProtectHome=true
    PrivateDevices=true
    ProtectControlGroups=true
    ProtectKernelModules=true
    ProtectKernelTunables=true
    ProtectKernelLogs=true
    SystemCallArchitectures=native
    RestrictNamespaces=true
    LockPersonality=true
    RestrictRealtime=true
    RemoveIPC=true
    ProtectHostname=true
    ProtectClock=true
    ProtectProc=invisible
    ProcSubset=pid
    RestrictSUIDSGID=true
    
  • /etc/systemd/system/php-fpm@.socket :

    [Unit]
    PartOf=php-fpm@.service
    Documentation=https://freedesktop.org/wiki/Software/systemd/DaemonSocketActivation/#php-fpm
    
    [Socket]
    ListenStream=/run/php/%i.socket
    SocketMode=0660
    SocketUser=php
    SocketGroup=www-data
    
    [Install]
    WantedBy=sockets.target
    
  • /etc/php/7.4/fpm/mon-application.conf (pour chaque application PHP que vous souhaitez isoler) :

    [global]
    pid = /run/mon-application/pid
    error_log = /var/log/mon-application/error.log
    
    [www]
    listen = /run/php/mon-application.socket
    access.log = /var/log/mon-application/access.log
    php_admin_value[session.save_path] = /var/lib/mon-application
    php_admin_value[pcre.jit] = 0
    
    ; Ci-dessous sont les options proposées par Debian par défaut pour un worker php
    ; Je vous renvoie à la documentation pour plus d'info : https://www.php.net/manual/fr/install.fpm.configuration.php
    pm = dynamic
    pm.max_children = 5
    pm.start_servers = 2
    pm.min_spare_servers = 1
    pm.max_spare_servers = 3
    

Nginx

  • /etc/systemd/system/nginx.service :

    [Unit]
    Description=A high performance web server and a reverse proxy server
    Documentation=man:nginx(8)
    After=network.target nss-lookup.target nginx.socket
    # On a besoin de nginx.socket pour ouvrir les ports et les envoyer à nginx
    Requires=nginx.socket
    
    [Service]
    PIDFile=/run/nginx/pid
    ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
    ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/nginx/pid
    TimeoutStopSec=5
    Environment=NGINX=3:4:5:6:
    NonBlocking=true
    # dans la ligne suivante : pensez à ajouter toute application pour laquelle vous auriez créé un groupe
    SupplementaryGroups=ssl-cert mon-application
    
    # Options de durcissement
    User=www-data
    PrivateUsers=true
    LogsDirectory=nginx
    ProtectSystem=strict
    RuntimeDirectory=nginx
    ReadOnlyPaths=/etc/certificats/actif/
    PrivateTmp=true
    NoNewPrivileges=true
    RestrictAddressFamilies=AF_UNIX
    IPAddressDeny=any
    PrivateNetwork=true
    SystemCallFilter=@system-service
    SystemCallFilter=~@resources @privileged
    CapabilityBoundingSet=
    MemoryDenyWriteExecute=true
    UMask=0077
    ProtectHome=true
    PrivateDevices=true
    ProtectControlGroups=true
    ProtectKernelModules=true
    ProtectKernelTunables=true
    ProtectKernelLogs=true
    SystemCallArchitectures=native
    RestrictNamespaces=true
    LockPersonality=true
    RestrictRealtime=true
    RemoveIPC=true
    ProtectHostname=true
    ProtectClock=true
    ProtectProc=invisible
    ProcSubset=pid
    RestrictSUIDSGID=true
    
    [Install]
    WantedBy=multi-user.target
    
  • /etc/systemd/system/nginx.socket :

    [Unit]
    PartOf=nginx.service
    Documentation=https://freedesktop.org/wiki/Software/systemd/DaemonSocketActivation/
    
    [Socket]
    ListenStream=80
    ListenStream=0.0.0.0:80
    ListenStream=443
    ListenStream=0.0.0.0:443
    BindIPv6Only=ipv6-only
    
  • Dans /etc/nginx/nginx.conf, il faut :

    • retirer la directive user
    • modifier la directive pid pour indiquer un fichier sous /run/nginx
    • adapter les directives fastcgi_pass pour pointer sur les adresses du type unix:/run/php/mon-application.socket

    Exemple d'un fichier de configuration minimal :

    pid /run/nginx/pid;
    
    events {
    }
    
    http {
        include mime.types;
    
        # Serveur HTTP (sans chiffrement)
        server {
            listen [::]:80 default_server;
            listen 80 default_server;
            server_name _;
            return 404;
        }
        # Afficher les statistiques nginx pour les requêtes depuis localhost et ciblant 127.0.1.1 :
        server {
            listen 127.0.1.1:80;
            server_name _;
            stub_status;
        }
    
        # Serveur HTTP redirigeant vers HTTPS
        server {
            listen [::]:80;
            listen 80;
            server_name mon-application.example.com;
    
            # On redirige vers la version https
            location / {
                return 302 https://$host$request_uri;
            }
        }
    
        # Serveur HTTPS (avec chiffrement TLS)
        server {
            listen [::]:443 ssl default_server;
            listen 443 ssl default_server;
            server_name mon-application.example.com;
    
            ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
            ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
            ssl_protocols TLSv1.3;
    
            index index.php index.html;
            root /var/www/mon-application/public/;
            location ~ \.php$ {
                include fastcgi.conf;
                fastcgi_pass unix:/run/php/mon-application.socket;
            }
        }
    }
    

Installation

Les commandes suivantes sont à lancer avec root ou via sudo

systemctl disable --now php7.4-fpm.service              # Désactivation des anciens processus si nécessaire
systemctl daemon-reload                                 # Prise en compte des nouveaux fichiers systemd
                # pour chaque application PHP installée :
groupadd --system mon-application
chown -R root:mon-application /var/www/mon-application
find /var/www/mon-application -type f -exec chmod 640 {} + -o -type d -exec echo chmod 750 {} + 
systemctl enable --now php-fpm@mon-application.socket
                # Pour chaque clé/certificat TLS utilisé par nginx :
chgrp ssl-cert /etc/xxx/yyyy/mes-certificats.key
chmod 640 /etc/xxx/yyyy/mes-certificats.key
systemctl restart nginx

Activation par socket

Systemd peut se placer en écoute sur des ports réseau, des sockets ou des files FIFO puis transférer les flux entrants aux programmes correspondants, pour peu que ceux-ci soient configurés pour : ils reçoivent alors ces sockets sous forme de file descriptors numérotés à partir de 3 (après STDIN, STDOUT et STDERR). Il est possible de ne démarrer le service voulu que lorsqu'un premier message arrive sur la socket en question, c'est même la motivation initiale de cet outil.

Nginx peut être configuré pour récupérer ces sockets grâce à la variable d'environnement (non documentée) NGINX. La seule façon dont j'ai réussi à faire fonctionner nginx de cette façon a été d'indiquer la valeur 3:4:5:6: (info glanée sur la page DaemonSocketActivation for nginx and php-fpm).

PHP-FPM peut aussi être configuré pour récupérer ces sockets grâce à une variable d'environnement non documentée : FPM_SOCKETS. Elle est de la forme : /run/php/app.socket=3 ou /run/php/pool1.socket=3,/run/php/pool2.socket=4,/run/php/pool3.socket=5 si on a plusieurs pool de workers PHP orchestrés par FPM (mais s'il s'agit de séparer plusieurs applications, il vaut mieux utiliser des services systemd différents, cf. plus bas).

Confiner les services avec systemd

Directives de confinement

Systemd met à disposition de nombreuses options pour confiner les services :

  • User, Group : le service est lancé avec les privilèges de l'utilisateur et/ou du groupe indiqué. Cela évite de lancer avec les droits de l'utilisateur root tout-puissant (ce qui est fait par défaut pour nginx et php-fpm). Il faut alors s'assurer que l'utilisateur indiqué a bien accès aux ressources nécessaires (fichiers PHP pour php-fpm, fichiers statiques servis par nginx, etc.)
  • DynamicUser : le service est lancé sous un utilisateur créé pour ça, et détruit lorsque le service s'éteint. Il est possible de donner des droit sur des répertoires particuliers via l'option SupplementaryGroups
  • PrivateNetwork, PrivateUsers, PrivateIPC, ProtectHostname, PrivateMounts : place le service dans des namespaces isolés du reste du système, reprenant ainsi les outils d'isolation utilisés par les techologies de conteneurisation (Docker, LXC). Si on souhaite ajouter une cage "chroot" pour se rapprocher davantage d'un conteneur type docker, on peut utiliser les directives RootDirectory ou RootImage
  • PrivateTmp, ProtectHome, ProtectSystem : isole /tmp des autres applications, rend /home et /root inaccessibles et place l'ensemble du système en lecture seule. On peut relâcher ces contraintes en indiquant des répertoires qui doivent rester accessibles en lecture ou en écriture (ReadOnlyPaths,ReadWritePaths) ou plus simplement en indiquant quels répertoires sous /run, /var/lib, /var/cache ou /var/log sont utilisés (RuntimeDirectory, StateDirectory, CacheDirectory et LogsDirectory)
  • SystemCallFilter, CapabilityBoundingSet : limitent les appels système et les capabilities atteignables par le processus ou ses enfants.
  • IPAddressAllow, IPAddressDeny : filtrer les flux réseaux autorisés en entrée et en sortie. Les flux établis via les sockets associés au service ne sont pas concernés par ces restrictions.

… et tout un tas d'autres qu'il est fastidieux de lister ici. Je vous renvoie à Mastering systemd: Securing and sandboxing applications and services qui présente les options introduites dans RHEL 7 et 8. De nouvelles directives sont ajoutées au fil des versions de systemd.

Évaluer l'efficacité de ces mesures

La commande systemd-analyze security nginx.service permet de lister les directives appliquées ou non et de calculer un score entre 0 (service fortement durci, dont la compromission aura un impact minimal sur le système en terme de sécurité) et 10 (service non durci, dont la compromission peut entraîner la compromission de tout le système). Sur une Debian stable, passer du fichier nginx.service proposé par défaut à celui poposé ci-dessus fait descendre le score de 9,6 à 0,2.

Pour évaluer plus concrètement le confinement mis en place, on peut déployer un web shell PHP. Quelques résultats :

  • ls /home : Error in Code Execution --> ls: cannot open directory '/home': Permission denied
  • ls -alh /run : on constate que tous les fichiers sont détenus soit par root, soit par nobody (on ne "voit pas" les autres utilisateurs). Cependant, getent passwd permet de retrouver la liste des comptes installés.
  • ls /tmp : vide
  • ls /proc -alh : on ne voit que les processus exécutés par php-mon-application.
  • nslookup linuxfr.org : Error in Code Execution --> ;; connection timed out; no servers could be reached
  • sudo /bin/true : Error in Code Execution --> sudo: effective uid is not 0, is sudo installed setuid root?

Relâcher les mesures de confinement

Certaines de ces mesures peuvent être trop drastiques pour les applications hébergées. Le blocage du réseau, par exemple, empêche nginx de récupérer ses réponses OCSP pour agrafage, ou peut l'empêcher de faire reverse proxy vers des applications sur d'autres machines ou sur la même machine mais accessibles uniquement via TCP/IP (directives proxy_pass http://127.0.0.1:xxxx/;). Si votre base de données est hébergée sur le même serveur, l'application PHP peut sans doute s'y connecter par socket Unix, mais si ça n'est pas le cas elle aura besoin d'accéder au réseau. Elle peut aussi avoir besoin d'écrire dans tel ou tel répertoire. Enfin, il est possible que telle ou telle bibliothèque ait besoin d'appels systèmes indûment bloqués par le confinement trop strict imposé (c'est ainsi qu'on a dû désactiver le JIT pcre dans la configuration car incompatible avec la directive MemoryDenyWriteExecute).

Pour cela, vous pouvez modifier directement les fichiers .service ci-dessus ou créer le dossier /etc/systemd/system/php-fpm@mon-application.service.d/ (par exemple) et y ajouter un fichier relachement.conf :

[Service]
# Pour rendre le dossier 'upload' accessible en écriture, il faut ̀ chmod 660` le dossier et ajouter la ligne :
ReadWritePaths=/var/www/mon-application/upload

# Pour autoriser les flux réseau :
PrivateNetwork=false
RestrictAddressFamilies=
# Si on veut ouvrir à des requêtes sur localhost uniquement :
RestrictAddressFamilies=AF_UNIX AF_INET
IPAddressAllow=localhost
# Si on veut ouvrir à des requêtes sur tout internet :
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
IPAddressAllow=any

Instances de services systemd

Les fichiers PHP de systemd ci-dessus contiennent un @ et on a indiqué %i à plusieurs reprises : cela permet de créer plusieurs instances de services similaires.

Ainsi, si vous avez 3 applications différentes, vous pouvez créer autant de fichiers de configuration sous /etc/php/7.4/fpm/ et activer ces services indépendamment avec systemctl start|stop|reload|status php-fpm@applicationX.service. Chaque service proposera sa propre socket sous /run/php, il faudra configurer nginx en conséquence.

En restreignant les droits d'accès à /var/www/applicationX/ comme proposé ci-dessus (section installation) vous empêchez un attaquant qui compromettrait une application d'accéder aux informations liées à une autre des applications.

Considérations de sécurité

Sauf faille de sécurité supplémentaire, une fois ces mesures mises en place la compromission de nginx ou php-fpm ne pourra plus aboutir aux conséquences suivantes :

  • installation d'applications arbitraires / rootkit
  • accès aux clés SSH du serveur
  • escalade de privilège vers root
  • exfiltration de données ou exécution de code via Server-Side Request Forgery
  • accès direct aux données stockées sur le serveur

Néanmoins :

  • l'attaquant qui compromet une application PHP déployée sur le serveur pourra toujours :
    • voler les accès et les données d'un autre utilisateur
    • accéder à la base de données sans filtre ou à toute donnée à laquelle l'application donne accès (il ne pourra pas accéder au code ou aux données d'une autre application hébergée sur la même machine)
  • l'attaquant qui compromet nginx pourra inévitablement accéder aux clés TLS et potentiellement déchiffrer le traffic des autres usagers de l'application

Pour sécuriser davantage, il faut configurer un Linux Security Module comme apparmor ou SELinux.

Références

  • # Confiner les applications avec systemd

    Posté par  . Évalué à 6.

    Merci pour le journal.

    Comme je n'utilise pas nginx et php, j'ai cherché à utiliser ces possibilités pour des applications classiques (= pas des services).

    C'est possible en utilisant systemd-run.

    Par exemple, pour lancer un shell qui n'aura pas d'accès au réseau, et pour lequel le répertoire Home sera en lecture seule:

    systemd-run --user --property=PrivateUsers=true --property=PrivateNetwork=true --property=ProtectHome=read-only --wait -q --shell

    Ou encore pour lancer gedit sans accès réseau ni accès au Home:

    systemd-run --user --property=PrivateUsers=true --property=PrivateNetwork=true --property=ProtectHome=yes --wait -q gedit

    (PrivateUsers=true semble être nécessaire pour pouvoir utiliser PrivateNetwork=true sans droits particuliers)

    En utilisant un alias, ça donne un moyen simple de lancer des applis avec des droits limités.

    • [^] # Re: Confiner les applications avec systemd

      Posté par  (site web personnel) . Évalué à 2.

      Pour des applications graphiques, on peut installer des applications depuis flathub avec flatpak (via "Logiciels" sous Gnome) et restreindre les droits avec flatseal.

      Mais on se rapproche des conteneurs, avec leur principal défaut : ils embarquent leurs dépendances, donc si une faille touche une librairie partagée, type openssl, il faut attendre que l'empaqueteur de chaque application qui l'embarque mette à jour sa librairie (ou, s'agissant de flatpak, la version de la plate-forme sur laquelle il repose).

      • [^] # Re: Confiner les applications avec systemd

        Posté par  (site web personnel) . Évalué à 2.

        C'est pas spécialement propre à des conteneurs, tu as le même souci avec n'importe quoi, c'est la difficulté de comprendre ce qui est supporté et/ou maintenu qui pose souci.

        On voit pas le probléme pour les distributions car si le logiciel est dispo, alors il est maintenu (à minima). Mais par exemple, sous Ubuntu, tu n'as pas les mêmes garanties entre le dépôt main et universe.

        Techniquement, il n'y a rien qui empêcherais d'avoir un dépôt de conteneur supporté par un des projets de distributions, et certains essayent (exemple, Fedora).

        Mais ce que veulent les gens, c'est pas d'avoir des paquets plus facile à utiliser (c'est relativement facile) ou à faire (c'est surtout dur à cause des guidelines), mais bien de sortir des restrictions du à la distribution unifié et synchronisé de logiciels.

        Sauf que du coup, c'est la foire. Mais c'est la foire parce que c'est ce qu'on cherche.

  • # Commentaire supprimé

    Posté par  . Évalué à 8.

    Ce commentaire a été supprimé par l’équipe de modération.

    • [^] # Re: Et les pools php-fpm ?

      Posté par  (site web personnel) . Évalué à 6.

      Je cherche à minimiser le nombre d'applications qui tournent sous root : le moins j'en ai, le mieux je me porte.

      En l'occurrence, ce qui a initialement motivé l'écriture de cet article, c'est la faille PHP-FPM local root vulnerability publiée en octobre 2021 : les workers FPM peuvent contaminer le processus maître et, si le maître est root, gagner les pleins pouvoirs sur la machine. Déléguer à FPM le rôle de créer les workers en tant qu'utilisateurs distincts nécessite de donner à FPM les droits root : je passe donc par systemd pour remplir cette fonction (systemd ayant déjà ces droits par nature) et je retire les droits root à PHP-FPM.

      • [^] # Commentaire supprimé

        Posté par  . Évalué à 3.

        Ce commentaire a été supprimé par l’équipe de modération.

  • # CAP_NET_BIND_SERVICE

    Posté par  (site web personnel) . Évalué à 6.

    Avec la capabilitécapacité CAP_NET_BIND_SERVICE nginx n'a pas besoin d'être root pout binder le 443 et le 80. Elle peut être ajoutée directement dans l'unit systemd.

    • [^] # Re: CAP_NET_BIND_SERVICE

      Posté par  . Évalué à 6.

      Mais ça peut lui permettre d'écouter sur un autre port (surtout s'il est malveillant). Donc je préfère l'autre méthode.

      « Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche

    • [^] # Re: CAP_NET_BIND_SERVICE

      Posté par  . Évalué à 3.

      Cependant, cela ne va pas fonctionner avec PrivateNetwork=true.

      • [^] # Re: CAP_NET_BIND_SERVICE

        Posté par  (site web personnel) . Évalué à 4.

        Cependant, cela ne va pas fonctionner avec PrivateNetwork=true.

        Oui PrivateNetwork=true c'est vraiment intéressant. Mais comment se comporte un wordpress de base lorsqu'il est isolé du réseau ?

        Par ailleurs les socket systemd ont un overhead non négligeable. Pour ma part, j'ai mesuré ~ 15% d’écart

    • [^] # Re: CAP_NET_BIND_SERVICE

      Posté par  (site web personnel) . Évalué à 2.

      Comment vous feriez pour gérer plusieurs configurations?

      Actuellement, tout ce qui arrive sur le 80 (j'ai un reverse proxy en amont) est renvoyé vers un port en particulier selon le domaine et le sous-domaine. Je trouve que c'est pratique.

      Donc, j'ai un nginx qui écoute les ports 80 à 100.
      Si je dois limiter l'écoute des ports 80 (et 443 le cas échéant) pour nginx, je vis mal comment faire autrement et avoir plusieurs configurations.

      Pourquoi bloquer la publicité et les traqueurs : https://greboca.com/Pourquoi-bloquer-la-publicite-et-les-traqueurs.html

      • [^] # Re: CAP_NET_BIND_SERVICE

        Posté par  . Évalué à 3.

        Utilise des sockets unix pour la communication entre le reverse proxy et les instances nginx.

      • [^] # Re: CAP_NET_BIND_SERVICE

        Posté par  (site web personnel) . Évalué à 1.

        Comment vous feriez pour gérer plusieurs configurations?

        Actuellement, tout ce qui arrive sur le 80 (j'ai un reverse proxy en amont) est renvoyé vers un port en particulier selon le domaine et le sous-domaine. Je trouve que c'est pratique.

        Nginx peut servir plusieurs domaines sur le même port et la même adresse IP.

        Par exemple, la configuration suivante est valide et fait bien ce qu'on attend :

        server {
            listen 80;
            listen ::80;
            server_name application1.example.com;
            # la configuration de application 1, avec les blocs location si tu veux ...
        }
        
        server {
            listen 80;
            listen ::80;
            server_name application2.example.net;
            # la configuration de application 2, avec les blocs location si tu veux ...
        }
        
        server {
            listen 80;
            listen ::80;
            server_name application3.example.fr application3.example.net;
            # la configuration de application 3, avec les blocs location si tu veux ...
        }
        

        nginx va se baser sur l'hôte demandé dans la requête HTTP pour sélectionner le bon bloc server.

        Référence : Server names (doc nginx).

        • [^] # Re: CAP_NET_BIND_SERVICE

          Posté par  (site web personnel) . Évalué à 2.

          Oui, c'est ce que je fais, pour le port 80 qui me sert de triage, puis je sépare ensuite les configuration avec un port différent.

          Parfois la configuration peut être assez longue, ou bien je veux avoir plusieurs versions. Tout dans un seul fichier, ce ne serait pas supportable.

          Pourquoi bloquer la publicité et les traqueurs : https://greboca.com/Pourquoi-bloquer-la-publicite-et-les-traqueurs.html

          • [^] # Re: CAP_NET_BIND_SERVICE

            Posté par  (site web personnel) . Évalué à 4. Dernière modification le 04 février 2022 à 12:02.

            Tout dans un seul fichier, ce ne serait pas supportable.

            La configuration nginx par défaut, pour Debian, résout ce problème avec :

            
            http {
                    # snip
                    include /etc/nginx/conf.d/*.conf;
                    include /etc/nginx/sites-enabled/*;
            }
            

            Avec l'idée que chaque bloc serveur dispose d'un fichier correspondant dans /etc/nginx/sites-available/ et activé par un lien depuis /etc/nginx/sites-enabled/.

            • [^] # Re: CAP_NET_BIND_SERVICE

              Posté par  (site web personnel) . Évalué à 2. Dernière modification le 04 février 2022 à 13:51.

              Et dire que je me sers de ces include pour tout ce qui est générique… ça m'était sorti de la tête…

              Du coup, je vais même pouvoir faire un test sans trop changer mon infra, et réduire aussi le nombre de ports en écoute par Nginx au fur et à mesure… et je ne serai plus obligé de garder une liste des ports utilisés et utilisables…

              Merci pour le rappel!

              Pourquoi bloquer la publicité et les traqueurs : https://greboca.com/Pourquoi-bloquer-la-publicite-et-les-traqueurs.html

  • # uWSGI

    Posté par  . Évalué à 4.

    Très intéressant, merci.

    Je m'étais attelé à la même tâche avec uWSGI, que je trouvais justement plus facile d'intégrer à l'activation par socket de systemd. Malheureusement… le plugin PHP (voir uWSGI en entier) est plutôt à l'abandon et le support de PHP 8.1 (qui vient d'arriver dans Debian/testing) est encore bien cassé. Le correctif a été intégré, mais il n'y a toujours pas de version publiée.

    Donc je vais me pencher pour l'utilisation de PHP-FPM, en enlevant les pools que j'utilisais jusque là.

    J'irais même jusqu'à créer des utilisateurs différents par applications (www-application1, etc) et à le mettre comme paramètre de l'instance php-fpm@.socket:

    SocketUser=www-%i
    SocketGroup=www-%i
    

    Mais y a un truc que je ne comprends pas… Dans php-fpm@.service il y a SupplementaryGroups=%i ce qui veut dire que le groupe mon-application doit exister. Par contre, php-fpm@.socket crée un socket 0660 appartenant à php:www-data. Il manque pas un truc ?

    Ah, et pour ReadWritePaths avec DynamicUser, ça fait quoi ? Les fichiers sont écrits avec un UID/GID qui peut être réutilisé :

    Care should be taken that any processes running as part of a unit for which dynamic users/groups are enabled do not leave files or directories owned by these users/groups around, as a different unit might get the same UID/GID assigned later on, and thus gain access to these files or directories.

    J'ai l'impression qu'un utilisateur statiquement défini est préférable si l'application a besoin d'écrire son fichier de configuration (au hasard, Nextcloud).

    Pour rebondir, d'après la documentation, si DynamicUsers=true, plein d'options sont mises, donc le fichier est plein de redondances. Mais, c'est vrai, des fois, il vaut mieux être explicite…

    • [^] # Re: uWSGI

      Posté par  . Évalué à 3.

      Je me réponds à :

      Ah, et pour ReadWritePaths avec DynamicUser, ça fait quoi ? Les fichiers sont écrits avec un UID/GID qui peut être réutilisé

      En effet, pour la documentation de StateDirectory et consort, ça dit :

      If DynamicUser= is used, the logic for CacheDirectory=, LogsDirectory= and StateDirectory= is slightly altered: the directories are created below /var/cache/private, /var/log/private and /var/lib/private, respectively, which are host directories made inaccessible to unprivileged users, which ensures that access to these directories cannot be gained through dynamic user ID recycling. Symbolic links are created to hide this difference in behaviour. Both from perspective of the host and from inside the unit, the relevant directories hence always appear directly below /var/cache, /var/log and /var/lib.

      C'est compliqué. Ça veut dire aussi que l'application doit se trouver dans /var/lib/mon-application. J'avoue que j'aime bien /var/www/application.example.com/mon-application

    • [^] # Re: uWSGI

      Posté par  . Évalué à 5.

      Allez, je partage mon retour, parce que je pense avoir amélioré le truc. Attention, seulement la partie FPM, je suis resté classique pour nginx, il tourne toujours en root.

      Ce que je n'aime pas, c'est devoir écrire partout mon-application. Pour installer une nouvelle application, il faut copier-coller un fichier, et faire du rechercher-remplacer partout. Moins j'en fais, mieux je me porte. Et surtout, la maintenance est plus facile en cas d'édition d'un fichier.
      Il se trouve que PHP (et donc FPM) supporte les variables dans le fichier de configuration. Donc on peut faire son fichier qui ressemble à :

      • /etc/php/7.4/fpm/base.conf
      [global]
      pid = /run/${APP_NAME}/pid
      error_log = /var/log/${APP_NAME}/error.log
      
      [${APP_NAME}]
      listen = /run/php/${APP_NAME}.sock
      access.log = /var/log/${APP_NAME}/access.log
      
      ; single entry point
      security.limit_extensions = /index.php
      
      pm = ondemand
      pm.max_children = 5
      pm.process_idle_timeout = 600s;

      Oui, je préfère ondemand, mais ce n'est pas le sujet. Le sujet, c'est ${APP_NAME}. Comment on lui passe ? Hé bien, on demande à systemd :

      • /etc/systemd/system/php-fpm@.service
      [Service]
      Environment="APP_NAME=%i"

      Et avec ça, il n'y a parfois aucune configuration supplémentaire. Par exemple, pour Roundcube packagé par Debian, j'ai un simple lien symbolique de roundcube.conf vers base.conf. Le service systemd est édité, pour autoriser les connexions à mon serveur de courriel.

      D'ailleurs, je me suis permis de renforcer la sécurité en utilisant security.limit_extensions = /index.php ce qui empêche PHP d'accepter autre chose que /index.php. La valeur par défaut est .php, acceptant tous les fichiers PHP. Comme la plupart des applications n'ont plus qu'un seul point d'entrée, c'est toujours bon à prendre. Même si ça ne vous empêche pas de faire une configuration correcte de votre serveur Web en amont.

      Enfin, clairement, DynamicUser=true, c'est compliqué. Par exemple, Roundcube de Debian peuple /var/lib/roundcube avec des liens symboliques un peu partout, et il faut que tout ça soit accessible à cet utilisateur dynamique. C'est quand même compliqué.
      Donc j'ai mis en commentaire ça, et pour que les sessions marchent, j'ai remis ReadWritePaths=/var/lib/php/sessions. Oui, a priori ça va mettre les sessions dans le même répertoire, mais les droits semblent corrects…

  • # Non documenté

    Posté par  . Évalué à 8.

    Il sait faire attention avec les fonctionnalités non documentées. Ça peut venir du fait que ça n'est pas sensé être utilisé (c'est encore expérimental ou ça sert pour l'intégration continue des développeurs). Ça risque d'être bugué ou d'évoluer d'une version à l'autre (même sur une version micro).

    Ça vaut le coup de proposer de le documenter pour lancer la discussion chez les dev et voter si c'est effectivement quelque chose d'exposé ou non.

    https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

  • # Super intéressant !

    Posté par  (site web personnel) . Évalué à 1.

    Merci pour ces détails techniques dont je vais pouvoir m’inspirer :-)

    J’avais déjà commencé à m’attaquer à la sécurité des processus serveurs (web, mais aussi mail, etc.). Pour le web par exemple, j’ai un HAProxy qui fait toute l’écoute réseau, et ensuite des sockets Unix permettent les communications HAProxy–Nginx et Nginx–php-fpm. Ça, plus une conf nftables très restrictive, font que Nginx n’a pas accès au réseau.

    Mais, ayant appris systemd sur le tas, je sais que je peux améliorer ce premier jet, et ton article va m’y aider :-)

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.