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 install

Despué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/testfile

El 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/encfs

Nota 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.so

Pam_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/onsessionopen

  • onsessionclose=/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.so

Como 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.so

Los 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 yes

al 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.conf

Ahora 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.2

donde 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/network

cuando estoy conectado como yo mismo (sbon). El directorio /home/sbon/network tiene que existir.

Share: X/Twitter LinkedIn

Recibe nuevas publicaciones en tu bandeja de entrada.

No spam. Cancela la suscripción en cualquier momento.