Nginx с несколькими доменами / субдоменами и переменной ssl_certificate

У нас есть Nginx с сотнями прокси для нескольких доменов / поддоменов. Как это проводится годами, каждый новый домен, мы просто идем туда и создаем новый файл conf с серверным блоком, содержащим HTTP и HTTPS.

Обратите внимание на то, что многие из этих серверных блоков имеют одинаковые параметры прокси, только меняют бэкэнд, и они напоминают: «Хм, могу ли я попытаться поместить все это в одну пару серверных блоков с http и https?» и решаем попробовать :)

Моя идея заключалась в том, чтобы сопоставить переменные с адресом с серверной частью и с используемыми сертификатами, поскольку это единственная информация, которая изменяется в большинстве имеющихся у нас серверных блоков, таким образом, для новых доменов. просто добавил бы линии на карты.

HTTP работает нормально, никаких проблем, проблема - HTTPS.

У меня есть несколько доменов, например «example02.com, example01.com, ...», и я использую групповой сертификат для каждого из этих доменов, поэтому в зависимости от хоста я сопоставляю имя сертификата с переменной и использую его в ssl_certificate и ssl_certificate_key, как показано ниже, которые я видел только возможными, начиная с Nginx 1.15, выглядят так. (Я использую 1.18 прямо сейчас)

Проблема в том, что при том, как я создаю указанный ниже серверный блок, только www.example01.com будет работать с правильным сертификатом, когда вы попытаетесь войдите в www.example02.com , он все равно будет пытаться загрузить сертификат * .example01.com.

Можно просто создать серверный блок для каждого домена, чтобы обслуживать несколько поддоменов с разными сертификатами, но пока я просто пытаюсь выяснить, можно ли это сделать с помощью пары серверных блоков, и проверяю, делаю ли я что-то не так.

map $host $backendaddr {
        default         "127.0.0.1:4444";
        www.example01.com   "127.0.0.1:4445"
        www.example02.com   "127.0.0.1:4446"
}

map $host $certificate {
        default         "example.com";
        www.example01.com   example01.com;
        www.example02.com   example02.com;
}

server {
   listen 80;
   server_name www.example01.com www.example02.com;
   access_log /var/log/nginx/access-http.log main;

   proxy_set_header Host $host;
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
   proxy_connect_timeout 1800;
   proxy_read_timeout 1800;
   proxy_send_timeout 1800;
   location / {
      proxy_pass http://$backendaddr$request_uri;
   }
}

server {
   listen 443 ssl;
   server_name  www.example01.com www.example02.com;
   access_log /var/log/nginx/access-https.log main;
   
   ssl_certificate ssl/$certificate.pem;
   ssl_certificate_key ssl/$certificate.key;

   proxy_set_header Host $host;
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
   proxy_connect_timeout 1800;
   proxy_read_timeout 1800;
   proxy_send_timeout 1800;
   location / {
      proxy_pass https://$backendaddr$request_uri;
   }
}
1
задан 30 March 2021 в 17:55
1 ответ

Проблема в том, что переменная $host используется в map для сертификатов. Поток запросов в TLS соединении выглядит примерно так:

  1. Клиент посылает серверу пакет TLS Hello после TCP handshake. Пакет Hello включает поле SNI, которое сообщает виртуальный хост, к которому пытается подключиться клиент.
  2. Серверу необходимо выбрать сертификат для отправки клиенту. $host переменная заполняется только из следующего:
  • Имя хоста из строки запроса
  • Имя хоста из Host заголовка запроса
  • имя_сервера, соответствующее запросу

Единственной доступной информацией здесь является имя_сервера, поэтому www.example01.com используется как значение для $host переменной.

Чтобы достичь поставленной цели, вы можете попробовать следующее:

server {
    listen 443 ssl;
    server_name www.example01.com www.example02.com;
    ssl_preread on;
    ...
}

map $ssl_preread_server_name $certificate {
    default           example.com;
    www.example01.com example01.com;
    www.example02.com example02.com;
}

ssl_preread on заставляет nginx назначать переменные на основе пакета TLS ClientHello. $ssl_preread_server_name содержит значение поля SNI из пакета ClientHello, которое затем может быть использовано для выбора сертификата.

1
ответ дан 24 April 2021 в 00:49

Теги

Похожие вопросы