Nginx支持SNI证书,已经ssl_server_name的使用

整理了一些网上的资料,这里记录一下,供大家参考

什么是SNI?

    传统的应用场景中,一台服务器对应一个IP地址,一个域名,使用一张包含了域名信息的证书。随着云计算技术的普及,在云中的虚拟机有了一个IP,对应多个域名,使用多张证书的应用场景,SNI技术应运而生。SNI(Server Name Indication),即实现了一个服务器使用多个域名证书的TLS扩展,支持用户配置多个域名证书。

SNI请求特点

    HTTP请求的Host字段在请求的Header中。发起HTTPS请求时,在TLS握手阶段,还无法进行HTTP数据的解析,此时TLS协议的Client Hello字段新增了一个Server Name字段,请求的客户端可以通过这个字段填充请求的Host信息,而服务端在TLS握手阶段就可以选择请求处理的证书,实现SNI的功能。

 各种工具对SNI的支持各种工具对SNI的支持icon-default.png?t=N7T8https://en.wikipedia.org/wiki/Server_Name_Indication

(1)主流的浏览器  可以理解为客户端工具

(2)curl和wget之类的命令行工具  可以理解为客户端工具

curl和nginx关于SNI一些细节

 已知的问题:curl和jdk'版本过低(客户端不支持SNI)','导致'ssl握手的'SNI'问题

 (3)库和编程语言 可以理解为客户端工具

Nginx支持SNI证书,已经ssl_server_name的使用_第1张图片

 (4)web服务器 可以理解为服务端

  • 默认SNI是'开启的'

Nginx支持的配置

    Nginx支持SNI,允许在同一个TLS服务端口下,配置不同的域名,用户通过请求不同的证书域名,可返回相应的upstream响应结果。

    本示例配置了一个证书域名为“lwl.test.com”的单向认证代理服务,一个证书域名为“lwl.default.com”的双向认证代理服务,使用相同的443端口,具体配置如下:

# Settings for a TLS enabled server.
    upstream lwl.test.com {
        server 192.168.58.196;
    }

    upstream default {
        server 192.168.58.195;
    }

  server {
        listen       443 ssl;
        listen       [::]:443 ssl;
        server_name  lwl.test.com;
        root         /usr/share/nginx/html;

        ssl_certificate /etc/nginx/ssl_sni/server/lwl.test.com/server.crt;
        ssl_certificate_key /etc/nginx/ssl_sni/server/lwl.test.com/server.key;

        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout  10m;
        ssl_ciphers HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers on;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Cookie $http_cookie;
            proxy_pass http://lwl.test.com;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }

   server {
        listen       443 ssl;
        listen       [::]:443 ssl;
        server_name  lwl.default.com;
        root         /usr/share/nginx/html;

        ssl_certificate /etc/nginx/ssl_sni/server/lwl.default.com/server.crt;
        ssl_certificate_key /etc/nginx/ssl_sni/server/lwl.default.com/server.key;
        ssl_client_certificate /etc/nginx/ssl_sni/private/ca.crt;
        ssl_verify_client on;

        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout  10m;
        ssl_ciphers HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers on;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Cookie $http_cookie;
            proxy_pass http://default;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }

单向认证测试

    基于以上配置,使用域名为“lwl.test.com”的证书进行单向认证的HTTPS访问,请求如下:
# curl https://lwl.test.com:443 --cacert /etc/nginx/ssl_sni/private/ca.crt

返回的响应为:


同时对当前的HTTPS请求进行抓包,可以看到,请求TLS握手阶段Client Hello请求中,包含了Server Name为lwl.test.com的字段:

Nginx支持SNI证书,已经ssl_server_name的使用_第2张图片

双向认证测试

    基于以上配置,使用域名为“lwl.default.com”的证书进行双向认证的HTTPS访问,请求如下:   

# curl --cert /etc/nginx/ssl_sni/users/client.crt:123456 --key /etc/nginx/ssl_sni/users/client.key 'https://lwl.default.com:443' --cacert /etc/nginx/ssl_sni/private/ca.crt

返回的响应为:

同时对当前的HTTPS请求进行抓包,可以看到,请求TLS握手阶段Client Hello请求中,包含了Server Name为lwl.default.com的字段:  

Nginx支持SNI证书,已经ssl_server_name的使用_第3张图片

至此验证了Nginx可同时支持多个TLS证书的功能。

多证书配置

    为支持多个SNI证书,一种Nginx配置文件形式为,每个证书配置单独一个server段,此处以两个单向认证证书,一个双向认证证书为例,配置如下:

# Settings for a TLS enabled server.
    upstream lwl.test.com {
        server 192.168.58.196;
    }

    upstream lwl.test1.com {
        server 192.168.58.194:8081;
    }

    upstream default {
        server 192.168.58.195;
    }

  server {
        listen       443 ssl;
        listen       [::]:443 ssl;
        server_name  lwl.test.com;
        root         /usr/share/nginx/html;

        ssl_certificate /etc/nginx/ssl_sni/server/lwl.test.com/server.crt;
        ssl_certificate_key /etc/nginx/ssl_sni/server/lwl.test.com/server.key;

        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout  10m;
        ssl_ciphers HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers on;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Cookie $http_cookie;
            proxy_pass http://lwl.test.com;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }

   server {
        listen       443 ssl;
        listen       [::]:443 ssl;
        server_name  lwl.test1.com;
        root         /usr/share/nginx/html;

        ssl_certificate /etc/nginx/ssl_sni/server/lwl.test1.com/server.crt;
        ssl_certificate_key /etc/nginx/ssl_sni/server/lwl.test1.com/server.key;

        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout  10m;
        ssl_ciphers HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers on;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Cookie $http_cookie;
            proxy_pass http://lwl.test1.com;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }

   server {
        listen       443 ssl;
        listen       [::]:443 ssl;
        server_name  lwl.default.com;
        root         /usr/share/nginx/html;

        ssl_certificate /etc/nginx/ssl_sni/server/lwl.default.com/server.crt;
        ssl_certificate_key /etc/nginx/ssl_sni/server/lwl.default.com/server.key;
        ssl_client_certificate /etc/nginx/ssl_sni/private/ca.crt;
        ssl_verify_client on;

        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout  10m;
        ssl_ciphers HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers on;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Cookie $http_cookie;
            proxy_pass http://default;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
可以看到,随着加载证书个数的增加,Nginx配置篇幅增长较大,不利于配置维护。

优化的配置方式

    启用了sni的nginx,如下变量会被赋值

$ssl_server_name

默认SNI是'开启的'

检查'nginx server端'是否支持SNI扩展协议:'nginx -V'

      在1.7.0版本开始,Nginx支持通过$ssl_server_name 变量获取TLS中的Server Name,我们可以据此结合map映射指令,提升配置的重用度,一种优化后的配置方式如下:

例子1:

# Settings for a TLS enabled server.
    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout  10m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    map $ssl_server_name $ssl_string {
        lwl.test.com lwl.test.com;
        lwl.test1.com lwl.test1.com;
        default lwl.default.com;
    }

    upstream lwl.test.com {
        server 192.168.58.196;
    }

    upstream lwl.test1.com {
        server 192.168.58.194:8081;
    }

    upstream lwl.default.com {
        server 192.168.58.195;
    }

  server {
        listen       443 ssl;
        listen       [::]:443 ssl;
        server_name  $ssl_string;
        root         /usr/share/nginx/html;

        ssl_certificate /etc/nginx/ssl_sni/server/$ssl_string/server.crt;
        ssl_certificate_key /etc/nginx/ssl_sni/server/$ssl_string/server.key;

        include /etc/nginx/default.d/*.conf;

        location / {
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Cookie $http_cookie;
            proxy_pass http://$ssl_string;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }

例子2:

stream {
       upstream test {
               server 127.0.0.1:50001;
       }

       map $ssl_server_name $sni_string {
               test1.www.local test1;
               test2.www.local test2;
               test3.www.local test3;
               default test1;
       }

       map $ssl_server_name $sni_string445 {
               test1.www.local test4451;
               test2.www.local test4452;
               test3.www.local test4453;
               default test4451;
       }


       server {
               listen 444 ssl;
               ssl_certificate /data/sni/sni_${sni_string}.cer;
               ssl_certificate_key /data/sni/sni_${sni_string}.key;
               proxy_pass test;
       }
       server {
               listen 445 ssl;
               ssl_certificate /data/sni445/sni_${sni_string445}.cer;
               ssl_certificate_key /data/sni445/sni_${sni_string445}.key;
               proxy_pass test;
       }

}

注意,如果希望map命令支持host的最长匹配与正则,需要再添加hostnames关键字。

Module ngx_http_map_module

map命令的本质,可以理解为通过旧的变量定义出了一个新的变量。

 模拟tcp客户端的方法:

openssl s_client -connect t9:5000 -CAfile ~/Keys/https/root/root.cer -servername test2.www.local

[openssl][nginx] 使用openssl模拟ssl/tls客户端测试nginx stream

模拟http客户端的方法:

curl --cacert ~/Keys/https/root/root.cer -vvvv  https://test1.tls.local/
curl --cacert ~/Keys/https/root/root.cer -vvvv  https://test2.tls.local/
curl --cacert ~/Keys/https/root/root.cer -vvvv  https://test3.tls.local/
curl --cacert ~/Keys/https/root/root.cer -vvvv  https://test3.www.local/

结束语

    本文通过结合Nginx的 $ssl_server_name 变量与map指令,提供了一种支持多个SNI证书配置的实现方式,提高了配置维护的重用度。


参考

https://www.nginx.org.cn/article/detail/12416
https://www.cnblogs.com/hugetong/p/11727275.htmlhttps://blog.csdn.net/wzj_110/article/details/110149984

你可能感兴趣的:(Nginx,数据库)