diff --git a/irods/test/scripts/files_for_test012/pam_clear_token.c b/irods/test/scripts/files_for_test012/pam_clear_token.c new file mode 100644 index 000000000..8590287ce --- /dev/null +++ b/irods/test/scripts/files_for_test012/pam_clear_token.c @@ -0,0 +1,36 @@ +/* +To build, you need the PAM development library. Once installed, +run the following: + + gcc -fPIC -fno-stack-protector -o pam_clear_token.o -c main.c + gcc -shared -o pam_clear_token.so pam_clear_token.o +*/ + +#include +#include +#include + +#include + +PAM_EXTERN int pam_sm_authenticate(pam_handle_t* pamh, int flags, int argc, const char** argv) +{ + (void) flags; + (void) argc; + (void) argv; + + // Clear the current auth token. + pam_set_item(pamh, PAM_AUTHTOK, NULL); + pam_set_item(pamh, PAM_OLDAUTHTOK, NULL); + + return PAM_SUCCESS; +} + +PAM_EXTERN int pam_sm_setcred(pam_handle_t* pamh, int flags, int argc, const char** argv) +{ + (void) pamh; + (void) flags; + (void) argc; + (void) argv; + + return PAM_SUCCESS; +} diff --git a/irods/test/scripts/files_for_test012/pam_interactive b/irods/test/scripts/files_for_test012/pam_interactive new file mode 100644 index 000000000..40a3a8b6e --- /dev/null +++ b/irods/test/scripts/files_for_test012/pam_interactive @@ -0,0 +1,17 @@ +# This file is for testing PAM authentication with iRODS +# using the pam_interactive authentication scheme. + +# Prompt for the first password, from /etc/shadow. +auth required pam_unix.so + +# This is a custom PAM module that clears the success token. Without +# this, the "auth" lines which follow are skipped. +auth required /t012/pam_clear_token.so + +# Prompt for the second password, from the user database file created +# earlier. The use of "crypt=crypt" is required for this to work. It +# tells the module that the passwords are encrypted. +auth required pam_userdb.so db=/t012/pam_userdb crypt=crypt + +# Do the normal user account stuff. +account required pam_unix.so diff --git a/irods/test/scripts/test011_pam_interactive.bats b/irods/test/scripts/test011_pam_interactive.bats new file mode 100755 index 000000000..b5b269b11 --- /dev/null +++ b/irods/test/scripts/test011_pam_interactive.bats @@ -0,0 +1,46 @@ +#!/usr/bin/env bats + +# The tests in this BATS module must be run as a (passwordless) sudo-enabled user. +# It is also required that the python irodsclient be installed under irods' ~/.local environment. + +. $BATS_TEST_DIRNAME/test_support_functions + +setup() { + [ -f /tmp/test011_flag ] || { + rm -fr ~/.irods + /prc/test_harness/utility/iinit.py host localhost \ + port 1247 \ + zone tempZone \ + user rods \ + password rods \ + + ## Because iRODS 5+ negotiates for SSL automatically: + CLIENT_JSON=~/.irods/irods_environment.json + jq '.irods_client_server_policy="CS_NEG_REFUSE"' >$CLIENT_JSON.$$ <$CLIENT_JSON && \ + mv $CLIENT_JSON.$$ $CLIENT_JSON + + sudo apt install irods-auth-plugin-pam-interactive-{client,server} + + setup_pam_login_for_user "rods" alice + + # Tests require only the irods_environment.json + rm -f ~/.irods/.irodsA + + ## Switch over to scheme to be tested. + jq '.irods_authentication_scheme="pam_interactive"' >$CLIENT_JSON.$$ <$CLIENT_JSON && \ + mv $CLIENT_JSON.$$ $CLIENT_JSON + } + touch /tmp/test011_flag +} + +original_test_suite() +{ + local USER="alice" + local PASSWORD="rods" + sudo chpasswd <<<"$USER:$PASSWORD" + python -m unittest irods.test.pam_interactive_test_must_run_manually +} + +@test "original_pam_interactive_tests" { + original_test_suite +} diff --git a/irods/test/scripts/test012_pam_interactive_multistep.bats b/irods/test/scripts/test012_pam_interactive_multistep.bats new file mode 100755 index 000000000..6b2757a9e --- /dev/null +++ b/irods/test/scripts/test012_pam_interactive_multistep.bats @@ -0,0 +1,88 @@ +#!/usr/bin/env bats + +# The tests in this BATS module must be run as a (passwordless) sudo-enabled user. +# It is also required that the python irodsclient be installed under irods' ~/.local environment. + +SKIP_IINIT_FOR_PASSWORD=yes + +. $BATS_TEST_DIRNAME/test_support_functions + +export TESTUSER=alice +export FIRST_PASSWORD=somerods +export SECOND_PASSWORD=otherrods + +setup() { + [ -f /tmp/test012_flag ] || { + rm -fr ~/.irods + /prc/test_harness/utility/iinit.py host localhost \ + port 1247 \ + zone tempZone \ + user rods \ + password rods \ + + sudo apt update + sudo apt install -y db-util libpam0g-dev + + ## Because iRODS 5+ negotiates for SSL automatically: + CLIENT_JSON=~/.irods/irods_environment.json + jq '.irods_client_server_policy="CS_NEG_REFUSE"' >$CLIENT_JSON.$$ <$CLIENT_JSON && \ + mv $CLIENT_JSON.$$ $CLIENT_JSON + + sudo apt install irods-auth-plugin-pam-interactive-{client,server} + + setup_pam_login_for_user "${FIRST_PASSWORD}" $TESTUSER + sudo cp $BATS_TEST_DIRNAME/files_for_test012/pam_interactive /etc/pam.d/irods + sudo mkdir /t012 && sudo gcc -o /t012/pam_clear_token.so -fno-stack-protector -shared -fPIC $BATS_TEST_DIRNAME/files_for_test012/pam_clear_token.c + + db_file=/t012/pam_userdb.db + sudo db_load -T -t hash "$db_file" <<<"${TESTUSER}"$'\n'"${SECOND_PASSWORD}" + sudo chown root:root "$db_file" + sudo chmod 600 "$db_file" + + # Tests require only the irods_environment.json + rm -f ~/.irods/.irodsA + + ## Switch over to scheme to be tested. + jq '.irods_authentication_scheme="pam_interactive"' >$CLIENT_JSON.$$ <$CLIENT_JSON && \ + mv $CLIENT_JSON.$$ $CLIENT_JSON + } + touch /tmp/test012_flag +} + +@test "pam_interactive_test_multistep_with_correct_passwords" { +: +python -c" +import getpass +import irods +import os +from unittest.mock import patch + +def getpass_new_callable(answers=()): + class iterate_answers: + def __init__(self,answers = answers): + self.answers = answers + self.count = 0 + def __call__(self,*_): + count = self.count + self.count += 1 + ans = self.answers[count] + print ('*** giving answer:', ans) + return ans + return lambda : iterate_answers() + +home = None + +with patch( + 'getpass.getpass', + new_callable=getpass_new_callable(answers=[os.environ['FIRST_PASSWORD'],os.environ['SECOND_PASSWORD']]) +): + sess = irods.helpers.make_session(test_server_version=False) + home = sess.collections.get(f'/{sess.zone}/home/{sess.username}') + +if home is None: + exit(2) +username = os.environ['TESTUSER'] +if not home.path.endswith(f'/{username}'): + exit(1) +" +} diff --git a/test_harness/single_node/docker_container_driver.sh b/test_harness/single_node/docker_container_driver.sh index 996f54778..57a5f324d 100755 --- a/test_harness/single_node/docker_container_driver.sh +++ b/test_harness/single_node/docker_container_driver.sh @@ -12,6 +12,7 @@ # Options: +ENV_VARS=() IRODS_CONTROL_PATH="" KILL_TEST_CONTAINER=1 RUN_AS_USER="" @@ -26,6 +27,9 @@ usage() { $0 [options] /external/path/to/script" echo 'Options : + -E SETTING set in docker environment: argument is a single env setting like: "ENVVAR=" where + can may contain whitespace. "-E" may occur multiple times. + -e SETTINGS set in docker environment: argument is a list of env settings (format: VAR=VAL) separated by spaces -V (extra verbosity). Prints out useful stuff including VERSION information -u USERNAME run test in container as this USERNAME -i invoke container with -i and -t @@ -60,6 +64,13 @@ while [[ $1 = -* ]]; do elif [ "$1" = -r ]; then REMOVE_OPTION="$2" shift 2 + elif [ "$1" = -E ]; then + ENV_VARS+=(-e "$2") + shift 2 + elif [ "$1" = -e ]; then + ENV_VAR_ARRAY=($2) + ENV_VARS="${ENV_VAR_ARRAY[*]/#/-e }" + shift 2 elif [ "$1" = -w ]; then EXPLICIT_WORKDIR="$2" shift 2 @@ -126,7 +137,7 @@ INNER_MOUNT=/prc # Start the container. echo image="[$image]" -CONTAINER=$($DOCKER run -d -v "$reporoot:$INNER_MOUNT:ro" $INTERACTIVE_OPTION $REMOVE_OPTION \ +CONTAINER=$($DOCKER run "${ENV_VARS[@]}" -d -v "$reporoot:$INNER_MOUNT:ro" $INTERACTIVE_OPTION $REMOVE_OPTION \ -e "IRODS_CONTROL_PATH=$IRODS_CONTROL_PATH" $image) # Wait for iRODS and database to start up. diff --git a/test_harness/single_node/test_script_parameters b/test_harness/single_node/test_script_parameters index 4b94d58e6..f7b977296 100644 --- a/test_harness/single_node/test_script_parameters +++ b/test_harness/single_node/test_script_parameters @@ -21,6 +21,8 @@ declare -A wrappers=( [test008_prc_write_irodsA_utility_in_native_mode.bats]=../login_auth_test.sh [test009_test_special_characters_in_pam_passwords_auth_framework.bats]=../login_auth_test.sh [test010_issue_362_rogue_chars_in_pam_password.bats]=../login_auth_test.sh + [test011_pam_interactive.bats]=../login_auth_test.sh + [test012_pam_interactive_multistep.bats]=../login_auth_test.sh ) # keys for Image and User refer to the basename after resolution to a wrapper if one is used