Este artículo explica cómo listar todos los usuarios conectados a un servidor OpenVPN utilizando un pequeño script Bash.

Existe una forma de comunicarse con el demonio de openvpn, a fin de obtener estadísticas de conexiones, sin necesidad de tener habilitada la interfaz de administración. Consiste en enviar la señal SIGUSR2 al proceso correspondiente al demonio openvpn. De acuerdo a la documentación oficial de OpenVPN, esta señal está reservada para que el demonio vuelque las estadísticas de conexiones en el syslog.

En los sistemas Unix, las señales a procesos se envían con el comando kill. El cual, a pesar de su nombre, no sirve sólo para matar procesos, sino para enviar todo tipo de señales:

root@debian7:~# /bin/kill -L
 1 HUP      2 INT      3 QUIT     4 ILL      5 TRAP     6 ABRT     7 BUS
 8 FPE      9 KILL    10 USR1    11 SEGV    12 USR2    13 PIPE    14 ALRM
15 TERM    16 STKFLT  17 CHLD    18 CONT    19 STOP    20 TSTP    21 TTIN
22 TTOU    23 URG     24 XCPU    25 XFSZ    26 VTALRM  27 PROF    28 WINCH
29 POLL    30 PWR     31 SYS

Como se observa, la opción -L permite listar todas las señales disponibles en formato de tabla.

Tener en cuenta que algunas shells (como Bash) incluyen su propia versión embebida de kill, la cual posee diferentes argumentos a la versión de kill provista por el paquete util-linux (/bin/kill).

Para listar las señales utilizando la versión embebida de Bash, se debe utilizar la opción -l:

root@debian7:~# kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

Esta versión permite enviar el doble de señales. Notar, sin embargo, que los nombres cambian. Por ejemplo SIGUSR2 corresponde con USR2.

Veamos entonces cómo enviar una señal SIGUSR2 a un demonio openvpn utilizando kill (esta vez en un sistema OpenBSD). Primero es necesario determinar el PID (process ID) del demonio:

# ps -ax | grep "[o]penvpn --daemon"
66790 ??  Ss    114:09.05 /usr/local/sbin/openvpn --daemon --config /etc/openvpn/server.conf
# ps -ax -o pid,command | grep "[o]penvpn" | cut -d' ' -f1
66790

Luego enviar la señal USR2 utilizando kill:

# kill -USR2 66790

Al enviar esta señal, el demonio envía información estadística sobre conexiones al log del sistema:

# less /var/log/messages

Al abrir el log del sistema con algún visor de textoi, al final del log se encontrará información como la siguiente:

Sep 11 09:02:54 obsd openvpn[66790]: OpenVPN CLIENT LIST
Sep 11 09:02:54 obsd openvpn[66790]: Updated,Tue Sep 11 09:02:54 2018
Sep 11 09:02:54 obsd openvpn[66790]: Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since
Sep 11 09:02:54 obsd openvpn[66790]: juan.topo,192.168.123.45:49598,12591,12503,Tue Sep 11 08:24:05 2018
Sep 11 09:02:54 obsd openvpn[66790]: reverendo.alegria,192.168.123.33:33376,3871714,3806955,Tue Sep  4 09:25:52 2018
Sep 11 09:02:54 obsd openvpn[66790]: juan.topo,192.168.123.13:65424,1860375,1233229,Sat Sep  8 09:40:25 2018
Sep 11 09:02:54 obsd openvpn[66790]: hombre.duff,192.168.123.201:24263,30744,29671,Tue Sep 11 07:24:21 2018
Sep 11 09:02:54 obsd openvpn[66790]: ROUTING TABLE
Sep 11 09:02:54 obsd openvpn[66790]: Virtual Address,Common Name,Real Address,Last Ref
Sep 11 09:02:54 obsd openvpn[66790]: 10.8.0.10,reverendo.alegria,192.168.123.33:33376,Tue Sep  4 09:25:54 2018
Sep 11 09:02:54 obsd openvpn[66790]: 10.8.0.18,juan.topo,192.168.123.45:49598,Tue Sep 11 08:24:05 2018
Sep 11 09:02:54 obsd openvpn[66790]: 10.8.0.14,hombre.duff,192.168.123.201:24263,Tue Sep 11 07:24:23 2018
Sep 11 09:02:54 obsd openvpn[66790]: 10.8.0.6,juan.topo,192.168.123.13:65424,Sat Sep  8 09:40:26 2018
Sep 11 09:02:54 obsd openvpn[66790]: GLOBAL STATS
Sep 11 09:02:54 obsd openvpn[66790]: Max bcast/mcast queue length,0
Sep 11 09:02:54 obsd openvpn[66790]: END

La salida muestra una lista de clientes y tabla de ruteo.

Con el objetivo de simplificar y automatizar este proceso, me dispuse a crear un pequeño script. La idea es recuperar del log de sistema sólo la salida correspondiente a la respuesta del demonio a la señal USR2. Para ello se me ocurrió enviar un mensaje especifico al log del sistema, a modo de flag que permita delimitar claramente el comienzo de la respuesta. Este mensaje debe ser único para cada ejecución del script, con lo cual la fecha actual en un formato único es una buena opción.

Primero es necesario recuperar el PID del demonio openvpn en una variable:

# OPENVPN_PID=$(ps -ax -o pid,command | grep "[o]penvpn" | cut -d' ' -f1)
# echo $OPENVPN_PID
66790

Luego crear el flag utilizando la fecha actual en un formato particular:

# OPENVPN_STATUS=$(date +%Y%m%d%H%M%S)
# echo $OPENVPN_STATUS
20180911114324

Finalmente se debe enviar la señal USR2 igual que en el ejemplo, y cortar el log desde la aparición del flag hasta la próxima aparición de la cadena "END".

El código fuente del script es el siguiente:

#!/bin/sh

# Configuration
# WAIT:   Amount of seconds to wait for the client list
# SYSLOG: OpenVPN daemon logs location
#
#  On most Linux systems:
#  SYSLOG=/var/log/syslog
#
#  On OpenBSD systems:
#  SYSLOG=/var/log/messages
#
WAIT=2
SYSLOG=/var/log/syslog

# Get openvpn daemon PID
OPENVPN_PID=$(ps -ax -o pid,command | grep "[o]penvpn --daemon" | cut -d' ' -f1)

if [ -z "$OPENVPN_PID" ]; then
  echo "Server not running?"
  exit 1
fi

# Get current datetime
OPENVPN_STATUS=$(date +%Y%m%d%H%M%S)

# Send starting message (flag) to syslog
logger "OpenVPN Server Status - $OPENVPN_STATUS"

# Signal openvpn daemon
kill -USR2 $OPENVPN_PID

# Wait x seconds
sleep $WAIT

# Recover openvpn log messages
grep -A 1000 $OPENVPN_STATUS $SYSLOG | grep -B 1000 'END'

A su vez, lo he publicado en GitHub: linuxitux / scripts / security / openvpn_status.sh

Ejemplo de uso:

# ./openvpn_status.sh
Sep 11 13:01:02 obsd bowman: OpenVPN Server Status - 20180911130102
Sep 11 13:01:02 obsd openvpn[66790]: OpenVPN CLIENT LIST
Sep 11 13:01:02 obsd openvpn[66790]: Updated,Tue Sep 11 13:01:02 2018
Sep 11 13:01:02 obsd openvpn[66790]: Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since
Sep 11 13:01:02 obsd openvpn[66790]: juan.topo,192.168.123.45:49598,83128,81351,Tue Sep 11 08:24:05 2018
Sep 11 13:01:02 obsd openvpn[66790]: reverendo.alegria,192.168.123.33:33376,3963434,3897123,Tue Sep  4 09:25:52 2018
Sep 11 13:01:02 obsd openvpn[66790]: juan.topo,192.168.123.13:65424,1964241,1301889,Sat Sep  8 09:40:25 2018
Sep 11 13:01:02 obsd openvpn[66790]: hombre.duff,192.168.123.201:24263,101226,98479,Tue Sep 11 07:24:21 2018
Sep 11 13:01:02 obsd openvpn[66790]: ROUTING TABLE
Sep 11 13:01:02 obsd openvpn[66790]: Virtual Address,Common Name,Real Address,Last Ref
Sep 11 13:01:02 obsd openvpn[66790]: 10.8.0.10,reverendo.alegria,192.168.123.33:33376,Tue Sep  4 09:25:54 2018
Sep 11 13:01:02 obsd openvpn[66790]: 10.8.0.18,juan.topo,192.168.123.45:49598,Tue Sep 11 08:24:05 2018
Sep 11 13:01:02 obsd openvpn[66790]: 10.8.0.14,hombre.duff,192.168.123.201:24263,Tue Sep 11 07:24:23 2018
Sep 11 13:01:02 obsd openvpn[66790]: 10.8.0.6,juan.topo,192.168.123.13:65424,Sat Sep  8 09:40:26 2018
Sep 11 13:01:02 obsd openvpn[66790]: GLOBAL STATS
Sep 11 13:01:02 obsd openvpn[66790]: Max bcast/mcast queue length,0
Sep 11 13:01:02 obsd openvpn[66790]: END

Así como utilicé grep podría haber utilizado sed o awk. Tener en cuenta que (al resolver el corte con grep, en lugar de utilizar una herramienta más potente) si hay unos 500 usuarios conectados, el script probablemente falle al recuperar el texto. En tal caso será necesario optar por una mejor solución con awk o sed. Se aceptan sugerencias, o mejor pull requests, para recuperar el texto desde la línea que contiene $OPENVN_STATUS hasta la que contiene la cadena "END".

Referencias


Tal vez pueda interesarte


Compartí este artículo