¿Cómo usar cert-manager y HashiCorp Vault para gestionar certificados?
Jintao Zhang
March 2, 2023
¿Qué problema resuelve cert-manager?
Cert-manager fue lanzado como código abierto por JETSTACK en 2017 y posteriormente donado a la CNCF (Cloud Native Computing Foundation), convirtiéndose en un proyecto de nivel sandbox. En octubre de 2022, se convirtió en un proyecto en incubación de la CNCF.
cert-manager
puede gestionar automáticamente los certificados x.509 en Kubernetes y OpenShift. Hace que los certificados y las solicitudes de firma de certificados sean el primer tipo de recursos admitidos en Kubernetes, un proceso implementado mediante CRD. Además, cert-manager permite a los desarrolladores solicitar certificados para mejorar rápidamente la seguridad del acceso a las aplicaciones.
Veamos cómo se gestionaban los certificados en Kubernetes antes de la aparición de cert-manager.
Cómo se gestionaban los certificados en Kubernetes
Existen principalmente dos formas nativas de almacenar datos en Kubernetes:
- ConfigMap
- Secret
Sin embargo, toda la información en ConfigMap está en texto plano. Por lo tanto, es adecuado para almacenar información de configuración común, pero no es adecuado para información privada como los certificados.
Cuando se diseñó Kubernetes, se recomendó usar Secret
para almacenar información relevante como certificados, y también se proporcionó soporte para esto. Podemos almacenar fácilmente la información del certificado a través de kubectl create secret tls
. Por ejemplo:
➜ ~ 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
A través del comando anterior, se crea un recurso Secret llamado moelove-tls
de tipo kubernetes.io/tls
en Kubernetes.
Este recurso puede ser referenciado directamente para obtener la información del certificado correspondiente si la aplicación necesita usarlo. En la mayoría de los casos, lo usaremos en el contexto del Ingress Controller. Por ejemplo:
➜ ~ 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: {}
A través del comando anterior, se crea un recurso Ingress llamado moelove-ing
. Su dominio se declara como moelove.info
, y se agrega protección de certificado a este dominio usando moelove-tls
. Después de que el componente Ingress Controller correspondiente obtenga el recurso Ingress, el componente puede configurar automáticamente el certificado para este dominio, mejorando así la seguridad del sitio web.
¿Qué problemas hemos encontrado?
Emisión engorrosa de certificados
En el contenido anterior, no demostré cómo emitir certificados. Si estás interesado, puedes consultar la Documentación de OpenSSL. Durante el proceso de emisión de certificados, es necesario comprender muchos conceptos. Además, el proceso de firma ocurre fuera del clúster de Kubernetes, y es imposible entender lo que sucedió específicamente a través del método de configuración "declarativo". En particular, los certificados pueden tener muchos algoritmos de cifrado, configuraciones, etc.
Por lo tanto, si usas el método predeterminado, solo puedes almacenar el certificado y la clave generados en Kubernetes Secrets.
Renovación/Re-firma engorrosa de certificados
Todos sabemos que los certificados tienen una fecha de expiración. Antes de que el certificado expire o sea revocado, se debe preparar un nuevo certificado, y la fecha de expiración del nuevo debe ser posterior a la del anterior.
La gestión de certificados en Kubernetes Secrets necesita mejoras porque:
-
No hay una verificación automática de la fecha de expiración: Puedes almacenar certificados arbitrarios en Kubernetes, independientemente de si el certificado ha expirado o no.
-
No hay verificación de datos inválidos: Si los datos almacenados en Kubernetes Secrets están corruptos o son inválidos, no hay un manejo especial en Kubernetes.
Falta de seguridad
La información del certificado y la clave almacenada en Kubernetes Secrets solo está codificada en base64. Por lo tanto, cualquiera que obtenga los datos puede decodificarlos en base64 para obtener los datos reales. Por ejemplo:
➜ ~ 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-----
Los datos originales relacionados con el certificado se pueden obtener a través del comando anterior.
Por otro lado, cuando queremos actualizar los datos del certificado y la clave, se pueden actualizar directamente sin ningún proceso de confirmación secundaria.
Esto es inconsistente con las políticas de seguridad en la mayoría de los escenarios.
A continuación, veamos cómo cert-manager resuelve estos problemas.
Cómo cert-manager resuelve estos problemas
Emisión automática
Cert-manager se desarrolla y extiende a través de CRD, lo que agrega e implementa los recursos Issuers
y ClusterIssuers
, que representan la CA (autoridad de certificación) del certificado.
También admite una variedad de tipos integrados y puede integrarse fácilmente con componentes externos, como:
-
SelfSigned: Certificado autofirmado
-
CA: Proporcionar CA para la emisión
-
Vault: Usar HashiCorp Vault para la emisión
-
Venafi: Usar Venafi para la emisión
-
External: Usar algunos componentes externos para la firma, como:
-
ACME (Entorno de gestión de certificados automatizado)
Estos componentes se pueden usar para emitir certificados de manera conveniente. El contenido posterior tomará Vault como ejemplo para una introducción específica.
Renovación/Re-firma automática
En cert-manager, podemos renovar fácilmente el certificado manualmente a través de cmctl
, y al mismo tiempo, cert-manager verificará automáticamente la validez y la integridad del certificado.
Si el certificado expira o los datos del certificado están incompletos, puede desencadenar automáticamente la re-emisión del certificado, ahorrando costos de mano de obra y mantenimiento.
Garantía de seguridad
En cert-manager, se agrega el recurso signers
a través de CRD (CustomResourceDefinitions), lo que permite que la solicitud de certificado sea confirmada, Approved
o Denied
. Solo después de la aprobación entrará en vigencia y se emitirá el certificado. Es una forma más segura.
Cómo APISIX Ingress Controller se integra con cert-manager
Instalación
Apache APISIX Ingress Controller es un controlador de Ingress de Kubernetes que puede admitir la configuración de reglas de proxy a través de Ingress, recursos personalizados y Gateway API.
A continuación, demostraremos cómo integrar APISIX Ingress Controller con cert-manager para agregar un certificado TLS al nombre de dominio del proxy y mejorar la seguridad.
Al mismo tiempo, usaremos Vault para emitir certificados.
Desplegar APISIX Ingress Controller
Desplegar APISIX Ingress Controller es muy simple: solo necesitas realizar los siguientes pasos:
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
Cuando todos los Pods estén en estado "Running", el despliegue habrá sido exitoso.
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
Desplegar Vault
Al desplegar Vault, también se puede usar Helm. Aquí agregué una configuración --set "server.dev.enabled=true"
para que pueda usarse directamente después del despliegue sin operaciones adicionales. (Nota: esta configuración no debe usarse en un entorno de producción).
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
Después de completar el despliegue, el Pod en estado "Running" demuestra que el despliegue ha sido completado.
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
A continuación, ingresa a Vault para operar, donde se habilita la capacidad pki y se configura la política correspondiente.
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
A continuación, configura la autenticación de 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
Después de completar las operaciones anteriores, el siguiente paso es desplegar cert-manager.
Desplegar cert-manager
Ahora puedes instalar cert-manager a través de Helm, y el proceso de instalación es relativamente simple.
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/
Verifica el estado de los Pods:
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
Luego podemos comenzar la configuración y validación.
¿Cómo configurar y validar?
Configurar y emitir certificados
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
Crear Issuer
A través de esta configuración, Vault se usará como la autoridad de certificación, y la emisión automática se realizará haciendo referencia al rol y al secreto configurados en 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
Crear Certificado
A través de esta configuración, el certificado se puede emitir automáticamente y se puede referenciar a través de moelove-info-tls
durante su uso posterior.
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
Validación
A continuación, verificaremos mediante la creación de un servicio HTTPBIN.
Primero, crea una aplicación HTTPBIN y el Service correspondiente.
kubectl run httpbin --image kennethreitz/httpbin kubectl expose pod httpbin --port=80
Luego define los siguientes recursos para el proxy y la referencia de certificados:
# 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
Aplica estos recursos al clúster. Luego usa kubectl port-forward
para redirigir el puerto 443 de APISIX a local y realiza una prueba de acceso:
$ ~ 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" }
Se puede ver que el certificado HTTPS se ha configurado correctamente para el dominio moelove.info
, y se ha configurado un proxy para él a través de APISIX Ingress Controller.
Conclusión
Existen dos métodos de almacenamiento predeterminados de certificados en Kubernetes: ConfigMap y Secret. Sin embargo, la emisión y renovación/re-firma de certificados son engorrosas, y la seguridad necesita mejoras.
Cert-manager resolvió estos problemas y se convirtió gradualmente en el estándar de facto en el campo de la emisión/gestión de certificados en el ecosistema de Kubernetes. Además, puede integrarse con herramientas como Vault, lo que es más seguro.
Apache APISIX Ingress Controller está comprometido a crear un controlador de Ingress fácil de usar, por lo que se agregó tempranamente una capacidad completa de integración con cert-manager. Los usuarios pueden usar cert-manager para emitir certificados a través de Vault en Apache APISIX Ingress Controller y proporcionar un proxy HTTPS para las aplicaciones.