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:
HaProxy utiliza Syslog como sistema de log, por lo tanto debemos configurarlo, en mi caso utilizaré la facility local0 para los logs del HaProxy:
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-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:
Arrancamos el servicio:
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.
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:
service haproxy start
Una buena manera de ver el estado del HaProxy es mediante la herramienta hatop, asà que la instalamos:
La arrancamos indicándole el socket de estadÃsticas:
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:
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;
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