enh: scp and sftp with mfa support

This commit is contained in:
Stéphane Lesimple 2023-11-02 15:26:43 +00:00 committed by Stéphane Lesimple
parent b48463076f
commit 1bcec68d2a
5 changed files with 373 additions and 90 deletions

View file

@ -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
# <custom-section>
SELF="%SELF%"
BASTION_CMD="%BASTION_CMD%"
VERSION="%VERSION%"
# </custom_section>
: "${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 <<EOF
usage: $0 [-i identity_file] [-P port] [-o ssh_option] source target
EOF
exit 1
}
while [ -n "${1:-}" ]; do
case "$1" in
"-i")
if [ -z "${2:-}" ]; then
echo "scpwrapper: missing argument after '-i'" >&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";
}

View file

@ -1,4 +1,6 @@
{
"execution_mode": "binary",
"stealth_stdout": true
"stealth_stdout": true,
"jit_mfa": "token",
"jit_mfa_allow_no_host": true
}

View file

@ -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
# <custom-section>
SELF="%SELF%"
BASTION_CMD="%BASTION_CMD%"
VERSION="%VERSION%"
# </custom_section>
: "${BASTION_SFTP_DEBUG:=}"
[ "$BASTION_SFTP_DEBUG" = 1 ] && echo "sftpwrapper: args: $*" >&2
BASTION_SSH_EXTRA_ARGS=""
BASTION_SFTP_EXTRA_ARGS=""
usage() {
cat >&2 <<EOF
usage: $0 [-i identity_file] [-P port] [-o ssh_option] [-b batchfile] [-F ssh_config] [-l limit] [-afprv] destination
EOF
exit 1
}
while [ -n "${1:-}" ]; do
case "$1" in
'-a'|'-f'|'-p'|'-r')
BASTION_SFTP_EXTRA_ARGS="$BASTION_SFTP_EXTRA_ARGS $1"
shift;;
'-v')
BASTION_SSH_EXTRA_ARGS="$BASTION_SSH_EXTRA_ARGS $1"
BASTION_SFTP_EXTRA_ARGS="$BASTION_SFTP_EXTRA_ARGS $1"
shift;;
'-i'|'-o'|'-F')
if [ -z "${2:-}" ]; then
echo "sftpwrapper: missing argument after '$1'" >&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.

View file

@ -1,3 +1,5 @@
{
"stealth_stdout": true
"stealth_stdout": true,
"jit_mfa": "token",
"jit_mfa_allow_no_host": true
}

View file

@ -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();