.. _ssh-ftp: ************************* |SSH| como servidor |FTP| ************************* .. nota:: Para la instalación y configuración de un servidor |SSH| consulte :ref:`el apartado referente la administración remota `. Bajo el epígrafe trataremos de afinar la configuración de un servidor |SSH| para que cumpla las veces de un servidor |FTP|. Esto supone la posibilidad de implementar los siguientes aspectos, aparte de la obvia transmisión de ficheros: * Cortar la conexión tras un tiempo de inactividad. * Limitar el número de conexiones simultáneas. * Habilitar la transferencia anónima. * Enjaular usuarios. * Usuarios virtuales. De todas ellos, sólo el último no es posible, al menos no si pretendemos realizar el acceso con :ref:`usuarios realmente virtuales `. Restringir el acceso a otros servicios, en cambio, sí es posible. Antes de entrar en harina, definiremos un grupo *ftpusers* al que pertenecerán todos aquellos usuarios de los que queremos que su relación con el servidor se limite a la transferencia de ficheros:: # addgroup --system ftpusers Además, nos proponemos implementar dos versiones distintas: #. Un servidor |FTP| puro, que sólo permita la conexión mediante un cliente, a la manera de como lo hacen los servidores |FTP| tradicionales. #. Un servidor |FTP| *con esteroides*, que habilite también el uso de :ref:`scp ` e incluso de un *script* que permita el cambio de contraseña al usuario. .. warning:: Es preciso notar, antes de empezar que, aunque hablemos de servidor |FTP|, lo que implementamos es un servidor |sFTP|, que no es lo mismo, aunque sirva para lo mismo: no habrá dos canales independientes ni podrán usarse clientes |FTP| (aunque muchos como `Filezilla `_ también implementan el protocolo |sFTP|) .. |sFTP| replace:: :abbr:`sFTP (Secure SHell FTP)` |FTP| puro ========== Lograr esto es relativamente sencillo gracias a que el servidor de *openssh* dispone de un servidor |sFTP| interno, que propicia que se puedan hacer enjaulamientos sin necesidad de preparar jaulas. Configuración básica -------------------- Basta añadir a :file:`/etc/ssh/sshd_config` las siguientes líneas:: Match Group ftpusers ChrootDirectory %h ForceCommand internal-sftp X11Forwarding no AllowTcpForwarding no ClientAliveInterval 120 ClientAliveCountMax 0 Es decir, creamos una configuración particular para los usuarios pertenecientes a grupo *ftpusers* para que sólo puedan usar el servidor como servidor FTP, no puedan hacer túneles *TCP*, ni arrancar aplicaciones gráficas, se desconecten automáticamente tras 2 minutos de inactividad y estén enjaulados dentro su propio directorio personal (*%h*). .. warning:: Para que el enjaulamiento funcione es necesario que todos los directorios que constituyen la ruta de la jaula pertenezcan al administrador\ [#]_. .. nota:: El resto de aspectos *no básicos* que incluiremos en este |FTP| puro, son tambien aplicables al :ref:`ftp-est`. Acceso anónimo -------------- Definitivamente, no es buena idea permitir un acceso anónimo ya que este suele incluirse para permitir la descarga de ficheros y, para esto, es mejor usar un servidor web, por ejemplo. Si, a pesar de todo, deseamos permitirlo, debemos primero crear un usuario adecuado:: # adduser ftp --ingroup ftpusers --no-create-home --home /srv/ftp/anonymous --shell /bin/false --disabled-password --gecos "Anonimo" # useradd anonymous -g $(getent group ftpusers | cut -d: -f3) -M -d /srv/ftp/anonymous/ -s /bin/false -c "Anonimo" -o -u $(id -u ftp) # mkdir -p /srv/ftp/anonymous Lo que hacemos es crear un usuario, *ftp*, y otro usuario llamado *anonymous*, que en realidad es el mismo porque comparte **uid** con él. Además debemos usar el módulo *pam_ftp* en la autenticación, por lo que deberemos añadir en :file:`/etc/pam.d/sshd` justamente antes de que se incluya la configuración de autenticación la línea que carga ese módulo:: auth sufficient pam_ftp.so ignore # Standard Un*x authentication. @include common-auth .. warning:: Hacer esto es una pésima idea. Incluso la página de manual del módulo advierte de que «*this module is not safe and easily spoofable*». .. _ssh-ftp-limits: Limitación de conexiones ------------------------ El servidor |SSH| no permite directamente la limitación en el número de conexiones, ni totales ni por usuario; pero puede lograrse su implementación actuando sobre el proceso de autenticación. Como las sessiones de |FTP|, no abren para el servidor consolas interactivas, es absolutamente inútil usar *pam_limit*, ya que *maxlogins* y *maxsyslogins* sólo son aplicables cuando se abren *shells* en el servidor. Por tanto, añadir al fichero de configuración una línea de este tipo:: @ftpusers - maxlogins 2 %ftpusers - maxlogins 4 que, teóricamente, limitaría el número máximo de sesiones parsa cada usuario de *ftpusers* a **2** y el total de sesiones para todos los integrantes del grupo a **4** es absolutamente inútil\ [#]_. Como esta solución es inútil, hemos de aplicar otra basada en la creación de un *script* que ejecute *pam_exec*. Es :download:`éste `. Para aplicar el *script* basta con añadir al final de :file:`/etc/pam.d/sshd` la siguiente línea (suponemos alojado el *script* en :file:`/usr/local/bin`):: session required pam_exec.so quiet /usr/local/bin/sftp_limit.sh max_per_user=2 .. warning:: El *script* es muy simple y, aunque sólo se aplica a los grupos de usuarios que le indiquemos (*ftpusers* en nuestro caso), en el caso del *maxclients* y *max_per_ip* cuenta cualquier conexión |SSH|, la haga quien la haga. Como consecuencia, todos los usuarios que acceden por |SSH| contribuyen a alcanzar el máximo, aunque no les afecte. Cuota ===== .. seealso:: Consulte cómo crear :ref:`cuotas de disco `. .. _ftp-est: |FTP| con esteroides ==================== En este caso, la dificultad estriba en que para que podamos enjaular usuarios, necesitamos construir una jaula con todo lo necesario. Recordemos, además, que nuestro propósito es permitir al usuario: * El acceso |sFTP|. * El uso de :ref:`scp `. * El cambio de contraseña. .. nota:: Si sólo nos interesaran las dos primeras acciones, podríamos usar `rssh `_. Para resolver la papeleta tiraremos |mdash|\ cómo no\ |mdash| de *script*. La idea es crear uno que dependiendo de qué cliente usemos (si :ref:`ssh `, si :ref:`scp ` o si :ref:`sftp `) indique al servidor cómo debe comportarse (pedir contraseña, permitir la copia remota o ejecutar el servidor |sFTP|, respectivamente)\ [#]_. Además el propio *script* se encargará de realizar el enjaulado. Necesitamos cinco ingredientes: #. :download:`el propio script ` que colocaremos en :file:`/usr/local/bin`. #. La utilidad :command:`fakechroot` que permite enjaular con un usuario distinto del administrador:: # apt-get install fakechroot #. El directorio donde haremos el enjaulamiento (:file:`/srv/ftp`):: # mkdir -p /srv/ftp #. Asegurarnos de que :file:`/etc/ssh/sshd_config` contiene descomentada la línea\ [#]_\ [#]_:: Subsystem sftp /usr/lib/openssh/sftp-server -d %d #. Un grupo al que pertenezcan los usuarios del |FTP| y un usuario de pruebas\ [#]_:: # addgroup --system ftpusers # adduser paco --ingroup ftpusers --no-create-home --home /home/paco --gecos "PacoFTP" --shell /bin/sh # mkdir -p /home/paco/.ssh # chown -R paco:ftpusers /home/paco Hecho esto, tantearemos dos soluciones: una en que todos los usuarios enjaulados comparten una jaula común; y otra en la que cada usuario se enjaula en una distinta. .. _mkchroot: .. rubric:: Creación de jaulas Antes, sin embargo, es preciso aclarar cómo crear jaulas apropiadas. En principio, una :dfn:`jaula` es un subárbol del sistema de ficheros en que se encierra la acción de un usuario o programa. Esto implica, que dicho subárbol debe contener todos los componentes (programas y librerias) necesarios para que se puedan desarrollar dentro de él las acciones para la que ha sido concebida. Por ejemplo, si queremos que un usuario enjaulado pueda copiar ficheros es obvio que dentro de la jaula debe encontrarse el ejecutable :ref:`cp `. Por tanto, para crear una jaula primero debemos determinar cuáles son los ficheros que necesitaremos dentro de ella. En nuestro caso, son exclusivamente estos\ [#]_\ [#]_:: /etc/nsswitch.conf /etc/ld.so.conf /etc/ld.so.cache /etc/ld.so.conf.d/* /lib/x86_64-linux-gnu/libnss_compat.so.2 /lib/x86_64-linux-gnu/libnss_files.so.2 /lib/x86_64-linux-gnu/libnsl.so.1 /usr/bin/scp /usr/lib/openssh/sftp-server O sea, los ficheros de configuración y las librerías necesarios para resolver usuarios, y los dos programas que se usarán dentro de la jaula\ [#]_. Por supuesto, a esta lista es necesario añadir: * Las librerías que usan los dos programas\ [#]_. * Algunos dispositivos (:file:`/dev/null` y :file:`/dev/zero`). Para evitarnos la tediosa tarea hacer a mano la jaula, hemos escrito... :download:`otro script `, que admite por la entrada estándar la lista de ficheros y exige como argumento la dirección que ocupará la jaula. Así, si almacenamos la lista anterior en :file:`req.txt` y queremos crear la jaula en :file:`/srv/ftp`, podemos hacer lo siguiente:: # ./enjaular.sh /srv/ftp < req.txt .. warning:: Recuerde que los nuevos usuarios que cree no se añadirán automáticamente al :file:`/etc/passwd` de la jaula. Jaula única ----------- .. warning:: Es necesario que lea (y haga) los preliminares de :ref:`FTP con esteroides `. Nuestra primera intención es enjaular a todos los usuarios del |FTP|, dentro de :file:`/srv/ftp`. Para ello debemos añadir a :file:`/etc/ssh/sshd_config`, lo siguiente:: Match Group ftpusers X11Forwarding no AllowTcpForwarding no ForceCommand /usr/local/bin/rssh.sh /srv/ftp A continuación debemos copiar la lista de ficheros requeridos en un fichero (p.e. :file:`/srv/ftp`) y crear la jaula:: # ./enjaular.sh /srv/ftp`< req.txt y, finalmente, copiar el *script* en la ruta apropiada:: # cp /path/donde/este/rssh.sh /usr/local/bin Además, :file:`/home` debe aparecer dentro de la jaula, así que:: # ln -s /home /srv/ftp/home .. note:: Recuérdese que se fijó el directorio personal del usuario como el habitual :file:`/home/usuario`, pero, como estos usuarios siempre actúan enjaulados, su directorio real es :file:`/srv/ftp/home/usuario`, .. note:: Con esta solución, al usar :ref:`scp `, el directorio remoto de trabajo es la raíz de la jaula no el directorio personal, por lo que algo como:: $ scp fichero.txt paco@servidor: intentaría copiar :file:`fichero.txt` en :file:`/srv/ftp` y no en :file:`/srv/ftp/home/paco`\ [#]_. .. warning:: Obsérvese que la *shell* del usuario es :command:`sh`, porque así lo requiere el servidor |SSH| para ejecutar nuestro *script*. Esta circunstancia provoca que el usuario puede autenticarse sin restricciones en cualquier otro servicio. Por ejemplo, a través de *login*, si alcanza físicamente el servidor. Para evitar este inconveniente podemos añadir al final de :file:`/etc/pam.d/common-session`, las líneas:: session [success=2 default=ignore] pam_succeed_if.so service = sshd session [success=1 default=ignore] pam_succeed_if.so user notingroup ftpusers session required pam_deny.so session required pam_permit.so las cuales harán que el acceso falle cuando el servicio no sea *sshd* y el usuario pertenezca al grupo *ftpusers*. .. note:: El usuario puede, incluso, usar autenticación con claves si quiere, aunque no podrá usar :ref:`ssh-copy-id ` para subirla, ni utilizar más de una pareja de claves:: $ scp .ssh/id_ecdsa.pub paco@servidor:~/.ssh/authorized_keys Jaula personal -------------- .. warning:: Es necesario que lea (y haga) los preliminares de :ref:`FTP con esteroides `. Si queremos una jaula personal distinta para cada usuario es obvio que debemos construirla, pero crear físicamente una distinta para cada uno es un desperdicio de espacio en disco. Podríamos usar enlaces duros, pero sigue siendo una solución bastante guarra. Una estrategia más elegante es la siguiente: * Se construye la base de la jaula que contiene todos los ficheros de configuracion, ejecutables y librerías necesarios en una ubicación, p.e. :file:`/var/lib/sftp_jail`. * Cuando un usuario de |FTP| accede, sin tener abierta otra sesión previa, se crea la vuelo la jaula fusionando con `overlay `_ el directorio anterior con el directorio personal del usuario. Cuando el usuario abandona el |FTP| sin dejar otras sesiones abiertas, se destruye la jaula. * Al construir al vuelo la jaula, se crea también un fichero :file:`/etc/passwd` con solamente una línea: la referente al usuario. Eso impedirá que dentro de la jaula haya información sobre el resto de usuarios. Empecemos por indicar cuál es la configuración a añadir dentro de :file:`/etc/ssh/sshd_config`:: Match Group ftpusers X11Forwarding no AllowTcpForwarding no ForceCommand /usr/local/bin/rssh.sh %x/ftp o sea, la misma que anteriormente, excepto por el hecho de que ahora el directorio de enjaulamiento es :file:`%x/ftp`. ``%x`` representa el directorio ``XDG_RUNTIME_DIR`` que crea :program:`systemd` para cada usuario\ [#]_. Como en el caso anterior hemos de crear la base de jaula:: # ./enjaular.sh /var/lib/sftp_jail Finalmente, crear al vuelo la jaula durante la autenticación, requiere manipular *pam*. El script :download:`sftp_jail.sh ` para *pam_exec* hace esto suponiendo, de modo predeterminado, que la jaula se montará en :file:`%x/ftp`, que la base de la jaula está en :file:`/var/lib/sftp_jail` y que la parte de la jaula que contiene los datos de usuario está en :file:`/srv/ftp/nombre_usuario` \[#]_. Para que *pam* ejecute el script se ha preparado un :download:`fichero de automatización ` para :command:`pam-auth-update` que, además, soluciona el problema de que el usuario pueda acceder al sistema con otros servicios:: # mv /path/a/sftp_jail /usr/share/pam-configs # pam-auth-update Por último, necesitamos que el directorio personal de usuario aparezca debajo en uno de los componentes que constituirá el sistema de ficheros fusionado, de modo que para cada usuario habrá que hacer lo siguiente:: # mkdir -p /srv/ftp/paco/home # ln -s /home/paco /srv/ftp/paco/home .. rubric:: Notas al pie .. [#] Para cumplir con este precepto podríamos crear los usuarios del FTP así:: # adduser paco --ingroup ftpusers --no-create-home --home /srv/ftp/paco --gecos "PacoFTP" --shell /bin/false # mkdir -p /srv/ftp/paco/incoming # chown paco:ftpusers /srv/ftp/paco/incoming Gracias a la inclusión del subdirectorio :file:`incoming`, el usuario podrá subir ficheros al servidor. .. [#] En cambio, si metiéramos a todos los usuarios con acceso al *ssh* en un grupo, si sería útil para limitar el número de acceso simultáneosi a una *shell* del servidor. .. [#] *ssh* proporciona una variable de ambiente llamada ``SSH_ORIGINAL_COMMAND`` en la que se almacena qué se pretende hacer: * Si se ejecutó el cliente |sFTP|, la variable contendrá el ejecutable del servidor |sFTP|, o sea, el valor de la directiva :code:`Subsystem sftp`, de la que se habla a continuación. * Si se ejecutó :command:`scp`, contendrá el propio comando :command:`scp` y una serie de argumento pertinentes. * Si se ejecutó el cliente :command:`ssh`, o no contendrá nada o contendrá las ordenes que se añadieron al cliente al final de la línea. .. [#] Nótese que se ha añadido la opción ``-d %d`` para que el servidor use como directorio inicial el directorio de usuario. .. [#] Existe otro servidor sftp, `gesftpserver `_, con capacidades extra (p.e. permite distinguir entre transferencia de texto o binaria como los servidores |FTP| clásicos). Tiene paquete en *debian* y para usarlo es tan fácil como cambiar la línea:: Subsystem sftp /usr/lib/gesftpserver -d %d Para aprovecharlo eso sí, se necesitan clientes que soporten más allá de la versión 3 de protocolo |sFTP|. Para una lista de clientes consulte `la página proporcionada por el propio gesftpserver `_. .. [#] Aún falta por crear el directorio personal del usuario, pero esto se tratará más adelante, porque su ubicación exacta depende de si hacemos una jaula común o una jaula personal para cada usuario. .. [#] No se necesitan más, porque el *script* que usaremos no actúa enjaulado: es él el que enjaula a :command:`scp` y al servidor |sFTP|, que sí hemos incluido en la jaula. .. [#] Si se usa la *pseudoshell* `rssh `_, la lista es la misma y hay que añadir la propia *pseudoshell* y el ejecutable :command:`/usr/lib/rssh/rssh_chroot_helper`. El paquete de *debian* incluye un *script* para la creación de la jaula (:command:`mkchroot`) entre la documentación, pero puede usarse el *script* presentado aquí. .. [#] En la lista hay un ilustre olvidado: :file:`/etc/passwd`. No se incluye, porque el *script* posterior se encargará de hacer un enlace simbólico. .. [#] Para conocer las librerías que usa un programa puede usarse :command:`ldd`:: $ ldd /usr/bin/scp linux-vdso.so.1 (0x00007ffdb69fb000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f04e9ba0000) /lib64/ld-linux-x86-64.so.2 (0x00007f04ea160000) .. [#] Es posible arreglar este pequeño detalle, pero exige incluir :command:`sh` en la jaula. .. [#] El *script* también admite los símbolos: ``%u`` Nombre del usuario ``%h`` Nombre del directorio personal ``%n`` *UID* del usuario ``%x`` Directorio ``XDG_RUNTIME_DIR``. .. [#] El *script* admite parámetros para modificar su comportamiento (échele un vistazo al código), así que puede editarse el :download:`fichero de actualización automática de pam ` ya referido y añadirlos a:: optional pam_exec.so /usr/local/bin/sftp_jail.sh .. |mdash| unicode:: U+2014