Gestión de secretos en Amazon EKS: introducción a la serie

Gestión de secretos en Amazon EKS: introducción a la serie

En la entrada de hoy vamos a iniciar una serie de artículos sobre un tema especialmente importante cuando trabajamos con Kubernetes y Amazon EKS: la gestión de secretos y credenciales utilizados por las aplicaciones.

A lo largo de la serie veremos diferentes formas de almacenar secretos fuera del clúster y, posteriormente, hacer que nuestras aplicaciones puedan utilizarlos de una manera segura y automatizada.

Nos centraremos principalmente en dos soluciones con las que he tenido la oportunidad de trabajar: HashiCorp Vault y AWS Secrets Manager.

Además, veremos diferentes mecanismos para integrar estas bóvedas con nuestros Pods en Amazon EKS.

La serie estará compuesta, inicialmente, por las siguientes entradas:

  1. Introducción a la gestión de secretos en Amazon EKS.
  2. Instalación de HashiCorp Vault en Amazon EKS.
    1. Inyección de secretos en Pods mediante HashiCorp Vault Agent Injector.
  3. Gestión de secretos utilizando AWS Secrets Manager.
    1. Acceso a secretos de AWS Secrets Manager desde Pods de EKS.
  4. Instalación y configuración de External Secrets Operator.
    1. Integración de External Secrets Operator con AWS Secrets Manager.
    2. Integración de External Secrets Operator con HashiCorp Vault.

La idea no será simplemente instalar herramientas. Como hacemos habitualmente en el blog, intentaremos entender primero qué problema estamos resolviendo, cómo funciona cada solución y qué implicaciones tiene utilizar una opción u otra.

El problema de las credenciales

En prácticamente cualquier ecosistema las credenciales son un componente fundamental.

Nuestras aplicaciones necesitan constantemente autenticarse contra otros sistemas o servicios.

Por ejemplo, una aplicación ejecutándose dentro de EKS podría necesitar conectarse a:

  • Una base de datos PostgreSQL o MySQL.
  • Una API externa.
  • Un servicio interno de nuestra organización.
  • Un clúster de Redis.
  • Un servidor Kafka.
  • Una aplicación de terceros.
  • Un servicio que requiera un usuario y una contraseña.
  • Cualquier sistema que utilice tokens, certificados o API Keys.

Para poder realizar estas conexiones necesitaremos gestionar información sensible como:

username
password
API keys
tokens
certificates
connection strings

La pregunta realmente importante es:

¿Dónde almacenamos estas credenciales y cómo hacemos para que nuestras aplicaciones puedan utilizarlas de forma segura?

Kubernetes Secrets

Kubernetes proporciona de manera nativa un recurso llamado Secret, diseñado precisamente para almacenar información sensible dentro del clúster.

Un ejemplo muy sencillo podría ser:

apiVersion: v1
kind: Secret
metadata:
  name: database-credentials
type: Opaque
stringData:
  username: app-user
  password: my-super-secret-password

Posteriormente podríamos utilizar este Secret dentro de un Pod:

env:
  - name: DB_USERNAME
    valueFrom:
      secretKeyRef:
        name: database-credentials
        key: username

  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: database-credentials
        key: password

Desde el punto de vista funcional esto funciona perfectamente.

El problema comienza cuando pensamos en cómo gestionar estos Secrets a escala.

¿Dónde guardamos los Secrets?

Supongamos que nuestra aplicación se despliega utilizando GitOps, Helm, Terraform o simplemente manifiestos de Kubernetes almacenados en Git.

La primera tentación podría ser almacenar también el Secret dentro del repositorio:

apiVersion: v1
kind: Secret
metadata:
  name: database-credentials
data:
  username: YXBwLXVzZXI=
  password: bXktcGFzc3dvcmQ=

Pero aquí aparece uno de los errores más frecuentes.

Los valores dentro de data están codificados en Base64, pero Base64 no es cifrado.

Cualquier persona que tenga acceso al repositorio podría decodificar fácilmente el contenido:

echo "bXktcGFzc3dvcmQ=" | base64 -d

Por este motivo, nunca deberíamos almacenar credenciales directamente en nuestros repositorios Git.

Cifrar secretos dentro de Git

Existen diferentes soluciones que permiten almacenar Secrets cifrados dentro de un repositorio Git.

Por ejemplo, podemos utilizar herramientas como:

  • SOPS.
  • Sealed Secrets.
  • Diferentes sistemas de cifrado integrados con pipelines.
  • Soluciones internas desarrolladas por cada organización.

Estas herramientas pueden ser perfectamente válidas dependiendo de la arquitectura y de los requerimientos de cada empresa.

Sin embargo, sigue existiendo una pregunta importante:

¿Queremos que Git sea el lugar donde gestionamos el ciclo de vida de nuestras credenciales?

En muchas arquitecturas la respuesta será que no.

Especialmente cuando empezamos a manejar cientos de aplicaciones, diferentes ambientes y credenciales que deben rotarse periódicamente.

¿Qué sucede cuando tenemos muchos clústeres?

Imaginemos una organización con varios ambientes:

DEV
QAT
STAGING
PRODUCTION

Y además diferentes clústeres EKS:

eks-dev
eks-qat
eks-staging
eks-production

Cada aplicación podría necesitar varias credenciales.

Por ejemplo:

database-password
redis-password
external-api-token
payment-api-key
certificate

Si comenzamos a crear y mantener manualmente Kubernetes Secrets en cada clúster, rápidamente podemos terminar con cientos o incluso miles de secretos distribuidos entre diferentes namespaces y ambientes.

En ese momento comienzan a aparecer varios problemas:

  • ¿Quién creó el secreto?
  • ¿Cuándo fue actualizado?
  • ¿Cuándo debe rotarse?
  • ¿Qué aplicaciones lo utilizan?
  • ¿Quién tiene permisos para modificarlo?
  • ¿Cómo sincronizamos el mismo secreto entre varios clústeres?
  • ¿Qué ocurre cuando cambia una contraseña?

Aquí es donde el concepto de bóveda de secretos empieza a tener mucho más sentido.


Utilizar una bóveda de secretos

Una bóveda de secretos nos permite separar la gestión de credenciales de la infraestructura donde se ejecutan nuestras aplicaciones.

En lugar de guardar las credenciales directamente dentro de Kubernetes, podemos almacenarlas en un sistema diseñado específicamente para gestionar información sensible.

Conceptualmente podríamos tener algo como:

                    Secret Vault
                        │
           ┌────────────┴────────────┐
           │                         │
   HashiCorp Vault            AWS Secrets Manager
           │                         │
           └────────────┬────────────┘
                        │
                       EKS
                        │
                       Pod
                        │
                   Application

La aplicación necesita una credencial, pero la fuente de verdad se encuentra fuera del clúster.

Esto nos proporciona una arquitectura mucho más flexible.

HashiCorp Vault

HashiCorp Vault es probablemente una de las soluciones de gestión de secretos más conocidas dentro del ecosistema cloud native.

Vault nos permite centralizar diferentes tipos de información sensible, como:

Passwords
API Keys
Tokens
Certificates
Database credentials
SSH credentials

Pero Vault va mucho más allá de almacenar secretos estáticos.

También dispone de funcionalidades como:

  • Control de acceso mediante políticas.
  • Auditoría.
  • Rotación de secretos.
  • Versionado.
  • Generación de credenciales dinámicas.
  • Integración con Kubernetes.
  • Gestión de certificados mediante PKI.

Una característica especialmente interesante para nosotros será su integración con Kubernetes.

Vault puede autenticar un Pod utilizando su identidad de Kubernetes y posteriormente permitirle acceder únicamente a los secretos autorizados mediante las políticas configuradas en Vault.

Vault Agent Injector

Una de las opciones que analizaremos será Vault Agent Injector.

Vault Injector utiliza un Mutating Admission Webhook de Kubernetes para modificar automáticamente nuestros Pods durante su creación.

Podemos, por ejemplo, añadir algunas anotaciones:

metadata:
  annotations:
    vault.hashicorp.com/agent-inject: "true"
    vault.hashicorp.com/role: "my-app"
    vault.hashicorp.com/agent-inject-secret-database: "secret/data/database"

Cuando Kubernetes crea el Pod, Vault Injector puede añadir automáticamente los componentes necesarios para obtener los secretos desde Vault.

Conceptualmente el flujo será similar a este:

Pod --> ServiceAccount --> Kubernetes Authentication --> HashiCorp Vault --> Policy --> Secret --> Pod

Todo esto lo veremos con bastante más detalle en las siguientes entradas.

AWS Secrets Manager

Cuando trabajamos principalmente dentro de AWS también tenemos una alternativa completamente administrada: AWS Secrets Manager.

Secrets Manager nos permite almacenar y gestionar secretos directamente dentro de AWS.

Por ejemplo:

/prod/database

podría contener:

{
  "username": "appuser",
  "password": "password123"
}

Una de las grandes ventajas cuando trabajamos con EKS es que podemos utilizar las capacidades de IAM para controlar qué aplicaciones pueden acceder a determinados secretos.

Por ejemplo, podríamos definir que una aplicación tenga acceso únicamente a:

/prod/database/app1

mientras que otra aplicación únicamente pueda acceder a:

/prod/database/app2

De esta manera seguimos el principio de mínimo privilegio.

Identidad del Pod

Aquí aparece otro concepto muy importante que ya hemos tratado anteriormente en el blog.

Nuestros Pods necesitan una identidad para acceder a servicios de AWS.

En Amazon EKS podemos utilizar mecanismos como:

IRSAoEKS Pod Identity

Esto permite que una aplicación pueda obtener permisos IAM sin necesidad de almacenar credenciales estáticas de AWS dentro del Pod.

Conceptualmente:

Pod --> ServiceAccount --> EKS Pod Identity --> IAM Role --> AWS Secrets Manager --> Secret

El IAM Role únicamente tendrá permisos para leer los secretos que necesite la aplicación.

External Secrets Operator

Otra de las herramientas que veremos durante esta serie será External Secrets Operator, probablemente una de las integraciones más interesantes cuando queremos mantener el modelo tradicional de Kubernetes Secrets pero almacenar la información sensible fuera del clúster.

External Secrets Operator actúa como puente entre Kubernetes y nuestra bóveda de secretos.

Por ejemplo:

AWS Secrets Manager --> External Secrets Operator --> Kubernetes Secret --> Pod

O exactamente el mismo concepto utilizando Vault:

HashiCorp Vault --> External Secrets Operator --> Kubernetes Secret --> Pod

De esta manera la fuente de verdad continúa siendo nuestra bóveda, mientras que External Secrets Operator se encarga de sincronizar la información con Kubernetes.

Un recurso podría verse conceptualmente de esta manera:

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: database-credentials
spec:
  refreshInterval: 1h

  target:
    name: database-credentials

  data:
    - secretKey: password
      remoteRef:
        key: prod/database
        property: password

External Secrets Operator consultará la bóveda configurada y creará o actualizará el Kubernetes Secret correspondiente.

Diferentes formas de entregar secretos a nuestras aplicaciones

Durante la serie veremos que no existe una única manera de resolver este problema.

Dependiendo de la solución utilizada podemos terminar con arquitecturas diferentes.

Por ejemplo:

Kubernetes Secret tradicional

Kubernetes Secret --> Pod

HashiCorp Vault Injector

Vault --> Vault Agent --> Pod

AWS Secrets Manager

Secrets Manager --> Pod

External Secrets Operator

Secrets Manager / Vault --> External Secrets Operator --> Kubernetes Secret --> Pod

Cada alternativa tiene ventajas y desventajas que iremos analizando.

La rotación de credenciales

Otra razón importante para centralizar los secretos es la rotación de credenciales.

Supongamos que tenemos una contraseña de base de datos utilizada por diez aplicaciones.

Si gestionamos esta contraseña manualmente dentro de varios Kubernetes Secrets, cambiarla podría convertirse en un proceso bastante complicado.

Podríamos tener que:

Cambiar password en la base de datos
        ↓
Actualizar Secret cluster A
        ↓
Actualizar Secret cluster B
        ↓
Actualizar Secret cluster C
        ↓
Reiniciar aplicaciones

Cuantos más clústeres y aplicaciones tengamos, más complejo será el proceso.

Una bóveda nos permite centralizar esa información y automatizar parte de su ciclo de vida.

¿Por qué es especialmente importante en ambientes regulados?

La gestión de credenciales siempre es importante, pero adquiere todavía más relevancia cuando trabajamos con ambientes sujetos a requisitos estrictos de seguridad o cumplimiento.

Por ejemplo, en entornos relacionados con PCI DSS, normalmente tendremos controles más estrictos sobre aspectos como:

  • Acceso a información sensible.
  • Separación de responsabilidades.
  • Rotación de credenciales.
  • Auditoría de accesos.
  • Gestión de permisos.
  • Trazabilidad de cambios.

Por esta razón resulta especialmente interesante disponer de una solución centralizada donde podamos definir claramente:

Quién puede acceder
A qué secreto
Desde qué aplicación
Y bajo qué permisos

En lugar de distribuir credenciales manualmente entre servidores, pipelines, repositorios y clústeres.

No existe una única solución correcta

Es importante aclarar algo antes de continuar con la serie.

No existe una única solución correcta para gestionar secretos en Kubernetes.

La arquitectura adecuada dependerá de muchos factores:

  • El tamaño de la infraestructura.
  • Cantidad de clústeres.
  • Número de aplicaciones.
  • Requerimientos de seguridad.
  • Requerimientos de compliance.
  • Experiencia del equipo.
  • Cloud utilizado.
  • Necesidad de multi-cloud.
  • Nivel de automatización.
  • Costos operacionales.

Por ejemplo, si nuestra infraestructura está completamente en AWS, utilizar AWS Secrets Manager puede resultar una opción bastante natural.

Sin embargo, si tenemos infraestructura distribuida entre diferentes clouds, datacenters o plataformas, HashiCorp Vault puede ofrecernos una capa centralizada independiente del proveedor.

También podemos utilizar una combinación de varias soluciones.

Lo importante: evitar la gestión manual

Independientemente de la herramienta que utilicemos, uno de los principales objetivos debería ser reducir al máximo la gestión manual de credenciales.

Queremos evitar procesos como:

Copiar contraseña
        ↓
Editar YAML
        ↓
Crear Kubernetes Secret
        ↓
Actualizar aplicación
        ↓
Repetir en otro cluster

Y acercarnos más a algo como:

Secret Vault
      │
      │ Authentication
      ▼
Kubernetes / EKS
      │
      ▼
Application

Donde tanto la identidad como el acceso a los secretos estén definidos mediante configuraciones reproducibles y políticas de acceso.

¿Qué veremos en las próximas entradas?

En las siguientes publicaciones comenzaremos a llevar todos estos conceptos a la práctica.

Primero trabajaremos con HashiCorp Vault.

Veremos cómo desplegarlo en Amazon EKS y cómo integrarlo con Kubernetes para permitir que nuestros Pods puedan autenticarse y consumir secretos utilizando Vault Agent Injector.

Posteriormente nos moveremos al ecosistema nativo de AWS y veremos cómo utilizar AWS Secrets Manager desde nuestras aplicaciones ejecutándose en EKS.

Finalmente añadiremos External Secrets Operator, integrándolo primero con AWS Secrets Manager y posteriormente con HashiCorp Vault.

La idea será terminar la serie pudiendo comparar diferentes arquitecturas y entender cuándo puede resultar más conveniente utilizar cada una.

Conclusiones

La gestión de secretos es uno de esos temas que puede parecer sencillo cuando tenemos pocas aplicaciones, pero que rápidamente se convierte en un problema importante cuando nuestra infraestructura comienza a crecer.

Crear manualmente un Kubernetes Secret puede resolver una necesidad puntual, pero difícilmente será la mejor estrategia cuando manejamos muchos clústeres, aplicaciones y ambientes.

Centralizar nuestras credenciales en una bóveda como HashiCorp Vault o AWS Secrets Manager nos permite construir una arquitectura mucho más escalable y, sobre todo, establecer claramente cómo nuestras aplicaciones acceden a la información sensible que necesitan.

Pero guardar los secretos en una bóveda es solamente una parte del problema.

La siguiente pregunta será:

¿Cómo hacemos para que nuestros Pods en Amazon EKS puedan autenticarse de forma segura contra esa bóveda y obtener únicamente los secretos que realmente necesitan?

Eso es precisamente lo que iremos viendo durante las próximas entradas de esta serie.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

K8S

Artículo anterior

IRSA vs. EKS Pod IdentityNew!!