Setting up virtual users in vsftpd
I’ve set up a FTP server for me and my colleagues. This was a matter of honor (don’t want to go all Duke Nukem on my friends) and as a such, it had to be done correctly. So, I’ve had some requirements prior to setting up:
- It must be secure
- It must scale (hey, we start small, but who said fiber is out of the question?)
- It must work with troublesome NAT-environments as well
- It must allow as much fine-grained control as possible.
- Users having access to the FTP service shall not be granted access to other aspects of the server
- Every user must have personal credentials
Naturally, that turned me to good ‘ol vsftpd. A true bitch when it comes to configuring, but as soon as it works, it’s rock solid. To fullfill requirement 5, not allowing FTP users access to other parts of the system, I chose to create an independent user database, not known to, nor used by the rest of the system. This is way more secure than defining access levels – what doesn’t exist, can’t do anything. As it turns out this isn’t exactly trivial with vsftpd – but it’s not rocket science, either. While there are some HOWTO’s floating around for this – the majority of themÂ suck hairy balls, in my humble opinion.
The following description is tailored to a Debian installation, but can be easily adopted to other distributions – and if you can’t do this, you shouldn’t be setting up an FTP server that’s connected to the internet anyway.
Now, let’s get started. Given the somewhat lackingÂ nature of vsftpd’s documentation, it took me a while to figure out that one must implement virtual users in vsftpd with PAM.Â vsftpd relies heavily on it for authentication; it doesn’t implement any user authentication backends by itself 1 like ProFTPD (which is a good thing). This requirement makes the next step crystal clear:
With PAM, one has a myriad of possibilities (read: modules) to chose from to implement a user database separate from the system (and other services using PAM, for that matter), ranging from simple non-encrypted text files to full fledged battle-hardened authentication schemes. As it’s 2011, I don’t recommend unencrypted credentials – but we don’t want to get too complex either (KISS!). The PAM moduleÂ
pam_pwdfile.soÂ is perfect for this. It’s available in the package libpam-pwdfile.
A word about security.
pam_pwdfile.so is only as secure as your system’s crypt() function. That can mean anything from utterly horrible to fairly secure. If this is a concern to you, seek an alternative – vsftpd doesn’t care. Just adjust the instructions given for PAM as necessary.
The next step is to create a PAM service definition for vsftpd (one could use the stock one, but I preferred to go from scratch; it’s easier to debug). Create a file with a name to your liking (I chose to name it “vsftpd-virtual”) in
/etc/pam.d/.Â It should look like this:
auth required pam_listfile.so item=user sense=deny file=/etc/ftpusers onerr=succeed auth [success=done default=ignore] pam_unix.so nullok_secure auth sufficient pam_pwdfile.so pwdfile /etc/vsftpd.passwd account required pam_permit.so auth required pam_deny.so
The first line make sure that some standard behaviour (per ftpd(8)) is retained, it’s simply copied from Debian’s stock vsftpd PAM service definition. The next two lines allow either system users or virtual users to login – which is what I want. The “account” line is required, but as I don’t care for expired users or similar, it’s set to give an OK in any case. The last line is needed because of the “stacking” philosophy of PAM 2.
Create the user database
The next step is to create
/etc/vsftpd.passwd. It can be created with htpasswd:
htpasswd -c /etc/vsftpd.passwd USERNAME
Obviously, you replace USERNAME with any username you like. Adding further users, deleting them, updating passwords is explained in the Manpage of htpasswd.
Note:Â I found some erratic behaviour with htpasswd. On some runs, when creating new users, it seemed to MD5-hash (to further it, it uses a custom version of the algorithm) the passwords, instead of crypt(). That can be remedied by simply re-running htpasswd (you may want to force the use of crypt() with the –d option).
Testing the PAM service
Before going on, make sure that the above things works. That’s were
pamtester comes into play. It’s a little tool that does nothing more than validate an username/password pair against particular PAM service. For Debian, it’s available inÂ libpam-dotfile. Test by:
pamtester vsftpd-virtual USERNAME
whereas “vsftpd-virtual” is the name of the file you’ve created earlier on. Don’t continue unless wrong logins are rejected, and good ones accepted. You’ll spare yourself a lot of hassle in case something else doesnt’ work as expected later on.
Now having your PAM supporting user that aren’t really exisiting on the system, it’s now time to configure vsftpd (usually done in /etc/vsftpd.conf). These are the important bits:
The first important thing to note ist that vsftpd itself distinguishes only between two kinds of users: anonymousÂ and authenticated. By Default, vsftpd allows only anonymous logins. To enable authentication, one must setÂ
local_enableÂ to YES, which allows vsftpd to use the PAM framework. For me, that opens up the system a bit too wide – also, it still wouldn’t allow virtual users to login: Since they are not known to the system, vsftpd would fail to continue the session after login, since it cannot “become” this user. Hence, theÂ
guest_enableÂ 3setting. This will cause vsftpd to remap any user that logged in to a single, preset user â€“ “ftp”Â by default. This is good here.
That’s it! After restarting vsftpd, you’ll have virtual users working.
- The “secure_email_list”Â isn’t a real backend, it just validates login credentials for otherwise anonymous logins, whereas virtual users are handled differently. ↩
- If none of the “auth” modules above (so, earlier evaluated) returns sucess, access would be granted in any case, since they are both not explicitly required – just sufficient. This is an important distinction. But wait, the last line reads “required”, and
pam_deny.soÂ (as the name suggest) does return failure in every case, wouldn’t that deny anyoneÂ from using the service? The answer is NoÂ – the first “auth”Â module to be sufficient for authorization andÂ returning success causes the subsequent “auth” modules not to be evaluated. ↩
- This will however, force the virtual user’s restrictions on the regular users as well. If you don’t want this, look into the chroot_local_user andÂ chroot_list_enable settings. ↩