Generación de contraseñas con Cloudflare Workers

En este artículo se trata el proceso y las opciones para la generación de contraseñas utilizando Cloudflare Workers.

Generación de contraseñas con Cloudflare Workers

Este es realmente un desarrollo de esos en los que inviertes más tiempo del que acabas ahorrando para solucionar un problema que ya tenía solución, pero con el que aprendes mogollón. Sin duda, los mejores.

Si eres un poquito friki 🤓, estoy seguro de que en algún momento has tenido la necesidad de generar contraseñas. Ya sea para darte de alta en algún servicio online, si eres administador/a de algún entorno para cambiársela a algún usuario/a o para cualquier otra función. Contraseñas, contraseñas, contraseñas, ¡necesitamos contraseñas!

En una de esas veces en la que era necesario, en vez de acceder a uno de los millones de generadores online existentes me pregunté si podría crear alguno. Ya tenía el runrún de cacharrear algo con los Workers de Cloudflare. Se juntó el hambre con las ganas de comer y hasta aquí hemos llegado.

¿Pero qué es esto de los Workers de Cloudflare? Muy buena pregunta. La propia empresa lo define de la siguiente manera en una entrada de blog:

Workers es una plataforma que le permite ejecutar JavaScript en el perímetro global de Cloudflare de más de 175 centros de datos. Con solo unas pocas líneas de código, puede enrutar solicitudes HTTP, modificar respuestas o incluso crear nuevas respuestas sin un servidor de origen.

Si simplemente quieres una contraseña y estás trabajando bajo Linux, recuerda que puedes utilizar el siguiente comando:

openssl rand -base64 24 # Siendo 24 variable
# Resultado: EcidUiQ2crhxJrC0y99Y4JfexuuF7Kna

Instrucciones de uso y posibilidades

Puedes acceder a la versión live de este worker a través del siguiente enlace:

Según se accede a esta dirección devolverá la cadena de texto generada. Nada más. Esto es especialmente importante para usarlo en scripts en las que no son necesarias, y de hecho incomodan, las etiquetas HTML.

Te devolverá una cadena de 24 caracteres aleatorios formada por números, letras en mayúscula y minúscula y caracteres especiales.

Pero, ¿y si solo quiero 12 caracteres? ¿Y si no quiero caracteres especiales? ¿Y si únicamente necesito números en la cadena aleatoria? ¡Está todo pensado!

Vamos a ver las distintas opciones:

Ruta Longitud Caracteres Ejemplo
/ 24 Todos ck%dfDCXNu]ng+~usM|&&y{O
/8 8 Todos @JaeFdgR
/n 24 Solo números 458399725095831641438718
/a 24 Alfabéticos KaSIttubXUCFrCCFmepblJNm
/A 24 Alfanuméricos HBavK5MZHkXSCWGaftIMeaSf
/8/a 8 Alfabéticos FRvHGcmZ
/8/n 8 Solo números 59215032
/8/A 8 Alfanuméricos 3IQW1G9u
💡
No hay razón para utilizar ambos modificadores. Si no indicas valor, tomará los predeterminados: 24 de longitud y todos los caracteres. Pero, importante: si utilizas ambas, primero has de definir la longitud.

Código fuente

El worker está escrito en JavaScript y se divide en 3 partes:

  • El eventListener para cuando se realice la petición.
  • La función que "hace la magia".
  • La función que maneja la petición y crea la respuesta.

La parte realmente más interesante es la segunda, sin ninguna duda. Así que vamos a despachar las otras dos y así nos centramos en "donde hay chicha".

EventListener

Simplemente responde al evento con la respuesta de manejador de peticiones (siguiente apartado).

addEventListener("fetch", event => {
    event.respondWith(handleRequest(event.request))
})

handleRequest

Es la función a la que llama el EventListener, guarda la URL de la petición HTTP como una variable y luego se la pasa a la función genPassword para que la trate. Lo que retorne esta función es la respuesta a la petición web. Se añade la cabecera content-type para que el navegador lo trate como debe.

async function handleRequest(request) {
    const url = new URL(request.url);
    return new Response(genPassword(url.pathname), {
        headers: { 'content-type': 'text/plain' },
    })
}

Función genPassword

Esta es la función principal de la función. Como es un poco extensa, la dividiremos en varios bloques.

function genPassword(pathname) {
    let regex_num = new RegExp('^\/[0-9]+$');
    let regex_alpha = new RegExp('^\/[a,n,A]+$');
    let regex_alphanum = new RegExp('^\/[0-9]+\/[a,n,A]+$');
    var chars = "0123456789abcdefghijklmnopqrstuvwxyz!#$%&()*+,-./:;<=>?@[\]^_{|}~ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    
// [COSAS MÁGICAS]
    
 var password = "";
    for (var i = 0; i < passwordLength; i++) {
        var randomNumber = Math.floor(Math.random() * chars.length);
        password += chars.substring(randomNumber, randomNumber + 1);
    }
    console.log('Más información: https://pglez.es/genera-password-overview');
    return password;
}

En esta parte de la función, que realmente son dos partes (la inicial y la final) se declaran las variables iniciales y se calcula la contraseña. Las expresiones regulares que se ven al comienzo son las que se usan para comparar el path de la petición HTTP (pasada a la función como pathname) con las distintas posibilidades vistas en la tabla anterior en este artículo.

La parte faltante del código se organiza de la siguiente manera:

Si [[ pathname == '/' ]] 
  |
  |--> Longitud = 24 y listo

Si [[ pathname concuerda con la expresión regular 'regex_num' ]]
  |
  |--> Haz cosas (1)
   
Si [[ pathname concuerda con la expresión regular 'regex_alpha' ]]
  |
  |--> Haz cosas (2)

Si [[ pathname concuerda con la expresión regular 'regex_alphanum' ]]
  |
  |--> Haz cosas (3)

else --> Devuelve un error

Vamos a verlas a continuación.

Haz cosas 1: regex_num

Esta opción solo actúa cuando únicamente se añaden números al path. /30 o /8, por ejemplo. Es realmente sencillo, como vamos a ver a continuación. Únicamente retira la barra o slash de pathname y lo convierte a entero, para luego asignarle este valor a la variable que limita la función de cálculo aleatorio.

else if (regex_num.test(pathname)) {
        pathname = parseInt(pathname.substr(1));
        var passwordLength = pathname;
    }

Haz cosas 2: regex_alpha

Si bien esta parte del código es algo más extensa respecto a la anterior, es medianamente simple.

else if (regex_alpha.test(pathname)) {
        var passwordLength = 24;
        pathname = pathname.substr(1);
        if (pathname == 'a') {
            var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        } else if (pathname == 'n') {
            var chars = "1234567890";
        } else if (pathname == 'A'){
            var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
        } else {
            return "ERROR en la solicitud. +info: https://pglez.es/genera-password-overview - 1";
        }

Como únicamente se ha indicado en el path la letra, se entiende que el valor por defecto de 24 de longitud es correcto. Por tanto, definimos a 24 passwordLength. Aunque no es necesario, por estandarizar un poco, se retira de nuevo el slash de pathname. El resto es sencillo, según qué letra llegue a la función los posibles caracteres que pueden usarse en el aleatorio varían. Si bien el último else no debería ejecutarse nunca, se añade por si evoluciona el código.

Haz cosas 3: regex_alphanum

Este bloque de código se ejecutará cuando el pathname coincida con la expresión regular '^\/[0-9]+\/[a,n,A]+$'. Es realmente una combinación de los dos fragmentos anteriores:

else if (regex_alphanum.test(pathname)) {
        let gen_opt = pathname.substr(-1);
        var passwordLength = parseInt(pathname.substr(1).slice(0, -2));
        if (gen_opt == 'a') {
            var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        } else if (gen_opt == 'n') {
            var chars = "1234567890";
        } else if (gen_opt == 'A'){
            var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
        } else {
            return "ERROR en la solicitud. +info https://pglez.es/genera-password-overview - 2";
        }

En las primeras 2 líneas del condicional vemos:

  • Acceder a la opción indicada en el path:
    let gen_opt = pathname.substr(-1);
    
    Guardamos el último caracter por la derecha. Gracias a la expresión regular sabemos que no va a llegar hasta aquí una entrada que no cumpla con lo indicado.
  • Acceder a la longitud indicada en el path:
    var passwordLength = parseInt(pathname.substr(1).slice(0, -2));
    
    Para la longitud deseada debemos mantener únicamente la parte central del pathname: ni la barra inicial, ni la opción contenida en los dos últimos caracteres.
    Esto es justo lo que hacemos aquí. En vez de "coger" la parte que nos interesa, que puede cambiar dependiendo de la longitud deseada (4,10,200,1000...), retiramos lo que sabemos que siembpre se va a mantener: un caracter por la izquierda y dos por la derecha.

La parte que resta es igual a lo visto anteriormente, según la opción indicada, se utilizaran unos caracteres u otros para generar la contraseña aleatoria. En cualquier otro caso retornará un error.

Para terminar, tenemos el último else, que se activará en caso de no concordar con ninguna expresión regular de las anteriores:

    else {
        return "ERROR en la solicitud. +info: https://pglez.es/genera-password-overview - 3";
    }

Para terminar la función, justo después del cálculo de la contraseña tenemos un console.log para volcar un enlace con más información y el return del resultado:

	console.log('Más información: https://pglez.es/genera-password-overview');
    return password;

Resumen y tareas pendientes

De forma sencilla y compatible con automatizaciones podemos generar contraseñas con una simple petición web. Estas contraseñas son medianamente configurables, lo que permite aplicarlas en prácticamente cualquier ámbito.

La ventaja de usar esta herramienta y no otras recae en la rapidez y en la no existencia de rastreadores, publicidad y gasto de ancho de banda. El objetivo es generar una contraseña, nada más.

Queda pendiente revisar por si hubiera lugar para optimización del código, asi como evitar la solicitud de la generación de contraseñas de longitudes altas (del orden de decenas miles). En cualquier caso, Cloudflare parará la ejecución cuando supere ciertos milisegundos de CPU, al estar en el plan gratuito.

El código completo de este worker está disponible en Github:


¿Alguna duda?

No dudes en ponerte en contacto conmigo para cualquier duda, sugerencia, queja o aclaración que creas necesaria. ¡Será un placer hablar contigo!