欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

nginx配置动态ssl域名转发

程序员文章站 2022-06-11 11:56:35
...

nginx配置动态ssl域名转发

应用场景

作为第三方站点服务供应商,我们需要每天对接大量的第三方域名及其ssl证书,如果我们为每个客户的域名配置一个server block的话,可能会造成大量的配置冗余,并面临批量更新的问题。实践中,最理想的对接方式是:客户只需要将他们的域名证书公私钥提供给第三方供应商,并将改域名解析到第三方站点服务供应商的网关外网ip即可完成站点对接服务。为了方便网关管理各种第三方域名对接,我们可以通过nginx的default_server来支持ssl域名的动态转发。

具体需求描述

有个客户域名为example.guest.com,解析到第三方供应商的网关外网ip,第三方网关根据客户域名,查询并设置该域名的公私钥,并代理到后端预先给客户定制(每个客户需要配置不同的主题色和logo)的站点域名test-123.domain.com。

网关配置

我方网关代理配置:

server {
    listen 443 default_server ssl;
    server_name _;

    ssl_certificate      ssl/domain.com.pem;
    ssl_certificate_key  ssl/domain.com.key;
    ssl_session_cache    shared:SSL:10m;
    ssl_session_timeout  10m;

    ssl_protocols        TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers          HIGH:!aNULL:!MD5:!kEDH;
    ssl_prefer_server_ciphers   on;

    ssl_certificate_by_lua_file 'conf/dynamic_ssl.lua';

    location / {            
        set $userdomain default;
        access_by_lua_file 'conf/cname.lua';
        proxy_pass http://$userdomain;
        proxy_set_header Host $userdomain;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

server {
    listen       8080;
    server_name  ~^(?<subdomain>test-\d+)\.domain\.com$;
    lua_check_client_abort      on;
    location / {
        root html;
    }
}

dynamic_ssl.lua的实现思路如下:

  1. 获取客户ssl server_name
  2. 通过server_name获取ssl证书的公私钥
  3. 更新并替换当前连接的ssl公私钥

代码示例:

local server_name = ngx_ssl.server_name()
if not server_name then
    return
end

ngx_ssl.clear_certs()
-- util.get_certificates函数返回ssl证书的公私钥,此自定义函数需自己实现
local der_cert_chain,der_priv_key = util.get_certificates(server_name)
if not der_cert_chain or not der_priv_key then
  return ngx.exit(403)
end
local ok1,err1 = ngx_ssl.set_der_cert(der_cert_chain)
if not ok1 then
    return ngx.exit(403)
end
local ok2,err3 = ngx_ssl.set_der_priv_key(der_priv_key)
if not ok2 then
    return ngx.exit(403)
end

cname.lua只需要设置$userdomain变量即可

-- util.get_cname通过$host获取我方配置的站点域名
local userdomain = util.get_cname(ngx.var.host)
if not userdomain then
	ngx.exit(404)
end
ngx.var.userdomain = userdomain
return

遇到的问题

如上dynamic_ssl.lua中,如果将ngx_ssl.clear_certs()放置到脚本的第一行

如果客户第三方想直接代理到我方的域名站点test.domain.com

server {
    listen 443 ssl;
    server_name test.domain.com;

    ssl_certificate      ssl/domain.com.pem;
    ssl_certificate_key  ssl/domain.com.key;
    ssl_session_cache    shared:SSL:10m;
    ssl_session_timeout  10m;

    ssl_protocols        TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers          HIGH:!aNULL:!MD5:!kEDH;
    ssl_prefer_server_ciphers   on;
    location / {
        root html;
        index index.html;
    }
 }

客户第三方代理配置如下:

server {
    listen 443 ssl;
    server_name example.guest.com;
    ssl_certificate ssl/guest.com.pem;
    ssl_certificate_key ssl/guest.com.key;
    ssl_session_cache    shared:SSL:10m;
    ssl_session_timeout  10m;

    ssl_protocols        TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers          HIGH:!aNULL:!MD5:!kEDH;
    ssl_prefer_server_ciphers   on;
    location / {
        proxy_pass https://test.domain.com;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header X-real-ip $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
    }
}

此时查看我方网关的error_log,会出现如下报错信息:
2019/12/10 01:00:58 [crit] 23950#23950: *59484960 SSL_do_handshake() failed (SSL: error:1417A179:SSL routines:tls_post_process_client_hello:cert cb error) while SSL handshaking, client: 192.168.0.1, server: 0.0.0.0:443

分析原因:
通过报错日志可以发现,ssl server_name变成了0.0.0.0,也就是说nginx网关将客户端proxy_pass过来的test.domain.com域名清掉了,重置成了0.0.0.0。
分析定位问题,参考openresty ssl_certificate_by_lua_file指令
原来ssl_certificate_by_lua_file的执行阶段为 right-before-SSL-handshake
也就是说在所有在443端口做ssl握手前都会去执行这段代码。