Skip to main content

SecretStore

A primeira coisa que precisamos fazer ao usar o External Secrets Operator (ESO) é escolher o provider que vai armazenar os segredos — como AWS Secrets Manager, SSM Parameter Store, GCP Secrets, entre outros.

Não faz sentido estudar todos os providers a fundo: cada um tem detalhes diferentes, e a documentação cobre isso muito bem. O que todos têm em comum é que precisam ser configurados por meio de um SecretStore.

Exemplo com AWS Secrets Manager

Não vamos detalhar a fundo sobre IAM na AWS, apenas o conceito necessário que você precisa saber.

Um dos providers mais utilizados é o AWS Secrets Manager. Para usá-lo, precisamos:

  1. Criar o recurso SecretStore
  2. Criar um secret com as credenciais da AWS no cluster (caso não use IRSA). Veremos essa abordagem mais pra frente.
  3. Garantir que a IAM Role tenha permissões corretas

Antes de começar, é necessário ter as chaves do IAM na AWS e saber qual role vamos usar.

Conceitos Rápidos Importantes sobre IAM

IAM User >>> Assume uma Role >>> E a Role possui permissões.

Um usuário geralmente possui as seguintes chaves que são usadas para identificá-lo:

  • access-key-id
  • secret-access-key

Uma segunda abordagem que vamos falar aqui é de uma service account do cluster Kubernetes assumir uma role da AWS diretamente, fazendo o papel de IAM User.

Service Account (RBAC Kubernetes) >>> Assume uma Role AWS >>> E a Role possui permissões.

Se observar bem, ambos os casos precisam da role que será assumida. É nela que colocamos as permissões necessárias.

Exemplo com IAM User

Crie na AWS um IAM user chamado external-secrets-iam (ou qualquer outro nome) e gere as credenciais de acesso. Vários tutoriais mostram como fazer isso. Precisamos dessas credenciais como secret dentro do Kubernetes para referenciá-las.

# Criando uma secret aws-external-secrets-credentials no namespace external-secrets
kubectl create secret generic aws-external-secrets-credentials --from-literal=access-key=xxxxxxxxxxxx --from-literal=secret-access-key=xxxxxxxxxxxxxxxxxxxx -n external-secrets

Aqui temos o exemplo do SecretStore usando essas credenciais. Ainda não criamos a role. Um IAM User pode assumir várias roles, por isso precisamos referenciar qual será usada, mas ainda não criamos.

apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: aws-secrets-manager
spec:
provider:
aws:
service: SecretsManager
role: arn:aws:iam::123456789012:role/external-secrets # Role que vamos usar (criaremos mais adiante)
region: us-east-2
auth:
secretRef: # Referência ao secret criado anteriormente
accessKeyIDSecretRef:
name: aws-external-secrets-credentials
key: access-key
secretAccessKeySecretRef:
name: aws-external-secrets-credentials
key: secret-access-key

Essa role precisa de permissões de leitura no Secrets Manager para que o ESO possa sincronizar as secrets que vamos mapear no futuro.

O Parameter Store também é suportado, mas deve ser criado um novo SecretStore apontando para o serviço correto.

apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: aws-parameter-store
spec:
provider:
aws:
service: ParameterStore
role: arn:aws:iam::ID_DA_SUA_CONTA:role/external-secrets
region: us-east-2
auth:
secretRef:
accessKeyIDSecretRef:
name: aws-external-secrets-credentials
key: access-key
secretAccessKeySecretRef:
name: aws-external-secrets-credentials
key: secret-access-key

A mesma IAM Role pode ser usada, desde que tenha permissões para os dois serviços — que é o que faremos aqui.

Policy (Permissão na Role)

Crie uma role na AWS chamada external-secrets, pois foi esse nome usado nos exemplos acima, e adicione permissões para os serviços principais do ESO.

É uma boa prática limitar o acesso da role apenas aos secrets/parameters que ela deve acessar. Podemos fazer isso com Resource Tags. Caso não seja necessário, é só remover a condição.

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SecretsManagerAccess",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:GetResourcePolicy",
"secretsmanager:DescribeSecret",
"secretsmanager:ListSecrets",
"secretsmanager:ListSecretVersionIds",
"secretsmanager:BatchGetSecretValue"
],
"Resource": [
"*"
],
"Condition": {
"StringEquals": {
"secretsmanager:ResourceTag/external-secrets": "true"
}
}
},
{
"Sid": "SSMParameterStoreAccess",
"Effect": "Allow",
"Action": [
"ssm:GetParameter",
"ssm:GetParameters",
"ssm:GetParametersByPath",
"ssm:DescribeParameters"
],
"Resource": [
"*"
],
"Condition": {
"StringEquals": {
"ssm:ResourceTag/external-secrets": "true"
}
}
}
]
}

Push Secret

O ESO também suporta o recurso PushSecret, que permite criar e atualizar secrets no provider (como o Secrets Manager). Para isso, precisamos adicionar permissões extras. Geralmente gosto de fazer isso em uma policy separada.

OBS: Use sua região e ID da conta.

{
"Effect": "Allow",
"Action": [
"secretsmanager:CreateSecret",
"secretsmanager:PutSecretValue",
"secretsmanager:TagResource",
"secretsmanager:DeleteSecret"
],
"Resource": [
"arn:aws:secretsmanager:us-east-2:ID_DA_SUA_CONTA:secret:dev-*"
]
}

O ideal é ser mais restritivo, por isso prefiro usar um controle por tag no delete:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:CreateSecret",
"secretsmanager:PutSecretValue",
"secretsmanager:TagResource"
],
"Resource": [
"arn:aws:secretsmanager:us-east-2:ID_DA_SUA_CONTA:secret:dev-*"
]
},
{
"Effect": "Allow",
"Action": [
"secretsmanager:DeleteSecret"
],
"Resource": [
"arn:aws:secretsmanager:us-east-2:ID_DA_SUA_CONTA:secret:dev-*"
],
"Condition": {
"StringEquals": {
"secretsmanager:ResourceTag/managed-by": "external-secrets"
}
}
}
]
}

Esse prefixo dev-* pode seguir qualquer padrão: nome do app, namespace, cluster, etc.

Para Parameter Store (Push), a ideia é similar:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ssm:GetParameter*",
"ssm:PutParameter*",
"ssm:AddTagsToResource",
"ssm:ListTagsForResource"
],
"Resource": [
"arn:aws:ssm:us-east-2:ID_DA_SUA_CONTA:secret-manager/dev-*"
]
},
{
"Effect": "Allow",
"Action": [
"ssm:DeleteParameter*"
],
"Resource": [
"arn:aws:ssm:us-east-2:ID_DA_SUA_CONTA:secret-manager/dev-*"
],
"Condition": {
"StringEquals": {
"ssm:ResourceTag/managed-by": "external-secrets"
}
}
}
]
}

Ao final, configure para que o IAM User external-secrets-iam possa assumir a role external-secrets. Isso é feito na role, não no user:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::ID_DA_SUA_CONTA:user/external-secrets-iam"
},
"Action": "sts:AssumeRole"
}
]
}

IAM Role com IRSA

A configuração com chaves estáticas (accessKey/secretAccessKey) em um Secret do Kubernetes tem alguns riscos:

  • Segurança: São credenciais de longa duração. Se vazarem, podem ser usadas de qualquer lugar.
  • Gerenciamento: Rotação manual é trabalhosa e muitas vezes esquecida.

IRSA (IAM Roles for Service Accounts) é a forma nativa e segura de dar permissões AWS a pods no EKS sem usar AWS credentials no pod. Um pod utilizar uma service account e se esta service account tiver permissão assumir a role na AWS não precisamos passar as credenciais via secretRef.

  • Segurança Superior: Sem chaves longas armazenadas. O pod usa tokens temporários gerados e validados pelo STS.
  • Gerenciamento Simplificado: Sem criar, rotacionar ou distribuir chaves manualmente.

Ao adicionar uma relação de confiança na role, podemos dizer que a service account (external-secrets) criada pelo external-secrets no namespace (external-secrets) pode assumer a role external-secrets na AWS. Mais uma vez lembrando esta é uma configuração da role external-secrets.

Você precisa configurar um OIDC Provider usado pelo cluster para a federação pois ele é o seu IDP. Provavelmente você já deve ter o OIDC Provider configurado. Caso não tenha busque melhores informações sobre isso senão fugiremos muito do assunto.

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AssumeRulesExternalSecrets",
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::ID_DA_SUA_CONTA:oidc-provider/oidc.eks.us-east-2.amazonaws.com/id/XXXXXXXXXXXXXXXXXXXXX"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.us-east-2.amazonaws.com/id/XXXXXXXXXXXXXXXXXXXXX:sub": "system:serviceaccount:external-secrets:external-secrets"
}
}
}
]
}

Assim, você pode remover os secretRef no SecretStore e usar JWT.

apiVersion: external-secrets.io/v1
kind: ClusterSecretStore # Já vamos explicar Por que mudamos para ClusterSecretStore ao invés de SecretStore
metadata:
name: aws-secrets-manager
spec:
provider:
aws:
service: SecretsManager
region: "us-east-2"
auth:
jwt:
serviceAccountRef: # Dizemos qual a service account será usada e em qual namespace estará.
name: external-secrets
namespace: external-secrets

Porém a service account external-secret precisa ter uma annotation nela dizendo qual a service account ela pode assumir. Isso pode ser definido no values.yaml durante a instalação do helm chart.

#values.yaml
###...
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::1234567889911:role/external-secrets
###...

Que iria ser refletido no service account external-secrets criado pelo chart.

❯ kubectl describe sa -n external-secrets external-secrets

Name: external-secrets
Namespace: external-secrets
Labels: app.kubernetes.io/instance=external-secrets
app.kubernetes.io/managed-by=Helm
app.kubernetes.io/name=external-secrets
app.kubernetes.io/version=v0.18.1
helm.sh/chart=external-secrets-0.18.1
Annotations: eks.amazonaws.com/role-arn: arn:aws:iam::1234567889911:role/external-secrets ### Aqui...
meta.helm.sh/release-name: external-secrets
meta.helm.sh/release-namespace: external-secrets
Image pull secrets: <none>
Mountable secrets: <none>
Tokens: <none>
Events: <none>

Aplique o SecretStore da melhor maneira de acordo com a solução que você escolheu.

kubectl apply -f clustersecretstore.yaml

ClusterSecretStore vs SecretStore

Ambos são recursos do External Secrets Operator (ESO) usados pra definir como e com quem o ESO se conecta a um provider (tipo AWS Secrets Manager, SSM, etc). A diferença entre eles é basicamente o escopo de onde podem ser usados.

SecretStore

Escopo: Namespace

Uso: Só pode ser referenciado por ExternalSecret dentro do mesmo namespace

Ideal quando: Você quer separar segredos por times, ambientes ou apps com políticas isoladas

apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: aws-store
namespace: app-dev
...

Um ExternalSecret no namespace app-prod não pode usar esse SecretStore acima.

Por que faríamos isso? Podemos ter roles diferentes sendo que a permissão dentro de cada uma delas é diferente para as tags. Por exemplo uma role que só permite ler as segredos com a tag team=frontend e outra role team=backend.

ClusterSecretStore

Escopo: Cluster-wide (global)

Uso: Pode ser referenciado por ExternalSecret em qualquer namespace

Ideal quando: Você quer uma única configuração para vários namespaces, centralizar o acesso ao provider, ou padronizar uso da role.

Perfeito pra clusters com múltiplos apps/namespaces usando o mesmo provedor e role.

Se for definir só um para o cluster, é o ClusterSecretStore que você esta buscando.