Un script que inicia un cliente VPN de proveedor se queda colgado hasta que expira el tiempo, y el registro muestra que el cliente pide un código de verificación que nadie escribió. La contraseña se había enviado, así que la conclusión obvia es que la contraseña es incorrecta. No lo es. La petición del código es una conversación aparte en un canal aparte, y su script nunca la abrió.

Revise las cinco comprobaciones siguientes en orden. Deténgase en la primera que falle, porque cada paso posterior supone que los anteriores se cumplen.

Comprobación 1: ¿el cliente lee la entrada estándar, siquiera?

Pruébelo antes de escribir cualquier automatización: envíe un secreto ficticio al cliente por una tubería y observe qué ocurre. Si imprime su petición y luego espera, no está leyendo la tubería.

Muchos clientes abren el dispositivo de terminal directamente, o se niegan a tomar un secreto de un flujo que no es un terminal, para que una tubería perdida no pueda filtrar una contraseña. OpenVPN es la excepción documentada, porque --auth-user-pass acepta una ruta de archivo. Los clientes de proveedor que piden un código de un solo uso normalmente no tienen una opción equivalente, y su petición espera un terminal que su script nunca proporcionó.

Comprobación 2: ¿tiene el proceso hijo un terminal?

Un pty asigna un terminal al hijo, lo que convierte las peticiones en salida legible que puede responder en orden.

import pexpect

child = pexpect.spawn("openconnect vpn.example.net", encoding="utf-8", timeout=30)
child.expect("GROUP:")
child.sendline("staff")
child.expect("USERNAME:")
child.sendline("alice")
child.expect("PASSWORD:")
child.sendline(PASSWORD)
child.expect("Connected as", timeout=60)

Las cadenas de petición anteriores son ilustrativas; copie el texto exacto que imprime su cliente, incluidos mayúsculas y puntuación. Cada expectativa lleva un tiempo de espera, y un hijo cerrado lanza una excepción en lugar de devolver un estado, así que envuelva la secuencia y trate el final de archivo como un fallo. Fíjese en lo que este bloque no hace: el hijo muere con el script, porque un pty no es un mecanismo de desacople y disown es una orden interna del intérprete que no puede alcanzar un proceso que su script lanzó.

Comprobación 3: ¿hay una entrada no interactiva compatible?

El raspado de pantalla se rompe con las actualizaciones del cliente, así que busque primero una opción documentada.

Revise la salida de ayuda por si hay una opción de token o de código de un solo uso, y compruebe si el servicio acepta el código añadido al campo de contraseña. Donde el cliente lee un archivo de credenciales, dele el archivo en lugar de una petición:

openvpn --config office.ovpn --auth-user-pass <(printf "%s\n%s\n" "$USER_NAME" "$PASSWORD")

La sustitución de procesos da al cliente una ruta legible en sistemas que ofrecen /dev/fd. El secreto sigue siendo un secreto desde el punto de vista del cliente, así que manténgalo fuera de los registros, del historial del intérprete y de las listas de procesos. Si el cliente rechaza /dev/fd o abre la ruta de credenciales más de una vez, escriba un archivo real con permisos restrictivos y bórrelo en el mismo script.

Comprobación 4: ¿sobrevivirá el túnel al script?

Por defecto el túnel muere con su padre, que es lo contrario de lo que necesita un script de arranque.

disown -h solo ayuda cuando lanzó el cliente como una tarea del intérprete, porque edita la tabla de tareas de ese intérprete y no puede alcanzar un proceso que su script lanzó mediante un pty. Un gestor de servicios es el mejor hogar para todo lo que debe reconectarse sin supervisión, porque reinicia el cliente y registra por qué murió. En cualquier caso, espere un código nuevo después de cada reconexión: un token de un solo uso se consume con la primera autenticación correcta.

Observe qué hace sudo con la secuencia. La petición de contraseña de sudo no es la petición del VPN, y responderla con la contraseña del VPN es la forma más común de que un orden de expectativas por lo demás correcto siga fallando.

Comprobación 5: ¿debería el código de una persona estar en un script?

Esta es la comprobación que la gente se salta. Un token pertenece a una persona, mientras que un túnel que debe levantarse según un horario pertenece a la máquina.

Si una tarea debe reconectarse sin supervisión, pregunte si un certificado de máquina o una cuenta de servicio pueden sustituir el inicio de sesión interactivo. Automatizar el código de un solo uso de alguien convierte un segundo factor en un secreto almacenado, lo que es un cambio de política y no una comodidad de scripting. Donde el servicio admite ambas cosas, elija la credencial que encaje con cómo se ejecuta realmente la tarea. Escriba la respuesta, porque la siguiente persona preguntará por qué el script guarda un token.

Cómo se ve un script que funciona

El script imprime el paso al que llegó, nunca el secreto, y sale con un código distinto de cero ante cualquier expectativa fallida.

Pruébelo rompiéndolo a propósito: contraseña incorrecta, grupo incorrecto, token caducado. Cada fallo debería detenerse en un paso con nombre en lugar de colgarse. Después ejecútelo una vez contra el servicio real y compruebe el túnel desde fuera del script, no desde dentro.

Artículos relacionados