Vsftpd FTP Server With Virtual Users (CentOS 7)

vsftpd ("Very Secure FTP Daemon") is an FTP server for Linux and supports PAM ("pluggable authentication modules"). A virtual user cannot login to the Linux system and is considered more secure than using a "real" user whom can login to a Linux system. This guide offers a script that demonstrates one way to setup a vsftpd server using PAM with a Berkeley DB while using virtual users with encrypted passwords.

This test box is a CentOS 7.2 minimal install.  It had iptables installed and no firewalld.  I wanted to work with firewalld so installed firewalld.

#!/bin/bash

#------------------------------------------------------------------------------------
# Install vsFTPd
#------------------------------------------------------------------------------------

yum install -y vsftpd
systemctl enable vsftpd.service
mkdir -p /etc/vsftpd/vconf

#------------------------------------------------------------------------------------
# Configure vsFTPd data directory and user
#------------------------------------------------------------------------------------

mkdir -p /data/ftp
useradd -s /sbin/nologin -d /data/ftp vsftpd
chown -R vsftpd:vsftpd /data/ftp

#------------------------------------------------------------------------------------
# Configure vsFTPd (/etc/vsftpd/vsftpd.conf)
#------------------------------------------------------------------------------------

cp /etc/vsftpd/vsftpd.conf{,.original}

sed -i "s/^.*anonymous_enable.*/anonymous_enable=NO/g" /etc/vsftpd/vsftpd.conf
sed -i "/^xferlog_std_format*a*/ s/^/#/" /etc/vsftpd/vsftpd.conf
sed -i "s/#idle_session_timeout=600/idle_session_timeout=900/" /etc/vsftpd/vsftpd.conf
sed -i "s/#nopriv_user=ftpsecure/nopriv_user=vsftpd/" /etc/vsftpd/vsftpd.conf
sed -i "/#chroot_list_enable=YES/i\chroot_local_user=YES" /etc/vsftpd/vsftpd.conf
sed -i 's/listen=NO/listen=YES/' /etc/vsftpd/vsftpd.conf
sed -i 's/listen_ipv6=YES/listen_ipv6=NO/' /etc/vsftpd/vsftpd.conf

echo 'allow_writeable_chroot=YES
guest_enable=YES
guest_username=vsftpd
local_root=/data/ftp/$USER
user_sub_token=$USER
virtual_use_local_privs=YES
user_config_dir=/etc/vsftpd/vconf' >> /etc/vsftpd/vsftpd.conf

systemctl start vsftpd.service

#------------------------------------------------------------------------------------
# Configure pam (/etc/pam.d/vsftpd)
#------------------------------------------------------------------------------------

cp /etc/pam.d/vsftpd{,.original}

echo '#%PAM-1.0
auth required pam_userdb.so db=/etc/vsftpd/password crypt=crypt
account required pam_userdb.so db=/etc/vsftpd/password crypt=crypt
session required pam_loginuid.so' > /etc/pam.d/vsftpd

#------------------------------------------------------------------------------------
# Configure firewalld
#------------------------------------------------------------------------------------

yum install -y firewalld
systemctl start firewalld.service
systemctl enable firewalld.service
firewall-cmd --permanent --add-service=ftp
firewall-cmd --reload

#------------------------------------------------------------------------------------
# Configure selinux
#------------------------------------------------------------------------------------

setsebool -P ftpd_full_access 1

Create a user.

mkdir -p /data/ftp/testuser1
chown -R vsftpd:vsftpd /data/ftp

# /etc/vsftpd/vconf/testuser1
echo 'dirlist_enable=YES
download_enable=YES
local_root=/data/ftp/testuser1
write_enable=YES' > /etc/vsftpd/vconf/testuser1

echo 'testuser1' | tee /etc/vsftpd/password{,-nocrypt} > /dev/null

myval=$(openssl rand -base64 6)
echo $myval >> /etc/vsftpd/password-nocrypt
echo $(openssl passwd -crypt $myval) >> /etc/vsftpd/password

# /etc/vsftpd/password.db
db_load -T -t hash -f /etc/vsftpd/password /etc/vsftpd/password.db

It is worth explaining what is accomplished with the myval variable. Since pam has two lines with crypt=crypt appended. The passwords need to be encrypted.  The goal here is to generate a random password using openssl rand then encrypting that password with openssl passwd -crypt.  The output to two files, one for the encrypted password to be added to the database, the other for the non encrypted password to test the user. Using tee allows this script to create two files with the username. To append to the files rather than overwrite them, tee -a may be used. This one-liner replaces the following two lines.

echo 'testuser1' > /etc/vsftpd/password
echo 'testuser1' > /etc/vsftpd/password-nocrypt

If you remove the crypt=crypt from the PAM file, the clear text password is readable via db_dump -d a /etc/vsftpd/password.db. The example below is with the crypt=crypt appendices.

[root@test]# db_dump -d a /etc/vsftpd/password.db | grep len
        [000] 4086 len:   9 data: testuser1
        [001] 4072 len:  13 data: MV5Bh.YsxmsB6
[root@test]# cat password
testuser1
MV5Bh.YsxmsB6
[root@test]# cat password-nocrypt
testuser1
3ySBG56Y

For this example, use your favorite ftp client with your host IP (ie. 127.0.0.1), username: testuser1, and password (from the password-nocrypt file): 3ySBG56Y and all should work.

[root@test]# ftp 127.0.0.1
Connected to 127.0.0.1 (127.0.0.1).
220 (vsFTPd 3.0.2)
Name (127.0.0.1:root): testuser1
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> 

I tried to work out all the kinks, but some additional tweaking may be needed. This is at least a fairly good start to get things going.

Source(s)
http://www.alexlinux.com/vsftpd-virtual-users-centos-7-example/
http://yhz.me/blog/Centos-7-Install-Vsftp.html
https://it.megocollector.com/scripts/vsftpd-ftp-server-with-virtual-users-mysql-pam/
http://www.netkiller.cn/storage/ftp/vsftpd.html
http://edvoncken.net/2011/03/tip-encrypted-passwords-just-add-salt/
http://www.neant.ro/2012/04/secure-ftp-with-vsftpd/
http://stackoverflow.com/questions/15767273/encrypted-password-in-berkeley-db-for-vsftpd-using-pam-userdb-so
http://www.howtogeek.com/howto/30184/10-ways-to-generate-a-random-password-from-the-command-line/
http://stackoverflow.com/questions/4505339/bash-ambiguous-redirect-redirect-to-multiple-files