Ir ao conteúdo

Integrando AWS Secrets Manager ao EKS usando External Secrets

Gerir segredos em larga escala é uma tarefa complicada para muitas empresas, principalmente em ambientes com Kubernetes, GitOps e automações por todo lado. Com o advento do GitOps, somos incentivados a armazenar e gerenciar a infraestrutura como código em um repositório Git, mas, os segredos são uma exceção, e tendem a ser armazenados em cofres de segredo, como AWS Secrets Manager, Azure Keyvaults ou HashiCorp Vault. Então na postagem de hoje, vamos aprender como sincronizar nossos segredos armazenados no AWS Secrets Manager com nosso cluster de Elastic Kubernetes Services (EKS) utilizando External Secrets.

Sobre o External Secrets Operator (ESO)

O External Secrets Operator é um operador de Kubernetes open-source iniciado pela GoDaddy que permite a integração de um cofre de segredos com Kubernetes através de dois recursos principais, o SecretStore e o ExternalSecret. Além de sincronizar os segredos do cofre para o cluster, o ESO também tem a habilidade de transformar o nome deste segredo para atender a qualquer padrão esperado pela sua aplicação.

Criando um segredo no AWS Secrets Manager

Como primeiro passo deste tutorial, iremos executar o terraform a seguir para criar um segredo do tipo chave/valor e simular um segredo de um banco de dados armazenado no AWS Secrets Manager:

## Cria o segredo
resource "aws_secretsmanager_secret" "secret" {
  name = "containscloud-lab-secret"
}

## Adiciona valores no formato de chave/valor
resource "aws_secretsmanager_secret_version" "secret_verison" {
  secret_id     = aws_secretsmanager_secret.secret.id
  secret_string = jsonencode({
    sqldb_host = "db01.database.com"
    sqldb_user = "app01_operator"
    sqldb_password = "@helloworld"
    oracledb_host = "db01.oracle.com"
  })
}

Configurando a IAM role e policy na AWS

Neste passo, iremos criar os recursos de Identity and Access Management (IAM) que serão utilizados pelo operador para autenticar e obter segredos. Além disso, também iremos associar a identidade ao cluster utilizando EKS Pod Identity Agent.

A primeira role será associada a Service Account do External Secrets e não terá policy associada, enquanto a segunda role será configurada no recurso SecretStore e será assumida exclusivamente no momento da atualização dos segredos, seguindo assim o conceito de privilégios mínimos.

## Cria a role eso-role para a service account do operador
resource "aws_iam_role" "eso_role" {
  name = "eso-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = [
        "sts:AssumeRole",
        "sts:TagSession"
      ]
      Effect = "Allow"
      Principal = {
        Service = [
					"pods.eks.amazonaws.com"
				]
      }
    }]
  })
}

## Cria a role workload01-eso-role para o SecretStore
Create the eso-role for the operator
resource "aws_iam_role" "workload01_eso_role" {
  name = "workload01-eso-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = [
        "sts:AssumeRole",
        "sts:TagSession"
      ]
      Effect = "Allow"
      Principal = {
        AWS = aws_iam_role.eso_role.arn
      }
    }]
  })
}

## Cria o documento da policy com as ações necessárias para obter o
## secret criado no último passo.
## Ref: https://external-secrets.io/v0.9.13/provider/aws-secrets-manager/
data "aws_iam_policy_document" "eso_policy" {
    statement {
      sid = "eso"
      effect = "Allow"
      actions = [
        "secretsmanager:GetResourcePolicy",
        "secretsmanager:GetSecretValue",
        "secretsmanager:DescribeSecret",
        "secretsmanager:ListSecretVersionIds",
        "secretsmanager:ListSecrets"
      ]
      resources = [
        aws_secretsmanager_secret.secret.arn
      ]
    }

    statement {
      sid = "allowListSecrets"
      effect = "Allow"
      actions = [ 
        "secretsmanager:ListSecrets"
      ]
      resources = [
        "*"
      ]
    }
}

## Cria a policy eso-policy com base no documento criado acima.
resource "aws_iam_policy" "eso_policy" {
  name        = "eso-policy"
  description = "Policy for ExternalSecretsOperator read content from SecretsManager of workload01"
  policy      = data.aws_iam_policy_document.eso_policy.json
}

## Associa a policy com a role workload01-eso-role
resource "aws_iam_role_policy_attachment" "attach" {
  role       = aws_iam_role.workload01_eso_role.name
  policy_arn = aws_iam_policy.eso_policy.arn
}

## Associa a eso-role com o EKS, especificando o namespace e service account do external secrets.
## Ref: https://github.com/external-secrets/external-secrets/issues/2951#issuecomment-1866943943
resource "aws_eks_pod_identity_association" "role_association" {
  cluster_name    = "eks-lab-cluster"
  namespace       = "external-secrets"
  service_account = "external-secrets"
  role_arn        = aws_iam_role.eso_role.arn
}

Instalando o operador no cluster

Agora que as roles já estão criadas, vamos instalar o External Secrets Operator e criar o namespace padrão para o workload, neste caso, workload01:

## Adiciona o repositótio a lista local e instala o HelmChart.
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets -n external-secrets --create-namespace

## Cria o namespace.
kubectl create namespace workload01

O cluster de EKS utilizado neste lab está disponível em: https://github.com/diego7marques/eks-lab-cluster

Criando o SecretStore e ExternalSecrets

O recurso SecretStore isola as configurações relacionadas ao seu cofre de segredos e como o operador deve autenticar-se. Esta configuração é namespaced, e pode ser utilizada por múltiplos ExternalSecrets. Se você deseja utilizar o mesmo SecretStore para múltiplos namespaces, use ClusterSecretStore como kind.

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: containscloud-lab-store
  namespace: workload01
spec:
  provider:
    aws:
      service: SecretsManager
      region: us-east-1
      # Role que vai ser assumida pelo operador no momento de buscar os segredos
      role: arn:aws:iam::<AWS_ACCOUNT_ID>:role/workload01-eso-role

Se funcionar corretamente, o resultado deverá ser:

Já o recurso ExternalSecrets permite que você especifique a lógica relacionada ao workload, como reescrever o nome dos segredos ou alterar o formato do mesmo. No exemplo a seguir, iremos remover o prefixo sqldb_ do nome do secret:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: "workload01-database-secrets"
  namespace: workload01
spec:
  secretStoreRef:
    name: containscloud-lab-store
    kind: SecretStore
  ## Pega os segredos do AWS Secrets Manager
  dataFrom:
  - extract:
      key: containscloud-lab-secret
      conversionStrategy: Default
      decodingStrategy: Auto
    ## Reescreve o nome do segredo para remover o prefixo sqldb_
    rewrite:
    - regexp:
        source: ^sqldb_
        target: ""
  ## Intervalo de atualização dos dados
  refreshInterval: 60s
  target:
    name: workload01-database
    creationPolicy: Owner
    deletionPolicy: Retain

E está feito! Depois de aplicar o ultimo arquivo, os secrets serão sincronizados e estão prontos para serem utilizados 🙂

Conclusão

Como podemos ver, o operador External Secrets oferece uma abordagem direta para importar segredos com segurança para o Kubernetes, o que simplifica todo o processo de gerenciamento de segredos e aumenta a segurança e a capacidade de manutenção. Além disso, sua adaptabilidade significa que você não está preso a um único cofre de segredos. Então, por que não simplificar sua vida incluindo essa ferramenta em sua caixa de ferramentas? 😛

O código utilizado neste post está disponível em: https://github.com/diego7marques/external-secrets-aws-secrets-manager


Descubra mais sobre contains(cloud)

Assine para receber nossas notícias mais recentes por e-mail.

Publicado emAWSKubernetes

Seja o primeiro a comentar

Deixe um comentário

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