diff --git a/tests/run b/tests/run index f28e2f8..de0123f 100755 --- a/tests/run +++ b/tests/run @@ -1,58 +1,78 @@ #!/bin/bash # See: https://github.com/kward/shunit2 +# Usage: tests/run [OPTION]... IMAGE_NAME +# Options: --verbose --no-cleanup +# Example: tests/run atmoz/sftp:alpine + +# Booleans +OPTION_TRUE=0 +OPTION_FALSE=1 + +# Options +OPTION_VERBOSE="${OPTION_VERBOSE:-"$OPTION_FALSE"}" +OPTION_CLEANUP="${OPTION_CLEANUP:-"$OPTION_TRUE"}" + +# ARGUMENTS: +while [ "$#" -ge 1 ]; do + case "$1" in + --verbose) OPTION_VERBOSE="$OPTION_TRUE"; shift ;; + --no-cleanup) OPTION_VERBOSE="$OPTION_FALSE"; shift ;; + --) break ;; # rest of arguments goes to shunit2 + --*) echo "Unknown argument: $1"; exit 2 ;; + *) imageName="$1"; shift ;; + esac +done -argImage=$1 -argOutput=${2:-"quiet"} -argCleanup=${3:-"cleanup"} testDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -imageName="$argImage" tmpDir="$(mktemp -d /tmp/atmoz_sftp_XXXX)" sshKeyPri="$tmpDir/rsa" sshKeyPub="$tmpDir/rsa.pub" sshHostEd25519Key="$tmpDir/ssh_host_ed25519_key" sshHostKeyMountArg="--volume=$sshHostEd25519Key:/etc/ssh/ssh_host_ed25519_key" sshKnownHosts="$tmpDir/known_hosts" +runArgs=("-P" "--network=private" "$sshHostKeyMountArg") -if [ $UID != 0 ] && ! groups | grep -qw docker; then - echo "Run with sudo/root or add user $USER to group 'docker'" - exit 1 +# podman or docker +if [ -z "$CONTAINER_ENGINE" ]; then + if type podman &>/dev/null; then + CONTAINER_ENGINE="podman" + else + CONTAINER_ENGINE="docker" + fi fi if [ ! -f "$testDir/shunit2/shunit2" ]; then - echo "Could not find shunit2 in $testDir/shunit2." + echo "Could not find shunit2 in $testDir/shunit2" echo "Run 'git submodule update --init'" exit 2 fi -if [ -z "$argImage" ]; then - echo "Missing image name" +if [ -z "$imageName" ]; then + echo "Missing name of image you want to run tests against" exit 3 fi -if [ "$argOutput" == "quiet" ]; then - redirect="/dev/null" -else +if [ "$OPTION_VERBOSE" == "$OPTION_TRUE" ]; then redirect="/dev/stdout" +else + redirect="/dev/null" fi -# clear argument list (or shunit2 will try to use them) -set -- - ############################################################################## -## Helper functions +## Setup: shunit2 functions ############################################################################## function oneTimeSetUp() { # Generate temporary ssh keys for testing if [ ! -f "$sshKeyPri" ]; then - ssh-keygen -t rsa -f "$sshKeyPri" -N '' > "$redirect" 2>&1 + ssh-keygen -t rsa -f "$sshKeyPri" -N '' < /dev/null > "$redirect" 2>&1 fi # Private key can not be read by others (sshd will complain) chmod go-rw "$sshKeyPri" # Generate host key - ssh-keygen -t ed25519 -f "$sshHostEd25519Key" < /dev/null + ssh-keygen -t ed25519 -f "$sshHostEd25519Key" -N '' < /dev/null } function setUp() { @@ -67,34 +87,41 @@ function setUp() { function tearDown() { retireContainer "$containerName" - if [ "$argCleanup" == "cleanup" ] && [ -d "$containerTmpDir" ]; then + if [ "$OPTION_CLEANUP" == "$OPTION_TRUE" ] && [ -d "$containerTmpDir" ]; then rm -rf "$containerTmpDir" || true # Can fail on GitHub Actions fi } +############################################################################## +## Helper functions +############################################################################## + +function container() { + "$CONTAINER_ENGINE" "$@" +} function retireContainer() { - if [ "$(docker ps -qaf name="$1")" ]; then - if [ "$argOutput" != "quiet" ]; then - echo "Docker log for $1:" - docker logs "$1" + if [ "$(container ps -qaf name="$1")" ]; then + if [ "$OPTION_VERBOSE" == "$OPTION_TRUE" ]; then + echo "container log for $1:" + container logs "$1" fi - if [ "$argCleanup" == "cleanup" ]; then - docker rm -fv "$1" > "$redirect" 2>&1 + if [ "$OPTION_CLEANUP" == "$OPTION_TRUE" ]; then + container rm -fv "$1" > "$redirect" 2>&1 fi fi } -function getSftpIp() { - docker inspect -f "{{.NetworkSettings.IPAddress}}" "$1" +function getSftpPort() { + container container port "$1" | cut -d: -f2 | head -1 } function runSftpCommands() { - ip="$(getSftpIp "$1")" + port="$(getSftpPort "$1")" user="$2" shift 2 - echo "$ip $(cat "$sshHostEd25519Key.pub")" >> "$sshKnownHosts" + echo "127.0.0.1 $(cat "$sshHostEd25519Key.pub")" >> "$sshKnownHosts" commands="" for cmd in "$@"; do @@ -104,7 +131,7 @@ function runSftpCommands() { echo "$commands" | sftp \ -i "$sshKeyPri" \ -oUserKnownHostsFile="$sshKnownHosts" \ - -b - "$user@$ip" \ + -b - -P "$port" "$user@127.0.0.1" \ > "$redirect" 2>&1 status=$? @@ -113,17 +140,19 @@ function runSftpCommands() { } function waitForServer() { - containerName="$1" - echo -n "Waiting for $containerName to open port 22 ..." + local containerName="$1" + local port + + echo -n "Waiting for $containerName to open a port ..." for _ in {1..30}; do - sleep 1 - ip="$(getSftpIp "$containerName")" echo -n "." - if [ -n "$ip" ] && nc -z "$ip" 22; then - echo " OPEN" + port="$(getSftpPort "$containerName")" + if [ -n "$port" ] && ssh-keyscan -4 -T 1 -p "$port" "127.0.0.1" &>/dev/null; then + echo " PORT $port OPEN" return 0; fi + sleep 0.5 done echo " TIMEOUT" @@ -135,7 +164,7 @@ function waitForServer() { ############################################################################## function testSmallestUserConfig() { - docker run --name "$containerName" "$sshHostKeyMountArg" \ + container run --name "$containerName" "${runArgs[@]}" \ --entrypoint="/bin/sh" \ "$imageName" \ -c "create-sftp-user u: && id u" \ @@ -144,7 +173,7 @@ function testSmallestUserConfig() { } function testCreateUserWithDot() { - docker run --name "$containerName" "$sshHostKeyMountArg" \ + container run --name "$containerName" "${runArgs[@]}" \ --entrypoint="/bin/sh" \ "$imageName" \ -c "create-sftp-user user.with.dot: && id user.with.dot" \ @@ -153,7 +182,7 @@ function testCreateUserWithDot() { } function testUserCustomUidAndGid() { - id="$(docker run --name "$containerName" "$sshHostKeyMountArg" \ + id="$(container run --name "$containerName" "${runArgs[@]}" \ --entrypoint="/bin/sh" \ "$imageName" \ -c "create-sftp-user u::1234:4321: > /dev/null && id u" )" @@ -169,14 +198,14 @@ function testUserCustomUidAndGid() { } function testCommandPassthrough() { - docker run --name "$containerName" "$sshHostKeyMountArg" \ + container run --name "$containerName" "${runArgs[@]}" \ "$imageName" test 1 -eq 1 \ > "$redirect" 2>&1 assertTrue "command passthrough" $? } function testUsersConf() { - docker run --name "$containerName" "$sshHostKeyMountArg" -d \ + container run --name "$containerName" "${runArgs[@]}" -d \ -v "$testDir/files/users.conf:/etc/sftp/users.conf:ro" \ "$imageName" \ > "$redirect" 2>&1 @@ -184,21 +213,21 @@ function testUsersConf() { waitForServer "$containerName" assertTrue "waitForServer" $? - docker exec "$containerName" id user-from-conf > /dev/null + container exec "$containerName" id user-from-conf > /dev/null assertTrue "user-from-conf" $? - docker exec "$containerName" id test > /dev/null + container exec "$containerName" id test > /dev/null assertTrue "test" $? - docker exec "$containerName" id user.with.dot > /dev/null + container exec "$containerName" id user.with.dot > /dev/null assertTrue "user.with.dot" $? - docker exec "$containerName" test -d /home/test/dir1 -a -d /home/test/dir2 + container exec "$containerName" test -d /home/test/dir1 -a -d /home/test/dir2 assertTrue "dirs exists" $? } function testLegacyUsersConf() { - docker run --name "$containerName" "$sshHostKeyMountArg" -d \ + container run --name "$containerName" "${runArgs[@]}" -d \ -v "$testDir/files/users.conf:/etc/sftp-users.conf:ro" \ "$imageName" \ > "$redirect" 2>&1 @@ -206,12 +235,12 @@ function testLegacyUsersConf() { waitForServer "$containerName" assertTrue "waitForServer" $? - docker exec "$containerName" id user-from-conf > /dev/null + container exec "$containerName" id user-from-conf > /dev/null assertTrue "user-from-conf" $? } function testCreateUsersUsingEnv() { - docker run --name "$containerName" "$sshHostKeyMountArg" -d \ + container run --name "$containerName" "${runArgs[@]}" -d \ -e "SFTP_USERS=user-from-env: user-from-env-2:" \ "$imageName" \ > "$redirect" 2>&1 @@ -219,15 +248,15 @@ function testCreateUsersUsingEnv() { waitForServer "$containerName" assertTrue "waitForServer" $? - docker exec "$containerName" id user-from-env > /dev/null + container exec "$containerName" id user-from-env > /dev/null assertTrue "user-from-env" $? - docker exec "$containerName" id user-from-env-2 > /dev/null + container exec "$containerName" id user-from-env-2 > /dev/null assertTrue "user-from-env-2" $? } function testCreateUsersUsingCombo() { - docker run --name "$containerName" "$sshHostKeyMountArg" -d \ + container run --name "$containerName" "${runArgs[@]}" -d \ -v "$testDir/files/users.conf:/etc/sftp-users.conf:ro" \ -e "SFTP_USERS=user-from-env:" \ "$imageName" \ @@ -237,18 +266,18 @@ function testCreateUsersUsingCombo() { waitForServer "$containerName" assertTrue "waitForServer" $? - docker exec "$containerName" id user-from-conf > /dev/null + container exec "$containerName" id user-from-conf > /dev/null assertTrue "user-from-conf" $? - docker exec "$containerName" id user-from-env > /dev/null + container exec "$containerName" id user-from-env > /dev/null assertTrue "user-from-env" $? - docker exec "$containerName" id user-from-cmd > /dev/null + container exec "$containerName" id user-from-cmd > /dev/null assertTrue "user-from-cmd" $? } function testWriteAccessToAutocreatedDirs() { - docker run --name "$containerName" "$sshHostKeyMountArg" -d \ + container run --name "$containerName" "${runArgs[@]}" -d \ -v "$sshKeyPub":/home/test/.ssh/keys/id_rsa.pub:ro \ "$imageName" "test::::testdir,dir with spaces" \ > "$redirect" 2>&1 @@ -264,18 +293,19 @@ function testWriteAccessToAutocreatedDirs() { "exit" assertTrue "runSftpCommands" $? - docker exec "$containerName" test -d /home/test/testdir/test + container exec "$containerName" test -d /home/test/testdir/test assertTrue "testdir write access" $? - docker exec "$containerName" test -d "/home/test/dir with spaces/test" + container exec "$containerName" test -d "/home/test/dir with spaces/test" assertTrue "dir with spaces write access" $? } function testWriteAccessToLimitedChroot() { # Modified sshd_config with chrooted home subdir tmpConfig="$(mktemp)" - sed 's/^ChrootDirectory.*/ChrootDirectory %h\/sftp/' \ - < "$testDir/../files/sshd_config" > "$tmpConfig" + container run --rm --entrypoint=bash "$imageName" -c "cat /etc/ssh/sshd_config" | + sed 's/^ChrootDirectory.*/ChrootDirectory %h\/sftp/' \ + > "$tmpConfig" # Set correct permissions on chroot tmpScript="$(mktemp)" @@ -286,7 +316,7 @@ chmod 755 /home/*/sftp EOF chmod +x "$tmpScript" - docker run --name "$containerName" "$sshHostKeyMountArg" -d \ + container run --name "$containerName" "${runArgs[@]}" -d \ -v "$sshKeyPub":/home/test/.ssh/keys/id_rsa.pub:ro \ -v "$tmpConfig:/etc/ssh/sshd_config" \ -v "$tmpScript:/etc/sftp.d/limited_home_dir" \ @@ -302,7 +332,7 @@ EOF "exit" assertTrue "runSftpCommands" $? - docker exec "$containerName" test -d /home/test/sftp/upload/test + container exec "$containerName" test -d /home/test/sftp/upload/test assertTrue "limited chroot write access" $? } @@ -314,7 +344,7 @@ function testBindmountDirScript() { > "$containerTmpDir/mount.sh" chmod +x "$containerTmpDir/mount.sh" - docker run --name "$containerName" "$sshHostKeyMountArg" -d \ + container run --name "$containerName" "${runArgs[@]}" -d \ --privileged=true \ -v "$sshKeyPub":/home/custom/.ssh/keys/id_rsa.pub:ro \ -v "$containerTmpDir/custom/bindmount":/custom \ @@ -331,12 +361,12 @@ function testBindmountDirScript() { "exit" assertTrue "runSftpCommands" $? - docker exec "$containerName" test -d /home/custom/bindmount/test + container exec "$containerName" test -d /home/custom/bindmount/test assertTrue "directory exist" $? } function testDuplicateSshKeys() { - docker run --name "$containerName" "$sshHostKeyMountArg" -d \ + container run --name "$containerName" "${runArgs[@]}" -d \ -v "$sshKeyPub":/home/user/.ssh/keys/key1.pub:ro \ -v "$sshKeyPub":/home/user/.ssh/keys/key2.pub:ro \ "$imageName" "user:" \ @@ -345,7 +375,7 @@ function testDuplicateSshKeys() { waitForServer "$containerName" assertTrue "waitForServer" $? - lines="$(docker exec "$containerName" sh -c \ + lines="$(container exec "$containerName" sh -c \ "wc -l < /home/user/.ssh/authorized_keys")" assertEquals "1" "$lines" }