7G firewall nginx configuration

I had a slow afternoon, so I thought I’d sit down and try to figure this out. If you’re running apache, then its a lot easier to use the apache version: its basically just adding 50 lines to your .htaccess file. And then downloading and configuring a php script to activate logging.

But maybe you want to do it in nginx, so here’s how to do it.

  1. Visit the 7g firewall for nginx page, and download the zip file.
  2. Put the two files somewhere on your server, in the /etc/nginx directory. I put mine in /etc/nginx/7g/7g-firewall.conf and /etc/nginx/7g/7g.conf
  3. The first file needs to be included in /etc/nginx/nginx.conf, somewhere between “http {” and the final “}” I put it at the end, before the final includes. So it looks like this
    #7g firewall 
    include /etc/nginx/7g/7g-firewall.conf;

    # Wildcard include
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/conf.d/domains/*.conf;
}
  1. The other file needs to be included in the configuration of the site(s) that you want to activate the firewall for. As hestia already has a mechanism for doing this, including any nginx.conf_* files, I just used that. So I copied the file into place. (I decided to do it this way rather than symlinking because of the next step)
cp /etc/nginx/7g/7g.conf /home/user/conf/web/domain.com/nginx.conf_7g 
cp /etc/nginx/7g/7g.conf /home/user/conf/web/domain.com/nginx.ssl.conf_7g
  1. Test the config with nginx -t. If there are no errors then you can ‘systemctl restart nginx’

At this point, you are up and running. You can test it by trying to access https://domain.com/db.sql, for example, and instead of getting a 404, you’ll get a firm 403 forbidden. But I was expecting to see something in my logs, so I decided to set that up, at least for a while so I could see what was going on. There are two further steps in this case.

  1. Add a line which will log the requests to the two files (both of them) you copied to your domain’s conf directory in step 4. Add it at the end of the file before the 403 and 405 stanzas, like this
access_log /var/log/apache2/domains/domain.com.7g.log 7g_log if=${7g_drop};

if ($7g_drop = 1) {
	return 403;
}

if ($7g_drop = 2) {
	return 405;
}
  1. You’ve just told it to log to a separate 7g log if one of the rules has been triggered. And you’ve told it to use the 7g_log format, so you need to tell nginx about that. Back in /etc/nginx/nginx.conf, search for the other log_format directives and add this one in there.
    log_format 7g_log '[$time_local] ["$args"] $remote_addr $http_host "$request" $status $request_time "$http_referer" "$http_user_agent"';

Once again nginx -t will let you know if you’ve messed up the formatting, and if not, you’re good to go and ‘systemctl restart nginx’. When you test the firewall again, you’ll see entries in the new logfile, so its easier to see that its working, and the log file will tell you what rule was triggered, so that if the rule is breaking some functionality on your site, you can disable or modify it.

4 Likes

I do exactly the same in the same way but I modify all files to include a comment with “7g” in the file so si can run:
nginx -T | grep “7g”
I get all the domains configured to use 7g
I have the scripts that do the job if the hestiaCP team want me to submit a PR

1 Like

Can you post it to me?

Install script:

usage:
./nm-nginx-7g-firewall-instalar.sh %username% %domain% [restart:no]
./nm-nginx-7g-firewall-instalar.sh myuser mydomain.com
./nm-nginx-7g-firewall-instalar.sh myuser mydomain.com no

nm-nginx-7g-firewall-instalar.sh

#!/bin/bash

nginx -t > /dev/null 2>&1 || abort "Nginx está mal configurado. Hay que configurarlo correctamente antes de usar esta herramienta."

#No reiniciar nginx si el parámetro $3 es "no" - NO SUFICIENTEMENTE PROBADO
if [ "$3" == "no" ]; then
reiniciarnginx="0"
else
reiniciarnginx="1"
fi

#echo "Reiniciar Nginx: $reiniciarnginx"

#importo funciones
source /opt/nm-bin/functions.sh

#compruebo que se ejecuta como root.
comoroot

#pido usuario y dominio
get_username_domain $1 $2

#si está instalado salgo
if [ -f /home/$username/conf/web/$domain/nginx.conf_7g ]; then
  msg "7g firewall ya estaba instalado para $domain."
  exit 0
  #else
  #msg "7g firewall no instalado en $domain procedo con la instalación"
  fi

#si no está instalado, compruebo que está puesto en la máquina
if [ -f /etc/nginx/conf.d/7g-firewall.conf ] && [ -f /etc/nginx/7g/7g.conf ] ; then
  #msg "7g firewall instalado en la máquina."
  echo > /dev/null 2>&1
  else
  msg "7g firewall no instalado en la máquina. Procedo con la instalación."
  #instalación para la máquina
  ##############################

SEVENGURL="https://perishablepress.com/7g-firewall-nginx/"
DOWNLOADURL=$(curl -s $SEVENGURL | sed -E -n -e "/^<div class=\"download\">$/{n;p}" \
    | cut -d"\"" -f2)

if echo "$DOWNLOADURL" | grep -E "^https://perishablepress.com/downloads/[0-9]+/?$" -q; then
    PROCEED=1
fi

if [[ $PROCEED ]]; then
        #me muevo al home para descargar el .zip
    cd ~ || exit
    wget -O 7g.zip "$DOWNLOADURL"

    #si se descargó bien
    if file -i 7g.zip | grep "application/zip" -q; then
        #si falla es porque no está instalada atool. instalo y repito comando.
        aunpack 7g.zip || apt install atool -y && aunpack 7g.zip
        
        mkdir /etc/nginx/7g
        find ~/7g -name '7g.conf' -exec cp {} /etc/nginx/7g \;
        find ~/7g -name '7g-firewall.conf' -exec sudo cp {} /etc/nginx/conf.d \;
        if [[ ! -f /etc/nginx/7g/7g.conf ]]; then
            echo "7g.conf cannot be found in the archive."
        fi

        if [[ ! -f /etc/nginx/conf.d/7g-firewall.conf ]]; then
            echo "7g-firewall.conf cannot be found in the archive."
            else
            #dejo la marca para encontrarlo con nginx -T | grep 7G
            sed -i "s/7G FIREWALL - NGINX v1.5/7G firewall - General rules and filters/g" /etc/nginx/conf.d/7g-firewall.conf
        fi
    #no se descargó
    else
        echo "7G Firewall cannot be downloaded."
        echo "URL was $DOWNLOADURL"
    fi
else
    #la url no cumple con la expresión regular para ser válida
    echo "7G Firewall will not be installed."
    echo "URL was $DOWNLOADURL"
fi

  
  
  ##############################
  fi

#ahora lo instalo para el dominio
cp /etc/nginx/7g/7g.conf /home/$username/conf/web/$domain/nginx.conf_7g
#Dejo constancia de que está instalado para encontrarlo al hacer "nginx -T | grep 7g"
sed -i "s/7G FIREWALL - NGINX v1.5/7G firewall - $domain/g" /home/$username/conf/web/$domain/nginx.conf_7g
msg "Listo - 7G firewall - $domain"

echo "reiniciar nginx - $domain: $reiniciarnginx"
if [ "$reinciarnginx" == "1" ]; then
nginx -t > /dev/null 2>&1  && service nginx restart || error "Fallo en la configuración de Nginx. No puedo reiniciar porque se caerían todas las webs."
else
nginx -t > /dev/null 2>&1  && msg "Configuración Nginx Correcta. $domain" || error "Fallo en la configuración de Nginx. $domain"
fi

disable 7g firewall in one domain: nm-nginx-7g-firewall-quitar.sh

usage:
./nm-nginx-7g-firewall-quitar.sh %username% %domain%
./nm-nginx-7g-firewall-instalar.sh myuser mydomain.com

nm-nginx-7g-firewall-quitar.sh

#!/bin/bash

nginx -t > /dev/null 2>&1 || abort "Nginx está mal configurado. Hay que configurarlo correctamente antes de usar esta herramienta."

#importo funciones
source /opt/nm-bin/functions.sh

#compruebo que se ejecuta como root.
comoroot

#pido usuario y dominio
get_username_domain $1 $2

#si está instalado salgo
if [ ! -f /home/$username/conf/web/$domain/nginx.conf_7g ]; then
  msg "7g firewall no está instalado para $domain."
  exit 0
  else
  msg "7g firewall está instalado en $domain procedo con la desinstalación"
  rm /home/$username/conf/web/$domain/nginx.conf_7g
  fi

msg "Listo"
nginx -t > /dev/null 2>&1  && service nginx restart || error "Fallo en la configuración de Nginx. No puedo reiniciar porque se caerían todas las webs."

support functions:

They should be in this path: /opt/nm-bin/functions.sh

#Para importar estas funciones hay que poner este código en los scripts
#source /opt/nm-bin/functions.sh

function serverncpu {
        grep -c ^processor /proc/cpuinfo
        }

function serverload {
        uptime | cut -d',' -f4 | cut -d':' -f2
        }

function servermaindiskfree {
        #df -h | grep '^/dev.*% /$' | cut -c27-32 | sed 's/[[:space:]]//g'
        df -h | grep '^/dev.*% /$' | tr -s ' ' | cut -d' ' -f4
        }
        
function servermaindisktotal {
        #df -h | grep '^/dev.*% /$' | cut -c21-26 | sed 's/[[:space:]]//g'
        df -h | grep '^/dev.*% /$' | tr -s ' ' | cut -d' ' -f2
        }

function servermaindiskpfree {
        
        xfree=$(df | grep '^/dev.*% /$' | tr -s ' ' | cut -d' ' -f4)
        xtotal=$(df | grep '^/dev.*% /$' | tr -s ' ' | cut -d' ' -f2)
        echo "$xfree * 100 / $xtotal" | bc
        }

function serverdiskusage {
        df -h | grep "Filesystem\|^/dev/.*% /$\|^/dev/.*% /home.*"
        }



#Escribe en el fichero log
#Sintaxis: logwrite "Mensaje" [fichero.log]
#Ejemplo: logwrite "Todo genial"
# Salida: /opt/nm-bin/logs/nm-logfile.log
# 2022-12-01 14:30:52 nm-script.sh: "Mensaje" 

function logwrite {

        if [ -z "$1" ]; then
        msg "No puedo escribir en el log. No hay mensaje"
        else
        
                if [ -z "$2" ]; then
                logfile="/opt/nm-bin/logs/nm-logfile.log"
                else
                logfile="$2"
                fi
        timestamp=$(date +"%Y-%m-%d %H:%M:%S")
        echo "$timestamp - ${0##*/}: $1" >> $logfile     
        fi
        
}

#para evitar la ejecución de scripts auxiliares
function prevenir_ejecucion {
        if [ ${0##*/} == ${BASH_SOURCE[0]##*/} ]; then 
        abort "Este es un script auxiliar que contiene funciones para otros scripts pero no hace nada."
fi
}
#prevenir_ejecucion
#hay que probar si funciona con functions.sh o con otros scripts

#Compruebo los permisos de un fichero
function permisos_comprobar {
        if [ ! -f $1 ]; then
        error "No existe el fichero $1"
        permisos="0"
        else
                #si el fichero tiene los permisos buscados
                permisos=$(stat -c '%a' $1)
                if [ "$2" = "$permisos" ]; then
                        return 0
                        echo "return 0"
                else
                        return 1
                        echo "return 1"
                fi
        fi
}

#Me da el día de hoy en formato YYYYMMDD: 20220629
hoy=$(date +'%Y%m%d')

#Me da el directorio desde el que se ejecutan los scripts
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"

#comoroot: fuerza que el script sea ejecutado como root.
function comoroot {
        if [ "$EUID" -ne 0 ]; then
        error "Este comando debe ejecutarse como root."
        fi
}

#compruebo mínimo de argumentos
#ejemplo: minargs 2 $#
# hay que pasar la variable $# que contiene el número de argumentos con el que fue llamado el script.
# $# no se puede usar dentro de la función porque devolverá el número de argumentos con los que la función fue llamada.
function minargs {
        if [ -z $2 ]; then
        abort "El script $0 tiene un error: Has llamado a la función maxargs sin pasarle el número de argumentos. Habla con el desarrollador."
        fi
        if [ $2 -lt $1 ]; then
        abort "Este comando necesita al menos $1 argumentos." 
        fi
}
#ejemplo: maxargs 2 $#
function maxargs {
        if [ -z $2 ]; then
        abort "El script $0 tiene un error: Has llamado a la función maxargs sin pasarle el número de argumentos. Habla con el desarrollador."
        fi
        if [ $2 -gt $1 ]; then
        abort "Este comando acepta como máximo $1 argumentos."
        
        fi
}

#ejemplo: exactargs 2 $#
function exactargs {
        if [ -z $2 ]; then
        abort "El script $0 tiene un error: Has llamado a la función maxargs sin pasarle el número de argumentos. Habla con el desarrollador."
        fi
        if [ $2 -ne $1 ]; then
        abort "Este comando acepta solo $1 argumentos."
        
        fi
}


#useralta: crea un nuevo usuario en el sistema y le pone bash.
#useralta user passwordhaseado
function useralta {
        # $1 user
        # $2 password hasheado
        useradd -m -p $2 $1 
        chsh -s /bin/bash $1
        cat /opt/nm-bin/instalar/bashrc_include >> /home/$1/.bashrc
}

#testabort: comprueba que el último comando fue ejecutado con éxito. Si se ejecutó, continua, si hubo error, aborta.
function testabort {
        if [ $? -eq 0 ]; then
        #hecho
        true
        else
        abort "${1}"
        fi
}

#testerror: comprueba que el último comando fue ejecutado con éxito. Si se ejecutó, continua, si hubo error, avisa y continua.
#testerror "Msg si OK" "Msg si ERROR"
function testerror {
        if [ $? -eq 0 ]; then
        #pongo la línea porque a veces el stdout no cambia de líneas
        echo
        msg "$1"
        else
        #pongo la línea porque a veces el stdout no cambia de líneas
        echo
        error "$2"
        fi
}


#instalación silenciosa
function instalar {
        apt install -qq -o=Dpkg::Use-Pty=0 ${1} ${2} ${3} ${4} ${5} ${6} ${7} ${8} ${9} > /dev/null
}

#Muestra mensaje de avance
function msg {
        echo -e "[ * ] $1..."
}

function warn {
        echo -e "/ ! \ $1..."
}

#Mensaje de error pero no aborta
function error {
        echo -e "[ !!! ] Error: $1"
        logwrite "Error: $1"
}

function hecho {
        echo "      Hecho."
}

#tira error y aborta
function abort {
        echo -e "[ !!! ] Error: $1"
        logwrite "Error: $1"
        exit 1
}

function titulo  {
        echo " "
        echo " "
        echo -e "[  $1  ]"
        echo " "
        echo " "
}

function pause {
        #descarto la variable descartar
        read -p "Pulsa Enter para continuar... (Ctrl + C para salir)" descartar
}

#Captura los valores de username y domain si no están en $1 y $2
function get_username_domain {
        #si está vacío el parámetro 1 pido el username
        if [ -z "$1" ]
        then
        echo
        echo "Datos del panel HESTIA: Asegúrate de que coinciden."
        echo
        read -p 'Username: ' username
        read -p 'Dominio: ' domain
        else
        username=$1
        #su está vacío el parámetro 2 pido el dominio
        if [ -z "$2" ]
                then
                read -p 'Dominio: ' domain
                else
                domain=$2
                fi
        fi
}
# username no puede tener más de 15 caracteres

function get_domain_username {
              #si está vacío el parámetro 1 pido el domain
        if [ -z "$1" ]
        then
        echo
        echo "Datos del panel HESTIA: Asegúrate de que coinciden."
        echo
        read -p 'Dominio: ' domain
        #busco si hay un username ya para ese domain
        search_username_domain $domain
        read -p 'Username: ' username
        
        else
        username=$1
        #si está vacío el parámetro 2 pido el dominio
        if [ -z "$2" ]
                then
                read -p 'Dominio: ' domain
                else
                domain=$2
                fi
        fi  
}


#search_username_domain: busca el usuario dado un dominio
#Sintaxis: search_username_domain dominio.com
#devuelve $username $domain
function search_username_domain {

        local dominio="$1"
        local file="/opt/nm-bin/listado-usuarios-dominios-web"

        #Me la juego con un método rápido
        if [ -d /home/$dominio/web/$dominio ]; then
        username=$dominio
        domain=$dominio
        msg "Existe /home/$dominio/web/$dominio => username: $username | domain: $domain"
        return 0
        else
        #resto del código

                #si no existe el fichero lo creo.
                #if  [ ! -f "$file" ]; then
                #        /opt/nm-bin/nm-listar-dominios-web.sh
                #else
                #        #compruebo si tiene más de 7 días
                #        if test `find "$file" -mtime +6`
                #        then
                #        msg "$file tiene más de 7 días. Lo regenero"
                #        /opt/nm-bin/nm-listar-dominios-web.sh
                #        fi
                #fi
                
                #regenero el fichero
                /opt/nm-bin/nm-listar-dominios-web.sh

                #compruebo si el dominio está en la columna 2
                cat $file | cut -d',' -f2 | grep -w "$dominio" > /dev/null

                if [ $? -eq 0 ]; then
                #asumo que solo puede haber el dominio una vez.
                username=$(cat $file | grep  ",$dominio"  | head -n 1 | cut -d',' -f1)
                domain=$dominio
                return 0
                else
                error "No encontrado: $dominio en $file"
                return 1
                fi
        
        
        fi
       

}


#Leo los valores de un fichero de configuración
#ejemplo: read_properties config.txt
read_properties()
{
  if [ ! -f "$1" ]; then
        abort "Falta el fichero $1"
  fi
  file="$1"
  while IFS="=" read -r key value; do
    case "$key" in
      '#'*) ;;
      *)
        eval "$key=\"$value\""
    esac
  done < "$file"
}

#iterar por todos los $domains para todos los $usernames
function iterar_username_domain {
        for username in $($HESTIA/bin/v-list-users | cut -d' ' -f1 | tail -n +3); do
        for domain in $($HESTIA/bin/v-list-web-domains $username  | cut -d' ' -f1 | grep "\."); do
        #comprobaciones
                if [ "$username" != "admin" ]; then
                        #el usuario no es admin
                        if [ ! -z "$domain" ]; then
                                #el usuario no es admin y además tiene dominio
                                if [ ! -z "$cmd1" ]; then
                                        echo "CMD 1: $cmd1"
                                        eval "$cmd1"
                                        else
                                        abort "No hay comando que ejecutar"
                                        fi
                                if [ ! -z "$cmd2" ]; then
                                        echo "CMD 2: $cmd2"
                                        eval "$cmd2"
                                        fi
                                if [ ! -z "$cmd3" ]; then
                                        echo "CMD 3: $cmd3"
                                        eval "$cmd3"
                                        fi
                                fi
                        fi
                done
                done

        }

#Quitar suspensión de usuario
function unsuspend_username_domain {
        msg "Quito las suspensiones de usuario:$username y dominio:$domain"
        $HESTIA/bin/v-unsuspend-user $username > /dev/null 2>&1
        $HESTIA/bin/v-unsuspend-web-domain $username $domain > /dev/null 2>&1
        }

#comprobar que son correctos $username y $domain
function check_username_domain {
        check_username $1
        check_domain $2
}

function check_username {
        if [ "$1" == "" ]; then
                error "El usuario no puede estar vacío"
                
                fi
        #Listo los usuarios a ver si está en la lista...
        $HESTIA/bin/v-list-users | grep "${1}" > /dev/null
        if [ $? -eq 1 ]; then
                error "El usuario: $1 no existe en el sistema."        
        fi
        if [ $? -gt 1 ]; then
                error "Error no identificado."        
        fi       
        
        
} 

function check_domain {
        if [ "$1" == "" ]; then
                error "El dominio no puede estar vacío"
                return 2
                fi
        #Listo los usuarios a ver si está en la lista...
        $HESTIA/bin/v-search-domain-owner "${1}" > /dev/null
        if [ $? -eq 3 ]; then
                error "El dominio: $1 no existe en el sistema."
                return 1
        fi
        if [ $? -gt 0 ]; then
                error "Error no identificado."
                return 1
        fi
        return 0
}

function validateip {
        ip=$1
        
        if [ -z "$ip" ]; then
                abort "No se ha suministrado una IP"
                fi
        
        if [[ "$ip" =~ ^(([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))\.){3}([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))$ ]]; then
                return 0
                else
                abort "La IP: $ip - no es válida."
                fi
        }
2 Likes

Thanks

Can 7g be activated for all domains at once (of a user or general)? Like brotli.

Two ways.

  1. place the rules in your nginx template or use the includes provided inside the templates
  2. run this scripts inside a loop for all users

Hi!
I did steps 1-4 of this post:

And it works for each domain, where I did the cp command (test with db.sql successful).

But if I try to integrate it the way here it is described:

… then nginx fails to start.
“nginx -v” shows nginx version: nginx/1.25.5

The website describes the steps as follows:

1) Add 7g-firewall.conf to /etc/nginx/conf.d
2) Add 7g.conf to /etc/nginx/snippets
3) Add include /etc/nginx/snippets/7g.conf; to the server directive

Perhaps I made a mistake at step 3. Could you explain the last step? I added

include /etc/nginx/snippets/7g.conf;

at the end of “http {” like in the first post. It this the wrong place or even the wrong file?

Step 3 basically needs to be added to
/home/user/conf/web/domain.com/nginx.conf and nginx.ssl.conf

If you do that directly, the config will get overwritten when you rebuild the domain templates. (probably during the next hestia upgrade). However you’ll notice at the end of each of those files an include statement which will include
include /home/dl/conf/web/datalude.com/nginx.conf_*;
include /home/dl/conf/web/datalude.com/nginx.ssl.conf_*;

So we can take advantage of this and put our code in files called eg
/home/dl/conf/web/datalude.com/nginx.conf_7g
/home/dl/conf/web/datalude.com/nginx.ssl.conf_7g

You can either add the
include /etc/nginx/snippets/7g.conf;
statement here, or just copy the whole file over.