
No Kubernetes, a ideia é automatizar e evitar duplicação de código. Consumir secrets de um secret manager deve seguir a mesma lógica. Veja como fazer isso na prática.
Este post dá sequência ao meu post anterior sobre como automatizar a injeção de secrets em Pods Kubernetes quando se usa o Vault.
No Kubernetes, os secrets continuam sendo texto puro codificado em base64.
Dá para criptografar os secrets em repouso, mas eles não ficam criptografados no próprio Pod, e a chave de criptografia é global para o Etcd (onde os secrets são armazenados). Isso gera um risco em um cluster multi-tenant, em que todos os secrets são criptografados com a mesma chave global.
Injete secrets da AWS, GCP ou Vault em um Pod Kubernetes com segurança
O GCP KMS permite criar uma envelope key na camada da aplicação para resolver esse problema. Mesmo assim, no fim das contas, o Pod acaba consumindo os secrets em texto puro.
Além do Hashicorp Vault, existem os secret managers da AWS e do GCP.
AWS e GCP não oferecem tantos recursos quanto o Vault — como backends de autenticação, auditoria de acesso a secrets, revogação ou até secrets dinâmicos —, mas oferecem uma forma parecida de armazenar secrets com versionamento.
Desafio: consumir secrets em Pods Kubernetes
Consumir esses secrets em um Pod Kubernetes traz um desafio: você precisa escrever um script wrapper customizado para lidar com a autenticação e o consumo dos secrets como variáveis de ambiente. Depois, é preciso substituir esse script pelo processo wrapper, para que ele herde todas as variáveis de ambiente e assuma o PID 1 — fundamental para tratar corretamente os sinais de término em containers.
No Kubernetes, a ideia é automatizar e reduzir a duplicação de código necessária para gerenciar esse tipo de tarefa de apoio (outro exemplo seria automatizar DNS e certificados). Consumir secrets de um secret manager no Kubernetes deve seguir a mesma lógica.
Algumas empresas preferem não manter uma instalação do Vault e, só por isso, optam pelos secret managers da AWS ou do GCP.
Os webhooks do Kubernetes que você precisa conhecer
O Kubernetes tem um admission controller nativo, capaz de interceptar requisições ao API server antes da persistência do objeto, mas depois que a requisição é autenticada e autorizada.
Existem dois controllers especiais embutidos no binário kube-apiserver:
- MutatingAdmissionWebhook
- ValidatingAdmissionWebhook
Eles são responsáveis pela mutação e validação das requisições, depois da autenticação e autorização.
Os controllers de mutação podem modificar os objetos que admitem, enquanto os de validação podem ou não admitir um objeto no API server.
Esses objetos de webhook são instalados como qualquer outro objeto do Kubernetes, via kubectl (eles consistem no endereço https da lógica do webhook em si).
Uma vez instalados, toda requisição passa por essa lógica.
Mutation Webhook para consumo de secrets
O código e o conhecimento a seguir se baseiam fortemente no excelente trabalho da Banzai Cloud, e quero dar todo o crédito a eles (vale muito a pena ler o blog deles, é excelente!).
As principais diferenças entre os webhooks da Banzai Cloud e os meus são:
- Suporte aos secret managers da AWS, GCP e Vault.
- Remoção dos consulTemplates e do recurso de secrets dinâmicos do webhook da Banzai Cloud.
- Possibilidade de autenticar no Vault não só com um backend Kubernetes, mas também com um backend GCP.
- Usar secrets explícitos do secret manager ou trazer todos os secrets de uma vez.
- Usar o caminho do secret como diretório e buscar todos os secrets dentro dele (limitado ao primeiro nível).
- Usar nomes de secrets como chaves quando o caminho do secret termina com
/e cada valor é um valor único:
O caso de uso é melhorar o gerenciamento do conteúdo dos secrets — reduzindo a chance de erro ao adicionar ou editar um secret e diminuindo o número de etapas necessárias (ler, anexar, atualizar). 7. Usar caminho de secret com curinga; por exemplo, se o caminho terminar com / e houver alguns secrets que começam com db_, o caminho pode ser /secret/path/db_*. 8. Detecção automática se o secret é KV1 ou KV2 (não é mais preciso prefixar /data no caminho do secret).
Como o Mutation Webhook funciona
Toda nova requisição ao API server passa por este webhook.
O webhook verifica se o objeto é um Pod e só faz a mutação se ele tiver annotations específicas, por exemplo:
https://gist.github.com/innovia/3e58eeb38668c9f4da4f1f76a736fbca
Quando encontra essas annotations, ele modifica o objeto Pod assim:
- Adiciona um volume compartilhado em memória
- Adiciona um init container com o binário vault-env e um comando para copiar o vault-env para esse volume compartilhado
- Altera o comando do Pod para vault-env
- Adiciona variáveis de ambiente do vault (ROLE, CA_PATH, SECRET_PATH) para facilitar as operações com o vault.
Vale ressaltar:
O webhook não adiciona os secrets como variáveis de ambiente no Pod. Nenhum dado confidencial é gravado em disco, nem mesmo temporariamente, nem fica visível no objeto Pod modificado.
Todos os secrets ficam armazenados em memória e só são visíveis para o processo que os solicita.
Usando charts sem container.command e container.args explícitos
Agora o webhook consegue determinar o ENTRYPOINT e o CMD do container com base nos metadados da imagem consultados no registry. Esses dados ficam em cache até o Pod do webhook ser reiniciado. Se o registry for público (sem autenticação), você não precisa fazer nada; mas se exigir autenticação, as credenciais precisam estar disponíveis na seção imagePullSecrets do Pod.
Quase todo chart permite definir suas podAnnotations, então é só consultar as annotations disponíveis na página README do webhook.
Consumindo secrets fora do Kubernetes
A ferramenta secrets-consumer-env pode ser executada definindo algumas variáveis de ambiente — confira o README.md para todas as opções disponíveis.
Instalando o webhook
Antes de instalar este chart, é preciso criar um namespace para ele. Isso acontece por causa da ordem em que os recursos dos charts são aplicados (o Helm reúne todos os recursos de um chart e suas dependências, agrupa por tipo de recurso e os instala em uma ordem predefinida — veja aqui — Helm 2.10).
O MutatingWebhookConfiguration é criado antes do próprio Pod backend que atua como webhook. O Kubernetes também tentaria fazer a mutação desse Pod, mas ele ainda não está pronto para isso (recursão infinita na lógica).
export WEBHOOK_NS=`<namepsace>`WEBHOOK_NS=${WEBHOOK_NS:-vault-secrets-webhook}kubectl create namespace "${WEBHOOK_NS}"kubectl label ns "${WEBHOOK_NS}" name="${WEBHOOK_NS}"Baixe o chart:
git clone https://github.com/innovia/secrets-consumer-webhook.gitInstale o chart:
helm upgrade --namespace ${WEBHOOK_NS} --install secrets-consumer-webhook secrets-consumer-webhook --waitOBSERVAÇÃO: --wait é necessário por conta de problemas de timing do Helm; veja esta issue.
Sobre os GKE Private Clusters
Quando o Google configura o control plane para clusters privados, ele cria automaticamente o VPC peering entre a rede do seu cluster Kubernetes e um projeto separado gerenciado pelo Google.
As regras geradas automaticamente abrem apenas as portas 10250 e 443 entre masters e nodes. Ou seja, para usar o componente do webhook em um GKE private cluster, você precisa configurar uma regra de firewall adicional que permita ao CIDR dos seus masters acessar o Pod do webhook na porta 8443.
Para mais detalhes sobre como adicionar regras de firewall para os nodes do control plane do GKE, consulte a documentação do GKE.
Secrets explícitos vs. não explícitos (pegar todos)
Você pode escolher quais secrets quer expor ao seu processo ou trazer todos os secrets de um caminho/nome.
Para selecionar explicitamente os secrets do secret manager, adicione uma variável de ambiente ao seu Pod usando esta convenção:
env:- name: <variable name to export> value: vault:<vault key name from secret>Configurando a autenticação com backend Kubernetes no Vault
O Vault pode autenticar no Kubernetes usando uma service account do Kubernetes.
Ele faz isso por meio de outra service account, chamada vault-reviewer, com a permissão auth-delegator, que permite encaminhar o token de outra service account para autenticação no master do Kubernetes.
Após a autenticação no Kubernetes, o Vault retorna um client token que pode ser usado para fazer login no Vault. O Vault verifica um mapeamento entre uma role do vault, service account, namespace e a policy para permitir ou negar o acesso.
Esse token da service account vault-reviewer é configurado dentro do vault usando a CLI do vault.
Vamos criar a service account para esse vault-reviewer.
https://gist.github.com/innovia/5435f2336e4dd0045dbb5842880b3334
Atenção: se você instalou o Vault em outro namespace, ajuste este arquivo conforme necessário.
kubectl apply -f vault-reviewer.yamlHabilite o backend de autenticação Kubernetes:
$ vault loginToken (will be hidden):$ vault auth enable kubernetesSuccess! Enabled kubernetes auth method at: kubernetes/Configure o Vault com o token e o CA do Vault-reviewer:
Atenção: se você instalou o Vault em outro namespace, use a flag -n depois de cada comando kubectl.
$ VAULT_SA_TOKEN_NAME=$(kubectl get sa vault-reviewer -o jsonpath="{.secrets[*]['name']}")$ SA_JWT_TOKEN=$(kubectl get secret "$VAULT_SA_TOKEN_NAME" -o jsonpath="{.data.token}" | base64 --decode; echo)$ SA_CA_CRT=$(kubectl get secret "$VAULT_SA_TOKEN_NAME" -o jsonpath="{.data['ca\.crt']}" | base64 --decode; echo)$ vault write auth/kubernetes/config token_reviewer_jwt="$SA_JWT_TOKEN" kubernetes_host=https://kubernetes.default kubernetes_ca_cert="$SA_CA_CRT"Success! Data written to: auth/kubernetes/configExemplo de mapeamento de role para uma service account e namespace no Vault
vault write auth/kubernetes/role/tester bound_service_account_names=tester bound_service_account_namespaces=default policies=test_policy ttl=1hConfigurando a autenticação com backend GCP no Vault
Habilite o método de autenticação Google Cloud:
$ vault auth enable gcpConfigure as credenciais do método de autenticação:
$ vault write auth/gcp/config \ credentials=@/path/to/credentials.jsonSe estiver usando credenciais de instância ou quiser informar as credenciais por variável de ambiente, pule esta etapa.
Crie uma role nomeada:
Para uma role do tipo iam:
$ vault write auth/gcp/role/my-iam-role \ type="iam" \ policies="dev,prod" \ bound_service_accounts="[email protected]"Para uma role do tipo gce:
$ vault write auth/gcp/role/my-gce-role \ type="gce" \ policies="dev,prod" \ bound_projects="my-project1,my-project2" \ bound_zones="us-east1-b" \ bound_labels="foo:bar,zip:zap" \ bound_service_accounts="[email protected]"Permissões GCP necessárias
Permissões do Vault Server
Para roles do Vault do tipo iam, o Vault pode receber as seguintes roles:
roles/iam.serviceAccountKeyAdminPara roles do Vault do tipo gce, o Vault pode receber as seguintes roles:
roles/compute.viewerSe preferir criar uma role customizada apenas com as permissões GCP estritamente necessárias, use a seguinte lista:
iam.serviceAccounts.getiam.serviceAccountKeys.getcompute.instances.getcompute.instanceGroups.listPermissões para autenticar no Vault
Lembre-se: as permissões mencionadas acima são concedidas aos servidores Vault. A service account IAM ou a instância GCE que está se autenticando no Vault precisa ter a seguinte role:
roles/iam.serviceAccountTokenCreatorfique à vontade para conferir o código-fonte: