HashiCorp Vault Secure Storage Backend in Apache APISIX Ecosystem

API7.ai

January 24, 2022

Ecosystem

Con el auge de la arquitectura basada en microservicios, mantener la seguridad se ha vuelto mucho más desafiante que antes. Hemos superado el punto en el que nuestras 100 instancias de servidores backend acceden a nuestro servidor de base de datos con una única credencial secreta estática, ya que, en caso de una filtración de credenciales, todo el sistema se vería comprometido, y la revocación de esa credencial causaría una interrupción masiva del servicio (ahora nadie puede acceder a nada a menos que se reconfiguren las instancias). No podemos eliminar la posibilidad de una brecha de seguridad porque, a veces, ocurre lo inesperado. En su lugar, depende totalmente de nosotros controlar el radio de impacto en estas situaciones. Para abordar escenarios como este, una solución popular como HashiCorp Vault entra en escena en un entorno de producción para actuar como un sistema de gestión de secretos y cifrado basado en identidad. En este artículo, he demostrado cómo integrar Vault con el plugin jwt-auth de Apache APISIX (una puerta de enlace API nativa de la nube) para aprovechar eficazmente lo mejor de ambos mundos.

¿Qué es Vault?

HashiCorp Vault está diseñado para ayudar a las organizaciones a gestionar el acceso a secretos y transmitirlos de manera segura dentro de una organización. Los secretos se definen como cualquier forma de credencial sensible que debe ser controlada y monitoreada de cerca, y que puede usarse para desbloquear información sensible. Los secretos pueden ser contraseñas, claves API, claves SSH, tokens RSA u OTP. En el mundo real, es muy común tener una dispersión de secretos donde estos se almacenan en archivos de configuración o como variables en el código del programa, lo que a veces incluso termina en sistemas de control de versiones como GitHub, BitBucket o GitLab, lo que representa una gran amenaza para la seguridad. Vault resuelve este problema centralizando los secretos. Proporciona almacenamiento cifrado para secretos estáticos, generación de secretos dinámicos con un plazo de TTL, autenticación de usuarios (máquinas o humanos) para asegurarse de que estén autorizados a acceder a un secreto en particular, y mucho más. De esta manera, incluso en caso de una brecha de seguridad, el radio de impacto es mucho menor y está contenido.

Vault facilita mucho el control y la gestión del acceso al proporcionarnos una interfaz unilateral para gestionar cada secreto en su infraestructura. No solo eso, también ofrece la flexibilidad de crear registros de auditoría detallados y realizar un seguimiento de quién accedió a qué.

HashiCorp Vault

Sobre el plugin jwt-auth de APISIX

Es un plugin de autenticación que se puede adjuntar a cualquier ruta de APISIX para realizar la autenticación JWT (JSON Web Token, leer más) antes de que la solicitud se reenvíe al URI de upstream. En resumen, es un mecanismo de autenticación seguro que conduce a la autorización de recursos críticos. Normalmente, se utiliza una clave privada o un secreto de texto para firmar el JWT. El receptor del JWT verificará la firma para asegurarse de que el token no ha sido alterado después de ser firmado por el emisor. La integridad total del mecanismo JWT depende del secreto de firma (ya sea un secreto de texto o un par de claves RSA). Esto dificulta que fuentes no autenticadas adivinen la clave de firma e intenten cambiar las afirmaciones dentro del JWT.

Por lo tanto, el almacenamiento de estas claves en un entorno seguro es extremadamente crucial. Si caen en manos equivocadas, podría comprometer la seguridad de toda la infraestructura. Aunque desde el lado de APISIX tomamos todas las medidas para seguir las prácticas estándar de SecOps, es bastante común en entornos de producción tener una solución centralizada de gestión de claves como HashiCorp Vault para tener registros de auditoría detallados, rotación periódica de claves, revocación de claves, etc. Y sería un problema bastante engorroso si cada vez que ocurre una rotación de claves en toda la infraestructura, tuvieras que actualizar la configuración de Apache APISIX.

Pasos para usar Vault con Apache APISIX

Para la integración con Vault, Apache APISIX necesita cargar la configuración de Vault en config.yaml.

Internamente, APISIX se comunica con el servidor de Vault a través de las APIs HTTP del motor de secretos KV v1. Como la mayoría de las soluciones empresariales prefieren usar el motor de secretos KV - Versión 1 en sus entornos de producción, durante la fase inicial de soporte de APISIX-Vault, hemos optado por la versión 1. En versiones posteriores, agregaremos soporte para K/V versión 2.

La idea principal de usar Vault, en lugar del backend etcd de APISIX, es la preocupación por la seguridad en un entorno de baja confianza. Los desarrolladores de APISIX entendemos seriamente sus prioridades. Por eso recomendamos usar tokens de acceso de Vault que tengan un alcance limitado y puedan otorgar al servidor de APISIX acceso limitado.

Configurar Vault

Si ya tienes una instancia de Vault en funcionamiento con los privilegios necesarios, siéntete libre de omitir esta sección. Esta sección comparte las mejores prácticas para usar Vault dentro del ecosistema de Apache APISIX. Sigue los pasos mencionados a continuación.

Paso 1: Iniciar un servidor de Vault

Aquí tienes varias opciones, siéntete libre de elegir entre Docker, binarios precompilados o compilar desde el código fuente. Como para comunicarte con el servidor de Vault necesitas un cliente CLI de Vault, prefiero usar binarios precompilados en lugar del enfoque de Docker. De todos modos, depende totalmente de ti (siéntete libre de consultar la documentación oficial de instalación de Vault). Para iniciar un servidor de desarrollo, ejecuta el siguiente comando.

$ vault server -dev -dev-root-token-id=root
…
ADVERTENCIA: ¡El modo de desarrollo está habilitado! En este modo, Vault se ejecuta completamente en memoria
y comienza desbloqueado con una única clave de desbloqueo. El token raíz ya está
autenticado en la CLI, por lo que puedes comenzar a usar Vault de inmediato.
Es posible que necesites establecer la siguiente variable de entorno:
export VAULT_ADDR='http://127.0.0.1:8200'
La clave de desbloqueo y el token raíz se muestran a continuación en caso de que quieras
bloquear/desbloquear Vault o reautenticarte.
Clave de desbloqueo: 12hURx2eDPKK1tzK+8TkgH9pPhPNJFpyfc/imCLgJKY=
Token raíz: root
¡El modo de desarrollo NO debe usarse en instalaciones de producción!

Configura tu CLI actual con las variables de entorno correctas.

export VAULT_ADDR='http://127.0.0.1:8200'
export VAULT_TOKEN='root'

Habilita el motor de secretos k/v versión 1 de Vault con un prefijo de ruta adecuado. En esta demostración, elegiremos la ruta kv para evitar colisiones con la ruta de secretos predeterminada de Vault para la versión 2 de kv.

$ vault secrets enable -path=kv -version=1 kv
Éxito: ¡El motor de secretos kv se ha habilitado en: kv/

# Para reconfirmar el estado, ejecuta
$ vault secrets list
Ruta          Tipo         Acceso              Descripción
---- ---- -------- -----------
cubbyhole/    cubbyhole    cubbyhole_4eeb394c    almacenamiento secreto privado por token
identity/     identity     identity_5ca6201e     almacén de identidad
kv/           kv           kv_92cd6d37           n/a
secret/       kv           kv_6dd46a53           almacenamiento de secretos clave/valor
sys/          system       system_2045ddb1       puntos finales del sistema utilizados para control, políticas y depuración

Paso 2: Generar un token de acceso de Vault para APISIX

Este artículo trata sobre el uso de Vault desde la perspectiva del plugin jwt-auth. Por lo tanto, para un consumidor de APISIX (si no estás familiarizado con los consumidores en el ecosistema de APISIX, por favor lee la documentación sobre el Consumidor de Apache APISIX) con nombre de usuario jack, el plugin jwt-auth busca (si está habilitado con la configuración de Vault) secretos en la ruta <vault.prefix dentro de config.yaml>/consumer/<consumer.username>/jwt-auth en el almacenamiento kv de Vault. En este contexto, si estás asignando el espacio de nombres kv/apisix (ruta de Vault) como vault.prefix dentro de config.yaml para la recuperación de datos relacionados con APISIX, te sugerimos crear una política para la ruta kv/apisix/consumer/. El asterisco adicional () al final asegura que la política permita la lectura de cualquier ruta que tenga el prefijo kv/apisix/consumer.

Crea un archivo de política en el lenguaje de configuración de HashiCorp (HCL).

$ tee apisix-policy.hcl << EOF
path "kv/apisix/consumer/*" {
    capabilities = ["read"]
}
EOF

Aplica la política en la instancia de Vault.

$ vault policy write apisix-policy apisix-policy.hcl

Éxito: ¡Política cargada: apisix-policy!

Genera un token con la política recién definida que ha sido configurada con un límite de acceso reducido.

$ vault token create -policy="apisix-policy"

Clave                  Valor
--- -----
token                s.KUWFVhIXgoRuQbbp3j1eMVGa
token_accessor       nPXT3q0mfZkLmhshfioOyx8L
token_duration       768h
token_renewable      true
token_policies       ["apisix-policy" "default"]
identity_policies    []
políticas             ["apisix-policy" "default"]

En esta demostración, s.KUWFVhIXgoRuQbbp3j1eMVGa es tu token de acceso.

Agregar la configuración de Vault en Apache APISIX

Como se discutió anteriormente, Apache APISIX se comunica con la instancia de Vault a través de las APIs HTTP de Vault. La configuración necesaria debe agregarse en config.yaml. Aquí está la información breve sobre los diferentes campos que puedes usar:

  • host: La dirección del host donde se está ejecutando el servidor de Vault.
  • timeout: Tiempo de espera HTTP para cada solicitud.
  • token: El token generado desde la instancia de Vault que puede otorgar acceso para leer datos desde Vault.
  • prefix: Habilitar un prefijo te permite aplicar mejor las políticas, generar tokens con alcance limitado y controlar estrictamente los datos que pueden ser accedidos desde APISIX. Los prefijos válidos son (kv/apisix, secret, etc.)
vault:
  host: 'http://0.0.0.0:8200'
  timeout: 10
  token: 's.KUWFVhIXgoRuQbbp3j1eMVGa'
  prefix: 'kv/apisix'

Crear un Consumidor de APISIX

APISIX tiene una abstracción a nivel de consumidor que va de la mano con los escenarios de autenticación. Para habilitar la autenticación para cualquier ruta de APISIX, se necesita un consumidor con una configuración adecuada para ese tipo específico de servicio de autenticación. Solo entonces APISIX puede reenviar la solicitud al URI de upstream al realizar con éxito la autenticación con respecto a la configuración del consumidor. El consumidor de APISIX tiene dos campos: uno es username (requerido) para identificar un consumidor de los demás, y el otro es plugins que contiene las configuraciones específicas del plugin del consumidor.

Aquí, en este artículo, crearemos un consumidor con el plugin jwt-auth. Este plugin realiza la autenticación JWT para la(s) ruta(s) o servicio(s) respectivo(s).

Para habilitar jwt-auth con la configuración de Vault, realiza una solicitud a:

$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "username": "jack",
    "plugins": {
        "jwt-auth": {
            "key": "test-key",
            "vault": {}
        }
    }
}'

Aquí, el plugin busca el secreto de la clave dentro de la ruta de Vault (<vault.prefix desde conf.yaml>/consumer/jack/jwt-auth) para el consumidor jack mencionado en la configuración del consumidor y lo usa para la firma y verificación JWT posteriores. Si la clave no se encuentra en la misma ruta, el plugin registra un error y no puede realizar la autenticación JWT.

Configurar un Servidor de Upstream de Prueba

Para probar el comportamiento, puedes crear una ruta para un upstream (un manejador simple de ping que devuelve pong). Puedes configurarlo con un servidor HTTP simple en Go.

// servidor de upstream simple
package main

import "net/http"

func ping(w http.ResponseWriter, req *http.Request) {
    w.Write([]byte("secure/pong\n"))
}

func main() {
    http.HandleFunc("/secure/ping", ping)
    http.ListenAndServe(":9999", nil)
}

Crear una Ruta de APISIX con Autenticación Habilitada

Crea una ruta de APISIX con este servidor HTTP seguro de ping y el plugin de autenticación jwt-auth habilitado.

$ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "plugins": {
        "jwt-auth": {}
    },
    "upstream": {
        "nodes": {
            "127.0.0.1:9999": 1
        },
        "type": "roundrobin"
    },
    "uri": "/secure/ping"
}'

Generar un Token desde el Plugin jwt-auth

Ahora, firma un secreto JWT desde APISIX que se puede usar y pasar para realizar solicitudes a la ruta proxy [http://localhost:9080/secure/ping](http://localhost:9080/secure/ping) en el servidor de APISIX.

$ curl http://127.0.0.1:9080/apisix/plugin/jwt/sign\?key\=test-key -i
HTTP/1.1 200 OK
Fecha: Mar, 18 Ene 2022 07:50:57 GMT
Tipo de contenido: text/plain; charset=utf-8
Transfer-Encoding: chunked
Conexión: keep-alive
Servidor: APISIX/2.11.0

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ0ZXN0LWtleSIsImV4cCI6MTY0MjU3ODY1N30.nkyev1_KUapVgY_QVYETsSApA6gEkDWS8tsHFV1EpD8

En el paso anterior, si ves un mensaje como failed to sign jwt, asegúrate de tener una clave secreta almacenada en la ruta kv/apisix/consumers/jack/jwt-auth de Vault.

# ejemplo
$ vault kv put kv/apisix/consumer/jack/jwt-auth secret=$ecr3t-c0d3
Éxito: ¡Datos escritos en: kv/apisix/consumer/jack/jwt-auth!

Solicitar al Servidor de APISIX

Ahora, realiza una solicitud al proxy de APISIX para la ruta /secure/ping. Tras una validación exitosa, reenviará la solicitud a nuestro servidor HTTP en Go.

$ curl http://127.0.0.1:9080/secure/ping -H 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ0ZXN0LWtleSIsImV4cCI6MTY0MjU3ODU5M30.IYudBr7FTgRme70u4rEBoYNtGmGByzgfGlt8hctI__Q' -i
HTTP/1.1 200 OK
Tipo de contenido: text/plain; charset=utf-8
Longitud del contenido: 12
Conexión: keep-alive
Fecha: Mar, 18 Ene 2022 08:00:04 GMT
Servidor: APISIX/2.11.0

secure/pong

Cualquier solicitud sin un JWT válido generará un error HTTP 401 Unauthorized.

$ curl http://127.0.0.1:9080/secure/ping -i
HTTP/1.1 401 Unauthorized
Fecha: Mar, 18 Ene 2022 08:00:33 GMT
Tipo de contenido: text/plain; charset=utf-8
Transfer-Encoding: chunked
Conexión: keep-alive
Servidor: APISIX/2.11.0

{"message":"Missing JWT token in request"}

Diferentes Casos de Uso donde Vault se Puede Integrar con el Plugin jwt-auth de APISIX

El plugin jwt-auth de Apache APISIX se puede configurar para obtener claves secretas de texto simple, así como pares de claves públicas-privadas RS256 desde el almacenamiento de Vault.

:::note Para la versión inicial de esta integración, el plugin espera que el nombre de la clave de los secretos almacenados en la ruta de Vault esté entre [ secret, public_key, private_key] para usar la clave correctamente. En futuras versiones, agregaremos soporte para referenciar claves con nombres personalizados. :::

  1. Has almacenado un secreto de firma HS256 en Vault y deseas usarlo para la firma y verificación JWT.

    $ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
    {
        "username": "jack",
        "plugins": {
            "jwt-auth": {
                "key": "key-1",
                "vault": {}
            }
        }
    }'
    

    Aquí, el plugin busca la clave secret dentro de la ruta de Vault (<vault.prefix desde conf.yaml>/consumer/jack/jwt-auth) para el consumidor jack mencionado en la configuración del consumidor y la usa para la firma y verificación JWT posteriores. Si la clave no se encuentra en la misma ruta, el plugin registra un error y no puede realizar la autenticación JWT.

  2. Pares de claves RSA RS256, tanto la clave pública como la privada están almacenadas en Vault.

    $ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
    {
        "username": "jim",
        "plugins": {
            "jwt-auth": {
                "key": "rsa-keypair",
                "algorithm": "RS256",
                "vault": {}
            }
        }
    }'
    

    El plugin busca las claves public_key y private_key dentro de la ruta kv de Vault (<vault.prefix desde conf.yaml>/consumer/jim/jwt-auth) para jim mencionado en la configuración del plugin de Vault. Si no se encuentran, la autenticación falla.

    Si no estás seguro de cómo almacenar claves públicas y privadas en el almacenamiento kv de Vault, usa este comando:

    # siempre que tu directorio actual contenga los archivos llamados "public.pem" y "private.pem"
    $ vault kv put kv/apisix/consumer/jim/jwt-auth public_key=@public.pem private_key=@private.pem
    Éxito: ¡Datos escritos en: kv/apisix/consumer/jim/jwt-auth!
    
  3. Clave pública en la configuración del consumidor, mientras que la clave privada está en Vault.

    $ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
    {
        "username": "john",
        "plugins": {
            "jwt-auth": {
                "key": "user-key",
                "algorithm": "RS256",
                "public_key": "-----BEGIN PUBLIC KEY-----\n……\n-----END PUBLIC KEY-----"
                "vault": {}
            }
        }
    }'
    

    Este plugin usa la clave pública RSA desde la configuración del consumidor y usa la clave privada obtenida directamente desde Vault.

Deshabilitar Vault desde el Plugin

Ahora, para deshabilitar la búsqueda en Vault desde el plugin jwt-auth, simplemente elimina el objeto vacío de Vault de la configuración del plugin del consumidor (en este caso, es jack). Esto hará que el plugin JWT busque los secretos de firma (tanto HS256/HS512 como pares de claves RS512) en la configuración del plugin para las solicitudes posteriores a la ruta URI donde se ha habilitado la configuración de jwt-auth. Incluso si tienes la configuración de Vault habilitada en config.yaml de APISIX, no se enviará ninguna solicitud al servidor de Vault.

Los plugins de APISIX se recargan en caliente, por lo que no es necesario reiniciar APISIX.

$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "username": "jack",
    "plugins": {
        "jwt-auth": {
            "key": "test-key",
            "secret": "my-secret-key"
        }
    }
}'

Resumen

Este artículo te presenta la próxima versión de la integración de Vault con Apache APISIX y los detalles relacionados.

Siéntete libre de iniciar una discusión en GitHub Discussions o comunicarte a través de la lista de correo.

Tags: