From 07b4a555fff1d4c946c0f2233999f45b5dd3c545 Mon Sep 17 00:00:00 2001 From: Tim Robinson Date: Tue, 1 May 2018 22:01:08 +1000 Subject: [PATCH] Major update to stretch and many improvements Improvements: * Update to debian stretch * Fix Dockerfile style * Add FTP UID/GID Docker build args * Add run-ssl to Makefile for testing SSL config * Fix scripts style * Fix scripts issues reported by shellcheck * Add FTP_CHOWN_ROOT function to chown /srv in the container * Add allow_writeable_chroot=YES to vsftpd_ssl.conf to match vsftpd.conf * Update README.md --- .gitignore | 1 + Dockerfile | 30 +++++++++++++++++++----------- Makefile | 27 +++++++++++++++------------ README.md | 28 +++++++++++++++++++++------- add-virtual-user.sh | 27 ++++++++++++++------------- entry.sh | 45 ++++++++++++++++++++++++++------------------- vsftpd_ssl.conf | 1 + 7 files changed, 97 insertions(+), 62 deletions(-) diff --git a/.gitignore b/.gitignore index e0a126e..191bb34 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.swp srv/ env +vsftpd.pem diff --git a/Dockerfile b/Dockerfile index b42ee7c..d66596f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,24 @@ -FROM debian:jessie +FROM debian:stretch -RUN groupadd -g 48 ftp && \ - useradd --no-create-home --home-dir /srv -s /bin/false --uid 48 --gid 48 -c 'ftp daemon' ftp +ARG FTP_UID=48 +ARG FTP_GID=48 +RUN set -x \ + && groupadd -g ${FTP_GID} ftp \ + && useradd --no-create-home --home-dir /srv -s /bin/false --uid ${FTP_UID} --gid ${FTP_GID} -c 'ftp daemon' ftp \ + ; -RUN apt-get update \ - && apt-get install -y --no-install-recommends vsftpd db5.3-util whois \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* +RUN set -x \ + && apt-get update \ + && apt-get install -y --no-install-recommends vsftpd db5.3-util whois \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ + ; -RUN mkdir -p /var/run/vsftpd/empty /etc/vsftpd/user_conf /var/ftp /srv && \ - touch /var/log/vsftpd.log && \ - rm -rf /srv/ftp +RUN set -x \ + && mkdir -p /var/run/vsftpd/empty /etc/vsftpd/user_conf /var/ftp /srv \ + && touch /var/log/vsftpd.log \ + && rm -rf /srv/ftp \ + ; COPY vsftpd*.conf /etc/ COPY vsftpd_virtual /etc/pam.d/ @@ -21,4 +29,4 @@ VOLUME ["/etc/vsftpd", "/srv"] EXPOSE 21 4559 4560 4561 4562 4563 4564 ENTRYPOINT ["/entry.sh"] -CMD ["vsftpd"] \ No newline at end of file +CMD ["vsftpd"] diff --git a/Makefile b/Makefile index 6754fcd..fefb88c 100644 --- a/Makefile +++ b/Makefile @@ -1,25 +1,28 @@ -docker_tag = panubo/vsftpd - -UNAME_S := $(shell uname -s) -ifeq ($(UNAME_S),Linux) - APP_HOST := localhost -endif -ifeq ($(UNAME_S),Darwin) - APP_HOST := $(shell docker-machine ip default) -endif +NAME := panubo/vsftpd +TAG := latest build: - docker build -t $(docker_tag) . + docker build --build-arg FTP_UID=$(shell id -u) --build-arg FTP_GID=$(shell id -g) -t $(NAME):$(TAG) . bash: - docker run --rm -it $(docker_tag) bash + docker run --rm -it $(NAME):$(TAG) bash env: @echo "FTP_USER=ftp" >> env @echo "FTP_PASSWORD=ftp" >> env +vsftpd.pem: + openssl req -new -newkey rsa:2048 -days 365 -nodes -sha256 -x509 -keyout vsftpd.pem -out vsftpd.pem -subj '/CN=self_signed' + run: env - $(eval ID := $(shell docker run -d --env-file env -v $(shell pwd)/srv:/srv ${docker_tag})) + $(eval ID := $(shell docker run -d --env-file env -v $(shell pwd)/srv:/srv ${NAME}:${TAG})) + $(eval IP := $(shell docker inspect --format '{{ .NetworkSettings.IPAddress }}' ${ID})) + @echo "Running ${ID} @ ftp://${IP}" + @docker attach ${ID} + @docker kill ${ID} + +run-ssl: env vsftpd.pem + $(eval ID := $(shell docker run -d --env-file env -v $(shell pwd)/srv:/srv -v $(PWD)/vsftpd.pem:/etc/ssl/certs/vsftpd.crt -v $(PWD)/vsftpd.pem:/etc/ssl/private/vsftpd.key ${NAME}:${TAG} vsftpd /etc/vsftpd_ssl.conf)) $(eval IP := $(shell docker inspect --format '{{ .NetworkSettings.IPAddress }}' ${ID})) @echo "Running ${ID} @ ftp://${IP}" @docker attach ${ID} diff --git a/README.md b/README.md index 26e862b..9996e48 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,11 @@ There are a few limitations but it will work if you are using host networking `--net host` or have a direct/routed network between the Docker container and the client. -## Virtual User +## Virtual Users -The FTP user has been set to uid 48 and gid 48. +This VSFTPD container uses virtual users. Each user that logs in will have the same system UID and GID. The real users has UID and GID of 48:48 by default, however using build-args this can be changed when building the container. + +For example use `--build-arg FTP_UID=1000 --build-arg FTP_GID=1000` to set the UID and GID to 1000:1000. ## Options @@ -16,20 +18,22 @@ The following environment variables are accepted. - `FTP_USER`: Sets the default FTP user -- `FTP_PASSWORD`: Plain text password, or +- `FTP_PASSWORD`: Plain text password (not recommended), or -- `FTP_PASSWORD_HASH`: Sets the password for the user specified above. This +- `FTP_PASSWORD_HASH`: Sets the password for the user specified by `FTP_USER`. This requires a hashed password such as the ones created with `mkpasswd -m sha-512` which is in the _whois_ debian package. -- `FTP_USER_*`: Adds mutliple users. Value must be in the form of `username:hash`. Should not be used in conjunction with `FTP_USER` and `FTP_PASSWORD(_HASH)`. +- `FTP_USER_*`: Adds multiple users. Value must be in the form of `username:hash`. Should not be used in conjunction with `FTP_USER` and `FTP_PASSWORD(_HASH)`. -- `FTP_USERS_ROOT`: sets `local_root=/srv/$USER` so each user is chrooted to their own directory instead of a shared one. +- `FTP_USERS_ROOT`: if set the vsftpd `local_root` will be set to `/srv/$USER` so each user is chrooted to their own directory instead of a shared one. + +- `FTP_CHOWN_ROOT`: if set `chown` will be run against `/srv` setting the FTP user and group as owner and group of the directory. _Note: chown is run non-recursively ie. will only chown the root`_ ## Usage Example ``` -docker run --rm -it -p 21:21 -p 4559:4559 -p 4560:4560 -p 4561:4561 -p 4562:4562 -p 4563:4563 -p 4564:4564 -e FTP_USER=panubo -e FTP_PASSWORD=panubo docker.io/panubo/vsftpd +docker run --rm -it -p 21:21 -p 4559-4564:4559-4564 -e FTP_USER=ftp -e FTP_PASSWORD=ftp docker.io/panubo/vsftpd:latest ``` ## SSL Usage @@ -48,3 +52,13 @@ docker run --rm -it \ -v `pwd`/server.pem:/etc/ssl/private/vsftpd.key:ro \ docker.io/panubo/vsftpd vsftpd /etc/vsftpd_ssl.conf ``` + +## Security + +Currently `allow_writeable_chroot` is turned ON, however this isn't recommended as a security precaution. We might look at making this configurable in the future. The main consequence of turning this off is that the `local_root` can not be writable by the FTP user. + +See [serverfault: vsftp: whu is allow_writable_chroot=YES a bad idea?](https://serverfault.com/q/743949/259651) + +## Logs + +To get the FTP logs mount `/var/log` outside of the container. For example add `-v /var/log/ftp:/var/log` to your `docker run ...` command. diff --git a/add-virtual-user.sh b/add-virtual-user.sh index a5bd71c..af0c4e8 100755 --- a/add-virtual-user.sh +++ b/add-virtual-user.sh @@ -1,23 +1,24 @@ #!/usr/bin/env bash +# Adds a virtual ftp user to /etc/vsftpd/virtual-users.db set -e -[ "$DEBUG" == 'true' ] && set -x +[[ "${DEBUG}" == "true" ]] && set -x -DB=/etc/vsftpd/virtual-users.db +DB="/etc/vsftpd/virtual-users.db" -if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then - echo "Usage: $0 [-d] " >&2 - echo >&2 - echo "[ -d ] Delete the database first" >&2 - exit 1 +if [[ "${#}" -lt 2 ]] || [[ "${#}" -gt 3 ]]; then + echo "Usage: $0 [-d] " >&2 + echo >&2 + echo "[ -d ] Delete the database first" >&2 + exit 1 fi -if [ "$1" == "-d" ]; then - if [ -f $DB ]; then - rm $DB - fi - shift +if [[ "${1}" == "-d" ]]; then + if [[ -f "${DB}" ]]; then + rm "${DB}" + fi + shift fi -echo -e "$1\n$2" | db5.3_load -T -t hash $DB +printf '%s\n%s\n' "${1}" "${2}" | db5.3_load -T -t hash "${DB}" diff --git a/entry.sh b/entry.sh index 60e6c62..4e192d3 100755 --- a/entry.sh +++ b/entry.sh @@ -1,34 +1,41 @@ #!/usr/bin/env bash +# vsftpd container entrypoint script set -e -[ "$DEBUG" == 'true' ] && set -x +[[ "${DEBUG}" == "true" ]] && set -x # Generate password if hash not set -if [ ! -z "$FTP_PASSWORD" -a -z "$FTP_PASSWORD_HASH" ]; then - FTP_PASSWORD_HASH=$(echo "$FTP_PASSWORD" | mkpasswd -s -m sha-512) +if [[ ! -z "${FTP_PASSWORD}" ]] && [[ -z "${FTP_PASSWORD_HASH}" ]]; then + FTP_PASSWORD_HASH="$(echo "${FTP_PASSWORD}" | mkpasswd -s -m sha-512)" fi -if [ ! -z "$FTP_USER" -a ! -z "$FTP_PASSWORD_HASH" ]; then - /add-virtual-user.sh -d "$FTP_USER" "$FTP_PASSWORD_HASH" +if [[ ! -z "${FTP_USER}" ]] || [[ ! -z "${FTP_PASSWORD_HASH}" ]]; then + /add-virtual-user.sh -d "${FTP_USER}" "${FTP_PASSWORD_HASH}" fi # Support multiple users -while read user; do - IFS=: read name pass <<< "${!user}" - echo "Adding user $name" - /add-virtual-user.sh "$name" "$pass" +while read -r user; do + IFS=: read -r name pass <<< "${!user}" + echo "Adding user ${name}" + /add-virtual-user.sh "${name}" "${pass}" done < <(env | grep "FTP_USER_" | sed 's/^\(FTP_USER_[a-zA-Z0-9]*\)=.*/\1/') # Support user directories -if [ ! -z "$FTP_USERS_ROOT" ]; then - sed -i 's/local_root=.*/local_root=\/srv\/$USER/' /etc/vsftpd*.conf +if [[ ! -z "${FTP_USERS_ROOT}" ]]; then + # shellcheck disable=SC2016 + sed -i 's/local_root=.*/local_root=\/srv\/$USER/' /etc/vsftpd*.conf fi -function vsftpd_stop { +# Manage /srv permissions +if [[ ! -z "${FTP_CHOWN_ROOT}" ]]; then + chown ftp:ftp /srv +fi + +vsftpd_stop() { echo "Received SIGINT or SIGTERM. Shutting down vsftpd" # Get PID - pid=$(cat /var/run/vsftpd/vsftpd.pid) + pid="$(cat /var/run/vsftpd/vsftpd.pid)" # Set TERM kill -SIGTERM "${pid}" # Wait for exit @@ -37,13 +44,13 @@ function vsftpd_stop { echo "Done" } -if [ "$1" == "vsftpd" ]; then +if [[ "${1}" == "vsftpd" ]]; then trap vsftpd_stop SIGINT SIGTERM - echo "Running $@" - $@ & - pid="$!" + echo "Running ${*}" + "${@}" & + pid="${!}" echo "${pid}" > /var/run/vsftpd/vsftpd.pid - wait "${pid}" && exit $? + wait "${pid}" && exit ${?} else - exec "$@" + exec "${@}" fi diff --git a/vsftpd_ssl.conf b/vsftpd_ssl.conf index 4abd188..14578e6 100644 --- a/vsftpd_ssl.conf +++ b/vsftpd_ssl.conf @@ -15,6 +15,7 @@ user_sub_token=$USER #local_root=/srv/$USER local_root=/srv/ userlist_enable=NO +allow_writeable_chroot=YES # Logging log_ftp_protocol=YES