Skip to content

Commit d8f9423

Browse files
committed
fix: scp/sftp: correctly bypass JIT MFA if asked to, when old helpers are used
1 parent c2a6faf commit d8f9423

File tree

3 files changed

+280
-255
lines changed

3 files changed

+280
-255
lines changed

bin/shell/osh.pl

+40-22
Original file line numberDiff line numberDiff line change
@@ -1671,17 +1671,20 @@ sub get_details_from_access_array {
16711671
return R('OK', value => {sshKeysArgs => \@sshKeysArgs, mfaRequired => $mfaRequired});
16721672
}
16731673

1674-
sub do_jit_mfa {
1674+
# can exit if prerequisites are not met (i.e. MFA required but not configured on account)
1675+
# return a true OVH::Result if MFA is not needed/can be skipped
1676+
# a false OVH::Result otherwise
1677+
sub may_skip_mfa {
16751678
my %params = @_;
16761679
my $mfaType = $params{'mfaType'}; # password|totp|any|none
16771680
my $actionType = $params{'actionType'}; # host|plugin
16781681

16791682
if (!$mfaType || !$actionType) {
1680-
return R('ERR_MISSING_PARAMETER', msg => "Missing mandatory parameters to do_jit_mfa");
1683+
return R('ERR_MISSING_PARAMETER', msg => "Missing mandatory parameters to may_skip_mfa");
16811684
}
16821685

16831686
if (!grep { $mfaType eq $_ } qw{ totp password any none }) {
1684-
return R('ERR_INVALID_PARAMETER', msg => "Invalid parameter 'mfaType' for do_jit_mfa");
1687+
return R('ERR_INVALID_PARAMETER', msg => "Invalid parameter 'mfaType' for may_skip_mfa");
16851688
}
16861689

16871690
return R('OK_NO_MFA_REQUIRED') if $mfaType eq 'none';
@@ -1738,31 +1741,48 @@ sub do_jit_mfa {
17381741

17391742
if ($skipMFA) {
17401743
osh_print("... skipping as your account is exempt from MFA.");
1744+
return R('OK_ACCOUNT_HAS_MFA_BYPASS');
17411745
}
17421746
elsif ($realmMFA) {
17431747
osh_print("... you already validated MFA on the bastion you're coming from.");
1748+
return R('OK_ACCOUNT_HAS_VALIDATED_MFA_REALM');
17441749
}
17451750
elsif ($ENV{'OSH_PROACTIVE_MFA'}) {
17461751
osh_print("... you already validated MFA proactively.");
1752+
return R('OK_ACCOUNT_HAS_VALIDATED_MFA_PROACTIVELY');
17471753
}
1748-
else {
1749-
$localfnret = OVH::Bastion::do_pamtester(self => $self, sysself => $sysself);
1750-
main_exit(OVH::Bastion::EXIT_MFA_FAILED, 'mfa_failed', $localfnret->msg) if !$localfnret;
1751-
1752-
# craft this so that the remote server, which can be a bastion in case we're chaining,
1753-
# can enforce its own policy. This should be serialized in LC_BASTION_DETAILS on egress side
1754-
my %mfaInfo = (
1755-
validated => \1,
1756-
reason => "mfa_required_for_$actionType",
1757-
type => {
1758-
password => $isMfaPasswordConfigured ? \1 : \0,
1759-
totp => $isMfaTOTPConfigured ? \1 : \0,
1760-
}
1761-
);
17621754

1763-
return R('OK_VALIDATED', value => {mfaInfo => \%mfaInfo});
1755+
# no skip, mfa is required
1756+
return R('KO_MFA_REQUIRED');
1757+
}
1758+
1759+
sub do_jit_mfa {
1760+
my %params = @_;
1761+
my $mfaType = $params{'mfaType'}; # password|totp|any|none
1762+
my $actionType = $params{'actionType'}; # host|plugin
1763+
1764+
my $localfnret = may_skip_mfa(mfaType => $mfaType, actionType => $actionType);
1765+
if ($localfnret->is_ok) {
1766+
# skip, localfnret includes the detailed reason
1767+
return $localfnret;
17641768
}
1765-
return R('OK');
1769+
1770+
# otherwise, do mfa
1771+
$localfnret = OVH::Bastion::do_pamtester(self => $self, sysself => $sysself);
1772+
main_exit(OVH::Bastion::EXIT_MFA_FAILED, 'mfa_failed', $localfnret->msg) if !$localfnret;
1773+
1774+
# craft this so that the remote server, which can be a bastion in case we're chaining,
1775+
# can enforce its own policy. This should be serialized in LC_BASTION_DETAILS on egress side
1776+
my %mfaInfo = (
1777+
validated => \1,
1778+
reason => "mfa_required_for_$actionType",
1779+
type => {
1780+
password => $isMfaPasswordConfigured ? \1 : \0,
1781+
totp => $isMfaTOTPConfigured ? \1 : \0,
1782+
}
1783+
);
1784+
1785+
return R('OK_VALIDATED', value => {mfaInfo => \%mfaInfo});
17661786
}
17671787

17681788
sub do_plugin_jit_mfa {
@@ -1830,7 +1850,7 @@ sub do_plugin_jit_mfa {
18301850
}
18311851

18321852
# not required? we're done
1833-
if (!$mfaType) {
1853+
if (!$mfaType || may_skip_mfa(mfaType => $mfaType, actionType => 'plugin')) {
18341854
if ($generateMfaToken) {
18351855
# return a dummy token so that our caller is happy, then exit
18361856
print("MFA_TOKEN=notrequired\n");
@@ -1888,8 +1908,6 @@ sub do_plugin_jit_mfa {
18881908
return R('OK_JIT_MFA_VALIDATED');
18891909
}
18901910
elsif ($generateMfaToken) {
1891-
osh_print("MFA token generation requested, entering MFA phase...");
1892-
18931911
# do MFA
18941912
$localfnret = do_jit_mfa(
18951913
actionType => 'plugin',

tests/functional/tests.d/340-selfaccesses.sh

-202
Original file line numberDiff line numberDiff line change
@@ -196,208 +196,6 @@ testsuite_selfaccesses()
196196
nocontain "Permission denied"
197197
contain "$randomstr"
198198

199-
# scp & sftp
200-
201-
# these are the old pre-3.14.15 helper versions, we want to check for descendant compatibility
202-
cat >/tmp/scphelper <<'EOF'
203-
#! /bin/sh
204-
while ! [ "$1" = "--" ] ; do
205-
if [ "$1" = "-l" ] ; then
206-
remoteuser="--user $2"
207-
shift 2
208-
elif [ "$1" = "-p" ] ; then
209-
remoteport="--port $2"
210-
shift 2
211-
elif [ "$1" = "-s" ]; then
212-
# caller is a newer scp that tries to use the sftp subsystem
213-
# instead of plain old scp, warn because it won't work
214-
echo "scpwrapper: WARNING: your scp version is recent, you need to add '-O' to your scp command-line, exiting." >&2
215-
exit 1
216-
else
217-
sshcmdline="$sshcmdline $1"
218-
shift
219-
fi
220-
done
221-
host="$2"
222-
scpcmd=`echo "$3" | sed -e 's/#/##/g;s/ /#/g'`
223-
EOF
224-
echo "exec ssh -p $remote_port $account0@$remote_ip -T \$sshcmdline -- \$remoteuser \$remoteport --host \$host --osh scp --scp-cmd \"\$scpcmd\"" >> /tmp/scphelper
225-
chmod +x /tmp/scphelper
226-
227-
cat >/tmp/sftphelper <<'EOF'
228-
#! /usr/bin/env bash
229-
shopt -s nocasematch
230-
231-
while ! [ "$1" = "--" ] ; do
232-
# user
233-
if [ "$1" = "-l" ] ; then
234-
remoteuser="--user $2"
235-
shift 2
236-
elif [[ $1 =~ ^-oUser[=\ ]([^\ ]+)$ ]] ; then
237-
remoteuser="--user ${BASH_REMATCH[1]}"
238-
shift
239-
elif [ "$1" = "-o" ] && [[ $2 =~ ^user=([0-9]+)$ ]] ; then
240-
remoteuser="--user ${BASH_REMATCH[1]}"
241-
shift 2
242-
243-
# port
244-
elif [ "$1" = "-p" ] ; then
245-
remoteport="--port $2"
246-
shift 2
247-
elif [[ $1 =~ ^-oPort[=\ ]([0-9]+)$ ]] ; then
248-
remoteport="--port ${BASH_REMATCH[1]}"
249-
shift
250-
elif [ "$1" = "-o" ] && [[ $2 =~ ^port=([0-9]+)$ ]] ; then
251-
remoteport="--port ${BASH_REMATCH[1]}"
252-
shift 2
253-
254-
# other '-oFoo Bar'
255-
elif [[ $1 =~ ^-o([^\ ]+)\ (.+)$ ]] ; then
256-
sshcmdline="$sshcmdline -o${BASH_REMATCH[1]}=${BASH_REMATCH[2]}"
257-
shift
258-
259-
# don't forward -s
260-
elif [ "$1" = "-s" ]; then
261-
shift
262-
263-
# other stuff passed directly to ssh
264-
else
265-
sshcmdline="$sshcmdline $1"
266-
shift
267-
fi
268-
done
269-
270-
# after '--', remaining args are always host then 'sftp'
271-
host="$2"
272-
subsystem="$3"
273-
if [ "$subsystem" != sftp ]; then
274-
echo "Unknown subsystem requested '$subsystem', expected 'sftp'" >&2
275-
exit 1
276-
fi
277-
278-
# if host is in the form remoteuser@remotehost, split it
279-
if [[ $host =~ @ ]]; then
280-
remoteuser="--user ${host%@*}"
281-
host=${host#*@}
282-
fi
283-
EOF
284-
echo "exec ssh -p $remote_port $account0@$remote_ip -T \$sshcmdline -- \$remoteuser \$remoteport --host \$host --osh sftp" >> /tmp/sftphelper
285-
chmod +x /tmp/sftphelper
286-
287-
## get both helpers first
288-
for proto in scp sftp; do
289-
success $proto $a0 --osh $proto
290-
if [ "$COUNTONLY" != 1 ]; then
291-
get_json | $jq '.value.script' | base64 -d | gunzip -c > /tmp/${proto}wrapper
292-
perl -i -pe 'print "BASTION_SCP_DEBUG=1\nBASTION_SFTP_DEBUG=1\n" if ++$line==2' "/tmp/${proto}wrapper"
293-
chmod +x /tmp/${proto}wrapper
294-
fi
295-
done
296-
unset proto
297-
298-
# scp
299-
300-
## detect recent scp
301-
local scp_options=""
302-
if [ "$COUNTONLY" != 1 ]; then
303-
if scp -O -S /bin/true a: b 2>/dev/null; then
304-
echo "scp: will use new version params"
305-
scp_options="-O"
306-
else
307-
echo "scp: will use old version params"
308-
fi
309-
fi
310-
311-
success forscp $a0 --osh selfAddPersonalAccess --host 127.0.0.2 --scpup --port 22
312-
313-
sleepafter 2
314-
run scp_downloadfailnoright_old scp $scp_options -F $mytmpdir/ssh_config -S /tmp/scphelper -i $account0key1file $shellaccount@127.0.0.2:uptest /tmp/downloaded
315-
retvalshouldbe 1
316-
contain "Sorry, but even"
317-
318-
run scp_downloadfailnoright_new env BASTION_SCP_DEBUG=1 /tmp/scpwrapper -i $account0key1file $shellaccount@127.0.0.2:uptest /tmp/downloaded
319-
retvalshouldbe 1
320-
contain "Sorry, but even"
321-
322-
success forscp $a0 --osh selfAddPersonalAccess --host 127.0.0.2 --scpdown --port 22
323-
324-
sleepafter 2
325-
run scp_downloadfailnofile_old scp $scp_options -F $mytmpdir/ssh_config -S /tmp/scphelper -i $account0key1file $shellaccount@127.0.0.2:uptest /tmp/downloaded
326-
retvalshouldbe 1
327-
contain "through the bastion from"
328-
contain "Error launching transfer"
329-
contain "No such file or directory"
330-
nocontain "Permission denied"
331-
332-
run scp_downloadfailnofile_new /tmp/scpwrapper -i $account0key1file $shellaccount@127.0.0.2:uptest /tmp/downloaded
333-
retvalshouldbe 1
334-
contain "through the bastion from"
335-
contain "Error launching transfer"
336-
contain "No such file or directory"
337-
nocontain "Permission denied"
338-
339-
run scp_invalidhostname_old scp $scp_options -F $mytmpdir/ssh_config -S /tmp/scphelper -i $account0key1file $shellaccount@_invalid._invalid:uptest /tmp/downloaded
340-
retvalshouldbe 1
341-
contain REGEX "Sorry, couldn't resolve the host you specified|I was unable to resolve host"
342-
343-
run scp_invalidhostname_new /tmp/scpwrapper -i $account0key1file $shellaccount@_invalid._invalid:uptest /tmp/downloaded
344-
retvalshouldbe 1
345-
contain REGEX "Sorry, couldn't resolve the host you specified|I was unable to resolve host"
346-
347-
success scp_upload_old scp $scp_options -F $mytmpdir/ssh_config -S /tmp/scphelper -i $account0key1file /etc/passwd $shellaccount@127.0.0.2:uptest
348-
contain "through the bastion to"
349-
contain "Done,"
350-
351-
success scp_upload_new /tmp/scpwrapper -i $account0key1file /etc/passwd $shellaccount@127.0.0.2:uptest
352-
contain "through the bastion to"
353-
contain "Done,"
354-
355-
success scp_download_old scp $scp_options -F $mytmpdir/ssh_config -S /tmp/scphelper -i $account0key1file $shellaccount@127.0.0.2:uptest /tmp/downloaded
356-
contain "through the bastion from"
357-
contain "Done,"
358-
359-
success scp_download_new /tmp/scpwrapper -i $account0key1file $shellaccount@127.0.0.2:uptest /tmp/downloaded
360-
contain "through the bastion from"
361-
contain "Done,"
362-
363-
success forscpremove1 $a0 --osh selfDelPersonalAccess --host 127.0.0.2 --scpup --port 22
364-
success forscpremove2 $a0 --osh selfDelPersonalAccess --host 127.0.0.2 --scpdown --port 22
365-
366-
# sftp
367-
368-
run sftp_no_access_old sftp -F $mytmpdir/ssh_config -S /tmp/sftphelper -i $account0key1file $shellaccount@127.0.0.2
369-
retvalshouldbe 255
370-
contain "Sorry, but even"
371-
372-
run sftp_no_access_new /tmp/sftpwrapper -i $account0key1file $shellaccount@127.0.0.2
373-
retvalshouldbe 255
374-
contain "Sorry, but even"
375-
376-
success forsftp $a0 --osh selfAddPersonalAccess --host 127.0.0.2 --sftp --port 22
377-
378-
if [ "$COUNTONLY" != 1 ]; then
379-
cat >"/tmp/sftpcommands" <<'EOF'
380-
ls
381-
exit
382-
EOF
383-
fi
384-
385-
success sftp_access_old sftp -F $mytmpdir/ssh_config -b /tmp/sftpcommands -S /tmp/sftphelper -i $account0key1file $shellaccount@127.0.0.2
386-
contain 'sftp> ls'
387-
contain 'uptest'
388-
contain 'sftp> exit'
389-
contain '>>> Done,'
390-
391-
success sftp_access_new /tmp/sftpwrapper -b /tmp/sftpcommands -i $account0key1file $shellaccount@127.0.0.2
392-
contain 'sftp> ls'
393-
contain 'uptest'
394-
contain 'sftp> exit'
395-
contain '>>> Done,'
396-
397-
success forsftpremove $a0 --osh selfDelPersonalAccess --host 127.0.0.2 --sftp --port 22
398-
399-
# /scp & sftp
400-
401199
# (forced commands)
402200

403201
# ESCAPE HELL

0 commit comments

Comments
 (0)