HashiCorp Vault: Entregando segredos para uma aplicação no Kubernetes

Este é o quarto post da série sobre o vault. Você pode ver os posts anteriores clicando nos links a seguir:

Obrigado Filipe MaiaAndré Luis Soares e Kleberson Sartori pelo apoio no dia a dia e compartilhamento do conhecimento com o Vault.

Introdução

Considerando que o Vault está instalado e configurado no Kubernetes (veja os tutoriais anteriores desta série), mostrarei como gerenciar os segredos no Vault e compartilhá-los com duas aplicações que serão executadas no mesmo cluster. Vou mostrar cada etapa, desde a criação de um path no Vault para armazenar segredos até a configuração de um sidecar do Vault Agent em um arquivo de deployment, para que ele injete automaticamente os segredos no container de cada aplicação.

Etapa 1: Criando o path KV no Vault

Primeiro, vamos criar o path do tipo kv no Vault, que é o repositório onde nossos segredos serão armazenados. O tipo kv (Key-Value) permite armazenar dados de configuração e segredos como variáveis de ambiente que podem ser acessadas pelas aplicações.

Acesse o cluster Kubernetes (esse passo será omitido, pois pode ser feito de diferentes formas. Escolha a mesma que usou para acessar o cluster durante a execução do segundo tutorial dessa série).

Crie um port-forward do pod vault-0 para acessar a interface web do vault e inicializá-lo.

kubectl port-forward pod/vault-0 8200:8200 -n vault

Abra outro terminal e acesse o Vault:

export VAULT_ADDR=http://127.0.0.1:8200

# Faça o login no vault
vault login

Crie o path kv no Vault para agrupar o conjunto de secrets de diferentes aplicações/microsserviços que se relacionam entre si. É nesse path que serão criados subpaths, uma para cada aplicação/microsserviço, para hospedar os segredos.

PATH_KV_NAME="my-group-secrets"
vault secrets enable -path=$PATH_KV_NAME kv

Crie alguns segredos para cada aplicação no path recém criado.

vault kv put $PATH_KV_NAME/my-app-1 \
  DB_PASSWORD="password1" \
  DB_USER="user1" \
  DB_HOST="172.17.0.1" \
  DB_PORT="5432" \
  DB_NAME="db1"
vault kv put $PATH_KV_NAME/my-app-2 \
  DB_PASSWORD="password2" \
  DB_USER="user2" \
  DB_HOST="172.17.0.2" \
  DB_PORT="3306" \
  DB_NAME="db2"

Cada comando mostrado anteriormente cria os paths my-group-secrets/my-app-1 e my-group-secrets/my-app-2 e armazena os valores das variáveis DB_PASSWORD, DB_USER, DB_HOST, DB_PORT e DB_NAME com os respectivos conteúdos que serão acessados por cada aplicação.

Com o path KV configurado, o próximo passo é adicionar permissões de acesso para que somente usuários ou aplicações autorizadas possam ler e escrever segredos nesse path.

vault policy write policy-my-group-secrets -<<EOF
path "my-group-secrets/*" {
  capabilities = ["read", "list"]
}
EOF

Com a configuração definida para a política policy-my-group-secrets, qualquer usuário ou aplicação que futuramente for associado a ela terá permissão apenas de leitura no path my-group-secrets.

Etapa 2: Criando uma Service Account no Kubernetes para Acesso ao Vault

Para garantir que apenas aplicações específicas no Kubernetes possam acessar o Vault, precisamos criar uma Service Account (SA) para cada aplicação.

cat <<EOF > sa-my-app-1.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: sa-my-app-1
EOF
cat <<EOF > sa-my-app-2.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: sa-my-app-2
EOF

Execute o seguinte comando para criar as service account no Kubernetes.

MY_NAMESPACE=my-namespace
kubectl create namespace $MY_NAMESPACE
kubectl apply -f sa-my-app-1.yaml -f sa-my-app-1.yaml -n $MY_NAMESPACE

Agora crie duas roles (uma para cada aplicação) associando a service account com a política policy-my-group-secrets para conseguir ler os segredos no path específico.

# Role role-my-app-1
vault write "auth/kubernetes/role/role-my-app-1" \
  bound_service_account_names="sa-my-app-1" \
  bound_service_account_namespaces="$MY_NAMESPACE" \
  token_policies="policy-my-group-secrets"
# Role role-my-app-2
vault write "auth/kubernetes/role/role-my-app-2" \
  bound_service_account_names="sa-my-app-2" \
  bound_service_account_namespaces="$MY_NAMESPACE" \
  token_policies="policy-my-group-secrets"

Etapa 3: Configurando o Sidecar do Vault Agent no Deployment

Para que cada aplicação obtenha os segredos diretamente do Vault, adicione o Vault Agent como um container sidecar no deployment. Os arquivos de deployment completo serão mostrados adiante.

Perceba que na seção de annotations é correlacionada a role criada anteriormente para cada aplicação para conseguir ler os segredos no path específico.

Crie os arquivos de deployment de cada aplicação com os seguintes comandos.

cat <<EOF > deployment-my-app-1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: my-app-1
  name: my-app-1
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: my-app-1
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: my-app-1
      annotations:
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/agent-requests-cpu: "10m"
        vault.hashicorp.com/agent-requests-mem: "32Mi"
        vault.hashicorp.com/agent-limits-cpu: "20m"
        vault.hashicorp.com/agent-limits-mem: "64Mi"
        vault.hashicorp.com/role: "role-my-app-1"
        vault.hashicorp.com/agent-inject-secret-config: "my-group-secrets/my-app-1"
        vault.hashicorp.com/agent-inject-template-config: |
          {{- with secret "my-group-secrets/my-app-1" -}}
          {{ range $k, $v := .Data.data }}
          export {{ $k }}="{{ $v }}"
          {{ end }}
          {{- end }}
    spec:
      serviceAccountName: sa-my-app-1
      containers:
      - name: my-app-1
        image: paulbouwer/hello-kubernetes:1.10.1
        ports:
          - containerPort: 8080
        imagePullPolicy: IfNotPresent
        command: ["sh", "-c"]
        args: ["source /vault/secrets/config"]
        resources:
          limits:
            memory: "128Mi"
            cpu: "200m"
          requests:
            memory: "64Mi"
            cpu: "50m"
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
EOF

cat <<EOF > deployment-my-app-2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: my-app-2
  name: my-app-2
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: my-app-2
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: my-app-2
      annotations:
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/agent-requests-cpu: "10m"
        vault.hashicorp.com/agent-requests-mem: "32Mi"
        vault.hashicorp.com/agent-limits-cpu: "20m"
        vault.hashicorp.com/agent-limits-mem: "64Mi"
        vault.hashicorp.com/role: "role-my-app-2"
        vault.hashicorp.com/agent-inject-secret-config: "my-group-secrets/my-app-2"
        vault.hashicorp.com/agent-inject-template-config: |
          {{- with secret "my-group-secrets/my-app-2" -}}
          {{ range $k, $v := .Data.data }}
          export {{ $k }}="{{ $v }}"
          {{ end }}
          {{- end }}
    spec:
      serviceAccountName: sa-my-app-2
      containers:
      - name: my-app-2
        image: paulbouwer/hello-kubernetes:1.10.1
        ports:
          - containerPort: 8080
        imagePullPolicy: IfNotPresent
        command: ["sh", "-c"]
        args: ["source /vault/secrets/config"]
        resources:
          limits:
            memory: "128Mi"
            cpu: "200m"
          requests:
            memory: "64Mi"
            cpu: "50m"
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
EOF

Crie os deployments com o seguinte comando:

kubectl apply -f deployment-my-app-1.yaml -f deployment-my-app-2.yaml  -n $MY_NAMESPACE

Inspecione os pods com os seguintes comandos:

kubectl get pods -n $MY_NAMESPACE
kubectl describe pod/NAME_POD1 -n $MY_NAMESPACE
kubectl describe pod/NAME_POD2 -n $MY_NAMESPACE

Se tudo estiver configurado corretamente, dentro de cada conteiner principal será criado o arquivo /vault/secrets/config conteúdo todos os segredos cadastrados no path kv de cada aplicação no Vault.

Apesar de não aparecer na lista de variáveis de ambiente do container principal, esses segredos são carregado automaticamente com o comando source /vault/secrets/config no conteiner principal. Dessa forma, a aplicação conseguirá acessar os segredos corretamente.

Conclusão

O Vault Agent atua como intermediário, buscando e injetando segredos no container principal da aplicação. Com as etapas mostradas neste tutorial, você tem condições de configurar uma solução segura para gerenciar segredos no Kubernetes com o HashiCorp Vault, garantindo que cada aplicação receba apenas os segredos necessários.

Referências

Categories: , , , , ,

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *