En este artículo, ejecutaremos un servidor NATS y lo aseguraremos; por lo que las conexiones de los clientes están encriptadas con TLS, y los usuarios finales se autentican con JWT y solo se les permite un acceso restringido a los sujetos. Utilizaremos la función de administración de cuentas NATS 2.0 para aislar las secuencias de mensajes.
Este artículo está dividido en varias partes:
  • Presentación de un caso de uso simple.
  • Creación del certificado del servidor.
  • Ejecutar un servidor NATS con TLS
  • Autenticación / Autorización en NATS
  • Configuración de un operador, cuentas y usuarios
  • Prueba de toda la configuración

Un simple caso de uso Pub / Sub

En un artículo anterior, Comenzando con NATS , presentamos los patrones disponibles en NATS. Ahora nos centraremos en Publicar / Suscribir.
Imaginemos que tenemos una aplicación web utilizada por varios clientes. Cuando un cliente utiliza un navegador para acceder a la aplicación, se suscribe automáticamente a un WebSocket para recibir notificaciones de eventos de cualquier tipo. Para fortalecer el proceso y no depender demasiado de WebSockets, especialmente si se generan muchos eventos, decidimos configurar una publicación / suscripción utilizando el sistema de mensajería NATS. Los requisitos son bastante simples:
  • la conexión a este sistema debe asegurarse con mTLS
  • todos los usuarios que se conectan al sistema de mensajería deben estar autenticados
  • la aplicación debe usar una cuenta de usuario especial que otorgue el derecho de publicar mensajes de cualquier tipo
  • la aplicación publica los eventos relacionados con el cliente en el tema " clients.NAME.events " de NATS, NAME es el nombre del cliente
  • Un cliente sólo puede suscribirse al “ customers.NAME.> ” sujeto para que pueda recibir mensajes publicados en “ customers.NAME.events” sino también todos los mensajes de abajo de la “ customers.NAME jerarquía” (por si acaso se necesita en este el futuro)
El siguiente esquema da un ejemplo de 2 mensajes generados por la aplicación (el editor ). Cada mensaje se publica sobre un tema específico que contiene el nombre del cliente con el que está relacionado el mensaje. Cada cliente (los 3 nombres de compañía falsos en la parte inferior del esquema) está suscrito a un asunto dedicado, por lo que solo puede recibir mensajes enviados en ese.
Ejemplo de Pub / Sub con aislamiento de secuencias de mensajes

Crear el certificado del servidor

Para asegurar nuestro servidor NATS, crearemos un certificado x509. Firmaremos este certificado con nuestra propia autoridad de certificación autofirmada.
Para esta tarea, usamos cfssl , un kit de herramientas TLS que viene de Cloudflare .

cloudflare / cfssl

CFSSL es la navaja suiza PKI / TLS de CloudFlare. Es una herramienta de línea de comandos y un servidor API HTTP para firmar ...

github.com

Nota: podríamos usar comandos OpenSSL pero la herramienta cfssl es más amigable :)

autoridad de certificación

Con cfssl, la creación de una CA autofirmada es fácil, ya que solo requiere un archivo de configuración como el siguiente.
{ 
    "CN": "Techwhale CA", 
    "clave": { 
        "algo": "rsa", 
        "tamaño": 2048 
    }, 
    "nombres": [ 
        { 
               "C": "FR", 
               "L": "Antibes ", 
               " O ":" Techwhale ", 
               " ST ":" PACA " 
        } 
    ] 
}
En este archivo, proporcionamos la información de una compañía ficticia llamada Techwhale ubicada en Antibes, Francia (la gran ciudad por cierto :)). Guardamos este contenido en un archivo ca.json y creamos la CA:
$ cfssl gencert -initca ca.json | cfssljson -bare ca
Varios archivos se crean a partir de este comando:
$ árbol. 
. 
├── ca-key.pem 
├── ca.csr 
├── ca.json 
└── ca.pem
Usaremos ca.pem y ca-key.pem para firmar el certificado del servidor en la siguiente parte.

Certificado del servidor

Primero, en un archivo de configuración config.json , definimos el perfil del certificado que queremos crear. Aquí solo se define un perfil, ya que solo crearemos un certificado para el servidor.
{ 
    "firma": { 
        "predeterminado": { 
            "vencimiento": "43800h" 
        }, 
        "perfiles": {    
            "servidor": { 
                "vencimiento": "43800h", 
                "usos": [ 
                    "firma", 
                    "firma digital ", 
                    " cifrado de clave ", 
                    " autenticación del servidor " 
                ] 
            } 
        } 
    } 
}
Nota: la conexión mTLS (TLS mutuo) podría configurarse para que un servidor también pueda autenticar a un cliente. Pero con mTLS, la revocación de la certificación puede ser dolorosa. A continuación veremos que NATS proporciona un gran mecanismo para la autenticación de clientes en función de la emisión de JWT.
Para crear el certificado del servidor, primero definimos un archivo server.json que enumera los dominios que queremos que contenga el certificado del servidor. En el ejemplo actual, suponemos que nuestro servidor NATS está disponible en el dominio messaging.techwhale.io.
{ 
    "CN": "Servidor", 
    "hosts": [ 
        "127.0.0.1", 
        "messaging.techwhale.io" 
    ] 
}
A continuación, generamos el certificado del servidor y lo firmamos con la CA creada anteriormente.
$ cfssl gencert -ca = ca.pem -ca-key = ca-key.pem -config = config.json \ 
  -profile = servidor server.json | servidor cfssljson -bare
Ahora tenemos nuestra CA y nuestro certificado de servidor listos.
$ árbol. 
. 
├── ca-key.pem
 ├── ca.csr 
├── ca.json 
├── ca.pem
 ├── config.json 
├── server-key.pem
 ├── server.csr 
├── servidor .json 
└── server.pem
En la siguiente parte, utilizaremos esos certificados (los archivos * .pem enumerados anteriormente) para proteger nuestro servidor NATS.

Ejecutar un servidor NATS con TLS

Estamos acostumbrados a TLS cuando accedemos a un sitio web seguro. El cliente (navegador web) tiene una lista de autoridades de certificación confiables, entre ellas la que se utiliza para firmar el certificado del servidor, por lo que sabe que puede confiar en el servidor.
En el ejemplo de esta publicación, configuraremos el servidor para que esté configurado con su certificado y clave privada. Además, proporcionaremos la CA al cliente para que pueda autenticar el servidor.

Configuración del servidor

Utilizaremos el siguiente archivo server.conf para configurar el servidor NATS.
escuchar: 0.0.0.0:4222 tls: { 
  cert_file: "./certs/server.pem" 
  key_file: "./certs/server-key.pem" 
}
El servidor envía su certificado ( server.pem ) durante el protocolo de enlace TLS para que el cliente pueda validar que ha sido firmado por una CA que conoce.
Entonces podemos ejecutar el servidor:
$ nats-server -c server.conf

Configuración del cliente

Puede encontrar un ejemplo de cliente de Python en https://gitlab.com/lucj/nats-demo/blob/master/consumer-TLS.py .
Ejemplo de código de aplicación para conectarse al servidor NATS a través de TLS
De este fragmento, podemos ver que:
  • el cliente se conecta al servidor NATS disponible en messaging.techwhale.io
  • el cliente recibe el archivo CA ( ca.pem) . Durante el protocolo de enlace TLS, puede validar el certificado proporcionado por el servidor, asegurándose de que haya sido firmado por esta CA

Probar la conexión

Ahora podemos ejecutar el servidor NATS y conectar un cliente a través de una conexión TLS.
Cliente que se conecta al servidor NATS a través de TLS
Desde los registros del servidor anteriores, podemos ver:
  • el servidor se inicia con la opción TLS activada ("Se requiere TLS para la conexión del cliente")
  • el cliente puede conectarse a través de TLS ("TLS handcheck complete") y suscribirse al tema nats.demo
Con esta configuración, la comunicación entre un cliente y el servidor NATS se cifra. El cliente puede autenticar el servidor pero el servidor aún no está habilitado para identificar al cliente. En el siguiente paso, utilizaremos el modelo de seguridad NATS 2.0 para configurar la autenticación del cliente (para que el servidor sepa que puede confiar en un cliente) y la autorización (para que un cliente tenga un acceso restringido al tema al que puede publicar o suscribirse).

Autenticación / Autorización en NATS

NATS 2.0 presenta un nuevo modelo de seguridad descentralizado con varias entidades organizadas jerárquicamente:
  • Un operador es responsable de ejecutar un clúster NATS y de firmar JWT de cuenta. Un operador tiene una clave privada y un JWT público firmado con esa clave
  • Una cuenta es la capa de aislamiento cuando se trata de comunicaciones. Por defecto, los usuarios de diferentes cuentas no pueden comunicarse, pero es posible compartir de forma segura los flujos de mensajes y servicios entre cuentas. Una cuenta como JWT pública firmada por el operador.
  • Los usuarios son emitidos por una cuenta. Define los límites con respecto al uso y la autorización sobre el espacio temático. Un usuario como JWT público firmado por la cuenta emisora. El JWT del usuario y su clave privada se guardan en un archivo de credibilidad (credenciales), este archivo es todo lo que un usuario necesita para autenticarse en el servidor NATS
NATS ofrece varias otras formas de autenticar a un usuario:
  • Autenticación de token
  • Nombre de usuario / credenciales de contraseña
  • Certificado TLS
  • NKEY con desafío
  • Cuentas
Nota: si desea saber más sobre cómo se usa cada opción de configuración, puede consultar la documentación oficial de NATS
En el ejemplo de esta publicación de blog, utilizaremos el enfoque JWT para autenticar las diferentes entidades en el sistema. Esos tokens (más sobre eso en https://jwt.io/ ) que se consideran públicos en NATS se crean con el NSC . Esta herramienta simplifica las tareas de creación y gestión de identidades y otros artefactos JWT. Por defecto, los JWT se guardan en ~ / .nsc y los secretos en ~ / .nkeys

Configuración de un operador, cuentas y usuarios

Primero creamos un operador a cargo de ejecutar nuestro servidor NATS:
$ nsc agregar operador -n op
Podemos usar NSC para describir una entidad y obtener información adicional.
Detalle del operador que estará a cargo del servidor NATS
Lo importante a tener en cuenta aquí es el hecho de que la ID del operador es la misma que la ID del emisor , ya que el operador ha sido firmado por sí mismo.
A continuación, creamos una cuenta llamada admin , usaremos esta como la entidad principal en la que definiremos un usuario administrador más adelante.
$ nsc agregar cuenta -n admin
Nota: nsc también se puede usar de manera interactiva, haciendo preguntas para varias configuraciones, usando el indicador -i / --interactive .
luc @ saturn: ~ $ nsc agregar cuenta -i 
? nombre de cuenta admin 
? generar una cuenta nkey Sí 
? válido 0 
? válido hasta (0 es siempre) 0
Podemos describir la cuenta como lo hemos hecho para el operador.
Detalle del uso de la cuenta para fines administrativos
En el resultado anterior, podemos ver que la ID del emisor es la ID del operador , ya que el token de la cuenta ha sido firmado por el operador que hemos creado anteriormente. No se ha establecido una configuración especial en el nivel de la cuenta, como podemos ver en todos los valores ilimitados . Volveremos a la parte de Importaciones / Exportaciones pronto.
A continuación, creamos un usuario en la cuenta de administrador y le damos el derecho de publicar y suscribirse a '>'. El uso de este carácter especial le da al usuario los derechos para publicar y suscribirse a todos los temas posibles.
Creación de un usuario dentro de la cuenta de administrador.
Al crear un usuario, se crea un archivo .creds (como podemos ver en el resultado del comando anterior). Un usuario usará su archivo de credenciales para autenticarse en un servidor NATS.
Al describir al usuario, podemos ver que ha sido firmado por la cuenta de administrador . También podemos ver los temas a los que puede publicar y suscribirse.
Detalles del usuario administrador creado dentro de la cuenta de administrador
Luego creamos una cuenta para un cliente, consideremos una empresa falsa llamada OrtizPLC
$ nsc agregar cuenta -n OrtizPLC
Dentro de esa cuenta, creamos un usuario llamado ortiz. Solo queremos que este usuario pueda suscribirse a los asuntos clientes.OrtizPLC.>" (Todos los temas de la jerarquía clientes.OrtizPLC ). Además, no debería tener derecho a publicar sobre cualquier tema.
$ nsc agregar usuario -a OrtizPLC -n ortiz \ 
  --deny-pub ">" \ 
  --allow-sub "clients.OrtizPLC.>"
Nota: si a un usuario se le niega publicar en cualquier tema, podríamos darle el permiso de respuesta para que pueda publicar en un tema de respuesta que se le ha dado (patrón de Solicitud / Respuesta).

Vista rápida de JWT y nkeys

Dentro de la carpeta ~ / .nsc, podemos ver todos los JWT creados, uno para cada entidad (1 operador, 2 cuentas, 1 usuario por cuenta).
JWT creado para cada entidad
Centrémonos en el JWT creado para el usuario ortiz emitido desde la cuenta OrtizPLC .
Desde JWT.io podemos decodificar fácilmente el contenido de este JWT y obtener información relacionada con varios usuarios, entre ellos los permisos adjuntos al usuario:
  • denegado para publicar en ' > '
  • permitido suscribirse a ' clients.OrtizPLC.> '
creds (archivo de credenciales) del usuario ortiz
Además, desde la carpeta ~ / .nkeys (toda la información allí debe permanecer privada) podemos ver los archivos de credenciales creados para cada usuario.
Echemos un vistazo al contenido del archivo de credibilidad del usuario ortiz .
archivo creds (credenciales) creado para el usuario ortiz
Como podemos ver, contiene su clave JWT pública, así como la clave privada asociada. Cuando un usuario se conecta al servidor NATS que proporciona su archivo de credibilidad, y debido a que el servidor se inicia con el conocimiento del JWT del operador, se verifica la cadena de confianza. Va desde el usuario a la cuenta que firmó su JWT, al operador que firmó el JWT de la cuenta. Esta cadena de confianza permite al servidor autenticar al usuario y asegurarse de que puede confiar en él.

Compartir mensajes entre cuentas

Ahora queremos asegurarnos de que el usuario de ortiz pueda suscribirse a los mensajes publicados por el usuario administrador sobre el tema " clientes.OrtizPLC.> ".
Para compartir mensajes entre cuentas, tenemos que exportar una transmisión desde la cuenta de administrador e importarla en la cuenta de OrtizPLC.
  • Crear una exportación
El siguiente comando crea una exportación desde la cuenta de administrador . Permitirá que las cuentas que lo importen se suscriban al tema " clientes.OrtizPLC.> ". Esta exportación no se pone a disposición del público.
$ nsc add export -a admin -n admin \ 
  -s "clientes.OrtizPLC.>" --privado
Al describir la cuenta de administrador una vez más, vemos una tabla que muestra la transmisión privada que acabamos de exportar.
Transmita la exportación por la cuenta de administrador para que los mensajes se puedan compartir con otras cuentas
A continuación, importaremos esta transmisión a la cuenta de OrtizPLC .
Primero necesitamos obtener la identificación de la cuenta OrtizPLC
$ export ID = $ (nsc describe la cuenta OrtizPLC | grep "Account ID" | awk '{print $ 5}')
Luego creamos un token de activación para permitir que la cuenta OrtizPLC importe la transmisión
$ nsc generar activación -o activación.jwt \ 
  --account admin \ 
  --target-account $ ID \ 
  --subject "customers.OrtizPLC.>"
Usando este token de activación podemos crear una importación en la cuenta OrtizPLC :
$ nsc add import -a OrtizPLC --token automation.jwt
Al describir la cuenta de OrtiZPLC , podemos ver una tabla que muestra la importación disponible.
Transmisión importada a la cuenta OrtizPLC
Un usuario dentro de la cuenta OrtizPLC ahora podrá suscribirse al asunto clients.OrtizPLC .>" Y recibir un mensaje publicado por un usuario administrador. Esto es lo que probaremos pronto.

Usar el servidor de cuentas NATS

El servidor de cuentas NATS es un servidor HTTP que aloja y sirve JWT para la autenticación de cuentas NATS 2.0. Puede servir JWT de varias tiendas:
  • un directorio
  • un directorio NSC
  • memoria (para fines de prueba)
A continuación, ejecutaremos un servidor de cuenta NATS y configuraremos NATS para que obtenga JWT de ese servidor.
Nota: podríamos configurar el servidor NATS de manera que lea directamente los JWT de la memoria o de un directorio, pero en esta configuración, la actualización de una entidad requeriría una recarga del servidor. Además, es posible configurar NATS de manera que el servidor de cuentas lo notifique automáticamente en caso de que las cuentas / usuarios se actualicen.
Primero recuperamos el servidor de la cuenta con el siguiente comando:
$ ir a obtener github.com/nats-io/nats-account-server
Luego lo ejecutamos proporcionando un camino hacia el operador que hemos creado previamente.
$ nats-account-server -nsc ~ / .nsc / nats / op
Nota: el servidor de cuentas también se puede ejecutar desde una imagen de contenedor disponible en DockerHub
Luego, para que el servidor NATS lea los JWT del servidor de cuentas, necesitamos actualizar su archivo de configuración proporcionando:
  • una referencia al operador JWT
  • la URL del servidor de la cuenta
escuchar: 0.0.0.0:4222 operador: /Users/luc/.nsc/nats/op/op.jwt 
resolver: URL (http: // localhost: 9090 / jwt / v1 / accounts /) tls: { 
  cert_file: ". /certs/server.pem " 
  archivo_clave:" ./certs/server-key.pem " 
}
Entonces podemos iniciar el servidor NATS con esta nueva configuración.
$ nats-server -DV -c server.conf
En la siguiente captura de pantalla, se inicia la cuenta del servidor nats (panel superior) y se ejecuta un servidor nats (panel inferior) utilizando el servidor de cuentas como proveedor de identidad (JWT).
El servidor NATS obtiene JWT del servidor de cuentas

Probar toda la configuración

Para asegurarnos de que todo funcione como se espera, ahora:
  • conectarse utilizando el usuario ortiz y suscribirse a "clientes". OrtizPLC .> "Sujeto
  • conectarse usando el administrador de usuario y publicar un mensaje sobre el tema " clientes.OrtizPLC.events "
  • asegúrese de que el usuario ortiz reciba el mensaje
Los clientes de Python consumer.py y producer.py de ejemplo utilizados para esta prueba están disponibles en el repositorio https://gitlab.com/lucj/nats-demo .
El usuario ortiz recibe un mensaje publicado por el usuario administrador sobre los clientes en cuestión.
El usuario ortiz solo puede suscribirse a un tema de la jerarquía clientes.OrtizPLC " (por ejemplo: customer.OrtizPLC.events , clients.OrtizPLC.alerts , ...). La captura de pantalla a continuación muestra el error encontrado si intenta suscribirse a un tema que pertenece a otro cliente (LynchLLC, tal vez uno de sus competidores :)
El usuario ortiz intenta suscribirse a mensajes relacionados con otro cliente

Resumen

Espero que este tutorial te haya dado algunas ideas sobre cómo configurar un mensaje Pub / Sub con NATS. El enfoque que utilizamos en este artículo se define como entrega "como máximo una vez" ya que el sistema de mensajería no guarda los mensajes. En un próximo artículo, nos enfocaremos en NATS Streaming asegurando la persistencia del mensaje para obtener "al menos una vez" entrega.
Crédito: gracias a Derek Collison por revisar el artículo y ayudarme a comprender mejor el modelo de autenticación de NATS.