Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Cómo inyectar secretos de AWS, GCP o Vault en un Pod de Kubernetes

By Ami MahloofMar 23, 20208 min read

Esta página también está disponible en English, Deutsch, Français, Italiano, 日本語 y Português.

1 pylutmoqk4ivd vequwuda

En el mundo de Kubernetes buscamos automatizar y reducir al mínimo la duplicación de código. Consumir secretos desde un secret manager en Kubernetes debería seguir esa misma lógica. Aquí te mostramos cómo lograrlo.

Esta publicación retoma el hilo de mi post anterior, donde expliqué cómo automatizar la inyección de secretos en Pods de Kubernetes cuando se utiliza Vault.

Los secretos en Kubernetes siguen siendo texto plano codificado en base64.

Aunque puedes cifrar los secretos en reposo, no se cifran dentro del Pod, y la clave de cifrado es global para Etcd (donde se almacenan los secretos), lo que genera un riesgo en un cluster multi-tenant donde todos los secretos quedan cifrados con la misma clave global.

1 pylutmoqk4ivd vequwudaInyecta secretos de AWS, GCP o Vault en un Pod de Kubernetes de forma segura

GCP KMS te permite crear una clave de capa de aplicación tipo envelope para resolver este problema. Pero, aun así, al final del recorrido el Pod termina consumiendo los secretos en texto plano.

Además de Hashicorp Vault, están los secret managers de AWS y GCP.

Aunque AWS y GCP no ofrecen tantas funciones como Vault —backends de autenticación, auditoría de acceso a secretos, revocación o incluso secretos dinámicos—, sí brindan una forma similar de almacenar secretos con versiones.

El reto: consumir secretos en Pods de Kubernetes

Consumir estos secretos dentro de un Pod de Kubernetes plantea un desafío: hay que escribir un script wrapper personalizado para gestionar la autenticación y el consumo de secretos como variables de entorno. Después, debes reemplazar ese script con el proceso wrapper para que herede todas las env vars y se convierta en PID: 1, algo crítico para manejar correctamente las señales de terminación en contenedores.

En el mundo de Kubernetes buscamos automatizar y reducir al mínimo la duplicación de código necesaria para gestionar este tipo de tareas de soporte (otro ejemplo sería automatizar DNS y certificados). Consumir secretos desde un secret manager en Kubernetes debería seguir esa misma lógica.

Algunas empresas prefieren no hacerse cargo de una instalación de Vault y, solo por esa razón, optan por los secret managers de AWS o GCP.

Los webhooks de Kubernetes que tienes que conocer

Kubernetes incluye un admission controller integrado capaz de interceptar las solicitudes al API server de Kubernetes antes de que el objeto se persista, pero después de que la solicitud haya sido autenticada y autorizada.

Hay dos controladores especiales integrados en el binario kube-apiserver:

  • MutatingAdmissionWebhook
  • ValidatingAdmissionWebhook

Estos se encargan de la mutación y validación de las solicitudes después de la autenticación y autorización.

Los controladores de mutación pueden modificar los objetos que admiten, mientras que los de validación pueden admitir o no un objeto en el API server.

Estos objetos webhook se instalan como cualquier otro objeto de Kubernetes mediante kubectl (consisten en la dirección https donde reside la lógica del webhook).

Una vez instalados, todas las solicitudes pasarán por la lógica de este webhook.

Mutation Webhook para consumir secretos

El siguiente código y conocimiento se apoyan en gran medida en el excelente trabajo realizado por Banzai Cloud, y quiero darles todo el crédito (¡deberías leer su blog, es excelente!).

Las principales diferencias entre los webhooks de Banzai Cloud y los míos son:

  1. Posibilidad de usar los secret managers de AWS, GCP y Vault.
  2. Eliminación de consulTemplates y de la función de secretos dinámicos del webhook de Banzai Cloud.
  3. Posibilidad de autenticar Vault no solo con un backend de Kubernetes, sino también con uno de GCP.
  4. Usar secretos explícitos del secret manager u obtener todos los secretos.
  5. Usar la ruta del secreto como directorio y obtener todos los secretos por debajo (limitado al primer nivel).
  6. Usar los nombres de los secretos como claves cuando la ruta del secreto termina con / y cada valor es un valor único:

El caso de uso es manejar mejor el contenido de los secretos: minimizar errores al añadir o editar un secreto y reducir la cantidad de pasos necesarios (leer, anexar, actualizar). 7. Usar rutas de secretos con comodín; por ejemplo, si la ruta del secreto termina con / y hay varios secretos que comienzan con db_, la ruta puede ser /secret/path/db_* 8. Detección automática de si el secreto es KV1 o KV2 (ya no hace falta anteponer /data en la ruta del secreto).

Cómo funciona el Mutation Webhook

Cada nueva solicitud al API server pasa por este webhook.

El webhook verifica si el objeto es un Pod y solo lo muta si contiene anotaciones específicas, por ejemplo:

https://gist.github.com/innovia/3e58eeb38668c9f4da4f1f76a736fbca

Cuando encuentra estas anotaciones, modifica el objeto Pod de la siguiente manera:

  • Añade un volumen compartido en memoria
  • Añade un init container con el binario vault-env y un comando para copiar vault-env a ese volumen compartido
  • Cambia el comando del Pod a vault-env
  • Añade variables de entorno de vault (ROLE, CA_PATH, SECRET_PATH) para facilitar las operaciones con vault.

Importante:

El webhook no añade los secretos como env vars en el Pod. Ningún dato confidencial se persiste en disco —ni siquiera de forma temporal— ni en etc., y tampoco se pueden ver en el objeto Pod modificado.

Todos los secretos se almacenan en memoria y solo son visibles para el proceso que los solicita.

Usar charts sin container.command y container.args explícitos

El webhook ahora es capaz de determinar el ENTRYPOINT y CMD del contenedor a partir de los metadatos de la imagen consultados desde el image registry. Estos datos quedan en caché hasta que se reinicie el Pod del webhook. Si el registry es de acceso público (sin autenticación), no necesitas hacer nada; si requiere autenticación, las credenciales deben estar disponibles en la sección imagePullSecrets del Pod.

Casi todos los charts permiten configurar tus podAnnotations, así que puedes consultar las anotaciones disponibles en la página README del webhook.

Consumir secretos fuera de Kubernetes

La herramienta secrets-consumer-env se ejecuta configurando algunas env vars; consulta el README.md para ver todas las opciones disponibles.

Instalar el webhook

Antes de instalar este chart debes crear un namespace para él. Esto se debe al orden en que se aplican los recursos de los charts (Helm recopila todos los recursos de un chart y sus dependencias, los agrupa por tipo de recurso y los instala en un orden predefinido (ver aquí — Helm 2.10).

El MutatingWebhookConfiguration se crea antes que el Pod backend que actúa como el webhook en sí. Kubernetes también querrá mutar ese Pod, pero todavía no está listo para hacerlo (recursión infinita en la lógica).

export WEBHOOK_NS=`<namepsace>`
WEBHOOK_NS=${WEBHOOK_NS:-vault-secrets-webhook}
kubectl create namespace "${WEBHOOK_NS}"
kubectl label ns "${WEBHOOK_NS}" name="${WEBHOOK_NS}"

Obtén el chart:

git clone https://github.com/innovia/secrets-consumer-webhook.git

Instala el chart:

helm upgrade --namespace ${WEBHOOK_NS} --install secrets-consumer-webhook secrets-consumer-webhook --wait

NOTA: --wait es necesario por problemas de timing de Helm; consulta este issue.

Sobre los clusters privados de GKE

Cuando Google configura el control plane para clusters privados, también configura automáticamente un VPC peering entre la red de tu cluster de Kubernetes y un proyecto independiente gestionado por Google.

Las reglas autogeneradas solo abren los puertos 10250 y 443 entre masters y nodos. Esto significa que, para usar el componente webhook con un cluster privado de GKE, debes configurar una regla de firewall adicional que permita al CIDR de tus masters acceder a tu Pod webhook por el puerto 8443.

Puedes encontrar más información sobre cómo añadir reglas de firewall para los nodos del control plane de GKE en la documentación de GKE.

Secretos explícitos vs. no explícitos (obtener todos)

Tienes la opción de elegir qué secretos quieres exponer a tu proceso, u obtener todos los secretos de una ruta o nombre dado.

Para seleccionar explícitamente los secretos del secret manager, añade una env var a tu Pod siguiendo esta convención:

env:
- name: <variable name to export>
value: vault:<vault key name from secret>

Configurar la autenticación con backend de Kubernetes en Vault

Vault puede autenticarse en Kubernetes usando una service account de Kubernetes.

Lo hace mediante otra service account llamada vault-reviewer con permiso auth-delegator, lo que le permite pasar el token de otra service account para autenticarse contra el master de Kubernetes.

Una vez que la autenticación con Kubernetes es exitosa, Vault devuelve un client token que sirve para iniciar sesión en Vault. Vault verificará un mapeo entre un rol de vault, la service account, el namespace y la política para permitir o denegar el acceso.

Este token de la service account vault-reviewer se configurará dentro de vault usando el CLI de vault.

Vamos a crear la service account para ese vault-reviewer.

https://gist.github.com/innovia/5435f2336e4dd0045dbb5842880b3334

Enlace al gist original

Ten en cuenta que, si configuraste Vault en otro namespace, debes actualizar este archivo en consecuencia.

kubectl apply -f vault-reviewer.yaml

Habilita el backend de autenticación de Kubernetes:

$ vault login
Token (will be hidden):
$ vault auth enable kubernetes
Success! Enabled kubernetes auth method at: kubernetes/

Configura Vault con el token y la CA del Vault-reviewer:

Nota: si configuraste Vault en otro namespace, añade el flag -n después de cada comando kubectl.

$ VAULT_SA_TOKEN_NAME=$(kubectl get sa vault-reviewer -o jsonpath="{.secrets[*]['name']}")
$ SA_JWT_TOKEN=$(kubectl get secret "$VAULT_SA_TOKEN_NAME" -o jsonpath="{.data.token}" | base64 --decode; echo)
$ SA_CA_CRT=$(kubectl get secret "$VAULT_SA_TOKEN_NAME" -o jsonpath="{.data['ca\.crt']}" | base64 --decode; echo)
$ vault write auth/kubernetes/config token_reviewer_jwt="$SA_JWT_TOKEN" kubernetes_host=https://kubernetes.default kubernetes_ca_cert="$SA_CA_CRT"
Success! Data written to: auth/kubernetes/config

Ejemplo de mapeo de un rol a una service account y un namespace en Vault

vault write auth/kubernetes/role/tester
bound_service_account_names=tester
bound_service_account_namespaces=default
policies=test_policy
ttl=1h

Configurar la autenticación con backend de GCP en Vault

Habilita el método de autenticación de Google Cloud:

$ vault auth enable gcp

Configura las credenciales del método de autenticación:

$ vault write auth/gcp/config \ credentials=@/path/to/credentials.json

Si usas credenciales de instancia o quieres especificar las credenciales mediante una variable de entorno, puedes saltarte este paso.

Crea un rol con nombre:

Para un rol de tipo iam:

$ vault write auth/gcp/role/my-iam-role \
type="iam" \
policies="dev,prod" \
bound_service_accounts="[email protected]"

Para un rol de tipo gce:

$ vault write auth/gcp/role/my-gce-role \
type="gce" \
policies="dev,prod" \
bound_projects="my-project1,my-project2" \
bound_zones="us-east1-b" \
bound_labels="foo:bar,zip:zap" \
bound_service_accounts="[email protected]"

Permisos de GCP requeridos

Permisos del Vault Server

Para roles de Vault de tipo iam, se le pueden otorgar a Vault los siguientes roles:

roles/iam.serviceAccountKeyAdmin

Para roles de Vault de tipo gce, se le pueden otorgar a Vault los siguientes roles:

roles/compute.viewer

Si prefieres crear un rol personalizado únicamente con los permisos de GCP estrictamente necesarios, usa la siguiente lista:

iam.serviceAccounts.get
iam.serviceAccountKeys.get
compute.instances.get
compute.instanceGroups.list

Permisos para autenticarse contra Vault

Ten en cuenta que los permisos mencionados antes se otorgan a los servidores de Vault. La service account de IAM o la instancia de GCE que se autentica contra Vault debe tener el siguiente rol:

roles/iam.serviceAccountTokenCreator

Si quieres, revisa el código fuente:

https://github.com/doitintl/secrets-consumer-webhook.git