Como dimensionar horizontalmente a terminação SSL por trás do balanceamento de carga HAProxy?

6

Eu tenho procurado e ninguém parece estar tentando escalar a terminação SSL do jeito que sou e estou curioso para saber porque minha abordagem parece tão incomum.

Aqui está o que eu quero fazer, seguido do porquê:

  10.0.1.1  10.0.1.2 - 10.0.1.5
-----+--------+----+----+----+
     |        |    |    |    |
  +--+--+   +-+-++-+-++-+-++-+-+
  | LB1 |   | A || B || C || D |
  +-----+   +---++---++---++---+
haproxy 1.5 haproxy 1.5 + tomcat
 tcp mode    http mode

Por que essa configuração maluca de Internet -> HAProxy (tcp mode) -> HAProxy (http mode) -> Tomcat ? Em duas palavras: segurança e escalabilidade

Ao descarregar a terminação SSL para os backends da Web (AD) que executam o HAProxy 1.5 e o Tomcat ouvindo apenas na interface de loopback, posso garantir que todo o tráfego é criptografado do cliente para o servidor, sem possibilidade de detectar nada que não seja local para o backend da web.

Além disso, à medida que a demanda de SSL aumenta, posso simplesmente criar novos servidores de back-end (baratos) atrás do balanceador de carga.

Por fim, elimina a exigência de ter os certificados ativos no LB externo e adiciona segurança adicional ao fazer isso, pois um LB comprometido não terá nenhum pems ou certs nele.

Minha situação parece muito semelhante a esta: por que não há exemplos de balanceadores de carga de software dimensionáveis horizontalmente balanceando SSL? , mas não estou usando sessões baseadas em arquivo e, se possível, gostaria de evitar o balanceamento por IP, já que os clientes podem estar vindo de trás de um NAT. / p>

Eu tentei seguir as instruções HAProxy no documento de configuração para usar a tabela de palitos com o ID de SSL ( link ), mas isso não parece manter minha sessão em um servidor de back-end (recarregando a página de estatísticas A / admin? que mostra os saltos de nome de nó em todos os meus servidores de back-end).

É evidente que o balanceamento de carga round-robin está funcionando, mas as sessões persistentes não são.

Veja um exemplo da minha configuração de LB:

global
    log 127.0.0.1 local0 notice
    maxconn 200
    daemon
    user appserver
    group appserver
    stats socket /tmp/haproxy

defaults
    log     global
    mode    tcp
    timeout client  5000ms
    timeout connect 50000ms
    timeout server  50000ms

    option contstats

frontend frontend_http
    log global
    bind *:80
    default_backend backend_http_servers

frontend frontend_ssl
    log global
    bind *:443
    default_backend backend_servers

listen stats :8888
    mode http
    stats enable
    stats hide-version
    stats uri /

#################################################################################################
## NOTE: Anything below this section header will be generated by the bootstrapr process and may be 
##       re-generated at any time losing manual changes
#################################################################################################
##          BACKENDS
#################################################################################################
backend backend_http_servers
    mode tcp

    #option httpchk

    server webA:8081 webA:8081 check port 8081
    server webB:8081 webB:8081 check port 8081
    # This configuration is for HTTPS affinity from frontdoor to backend

    # Learn SSL session ID from both request and response and create affinity
    backend backend_servers
    mode tcp

    balance roundrobin
    option ssl-hello-chk
    #option httpchk

    # maximum SSL session ID length is 32 bytes
    stick-table type binary len 32 size 30k expire 30m

    acl clienthello req_ssl_hello_type 1
    acl serverhello rep_ssl_hello_type 2

    # use tcp content accepts to detects ssl client and server hello
    tcp-request inspect-delay 5s
    tcp-request content accept if clienthello

    # no timeout on response inspect delay by default
    tcp-response content accept if serverhello

    # SSL session ID (SSLID) may be present on a client or server hello
    # Its length is coded on 1 byte at offset 43 and its value starts
    # at offset 44
    # Match and learn on request if client hello
    stick on payload_lv(43,1) if clienthello

    # Learn on response if server hello
    stick store-response payload_lv(43,1) if serverhello

############################################
# HTTPS BACKENDS
############################################
    server webA:8443 webA:8443 check port 8443
    server webB:8443 webB:8443 check port 8443

Um exemplo da minha configuração de back-end para o webA se parece com:

global
    log 127.0.0.1 local0 info
    maxconn 200
    daemon

defaults
    log     global
    mode    http
    option  dontlognull
    option  forwardfor
    option  httplog
    option  httpchk # checks server using HTTP OPTIONS on / and marks down if not 2xx/3xx status
    retries 3
    option redispatch
    maxconn         200
    timeout client  5000
    timeout connect 50000
    timeout server  50000

frontend frontend_http
    log global

    # only allow connections if the backend server is alive
    monitor fail if { nbsrv(backend_application) eq 0 }

    reqadd X-Forwarded-Proto:\ http    # necessary for tomcat RemoteIPValve to report the correct client IP and port
    reqadd X-Forwarded-Protocol:\ http # necessary because who knows what's actually correct?
    reqadd X-Forwarded-Port:\ 80       # also here for safety
    bind *:8081
    default_backend backend_application

frontend frontend_ssl
    log global

    # only allow connections if the backend server is alive
    monitor fail if { nbsrv(backend_application) eq 0 }

    reqadd X-Forwarded-Proto:\ https    # necessary for tomcat RemoteIPValve to report the correct client IP and port
    reqadd X-Forwarded-Protocol:\ https # necessary because who knows what's actually correct?
    reqadd X-Forwarded-Port:\ 443       # also here for safety
    reqadd X-Forwarded-SSL:\ on         # also here for safety
    bind *:8443 ssl crt /path/to/default.pem crt /path/to/additional/certs crt /path/to/common/certs
    default_backend backend_application
 #################################################################################################
#           Backends
#################################################################################################
backend backend_haproxy
    stats enable
    stats show-node
    stats uri    /haproxy
    acl acl_haproxy url_beg /haproxy
    redirect location /haproxy if !acl_haproxy

backend backend_application
    stats enable
    stats show-node
    stats uri  /haproxy
    option httpclose
    option forwardfor
    acl acl_haproxy url_beg /haproxy
    server 127.0.0.1:8080 127.0.0.1:8080 check port 8080

Nesta configuração, uma conexão SSL (ou não-SSL) é roteada através do LB para um dos backends de uma maneira round-robin. No entanto, quando eu recarregar a página (fazer uma nova solicitação), é claro que mudo para outro back-end, independentemente de SSL ou não.

Eu testo isso indo para https://LB/haproxy , que é a URL da página de estatísticas de back-end com o nome do nó (mostra webA pela primeira vez e webB após um recarregamento, e assim por diante com cada recarregamento subseqüente). Indo para http://LB:8888 mostra as estatísticas do LB e mostra todos os meus backends saudáveis.

O que preciso alterar para que as sessões fiquem em um back-end quando o SSL é encerrado no back-end?

Edit: Pergunta: Por que não saltar em servidores de back-end e armazenar a sessão em um armazenamento central (como o memcached)?

Resposta: Como o aplicativo herdado é extremamente frágil e quebra quando a sessão é transportada pelos servidores. Contanto que o usuário permaneça no mesmo back-end, o aplicativo funcionará conforme o esperado. Isso será alterado eventualmente (reescrito), mas não no curto prazo.

    
por BrionS 04.04.2013 / 20:43

1 resposta

4

Em primeiro lugar, isso adiciona complexidade desnecessária aos seus servidores da Web.

Em segundo lugar, encerrar a conexão SSL na LB significa que você pode usar keep-alive no lado do cliente para a conexão, reduzindo a parte complexa de estabelecer a conexão. Além disso, o uso mais eficiente de recursos é agrupar cargas de trabalho semelhantes. Muitas pessoas separam conteúdo estático e dinâmico, SSL no LB significa que ambos podem vir de diferentes servidores através da mesma conexão.

Em terceiro lugar, o SSL geralmente é dimensionado a uma taxa diferente da exigida pelo seu aplicativo da web. Eu acho que a falta de exemplos se deve ao fato de que um único par LB ou round robin dns é suficiente para a maioria das pessoas. Parece-me que você pode estar superestimando a carga de trabalho SSL.

Além disso, não tenho certeza sobre seu raciocínio em relação à segurança. Além do fato de que o servidor da web já está executando muito mais serviços com possíveis explorações, se houver alguma vulnerabilidade na LB, você acaba de introduzi-los em seus servidores da Web também!

    
por 04.04.2013 / 21:29