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

  • External Secrets Operator (ESO) instalado.

  • 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.