搭建 sniproxy

sniproxy 源码在 https://github.com/dlundquist/sniproxy,它的作用是:

Proxies incoming HTTP and TLS connections based on the hostname contained in the initial request of the TCP session.

 

安装:

rpm -ivh http://mirror.zhoufengjie.cn/centos/el6/x86_64/RPMS/tyumenmirror-1.0-1.el6.noarch.rpm

yum -y install sniproxy

如果使用源码编译,最要把 udns 编译进去,否则如果配置 .* *:443 类似规则的时候会报:Only socket address backends are permitted when compiled without libudns

 

修改配置文件 /usr/local/sniproxy/etc/sniproxy.conf:

user daemon
pidfile /var/run/sniproxy.pid

error_log {
  syslog daemon
  priority notice
}

listen 443 {
  protocol tls
  table https_hosts

  access_log {
    filename /var/log/sniproxy.log
  }
}

table https_hosts {
  .* *:443
}

listen 80 {
  protocol http
  table http_hosts

  access_log {
    filename /var/log/sniproxy.log
  }
}

table http_hosts {
  .* *:80
}

table {
  .* 127.0.0.1
}

启动:

/usr/local/sniproxy/sbin/sniproxy -c /usr/local/sniproxy/etc/sniproxy.conf

 

然后修改 /etc/hosts 测试:

52.221.229.x play.google.com
52.221.229.x www.baidu.com

# curl -I “https://play.google.com/store/apps/details?hl=en&id=tr.com.fugo.kelimeavi2.en”
HTTP/1.1 200 OK

# curl -I http://www.baidu.com
HTTP/1.1 200 OK

都是 OK 的。

 

修改 hosts 很麻烦,可以使用  dnsmasq 来管理你的解析,在 dnsmasq 上把你需要的域名修改成你的 sniproxy,配合 dnscrypt,防止 DNS 被污染。详情请看:

https://www.logcg.com/archives/981.html

https://gist.github.com/tawateer/fff8798407693d74b80d44e46806cc82

 

python requests 信任自签名证书

我在一台 Nginx 上搭建 https 服务,证书使用自签名的证书,然后使用 Python 访问 https 服务。

 

解决方法1:

#!/bin/env python

import requests

url = "https://52.77.252.184/test.txt"

ret = requests.get(url, verify="/tmp/ssl/requests_test.crt")
print ret.status_code

通过 verify 指定证书,表示相信此证书(requests_test.crt 是服务器端证书);也可以用 verify=False,表示不验证服务器端的证书。

 

解决方法2:

设置环境变量 REQUESTS_CA_BUNDLE:
export REQUESTS_CA_BUNDLE=/tmp/ssl/requests_test.crt

然后使用 request 访问。

#!/bin/env python

import requests

url = "https://52.77.252.184/test.txt"

ret = requests.get(url)
print ret.status_code

遇到的一个用于 LVS 检查的 Nginx ssl 配置的大坑

参照 我之前遇到的  ,这次的坑 更大了一点,导致贵司最重要的数个域名挂了 6 min,真是令人伤心啊。

看下面的用于 LVS 检查的配置:

server {
    listen 80 default_server;
    listen 443 ssl default_server;

    # Do not delete it, otherwise lvs http check will fail, it’s terrible!!!
    location /http_check/index.html {
        root /home/work/nginx/conf/;
        open_file_cache off;
    }

    location /nginx_status {
        stub_status on;
        access_log off;
        allow 10.0.0.0/8;
        deny all;
    }
}

 

坑的主角是 linsten 443 后面的 ssl,如果不加 ssl ,而且 在 Nginx 其他的配置里 没有 linsten 443 ssl 的话,那么 LVS 检查就会失败,报 400,而且 Nginx 日志显示类似的乱码:

10.0.19.34 – – [05/Feb/2015:00:01:05 +0800] “\x16\x03\x01\x01\x02\x01\x00\x00\xFE\x03\x03T\xD2BA\xC2\x03G\xCB\xF1M\xCE\xD5\
xB5\xF0 \xB8e\x0F\x1Cwh\x06%,\xDB\xFD\xD7\x0C\xE9\xD8\xBB\xCA\x00\x00\x94\xC00\xC0,\xC0(\xC0$\xC0\x14\xC0” 400 588 “-” “-”

 

今天我「走运了」,修改配置 恰巧引发的了 LVS SSL 检查的 400 ,根据 上面的链接中的解释,如果 LVS 检查某个 VIP 的后端的某个端口全部失败时,会删除 这个 VIP ,这个 VIP 上面的其他端口 也就没了。所以今天才导致数个重要域名无法访问,真是罪过啊 。

 

Nginx 单IP下 配置多个server https 的问题

很早之前就遇到类似的问题,今天写下来以防忘记。


我们的负载均衡架构是这样的:

请求 —> 网络 —> LVS集群 —> Nginx 集群 —> APP

LVS使用FULLNAT模式,每台Nginx 机器只有一个IP(内网IP),LVS也是把流量转到这个IP。如果Nginx想对多个域名使用 https,比如两个域名 wandoujia.com 和 wandoulabs.com ,是可能有问题的。


看下面的配置(两个server写在不同文件中,用 include * 加载):

server {
    listen 80;
    listen 443 ssl;
    server_name test.wandoujia.com;
    ssl_certificate wandoujia.crt;
    ssl_certificate_key wandoujia.key;
    ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers HIGH:!aNULL:!MD5;
    …
}

server {
    listen 80;
    listen 443 ssl;
    server_name test.wandoulabs.com;
    ssl_certificate wandoulabs.crt;
    ssl_certificate_key wandoulabs.key;
    ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers HIGH:!aNULL:!MD5;
    …
}

这样你访问 https://test.wandoujia.com 或者 https://test.wandoulabs.com,都可能报证书有问题,这是为什么。

事实上,SSL运行在TCP之上(SSL/TLS协议),ssl通过四次握手和服务器(这里是Nginx,LVS纯转发,可忽略)的IP + PORT(443)建立ssl连接,建立连接之后浏览器才会发送HTTP请求。所以在Nginx收到HTTP请求之后才知道Host,才知道转到哪个server 去处理,所以在SSL连接建立的时候Nginx是不知道用哪个 Server 的SSL配置的,在这种情况下,Nginx会使用它加载到的第一个SSL配置(需验证)。当然如果你在listen 443的后面加上 default_server,Nginx就会使用此SSL配置,即:

listen 443 default_server ssl;

那么对于单域名的https,我更喜欢把SSL配置写在http配置里,在server只需要加上 listen 443 ssl;,类似:

http {
    …
    ssl_certificate wandoujia.crt;
    ssl_certificate_key wandoujia.key;
    #ssl_certificate wandoulabs.crt;
    #ssl_certificate_key wandoulabs.key;
    ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers HIGH:!aNULL:!MD5;
    …
}

server {
    listen 80;
    listen 443 ssl;
    server_name test.wandoujia.com;
    …
}

那么怎么才能实现多域名的 https 呢,是有办法的,叫做 TLS Server Name Indication extension(SNI, RFC 6066),它允许浏览器在SSL握手的时候发送请求的server name,也就是 Host,这样 Nginx 就能找到对应server 的SSL配置。

但是要浏览器支持SNI 才可以,支持SNI的浏览器有:

Opera 8.0;
MSIE 7.0 (but only on Windows Vista or higher);
Firefox 2.0 and other browsers using Mozilla Platform rv:1.8.1;
Safari 3.2.1 (Windows version supports SNI on Vista or higher);
and Chrome (Windows version supports SNI on Vista or higher, too).

同样,在服务器端 openssl 要支持SNI,编译的时候加上–enable-tlsext 即可,不过从 0.9.8j 版本开始编译的时候默认会加。openssl 支持了SNI,Nginx 才可以work,确认是否OK:

$ ./nginx -V
…
TLS SNI support enabled
…

所以,如果想要实现多域名https,要确认你的Nginx 支持 TLS SNI support,然后在server 单独配置SSL, 但是要注意,某些浏览器会有问题,而且因为Nginx加载server 的顺序不同(特别是每个server在同一目录的不同文件中,然后在nginx.conf主配置文件中用 include * 包含),可能会出现奇怪的问题,我是遇到过的。