Anuncio

External Secrets Operator: Gestión Segura de Secretos en Kubernetes

Actualizado: July 2026 • 9 min de lectura
External Secrets Operator: Gestión Segura de Secretos en Kubernetes

Uno de los problemas más persistentes en la gestión de clústeres de Kubernetes es el tratamiento de información sensible: contraseñas de bases de datos, claves de API, certificados TLS y tokens de acceso. Los Secrets nativos de Kubernetes resuelven parte del problema, pero tienen una debilidad fundamental: están codificados en Base64, no cifrados, lo que los hace potencialmente vulnerables si alguien accede al repositorio Git o a la API de Kubernetes sin los permisos adecuados.

External Secrets Operator (ESO) es la solución estándar de la industria para resolver este problema. Permite sincronizar secretos desde proveedores externos (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, Google Secret Manager, etc.) directamente a objetos Secret nativos de Kubernetes, de forma automática y segura.


El problema con los Secrets nativos de Kubernetes

Cuando defines un Secret de Kubernetes de forma estándar, el valor se almacena en etcd codificado en Base64:

apiVersion: v1
kind: Secret
metadata:
  name: mi-secreto
type: Opaque
data:
  password: cGFzc3dvcmQxMjM=  # "password123" en Base64 — ¡no es cifrado!

Esto presenta varios problemas críticos:

  • No es cifrado real: Base64 es una codificación reversible trivialmente. Cualquier persona con acceso al objeto puede decodificarlo.
  • Riesgo en Git: Si commitas tus manifiestos en un repositorio, el secreto queda expuesto en el historial para siempre.
  • Rotación manual: Actualizar credenciales requiere modificar archivos YAML manualmente y redesplegar.
  • Sin auditoría: No hay registro de quién leyó el secreto ni cuándo.

External Secrets Operator resuelve todos estos problemas delegando el almacenamiento real de los secretos a sistemas diseñados específicamente para ello.


Arquitectura de External Secrets Operator

ESO funciona mediante tres recursos personalizados (CRDs) principales:

┌─────────────────────────────────────────────────────┐
│                  Kubernetes Cluster                  │
│                                                      │
│  ┌──────────────────┐      ┌──────────────────────┐ │
│  │  ExternalSecret  │─────▶│  SecretStore /       │ │
│  │  (qué secreto    │      │  ClusterSecretStore  │ │
│  │   quiero y cómo  │      │  (cómo conectar al   │ │
│  │   mapearlo)      │      │   proveedor externo) │ │
│  └────────┬─────────┘      └──────────┬───────────┘ │
│           │                           │              │
│           ▼                           ▼              │
│  ┌──────────────────┐      ┌──────────────────────┐ │
│  │  Secret nativo   │      │  External Secrets    │ │
│  │  de Kubernetes   │◀─────│  Operator (ESO)      │ │
│  │  (auto-generado) │      │  (controlador)       │ │
│  └──────────────────┘      └──────────────────────┘ │
└─────────────────────────────────────────────────────┘
                              │
                              ▼
              ┌───────────────────────────┐
              │   Proveedor Externo        │
              │   (AWS SM / Vault /        │
              │    Azure KV / GCP SM)      │
              └───────────────────────────┘
  • SecretStore: Define cómo conectarse al proveedor de secretos externo (credenciales, región, endpoint). Es namespaced (solo aplica a un namespace).
  • ClusterSecretStore: Igual que SecretStore pero con alcance a todo el clúster.
  • ExternalSecret: Define qué secretos obtener del proveedor y cómo mapearlos a un Secret de Kubernetes.

El operador vigila estos recursos y sincroniza automáticamente los secretos según el intervalo configurado.


Instalación con Helm

La forma más rápida de instalar ESO es mediante su chart oficial de Helm:

helm repo add external-secrets https://charts.external-secrets.io
helm repo update

helm install external-secrets \
  external-secrets/external-secrets \
  --namespace external-secrets \
  --create-namespace \
  --set installCRDs=true

Verifica que los pods estén corriendo correctamente:

kubectl get pods -n external-secrets
# NAME                                               READY   STATUS    RESTARTS   AGE
# external-secrets-xxxxxxxxx-xxxxx                  1/1     Running   0          60s
# external-secrets-cert-controller-xxxxxx-xxxxx     1/1     Running   0          60s
# external-secrets-webhook-xxxxxxxxx-xxxxx          1/1     Running   0          60s

Ejemplo práctico: AWS Secrets Manager

1. Configurar el SecretStore

Primero, crea un Secret de Kubernetes con las credenciales de AWS (o usa IRSA si estás en EKS):

apiVersion: v1
kind: Secret
metadata:
  name: awssm-credentials
  namespace: produccion
type: Opaque
stringData:
  access-key: "AKIAIOSFODNN7EXAMPLE"
  secret-access-key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"

A continuación, define el SecretStore que apunta a AWS Secrets Manager:

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: aws-secrets-manager
  namespace: produccion
spec:
  provider:
    aws:
      service: SecretsManager
      region: eu-west-1
      auth:
        secretRef:
          accessKeyIDSecretRef:
            name: awssm-credentials
            key: access-key
          secretAccessKeySecretRef:
            name: awssm-credentials
            key: secret-access-key

2. Crear el ExternalSecret

Define qué secreto obtener de AWS y cómo exponerlo en Kubernetes:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: mi-base-de-datos
  namespace: produccion
spec:
  refreshInterval: "1h"          # Sincronizar cada hora
  secretStoreRef:
    name: aws-secrets-manager
    kind: SecretStore
  target:
    name: db-credentials          # Nombre del Secret de K8s que se creará
    creationPolicy: Owner
  data:
    - secretKey: username         # Clave en el Secret de K8s
      remoteRef:
        key: produccion/mi-app/db # Nombre del secreto en AWS Secrets Manager
        property: username        # Campo dentro del secreto JSON
    - secretKey: password
      remoteRef:
        key: produccion/mi-app/db
        property: password

ESO creará automáticamente un Secret de Kubernetes llamado db-credentials en el namespace produccion con los valores obtenidos de AWS.

3. Usar el secreto en tu Pod

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mi-aplicacion
  namespace: produccion
spec:
  replicas: 2
  selector:
    matchLabels:
      app: mi-aplicacion
  template:
    metadata:
      labels:
        app: mi-aplicacion
    spec:
      containers:
        - name: app
          image: mi-imagen:latest
          env:
            - name: DB_USER
              valueFrom:
                secretKeyRef:
                  name: db-credentials   # Secret gestionado por ESO
                  key: username
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: password

Ejemplo con HashiCorp Vault

Si tu organización usa HashiCorp Vault, la configuración del SecretStore es igualmente sencilla:

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-backend
  namespace: produccion
spec:
  provider:
    vault:
      server: "https://vault.mi-empresa.com"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "mi-app-role"
          serviceAccountRef:
            name: "mi-app-sa"

Con autenticación Kubernetes nativa, Vault verifica la identidad del Pod mediante su ServiceAccount, eliminando la necesidad de gestionar credenciales estáticas de Vault.


Sincronización completa con dataFrom

En lugar de mapear campo a campo, puedes sincronizar todos los campos de un secreto externo de una sola vez usando dataFrom:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: config-completa
  namespace: produccion
spec:
  refreshInterval: "30m"
  secretStoreRef:
    name: aws-secrets-manager
    kind: SecretStore
  target:
    name: app-config-completa
  dataFrom:
    - extract:
        key: produccion/mi-app/config  # Todos los campos de este secreto JSON

Si el secreto en AWS contiene {"DB_HOST": "...", "DB_PORT": "5432", "API_KEY": "..."}, el Secret de Kubernetes tendrá automáticamente los tres campos.


Buenas prácticas de seguridad

  1. Usa ClusterSecretStore con precaución: Permite a cualquier namespace obtener secretos. Restringe qué namespaces pueden usarlo mediante namespaceSelector.
  2. Configura refreshInterval apropiado: Para credenciales críticas, intervalos cortos (15m-1h) garantizan que las rotaciones se propaguen rápidamente.
  3. Monitoriza el estado de los ExternalSecrets:
    kubectl get externalsecret -n produccion
    # NAME               STORE                REFRESH INTERVAL   STATUS   READY
    # mi-base-de-datos   aws-secrets-manager  1h                 Valid    True
    
  4. Usa IRSA en EKS (IAM Roles for Service Accounts): Evita almacenar credenciales de AWS en Kubernetes. El operador obtiene permisos directamente a través de la identidad del Pod.
  5. Combina con GitOps: Los manifiestos de ExternalSecret son seguros para versionar en Git — no contienen valores sensibles, solo referencias a ellos.

Conclusión

External Secrets Operator transforma radicalmente la gestión de secretos en Kubernetes. En lugar de luchar contra las limitaciones de los Secrets nativos, permite aprovechar la potencia y seguridad de plataformas especializadas como AWS Secrets Manager, HashiCorp Vault o Azure Key Vault, con integración perfecta en el ecosistema Kubernetes.

La combinación de ESO + GitOps (con ArgoCD o Flux) representa la arquitectura de referencia para equipos que buscan un control total sobre su infraestructura manteniendo los más altos estándares de seguridad: los manifiestos en Git nunca contienen secretos reales, y los secretos en el clúster se actualizan automáticamente cuando rotan en el proveedor externo.