Назад | Перейти на главную страницу

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

У меня есть веб-сервер с множеством виртуальных серверов. Только 1 из них - SSL. Проблема в том, что из-за того, что нет общего серверного блока, прослушивающего SSL, любой запрос https к другим сайтам обслуживается 1 блоком SSL.

Моя конфигурация, по сути, выглядит так:

# the catch all
server {
  listen 80 default;

  # I could add this, but since I have no default cert, I cannot enable SSL,
  # and this listen ends up doing nothing (apparently).
  # listen 443; 

  server_name _;
  # ...
}

# some server
server {
  listen 80;
  server_name server1.com;
  # ...
}

# some other server ...
server {
  listen 80;
  server_name server2.com;
  # ...
}

# ... and it's https equivalent
server {
  listen 443;
  ssl on;
  server_name server2.com;
  # ...
}

Теперь, когда для 443 нет слушателя по умолчанию, запрос вроде https://server1.com в конечном итоге будет обслуживаться server2.com блок https. Это следует логике для server_name в документы.

Если совпадений нет, будет использоваться блок server {...} в файле конфигурации в следующем порядке:

  1. блок сервера с соответствующей директивой прослушивания, отмеченной как [default | default_server]
  2. первый блок сервера с соответствующей директивой прослушивания (или неявное прослушивание 80;)

Какое решение этой проблемы предпочтительнее? Нужно ли мне настраивать фиктивный сертификат для моего блока сервера catch all, чтобы я мог прослушивать 443 и обрабатывать плохие запросы? Есть ли параметр, о котором я не знаю, который приводит к точному совпадению имени хоста с server?

В идеале я бы хотел, чтобы nginx вообще не обслуживал https, если имя хоста не совпадает, или чтобы он перенаправлял на http на том же хосте.

Ни то, ни другое невозможно. Подключение от клиента к https://foo.example.com/ не может быть принят ничем, кроме сертификата SSL с именем "foo.example.com" в качестве одного из имен. Нет возможности перенаправить, пока SSL-соединение не будет принято.

Если вы настроите каждый сайт для SSL, пользователь, щелкнувший по ошибке сертификата, получит сайт, который они запросили. Если вы настроили для SSL сайт «для всех», который предоставляет только страницу с ошибкой, и настроили виртуальный хостинг на основе имени для одного сайта, который должен поддерживать SSL, вы можете предоставлять клиентам страницу с ошибкой.

Виртуальный хостинг SSL и HTTP просто несовместимы друг с другом.

Единственный способ сделать это - создать самоподписанный сертификат SSL и использовать его для управления входящими https-запросами. Вы можете создать самоподписанный сертификат SSL, выполнив несколько простых шагов. изложено в этом посте.

Допустим, вы создали самозаверяющий сертификат с именем файла server.crt. Затем вы должны добавить в свою конфигурацию nginx следующее:

server {
    listen  443;

    ssl    on;
    ssl_certificate         /etc/nginx/ssl/server.crt;
    ssl_certificate_key     /etc/nginx/ssl/server.key;

    server_name server1.com;

    keepalive_timeout 60;
    rewrite ^       http://$server_name$request_uri? permanent;
}

Вы по-прежнему будете получать предупреждающее сообщение об использовании SSL в браузере, но, по крайней мере, сможете контролировать, что будет дальше.

Добавьте блок сервера для приема всей почты домена и верните код состояния 444. Он сообщает nginx о необходимости закрыть соединение перед отправкой любых данных.

server {
    listen 443 default_server ssl;
    server_name _;
    return 444;
}

В наши дни вы можете использовать расширение TLS Server Name Indication (SNI, RFC 6066). Слушатель HTTPS сможет распознать доменное имя до обслуживания соответствующего сертификата.

Это означает, что вам понадобятся сертификаты для ВСЕХ ваших доменов, и когда SNI используется для распознавания одного из других доменов, вы можете просто использовать перенаправление HTTP 301 на незашифрованную версию HTTP, если только имя сервера не совпадает с единственным, который требует шифрование.

Дополнительная информация о SNI доступна в документации nginx http://nginx.org/en/docs/http/configuring_https_servers.html

Сопоставьте запрошенное имя хоста с действительными именами хостов в http {} блок:

map $ssl_server_name $correct_hostname_example {
  default 0;
  example.com 1;
  www.example.com 1;
}

А потом в server {} заблокировать уничтожение соединений с неправильным именем хоста:

if ($correct_hostname_example = 0) {
  return 444;
}

При необходимости используйте несколько карт для нескольких серверных блоков. Соединение по-прежнему будет установлено с использованием одного из ваших сертификатов, но если этот последний блок присутствует в каждом блоке сервера, который обслуживает SSL, то вы фактически «заблокируете» соединения с недопустимыми именами хостов. Это может быть необходимо только в первом блоке сервера, но добавление его к каждому блоку сервера гарантирует, что порядок не имеет значения.

В $ssl_server_name переменная присутствует в nginx 1.7 и выше.

Вот как я решил проблему:

  1. Создать самоподписанный сертификат:

openssl req -nodes -x509 -newkey rsa:4096 -keyout self_key.pem -out self_cert.pem -days 3650

  1. Скопируйте его туда, где его найдет NginX:

cp self*.pem /etc/nginx/ssl/

  1. Создайте комплексный маршрут:
server {
    listen 443 default_server ssl;

    ssl on;
    ssl_certificate /etc/nginx/ssl/self_cert.pem;
    ssl_certificate_key /etc/nginx/ssl/self_key.pem;

    return 301 http://$host;
}

Что это будет делать: это даст вам предупреждение (никак) на любом сервере, у которого нет собственного сертификата, но в предупреждении не будет указано неправильное имя сертификата. Если пользователь нажимает «все равно посетить», он будет перенаправлен на набранную им версию сайта, отличную от SSL.

предостережение:

если ваш сайт с поддержкой SLL определяет только www.example.com (и нет example.com) тогда ваш универсальный маршрут будет обслуживать https://example.com с самоподписанным сертификатом и соответствующим предупреждением.

Перенаправить на http:

server {
    listen       443;
    server_name  *.com;
    rewrite        ^ http://$host$request_uri? permanent;
}    

Возврат 404 :

server {
    listen       443;
    server_name  *.com;
    return 404;
}