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.
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
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;
}
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)
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.
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;
}
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.
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.
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
#!/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
#!/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
}
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.