NUTS and BOLTS Secure FTP on Solaris Lead image: Juan Manuel Ordonez,
Juan Manuel Ordonez,

Creating an SFTP jail


Restricting FTP access on Solaris can be tricky. We show you how to create a secure chroot environment that restricts an SFTP user to a specific directory. By Ben Patridge

As gatekeepers of the data center, Unix administrators sometimes receive a request to create a Secure File Transfer Protocol (SFTP) account that will only allow the user to view files within that directory. SFTP is preferred over the standard FTP in most customer-facing environments because the username and password are not transmitted in cleartext, nor is the data in transit. Standard FTP has provisions within the .ftpaccess file to create a more restrictive user environment. However, when using SFTP out of the box, users may change directories (cd) and view (ls) whatever they choose within the server, even /.


To clamp down on users and thereby restrict them to a specific home directory so they can't operate outside of that home directory requires the creation of a chrooted, or "jailed," directory.

"Chroot" is the term for this type of restricted directory. With chroot, users are unable to move outside their "cell" and can only view their surroundings. Just think of how you feel in a cubicle. On Linux, this setup is fairly straightforward. However, for those of us who are tethered to a Solaris environment, this task requires some configuration gymnastics to actually get it done. Like any good Unix disciple, I trolled the Internet for weeks before I found enough bits and pieces to consolidate the fragmented virtual Google filesystem of information into a more contiguous aggregated cookbook method.

Set Your Environment

Before you proceed, the most important step is to ensure your environment variables are set. For newbies, environment variables tell the current shell where to find everything. I prefer to set mine in .bash_profile in my $HOME directory; thus, with each new instantiation of a shell, the parameters will remain. Listing 1 shows an excerpt of the crucial variables in my .bash_profile. Note that this setup assumes you will later install OpenSSH in /usr/local/.

Listing 1: .bash_profile Variables

01 CC=gcc
02 CPPFLAGS=-I/usr/local/ssl/include
03 EDITOR=vi
04 LD_LIBRARY_PATH=/usr/local/lib:/usr/local/include:/usr/local/lib/sparcv9
05 PATH=/usr/local/bin:/usr/local/mysql:/usr/local/mysql/bin:/usr/local/sparc-sun-solaris2.10/bin:/usr/local/ssl:/usr/local/ssl/lib:/usr/local/sbin:/usr/local/include:/usr/local/include/libxml2:/usr/bin:/usr/sbin:/usr/lib:/usr/openwin/bin
06 LDFLAGS=-lstdc++

You must ensure you have the GNU GCC compiler [1]. Additional dependencies exist, but I'll assume you are familiar with GCC.

Install TCP Wrappers

The next step is to install TCP Wrappers [1], which is a host networking Access Control List (ACL) system used to screen (filter) access to Unix TCP/IP servers. Essentially, this tool enhances the native abilities of inetd, allowing additional logging support and the ability to return messages for each connection. It also permits inetd to accept only specific connections. Inetd [2] is what controls automatic starting of all Internet services, such as FTP, telnet, POP, SMTP, and so on.

Solaris makes installing TCP Wrappers relatively easy if you opt to download the package from the website (Listing 2).

Listing 2: Installing TCP Wrappers

01 bash#> cd /tmp
02 bash#> ls tcp_wrap*
03 tcp_wrappers-7.6.sol10-sparc-local
04 bash#>
05 bash#> pkgadd -d tcp_wrappers-7.6-sol10-sparc-local
07 The following packages are available:
08   1  SMCtcpdwr     tcp_wrappers
09                    (sparc) 7.6
11 Select package(s) you wish to process (or 'all' to process all packages). (default: all) [?,??,q]: all
13 Processing package instance <SMCtcpdwr> from </home/bpatridge/dev/tcp_wrappers-7.6-sol10-sparc-local>
14 ...

Typically, I'm a source compiling sort of guy, but I found the package more expeditious for TCP Wrappers.

Install and Configure OpenSSH

The next step is to download and install OpenSSH [3]. The OpenSSH suite of software allows you to run an SSL encryption-based web server, allows SSH secure terminal access as opposed to telnet, and provides SFTP, a file- and password-encrypted method of transmitting data between hosts. After downloading, enter:

tar -xzvf openssh-ver.tar.gz

The -xzvf option extracts and gunzips the file in one command. By default, it installs in /usr/local, where you can then enter:

./configure --with-tcp-wrappers
make install

Next, I recommend setting the following options into a new sshd_config. Be sure to back up the original,

cp sshd_config sshd_config.ORIGINAL

which can now be replaced solely with the sshd configuration directives shown in Listing 3. Line 16 specifies that SFTP will listen on port 2202 instead of the default SSH port 22. This option is configurable and left at 2202 for testing. Upon completion of the initial test, you can reset this to Port 22, then restart OpenSSH.

Listing 3: sshd Configuration

01 bash#>cd /usr/local/etc
02 bash#> echo >sshd_config
03 bash#> vi sshd_config
04 AllowTcpForwarding no
05    ClientAliveCountMax 3
06    ClientAliveInterval 0
07    Compression delayed
08    LoginGraceTime 60s
09    LogLevel DEBUG3
10    MaxAuthTries 2
11    PasswordAuthentication yes
12    PermitEmptyPasswords no
13    PermitRootLogin no
14    PermitTunnel no
15    PermitUserEnvironment no
16    Port 2202
17    Protocol 2
18    StrictModes yes
19    SyslogFacility AUTH
20    TCPKeepAlive yes
21    UseDNS no
22    UsePrivilegeSeparation yes
23    Subsystem sftp  /usr/local/libexec/sftp-server
24    Match Group sftponly
25       ChrootDirectory /export/home/jail/sftpuser/home/sftpuser
26       ForceCommand internal-sftp
27       X11Forwarding no
28       AllowTcpForwarding no
29 bash#>

The most critical part is the last stanza (lines 24-28). This section specifies the new chroot directory and limits the user to the internal SSH SFTP process. The last two lines disallow X11 (graphical) forwarding and TCP port forwarding.

Information regarding the other directives can be found within the man pages for sshd_config [4].

Add a New SFTP User

After you've completed the preceding steps, you can configure the new SFTP user. As a proud command-line vi junkie, I prefer the old school method of editing /etc/password to add a new user:

sftpuser:x:30680:121213:Jail user :/export/home/jail/sftpuser/./home/sftpuser/:/usr/local/libexec/sftp-server

Appended to the user's home directory is ./home/sftpuser, which is required to signify that the chrooted directory for this user will now be perceived as /home/sftpuser rather than the full path.

Also, the shell is not a login shell (/bin/bash, /bin/sh, etc.) and must be set to the internal sftp-server process (/usr/local/libexec/sftp-server).

Add group 121213 to /etc/group,


then set the password for the user:

passwd sftponly

Optionally, you can ensure the default umask is set appropriately, if required (i.e., inside the /etc/default/login).

Create the chrooted Environment

To further simplify the process, create a chrooted environment for your new user, sftpuser, with the jail.ksh script shown in Listing 4. This script was originally published by Kent Cowgill, but the website has since disappeared. Note that you only need to modify lines 32 and 33. The remaining commands in the script create all the devices necessary for a virtual system environment for the chroot user.Because chroot users must not view anything outside of their own directory, they must have available all the tools and devices necessary to execute the commands ls, cd, and the like within their chrooted directory.

Listing 4: jail.ksh

001 #!/bin/ksh
003 #  script: jail.ksh
004 # version: 1.0
005 #    date: 9/27/2002
006 #  author: Kent Cowgill
007 #
008 # Description:
009 # This script sets up a minimal jail for chrooted users for ssh and
010 # sftp. Minimal error checking is performed -- Most errors are
011 # harmless: Adding existing groups, creating existing directories,
012 # etc. Please modify the JAILUSER and JAILGROUP variables in the
013 # script according to your specific needs and requirements.
014 #
015 # Disclaimer: Use this script at your own risk.  I cannot ensure that
016 # it will perform exactly as described on your system.  By using this
017 # script, you acknowledge that you have read and understand all the
018 # commands contained herein, and waive any claim against Kent Cowgill
019 # for any harm to your system.
020 #
021 # (c) 2002 Kent Cowgill.  Permission to modify and distribute is
022 # granted on condition the copyright message is included and
023 # modifications are clearly identified.
024 #
025 # For suggestions, additions, and corrections, I thank Alex Kramarov,
026 # Steven M. Christianson, james@firstaidmusic, Gabriele Facciolo,
027 # Eileen Coles, Hugh McLenagh, and Walter G. Aiello.
028 #
029 # For changes, suggestions, corrections, enhancements, comments, or
030 # criticisms, email
032 JAILUSER=jailuser
033 JAILGROUP=jailgroup
034 ####################################################################
036 ####################################################################
038 /usr/sbin/groupadd $JAILGROUP
040 mkdir /export/home/jail
041 chown root:$JAILGROUP /export/home/jail
042 chmod 750 /export/home/jail
044 /usr/sbin/useradd -g $JAILGROUP -c "Jail user $JAILUSER" \
045   -d /export/home/jail/$JAILUSER/./home/$JAILUSER -s /bin/sh $JAILUSER
047 mkdir /export/home/jail/$JAILUSER
048 chown $JAILUSER:$JAILGROUP /export/home/jail/$JAILUSER
050 cd /export/home/jail/$JAILUSER
051 mkdir etc
052 mkdir bin
053 mkdir usr
054 mkdir usr/bin
055 mkdir usr/local
056 mkdir usr/local/bin
057 mkdir usr/local/libexec
058 mkdir usr/local/sbin
059 mkdir usr/local/lib
060 mkdir usr/local/ssl
061 mkdir usr/local/ssl/lib
062 mkdir usr/lib
063 mkdir usr/platform
064 mkdir usr/platform/`uname -i`
065 mkdir usr/platform/`uname -i`/lib
066 mkdir dev
067 mkdir devices
068 mkdir devices/pseudo
069 mkdir home
071 cd /export/home/jail/$JAILUSER
072 APPS='bin/cp bin/ls bin/mkdir bin/mv bin/pwd bin/rm bin/rmdir bin/sh'
073 for i in $APPS; do
074   cp /$i ./$i
075   LIBS=`ldd ./$i | awk '{print $3}'`
076   for l in $LIBS; do
077     if [[ ! -d ./`dirname $l` ]]; then
078       mkdir ./`dirname $l` > /dev/null
079     fi
080     cp $l .$l
081   done
082 done
084 cd /export/home/jail/$JAILUSER/devices/pseudo
085 mknod mm@0:zero c 13 12
086 mknod mm@0:null c 13 2
087 cd /export/home/jail/$JAILUSER/dev
088 ln -s ../devices/psuedo/mm@0:zero zero
089 ln -s ../devices/pseudo/mm@0:null null
091 cd /export/home/jail/$JAILUSER
092 BINS="usr/local/bin/ssh usr/local/libexec/sftp-server usr/local/sbin/sshd usr/local/lib/ usr/local/ssl/lib/ usr/lib/ usr/platform/`uname -i`/lib/ usr/lib/"
093 for i in $BINS; do
094   cp /$i ./$i
095 done
097 mkdir /export/home/jail/$JAILUSER/home/$JAILUSER
098 chown $JAILUSER:$JAILGROUP /export/home/jail/$JAILUSER/home/$JAILUSER
100 touch /export/home/jail/$JAILUSER/etc/passwd
101 touch /export/home/jail/$JAILUSER/etc/group
103 echo "$JAILUSER:x:`/usr/xpg4/bin/id -u $JAILUSER`:`/usr/xpg4/bin/id-g $JAILGROUP`::/home/$JAILUSER:/bin/sh" > \
104       /export/home/jail/$JAILUSER/etc/passwd
106 echo "$JAILGROUP::`/usr/xpg4/bin/id -g $JAILUSER`:$JAILUSER" > \
107      /export/home/jail/$JAILUSER/etc/group
109 echo "done!"

After you have adjusted the file permissions to 755 so the file can be executed, you can issue:


At this point, you should have a full chrooted environment in /export/home/jail,

ls -al /export/home|grep jail
  drwxr-xr-x   3 root   root   96 Sep 28 18:52 jail
ls -alR /export/home/jail

and the permissions should be root:root, 755 all the way down the directory structure.

Next, you should verify that all the correct files were created by typing:

find /export/home/jail

Starting the sshd (SFTP) Daemon

To verify that the server is listening and to watch for any errors, start sshd and verify that it works by starting it in debug mode with the -d option (Listing 5).

Listing 5: Verify sshd

01 bash#> /usr/local/sbin/sshd -d
02    debug1: sshd version OpenSSH_5.6p1
03    debug1: read PEM private key done: type RSA
04    debug1: private host key: #0 type 1 RSA
05    debug1: read PEM private key done: type DSA
06    debug1: private host key: #1 type 2 DSA
07    debug1: rexec_argv[0]='/usr/local/sbin/sshd'
08    debug1: rexec_argv[1]='-d'
09    debug1: Bind to port 2202 on ::.
10    Server listening on :: port 2202.
11    debug1: Bind to port 2202 on
12    Server listening on port 2202.

Now the fun part begins, and you can commence testing:

bash#> sftp -P2202 sftpuser@myserver01
    sftpuser@myserver01's password:
    Connected to myserver01.
    sftp> pwd
    Remote working directory: /
    sftp> ls
    sftp> cd /
    sftp> pwd
    Remote working directory: /

If all goes well, your users now will be officially chrooted into their own directory, and they will not be able to cd to any other directory outside of /export/home/jail/sftpuser/home/sftpuser.