As I’ll elaborate on in a few days1, when I added rate-limiting to nginx, I unintentionally blocked some legitimate traffic. Rather than make exceptions for these sources, I chose to provide certain services with read-only SFTP access to the specific directories they require.
It’s worth noting that in my case, I needed to grant particular users, not user groups, access to certain directories. Also, I have no need for any of these special users to access the same items. As a result, the following is tailored to user-level access to discrete directories, but can be set up using groups instead. I won’t detail that here, but the following should be sufficient for one to extrapolate how it would work for groups and shared directories.
Configuring this access broadly consists of three steps:
- create the user and its home directory, which will hold the read-only mirror;
sshdto permit only SFTP connections, and limit access to a particular directory; and
- configure the filesystem to mount read-only mirrors within the new user’s home directory.
It’s also worth noting that, while my examples reference WordPress and the VaultPress plugin, that’s simply a demonstration of use case. Nothing that I describe herein is particular to any service or CMS; the following requires only Debian and OpenSSH.
Adding a user
Since the directories I need to provide access to are specific to each read-only user I’ll create, I’m able to use the user’s home directory as its jail. If multiple users were to access the same directory, this approach wouldn’t be appropriate, but for my needs, it makes the configuration very logical.
First, because this user should have access the server via SFTP, an appropriate shell environment should be used to ensure that the user can’t gain unwanted SSH access. Fortunately, OpenSSH provides an SFTP-only shell that we can assign to the user, after one small change. For the OpenSSH server to be accepted as a login shell, it must be listed in
/usr/lib/openssh/sftp-server isn’t listed in your
/etc/shells, add it with this command:
echo '/usr/lib/openssh/sftp-server' >> /etc/shells
Now we can create the user:
useradd -s /usr/lib/openssh/sftp-server -d /home/vp vp
The above command creates the user
vp with the home directory
/home/vp and the login shell we just configured. The
-d parameter is redundant because it specifies the default value, but I’ve included it for clarity.
If you’re confused right now as to why the user’s home directory isn’t set to that which holds the files the user will have access to, don’t worry–we’ll get to that part in due time. As I mentioned, we’ll share a read-only mirror with the new user (
vp in my example) in the third part of this discussion.
With the user and home directory created, we can configure
sshd to restrict that user’s access to an SFTP connection to its home directory. We do so by leveraging OpenSSH’s
chroot restricts a user and its operations to the directory specified. As the name implies, it modifies which directory appears to be the root directory for the user’s processes. This “jail” doesn’t allow the user to explore anywhere beyond the specified directory.
ls -la / will display the contents of the chroot’d directory, not the server’s actual root directory.
First, if for some reason
/home/vp wasn’t created by the
useradd command, create it:
Next, set the directory’s permissions as required for
chown -R root:root /home/vp
Notice that the directory is owned by
root, not the
vp user. A proper
chroot environment uses a directory within a
root-owned directory; in our case,
/home/vp is the root-owned directory. This approach will also ensure that the user won’t have SFTP access to sensitive directories, such as
/home/vp/.ssh/. To that end, create the directory that will serve as the
mkdir /home/vp/chroot chown root:root /home/vp/chroot
While we’re making directories, let’s add the user’s SSH configuration directory:
mkdir /home/vp/.ssh chown vp:vp /home/vp/.ssh
As we’ll see, while the
.ssh directory is owned by the
vp user, that user won’t have access to it via SFTP.
With the requisite directories in place, we can configure
sshd and its
chroot jail. To the end of your
# User and group restrictions Match User vp ChrootDirectory /home/vp/chroot AllowTCPForwarding no X11Forwarding no ForceCommand internal-sftp
Depending on the age of your
sshd configuration, you may need to change the SFTP subsystem to:
Subsystem sftp internal-sftp
Older configurations may specify the login shell I referenced earlier,
/usr/lib/openssh/sftp-server; replacing this with
internal-sftp had no negative impact on my VPS, and is needed for the preceding configuration change.
At this point, restart
To test that the configuration is working as expected, I’ll rely on public-key authentication2. To begin, we need an
authorized_keys file owned by the
sudo -u vp -H touch /home/vp/.ssh/authorized_keys
I temporarily added my public key to this file to confirm that I could connect via SFTP, and that SSH connections were denied. When attempting to connect via SSH, you should receive this message:
>> ssh email@example.com
This service allows sftp connections only.
Connection to example.com closed.
If you’re able to connect and perform functions not supported by SFTP, confirm that you reloaded
sshd and that the configuration changes were saved.
At this point, the
vp user has restricted access to an empty directory, which is owned by the root user. It’s time to mirror some directories.
Adding read-only mounts
Since we’re using a
chroot jail, symbolic links aren’t an option. While I can create them as the
root user, the
vp user won’t be able to follow them if they lead outside of the
chroot directory. In their place, we’ll use mount binds, and add them to
/etc/fstab to guarantee persistence. Besides being something supported within a
chroot jail, the
mount command supports a read-only flag (
-o ro), to further enforce that aspect of this setup.
mount involves two steps: creating placeholders for the mirrored directories, and specifying the mounts.
/home/vp/chroot, I created one file and one directory. The file is necessary because my
wp-config.php is one directory above my WordPress webroot, as the CMS supports.
touch /home/vp/chroot/wp-config.php mkdir /home/vp/chroot/public_html chown vp:vp /home/vp/chroot/*
In the above, replace
public_html with whatever you’ve named your webroot. If your
wp-config.php is in the same directory as
wp-settings.php, omit the
touch step, and any further commands related to
wp-config.php. Also, be sure not to change the ownership of
/home/chroot/public_html itself, as that should remain owned by the
root user and group; the
vp user should only own the contents of
/home/chroot/public_html, not the directory itself.
With the placeholders created, we can link them to their sources:
mount --bind -o ro /srv/www/wordpress/wp-config.php /home/vp/chroot/wp-config.php mount --bind -o ro /srv/www/wordpress/public_html /home/vp/chroot/public_html
Assuming no errors with the above commands, the placeholders should reflect the same contents as their sources. For example,
ls -la /home/vp/chroot/public_html will match the results of
ls -la /srv/www/wordpress/public_html.
So that these changes persist server restarts, they must be defined in
/srv/www/wordpress/wp-config.php /home/vp/chroot/wp-config.php none bind,ro 0 0 /srv/www/wordpress/public_html/ /home/vp/chroot/public_html/ none bind,ro 0 0
At this point, despite restarts, the
vp user will have read-only access to my WordPress installation’s webroot and
From the VaultPress dashboard, configuring SFTP access generates a public key, which is added to the
/home/vp/.ssh/authorized_keys file created earlier. VaultPress will test the connection and use it automatically.