Linux

具有多個域/子域和 ssl_certificate 變數的 Nginx

  • March 30, 2021

我們有一個 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;
  }
}

問題是用於證書$host的變數。mapTLS 連接中的請求流程如下所示:

  1. 客戶端在 TCP 握手後向伺服器發送 TLS Hello 數據包。Hello 數據包包含SNI欄位,它告訴虛擬主機客戶端嘗試連接。
  2. 伺服器需要為客戶端選擇要發送的證書。$host變數僅由以下內容填充:
  • 請求行中的主機名
  • Host請求標頭中的主機名
  • server_name匹配請求

此處唯一可用的資訊是server_name,因此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包含來自 ClientHello 數據包的 SNI 欄位的值,然後可用於選擇證書。

引用自:https://serverfault.com/questions/1058832