OpenResty中ssl_certificate_by_lua_block执行前如何获取请求User-Agent
核心结论
不存在能同时满足「早于ssl_certificate_by_lua_block执行」和「可读取User-Agent」的OpenResty指令,这是网络协议分层的物理限制,和API权限设计无关。
原因说明
- SSL握手流程发生在HTTP请求传输之前:TCP三次握手完成后,客户端首先发送SSL Client Hello报文启动TLS握手,
ssl_client_hello_by_lua_block、ssl_certificate_by_lua_block都运行在这个阶段。此时加密通道尚未建立,客户端根本不会发送HTTP请求报文,自然不存在User-Agent这类HTTP头字段。你调用ngx.req.get_headers()抛出API禁用的错误,本质是当前上下文根本没有可读取的HTTP请求数据,不是权限配置问题。 - SSL Client Hello报文本身仅携带TLS版本、支持的加密套件、SNI(访问域名)等TLS层字段,不包含任何HTTP协议相关内容,不可能从中解析出User-Agent。
rewrite_by_lua_block属于HTTP请求处理阶段的指令,必须等SSL握手完成、加密通道建立、客户端发送完HTTP请求头之后才会执行,执行时机必然晚于ssl_certificate_by_lua_block,无法满足证书生成前判断UA的需求。
可行替代方案
协议层面不可能在第一次SSL握手时拿到UA,只能通过分层逻辑实现接近需求的效果:
- 拆分拦截逻辑为两层:
- SSL握手阶段:对所有首次发起请求的客户端正常走动态证书签发流程
- HTTP请求处理最前置(用
rewrite_by_lua_block即可):第一时间读取User-Agent做判断,如果是需要拦截的请求,直接返回444状态码断开连接,同时将客户端IP写入lua共享内存的临时黑名单,设置合理的过期时间(比如10分钟) - SSL Client Hello阶段增加校验:每次握手时先检查客户端IP是否在共享内存黑名单里,如果在直接拒绝SSL握手,不再走证书签发流程
- 这种方案的效果是:恶意UA的第一次请求会走完证书流程,但不会得到任何业务响应,后续同IP的恶意请求会在SSL握手阶段被直接拦截,不会消耗证书签发资源,是当前协议限制下的最优解。
参考配置逻辑示例
lua_shared_dict ssl_blacklist 10m; server { listen 443 ssl; # SSL握手最先执行的阶段 ssl_client_hello_by_lua_block { local blacklist = ngx.shared.ssl_blacklist local client_ip = ngx.var.remote_addr -- 如果IP在黑名单直接拒绝握手 if blacklist:get(client_ip) == 1 then return ngx.exit(444) end local ssl_clt = require "ngx.ssl.clienthello" local host, err = ssl_clt.get_client_hello_server_name() ngx.log(ngx.ERR, "handshake host -- > ", host) } ssl_certificate_by_lua_block { -- 首次请求正常走证书签发逻辑 auto_ssl:ssl_certificate() } ssl_certificate /etc/ssl/resty-auto-ssl-fallback.crt; ssl_certificate_key /etc/ssl/resty-auto-ssl-fallback.key; # HTTP阶段最先执行,拿到UA做判断 rewrite_by_lua_block { local blacklist = ngx.shared.ssl_blacklist local user_agent = ngx.req.get_headers()["user-agent"] or "" ngx.log(ngx.ERR, "request user_agent -- > ", user_agent) -- 这里替换成你自己的UA判断规则,比如拦截特定扫描器 if string.find(string.lower(user_agent), "bad_scanner", 1, true) then local client_ip = ngx.var.remote_addr -- 把IP加入黑名单10分钟 blacklist:set(client_ip, 1, 600) return ngx.exit(444) end } location / { proxy_pass http://backend_proxy$request_uri; } }
注意:不存在任何方式能在首次SSL握手时获取HTTP层的User-Agent,所有声称能实现该能力的方案都违反TCP/SSL/HTTP协议栈的分层设计逻辑。
内容的提问来源于stack exchange,提问作者Nagri
相关产品推荐
相关产品推荐

