Devops
Secretos en k8s III – ArgoCD Vault Plugin, External Secrets y Reloader
En el capítulo anterior decidimos llevar todos nuestros secretos a Azure Key Vault y definimos lo que esperábamos de la solución que debía hacerlos llegar a nuestros clústeres de AKS. Git describiría el despliegue, Key Vault guardaría los valores, y algo en medio tendría que sincronizar ambos mundos. También nos encontramos con el problema del bootstrap: para leer secretos de Key Vault, los clústeres necesitan credenciales, y esas credenciales también son secretos.
Lo que todavía no teníamos eran las herramientas. Había cierta urgencia por tener una solución funcionando, así que construir algo desde cero no era una opción: habría supuesto más tiempo de desarrollo y de las pruebas de seguridad que cualquier componente nuevo tiene que superar en la empresa. En su lugar, miramos lo que ya estaba aprobado y funcionando en otros proyectos dentro de la organización. Encontramos tres candidatas: ArgoCD Vault Plugin, Secrets Store CSI Driver y External Secrets Operator. Tuvimos la oportunidad de probar las dos primeras en nuestros propios clústeres, así que parte de este análisis viene de nuestra experiencia y no solo de la documentación.
ArgoCD Vault Plugin
Como usamos ArgoCD para desplegar nuestras aplicaciones, la primera opción que consideramos fue su plugin para sincronizar con almacenes de secretos. La idea es que el Repo Server de ArgoCD, cuando renderiza las plantillas de Helm de una aplicación, sustituye las referencias a Key Vault por los valores reales antes de comparar el resultado con lo que está desplegado en el clúster. Si un valor cambia en Key Vault, el manifiesto renderizado es distinto del desplegado y ArgoCD actualiza el secreto automáticamente. Es ArgoCD quien crea el secreto en el clúster.
Esto encaja perfectamente con GitOps, y la rotación funciona sin ningún componente adicional. El problema es la dependencia. En nuestro entorno, ArgoCD es una herramienta compartida, instalada en su propio clúster y que gestiona aplicaciones de muchos equipos en muchos clústeres. Hoy lo mantiene nuestro equipo, pero no siempre fue así y podría volver a cambiar. Usarlo para todos nuestros secretos acoplaría cada aplicación a una herramienta que se puede modificar, mover o incluso sustituir por decisiones que no son nuestras. Además, ArgoCD también necesita un Service Principal para leer de Key Vault, y esas credenciales no se pueden gestionar con el mismo plugin.
Secrets Store CSI Driver
Secrets Store CSI Driver va en la dirección contraria. Se instala en el clúster y se conecta a Key Vault a través de la Container Storage Interface. Cada aplicación declara en un SecretProviderClass qué valores necesita, y el driver los obtiene cuando arranca el pod, montándolos como ficheros y, opcionalmente, creando un Secret de Kubernetes.
La mayor ventaja para nosotros era que todo ocurre dentro del clúster, sin depender de ArgoCD. Pero nuestra experiencia con él no fue buena. Aunque probamos distintas configuraciones, nunca conseguimos que los secretos se actualizaran automáticamente tras una rotación en Key Vault: teníamos que borrar el Secret de Kubernetes y reiniciar el ReplicaSet o el Job que creaba los pods. También teníamos que controlar el orden de los despliegues, porque cuando un secreto lo usaban varios pods, los que no lo creaban tenían que esperar a que existiera. Con charts de terceros como Redis, MongoDB, PostgreSQL o Superset, configurar los volúmenes era complicado y en muchos casos tuvimos que modificar los charts de Helm originales. Y las credenciales del driver tenían que guardarse en un Secret de Kubernetes en cada namespace, lo que nos llevaba exactamente a la duplicación que describimos en el capítulo anterior, con un proceso de rotación 100 % manual.
External Secrets Operator
External Secrets Operator también se ejecuta dentro del clúster, pero con un modelo distinto. En lugar de montar volúmenes en los pods, definimos un recurso ExternalSecret que describe qué valor hay que leer de Key Vault y qué Secret de Kubernetes hay que crear con él. El operador lee ese recurso, se conecta al almacén configurado y crea un Secret estándar, que refresca de forma periódica. La aplicación no sabe, ni necesita saber, de dónde viene el valor. Esto resolvía varios de los problemas que teníamos con el CSI Driver. El secreto existe de forma independiente de cualquier pod, así que no hay que gestionar el orden de despliegue, y los charts de terceros pueden usarlo sin modificaciones. Cuando el valor cambia en Key Vault, el Secret se actualiza en el siguiente refresco. Sin embargo, tampoco era una solución completa. External Secrets Operator también necesita credenciales para acceder a Key Vault, así que el problema del bootstrap seguía ahí. Y actualizar un Secret de Kubernetes no significa que una aplicación en marcha vaya a usar el nuevo valor.
Ninguna herramienta lo resolvía todo
Después de este análisis estaba claro que ninguna de las tres opciones cumplía por sí sola todos nuestros requisitos. ArgoCD Vault Plugin era muy bueno trayendo valores de Key Vault antes de que exista nada más en el clúster, pero no queríamos que todas nuestras aplicaciones dependieran de él. External Secrets Operator era la mejor opción para la sincronización del día a día, pero no podía crear sus propias credenciales y no reiniciaba las aplicaciones.
Así que, en lugar de elegir una herramienta frente a las demás, decidimos usar cada una para lo que mejor hace, y añadir un pequeño cuarto componente para cerrar el último hueco.
External Secrets para los secretos de las aplicaciones
External Secrets Operator pasó a ser el mecanismo principal para sincronizar los secretos de las aplicaciones. La razón principal fue de arquitectura: separa la sincronización de secretos del despliegue de aplicaciones. ArgoCD sigue desplegando las aplicaciones y manteniendo el estado deseado definido en Git, y eso incluye los recursos ExternalSecret, que solo describen qué leer y dónde crearlo. External Secrets Operator, que se ejecuta en cada clúster, se encarga de mantener los Secrets de Kubernetes alineados con Key Vault. Si algún día cambiamos de herramienta de despliegue, la forma en que nuestras aplicaciones reciben sus secretos no tiene por qué cambiar.
También significa que las aplicaciones trabajan con Secrets estándar de Kubernetes, como siempre. Para nuestros desarrolladores no cambió nada en la forma de consumir la configuración. La diferencia es que ya nadie tiene que crear esos secretos a mano.
Reloader: sincronizar no es recargar
Todavía quedaba un hueco. Imaginemos que se rota la contraseña de una base de datos en Key Vault. External Secrets Operator detecta el nuevo valor y actualiza el Secret de Kubernetes. Todo parece correcto. Pero la aplicación leyó esa contraseña de una variable de entorno cuando arrancó el contenedor, así que el proceso en marcha sigue teniendo la antigua. El secreto está sincronizado, pero la aplicación no.
Para resolverlo añadimos Reloader. Es un pequeño controlador que vigila ConfigMaps y Secrets y, cuando uno de ellos cambia, lanza un reinicio progresivo de los Deployments, StatefulSets o DaemonSets que lo usan. Solo hay que añadir una anotación a los workloads que deben reiniciarse. Con él, a un cambio en un Secret de Kubernetes le sigue automáticamente el arranque de pods nuevos con el nuevo valor.
ArgoCD Vault Plugin, solo para el bootstrap
Quedaba el problema del bootstrap. External Secrets Operator necesita credenciales para autenticarse contra Key Vault y no puede crearlas él mismo. Aquí nos resultó muy útil la experiencia previa con ArgoCD Vault Plugin.
Creamos una aplicación de ArgoCD dedicada que usa el plugin para leer de Key Vault las credenciales de External Secrets y crear los Secrets de Kubernetes correspondientes en los clústeres donde se necesitan. Como el plugin compara los manifiestos renderizados con los recursos desplegados, cuando estas credenciales se rotan en Key Vault, ArgoCD propaga los nuevos valores automáticamente. Sigue habiendo varias copias de las credenciales en los clústeres, pero ya nadie las mantiene a mano. Todas se generan a partir de un único valor en Key Vault.
Lo importante es el límite de esta responsabilidad. ArgoCD gestiona solo este pequeño conjunto de credenciales. No interviene en la sincronización de los secretos de las aplicaciones y, si ArgoCD deja de estar disponible durante un tiempo, External Secrets Operator sigue funcionando con las credenciales que ya tiene.
El único paso que no pudimos automatizar
Todavía queda una dependencia al principio de la cadena: el propio ArgoCD necesita credenciales para leer de Key Vault. Como dijimos en el capítulo anterior, no hay forma de eliminar del todo esta primera relación de confianza sin añadir otro sistema externo, que tendría el mismo problema. Por eso, la instalación inicial requiere una operación manual y controlada, que hace una sola vez un administrador con privilegios.
Lo aceptamos. La diferencia con el pasado es que el trabajo manual se reduce ahora a esta relación de confianza inicial, y no se repite cada vez que se crea o se rota un secreto.
Una rotación, de principio a fin
La mejor forma de ver el resultado es seguir una rotación real. Un usuario con privilegios actualiza la contraseña de una base de datos en Azure Key Vault. Nadie abre un repositorio ni se conecta a un clúster. Pocos minutos después, External Secrets Operator detecta el nuevo valor en su siguiente refresco y actualiza el Secret de Kubernetes en el namespace de la aplicación. Reloader ve que el Secret ha cambiado y lanza un reinicio progresivo del Deployment que lo usa. Los pods nuevos arrancan con la nueva contraseña y los antiguos se eliminan.
La única acción humana fue la primera. El resto es ahora supervisión: comprobar que todo ha funcionado como se esperaba, en lugar de ejecutar cada paso a mano.
Mirando atrás
Cuando empezamos este proceso, nuestros secretos estaban en texto plano en nuestros repositorios. SealedSecrets resolvió ese problema urgente, pero el coste de crear y rotar secretos creció con la plataforma. Llevar los valores a Azure Key Vault nos dio una fuente de verdad central, pero también nos mostró que la sincronización, el bootstrap y la rotación eran problemas distintos que necesitaban respuestas distintas.
La arquitectura que tenemos ahora separa esas responsabilidades. Azure Key Vault guarda los secretos. ArgoCD, con su Vault Plugin, se encarga del bootstrap. External Secrets Operator sincroniza los secretos de las aplicaciones. Reloader se asegura de que las aplicaciones los usen. Y Git sigue describiéndolo todo, sin contener ningún valor secreto.
No es perfecta. Sigue habiendo un primer paso manual, y cada componente nuevo es una cosa más que mantener y actualizar. Pero es una gran mejora respecto a donde empezamos, y nos da una base sólida para las siguientes iteraciones.
Probablemente la lección más útil de todo este proceso es que gestionar secretos a escala no consiste solo en decidir dónde se guardan. También importa cómo se crean, se sincronizan, se rotan y se consumen, y asumir que cada solución resolverá un problema y te mostrará el siguiente.