赞
踩
作者张晋涛,API7.ai 云原生技术专家
cert-manager 解决了什么问题
cert-manager[1] 是由 JETSTACK[2] 在 2017 年开源,并在 2020 年 11 月正式捐赠给了 CNCF 成为 sandbox 级别的项目。在 2022 年 10 月正式成为 CNCF incubating 级别的项目。
cert-manager 主要用来自动化的管理 Kubernetes 和 OpenShift 中的 x509 证书身份,它使证书和证书签发结构成为了 Kubernetes 上第一类支持的资源(通过 CRD 实现),并且允许开发者可以很简单的为应用程序申请证书,以便提升应用访问的安全性。
那么我们来看看在 cert-manager 出现之前,在 Kubernetes 中如何管理证书呢?
Kubernetes 中的证书如何管理
在 Kubernetes 中存储数据主要有以下两种原生的方式:
ConfigMap
Secret
不过 ConfigMap 中所有的信息都是明文的,存储一些相对普通的配置信息还可以,但对于证书这类比较私密的信息,就不那么适用了。
Kubernetes 在设计的时候就推荐使用 Secret 来存储证书等相关信息,并且还为此提供了默认支持。我们可以很简单的通过 kubectl create secret tls
来存储证书的信息。比如:
➜ ~ kubectl create secret tls moelove-tls --cert=./cert.pem --key=./cert-key.pem secret/moelove-tls created ➜ ~ kubectl get secret moelove-tls -oyaml apiVersion: v1 data: tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNFekNDQWJtZ0F3SUJBZ0lVVHhCTC9aQkdpOEJCOUFVN2JRWi9jK3c2L1Rzd0NnWUlLb1pJemowRUF3SXcKVFRFTE1Ba0dBMVVFQmhNQ1EwNHhFREFPQmdOVkJBY1RCMEpsYVdwcGJtY3hGVEFUQmdOVkJBb1RERTF2WlV4dgpkbVVnU1U1R1R6RVZNQk1HQTFVRUF4TU1iVzlsYkc5MlpTNXBibVp2TUI0WERUSXlNVEF4T1RBM01UY3dNRm9YCkRUSXpNVEF4T1RBM01UY3dNRm93VFRFTE1Ba0dBMVVFQmhNQ1EwNHhFREFPQmdOVkJBY1RCMEpsYVdwcGJtY3gKRlRBVEJnTlZCQW9UREUxdlpVeHZkbVVnU1U1R1R6RVZNQk1HQTFVRUF4TU1iVzlsYkc5MlpTNXBibVp2TUZrdwpFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVVTcEFjNGE1UXQwQ0NVa2hGSGY3WnZvR1FReVVPUUxSClJhZG0rSUUrV1ZkOThyWkc5NFpob08ybDZSWkY2MnVPN3FpZ2VsaUJwY0FGQ3FzWU9HNnVLcU4zTUhVd0RnWUQKVlIwUEFRSC9CQVFEQWdXZ01CMEdBMVVkSlFRV01CUUdDQ3NHQVFVRkJ3TUJCZ2dyQmdFRkJRY0RBakFNQmdOVgpIUk1CQWY4RUFqQUFNQjBHQTFVZERnUVdCQlFnS01icnBUb3k4NVcvRy9hMGZtYzlDMUJRbURBWEJnTlZIUkVFCkVEQU9nZ3h0YjJWc2IzWmxMbWx1Wm04d0NnWUlLb1pJemowRUF3SURTQUF3UlFJZ1EzTzhJZ0N2MlRkNUhhV00KcE1LWmRCLzNXdEMreERlSVdPbER6L2hCdzE0Q0lRRExQNG0weFpmSkJvRGc5cERocThGdHN5VDdVZVhVdlZGQQpsS0tReFZNOXFBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= tls.key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUsyZjZHQlNZQ0R4eVoycnB2bVZ1YW5MNDhxeW9SK1NiWmxiQzNqSUZybzhvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFVVNwQWM0YTVRdDBDQ1VraEZIZjdadm9HUVF5VU9RTFJSYWRtK0lFK1dWZDk4clpHOTRaaApvTzJsNlJaRjYydU83cWlnZWxpQnBjQUZDcXNZT0c2dUtnPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo= kind: Secret metadata: creationTimestamp: "2022-10-19T07:24:26Z" name: moelove-tls namespace: default resourceVersion: "2103326" uid: 14f86514-a1d1-4d99-b000-9ed8b5189d56 type: kubernetes.io/tls
通过上述命令,在 Kubernetes 中创建了一个名为 moelove-tls
的 secret 资源,并且它是 kubernetes.io/tls
类型的。
应用程序想要使用的时候,直接引用该资源即可从中获取相应的证书信息。多数情况下,我们会将其用于 Ingress 的场景中。比如:
➜ ~ kubectl create ingress moelove-ing --rule="moelove.info/=moelove:8080,tls=moelove-tls" ingress.networking.k8s.io/moelove-ing created ➜ ~ kubectl get ing moelove-ing -oyaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: creationTimestamp: "2022-10-19T07:32:43Z" generation: 1 name: moelove-ing namespace: default resourceVersion: "2104268" uid: b90f09f7-8036-4b9f-9744-a247141ea8da spec: rules: - host: moelove.info http: paths: - backend: service: name: moelove port: number: 8080 path: / pathType: Exact tls: - hosts: - moelove.info secretName: moelove-tls status: loadBalancer: {}
通过上述命令,创建了一个名为 moelove-ing
的 Ingress 资源,并声明其域名为 moelove.info
, 且使用 moelove-tls
为此域名添加了证书保护。对应的 Ingress controller 组件获取到此 Ingress 资源后,便可自动的将证书配置给此域名使用,进而提升网站的安全性。
遇到了哪些问题
我们来看看在此过程中遇到了哪些问题呢?
上述内容中我并没有演示如何进行证书的签发,如果你对此感兴趣可以查看 OpenSSL 的文档[3] 。在证书的签发过程中,需要理解很多概念。而且签发过程都发生在 Kubernetes 集群之外,不能很好的通过“声明式”配置的方式来了解具体发生了什么事情。尤其是证书可以有多种不同的加密算法,各种不同的配置等。
所以如果使用默认的方式,只能最后将生成的证书和密钥存储在 Kubernetes 的 Secrets 中。
我们都知道证书是有过期时间的,在证书过期或者被吊销之前,必须准备好新的证书,并且保证其过期时间要晚于原证书的过期时间。
在 Kubernetes Secrets 中存储的证书,从这方面考虑的话有些不足:
不存在自动化的过期时间检查
也就是说,你可以在 Kubernetes 中存储任意的证书,无论该证书是否已经过期。
不存在无效数据的检查
也就是说,如果存储在 Kubernetes Secrets 中的数据是损坏的,或者是无效的,那么在 Kubernetes 中也不会有什么特殊的处理。
事实上,存储在 Kubernetes Secretes 中的证书和密钥信息,仅仅是进行了 base64 的编码,任何人只要拿到了此数据,均可对其进行 base64 的解码,进而获取到其中的真实数据。比如:
- ➜ ~ kubectl get secrets moelove-tls -o jsonpath='{ .data.tls\.crt }' |base64 -d
- -----BEGIN CERTIFICATE-----
- MIICEzCCAbmgAwIBAgIUTxBL/ZBGi8BB9AU7bQZ/c+w6/TswCgYIKoZIzj0EAwIw
- TTELMAkGA1UEBhMCQ04xEDAOBgNVBAcTB0JlaWppbmcxFTATBgNVBAoTDE1vZUxv
- dmUgSU5GTzEVMBMGA1UEAxMMbW9lbG92ZS5pbmZvMB4XDTIyMTAxOTA3MTcwMFoX
- DTIzMTAxOTA3MTcwMFowTTELMAkGA1UEBhMCQ04xEDAOBgNVBAcTB0JlaWppbmcx
- FTATBgNVBAoTDE1vZUxvdmUgSU5GTzEVMBMGA1UEAxMMbW9lbG92ZS5pbmZvMFkw
- EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUSpAc4a5Qt0CCUkhFHf7ZvoGQQyUOQLR
- Radm+IE+WVd98rZG94ZhoO2l6RZF62uO7qigeliBpcAFCqsYOG6uKqN3MHUwDgYD
- VR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNV
- HRMBAf8EAjAAMB0GA1UdDgQWBBQgKMbrpToy85W/G/a0fmc9C1BQmDAXBgNVHREE
- EDAOggxtb2Vsb3ZlLmluZm8wCgYIKoZIzj0EAwIDSAAwRQIgQ3O8IgCv2Td5HaWM
- pMKZdB/3WtC+xDeIWOlDz/hBw14CIQDLP4m0xZfJBoDg9pDhq8FtsyT7UeXUvVFA
- lKKQxVM9qA==
- -----END CERTIFICATE-----
通过以上命令便直接拿到了证书相关的原始数据。
另一方面,当我们要更新证书和密钥数据的时候,并不存在二次确认的过程,可以直接进行更新。这对于大多数场景下都是不符合安全策略的。
接下来我们看看 cert-manager 如何解决这些问题。
cert-manager 如何解决
cert-manager 通过 CRD 的方式进行开发和扩展,其添加和实现了 Issuers
和 ClusterIssuers
资源,代表证书的签发机构(CA)。
并且支持了多种内置类型,以及可以很方便的与外部组件进行集成,比如:
SelfSigned:自签证书
CA:提供 CA 进行签发
Vault:使用 Vault 进行签发
Venafi:使用 Venafi 进行签发
External:使用一些外部的组件进行签发,比如:
- kms-issuer[4] :使用 AWS KMS 签发
- google-cas-issuer[5] :使用 Google CAS 进行签发
- [origin-ca-issuer][6]:使用 Cloudflare Origin CA[7] 进行签发
ACME(Automated Certificate Management Environment ):自动化进行证书签发
通过这些组件可以非常方便的进行证书的签发。后续内容中将会以 Vault 为例进行具体介绍。
自动续签/重签
cert-manager 中我们可以非常方便的通过 cmctl
CTL 手动的进行证书 renew 操作,同时 cert-manager 也会自动的检查证书的有效期和证书的完整性。
如果出现证书过期,或者证书数据不完整,都可以自动触发证书的重新签发,节约了人力成本和维护成本。
安全性保障
cert-manager 中通过 CRD 的方式增加了 signers
资源,允许对证书请求进行确认,进行 Approved
或者 Denied
。只有 Approve 后,才会真正生效,并签发证书。这样可以更加的安全。
APISIX Ingress 如何与 cert-manager 集成
安装部署
Apache APISIX Ingress 是一个 Kubernetes Ingress controller,可以支持通过 Ingress,自定义资源,以及 Gateway API 等方式进行代理规则的配置。
接下来演示如何将 APISIX Ingress 与 cert-manager 集成,为代理的域名添加 TLS 证书,提升安全性。
同时,我们使用 Vault 进行证书的签发。
部署 APISIX Ingress controller
部署 APISIX Ingress 很简单,仅需要执行如下步骤即可:
- tao@moelove:~$ helm repo add apisix https://charts.apiseven.com
- tao@moelove:~$ helm repo add bitnami https://charts.bitnami.com/bitnami
- tao@moelove:~$ helm repo update
- tao@moelove:~$ helm install apisix apisix/apisix --set gateway.tls.enabled=true --set gateway.type=NodePort --set ingress-controller.enabled=true --set ingress-controller.config.apisix.serviceNamespace=apisix --namespace apisix --create-namespace --set ingress-controller.config.apisix.serviceName=apisix-admin --set ingress-controller.config.ingressPublishService="apisix/apisix-gateway"
- NAME: apisix
- LAST DEPLOYED: Wed Oct 19 21:33:37 2022
- NAMESPACE: apisix
- STATUS: deployed
- REVISION: 1
- TEST SUITE: None
- NOTES:
- 1. Get the application URL by running these commands:
- export NODE_PORT=$(kubectl get --namespace apisix -o jsonpath="{.spec.ports[0].nodePort}" services apisix-gateway)
- export NODE_IP=$(kubectl get nodes --namespace apisix -o jsonpath="{.items[0].status.addresses[0].address}")
- echo http://$NODE_IP:$NODE_PORT
待所有的 Pod 处于 Running 状态便表示已经部署成功。
- tao@moelove:~$ kubectl -n apisix get pods
- NAME READY STATUS RESTARTS AGE
- apisix-777c9fdd67-rf8zs 1/1 Running 0 6m48s
- apisix-etcd-0 1/1 Running 0 6m48s
- apisix-etcd-1 1/1 Running 0 6m48s
- apisix-etcd-2 1/1 Running 0 6m48s
- apisix-ingress-controller-568544b554-k7nd4 1/1 Running 0 6m48s
部署 Vault
部署 Vault 的时候,也可以使用 Helm 。这里我增加了一个 --set "server.dev.enabled=true"
配置项,这样在部署后无需进额外操作便可直接使用了。(注意这个配置不要用到生产环境)
- tao@moelove:~$ helm repo add hashicorp https://helm.releases.hashicorp.com
- tao@moelove:~$ helm install vault hashicorp/vault --set "injector.enabled=false" --set "server.dev.enabled=true"
- NAME: vault
- LAST DEPLOYED: Wed Oct 19 21:53:50 2022
- NAMESPACE: default
- STATUS: deployed
- REVISION: 1
- NOTES:
- Thank you for installing HashiCorp Vault!
-
-
- Now that you have deployed Vault, you should look over the docs on using
- Vault with Kubernetes available here:
-
-
- https://www.vaultproject.io/docs/
-
-
- Your release is named vault. To learn more about the release, try:
-
-
- $ helm status vault
- $ helm get manifest vault
在部署完成后,当 Pod 处于 Running 状态时,说明已经部署完成。
- tao@moelove:~$ kubectl get pods
- NAME READY STATUS RESTARTS AGE
- vault-0 1/1 Running 0 29s
- tao@moelove:~$ kubectl get svc
- NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
- kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 84m
- vault ClusterIP 10.96.190.88 <none> 8200/TCP,8201/TCP 4m14s
- vault-internal ClusterIP None <none> 8200/TCP,8201/TCP 4m14s
接下来进入 Vault 内进行操作,此处开启了 pki 的能力,并且配置了对应的 policy。
- tao@moelove:~$ kubectl exec -it vault-0 -- sh
- / $ vault secrets enable pki
- Success! Enabled the pki secrets engine at: pki/
- / $ vault write pki/root/generate/internal common_name=moelove.info ttl=8760h
- Key Value
- --- -----
- certificate -----BEGIN CERTIFICATE-----
- MIIDODCCAiCgAwIBAgIUds5uMJV9rOkwFEt6Xof5T2SVFccwDQYJKoZIhvcNAQEL
- ...
- VM4DRVgDkqY9JdHU
- -----END CERTIFICATE-----
- expiration 1668983612
- issuer_id 8df13015-7c70-df9a-7bb7-9b3b4afe7f82
- issuer_name n/a
- issuing_ca -----BEGIN CERTIFICATE-----
- MIIDODCCAiCgAwIBAgIUds5uMJV9rOkwFEt6Xof5T2SVFccwDQYJKoZIhvcNAQEL
- ...
- VM4DRVgDkqY9JdHU
- -----END CERTIFICATE-----
- key_id c9fcfcb0-3548-a9a7-e706-30510592c797
- key_name n/a
- serial_number 76:ce:6e:30:95:7d:ac:e9:30:14:4b:7a:5e:87:f9:4f:64:95:15:c7
- / $
- / $ vault write pki/config/urls issuing_certificates="http://vault.default:8200/v1/pki/ca" crl_distribution_points="http://vault.default:8200/v1/pki/crl"
- Success! Data written to: pki/config/urls
- / $ vault write pki/roles/moelove-dot-info allowed_domains=moelove.info allow_subdomains=true max_ttl=72h
- Success! Data written to: pki/roles/moelove-dot-info
- / $
- / $ vault policy write pki - <<EOF
- > path "pki*" { capabilities = ["read", "list"] }
- > path "pki/sign/moelove-dot-info" { capabilities = ["create", "update"] }
- > path "pki/issue/moelove-dot-info" { capabilities = ["create"] }
- > EOF
- Success! Uploaded policy: pki
接下来,配置 Kubernetes 认证:
- / $ vault auth enable kubernetes
- Success! Enabled kubernetes auth method at: kubernetes/
- / $ vault write auth/kubernetes/config kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443"
- Success! Data written to: auth/kubernetes/config
- / $ vault write auth/kubernetes/role/issuer bound_service_account_names=issuer bound_service_account_namespaces=default policies=pki ttl=20m
- Success! Data written to: auth/kubernetes/role/issuer
完成上述操作后,下面部署 cert-manager。
部署 cert-manager
现在可以通过 Helm 安装 cert-manager 了,安装的过程也比较简单。
- tao@moelove:~$ helm repo add jetstack https://charts.jetstack.io
- tao@moelove:~$ helm repo update jetstack
- Hang tight while we grab the latest from your chart repositories...
- ...Successfully got an update from the "jetstack" chart repository
- Update Complete. ⎈Happy Helming!⎈
- tao@moelove:~$ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.10.0/cert-manager.crds.yaml
- customresourcedefinition.apiextensions.k8s.io/clusterissuers.cert-manager.io created
- customresourcedefinition.apiextensions.k8s.io/challenges.acme.cert-manager.io created
- customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created
- customresourcedefinition.apiextensions.k8s.io/issuers.cert-manager.io created
- customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created
- customresourcedefinition.apiextensions.k8s.io/orders.acme.cert-manager.io created
- tao@moelove:~$ helm install \
- > cert-manager jetstack/cert-manager \
- > --namespace cert-manager \
- > --create-namespace \
- > --version v1.10.0
-
-
- xNAME: cert-manager
- LAST DEPLOYED: Wed Oct 19 22:51:06 2022
- NAMESPACE: cert-manager
- STATUS: deployed
- REVISION: 1
- TEST SUITE: None
- NOTES:
- cert-manager v1.10.0 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/
接下来检查 Pod 的状态:
- tao@moelove:~$ kubectl -n cert-manager get pods
- NAME READY STATUS RESTARTS AGE
- cert-manager-69b456d85c-znpq4 1/1 Running 0 117s
- cert-manager-cainjector-5f44d58c4b-wcd27 1/1 Running 0 117s
- cert-manager-webhook-566bd88f7b-7rptf 1/1 Running 0 1
接下来便可以开始配置和验证了。
如何配置及验证
- tao@moelove:~$ kubectl create serviceaccount issuer
- serviceaccount/issuer created
- tao@moelove:~$ kubectl get secret
- NAME TYPE DATA AGE
- sh.helm.release.v1.vault.v1 helm.sh/release.v1 1 36m
- tao@moelove:~$ vim issuer-secret.yaml
- tao@moelove:~$ cat issuer-secret.yaml
- apiVersion: v1
- kind: Secret
- metadata:
- name: issuer-token-moelove
- annotations:
- kubernetes.io/service-account.name: issuer
- type: kubernetes.io/service-account-token
- tao@moelove:~$ kubectl apply -f issuer-secret.yaml
- secret/issuer-token-moelove created
- tao@moelove:~$ kubectl get sa,secret
- NAME SECRETS AGE
- serviceaccount/default 0 118m
- serviceaccount/issuer 0 2m11s
- serviceaccount/vault 0 38m
-
-
- NAME TYPE DATA AGE
- secret/issuer-token-moelove kubernetes.io/service-account-token 3 35s
- secret/sh.helm.release.v1.vault.v1 helm.sh/release.v1 1 38m
创建 Issuer
通过此配置将使用 Vault 作为证书签发机构,通过引用在 Vault 中配置的 role 和 secret 等,进行自动的签发。
tao@moelove:~$ cat vault-issuer.yaml apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: vault-issuer namespace: default spec: vault: server: http://vault.default path: pki/sign/moelove-dot-info auth: kubernetes: mountPath: /v1/auth/kubernetes role: moelove-dot-info secretRef: name: issuer-token-moelove key: token tao@moelove:~$ kubectl apply -f vault-issuer.yaml issuer.cert-manager.io/vault-issuer created
创建证书
通过此处的配置即可自动的签发证书,并在后续使用时候可以通过 moelove-info-tls
进行引用。
- tao@moelove:~$ cat moelove-dot-info-cert.yaml
- apiVersion: cert-manager.io/v1
- kind: Certificate
- metadata:
- name: moelove-info
- namespace: default
- spec:
- secretName: moelove-info-tls
- issuerRef:
- name: vault-issuer
- commonName: www.moelove.info
- dnsNames:
- - www.moelove.info
- tao@moelove:~$ kubectl apply -f moelove-dot-info-cert.yaml
- certificate.cert-manager.io/moelove-info created
接下来通过代理一个 HTTPBIN 的服务进行验证。
首先创建一个 HTTPBIN 的应用程序,并创建相应的 Service。
- kubectl run httpbin --image kennethreitz/httpbin
- kubectl expose pod httpbin --port=80
然后定义如下资源进行代理和引用证书:
# Define ApisixTls Objects apiVersion: apisix.apache.org/v2 kind: ApisixTls metadata: name: moelove spec: hosts: - moelove.info secret: name: moelove-info-tls --- # Define the route to access the backend apiVersion: apisix.apache.org/v2 kind: ApisixRoute metadata: name: moelove spec: http: - name: httpbin match: paths: - /* hosts: - moelove.info backends: - serviceName: httpbin servicePort: 80
将这些资源应用到集群中即可。然后通过 kubectl port-forward
转发 APISIX 的 443 端口到本地后, 进行测试访问:
- $ ~ kubectl port-forward -n ingress-apisix svc/apisix-gateway 8443:443 &
- $ ~ curl -sk https://moelove.info:8443/ip --resolve 'moelove.info:8443:127.0.0.1'
- {
- "origin": "172.17.18.1"
- }
可以看到,已经正确的为 moelove.info
这个域名配置了 HTTPS 证书,并且通过 APISIX Ingress 为其配置了代理。
总结
本文中介绍了 Kubernetes 中证书的默认存储方式,以及这种方式存在的一些痛点。cert-manager 的出现比较好的解决了这些问题,逐步成为了 Kubernetes 生态中证书签发/管理领域中的事实标准。并且其可以和 Vault 等工具进行集成,更加的安全。
Apache APISIX Ingress 项目致力于打造更好用的 Ingress controller,所以很早就添加了完善的 cert-manager 集成能力。本篇中也通过理论加实践的方式为读者介绍了如何在 Apache APISIX Ingress 中配合使用 cert-manager 通过 Vault 签发的证书,并为应用程序提供 HTTPS 代理。希望对读者能有所帮助。
[1] https://cert-manager.io/
[2] https://www.jetstack.io/
[3] https://www.openssl.org/docs/faq.html
[4] https://github.com/Skyscanner/kms-issuer
[5] https://github.com/jetstack/google-cas-issuer
[6] https://github.com/cloudflare/origin-ca-issuer
[7] https://developers.cloudflare.com/ssl/origin-configuration/origin-ca
11
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。