在Kubernetes环境中实现证书管理自动化

NGINX | 2022年10月05日

有效的SSL/TLS证书是现代应用环境的核心要求。遗憾的是,在部署应用时,证书续期管理往往未得到充分重视。证书的有效期有限,从DigiCert证书约13个月到Let’s Encrypt证书90天不等。为确保访问安全,必须在证书到期前续期或重新签发。大多数运维团队工作繁重,有时难免疏忽证书续期,导致证书临近到期,甚至已经过期时才匆忙处理。

情况不必如此。通过适当的规划和准备,可以实现证书管理的自动化并简化流程。下面将介绍一种使用三项技术的Kubernetes解决方案:

本博客将介绍如何为端点提供唯一且可自动续订和更新的证书,从而简化证书管理。

Kubernetes环境中的证书

在深入了解技术细节之前,需要先定义一些术语。“TLS证书”是指在Ingress控制器上启用HTTPS连接所需的两个组件:

  • 证书
  • 私钥

证书和私钥均由Let’s Encrypt签发。有关TLS证书工作原理的完整说明,请参阅DigiCert的文章《TLS/SSL证书的工作原理》。

在Kubernetes中,这两个组件存储为Secret。Kubernetes工作负载(例如NGINX Ingress Controller和cert-manager)可以写入和读取这些Secret。能够访问Kubernetes安装环境的用户也可以管理这些Secret。

cert-manager 简介

The cert-manager项目是一款适用于Kubernetes和OpenShift的证书控制器。部署到Kubernetes后,cert-manager会自动签发Ingress控制器所需的证书,并确保这些证书有效且为最新版本。此外,它还会跟踪证书的到期日期,并按配置的时间间隔尝试续期。虽然它支持众多公共和私有证书颁发机构,但本文将介绍它与Let’s Encrypt的集成。

Cert Manager 示意图

两种质询类型

使用 Let’s Encrypt 时,所有证书管理工作都会自动完成。这虽然非常方便,但也带来了一个问题:该服务如何确认相关完全限定域名(FQDN)归您所有?

此问题可通过质询解决。您需要响应一项验证请求,而只有能够访问特定域名DNS记录的人员才能提供所需信息。质询分为以下两种形式:

  1. HTTP-01:要完成此质询,需要为待签发证书的完全限定域名(FQDN)配置DNS记录。例如,如果服务器的IP地址为www.xxx.yyy.zzz,FQDN为cert.example.com,质询机制将在位于www.xxx.yyy.zzz的服务器上提供一个令牌,Let’s Encrypt服务器将尝试通过cert.example.com访问该令牌。如果访问成功,即通过质询并签发证书。HTTP-01无需直接访问DNS提供商,因此是较为简单的证书生成方式。此类质询始终通过80端口(HTTP)进行。请注意,使用HTTP-01质询时,cert-manager将利用Ingress控制器提供质询令牌。

HTTP-01示意图

  1. DNS-01:此质询会创建一条包含令牌的DNS TXT记录,随后由颁发机构进行验证。如果令牌通过验证,即可证明您拥有该域,并可为其记录签发证书。与HTTP-01质询不同,使用DNS-01质询时,FQDN无需解析到服务器的IP地址,甚至无需实际存在。此外,端口80被阻止时也可使用DNS-01。虽然这种方式使用方便,但需要通过API令牌向cert-manager安装实例授予访问DNS基础设施的权限。

DNS-01 示意图

Ingress 控制器

An Ingress控制器是Kubernetes的一项专用服务,可将集群外部流量引入集群,对流量进行负载均衡并将其分配到内部Pod(由一个或多个容器组成),同时管理出站流量。此外,Ingress控制器通过Kubernetes API进行控制,并会在添加、移除Pod或Pod发生故障时监控并更新负载均衡配置。

要详细了解Ingress控制器,请阅读以下博客:

在以下示例中,我们将使用由F5 NGINX开发和维护的NGINX Ingress Controller。

证书管理示例

这些示例假定您有一个可用于测试且正常运行的Kubernetes安装,并且该安装可以分配外部IP地址(Kubernetes LoadBalancer对象)。此外,还假定您可以通过端口80和端口443接收流量(使用HTTP-01质询时),或仅通过端口443接收流量(使用DNS-01质询时)。这些示例使用Mac OS X进行演示,但也适用于Linux或WSL。

还需要一个DNS提供商和一个可调整A记录的FQDN。如果使用HTTP-01质询,只需能够添加A记录(或由他人代为添加)。如果使用DNS-01质询,则需要通过API访问受支持的DNS提供商或受支持的Webhook提供商。

部署 NGINX Ingress Controller

最简单的方法是通过Helm进行部署。此部署支持同时使用Kubernetes Ingress和NGINX Virtual Server CRD。

  1. 添加NGINX仓库。
$ helm repo add nginx-stable https://helm.nginx.com/stable  "nginx-stable" has been added to your repositories 
  1. 更新存储库。
$ helm repo update  Hang tight while we grab the latest from your chart repositories...
  ...Successfully got an update from the "nginx-stable" chart repository
  Update Complete. ⎈Happy Helming!⎈ 
  1. 部署Ingress控制器。
$ helm install nginx-kic nginx-stable/nginx-ingress \  --namespace nginx-ingress  --set controller.enableCustomResources=true \ 
  --create-namespace  --set controller.enableCertManager=true 
  NAME: nginx-kic
  LAST DEPLOYED: Thu Sep  1 15:58:15 2022
  NAMESPACE: nginx-ingress
  STATUS: deployed
  REVISION: 1
  TEST SUITE: None
  NOTES:
  The NGINX Ingress Controller has been installed. 
  1. 检查部署,并获取Ingress控制器出口的IP地址。请注意,如果没有有效的IP地址,将无法继续。
$ kubectl get deployments --namespace nginx-ingress  NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
  nginx-kic-nginx-ingress   1/1     1            1           23s
  $ kubectl get services --namespace nginx-ingress
  NAME                      TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)                      AGE
  nginx-kic-nginx-ingress   LoadBalancer   10.128.60.190   www.xxx.yyy.zzz   80:31526/TCP,443:32058/TCP   30s 

添加DNS A记录

具体流程取决于您的DNS提供商。Let’s Encrypt服务器必须能够解析此DNS名称,因此可能需要等待记录传播后才能生效。有关详细信息,请参阅SiteGround文章什么是DNS传播,为什么需要这么长时间?

所选的FQDN能够正常解析后,即可继续下一步。

$ host cert.example.com  cert.example.com has address www.xxx.yyy.zzz

部署 cert-manager

下一步是部署最新版本的 cert-manager。我们仍将使用 Helm 进行部署。

  1. 添加Helm存储库。
$ helm repo add jetstack https://charts.jetstack.io  "jetstack" has been added to your repositories 
  1. 更新存储库。
$ helm repo update  Hang tight while we grab the latest from your chart repositories...
  ...Successfully got an update from the "nginx-stable" chart repository
  ...Successfully got an update from the "jetstack" chart repository
  Update Complete. ⎈Happy Helming!⎈ 
  1. 部署cert-manager。
$ helm install cert-manager jetstack/cert-manager \  --namespace cert-manager --create-namespace \
  --version v1.9.1  --set installCRDs=true 
  NAME: cert-manager
  LAST DEPLOYED: Thu Sep  1 16:01:52 2022 
  NAMESPACE: cert-manager
  STATUS: deployed
  REVISION: 1 
  TEST SUITE: None
  NOTES:
  cert-manager v1.9.1 has been deployed successfully!
  In order to begin issuing certificates, you will need to set up a ClusterIssuer
  or Issuer resource (for example, by creating a 'letsencrypt-staging' issuer).
  More information on the different types of issuers and how to configure them
  can be found in our documentation:
  https://cert-manager.io/docs/configuration/ For information on how to configure cert-manager to automatically provision
  Certificates for Ingress resources, take a look at the `ingress-shim`
  documentation:
  https://cert-manager.io/docs/usage/ingress/
  1. 验证部署。
$ kubectl get deployments --namespace cert-manager  NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
  cert-manager              1/1     1            1           4m30s
  cert-manager-cainjector   1/1     1            1           4m30s
  cert-manager-webhook      1/1     1            1           4m30s 

部署NGINX Cafe示例

我们将使用NGINX Cafe示例来提供后端Deployment和Service。这是NGINX文档中常用的示例。本次不会部署Ingress。

  1. 克隆NGINX Ingress GitHub项目。
$ git clone https://github.com/nginxinc/kubernetes-ingress.git  Cloning into 'kubernetes-ingress'...
  remote: Enumerating objects: 44979, done.
  remote: Counting objects: 100% (172/172), done.
  remote: Compressing objects: 100% (108/108), done.
  remote: Total 44979 (delta 87), reused 120 (delta 63), pack-reused 44807
  Receiving objects: 100% (44979/44979), 60.27 MiB | 27.33 MiB/s, done.
  Resolving deltas: 100% (26508/26508), done. 
  1. 切换到 examples 目录。该目录包含多个示例,用于演示 Ingress 控制器的各种配置。此处使用 complete-example 目录下提供的示例。
$ cd ./kubernetes-ingress/examples/ingress-resources/complete-example 
  1. 部署NGINX Cafe示例。
$ kubectl apply -f ./cafe.yaml
  deployment.apps/coffee created
  service/coffee-svc created
  deployment.apps/tea created
  service/tea-svc created
  1. 使用kubectl get命令验证部署和服务。确保Pod显示为READY,服务显示为running。以下示例展示了预期结果的典型样例。请注意,kubernetes服务是一项系统服务,与NGINX Cafe示例在同一命名空间(default)中运行。
$ kubectl get deployments,services  --namespace default  NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
  deployment.apps/coffee   2/2     2            2           69s
  deployment.apps/tea      3/3     3            3           68s
NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
  service/coffee-svc   ClusterIP   10.128.154.225   <none>        80/TCP    68s
  service/kubernetes   ClusterIP   10.128.0.1       <none>        443/TCP   29m
	service/tea-svc      ClusterIP   10.128.96.145    <none>        80/TCP    68s 

部署ClusterIssuer

在cert-manager中,ClusterIssuer可用于颁发证书。它是一个集群范围的对象,可由任何命名空间引用,并可供使用所定义证书颁发机构的任何证书请求使用。在本例中,任何Let’s Encrypt证书请求都可由此ClusterIssuer处理。

为所选质询类型部署ClusterIssuer。虽然这不在本文的讨论范围内,但可以使用高级配置选项在ClusterIssuer中指定多个解析器(根据选择器字段进行选择)。

ACME质询基础知识

自动证书管理环境(ACME)协议用于确定您是否拥有某个域名,从而判断是否可以向您颁发Let’s Encrypt证书。此质询需要传递以下参数:

  • metadata.name:ClusterIssuer名称,在Kubernetes安装环境中必须唯一。稍后在示例中颁发证书时将使用此名称。
  • spec.acme.email:这是您为生成证书而向Let’s Encrypt注册的电子邮件地址。请使用您本人的电子邮件地址。
  • spec.acme.privateKeySecretRef:这是用于存储私钥的Kubernetes Secret的名称。
  • spec.acme.solvers:此项应保持不变。它指定所使用的质询类型(ACME称之为求解器),即HTTP-01或DNS-01,以及应应用到的Ingress类;在本例中为nginx。

使用HTTP-01

此示例说明如何设置ClusterIssuer,以使用HTTP-01质询验证域名所有权并获取证书。

  1. 创建使用HTTP-01进行质询的ClusterIssuer。
$ cat << EOF | kubectl apply -f   apiVersion: cert-manager.io/v1
  kind: ClusterIssuer
  metadata:
    name: prod-issuer
  spec:
    acme:
      email: example@example.com
      server: https://acme-v02.api.letsencrypt.org/directory
      privateKeySecretRef:
        name: prod-issuer-account-key
      solvers:
      - http01:
         ingress:
           class: nginx
  EOF
  clusterissuer.cert-manager.io/prod-issuer created 
  1. 验证ClusterIssuer(其状态应显示为“就绪”)。
$ kubectl get clusterissuer  NAME          READY   AGE
	prod-issuer   True    34s 

使用DNS-01

此示例说明如何设置ClusterIssuer,以使用DNS-01质询验证域名所有权。根据所使用的DNS提供商,可能需要使用Kubernetes Secret存储令牌。此示例使用Cloudflare。请注意命名空间的使用。cert-manager应用程序部署在cert-manager命名空间中,需要有权访问该Secret。

在此示例中,需要一个Cloudflare API令牌,可在账户中创建。需要将该令牌填入下方的<API Token>行。如果不使用Cloudflare,则需要按照所用提供商的文档操作。

  1. 为API令牌创建一个Secret。
$ cat << EOF | kubectl apply -f   apiVersion: v1
  kind: Secret
  metadata:
    name: cloudflare-api-token-secret
    namespace: cert-manager
  type: Opaque
  stringData:
    api-token: <API Token> 
  EOF 
  1. 创建使用DNS-01完成质询的颁发者。
$ cat << EOF | kubectl apply -f   apiVersion: cert-manager.io/v1
  kind: ClusterIssuer
  metadata:
    name: prod-issuer
  spec:
    acme:
      email: example@example.com
      server: https://acme-v02.api.letsencrypt.org/directory
      privateKeySecretRef:
        name: prod-issuer-account-key
      solvers:
        - dns01:
            cloudflare:
              apiTokenSecretRef:
                name: cloudflare-api-token-secret
                key: api-token
  EOF 
  1. 验证颁发者(其状态应显示为“就绪”)。
$ kubectl get clusterissuer  NAME          READY   AGE
	prod-issuer   True    31m 

部署Ingress

现在,我们终于可以部署一直以来为之准备的应用Ingress资源了。该资源会将流量路由到之前部署的NGINX Cafe应用。

使用 Kubernetes Ingress

如果使用标准Kubernetes Ingress资源,请使用以下部署YAML配置Ingress并申请证书。

apiVersion: networking.k8s.io/v1   kind: Ingress 
  metadata: 
    name: cafe-ingress 
    annotations: 
      cert-manager.io/cluster-issuer: prod-issuer 
      acme.cert-manager.io/http01-edit-in-place: "true" 
  spec: 
    ingressClassName: nginx 
    tls: 
    - hosts: 
      - cert.example.com 
      secretName: cafe-secret 
    rules: 
    - host: cert.example.com 
      http: 
        paths: 
        - path: /tea 
          pathType: Prefix 
          backend: 
            service: 
              name: tea-svc 
              port: 
                number: 80 
        - path: /coffee 
          pathType: Prefix 
          backend: 
            service: 
              name: coffee-svc 
              port: 
number: 80 

建议查看清单中的几个关键部分:

  • 调用的API是标准Kubernetes Ingress。
  • 此配置的关键部分位于metadata.annotations下,其中将acme.cert-manager.io/http01-edit-in-place设置为“true”。此值为必填项,用于调整质询的提供方式。有关详细信息,请参阅《支持的注解》文档。也可以使用主节点/从节点配置来处理此问题。
  • The spec.ingressClassName指我们已安装并将使用的NGINX Ingress控制器。
  • The spec.tls.secret Kubernetes Secret资源用于存储Let’s Encrypt颁发证书时返回的证书密钥。
  • 主机名cert.example.com已在spec.tls.hosts和spec.rules.host中指定。ClusterIssuer为此主机名颁发了证书。
  • The spec.rules.http部分定义路径,以及处理这些路径请求的后端服务。例如,发往/tea的流量将被定向到tea-svc的80端口。
  1. 根据您的安装环境修改上述清单。至少需要更改spec.rules.host和spec.tls.hosts的值,但应检查配置中的所有参数。
  2. 应用清单。
$  kubectl apply -f ./cafe-virtual-server.yaml  virtualserver.k8s.nginx.org/cafe created 
  1. 等待证书签发。READY字段的值应为“True”。
$ kubectl get certificates  NAME                                      READY   SECRET        AGE
  certificate.cert-manager.io/cafe-secret   True    cafe-secret   37m 

使用NGINX Virtual Server和Virtual Routes

如果使用NGINX CRD,则需要使用以下部署YAML来配置Ingress。

  apiVersion: k8s.nginx.org/v1 
  kind: VirtualServer 
  metadata: 
    name: cafe 
  spec: 
    host: cert.example.com 
    tls: 
      secret: cafe-secret 
      cert-manager: 
        cluster-issuer: prod-issuer 
    upstreams: 
      - name: tea 
        service: tea-svc 
        port: 80 
      - name: coffee 
        service: coffee-svc 
        port: 80 
    routes: 
      - path: /tea 
        action: 
          pass: tea 
      - path: /coffee 
        action: 
          pass: coffee

同样,建议回顾一下清单中的几个关键部分:

  • 调用的API是NGINX专用于VirtualServer资源的k8s.nginx.org/v1。
  • The spec.tls.secret Kubernetes Secret资源用于存储Let’s Encrypt签发证书时返回的证书密钥。
  • 我们的主机名cert.example.com在spec.host中指定。ClusterIssuer为此主机名颁发了证书。
  • The spec.upstreams 的值指向后端服务,包括相应端口。
  • The spec.routes 定义路由以及命中这些路由时要执行的操作。
  1. 根据您的安装环境修改上述清单。至少需要更改spec.host值,但建议检查配置中的所有参数。
  2. 应用清单。
$  kubectl apply -f ./cafe-virtual-server.yaml  virtualserver.k8s.nginx.org/cafe created
  1. 等待证书签发。状态应显示为“有效”。
$ kubectl get VirtualServers  NAME   STATE   HOST                    IP             PORTS      AGE
  cafe   Valid   cert.example.com   www.xxx.yyy.zzz   [80,443]   51m 

查看证书

可以通过Kubernetes API查看证书。系统将显示证书的详细信息,包括证书大小及其关联的私钥。

$ kubectl describe secret cafe-secret  Name:         cafe-secret
  Namespace:    default
  Labels:       <none>
  Annotations:  cert-manager.io/alt-names: cert.example.com
                cert-manager.io/certificate-name: cafe-secret
                cert-manager.io/common-name: cert.example.com
                cert-manager.io/ip-sans:
                cert-manager.io/issuer-group:
                cert-manager.io/issuer-kind: ClusterIssuer
                cert-manager.io/issuer-name: prod-issuer
                cert-manager.io/uri-sans:Type:  kubernetes.io/tlsData
  ====
  tls.crt:  5607 bytes
  tls.key:  1675 bytes 

如需查看实际的证书和密钥,可以运行以下命令。(注意:这确实反映了Kubernetes Secret的一个弱点,即任何拥有必要访问权限的人都可以读取它们。)

$ kubectl get secret cafe-secret -o yaml

测试Ingress

测试证书。可以使用任意方法。以下示例使用cURL。如果成功,将显示与前文类似的信息块,其中包括服务器名称、服务器内部地址、日期、所选URI(路由)(coffee或tea)以及请求ID。如果失败,将返回HTTP错误代码,最可能是400或301。

$ curl https://cert.example.com/tea
  Server address: 10.2.0.6:8080
  Server name: tea-5c457db9-l4pvq
  Date: 02/Sep/2022:15:21:06 +0000
  URI: /tea
  Request ID: d736db9f696423c6212ffc70cd7ebecf
  $ curl https://cert.example.com/coffee
  Server address: 10.2.2.6:8080
  Server name: coffee-7c86d7d67c-kjddk
  Date: 02/Sep/2022:15:21:10 +0000
  URI: /coffee
Request ID: 4ea3aa1c87d2f1d80a706dde91f31d54 

证书续期

一开始,我们曾表示,这种方法无需管理证书续期。不过,我们还没有解释它是如何实现的。为什么?因为这是cert-manager内置的核心功能。在这一自动化流程中,当cert-manager发现证书不存在、已过期、将在15天内过期,或者用户通过CLI请求新证书时,系统就会自动申请新证书。整个过程就是这么简单。

常见问题

NGINX Plus怎么样?

如果您是NGINX Plus订阅用户,唯一的区别在于安装NGINX Ingress Controller。请参阅NGINX文档中的“Helm安装”部分,了解如何修改上述Helm命令来完成安装。

应该使用哪种质询类型?

这主要取决于您的用例。

HTTP-01质询方法要求将端口80向互联网开放,并为Ingress控制器的IP地址正确配置DNS A记录。除创建A记录外,此方法无需访问DNS提供商。

当无法将端口80开放至互联网时,可以使用DNS-01质询方法。此方法只要求cert-manager能够通过出站连接访问DNS提供商。不过,您必须能够访问DNS提供商的API,具体所需的访问权限级别因提供商而异。

如何排查问题?

由于Kubernetes非常复杂,很难提供有针对性的故障排除信息。如果遇到问题,欢迎通过NGINX Community Slack向我们咨询(NGINX Plus订阅用户可以使用常规支持渠道)。

立即开始

申请免费30天试用,开始体验集成NGINX App Protect WAF和DoS的NGINX Ingress Controller,并下载永久免费使用的NGINX Service Mesh。


分享
标签: DNS, SSL/TLS, F5 NGINX, 2022, Tech

相关博客文章

NGINX Plus R26 正式发布
NGINX | 2022/02/15

NGINX Plus R26 正式发布

NGINX Plus R26通过缓存JSON Web密钥集加快JWT验证,并强化TLS握手,从而提升安全性。NGINX JavaScript的新功能包括增强对异步函数的支持,以及实现WebCrypto API。

使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击
NGINX | 2022/02/10

使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击

gRPC具备开发人员设计和部署现代应用所需的速度与灵活性,但其开放性也使其极易受到第7层DoS攻击。NGINX App Protect DoS可保护gRPC应用免受此类破坏性攻击。

Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划
NGINX | 2021/09/13

Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划

了解选择错误的Ingress控制器可能带来的风险,以及如何从关键方面着手,让您的选择更好地适应未来需求。

NGINX Plus R19 正式发布
NGINX | 2019/09/03

NGINX Plus R19 正式发布

NGINX Plus R19扩展了监控功能,不仅提供更多指标,还新增了指标分析方式;增加试运行模式,用于测试限速效果;增强键值存储,以支持IP地址范围;并使限速更加动态。

NGINX现已正式加入F5
NGINX | 2019/05/09

NGINX现已正式加入F5

NGINX现已正式成为F5的一部分。Gus Robertson现任F5 NGINX业务部门负责人。他阐述了两家公司在人才、文化和愿景方面融合的价值,以及双方将如何携手增强NGINX的开源产品和商业产品。

使用NGINX和NGINX Plus实现高性能缓存
NGINX | 2016/08/24

使用NGINX和NGINX Plus实现高性能缓存

我将介绍NGINX在内容缓存和性能方面的工作原理,并分享一些控制NGINX内容缓存方式的技巧。