Реми Ван Элст. nginx 1.15.2, ssl_preread_protocol, совмещение HTTPS и SSH на одном порту

Это перевод статьи: Remy van Elst. nginx 1.15.2, ssl_preread_protocol, multiplex HTTPS and SSH on the same port.

Содержание

Введение

В блоге NGINX недавно была хорошая статья о новой возможности NGINX 1.15.2 - предварительном чтении протокола SSL. Она позволяет совмещать HTTPS и другие протоколы SSL на одном порту или, по утверждениям в блоге, "различать SSL/TLS и другие протоколы при перенаправлении трафика через TCP-прокси (stream)". Этим можно воспользоваться для запуска SSH и HTTPS на одном порту (или любого другого протокола SSL вместо HTTPS). Запустив SSH и HTTPS на одном порту, можно обойти ограничения фаервола. Если сеанс выглядит как HTTPS, nginx обработает его, а если выглядит как что-то другое, он перенаправит его в другую программу. Раньше я пользовался для этого SSLH, но теперь можно воспользоваться веб-сервером nginx.

Нужно воспользоваться NGINX в режиме прокси. Это означает, что nginx будет действовать как балнсировщик нагруки или прокси-сервер перед приложением (таким как Django, Rails и т.п.).

  • Обновление от 12-01-2020: добавлена команда apt-key add. Добавлен пример настройки SSH и HTTPS на одном сервере.

Установка последней версии NGINX

nginx предоставляет репозитории для дистрибутивов CentOS, Debian/Ubuntu и SUSE. В этом примере используется Ubuntu.

Скачайте ключ для проверки цифровой подписи:

wget http://nginx.org/keys/nginx_signing.key

Добавьте ключ в доверенные:

apt-key add nginx_signing.key

Добавьте репозиторий:

echo "deb http://nginx.org/packages/mainline/ubuntu/ bionic nginx" > /etc/apt/sources.list.d/nginx.list
echo "deb-src http://nginx.org/packages/mainline/ubuntu/ bionic nginx" >> /etc/apt/sources.list.d/nginx.list

Замените bionic на используемую версию Ubuntu (для определения версии воспользуйтесь командой lsb_release -a).

Установите nginx из только что добавленного репозитория:

apt-get update
apt-get install nginx

Настройка nginx для предварительного чтения протокола SSL

Согласно блогу nginx:

Следующий шаблон конфигурации использует переменную $ssl_preread_protocol в блоке map для назначения значения переменной $upstream, которое используется в качестве имени группы upstream, соответствующей протоколу, подходящему для подключения. Директива proxy_pass передаёт запрос в выбранную группу upstream. Отметим, что для работы переменной $ssl_preread_protocol необходимо вставить директиву ssl_preread on в блок server.

Указанный ниже фрагмент конфигурации нужно поместить в корень конфигурации nginx, а не в блок server.

stream {
    upstream ssh {
        server 192.0.2.10:22;
    }

    upstream https {
        server 192.0.2.20:443;
    }

    map $ssl_preread_protocol $upstream {
        default ssh;
        "TLSv1.3" https;
        "TLSv1.2" https;
        "TLSv1.1" https;
        "TLSv1.0" https;
    }

    # SSH и SSL на одном порту
    server {
        listen 443;
        proxy_pass $upstream;
        ssl_preread on;
    }
}

В приведённой конфигурации, если обнаружен протокол TLSv1.2, считается что это трафик HTTPS, который направляется на сервер HTTPS (192.0.2.20). В противном случае трафик направляется на узел SSH (192.0.2.10).

SSH и HTTPS на одном сервере

Если нужно разделить SSH и HTTPS на одном сервере, конфигурация будет немного другой. Сначала нужно убедиться в отсутствии веб-сайтов, ожидающих подключений на порту 443, потому что nginx будет использовать этот порт для проксирования.

Ни один другой сайт в nginx не должен использовать порт 443. Поменяйте блоки listen для использования, например, порта 8443:

listen [::]:8443 http2;
listen 8443 http2;

Конфигурацию для SSH/SSL нужно поместить не в директиву server, а в корень конфигурации nginx:

stream {
    upstream ssh {
        server 127.0.0.1:22;
    }

    upstream https {
        server 127.0.0.1:8443;
    }

    map $ssl_preread_protocol $upstream {
        default ssh;
        "TLSv1.2" https;
        "TLSv1.3" https;
        "TLSv1.1" https;
        "TLSv1.0" https;
    }

    # SSH и SSL на одном порту
    server {
        listen 443;
        proxy_pass $upstream;
        ssl_preread on;
    }
}

Другие прелести ssl_preread

Модуль ssl_preread умеет определять не только протокол. Также поддерживается определение имени сервера SNI, что позволяет прокси направлять трафик к разным серверам на основании запрошенного имени узла SSL. Процитирую документацию:

map $ssl_preread_server_name $name {
    backend.example.com backend;
    default backend2;
}

upstream backend {
    server 192.168.0.1:12345;
    server 192.168.0.2:12345;
}

upstream backend2 {
    server 192.168.0.3:12345;
    server 192.168.0.4:12345;
}
server {
    listen 12346;
    proxy_pass $name;
    ssl_preread on;
}

Учтите, что здесь необходима версия nginx новее, чем распространяемая по умолчанию с выпусками Ubuntu 16.04 или 18.04.