SNI是TLS协议扩展, 在握手的开始标识其尝试连接的主机名, 当多个HTTPS服务部署在同一IP地址上,客户端就可以通过这个标识指定它将使用哪一个服务, 同时服务端也无需使用相同的证书,它在概念上相当于HTTP/1.1基于名称的虚拟主机。SNI扩展最早在2003年的RFC 3546中出现。
HTTP服务通过 Http header “Host”, 来选择指定服务, HTTPS服务就是通过这个SNI来区分。通过可以通过nginx来验证一下
| |
http使用curl 通过后-H 增加header可以选择不同的主机
| |
https服务可以使用openssl sclient来验证
| |
可以看到servername传送的不同, 选择的ssl服务也不同。特殊的如果servername 是一个ip, 处理也是一样的,创
这里servername与证书中的common name设置为一样,如果要支持多个域名或ip可以通过SAN配置。
对应nginx中sni的判断 ngx_http_find_virtual_server
如果是https服务 被调用两次 针对servername的回调函数, ngx_http_ssl_servername, 第二次调用是ngx_http_set_virtual_server。
所以可以设置SNI来选择证书, 然后通过后Header来重新选择服务。
| |
在wiki SNI此词条里还讲到到安全部分, SNI是明文存放的, 所以虽然tls是加密的,其实还是查询出来访问的网站的名称。Encrypted Client Hello (ECH)则是为了解决这个问题。
SNI Passthrough
如果需要将客户端上送SNI传送到下游服务上,对应如下配置:
| |
也就是把客户端上送的SNI再发送下游服务器时再设置一下。
详细的可以参考这篇blog https://blog.martdj.nl/2023/11/09/nginx-as-reverse-proxy-and-sni/
作者描述主要问题是针对一个ip托管多个tls服务网站,未开启sni passthrough 无法通过SNI获取的server_name进行路由,无法区分是从哪个站点发起的。
SNI Routing
当然获取了SNI后,可以通过他进行选择下游服务器。通过map映射需要转发后端服务, 当然也可以根据需要设置默认使用的证书与key。
| |
可以参考下面的链接。https://gist.github.com/kekru/c09dbab5e78bf76402966b13fa72b9d2
nginx plus 可以配置lazy load, 配置更方便一些。
| |
https://www.infoq.com/news/2019/04/nginx-plus-release-18/
openresty 可以通过 ssl_certificate_by_lua_block 进行配置, 可以参考下面的链接。
https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl.md#raw_client_addr
参考及引用
https://jvns.ca/blog/2016/07/14/whats-sni/
https://en.wikipedia.org/wiki/Server_Name_Indication#Security_implications
图片from李華欽