DevOps

Secretos y datos confidenciales en Kubernetes: gestión segura con Secrets, Vault y sealed-secrets

Ruslan Ismailov Publicado 12 min de lectura
S

Introducción: por qué los Kubernetes Secrets nativos no son seguros por defecto

Los Kubernetes Secrets son lo primero que le viene a la mente a un ingeniero DevOps cuando necesita pasar una contraseña de base de datos o una clave de API a un Pod. Sin embargo, la mayoría de los equipos subestiman los riesgos: por defecto, los secretos se almacenan en etcd en formato Base64 — esto no es cifrado, sino únicamente codificación. Cualquiera que obtenga acceso a una copia de seguridad de etcd o al objeto Secret mediante kubectl podrá leer los datos en texto plano de inmediato.

Las amenazas reales son las siguientes:

  • Compromiso de etcd — un atacante con acceso a un snapshot de etcd obtiene todos los secretos del clúster.
  • Permisos RBAC excesivos — los desarrolladores obtienen accidentalmente permisos de get secrets en el namespace de producción.
  • Secretos en Git — un manifiesto con datos codificados se confirma en el repositorio.
  • Ausencia de rotación — la misma contraseña se usa durante años.

En 2026, la gestión de secretos en Kubernetes no es una opción, sino un elemento obligatorio de una infraestructura segura. Analicemos los enfoques y las buenas prácticas.

Panorama de enfoques para la gestión de secretos

1. Kubernetes Secrets nativos

Mecanismo integrado, sencillo de usar, pero que requiere protección adicional:

  • Habilitar el cifrado en reposo mediante EncryptionConfiguration en el servidor de API.
  • Restringir RBAC: conjunto mínimo de permisos a nivel de namespace.
  • Habilitar Audit Logging para rastrear el acceso.

2. HashiCorp Vault

Almacén de secretos completo con credenciales dinámicas, mecanismo de lease y auditoría detallada. Se integra con Kubernetes mediante Agent Injector (sidecar) o CSI Provider. Indicado para equipos grandes y altos requisitos de seguridad.

3. Sealed Secrets (Bitnami)

Controlador para Kubernetes que permite almacenar secretos cifrados directamente en Git de forma segura. El secreto se cifra con la clave pública del clúster y solo puede ser descifrado por el propio clúster. Ideal para el enfoque GitOps.

4. External Secrets Operator

Operador que sincroniza secretos desde almacenes externos (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, Azure Key Vault) hacia Kubernetes Secrets nativos. Permite gestionar los secretos de forma centralizada fuera del clúster.

Práctica: configuración de Sealed Secrets en el clúster

Sealed Secrets es la opción óptima para equipos que practican GitOps. Veamos la instalación y el uso paso a paso.

Paso 1: Instalación del controlador

helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets\nhelm repo update\nhelm install sealed-secrets sealed-secrets/sealed-secrets \\\n  --namespace kube-system \\\n  --set fullnameOverride=sealed-secrets-controller

Paso 2: Instalación de kubeseal CLI

# Linux\nwget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.26.0/kubeseal-0.26.0-linux-amd64.tar.gz\ntar xvf kubeseal-0.26.0-linux-amd64.tar.gz\nsudo mv kubeseal /usr/local/bin/

Paso 3: Creación y cifrado del secreto

Primero creamos un manifiesto Secret normal y luego lo ciframos con kubeseal:

kubectl create secret generic db-credentials \\\n  --from-literal=DB_PASSWORD=supersecret123 \\\n  --from-literal=DB_USER=appuser \\\n  --namespace=production \\\n  --dry-run=client -o yaml | \\\n  kubeseal --controller-name=sealed-secrets-controller \\\n           --controller-namespace=kube-system \\\n           --format yaml > sealed-db-credentials.yaml

El manifiesto resultante sealed-db-credentials.yaml tiene el siguiente aspecto:

apiVersion: bitnami.com/v1alpha1\nkind: SealedSecret\nmetadata:\n  name: db-credentials\n  namespace: production\nspec:\n  encryptedData:\n    DB_PASSWORD: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEq...\n    DB_USER: AgAKIBDLs8yHFUVCG+p1k2M3N4...\n  template:\n    metadata:\n      name: db-credentials\n      namespace: production\n    type: Opaque

Este archivo puede confirmarse de forma segura en Git — sin acceso a la clave privada del clúster es imposible descifrarlo. Aplicamos el manifiesto:

kubectl apply -f sealed-db-credentials.yaml

El controlador creará automáticamente un Secret nativo en el namespace production.

Integración de HashiCorp Vault con Kubernetes

Agent Injector

Vault Agent Injector funciona como un mutating admission webhook: al crear un Pod con las anotaciones correspondientes, se añade automáticamente un contenedor sidecar que se autentica en Vault y monta los secretos en el sistema de archivos del Pod.

Ejemplo de anotaciones para un Pod:

apiVersion: v1\nkind: Pod\nmetadata:\n  name: go-app\n  namespace: production\n  annotations:\n    vault.hashicorp.com/agent-inject: "true"\n    vault.hashicorp.com/role: "go-app-role"\n    vault.hashicorp.com/agent-inject-secret-config.env: "secret/data/production/go-app"\n    vault.hashicorp.com/agent-inject-template-config.env: |\n      {{- with secret "secret/data/production/go-app" -}}\n      export DB_PASSWORD={{ .Data.data.db_password }}\n      export API_KEY={{ .Data.data.api_key }}\n      {{- end }}\nspec:\n  serviceAccountName: go-app-sa\n  containers:\n  - name: go-app\n    image: myregistry/go-app:latest

CSI Provider

Vault CSI Provider monta los secretos como archivos normales a través del Kubernetes Secrets Store CSI Driver — sin contenedor sidecar, lo que reduce el overhead. Es adecuado cuando la aplicación ya sabe leer la configuración desde archivos.

apiVersion: secrets-store.csi.x-k8s.io/v1\nkind: SecretProviderClass\nmetadata:\n  name: vault-db-credentials\n  namespace: production\nspec:\n  provider: vault\n  parameters:\n    vaultAddress: "https://vault.example.com"\n    roleName: "go-app-role"\n    objects: |\n      - objectName: "db_password"\n        secretPath: "secret/data/production/go-app"\n        secretKey: "db_password"

Configuración de Kubernetes Auth en Vault

# Habilitamos la autenticación de Kubernetes\nvault auth enable kubernetes\n\n# Configuramos la conexión al clúster\nvault write auth/kubernetes/config \\\n  kubernetes_host="https://kubernetes.default.svc" \\\n  kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \\\n  token_reviewer_jwt=@/var/run/secrets/kubernetes.io/serviceaccount/token\n\n# Creamos el rol para la aplicación\nvault write auth/kubernetes/role/go-app-role \\\n  bound_service_account_names=go-app-sa \\\n  bound_service_account_namespaces=production \\\n  policies=go-app-policy \\\n  ttl=1h

Gestión de secretos en el pipeline de CI/CD

GitHub Actions

En GitHub Actions, los secretos se almacenan en la configuración del repositorio o de la organización y se pasan al workflow mediante variables de entorno. Nunca imprimas secretos en los logs.

name: Deploy to Kubernetes\non:\n  push:\n    branches: [main]\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Configure kubectl\n        uses: azure/k8s-set-context@v3\n        with:\n          kubeconfig: ${{ secrets.KUBECONFIG }}\n      - name: Deploy application\n        env:\n          REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}\n        run: |\n          echo "$REGISTRY_TOKEN" | docker login registry.example.com -u ci --password-stdin\n          kubectl apply -f k8s/

GitLab CI

En GitLab CI, los secretos se definen como variables Protected y Masked en la configuración del proyecto. Para la integración con HashiCorp Vault, GitLab soporta un mecanismo JWT nativo:

deploy:production:\n  stage: deploy\n  id_tokens:\n    VAULT_ID_TOKEN:\n      aud: https://vault.example.com\n  secrets:\n    DB_PASSWORD:\n      vault: production/go-app/db_password@secret\n      file: false\n  script:\n    - kubectl create secret generic db-credentials \\\n        --from-literal=DB_PASSWORD="$DB_PASSWORD" \\\n        --namespace=production \\\n        --dry-run=client -o yaml | kubectl apply -f -

Este enfoque permite obtener secretos directamente desde Vault sin almacenarlos en GitLab, lo que respeta el principio de mínima confianza.

Rotación de secretos sin tiempo de inactividad

La rotación de secretos es un proceso crítico que a menudo se olvida hasta el primer incidente. Veamos la estrategia para aplicaciones en Go y PHP.

Estrategia de escritura doble

  1. En Vault se crea una nueva versión del secreto, mientras la anterior permanece activa.
  2. La aplicación se reinicia con el nuevo secreto (rolling update en Kubernetes).
  3. Tras un despliegue exitoso, la versión anterior se marca como obsoleta.

Aplicación Go: recarga en caliente de la configuración

Las aplicaciones Go pueden utilizar el SDK de Vault para actualizar credenciales dinámicamente sin necesidad de reinicio:

// Ejemplo simplificado de monitoreo de lease
func renewSecret(client *vault.Client, secret *vault.Secret) {
    renewer, _ := client.NewLifetimeWatcher(&vault.LifetimeWatcherInput{
        Secret: secret,
        Increment: 3600,
    })
    go renewer.Start()
    defer renewer.Stop()
    for {
        select {
        case renewal := <-renewer.RenewCh():
            log.Printf("Secret renewed: %v", renewal.Secret.LeaseDuration)
        case err := <-renewer.DoneCh():
            log.Printf("Renewal failed, fetching new secret: %v", err)
            // Obtener nuevo secreto
            return
        }
    }
}

Aplicación PHP: rotación mediante Kubernetes rolling update

Las aplicaciones PHP, por lo general, no mantienen conexiones de larga duración, por lo que el rolling update estándar de Kubernetes resuelve la tarea sin tiempo de inactividad:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: php-app
  namespace: production
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 1
  template:
    spec:
      containers:
      - name: php-app
        image: myregistry/php-app:latest
        envFrom:
        - secretRef:
            name: db-credentials

Al rotar el secreto, basta con actualizar el objeto Secret y ejecutar kubectl rollout restart deployment/php-app -n production.

Auditoría y monitoreo del acceso a los secretos

La seguridad sin monitoreo es una ilusión. Configure los siguientes mecanismos:

Kubernetes Audit Logging

Habilite la auditoría a nivel del servidor de API, registrando los accesos al recurso secrets:

apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
  resources:
  - group: ""
    resources: ["secrets"]
  verbs: ["get", "list", "watch"]
- level: RequestResponse
  resources:
  - group: ""
    resources: ["secrets"]
  verbs: ["create", "update", "patch", "delete"]

Vault Audit Devices

En HashiCorp Vault, habilite el dispositivo de auditoría file o syslog:

vault audit enable file file_path=/var/log/vault/audit.log

Todos los accesos a los secretos se registran indicando el cliente, la hora y el resultado de la operación. Integre los logs con ELK Stack o Grafana Loki para un análisis centralizado.

Alertas

Configure alertas para eventos anómalos: listado masivo de secretos, acceso desde namespaces inesperados, intentos de acceso con tokens revocados.

Checklist de seguridad para un clúster en producción

  • Cifrado en reposo — habilitar EncryptionConfiguration para etcd con proveedor AES-CBC o KMS.
  • Minimalismo en RBAC — principio de mínimo privilegio: roles solo para los namespaces y recursos necesarios, sin wildcards.
  • Sin secretos en Git en texto plano — usar Sealed Secrets o External Secrets Operator.
  • Audit logs habilitados — mínimo nivel Metadata para los recursos de tipo secrets.
  • Rotación configurada — rotación automática mediante Vault lease o un planificador externo.
  • Network Policy — restringir el acceso de red a Vault y otros almacenes de secretos.
  • Tokens de ServiceAccount con TTL limitado — usar Bound Service Account Tokens (no estáticos).
  • Deshabilitar el montaje automático de ServiceAccountautomountServiceAccountToken: false en el Deployment si el token no es necesario.
  • Escaneo de imágenes — analizar las imágenes Docker en busca de secretos hardcodeados (truffleHog, gitleaks en CI/CD).
  • Monitoreo de anomalías — alertas ante comportamientos inusuales al trabajar con secretos.

Conclusión

La gestión segura de secretos en Kubernetes es una tarea de múltiples capas que requiere elegir las herramientas correctas según el contexto. Para equipos pequeños con enfoque GitOps, Sealed Secrets cubre la mayoría de los riesgos. Para infraestructuras enterprise con requisitos de cumplimiento normativo, HashiCorp Vault ofrece control total: credenciales dinámicas, auditoría detallada y gestión centralizada de políticas. External Secrets Operator integra de forma flexible Kubernetes con los almacenes de secretos en la nube.

Independientemente de la herramienta elegida, la base de la seguridad se apoya en tres pilares: mínimos privilegios, auditoría de todos los accesos y rotación regular. Implemente estas prácticas hoy mismo — y su clúster estará preparado para los requisitos de seguridad de 2026.

Tecnologías

Etiquetas

Ruslan Ismailov

Desarrollador Senior Web / Backend. Desarrollador senior web/backend con 9 años de experiencia. Stack: PHP, Laravel, PostgreSQL, Redis, Docker, Kubernetes, REST, microservicios, CI/CD. Más sobre mí →