Esta pagina se ve mejor con JavaScript habilitado

HaProxy en FreeBSD

 ·  🎃 kr0m

HaProxy es un balanceador de tráfico HTTP/HTTPS y conexiones Tcp pero está principalmente enfocado al tráfico HTTP/HTTPS, se trata de un software muy flexible debido a su gran número de opciones y parámetros de configuración, en este ejemplo veremos como discriminar tráfico mediante ACLs y como balancear tráfico HTTP/HTTPS.

Instalamos HaProxy:

pkg install haproxy

HaProxy utiliza Syslog como sistema de log, por lo tanto debemos configurarlo, en mi caso utilizaré la facility local0 para los logs del HaProxy:

mkdir /usr/local/etc/syslog.d/
vi /usr/local/etc/syslog.d/haproxy.conf

local0.* /var/log/haproxy-traffic.log  
local0.notice /var/log/haproxy-admin.log

Generamos los ficheros de log:

touch /var/log/haproxy-traffic.log
touch /var/log/haproxy-admin.log

Si se trata de una jail debemos tener en cuenta que esta no es capaz de acceder a /dev/log, debido a esto tendremos que configurar HaProxy para que envíe los logs por red a la ip de la propia jail.

Syslogd viene por defecto con el logging remoto deshabilitado, para habilitarlo cambiamos las flags de arranque:

sysrc syslogd_flags="-c"

Arrancamos el servicio:

sysrc syslogd_enable="yes"
service syslogd restart

En la sección defaults de la configuración del HaProxy dejamos comentadas las opciones de log, de este modo quedan preparadas en caso de necesidad de debug.

El resto de configuración consiste en filtrar por nombre de dominio y enviarlo al backend pertinente, si se accede por HTTP se realizará un redirect a HTTPS a no ser que se trate de LetsEncrypt intentando renovar el certificado.

El HaProxy ha sido configurado en modo passthrought, de esta manera cada web es responsable de la renovación de su propio certificado, el modo passthrought es el mas seguro ya que los datos no viajan en plano en ningún momento.

Solo cabe destacar que cuando enviamos el tráfico del HaProxy a los servidores web estos solo verán como ip origen la del HaProxy, no la del cliente real, para evitar esto haremos que el HaProxy añada una cabecera llamada X-Forwarded-For para HTTP y utilice ProxyProtocol para HTTPS, de este modo con un poco de configuración adicional en el servidor web ya será capaz de determinar la ip correcta.

vi /usr/local/etc/haproxy.conf
global
    daemon
    maxconn 5000
    log 192.168.69.11:514 local0
    user nobody
    group nobody
    stats socket /var/run/haproxy.sock user nobody group nobody mode 660 level admin

    ssl-default-bind-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
    ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
    
defaults
    timeout connect 10s
    timeout client 30s
    timeout server 30s
    #log global
    mode http
    #option httplog

listen stats
    bind *:8404
    stats enable
    stats uri /stats
    stats refresh 5s
    stats auth kr0m:PASSWORD

frontend HTTP
    bind :80
    option forwardfor

    acl letsencrypt path_beg /.well-known/acme-challenge/
    http-request redirect scheme https unless letsencrypt
    
    acl rxwod hdr(host) -i rxwod.alfaexploit.com
    acl drwho hdr(host) -i mail.alfaexploit.com
    acl alfaexploit hdr(host) -i alfaexploit.com
    acl www_alfaexploit hdr(host) -i www.alfaexploit.com

    http-request deny if !rxwod !drwho !alfaexploit !www_alfaexploit

    use_backend rxwod if rxwod
    use_backend drwho if drwho
    use_backend alfaexploit if alfaexploit
    use_backend alfaexploit if www_alfaexploit

frontend HTTP-SSL
    bind :443
    mode tcp

    acl rxwod_ssl req.ssl_sni -i rxwod.alfaexploit.com
    acl drwho_ssl req.ssl_sni -i mail.alfaexploit.com
    acl alfaexploit_ssl req.ssl_sni -i alfaexploit.com
    acl www_alfaexploit_ssl req.ssl_sni -i www.alfaexploit.com

    tcp-request inspect-delay 2s
    tcp-request content reject if !rxwod_ssl !drwho_ssl !alfaexploit_ssl !www_alfaexploit_ssl

    use_backend rxwod_ssl if rxwod_ssl
    use_backend drwho_ssl if drwho_ssl
    use_backend alfaexploit_ssl if alfaexploit_ssl
    use_backend alfaexploit_ssl if www_alfaexploit_ssl

backend rxwod
    option httpchk GET /
    server rxWod 192.168.69.10:80 check

backend drwho
    option httpchk GET /
    server drwho 192.168.69.6:80 check

backend alfaexploit
    option httpchk GET /
    server mistery 192.168.69.3:80 check

backend rxwod_ssl
    mode tcp
    option ssl-hello-chk
    server rxWod 192.168.69.10:443 check sni req.ssl_sni send-proxy-v2

backend drwho_ssl
    mode tcp
    option ssl-hello-chk
    server drwho 192.168.69.6:443 check sni req.ssl_sni send-proxy-v2

backend alfaexploit_ssl
    mode tcp
    option ssl-hello-chk
    server mistery 192.168.69.3:443 check sni req.ssl_sni send-proxy-v2

Arrancamos el servicio:

sysrc haproxy_enable="yes"
service haproxy start

Una buena manera de ver el estado del HaProxy es mediante la herramienta hatop, así que la instalamos:

pkg install hatop

La arrancamos indicándole el socket de estadísticas:

hatop -s /var/run/haproxy.sock

Otra opción es mediante la interfaz web, http://192.168.69.11:8404/stats




A continuación muestro una configuración de Nginx a modo de ejemplo de como deberíamos configurar el servidor web para que lea correctamente la ip del cliente.

El fichero de configuración principal lo dejamos prácticamente como viene por defecto pero asignamos a la variable $proxy_protocol_addr el valor de la cabecera X-Forwarded-For y definimos un nuevo formato de log llamado proxyprotocollogformat que utilizará dicha variable.

    proxy_set_header X-Forwarded-For $proxy_protocol_addr;  
    log_format proxyprotocollogformat  
            '$proxy_protocol_addr - $remote_user [$time_local] '  
            '"$request" $status $body_bytes_sent '  
            '"$http_referer" "$http_user_agent"';

Luego incluimos el fichero de configuración del vhost en si mismo, en este caso alfaexploit:

include alfaexploit.conf;

El fichero quedaría del siguiente modo:

vi /usr/local/etc/nginx/nginx.conf

worker_processes  1;
load_module /usr/local/libexec/nginx/ngx_http_modsecurity_module.so;
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    proxy_set_header X-Forwarded-For $proxy_protocol_addr;
    log_format proxyprotocollogformat
            '$proxy_protocol_addr - $remote_user [$time_local] '
            '"$request" $status $body_bytes_sent '
            '"$http_referer" "$http_user_agent"';
    include alfaexploit.conf;
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       80;
        server_name  localhost;
        location / {
            root   /usr/local/www/nginx;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/local/www/nginx-dist;
        }
    }
}

El fichero de configuración del vhost consta de dos partes, la configuración HTTP y la HTTPS.

En la primera indicaremos que solo debemos fiarnos de 192.168.69.11(HaProxy ip) cuando obtengamos los datos de la ip origen de la petición y esta información la obtendremos leyendo la cabecera X-Forwarded-For.

   set_real_ip_from    192.168.69.11;  
   real_ip_header      X-Forwarded-For;

En la segunda habilitaremos ProxyProtocol en la sentencia listen, indicaremos que solo debemos fiarnos de 192.168.69.11(HaProxy ip) cuando obtengamos los datos de la ip origen de la petición pero en esta ocasión obtendremos dicha información a través de ProxyProtocol.

   listen 443 ssl default_server proxy_protocol;  
   set_real_ip_from 192.168.69.11;  
   real_ip_header proxy_protocol;
vi /usr/local/etc/nginx/alfaexploit.conf
server {
    listen      80 default_server;
    server_name alfaexploit.com www.alfaexploit.com;
    set_real_ip_from    192.168.69.11;
    real_ip_header      X-Forwarded-For;
    root /usr/local/www/nginx/alfaexploit/public;
    modsecurity on;
    modsecurity_rules_file /usr/local/etc/modsec/main.conf;
    location / {
        try_files $uri /index.php$is_args$args;
    }
    
    location ~ ^/index\.php(/|$) {
        fastcgi_pass unix:/var/run/php-fpm.sock;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        internal;
    }
    
    location ~ \.php$ {
        return 404;
    }
}

server {
    listen 443 ssl default_server proxy_protocol;
    server_name alfaexploit.com www.alfaexploit.com;
    set_real_ip_from 192.168.69.11;
    real_ip_header proxy_protocol;
    root /usr/local/www/nginx/alfaexploit/public;
    modsecurity on;
    modsecurity_rules_file /usr/local/etc/modsec/main.conf;
    location / {
        try_files $uri /index.php$is_args$args;
    }
    location ~ ^/index\.php(/|$) {
        fastcgi_pass unix:/var/run/php-fpm.sock;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        internal;
    }
    location ~ \.php$ {
        return 404;
    }
    ssl_certificate "/usr/local/etc/ssl/alfaexploit.com/fullchain.cer";
    ssl_certificate_key "/usr/local/etc/ssl/alfaexploit.com/alfaexploit.com.key";
}

NOTA: Podríamos utilizar Proxy Protocol también para HTTP pero ProxyProtocol es un protocolo binario por lo tanto resulta mas complicado de debugear en caso de problemas.


Como paso final debemos cambiar los DNS de todos nuestros dominios para que apunten a la ip del HaProxy y si utilizamos IPFW configurar las reglas de filtrado para que solo se tenga acceso a los servidores web a través del HaProxy.

La configuración IPFW debería ser muy similar a la siguiente si utilizamos jails o iocage :

#!/bin/sh
# Flush out the list before we begin.
ipfw -q -f flush

# Set rules command prefix
cmd="ipfw -q add"
wanif="nfe0"

# No restrictions on Loopback Interface
$cmd 00010 allow all from any to any via lo0

# -----------------------------------------------------
# Generic traffic Host + Jails
# Allow access to public DNS
# DNS TCP
$cmd 00100 allow tcp from me to 8.8.8.8 53 out via $wanif
$cmd 00100 allow tcp from 8.8.8.8 53 to me in via $wanif

$cmd 00100 allow tcp from me to 8.8.4.4 53 out via $wanif
$cmd 00100 allow tcp from 8.8.4.4 53 to me in via $wanif

# DNS UDP
$cmd 00101 allow udp from me to 8.8.8.8 53 out via $wanif
$cmd 00101 allow udp from 8.8.8.8 53 to me in via $wanif

$cmd 00101 allow udp from me to 8.8.4.4 53 out via $wanif
$cmd 00101 allow udp from 8.8.4.4 53 to me in via $wanif

# Allow HTTP
$cmd 00200 allow tcp from me to any 80 out via $wanif
$cmd 00200 allow tcp from any 80 to me in via $wanif

# Allow HTTPS
$cmd 00300 allow tcp from me to any 443 out via $wanif
$cmd 00300 allow tcp from any 443 to me in via $wanif

# Allow SMTP
$cmd 00400 allow tcp from me to any 25 out via $wanif
$cmd 00400 allow tcp from any 25 to me in via $wanif

$cmd 00401 allow tcp from me to any 587 out via $wanif
$cmd 00401 allow tcp from any 587 to me in via $wanif

# Allow ping
$cmd 00500 allow icmp from me to any out via $wanif
$cmd 00500 allow icmp from any to me in via $wanif

# Allow NTP
$cmd 00600 allow udp from me to any 123 out via $wanif
$cmd 00600 allow udp from any 123 to me in via $wanif

# Allow SSH
# Outbound
$cmd 00700 allow tcp from me to any 22 out via $wanif
$cmd 00700 allow tcp from any 22 to me in via $wanif

# Inbound
$cmd 00701 allow tcp from any to me 22 in via $wanif
$cmd 00701 allow tcp from me 22 to any out via $wanif

# -----------------------------------------------------
# Jails services specific traffic

# HAProxy Stats: 8404
$cmd 00800 allow tcp from 192.168.69.4 to 192.168.69.11 8404 in via $wanif
$cmd 00800 allow tcp from 192.168.69.11 8404 to 192.168.69.4 out via $wanif

# HTTP + HTTPS: INET - HAProxy
$cmd 00801 allow tcp from any to 192.168.69.11 80 in via $wanif
$cmd 00801 allow tcp from 192.168.69.11 80 to any out via $wanif

$cmd 00802 allow tcp from any to 192.168.69.11 443 in via $wanif
$cmd 00802 allow tcp from 192.168.69.11 443 to any out via $wanif

# SMTP
$cmd 00900 allow tcp from any to 192.168.69.6 25 in via $wanif
$cmd 00900 allow tcp from 192.168.69.6 25 to any out via $wanif

# Deny and log all other connections
$cmd 60000 deny log all from any to any
Si te ha gustado el artículo puedes invitarme a un RedBull aquí