Haproxy для прокси на основе имени SSH
У меня есть хост-машина с несколькими контейнерами lxc. Я пытаюсь дать SSH доступ к контейнерам непосредственно на основе доменных имен. Для этого я попытался настроить HAProxy. Можно легко добиться этого с помощью ACL в режиме http. Когда я пытаюсь сделать то же самое с режимом tcp для ssh на основе acls, я не могу добиться предоставления 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 прокси работает нормально с этими acls, но я сталкиваюсь с проблемой с трафиком SSH.
Я получаю следующую ошибку.
ssh_exchange_identification: соединение закрыто удаленным хостом
4 ответа
Я использую экземпляр HAproxy, работающий на pfSense именно для той цели, которую вы искали.
Я написал подробное описание здесь: http://loredo.tumblr.com/post/116633549315/geeking-out-with-haproxy-on-pfsense-the-ultimate
Я собираюсь пойти еще дальше с этой настройкой: порт 443 используется совместно для трафика SSH, SSL/TLS и OpenVPN, а SSH защищен с помощью клиентского сертификата X.509:
- обычный HTTPS-трафик (действует как обычный обратный прокси-сервер для защиты веб-трафика)
- обычный HTTPS-трафик с аутентификацией по сертификату пользователя X509
- OpenVPN коммутируемый трафик
- TLS-туннельный трафик SSH, включая аутентификацию по сертификату пользователя X509 (шлюз SSLH)
Это также защищает от сканирования портов в точках входа SSH. Кроме того, он может помочь с переходом с IPv4 на IPv6 (и наоборот), гибкими решениями для совместной работы и домашнего офиса для администраторов и т. П.
Я знаю, что есть этот блестящий инструмент SSLH, но это решение гораздо более гибкое благодаря мощности HAproxy.
Это файл haproxy.cfg, созданный pfSense на основе моего блога для вашей справки:
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
Это невозможно. Протокол HTTP отличается, поскольку существует концепция "виртуального хоста", и HAProxy может различать разные хосты с помощью заголовка "Host:". SSH не имеет ничего подобного, поэтому lxc-host не может знать контейнер, вы пытаетесь подключиться.
Но вы можете использовать другую функцию SSH, которая называется "SSH gateway". внутри ~/.ssh/authorized_keys
E сть command=
вариант. Сначала настраивает ssh на основе ключей с вашего lxc-хоста на 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
на основе клиентского ключа.
Узнать больше:
Боюсь, это невозможно. Протокол SSH не поддерживает имена хостов. Он просто подключается к IP (после разрешения конечно) и устанавливает зашифрованное соединение. Не существует понятия "виртуальные хосты".
Блог HAProxy предлагает интересный подход: Маршрутизация SSH-соединений с помощью HAProxy | 21 декабря 2020 г.
Чтобы направить SSH-соединения на разные серверы, вы должны знать, к какому серверу хочет получить доступ пользователь. Как было сказано ранее, HAProxy не анализирует протокол SSH и, в любом случае, этот протокол не дает никакой подсказки о пункте назначения. Итак, нам нужно обернуть соединения внутри другого протокола, который поможет в этом вопросе.
Мы будем использовать протокол TLS и его расширение SNI вместе с функцией SSHProxyCommand. Или, другими словами, мы оборачиваем наши соединения 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. Переключатель имени сервера позволяет вам установить содержимое поля SNI. Чтобы сделать эту команду короче, рассмотрите возможность создания псевдонима bash или сценария.