Openresty SSL 配置

发布时间: 更新时间: 总字数:1222 阅读时间:3m 作者: IP上海 分享 网址

OpenResty 如何配置 HTTPs/SSL 证书介绍。

SSL

  • SSL/TLS 原理详解
  • OpenResty 使用 SSL 证书配置,ssl_certificate/ssl_certificate_key 加载静态证书如下:
server {
  // 监听端口
  listen 443 ssl http2;
  // 证书,必须是 PEM 格式
  ssl_certificate  ssl/demo.crt;
  // 私钥,必须是 PEM 格式
  ssl_certificate_key  ssl/demo.key;
  // 回话超时时间为 10 分钟
  ssl_session_timeout  10m;
  // 优先使用服务器加密算法
  ssl_prefer_server_ciphers  on;
}

ngx.ssl

ngx.ssl 库提供了专门处理 HTTPs 功能的接口。SSL操作比较耗时,建议使用 lrucache 共共享内存缓存起来,SSL 相关的内置变量

  • $ssl_cipher
  • $scheme
-- 显示加载
local ssl = require "ngx.ssl"

-- 获取版本号,数字格式
local ver, err = ssl.get_tls1_version()
ngx.say(string.format("0x%x", ver))

-- 低于 TLS1.0 则拒绝连接
if ver < ssl.TLS1_VERSION then
  ngx.exit(400)
end

-- 获取字符格式版本号
local ver, err = ssl.get_tls1_version_str()
ngx.say(string.format("ver: ", ver))

-- 获取 SNI 主机名,即访问服务的域名
local name, err = ssl.server_name()
ngx.say("sni: ", name)

-- 服务器地址
-- addr_type inet: IPv4;inet6: IPv6;unix: Unix Domain Socket
addr_data, addr_type, err = ssl.raw_server_addr()

-- IPv4/IPv6 地址是二进制数字,解析如下
local byte = string.byte
string.format("%d.%d.%d.%d", byte(addr_data, 1), byte(addr_data, 2), byte(addr_data, 3), byte(addr_data, 4))

-- 客户端地址
addr_data, addr_type, err = ssl.raw_client_addr()

动态加载证书示例

  • 加载动态证书,也需要配置 ssl_certificate/ssl_certificate_key(Nginx 内部机制决定)
server {
  // 监听端口
  listen 443 ssl http2;
  // 占位用,无实际意义
  ssl_certificate  ssl/demo.crt;
  // 占位用,无实际意义
  ssl_certificate_key  ssl/demo.key;

  // 回话超时时间为 10 分钟
  ssl_session_timeout  10m;
  // 优先使用服务器加密算法
  ssl_prefer_server_ciphers  on;

  // 动态加载证书
  ssl_certificate_by_lua_file  cert.lua;
  ...
}
  • 通过 ssl_certificate_by_lua 加载证书,在 server 中和 ssl_certificate/ssl_certificate_key 是同一个级别的,cert.lua 示例:
-- 显示加载
local ssl = require "ngx.ssl"

-- 解析 PEM 格式证书并设置
cert, err = ssl.parse_pem_cert(pem_cert)
ok, err = ssl.set_cert(cert)

-- 证书缓存
certs = {}
local name, err = ssl.server_name()
if not name then
  ngx.exit(ngx.ERROR)
end

if not certs[name]; then
  ngx.exit(ngx.ERROR)
end

-- 解析 cert
local cert, err = ssl.parse_pem_cert(certs[name].cert)
if not cert then
  ngx.say("failed: ", err)
  ngx.exit(ngx.ERROR)
end

-- 设置证书
local ok, err = ssl.set_cert(cert)
if not ok then
  ngx.say("failed: ", err)
  ngx.exit(ngx.ERROR)
end

-- 设置私钥
local priv_key, err = ssl.parse_pem_priv_key(pem_priv_key)
if not key then
  ngx.say("failed: ", err)
  ngx.exit(ngx.ERROR)
end

local ok, err = ssl.set_priv_key(key)
if not ok then
  ngx.say("failed: ", err)
  ngx.exit(ngx.ERROR)
end

ocsp

  • server 中使用 ssl_stapling on 开启 OCSP Stapling 功能,ngx.ocsp 库提供相关操作
  • 查询证书状态步骤
    • 将证书转化为 DER 格式 ssl.cert_pem_to_der
    • 获取 OCSP 服务器的 URL ocsp.get_ocsp_responder_from_der_chain
    • 生成 OCSP 请求体 ocsp.create_ocsp_request
    • 通过 HTTP 协议发送请求,获取响应
    • 检查响应 oscp.validate_ocsp_response
local ocsp = require "ngx.ocsp"
local ssl = require "ngx.ssl"
local http = require "resty.http"

local cert, err = ssl.parse_pem_cert(pem_cert)

-- 证书格式转化为 DER 格式
local der_cert, err = ssl.cert_pem_to_der(cert)

-- 获取 OCSP 服务器的 URL
local ocsp_url, err = ocsp.get_ocsp_responder_from_der_chain(der_cert)

-- 生成 OCSP 请求体
local ocsp_req, err = ocsp.create_ocsp_request(der_cert)

- 通过 HTTP 协议发送请求,获取响应
local httpc = http.new()
local res, err = httpc:request_uri(ocsp_url, {
  method = "POST",
  body = ocsp_req,
  headers = {
    ["Content-Type"] = "application/ocsp-request",
  }
})
local ocsp_resp = res.body

- 检查响应
if ocsp_resp and #ocsp_resp > 0 then
  local ok, err = ocsp.validate_ocsp_response(ocsp_resp, der_cert)
  if not ok then
    ngx.say("fail to validate: ", err)
    ngx.exit(ngx.ERROR)
  end
end

-- 可以将 ocsp_resp 缓存起来,提高效率
-- 通知客户端,必须在 ssl_certificate_by_lua 阶段
local ok, err = ocsp.set_ocsp_status_resp(ocsp_resp)
if not ok then
  ngx.say("fail to set: ", err)
  ngx.exit(ngx.ERROR)
end

SSL 会话复用

会话复用(Session Resumption) SSL 完成握手后,将通信使用的密钥存起来,下次直接使用。有两种实现方式:

  • Session ID Nginx 的 ssl_session_cache 支持,仅支持本机复用
    • 通过 ssl_session_fetch_by_luassl_session_store_by_lua 可以将会话存储到 Redis 等外部服务器,实现多实例复用
    • ssl_session_fetch_by_lua 发生在 ssl_certificate_by_lua 之前,获取历史会话信息
    • ssl_session_store_by_lua 发生在 ssl_certificate_by_lua 之后,Preread 之前,用来存储回话信息
    • 通过 ngx.ssl.session 库实现,由于 Session ID 存在缺陷,TLS1.2 中被 Session Tickets 取代
  • Session Tickets 会将服务器加密后的会话信息发送给客户端,有客户端负责保存(Ticket),再次 TLS 通信时,客户端只需要将 Ticket 发送给服务器验证即可
server {
  listen 443 ssl;

  ssl_session_tickets on;  // 启用 session Tickets
  ssl_session_ticket_key  ssl/ticket.key;  // 加密 ticket 使用的密钥文件,ngx.ssl.session.ticket.key_rotation 可以利用 memcached 使用 ticket 的定期轮换 rotation
}
Home Archives Categories Tags Statistics
本文总阅读量 次 本站总访问量 次 本站总访客数