From 1bcec68d2a0eb255f7702d3181d2978f5acd8706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lesimple?= Date: Thu, 2 Nov 2023 15:26:43 +0000 Subject: [PATCH] enh: scp and sftp with mfa support --- bin/plugin/open/scp | 217 ++++++++++++++++++++++++++++++++------ bin/plugin/open/scp.json | 4 +- bin/plugin/open/sftp | 189 ++++++++++++++++++++++++++------- bin/plugin/open/sftp.json | 4 +- bin/shell/osh.pl | 49 +++++---- 5 files changed, 373 insertions(+), 90 deletions(-) diff --git a/bin/plugin/open/scp b/bin/plugin/open/scp index 1c9bb12..0c606e8 100755 --- a/bin/plugin/open/scp +++ b/bin/plugin/open/scp @@ -43,33 +43,185 @@ sub help { # same thing for -- $bastionCommand =~ s/ --/ /; - my $script = <<'EOF'; -#! /bin/sh -[ "$BASTION_SCP_DEBUG" ] && echo "scpwrapper: args: $*" >&2 -while ! [ "$1" = "--" ] ; do - if [ "$1" = "-l" ] ; then - remoteuser="--user $2" + my $script = <<'END_OF_SCRIPT'; +#! /usr/bin/env bash +set -u +# +SELF="%SELF%" +BASTION_CMD="%BASTION_CMD%" +VERSION="%VERSION%" +# + +: "${BASTION_SCP_DEBUG:=}" +[ "$BASTION_SCP_DEBUG" = 1 ] && echo "scpwrapper: args: $*" >&2 + +BASTION_SSH_EXTRA_ARGS="" +BASTION_SCP_EXTRA_ARGS="" + +LOCAL_PATH="" + +REMOTE_HOST="" +REMOTE_PORT=22 +REMOTE_USER="$SELF" +REMOTE_PATH="" + +usage() { + cat >&2 <&2 + exit 1 + fi + BASTION_SSH_EXTRA_ARGS="$BASTION_SSH_EXTRA_ARGS -i $2" + BASTION_SCP_EXTRA_ARGS="$BASTION_SCP_EXTRA_ARGS -i $2" + shift 2;; + "-o") + if [ -z "${2:-}" ]; then + echo "scpwrapper: missing argument after '-o'" >&2 + exit 1 + fi + BASTION_SSH_EXTRA_ARGS="$BASTION_SSH_EXTRA_ARGS -o $2" + BASTION_SCP_EXTRA_ARGS="$BASTION_SCP_EXTRA_ARGS -o $2" + shift 2;; + "-P") + if [ -z "${2:-}" ]; then + echo "scpwrapper: missing argument after '-P'" >&2 + exit 1 + fi + REMOTE_PORT="$2" + shift 2;; + "-r") + BASTION_SCP_EXTRA_ARGS="$BASTION_SCP_EXTRA_ARGS -r" + shift;; + "-*") + echo "scpwrapper: unsupported option '$1'" >&2 + exit 1;; + *) break;; + esac +done + +# here, we should have SRC in $1, and DST in $2 +[ $# -ne 2 ] && usage + +src="${1:-}" +dst="${2:-}" +way="" + +if [[ $src =~ : ]]; then + # it's a download + if [[ $src =~ ^(([^@:]*)@)?([^@:]+):(.*)?$ ]]; then + REMOTE_USER="${BASH_REMATCH[2]:-$SELF}" + REMOTE_HOST="${BASH_REMATCH[3]}" + REMOTE_PATH="${BASH_REMATCH[4]}" + else + echo "scpwrapper: couldn't parse source '$src'" >&2 + exit 1 + fi + LOCAL_PATH="$dst" + way=download + [ "$BASTION_SCP_DEBUG" = 1 ] && echo "scpwrapper: will download '$REMOTE_PATH' from $REMOTE_USER@$REMOTE_HOST:$REMOTE_PORT to '$LOCAL_PATH'" >&2 +elif [[ $dst =~ : ]]; then + # it's an upload + if [[ $dst =~ ^(([^@:]*)@)?([^@:]+):(.*)?$ ]]; then + REMOTE_USER="${BASH_REMATCH[2]:-$SELF}" + REMOTE_HOST="${BASH_REMATCH[3]}" + REMOTE_PATH="${BASH_REMATCH[4]}" + else + echo "scpwrapper: couldn't parse destination '$dst'" >&2 + exit 1 + fi + LOCAL_PATH="$src" + way=upload + [ "$BASTION_SCP_DEBUG" = 1 ] && echo "scpwrapper: will upload '$LOCAL_PATH' to $REMOTE_USER@$REMOTE_HOST:$REMOTE_PORT as '$REMOTE_PATH'" >&2 +else + echo "scpwrapper: no remote host found in your command '$src $dst'" >&2 + exit 1 +fi + +t=$(mktemp) +# shellcheck disable=SC2064 +trap "rm -f $t" EXIT + +# shellcheck disable=SC2086 +[ "$BASTION_SCP_DEBUG" = 1 ] && set -x +$BASTION_CMD -t $BASTION_SSH_EXTRA_ARGS -- --osh scp --host "$REMOTE_HOST" --port "$REMOTE_PORT" --user "$REMOTE_USER" --generate-mfa-token | tee "$t" +[ "$BASTION_SCP_DEBUG" = 1 ] && set +x +token=$(grep -Eo '^MFA_TOKEN=[a-zA-Z0-9,]+' "$t" | tail -n 1 | cut -d= -f2) + +if [ -z "$token" ]; then + echo "scpwrapper: Couldn't get an MFA token, aborting." >&2 + exit 1 +fi + +# detect whether we need '-O' for this scp or not +t2=$(mktemp) +if scp -O "$t2" "$t" >/dev/null 2>&1; then + BASTION_SCP_EXTRA_ARGS="$BASTION_SCP_EXTRA_ARGS -O" +fi +rm -f "$t2" + +# now craft the wrapper to be used by scp through -S +cat >"$t" <<'EOF' +#! /usr/bin/env bash + +REMOTE_USER=${USER:-} +REMOTE_PORT=22 +sshcmdline="" + +[ "$BASTION_SCP_DEBUG" = 1 ] && echo "scphelper: args: $*" >&2 +while ! [ "${1:-}" = "--" ] ; do + # handle several ways of specifying remote user + if [ "${1:-}" = "-l" ] ; then + REMOTE_USER="${2:-}" shift 2 - elif [ "$1" = "-p" ] ; then - remoteport="--port $2" + elif [ "${1:-}" = "-p" ] ; then + REMOTE_PORT="${2:-}" shift 2 - elif [ "$1" = "-s" ]; then + 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 + echo "scphelper: 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'` -[ "$BASTION_SCP_DEBUG" ] && echo "scpwrapper: exec #BASTIONCMD# -T $sshcmdline $BASTION_SCP_EXTRA_ARGS -- $remoteuser $remoteport --host $host --osh scp --scp-cmd \"$scpcmd\"" >&2 -exec #BASTIONCMD# -T $sshcmdline $BASTION_SCP_EXTRA_ARGS -- $remoteuser $remoteport --host $host --osh scp --scp-cmd "$scpcmd" -EOF - $script =~ s{#BASTIONCMD#}{$bastionCommand}g; +[ "$BASTION_SCP_DEBUG" = 1 ] && echo "scphelper: remaining args: $*" >&2 + +# sane default +: "${REMOTE_USER:-$USER}" +[ -z "$REMOTE_USER" ] && REMOTE_USER="$(whoami)" + +REMOTE_HOST="$2" +scpcmd=$(echo "$3" | sed -e 's/#/##/g;s/ /#/g') + +# and go +[ "$BASTION_SCP_DEBUG" = 1 ] && set -x +EOF +echo "exec $BASTION_CMD -T \$sshcmdline $BASTION_SSH_EXTRA_ARGS -- --user \"\$REMOTE_USER\" --port \"\$REMOTE_PORT\" --host \"\$REMOTE_HOST\" --osh scp --scp-cmd \"\$scpcmd\" --mfa-token $token" >> "$t" +chmod +x "$t" + +# don't use exec below, because we need the trap to be executed on exit +export BASTION_SCP_DEBUG +[ "$BASTION_SCP_DEBUG" = 1 ] && set -x +case "$way" in + upload) scp $BASTION_SCP_EXTRA_ARGS -S "$t" -P "$REMOTE_PORT" "$LOCAL_PATH" "$REMOTE_USER"@"$REMOTE_HOST":"$REMOTE_PATH";; + download) scp $BASTION_SCP_EXTRA_ARGS -S "$t" -P "$REMOTE_PORT" "$REMOTE_USER"@"$REMOTE_HOST":"$REMOTE_PATH" "$LOCAL_PATH";; +esac +END_OF_SCRIPT + + $script =~ s{%BASTION_CMD%}{$bastionCommand}g; + $script =~ s{%SELF%}{$self}g; + $script =~ s{%VERSION%}{$OVH::Bastion::VERSION}g; my $compressed = ''; gzip \$script => \$compressed; my $base64 = encode_base64($compressed); @@ -79,27 +231,29 @@ Description: Transfers files to/from a host through the bastion Usage: - To use scp through the bastion, you need a helper script to use with -your scp client. It'll be specific to your account, don't share it with -others! To download your customized script, copy/paste this command: + To use scp through the bastion, you need a wrapper script to use instead of +calling your scp client directly. It'll be specific to your account and this bastion, +don't share it with others! To download your customized script, copy/paste this command: EOF - print "\necho \"$base64\"|base64 -d|gunzip -c > ~/scp_$bastionName && chmod +x ~/scp_$bastionName\n\n"; + print "\necho \"$base64\"|base64 -d|gunzip -c > ~/scp-via-$bastionName && chmod +x ~/scp-via-$bastionName\n\n"; osh_info <<"EOF"; -To use scp through this bastion, add `-S ~/scp_$bastionName` to your regular scp command. -For example, to upload a file: -\$ scp -S ~/scp_$bastionName localfile login\@server:/dest/folder/ +To use scp through this bastion, use this script instead of your regular scp command. +For example, to upload a file to a remote server with ssh listening on port 222 there: +\$ ~/scp-via-$bastionName -i ~/.ssh/mysshkey -P 222 localfile login\@server:/dest/folder/ Or to recursively download a folder contents: -\$ scp -S ~/scp_$bastionName -r login\@server:/src/folder/ /tmp/ +\$ ~/scp-via-$bastionName -i ~/.ssh/mysshkey -r login\@server:/src/folder/ /tmp/ The following environment variables modify the behavior of the script: - `BASTION_SCP_DEBUG`: if set to 1, debug info is printed on the console - `BASTION_SCP_EXTRA_ARGS`: if set, the contents of this variable is added - to the resulting ssh command called by the script + to the resulting scp command called by the script +- `BASTION_SSH_EXTRA_ARGS`: if set, the contents of this variable is added + to the resulting ssh commands used in the script For example: -\$ BASTION_SCP_DEBUG=1 BASTION_SCP_EXTRA_ARGS="-v" scp -S ~/scp_$bastionName file1 login\@srv:/tmp +\$ BASTION_SCP_DEBUG=1 BASTION_SSH_EXTRA_ARGS="-v" ~/scp-via-$bastionName file1 login\@srv:/tmp Please note that you need to be granted for uploading or downloading files with scp to/from the remote host, in addition to having the right to SSH to it. @@ -230,11 +384,12 @@ $fnret = OVH::Bastion::execute(cmd => \@cmd, expects_stdin => 1, is_binary => 1) if ($fnret->err ne 'OK') { osh_exit 'ERR_TRANSFER_FAILED', "Error launching transfer: $fnret"; } -print STDERR ">>> Done, " - . $fnret->value->{'bytesnb'}{'stdin'} - . " bytes uploaded, " - . $fnret->value->{'bytesnb'}{'stdout'} - . " bytes downloaded.\n"; +print STDERR sprintf( + ">>> Done, %d bytes uploaded, %d bytes downloaded\n", + $fnret->value->{'bytesnb'}{'stdin'} + 0, + $fnret->value->{'bytesnb'}{'stdout'} + 0 +); + if ($fnret->value->{'sysret'} != 0) { print STDERR ">>> On bastion side, scp exited with return code " . $fnret->value->{'sysret'} . ".\n"; } diff --git a/bin/plugin/open/scp.json b/bin/plugin/open/scp.json index 457cada..fbb179e 100644 --- a/bin/plugin/open/scp.json +++ b/bin/plugin/open/scp.json @@ -1,4 +1,6 @@ { "execution_mode": "binary", - "stealth_stdout": true + "stealth_stdout": true, + "jit_mfa": "token", + "jit_mfa_allow_no_host": true } diff --git a/bin/plugin/open/sftp b/bin/plugin/open/sftp index d9d096d..2640ff6 100755 --- a/bin/plugin/open/sftp +++ b/bin/plugin/open/sftp @@ -42,36 +42,137 @@ sub help { # same thing for -- $bastionCommand =~ s/ --/ /; - my $script = <<'EOF'; + my $script = <<'END_OF_SCRIPT'; +#! /usr/bin/env bash +set -u +shopt -s nocasematch +# +SELF="%SELF%" +BASTION_CMD="%BASTION_CMD%" +VERSION="%VERSION%" +# + +: "${BASTION_SFTP_DEBUG:=}" +[ "$BASTION_SFTP_DEBUG" = 1 ] && echo "sftpwrapper: args: $*" >&2 + +BASTION_SSH_EXTRA_ARGS="" +BASTION_SFTP_EXTRA_ARGS="" + +usage() { + cat >&2 <&2 + exit 1 + fi + BASTION_SSH_EXTRA_ARGS="$BASTION_SSH_EXTRA_ARGS $1 $2" + BASTION_SFTP_EXTRA_ARGS="$BASTION_SFTP_EXTRA_ARGS $1 $2" + shift 2;; + '-b'|'-l') + if [ -z "${2:-}" ]; then + echo "sftpwrapper: missing argument after '$1'" >&2 + exit 1 + fi + BASTION_SFTP_EXTRA_ARGS="$BASTION_SFTP_EXTRA_ARGS $1 $2" + shift 2;; + "-P") + if [ -z "${2:-}" ]; then + echo "sftpwrapper: missing argument after '$1'" >&2 + exit 1 + fi + REMOTE_PORT="$2" + shift 2;; + "-*") + echo "sftpwrapper: unsupported option '$1'" >&2 + exit 1;; + *) break;; + esac +done + +# here, we should have only destination in $1 +[ $# -ne 1 ] && usage + +dst="${1:-}" +if [[ $dst =~ ^(sftp://)?(([^@:/]+)@)?([^@:/]+)(:([0-9]+))?(/(.+))?$ ]]; then + REMOTE_USER="${BASH_REMATCH[3]:-$SELF}" + REMOTE_HOST="${BASH_REMATCH[4]}" + REMOTE_PORT="${BASH_REMATCH[6]:-22}" + REMOTE_PATH="${BASH_REMATCH[7]}" + [ "$BASTION_SFTP_DEBUG" = 1 ] && echo "sftpwrapper: parsed user=$REMOTE_USER host=$REMOTE_HOST port=$REMOTE_PORT path=$REMOTE_PATH" >&2 +else + echo "sftpwrapper: couldn't parse destination '$dst'" >&2 + exit 1 +fi + +if [ -z "${REMOTE_HOST:-}" ]; then + echo "sftpwrapper: no remote host found in your command '$dst'" >&2 + exit 1 +fi + +t=$(mktemp) +# shellcheck disable=SC2064 +trap "rm -f $t" EXIT + +# shellcheck disable=SC2086 +[ "$BASTION_SFTP_DEBUG" = 1 ] && set -x +$BASTION_CMD -t $BASTION_SSH_EXTRA_ARGS -- --osh sftp --host "$REMOTE_HOST" --port "$REMOTE_PORT" --user "$REMOTE_USER" --generate-mfa-token | tee "$t" +[ "$BASTION_SFTP_DEBUG" = 1 ] && set +x +token=$(grep -Eo '^MFA_TOKEN=[a-zA-Z0-9,]+' "$t" | tail -n 1 | cut -d= -f2) + +if [ -z "$token" ]; then + echo "sftpwrapper: Couldn't get an MFA token, aborting." >&2 + exit 1 +fi + +# now craft the wrapper to be used by sftp through -S +cat >"$t" <<'EOF' #! /usr/bin/env bash shopt -s nocasematch -[ "$BASTION_SFTP_DEBUG" ] && echo "sftpwrapper: args: $*" >&2 -while ! [ "$1" = "--" ] ; do - # user - if [ "$1" = "-l" ] ; then - remoteuser="--user $2" +REMOTE_USER=${USER:-} +REMOTE_PORT=22 +sshcmdline="" + +[ "$BASTION_SFTP_DEBUG" = 1 ] && echo "sftphelper: args: $*" >&2 +while ! [ "${1:-}" = "--" ] ; do + # handle several ways of specifying remote user + if [ "${1:-}" = "-l" ] ; then + REMOTE_USER="${2:-}" shift 2 - elif [[ $1 =~ ^-oUser[=\ ]([^\ ]+)$ ]] ; then - remoteuser="--user ${BASH_REMATCH[1]}" + elif [[ ${1:-} =~ ^-oUser[=\ ]([^\ ]+)$ ]] ; then + REMOTE_USER="${BASH_REMATCH[1]}" shift - elif [ "$1" = "-o" ] && [[ $2 =~ ^user=([0-9]+)$ ]] ; then - remoteuser="--user ${BASH_REMATCH[1]}" + elif [ "${1:-}" = "-o" ] && [[ ${2:-} =~ ^user=([0-9]+)$ ]] ; then + REMOTE_USER="${BASH_REMATCH[1]}" shift 2 - # port - elif [ "$1" = "-p" ] ; then - remoteport="--port $2" + # handle several ways of specifying remote port + elif [ "${1:-}" = "-p" ] ; then + REMOTE_PORT="${2:-22}" shift 2 - elif [[ $1 =~ ^-oPort[=\ ]([0-9]+)$ ]] ; then - remoteport="--port ${BASH_REMATCH[1]}" + elif [[ ${1:-} =~ ^-oPort[=\ ]([0-9]+)$ ]] ; then + REMOTE_PORT="${BASH_REMATCH[1]}" shift - elif [ "$1" = "-o" ] && [[ $2 =~ ^port=([0-9]+)$ ]] ; then - remoteport="--port ${BASH_REMATCH[1]}" + elif [ "${1:-}" = "-o" ] && [[ $2 =~ ^port=([0-9]+)$ ]] ; then + REMOTE_PORT="${BASH_REMATCH[1]}" shift 2 # other '-oFoo Bar' - elif [[ $1 =~ ^-o([^\ ]+)\ (.+)$ ]] ; then + elif [[ ${1:-} =~ ^-o([^\ ]+)\ (.+)$ ]] ; then sshcmdline="$sshcmdline -o${BASH_REMATCH[1]}=${BASH_REMATCH[2]}" shift @@ -86,26 +187,41 @@ while ! [ "$1" = "--" ] ; do fi done +[ "$BASTION_SFTP_DEBUG" = 1 ] && echo "sftphelper: remaining args: $*" >&2 + +# sane default +: "${REMOTE_USER:-$USER}" +[ -z "$REMOTE_USER" ] && REMOTE_USER="$(whoami)" + # after '--', remaining args are always host then 'sftp' -host="$2" +REMOTE_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#*@} +# if host is in the form REMOTE_USER@REMOTE_HOST, split it +if [[ $REMOTE_HOST =~ @ ]]; then + REMOTE_USER="${REMOTE_HOST%@*}" + REMOTE_HOST="${REMOTE_HOST#*@}" fi # and go -[ "$BASTION_SFTP_DEBUG" ] && echo "sftpwrapper: exec #BASTIONCMD# -T $sshcmdline $BASTION_SFTP_EXTRA_ARGS -- $remoteuser $remoteport --host $host --osh sftp" >&2 -exec #BASTIONCMD# -T $sshcmdline $BASTION_SFTP_EXTRA_ARGS -- $remoteuser $remoteport --host $host --osh sftp +[ "$BASTION_SFTP_DEBUG" = 1 ] && set -x EOF +echo "exec $BASTION_CMD -T \$sshcmdline $BASTION_SSH_EXTRA_ARGS -- --user \"\$REMOTE_USER\" --port \"\$REMOTE_PORT\" --host \"\$REMOTE_HOST\" --osh sftp --mfa-token $token" >> "$t" +chmod +x "$t" - $script =~ s{#BASTIONCMD#}{$bastionCommand}g; +# don't use exec below, because we need the trap to be executed on exit +export BASTION_SFTP_DEBUG +[ "$BASTION_SFTP_DEBUG" = 1 ] && set -x +sftp $BASTION_SFTP_EXTRA_ARGS -S "$t" sftp://"$REMOTE_USER"@"$REMOTE_HOST":"$REMOTE_PORT""$REMOTE_PATH" +END_OF_SCRIPT + + $script =~ s{%BASTION_CMD%}{$bastionCommand}g; + $script =~ s{%SELF%}{$self}g; + $script =~ s{%VERSION%}{$OVH::Bastion::VERSION}g; my $compressed = ''; gzip \$script => \$compressed; my $base64 = encode_base64($compressed); @@ -115,25 +231,27 @@ Description: Transfers files to/from a host through the bastion Usage: - To use sftp through the bastion, you need a helper script to use with -your sftp client. It'll be specific to your account, don't share it with others! -To download your customized script, copy/paste this command: + To use sftp through the bastion, you need a wrapper script to use instead of +calling your sftp client directly. It'll be specific to your account and this bastion, +don't share it with others! To download your customized script, copy/paste this command: EOF - - print "\necho \"$base64\"|base64 -d|\\\ngunzip -c > ~/sftp_$bastionName && chmod +x ~/sftp_$bastionName\n\n"; + print "\necho \"$base64\"|base64 -d|gunzip -c > ~/sftp-via-$bastionName \\\n" + . "&& chmod +x ~/sftp-via-$bastionName\n\n"; osh_info <<"EOF"; -To use sftp through this bastion, add `-S ~/sftp_$bastionName` to your regular sftp command. +To use sftp through this bastion, use this script instead of your regular sftp command. For example: -\$ sftp -S ~/sftp_$bastionName login\@server +\$ ~/sftp-via-$bastionName login\@server:port The following environment variables modify the behavior of the script: - `BASTION_SFTP_DEBUG`: if set to 1, debug info is printed on the console - `BASTION_SFTP_EXTRA_ARGS`: if set, the contents of this variable is added - to the resulting ssh command called by the script + to the resulting sftp command called by the script +- `BASTION_SSH_EXTRA_ARGS`: if set, the contents of this variable is added + to the resulting ssh commands used in the script For example: -\$ BASTION_SFTP_DEBUG=1 BASTION_SFTP_EXTRA_ARGS="-v" sftp -S ~/sftp_$bastionName login\@server +\$ BASTION_SFTP_DEBUG=1 BASTION_SSH_EXTRA_ARGS="-v" ~/sftp-via-$bastionName login\@server Please note that you need to be granted to be allowed to use sftp to the remote host, in addition to having the right to SSH to it. @@ -223,7 +341,6 @@ push @cmd, '-s'; my $atleastonekey = 0; foreach my $keyfile (keys %keys) { - # only use the key if it has been seen in both allow_deny() calls, this is to avoid # a security bypass where a user would have group access to a server, but not to the # !sftp special user, and we would add himself this access through selfAddPrivateAccess. diff --git a/bin/plugin/open/sftp.json b/bin/plugin/open/sftp.json index ffe09c9..ee55756 100644 --- a/bin/plugin/open/sftp.json +++ b/bin/plugin/open/sftp.json @@ -1,3 +1,5 @@ { - "stealth_stdout": true + "stealth_stdout": true, + "jit_mfa": "token", + "jit_mfa_allow_no_host": true } diff --git a/bin/shell/osh.pl b/bin/shell/osh.pl index f5d799e..80977f2 100755 --- a/bin/shell/osh.pl +++ b/bin/shell/osh.pl @@ -987,9 +987,8 @@ if ($osh_command) { # run MFA for this plugin if needed $fnret = do_jit_mfa( - actionType => 'plugin', - mfaType => $MFArequiredForPlugin, - ingressRealm => \%ingressRealm, + actionType => 'plugin', + mfaType => $MFArequiredForPlugin, ); if (!$fnret) { # shouldn't happen because do_jit_mfa() exits by itself on error, but we never know... @@ -1511,9 +1510,8 @@ if (!$logret) { # if we have JIT MFA, do it now if ($JITMFARequired) { $fnret = do_jit_mfa( - actionType => 'host', - mfaType => $JITMFARequired, - ingressRealm => \%ingressRealm, + actionType => 'host', + mfaType => $JITMFARequired, ); if (!$fnret) { # shouldn't happen because do_jit_mfa() exits by itself, but we never know... @@ -1646,12 +1644,11 @@ sub get_details_from_access_array { } sub do_jit_mfa { - my %params = @_; - my $ingressRealm = $params{'ingressRealm'}; - my $mfaType = $params{'mfaType'}; # password|totp|any - my $actionType = $params{'actionType'}; # host|plugin + my %params = @_; + my $mfaType = $params{'mfaType'}; # password|totp|any|none + my $actionType = $params{'actionType'}; # host|plugin - if (!$ingressRealm || !$mfaType || !$actionType) { + if (!$mfaType || !$actionType) { return R('ERR_MISSING_PARAMETER', msg => "Missing mandatory parameters to do_jit_mfa"); } @@ -1671,7 +1668,7 @@ sub do_jit_mfa { if ($hasMfaTOTPBypass) { $skipMFA = 1; } - elsif ($ingressRealm->{'mfa'}{'totp'} && $ingressRealm->{'mfa'}{'validated'}) { + elsif ($ingressRealm{'mfa'}{'totp'} && $ingressRealm{'mfa'}{'validated'}) { $realmMFA = 1; } else { @@ -1685,7 +1682,7 @@ sub do_jit_mfa { if ($hasMfaPasswordBypass) { $skipMFA = 1; } - elsif ($ingressRealm->{'mfa'}{'password'} && $ingressRealm->{'mfa'}{'validated'}) { + elsif ($ingressRealm{'mfa'}{'password'} && $ingressRealm{'mfa'}{'validated'}) { $realmMFA = 1; } else { @@ -1699,7 +1696,7 @@ sub do_jit_mfa { if ($hasMfaPasswordBypass || $hasMfaTOTPBypass) { $skipMFA = 1; } - elsif ($ingressRealm->{'mfa'}{'validated'}) { + elsif ($ingressRealm{'mfa'}{'validated'}) { $realmMFA = 1; } else { @@ -1742,7 +1739,8 @@ sub do_jit_mfa { sub do_plugin_jit_mfa { my %params = @_; - my $pluginJitMfa = $params{'pluginJitMfa'}; + my $pluginJitMfa = $params{'pluginJitMfa'}; ### XXX NOT USED + my $localfnret; if (!$host) { @@ -1790,21 +1788,21 @@ sub do_plugin_jit_mfa { my @accessListForPlugin = @{$localfnret->value || []}; # and check whether we need JIT MFA - my $mfaRequired; + my $mfaType; $localfnret = get_details_from_access_array( accessList => \@accessListForPlugin, quiet => ($quiet || $mfaToken), useKey => $useKey ); if ($localfnret && $localfnret->value->{'mfaRequired'}) { - $mfaRequired = $localfnret->value->{'mfaRequired'}; + $mfaType = $localfnret->value->{'mfaRequired'}; } elsif (!$localfnret) { main_exit(OVH::Bastion::EXIT_ACCESS_DENIED, "access_denied", $localfnret->msg); } # not required? we're done - if (!$mfaRequired) { + if (!$mfaType) { if ($generateMfaToken) { # return a dummy token so that our caller is happy, then exit print "MFA_TOKEN=notrequired\n"; @@ -1867,9 +1865,18 @@ sub do_plugin_jit_mfa { elsif ($generateMfaToken) { print "MFA token generation requested, entering MFA phase...\n"; - $localfnret = OVH::Bastion::do_pamtester(self => $self, sysself => $sysself); - $localfnret or main_exit(OVH::Bastion::EXIT_MFA_FAILED, 'mfa_failed', $localfnret->msg); - + # do MFA + $localfnret = do_jit_mfa( + actionType => 'plugin', + mfaType => $mfaType, + ); + if (!$localfnret) { + # shouldn't happen because do_jit_mfa() exits by itself on error, but we never know... + main_exit(OVH::Bastion::EXIT_MFA_FAILED, 'mfa_failed', "Couldn't complete MFA"); + } + elsif ($localfnret->value && ref $localfnret->value eq 'HASH' && $localfnret->value->{'mfaInfo'}) { + $bastion_details{'mfa'} = $localfnret->value->{'mfaInfo'}; + } # if we're still here, MFA has been validated, generate a token, save and return it require Digest::SHA; my $now = time();