From da53454b4ba0491f8c4d3c0ca4ecf2225c7d121a Mon Sep 17 00:00:00 2001 From: Adrian Dvergsdal Date: Sun, 8 Oct 2017 18:36:51 +0200 Subject: [PATCH 1/3] Validation of args and allow command override Using regex to validate arguments. Remove readme and usage output from image (expecting people to read documentation online). Some cleanups. --- Dockerfile | 2 +- README.md | 2 +- entrypoint | 114 +++++++++++++++++++++++++++++++---------------------- tests/run | 3 +- 4 files changed, 70 insertions(+), 51 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8d56ac7..e23cb29 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,7 @@ FROM debian:stretch MAINTAINER Adrian Dvergsdal [atmoz.net] +# Steps done in one RUN layer: # - Install packages # - OpenSSH needs /var/run/sshd to run # - Remove generic host keys, entrypoint generates unique keys @@ -12,7 +13,6 @@ RUN apt-get update && \ COPY sshd_config /etc/ssh/sshd_config COPY entrypoint / -COPY README.md / EXPOSE 22 diff --git a/README.md b/README.md index 48e2f38..41bb226 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ This is an automated build linked with the [debian](https://hub.docker.com/_/deb # Usage -- Required: define users as command arguments, STDIN or mounted in `/etc/sftp/users.conf` +- Required: define users in command arguments or in file mounted as `/etc/sftp/users.conf` (syntax: `user:pass[:e][:uid[:gid[:dir1[,dir2]...]]]...`). - Set UID/GID manually for your users if you want them to make changes to your mounted volumes with permissions matching your host filesystem. diff --git a/entrypoint b/entrypoint index 0a09caa..7288e47 100755 --- a/entrypoint +++ b/entrypoint @@ -1,45 +1,57 @@ #!/bin/bash set -e -export DEBIAN_FRONTEND=noninteractive +# Paths userConfPath="/etc/sftp/users.conf" userConfPathLegacy="/etc/sftp-users.conf" userConfFinalPath="/var/run/sftp/users.conf" -function printHelp() { - echo "Add users as command arguments, STDIN or mounted in $userConfPath" - echo "Syntax: user:pass[:e][:uid[:gid[:dir1[,dir2]...]]] ..." - echo "Use --readme for more information and examples." +# Extended regular expression (ERE) for arguments +reUser='[a-z_][a-z0-9._-]{0,31}' +rePass='[^:]{0,255}' +reUid='[[:digit:]]*' +reGid='[[:digit:]]*' +reDir='[^:]*' +reArgs="^($reUser)(:$rePass)(:e)?(:$reUid)?(:$reGid)?(:$reDir)?$" +reArgsMaybe="^[^:[:space:]]+:.*$" # Smallest indication of attempt to use argument + +function log() { + echo "[entrypoint] $@" } -function printReadme() { - cat /README.md - echo "TIP: Read this in HTML format here: https://github.com/atmoz/sftp" +function validateArg() { + name="$1" + val="$2" + re="$3" + + if [[ "$val" =~ ^$re$ ]]; then + return 0 + else + log "ERROR: Invalid $name \"$val\", do not match required regex pattern: $re" + return 1 + fi } function createUser() { - IFS=':' read -a param <<< $@ - user="${param[0]}" - pass="${param[1]}" + log "Parsing user data: \"$@\"" - if [ "${param[2]}" == "e" ]; then + IFS=':' read -a args <<< $@ + index=0 + + user="${args[0]}"; validateArg "username" "$user" "$reUser" || return 1 + pass="${args[1]}"; validateArg "password" "$pass" "$rePass" || return 1 + + if [ "${args[2]}" == "e" ]; then chpasswdOptions="-e" - uid="${param[3]}" - gid="${param[4]}" - dir="${param[5]}" - else - uid="${param[2]}" - gid="${param[3]}" - dir="${param[4]}" + index=1 fi - if [ -z "$user" ]; then - echo "FATAL: You must at least provide a username." - exit 1 - fi + uid="${args[$[$index+2]]}"; validateArg "UID" "$uid" "$reUid" || return 1 + gid="${args[$[$index+3]]}"; validateArg "GID" "$gid" "$reGid" || return 1 + dir="${args[$[$index+4]]}"; validateArg "dirs" "$dir" "$reDir" || return 1 if $(cat /etc/passwd | cut -d: -f1 | grep -q "^$user:"); then - echo "WARNING: User \"$user\" already exists. Skipping." + log "WARNING: User \"$user\" already exists. Skipping." return 0 fi @@ -91,14 +103,11 @@ function createUser() { fi } -if [[ $1 =~ ^--help$|^-h$ ]]; then - printHelp - exit 0 -fi - -if [ "$1" == "--readme" ]; then - printReadme - exit 0 +# Allow running other programs, e.g. bash +if [[ -z "$1" || "$1" =~ $reArgsMaybe ]]; then + startSshd=true +else + startSshd=false fi # Backward compatibility with legacy config path @@ -116,29 +125,32 @@ if [ ! -f "$userConfFinalPath" ]; then cat "$userConfPath" | grep -v -e '^$' > "$userConfFinalPath" fi - # Append users from arguments to final config - for user in "$@"; do - echo "$user" >> "$userConfFinalPath" - done - # Append users from STDIN to final config + # DEPRECATED on 2017-10-08, DO NOT USE + # TODO: Remove code after 6-12 months if [ ! -t 0 ]; then while IFS= read -r user || [[ -n "$user" ]]; do echo "$user" >> "$userConfFinalPath" done fi - # Check that we have users in config - if [ "$(cat "$userConfFinalPath" | wc -l)" == 0 ]; then - echo "FATAL: No users provided!" - printHelp - exit 3 + if $startSshd; then + # Append users from arguments to final config + for user in "$@"; do + echo "$user" >> "$userConfFinalPath" + done fi - # Import users from final conf file - while IFS= read -r user || [[ -n "$user" ]]; do - createUser "$user" - done < "$userConfFinalPath" + # Check that we have users in config + if [[ -f "$userConfFinalPath" && "$(cat "$userConfFinalPath" | wc -l)" > 0 ]]; then + # Import users from final conf file + while IFS= read -r user || [[ -n "$user" ]]; do + createUser "$user" + done < "$userConfFinalPath" + elif $startSshd; then + log "FATAL: No users provided!" + exit 3 + fi # Generate unique ssh keys for this container, if needed if [ ! -f /etc/ssh/ssh_host_ed25519_key ]; then @@ -153,11 +165,17 @@ fi if [ -d /etc/sftp.d ]; then for f in /etc/sftp.d/*; do if [ -x "$f" ]; then - echo "Running $f ..." + log "Running $f ..." $f fi done unset f fi -exec /usr/sbin/sshd -D -e +if $startSshd; then + log "Executing sshd" + exec /usr/sbin/sshd -D -e +else + log "Executing $@" + exec "$@" +fi diff --git a/tests/run b/tests/run index f02540f..95ed8bb 100755 --- a/tests/run +++ b/tests/run @@ -169,6 +169,7 @@ function testDir() { assertReturn $? 0 } +# Smallest user config possible function testMinimalContainerStart() { $skipAllTests && skip && return 0 @@ -177,7 +178,7 @@ function testMinimalContainerStart() { $sudo docker run \ --name "$tmpContainerName" \ -d "$sftpImageName" \ - minimal \ + m: \ > "$redirect" waitForServer $tmpContainerName From ce4a480b3ec6d7ccf01647c114adbfed52674499 Mon Sep 17 00:00:00 2001 From: Adrian Dvergsdal Date: Sun, 8 Oct 2017 18:44:15 +0200 Subject: [PATCH 2/3] Only change permission on newly created dirs --- entrypoint | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/entrypoint b/entrypoint index 7288e47..c1cd24e 100755 --- a/entrypoint +++ b/entrypoint @@ -91,14 +91,18 @@ function createUser() { chmod 600 /home/$user/.ssh/authorized_keys fi - # Make sure dirs exists and has correct permissions + # Make sure dirs exists if [ -n "$dir" ]; then - IFS=',' read -a dirParam <<< $dir - for dirPath in ${dirParam[@]}; do - dirPath=/home/$user/$dirPath - echo "Creating and/or setting permissions on $dirPath" - mkdir -p $dirPath - chown -R $uid:users $dirPath + IFS=',' read -a dirArgs <<< $dir + for dirPath in ${dirArgs[@]}; do + dirPath="/home/$user/$dirPath" + if [ ! -d "$dirPath" ]; then + log "Creating directory: $dirPath" + mkdir -p $dirPath + chown -R $uid:users $dirPath + else + log "Directory already exists: $dirPath" + fi done fi } From f315792bd7e3733b9694f9ad2d7167f2c19e03ef Mon Sep 17 00:00:00 2001 From: Adrian Dvergsdal Date: Sun, 8 Oct 2017 19:08:53 +0200 Subject: [PATCH 3/3] Allow comments in users.conf --- entrypoint | 3 ++- tests/run | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/entrypoint b/entrypoint index c1cd24e..d3a528f 100755 --- a/entrypoint +++ b/entrypoint @@ -14,6 +14,7 @@ reGid='[[:digit:]]*' reDir='[^:]*' reArgs="^($reUser)(:$rePass)(:e)?(:$reUid)?(:$reGid)?(:$reDir)?$" reArgsMaybe="^[^:[:space:]]+:.*$" # Smallest indication of attempt to use argument +reArgSkip='^([[:blank:]]*#.*|[[:blank:]]*)$' # comment or empty line function log() { echo "[entrypoint] $@" @@ -126,7 +127,7 @@ if [ ! -f "$userConfFinalPath" ]; then # Append mounted config to final config if [ -f "$userConfPath" ]; then - cat "$userConfPath" | grep -v -e '^$' > "$userConfFinalPath" + cat "$userConfPath" | grep -v -E "$reArgSkip" > "$userConfFinalPath" fi # Append users from STDIN to final config diff --git a/tests/run b/tests/run index 95ed8bb..0453473 100755 --- a/tests/run +++ b/tests/run @@ -44,6 +44,10 @@ function beforeTest() { mkdir "$tmpDir" echo "test::$(id -u):$(id -g):dir1,dir2" >> "$tmpDir/users" + echo "" >> "$tmpDir/users" # empty line + echo "# comments are allowed" >> "$tmpDir/users" + echo " " >> "$tmpDir/users" # only whitespace + echo " # with whitespace in front" >> "$tmpDir/users" echo "user.with.dot::$(id -u):$(id -g)" >> "$tmpDir/users" $sudo docker run \ -v "$tmpDir/users:/etc/sftp/users.conf:ro" \