OpenResty 1.31.1.1 落地:precontent 阶段、按请求校验上游证书与 cosocket TLS
发布说明与 changelog 见 OpenResty 1.31.1.1 正式发布,指令细节以 lua-nginx-module README 为准。这一版把 nginx 内核从 1.29.2 升到 1.31.1,OpenSSL 3.5.5 → 3.5.6,lua-nginx-module 到 v0.10.31。对写配置的人影响最直接的,是下面这几处新增能力。
一、precontent_by_lua:内容处理前多插一段 Lua
lua-nginx-module v0.10.31 新增 precontent_by_lua*。阶段顺序变成:
rewrite_by_lua* → access_by_lua* → precontent_by_lua* → content(proxy_pass / 静态文件 / content_by_lua*)
几个指令的分工,可以按"此时请求走到哪一步"来理解:
rewrite_by_lua*:最早,适合改 URI、设变量、整条请求级别的重定向;access_by_lua*:访问控制,鉴权、IP 黑白名单,和 nginx 自带的allow/deny同阶段;precontent_by_lua*:新增,位于 access 之后、真正执行proxy_pass、静态文件或content_by_lua*之前,用来做鉴权收尾、限流扣减、上游路径改写这类"已经确定要进内容处理"的动作;content_by_lua*:内容处理器本身,负责产生响应体。
示例:
location /api/ {
access_by_lua_block {
-- 粗粒度拦截放在 access
if ngx.var.binary_remote_addr == "10.0.0.99" then
return ngx.exit(ngx.HTTP_FORBIDDEN)
end
}
precontent_by_lua_block {
local token = ngx.req.get_headers()["Authorization"]
if not token then
ngx.log(ngx.WARN, "missing auth, uri=", ngx.var.uri)
return ngx.exit(ngx.HTTP_UNAUTHORIZED)
end
local limit = ngx.shared.api_limit
local key = "rl:" .. ngx.var.binary_remote_addr
local n, err = limit:incr(key, 1, 0, 60)
if not n then
ngx.log(ngx.ERR, "limit incr failed: ", err)
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
if n > 100 then
return ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS)
end
-- 改写交给上游的路径
ngx.req.set_uri("/internal" .. ngx.var.uri, false)
}
proxy_pass http://app_backend;
}
注意点:
- 不要在这个阶段输出响应体。
ngx.say/ngx.print属于内容处理,放在这里和proxy_pass冲突;拒绝请求用ngx.exit(状态码),返回信息可以放 header 或用content_by_lua*承接。 ngx.exit与proxy_pass的配合:ngx.exit(ngx.HTTP_UNAUTHORIZED)之后请求即终止,后面的proxy_pass不会执行;想放行就正常返回 handler,不要调ngx.exit。ngx.exit(ngx.OK)在 precontent 阶段的语义按模块 README,别按 rewrite 阶段的习惯套。- 执行条件按模块 README 确认:哪些 location 上下文生效、内部重定向和 upstream 重试时各执行几次,上线前用
error_log ... debug打点确认,不要想当然。
二、proxy_ssl_verify_by_lua*:按请求决定上游证书校验
proxy_ssl_verify / proxy_ssl_verify_depth 是静态指令,一条 location 只能有一种设置。网关里如果有多个上游、其中一部分还在用自签证书,过去只能整条 location 关校验或者拆 location。新增的 proxy_ssl_verify_by_lua* 让校验开关和校验深度按请求决定。
示意配置:
location /upstream/ {
set $verify_upstream "on";
set $verify_depth 2;
proxy_ssl_verify $verify_upstream;
proxy_ssl_verify_depth $verify_depth;
proxy_ssl_trusted_certificate /etc/ssl/certs/internal-ca.pem;
proxy_ssl_verify_by_lua_block {
local host = ngx.var.upstream_host or ""
if host == "legacy.internal" then
ngx.var.verify_upstream = "off"
else
ngx.var.verify_upstream = "on"
ngx.var.verify_depth = 1
end
}
proxy_pass https://$upstream_host;
}
proxy_ssl_verify 是否接受变量、by_lua 块里设置的结果以何种方式映射到校验开关与深度,以模块 README 里的示例为准;上面这段只示意"在 handler 里按上游名分支"这个结构。生产上不建议对自签名上游直接 off,更稳的做法是给它单独挂一份 CA:
proxy_ssl_trusted_certificate /etc/ssl/certs/legacy-ca.pem;
proxy_ssl_verify_depth 1;
三、tcpsock:getsslsession 与 settrustedstore:可信 CA 下沉到单次握手
lua_ssl_trusted_certificate 是模块级全局配置,一个 worker 里所有 cosocket TLS 握手共用同一条信任链。v0.10.31 之后可以用 settrustedstore 给单次握手指定可信 CA,getsslsession 拿到握手后的 session 供后续连接复用。
local sock = ngx.socket.tcp()
sock:settimeout(3000)
-- 本次握手只信这份 CA,不依赖全局 lua_ssl_trusted_certificate
sock:settrustedstore("/etc/ssl/certs/internal-ca.pem")
local ok, err = sock:connect("10.0.0.21", 8443)
if not ok then
ngx.log(ngx.ERR, "connect failed: ", err)
return
end
local session, err = sock:getsslsession()
if session then
ngx.shared.tls_sessions:set("up:10.0.0.21", session, 300)
end
settrustedstore 要在握手前调用;getsslsession 在握手完成后取 session。session 如何回填到下一次握手(复用接口名与调用时机)、settrustedstore 与模块级配置的优先级,以模块文档为准。这套组合适合网关里同时对接公有 CA 上游和自签 mTLS 上游的场景:不用为了一个自签上游把全局信任链放宽。
四、keepintvl / keepcnt:TCP keepalive 按连接设置
changelog 里给 TCP 套接字新增了 keepintvl 和 keepcnt 两个选项,对应内核的 TCP_KEEPINTVL(探测间隔)和 TCP_KEEPCNT(探测次数)。以往这两个值只能在系统 sysctl 或 nginx 主配置层面统一,现在可以落到单条 cosocket 连接上。
指令名、参数位置(是建连参数还是 nginx 指令默认值)以 lua-nginx-module README 为准,不要照抄别的版本示例。验证时可以起一条长连接后用 ss 看内核定时器:
ss -tnoi '( dport = :6379 )'
输出里的 timer:(keepalive,...) 能反映探测间隔是否按预期生效。
五、升级前自查这几条路径
- 内核跨了两个 minor(1.29.2 → 1.31.1),第三方 C 模块要按新内核重编,动态模块同样。
- OpenSSL 3.5.6,涉及自编译或链接系统 OpenSSL 的部署要一起换。
- 修复集中在 QUIC 连接关闭、worker 关闭、SSL 缓存相关的崩溃。对上号的场景:开了 HTTP/3;worker 退出时还持有 cosocket 或定时器;用了
ssl_session_cache或在 Lua 里碰过 SSL session 复用。这几条路径上的服务,升级后先小流量跑一轮,重点看 worker 退出时有没有 crash log。 - lua-resty-core 这版是 v0.1.34rc2,生产按发布包里的实际版本走。
- LuaJIT 到 v2.1-20260415,新增
ffi.abi("dualnum");ARM64、s390x 和 FFI 有修复,用到 FFI 调用的模块建议回归一遍。 - 小改动顺手可用的:
ngx.header['WWW-Authenticate']支持 table 设多值;lua-resty-mysql v0.30 支持 ed25519;xss-nginx-module v0.07 支持动态模块构建。
六、验证方法
用 resty 单独跑 cosocket 逻辑,不用起整个 nginx:
resty -e '
local sock = ngx.socket.tcp()
sock:settimeout(2000)
sock:settrustedstore("/etc/ssl/certs/internal-ca.pem")
print(sock:connect("10.0.0.21", 8443))
'
用 openssl s_client 对照服务端证书链,确认自签 CA 该信谁:
openssl s_client -connect 10.0.0.21:8443 \
-servername backend.internal \
-showcerts -CAfile /etc/ssl/certs/internal-ca.pem
阶段执行和校验分支用 debug 日志确认:
error_log logs/error.log debug;
tail -f logs/error.log | grep -E 'precontent|ssl|verify'
手上这套配置改完之后,precontent_by_lua 里 ngx.exit 的分支、proxy_ssl_verify_by_lua* 按上游切换的分支,都要各打一条日志验证过一次再上量。