Tag: Shell

Comprobar permisos de Administrador en una ejecución (estilo 🐍 Python)

Habitualmente nos encontramos con la situación en la que debemos ejecutar ciertos programas o flujos de ejecución y necesitamos permisos de Administrador. Estas situaciones se nos presentan cuando tenemos que manejar carpetas y ficheros en acciones que impliquen lectura, escritura o ejecución en directorios protegidos del sistema. Para ello se requiere una elevación de permisos que deberemos comprobar si existe antes de lanzar nuestra ejecución.

Aprovechando que seguramente tengamos que hacer esto varias veces, creo que la mejor opción de hacerlo es con el mejor lenguaje pegamento que existe hasta la fecha, que no es ni más ni menos que Python. Por ello la solución final que vamos a plantear es excelente tanto para administradores de sistemas como para desarrolladores, siendo además multiplataforma GNU/Linux y Windows.

Tenemos que tener en cuenta que GNU/Linux y Windows no funcionan igual para la gestión de permisos. En Windows preguntaremos desde la API disponible si el usuario es administrador, mientras que en GNU/Linux preguntaremos por si el usuario tiene permisos root.

import ctypes, os
from sys import exit


def is_admin():
    is_admin = False
    try:
        is_admin = os.getuid() == 0
    except AttributeError:
        is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0

    print ("Admin privileges: {}".format(is_admin))
    return is_admin

Para mirar los permisos en Windows usaremos la librería ctypes y para los permisos en GNU/Linux usaremos la librería os. Ambas están incluida en Python. Por una razón arbitraría se ha elegido comprobar primero GNU/Linux y después Windows.

Cuando se comprueba en GNU/Linux invocamos a la función os.getuid que nos devolverá 0 en el caso de ser root. Si llamamos a la función desde Windows con Python nos elevará una excepción del tipo AttributeError, que aprovecharemos a capturar para hacer la comprobación en Windows invocando a la función ctypes.windll.shell32.IsUserAnAdmin. En ambos casos guardaremos el booleano de la evaluación y lo devolveremos al final de la función.

Hasta aquí sencillo, pero esto es código Python y es necesario tener el interprete de Python, con el que obliga al sistema a tener instalado el intérprete de Python. Para solucionarlo, deberíamos convertir nuestro código en un programa añadiendo un punto de entrada y devolviendo un código de retorno tras la ejecución.

import ctypes, os
from sys import exit


def is_admin():
    is_admin = False
    is_win = False
    try:
        is_admin = os.getuid() == 0
    except AttributeError:
        is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0
        is_win = True

    print ("Admin privileges: {}".format(is_admin))
    return is_admin, is_win

Antes modificaremos un poco la función is_admin()añadiendo un valor is_win que usaremos para saber que estamos comprobando en GNU/Linux o Windows los permisos de administrador, devolviendo el valor como un segundo valor en el retorno de la función.

Recordad que Python es posible recoger múltiples valores de retorno en una variable como una tupla, en variables separadas o incluso tirar los valores que no nos interesen.

def my_function():
    a = 2
    b = 3
    return a, b

a = my_function()
print(a)  # out: (2, 3)

a, b = my_function()
print(a)  # out: 2
print(b)  # out: 3

a, _ = my_function()  #  b es ignorado
print(a)  # out: 2

Además añadimos un punto de entrada debajo de la función is_admin().

if __name__ == "__main__":
    is_admin, is_win = is_admin()
    # Converting boolean to integer 
    ret = int(is_admin == False)
    if is_win:
        exit(ret)
    else:
        exit(ret * -1)

Comprobar si __name__ es __main__, es una excelente forma de hacer que cada unidad de fichero Python sea ejecutable si se quiere. El nombre de __main__ sólo es dado al fichero Python que es dado al intérprete para ser ejecutado y en caso de no serlo, siempre puede ser importado en otro fichero Python, sin que el punto de entrada afecte.

Como vemos, ahora estamos recogiendo en el retorno is_admin e is_win. Necesitamos el segundo valor para decidir el valor de retorno, ya que los códigos de error en Windows son enteros positivos y en GNU/Linux son enteros negativos. El único punto en común es que devolver un 0 es señal de que la ejecución fue correcta. Sabiendo esto debemos adaptar el retorno de los errores dependiendo el sistema.

💾 Binarizando nuestra aplicación Python

Para convertirlo en un ejecutable usaremos PyInstaller, el cual podéis instalar con un simple pip install pyinstaller. Para binarizar nuestra aplicación en un ejecutable autocontenido con el interprete de Python ejecutaremos:

pyinstaller -F is_admin.py

De esta forma resultará, mucho mas sencilla la distribución de nuestra aplicación.

👔 Puesta en Producción

Ahora vamos a plantear los dos casos de uso para Windows y GNU/Linux. Como aclaración, decir que los ejemplos expuestos se pueden usar tanto con la versión binarizada o con la de código invocando al intérprete de Python.

Windows

@echo off

cd
cd /D "%~dp0"
cd

#REM python is_admin.py
is_admin.exe

if errorlevel 1 (
   echo Exit Code is %errorlevel%
   echo.
   echo Admin privileges required
   echo Run it again as Administrator
   echo.
   pause
   exit /b %errorlevel%
)

#REM [your_executable].exe

pause

👀 Puedes descargar la versión binarizada de is_admin.exe

GNU/Linux

#!/bin/bash

pwd
cd `dirname $0`
SCRIPTDIR=`pwd`
pwd

#python is_admin.py
is_admin


if [ $? -eq 0 ]
then
  # [your_executable]
else
  echo Exit Code is $?
  echo.
  echo Admin privileges required
  echo Run it again as Administrator
  echo.
  read -rsp $'Press any key to continue...\n' -n 1 key
  exit $?
fi

read -rsp $'Press any key to continue...\n' -n 1 key

Si no estás tan familiarizado con el Bash te invito a que visites el proyecto ExplainShell para que puedas obtener la explicación de los diferentes comandos y argumentos.

🥅 Conclusiones

La solución presentada, sin ser seguramente perfecta, asegura tanto a administradores de sistemas como desarrolladores, comprobar si existen los permisos de administrador necesarios de manera sencilla; pudiendo hacerlo con una versión binarizada autocontenida que incluya el intérpretete Python, ejecutándolo usando el intérprete de Python del sistema gracias a ser todo dependencias internas, o llamando directamente a la función is_admin() si se quiere integrar en un desarrollo.

El código completo puedes encontrarlo en el siguiente gist de GitHub:

Espero que esta forma de trabajar te resulte útil y puedas extrapolarla y usar para otros casos Python como tu lenguaje pegamento generando código y aplicaciones altamente reutilizables.

Hidden Console Start (HCS) – Esconde la consola y lanza procesos/aplicaciones en segundo plano

Dicen que no te das cuenta de algo hasta que lo pierdes... Esto es lo que me ha pasado cuando recientemente mi orientación profesional ha cambiado y he tenido  que trabajar con sistemas Windows en vez de GNU-Linux.

Acostumbrado a tener corriendo servicios en distintas máquinas con GNU-Linux acabas echando de menos su shell y la bestialidad de herramientas que hay. Vale sí lo reconozco, al final trabajaba en una máquina Windows, pero porque sigo pensando que las herramientas de desarrollo siguen siendo mejor que las que hay disponibles en GNU-Linux. Hay que reconocer que un Windows lo uso sólo para desarrollar (eso sí multiplataforma) y para jugar, para lo demás GNU-Linux sin duda!!!

Una de las cosas que he echado en falta recientemente es la posibilidad de usar el operador & de background tan potente en GNU-Linux. Este operador permite lanzar un proceso que se sigue ejecutando sin bloquear la shell y si la cerramos, éste sigue en segundo plano. Pues esto tan chulo, no puedes hacerlo en un Windows, porque su comando start no lo permite y la opción /B del comando no impide que al cerrarse la consola de comandos el proceso que has lanzado muera. A no ser que el proceso que has lazando cree su propio hilo no dependiente del proceso de la consola. Y tampoco es posible lanzar un proceso de consola de comandos sin consola, a no ser que esté programado explícitamente que la consola se oculte o no aparezca. Windows en este aspecto se cubre mucho, porque la única forma de crear procesos en background es construyendo servicios de Windows que tienen sus propias reglas a cumplir y por consiguiente hace falta desarrollo específico para que algo corra en segundo plano.

Visto el panorama y teniendo cierto conocimiento e idea, me decidí a generar una herramienta similar que funcionase en Windows como el operador &, o por lo menos a intentarlo. Para ello lo primero pensé en que quizás lo más adecuado fuese hacerlo en multiplataforma y generar una solución que funcionase tanto en Windows como en GNU-Linux. Atendiendo a este requisito, enseguida a mi mente llegó Python, porque además su instalación de paquetes es sencilla y potente a partes iguales, por no decir que es posible generar binarios (por ejemplo un .exe en Windows) si hiciera falta gracias a herramientas como py2exe y PyInstaller.

Con el punto de partida claro y unas horas de desarrollo, consigo lo que bautizo como Hidden Console Start o HCS. El proyecto puedes encontrarlo en Github:

Y su instalación es sencilla si tienes ya Python en tu equipo. En caso de no tener Python, pásate por la web de Python y descárgate la última versión disponible. Una vez instalado Python, solo tienes que ejecutar el siguiente comando en el CMD o PowerShell para instalar HCS:

pip install hcs --upgrade

Una vez instalado puedes ejecutar el proceso o aplicación que se quiera ejecutando HCS de la siguiente forma:

hcs -e "P1" "P2" ... "Pn-1" "Pn"

Pongamos un ejemplo:

hcs -e "ping 127.0.0.1 > log1.txt" "ping 192.168.1.17 > log2.txt"

En el ejemplo se lanzan dos comandos ping a distintas direcciones que son guardados en log1.txt y log2.txt respectivamente. Como se puede ver la consola de comandos no queda bloqueada y la información de los comandos ejecutados se va guardando el los ficheros.

Si por alguna razón tus procesos o aplicaciones no mueren o acaban, puedes finalizarlos en el caso de Windows abriendo el administrador de tareas:

Y en el caso de GNU-Linux con htop:

De esta forma podemos lanzar procesos y aplicaciones en segundo plano en sistemas Windows de una forma más o menos equivalente a como lo haríamos en GNU-Linux. Y obviamente podríamos usar HCS en GNU-Linux porque también funciona, pudiendo usarlo de la misma manera que en Windows.

Resetear contraseña olvidada de tu Raspberry Pi

La semana pasada me encontré con el problema de tener que acceder vía SSH a una Raspberry Pi (rpi), para poder gobernar una serie de maquinas accesibles desde la red local a la que se conectaba dicha rpi, y no poder hacerlo porque no recordaba la contraseña...

Al ver que no recordaba la contraseña me puse a buscar la mejor solución para poder volver a tener acceso a la rpi. Alguno puede pensar que con volver a poner el sistema operativo Raspbian, queda todo solucionado, y tiene razón. El problema, es que toda la configuración y software específico se borra, teniendo que volver a cargar el sistema operativo, instalar el software y generar de nuevo las configuraciones... vamos un peñazo XD .

Alguno se le ocurriría montar un script en el que ir probando las posibles combinaciones por fuerza bruta. En mi caso no era una opción, ni suele serlo en general. Con que cumplas los criterios básicos de una contraseña decente la combinatoria se dispara... Los tiempos en los que las claves eran de hasta 6 caracteres normalmente sólo letras y/o números han quedado atrás.

Con todo ello en mente le di alguna vuelta más al asunto...

Me puse a pensar cuál es nivel de seguridad que me podría permitir resetear la clave, y pronto caí que en el físico. Tenía acceso físico al hardware de la rpi, con lo que cogí la tarjeta SD y la introduje en mi ordenador. En su interior enseguida encontré un archivo, que mirando un poco en Internet, entendí que me podía dar la clave para poder acceder al sistema antes de que completamente se cargase y me obligase a idetificarme con las credenciales de acceeso, de la cual había perdido la contraseña.

El fichero en cuestión es el "cmdline.txt" y contiene parámetros específicos para la carga del kernel de Raspbian:

dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait

Según la documentación es posible cargar la ejecución de cualquier binario con el parámetro "init", y en este caso nos interesa cargar la shell. Para ello añadimos "init=/bin/sh" al final de la línea precedido por un espacio:

dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait init=/bin/sh

Hecha esta modificación en el fichero lo guardamos, volvemos a introducir la SD en la rpi y la arrancamos. Esta vez durante el arranque, se parará en cierto momento la carga del sistema y nos aparecerá un prompt en el que podremos escribir comandos.

Antes de escribir cualquier comando debemos remontar el volumen para que efectivamente guarde los cambios. Para ello vamos a volver a montar el sistema de ficheros como lectura y escritura con el siguiente comando.

mount -rw -o remount /

Una vez hecho, vamos a invocar al comando que nos permite resetear la contraseña de nuestro usuario (normalmente pi si no los has cambiado):

passwd pi

Te pedirá que introduzcas la contraseña y otra vez más para confirmarla. Si todo ha ido bien verás un mensaje de este tipo:

passwd: password updated successfully

Ahora sincronizaremos los cambios que hemos hecho que están en memoria, para que queden persistentes en la memoria de la SD. Después reanudaremos el boot del sistema:

sync
exec /sbin/init

Una vez se haya cargado el sistema comprueba que puedes acceder con la clave que intruduciste y apaga la rpi con:

sudo poweroff

Una vez apagada la rpi volvemos a cargar la SD en el ordenador y editamos de nuevo el fichero "cmdline.txt" y lo volvemos a dejar como al inicio sin el parámetro "init":

dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait

Ahora ya podemos volver a poner la tarjeta SD en la Raspberry Pi y podremos volver a tener el sistema Raspbian como si nunca hubiéramos perdido la contraseña.

Recordad que esto ha sido posible gracias a tener acceso físico al hardware que ejecuta el software. El nivel físico siempre es el nivel más débil a nivel técnico y de infraestructura, por eso en Seguridad Informática hay que cuidar no sólo a quién damos acceso mediante credenciales, sino también quién tiene acceso físicamente al hardware donde se corre el software.

Añadir fecha y hora al nombre de un archivo en la consola de comandos de Windows

Habitualmente suelo trabajar con la consola de comandos de Windows y con scripts .BAT. Una de las tareas mas habituales es generar outputs en forma de fichero y una buena práctica suele ser añadir al nombre del fichero la fecha y la hora.

Una forma sencilla de mostrar la fecha en la consola de comandos de Windows es poner el siguiente comando:

echo %date%

Y la forma de mostrar la hora es:

echo %time%

Sabiendo esto podemos pasar al siguiente paso para poder añadirlo al nombre de un fichero. Como has podido observar al obtener la fecha, la obtenemos con el signo "/" intercalado entre el día, mes y año. Del mismo modo la hora aparece separada por el signo ":" , para separar las horas, los minutos y los segundos. Además la parte de los segundo viene con "," para marcar los decimales para las décimas de segundo. Los tres símbolos citados, no están permitidos a la hora de incluirlos en un nombre de un fichero en Windows, por lo que procedemos a quitarlos de la siguiente manera:

echo MyFile_%date:~-4,4%-%date:~-7,2%-%date:~-10,2%_%time:~0,2%-%time:~3,2%-%time:~6,2%.txt

Si analizamos el código, lo que se hace es una concatenación de valores que se imprimen como nombre. La fecha se le da la vuelta porque a la hora de ordenar por nombre, a nombres iguales se puede ordenar por años con una simple ordenación por orden alfabético. En el caso de la hora no es necesario darle la vuelta.

Para cada variable date o time se cogen los caracteres que nos interesan de la siguiente forma:

%variable:~startIndex,numChars%

startIndex determina la posición desde la que se empiezan a coger los caracteres dentro de la variable. El índice empieza en 0 y también puede ser negativo el índice, indicando la posición de inicio desde el final de la variable.

numChars determina el número de caracteres que se van a coger desde el startIndex fijado.

cmd-win-append-date-time

De esta forma y extrapolándolo  a otros comando, es posible añadir fecha y hora, sólo fecha o solo hora al nombre de un fichero de un output de un comando.