Clúster de Balanceadores HAProxy con SSL Offloading y Keepalived

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

 

Leave a Reply

Your email address will not be published. Required fields are marked *