En este post vamos a configurar dos balanceadores HAProxy con SSL Offloading sobre CentOS 7.
Dichos balanceadores trabajarán en modo master-slave.
Además usaremos Keepalived como daemon responsable de la alta disponibilidad, que nos permitirá levantar las IP’s flotantes del clúster ( VRRP ) y responder con un failover en caso de necesidad.
Iptables será el responsable del firewalling y del nat de salida y de servicios no balanceados.
Topología de red:
Direccionamiento:
loadbalancer-1: interfaz Wan = bond0 interfaz lan = bond1 ip wan = 198.51.100.1 /24 ip lan = 10.0.0.1 /24 loadblancer-2: interfaz Wan = bond0 interfaz lan = bond1 ip wan = 198.51.100.2 /24 ip lan = 10.0.0.2 /24 VIP's: servicio público 100 = 198.51.100.100 VIP LAN = 10.0.0.254 Esta VIP LAN será la Gateway de los servidores internos
Keepalived:
Keepalived es el daemon responsable de las IP’s flotantes ( VIP’s ) y por tanto de la alta disponibilidad del sistema. Keepalived intercambiará mensajes VRRP unicast entre ambos balanceadores para determinar qué balanceador és el activo y cuál es el pasivo.
Queremos monitorizar el estado de haproxy y de los interfaces de red para tomar una decisión de failover en caso de que exista un fallo.
Configuración de Keepalived en LoadBalancer-1 ( master ):
# # ### SCRIPTS # vrrp_script chk_haproxy { script "pidof haproxy" interval 2 } #### VRRP INSTANCES # # LAN # vrrp_instance LAN_VIP { state MASTER interface bond1 virtual_router_id 126 priority 101 advert_int 1 authentication { auth_type PASS auth_pass capa3.es } unicast_src_ip 10.0.0.1 unicast_peer { 10.0.0.2 virtual_ipaddress { 10.0.0.254 } track_interface { bond0 bond1 } track_script { chk_haproxy } } # # WAN # vrrp_instance SERVICIO_PUBLICO_100 { state MASTER interface bond0 virtual_router_id 248 priority 101 advert_int 1 authentication { auth_type PASS auth_pass capa3.es } unicast_src_ip 198.51.100.1 unicast_peer { 198.51.100.2 virtual_ipaddress { 198.51.100.100 } track_interface { bond0 bond1 } track_script { chk_haproxy } }
Configuración de Keepalived en LoadBalancer-2 ( slave ):
# # ### SCRIPTS # vrrp_script chk_haproxy { script "pidof haproxy" interval 2 } #### VRRP INSTANCES # # LAN # vrrp_instance LAN_VIP { state BACKUP interface bond1 virtual_router_id 126 priority 100 advert_int 1 authentication { auth_type PASS auth_pass capa3.es } unicast_src_ip 10.0.0.2 unicast_peer { 10.0.0.1 } virtual_ipaddress { 10.0.0.254 } track_interface { bond0 bond1 } track_script { chk_haproxy } } # # WAN # vrrp_instance SERVICIO_PUBLICO_100 { state BACKUP interface bond0 virtual_router_id 248 priority 100 advert_int 1 authentication { auth_type PASS auth_pass capa3.es } unicast_src_ip 198.51.100.2 unicast_peer { 198.51.100.1 } virtual_ipaddress { 198.51.100.100 } track_interface { bond0 bond1 } track_script { chk_haproxy } }
HAPROXY Resumido:
Frontend SSL Offloading = 198.51.100.100 & tcp/443 SNI Inspection determina a que backend va el tráfico descifrado. x-forwarded-proto = https x-forwarded-for balanceo round-robin health check = HEAD /check-alive ( espera un 200 ) Frontend HTTP = 198.51.100.100 & tcp/80 Inspección de Host-Header determina a que backend va el tráfico. x-forwarded-proto = http x-forwarded-for balanceo round-robin health check = HEAD /check-alive ( espera un 200 )
HAPROXY detallado:
Definimos un frontend con IP pública 198.51.100.100 ; correspondiente a la VIP SERVICIO_PUBLICO_100 de la configuración de Keepalived
En dicho frontend que llamaremos SERVICIO_PUBLICO_100_SSL, escucharemos en el puerto tcp/443, con un certificado wildcard del dominio capa3.es y balancearemos descifrando el tráfico HTTPS y lo enviaremos descifrado a los frontales, que escucharán en el puerto tcp/80 ( http ).
Es decir, que haremos SSL offloading en el lado WAN de HAProxy.
Como nota adicional, haremos un balanceo de capa de aplicación antes de enviarlo a los frontales:
En base al subdominio, en concreto al SNI “Server Name Indication” de la petición que nos llegue por SSL a esa VIP, tomaremos decisiones de balanceo hacia diferentes “backends”.
Lo mismo haremos con las peticiones HTTP en claro que nos lleguen a la misma IP pública, pero en este caso inspeccionaremos la cabecera http host o “host header”.
Por ejemplo si la petición que nos llega es https://portugal.capa3.es/lo_que_sea lo enviaremos la granja de frontales que sirven la aplicación para Portugal.
Si la petición que nos llega es https://france.capa3.es/lo_que_sea lo enviaremos a los backend de Francia … y así con el resto de paises o subdominios que tengamos.
Finalmente si no hace match en ninguna regla de inspección lo enviaremos a una granja de servidores llamada “backend_default”
En ambos casos, frontends http y ssl, como siempre lo vamos a enviar descifrado hacia los backends, añadiremos la cabecera x-forwarded-proto que informará a la aplicación si la petición original fue https o http.
También informaremos a los servidores de los backends la IP real del cliente mediante la cabecera x-frowarded-for
Los balanceadores harán health checks a los servidores reales usando una URI de check.
Estos health checks serán http HEAD y el balanceador espera un http 200 como OK para considerar que un servidor está dentro del balanceo.
Por ejemplo: HEAD a http://10.0.0.24/check-alive
Esta sería una configuración básica que hace lo descrito y obtiene una calificación A+ en los tests SSL de ssllabs.com :
global log 127.0.0.1 local2 chroot /var/lib/haproxy pidfile /var/run/haproxy.pid maxconn 500000 user haproxy group haproxy daemon stats socket /var/lib/haproxy/stats # # CIFRADO # tune.ssl.default-dh-param 2048 ssl-default-bind-options no-sslv3 no-tls-tickets tune.ssl.cachesize 100000 tune.ssl.lifetime 600 tune.ssl.maxrecord 1460 ssl-default-bind-ciphers kEECDH+aRSA+AES:kRSA+AES:+AES256:!kEDH:!LOW:!EXP:!MD5:!aNULL:!eNULL defaults mode http log global option httplog option dontlognull option http-server-close option forwardfor except 127.0.0.0/8 option redispatch retries 3 timeout http-request 10s timeout queue 1m timeout connect 10s timeout client 1m timeout server 1m timeout http-keep-alive 10s timeout check 10s # # FRONTENDs # # # FRONTEND SERVICIO_PUBLICO_100_SSL # frontend SERVICIO_PUBLICO_100_SSL mode http option http-keep-alive option forwardfor reqadd X-Forwarded-Proto:\ https http-response set-header Strict-Transport-Security max-age=31536000;\ includeSubdomains;\ preload http-response set-header X-Frame-Options DENY http-response set-header X-Content-Type-Options nosniff bind 198.51.100.100:443 ssl crt /etc/ssl/certs/wildcard-capa3.pem # Isnpección SNI de TLS use_backend backend_portugal if { ssl_fc_sni portugal.capa3.es } use_backend backend_france if { ssl_fc_sni france.capa3.es } use_backend backend_secure if { ssl_fc_sni_end secure.capa3.es } default_backend backend_default # # FRONTEND SERVICIO_PUBLICO_100_HTTP # frontend SERVICIO_PUBLICO_100_HTTP mode http bind 198.51.100.100:80 option http-keep-alive option forwardfor reqadd X-Forwarded-Proto:\ http use_backend backend_portugal if { hdr_end(host) -i portugal.capa3.es } use_backend backend_france if { hdr_end(host) -i france.capa3.es } # default_backend backend_default # # BACKENDS # # BACKEND PORTUGAL backend backend_portugal mode http balance roundrobin option httpchk HEAD /check-alive http-check expect status 200 # timers Health-Check default-server inter 15s fall 2 rise 1 # server portugal_20 10.0.0.20:80 check server portugal_21 10.0.0.21:80 check server portugal_22 10.0.0.22:80 check server portugal_23 10.0.0.23:80 check server portugal_24 10.0.0.24:80 check # BACKEND FRANCE backend backend_france mode http balance roundrobin option httpchk HEAD /check-alive http-check expect status 200 # timers Health-Check default-server inter 15s fall 2 rise 1 # server france_40 10.0.0.40:80 check server france_41 10.0.0.41:80 check server france_42 10.0.0.42:80 check server france_43 10.0.0.43:80 check server france_44 10.0.0.44:80 check # BACKEND DEFAULT backend backend_default mode http balance roundrobin option httpchk HEAD /check-alive http-check expect status 200 # timers Health-Check default-server inter 15s fall 2 rise 1 # server frontal_10 10.0.0.10:80 check server frontal_11 10.0.0.11:80 check server frontal_12 10.0.0.12:80 check server frontal_13 10.0.0.13:80 check server frontal_14 10.0.0.14:80 check ## STATS HAPROXY # user = admin, password = capa3.es listen stats 10.0.0.1:5555 mode http stats enable stats uri /stats stats realm HAProxy\ Statistics stats auth admin:capa3.es stats refresh 15s
Y finalmente iptables:
[root@loadbalancer-1 ~]# cat /etc/sysconfig/iptables *mangle :PREROUTING ACCEPT [895:122951] :INPUT ACCEPT [464:39534] :FORWARD ACCEPT [431:83417] :OUTPUT ACCEPT [555:274313] :POSTROUTING ACCEPT [986:357730] # # BASIC ANTI DDoS # -A PREROUTING -i bond0 -m conntrack --ctstate INVALID -j DROP -A PREROUTING -i bond0 -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j DROP -A PREROUTING -i bond0 -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG NONE -j DROP -A PREROUTING -i bond0 -p tcp -m tcp --tcp-flags FIN,SYN FIN,SYN -j DROP -A PREROUTING -i bond0 -p tcp -m tcp --tcp-flags SYN,RST SYN,RST -j DROP -A PREROUTING -i bond0 -p tcp -m tcp --tcp-flags FIN,SYN FIN,SYN -j DROP -A PREROUTING -i bond0 -p tcp -m tcp --tcp-flags FIN,RST FIN,RST -j DROP -A PREROUTING -i bond0 -p tcp -m tcp --tcp-flags FIN,ACK FIN -j DROP -A PREROUTING -i bond0 -p tcp -m tcp --tcp-flags ACK,URG URG -j DROP -A PREROUTING -i bond0 -p tcp -m tcp --tcp-flags FIN,ACK FIN -j DROP -A PREROUTING -i bond0 -p tcp -m tcp --tcp-flags PSH,ACK PSH -j DROP -A PREROUTING -i bond0 -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,RST,PSH,ACK,URG -j DROP -A PREROUTING -i bond0 -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG NONE -j DROP -A PREROUTING -i bond0 -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,PSH,URG -j DROP -A PREROUTING -i bond0 -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,PSH,URG -j DROP -A PREROUTING -i bond0 -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,RST,ACK,URG -j DROP COMMIT # *nat :PREROUTING ACCEPT [105178:6344332] :INPUT ACCEPT [4930:270900] :OUTPUT ACCEPT [196552:11795123] :POSTROUTING ACCEPT [201498:12052320] # nat saliente -A POSTROUTING -s 10.0.0.0/24 -o bond0 -j MASQUERADE # COMMIT # *filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [3457651:1777466788] :OUTPUT ACCEPT [1684673:891049858] -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT -A INPUT -p icmp -j ACCEPT -A INPUT -i lo -j ACCEPT # # ACEPTAMOS VRRP UNICAST DEL OTRO FW -A INPUT -s 198.51.100.1/30 -i bond0 -p vrrp -j ACCEPT -A INPUT -s 10.0.0.0/30 -i bond1 -p vrrp -j ACCEPT # # Balanceos # -A INPUT -i bond0 -d 198.51.100.100 -p tcp -m tcp --dport 80 -j ACCEPT -A INPUT -i bond0 -d 198.51.100.100 -p tcp -m tcp --dport 443 -j ACCEPT # # LAN # -A INPUT -s 10.0.0.0/24 -i bond1 -j ACCEPT -A INPUT -j REJECT --reject-with icmp-host-prohibited COMMIT
Y se me olvidaba … sysctl.conf
/etc/sysctl.conf :
net.ipv4.ip_forward = 1 net.ipv4.ip_nonlocal_bind = 1