翻譯自: ngx.ssl - Lua API for controlling NGINX downstream SSL handshakes
1. 概要
# 注意:如果你使用的是 OpenResty 1.9.7.2+,則不需要該行
lua_package_path "/path/to/lua-resty-core/lib/?.lua;;";
server {
listen 443 ssl;
server_name test.com;
# useless placeholders: just to shut up NGINX configuration
# loder errors:
ssl_certificate /path/to/fallback.crt;
ssl_certificate_key /path/to/fallback.key;
ssl_certificate_by_lua_block {
local ssl = require "ngx.ssl"
-- clear the fallback certificates and private keys
-- set by the ssl_certificate and ssl_certificate_key
-- directives above:
local ok, err = ssl.clear_certs()
if not ok then
ngx.log(ngx.ERR, "failed to clear existing (fallback) certificates")
return ngx.exit(ngx.ERROR)
end
-- assuming the user already the my_load_certificate_chain()
-- herself.
local pem_cert_chain = assert(my_load_certifiate_chain())
local der_cert_chain, err = ssl.cert_pem_to_der(pem_cert_chain)
if not der_cert_chain then
ngx.log(ngx.ERR, "failed to convert certificate chain ",
"from PEM to DER: ", err)
return ngx.exit(ngx.ERROR)
end
local ok, err = ssl.set_der_cert(der_cert_chain)
if not ok then
ngx.log(ngx.ERR, "failed to set DER cert: ", err)
return ngx.exit(ngx.ERROR)
end
-- assuming the user already defined the my_load_private_key()
-- function herself.
local pem_pkey = assert(my_load_private_key())
local der_pkey, err = ssl.priv_key_pem_to_der(pem_pkey)
if not der_pkey then
ngx.log(ngx.ERR, "failed to convert private key ",
"from PEM to DER: ", err)
return ngx.exit(ngx.ERROR)
end
local ok, err = ssl.ser_der_priv_key(der_pkey)
if not ok then
ngx.log(ngx.ERR, "failed to set DER private key: ", err)
return ngx.exit(ngx.ERROR)
end
}
location / {
root html;
}
}
2. 描述
該 Lua 模塊提供 API 函數來控制類似 ssl_certificate_by_lua*(ngx_lua 模塊) 等上下文的 SSL 握手過程。
OpenSSL 允許我們動態地設置證書和私鑰,因此期望可以在建立連接前才設置證書和私鑰,這樣,可以結合 SNI,針對不同的請求域名動態設置不同的證書和私鑰,而無需事先把可能用到的證書和私鑰都准備好。該 lua 模塊提供的 API 以在 ssl_certificate_by_lua* 指令的上下文中支持此種情況。
在 Lua 中加載 ngx.ssl 模塊,因如下:
local ssl = require "ngx.ssl"
3. 方法
3.1 clear_certs
語法:ok, err = ssl.clear_certs()
上下文:ssl_certificate_by_lua*
- 清除在當前 SSL 連接中設置的任何已經存在的 SSL 證書和私鑰。
- 成功返回 true,失敗返回一個 nil 值並且通過字符串描述錯誤。
3.2 cert_pem_to_der
語法:der_cert_chain, err = ssl.cert_pem_to_der(pem_cert_chain)
上下文:任意
- 將 PEM 格式的 SSL 證書鏈數據轉換為 DER 格式(轉換后的 der 格式數據將用於 set_der_cert 函數)。
- 失敗,則返回 nil 值,並且通過字符串描述錯誤。
- openssl 命令行工具可能無法正確將 SSL 證書鏈從 PEM 格式轉換為 DER,因此總是使用該 Lua 函數進行轉換。你可以總是使用類似 lua-resty-lrucache 或者 ngx_lua API(如 lua_shared_dict)等庫來緩存 DER 格式的結果。
- 該函數可以在任何上下文中使用。
3.3 set_der_cert
語法:ok, err = ssl.set_der_cert(der_cert_chain)
上下文:ssl_certificate_by_lua*
- 為當前的 SSL 連接設置 DER 格式的 SSL 證書鏈數據。注意,該 DER 數據是直接保存在 Lua 字符串參數中的。不需要外部文件支持。
- 若成功返回 true,否則返回 nil 並且通過字符串描述錯誤。
- 注意,SSL 證書鏈通常以 PEM 格式編碼,因此首先需要使用 cert_perm_to_der 函數進行轉換。
3.4 priv_key_pem_to_der
語法:der_priv_key, err = ssl.priv_key_perm_to_der(perm_priv_key)
上下文:任意
- 將 PEM 格式的 SSL 私鑰數據轉換為 DER 格式(用於 set_der_priv_key 函數)。
- 失敗,返回 nil 並通過字符串描述錯誤。
- 可選地,你可以使用 openssl 命令行工具脫機下執行 PEM 轉換為 DER,如下:
openssl rsa -in key.pem -outform DER -out key.der
- 該函數可以在任意上下文中調用。
3.5 set_der_priv_key
語法:ok, err = ssl.set_der_priv_key(der_priv_key)
上下文:ssl_certificate_by_lua*
- 為當前 SSL 連接設置 DER 格式的私鑰。
- 成功返回 true,失敗返回 nil 並通過字符串描述錯誤。
- 通常,私鑰是以 PEM 格式編碼的。可以使用 priv_key_perm_to_der 函數或者使用 openssl 命令行工具脫機將 PEM 轉換為 DER,如下:
openssl rsa -in key.pem -outform DER -out key.der
3.6 server_name
語法:name, err = ssl.server_name()
上下文:任意
- 返回由客戶端設置的 TLS SNI(Server Name Indication) 名。若客戶端沒有設置則返回 nil。
- 失敗,返回 nil 並通過字符串描述錯誤。
- 通常我們使用 SNI 名作為域名(如 www.openresty.org)來標識當前的 web 站點,同時為當該網站加載相應的 SSL 證書鏈和私鑰。
- 注意,並不是所有的 https 客戶端都設置 SNI 名,因此當從客戶端握手請求中缺失 SNI 名時,我們可以使用客戶端訪問的服務器 IP 地址來標識該網站。有關更多詳細信息,參閱 raw_server_addr 方法。
- 可以在下游 https 的任何上下文中調用該函數。
3.7 raw_server_addr
語法:addr_data, addr_type, err = ssl.raw_server_addr()
上下文:任意
- 返回當前 SSL 連接中客戶端實際訪問的原始服務器地址。
- 前兩個返回值分別表示地址數據和地址類型的字符串,根據地址類型值對地址值進行不同的解釋:
- unix: 地址數據是 UNIX 域套接字的文件路徑。
- inet:地址數據是 4 字節長的二進制 IPv4 地址。
- inet6:地址數據時 16 字節長的二進制 IPv6 地址。
- 失敗返回 nil,並通過字符串描述錯誤。
- 以下代碼顯示如何將 UNIX 域套接字和 IPv4 地址打印為人類可讀的字符串:
local ssl = require "ngx.ssl"
local byte = string.byte
local addr, addrtyp, err = ssl.raw_server_addr()
if not addr then
ngx.log(ngx.ERR, "failed to fetch raw server addr: ", err)
return
end
if addrtyp == "inet" then -- IPv4
ip = string.format("%d.%d.%d.%d", byte(addr, 1), byte(addr, 2),
byte(addr, 3), byte(addr, 4))
print("Using IPv4 address: ", ip)
elseif addrtyp == "unix" then -- UNIX
print("Using unix socket file ", addr)
else -- IPv6
-- leave as an exercise for the readers
end
- 可以在下游 https 的任何上下文中調用該函數。
3.8 raw_client_addr
語法:addr_data, addr_type, err = ssl.raw_client_addr()
上下文:任意