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;
- configure
sshd
to 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 /etc/shells
. If /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.
Configuring sshd
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
support.
Using 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:
mkdir /home/vp
Next, set the directory’s permissions as required for chroot
‘ing:
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 chroot
:
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 /etc/ssh/sshd_config
, add:
# 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 sshd
.
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 vp
user:
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 vp@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.
Using mount
involves two steps: creating placeholders for the mirrored directories, and specifying the mounts.
First, within /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 /etc/fstab
:
/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
Conclusions
At this point, despite restarts, the vp
user will have read-only access to my WordPress installation’s webroot and wp-config.php
.
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.