Haproxy 用于基于 SSH 名称的代理

Haproxy 用于基于 SSH 名称的代理

我有一台装有多个 lxc 容器的主机。我试图根据域名直接授予容器 ssh 访问权限。为此,我尝试设置 HAProxy。可以使用 http 模式下的 ACL 轻松实现此目的。当我尝试使用基于 acls 的 ssh 的 tcp 模式进行相同操作时,我无法直接授予容器 ssh 访问权限。以下是我在 haproxy.cfg 中使用的代码片段。

listen SSHD :2200
    mode tcp
    acl is_apple hdr_dom i apple
    acl is_orange hdr_dom -i orange
    use_backend apple if is_apple
    use_backend orange if is_orange

backend apple
    mode tcp
    server apple 10.0.3.221:22

backend orange
    mode tcp
    server orange 10.0.3.222:22

其中 apple.myhost.com 和 orange.myhost.com 是用于访问每个容器的域名。HTTP 代理可以很好地与这些 acl 配合使用,但我遇到了 SSH 流量问题。

我收到以下错误。

ssh_exchange_identification:远程主机关闭连接

答案1

我正在使用在 pfSense 上运行的 HAproxy 实例来实现您所寻找的目的。

我在这里写了详细的描述: https://julian.pawlowski.me/geeking-out-with-haproxy-on-pfsense-the-ultimate/

我将进一步完善此设置:端口 443 由 SSH、SSL/TLS 和 OpenVPN 流量共享,同时使用 X.509 客户端证书保护 SSH:

  • 正常 HTTPS 流量(充当保护网络流量的正常反向代理)
  • 使用 X509 用户证书身份验证的正常 HTTPS 流量
  • OpenVPN 拨入流量
  • TLS 隧道 SSH 流量,包括 X509 用户证书认证(SSLH 网关)

这还可以防止对 SSH 入口点的端口扫描。此外,它还可以帮助实现 IPv4 到 IPv6(反之亦然)的转换、灵活的协作和管理员的家庭办公解决方案等。

我知道有一个很棒的小工具 SSLH,但是由于 HAproxy 的强大功能,这个解决方案更加灵活。

这是 pfSense 根据我的博客文章创建的 haproxy.cfg 文件,供您参考:

global
    maxconn         2000
    stats socket /tmp/haproxy.socket level admin
    uid         80
    gid         80
    nbproc          1
    chroot          /tmp/haproxy_chroot
    daemon
    tune.ssl.default-dh-param   2048
    # Modern browser compatibility only as mentioned here:
    # https://wiki.mozilla.org/Security/Server_Side_TLS
    ssl-default-bind-ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK

    # Time-to-first-Byte (TTFB) value needs to be optimized based on
    # the actual public certificate chain
    # see https://www.igvita.com/2013/10/24/optimizing-tls-record-size-and-buffering-latency/
    tune.ssl.maxrecord 1370

listen HAProxyLocalStats
    bind 127.0.0.1:2200 name localstats
    mode http
    stats enable
    stats admin if TRUE
    stats uri /haproxy_stats.php?haproxystats=1
    timeout client 5000
    timeout connect 5000
    timeout server 5000

frontend HTTP_redirect
    bind            0.0.0.0:80 name 0.0.0.0:80   
    mode            http
    log         global
    option          http-keep-alive
    timeout client      30000
    default_backend     _ssl-redirect_http_ipvANY

frontend LAN_HTTPS
    bind            10.108.2.1:443 name 10.108.2.1:443 ssl no-sslv3 no-tls-tickets no-tlsv10 no-tlsv11 crt /var/etc/haproxy/LAN_HTTPS.pem  
    mode            http
    log         global
    option          http-keep-alive
    option          forwardfor
    acl https ssl_fc
    reqadd X-Forwarded-Proto:\ http if !https
    reqadd X-Forwarded-Proto:\ https if https
    timeout client      30000
    # Remove headers that expose security-sensitive information.
    rspidel ^Server:.*$
    rspidel ^X-Powered-By:.*$
    rspidel ^X-AspNet-Version:.*$
    default_backend     gwsch01_http_ipvANY

frontend WAN_443-merged
    bind            178.26.150.88:443 name 178.26.150.88:443   
    mode            tcp
    log         global
    timeout client      7200000
    tcp-request inspect-delay 5s

    # block SSLv3 as early as possible
    acl sslv3 req.ssl_ver 3
    tcp-request content reject if sslv3
    tcp-request content accept if { req.ssl_hello_type 1 } or !{ req.ssl_hello_type 1 }
    acl         aclusr_custom_req.ssl_hello_type_201    req.ssl_hello_type 1
    acl         aclusr_custom_req.ssl_sni_20-m_20end_20-i_20.ssh.example.com    req.ssl_sni -m end -i .ssh.example.com
    acl         aclusr_custom_req.ssl_sni_20-m_20end_20-i_20.vpn.example.com    req.ssl_sni -m end -i .vpn.example.com
    acl         aclusr_custom_req.len_200   req.len 0
    use_backend     _WAN_HTTPS_tcp_ipvANY if aclusr_custom_req.ssl_hello_type_201 !aclusr_custom_req.ssl_sni_20-m_20end_20-i_20.ssh.example.com !aclusr_custom_req.ssl_sni_20-m_20end_20-i_20.vpn.example.com 
    use_backend     _WAN_HTTPS_auth_tcp_ipvANY if aclusr_custom_req.ssl_hello_type_201 aclusr_custom_req.ssl_sni_20-m_20end_20-i_20.vpn.example.com 
    use_backend     _openvpn_tcp_ipvANY if aclusr_custom_req.len_200 aclusr_custom_req.ssl_hello_type_201 
    use_backend     _WAN_SSLH_tcp_ipvANY if aclusr_custom_req.ssl_hello_type_201 aclusr_custom_req.ssl_sni_20-m_20end_20-i_20.ssh.example.com 
    default_backend     _none_tcp_ipvANY

frontend WAN_HTTPS
    bind            127.0.0.1:2043 name 127.0.0.1:2043 ssl no-sslv3 no-tls-tickets no-tlsv10 no-tlsv11 crt /var/etc/haproxy/WAN_HTTPS.pem  accept-proxy npn http/1.1
    mode            http
    log         global
    option          http-keep-alive
    option          forwardfor
    acl https ssl_fc
    reqadd X-Forwarded-Proto:\ http if !https
    reqadd X-Forwarded-Proto:\ https if https
    timeout client      7200000
    # Remove headers that expose security-sensitive information.
    rspidel ^Server:.*$
    rspidel ^X-Powered-By:.*$
    rspidel ^X-AspNet-Version:.*$
    default_backend     _none_http_ipvANY

frontend WAN_HTTPS_auth-merged
    bind            127.0.0.1:2044 name 127.0.0.1:2044 ssl no-sslv3 no-tls-tickets no-tlsv10 no-tlsv11 crt /var/etc/haproxy/WAN_HTTPS_auth.pem ca-file /var/etc/haproxy/clientca_WAN_HTTPS_auth.pem verify required  accept-proxy npn http/1.1
    mode            http
    log         global
    option          http-keep-alive
    option          forwardfor
    acl https ssl_fc
    reqadd X-Forwarded-Proto:\ http if !https
    reqadd X-Forwarded-Proto:\ https if https
    timeout client      7200000
    # Remove headers that expose security-sensitive information.
    rspidel ^Server:.*$
    rspidel ^X-Powered-By:.*$
    rspidel ^X-AspNet-Version:.*$
    acl         aclusr_host_matches_gwsch01.vpn.example.com hdr(host) -i gwsch01.vpn.example.com
    use_backend     gwsch01_http_ipvANY if aclusr_host_matches_gwsch01.vpn.example.com 
    default_backend     _none_http_ipvANY

frontend WAN_SSLH-merged
    bind            127.0.0.1:2022 name 127.0.0.1:2022 ssl no-sslv3 no-tls-tickets no-tlsv10 no-tlsv11 crt /var/etc/haproxy/WAN_SSLH.pem ca-file /var/etc/haproxy/clientca_WAN_SSLH.pem verify required  accept-proxy npn ssh/2.0
    mode            tcp
    log         global
    timeout client      7200000
    acl         aclusr_custom_ssl_fc_sni_reg_20gwsch01.ssh.example.com  ssl_fc_sni_reg gwsch01.ssh.example.com
    acl         aclusr_custom_ssl_fc_npn_20-i_20ssh_2f2.0   ssl_fc_npn -i ssh/2.0
    use_backend     SSH_gwsch01_https_ipvANY if aclusr_custom_ssl_fc_sni_reg_20gwsch01.ssh.example.com aclusr_custom_ssl_fc_npn_20-i_20ssh_2f2.0 
    default_backend     _none_https_ipvANY

backend _ssl-redirect_http_ipvANY
    mode            http
    timeout connect     30000
    timeout server      30000
    retries         3
    option          httpchk
    redirect scheme https code 301

backend gwsch01_http_ipvANY
    mode            http
    rspadd Strict-Transport-Security:\ max-age=31536000;
    rspirep ^(Set-Cookie:((?!;\ secure).)*)$ \1;\ secure if { ssl_fc }
    timeout connect     3000
    timeout server      7200000
    retries         2
    option          httpchk
    server          gwsch01 127.0.0.1:8443 ssl  verify none 

backend _none_tcp_ipvANY
    mode            tcp
    timeout connect     30000
    timeout server      30000
    retries         3
    option          httpchk OPTIONS / 
    server          none 127.0.0.1:61235 check inter 1000 disabled 

backend _WAN_HTTPS_tcp_ipvANY
    mode            tcp
    timeout connect     30000
    timeout server      7200000
    retries         3
    option          httpchk
    server          WAN_HTTPS 127.0.0.1:2043 check-ssl  verify none send-proxy 

backend _WAN_HTTPS_auth_tcp_ipvANY
    mode            tcp
    timeout connect     30000
    timeout server      7200000
    retries         3
    option          httpchk
    server          WAN_HTTPS_auth 127.0.0.1:2044 check-ssl  verify none send-proxy 

backend _openvpn_tcp_ipvANY
    mode            tcp
    timeout connect     3000
    timeout server      7200000
    retries         2
    option          httpchk
    server          openvpn1 127.0.0.1:1194  

backend _WAN_SSLH_tcp_ipvANY
    mode            tcp
    timeout connect     30000
    timeout server      7200000
    retries         3
    option          httpchk
    server          WAN_SSLH 127.0.0.1:2022 check-ssl  verify none send-proxy 

backend _none_http_ipvANY
    mode            http
    timeout connect     30000
    timeout server      30000
    retries         3
    option          httpchk OPTIONS / 
    server          none 127.0.0.1:61235 check inter 1000 disabled 

backend _none_https_ipvANY
    mode            tcp
    timeout connect     30000
    timeout server      30000
    retries         3
    option          httpchk OPTIONS / 
    server          none 127.0.0.1:61235 check inter 1000 disabled 

backend SSH_gwsch01_https_ipvANY
    mode            tcp
    timeout connect     3000
    timeout server      7200000
    retries         2
    option          httpchk
    server          ssh_gwsch01 127.0.0.1:22

答案2

这是不可能的。HTTP 协议不同,因为存在“虚拟主机”概念,并且 HAProxy 可以使用“Host:”标头区分不同的主机。SSH 没有类似的东西,因此 lxc-host 无法知道您尝试连接的容器。

但是您可以使用另一个 SSH 功能,称为“SSH 网关”。里面~/.ssh/authorized_keys有一个command=选项。首先从您的 lxc-host 设置基于密钥的 ssh 到 apple 和 orange。然后将这些行放入 lxc 的authorized_keys文件中:

command="ssh -q -t user@apple $SSH_ORIGINAL_COMMAND" ssh-rsa AAAAsomeB3N...== user@client
command="ssh -q -t user@orange $SSH_ORIGINAL_COMMAND" ssh-rsa AAAAanotherB3N...== user@client

现在 lxs 主机可以根据客户端密钥自动连接到apple和。orange

查看更多:

答案3

恐怕这是不可能的。SSH 协议不支持主机名。它只是连接到 IP(当然是在解析之后)并设置加密连接。没有“虚拟主机”的概念。

答案4

HAProxy 的博客提供了一种有趣的方法:使用 HAProxy 路由 SSH 连接 | 2020 年 12 月 21 日

为了将 SSH 连接路由到不同的服务器,您必须知道用户想要访问哪个服务器。如前所述,HAProxy 不会分析 SSH 协议,而且无论如何,该协议不会提供有关目的地的任何提示。因此,我们必须将连接包装在另一个有助于这一点的协议中。

我们将使用 TLS 协议及其 SNI 扩展以及 SSH ProxyCommand 功能。或者换句话说,我们将使用 TLS 包装我们的连接,但我们这样做只是为了利用 SNI,以便客户端可以告诉我们他们想要连接到哪个服务器。我们不会使用 TLS 的加密功能。

[...]

从您的客户端,您可以使用以下命令访问您的 SSH 服务器:

$ ssh -o ProxyCommand="openssl s_client -quiet -connect 172.16.0.10:2222 -servername server1" dummyName1 
$ ssh -o ProxyCommand="openssl s_client -quiet -connect 172.16.0.10:2222 -servername server2" dummyName2 
$ ssh -o ProxyCommand="openssl s_client -quiet -connect 172.16.0.10:2222 -servername server3" dummyName3 

请注意,ssh 命令要求您发送要连接的服务器的名称。我们将其设置为 dummyName,因为我们改用 ProxyCommand 字段指定服务器名称。servername 开关允许您设置 SNI 字段内容。为了使此命令更短,请考虑创建 bash 别名或脚本。

相关内容