Como Usar o cert-manager e o HashiCorp Vault para Gerenciar Certificados?
Jintao Zhang
March 2, 2023
Qual Problema o cert-manager Resolve
Lançado como código aberto pela JETSTACK em 2017, o cert-manager foi doado à CNCF (Cloud Native Computing Foundation) e se tornou um projeto de nível sandbox. Em outubro de 2022, tornou-se um projeto em incubação da CNCF.
O cert-manager
pode gerenciar automaticamente os certificados x.509 no Kubernetes e OpenShift. Ele torna os certificados e as solicitações de assinatura de certificados o primeiro tipo de recurso suportado no Kubernetes, processo que foi implementado por CRD. Além disso, o cert-manager permite que os desenvolvedores solicitem um certificado para melhorar rapidamente a segurança de acesso ao aplicativo.
Vamos dar uma olhada em como os certificados eram gerenciados no Kubernetes antes do cert-manager aparecer.
Como os Certificados Eram Gerenciados no Kubernetes
Existem principalmente duas maneiras nativas de armazenar dados no Kubernetes:
- ConfigMap
- Secret
No entanto, todas as informações no ConfigMap são em texto simples. Portanto, armazenar algumas informações de configuração relativamente comuns é aceitável; mas não é adequado para informações privadas como certificados.
Quando o Kubernetes foi projetado, foi recomendado usar Secret
para armazenar informações relevantes, como certificados, e ele também fornece suporte para isso. Podemos facilmente armazenar informações de certificados através de kubectl create secret tls
. Por exemplo:
➜ ~ 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
Através do comando acima, um recurso Secret chamado moelove-tls
, do tipo kubernetes.io/tls
, é criado no Kubernetes.
Esse recurso pode ser diretamente referenciado para obter as informações correspondentes do certificado, se o aplicativo precisar usá-lo. Na maioria dos casos, usaremos isso no cenário do Ingress Controller. Por exemplo:
➜ ~ 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: {}
Através do comando acima, um recurso Ingress chamado moelove-ing
é criado. Seu domínio é declarado como moelove.info
, e a proteção de certificado é adicionada a esse domínio usando moelove-tls
. Após o componente Ingress Controller correspondente obter o recurso Ingress, o componente pode configurar automaticamente o certificado para esse domínio, melhorando assim a segurança do site.
Quais Problemas Enfrentamos
Emissão de Certificados Trabalhosa
No conteúdo acima, não demonstrei como emitir certificados. Se estiver interessado, você pode verificar a Documentação do OpenSSL. Durante o processo de emissão de certificados, muitos conceitos precisam ser entendidos. Além disso, o processo de assinatura ocorre fora do cluster Kubernetes, e é impossível entender o que aconteceu especificamente através do método de configuração "declarativo". Em particular, os certificados podem ter muitos algoritmos de criptografia, configurações, etc.
Portanto, se você usar o método padrão, só poderá armazenar o certificado e a chave gerados no Kubernetes Secrets.
Renovação/Reassinação de Certificados Trabalhosa
Sabemos que os certificados têm um prazo de validade. Antes que o certificado expire ou seja revogado, um novo certificado deve ser preparado, e o prazo de validade do novo deve ser posterior ao do antigo.
O gerenciamento de certificados no Kubernetes Secrets precisa ser melhorado porque:
-
Não há verificação automática do prazo de validade: Você pode armazenar certificados arbitrários no Kubernetes, independentemente de o certificado ter expirado ou não.
-
Não há verificação de dados inválidos: Se os dados armazenados no Kubernetes Secrets estiverem corrompidos ou inválidos, não há tratamento especial no Kubernetes.
Falta de Segurança
O certificado e as informações críticas armazenadas no Kubernetes Secrets são apenas codificadas em base64. Portanto, qualquer pessoa que obtenha os dados pode decodificá-los em base64 para obter os dados reais. Por exemplo:
➜ ~ 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-----
Os dados originais relacionados ao certificado podem ser obtidos através do comando acima.
Por outro lado, quando queremos atualizar os dados do certificado e da chave, eles podem ser atualizados diretamente sem qualquer processo de confirmação secundária.
Isso é inconsistente com as políticas de segurança na maioria dos cenários.
A seguir, vamos ver como o cert-manager resolve esses problemas.
Como o cert-manager Resolve Esses Problemas
Emissão Automática
O cert-manager é desenvolvido e estendido através de CRD, que adiciona e implementa os recursos Issuers
e ClusterIssuers
, representando a CA (autoridade certificadora) do certificado.
Ele também suporta uma variedade de tipos internos e pode ser facilmente integrado com componentes externos, como:
-
SelfSigned: Certificado autoassinado
-
CA: Fornecer CA para emissão
-
Vault: Usar HashiCorp Vault para emissão
-
Venafi: Usar Venafi para emissão
-
External: Usar alguns componentes externos para assinatura, como:
-
ACME (Ambiente de Gerenciamento de Certificados Automatizado)
Esses componentes podem ser usados para emitir certificados de forma conveniente. O conteúdo subsequente usará o Vault como exemplo para uma introdução específica.
Renovação/Reassinação Automática
No cert-manager, podemos facilmente renovar o certificado manualmente através do cmctl
, e ao mesmo tempo, o cert-manager verificará automaticamente o prazo de validade e a integridade do certificado.
Se o certificado expirar ou os dados do certificado estiverem incompletos, ele pode automaticamente acionar a reemissão do certificado, economizando custos de mão de obra e manutenção.
Garantia de Segurança
No cert-manager, o recurso signers
é adicionado através de CRD (CustomResourceDefinitions), o que permite que a solicitação de certificado seja confirmada, Aprovada
ou Negada
. Somente após a aprovação, ele entrará em vigor e o certificado será emitido. É uma maneira mais segura.
Como o APISIX Ingress Controller se Integra com o cert-manager
Instalação
O Apache APISIX Ingress Controller é um Kubernetes Ingress Controller que pode suportar a configuração de regras de proxy através de Ingress, recursos personalizados e Gateway API.
A seguir, demonstraremos como integrar o APISIX Ingress Controller com o cert-manager para adicionar um certificado TLS ao domínio do agente, melhorando a segurança.
Ao mesmo tempo, usaremos o Vault para emitir certificados.
Implantar o APISIX Ingress Controller
A implantação do APISIX Ingress Controller é muito simples: você só precisa executar os seguintes passos:
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
Quando todos os Pods estiverem no estado de execução, a implantação será bem-sucedida.
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
Implantar o Vault
Ao implantar o Vault, o Helm também pode ser usado. Aqui, adicionei um --set "server.dev.enabled=true"
para que ele possa ser usado diretamente após a implantação, sem operações adicionais. (Observe que essa configuração não deve ser usada em um ambiente de produção.)
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
Após concluir a implantação, o Pod no estado de execução demonstra que a implantação foi concluída.
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
Em seguida, entre no Vault para operar, onde a capacidade pki é habilitada e a política correspondente é configurada.
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
Em seguida, configure a autenticação do 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
Após concluir as operações acima, o próximo passo é implantar o cert-manager.
Implantar o cert-manager
Agora você pode instalar o cert-manager através do Helm, e o processo de instalação é relativamente simples.
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/
Verifique o status do 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
Então podemos começar a configuração e validação.
Como Configurar e Validar?
Configurar e 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
Criar Issuer
Através dessa configuração, o Vault será usado como a autoridade certificadora, e a emissão automática será realizada referenciando a função e o segredo configurados no 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
Criar Certificado
Através da configuração aqui, o certificado pode ser emitido automaticamente e pode ser referenciado através de moelove-info-tls
durante o uso subsequente.
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
Validação
A seguir, valide por meio do proxy de um serviço HTTPBIN.
Primeiro, crie um aplicativo HTTPBIN e crie o Service correspondente.
kubectl run httpbin --image kennethreitz/httpbin kubectl expose pod httpbin --port=80
Em seguida, defina os seguintes recursos para proxy e referência de certificados:
# Definir Objetos ApisixTls apiVersion: apisix.apache.org/v2 kind: ApisixTls metadata: name: moelove spec: hosts: - moelove.info secret: name: moelove-info-tls --- # Definir a rota para acessar o 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
Aplique esses recursos ao cluster. Em seguida, use kubectl port-forward
para encaminhar a porta 443 do APISIX para o local e realize o teste de acesso:
$ ~ 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" }
Pode-se ver que o certificado HTTPS foi corretamente configurado para o domínio moelove.info
, e um proxy foi configurado para ele através do APISIX Ingress Controller.
Conclusão
Existem duas maneiras padrão de armazenar certificados no Kubernetes: ConfigMap e Secret. No entanto, a emissão e renovação/reassinação de certificados são trabalhosas, e a segurança precisa ser melhorada.
O cert-manager resolveu esses problemas e gradualmente se tornou o padrão de fato no campo de emissão/gerenciamento de certificados no ecossistema Kubernetes. Além disso, ele pode ser integrado com ferramentas como o Vault, o que é mais seguro.
O Apache APISIX Ingress Controller está comprometido em criar um Ingress Controller amigável, então uma capacidade completa de integração com o cert-manager foi adicionada desde cedo. Os usuários podem usar o cert-manager para emitir certificados através do Vault no Apache APISIX Ingress Controller e fornecer proxy HTTPS para aplicativos.