Seguridad · 12 min read · Jan 10, 2026
Creando un Directorio Seguro Con PAM Y EncFS
Creando un Directorio Seguro Con PAM Y EncFS
Contenidos
. Introducción
. Instalación de encfs
. Instalación de pam_script
. Ajustando la configuración de PAM
. El resultado
Introducción
Trabajo mucho con programas que requieren credenciales
Ejemplos de esos programas son:
. mount.cifs
. fusesmb (busca detalles aquí)
Ahora, en mi red (y en otras) las credenciales proporcionadas al iniciar sesión podrían (y deberían) ser utilizadas por esos programas. ¿Cómo puedes recuperar estas credenciales, proporcionando suficiente seguridad?
Con el módulo PAM pam_script es posible almacenar la contraseña en un archivo, que será utilizado por fusesmb y mount.cifs para leer la contraseña.
Para lograr seguridad, uno podría hacer que el usuario que inicia sesión sea el propietario y negar la lectura/escritura a cualquier otra persona. Eliminar este archivo cuando el usuario termine su sesión.
Esto es suficiente, para el tiempo de ejecución. Pero me preguntaba, ¿qué pasa si el sistema se bloquea y el archivo con las credenciales permanece en el disco duro? ¡Cualquiera que pueda montar este disco duro con, por ejemplo, un lifecd, puede leer este archivo!
¡Por eso estaba buscando una forma de cifrar este archivo!
¡Con encfs esto es muy posible! En tiempo de ejecución proporciona una interfaz a archivos y directorios cifrados, que solo existen en tiempo de ejecución. ¡Cuando el sistema no está en funcionamiento, solo hay archivos cifrados, inútiles cuando no conoces la clave! Y esta clave es exactamente la contraseña (cifrada). ¡Por eso he elegido una combinación de PAM y Encfs!
Busca el sitio web de Encfs: http://freshmeat.net/projects/encfs
Esta construcción está destinada a proporcionar suficiente seguridad para el tiempo de ejecución y el tiempo de inactividad (después de un bloqueo) para almacenar información sensible, no para crear un directorio seguro permanente en tu disco duro para almacenar documentos.
Instalación de encfs
Por supuesto, FUSE debe estar instalado.
La instalación es muy sencilla:
(busca en el sitio web para rlog que es requerido por EncFs)
tar -xzf encfs-*.tar.gz
cd encfs-*
configure --prefix=/usr --sysconfdir=/etc --libexecdir=/usr/sbin
make
make installDespués de la instalación puedes probar si funciona:
mkdir -p ~/test/encrypted
mkdir -p ~/test/decrypted
encfs ~/test/encrypted ~/test/decrypted
mount (debería mostrar el montaje recién creado)
echo "Esto es muy secreto." > ~/test/decrypted/testfileEl directorio encrypted contiene los archivos cifrados, y permanece en el disco duro después de desmontar y/o apagar. El directorio decrypted es la interfaz a él, y desaparece cuando se desmonta y/o se apaga.
El archivo testfile debería aparecer en el directorio descifrado, y en forma cifrada en el directorio cifrado. El nombre y el contenido del archivo están cifrados.
He elegido un mapa separado en la máquina local donde para cada usuario que inicia sesión se almacenará un cifrado (y una interfaz a él):
install -m777 -o root -g root /var/lib/encfsNota las permisos: todos deben poder crear un directorio aquí. Más adelante en este documento se explica por qué.
Instalación de pam_script
La instalación es muy simple. Después de un comando make mueve la biblioteca al directorio adecuado, /lib/security.
tar -xzf pam-script-*.tar.gz
cd pam-script-*
make
mv pam_script.so /lib/security
chown root:root /lib/security/pam_script.so
chmod 755 /lib/security/pam_script.soPam_script.so utiliza algunos parámetros. Todos ellos están descritos en el README en el directorio fuente. Importantes son:
parámetros comunes
- runas=#user# : hace que el script se ejecute como el usuario #user#
parámetros solo en la parte de autenticación
- onauth=/path/to/onauth/script : ruta al script que se ejecuta cuando en la parte de autenticación
por defecto es /etc/security/onauth
solo en la parte de sesión
onsessionopen=/path/to/onsessionopen/script : ruta al script que se ejecuta cuando se inicia una sesión (válida)
por defecto es /etc/security/onsessionopenonsessionclose=/path/to/onsessionclose/script : ruta al script que se ejecuta cuando se cierra una sesión
por defecto es /etc/security/onsessionclose
Después de la instalación, lo más complejo es configurar el sistema para hacer uso de este módulo.
Ajustando la configuración de PAM
He utilizado pam_script en la parte de autenticación y en la parte de sesión del archivo de servicio pam (login,kde).
Primero describo cómo ajustar la parte de autenticación, donde pam_script se utiliza más de una vez.
La parte de autenticación
Pam_script tiene la capacidad (desde la versión 0.1.5) de obtener la contraseña proporcionada al iniciar sesión, y hacerla disponible a los scripts a través de una variable de entorno PAM_AUTHTOK.
El propósito aquí es crear un directorio seguro donde se almacena información confidencial (como credenciales).
El módulo se apila en la parte de autenticación:
cat /etc/pam.d/login-- snip --
auth required pam_shells.so
auth required pam_script.so expose=1
auth sufficient pam_unix.so use_first_pass
auth sufficient pam_ldap.so use_first_pass
auth required pam_script.so onauth=/etc/security/onauth.failed
auth required pam_deny.soComo puedes ver, uso pam_scripts.so múltiples veces:
La primera vez para ejecutar un script que crea un directorio cifrado (con encfs) y para escribir la contraseña secreta en un archivo en este directorio para su uso por programas sensibles a las credenciales como fusesmb y mount.cifs. Esto se hace justo antes del primer módulo de autenticación siguiente, pam_unix.
La última vez para ejecutar un script cuando la autenticación no es exitosa. Cuando los módulos de autenticación anteriores fallan (pam_unix y pam_ldap) (y solo entonces) se alcanza este módulo. Es necesario desmontar el directorio cifrado y eliminar archivos temporales.
Ten en cuenta que el último módulo de todos es pam_deny, que es realmente necesario. Sin él, cualquiera podría iniciar sesión. Esto se debe a que pam_script siempre devuelve “PAM_SUCCESS”, sin importar cuál sea el valor de retorno de los scripts.
No olvides agregar la bandera “use_first_pass” al módulo existente pam_unix.so.
Los scripts
cat /etc/security/onauth#!/bin/bash
retcode=0;
userid=$1
service=$2
authtok=$3
if [ -z "$authtok" ]; then
authtok=$PAM_AUTHTOK
fi;
userproperties=$(getent passwd | grep -m 1 -E "^$userid")
if [ -z "$userproperties" ]; then
#
# userproperties no encontrado: algo está mal
#
echo "Usuario no encontrado."
exit
fi;
homedir=$(echo $userproperties | cut -d ":" -f 6);
gidnr=$(echo $userproperties | cut -d ":" -f 4);
uidnr=$(echo $userproperties | cut -d ":" -f 3);
if [ -d /var/lib/encfs ]; then
# crear un seguro
if [ ! -d /var/lib/encfs/$userid ]; then
install -m700 -o $uidnr -g $gidnr -d /var/lib/encfs/$userid
fi;
if [ ! -d /var/lib/encfs/$userid/encrypted ]; then
install -m700 -o $uidnr -g $gidnr -d /var/lib/encfs/$userid/encrypted
fi;
if [ ! -d /var/lib/encfs/$userid/unencrypted ]; then
install -m700 -o $uidnr -g $gidnr -d /var/lib/encfs/$userid/unencrypted
fi;
if [ ! -d /var/lib/encfs/$userid/run ]; then
install -m700 -o $uidnr -g $gidnr -d /var/lib/encfs/$userid/run
fi;
#
# prueba que el directorio cifrado no esté ya montado
#
if [ -z "$(mount | grep -w /var/lib/encfs/$userid/unencrypted )" ]; then
#
# crear un programa que proporciona la contraseña
#
cd /var/lib/encfs/$userid
md5authsum=$(echo $authtok | md5sum | cut -d " " -f 1)
echo "$md5authsum" > run/tmp
echo "$md5authsum" >> run/tmp
chown $uidnr:$gidnr run/tmp
rm -rf encrypted/*
rm -f encrypted/.encfs*
cat run/tmp | encfs -S /var/lib/encfs/$userid/encrypted /var/lib/encfs/$userid/unencrypted -- -o allow_root 1>>/dev/null
fi;
#
# esto es de lo que se trata: almacenar las credenciales en un archivo
# en este caso la contraseña
#
if [ -n "$(mount | grep -w /var/lib/encfs/$userid/unencrypted )" ]; then
cd /var/lib/encfs/$userid/unencrypted/
if [ -f password.tmp ]; then
rm password.tmp
fi;
echo $authtok > password.tmp
fi;
fi;
Algunas observaciones:
Este script crea un directorio cifrado (si no existe ya) con encfs. Ten en cuenta la opción -S en encfs: la contraseña para este directorio cifrado se lee desde stdin y no se solicita.
Esta contraseña es la misma que se usa al iniciar sesión.
El directorio cifrado está en /var/lib/encfs/$userid.
La contraseña se escribe en un archivo, password.tmp. Este es un archivo temporal: la contraseña no tiene que ser la correcta. Más adelante en el proceso, dependiendo del éxito de la validación, este archivo se renombra (a password) o se elimina.
Es muy importante notar que el script se ejecuta como el usuario que inicia sesión, ¡no como root! Y debido a que programas como mount.cifs necesitan tener acceso a este directorio para leer el archivo de contraseña, se agrega la opción común de fuse allow_root.
#!/bin/bash
userid=$1
service=$2
userproperties=$(getent passwd | grep -m 1 -E "^$userid")
if [ -z "$userproperties" ]; then
#
# userproperties no encontrado: algo está mal
#
echo "Usuario no encontrado."
exit
fi;
homedir=$(echo $userproperties | cut -d ":" -f 6);
gidnr=$(echo $userproperties | cut -d ":" -f 4);
uidnr=$(echo $userproperties | cut -d ":" -f 3);
#
# este script se ejecuta cuando la autenticación falla
# un seguro cifrado aún se crea
# así que es importante eliminar este seguro nuevamente
#
if [ -d /var/lib/encfs ]; then
if [ -d /var/lib/encfs/$userid/unencrypted ]; then
#
# prueba que el directorio cifrado ya esté montado
#
if [ -n "$(mount | grep -w /var/lib/encfs/$userid/unencrypted )" ]; then
if [ $(w -h $userid | wc -l) -eq 0 ]; then
#
# este usuario no está conectado en más tty's
# solo elimina todo y desmonta el directorio cifrado
#
rm -rf /var/lib/encfs/$userid/unencrypted/*
fusermount -u /var/lib/encfs/$userid/unencrypted
rm -rf /var/lib/encfs/$userid/encrypted/*
rm -f /var/lib/encfs/$userid/encrypted/.encfs*
else
#
# este usuario aún está conectado
#
rm -f /var/lib/encfs/$userid/unencrypted/password.tmp
fi;
fi;
if [ -z "$(mount | grep -w /var/lib/encfs/$userid/unencrypted )" ]; then
rm -rf /var/lib/encfs/$userid/encrypted/*
rm -f /var/lib/encfs/$userid/encrypted/.encfs*
rm -rf /var/lib/encfs/$userid/unencrypted/*
fi;
fi;
fi;
Algunas observaciones:
Este script se ejecuta cuando las credenciales proporcionadas no son válidas. El directorio cifrado, que acaba de configurarse, se eliminará (desmontará) nuevamente y se limpiará. Esto es, por supuesto, solo cuando este usuario no esté conectado en ningún otro tty.
La parte de sesión
Cuando se alcanza la parte de sesión, es seguro que las credenciales proporcionadas (contraseña) son correctas. Esto significa que la contraseña - en la fase de autenticación almacenada en un archivo temporal - es correcta. Así que una cosa que hacer en la fase de sesión es mover el contenido de password.tmp al permanente, password.
Una segunda cosa es ejecutar scripts que necesitan estas credenciales para su propio uso, como montar recursos compartidos CIFS o fusesmb.
Es lógico que cualquier información confidencial permanezca dentro del directorio seguro. ¡De eso se trataba en primer lugar!
Ten en cuenta que este módulo ejecuta dos scripts por defecto:
. /etc/security/onsessionopen: cuando se inicia/abre una sesión;
. /etc/security/onsessionclose: cuando se cierra/termina una sesión.
Sigo el defecto, no hay razón para hacerlo de otra manera.
Mi archivo /etc/pam.d/login (la parte de sesión) se ve así:
cat /etc/pam.d/login-- snip --
session required pam_mkhomedir.so
session required pam_motd.so
session optional pam_mail.so empty dir=/var/mail
session optional pam_lastlog.so
session required pam_env.so
session required pam_script.so
session required pam_unix.so
session required pam_ldap.soLos scripts
Al abrir la sesión
cat /etc/security/onsessionopen#!/bin/bash
retcode=0;
userid=$1
service=$2
userproperties=$(getent passwd | grep -E "^$userid")
if [ -z "$userproperties" ]; then
#
# userproperties no encontrado: algo está mal
#
echo "Usuario no encontrado."
exit
fi;
homedir=$(echo $userproperties | cut -d ":" -f 6);
gidnr=$(echo $userproperties | cut -d ":" -f 4);
uidnr=$(echo $userproperties | cut -d ":" -f 3);
if [ -d /var/lib/encfs/$userid/encrypted ]; then
#
# prueba que el directorio cifrado esté montado
#
if [ -n "$(mount | grep -w /var/lib/encfs/$userid/unencrypted )" ]; then
if [ -f /var/lib/encfs/$userid/unencrypted/password.tmp ]; then
if [ -f /var/lib/encfs/$userid/unencrypted/password ]; then
#
# se encontró un archivo de contraseña antiguo
#
if [ -z "(diff /var/lib/encfs/$userid/unencrypted/password /var/lib/encfs/$userid/unencrypted/password.tmp)" ]; then
#
# nueva contraseña y antigua son las mismas
#
rm /var/lib/encfs/$userid/unencrypted/password.tmp
else
mv /var/lib/encfs/$userid/unencrypted/password.tmp /var/lib/encfs/$userid/unencrypted/password
fi;
else
#
# contraseña no encontrada: es el primer inicio de sesión.
# solo mueve el archivo de contraseña temporal al restante
#
mv /var/lib/encfs/$userid/unencrypted/password.tmp /var/lib/encfs/$userid/unencrypted/password
fi;
fi;
if [ -d /etc/session.d/pam/onsessionopen ]; then
for script in /etc/session.d/pam/onsessionopen/*.sh; do
if [ -x $script ]; then
eval $script $userid $service
fi
done;
fi;
fi;
fi;
Al cerrar la sesión
cat >> /etc/security/onsessionclose#!/bin/bash
userid=$1
service=$2
userproperties=$(getent passwd | grep -E "^$userid")
if [ -z "$userproperties" ]; then
#
# userproperties no encontrado: algo está mal
#
echo "Usuario no encontrado."
exit
fi;
homedir=$(echo $userproperties | cut -d ":" -f 6);
gidnr=$(echo $userproperties | cut -d ":" -f 4);
uidnr=$(echo $userproperties | cut -d ":" -f 3);
#
# este script se ejecuta cuando la autenticación falla
# un seguro cifrado aún se crea
# así que es importante eliminar este seguro nuevamente
#
if [ -d /var/lib/encfs ]; then
if [ -d /var/lib/encfs/$userid/unencrypted ]; then
#
# prueba que el directorio cifrado ya esté montado
#
if [ -n "$(mount | grep -w /var/lib/encfs/$userid/unencrypted )" ]; then
if [ $(w -h $userid | wc -l) -eq 0 ]; then
rm -rf /var/lib/encfs/$service.$userid/unencrypted/*
fusermount -u /var/lib/encfs/$userid/unencrypted
rm -rf /var/lib/encfs/$userid/encrypted/*
rm -f /var/lib/encfs/$userid/encrypted/.encfs*
fi;
else
rm -rf /var/lib/encfs/$userid/encrypted/*
fi;
if [ -d /etc/session.d/pam/onsessionclose ]; then
for script in /etc/session.d/pam/onsessionclose/*.sh; do
if [ -x $script ]; then
eval $script $userid $service
fi
done;
fi;
fi;
fi;Algunas observaciones:
. importante: las versiones tempranas de shadow (donde el programa de inicio de sesión es parte) no cerraban sesiones por defecto (versiones anteriores a 4.0.12). Tendrás que agregar:
CLOSE_SESSIONS yesal archivo /etc/login.defs.
Esta opción no está documentada, y no está presente en el archivo login.defs instalado por el paquete Shadow. Tendrás que agregarlo tú mismo.
En versiones más nuevas, esta opción se eliminó: la sesión siempre se cierra.
El resultado
El directorio cifrado contiene ahora las credenciales, que solo están disponibles para el propietario y root. Al crear un archivo mount.cifs.conf con estos valores:
touch /var/lib/encfs/$userid/unencrypted/mount.cifs.conf
chmod 600 /var/lib/encfs/$userid/unencrypted/mount.cifs.conf
echo "username=$userid" > /var/lib/encfs/$userid/unencrypted/mount.cifs.conf
echo -n "password=" >> /var/lib/encfs/$userid/unencrypted/mount.cifs.conf
cat /var/lib/encfs/$userid/unencrypted/password >> /var/lib/encfs/$userid/unencrypted/mount.cifs.confAhora montar un recurso compartido cifs es posible con:
/sbin/mount.cifs //fileserver/public /home/sbon/netshares/fileserver/public -o credentails=/var/lib/encfs/sbon/unencrypted/mount.cifs.conf,ip=192.168.0.2donde el directorio /home/sbon/netshares/fileserver/public existe, y hay un recurso compartido “public” disponible en “fileserver”, un servidor smb/cifs con número de ip 192.168.0.2.
Este comando debe ejecutarse como root. Root necesita tener acceso al directorio cifrado.
Un ejemplo más es fusesmb, que puede usar credenciales para navegar por el vecindario de red smb. Esto no se hace creando un archivo separado solo para las credenciales, sino en el archivo de configuración global por usuario de fusesmb en ~/.smb/fusesmb.conf:
(aquí creo un archivo de configuración simple solo con credenciales)
touch /var/lib/encfs/sbon/unencrypted/fusesmb.conf
chmod 600 /var/lib/encfs/sbon/unencrypted/fusesmb.conf
echo "[global]" > /var/lib/encfs/sbon/unencrypted/fusesmb.conf
echo "username = sbon" >> /var/lib/encfs/sbon/unencrypted/fusesmb.conf
echo -n "password = " >> /var/lib/encfs/sbon/unencrypted/fusesmb.conf
cat /var/lib/encfs/sbon/unencrypted/password >> /var/lib/encfs/sbon/unencrypted/fusesmb.conf
ln -sf /var/lib/encfs/sbon/unencrypted/fusesmb.conf /home/sbon/.smb/fusesmb.conf
El último enlace es porque fusesmb (iniciado por sbon) espera que el archivo de configuración esté allí.
Ahora inícialo con:
fusesmb /home/sbon/networkcuando estoy conectado como yo mismo (sbon). El directorio /home/sbon/network tiene que existir.
Recibe nuevas publicaciones en tu bandeja de entrada.
No spam. Cancela la suscripción en cualquier momento.