当前位置: 代码迷 >> 综合 >> OpenResty 动态SNI
  详细解决方案

OpenResty 动态SNI

热度:98   发布时间:2024-02-09 21:37:50.0

应用场景

SNI主要解决一台服务器只能使用一个证书(一个域名)的缺点,随着服务器对虚拟主机的支持,一个服务器上可以为多个域名提供服务,因此SNI必须得到支持才能满足需求。

解决方案

OpenResty 基于 Nginx 对 HTTPS 提供了非常好的支持,但要求 OpenSSL 的版本不能低于 1.0.2e。

Nginx配置

在 OpenResty 里搭建  HTTPS 服务需要使用三个核心指令,指定服务器的监听端口、证书和秘钥:

指令 描述
listen 监听端口,必须使用附加参数 ssl 启用 HTTPS
ssl_certificate 证书,必须是 PEM 格式 (占位作用)
ssl_certificate_key 私钥,必须是 PEM 格式 (占位作用)
ssl_prefer_server_ciphers 优化加密算法
ssl_session_timeout 会话超时时间

server {listen 8070;listen 8443 ssl;ssl_certificate       /data/egwnode/certs/product/openresty_ca/egwcrt.pem;ssl_certificate_key   /data/egwnode/certs/product/openresty_ca/egwkey.pem;ssl_certificate_by_lua_block{local sni = require("sni").new()sni:main()}location / {rewrite_by_lua_block {ngx.log(ngx.ERR, 'rewrite_by_lua_block')}access_by_lua_block {ngx.log(ngx.ERR, 'access_by_lua_block')}proxy_set_header  Connection      "";proxy_set_header  X-Real-IP       $remote_addr;proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;proxy_pass http://10.25.78.146:7777;}
}

主要代码

-- DateTime: 2020-05-19 13:50
-- Author:   tianxiaoyong@baosight.com
-- Desc:     Dynamic Sni
--local SNI = {__version=0.1}local ssl      = require("ngx.ssl")
local ocsp     = require("ngx.ocsp")
local http     = require("resty.http")local ngx      = ngx
local stringx  = stringx
local io_open  = io.openlocal conf = Base.conf
local tool = Base.tool
local cost = Base.cost
local json = Base.jsonlocal dao_cache = Base.dao_cache
local patches   = Base.patchesfunction SNI.new()local cls = {certificates_path = conf.certificates_path,certificates_mkey = cost.RetKeys.RET_KEY_CRTS,certificates_ocsp = cost.RetKeys.RET_KEY_OCSP,certificate_types = patches.table.init({'PEM', 'DER'}),http_timeout = 10000,}return setmetatable(cls, {__index = SNI})
endfunction SNI:_get_certificate_struct(crt, key, ctype)if not crt or not key thenngx.log(ngx.ERR, 'Any params not exists!')return endif not ctype thenctype = 'PEM'elsectype = stringx.strip(ctype)endif not self.certificate_types[ctype] thenngx.log(ngx.ERR, "Bad paramter of `ctype`!")returnendreturn {crt   = crt,key   = key,ctype = ctype}
endfunction SNI:get_certs(hostname)-- hostname.crtif hostname thenhostname = stringx.strip(hostname)elsengx.log(ngx.ERR, 'Paramter `hostname` not exists!')returnend-- 步骤一: 从 mlcache 中取出存储的证书数据块local res_mlcache_get, err = dao_cache:get(self.certificates_mkey)if not res_mlcache_get thenngx.log(ngx.ERR, err)returnend-- 步骤二: 根据从请求中取到的SNI,从证书块中取出对应的证书信息local certificate_struct = res_mlcache_get[hostname]-- 步骤三: 如果不存在则从本地读取证书if not certificate_struct thenlocal res_io_crt = io_open(string.format('%s/%s.crt', self.certificates_path, stringx.replace(hostname, ".", "-")))local res_io_key = io_open(string.format('%s/%s.key', self.certificates_path, stringx.replace(hostname, ".", "-")))if not res_io_crt or not res_io_key thenngx.log(ngx.ERR, string.format('The certificates of %s not found!', hostname))returnelselocal res_crt, res_key = res_io_crt:read('*all'), res_io_key:read('*all')-- 将读取到的证书信息,打成结构一致的结构体certificate_struct = self:_get_certificate_struct(res_crt, res_key)if not certificate_struct thenngx.log(ngx.ERR, "Failed to generate certificate struct!")return ngx.exit(ngx.HTTP_BAD_REQUEST)endres_io_crt:close()res_io_key:close()res_mlcache_get[hostname] = certificate_struct-- 将结构体存储到 mlcache中local res_mlcache_set, err = dao_cache:set(self.certificates_mkey, res_mlcache_get)if not res_mlcache_set thenngx.log(ngx.ERR, err)return ngx.exit(ngx.HTTP_BAD_REQUEST)endendendreturn certificate_struct 
endfunction SNI:verify_ocsp(hostname, cert_der)-- hostname: client SNI-- cert_der: der编码的证书if not hostname or not cert_der thenngx.log(ngx.ERR, 'Paramter `hostname` or `cert_der` not exists!')returnend-- 从mlcache中取出 ocsp 数据块local certificates_ocsp_get, err  = dao_cache:get(self.certificates_ocsp)if not certificates_ocsp_get thenngx.log(ngx.ERR, 'Failed to get `certificates_ocsp`! ', err)returnend-- 根据提供的hostname从数据块中取出对应的ocsp bodylocal hostname_ocsp = certificates_ocsp_get[hostname]local is_success = trueif not hostname_ocsp then-- 获取 OCSP 服务器的 URLis_success = falselocal ocsp_url, err = ocsp.get_ocsp_responder_from_der_chain(cert_der)if not ocsp_url thenngx.log(ngx.ERR, err)returnend-- 生成 OCSP 请求体local ocsp_req, err = ocsp.create_ocsp_request(cert_der)if not ocsp_req thenngx.log(ngx.ERR, err)returnendlocal httpc = http.new()httpc:set_timeout(self.http_timeout)local res, err = httpc:request_uri(ocsp_url, {method  = 'POST',body    = ocsp_req,headers = {['Content-Type'] = 'application/ocsp-request'}})if not res thenngx.log(ngx.ERR, 'Failed to verify this certificate!', err)returnendif res.status ~= 200 thenngx.log(ngx.ERR, 'OCSP responder returns bad HTTP status code: ',  res_status)returnendhostname_ocsp = res.bodyendif hostname_ocsp and #hostname_ocsp thenlocal ok, err = ocsp.validate_ocsp_response(hostname_ocsp, cert_der)if not ok thenngx.log(ngx.ERR, "Failed to validate ocsp response: ", err)returnend-- 设置当前ssl连接的 ocsp staplinglocal ok, err = ocsp.set_ocsp_status_resp(hostname_ocsp)if not ok thenngx.log(ngx.ERR, "Failed to set ocsp status response: ", err)returnendelsengx.log(ngx.ERR, "invalid ocsp body! ")returnendif not is_success thencertificates_ocsp_get[hostname] = res_bodylocal certificates_ocsp_set, err = dao_cache:set(self.certificates_ocsp, certificates_ocsp_get)if not certificates_ocsp_set thenngx.log(ngx.ERR, err)returnendis_success = trueendreturn is_success
endfunction SNI:main()-- TODO: 如果获取到的SNI是nil, 说明客户端没有设置SNI, 这时候需要--       从`raw_server_addr`函数中获取客户端的IP地址,进而通过dns--       逆向解析出SNI.local hostname, err = ssl.server_name()ngx.log(ngx.ERR, 'HostName: ', hostname)if not hostname thenngx.log(ngx.ERR, err)return ngx.exit(ngx.HTTP_BAD_REQUEST)endlocal ok, err = ssl.clear_certs()if not ok thenngx.log(ngx.ERR, 'Failed to clear the local certificates!', err)return ngx.exit(ngx.HTTP_BAD_REQUEST)endlocal certificate_struct = self:get_certs(hostname)-- ngx.log(ngx.INFO, json.encode(certificate_struct))if not certificate_struct thenngx.log(ngx.ERR, "Failed to get certificates!")return ngx.exit(ngx.HTTP_BAD_REQUEST)endlocal crt_der, key_derif certificate_struct.ctype == 'PEM' thencrt_der, err = ssl.cert_pem_to_der(certificate_struct.crt)if not crt_der thenngx.log(ngx.ERR, err)returnendkey_der, err = ssl.priv_key_pem_to_der(certificate_struct.key)if not key_der thenngx.log(ngx.ERR, err)returnendelsecrt_der = certificate_struct.crtkey_der = certificate_struct.keyendlocal set_crt, err = ssl.set_der_cert(crt_der)if not set_crt thenngx.log(ngx.ERR, 'Failed to set der cert to current request!', err)returnendlocal set_key, err = ssl.set_der_priv_key(key_der)if not set_key thenngx.log(ngx.ERR, 'Failed to set der pkey to current request!', err)return end
endreturn SNI

服务测试

$ curl https://127.0.0.1:8443 -k