O que são secrets?

De acordo com a doc do Kubernetes em minha tradução livre para pt-BR: Secrets é tipo um ConfigMap que guarda dados confidenciais.

Então, vamos começar com "similar ao ConfigMap", isso quer dizer que uma Secret pode ter dados organizados no tipo mapa entre outros tipos como Token, basic-auth, ssh-auth, tls etc. Por padrão as Secrets são "opaque", que significa que não estão em plain-text, porque automaticamente o Kubernetes vai codificar seu conteúdo em base64, mas isso não quer dizer muita coisa, visto que dentro do etcd por exemplo sim, elas estão salvas em plain-text e este será meu gancho para comentar sobre acessos.

Partindo do princípio que você está seguindo boas práticas de segmentar os acessos de usuários e que a granularidade do RBAC permite, por exemplo, ter acesso ao namespace que contém a secret, mas não ter acesso a ela, este é um ponto importante e talvez crucial para a segurança desses dados, mas lembre-se que qualquer um que tenha acesso ao namespace para criar um pod, consegue ler o conteúdo de secrets que estão ali.

Dada essa pequena introdução, vem a pergunta: por que separar secrets das aplicações? E aqui alguns pontos que vamos detalhar neste artigo:

  • Redução de Riscos: Manter dados confidenciais fora do código-fonte e dos repositórios diminui o risco de exposição acidental.

  • Controle de Acesso: A separação permite aplicar políticas de controle de acesso granular, garantindo que apenas as entidades autorizadas tenham acesso aos segredos.

  • Facilidade de Atualização: Secrets podem ser atualizados sem a necessidade de alterar o código da aplicação. Isso é especialmente útil em ambientes dinâmicos e de alta disponibilidade.

  • Rotação de Credenciais: Facilita a rotação periódica de credenciais, prática altamente recomendada em cenários dinâmicos em DevSecOps.

Como Segredos São Tratados em Cenários Cloud-Native

Em ambientes cloud-native, como os baseados em Kubernetes, a gestão de segredos deve ser automatizada e sempre em busca de ser o mais segura possível. Duas das abordagens mais populares para este fim são o HashiCorp Vault e Kubernetes External Secrets Operator. Mas antes de entrar em implementações, também é importante saber que as Secrets podem ser injetadas em seus containers de 2 formas principais: como variáveis de ambiente ou como volumes.

Abaixo alguns exemplos práticos:

Uma simples secret:

apiVersion: v1
kind: Secret
metadata:
  name: my-secret
type: Opaque
data:
  username: dXNlcm5hbWU=   # 'username' codificado em base64
  password: cGFzc3dvcmQ=   # 'password' codificado em base64

Usando uma secret como volume em um pod:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: mycontainer
      image: myimage
      volumeMounts:
        - name: secret-volume
          mountPath: "/etc/secret"    # local no container onde os campos da secret aparecem como arquivos
          readOnly: true
  volumes:
    - name: secret-volume
      secret:
        secretName: my-secret   # referência ao objeto Secret

Usando uma secret como variável de ambiente:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: mycontainer
      image: myimage
      env:
        - name: USERNAME
          valueFrom:
            secretKeyRef:
              name: my-secret
              key: username
        - name: PASSWORD
          valueFrom:
            secretKeyRef:
              name: my-secret   # referência ao objeto Secret
              key: password     # referência a um item/atributo do objeto Secret

Claro que existem cenários para os dois tipos de implementações de secrets e eu pretendo apontar aqui prós e contras para te auxiliar na escolha correta e consciente.

Variáveis de ambiente:

Prós: 

  • Intuitivo para devs, pois já estão acostumados com essa abordagem;

  • Performático, pois não depende de outros processos;

Contras:

  • Podem ser lidas com facilidade caso o container seja comprometido;

  • Complexidade de gestão, pela abordagem descentralizada;

Volumes:

Prós:

  • Maior isolamento, pois o acesso pode ser controlado mesmo dentro do container;

  • Formatos flexíveis, incluindo armazenamento de dados binários;

  • Permite atualização dinâmica, sem reiniciar o container;

Contras:

  • A leitura de arquivos pode exigir mudanças no código;

  • Performance de IO;

Opinião: caso sua aplicação não precise de centenas de pods rodando e lendo essas secrets, utilize volume, pois esta abordagem possui maior granularidade de isolamento e facilidade de auditoria, já que estamos tratando de segurança.

Conceitos adicionais:

Alguns outros conceitos são bem importantes quando estamos falando de segurança e também se aplicam às secrets:

O Princípio do menor privilégio é o primeiro deles, como já abordado por aqui, o Kubernetes possui uma alta granularidade de controle de acesso e este tema deve ser tratado com grande atenção, definir corretamente as permissões dos usuários, não apenas humanos, que acessem seus ambientes pode determinar o sucesso ou fracasso de sua estratégia de segurança.

Deixo aqui uma dica de leitura sobre o tema e também uma dica de ferramenta que pode lhe ajudar a revisar quem tem acesso e o que pode fazer em seu cluster aqui.

O segundo conceito importante é o de criptografia em descanso (encryption at rest), que é uma prática essencial para proteger dados armazenados contra acessos não autorizados e violações de segurança. No contexto de DevSecOps, onde segurança e operações são integradas desde o início do ciclo de desenvolvimento, pelo menos em teoria, a criptografia em descanso desempenha um papel crucial na proteção de informações sensíveis e na garantia de conformidade com regulamentações de segurança.

Criptografia em descanso refere-se ao processo de criptografar dados armazenados em dispositivos de armazenamento estático, como discos rígidos, bancos de dados, ou sistemas de armazenamento em nuvem. Isso garante que, mesmo que o armazenamento seja comprometido, os dados não possam ser acessados sem a chave de criptografia correta.

Este é um tema extenso e não vou entrar em sua parte prática aqui, principalmente pela complexidade, mas se desejar pode continuar sua leitura neste link da documentação. O ponto importante aqui é que ao menos coloque este tema em seu roadmap de DevSecOps.

O terceiro e último conceito, é a rotação de chaves ou segredos. A rotação de chaves é uma prática essencial na segurança de dados que envolve a substituição periódica de chaves criptográficas para minimizar o risco de comprometimento. Em um ambiente DevSecOps, a automação e a integração contínua tornam essa prática ainda mais eficiente e segura. 

Motivações para Rotação de Chaves

1 - Mitigação de Riscos:

  • Limitação de Exposição: Ao rotacionar chaves regularmente, a janela de tempo em que uma chave pode ser comprometida é reduzida, limitando a exposição de dados.

  • Resistência a Ataques: A rotação de chaves dificulta a vida dos atacantes, que precisam comprometer novas chaves em intervalos regulares, aumentando a segurança geral do sistema.

2 - Conformidade com Regulamentações:

  • Requisitos Legais: Muitas regulamentações de segurança de dados, como GDPR, PCI-DSS e HIPAA, exigem a rotação periódica de chaves para garantir a proteção contínua dos dados sensíveis.

  • Auditorias e Relatórios: A rotação de chaves facilita auditorias de segurança, demonstrando um compromisso com as melhores práticas de segurança e conformidade.

3 - Redução de Impacto de Comprometimento:

  • Minimização de Danos: Se uma chave for comprometida, a rotação regular garante que os dados protegidos por essa chave não permaneçam vulneráveis por longos períodos.

  • Resposta a Incidentes: A rotação de chaves pode ser uma resposta eficaz a incidentes de segurança, substituindo rapidamente chaves comprometidas.

4 - Manutenção da Integridade Criptográfica:

  • Renovação de Chaves: A rotação de chaves ajuda a evitar problemas relacionados à degradação da segurança criptográfica ao longo do tempo, como a reutilização excessiva de chaves.

  • Adoção de Novos Algoritmos: Facilita a adoção de novos algoritmos e padrões de criptografia, garantindo que as práticas de segurança permaneçam atualizadas.

5 - Confiança do Cliente e Reputação:

  • Transparência e Segurança: Demonstrar práticas robustas de rotação de chaves aumenta a confiança dos clientes e parceiros na segurança dos dados.

  • Proteção de Marca: A implementação de rotação de chaves pode proteger a reputação da empresa, mitigando os impactos negativos de potenciais violações de segurança.

Vamos explorar como implementar a rotação de chaves na prática usando HashiCorp Vault e Kubernetes External Secrets Operator, visto que o Kubernetes não possui um formato nativo para realizar este trabalho.

Hashicorp Vault

Vamos agora explorar aqui o HashiCorp Vault, que é um gestor de secrets. Embora não tenha sido criado exclusivamente para Kubernetes, ele se integra perfeitamente às necessidades de sistemas distribuídos, como os que estamos discutindo neste post. O Vault oferece uma solução robusta para o gerenciamento seguro de dados sensíveis, o que é crucial em ambientes onde segurança e escalabilidade são prioridades.

Passo-a-Passo para Instalar o HashiCorp Vault no Kubernetes

1. Adicionar o Repositório do Helm

$ helm repo add hashicorp https://helm.releases.hashicorp.com
$ helm repo update

2 Instalar o vault.

Neste tutorial vamos instalar apenas o modo dev, usado para ambiente de testes. Nesse modo nenhum setup ou pré-config é necessária, fazendo nosso lab ser mais fluido. Em caso de ambiente produtivo siga a documentação e recomendações de melhores práticas aqui.

$ helm install vault hashicorp/vault -n vault --create-namespace --set='server.dev.enabled=true' --set='ui.enabled=true'

3. Configuração e uso do secret

Agora, dentro do vault, é necessário criar os vínculos entre ele e o kubernetes, habilitar e configurar a autenticação do kubernetes, criar o bind entre a policy e serviceaccount do namespace que podem ler secrets:

$ kubectl exec -it vault-0 -- sh

# criando a policy
$ cat <<EOF > /home/vault/read-policy.hcl
path "secret*" {
 capabilities = ["read"]
}
EOF
$ vault policy write read-policy /home/vault/read-policy.hcl

# habilitando a autenticação pelo kubernetes
$ vault auth enable kubernetes

# configurando a autenticação via kubernetes
$ token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
$ vault write auth/kubernetes/config \
  token_reviewer_jwt="$token" \
  kubernetes_host=https://${KUBERNETES_PORT_443_TCP_ADDR} \
  kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt

# criando a role no vault e fazendo o bind entre a policy e a serviceaccount do namespace
$ vault write auth/kubernetes/role/vault-role \
  bound_service_account_names=vault-serviceaccount \
  bound_service_account_namespaces=pombo \
  policies=read-policy \
  ttl=1h
$ exit

4. Acesse o VaultUI e crie uma secret

$ kubectl port-forward -n vault svc/vault-ui 8200

Abra seu browser e digite http://localhost:8200

Token = root

Crie uma secret acessando: Secret Engine -> secret - > Create Secret +

5. Injetar o segredo nos pods na nossa aplicação

Esta é a parte final, onde usamos os segredos que registramos no Vault.

$ kubectl create namespace pombo
$ kubectl create serviceaccount -n pombo vault-serviceaccount

Vamos criar um pod:

$ kubectl apply -f - <<EOFapiVersion: apps/v1
kind: Deployment
metadata:
  name: vault-test
  namespace: pombo
  labels:
    app: read-vault-secret
spec:
  selector:
    matchLabels:
      app: read-vault-secret
  replicas: 1
  template:
    metadata:
      annotations:
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/agent-inject-status: "update"
        vault.hashicorp.com/agent-inject-secret-1secret: "secret/1secret"
        vault.hashicorp.com/agent-inject-template-1secret: |
          {{- with secret "secret/1secret" -}}
          chave={{ .Data.data.chave }}
          user={{ .Data.data.user }}
          pass={{ .Data.data.pass }}
          {{- end }

Verifique se foi criado o volume e os dados da secret estão lá dentro:

$ kubectl exec -ti -n pombo vault-test-db4dd4dff-hzhl7 -c nginx -- cat /vault/secrets/1secret
chave=valor
user=pombo
pass=123456

Se a secret for alterada no vault, adicionada ou removida alguma chave, o pod que está utilizando esta secret tem os seus valores atualizados dentro do pod, desde que a referência das chaves seja a mesma nas annotations do deploy ;-)

External Secrets Operator

O Kubernetes External Secrets Operator é uma solução que permite gerenciar segredos em Kubernetes a partir de fontes externas, como AWS Secrets Manager, Google Secrets Manager e também o HashiCorp Vault que vimos anteriormente.

Alguns pontos de destaque:

  • Custom Resource Definitions (CRDs): Utiliza CRDs para definir e sincronizar segredos de fontes externas com segredos do Kubernetes.

  • Integração com Backends de Segredos: Suporta diversos backends de segredos, permitindo flexibilidade na escolha do gerenciador de segredos.

  • Atualização Automática: Permite a atualização automática dos segredos no Kubernetes quando os segredos no backend são atualizados, garantindo que as aplicações sempre usem as credenciais mais recentes.

Inclusive entrevistamos os 2 mantenedores brasileiros do projeto no Kubicast, assista aqui.

Então vamos à prática, começando com a instalação do External-Secrets Operator no seu kubernetes:

$ helm repo add external-secrets https://charts.external-secrets.io
$ helm repo update
$ helm install external-secrets -n external-secrets --create-namespace external-secrets/external-secrets

Se precisar que o external-secrets operator se comunique com a cloud provider através de workload identity (gcp ou azure) ou role (aws) então siga os passos de cada cloud fornecendo as devidas annotations para a serviceaccount da instalação neste caso iremos usar GCP.

Neste ponto é necessário criar a SecretStore ou ClusterSecretStore, que indica seu cloud provider e como o operator vai acessar as secrets providas. Nesse exemplo usarei GCP/SecretManager. Aqui assumo que será uma ClusterSecretStore, pois pode servir secrets em qualquer namespace. Você pode ter várias ClusterSecretStore e caso deseje criar uma fonte exclusiva para um namespace, então crie uma SecretStore para esse mesmo namespace e as secrets podem ser providas também dessa outra fonte.

apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: meu-gcp-store
spec:
  provider:
    gcpsm:
      projectID: id-projeto-gcp
      auth:
        workloadIdentity:
          clusterLocation: southamerica-east1
          clusterName: prd
          serviceAccountRef:
            name: external-secrets
            namespace: external-secrets

Com isso em mãos, criamos a ExternalSecret, que nada mais é que a chamada a SecretManager do GCP usando os parâmetros da nossa ClusterSecretStore e indicando o namespace e nome da secret que vai ser criada no kubernetes:

apiVersion: external-secrets.io/v1beta1 
kind: ExternalSecret
metadata:
  name: find-by-tags
  namespace: app1
spec:
  refreshInterval: 5m
  secretStoreRef:
    name: meu-gcp-store
    kind: ClusterSecretStore
  target:
    name: secret-gcp-example
  dataFrom:
  - find:
      tags:
        external-secrets: "true"

Nesse exemplo, vamos buscar todas as secrets do GCP que tem a tag “external-secrets=true” e popular numa única secret kubernetes no namespace app1, na secret secret-gcp-example. Todos os dados contidos na secret do GCP serão copiados nessa operação.

Toda vez que alguma secret for alterada no GCP, o external-secret operator atualizará a secret dentro do cluster Kubernetes e eventualmente dentro do arquivo no pod. Sua aplicação é responsável em reler este arquivo.

Além de labels, o external-secrets pode obtê-las por nome, por versão e até mesmo usando filtro json dentro da secret. A estrutura da secret dentro do SecretsManager pode ser um json e o external-secrets obter chave-valor específico dentro do json.

Se quiser, o external-secrets operator ainda pode buscar secrets num Vault externo ao cluster, seguindo a documentação : https://external-secrets.io/latest/provider/hashicorp-vault/

Conclusão

Dados sensíveis precisam ser tratados de forma segura. Seguindo essas dicas você consegue criar um ambiente menos suscetível a vazamentos, mais fácil de gerir e responder a incidentes. Considere colocar essas práticas como exigências em seus projetos, tornando-as parte indispensável do seu dia-a-dia.

Social

Fale conosco

Almeda Campinas 802, CJ 12, Jardim Paulista,

São Paulo - SP, 01404-001

Faça parte do time

Nossos conteúdos

Social

Fale conosco

Almeda Campinas 802, CJ 12, Jardim Paulista,

São Paulo - SP, 01404-001

Faça parte do time

Nossos conteúdos

Social

Fale conosco

Almeda Campinas 802, CJ 12, Jardim Paulista,

São Paulo - SP, 01404-001

Faça parte do time

Nossos conteúdos