Как использовать cert-manager и HashiCorp Vault для управления сертификатами?

Jintao Zhang

March 2, 2023

Products

Какую проблему решает cert-manager

Проект cert-manager, открытый компанией JETSTACK в 2017 году, был передан в CNCF (Cloud Native Computing Foundation) и стал проектом уровня "песочницы". В октябре 2022 года он стал инкубируемым проектом CNCF.

cert-manager может автоматически управлять x.509 сертификатами в Kubernetes и OpenShift. Он делает сертификаты и запросы на подпись сертификатов первыми поддерживаемыми ресурсами в Kubernetes, что реализовано через CRD. Кроме того, cert-manager позволяет разработчикам быстро запрашивать сертификаты для повышения безопасности доступа к приложениям.

Давайте рассмотрим, как управлялись сертификаты в Kubernetes до появления cert-manager.

Как управлялись сертификаты в 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 создается ресурс Secret с именем moelove-tls и типом kubernetes.io/tls.

Этот ресурс может быть напрямую использован для получения соответствующей информации о сертификате, если приложению это необходимо. В большинстве случаев мы будем использовать его в контексте Ingress Controller. Например:

➜ ~ 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: {}

С помощью вышеуказанной команды создается ресурс Ingress с именем moelove-ing. Его доменное имя объявлено как moelove.info, и для этого доменного имени добавлена защита сертификатом с использованием moelove-tls. После того как соответствующий компонент Ingress Controller получит ресурс Ingress, он сможет автоматически настроить сертификат для этого доменного имени, тем самым повысив безопасность сайта.

С какими проблемами мы столкнулись

Сложность выдачи сертификатов

В вышеуказанном контенте я не демонстрировал, как выдаются сертификаты. Если вам интересно, вы можете ознакомиться с документацией OpenSSL. В процессе выдачи сертификатов необходимо понимать множество концепций. Более того, процесс подписания происходит вне кластера Kubernetes, и невозможно понять, что именно произошло, через "декларативный" метод конфигурации. В частности, сертификаты могут иметь множество различных алгоритмов шифрования, конфигураций и т.д.

Таким образом, если вы используете стандартный метод, вы можете только хранить сгенерированный сертификат и ключ в Kubernetes Secrets.

Сложность обновления/переподписания сертификатов

Мы все знаем, что сертификаты имеют срок действия. До истечения срока действия или отзыва сертификата необходимо подготовить новый сертификат, и срок действия нового сертификата должен быть позже, чем у старого.

Управление сертификатами в Kubernetes Secrets нуждается в улучшении, потому что:

  • Нет автоматической проверки срока действия: Вы можете хранить произвольные сертификаты в Kubernetes, независимо от того, истек ли срок действия сертификата или нет.

  • Нет проверки на недействительные данные: Если данные, хранящиеся в Kubernetes Secrets, повреждены или недействительны, в Kubernetes нет специальной обработки.

Недостаток безопасности

Сертификат и ключевая информация, хранящиеся в Kubernetes Secrets, только закодированы в 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: Использование HashiCorp Vault для выдачи

  • Venafi: Использование Venafi для выдачи

  • External: Использование некоторых внешних компонентов для подписания, таких как:

  • ACME (Automated Certificate Management Environment)

Эти компоненты могут быть использованы для удобной выдачи сертификатов. В последующем контенте будет приведен пример использования Vault для более подробного объяснения.

Автоматическое обновление/переподписание

В cert-manager мы можем легко обновить сертификат вручную через cmctl, и в то же время cert-manager автоматически проверяет срок действия и целостность сертификата.

Если срок действия сертификата истекает или данные сертификата неполны, это может автоматически вызвать повторную выдачу сертификата, что экономит трудозатраты и затраты на обслуживание.

Гарантия безопасности

В cert-manager через CRD (CustomResourceDefinitions) добавлен ресурс signers, который позволяет подтверждать запрос на сертификат, Approved или Denied. Только после подтверждения он вступит в силу, и сертификат будет выдан. Это более безопасный способ.

Как APISIX Ingress Controller интегрируется с cert-manager

Установка

Apache APISIX Ingress Controller — это Kubernetes Ingress Controller, который может поддерживать настройку правил прокси через Ingress, пользовательские ресурсы и Gateway API.

Далее мы покажем, как интегрировать APISIX Ingress Controller с cert-manager, чтобы добавить TLS-сертификат к доменному имени прокси для повышения безопасности.

В то же время мы используем Vault для выдачи сертификатов.

Развертывание APISIX Ingress Controller

Развертывание APISIX Ingress Controller очень просто: вам нужно выполнить следующие шаги:

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

Когда все Pods находятся в состоянии 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 the following: $ 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 и настраивается соответствующая политика.

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

Теперь вы можете установить cert-manager через Helm, и процесс установки относительно прост.

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 117s

Теперь мы можем начать настройку и проверку.

Как настроить и проверить?

Настройка и выдача сертификатов

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.

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

Затем определим следующие ресурсы для проксирования и ссылки на сертификаты:

# Определение объектов ApisixTls apiVersion: apisix.apache.org/v2 kind: ApisixTls metadata: name: moelove spec: hosts: - moelove.info secret: name: moelove-info-tls --- # Определение маршрута для доступа к бэкенду 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 для перенаправления порта 443 APISIX на локальный компьютер и выполним тестовый доступ:

$ ~ 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" }

Видно, что HTTPS-сертификат был правильно настроен для доменного имени moelove.info, и для него был настроен прокси через APISIX Ingress Controller.

Заключение

В Kubernetes существует два стандартных способа хранения сертификатов: ConfigMap и Secret. Однако процесс выдачи и обновления/переподписания сертификатов сложен, и безопасность нуждается в улучшении.

Cert-manager решает эти проблемы и постепенно становится де-факто стандартом в области выдачи/управления сертификатами в экосистеме Kubernetes. Кроме того, он может быть интегрирован с такими инструментами, как Vault, что делает его более безопасным.

Apache APISIX Ingress Controller стремится создать удобный Ingress Controller, поэтому на раннем этапе была добавлена полная возможность интеграции с cert-manager. Пользователи могут использовать cert-manager для выдачи сертификатов через Vault в Apache APISIX Ingress Controller и предоставлять HTTPS-прокси для приложений.

Tags: