Kubernetes 中的证书管理工具 - cert-manager

我在之前的文章写给开发人员的实用密码学(八)—— 数字证书与 TLS 协议 中,介绍了如何使用 openssl 生成与管理各种用途的数字证书,也简单介绍了如何通过 certbot 等工具与 ACME 证书申请与管理协议,进行数字证书的申请与自动更新(autorenew)。

这篇文章要介绍的 cert-manager,跟 certbot 这类工具有点类似,区别在于它是工作在 Kubernetes 中的。

cert-manager 是一个证书的自动化管理工具,用于在 Kubernetes 集群中自动化地颁发与管理各种来源、各种用途的数字证书。它将确保证书有效,并在合适的时间自动更新证书。

多的就不说了,证书相关的内容请参见我的写给开发人员的实用密码学(八)—— 数字证书与 TLS 协议 或者其他资料,现在直接进入正题。

注:cert-manager 的管理对象是「证书」,如果你仅需要使用非对称加密的公私钥对进行 JWT 签名、数据加解密,可以考虑直接使用secrets 管理工具 Vault.

https://cert-manager.io/docs/installation/helm/

官方提供了多种部署方式,使用 helm3 安装的方法如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 添加 cert-manager 的 helm 仓库
helm repo add jetstack https://charts.jetstack.io
helm repo update
# 查看版本号
helm search repo jetstack/cert-manager -l | head
# 下载并解压 chart,目的是方便 gitops 版本管理
helm pull jetstack/cert-manager --untar --version 1.8.2
helm install \
  cert-manager ./cert-manager \
  --namespace cert-manager \
  --create-namespace \
  # 下面这个参数会导致使用 helm 卸载的时候,会删除所有 CRDs,可能导致所有 CRDs 资源全部丢失!要格外注意
  --set installCRDs=true

cert-manager 支持多种 issuer,你甚至可以通过它的标准 API 创建自己的 Issuer。

但是总的来说不外乎三种:

  • 由权威 CA 机构签名的「公网受信任证书」: 这类证书会被浏览器、小程序等第三方应用/服务商信任
  • 本地签名证书: 即由本地 CA 证书签名的数字证书
  • 自签名证书: 即使用证书的私钥为证书自己签名

下面介绍下如何申请公网证书以及本地签名证书。

通过权威机构创建的公网受信证书,可以直接应用在边界网关上,用于给公网用户提供 TLS 加密访问服务,比如各种 HTTPS 站点、API。这是需求最广的一类数字证书服务。

cert-manager 支持两种申请公网受信证书的方式:

这里主要介绍使用 ACMEv2 协议申请公网证书,支持使用此开放协议申请证书的权威机构有:

  • 免费服务
    • Let’s Encrypt: 众所周知,它提供三个月有效期的免费证书。
    • ZeroSSL: 貌似也是一个比较有名的 SSL 证书服务
      • 通过 ACME 协议支持不限数量的 90 天证书,也支持多域名证书与泛域名证书。
      • 它提供了一个额外的 Dashboard 查看与管理所有申请的证书,这是比较方便的地方。
  • 付费服务

这里也顺便介绍下收费证书服务对证书的分级,以及该如何选用:

  • Domain Validated(DV)证书
    • 仅验证域名所有权,验证步骤最少,价格最低,仅需要数分钟即可签发。
    • 优点就是易于签发,很适合做自动化。
    • 各云厂商(AWS/GCP/Cloudflare,以及 Vercel/Github 的站点服务)给自家服务提供的免费证书都是 DV 证书,Let’s Encrypt 的证书也是这个类型。
      • 很明显这些证书的签发都非常方便,而且仅验证域名所有权。
      • 但是 AWS/GCP/Cloudflare/Vercel/Github 提供的 DV 证书都仅能在它们的云服务上使用,不提供私钥导出功能!
  • Organization Validated (OV) 证书
    • 是企业 SSL 证书的首选,通过企业认证确保企业 SSL 证书的真实性。
    • 除域名所有权外,CA 机构还会审核组织及企业的真实性,包括注册状况、联系方式、恶意软件等内容。
    • 如果要做合规化,可能至少也得用 OV 这个级别的证书。
  • Extended Validation(EV)证书
    • 最严格的认证方式,CA 机构会深度审核组织及企业各方面的信息。
    • 被认为适合用于大型企业、金融机构等组织或企业。
    • 而且仅支持签发单域名、多域名证书,不支持签发泛域名证书,安全性杠杠的。

ACME 支持 HTTP01 跟 DNS01 两种域名验证方式,其中 DNS01 是最简便的方法。

下面分别演示如何使用 AWS Route53 跟 AliDNS,通过 DNS 验证方式申请一个 Let’s Encrypt 证书。 (其他 DNS 提供商的配置方式请直接看官方文档)

非 AWS Route53 用户可忽略这一节

https://cert-manager.io/docs/configuration/acme/dns01/route53/

这里介绍一种不需要创建 ACCESS_KEY_ID/ACCESS_SECRET,直接使用 AWS EKS 官方的免密认证的方法。会更复杂一点,但是更安全可维护。

首先需要为 EKS 集群创建 OIDC provider,参见aws-iam-and-kubernetes, 这里不再赘述。

cert-manager 需要查询与更新 Route53 记录的权限,因此需要使用如下配置创建一个 IAM Policy, 可以命名为 <ClusterName>CertManagerRoute53Access(注意替换掉 <ClusterName>):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "route53:GetChange",
      "Resource": "arn:aws:route53:::change/*"
    },
    {
      "Effect": "Allow",
      "Action": ["route53:ChangeResourceRecordSets", "route53:ListResourceRecordSets"],
      "Resource": "arn:aws:route53:::hostedzone/*"
    },
    {
      "Effect": "Allow",
      "Action": "route53:ListHostedZonesByName",
      "Resource": "*"
    }
  ]
}

比如使用 awscli 创建此 policy:

1
2
3
aws iam create-policy \
  --policy-name XxxCertManagerRoute53Access \
  --policy-document file://cert-manager-route53-access.json

然后通过上述配置创建一个 IAM Role 并自动给 cert-manager 所在的 EKS 集群添加信任关系:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
export CLUSTER_NAME="xxx"
export AWS_ACCOUNT_ID="112233445566"

# 使用 eksctl 自动创建对应的 role 并添加信任关系
# 需要先安装好 eksctl
eksctl create iamserviceaccount \
  --cluster "${CLUSTER_NAME}" --name cert-manager --namespace cert-manager \
  --role-name "${CLUSTER_NAME}-cert-manager-route53-role" \
  --attach-policy-arn "arn:aws:iam::${AWS_ACCOUNT_ID}:policy/<ClusterName>CertManagerRoute53Access" \
  --role-only \
  --approve

之后需要为 cert-manager 的 ServiceAccount 添加注解来绑定上面刚创建好的 IAM Role,首先创建如下 helm values 文件 cert-manager-values.yaml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 如果把这个改成 false,也会导致 cert-manager 的所有 CRDs 及相关资源被删除!
installCRDs: true

serviceAccount:
  annotations:
    # 注意修改这里的 ${AWS_ACCOUNT_ID} 以及 ${CLUSTER_NAME}
    eks.amazonaws.com/role-arn: >-
      arn:aws:iam::${AWS_ACCOUNT_ID}:role/${CLUSTER_NAME}-cert-manager-route53-role      

securityContext:
  enabled: true
  # 根据官方文档,还得修改下这个,允许 cert-manager 读取 ServiceAccount Token,从而获得授权
  fsGroup: 1001

然后重新部署 cert-manager:

1
helm upgrade -i cert-manager ./cert-manager -n cert-manager -f cert-manager-values.yaml

这样就完成了授权。

在 xxx 名字空间创建一个 Iusser:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: letsencrypt-prod-aws
  namespace: xxx
spec:
  acme:
    # 用于接受域名过期提醒的邮件地址
    email: user@example.com
    # ACME 服务器,比如 let's encrypt、Digicert 等
    # let's encrypt 的测试 URL,可用于测试配置正确性
    # server: https://acme-staging-v02.api.letsencrypt.org/directory
    # let's encrypt 的正式 URL,有速率限制
    server: https://acme-v02.api.letsencrypt.org/directory

    # 用于存放 ACME 账号私钥的 Secret 名称,Issuer 创建时会自动生成此 secret
    privateKeySecretRef:
      name: letsencrypt-prod-aws

    # DNS 验证设置
    solvers:
      - selector:
          # 在有多个 solvers 的情况下,会根据每个 solvers 的 selector 来确定优先级,选择其中合适的 solver 来处理证书申请事件
          # 以 dnsZones 为例,越长的 Zone 优先级就越高
          # 比如在为 www.sys.example.com 申请证书时,sys.example.com 的优先级就比 example.com 更高
          dnsZones:
            - "example.com"
        dns01:
          # 使用 route53 进行验证
          route53:
            region: us-east-1
            # cert-manager 已经通过 ServiceAccount 绑定了 IAM Role
            # 这里不需要补充额外的 IAM 授权相关信息!

https://cert-manager.io/docs/configuration/acme/dns01/#webhook

cert-manager 官方并未提供 alidns 相关的支持,而是提供了一种基于 WebHook 的拓展机制。社区有第三方创建了对 alidns 的支持插件:

下面我们使用此插件演示下如何创建一个证书签发者。

首先需要在阿里云上创建一个子账号,名字可以使用 alidns-acme,给它授权 DNS 修改权限,然后为该账号生成 ACCESS_KEY_ID/ACCESS_SECRET。

完成后,使用如下命令将 key/secret 内容创建为 secret 供后续步骤使用:

1
2
3
4
# 注意替换如下命令中的 <xxx> 为你的 key/secret
kubectl -n cert-manager create secret generic alidns-secrets \
  --from-literal="access-token=<your-access-key-id>" \
  --from-literal="secret-key=<your-access-secret-key>"

接下来需要部署cert-manager-alidns-webhook 这个 cert-manager 插件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 添加 helm 仓库
helm repo add cert-manager-alidns-webhook https://devmachine-fr.github.io/cert-manager-alidns-webhook
helm repo update

# 安装插件
## 其中的 groupName 是一个全局唯一的标识符,用于标识创建此 webhook 的组织,建议使用公司域名
## groupName 必须与后面创建的 Issuer 中的 groupName 一致,否则证书将无法通过验证!
helm -n cert-manager install alidns-webhook \
  cert-manager-alidns-webhook/alidns-webhook \
  --set groupName=example.com

在 xxx 名字空间创建一个 Iusser:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: letsencrypt-prod-alidns
  namespace: xxx
spec:
  acme:
    # 用于接受域名过期提醒的邮件地址
    email: user@example.com
    # ACME 服务器,比如 let's encrypt、Digicert 等
    # let's encrypt 的测试 URL,可用于测试配置正确性
    # server: https://acme-staging-v02.api.letsencrypt.org/directory
    # let's encrypt 的正式 URL,有速率限制
    server: https://acme-v02.api.letsencrypt.org/directory

    # 用于存放 ACME 账号私钥的 Secret 名称,Issuer 创建时会自动生成此 secret
    privateKeySecretRef:
      name: letsencrypt-prod-alidns

    # DNS 验证设置
    solvers:
      - selector:
          # 在有多个 solvers 的情况下,会根据每个 solvers 的 selector 来确定优先级,选择其中合适的 solver 来处理证书申请事件
          # 以 dnsZones 为例,越长的 Zone 优先级就越高
          # 比如在为 www.sys.example.com 申请证书时,sys.example.com 的优先级就比 example.com 更高
          # 适用场景:如果你拥有多个域名,使用了多个域名提供商,就可能需要用到它
          dnsZones:
            - "example.com"
        dns01:
          webhook:
            config:
              accessTokenSecretRef:
                key: access-token
                name: alidns-secrets
              regionId: cn-beijing
              secretKeySecretRef:
                key: secret-key
                name: alidns-secrets
            # 这个 groupName 必须与之前部署插件时设置的一致!
            groupName: example.com
            solverName: alidns-solver

https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources

在创建证书前,先简单过一下证书的申请流程,示意图如下(出问题时需要靠这个来排查):

1
2
3
4
5
6
7
8
(  +---------+  )
  (  | Ingress |  ) Optional                                              ACME Only!
  (  +---------+  )
         |                                                     |
         |   +-------------+      +--------------------+       |  +-------+       +-----------+
         |-> | Certificate |----> | CertificateRequest | ----> |  | Order | ----> | Challenge |
             +-------------+      +--------------------+       |  +-------+       +-----------+
                                                               |

使用如下配置创建证书,并将证书保存到指定的 Secret 中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-com
  namespace: xxx
spec:
  # Secret names are always required.
  # Istio Gateway/Ingress/Gateway API 都可以通过直接引用这个 secret 来添加 TLS 加密。
  secretName: tls-example.com

  # secretTemplate is optional. If set, these annotations and labels will be
  # copied to the Secret named tls-example.com. These labels and annotations will
  # be re-reconciled if the Certificate's secretTemplate changes. secretTemplate
  # is also enforced, so relevant label and annotation changes on the Secret by a
  # third party will be overwritten by cert-manager to match the secretTemplate.
  secretTemplate:
    annotations:
      my-secret-annotation-1: "foo"
      my-secret-annotation-2: "bar"
    labels:
      my-secret-label: foo

  duration: 2160h # 90d
  renewBefore: 360h # 15d
  # https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.CertificatePrivateKey
  privateKey:
    algorithm: ECDSA # RSA/ECDSA/Ed25519,其中 RSA 应用最广泛,Ed25519 被认为最安全
    encoding: PKCS1 # 对于 TLS 加密,通常都用 PKCS1 格式
    size: 256 # RSA 默认为 2048,ECDSA 默认为 256,而 Ed25519 不使用此属性!
    rotationPolicy: Always # renew 时总是重新创建新的私钥
  # The use of the common name field has been deprecated since 2000 and is
  # discouraged from being used.
  commonName: example.com
  # At least one of a DNS Name, URI, or IP address is required.
  dnsNames:
    - example.com
    - "*.example.com"
  isCA: false
  usages:
    - server auth
    - client auth
  # uris:  # 如果想在证书的 subjectAltNames 中添加 URI,就补充在这里
  #   - spiffe://cluster.local/ns/sandbox/sa/example
  # ipAddresses:  # 如果想在证书的 subjectAltNames 添加 ip 地址,就补充在这里
  #   - 192.168.0.5
  subject:
    # 证书的补充信息
    # 字段索引:https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.X509Subject
    organizations:
      - xxx
  # Issuer references are always required.
  issuerRef:
    name: letsencrypt-prod-aws
    # name: letsencrypt-prod-alidns  # 如果你前面创建的是 alidns 那就用这个
    kind: Issuer # 如果你创建的是 ClusterIssuer 就需要改下这个值
    group: cert-manager.io

部署好 Certificate 后,describe 它就能看到当前的进度:

1
2
3
4
5
6
7
Events:
  Type    Reason     Age   From    Message
  ----    ------     ----  ----    -------
  Normal  Issuing    117s  cert-manager-certificates-trigger   Issuing certificate as Secret does not exist
  Normal  Generated  116s  cert-manager-certificates-key-manager      Stored new private key in temporary Secret resource "example.com-f044j"
  Normal  Requested  116s  cert-manager-certificates-request-manager  Created new CertificateRequest resource "example.com-unv3d"
  Normal  Issuing    20s   cert-manager-certificates-issuing   The certificate has been successfully issued

如果发现证书长时间未 Ready,可以参照官方文档 - Troubleshooting Issuing ACME Certificates, 按证书申请流程进行逐层排查:

  • 首先 cert-manager 发现 Certificate 描述的 Secret 不存在,于是启动证书申请流程
  • 首先生成私钥,存放在一个临时 Secret 中
  • 然后通过私钥以及 Certificate 资源中的其他信息,生成 CSR 证书申请请求文件
    • 这也是一个 CRD 资源,可以通过 kubectl get csr -n xxx 查看
  • 接着将 CSR 文件提交给 ACME 服务器,申请权威机构签发证书
    • 这对应 CRD 资源 kubectl get order
  • 对于上述 ACME 证书申请流程,Order 实际上会生成一个 DNS1 Challenge 资源
    • 可以通过 kubectl get challenge 检查此资源
  • challenge 验证通过后会逐层往回走,前面的 Order CSR 状态都会立即变成 valid
  • 最终证书签发成功,Certificate 状态变成 Ready,所有 Order CSR challenge 资源都被自动清理掉。

https://cert-manager.io/docs/projects/csi-driver/

直接使用 Certificate 资源创建的证书,会被存放在 Kubernetes Secrets 中,被认为并非足够安全。而 cert-manager csi-driver 则避免了这个缺陷,具体而言,它提升安全性的做法有:

  • 确保私钥仅保存在对应的节点上,并挂载到对应的 Pod,完全避免私钥被通过网络传输。
  • 应用的每个副本都使用自己生成的私钥,并且能确保在 Pod 的生命周期中证书跟私钥始终存在。
  • 自动 renew 证书
  • 副本被删除时,证书就会被销毁

总的说 csi-driver 主要是用来提升安全性的,有需要可以自己看文档,就不多介绍了。

Private CA 是一种企业自己生成的 CA 证书,通常企业用它来构建自己的 PKI 基础设施。

在 TLS 协议这个应用场景下,Private CA 颁发的证书仅适合在企业内部使用,必须在客户端安装上这个 CA 证书,才能正常访问由它签名的数字证书加密的 Web API 或者站点。Private CA 签名的数字证书在公网上是不被信任的

cert-manager 提供的 Private CA 服务有:

  • Vault: 鼎鼎大名了,Vault 是一个密码即服务工具,可以部署在 K8s 集群中,提供许多密码、证书相关的功能。
    • 开源免费
  • AWS Certificate Manager Private CA: 跟 Vault 的 CA 功能是一致的,区别是它是托管的,由 AWS 负责维护。
    • 每个 Private CA 证书:$400/month
    • 每个签发的证书(仅读取了私钥及证书内容后才会收费):按梯度一次性收费,0-1000 个以内是 $0.75 每个
  • 其他的自己看文档…

这个因为暂时用不上,所以还没研究,之后有研究再给补上。

TO BE DONE.

cert-manager 提供的 Certificate 资源,会将生成好的公私钥存放在 Secret 中,而 Istio/Ingress 都支持这种格式的 Secret,所以使用还是挺简单的。

以 Istio Gateway 为例,直接在 Gateway 资源上指定 Secret 名称即可:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: example-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
    - port:
        number: 8080
        name: http
        protocol: HTTP
      hosts:
        - product.example.com
      tls:
        httpsRedirect: true # sends 301 redirect for http requests
    - port:
        number: 8443
        name: https
        protocol: HTTPS
      tls:
        mode: SIMPLE # enables HTTPS on this port
        credentialName: tls-example.com # This should match the Certificate secretName
      hosts:
        - product.example.com # This should match a DNS name in the Certificate
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: product
spec:
  hosts:
    - product.example.com
  gateways:
    - example-gateway
  http:
    - route:
        - destination:
            host: product
            port:
              number: 8080
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: product
  name: product
  namespace: prod
spec:
  ports:
    - name: grpc
      port: 9090
      protocol: TCP
      targetPort: 9090
    - name: http
      port: 8080
      protocol: TCP
      targetPort: 8080
  selector:
    app: product
  sessionAffinity: None
  type: ClusterIP
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: product
spec:
  host: product
  # 定义了两个 subset
  subsets:
    - labels:
        version: v1
      name: v1
    - labels:
        version: v2
      name: v2
---
# 其他 deployment 等配置

之后再配合 VirtualService 等资源,就可以将 Istio 跟 cert-manager 结合起来啦。

注意,千万别使用 subPath 挂载,根据官方文档, 这种方式挂载的 Secret 文件不会自动更新!

既然证书被存放在 Secret 中,自然可以直接当成数据卷挂载到 Pods 中,示例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
    - name: nginx
      image: nginx:latest
      volumeMounts:
        - name: tls-example.com
          mountPath: "/certs/example.com"
          readOnly: true
  volumes:
    - name: tls-example.com
      secret:
        secretName: tls-example.com
        optional: false # default setting; "mysecret" must exist

对于 nginx 而言,可以简单地搞个 sidecar 监控下,有配置变更就 reload 下 nginx,实现证书自动更新。

或者可以考虑直接写个 k8s informer 监控 secret 的变更,有变更就直接 reload 所有 nginx 实例,总之实现的方式有很多种。

证书的过期时间是一个很重要的指标,证书过期了,网站就无法正常访问了。虽然正常情况下 cert-manager 应该能够自动更新证书,但是万一出现了问题,又没有及时发现,那就麻烦了。

因此,建议对证书的过期时间进行监控,当证书的过期时间小于一定阈值时,及时发出告警。

cert-manager 提供了 Prometheus 监控指标,可以直接使用 Prometheus 等工具进行监控告警。

官方文档是这个:https://cert-manager.io/docs/usage/prometheus-metrics/#scraping-metrics

文档中没详细列出所有的指标,可以直接接入到 Prometheus 中,然后通过 Grafana 查看。

比如要设置证书过期时间的告警,可以使用如下 PromQL:

1
(certmanager_certificate_expiration_timestamp_seconds - time())/3600/24 < 20

上面这个 PromQL 表示,如果证书的过期时间小于 20 天,就会触发告警。

服务端 TLS 协议的配置有许多的优化点,有些配置对性能的提升是很明显的,建议自行网上搜索相关资料,这里仅列出部分相关信息。

https://www.ssl.com/blogs/how-do-browsers-handle-revoked-ssl-tls-certificates/

https://imququ.com/post/why-can-not-turn-on-ocsp-stapling.html

https://www.digicert.com/help/

前面提到除了数字证书自带的有效期外,为了在私钥泄漏的情况下,能够吊销对应的证书,PKI 公钥基础设施还提供了 OCSP(Online Certificate Status Protocol)证书状态查询协议。

这导致了一些问题:

  • Chrome/Firefox 等浏览器都会定期通过 OCSP 协议去请求 CA 机构的 OCSP 服务器验证证书状态, 这可能会拖慢 HTTPS 协议的响应速度。
    • 所谓的定期是指超过上一个 OCSP 响应的 nextUpdate 时间(一般为 7 天),或者如果该值为空的话,Firefox 默认 24h 后会重新查询 OCSP 状态。
  • 因为客户端直接去请求 CA 机构的 OCSP 地址获取证书状态,这就导致 CA 机构可以获取到一些对应站点的用户信息(IP 地址、网络状态等)。

为了解决这两个问题,rfc6066 定义了 OCSP stapling 功能,它使服务器可以提前访问 OCSP 获取证书状态信息并缓存到本地,基本 Nginx/Caddy 等各大 Web 服务器或网关,都支持 OCSP stapling 协议。

在客户端使用 TLS 协议访问 HTTPS 服务时,服务端会直接在握手阶段将缓存的 OCSP 信息发送给客户端。因为 OCSP 信息会带有 CA 证书的签名及有效期,客户端可以直接通过签名验证 OCSP 信息的真实性与有效性,这样就避免了客户端访问 OCSP 服务器带来的开销。

而另一个方法,就是选用 ocsp 服务器在目标用户区域速度快的 CA 机构签发证书。

可以使用如下命令测试,确认站点是否启用了 ocsp stapling:

1
$ openssl s_client -connect www.digicert.com:443 -servername www.digicert.com -status -tlsextdebug < /dev/null 2>&1 | grep -i "OCSP response"

如果输出包含 OCSP Response Status: successful 就说明站点支持 ocsp stapling,如果输出内容为 OCSP response: no response sent 则说明站点不支持ocsp stapling。

实际上 Google/AWS 等大多数站点都不会启用也不需要启用 ocsp stapling,一是因为它们自己就是证书颁发机构,OCSP 服务器也归它们自己管,不存在隐私的问题。二是它们的 OCSP 服务器遍布全球,也不存在性能问题。这种情况下开个 OCSP Stapling 反而是浪费流量,因为每次 TLS 握手都得发送一个 OCSP 状态信息。

我测试发现只有 www.digicert.com/www.douban.com 等少数站点启用了 ocsp stapling,www.baidu.com/www.google.com/www.zhihu.com 都未启用 ocsp stapling.

相关内容