全栈迁移HTTPS/HTTP2总结系列文章
第一篇 - x.xx.com全站迁移HTTPS/HTTP2总结:
x.xx.com全站迁移HTTPS总结系列文章导航:
(一):投放端业务迁移介绍
(二):业务HTTP排查和梳理
(三):灰度策略和错漏检测
(四):CSP与HSTS安全策略
(五):HTTPS/HTTP2与性能提升
为了给广告主提供更安全可靠的广告系统,避免一些浏览器将系统表示为不安全, 消除广告投放端被中间人劫持插入广告, 保证广告主的账户安全.
此项工作主要处理了以下任务:
- 系统的http调用排查和梳理:我们梳理了约10个关联产品的约20多个相关域名, 并排查出存在MixContent页面50多个
- x.xx.com 域名下8个服务的HTTPS改造:域名收归为三个域名收归为 x.xx.com (HTML和CGI),cdn.com (JS资源)cdn.com (CSS样式资源)
- HTTP2相关特性测试和支持:开启HTTP2, HTTPS下页面资源加载性能提升40%
- 灰度放量机制和错漏排查机制:设计灰度放量机制和开发配套的监控系统确保没有遗漏。
- 部署相关安全协议: Upgrade-Insecure-Requests, HSTS, CSP等
整体迁移工作划分
x.xx.com整站迁移HTTPS工作分为如下三块内容:
- 业务侧及各个依赖模块HTTPS改造
- 服务层接入HTTPS(NGINX)
- HTTPS灰度上线
现在将这些工作中的一些经验和工作的技术分享如下
业务侧http资源排查和梳理
在推动x.xx.com业务代码兼容HTTPS时, 需要考虑的点非常多, 把工作上的一些要点记录了一下, 并梳理了几点方法论, 希望可以提供一些参考
灰度策略和错漏检测:
在推进HTTPS灰度的阶段, 由于投放端依赖的服务众多, 项目历史包袱较重, 即便是收归了域名之后, 在项目代码中 做了全部的资源url 替换, 有部分资源url是写的数据库中的, 或者是第三方服务加载的. 这部分请求是http的, HTTPS 网页中加载的 HTTP 资源被称之为 Mixed Content(混合内容). 这种场景首先考虑的时候使用 使用 Content-Security-Policy: upgrade-insecure-requests 升级不安全的请求。
但是面临的问题是: 我们不知道这些触发MixContent的资源是否支持https, 所以前期先部署了 MixContent上报策略.
Content-Security-Policy-Report-Only: block-all-mixed-content
通过近两周的每日MixContent上报统计, 我们排查出 x.xx.com 下面所有的 MixContent, 并逐个推进排查(排查的依赖域名20+, 存在MixContent页面50+)
通过两周的排查, 并确认所有的 http资源请求都支持 https之后 , 再部署了 upgrade-insecure-requests策略. 到达这个阶段就完整的实现了灰度的用户 地址栏的绿色协议头不会因为MixContent灰掉了.
上线upgrade-insecure-requests策略之后, https页面下http的请求会自动升级为https, 如果存在某些资源不支持https, 同时我们在MixContent排查阶段没有发现, 那么这个资源就会404, 我们在x.xx.com下所有的业务都引入异常上报, 对页面资源 404的进行上报, 经过一周的统计, 没有发现有集中的某个资源404, 初步可以确定当前投放端下所有的资源都是支持https, 也回归验证了MixContent排查的效果. 异常上报的接入可以参考: badjs
HSTS&CSP安全策略:
在网站全站HTTPS后,如果用户手动敲入网站的HTTP地址,或者从其它地方点击了网站的HTTP链接,通常依赖于服务端301/302跳转才能使用HTTPS服务。而第一次的HTTP请求就有可能被劫持,导致请求无法到达服务器,从而构成HTTPS降级劫持。
HSTS的作用是强制客户端(如浏览器)使用HTTPS与服务器创建连接。
当客户端通过HTTPS发出请求时,在服务器返回的超文本传输协议(HTTP)响应头中包含Strict-Transport-Security字段。
比如,https://x.xx.com/ 的响应头含有Strict-Transport-Security: max-age=31536000; 这意味着:
在接下来的一年中,浏览器向x.xx.com发送HTTP请求时,浏览器应当自动将 http 转写成 https.
HTTP2与性能提升:
从上面两个图可以看出, HTTP模式下, 资源的请求仍然遵循Chrome对同一源最大6个TCP连接的规则, 从timeline看出HTTP下多个tcp的stalled的时间(灰色部分)总和比较长, 呈现瀑布状
在HTTP2 下, 由于tcp连接的多路复用, 可以可以看出整个stalled的时间(灰色部分)大致为一个tcp的时间.
从整个耗时来算, HTTP2下加载20个js资源耗时110ms, HTTP下耗时 180ms, 我们在升级HTTPS/HTTP2之后, 在js css等静态资源的加载上反而比之前快了40%左右.
开发测试环境全环境HTTPS化
在部署https的同时, x.xx.com也部署了 HSTS策略(HTTP严格传输安全), 而我们在开发测试环境中会经常使用HTTP请求来开发调试, 目前已经在开发测试环境预发布环境全部部署了HTTPS证书, 为了跟外网环境保持高度一致, 建议在开发测试中使用https协议. 如果遇到需要抓包的场景, 建议配置代理的HTTPS抓包支持:
如果一定需要使用http访问, 可以chrome://net-internals/#hsts 删除对应的域名的 hsts缓存即可
第二篇 - 老业务HTTP排查和梳理
在推动x.xx.com老的业务代码兼容HTTPS时, 需要考虑的点非常多, 其中重要的包括如下几点:
- 不能简单的做全局替换: x.xx.com整个站点下依赖的各种服务约十多个, 各种资源和域名超过五十多个, 全局替换的问题在于, 如何确认这些依赖的域名都支持HTTPS了, 貌似没有比较好的办法, 只能全局搜索并一个个梳理出来一个个推动改造, 最好是有一个check list, 详细的记录各个服务或者资源被调用的地方, 目前是否支持https, 当前推进的进度, 相关的负责人, 截止的时间点等等, 定期去review各个模块的进度
并非所有的资源或者URL都在代码库中: 比如在广告投放端中, 有很多URL是写入到DB中的, 或者CGI层只是作为一个透传, 下面还依赖好几层更底层的服务, 而且这些URL调用方并非是只有投放端, 最开始的反应是在CGI层做一层替换, 例如获取qq信息等我们可以通过修改透传数据接口, 强行修改返回的url协议头, 但是更多接口对于返回的内容是不确定的, 贸然修改 成本高, 风险也高. 针对这种情况, 我们的处理方式是: 使用Upgrade-Insecure-Requests策略, 但是需要所有的资源的域名都支持https, 那么问题来了, 如何确认这些域名都支持https? 可以参考一篇文章看看我们是如何在灰度的流程中做minxContent上报错漏检测
并非所有的依赖方能及时支持https改造, 甚至有些老的接口已经处于不在维护的状况. 建议CGI层统一对这些不支持https的服务做一层封装转发, 系统尽量只依赖CGI的接口, 减少在前端做跨域跨系统的请求
一些底层的库 还有一些项目本身依赖旧的底层库, 里面有部分异步请求加载模块, 有部分异步加载模块的URL都是固定http协议头的, 如果短时间没有办法剔除前端代码对这些模块的依赖, 只能强行拉取代码进行升级兼容. 比如 底层的上报地址需要修改// 的兼容模式, 测速上报可以修改为 (location.protocol === ‘https:’ ? ‘//report.xxx.com/cgi-bin/r.cgi’ : ‘//report.xxx.com//cgi-bin/r.cgi’) + params.join(‘&’)
开发,测试环境, 预发布环境要先支持https, 最好的方式联系 运维提供相关的证书, 部署到开发环境和测试环境, 和外网保持高度一致. 推动各环境 部署https证书也是做https改造的一个重要准备工作之一, 建议在做完这块的工作之后, 把各个环境, 各个域名的证书的负责人落地实到wiki或者邮件中去
第三篇 - 灰度策略和错漏检测:
在”(一):投放端业务迁移介绍”文章中提到过,, https全站改造第三个的步骤就是灰度上线, 那么如何设置灰度的策略,并且真正利用灰度这个流程来推动https改造.
先看一下x.xx.com灰度的流程:
- 开始1%的灰度放量
- 上线MixContent上报策略, 以及相关的统计日报表
- 在灰度1%-20%期间, 通过MixContent统计的来逐步排查页面中遗漏的http资源
- 上线资源404上报机制, 用于排查因为某些静态资源站点不支持https导致的404错误
- HTTPS页面部署Upgrade-Insecure-Requests策略, HTTPS页面下http请求自动升级为 HTTPS
- 灰度到50%, 观察两周.
- 全量, http请求默认重定向到https请求, 并开启HSTS策略
这里有两个关键点:
- 灰度期间外网同时支持https 和 http, 方便用户在https有问题的时候直接切换为http的页面
- 在灰度的前期, 我们允许https页面MixContent的情况出现(地址栏协议头变灰)
在推进HTTPS灰度的阶段, 由于投放端依赖的服务众多, 项目历史包袱较重, 即便是收归了域名之后, 在项目代码中 做了全部的资源url 替换, 有部分资源url是写的数据库中的, 或者是第三方服务加载的. 这部分请求是http的, HTTPS 网页中加载的 HTTP 资源被称之为 Mixed Content(混合内容). 这种场景首先考虑的时候使用 使用 Content-Security-Policy: upgrade-insecure-requests 升级不安全的请求。
upgrade-insecure-requests
首先它是一个 CSP 指令。 W3C 工作组考虑到了我们升级 HTTPS 的艰难,在 2015 年 4 月份就出了一个 Upgrade Insecure Requests 的草案,他的作用就是让浏览器自动升级当前页面内容的http请求。 对于页面内的 图片 样式 js XHR 等请求生效, 但是对于a标签跳转不会生效. 同时,当自动升级https请求失败时, 不会自动降级到http, 资源就404或者网络错误了.
所以我们面临的问题是: 不知道这些触发MixContent的资源是否支持https, 所以前期先部署了 MixContent上报策略
MixContent上报策略:
Content-Security-Policy-Report-Only: block-all-mixed-content
Content-Security-Policy-Report-Only 可以让我们只上报违反CSP策略的情况, 而不会真正的拦截这些MixContent资源
block-all-mixed-content: 这条指令在当前页面为通过 HTTPS 协议加载的情况下禁止通过 HTTP 渠道加载任何资源。任何混合类型的资源请求都是被禁止的,包括混合活动内容和混合被动内容。这一条也适用于 iframe 中的文档,确保整体页面都不包含混合内容。
upgrade-insecure-requests 指令会在 block-all-mixed-content 之前执行;如果前者执行成功,后者就不再发挥任何作用。推荐的做法是设置二者之一,而不是全部
MixContent数据统计和查询
配置MixContent上报数据之后, 需要同步上线MixContent数据统计和查询
通过近两周的每日MixContent上报统计, 我们排查出 x.xx.com 下面所有的 MixContent, 并逐个推进排查(排查的依赖域名20+, 存在MixContent页面50+)
确认所有的 http资源请求都支持 https之后 , 再部署了 upgrade-insecure-requests策略. 到达这个阶段就完整的实现了灰度的用户 地址栏的绿色协议头不会因为MixContent灰掉了
资源加载失败上报
上线upgrade-insecure-requests策略之后, https页面下http的请求会自动升级为https, 如果存在某些资源不支持https, 同时我们在MixContent排查阶段没有发现, 那么这个资源就会404, 我们在x.xx.com下所有的业务都引入异常上报, 对页面资源 404的进行上报, 经过一周的统计, 没有发现有集中的某个资源404, 初步可以确定当前投放端下所有的资源都是支持https, 也回归验证了MixContent排查的效果. 异常上报的接入可以参考 badjs
第四篇 - CSP与HSTS安全策略
在全站升级HTTPS之后, 并不意味着站点就是安全的, 本文主要从 HTTPS重定向劫持 HSTS策略, CSP策略 这三个方面讲述投放端在web页面安全这块的建设
HTTPS 重定向劫持
- 重定向劫持劫持 攻击
中间人一旦发现有重定向到 HTTPS 网站的,拦下这个重定向,然后以 HTTPS 的方式,获取重定向后的内容,最后再以 HTTP 明文的方式,回复给用户。 - 重定向劫持之后一般会把页面中的https 请求替换为 http的, 不然第一次劫持了后面客户端依旧发起的是 https 的请求,无法劫持. 传统的方案是在中间人代理层替换 就是经典的中间人攻击工具 —— SSLStrip
- 新的攻击方案是在首次劫持返回的前端html 页面中插入代码, 劫持用户的https超链接, 降级为 http
- 页面被 ssl 劫持之后, 攻击者面对的主要问题是如果保证页面再次发起的请求是 http的,
页面批量超链接替换, 表单提交,window.open 弹窗,框架页面都是需要进行http降级处理的,否则就报跨域错误了.
- 页面被 ssl 劫持之后, 攻击者面对的主要问题是如果保证页面再次发起的请求是 http的,
分析了攻击方法, 下面是应对的方案:
通过代码跳转, 混淆替换代码明文的 https 超链接, 部署hsts
http://blog.jobbole.com/78590/?utm_source=blog.jobbole.com&utm_medium=relatedPosts
HTST:
HTTP Strict Transport Security (HSTS)
访问网站时,用户很少直接在地址栏输入https://,总是通过点击链接,或者3xx重定向,从HTTP页面进入HTTPS页面。攻击者完全可以在用户发出HTTP请求时,劫持并篡改该请求。
另一种情况是恶意网站使用自签名证书,冒充另一个网站,这时浏览器会给出警告,但是许多用户会忽略警告继续访问。
“HTTP严格传输安全”(简称 HSTS)的作用,就是强制浏览器只能发出HTTPS请求,并阻止用户接受不安全的证书。
它在网站的响应头里面,加入一个强制性声明。以下例子摘自维基百科。
Strict-Transport-Security: max-age=31536000; includeSubDomains
上面这段头信息有两个作用。
(1)在接下来的一年(即31536000秒)中,浏览器只要向example.com或其子域名发送HTTP请求时,必须采用HTTPS来发起连接。用户点击超链接或在地址栏输入http://www.example.com/,浏览器应当自动将http转写成https,然后直接向https://www.example.com/发送请求。
(2)在接下来的一年中,如果example.com服务器发送的证书无效,用户不能忽略浏览器警告,将无法继续访问该网站。
HSTS 很大程度上解决了 SSL 剥离攻击。只要浏览器曾经与服务器建立过一次安全连接,之后浏览器会强制使用HTTPS,即使链接被换成了HTTP。
该方法的主要不足是,用户首次访问网站发出HTTP请求时,是不受HSTS保护
https 安全问题不仅仅是后端只监听一个443端口就能解决的的事情, 更有前端的页面降级ssl 劫持, 重定向劫持等安全问题需要我们去关注
开发测试环境全环境HTTPS化
在部署https的同时, x.xx.com也部署了 HSTS策略(HTTP严格传输安全), 而我们在开发测试环境中会经常使用HTTP请求来开发调试, 目前已经在开发测试环境预发布环境全部部署了HTTPS证书, 为了跟外网环境保持高度一致, 建议在开发测试中使用https协议. 如果遇到需要抓包的场景, 建议配置代理的HTTPS抓包支持:
如果一定需要使用http访问, 可以chrome://net-internals/#hsts 删除对应的域名的 hsts缓存即可
CSP
在x.xx.com 全站迁移HTTPS过程中, 主要使用的CSP规则是 upgrade-insecure-requests 和 block-all-mixed-content, 作为在灰度升级阶段的一个辅助工具. 而在全站https之前, 我们是如何保证投放端的页面安全呢? 使用主要还是CSP规则, 同时配合投放端自研的监控上报系统进行数据展示(MixContent上报也是基于这个系统)
即使在升级HTTPS之后, 在页面安全这块依旧不能掉以轻心
第吴篇 - HTTPS/HTTP2与性能提升
升级后的性能对比
从上面两个图可以看出, HTTP模式下, 资源的请求仍然遵循Chrome对同一源最大6个TCP连接的规则, 从timeline看出HTTP下多个tcp的stalled的时间(灰色部分)总和比较长, 呈现瀑布状
在HTTP2 下, 由于tcp连接的多路复用, 可以可以看出整个stalled的时间(灰色部分)大致为一个tcp的时间.
从真的耗时来算, HTTP2下加载20个js资源耗时110ms, HTTP下耗时 180ms, 我们在升级HTTPS之后, 在js css等静态资源的加载上反而比之前快了40%左右.
后面继续探究性能提升背后的原理.
一个HTTPS请求为例,简单描述下接入转发流程:
用户发起HTTPS请求,首先到达网关入口。
网关入口将该请求转发给https网关入口。由于网关入口只是四层的转发,所以无法识别HTTPS请求内容。
https网关入口会对HTTPS请求进行卸载,也就是完成SSL握手及应用层内容的解密,将HTTPS转换成HTTP协议,再按照分流配置转发给业务侧RS。
业务侧收到HTTP请求后,生产响应内容并回复给https网关入口。
https网关入口将业务的响应内容进行加密,回复给网关入口。
网关入口再返回给用户。
了解过一个https请求的链路之后, 我们将从 TLS 和 HTTP/2 这两方方向分析性能提升背后的原理
TLS 优化
耗时:1
2curl -w "TCP handshake: %{time_connect}, SSL handshake: %{time_appconnect}\n" -so /dev/null https://x.xx.com
TCP handshake: 0.012235, SSL handshake: 0.044042
网关 的对其 tsl 握手的优化
网关针对HTTPS进行了全方位的优化。包括:
非对称加密—-异步代理计算
算法分离。
代理计算
异步执行。
网络协议栈全链路优化
TCP。包括拥塞窗口的调整,tcp fast open的支持,reuseport的支持。
SSL,分布式session cache, session ticket,False start, ocsp stapling file,动态record size等。
应用层。同时支持SPDY,HTTP2.
前后端优化
域名收归。通过页面资源及性能分析,确实域名收归方案,比如移动页面不超过3个
预建连接。https网关入口提供预连接页面,通过对热点页面的用户行为进行分析,提前建立连接,减少协议开销对用户体验的影响。
通过CDN及多地IDC就近完成HTTPS卸载。
session identifier
Session Identifier(会话标识符),是 TLS 握手中生成的 Session ID。服务端可以将 Session ID 协商后的信息存起来,浏览器也可以保存 Session ID,并在后续的 ClientHello 握手中带上它,如果服务端能找到与之匹配的信息,就可以完成一次快速握手。
Session Ticket
Session Identifier 机制有一些弊端,负载均衡中,多机之间往往没有同步 Session 信息,如果客户端两次请求没有落在同一台机器上就无法找到匹配的信息;2)服务端存储 Session ID 对应的信息不好控制失效时间,太短起不到作用,太长又占用服务端大量资源。
而 Session Ticket(会话记录单)可以解决这些问题,Session Ticket 是用只有服务端知道的安全密钥加密过的会话信息,最终保存在浏览器端。浏览器如果在 ClientHello 时带上了 Session Ticket,只要服务器能成功解密就可以完成快速握手。
部署的需要注意: 例如在 Nginx 中,就需要通过 ssl_session_ticket_key 参数让多台机器使用相同的 key 文件
HTTP/2
相比 HTTP/1.x,HTTP/2 在底层传输做了很大的改动和优化:
- HTTP/2 采用二进制格式传输数据,而非 HTTP/1.x 的文本格式。二进制格式在协议的解析和优化扩展上带来更多的优势和可能。
- HTTP/2 对消息头采用 HPACK 进行压缩传输,能够节省消息头占用的网络的流量。而 HTTP/1.x 每次请求,都会携带大量冗余头信息,浪费了很多带宽资源。头压缩能够很好的解决该问题。
- 多路复用,直白的说就是所有的请求都是通过一个 TCP 连接并发完成。HTTP/1.x 虽然通过 pipeline 也能并发请求,但是多个请求之间的响应会被阻塞的,所以 pipeline 至今也没有被普及应用,而 HTTP/2 做到了真正的并发请求。同时,流还支持优先级和流量控制。
- Server Push:服务端能够更快的把资源推送给客户端。例如服务端可以主动把 JS 和 CSS 文件推送给客户端,而不需要客户端解析 HTML 再发送这些请求。当客户端需要的时候,它已经在客户端了。
本次x.xx.com迁移HTTPS 并且升级到HTTP2主要使用到的特性是 TCP的多路复用, 在收归静态资源域名之后, 减少TCP握手环节的耗时, 也减少了TSL握手的耗时. 对静态资源的并行加载性能提升非常明显
文末的HTTP2.0总结文档中详细的对比了HTTP/2 与 HTTP1.X, 对HTTP2关键的四个特性做了原理上的分析.
另外关于传输层安全也有一篇翻译文档专门介绍 传输层安全性(Transport Layer Security,TLS)-译