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