HTTPS 通配符证书申请及 Kubernetes 中的应用

1,587 阅读9分钟

前言

很久很久以前,互联网大陆是一个野蛮开放的世界,在这片大陆上,任何人都可以发布网站、交换信息。然而野蛮的生长既带来了繁荣,也带来了混乱。一大批仿冒者开始蠢蠢欲动,一套马甲就可以假冒一个网站,被发现了换个马甲继续行骗。所有的私密信息都以明文的方式传递,传递途中更不知道被多少人窃听。不明真相的群众被骗的血本无归,既欺骗了广大的群众,又侵害了本尊的权益。

随后,官方推出了一些列的举措整顿了互联网大陆。而在业界,一种加密的数字证书逐渐流行起来。这种证书不仅能认证网站所有者的身份,还可以起到传输数据加密、完整性验证、防篡改的功能。这就是 HTTPS 证书,一种简单却极其有效的解决方案。

而到了今天,HTTPS 证书已经成为了支付交易,第三方调用(例如微信小程序)等场景的必备条件,也是不少网站安全防护的最低标准。

然而,一般的证书签发机构申请一份 HTTPS 证书动辄需要一年几千几万,对于个人站长或小型企业来说也是一笔不小的费用。

直到有一天,一个神秘的组织横空出世 —— Let's Encrypt。这个组织的愿景是,让天下的网站都能用上证书,构建一个更安全的互联世界。这简直就是个人站长和中小型网站的福音啊!

在 Let's Encrypt 组织的不断努力下,迄今已有超过 1.8 亿个网站申请了免费的 HTTPS 证书.

虽然 Let's Encrypt 的免费证书已经推出了好多年,但是我仍旧发现不少网站还是没有证书,甚至是一些比较“知名”的网站也没有。比如说这位:

可能他们的架还没吵完。HTTPS 证书?安全?不存在的!公章都没了!╮(╯▽╰)╭

所以,我觉得还是有必要来唠唠这个证书的申请问题。顺便说下,当前流行的 Kubernetes 集群中的使用方法。

申请 HTTPS 通配符证书

在 Let's Encrypt 组织成立之初,证书只能一个域名一个域名申请,每个二级域名都需要重新申请新的证书。虽说证书的申请步骤可以做到完全自动化,不过一张全域名的通配符证书,对于较大型网站来说,还是非常必要的。

终于,在 2018 年 3 月 13 日 Let's Encrypt 提供了通配符证书功能。接下来,我们就来申请一下 Let's Encrypt 的通配符证书。

首先,我们来了解下 Let's Encrypt 申请证书的验证方式。

Let's Encrypt 提供了三种方式来验证域名。

  • HTTP-01 验证方式:需要能让 Let's Encrypt 服务器访问域名的 80 端口,将随机生成的 TOKEN 放置在验证域名的 http://<YOUR_DOMAIN>/.well-known/acme-challenge/<TOKEN> 路径下,验证域名的所有权。
  • TLS-ALPN-01 验证方式:需要能让 Let's Encrypt 服务器访问域名的 443 端口,它使用自定义的 ALPN 协议来确保只有知道此验证类型的服务器才会响应验证请求。这还允许对此质询类型的验证请求使用与要验证的域名匹配的 SNI 字段,从而使其更安全。
  • DNS-01 验证方式:需要在该域名下的 TXT 记录中放置特定值来证明域名的 DNS 系统,将随机生成的 TOKEN 放置在 _acme-challenge.<YOUR_DOMAIN> DNS 的 TXT 记录中,由 Let's Encrypt 服务器查询 DNS 记录确认。

而申请通配符证书,只支持第三种 DNS-01 验证方式。

Let’s Encrypt 组织还很贴心的为不少DNS 解析服务商提供了自动整合申请。不过,别找了,大部分都是国外的,国内似乎只有阿里云 DNS。如果你的 DNS 解析服务商不在列表里,没关系,因为我的也不在,那就来和我手动操作吧。

注意,Let’s Encrypt 申请的证书只有 90 天的有效期。

DNS 检查

首先,确保你的 DNS 解析服务器能被外网访问到。大部分正规的公有云 DNS 解析服务是没有问题的,如果是自建或者小众 DNS,那就各显神通吧。

通过 DNSViz 可以分析域名的 DNS 解析服务,使用非常简单,输入域名,等待几分钟即可。分析报告非常详细,还能帮助我们找出 DNS 解析设置的不少问题。下面是 DNS 解析服务器状态分析页面。

添加证书颁发机构授权 CAA

CAA 也是一种 DNS 记录,它允许站点所有者指定允许哪些证书颁发机构(CA)颁发包含其域名的证书。这主要是用于避免证书被意外的颁发,现已经作为了强制检查机制。

最常见的 CAA 错误就是 SERVFAIL,可能像下面这样。

DNS problem: SERVFAIL looking up CAA for www.example.com

主要是 DNS 解析服务的 CAA 认证失败或者为空或者是不支持。首先确认你的 DNS 解析服务商支持 CAA,目前主流的公有云服务商都是支持的。然后添加一条 CAA 记录即可。

由 Let’s Encrypt 签发证书的话,值必须是 0 issue "letsencrypt.org"

CAA 有优先级,CAA 可以指定二级域名,或者是父域名,默认找最匹配的域名,如果不存在,则找父域名。

安装 certbot

Certbot 是一个可以自动从 Let’s Encrypt 申请域名证书的客户端程序。它还提供多种插件,提供了多种系统和软件的自动化申请部署流程。

安装也非常简单,在官网上可根据操作系统和对应的软件选择安装文档。

例如,CentOS 系统的安装方式如下。

sudo yum install certbot

申请通配符证书请安装 1.0.0 及以上版本。

手动获取通配符证书

安装完成后,执行如下命令申请证书。(请替换下面 *.example.com 为你的域名)

certbot certonly --manual -d *.example.com --preferred-challenges dns --server https://acme-v02.api.letsencrypt.org/directory

为了实现通配符证书,Let’s Encrypt 对 ACME 协议的实现进行了升级,只有 v2 协议才能支持通配符证书。使用 --server 指定 API。

首先需要我们进行确认。

然后生成了随机 TOKEN,需要我们添加到 DNS 的 TXT 记录上。

打开 DNS 解析控制台,添加 TXT 记录,类似下面这样。

需要确保解析设置成功,可以通过如下命令来获取解析记录。

dig -t txt _acme-challenge.example.com @8.8.8.8

返回的解析记录值应该为之前给我们的 TOKEN。

_acme-challenge.example.com. 300 IN TXT "7q1qb...IAj4"

修改成功后返回之前的命令行,回车下一步。

稍等片刻后,输出如下内容就申请成功啦!

我们申请的证书就放在了输出文件的路径下了,主要是四个文件:

  • cert.pem:证书
  • chain.pem:证书链
  • fullchain.pem:完整的证书链,就是 cert.pem 和 chain.pem 的整合
  • privkey.pem:私钥

配置证书

申请完证书就轮到我们使用证书了。因为我们在 Kubernetes 集群中使用,可能有下面这几个地方需要配置。

CDN/对象存储

Kubernetes 集群托管了我们的服务,但是静态资源还是托管在 CDN 或者对象存储服务(例如 S3,OSS)等比较好。

一般我们使用的 CDN 或者对象存储服务都有域名和 HTTPS 配置,每家的控制台都不一样,不过大同小异。

主要的步骤一般是配置域名,开启 HTTPS,上传我们申请到的 fullchain.pem 完整证书链和 privkey.pem 私钥文件。配置完成后,一般等一会即可生效,可以通过我们配置的域名的 https 协议访问测试。

不过有一点需要注意,我们申请的私钥是 X.509 格式的,文件头是

-----BEGIN PRIVATE KEY-----

而许多服务商需要的是下面这样的

-----BEGIN RSA PRIVATE KEY-----

这是两种不同的格式规范,我们可以通过 openssl 命令来进行转换。

openssl rsa -in privkey.pem -out out.pem

Nginx

Nginx 可以用作 Kubernetes 集群的前置负载均衡器,或者作为 Ingress Controller。Nginx 配置证书非常简单,如果是单机且直接对外访问,可以直接通过 certbot 的 Nginx 插件自动配置(使用 --nginx 参数,参考文档)。复杂的场景下我们也可以手动配置,主要是在 Nginx Server 的配置块的下面几个配置项:

listen 443 ssl;  # 监听 443 端口的 server
ssl_certificate /etc/letsencrypt/live/<path-to>/fullchain.pem;  # fullchain 的证书
ssl_certificate_key /etc/letsencrypt/live/<path-to>/privkey.pem; # 私钥
include /etc/letsencrypt/options-ssl-nginx.conf; # 这是 Nginx SSL 配置
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # 这是密钥参数配置,用来增强安全性,不知道怎么配也可以不管,详解可以看 http://www.ruanyifeng.com/blog/2014/09/illustration-ssl.html

当然,如果要增加 HTTP 自动跳转 HTTPS,就增加一个 80 Server 的配置块。

server {
    if ($host = www.example.com) {
        return 301 https://$host$request_uri;
    } # 自动跳转

    server_name www.example.com;

    listen 80;  # 监听 HTTP 80 端口
    return 404;
}

Traefik

Traefik 是另一个 Kubernetes 上的 Ingress Controller,当然它不仅可用于 Kubernetes,是容器时代比较简单快速的开源边缘路由产品。

我比较喜欢它的是天生适合 Kubernetes,简单好用,还提供了 UI Dashboard。在 Kubernetes 集群中安装只需要用 helm chart 即可,配置使用 Ingress 或者 CRD,非常容器化。

Traefik 本身提供了 Let's Encrypt 的整合,可以自动申请和更新普通证书。配置也不复杂,这里简单讲一下,一是在 Traefik 容器的启动参数上加上 tls 的 resolver 配置。

args:
- ...
- "--entryPoints.websecure.address=:443"  # 443 端口的 entrypoint,如果使用 helm chart,已经配置好了
- --certificatesresolvers.default.acme.tlschallenge=true  # 配置 ACME challenge
- --certificatesresolvers.default.acme.email=name@example.com  # 配置邮箱
- --certificatesresolvers.default.acme.storage=/data/acme.json  # 配置存储文件,可做持久化

然后我们用 IngressRoute 来暴露服务,IngressRoute 是 Traefik 提供的 CRD,比 Ingress 提供了更强大的功能。

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: ingressroutetls
  namespace: default
spec:
  entryPoints:
    - websecure
  routes:
  - match: Host(`your.example.com`) && PathPrefix(`/tls`)
    kind: Rule
    services:
    - name: whoami
      port: 80
  tls:
    certResolver: default

主要是最后一行,指定我们使用的 resolver 是之前配置的 default,然后 Traefik 就可以帮我们自动申请和更新证书了。完整的例子可以参考这里

当然我们申请的是通配符证书,又该如何配置呢?

在 Traefik 2.2 版本提供了 tlsstore 的 CRD,可以帮助我们管理通配符证书。

首先,创建一个 Secret,保存我们的证书链和私钥信息。

apiVersion: v1
kind: Secret
metadata:
  name: domainsecret
stringData:
  tls.crt: |-
    -----BEGIN CERTIFICATE-----
    ...
    -----END CERTIFICATE-----
  tls.key: |-
    -----BEGIN PRIVATE KEY-----
    ...
    -----END PRIVATE KEY-----

tls.crt 是 fullchain.pem 的内容,而 tls.key 是 privkey.pem 的内容。

安装 Secret 后再创建一个 tlsstore 的对象,关联刚才创建的 Secret。

apiVersion: traefik.containo.us/v1alpha1
kind: TLSStore
metadata:
  name: default
  namespace: default
spec:
  defaultCertificate:
    secretName:  domainsecret

最后,在 IngressRoute 的 tls 中做如下配置即可。

tls:
  store:
    name: default

这样所有配置的域名都自动使用了通配符证书了,以后更新也只需要更新 Secret 即可。

参考