Auto rotação de verdade de segredos com ESO e Vault
Esse post é tradução do post original em: https://dev.to/canelasevero/true-secrets-auto-rotation...
Requerimentos
Um cluster Kubernetes que você possa usar (kind, minikube, algo auto gerenciado) e kubectl para conectar ao cluster
CLI do Vault
Vault instalado pelo helm chart
O que queremos alcançar
Este guia tem como objetivo estabelecer uma rotação automática (a cada hora) do segredo de conexão de um banco de dados. Seguindo estas etapas, um administrador configura o processo uma vez, garantindo que o segredo seja atualizado/mudado a cada hora. Simultaneamente, a aplicação sempre manterá novas credenciais válidas para interações contínuas com o banco de dados.
Generators do ESO
⚠️ No momento em que este texto é escrito, esta funcionalidade está em estado alfa e queremos mais pessoas para ajudar a testá-la, para que possamos fazer melhorias e, eventualmente, promovê-la para estável.
A documentação a respeito é um pouco limitada, é por isso que estou publicando este guia no meu blog, enquanto procuramos melhores maneiras de incorporá-los à nossa documentação.
Mão na massa
Vamos garantir que comecemos com a mesma configuração localmente:
Instalei o Vault em um namespace chamado vault.
Você pode usar o comando
helm install vault hashicorp/vault -n vault --create-namespace
em vez do que foi fornecido no guia.Siga todas as etapas para iniciar o Vault, desbloqueá-lo e obter o cluster-keys.json com o token.
Você pode pular outras etapas.
Após iniciar adequadamente o Vault e desbloqueá-lo, anote seu token de autenticação.
Instalei o ESO no namespace default.
Neste guia, vamos usar a autenticação de token do Vault apenas por uma questão de simplicidade. No entanto, nunca use isso em configurações reais. Prefira a autenticação de conta de serviço.
Vamos fazer um encaminhamento de porta e autenticar em nosso desktop de trabalho para que não tenhamos que executar o Vault toda vez que precisamos rodar comandos.
Em um novo terminal (esse terminal ficará bloqueado).
kubectl -n vault port-forward service/vault 8200:8200
Em outro terminal.
export VAULT_ADDR=http://127.0.0.1:8200
vault login
## type your auth token
Deployment Simples do PostgreSQL
Para termos um exemplo interessante, vamos deployar o psql e configurá-lo para permitir que o Vault e outras cargas de trabalho se conectem a ele.
Primeiro, vamos criar um configmap com um usuário e senha de administrador para essa instância do psql (apenas por simplicidade e para chegar à outra parte do guia rapidamente).
cat <<EOF > postgres-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-config
labels:
app: postgres
data:
POSTGRES_DB: postgresdb
POSTGRES_USER: admin
POSTGRES_PASSWORD: psltest
EOF
Agora podemos criar o postgres-deployment.yaml.
cat <<EOF > postgres-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres # Sets Deployment name
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:10.1 # Sets Image
imagePullPolicy: "IfNotPresent"
ports:
- containerPort: 5432 # Exposes container port
envFrom:
- configMapRef:
name: postgres-config
EOF
E por último o serviço expondo o psql para outros workloads.
cat <<EOF > postgres-service.yaml
apiVersion: v1
kind: Service
metadata:
name: postgres # Sets service name
labels:
app: postgres # Labels and Selectors
spec:
type: NodePort # Sets service type
ports:
- port: 5432 # Sets port to run the postgres application
selector:
app: postgres
EOF
Applique todos esses arquivos com o kubectl apply -f <nome do arquivo>
.
Preparando o DB com nova role read-only
Dar exec direto pro pod to psql.
kubectl get pods # get pod name
kubectl exec -it <postgres-pod-name> -- bash
Mude para o usuário postgres e execute os comandos para criar a nova função.
su postgres
psql -c "CREATE ROLE \"ro\" NOINHERIT;"
psql -c "GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"ro\";"
Vamos usar essa função ao configurar o Vault para usar os Segredos Dinâmicos com o plugin psql.
Segredos Dinâmicos do Vault
Os Segredos Dinâmicos do Vault são, na verdade, destinados a serem usados como uma forma de obter credenciais de curta duração. No entanto, nada nos impede de usá-los em nosso processo de rotação automática. Existem vários outros plugins que se integram a outros sistemas, como credenciais da AWS, ou sistemas de emissão de certificados. A maioria desses também são interessantes no contexto do ESO, mas eu queria um exemplo autossuficiente, sem a necessidade de criar contas externas para você testar.
Primeiro, vamos habilitar o database engine.
vault secrets enable database
Depois disso, vamos configurar o engine de segredos do PostgreSQL, com as credenciais de administrador que tínhamos antes (estamos passando credenciais para a URL de conexão aqui, nunca faça isso fora dos labs de teste).
## POSTGRES_URL with name of the service and namespace
export POSTGRES_URL=postgres.default.svc.cluster.local:5432
vault write database/config/postgresql \
plugin_name=postgresql-database-plugin \
connection_url="postgresql://admin:psltest@$POSTGRES_URL/postgres?sslmode=disable" \
allowed_roles=readonly \
username="root" \
password="rootpassword"
Crie um arquivo SQL contendo o comando incluindo wildcards de template que será usado pelo Vault ao criar dinamicamente as funções.
tee readonly.sql <<EOF
CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}' INHERIT;
GRANT ro TO "{{name}}";
EOF
Escreva isso no Vault e configure a expiração padrão de novos papéis solicitados e outros campos (isso falhará se você não criou a ROLE 'ro' corretamente ao configurar o psql).
vault write database/roles/readonly \
db_name=postgresql \
creation_statements=@readonly.sql \
default_ttl=1h \
max_ttl=24h
Você já pode verificar dentro do Vault se consegue obter as credenciais temporárias antes de configurar outras etapas.
vault read database/creds/readonly
## response
Key Value
--- -----
lease_id database/creds/readonly/CPqcUrG55f8qfrA9QKMV3peO
lease_duration 1h
lease_renewable true
password 5p-xDWSC5Iu9z-hlZPrs
username v-root-readonly-SQjhNhGxxmKx9QaRKsxM-1690473242
Gerador ESO e ExternalSecret
Antes dos próximos passos, vamos codificar o token em base64 para que possamos aplicá-lo com um segredo. Pegue seu token do Vault e faça um echo dele em base64.
echo "somethinsomething" | base64
Agora podemos usar o novo CRD do Operador de Segredos Externos, o Gerador. Use o valor exibido acima para o segredo do token de autenticação (vault-token).
cat <<EOF > vaultDynamicSecret.yaml
apiVersion: generators.external-secrets.io/v1alpha1
kind: VaultDynamicSecret
metadata:
name: "psql-example"
spec:
path: "/database/creds/readonly" ## é assim que você escolhe qual caminho dinâmico do vault usar
method: "GET" ## este caminho só funcionará com GETs
# parameters: ## nenhum parâmetro necessário
# ...
provider:
server: "http://vault.vault.svc.cluster.local:8200" ## url do vault. Neste caso serviço vault no namespace vault
auth:
# aponta para um segredo que contém um token do vault
# https://www.vaultproject.io/docs/auth/token
tokenSecretRef: ## referência ao segredo que contém o token de autenticação do Vault
name: "vault-token"
key: "token"
---
apiVersion: v1
kind: Secret
metadata:
name: vault-token
data:
token: aHZzLkM4M0o2UWNQSW1YQkRJVU96aWNNNzVHdwo= ## token codificado em base64
EOF
Aplique este arquivo.
kubectl apply -f vaultDynamicSecret.yaml
E finalmente, podemos agora criar nosso ExternalSecret que, no final, permitirá que o operador crie o Kubernetes Secret final.
cat <<EOF > vaultDynamicSecret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: "psql-example-es"
spec:
refreshInterval: "1h" ## o mesmo que o tempo de expiração na configuração dinâmica do Vault, ou inferior, para que os aplicativos sempre tenham novas credenciais válidas
target:
name: psql-example-for-use ## o nome final do segredo kubernetes criado em seu cluster
dataFrom:
- sourceRef:
generatorRef:
apiVersion: generators.external-secrets.io/v1alpha1
kind: VaultDynamicSecret
name: "psql-example" ## referência ao gerador
EOF
Aplique isso e verifique se o status do ExternalSecret está ok.
kubectl get externalsecret
## response
NAME STORE REFRESH INTERVAL STATUS READY
psql-example-es 1h SecretSynced True
Se você encontrar erros aqui, verifique se usou o caminho certo no Gerador. Verifique também se você criou os papéis certos dentro do psql e se você pode pingar o vault a partir de um pod no namespace ESO.
Verificando o segredo final
Você deve obter um segredo contendo novos usuários e senhas com acesso somente leitura ao banco de dados a cada hora.
kubectl get secrets psql-example-for-use -o jsonpath="{.data}"
## response
{"password":"V2lSWUlqZzdvQS1yOTFaV2N1SWE=","username":"di1yb290LXJlYWRvbmx5LVlXQ3kzZ01hbkhSbGtuY3FqTUg2LTE2OTA0NzIwMzc="}
Para verificar um dos valores, você pode obtê-lo e decodificá-lo em base64.
kubectl get secrets psql-example-for-use -o jsonpath="{.data.password}" | base64 -d
Agora, sua aplicação pode usar este segredo, ele será automaticamente rotacionado e ainda será uma credencial válida para o banco de dados.
Ressalvas
Se você usa segredos como Variáveis de Ambiente, precisará usar algo para fazer os workloads obterem as novas credenciais, caso apenas percam a conexão. Você pode usar o projeto Reloader para isso.
Se você usa segredos como volumes, os pods receberão essa atualização automaticamente, e você não terá problemas de conexão, desde que sua aplicação consiga obter os novos valores.
Conclusão
É isso! Configuramos um segredo de rotação automática para uma conexão de banco de dados usando ESO e Vault. A mágica é, como dissemos, você pode configurá-la uma vez e esquecer disso. Seu segredo se atualiza a cada hora e seu aplicativo permanece conectado ao banco de dados com novas credenciais válidas. É seguro, você segue as melhores práticas em relação à rotação, e evita intervenção manual se isso não for necessário.