diff --git a/tests/functional/launch_tests_on_instance.sh b/tests/functional/launch_tests_on_instance.sh index 2743e03..d8f675b 100755 --- a/tests/functional/launch_tests_on_instance.sh +++ b/tests/functional/launch_tests_on_instance.sh @@ -416,15 +416,15 @@ run() printf '%b %b*** [%04d/%04d] %b::%b %b(%b)%b\n' "$(prefix)" "$BOLD_CYAN" "$testno" "$testcount" "$name" "$case" "$NOC$DARKGRAY" "$*" "$NOC" - # special case for scp: we need to wait a bit before terminating the test - sleepafter=0 - [[ $case =~ ^scp_ ]] && sleepafter=2 + # if not set, set to zero, see sleepafter() + : "${sleepafter:=0}" # put an invalid value in this file, should be overwritten. we also use it as a lock file. echo -1 > $outdir/$basename.retval # run the test flock "$outdir/$basename.retval" $screen "$outdir/$basename.log" -D -m -fn -ln bash -c "$* ; echo \$? > $outdir/$basename.retval ; sleep $sleepafter" flock "$outdir/$basename.retval" true + unset sleepafter # look for generally bad strings in the output _bad='at /usr/share/perl|compilation error|compilation aborted|BEGIN failed|gonna crash|/opt/bastion/|sudo:|ontinuing anyway|MAKETESTFAIL' @@ -509,6 +509,11 @@ plgfail() retvalshouldbe 100 } +sleepafter() +{ + sleepafter=$(($1 * opt_slowness_factor)) +} + ignorecodewarn() { code_warn_exclude="$*" @@ -691,11 +696,17 @@ runtests() fi echo "### RUNNING MODULE $modulename" - # as this is a loop, we do the check in a reversed way, see any included module for more info: dump_vars_and_funcs > "$tmp_a" + module_ret=0 + # as this is a loop, we do the shellcheck in a reversed way, see any included module for more info: # shellcheck disable=SC1090 - source "$module" || true + if source "$module"; then + module_ret=0 + else + module_ret=$? + fi dump_vars_and_funcs > "$tmp_b" + success module_postrun test "$module_ret" = 0 # put the backed up configuration back after each module, just in case the module modified it modulename=main diff --git a/tests/functional/tests.d/340-selfaccesses.sh b/tests/functional/tests.d/340-selfaccesses.sh index 0695c99..f0581f6 100644 --- a/tests/functional/tests.d/340-selfaccesses.sh +++ b/tests/functional/tests.d/340-selfaccesses.sh @@ -198,15 +198,99 @@ testsuite_selfaccesses() # scp & sftp + # these are the old pre-3.14.15 helper versions, we want to check for descendant compatibility + cat >/tmp/scphelper <<'EOF' +#! /bin/sh +while ! [ "$1" = "--" ] ; do + if [ "$1" = "-l" ] ; then + remoteuser="--user $2" + shift 2 + elif [ "$1" = "-p" ] ; then + remoteport="--port $2" + shift 2 + elif [ "$1" = "-s" ]; then + # caller is a newer scp that tries to use the sftp subsystem + # instead of plain old scp, warn because it won't work + echo "scpwrapper: WARNING: your scp version is recent, you need to add '-O' to your scp command-line, exiting." >&2 + exit 1 + else + sshcmdline="$sshcmdline $1" + shift + fi +done +host="$2" +scpcmd=`echo "$3" | sed -e 's/#/##/g;s/ /#/g'` +EOF + echo "exec ssh -p $remote_port $account0@$remote_ip -T \$sshcmdline -- \$remoteuser \$remoteport --host \$host --osh scp --scp-cmd \"\$scpcmd\"" >> /tmp/scphelper + chmod +x /tmp/scphelper + + cat >/tmp/sftphelper <<'EOF' +#! /usr/bin/env bash +shopt -s nocasematch + +while ! [ "$1" = "--" ] ; do + # user + if [ "$1" = "-l" ] ; then + remoteuser="--user $2" + shift 2 + elif [[ $1 =~ ^-oUser[=\ ]([^\ ]+)$ ]] ; then + remoteuser="--user ${BASH_REMATCH[1]}" + shift + elif [ "$1" = "-o" ] && [[ $2 =~ ^user=([0-9]+)$ ]] ; then + remoteuser="--user ${BASH_REMATCH[1]}" + shift 2 + + # port + elif [ "$1" = "-p" ] ; then + remoteport="--port $2" + shift 2 + elif [[ $1 =~ ^-oPort[=\ ]([0-9]+)$ ]] ; then + remoteport="--port ${BASH_REMATCH[1]}" + shift + elif [ "$1" = "-o" ] && [[ $2 =~ ^port=([0-9]+)$ ]] ; then + remoteport="--port ${BASH_REMATCH[1]}" + shift 2 + + # other '-oFoo Bar' + elif [[ $1 =~ ^-o([^\ ]+)\ (.+)$ ]] ; then + sshcmdline="$sshcmdline -o${BASH_REMATCH[1]}=${BASH_REMATCH[2]}" + shift + + # don't forward -s + elif [ "$1" = "-s" ]; then + shift + + # other stuff passed directly to ssh + else + sshcmdline="$sshcmdline $1" + shift + fi +done + +# after '--', remaining args are always host then 'sftp' +host="$2" +subsystem="$3" +if [ "$subsystem" != sftp ]; then + echo "Unknown subsystem requested '$subsystem', expected 'sftp'" >&2 + exit 1 +fi + +# if host is in the form remoteuser@remotehost, split it +if [[ $host =~ @ ]]; then + remoteuser="--user ${host%@*}" + host=${host#*@} +fi +EOF + echo "exec ssh -p $remote_port $account0@$remote_ip -T \$sshcmdline -- \$remoteuser \$remoteport --host \$host --osh sftp" >> /tmp/sftphelper + chmod +x /tmp/sftphelper + ## get both helpers first for proto in scp sftp; do success $proto $a0 --osh $proto if [ "$COUNTONLY" != 1 ]; then - tmpb64=$(get_json | $jq '.value.script') - base64 -d <<< "$tmpb64" | gunzip -c > "/tmp/${proto}helper" - perl -i -pe 'print "BASTION_SCP_DEBUG=1\nBASTION_SFTP_DEBUG=1\n" if ++$line==2' "/tmp/${proto}helper" - chmod +x "/tmp/${proto}helper" - unset tmpb64 + get_json | $jq '.value.script' | base64 -d | gunzip -c > /tmp/${proto}wrapper + perl -i -pe 'print "BASTION_SCP_DEBUG=1\nBASTION_SFTP_DEBUG=1\n" if ++$line==2' "/tmp/${proto}wrapper" + chmod +x /tmp/${proto}wrapper fi done unset proto @@ -215,41 +299,64 @@ testsuite_selfaccesses() ## detect recent scp local scp_options="" - run scp_checkversion $r0 "scp 2>&1 | grep -q O && echo NEWVERSION || echo OLDVERSION" - if get_stdout | grep -q NEWVERSION; then - echo "scp: will use new version params" - scp_options="-O" - elif get_stdout | grep -q OLDVERSION; then - echo "scp: will use old version params" - scp_options="" - else - contain "unknown scp version" + if [ "$COUNTONLY" != 1 ]; then + if scp -O -S /bin/true a: b 2>/dev/null; then + echo "scp: will use new version params" + scp_options="-O" + else + echo "scp: will use old version params" + fi fi success forscp $a0 --osh selfAddPersonalAccess --host 127.0.0.2 --scpup --port 22 - run scp_downloadfailnoright scp $scp_options -F $mytmpdir/ssh_config -S /tmp/scphelper -i $account0key1file $shellaccount@127.0.0.2:uptest /tmp/downloaded + sleepafter 2 + run scp_downloadfailnoright_old scp $scp_options -F $mytmpdir/ssh_config -S /tmp/scphelper -i $account0key1file $shellaccount@127.0.0.2:uptest /tmp/downloaded + retvalshouldbe 1 + contain "Sorry, but even" + + run scp_downloadfailnoright_new env BASTION_SCP_DEBUG=1 /tmp/scpwrapper -i $account0key1file $shellaccount@127.0.0.2:uptest /tmp/downloaded retvalshouldbe 1 contain "Sorry, but even" success forscp $a0 --osh selfAddPersonalAccess --host 127.0.0.2 --scpdown --port 22 - run scp_downloadfailnofile scp $scp_options -F $mytmpdir/ssh_config -S /tmp/scphelper -i $account0key1file $shellaccount@127.0.0.2:uptest /tmp/downloaded + sleepafter 2 + run scp_downloadfailnofile_old scp $scp_options -F $mytmpdir/ssh_config -S /tmp/scphelper -i $account0key1file $shellaccount@127.0.0.2:uptest /tmp/downloaded retvalshouldbe 1 contain "through the bastion from" contain "Error launching transfer" contain "No such file or directory" nocontain "Permission denied" - run scp_invalidhostname scp $scp_options -F $mytmpdir/ssh_config -S /tmp/scphelper -i $account0key1file $shellaccount@_invalid._invalid:uptest /tmp/downloaded + run scp_downloadfailnofile_new /tmp/scpwrapper -i $account0key1file $shellaccount@127.0.0.2:uptest /tmp/downloaded + retvalshouldbe 1 + contain "through the bastion from" + contain "Error launching transfer" + contain "No such file or directory" + nocontain "Permission denied" + + run scp_invalidhostname_old scp $scp_options -F $mytmpdir/ssh_config -S /tmp/scphelper -i $account0key1file $shellaccount@_invalid._invalid:uptest /tmp/downloaded retvalshouldbe 1 contain REGEX "Sorry, couldn't resolve the host you specified|I was unable to resolve host" - success scp_upload scp $scp_options -F $mytmpdir/ssh_config -S /tmp/scphelper -i $account0key1file /etc/passwd $shellaccount@127.0.0.2:uptest + run scp_invalidhostname_new /tmp/scpwrapper -i $account0key1file $shellaccount@_invalid._invalid:uptest /tmp/downloaded + retvalshouldbe 1 + contain REGEX "Sorry, couldn't resolve the host you specified|I was unable to resolve host" + + success scp_upload_old scp $scp_options -F $mytmpdir/ssh_config -S /tmp/scphelper -i $account0key1file /etc/passwd $shellaccount@127.0.0.2:uptest contain "through the bastion to" contain "Done," - success scp_download scp $scp_options -F $mytmpdir/ssh_config -S /tmp/scphelper -i $account0key1file $shellaccount@127.0.0.2:uptest /tmp/downloaded + success scp_upload_new /tmp/scpwrapper -i $account0key1file /etc/passwd $shellaccount@127.0.0.2:uptest + contain "through the bastion to" + contain "Done," + + success scp_download_old scp $scp_options -F $mytmpdir/ssh_config -S /tmp/scphelper -i $account0key1file $shellaccount@127.0.0.2:uptest /tmp/downloaded + contain "through the bastion from" + contain "Done," + + success scp_download_new /tmp/scpwrapper -i $account0key1file $shellaccount@127.0.0.2:uptest /tmp/downloaded contain "through the bastion from" contain "Done," @@ -258,15 +365,34 @@ testsuite_selfaccesses() # sftp - run sftp_no_access sftp -F $mytmpdir/ssh_config -S /tmp/sftphelper -i $account0key1file $shellaccount@127.0.0.2 + run sftp_no_access_old sftp -F $mytmpdir/ssh_config -S /tmp/sftphelper -i $account0key1file $shellaccount@127.0.0.2 + retvalshouldbe 255 + contain "Sorry, but even" + + run sftp_no_access_new /tmp/sftpwrapper -i $account0key1file $shellaccount@127.0.0.2 retvalshouldbe 255 contain "Sorry, but even" success forsftp $a0 --osh selfAddPersonalAccess --host 127.0.0.2 --sftp --port 22 - success sftp_access sftp -F $mytmpdir/ssh_config -b - -S /tmp/sftphelper -i $account0key1file $shellaccount@127.0.0.2 "<<< exit" - contain "sftp> exit" - contain ">>> Done," + if [ "$COUNTONLY" != 1 ]; then + cat >"/tmp/sftpcommands" <<'EOF' +ls +exit +EOF + fi + + success sftp_access_old sftp -F $mytmpdir/ssh_config -b /tmp/sftpcommands -S /tmp/sftphelper -i $account0key1file $shellaccount@127.0.0.2 + contain 'sftp> ls' + contain 'uptest' + contain 'sftp> exit' + contain '>>> Done,' + + success sftp_access_new /tmp/sftpwrapper -b /tmp/sftpcommands -i $account0key1file $shellaccount@127.0.0.2 + contain 'sftp> ls' + contain 'uptest' + contain 'sftp> exit' + contain '>>> Done,' success forsftpremove $a0 --osh selfDelPersonalAccess --host 127.0.0.2 --sftp --port 22 diff --git a/tests/functional/tests.d/395-mfa-scp-sftp.sh b/tests/functional/tests.d/395-mfa-scp-sftp.sh new file mode 100644 index 0000000..3c3ece8 --- /dev/null +++ b/tests/functional/tests.d/395-mfa-scp-sftp.sh @@ -0,0 +1,236 @@ +# vim: set filetype=sh ts=4 sw=4 sts=4 et: +# shellcheck shell=bash +# shellcheck disable=SC2086,SC2016,SC2046 +# below: convoluted way that forces shellcheck to source our caller +# shellcheck source=tests/functional/launch_tests_on_instance.sh +. "$(dirname "${BASH_SOURCE[0]}")"/dummy + +testsuite_mfa_scp_sftp() +{ + grant groupCreate + + # create group1 + success groupCreate $a0 --osh groupCreate --group $group1 --owner $account0 --algo ed25519 --size 256 + json .error_code OK .command groupCreate + local g1key + g1key="$(get_json | jq '.value.public_key.line')" + + revoke groupCreate + + # push group1 egress key to $shellaccount@localhost + success add_grp1_key_to_shellaccount $r0 "echo '$g1key' \>\> ~$shellaccount/.ssh/authorized_keys" + + # add server to group1 + success groupAddServer $a0 --osh groupAddServer --group $group1 --host 127.0.0.2 --user $shellaccount --port 22 + + # get helpers + local proto + for proto in scp sftp; do + success get_${proto}_helper $a0 --osh $proto + if [ "$COUNTONLY" != 1 ]; then + get_json | $jq '.value.script' | base64 -d | gunzip -c > /tmp/${proto}helper + chmod +x /tmp/${proto}helper + fi + done + + # scp: upload something (denied, not granted) + run scp_upload_denied /tmp/scphelper -i $account0key1file $shellaccount@127.0.0.2:passwd /tmp/ + retvalshouldbe 1 + contain 'MFA_TOKEN=notrequired' + contain 'you still need to be granted specifically for scp' + nocontain '>>> Done' + + # allow scpup + success allow_scpup $a0 --osh groupAddServer --group $group1 --host 127.0.0.2 --scpup --port 22 + + # scp: upload something + success scp_upload /tmp/scphelper -i $account0key1file /etc/passwd $shellaccount@127.0.0.2: + contain 'MFA_TOKEN=notrequired' + contain 'transferring your file through the bastion' + contain '>>> Done' + + # sftp: download something (denied, not granted) + run sftp_download_denied /tmp/sftphelper -i $account0key1file sftp://$shellaccount@127.0.0.2//etc/passwd + retvalshouldbe 255 + contain 'MFA_TOKEN=notrequired' + contain 'you still need to be granted specifically for sftp' + nocontain '>>> Done' + + # allow sftp + success allow_sftp $a0 --osh groupAddServer --group $group1 --host 127.0.0.2 --sftp --port 22 + + # sftp: download something + success sftp_download /tmp/sftphelper -i $account0key1file sftp://$shellaccount@127.0.0.2//etc/passwd + contain 'MFA_TOKEN=notrequired' + contain 'Fetching /etc/passwd' + contain '>>> Done' + + # set --personal-egress-mfa-required on this account + grant accountModify + success personal_egress_mfa $a0 --osh accountModify --account $account0 --personal-egress-mfa-required password + + # add personal access + grant selfAddPersonalAccess + success a0_add_personal_access_ssh $a0 --osh selfAddPersonalAccess --host 127.0.0.2 --user $shellaccount --port 22 --force + success a0_add_personal_access_scpup $a0 --osh selfAddPersonalAccess --host 127.0.0.2 --scpup --port 22 + success a0_add_personal_access_sftp $a0 --osh selfAddPersonalAccess --host 127.0.0.2 --sftp --port 22 + revoke selfAddPersonalAccess + + # scp: upload something after personal mfa, wont work + run scp_upload_personal_mfa_fail /tmp/scphelper -i $account0key1file /etc/passwd $shellaccount@127.0.0.2: + retvalshouldbe 1 + nocontain 'MFA_TOKEN=notrequired' + contain 'MFA token generation requested, entering MFA phase' + contain 'you need to setup the Multi-Factor Authentication for this plugin' + + # sftp: download something after personal mfa, wont work + run sftp_upload_personal_mfa_fail /tmp/sftphelper -i $account0key1file sftp://$shellaccount@127.0.0.2//etc/passwd + retvalshouldbe 1 + nocontain 'MFA_TOKEN=notrequired' + contain 'MFA token generation requested, entering MFA phase' + contain 'you need to setup the Multi-Factor Authentication for this plugin' + + # reset --personal-egress-mfa-required on this account + success personal_egress_nomfa $a0 --osh accountModify --account $account0 --personal-egress-mfa-required none + revoke accountModify + + # del personal access + grant selfDelPersonalAccess + success a0_del_personal_access_ssh $a0 --osh selfDelPersonalAccess --host 127.0.0.2 --user $shellaccount --port 22 + success a0_del_personal_access_scpup $a0 --osh selfDelPersonalAccess --host 127.0.0.2 --scpup --port 22 + success a0_del_personal_access_sftp $a0 --osh selfDelPersonalAccess --host 127.0.0.2 --sftp --port 22 + revoke selfDelPersonalAccess + + # now set MFA required on group + success group_need_mfa $a0 --osh groupModify --group $group1 --mfa-required password + + # scp: upload something after mfa, wont work + run scp_upload_mfa_fail /tmp/scphelper -i $account0key1file /etc/passwd $shellaccount@127.0.0.2: + retvalshouldbe 1 + nocontain 'MFA_TOKEN=notrequired' + contain 'MFA token generation requested, entering MFA phase' + contain 'you need to setup the Multi-Factor Authentication for this plugin' + + # sftp: download something after mfa, wont work + run sftp_upload_mfa_fail /tmp/sftphelper -i $account0key1file sftp://$shellaccount@127.0.0.2//etc/passwd + retvalshouldbe 1 + nocontain 'MFA_TOKEN=notrequired' + contain 'MFA token generation requested, entering MFA phase' + contain 'you need to setup the Multi-Factor Authentication for this plugin' + + # setup MFA on our account, step1 + run a0_setup_pass_step1of2 $a0f --osh selfMFASetupPassword --yes + retvalshouldbe 124 + contain 'enter this:' + local a0_password_tmp + a0_password_tmp=$(get_stdout | grep -Eo 'enter this: [a-zA-Z0-9_-]+' | sed -e 's/enter this: //') + + # setup our password, step2 + local a0_password='ohz8Ciujuboh' + script a0_setup_pass_step2of2 "echo 'set timeout $default_timeout; + spawn $a0 --osh selfMFASetupPassword --yes; + expect \":\" { sleep 0.2; send \"$a0_password_tmp\\n\"; }; + expect \":\" { sleep 0.2; send \"$a0_password\\n\"; }; + expect \":\" { sleep 0.2; send \"$a0_password\\n\"; }; + expect eof; + lassign [wait] pid spawnid value value; + exit \$value' | expect -f -" + retvalshouldbe 0 + unset a0_password_tmp + nocontain 'enter this:' + nocontain 'unchanged' + nocontain 'sorry' + json .command selfMFASetupPassword .error_code OK + + # scp: upload something after mfa, should work + script scp_upload_mfa_ok "echo 'set timeout $default_timeout; + spawn /tmp/scphelper -i $account0key1file /etc/passwd $shellaccount@127.0.0.2: ; + expect \"is required (password)\" { sleep 0.1; }; + expect \":\" { sleep 0.2; send \"$a0_password\\n\"; }; + expect eof; + lassign [wait] pid spawnid value value; + exit \$value' | expect -f -" + nocontain 'MFA_TOKEN=notrequired' + if [ "${capabilities[mfa]}" = 1 ] || [ "${capabilities[mfa-password]}" = 1 ]; then + retvalshouldbe 0 + contain 'MFA_TOKEN=v1,' + else + retvalshouldbe 1 + contain 'this bastion is missing' + fi + + # sftp: upload something after mfa, should work + script sftp_upload_mfa_ok "echo 'set timeout $default_timeout; + spawn /tmp/sftphelper -i $account0key1file sftp://$shellaccount@127.0.0.2//etc/passwd ; + expect \"is required (password)\" { sleep 0.1; }; + expect \":\" { sleep 0.2; send \"$a0_password\\n\"; }; + expect eof; + lassign [wait] pid spawnid value value; + exit \$value' | expect -f -" + nocontain 'MFA_TOKEN=notrequired' + if [ "${capabilities[mfa]}" = 1 ] || [ "${capabilities[mfa-password]}" = 1 ]; then + retvalshouldbe 0 + contain 'MFA_TOKEN=v1,' + else + retvalshouldbe 1 + contain 'this bastion is missing' + fi + + # provide invalid tokens manually + run scp_upload_bad_token_format $a0 --osh scp --host 127.0.0.2 --port 22 --user $shellaccount --mfa-token invalid + retvalshouldbe 125 + json .error_code KO_MFA_FAILED_INVALID_FORMAT + + local invalid_token + invalid_token="v1,$(date +%s -d '1 hour ago'),9f25d680b1bae2ef73abc3c62926ddb9c88f8ea1f4120b1125cc09720c74268b" + run scp_upload_bad_token_expired $a0 --osh scp --host 127.0.0.2 --port 22 --user $shellaccount --mfa-token "$invalid_token" + retvalshouldbe 125 + json .error_code KO_MFA_FAILED_EXPIRED_TOKEN + + invalid_token="v1,$(date +%s -d '1 hour'),9f25d680b1bae2ef73abc3c62926ddb9c88f8ea1f4120b1125cc09720c74268b" + run scp_upload_bad_token_future $a0 --osh scp --host 127.0.0.2 --port 22 --user $shellaccount --mfa-token "$invalid_token" + retvalshouldbe 125 + json .error_code KO_MFA_FAILED_FUTURE_TOKEN + + # remove MFA from account + if [ "${capabilities[mfa]}" = 1 ] || [ "${capabilities[mfa-password]}" = 1 ]; then + script a0_reset_password "echo 'set timeout $default_timeout; + spawn $a0 --osh selfMFAResetPassword; + expect \"additional authentication factor is required (password)\" { sleep 0.1; }; + expect \"word:\" { sleep 0.2; send \"$a0_password\\n\"; }; + expect eof; + lassign [wait] pid spawnid value value; + exit \$value' | expect -f -" + retvalshouldbe 0 + json .error_code OK .command selfMFAResetPassword + else + grant accountMFAResetPassword + success a0_reset_password $a0 --osh accountMFAResetPassword --account $account0 + fi + + # set account as exempt from MFA + grant accountModify + success a0_mfa_bypass $a0 --osh accountModify --account $account0 --mfa-password-required bypass + + # scp: upload something after exempt from mfa + success scp_upload_mfa_exempt_ok /tmp/scphelper -i $account0key1file /etc/passwd $shellaccount@127.0.0.2: + nocontain 'MFA_TOKEN=notrequired' + contain 'skipping as your account is exempt from MFA' + contain 'MFA_TOKEN=v1,' + + # sftp: upload something after mfa, should work + script sftp_upload_mfa_exempt_ok /tmp/sftphelper -i $account0key1file sftp://$shellaccount@127.0.0.2//etc/passwd + nocontain 'MFA_TOKEN=notrequired' + contain 'skipping as your account is exempt from MFA' + contain 'MFA_TOKEN=v1,' + + # reset account setup + success a0_mfa_default $a0 --osh accountModify --account $account0 --mfa-password-required no + revoke accountModify + + # delete group1 + success groupDestroy $a0 --osh groupDestroy --group $group1 --no-confirm +} + +testsuite_mfa_scp_sftp +unset -f testsuite_mfa_scp_sftp