Setting up a file transfer host
Had to setup a new file transfer host recently, with the following requirements:
- individual login accounts required (for customers, no anonymous access)
- support for (secure) downloads, ideally via a browser (no special software required)
- support for (secure) uploads, ideally via sftp (most of our customers are familiar with ftp)
Our target was RHEL/CentOS 7, but this should transfer to other linuxes pretty readily.
Here's the schema we ended up settling on, which seems to give us a good mix of security and flexibility.
- use apache with HTTPS and PAM with local accounts, one per customer, and
nologinshell accounts - users have their own groups (group=
$USER), and also belong to thesftpgroup - we use the
usersgroup for internal company accounts, but NOT for customers - customer data directories live in /data
- we use a 3-layer hierarchy for security:
/data/chroot_$USER/$USERare created with anologinshell - the
/data/chroot_$USERdirectory must be owned byroot:$USER, with permissions750, and is used for an sftp chroot directory (not writeable by the user) - the next-level
/data/chroot_$USER/$USERdirectory should be owned by$USER:users, with permissions2770(whereusersis our internal company user group, so both the customer and our internal users can write here) - we also add an ACL to
/data/chroot_$USERto allow the company-internalusersgroup read/search access (but not write)
We just use openssh internal-sftp to provide sftp access, with the following config:
Subsystem sftp internal-sftp Match Group sftp ChrootDirectory /data/chroot_%u X11Forwarding no AllowTcpForwarding no ForceCommand internal-sftp -d /%u
So we chroot sftp connections to /data/chroot_$USER and then (via the ForceCommand)
chdir to /data/chroot_$USER/$USER, so they start off in the writeable part of their
tree. (If they bother to pwd, they see that they're in /$USER, and they can chdir
up a level, but there's nothing else there except their $USER directory, and they
can't write to the chroot.)
Here's a slightly simplified version of the newuser script we use:
die() {
echo $*
exit 1
}
test -n "$1" || die "usage: $(basename $0) <username>"
USERNAME=$1
# Create the user and home directories
mkdir -p /data/chroot_$USERNAME/$USERNAME
useradd --user-group -G sftp -d /data/chroot_$USERNAME/$USERNAME -s /sbin/nologin $USERNAME
# Set home directory permissions
chown root:$USERNAME /data/chroot_$USERNAME
chmod 750 /data/chroot_$USERNAME
setfacl -m group:users:rx /data/chroot_$USERNAME
chown $USERNAME:users /data/chroot_$USERNAME/$USERNAME
chmod 2770 /data/chroot_$USERNAME/$USERNAME
# Set user password manually
passwd $USERNAME
And we add an apache config file like the following to /etc/httpd/user.d:
Alias /CUSTOMER /data/chroot_CUSTOMER/CUSTOMER <Directory /data/chroot_CUSTOMER/CUSTOMER> Options +Indexes Include "conf/auth.conf" Require user CUSTOMER </Directory>
(with CUSTOMER changed to the local username), and where conf/auth.conf has
the authentication configuration against our local PAM users and allows internal
company users access.
So far so good, but how do we restrict customers to their own /CUSTOMER tree?
That's pretty easy too - we just disallow customers from accessing our apache document
root, and redirect them to a magic '/user' endpoint using an ErrorDocument 403
directive:
<Directory /var/www/html> Options +Indexes +FollowSymLinks Include "conf/auth.conf" # Any user not in auth.conf, redirect to /user ErrorDocument 403 "/user" </Directory>
with /user defined as follows:
# Magic /user endpoint, redirecting to /$USERNAME
<Location /user>
Include "conf/auth.conf"
Require valid-user
RewriteEngine On
RewriteCond %{LA-U:REMOTE_USER} ^[a-z].*
RewriteRule ^\/(.*)$ /%{LA-U:REMOTE_USER}/ [R]
</Location>
The combination of these two says that any valid user NOT in auth.conf should
be redirected to their own /CUSTOMER endpoint, so each customer user lands
there, and can't get anywhere else.
Works well, no additional software is required over vanilla apache and openssh, and it still feels relatively simple, while meeting our security requirements.
blog comments powered by Disqus