Buenas tardes a todos. Me llamo Nathan Clark. Llevo 22 años usando JMP y trabajo en IDEXX Laboratories. Presentaré una ponencia sobre gestión de secretos junto con mi compañera, Sage Darling.
Hola. Soy Sage. Trabajo en IDEXX con Nathan. Soy ingeniera sénior de análisis y hoy les hablaremos de secretos.
Excelente. Empecemos hablando un poco sobre qué es un secreto. Normalmente, al menos desde la perspectiva del código, un secreto es una credencial necesaria para que el código funcione y permita acceder a ciertos recursos, pero que nadie más debería conocer. Es muy similar a las contraseñas con las que inicias sesión en tu ordenador. La usas a menudo, pero no debes compartirla con nadie.
Los secretos son mecanismos que las distintas plataformas han desarrollado para que estas credenciales existan de forma que sean invisibles una vez expuestas. Así, su uso en tareas en segundo plano resulta mucho más seguro. JMP no incorpora esta funcionalidad de forma nativa. Sin embargo, a medida que empezamos a trabajar más con código y procesos en segundo plano en JSL, hemos deseado contar con esta capacidad para compartir nuestro trabajo con otros y acceder a diferentes recursos sin revelar accidentalmente información confidencial.
En una conversación que tuve hace un tiempo con el soporte de JMP, conseguí este pequeño fragmento de código escrito por, creo que fue Evan McCorkle, quien lo diseñó como una forma de enmascarar estos secretos y algo que pudiera existir como lo que vemos aquí, pero luego se puede cifrar el script y usarlo.
Básicamente, utiliza una función que contiene los secretos, pero requiere un secreto secundario o una frase de acceso cuya existencia se conoce. Todas las funciones dentro de este código cifrado lo incluyen, pero nadie más lo sabrá. Incluso si saben que la función que intentan llamar es "usar el secreto A", no pueden acceder directamente a estos secretos sin conocer dicha frase. Una vez cifrado, se puede incluir como un script secreto y, así, acceder a esta información sin que el usuario se dé cuenta. Hasta ahora, al hablar con el soporte de JMP, a pesar de intentarlo, no han encontrado la manera de exponer el secreto durante las pruebas. Con el código en el que he estado trabajando, tampoco lo he logrado. Parece un excelente marco de trabajo para empezar a desarrollar una forma de almacenar secretos de manera segura, permitiendo a la vez que los usuarios los utilicen para realizar diversas actividades de código.
Hoy tenemos dos ejemplos. Les mostraré cómo lo hemos implementado para Snowflake. Aquí les presento, con permiso, la plantilla de conexión de Snowflake. Esta es básicamente la función que he escrito, con las partes más importantes ocultas.
Voy a ampliar esto un poco. Como ven, tengo la función `get_secrets` que almacena los secretos. En mi caso, para lo que queremos hacer, solo necesito un nombre de usuario y una contraseña para acceder a Snowflake con la configuración actual. Es un array de contraseñas bastante sencillo de almacenar aquí.
Pero también tengo otras funciones de soporte para utilizarlas. La primera crea una conexión real a la base de datos y simplemente devuelve ese nuevo conector de datos. Se trata de una cadena que usted mismo compone. Aquí tiene el conector de datos, que se conecta a todos los componentes específicos de su empresa. Lo único que necesita es tener instalado el controlador de Snowflake. Este es el nombre del controlador de Snowflake cuando lo instala desde el sitio web de Snowflake en su ordenador. De esta forma, puede establecer la conexión ODBC. Su base de datos será específica de su empresa. Sin embargo, los servidores de su empresa están conectados a Snowflake, etcétera. La contraseña se obtiene de este otro array que hemos recibido aquí. La introduce, y así tiene los demás componentes que puede conectar.
La última función que tengo va un paso más allá. Esta recibe la cadena de la consulta que se desea ejecutar, realiza todas las conexiones y ejecuta la consulta dentro de esta función. Finalmente, elimina todos los scripts de tabla que se generan a partir de esa tabla. Explicaré por qué esto puede ser útil en el ejemplo. Básicamente, es una capa adicional de protección para evitar que las credenciales se compartan o expongan accidentalmente.
Con esto en mente, les presento un ejemplo muy sencillo. En el primer ejemplo, simplemente realizo una consulta SQL directa, utilizando SQL personalizado e insertando la versión en cadena de la consulta. Al ejecutarla, obtengo una tabla de Snowflake. Se parece mucho a Big Class, porque, de hecho, lo es. Acabo de subir una copia a Snowflake. Esto les proporciona la tabla tal como la necesitan. Incluye información de origen, lo cual puede ser útil. Noté que, al hacerlo, se ocultan el nombre de usuario y la contraseña, tal como esperaba. Al investigar diferentes métodos, encontré un par de casos, ya fueran casualidades o no, en los que la contraseña no se exponía, pero sí el ID de usuario. Dependiendo del nivel de seguridad que necesiten para estas credenciales, por eso añadí la función que elimina esta información.
Si comento esta sección y ejecuto el código, se crea una nueva tabla con la otra función, donde ingreso esta consulta, y se crea lo mismo. La diferencia es que ahora la tabla no contiene ninguno de estos scripts. Eso es bueno. Para explicar mejor cómo funciona, por ejemplo, si quiero eliminar esto y seleccionar solo este gráfico de estrellas, todas las columnas, esto debería funcionar y las importará todas. Lo mismo ocurre si quiero agregar filtros. Se pueden agregar aquí. Funciona igual que el lenguaje SQL estándar al realizar estas consultas. Simplemente se pasa la cadena y se obtiene la tabla de datos limpia. Este es un ejemplo relativamente sencillo. Ahora le paso el tema a Sage, que tiene una versión más compleja de cómo esto puede funcionar con la estructura de capas de la función. Le paso el tema a Sage.
FoxDrive es un servicio de almacenamiento en carpetas en línea donde se puede guardar información. Nosotros lo usamos para muchas cosas diferentes, y por eso quise empezar por averiguar cómo autenticarme en Box para poder descargar archivos localmente desde Box a mi ordenador. Para ello, necesito crear una aplicación con Box Developers que me permita autenticarme. Esta aplicación requiere generar un JWT (token web JSON), que luego se utiliza para obtener un token de acceso y así acceder a Box. Aquí es donde mi situación se complicó un poco, ya que nunca antes había trabajado con JSON ni con tokens web JWT, así que terminé implementando esta solución.
Como describía Nathan, este es mi ejemplo a la derecha de cómo se ve mi archivo secrets.jsl. Actualmente no está cifrado, así que pueden ver su contenido. El mío comienza con una función para obtener los secretos. Aquí es donde se almacena toda la información de mi JWT. El JWT contiene información como el ID del cliente, el secreto del cliente y otros datos únicos de la aplicación. Los guardo en un array asociativo para que sean los secretos que luego se recuperarán. Para acceder a cada uno de estos datos, tengo otras funciones que acceden a los elementos dentro de esos arrays asociativos, comenzando con el ID del cliente.
Voy a mostrar solo uno de estos ejemplos, ya que si los explicara todos, verían todos los elementos del array asociativo y probablemente sería aburrido. Este es un ejemplo para obtener el ID del cliente. Dentro de este código, uso `secrets` para obtener `secrets`, que es mi clave de acceso a Box, y así obtener el ID del cliente. De nuevo, todo esto se mantiene en secreto para poder usarlo con esa función, ya que está cifrado.
Lo que tuve que añadir, que era un poco diferente, está relacionado con la obtención del token JWT de Box. Esto requiere un enfoque algo distinto, ya que los JWT requieren aserciones para obtener un nuevo token de acceso, lo que a su vez requiere un componente de firma. Debido a que se trata de un componente de firma, es información confidencial. Mi archivo secrets.jsl incluye funciones que procesan la información adicionalmente para obtenerla de Box. En este caso, mi función JWT... la voy a simplificar para que sea más fácil de ver. Mi función JWT crea la aserción con Box para poder enviar la información necesaria para obtener el token de acceso. Para ello, necesito obtener la información explícita que ya tengo para mis secretos: mi ID empresarial de Box, mi ID de cliente de Box, mi clave pública de Box, mi clave privada y mi clave secreta, que es única para mi aplicación.
A partir de ahí, tengo algunas soluciones para problemas como la falta de claves, lo cual es realmente importante. Para realizar la aserción JWT, tuve que usar Python. Esto también incluye otro ejemplo del uso de otro lenguaje dentro de secrets.jsl, lo que me obligó a maniobrar para asegurar que la información se pudiera imprimir. Por eso, la información se encuentra en secrets.jsl, encriptada, y no solo en el script que uso para acceder a la información JWT. Esta es también una oportunidad única para ver Python integrado en JSL, lo cual es genial.
Luego envío esa información al script de Python, que obtiene una firma específica para ese JWT. A partir de ahí, crea las credenciales y devuelve la aserción JWT para que pueda usarla más adelante. Ahora que tengo la aserción JWT, puedo obtener mi token de acceso. A partir de ahí, obtengo mi token JWT de Box, que vimos anteriormente, y luego uso mi client_ID y mi client_secret para crear un token. Para ello, utilizo la aserción JWT, mi client_ID, mis client_secrets y envío una solicitud HTTP a Box. Finalmente, obtengo mi nuevo token de acceso.
También incluyo aquí algunas funciones de análisis para quienes estén interesados en profundizar más, pero luego se enviará mi información relativa a si realmente obtuve el token de acceso y, posteriormente, se devolverá el token de acceso al usuario.
¿Dónde y cómo se usa esto? Voy a mostrarles un ejemplo, ya que podemos profundizar en temas interesantes sobre los tokens de acceso. Para integrarlo, tengo una carpeta genérica que les mostraré a modo de ejemplo, pero en este caso, solo les mostraré algunos datos de entrada.
Aquí incluyo la conexión de Box, que en mi caso está cifrada, porque es lo que se debe hacer después de procesar el archivo de secretos. Box organiza sus carpetas mediante códigos de identificación, lo que permite saber de forma unívoca en qué carpeta se encuentran los archivos que se deben buscar. Aquí es donde se colocaría el ID único de la carpeta. En este caso, solo estoy extrayendo archivos JMP. El proceso podría ser mucho más complejo según lo que se quiera hacer, pero mi ejemplo solo utiliza archivos JMP.
También incluí un parámetro para controlar la profundidad de la recursión. Box es interesante porque funciona de forma orientada a objetos. Se tiene un ID de carpeta, y dentro de esa carpeta se pueden tener otros IDs, por lo que es necesario buscar recursivamente en las carpetas. Permito que mi script acceda a tantas capas como considere necesario, en este caso, cinco. Luego, especifico la ubicación de descarga donde quiero guardar los archivos y cómo quiero nombrarlos. En este ejemplo, después de explicar cómo funciona y algunas de las complejidades relacionadas con el token de acceso (por motivos de seguridad), les mostraré cómo descargar algunos archivos JMP de Box.
Aquí hay algunas funciones para buscar recursivamente en archivos JSL. No voy a explicarlas ni a detallar las descargas de Python, sino que hablaré de la conexión principal con Box. En este caso, es donde comentábamos algunos aspectos de seguridad que podrían ser interesantes en cuanto a cuál es el mejor lugar para ubicarlo. La ubicación ideal varía según la empresa o persona, y el nivel de seguridad también depende del lugar. En mi caso, lo dejé aquí. Para empezar, esta función principal necesita obtener la información de Box. Para ello, necesito obtener mi token de acceso. Obtener mi token de acceso forma parte del archivo secrets.jsl que viste que estaba cifrado. En este caso, lo que hace es proporcionarme este token de acceso. Se trata de una variable que existe en este archivo JSL.
En este JSL, como obtengo este token de acceso aquí, si añadiera una instrucción print en la línea 583, por ejemplo, se podría ver el token. Lo interesante de Box es que estos tokens de acceso caducan. Prácticamente no hay riesgo de que alguien los use posteriormente. El riesgo se reduce, pero aún existe un riesgo de seguridad potencial si se imprime este token de acceso o se obtiene el script, se ejecuta y se imprime en esa ubicación.
Idealmente, en lugar de colocar el token de acceso de Box aquí, podrías mover los componentes de autenticación (tanto las pruebas como las búsquedas) a mi archivo secret.jsl para luego cifrarlos. De esta forma, ya no sería accesible y nadie más podría verlo. Es un caso de uso ligeramente diferente; puedes usarlo como prefieras. La forma más segura sería colocar el token de acceso de Box dentro de secrets.jsl y cifrarlo para que nadie pueda verlo. Pero en este caso, es un buen punto de partida; así es como lo dejé. Podemos hablarlo a partir de ahí.
Luego, obtiene el token de acceso de Box y, a partir de ahí, puedo buscar archivos de acceso de Box, ya que ahora tengo mi autenticación que confirma mi identidad y me da acceso. En mi caso, tengo una demo con un ejemplo donde extraigo información de un archivo en mi unidad de Box. Este archivo se llama Secrets_JMP Discovery, File Examples_Secrets y contiene algunas tablas JMP que voy a extraer y concatenar desde esta ubicación. Para ello, voy a ejecutar este script. A continuación, se escuchará la típica música de espera, porque a veces, al ejecutar un script, tarda un poco.
Autenticación, dime quién soy, dime quién eres. Aquí está. Este es el archivo que obtuve. Puedes ver mis tablas de origen; esos eran los nombres de las tablas JMP que tenía en esa ubicación de Box y estas son las tablas que se han concatenado. Solo un montón de bromas.
Eso es lo que he hecho con mis tokens de acceso de Box y la integración con JWT. Se lo voy a pasar a Nathan.
Con todo lo que Sage y yo hemos mostrado, ¿cómo se implementará esto realmente y cómo lograremos que, ahora que lo hemos hecho, más personas además de Sage y yo podamos colaborar? Lo hemos subido a GitHub. Aquí pueden ver el repositorio, que también está vinculado a la presentación, y nuestros ejemplos.
Ahora bien, dependiendo de cómo quieras organizarlo, tienes muchas opciones. Pero lo principal que hemos creado es esta plantilla de secretos. Es la que te mostré antes con ese valor base. Aquí tenemos diferentes ejemplos. Dentro de estos ejemplos, tenemos tres archivos principales: nuestra plantilla, que contiene toda la información. Aquí puedes completarla con tu información personal o de empresa, y esto es lo que se usa para el cifrado. Para mayor seguridad, te recomendamos que tomes esta plantilla, guardes el archivo en tu repositorio o clon y reemplaces "template" con la palabra "raw". De esta manera, toda la información importante queda oculta. En este archivo .gitignore, ignoraremos el archivo "raw.jsl". Así, esos archivos permanecen locales para que puedas trabajar con ellos. Conserva tus credenciales. Luego encriptas tu archivo original y lo subes como la versión encriptada de ese archivo.
Ahora, lo único que debería estar en GitHub es tu versión cifrada y funcional. En cada una de estas carpetas, tenemos documentación específica para su uso. La cantidad de información que quieras compartir, tanto en tu README principal como en la documentación, depende de lo que quieras que la gente sepa sobre cómo usar esta información. Si tienes alguna herramienta, o si utilizas algo como Project Jab, que Sage y yo presentamos el año pasado, esta permite crear complementos y extraer archivos de repositorios individuales. De esta forma, puedes obtener los archivos secretos que necesitas para realizar tu función. Es una buena manera de mantener esta información disponible y, al mismo tiempo, conservar distintos niveles de privacidad.
Como insinuó Sage, si te encuentras en una situación donde tu código se está volviendo un poco más complicado, es posible que, para mantener ese nivel de secreto, termines incluyendo cada vez más información en este archivo cifrado.
Como mencionaba Sage en su plantilla, tiene todas estas funciones que realizan mucho trabajo, pero al mostrar su ejemplo, destacó ese token de acceso específico. No recuerdo exactamente dónde se encuentra en la función, pero si no quieres que aparezca en tu ejemplo principal, simplemente agrega esa función a tu plantilla o archivo sin formato con plantilla, encripta el token y así queda oculto. Creo que las capas que tengas que atravesar dependerán de tus casos de uso y de lo que quieras lograr. En Snowflake, resultó ser relativamente fácil porque pude usar la conexión directamente o simplemente limpiar una tabla. Así que tu experiencia puede variar según lo que necesites conectar, ya sean API u otros recursos.
Pero creo que es una manera relativamente fácil de comprender y utilizar para explicar tus secretos y ofrecer muchas capas para que aquellos mejoren sus conocimientos de codificación, manteniendo al mismo tiempo prácticas sólidas de ciberseguridad para que todo siga siendo seguro y legal.
Creo que con esto concluimos todo lo que queríamos comentar hoy. Muchas gracias a todo el equipo de JMP por brindarnos esta oportunidad y gracias a todos los espectadores por echarle un vistazo.
Totalmente de acuerdo. Espero que estés guardando tus secretos a buen recaudo. Si quieres, puedes buscarnos en JMP Discovery este año; allí estaremos.
Sí, lo haremos. Gracias.