kubernetes 配置 https 证书

上周在做主站从 http 迁移 https 的过程中发生了严重了事故,明明只设置了主域名生效 https 证书,但是最后问题是所有的子域名也都自动发起 https 请求,导致多个业务子域名无法正常访问。

先整理回忆一下整个迁移过程:

申请 https 证书

我们的域名是在阿里云上采购的,也托管在阿里云上,对比了几个渠道的 https 的证书,价格也差不多,索引就在阿里云 上采购了,购买证书找个最便宜的就可以了,免费证书只能针对单域名,我们买了一个通配符域名,通配符域名有个小小的小坑,域名只能到子域名级,不能再到下一级,如果确实有需求,那要继续采购子域名的通配符域名。

例如:.aliyun.com的通配符证书只能对a.aliyun.com、b.aliyun.com域名进行保护。如果存在a.b.aliyun.com的域名需要购买.b.aliyun.com的通配符证书

托管服务就是可以在证书过期之前,自动更新证书,目前我也不清楚是下发一个新的证书,还是给老的证书续费,可以暂时不购买,意义不是特别大,我们选择成本最低的形式。

还有一点 https 证书有效期都只有一年,如果有骗子说两年三年或者更多那骗术就毫无技术含量。

采购成功需要填写申请的域名,这里有个域名所有权的问题,现在流行且方便的方法是添加一条 TXT 的 DNS 解析,如果是阿里云上采购的域名这里都是一键操作的很方便,申请成功后一般五分钟就能拿到证书。我们选择下载适配 nginx 的证书,后面备用。

配置 ingress

前面已经下载到 https 证书,在正式配置 ingress 之前,我们需要将证书的内容配置到配置到 kubernetes 的 secret 里面,当然有很多方法,我们选择最简单的方式,kubectl create secret tls secret名字 --key key的路径 --cert pem路径

这里我的一个习惯是在给 secret 命名上,如果是单域名 example-com-ingress-secret,如果是多域名前面加上一个 x,后面 ingress 配置上需要用到这个自己命名的 key。

1
$ kubectl create secret tls xxxx-com-ingress-secret --key xxx.com.key   --cert xxx.com.pem

接下来就是配置 ingress 部分,我这里用的是 helm,直接修改 value 即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
ingress:
enabled: true
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: xxx.com
paths:
- /
tls:
- secretName: xxxx-com-ingress-secret
hosts:
- xxx.com

如果是直接使用 ingress,那么 spec 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
spec:
rules:
- host: xxx.com
http:
paths:
- backend:
serviceName: xxx-svc
servicePort: 80
path: /
tls:
- hosts:
- xxx.com
secretName: xxxx-com-ingress-secret

面临两个问题

所有 http 协议的请求都无法正常访问,会 308 重定向到 https 协议上

因为有很多项目都是跑在 http 协议上,直接重定向到 https 会导致很多 api 不可用,所以我们需要保留 http 和 https 协议并行的方式,然后逐步将 http 协议替换成 https

所有子域名也都被 307 重定向到 https 协议上

这里也想纠正一下域名的称呼,com 为一级域名,xxx.com 为二级域名,yyy.xxx.com 为xxx.com 的子域名,xxx.com 为 yyy.xxx.com 父域名。

其实这里是最奇怪的一块,阿里云的 dns 解析只配置了 @.xxx.com 解析到 ingress 关联的 slb 的 ip,其他的子域名我都没有处理,难道 yy.xxx.com 的 dns 解析会先经过 xxx.com 的解析,去查了一些资料,这是不可能的情况。

解决问题

http 和 https 协议并行

这里我联系了阿里云的技术,找到 nginx-ingress-controller 所关联的 configmap ,找到 key 为 nginx-configuration 的 configmap,添加两行 force-ssl-redirect: 'falsessl-redirect: 'false'

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
data:
proxy-body-size: 20m
force-ssl-redirect: 'false'
ssl-redirect: 'false'
kind: ConfigMap
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: ''
labels:
app: ingress-nginx
name: nginx-configuration
namespace: kube-system

子域名不跳转 https

image.png

image.png

重新打开 Chrome,仔细分析发起的请求,看到一个关键词 HSTS,顺藤摸瓜看到了一个新的安全协议。这套协议让全站 https 变为了更为严格的模式 。

为什么是 HSTS?

在浏览器请求域名的时候,是会发起一个 http 请求,服务端接受到 http 请求再重定向到 https 协议上,后面的请求都建立于 https 协议上所以是安全的,但是问题在于发起的第一次 http 请求可能会被劫持,如果第一次请求被劫持了后面的内容也可能就不是从主站发过来的了。

为此 IETF 规定,在第一次成功发起 https 请求之后会在 response header 中添加一行 strict-transport-security: max-age=15724800; includeSubDomains,这就表明接下来浏览器如果访问 http 请求的时候不再向服务端发起而直接在本地重定向到 https 上,max-age 表示这个本地重定向的有效期,过期之后仍然会向服务端发起请求。includeSubDomains 这也是本次出问题的主凶,表明接下来这个域名的子域名也同样服从父域名的规则,http://yyy.xxx.com 也会本地直接重定向到 https://yyy.xxx.com ,所以就是这个原因感觉影响到了子域名的解析。

这里有杠精肯定想说我要是一次没有访问过 xxx.com,就拿不到第一次成功访问 https 的header,所以访问一个特别陌生网站还是可能被攻击,对!所以 IETF 又提供了一招,在浏览器里面内置了一系列 https 的网站的名单,如果访问这些网址是不需要像服务器发起一个 http 请求的,我们也可以向 https://hstspreload.org/ 提供自己的域名,hstspreload 上面的域名最终会被 Chrome、火狐等浏览器内置到内核当中。

回头解决子域名不跳转 https

根据 ingress-nginx 官网提供的解决方案,可以添加 hsts-include-subdomains 字段,这样在第一次成功发起 https 请求之后会在 response header 中添加 strict-transport-security: max-age=15724800,没有 includeSubDomains,这样子域名就可以走自己的 dns 逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
data:
proxy-body-size: 20m
force-ssl-redirect: 'false'
ssl-redirect: 'false'
hsts-include-subdomains: 'false'
kind: ConfigMap
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: ''
labels:
app: ingress-nginx
name: nginx-configuration
namespace: kube-system

小结

  • 互联网领域是绝对的错综复杂,各种协议交互穿插,都是为了维护这个不安全不可信的网络
  • 个人觉得对于 ingress-nginx 这个扩展还是很多改进的地方,不可以在单路的一个地方配置 https 证书,而是把证书配置分散在各个 ingress 文件中,从而不好管理证书
  • HSTS 协议简洁且强大,当然这些也都需要浏览器的配合,一个先进的浏览器本身就带着安全的光芒。

参考文档

坚持原创技术分享,您的支持将鼓励我继续创作!