From a27fe11b1502878eeb82d8826c3f505b115935ba Mon Sep 17 00:00:00 2001 From: Matteo Nastasi Date: Wed, 7 Oct 2026 09:06:45 +0200 Subject: [PATCH] write the nftables rules of the daemon machine, and point at the generated files With the daemon on a machine of its own its ports are open to whoever reaches them: the pool, where X-Real-Ip is believed and the bans are built on it; the control channel, which holds the whole daemon up to 3 seconds per connection; and php-fpm, when it listens on tcp to serve the front, which runs any php file of that machine for whoever asks. A "-m server" installation with -U tcp:// now writes in Etc .nft, from the prefix like the nginx files, to be copied by hand into /etc/nftables.d/. Its own table, accepting by default and dropping only the ports of brisk: the pool reachable by the front alone, the control channel by this machine alone, and lo accepted, because the daemon listens on its own address and usermgmt.php reaches the control channel from here. The address of the front comes from the new -I; without it nothing is written and the run says so. An ipv6 address gives an ip6 saddr rule. php-fpm is counted as serving the front when one of the pools in /etc/php/*/fpm/pool.d listens on tcp on an address that is not loopback; its port then joins the pool in both rules. A unix socket or 127.0.0.1 serves this machine alone and is left out, and the run says that php-fpm has to be configured first if it is meant to serve the front. The name of the installation and the size of the pool, until now worked out inside nginx_conf_gen, are shared with it through inst_name, inst_var and pool_size. system/nginx/brisk.conf now says that -S nginx writes the same configuration already aligned with the installation, and where; it and WARNING.txt mention -I and the nftables file. Checked by installing -m server into a scratch tree against fake php-fpm pools - tcp on the lan, unix socket plus loopback, port alone, ipv6 - and without -I: every file passes nft -c and loads in an isolated network namespace. The -m front nginx files come out as before and no .nft. Co-Authored-By: Claude Opus 5.5 (1M context) --- INSTALL.sh | 152 +++++++++++++++++++++++++++++++++++++--- WARNING.txt | 5 ++ system/nginx/brisk.conf | 16 ++++- 3 files changed, 163 insertions(+), 10 deletions(-) diff --git a/INSTALL.sh b/INSTALL.sh index 62c8cf0..1d5ec55 100755 --- a/INSTALL.sh +++ b/INSTALL.sh @@ -48,6 +48,13 @@ web_server="apache" # daemon host. An empty fpm_root means the root of the site here. fpm_pass="unix:/run/php/php8.4-fpm.sock" fpm_root="" +# With -m server and the daemon on tcp, INSTALL.sh writes in Etc the nftables +# rules that let only the front reach the daemon ports - and php-fpm, when +# the php-fpm of this machine serves the front. front_ip is the address of the +# front as this machine sees it; fpm_pool_glob is where the php-fpm pools are +# looked for. +front_ip="" +fpm_pool_glob="/etc/php/*/fpm/pool.d/*.conf" # # functions function usage () { @@ -55,7 +62,7 @@ function usage () { echo "$1 -h" echo "$1 chk - run lintian on all ph* files." echo "$1 pkg - build brisk packages." - echo "$1 [-W] [-n 3|5] [-c 2|8] [-t <(n>=4)>] [-T ] [-r ] [-G ] [-A ] [-a ] [-f ] [-p ] [-U ] [-D ] [-u ] [-d ] [-w ] [-k ] [-l ] [-y ] [-P ] [-m ] [-S ] [-F ] [-B ] [-x]" + echo "$1 [-W] [-n 3|5] [-c 2|8] [-t <(n>=4)>] [-T ] [-r ] [-G ] [-A ] [-a ] [-f ] [-p ] [-U ] [-D ] [-u ] [-d ] [-w ] [-k ] [-l ] [-y ] [-P ] [-m ] [-S ] [-F ] [-B ] [-I ] [-x]" echo " -h this help" echo " -f use this config file" echo " -p save preferences in the file" @@ -88,6 +95,8 @@ function usage () { echo " -F php-fpm address for nginx - def. \"$fpm_pass\"" echo " (e.g. 10.0.0.6:9000 when php-fpm runs on the daemon machine)" echo " -B site root on the php-fpm host - def. the root of the site here" + echo " -I address of the front machine - def. \"$front_ip\"" + echo " (-m server with -U tcp://: nftables rules written in Etc)" echo " -x copy tests as normal php - def. \"$test_add\"" echo } @@ -144,6 +153,122 @@ function ngx_put() { fi } +# The name of the installation, from the prefix: "/brisk26/" -> "brisk26". +# It names the generated files and, made safe for identifiers, the nginx +# upstream and map variable and the nftables table. +function inst_name() { + echo "$prefix_path" | sed 's@^/@@g;s@/$@@g;s@/@_@g;' +} + +function inst_var() { + inst_name | sed 's/[^A-Za-z0-9_]/_/g' +} + +# USOCK_POOL_N as the installed tree defines it +function pool_size() { + local n + + n="$(sed -n "s/^define('USOCK_POOL_N', *\([0-9]\+\)).*/\1/p" "${web_path}__/spush/brisk-spush.phh")" + echo "${n:-10}" +} + +# The tcp ports the php-fpm pools of this machine listen on for other +# machines. php-fpm serves the front only when it listens on tcp on an +# address that is not loopback: a unix socket, or 127.0.0.1, serves this +# machine alone. Nothing printed means php-fpm does not serve the front. +function fpm_front_ports() { + local f l host port + + for f in $fpm_pool_glob; do + [ -f "$f" ] || continue + sed -n 's/^[ \t]*listen[ \t]*=[ \t]*\([^ \t;]*\).*/\1/p' "$f" + done | while read l; do + case "$l" in + /*) continue ;; # unix socket + *:*) host="${l%:*}"; port="${l##*:}" ;; + *) host=""; port="$l" ;; # port alone: every address + esac + echo "$port" | grep -q '^[0-9]\+$' || continue + case "$host" in + 127.*|"[::1]"|localhost) continue ;; + esac + echo "$port" + done | sort -un +} + +# The nftables rules of the daemon machine of a split installation, written +# in Etc as .nft, to be copied by hand into /etc/nftables.d/. Over tcp +# the daemon believes X-Real-Ip, which the bans are built on, and its control +# channel holds the whole daemon up to 3 seconds per connection; php-fpm on +# tcp runs any php file of this machine for whoever reaches it. Only the +# front may reach the pool and php-fpm, and nobody but this machine the +# control channel. +function nft_conf_gen() { + local name var hp port pool_n fpm_ports ports_front ports_all nft_f tmp saddr + + name="$(inst_name)" + var="$(inst_var)" + nft_f="$etc_path/${name}.nft" + hp="${usock_path_pfx#*://}" + port="${hp##*:}" + pool_n="$(pool_size)" + + fpm_ports="$(fpm_front_ports | tr '\n' ' ' | sed 's/ *$//;s/ /, /g')" + ports_front="${port}-$((port + pool_n - 1))" + ports_all="${port}-$((port + pool_n))" + if [ -n "$fpm_ports" ]; then + ports_front="${ports_front}, ${fpm_ports}" + ports_all="${ports_all}, ${fpm_ports}" + fi + case "$front_ip" in + *:*) saddr="ip6 saddr" ;; + *) saddr="ip saddr" ;; + esac + + tmp="$(mktemp)" + { + echo "#!/usr/sbin/nft -f" + echo "# brisk /${name}/ - nftables rules of the daemon machine." + echo "# Generated by INSTALL.sh, do not edit: run INSTALL.sh again instead." + echo "# Copy it into /etc/nftables.d/, include that directory from" + echo '# /etc/nftables.conf (include "/etc/nftables.d/*.nft") and load it:' + echo "# nft -c -f /etc/nftables.d/${name}.nft && nft -f /etc/nftables.d/${name}.nft" + echo "#" + echo "# The table accepts by default and drops only the ports of brisk. With a" + echo "# firewall whose policy is drop, an accept here is not enough: the two" + echo "# accept rules have to go into that chain." + echo "#" + echo "# daemon pool ${port}-$((port + pool_n - 1)) the front only" + echo "# control channel $((port + pool_n)) this machine only (usermgmt.php)" + if [ -n "$fpm_ports" ]; then + echo "# php-fpm ${fpm_ports} the front only" + else + echo "# php-fpm not listed: no pool listens on tcp for other machines" + fi + echo + echo "table inet ${var} {" + echo " chain input {" + echo " type filter hook input priority filter; policy accept;" + echo + echo " # the daemon listens on its own address, not on 127.0.0.1: the" + echo " # connections of this machine to itself come in from lo" + echo " iif lo accept" + echo + echo " ${saddr} ${front_ip} tcp dport { ${ports_front} } accept" + echo + echo " tcp dport { ${ports_all} } drop" + echo " }" + echo "}" + } > "$tmp" + ngx_put "$tmp" "$nft_f" + if [ -n "$fpm_ports" ]; then + echo " php-fpm serves the front from here (port ${fpm_ports}): included" + else + echo " php-fpm does not listen on tcp here: only the daemon ports are covered." + echo " Configure php-fpm first and run INSTALL.sh again if it has to serve the front." + fi +} + # The nginx configuration of the site, for direct mode (-D TRUE), written in # Etc beside the brisk configuration. system/nginx/brisk.conf is the same # thing as a commented template; this one comes out already aligned with the @@ -158,11 +283,11 @@ function ngx_put() { # of the upstream: two installations, or another site with its own # $connection_upgrade, can share one nginx without a duplicate definition. function nginx_conf_gen() { - local name var pfx fpmr pool_n inet hp host port i f tmp + local name var pfx fpmr pool_n hp host port i f tmp local http_f srv_f inc_f prbuf ngx ngx_ver etc_rel - name="$(echo "$prefix_path" | sed 's@^/@@g;s@/$@@g;s@/@_@g;')" - var="$(echo "$name" | sed 's/[^A-Za-z0-9_]/_/g')" + name="$(inst_name)" + var="$(inst_var)" pfx="/$(echo "$prefix_path" | sed 's:^/*::;s:/*$::')" fpmr="$(echo "${fpm_root:-$document_root}" | sed 's:/*$::')" @@ -170,10 +295,7 @@ function nginx_conf_gen() { srv_f="$etc_path/nginx-${name}-server.conf" inc_f="$etc_path/nginx-${name}-daemon.inc" - pool_n="$(sed -n "s/^define('USOCK_POOL_N', *\([0-9]\+\)).*/\1/p" "${web_path}__/spush/brisk-spush.phh")" - if [ -z "$pool_n" ]; then - pool_n=10 - fi + pool_n="$(pool_size)" # proxy_request_buffering exists from nginx 1.7.11: older ones refuse # the whole configuration with "unknown directive". When the nginx of @@ -434,6 +556,7 @@ while [ $# -gt 0 ]; do -S*) web_server="$(get_param "-S" "$1" "$2")"; sh=$?;; -F*) fpm_pass="$(get_param "-F" "$1" "$2")"; sh=$?;; -B*) fpm_root="$(get_param "-B" "$1" "$2")"; sh=$?;; + -I*) front_ip="$(get_param "-I" "$1" "$2")"; sh=$?;; system) action=system ; sh=1;; -W) web_only="TRUE";; -x) test_add="TRUE";; @@ -478,6 +601,7 @@ echo " install_mode: \"$install_mode\"" echo " web_server: \"$web_server\"" echo " fpm_pass: \"$fpm_pass\"" echo " fpm_root: \"$fpm_root\"" +echo " front_ip: \"$front_ip\"" echo " test_add: \"$test_add\"" if [ ! -z "$outconf" ]; then @@ -508,6 +632,7 @@ if [ ! -z "$outconf" ]; then echo "web_server=\"$web_server\"" echo "fpm_pass=\"$fpm_pass\"" echo "fpm_root=\"$fpm_root\"" + echo "front_ip=\"$front_ip\"" echo "test_add=\"$test_add\"" ) > "$outconf" fi @@ -898,6 +1023,17 @@ if [ "$web_server" = "nginx" ]; then fi fi +# The daemon machine of a split installation: whatever the http server of the +# front, the ports here have to be closed to everybody but the front. +if [ "$install_mode" = "server" ] && echo "$usock_path_pfx" | grep -q '^tcp://'; then + if [ -z "$front_ip" ]; then + echo "nftables rules not written: pass the address of the front with -I." + else + echo "nftables rules:" + nft_conf_gen + fi +fi + if [ -d ${web_path} ]; then mv ${web_path} ${web_path}.old fi diff --git a/WARNING.txt b/WARNING.txt index 9a938c2..63f9c7c 100644 --- a/WARNING.txt +++ b/WARNING.txt @@ -24,6 +24,11 @@ brisk configuration, already aligned with -P, -U and the root of the site server {}, nginx--daemon.inc for the nine daemon urls). -F and -B say where php-fpm is when it does not run on the same machine. +With the daemon on a machine of its own (-m server -U tcp://...), pass -I +with the address of the front: INSTALL.sh writes in Etc .nft, the +nftables rules that leave the daemon ports, and php-fpm when it listens on +tcp there, reachable by the front alone. Copy it into /etc/nftables.d/. + The values to keep aligned with those passed to INSTALL.sh are listed at the top of brisk.conf: the root of the site, the url prefix and the path of the sockets (-U). The sockets are USOCK_POOL_N of them (10 by default), named diff --git a/system/nginx/brisk.conf b/system/nginx/brisk.conf index 622d5a8..f062189 100644 --- a/system/nginx/brisk.conf +++ b/system/nginx/brisk.conf @@ -14,6 +14,15 @@ # /brisk/ <- prefix_path (-P) # the unix sockets <- usock_path_pfx (-U) + + ".sock", N from 0 to # USOCK_POOL_N-1 (10 by default, in brisk-spush.phh) +# +# Instead of adapting this file by hand, INSTALL.sh -S nginx writes the same +# configuration already aligned with those values, in Etc beside the brisk +# configuration ( comes from -P): +# nginx-.conf http level: the upgrade map and the upstream +# nginx--server.conf to include inside the server {} of the site +# nginx--daemon.inc included by each of the nine daemon urls +# -F and -B say where php-fpm listens and where the tree is on that machine. +# -S nginx also leaves out the .htaccess files, which nginx never reads. # The pool of daemon sockets. max_fails=0 matters: when the daemon restarts # nginx would mark every socket as dead and answer 502 "no live upstreams" @@ -45,8 +54,11 @@ upstream brisk_daemon { # tcp the daemon still believes X-Real-Ip, which is what the bans and the # blacklist are built on, and it still speaks plain http, so the session # cookies cross the network in clear. The port has to be reachable by this -# machine and by nothing else. And the comet streams sit idle for as long as -# proxy_read_timeout allows, an hour below: anything stateful in between - +# machine and by nothing else: with -I
, the +# "-m server" installation writes the nftables rules that do it in its Etc, +# .nft, php-fpm included when it listens there for this machine. +# And the comet streams sit idle for as long as proxy_read_timeout allows, +# an hour below: anything stateful in between - # a firewall, a NAT - will drop them long before that unless it is told not # to, and the only symptom is a client that reconnects for no visible reason. -- 2.47.3