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.

相关内容