标签归档:虚拟主机

HTTPS和SNI

问题:有两个站点架在同一台服务器上,并且强制HTTPS访问,地址分别为https://sub.domain.com和https://admin.sub.domain.com,HTTPS证书签名的域名分别为*.domain.com和*.sub.domain.com,使用HttpClient(4.1.1版本)去调用一个接口:

1
2
3
4
5
6
7
public class TestGet {
  public static void main(String args[]) throws Exception {
    HttpGet httpGet = new HttpGet("https://sub.domain.com/api/nodes");
    HttpClient httpClient = new DefaultHttpClient();
    httpClient.execute(httpGet);
  }
}

返回 javax.net.ssl.SSLException异常:

Exception in thread "main" javax.net.ssl.SSLException: hostname in certificate didn't match:  != <*.sub.domain.com> OR <*.sub.domain.com>
    at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:231)
    at org.apache.http.conn.ssl.BrowserCompatHostnameVerifier.verify(BrowserCompatHostnameVerifier.java:54)
    at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:152)
    at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:133)
    at org.apache.http.conn.ssl.SSLSocketFactory.verifyHostname(SSLSocketFactory.java:559)
    at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:534)
    ……

问题分析:从异常信息来看,显然是SSL连接握手阶段出现证书不匹配的问题,客户端请求的是sub.domain.com域名,服务器返回了*.sub.domain.com的证书。但是浏览器访问又正常,这是为何?

解决方法:故事要从IPv4的设计开始说起,设计者认为32位够用了,后续的上层协议设计者也纷纷假设一个IP提供一个服务的前提,比如HTTP/1.0、SSL等,一个大坑就这样被埋下。从90年代中期开始,互联网发展太快,人们开始意识到IP资源越来越稀缺,于是着手设计IPv6,但是从IPv4到IPv6需要一个很长的过渡期,远远大于IPv4地址耗尽的时间。于是一些IP复用的方案出现了,CIDR和NAT技术能在一定程度上缓和IPv4资源稀缺的问题,互联网协议也适时进行了升级,比如HTTP,在HTTP/1.1版本中加入Host头,这个头部在虚拟主机(Virtual Host)中非常重要,它支持不同站点架在一个IP上,客户端通过Host头告知服务器需要访问的站点。但是Host头无法解决HTTPS场景中客户端与虚拟主机(多站点共享同一个IP)之间加密连接的建立,因为HTTPS依赖的SSL/TLS协议并没有同步跟进,SSL握手阶段客户端向服务器端发送的信息中未包含Host,所以服务器端也就没法返回正确的HTTPS证书了。这样的背景下,解决HTTPS虚拟主机的方法主要有:

1. 绑定不同的端口。为不同的虚拟主机绑定不同的HTTPS端口(默认端口443)。缺点显而易见,每次访问都需要显式指定端口号。
2. 绑定不同的IP。前面说过,IPv4资源越来越稀缺,而且这种方法也违背了虚拟主机的初衷。
3. 购买泛域名SSL证书(Wildcard Certificate)。
4. 购买多域名SSL证书(Multi Domain Certificate)。

这些方法都只是绕过限制,要根本解决问题还是得修改SSL/TLS协议,所以SNI(Server Name Indication)就适时被提了出来,它扩展了TLS协议(SSL 3.0不支持,在TLS 1.0以后支持,RFC4366RFC6606),在客户端请求的CLIENTHELLO阶段加入Host信息,告知服务器端要与哪个主机建立加密连接。
SNI

图片来源

实际上,SNI需要通信双方(服务端、客户端)都支持,根据Wiki上的总结,目前一些常见浏览器、服务器软件、类库的支持情况如下:

浏览器:

IE 7+
Mozilla Firefox 2.0+
Opera 8.0+
Google Chrome 5.0.342.1+
Safari 3.0+

注:这里的支持度还跟操作系统相关,比如Windows XP上所有IE均不支持,Chrome在不同操作系统下开始支持的版本也不同。

服务器软件:

Apache 2.2.12+
Nginx(依赖支持sni的openssl库)
Apache Traffic Server 3.2.0+
HAProxy 1.5+

类库:

OpenSSL: 0.9.8f(compiled in with config option '--enable-tlsext'), 0.9.8j+
libcurl / cURL 7.18.1+
Python 3.2
Qt 4.8
Oracle Java 7 JSSE
Apache HttpComponents 4.3.2
wget 1.14
Android 4.2 (Jellybean MR1)
Go (client and server)

Java是在JDK1.7里才支持SNI的,因此要在Java应用里使用,前提就是将OpenJDK或者Oracle JDK升到1.7,如果同时使用HttpClient进行HTTP接口调用,那么还必须将HttpClient版本升到4.3.2及以后,这个JIRA单描述了如何支持SNI(代码设计层面)的前因后果。

回到开头的异常问题,可以通过以下步骤修复:

1. 升级JDK至少到1.7。
2. 升级HttpClient至少到4.3.2。
3. 更新少量代码如下:

1
2
3
4
5
6
7
8
public class TestGet {
  public static void main(String args[]) throws Exception {
    HttpGet httpGet = new HttpGet("https://sub.domain.com/api/nodes");
    //HttpClient httpClient = new DefaultHttpClient();
    CloseableHttpClient httpClient = HttpClients.createDefault();
    httpClient.execute(httpGet);
  }
}

--EOF--

博客搬迁至DigitalOcean VPS

0x00: 起因

最近想把博客从国内的虚拟主机升级到VPS,原因有3点:
1. 虚拟主机空间有限,100M的空间大小(含数据库)捉襟见肘。
2. 灵活度太小。后期想新增几个博客,如果用VPS的话可以共用空间,降低成本。
3. 网络环境越来越差了,VPN已经成为必需品。

0x01: 选型

决定了购买国外的VPS以后,接下来的就是简单地评测工作。有两家待选:
1. Linode. 老牌VPS提供商,服务和售后服务稳定,其在东京的机房网络对国内用户非常友好。
2. DigtalOcean. VPS界的后起之秀,凭性价比取胜,它提供的VPS更接近云主机。对国内访问来说,有旧金山和新加坡两大机房可选,虽然物理上新加坡更近,但是种种资料表明旧金山的网络更加稳定,更有说法国内到新加坡的流量是经过旧金山的。

分别选取了两家最小规格VPS,Linode东京机房1024型(24G存储,1VCPU,2T流量),DigtalOcean旧金山机房和新加坡机房的512型(20G SSD存储,1VCPU, 1T流量)。用阿里测跑了下全网ping值,Linode稳定在80-100ms,DigtalOcean新加坡机房能保持在150ms以内,旧金山机房则在200-400ms不等。再在本地用ab跑了下Nginx静态页面测试,三个节点性能差不多,但是不知为何SSH到新加坡节点后操作异常卡顿,感觉其ping值名不副实,故首先排除DigtalOcean新加坡节点。

从测试情况来看,Linode VPS胜出,但是我却选择了DigtalOcean旧金山机房节点,因为它便宜了一半,5刀一个月,一年算下来360RMB。不是有句话说吗,选择恐惧症说到底还是因为穷。Linode给出的2TB流量对我这种个站来说完全多余,而且我相信DigtalOcean的SSD能在磁盘读写方面弥补一些网络延时造成的劣势。

0x02: 部署

1. 软件安装

部署采用广泛的LNMP架构(Linux+Nginx+MySQL+PHP),操作系统Debian7, 所有依赖软件安装用apt-get命令。

1
2
3
4
apt-get update
apt-get install nginx mysql-server-5.5
apt-get install php5 php5-mysql php5-cli php5-common
apt-get install php5-fpm php5-cgi

2. 数据导入

把原先博客的数据导出成sql文件,安装phpMyAdmin工具,phpMyAdmin是一个PHP编写的MySQL Web客户端管理软件,去官网下个最新版解压到Nginx vhost根目录下即可,安装成功后用http://ip/phpMyAdmin/index.php访问,根据提示导入sql数据。

3. 程序迁移
把原先博客的WordPress目录打包后解压至Nginx vhost根目录下,修改wp-config.php文件中的数据库用户名和密码,修改Nginx vhost配置后reload,理论上此时博客应该迁移成功了。Nginx的关键配置如下:

1
2
3
4
5
6
7
8
9
10
root /usr/share/nginx/www;
index index.php;
 
server_name fengchj.com *.fengchj.com localhost;
location ~ \.php$ {
  fastcgi_split_path_info ^(.+\.php)(/.+)$;
  fastcgi_pass unix:/var/run/php5-fpm.sock;
  fastcgi_index index.php;
  include fastcgi_params;
}

4. 修改DNS解析
去DNSPod上把域名的CNAME记录改成A记录,指到DigtalOcean VPS,保存后本地测试发现实时生效。

至此,博客迁移完毕。期间配置方面的问题可以查看Nginx日志(/var/log/nginx/access.log和error.log),比如Nginx worker进程跑在www-data用户下,而程序文件可能对其不可读写或执行时会报Permission Deny等等,这类问题通过Google很好解决。部署方面不想折腾的话可以使用LNMP一键安装包一键搞定必需软件的依赖和配置,不过我不喜欢这种定制化的方式,过程不可控。

0x03: 优化
1. 修改MySQL密码为强密码。混淆phpMyAdmin的访问路径,避免猜测攻击。
2. 安装七牛镜像存储WordPress插件。它支持博客静态文件CDN加速,免费的,绝对是WordPress必装插件之一。
3. 安装Disable Google Fonts插件。WordPress的Twenty Twelve主题在管理后台不知道什么原因依赖了Google的Open Sans字体,这在Google被禁的地方非常不友好,每次页面加载都被这个请求阻塞,安装这个插件后后台打开速度会提升不少。
4. 优化php-fpm和MySQL的内存使用策略,避免OOM Killer问题,参考『小内存VPS站点内存使用调优』一文。(2015-04-11更新)
以上就是本次博客迁移的全部过程,历时一天。按照惯例放出我的DigtalOcean邀请链接,点此连接注册可以获取10刀优惠,这可是相当于免费体验两个月的最小规格VPS了。

--EOF--