Virtualmin + SFTP + chroot

This guide examines setting up chroot’ed SFTP-only user accounts under Virtualmin.

The Rationale:

SFTP is a secure alternative to FTP and FTPS that uses SSH.  With this setup, no FTP server is needed, as the native sshd server is used instead, SSH does not require an SSL certificate (like FTPS), and is usually considered more secure.

However, one drawback is that FTP servers typically offer a simple config option to “restrict access to the user’s home directory”, whereas SFTP requires a chroot’ed setup to do this, which is more complex, and not supported natively by Virtualmin (or really any other CP).

Note: Giving users access to the full server is not as bad as it sounds.  Users are limited by file permissions as to what they can actually see or do, and Virtualmin sets home directory permissions to 750 by default, so users can’t snoop around each other’s files.  More importantly, restricting access to the user’s home directory only applies to FTP service.  This is just a minor inconvenience for any competent hacker, as access to the full server is easily obtained via the web server (which is not chroot’ed), using any web-based file manager (many CMSs come with one built-in).

However, for the average user, there may still be some value in restricting just their FTP access so as to at least discourage them from snooping around.

There are some complications to overcome:

  • The chroot directory must be root owned and world-readable (sshd checks this unconditionally for security reasons).
  • The user’s home directory should be relative to this root because the server will attempt to go to the directory relative to the root when the user logs in.
  • If you want to prevent users from being able to see even the list of other users on the server, then each user will need a separate chroot directory.

The typical solution involves a complex home directory path:
/home/chroot/domain/home/domain

Note that all directories in the path leading up to the final sub-directory should be root owned and world-readable.

Steps to follow:

1. Set the home directory template in Virtualmin accordingly:

Virtualmin -> System Settings -> Virtualmin Configuration -> Defaults for new domains -> Home directory base: /home/chroot/${DOM}/home

Virtualmin -> System Settings -> Virtualmin Configuration -> Defaults for new domains -> Home subdirectory: ${DOM}

Note that both settings are required, even if ${DOM} is the default, as Virtualmin will not correctly interpolate the directory unless a manual template is set.

2. Add a custom command to handle setting up and cleaning up the chroot:

Virtualmin -> System Settings -> Virtualmin Configuration ->Actions upon server and user creation -> Command to run before making changes to a server: /home/chroot/chroot.sh

Virtualmin -> System Settings -> Virtualmin Configuration ->Actions upon server and user creation -> Command to run after making changes to a server: /home/chroot/chroot.sh

3. Create the /home/chroot/chroot.sh as follows:

#!/bin/ksh

if [ ! "$VIRTUALSERVER_PARENT" ]
then
  if   [ "$VIRTUALSERVER_ACTION" == "CREATE_DOMAIN" ]
  then
    if [ ! "$VIRTUALSERVER_CREATED" ]
    then
      mkdir -p /home/chroot/$VIRTUALSERVER_DOM/home
    else
      echo "Setting up $VIRTUALSERVER_DOM to chroot'ed environment for sftp"

      usermod -d /home/$VIRTUALSERVER_DOM $VIRTUALSERVER_USER
      ln -s $VIRTUALSERVER_HOME /home

      echo " .. done"
    fi
  elif [ "$VIRTUALSERVER_ACTION" == "DELETE_DOMAIN" ]
  then
    if [ "$VIRTUALSERVER_CREATED" ]
    then
      echo "Cleaning up $VIRTUALSERVER_DOM's chroot'ed environment"

      rm -rf /home/chroot/$VIRTUALSERVER_DOM /home/$VIRTUALSERVER_DOM

      echo " .. done"
    fi
  fi
fi

And don’t forget to give it executable permissions.

This script:

  • Creates the home directory’s parent directory as Virtualmin will not do this automatically, and fail without it.
  • Changes the /etc/passwd home directory entry to be relative to the chroot (so it’s not the same as the real home directory!).
  • Adds a symbolic link from this home directory location to the real directory for convenience so that things like ~domain still work.

4. Add Virtualmin users to a secondary group that sshd can identify for SFTP-only access:

Virtualmin -> System Settings -> Server Templates -> Default Settings -> Administration user -> Add domain owners to secondary group: sftponly

Be sure to create this group.

5. Update /etc/ssh/sshd_config to set SFTP-only access for members of this group:

Subsystem       sftp    internal-sftp
Match Group sftponly
        ChrootDirectory /home/chroot/%u
        ForceCommand internal-sftp
        AllowTcpForwarding no

6. Reload sshd:

>systemctl reload sshd.service

7. Test the setup by creating a user, logging in via SFTP, and attempting to log in via SSH (hopefully unsuccessfully).

 

11 thoughts on “Virtualmin + SFTP + chroot

  1. Arnon,

    Very nice, I implemented this and it works great with one exception. Is it possible to create multiple sftp users and jail them to a specific sub-folder. For example, create sftp user BOB who only has access to /home/domain/public_html/files?

    We have users who do files exchanges and the like so it’d really be nice to jail them to the specific location. Maybe a second chroot script for creating only users?

    Thanks again for the great instructions!
    — Craig

    1. Hi Craig,

      Glad you found it useful.

      Short answer: Yes

      Long answer: Virtualmin does not provide a custom command hook for user creation the way it does for server creation, so this can either be done manually through the web interface, or less manually using a command-line script.

      To do it manually:
      – Create a new group (sftponly-sub) for these types of users that sshd can match to their home directories, and update /etc/ssh/sshd_config accordingly.
      – Create each new user using Edit Users -> Add a user to this server. Set the Home directory as desired, and be sure to restrict the Login permissions.
      – Then go to Webmin -> System -> Users and Groups -> Local Groups and add them to the new (sftponly-sub) group.
      I haven’t tried this myself so may have missed a step.

      If I was going to do this a lot, I would definitely write a script.

  2. Arnon,

    We already have a chroot at “/home/chroot/domain.tld”
    I’m trying to add another chroot at “/home/chroot/domain.tld/home/domain.tld/public_html/ftproot”

    I’ve been playing with this all day but haven’t had any success. From what I gather I don’t think what I want to do is possible since all directories up to the chroot have to be root owned. So in my path /home/chroot/domain.tld/home/domain.tld/public_html/ftproot/ I can’t create a chroot at “ftproot” since the “domain.tld/public_html/” is owned by the domain user (i.e. not “root”).

    What do you do to implement sftp for multiple users to a shared directly. Ideally I’d like to keep it contained to the domain.tld directory, but it seems that might not be possible.

    Any insight you can provide is appreciated!
    — Craig

    1. I think you are right about that.
      The best I can think of is to create the ftproot directory elsewhere (make it root owned there), and then use a symbolic link from the public_html directory. It does sound like a permissions nightmare though.
      Good luck!

  3. I tried this approach multiple times on different fresh CentOS 6 (64bit) and each time there were no errors, but php files don’t work in the virtual host while html files work perfectly.

  4. I followed all your steps and it is not working at all. Too many erros:

    1) Performing other Apache configuration ..
    .. configuration failed : Failed to copy /etc/php5/cgi/php.ini to /home/chroot/domain.tld/home/domain.tld/etc/php5/php.ini : cp: cannot create regular file `/home/chroot/domain.tld/home/domain.tld/etc/php5/php.ini’: No such file or directory

    2) Creating SSL certificate and private key ..
    .. SSL website failed! : Failed to open /home/chroot/domain.tld/home/domain.tld/ssl.cert.webmintmp.8887 : Permission denied at ../web-lib-funcs.pl line 1397, line 1

    3) Setting up AWstats reporting ..
    .. AWstats reporting failed! : virtualmin-awstats::feature_setup failed : Failed to open /home/chroot/domain.tld/home/domain.tld/cgi-bin/awstats.pl.webmintmp.8887 : No such file or directory at ../web-lib-funcs.pl line 1397, line 1.

    4) Post-creation command failed : sh: 1: /home/chroot/chroot.sh: not found

    Could you please give some help? Thank you

    1. The last error message gives the clue: The script mentioned in step 3 is not found – make sure it is there!

      That script creates the user’s home directory (among other things). The missing home directory then causes all the other “no such file or directory” errors, so make sure that directory gets created and has the correct permissions.

  5. More importantly, restricting access to the user’s home directory only applies to FTP service. This is just a minor inconvenience for any competent hacker, as access to the full server is easily obtained via the web server (which is not chroot’ed), using any web-based file manager (many CMSs come with one built-in)

    This is why I don’t bother with SFTP (using SSH).

    Instead, I implement FTPS, which is jailed automatically, and fully supported by Virtualmin.

    See my instructions at http://www.virtualmin.com/node/29262

    And, another by HowToForge at https://www.howtoforge.com/tutorial/proftpd-tls-installation-ubuntu-15-04/

    And, while most FTP clients implement FTPS, here’s some code at http://ftps.codeplex.com/

    Or, for PERL fans, see https://metacpan.org/pod/Net::FTPSSL

  6. First of all: sFTP with chroot should indeed be natively allowed by Virtualmin. It is slightly unbelievable that it is not build in.

    But on this manual:
    This is a nice way of setting the server up. I ran into some issues on Ubuntu 16 LTS, and would like to share the tips:

    1. Do not forget to close your ssh config code with Match All, to make sure everything else in there is read as well:
    Match Group sftponly

    with Match All

    2. Same location: do disable the line in there referring to an already present subsystem sftp (just add an #)

    3. For testing, make absolutely sure you keep your running ssh session open. If something goes wrong, you can always work from there!

    At this moment I’m left with one issue/question, that makes me wonder: what to do with your other users, that should be allowed to login using SSH. Should those be added to some safe group, or not and trust this script to work well enough?

    Michel

    1. I leave my other users as-is, no special group.
      Hosted users are created with /sbin/nologin shell, so even if the script fails, or the user is not added to the sftponly group, the worst-case scenario is not having access to SFTP – never unexpected access.

Leave a Reply

Your email address will not be published. Required fields are marked *