Copyright © 2011 Stefan Pettersson
THE DOCUMENTATION IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS DOCUMENTATION INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS DOCUMENTATION.
Revision History | ||
---|---|---|
Revision 1.0 | 8 Jan 2011 | stef |
First public release. |
Abstract
This HOWTO explains how vsftpd can be installed and configured on OpenBSD in a simple and secure way. Since both vsftpd and OpenBSD share security as a primary goal, mistakes in the installation and configuration are likely to pose larger security risks than mistakes in the source code. The configuration presented will serve local users their, chrooted, home directories, over TLS, and anonymous users a shared /pub directory (optionally with an upload/ directory). All while trying to fit nicely in the OpenBSD operating system.
Table of Contents
The OpenBSD package repository currently holds vsftpd 2.0.5 for OpenBSD 4.5 and vsftpd 2.2.2 for OpenBSD 4.8 while the most current version is vsftpd 2.3.2.
Here we will install from source since it's more illustrative. If you choose to use a package instead, be sure to give pkg_add the -vvv option so that you get a picture of how vsftpd is being set up.
We download the vsftpd tarball and its corresponding signature using ftp from the official ftp server vsftpd.beasts.org:
$ ftp -o vsftpd-2.3.2.tar.gz ftp://vsftpd.beasts.org/users/cevans/vsftpd-2.3.2.tar.gz [...] $ ftp -o vsftpd-2.3.2.tar.gz.asc ftp://vsftpd.beasts.org/users/cevans/vsftpd-2.3.2.tar.gz.asc [...]
Now we want to verify the detached cryptographic signature held in the .asc file. First, find out what key ID signed the package by trying to verify it. Then download the corresponding public key from a key server and use it to verify the signature. The gpg command is provided by the package gnupg.
$ gpg --verify vsftpd-2.3.2.tar.gz.asc gpg: Signature made Fri Aug 20 01:37:24 2010 CEST using DSA key ID 3C0E751C gpg: Can't check signature: public key not found $ gpg --recv-keys 3c0e751c gpg: requesting key 3C0E751C from hkp server keys.gnupg.net gpg: /home/stef/.gnupg/trustdb.gpg: trustdb created gpg: key 3C0E751C: public key "Chris Evans <chris@scary.beasts.org>" imported gpg: Total number processed: 1 gpg: imported: 1 $ gpg --verify vsftpd-2.3.2.tar.gz.asc gpg: Signature made Fri Aug 20 01:37:24 2010 CEST using DSA key ID 3C0E751C gpg: Good signature from "Chris Evans <chris@scary.beasts.org>" gpg: WARNING: This key is not certified with a trusted signature! gpg: There is no indication that the signature belongs to the owner. Primary key fingerprint: 8660 FD32 91B1 84CD BC2F 6418 AA62 EC46 3C0E 751C
The verification checked out. The warning message was expected, it means that GPG verified that the public key we downloaded from the key server was indeed used to sign the package we downloaded from beasts.org but that it can't promise that the key really belongs to Chris Evans. There's not much we can do about it really short of meeting Chris at some conference, verifying his identity and getting the key from him in person.
Before building vsftpd we will want to edit
builddefs.h
to match our requirements. Make sure SSL
is defined and that PAM as well as TCPWRAPPERS is undefined. OpenBSD does
not use PAM and we won't use TCPWRAPPERS in this HOWTO, so we might as
well skip them. builddefs.h
should look something like
this when you're finished:
#ifndef VSF_BUILDDEFS_H #define VSF_BUILDDEFS_H #undef VSF_BUILD_TCPWRAPPERS #undef VSF_BUILD_PAM #define VSF_BUILD_SSL #endif /* VSF_BUILDDEFS_H */
To build we just run make which should output a vsftpd binary.
$ make [...] $ ls -l vsftpd -rwxr-xr-x 1 stef staff 101444 Jan 6 11:50 vsftpd $ file vsftpd vsftpd: ELF 32-bit LSB executable, Intel 80386, version 1, for OpenBSD, dynamically linked (uses shared libs), stripped
Before copying the binaries and taking it for a test drive there are a few things to prepare. First, vsftpd will need a low-privileged user and group to run as. Also, we need an ftp user account to use for anonymous users.
We follow OpenBSD's convention of using an underscore as prefix for
user and group names used by daemons. By default,
groupadd will give the group an ID starting from
1000 but we'll place it in the range 500-999 to make it stand out from
the regular groups used by users. Just make sure it isn't defined in
/etc/group
or /etc/passwd
already since we're going to use the same number as UID for the user.
Also, we create a group that will be able to read the private key file corresponding to the certificate used for FTP over SSL/TLS. More about this later on in the HOWTO.
# groupadd -g 502 _vsftpd # groupadd ssl
We follow the same conventions for the low-privileged user account, set
its primary group to _vsftpd and make it a member of the ssl group.
Additionally we set the home directory to
/var/empty
(to which the user and group have no
write permissions), the login class to daemon (check
/etc/login.conf
for more information),the GECOS
field to a descriptive string and the shell to
/sbin/nologin
.
# useradd -u 502 -g _vsftpd -G ssl -d /var/empty -L daemon -c "vsftpd daemon,,," -s /sbin/nologin _vsftpd
To support anonymous FTP users we need an account called ftp as well.
It's more or less the same as _vsftpd but not in the daemon class and
its home directory is the anonymous FTP root. In our case,
/pub
.
# groupadd -g 503 ftp # useradd -m -u 503 -g ftp -d /pub -c "Anonymous FTP user,,," -s /sbin/nologin ftp
Now there are only three things to install; (1) the binary, (2) the man
pages and (3) the configuration file. This is handled by the
Makefile
and you can review what it'll do by checking
its install target (it won't install the sample configuration file). We'll
install it by hand though:
# install -m 555 -o root -g bin vsftpd /usr/local/sbin # install -m 444 -o root -g bin vsftpd.8 /usr/local/man/man8 # install -m 444 -o root -g bin vsftpd.conf.5 /usr/local/man/man5
Test if the manual pages are located and if the binary runs as expected.
# man vsftpd [...] # vsftpd -v vsftpd: version 2.3.2
Since we don't have a configuration file in place yet, we'll create a minimal one to start up the server for a test drive.
# cat > /etc/vsftpd.conf << EOF > listen=YES > background=YES > anonymous_enable=NO > local_enable=YES > secure_chroot_dir=/var/empty > EOF # vsftpd # ftp ftp> open localhost Trying ::1... ftp: connect to address ::1: Connection refused Trying 127.0.0.1... Connected to perf.ection.se. 220 (vsFTPd 2.3.2) Name (localhost:stef): 331 Please specify the password. Password: 230 Login successful. Remote system type is UNIX. Using binary mode to transfer files. ftp> ls 150 Here comes the directory listing. drwxr-xr-x 2 1000 20 512 Dec 11 21:40 bin drwxr-xr-x 8 1000 20 512 Jul 19 21:50 devel drwx------ 2 1000 20 512 Aug 17 12:46 mail -rw------- 1 1000 20 848753 Jan 06 00:48 mbox drwxr-xr-x 13 1000 20 512 Jan 03 21:40 mydocs -rw-r--r-- 1 1000 20 3403 Jan 05 20:53 notes drwxr-xr-x 9 1000 20 512 Sep 11 10:32 proj drwxr-xr-x 4 1000 20 512 Jul 19 22:14 rc drwxr-xr-x 3 1000 20 512 Nov 23 09:10 src drwxr-xr-x 2 1000 20 512 Jan 05 20:20 tmp 226 Directory send OK. ftp> get notes local: notes remote: notes 150 Opening BINARY mode data connection for notes (3403 bytes). 100% |********************************************| 3403 00:00 226 Transfer complete. 3403 bytes received in 0.00 seconds (8.36 MB/s) ftp> quit 221 Goodbye. # rm notes
Cool, it worked fine. Let's move on. Remember to kill the server:
# # ps aux | grep vsftpd root 20179 0.0 0.2 524 896 ?? Is 2:26PM 0:00.00 vsftpd # kill 20179
Everything in vsftpd is configured in the configuration file. There are no
command line options or arguments and no environment variables to care
about. (Actually, there is one command line option that will take a
configuration statement as argument and override what's in
vsftpd.conf
, but that's it.) All the defaults are
clearly documented in the manual too, so you are unlikely to be caught off
guard by something.
A complete vsftpd.conf
file is provided at the end of
this HOWTO.
The basic configuration covers that which both local and anonymous users share. We'll ignore the temporary configuration we used for the test-run above and start from scratch.
Run as a daemon (without the help of a inetd-style super-server), skip IPv6, run in the background and change the login banner to a generic one. (Remember to change the host name...)
listen=YES listen_ipv6=NO background=YES ftpd_banner=perf.ection.se FTP server ready
Use _vsftpd as the unpriviliged user and use
/var/empty
as the secure chroot directory.
nopriv_user=_vsftpd secure_chroot_dir=/var/empty
Define a few thresholds. Whether they are sensible or not depends on your hardware, the server's other duties and your requirements.
max_clients=20 max_per_ip=10 local_max_rate=0 anon_max_rate=0
Enable passive mode and define which ports that should be used for the incoming data channel connection. These are the ports you need to open in your firewall to let users connect to them.
pasv_enable=YES pasv_min_port=40000 pasv_max_port=41000
Only accept users listed in the file
/etc/ftpusers.allow
to log in. Everyone else is
denied before they get to enter the password. This is to prevent the
passwords from being sent in the clear. Note that the classic
/etc/ftpusers
is a blacklist rather than a
whitelist like in this case.
userlist_enable=YES userlist_deny=NO userlist_file=/etc/ftpusers.allow
Enable SSL/TLS, give location of the server certificate and key, only enable TLSv1 and force local users to connect using it. We will create and install the certificates later on in this document.
ssl_enable=YES debug_ssl=NO rsa_cert_file=/etc/ssl/perf.ection.se.crt rsa_private_key_file=/etc/ssl/private/perf.ection.se.key ssl_sslv2=NO ssl_sslv3=NO ssl_tlsv1=YES force_anon_logins_ssl=NO force_anon_data_ssl=NO force_local_logins_ssl=YES force_local_data_ssl=YES
We log to two files. To (1) /var/log/vsftpd.log
via
syslog (we'll modify /etc/syslog.conf
later) and to
(2) /var/log/xferlog
without going through syslog.
This to not confuse automatic log analysers that expect WU-FTPD-style
logs in xferlog
. Set log_ftp_protocol to YES if you
want to log all commands and responses to syslog as well. That way, you
can follow which directories the user checked out and possibly determine
what client she used. It's great for troubleshooting but the log output
will be at least ten times as large though.
syslog_enable=YES dual_log_enable=YES xferlog_file=/var/log/xferlog log_ftp_protocol=NO
Enable anonymous and local access. Modify according to your needs.
local_enable=YES anonymous_enable=YES
Make sure users are chrooted to their home directories (regardless if they're in a “chroot list” or not), that their shell is checked for validity and that files they upload are umasked properly.
chroot_local_user=YES chroot_list_enable=NO check_shell=YES local_umask=0077
Enable writing (upload and deletion) for local users.
write_enable=YES
Make vsftpd use the account ftp for anonymous access. Make anonymously uploaded files owned by the ftp account. Even if anonymous uploads are disallowed it's just not a good idea to let random users create files owned by the root user.
ftp_username=ftp anon_upload_enable=NO anon_umask=0777 anon_world_readable_only=YES chown_uploads=YES chown_username=ftp
If you want to give anonymous users the permission to upload files you
should set anon_upload_enable to YES and create an upload directory in
/pub
to where the ftp user has write permission.
The fact that anonymous users are only able to read world-readable files
and the default umask of 0777 means that one user cannot download files
uploaded by someone else.
# install -d -m 770 -o root -g ftp /pub/upload
Although we're finished with the main vsftpd configuration there are a few things left to tinker with to make vsftpd fit properly with our OpenBSD system.
We might want to give some local user access via FTP to her home
directory but not shell access. We can solve this by setting her default
shell to /sbin/nologin
but since we have configured
check_shell=YES, this “shell” must be added to
/etc/shells
.
# echo /sbin/nologin >> /etc/shells
Add /etc/vsftpd.conf
and
/etc/ftpusers.allow
to
/etc/changelist
so that they are included in the
intergrity checking of OpenBSD's daily security
script.
# echo /etc/vsftpd.conf >> /etc/changelist # echo /etc/ftpusers.allow >> /etc/changelist
Add our newly created _vsftpd user to
/etc/ftpusers
. We don't use it at all in our vsftpd
setup but other daemons might and we don't want our unprivileged user to
end up being able to log in.
# echo _vsftpd >> /etc/ftpusers
We do, however, use the /etc/ftpusers.allow
file
and we need to populate it with the names of the local users that should
be able to use the service.
# echo "# users allowed to log in to vsftpd" > /etc/ftpusers.allow # echo ftp >> /etc/ftpusers.allow # echo anonymous >> /etc/ftpusers.allow # echo stef >> /etc/ftpusers.allow
Make sure that the ftp facility is redirected to
/var/log/vsftpd.log
in
/etc/syslog.conf
like the following:
ftp.info /var/log/vsftpd.log
Add the following to /etc/newsyslog.conf
so that
/var/log/vsftpd.log
is rotated in the same way as
/var/log/xferlog
. You might wanna tweak the count,
size and when values according to your situation though.
# logfile_name owner:group mode count size when flags
/var/log/vsftpd.log 600 7 250 * Z
Lastly, make sure that the built-in ftpd is disabled.
$ grep ftpd /etc/rc.conf* /etc/rc.conf:# Set to NO if ftpd is running out of inetd /etc/rc.conf:ftpd_flags=NO # for non-inetd use: "-D" $ grep ftpd /etc/inetd.conf #ftp stream tcp nowait root /usr/libexec/ftpd ftpd -US #ftp stream tcp6 nowait root /usr/libexec/ftpd ftpd -US #tftp dgram udp wait root /usr/libexec/tftpd tftpd -s /tftpboot #tftp dgram udp6 wait root /usr/libexec/tftpd tftpd -s /tftpboot
Then, to make vsftpd start automatically at boot we need to give it an
entry in /etc/rc.local
. Make sure to put it above
the final echo '.' command:
if [ -x /usr/local/sbin/vsftpd ]; then echo -n " vsftpd" /usr/local/sbin/vsftpd fi
There are two options for creating certificates for our service. (1) Creating a certificate signing request (CSR) to be sent to a proper certificate authority (CA) like Comodo or VeriSign. Or, (2) creating a signing request signed with its own key; a self-signed certificate. We'll do the latter and we'll use the openssl command. Make sure you get the hostname right everywhere or the FTP client will complain about the mismatch.
$ openssl req -new -newkey rsa:2048 -nodes -subj '/CN=perf.ection.se' \ > -keyout perf.ection.se.key -out perf.ection.se.csr Generating a 2048 bit RSA private key .+++ ..........................................................+++ writing new private key to 'perf.ection.se.key' -----
Now we can sign our CSR with our corresponding key to create the certificate. If you are going to send it to a CA for signing you'll need a more accurate subject than just the common name (CN).
$ openssl x509 -req -days 365 -in perf.ection.se.csr \ > -signkey perf.ection.se.key -out perf.ection.se.crt Signature ok subject=/CN=perf.ection.se Getting Private key $ ls -l total 12 -rw-r--r-- 1 stef staff 989B Jan 8 22:16 perf.ection.se.crt -rw-r--r-- 1 stef staff 899B Jan 8 22:16 perf.ection.se.csr -rw-r--r-- 1 stef staff 1.6K Jan 8 22:16 perf.ection.se.key $ openssl x509 -in perf.ection.se.crt -noout -fingerprint SHA1 Fingerprint=AE:EA:78:12:5B:A2:D2:1A:72:70:A9:C4:F8:9C:ED:DE:A0:92:F5:35
The fingerprint is a string you can give to your users so that they can verify that they are connecting to the correct server. They will only need this the first time if they choose to trust the certificate signer.
The certificate perf.ection.se.crt
should be placed
in the directory /etc/ssl
and the key
perf.ection.se.key
in the sub-directory
private
. Make sure to chgrp both
files so that the ssl group we created during the preparation earlier
can read them.
# chown root:ssl perf.ection.se.* # chmod 444 perf.ection.se.crt # chmod 440 perf.ection.se.key # mv perf.ection.se.crt /etc/ssl # mv perf.ection.se.key /etc/ssl/private/ # chown root:ssl /etc/ssl/private/ # chmod 750 /etc/ssl/private/
# Run as a daemon (without the help of a inetd-style super-server), skip IPv6, # run in the background and change the login banner to a generic one. (Remember # to change the host name...) listen=YES listen_ipv6=NO background=YES ftpd_banner=perf.ection.se FTP server ready # Use _vsftpd as the unpriviliged user and use /var/empty as the secure chroot # directory. nopriv_user=_vsftpd secure_chroot_dir=/var/empty # Define a few thresholds. Whether they are sensible or not depends on your # hardware, the server's other duties and your requirements. max_clients=20 max_per_ip=10 local_max_rate=0 anon_max_rate=0 # Enable passive mode and define which ports that should be used for the # incoming data channel connection. pasv_enable=YES pasv_min_port=40000 pasv_max_port=41000 # Only accept users listed in the file /etc/ftpusers.allow to log in. Everyone # else is denied before they get to enter the password. This is to prevent the # passwords from being sent in the clear. Note that the classic /etc/ftpusers # is a blacklist rather than a whitelist like in this case. userlist_enable=YES userlist_deny=NO userlist_file=/etc/ftpusers.allow # Enable SSL/TLS, give location of the server certificate and key, only enable # TLSv1 and force local users to connect using it. ssl_enable=YES debug_ssl=NO rsa_cert_file=/etc/ssl/perf.ection.se.crt rsa_private_key_file=/etc/ssl/private/perf.ection.se.key ssl_sslv2=NO ssl_sslv3=NO ssl_tlsv1=YES force_anon_logins_ssl=NO force_anon_data_ssl=NO force_local_logins_ssl=YES force_local_data_ssl=YES # We log to two files. To (1) /var/log/vsftpd.log via syslog (we'll modify # /etc/syslog.conf later) and to (2) /var/log/xferlog without going through # syslog. This to not confuse automatic log analysers that expect WU-FTPD-style # logs in xferlog. Set log_ftp_protocol to YES if you want to log all commands # and responses to syslog as well. That way, you can follow which directories # the user checked out and possibly determine what client she used. The log # output will be at least ten times as large though. syslog_enable=YES dual_log_enable=YES xferlog_file=/var/log/xferlog log_ftp_protocol=NO # Enable anonymous and local access. Comment out according to your needs. local_enable=YES anonymous_enable=YES # Make sure users are chrooted to their home directories (regardless if they're # in a "chroot list" or not), that their shell is checked for validity and that # files they upload are umasked properly. chroot_local_user=YES chroot_list_enable=NO check_shell=YES local_umask=0077 # Enable writing (upload and deletion) for local users. write_enable=YES # Make vsftpd use the account ftp for anonymous access. Make anonymously # uploaded files owned by the ftp account. Even if anonymous uploads are # disallowed it's just not a good idea to let random users create files owned # by the root user. ftp_username=ftp anon_upload_enable=NO anon_umask=0777 anon_world_readable_only=YES chown_uploads=YES chown_username=ftp
Now that you're finished there are a few test cases to consider. Anonymous
users should end up in /pub
, be unable to leave that
directory, only read files that are world-readable and not be able to
upload any files. Local users will be forced to use TLS/SSL (negotiated
in-line on port 21/tcp) and be limited to their home directory where they
are able to upload files. Local users are only allowed if they're listed
in /etc/ftpusers.allow
. All access will be logged to
vsftpd.log
and xferlog
in
/var/log
.
When troubleshooting, it helps to set debug_ssl=YES and
log_ftp_protocol=YES in vsftpd.conf
. Also, if you are
using lftp for testing, remember the -d option to
output debug information.
$ lftp ftp://perf.ection.se/ cd ok, cwd=/ lftp perf.ection.se:/> ls -rw-r--r-- 1 0 0 0 Jan 08 22:42 testfile drwxrwxr-x 2 0 503 512 Jan 08 22:44 upload lftp perf.ection.se:/> quit $ lftp ftp://stef@perf.ection.se/ Password: cd ok, cwd=/ lftp stef@perf.ection.se:/> ls drwxr-xr-x 2 1000 20 512 Dec 11 21:40 bin drwxr-xr-x 8 1000 20 512 Jul 19 21:50 devel drwx------ 2 1000 20 512 Aug 17 12:46 mail -rw------- 1 1000 20 875195 Jan 08 21:40 mbox drwxr-xr-x 13 1000 20 512 Jan 03 21:40 mydocs -rw-r--r-- 1 1000 20 3403 Jan 05 20:53 notes drwxr-xr-x 9 1000 20 512 Sep 11 10:32 proj drwxr-xr-x 4 1000 20 512 Jul 19 22:14 rc drwxr-xr-x 3 1000 20 512 Nov 23 09:10 src drwxr-xr-x 2 1000 20 512 Jan 08 22:56 tmp lftp stef@perf.ection.se:/>
That should be it. Enjoy your FTP service. Please let me know if you encounter any errors in this document.