From 6e09b8f81f6ee23df7d19950b94cb24966857354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lesimple?= Date: Thu, 8 Aug 2024 12:19:46 +0000 Subject: [PATCH 1/4] refacto: move special protocols checks into a lib --- bin/plugin/open/scp | 94 ++++------------- bin/plugin/open/sftp | 79 +++------------ lib/perl/OVH/Bastion/Plugin/otherProtocol.pm | 100 +++++++++++++++++++ 3 files changed, 134 insertions(+), 139 deletions(-) create mode 100644 lib/perl/OVH/Bastion/Plugin/otherProtocol.pm diff --git a/bin/plugin/open/scp b/bin/plugin/open/scp index 0c606e8b8..36c8b9736 100755 --- a/bin/plugin/open/scp +++ b/bin/plugin/open/scp @@ -11,6 +11,7 @@ use lib dirname(__FILE__) . '/../../../lib/perl'; use OVH::Result; use OVH::Bastion; use OVH::Bastion::Plugin qw( :DEFAULT ); +use OVH::Bastion::Plugin::otherProtocol; # stdout is used by scp, so ensure we output everything through stderr local $ENV{'FORCE_STDERR'} = 1; @@ -279,12 +280,6 @@ if (not $ip) { osh_exit 'ERR_HOST_NOT_FOUND', "Sorry, couldn't resolve the host you specified ('$host'), aborting."; } -my $machine = $ip; -$machine = "$user\@$ip" if $user; -$port ||= 22; # scp uses 22 if not specified, so we need to test access to that port and not any port (aka undef) -$user ||= $self; # same for user -$machine .= ":$port"; - # decode the passed scp command my $decoded = $scpCmd; @@ -294,89 +289,42 @@ if ($decoded !~ /^(?:scp )(?:.*)-([tf]) (?:.+)$/) { # security osh_exit 'ERR_SECURITY_VIOLATION', "scp command format unrecognized"; } -my $userToCheck = $1 eq 't' ? '!scpupload' : '!scpdownload'; ## no critic (CaptureWithoutTest) ## false positive - -my %keys; -osh_debug("Checking access 1/2 of $self to $machine..."); -$fnret = OVH::Bastion::is_access_granted( - account => $self, - user => $user, - ipfrom => $ENV{'OSH_IP_FROM'}, - ip => $ip, - port => $port, - details => 1 -); -if (not $fnret) { - osh_exit 'ERR_ACCESS_DENIED', "Sorry, but you don't seem to have access to $machine"; -} -# get the keys we would try -foreach my $access (@{$fnret->value || []}) { - foreach my $key (@{$access->{'sortedKeys'} || []}) { - my $keyfile = $access->{'keys'}{$key}{'fullpath'}; - $keys{$keyfile}++ if -r $keyfile; - osh_debug("Checking access 1/2 keyfile: $keyfile"); - } -} +my $protocol = $1 eq 't' ? 'scpupload' : 'scpdownload'; ## no critic (CaptureWithoutTest) ## false positive -osh_debug("Checking access 2/2 of $self to $userToCheck of $machine..."); -$fnret = OVH::Bastion::is_access_granted( - account => $self, - user => $userToCheck, - ipfrom => $ENV{'OSH_IP_FROM'}, - ip => $ip, - port => $port, - exactUserMatch => 1, - details => 1 -); -if (not $fnret) { - osh_exit 'ERR_ACCESS_DENIED', - "Sorry, but even if you have ssh access to $machine, you still need to be granted specifically for scp"; +# basic mitigation for CVE-2020-15778 +if ($decoded =~ m{[\`\$\;><\|\&]}) { + osh_exit('ERR_SECURITY_VIOLATION', "Invalid characters detected, bailing out"); } -# get the keys we would try too -foreach my $access (@{$fnret->value || []}) { - foreach my $key (@{$access->{'sortedKeys'} || []}) { - my $keyfile = $access->{'keys'}{$key}{'fullpath'}; - $keys{$keyfile}++ if -r $keyfile; - osh_debug("Checking access 2/2 keyfile: $keyfile"); - } -} +$port ||= 22; # scp uses 22 if not specified, so we need to test access to that port and not any port (aka undef) +$user ||= $self; # same for user + +$fnret = OVH::Bastion::Plugin::otherProtocol::has_protocol_access( + account => $self, + user => $user, + ip => $ip, + port => $port, + protocol => $protocol, +); +$fnret or osh_exit($fnret); +my $machine = $fnret->value->{'machine'}; +my @keys = @{$fnret->value->{'keys'} || []}; # now build the command my @cmd = qw{ ssh -x -oForwardAgent=no -oPermitLocalCommand=no -oClearAllForwardings=yes }; push @cmd, ('-p', $port) if $port; push @cmd, ('-l', $user) if $user; -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 - # !scpupload special user, and we would add himself this access through selfAddPrivateAccess. - # in that case both allow_deny would return OK, but with different keys. - # we'll only use the keys that matched BOTH calls. - next unless $keys{$keyfile} == 2; - push @cmd, ('-i', $keyfile); - $atleastonekey = 1; -} - -if (not $atleastonekey) { - osh_exit('KO_ACCESS_DENIED', - "Sorry, you seem to have access through ssh and through scp but by different and distinct means (distinct keys)." - . " The intersection between your rights for ssh and for scp needs to be at least one."); -} - -# basic mitigation for CVE-2020-15778 -if ($decoded =~ m{[\`\$\;><\|\&]}) { - osh_exit('ERR_SECURITY_VIOLATION', "Invalid characters detected, bailing out"); +foreach my $key (@keys) { + push @cmd, ('-i', $key); } push @cmd, "--", $ip, $decoded; print STDERR ">>> Hello $self, transferring your file through the bastion " - . ($userToCheck eq '!scpupload' ? 'to' : 'from') + . ($protocol eq 'scpupload' ? 'to' : 'from') . " $machine...\n"; #print STDERR join('^', @cmd)."\n"; diff --git a/bin/plugin/open/sftp b/bin/plugin/open/sftp index 2640ff615..9eb2520e7 100755 --- a/bin/plugin/open/sftp +++ b/bin/plugin/open/sftp @@ -11,6 +11,7 @@ use lib dirname(__FILE__) . '/../../../lib/perl'; use OVH::Result; use OVH::Bastion; use OVH::Bastion::Plugin qw( :DEFAULT ); +use OVH::Bastion::Plugin::otherProtocol; # stdout is used by scp, so ensure we output everything through stderr local $ENV{'FORCE_STDERR'} = 1; @@ -277,60 +278,20 @@ if (not $ip) { osh_exit 'ERR_HOST_NOT_FOUND', "Sorry, couldn't resolve the host you specified ('$host'), aborting."; } -my $machine = $ip; -$machine = "$user\@$ip" if $user; -$port ||= 22; # sftp uses 22 if not specified, so we need to test access to that port and not any port (aka undef) +$port ||= 22; # scp uses 22 if not specified, so we need to test access to that port and not any port (aka undef) $user ||= $self; # same for user -$machine .= ":$port"; - -my %keys; -osh_debug("Checking access 1/2 of $self to $machine..."); -$fnret = OVH::Bastion::is_access_granted( - account => $self, - user => $user, - ipfrom => $ENV{'OSH_IP_FROM'}, - ip => $ip, - port => $port, - details => 1 -); - -if (not $fnret) { - osh_exit 'ERR_ACCESS_DENIED', "Sorry, but you don't seem to have access to $machine"; -} - -# get the keys we would try -foreach my $access (@{$fnret->value || []}) { - foreach my $key (@{$access->{'sortedKeys'} || []}) { - my $keyfile = $access->{'keys'}{$key}{'fullpath'}; - $keys{$keyfile}++ if -r $keyfile; - osh_debug("Checking access 1/2 keyfile: $keyfile"); - } -} -my $userToCheck = '!sftp'; -osh_debug("Checking access 2/2 of $self to $userToCheck of $machine..."); -$fnret = OVH::Bastion::is_access_granted( - account => $self, - user => $userToCheck, - ipfrom => $ENV{'OSH_IP_FROM'}, - ip => $ip, - port => $port, - exactUserMatch => 1, - details => 1 +$fnret = OVH::Bastion::Plugin::otherProtocol::has_protocol_access( + account => $self, + user => $user, + ip => $ip, + port => $port, + protocol => 'sftp', ); -if (not $fnret) { - osh_exit 'ERR_ACCESS_DENIED', - "Sorry, but even if you have ssh access to $machine, you still need to be granted specifically for sftp"; -} +$fnret or osh_exit($fnret); -# get the keys we would try too -foreach my $access (@{$fnret->value || []}) { - foreach my $key (@{$access->{'sortedKeys'} || []}) { - my $keyfile = $access->{'keys'}{$key}{'fullpath'}; - $keys{$keyfile}++ if -r $keyfile; - osh_debug("Checking access 2/2 keyfile: $keyfile"); - } -} +my $machine = $fnret->value->{'machine'}; +my @keys = @{$fnret->value->{'keys'} || []}; # now build the command @@ -339,22 +300,8 @@ push @cmd, ('-p', $port) if $port; push @cmd, ('-l', $user) if $user; 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. - # in that case both allow_deny would return OK, but with different keys. - # we'll only use the keys that matched BOTH calls. - next unless $keys{$keyfile} == 2; - push @cmd, ('-i', $keyfile); - $atleastonekey = 1; -} - -if (not $atleastonekey) { - osh_exit('KO_ACCESS_DENIED', - "Sorry, you seem to have access through ssh and through sftp but by different and distinct means (distinct keys)." - . " The intersection between your rights for ssh and for sftp needs to be at least one."); +foreach my $key (@keys) { + push @cmd, ('-i', $key); } push @cmd, "--", $ip, 'sftp'; diff --git a/lib/perl/OVH/Bastion/Plugin/otherProtocol.pm b/lib/perl/OVH/Bastion/Plugin/otherProtocol.pm new file mode 100644 index 000000000..5d080ff9e --- /dev/null +++ b/lib/perl/OVH/Bastion/Plugin/otherProtocol.pm @@ -0,0 +1,100 @@ +package OVH::Bastion::Plugin::otherProtocol; + +# vim: set filetype=perl ts=4 sw=4 sts=4 et: +use common::sense; + +use File::Basename; +use lib dirname(__FILE__) . '/../../../../../lib/perl'; +use OVH::Result; +use OVH::Bastion; + +# used by scp, sftp, rsync +# we need to verify that the account has access to the tuple (user@host:port), +# and also that, using the same access way (the same egress ssh keys), that they are granted +# for this host:port using another protocol than ssh (scp, sftp, rsync) +# this requirement will be lifted once we add the "protocol type" to the whole access tuple data model +sub has_protocol_access { + my %params = @_; + my $account = $params{'account'}; + my $user = $params{'user'}; + my $ipfrom = $params{'ipfrom'} || $ENV{'OSH_IP_FROM'}; + my $ip = $params{'ip'}; + my $port = $params{'port'}; + my $protocol = $params{'protocol'}; + + if (!$account || !$ipfrom || !$ip || !$protocol || !$user || !$port) { + return R('ERR_MSSING_PARAMETERS', msg => "Missing mandatory parameters for has_protocol_access"); + } + + my $machine = "$user\@$ip:$port"; + + my %keys; + osh_debug("Checking access 1/2 of $account to $machine..."); + my $fnret = OVH::Bastion::is_access_granted( + account => $account, + user => $user, + ipfrom => $ipfrom, + ip => $ip, + port => $port, + details => 1 + ); + + if (not $fnret) { + return R('KO_ACCESS_DENIED', msg => "Sorry, but you don't have access to $machine"); + } + + # get the keys we would try + foreach my $access (@{$fnret->value || []}) { + foreach my $key (@{$access->{'sortedKeys'} || []}) { + my $keyfile = $access->{'keys'}{$key}{'fullpath'}; + $keys{$keyfile}++ if -r $keyfile; + osh_debug("Checking access 1/2 keyfile: $keyfile"); + } + } + + osh_debug("Checking access 2/2 of !rsync to $user of $machine..."); + $fnret = OVH::Bastion::is_access_granted( + account => $account, + user => "!$protocol", + ipfrom => $ipfrom, + ip => $ip, + port => $port, + exactUserMatch => 1, + details => 1 + ); + if (not $fnret) { + return R('KO_ACCESS_DENIED', + msg => "Sorry, you have ssh access to $machine, but you need to be granted specifically for $protocol"); + } + + # get the keys we would try too + foreach my $access (@{$fnret->value || []}) { + foreach my $key (@{$access->{'sortedKeys'} || []}) { + my $keyfile = $access->{'keys'}{$key}{'fullpath'}; + $keys{$keyfile}++ if -r $keyfile; + osh_debug("Checking access 2/2 keyfile: $keyfile"); + } + } + + # 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 + # !$protocol special user, and they would add themselves this access through selfAddPrivateAccess. + # in that case both allow_deny would return OK, but with different keys. + # we'll only use the keys that matched BOTH calls. + my @validKeys; + foreach my $keyfile (keys %keys) { + next unless $keys{$keyfile} == 2; + push @validKeys, $keyfile; + } + + if (!@validKeys) { + return R('KO_ACCESS_DENIED', + msg => + "Sorry, you have access through ssh and $protocol but by different and distinct means (distinct keys)." + . " The intersection between your rights for ssh and for $protocol needs to be at least one."); + } + + return R('OK', value => {keys => \@validKeys, machine => $machine}); +} + +1; From 34fa77a0d6bf099dc4153d91077475044ccd8750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lesimple?= Date: Mon, 12 Aug 2024 11:32:46 +0000 Subject: [PATCH 2/4] enh: plugins: add --protocol to handle scp, sftp, rsync Replace --sftp --scpup --scpdown by --protocol PROTOCOL. Also take the opportunity to replace --user-any by --user * and --port-any by --port *. All the legacy options are still supported but are now undocumented. --- bin/plugin/group-aclkeeper/groupAddServer | 59 +++++++++++------- bin/plugin/group-aclkeeper/groupDelServer | 42 +++++++------ .../group-gatekeeper/groupAddGuestAccess | 5 +- .../group-gatekeeper/groupDelGuestAccess | 5 +- .../restricted/accountAddPersonalAccess | 61 +++++++++++-------- .../restricted/accountDelPersonalAccess | 50 ++++++++------- bin/plugin/restricted/selfAddPersonalAccess | 59 +++++++++++------- bin/plugin/restricted/selfDelPersonalAccess | 38 +++++++----- .../group-aclkeeper/groupAddServer.rst | 4 ++ .../group-aclkeeper/groupDelServer.rst | 4 ++ .../group-gatekeeper/groupAddGuestAccess.rst | 4 ++ .../group-gatekeeper/groupDelGuestAccess.rst | 4 ++ .../restricted/accountAddPersonalAccess.rst | 4 ++ .../restricted/accountDelPersonalAccess.rst | 4 ++ .../restricted/selfAddPersonalAccess.rst | 4 ++ .../restricted/selfDelPersonalAccess.rst | 4 ++ lib/perl/OVH/Bastion/Plugin/ACL.pm | 61 ++++++++++++++----- lib/perl/OVH/Bastion/Plugin/otherProtocol.pm | 4 +- lib/perl/OVH/Bastion/allowdeny.inc | 10 ++- lib/perl/OVH/Bastion/allowkeeper.inc | 16 ++--- 20 files changed, 289 insertions(+), 153 deletions(-) diff --git a/bin/plugin/group-aclkeeper/groupAddServer b/bin/plugin/group-aclkeeper/groupAddServer index 0e8d12561..79b487a33 100755 --- a/bin/plugin/group-aclkeeper/groupAddServer +++ b/bin/plugin/group-aclkeeper/groupAddServer @@ -15,34 +15,41 @@ my $remainingOptions = OVH::Bastion::Plugin::begin( userAllowWildcards => 1, options => { "group=s" => \my $group, - "user-any" => \my $userAny, - "port-any" => \my $portAny, - "scpup" => \my $scpUp, - "scpdown" => \my $scpDown, - "sftp" => \my $sftp, + "protocol=s" => \my $protocol, "force" => \my $force, # for slashes, and/or for servers that are down (no connection test) "force-key=s" => \my $forceKey, "force-password=s" => \my $forcePassword, "ttl=s" => \my $ttl, "comment=s" => \my $comment, + # undocumented/compatibility: + "user-any" => \my $userAny, + "port-any" => \my $portAny, + "scpup" => \my $scpUp, + "scpdown" => \my $scpDown, + "sftp" => \my $sftp, }, helptext => <<'EOF', Add an IP or IP block to a group's servers list -Usage: --osh SCRIPT_NAME --group GROUP [OPTIONS] +Usage: --osh SCRIPT_NAME --group GROUP --host HOST --user USER|* --port PORT|* [OPTIONS] --group GROUP Specify which group this machine should be added to --host HOST|IP|NET/CIDR Host(s) to add access to, either a HOST which will be resolved to an IP immediately, or an IP, or a whole network using the NET/CIDR notation - --user USER Specify which remote user should be allowed to connect as. + --user USER|PATTERN|* Specify which remote user should be allowed to connect as. Globbing characters '*' and '?' are supported, so you can specify a pattern that will be matched against the actual remote user name. - --user-any Synonym of '--user *', allows connecting as any remote user. - --port PORT Remote port allowed to connect to - --port-any Allow access to any remote port - --scpup Allow SCP upload, you--bastion-->server (omit --user in this case) - --scpdown Allow SCP download, you<--bastion--server (omit --user in this case) - --sftp Allow usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) + To allow any user, use '--user *' (you might need to escape '*' from your shell) + --port PORT|* Remote port allowed to connect to + To allow any port, use '--port *' (you might need to escape '*' from your shell) + --protocol PROTO Specify that a special protocol should be allowed for this HOST:PORT tuple, note that you + must not specify --user in that case. However, for this protocol to be usable under a given + remote user, access to the USER@HOST:PORT tuple must also be allowed. + PROTO must be one of: + scpup allow SCP upload, you--bastion-->server + scpdown allow SCP download, you<--bastion--server + sftp allow usage of the SFTP subsystem, through the bastion + rsync allow usage of rsync, through the bastion --force Don't try the ssh connection, just add the host to the group blindly --force-key FINGERPRINT Only use the key with the specified fingerprint to connect to the server (cf groupInfo) --force-password HASH Only use the password with the specified hash to connect to the server (cf groupListPasswords) @@ -51,8 +58,13 @@ Usage: --osh SCRIPT_NAME --group GROUP [OPTIONS] Examples:: - --osh SCRIPT_NAME --group grp1 --host 203.0.113.0/24 --user-any --port-any --force --comment '"a whole network"' - --osh SCRIPT_NAME --group grp2 --host srv1.example.org --user root --port 22 + --osh SCRIPT_NAME --group grp1 --host 203.0.113.0/24 --user '*' --port '*' --force --ttl 1d12h --comment '"a whole network"' + --osh SCRIPT_NAME --group grp2 --host srv1.example.org --user data --port 22 + --osh SCRIPT_NAME --group grp2 --host srv1.example.org --user file --port 22 + +Example to allow using sftp to srv1.example.org using remote user 'data' or 'file', in addition to the above commands:: + + --osh SCRIPT_NAME --group grp2 --host srv1.example.org --port 22 --protocol sftp EOF ); @@ -65,19 +77,21 @@ if (not $group or not $ip) { } $fnret = OVH::Bastion::Plugin::ACL::check( - user => $user, - userAny => $userAny, - port => $port, - portAny => $portAny, - scpUp => $scpUp, - scpDown => $scpDown, - sftp => $sftp + user => $user, + userAny => $userAny, + port => $port, + portAny => $portAny, + scpUp => $scpUp, + scpDown => $scpDown, + sftp => $sftp, + protocol => $protocol, ); if (!$fnret) { help(); osh_exit($fnret); } $user = $fnret->value->{'user'}; +$port = $fnret->value->{'port'}; $fnret = OVH::Bastion::is_valid_group_and_existing(group => $group, groupType => "key"); $fnret or osh_exit($fnret); @@ -127,7 +141,6 @@ if (not $force) { forcePassword => $forcePassword ); if ($fnret->is_ok and $fnret->err ne 'OK') { - # we have something to say, say it osh_info $fnret->msg; } diff --git a/bin/plugin/group-aclkeeper/groupDelServer b/bin/plugin/group-aclkeeper/groupDelServer index e54161e6a..f1be07ef7 100755 --- a/bin/plugin/group-aclkeeper/groupDelServer +++ b/bin/plugin/group-aclkeeper/groupDelServer @@ -14,31 +14,37 @@ my $remainingOptions = OVH::Bastion::Plugin::begin( header => "removing a server from a group", userAllowWildcards => 1, options => { - "group=s" => \my $group, + "group=s" => \my $group, + "protocol=s" => \my $protocol, + "force" => \my $force, + # undocumented/compatibility: "user-any" => \my $userAny, "port-any" => \my $portAny, "scpup" => \my $scpUp, "scpdown" => \my $scpDown, "sftp" => \my $sftp, - "force" => \my $force, }, helptext => <<'EOF', Remove an IP or IP block from a group's server list -Usage: --osh SCRIPT_NAME --group GROUP --host HOST [OPTIONS] +Usage: --osh SCRIPT_NAME --group GROUP --host HOST --user USER --port PORT [OPTIONS] --group GROUP Specify which group this machine should be removed from --host HOST|IP|NET/CIDR Host(s) to remove access from, either a HOST which will be resolved to an IP immediately, or an IP, or a whole network using the NET/CIDR notation - --user USER Specify which remote user was allowed to connect as. + --user USER|PATTERN|* Specify which remote user was allowed to connect as. Globbing characters '*' and '?' are supported, so you can specify a pattern that will be matched against the actual remote user name. - --user-any Synonym of '--user *', allowed connecting as any remote user. - --port PORT Remote port that was allowed to connect to - --port-any Use when access was allowed to any remote port - --scpup Remove SCP upload right, you--bastion-->server (omit --user in this case) - --scpdown Remove SCP download right, you<--bastion--server (omit --user in this case) - --sftp Remove usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) + If any user was allowed, use '--user *' (you might need to escape '*' from your shell) + --port PORT|* Remote port that was allowed to connect to + If any port was allowed, use '--port *' (you might need to escape '*' from your shell) + --protocol PROTO Specify that a special protocol allowance should be removed from this HOST:PORT tuple, note that you + must not specify --user in that case. + PROTO must be one of: + scpup allow SCP upload, you--bastion-->server + scpdown allow SCP download, you<--bastion--server + sftp allow usage of the SFTP subsystem, through the bastion + rsync allow usage of rsync, through the bastion This command adds, to an existing bastion account, access to a given server, using the egress keys of the group. The list of eligible servers for a given group is given by ``groupListServers`` @@ -60,19 +66,21 @@ if (not $group or not $ip) { } $fnret = OVH::Bastion::Plugin::ACL::check( - user => $user, - userAny => $userAny, - port => $port, - portAny => $portAny, - scpUp => $scpUp, - scpDown => $scpDown, - sftp => $sftp + user => $user, + userAny => $userAny, + port => $port, + portAny => $portAny, + scpUp => $scpUp, + scpDown => $scpDown, + sftp => $sftp, + protocol => $protocol, ); if (!$fnret) { help(); osh_exit($fnret); } $user = $fnret->value->{'user'}; +$port = $fnret->value->{'port'}; $fnret = OVH::Bastion::is_valid_group_and_existing(group => $group, groupType => "key"); $fnret or osh_exit($fnret); diff --git a/bin/plugin/group-gatekeeper/groupAddGuestAccess b/bin/plugin/group-gatekeeper/groupAddGuestAccess index 12e1e38cf..87ea34a07 100755 --- a/bin/plugin/group-gatekeeper/groupAddGuestAccess +++ b/bin/plugin/group-gatekeeper/groupAddGuestAccess @@ -22,6 +22,7 @@ my $remainingOptions = OVH::Bastion::Plugin::begin( "scpup" => \my $scpUp, "scpdown" => \my $scpDown, "sftp" => \my $sftp, + "rsync" => \my $rsync, "ttl=s" => \my $ttl, "comment=s" => \my $comment, }, @@ -44,6 +45,7 @@ Usage: --osh SCRIPT_NAME --group GROUP --account ACCOUNT [OPTIONS] --scpup Allow SCP upload, you--bastion-->server (omit --user in this case) --scpdown Allow SCP download, you<--bastion--server (omit --user in this case) --sftp Allow usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) + --rsync Allow usage of rsync through the bastion --ttl SECONDS|DURATION specify a number of seconds after which the access will automatically expire --comment '"ANY TEXT"' add a comment alongside this access. If omitted, we'll use the closest preexisting group access' comment as seen in groupListServers @@ -76,7 +78,8 @@ $fnret = OVH::Bastion::Plugin::ACL::check( portAny => $portAny, scpUp => $scpUp, scpDown => $scpDown, - sftp => $sftp + sftp => $sftp, + rsync => $rsync, ); if (!$fnret) { help(); diff --git a/bin/plugin/group-gatekeeper/groupDelGuestAccess b/bin/plugin/group-gatekeeper/groupDelGuestAccess index dbaef8155..2b85da5d0 100755 --- a/bin/plugin/group-gatekeeper/groupDelGuestAccess +++ b/bin/plugin/group-gatekeeper/groupDelGuestAccess @@ -22,6 +22,7 @@ my $remainingOptions = OVH::Bastion::Plugin::begin( "scpup" => \my $scpUp, "scpdown" => \my $scpDown, "sftp" => \my $sftp, + "rsync" => \my $rsync, }, helptext => <<'EOF', Remove a specific group server access from an account @@ -41,6 +42,7 @@ Usage: --osh SCRIPT_NAME --group GROUP --account ACCOUNT [OPTIONS] --scpup Remove SCP upload right, you--bastion-->server (omit --user in this case) --scpdown Remove SCP download right, you<--bastion--server (omit --user in this case) --sftp Remove usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) + --rsync Remove usage of rsync through the bastion This command removes, from an existing bastion account, access to a given server, using the egress keys of the group. The list of such servers is given by ``groupListGuestAccesses`` @@ -69,7 +71,8 @@ $fnret = OVH::Bastion::Plugin::ACL::check( portAny => $portAny, scpUp => $scpUp, scpDown => $scpDown, - sftp => $sftp + sftp => $sftp, + rsync => $rsync, ); if (!$fnret) { help(); diff --git a/bin/plugin/restricted/accountAddPersonalAccess b/bin/plugin/restricted/accountAddPersonalAccess index dbb9acfc2..f08a17dcb 100755 --- a/bin/plugin/restricted/accountAddPersonalAccess +++ b/bin/plugin/restricted/accountAddPersonalAccess @@ -16,34 +16,41 @@ my $remainingOptions = OVH::Bastion::Plugin::begin( userAllowWildcards => 1, options => { "account=s" => \my $account, - "user-any" => \my $userAny, - "port-any" => \my $portAny, - "scpup" => \my $scpUp, - "scpdown" => \my $scpDown, - "sftp" => \my $sftp, + "protocol=s" => \my $protocol, "force-key=s" => \my $forceKey, "force-password=s" => \my $forcePassword, "ttl=s" => \my $ttl, "comment=s" => \my $comment, + # undocumented/compatibility: + "user-any" => \my $userAny, + "port-any" => \my $portAny, + "scpup" => \my $scpUp, + "scpdown" => \my $scpDown, + "sftp" => \my $sftp, }, helptext => <<'EOF', Add a personal server access to an account -Usage: --osh SCRIPT_NAME --account ACCOUNT --host HOST [OPTIONS] +Usage: --osh SCRIPT_NAME --account ACCOUNT --host HOST --user USER --port PORT [OPTIONS] --account Bastion account to add the access to --host HOST|IP|NET/CIDR Host(s) to add access to, either a HOST which will be resolved to an IP immediately, or an IP, or a whole network using the NET/CIDR notation - --user USER Specify which remote user should be allowed to connect as. + --user USER|PATTERN|* Specify which remote user should be allowed to connect as. Globbing characters '*' and '?' are supported, so you can specify a pattern that will be matched against the actual remote user name. - --user-any Synonym of '--user *', allows connecting as any remote user. - --port PORT Remote port allowed to connect to - --port-any Allow access to any remote port - --scpup Allow SCP upload, you--bastion-->server (omit --user in this case) - --scpdown Allow SCP download, you<--bastion--server (omit --user in this case) - --sftp Allow usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) - --force-key FINGERPRINT Only use the key with the specified fingerprint to connect to the server (cf selfListEgressKeys) + To allow any user, use '--user *' (you might need to escape '*' from your shell) + --port PORT|* Remote port allowed to connect to + To allow any port, use '--port *' (you might need to escape '*' from your shell) + --protocol PROTO Specify that a special protocol should be allowed for this HOST:PORT tuple, note that you + must not specify --user in that case. However, for this protocol to be usable under a given + remote user, access to the USER@HOST:PORT tuple must also be allowed. + PROTO must be one of: + scpup allow SCP upload, you--bastion-->server + scpdown allow SCP download, you<--bastion--server + sftp allow usage of the SFTP subsystem, through the bastion + rsync allow usage of rsync, through the bastion + --force-key FINGERPRINT Only use the key with the specified fingerprint to connect to the server (cf accountListEgressKeys) --force-password HASH Only use the password with the specified hash to connect to the server (cf accountListPasswords) --ttl SECONDS|DURATION Specify a number of seconds (or a duration string, such as "1d7h8m") after which the access will automatically expire --comment "'ANY TEXT'" Add a comment alongside this server. Quote it twice as shown if you're under a shell. @@ -88,19 +95,21 @@ if (!$ip) { } $fnret = OVH::Bastion::Plugin::ACL::check( - user => $user, - userAny => $userAny, - port => $port, - portAny => $portAny, - scpUp => $scpUp, - scpDown => $scpDown, - sftp => $sftp + user => $user, + userAny => $userAny, + port => $port, + portAny => $portAny, + scpUp => $scpUp, + scpDown => $scpDown, + sftp => $sftp, + protocol => $protocol, ); if (!$fnret) { help(); osh_exit($fnret); } $user = $fnret->value->{'user'}; +$port = $fnret->value->{'port'}; if (defined $ttl) { $fnret = OVH::Bastion::is_valid_ttl(ttl => $ttl); @@ -108,6 +117,10 @@ if (defined $ttl) { $ttl = $fnret->value->{'seconds'}; } +if ($forceKey && $forcePassword) { + osh_exit 'ERR_INCOMPATIBLE_PARAMETERS', "Can't use --force-key and --force-password at the same time"; +} + if (not $account) { help(); osh_exit 'ERR_MISSING_PARAMETER', "Missing mandatory parameter 'account'"; @@ -129,9 +142,9 @@ if ($forcePassword) { $forcePassword = $fnret->value->{'hash'}; } -if ($forceKey && $forcePassword) { - osh_exit 'ERR_INCOMPATIBLE_PARAMETERS', "Can't use --force-key and --force-password at the same time"; -} +# +# Now do it +# # check plugin config if ($pluginConfig && $pluginConfig->{'self_remote_user_only'}) { diff --git a/bin/plugin/restricted/accountDelPersonalAccess b/bin/plugin/restricted/accountDelPersonalAccess index 25316a865..b7b8aa4bf 100755 --- a/bin/plugin/restricted/accountDelPersonalAccess +++ b/bin/plugin/restricted/accountDelPersonalAccess @@ -14,30 +14,36 @@ my $remainingOptions = OVH::Bastion::Plugin::begin( header => "removing personal access to a server from an account", userAllowWildcards => 1, options => { - "account=s" => \my $account, - "user-any" => \my $userAny, - "port-any" => \my $portAny, - "scpup" => \my $scpUp, - "scpdown" => \my $scpDown, - "sftp" => \my $sftp, + "account=s" => \my $account, + "protocol=s" => \my $protocol, + # undocumented/compatibility: + "user-any" => \my $userAny, + "port-any" => \my $portAny, + "scpup" => \my $scpUp, + "scpdown" => \my $scpDown, + "sftp" => \my $sftp, }, helptext => <<'EOF', Remove a personal server access from an account -Usage: --osh SCRIPT_NAME --account ACCOUNT --host HOST [OPTIONS] +Usage: --osh SCRIPT_NAME --account ACCOUNT --host HOST --user USER --port PORT [OPTIONS] --account Bastion account to remove access from --host HOST|IP|NET/CIDR Host(s) to remove access from, either a HOST which will be resolved to an IP immediately, or an IP, or a whole network using the NET/CIDR notation - --user USER Specify which remote user was allowed to connect as. + --user USER|PATTERN|* Specify which remote user was allowed to connect as. Globbing characters '*' and '?' are supported, so you can specify a pattern that will be matched against the actual remote user name. - --user-any Synonym of '--user *', allowed connecting as any remote user. - --port PORT Remote port that was allowed to connect to - --port-any Use when access was allowed to any remote port - --scpup Remove SCP upload right, you--bastion-->server (omit --user in this case) - --scpdown Remove SCP download right, you<--bastion--server (omit --user in this case) - --sftp Remove usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) + If any user was allowed, use '--user *' (you might need to escape '*' from your shell) + --port PORT|* Remote port that was allowed to connect to + If any port was allowed, use '--port *' (you might need to escape '*' from your shell) + --protocol PROTO Specify that a special protocol allowance should be removed from this HOST:PORT tuple, note that you + must not specify --user in that case. + PROTO must be one of: + scpup allow SCP upload, you--bastion-->server + scpdown allow SCP download, you<--bastion--server + sftp allow usage of the SFTP subsystem, through the bastion + rsync allow usage of rsync, through the bastion EOF ); @@ -49,19 +55,21 @@ if (!$ip) { } $fnret = OVH::Bastion::Plugin::ACL::check( - user => $user, - userAny => $userAny, - port => $port, - portAny => $portAny, - scpUp => $scpUp, - scpDown => $scpDown, - sftp => $sftp + user => $user, + userAny => $userAny, + port => $port, + portAny => $portAny, + scpUp => $scpUp, + scpDown => $scpDown, + sftp => $sftp, + protocol => $protocol, ); if (!$fnret) { help(); osh_exit($fnret); } $user = $fnret->value->{'user'}; +$port = $fnret->value->{'port'}; if (not $account) { help(); diff --git a/bin/plugin/restricted/selfAddPersonalAccess b/bin/plugin/restricted/selfAddPersonalAccess index 4af46c3ec..89babdab4 100755 --- a/bin/plugin/restricted/selfAddPersonalAccess +++ b/bin/plugin/restricted/selfAddPersonalAccess @@ -15,33 +15,40 @@ my $remainingOptions = OVH::Bastion::Plugin::begin( header => "adding personal access to a server on your account", userAllowWildcards => 1, options => { - "user-any" => \my $userAny, - "port-any" => \my $portAny, - "scpup" => \my $scpUp, - "scpdown" => \my $scpDown, - "sftp" => \my $sftp, + "protocol=s" => \my $protocol, "force-key=s" => \my $forceKey, "force-password=s" => \my $forcePassword, "force" => \my $force, "ttl=s" => \my $ttl, "comment=s" => \my $comment, + # undocumented/compatibility: + "user-any" => \my $userAny, + "port-any" => \my $portAny, + "scpup" => \my $scpUp, + "scpdown" => \my $scpDown, + "sftp" => \my $sftp, }, helptext => <<'EOF', Add a personal server access to your account -Usage: --osh SCRIPT_NAME --host HOST [OPTIONS] +Usage: --osh SCRIPT_NAME --host HOST --user USER --port PORT [OPTIONS] --host HOST|IP|NET/CIDR Host(s) to add access to, either a HOST which will be resolved to an IP immediately, or an IP, or a whole network using the NET/CIDR notation - --user USER Specify which remote user should be allowed to connect as. + --user USER|PATTERN|* Specify which remote user should be allowed to connect as. Globbing characters '*' and '?' are supported, so you can specify a pattern that will be matched against the actual remote user name. - --user-any Synonym of '--user *', allows connecting as any remote user. - --port PORT Remote port allowed to connect to - --port-any Allow access to any remote port - --scpup Allow SCP upload, you--bastion-->server (omit --user in this case) - --scpdown Allow SCP download, you<--bastion--server (omit --user in this case) - --sftp Allow usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) + To allow any user, use '--user *' (you might need to escape '*' from your shell) + --port PORT|* Remote port allowed to connect to + To allow any port, use '--port *' (you might need to escape '*' from your shell) + --protocol PROTO Specify that a special protocol should be allowed for this HOST:PORT tuple, note that you + must not specify --user in that case. However, for this protocol to be usable under a given + remote user, access to the USER@HOST:PORT tuple must also be allowed. + PROTO must be one of: + scpup allow SCP upload, you--bastion-->server + scpdown allow SCP download, you<--bastion--server + sftp allow usage of the SFTP subsystem, through the bastion + rsync allow usage of rsync, through the bastion --force Add the access without checking that the public SSH key is properly installed remotely --force-key FINGERPRINT Only use the key with the specified fingerprint to connect to the server (cf selfListEgressKeys) --force-password HASH Only use the password with the specified hash to connect to the server (cf selfListPasswords) @@ -84,19 +91,21 @@ if (!$ip) { } $fnret = OVH::Bastion::Plugin::ACL::check( - user => $user, - userAny => $userAny, - port => $port, - portAny => $portAny, - scpUp => $scpUp, - scpDown => $scpDown, - sftp => $sftp + user => $user, + userAny => $userAny, + port => $port, + portAny => $portAny, + scpUp => $scpUp, + scpDown => $scpDown, + sftp => $sftp, + protocol => $protocol, ); if (!$fnret) { help(); osh_exit($fnret); } $user = $fnret->value->{'user'}; +$port = $fnret->value->{'port'}; if (defined $ttl) { $fnret = OVH::Bastion::is_valid_ttl(ttl => $ttl); @@ -104,6 +113,10 @@ if (defined $ttl) { $ttl = $fnret->value->{'seconds'}; } +if ($forceKey && $forcePassword) { + osh_exit 'ERR_INCOMPATIBLE_PARAMETERS', "Can't use --force-key and --force-password at the same time"; +} + if ($forceKey) { $fnret = OVH::Bastion::is_valid_fingerprint(fingerprint => $forceKey); $fnret or osh_exit $fnret; @@ -116,9 +129,9 @@ if ($forcePassword) { $forcePassword = $fnret->value->{'hash'}; } -if ($forceKey && $forcePassword) { - osh_exit 'ERR_INCOMPATIBLE_PARAMETERS', "Can't use --force-key and --force-password at the same time"; -} +# +# Now do it +# # check plugin config if ($pluginConfig && $pluginConfig->{'self_remote_user_only'}) { diff --git a/bin/plugin/restricted/selfDelPersonalAccess b/bin/plugin/restricted/selfDelPersonalAccess index f49cf5a1e..42699cab8 100755 --- a/bin/plugin/restricted/selfDelPersonalAccess +++ b/bin/plugin/restricted/selfDelPersonalAccess @@ -14,6 +14,8 @@ my $remainingOptions = OVH::Bastion::Plugin::begin( header => "removing personal access to a server from an account", userAllowWildcards => 1, options => { + "protocol=s" => \my $protocol, + # undocumented/compatibility: "user-any" => \my $userAny, "port-any" => \my $portAny, "scpup" => \my $scpUp, @@ -23,19 +25,23 @@ my $remainingOptions = OVH::Bastion::Plugin::begin( helptext => <<'EOF', Remove a personal server access from your account -Usage: --osh SCRIPT_NAME --host HOST [OPTIONS] +Usage: --osh SCRIPT_NAME --host HOST --user USER --port PORT [OPTIONS] --host HOST|IP|NET/CIDR Host(s) to remove access from, either a HOST which will be resolved to an IP immediately, or an IP, or a whole network using the NET/CIDR notation - --user USER Specify which remote user was allowed to connect as. + --user USER|PATTERN|* Specify which remote user was allowed to connect as. Globbing characters '*' and '?' are supported, so you can specify a pattern that will be matched against the actual remote user name. - --user-any Synonym of '--user *', allowed connecting as any remote user. - --port PORT Remote port that was allowed to connect to - --port-any Use when access was allowed to any remote port - --scpup Remove SCP upload right, you--bastion-->server (omit --user in this case) - --scpdown Remove SCP download right, you<--bastion--server (omit --user in this case) - --sftp Remove usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) + If any user was allowed, use '--user *' (you might need to escape '*' from your shell) + --port PORT|* Remote port that was allowed to connect to + If any port was allowed, use '--port *' (you might need to escape '*' from your shell) + --protocol PROTO Specify that a special protocol allowance should be removed from this HOST:PORT tuple, note that you + must not specify --user in that case. + PROTO must be one of: + scpup allow SCP upload, you--bastion-->server + scpdown allow SCP download, you<--bastion--server + sftp allow usage of the SFTP subsystem, through the bastion + rsync allow usage of rsync, through the bastion EOF ); @@ -47,19 +53,21 @@ if (!$ip) { } $fnret = OVH::Bastion::Plugin::ACL::check( - user => $user, - userAny => $userAny, - port => $port, - portAny => $portAny, - scpUp => $scpUp, - scpDown => $scpDown, - sftp => $sftp + user => $user, + userAny => $userAny, + port => $port, + portAny => $portAny, + scpUp => $scpUp, + scpDown => $scpDown, + sftp => $sftp, + protocol => $protocol, ); if (!$fnret) { help(); osh_exit($fnret); } $user = $fnret->value->{'user'}; +$port = $fnret->value->{'port'}; my @command = qw{ sudo -n -u allowkeeper -- /usr/bin/env perl -T }; push @command, $OVH::Bastion::BASEPATH . '/bin/helper/osh-accountModifyPersonalAccess'; diff --git a/doc/sphinx/plugins/group-aclkeeper/groupAddServer.rst b/doc/sphinx/plugins/group-aclkeeper/groupAddServer.rst index 3ddbbcb89..b26080659 100644 --- a/doc/sphinx/plugins/group-aclkeeper/groupAddServer.rst +++ b/doc/sphinx/plugins/group-aclkeeper/groupAddServer.rst @@ -53,6 +53,10 @@ Add an IP or IP block to a group's servers list Allow usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) +.. option:: --rsync + + Allow usage of rsync through the bastion + .. option:: --force Don't try the ssh connection, just add the host to the group blindly diff --git a/doc/sphinx/plugins/group-aclkeeper/groupDelServer.rst b/doc/sphinx/plugins/group-aclkeeper/groupDelServer.rst index b1bf0a42c..f3a15309e 100644 --- a/doc/sphinx/plugins/group-aclkeeper/groupDelServer.rst +++ b/doc/sphinx/plugins/group-aclkeeper/groupDelServer.rst @@ -53,6 +53,10 @@ Remove an IP or IP block from a group's server list Remove usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) +.. option:: --rsync + + Remove usage of rsync through the bastion + This command adds, to an existing bastion account, access to a given server, using the egress keys of the group. The list of eligible servers for a given group is given by ``groupListServers`` diff --git a/doc/sphinx/plugins/group-gatekeeper/groupAddGuestAccess.rst b/doc/sphinx/plugins/group-gatekeeper/groupAddGuestAccess.rst index c7e5d898d..bcd87b816 100644 --- a/doc/sphinx/plugins/group-gatekeeper/groupAddGuestAccess.rst +++ b/doc/sphinx/plugins/group-gatekeeper/groupAddGuestAccess.rst @@ -58,6 +58,10 @@ Add a specific group server access to an account Allow usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) +.. option:: --rsync + + Allow usage of rsync through the bastion + .. option:: --ttl SECONDS|DURATION specify a number of seconds after which the access will automatically expire diff --git a/doc/sphinx/plugins/group-gatekeeper/groupDelGuestAccess.rst b/doc/sphinx/plugins/group-gatekeeper/groupDelGuestAccess.rst index e2a414baf..27c6c967b 100644 --- a/doc/sphinx/plugins/group-gatekeeper/groupDelGuestAccess.rst +++ b/doc/sphinx/plugins/group-gatekeeper/groupDelGuestAccess.rst @@ -57,6 +57,10 @@ Remove a specific group server access from an account Remove usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) +.. option:: --rsync + + Remove usage of rsync through the bastion + This command removes, from an existing bastion account, access to a given server, using the egress keys of the group. The list of such servers is given by ``groupListGuestAccesses`` diff --git a/doc/sphinx/plugins/restricted/accountAddPersonalAccess.rst b/doc/sphinx/plugins/restricted/accountAddPersonalAccess.rst index 5bfaf4069..fe285dacc 100644 --- a/doc/sphinx/plugins/restricted/accountAddPersonalAccess.rst +++ b/doc/sphinx/plugins/restricted/accountAddPersonalAccess.rst @@ -53,6 +53,10 @@ Add a personal server access to an account Allow usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) +.. option:: --rsync + + Allow usage of rsync through the bastion + .. option:: --force-key FINGERPRINT Only use the key with the specified fingerprint to connect to the server (cf selfListEgressKeys) diff --git a/doc/sphinx/plugins/restricted/accountDelPersonalAccess.rst b/doc/sphinx/plugins/restricted/accountDelPersonalAccess.rst index 34da98c6b..9d352ff20 100644 --- a/doc/sphinx/plugins/restricted/accountDelPersonalAccess.rst +++ b/doc/sphinx/plugins/restricted/accountDelPersonalAccess.rst @@ -53,3 +53,7 @@ Remove a personal server access from an account Remove usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) +.. option:: --rsync + + Remove usage of rsync through the bastion + diff --git a/doc/sphinx/plugins/restricted/selfAddPersonalAccess.rst b/doc/sphinx/plugins/restricted/selfAddPersonalAccess.rst index 94909fd12..433872b23 100644 --- a/doc/sphinx/plugins/restricted/selfAddPersonalAccess.rst +++ b/doc/sphinx/plugins/restricted/selfAddPersonalAccess.rst @@ -49,6 +49,10 @@ Add a personal server access to your account Allow usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) +.. option:: --rsync + + Allow usage of rsync through the bastion + .. option:: --force Add the access without checking that the public SSH key is properly installed remotely diff --git a/doc/sphinx/plugins/restricted/selfDelPersonalAccess.rst b/doc/sphinx/plugins/restricted/selfDelPersonalAccess.rst index c990d3a79..2a0581f25 100644 --- a/doc/sphinx/plugins/restricted/selfDelPersonalAccess.rst +++ b/doc/sphinx/plugins/restricted/selfDelPersonalAccess.rst @@ -49,3 +49,7 @@ Remove a personal server access from your account Remove usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) +.. option:: --rsync + + Remove usage of rsync through the bastion + diff --git a/lib/perl/OVH/Bastion/Plugin/ACL.pm b/lib/perl/OVH/Bastion/Plugin/ACL.pm index b94c87fba..eda63f295 100644 --- a/lib/perl/OVH/Bastion/Plugin/ACL.pm +++ b/lib/perl/OVH/Bastion/Plugin/ACL.pm @@ -9,8 +9,8 @@ use OVH::Bastion; sub check { my %params = @_; - my ($port, $portAny, $user, $userAny, $scpUp, $scpDown, $sftp) = - @params{qw{ port portAny user userAny scpUp scpDown sftp }}; + my ($port, $portAny, $user, $userAny, $scpUp, $scpDown, $sftp, $protocol) = + @params{qw{ port portAny user userAny scpUp scpDown sftp protocol }}; if ($user and $userAny) { return R('ERR_INCOMPATIBLE_PARAMETERS', @@ -18,6 +18,19 @@ sub check { . "both are contradictory, please check your command"); } + # legacy option mapping + $user = '*' if $userAny; + + if ($protocol) { + if ($scpUp or $scpDown or $sftp) { + return R('ERR_INCOMPATIBLE_PARAMETERS', msg => "Can't use --protocol with --scpup, --scpdown or --sftp"); + } + if (!grep { $protocol eq $_ } qw{ scpup scpdown sftp rsync }) { + return R('ERR_INVALID_PARAMETER', + msg => "The protocol '$protocol' is not supported, expected either scpup, scpdown, sftp or rsync"); + } + } + if ($scpUp and $scpDown) { return R('ERR_INCOMPATIBLE_PARAMETERS', msg => "You specified both --scpup and --scpdown, " @@ -26,23 +39,32 @@ sub check { if ($sftp and ($scpUp or $scpDown)) { return R('ERR_INCOMPATIBLE_PARAMETERS', - msg => "You specified both --scp* and --sftp, " - . "if you want to grant both protocols, please do it in two separate commands"); + msg => "You can specify only one of --sftp --scpup --scpdown at a time, " + . "if you want to grant several of those protocols, please do it in separate commands"); + } + + # legacy options mapping + if (!$protocol) { + $protocol = 'sftp' if $sftp; + $protocol = 'scpupload' if $scpUp; + $protocol = 'scpdownload' if $scpDown; } - if (($scpUp or $scpDown or $sftp) and ($user or $userAny)) { + if ($protocol and $user) { return R('ERR_INCOMPATIBLE_PARAMETERS', - msg => "To grant SCP or SFTP access, first ensure SSH access " - . "is granted to the machine (with the --user you need, or --user-any), then grant with --scpup and/or " - . "--scpdown and/or --sftp, omitting --user/--user-any"); + msg => "To grant access using the $protocol protocol, first ensure SSH access " + . "is granted to the machine (with the --user you need), then grant with --protocol, " + . "omitting --user"); } - $user = '!scpupload' if $scpUp; - $user = '!scpdownload' if $scpDown; - $user = '!sftp' if $sftp; + + # special user when a protocol is specified + $user = "!$protocol" if $protocol; if (not $user and not $userAny) { return R('ERR_MISSING_PARAMETER', - msg => "No user specified, if you want to add this server with any user, use --user-any"); + msg => + "No user specified, if you want to add this server with any user, use --user * (you might need to escape it from your shell)" + ); } if ($portAny and $port) { @@ -51,12 +73,21 @@ sub check { . "along with --port-any, both are contradictory, please check your command"); } - if (not $port and not $portAny) { + # legacy option mapping + $port = '*' if $portAny; + + if (not defined $port) { return R('ERR_MISSING_PARAMETER', - msg => "No port specified, if you want to add this server with any port, use --port-any"); + msg => + "No port specified, if you want to add this server with any port, use --port * (you might need to escape it from your shell)" + ); } - return R('OK', value => {user => $user}); + # now, remap port and user '*' back to undef + undef $user if $user eq '*'; + undef $port if $port eq '*'; + + return R('OK', value => {user => $user, port => $port, protocol => $protocol}); } 1; diff --git a/lib/perl/OVH/Bastion/Plugin/otherProtocol.pm b/lib/perl/OVH/Bastion/Plugin/otherProtocol.pm index 5d080ff9e..267c71755 100644 --- a/lib/perl/OVH/Bastion/Plugin/otherProtocol.pm +++ b/lib/perl/OVH/Bastion/Plugin/otherProtocol.pm @@ -23,7 +23,7 @@ sub has_protocol_access { my $protocol = $params{'protocol'}; if (!$account || !$ipfrom || !$ip || !$protocol || !$user || !$port) { - return R('ERR_MSSING_PARAMETERS', msg => "Missing mandatory parameters for has_protocol_access"); + return R('ERR_MISSING_PARAMETERS', msg => "Missing mandatory parameters for has_protocol_access"); } my $machine = "$user\@$ip:$port"; @@ -52,7 +52,7 @@ sub has_protocol_access { } } - osh_debug("Checking access 2/2 of !rsync to $user of $machine..."); + osh_debug("Checking access 2/2 of !$protocol to $user of $machine..."); $fnret = OVH::Bastion::is_access_granted( account => $account, user => "!$protocol", diff --git a/lib/perl/OVH/Bastion/allowdeny.inc b/lib/perl/OVH/Bastion/allowdeny.inc index 11f7dbdc4..8fc7f7adb 100644 --- a/lib/perl/OVH/Bastion/allowdeny.inc +++ b/lib/perl/OVH/Bastion/allowdeny.inc @@ -92,6 +92,10 @@ sub is_access_way_granted { $exactIpMatch = $exactPortMatch = $exactUserMatch = 1 if $exactMatch; + # normalize '*' to undef + undef $wantedUser if (defined $wantedUser && $wantedUser eq '*'); + undef $wantedPort if (defined $wantedPort && $wantedPort eq '*'); + # 'group', 'account', and 'way' parameters are only useful to, and checked by, get_acl_way() $fnret = OVH::Bastion::get_acl_way(way => $way, account => $account, group => $group); $fnret or return $fnret; @@ -109,6 +113,10 @@ sub is_access_way_granted { my $localForceKey = $entry->{'forceKey'}; my $localForcePassword = $entry->{'forcePassword'}; + # normalize '*' to undef + undef $allowedUser if (defined $allowedUser && $allowedUser eq '*'); + undef $allowedPort if (defined $allowedPort && $allowedPort eq '*'); + osh_debug("checking wanted " . (defined $wantedUser ? $wantedUser : '') . '@' . (defined $wantedIp ? $wantedIp : '') . ':' @@ -951,7 +959,7 @@ sub ssh_test_access_way { $user = $fnret->value; # skip special users and wildcarded-users which are not actual remote users - if ((grep { $user eq $_ } qw{ !scpupload !scpdownload !sftp }) || ($user =~ /[*?]/)) { + if ($user =~ /[*?]|^!/) { return R('OK_MAGIC_USER', msg => "Didn't really test the connection, as the specified user is special"); } diff --git a/lib/perl/OVH/Bastion/allowkeeper.inc b/lib/perl/OVH/Bastion/allowkeeper.inc index b4e0a2820..db4d43718 100644 --- a/lib/perl/OVH/Bastion/allowkeeper.inc +++ b/lib/perl/OVH/Bastion/allowkeeper.inc @@ -301,9 +301,9 @@ sub access_modify { my $action = $params{'action'}; # add or del - my $user = $params{'user'}; # if undef, means a user-wildcard access + my $user = $params{'user'}; # can be undef or '*' for a user-wildcard access my $ip = $params{'ip'}; # can be a single ip or prefix - my $port = $params{'port'}; # if undef, means a port-wildcard access + my $port = $params{'port'}; # can be undef or '*' for a port-wildcard access my $ttl = $params{'ttl'}; my $comment = $params{'comment'}; @@ -341,14 +341,10 @@ sub access_modify { # if undef, default to sudo==1 $sudo //= 1; - # due to how plugins work, sometimes user and port are just '', make them undef in those cases - undef $user if (defined $user && $user eq ''); - undef $port if (defined $port && $port eq ''); - - # allow wildcards, but standardize full wildcard to user=undef - if ($user && $user =~ m{^\*+$}) { - undef $user; - } + # normalize * into undef + # also, due to how plugins work, sometimes user and port are just '', make them undef in those cases + undef $user if (defined $user && ($user eq '*' || $user eq '')); + undef $port if (defined $port && ($port eq '*' || $port eq '')); # check way if ($way eq 'personal') { From cbd945aa0cc6be71f17a77b88b975bcffe856a4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lesimple?= Date: Mon, 12 Aug 2024 11:38:18 +0000 Subject: [PATCH 3/4] enh: plugins: add validate_tuple() so a plugin can validate user@host:port independently --- lib/perl/OVH/Bastion/Plugin.pm | 134 ++++++++++++++++++++++----------- 1 file changed, 90 insertions(+), 44 deletions(-) diff --git a/lib/perl/OVH/Bastion/Plugin.pm b/lib/perl/OVH/Bastion/Plugin.pm index f3320a08a..9cedbc025 100644 --- a/lib/perl/OVH/Bastion/Plugin.pm +++ b/lib/perl/OVH/Bastion/Plugin.pm @@ -21,16 +21,94 @@ our @EXPORT_OK = qw( help ); my $_helptext; sub help { osh_info $_helptext; return 1; } +# when init=1, validate the $user/$host/$ip/$port vars that are exported to the plugins, undef them when invalid. +# called in begin() below, which is called by most plugins. +# +# when init=0, explicitly called by the plugins themselves to change/validate the $user/$host/$ip/$port vars, +# from arguments they might have received themselves. +sub validate_tuple { + my %params = @_; + my $userAllowWildcards = $params{'userAllowWildcards'}; + my $init = $params{'init'}; + my $fnret; + + # user + if (exists $params{'user'} || $init) { + $user = $params{'user'} if !$init; + if (defined $user && $user ne '') { + $fnret = OVH::Bastion::is_valid_remote_user(user => $user, allowWildcards => $userAllowWildcards); + $fnret or osh_exit $fnret; + $user = $fnret->value; + } + else { + undef $user; + } + } + + # port + if (exists $params{'port'} || $init) { + $port = $params{'port'} if !$init; + if (defined $port && $port ne '') { + $fnret = OVH::Bastion::is_valid_port(port => $port); + $fnret or osh_exit $fnret; + $port = $fnret->value; + } + else { + undef $port; + } + } + + if ($init) { + if (defined $ip && $ip ne '') { + $fnret = OVH::Bastion::is_valid_ip(ip => $ip, allowPrefixes => 1); + $fnret or osh_exit $fnret; + $ip = $fnret->value->{'ip'}; + } + else { + # special case due to osh.pl: when host=1.2.3.0/24 then ip='' + # in that case, validate host and set ip to the same + if ($host =~ m{/}) { + $fnret = OVH::Bastion::is_valid_ip(ip => $host, allowPrefixes => 1); + $fnret or osh_exit $fnret; + $ip = $host = $fnret->value->{'ip'}; + } + else { + undef $ip; + } + } + } + elsif (exists $params{'host'}) { + $host = $params{'host'}; + if ($host) { + if ($host !~ m{^[a-zA-Z0-9._/:-]+$}) { + # can be an IP (v4 or v6), hostname, or prefix (with a /) + osh_exit('KO_INVALID_REMOTE_HOST', msg => "Remote host name '$host' seems invalid"); + } + $fnret = OVH::Bastion::get_ip(host => $host); + if (!$fnret) { + osh_exit('KO_INVALID_REMOTE_HOST', msg => "Remote host name '$host' couldn't be resolved"); + } + else { + $ip = $fnret->value->{'ip'}; + } + } + undef $host if $host eq ''; + } + + return R('OK'); +} + sub begin { my %params = @_; - my $options = $params{'options'}; - my $header = $params{'header'}; - my $argv = $params{'argv'}; - my $loadConfig = $params{'loadConfig'}; - my $exitOnSignal = $params{'exitOnSignal'}; - my $helpfunc = $params{'help'}; - my $userAllowWildcards = $params{'userAllowWildcards'}; + my $options = $params{'options'}; + my $header = $params{'header'}; + my $argv = $params{'argv'}; + my $loadConfig = $params{'loadConfig'}; + my $exitOnSignal = $params{'exitOnSignal'}; + my $helpfunc = $params{'help'}; + my $userAllowWildcards = $params{'userAllowWildcards'}; + my $allowUnknownOptions = $params{'allowUnknownOptions'}; $_helptext = $params{'helptext'}; my $fnret; @@ -61,42 +139,8 @@ sub begin { } } - # validate user, ip, port when specified, undef them otherwise (instead of '') - - if (defined $user && $user ne '') { - $fnret = OVH::Bastion::is_valid_remote_user(user => $user, allowWildcards => $userAllowWildcards); - $fnret or osh_exit $fnret; - $user = $fnret->value; - } - else { - undef $user; - } - - if (defined $ip && $ip ne '') { - $fnret = OVH::Bastion::is_valid_ip(ip => $ip, allowPrefixes => 1); - $fnret or osh_exit $fnret; - $ip = $fnret->value->{'ip'}; - } - else { - # special case due to osh.pl: when host=1.2.3.0/24 then ip='' - # in that case, validate host and set ip to the same - if ($host =~ m{/}) { - $fnret = OVH::Bastion::is_valid_ip(ip => $host, allowPrefixes => 1); - $fnret or osh_exit $fnret; - $ip = $host = $fnret->value->{'ip'}; - } - else { - undef $ip; - } - } - - if (defined $port && $port ne '') { - $fnret = OVH::Bastion::is_valid_port(port => $port); - $fnret or osh_exit $fnret; - $port = $fnret->value; - } - - undef $host if $host eq ''; + # validate and untaint user/host/ip/port, exit if problem + validate_tuple(init => 1, userAllowWildcards => $userAllowWildcards); # # Options from extraArgs @@ -107,7 +151,9 @@ sub begin { if (ref $options eq 'HASH' and %$options) { eval { local $SIG{__WARN__} = sub { push @optwarns, shift }; + Getopt::Long::Configure("pass_through") if $allowUnknownOptions; $result = Getopt::Long::GetOptionsFromArray(\@pluginOptions, %$options); + Getopt::Long::Configure("no_pass_through") if $allowUnknownOptions; }; if ($@) { die $@ } } @@ -134,7 +180,7 @@ sub begin { osh_header($header) if $header; - if (!$result) { + if (!$result && !$allowUnknownOptions) { $helpfunc->(); local $" = ", "; osh_exit 'ERR_BAD_OPTIONS', "Error parsing options: @optwarns"; From 1f5cef371bcfc949cdcee810d11ecfdc671ba8ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lesimple?= Date: Mon, 12 Aug 2024 11:37:06 +0000 Subject: [PATCH 4/4] feat: add rsync support to --protocol --- .../group-gatekeeper/groupAddGuestAccess | 65 ++- .../group-gatekeeper/groupDelGuestAccess | 58 +- bin/plugin/open/rsync | 149 +++++ bin/plugin/open/rsync.json | 4 + bin/plugin/open/sftp | 6 +- bin/shell/osh.pl | 11 +- .../rsync.override.rst | 31 + doc/sphinx-plugins-override/scp.override.rst | 2 +- doc/sphinx-plugins-override/sftp.override.rst | 2 +- doc/sphinx/index.rst | 2 +- .../group-aclkeeper/groupAddServer.rst | 58 +- .../group-aclkeeper/groupDelServer.rst | 48 +- .../group-gatekeeper/groupAddGuestAccess.rst | 51 +- .../group-gatekeeper/groupDelGuestAccess.rst | 55 +- doc/sphinx/plugins/open/index.rst | 1 + doc/sphinx/plugins/open/rsync.rst | 35 ++ doc/sphinx/plugins/open/scp.rst | 2 +- doc/sphinx/plugins/open/sftp.rst | 2 +- .../restricted/accountAddPersonalAccess.rst | 51 +- .../restricted/accountDelPersonalAccess.rst | 48 +- .../restricted/selfAddPersonalAccess.rst | 49 +- .../restricted/selfDelPersonalAccess.rst | 48 +- .../{sftp_scp.rst => sftp_scp_rsync.rst} | 60 +- docker/Dockerfile.tester | 2 +- lib/perl/OVH/Bastion/Plugin/ACL.pm | 2 +- lib/perl/OVH/Bastion/Plugin/groupSetRole.pm | 35 +- lib/perl/OVH/Bastion/Plugin/otherProtocol.pm | 9 +- tests/functional/launch_tests_on_instance.sh | 8 +- tests/functional/tests.d/340-selfaccesses.sh | 8 +- .../tests.d/395-mfa-scp-sftp-rsync.sh | 538 ++++++++++++++++++ tests/functional/tests.d/395-mfa-scp-sftp.sh | 445 --------------- 31 files changed, 1049 insertions(+), 836 deletions(-) create mode 100755 bin/plugin/open/rsync create mode 100644 bin/plugin/open/rsync.json create mode 100644 doc/sphinx-plugins-override/rsync.override.rst create mode 100644 doc/sphinx/plugins/open/rsync.rst rename doc/sphinx/using/{sftp_scp.rst => sftp_scp_rsync.rst} (60%) create mode 100644 tests/functional/tests.d/395-mfa-scp-sftp-rsync.sh delete mode 100644 tests/functional/tests.d/395-mfa-scp-sftp.sh diff --git a/bin/plugin/group-gatekeeper/groupAddGuestAccess b/bin/plugin/group-gatekeeper/groupAddGuestAccess index 87ea34a07..270880339 100755 --- a/bin/plugin/group-gatekeeper/groupAddGuestAccess +++ b/bin/plugin/group-gatekeeper/groupAddGuestAccess @@ -15,16 +15,17 @@ my $remainingOptions = OVH::Bastion::Plugin::begin( header => "add access to one server of a group to an account", userAllowWildcards => 1, options => { - "group=s" => \my $group, - "account=s" => \my $account, - "user-any" => \my $userAny, - "port-any" => \my $portAny, - "scpup" => \my $scpUp, - "scpdown" => \my $scpDown, - "sftp" => \my $sftp, - "rsync" => \my $rsync, - "ttl=s" => \my $ttl, - "comment=s" => \my $comment, + "group=s" => \my $group, + "protocol=s" => \my $protocol, + "account=s" => \my $account, + "ttl=s" => \my $ttl, + "comment=s" => \my $comment, + # undocumented/compatibility: + "user-any" => \my $userAny, + "port-any" => \my $portAny, + "scpup" => \my $scpUp, + "scpdown" => \my $scpDown, + "sftp" => \my $sftp, }, helptext => <<'EOF', Add a specific group server access to an account @@ -36,18 +37,22 @@ Usage: --osh SCRIPT_NAME --group GROUP --account ACCOUNT [OPTIONS] to the USER/HOST/PORT tuple you'll specify with the options below. --host HOST|IP|NET/CIDR Host(s) to add access to, either a HOST which will be resolved to an IP immediately, or an IP, or a whole network using the NET/CIDR notation - --user USER Specify which remote user should be allowed to connect as. + --user USER|PATTERN|* Specify which remote user should be allowed to connect as. Globbing characters '*' and '?' are supported, so you can specify a pattern that will be matched against the actual remote user name. - --user-any Synonym of '--user *', allows connecting as any remote user. - --port PORT Remote port allowed to connect to - --port-any Allow access to any remote port - --scpup Allow SCP upload, you--bastion-->server (omit --user in this case) - --scpdown Allow SCP download, you<--bastion--server (omit --user in this case) - --sftp Allow usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) - --rsync Allow usage of rsync through the bastion - --ttl SECONDS|DURATION specify a number of seconds after which the access will automatically expire - --comment '"ANY TEXT"' add a comment alongside this access. + To allow any user, use '--user *' (you might need to escape '*' from your shell) + --port PORT|* Remote port allowed to connect to + To allow any port, use '--port *' (you might need to escape '*' from your shell) + --protocol PROTO Specify that a special protocol should be allowed for this HOST:PORT tuple, note that you + must not specify --user in that case. However, for this protocol to be usable under a given + remote user, access to the USER@HOST:PORT tuple must also be allowed. + PROTO must be one of: + scpup allow SCP upload, you--bastion-->server + scpdown allow SCP download, you<--bastion--server + sftp allow usage of the SFTP subsystem, through the bastion + rsync allow usage of rsync, through the bastion + --ttl SECONDS|DURATION Specify a number of seconds after which the access will automatically expire + --comment '"ANY TEXT"' Add a comment alongside this access. Quote it twice as shown if you're under a shell. If omitted, we'll use the closest preexisting group access' comment as seen in groupListServers This command adds, to an existing bastion account, access to the egress keys of a group, @@ -72,20 +77,21 @@ if (not $ip and $host) { } $fnret = OVH::Bastion::Plugin::ACL::check( - user => $user, - userAny => $userAny, - port => $port, - portAny => $portAny, - scpUp => $scpUp, - scpDown => $scpDown, - sftp => $sftp, - rsync => $rsync, + user => $user, + userAny => $userAny, + port => $port, + portAny => $portAny, + scpUp => $scpUp, + scpDown => $scpDown, + sftp => $sftp, + protocol => $protocol, ); if (!$fnret) { help(); osh_exit($fnret); } $user = $fnret->value->{'user'}; +$port = $fnret->value->{'port'}; if (defined $ttl) { $fnret = OVH::Bastion::is_valid_ttl(ttl => $ttl); @@ -93,15 +99,14 @@ if (defined $ttl) { $ttl = $fnret->value->{'seconds'}; } +# act() will also call preconditions() which will check all the params $fnret = OVH::Bastion::Plugin::groupSetRole::act( account => $account, group => $group, action => 'add', type => 'guest', user => $user, - userAny => $userAny, port => $port, - portAny => $portAny, host => ($ip || $host), ttl => $ttl, comment => $comment, diff --git a/bin/plugin/group-gatekeeper/groupDelGuestAccess b/bin/plugin/group-gatekeeper/groupDelGuestAccess index 2b85da5d0..13505f428 100755 --- a/bin/plugin/group-gatekeeper/groupDelGuestAccess +++ b/bin/plugin/group-gatekeeper/groupDelGuestAccess @@ -15,34 +15,39 @@ my $remainingOptions = OVH::Bastion::Plugin::begin( header => "remove access from one server of a group from an account", userAllowWildcards => 1, options => { - "group=s" => \my $group, - "account=s" => \my $account, - "user-any" => \my $userAny, - "port-any" => \my $portAny, - "scpup" => \my $scpUp, - "scpdown" => \my $scpDown, - "sftp" => \my $sftp, - "rsync" => \my $rsync, + "group=s" => \my $group, + "protocol=s" => \my $protocol, + "account=s" => \my $account, + # undocumented/compatibility: + "user-any" => \my $userAny, + "port-any" => \my $portAny, + "scpup" => \my $scpUp, + "scpdown" => \my $scpDown, + "sftp" => \my $sftp, }, helptext => <<'EOF', Remove a specific group server access from an account Usage: --osh SCRIPT_NAME --group GROUP --account ACCOUNT [OPTIONS] - --group GROUP Specify which group to remove the guest access to ACCOUNT from --account ACCOUNT Bastion account remove the guest access from + --group GROUP Specify which group to remove the guest access to ACCOUNT from --host HOST|IP|NET/CIDR Host(s) to remove access from, either a HOST which will be resolved to an IP immediately, or an IP, or a whole network using the NET/CIDR notation - --user USER Specify which remote user was allowed to connect as. + --user USER|PATTERN|* Specify which remote user was allowed to connect as. Globbing characters '*' and '?' are supported, so you can specify a pattern that will be matched against the actual remote user name. - --user-any Synonym of '--user *', allowed connecting as any remote user. - --port PORT Remote port that was allowed to connect to - --port-any Use when access was allowed to any remote port - --scpup Remove SCP upload right, you--bastion-->server (omit --user in this case) - --scpdown Remove SCP download right, you<--bastion--server (omit --user in this case) - --sftp Remove usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) - --rsync Remove usage of rsync through the bastion + If any user was allowed, use '--user *' (you might need to escape '*' from your shell) + --port PORT|* Remote port that was allowed to connect to + If any user was allowed, use '--port *' (you might need to escape '*' from your shell) + --protocol PROTO Specify that a special protocol was allowed for this HOST:PORT tuple, note that you + must not specify --user in that case. However, for this protocol to be usable under a given + remote user, access to the USER@HOST:PORT tuple must also be allowed. + PROTO must be one of: + scpup allow SCP upload, you--bastion-->server + scpdown allow SCP download, you<--bastion--server + sftp allow usage of the SFTP subsystem, through the bastion + rsync allow usage of rsync, through the bastion This command removes, from an existing bastion account, access to a given server, using the egress keys of the group. The list of such servers is given by ``groupListGuestAccesses`` @@ -65,20 +70,21 @@ if (not $ip and $host) { } $fnret = OVH::Bastion::Plugin::ACL::check( - user => $user, - userAny => $userAny, - port => $port, - portAny => $portAny, - scpUp => $scpUp, - scpDown => $scpDown, - sftp => $sftp, - rsync => $rsync, + user => $user, + userAny => $userAny, + port => $port, + portAny => $portAny, + scpUp => $scpUp, + scpDown => $scpDown, + sftp => $sftp, + protocol => $protocol, ); if (!$fnret) { help(); osh_exit($fnret); } $user = $fnret->value->{'user'}; +$port = $fnret->value->{'port'}; $fnret = OVH::Bastion::Plugin::groupSetRole::act( account => $account, @@ -86,9 +92,7 @@ $fnret = OVH::Bastion::Plugin::groupSetRole::act( action => 'del', type => 'guest', user => $user, - userAny => $userAny, port => $port, - portAny => $portAny, host => ($ip || $host), sudo => 0, silentoverride => 0, diff --git a/bin/plugin/open/rsync b/bin/plugin/open/rsync new file mode 100755 index 000000000..e3deb9898 --- /dev/null +++ b/bin/plugin/open/rsync @@ -0,0 +1,149 @@ +#! /usr/bin/env perl +# vim: set filetype=perl ts=4 sw=4 sts=4 et: +use common::sense; + +use File::Basename; +use lib dirname(__FILE__) . '/../../../lib/perl'; +use OVH::Result; +use OVH::Bastion; +use OVH::Bastion::Plugin qw( :DEFAULT ); +use OVH::Bastion::Plugin::otherProtocol; +# stdout is used by rsync, so ensure we output everything through stderr +local $ENV{'FORCE_STDERR'} = 1; + +# don't output fancy stuff, this can get digested by rsync and we get garbage output +local $ENV{'PLUGIN_QUIET'} = 1; + +# rsync will craft a command-line for our plugin like this one (to upload): +# -l REMOTE_USER REMOTE_HOST rsync --server -vlogDtpre.iLsfxC . REMOTE_DIR +# and like this (to download) +# -l REMOTE_USER REMOTE_HOST rsync --server --sender -vlogDtpre.iLsfxC . REMOTE_DIR +# +# we parse the REMOTE_USER thanks to "options" below, and the remaining of the command-line +# is left untouched thanks to allowUnknownOptions==1 +my $remainingOptions = OVH::Bastion::Plugin::begin( + argv => \@ARGV, + header => undef, + allowUnknownOptions => 1, + options => { + "l=s" => \my $opt_user, + }, + helptext => <<'EOF', +rsync passthrough using the bastion + +Usage examples: + + rsync -va --rsh "ssh -T BASTION_USER@BASTION_HOST -p BASTION_PORT -- --osh rsync --" /srcdir remoteuser@remotehost:/dest/ + rsync -va --rsh "ssh -T BASTION_USER@BASTION_HOST -p BASTION_PORT -- --osh rsync --" remoteuser@remotehost:/srcdir /dest/ + +Note that you'll need to be specifically granted to use rsync on the remote server, +in addition to being granted normal SSH access to it. +EOF +); + +# validate $opt_user and export it as $user +OVH::Bastion::Plugin::validate_tuple(user => $opt_user); + +# validate host passed by rsync and export it as $host/$ip +if (ref $remainingOptions eq 'ARRAY' && @$remainingOptions) { + my $opt_host = shift(@$remainingOptions); + OVH::Bastion::Plugin::validate_tuple(host => $opt_host); +} +else { + osh_exit 'ERR_INVALID_COMMAND', + "No host found, this plugin should be called by rsync.\nUse \`--osh rsync --help\` for more information."; +} + +if (ref $remainingOptions eq 'ARRAY' && @$remainingOptions && $remainingOptions->[0] eq 'rsync') { + ; # ok, we'll pass all the remaining options as options to the remote server, which will start rsync +} +else { + osh_exit 'ERR_INVALID_COMMAND', + "This plugin should be called by rsync.\nUse \`--osh rsync --help\` for more information."; +} + +# +# code +# +my $fnret; + +if (not $host) { + help(); + osh_exit; +} + +if (not $ip) { + # note that the calling-side rsync will not passthrough this exit code, but most probably "1" instead. + osh_exit 'ERR_HOST_NOT_FOUND', "Sorry, couldn't resolve the host you specified ('$host'), aborting."; +} + +$port ||= 22; # rsync uses 22 if not specified, so we need to test access to that port and not any port (aka undef) +$user ||= $self; # same for user + +$fnret = OVH::Bastion::Plugin::otherProtocol::has_protocol_access( + account => $self, + user => $user, + ip => $ip, + port => $port, + protocol => 'rsync', +); +$fnret or osh_exit($fnret); + +my $machine = $fnret->value->{'machine'}; +my @keys = @{$fnret->value->{'keys'} || []}; +my $mfaRequired = $fnret->value->{'mfaRequired'}; + +# if we have an mfaRequired here, we have a problem because as we're run by rsync on the client side, +# it's too late to ask for any interactive user input now, as we don't have access to the terminal: +# we can only bail out if MFA was required for this host/group. +if ($mfaRequired) { + # is this account exempt from MFA? + my $hasMfaPasswordBypass = + OVH::Bastion::is_user_in_group(account => $sysself, group => OVH::Bastion::MFA_PASSWORD_BYPASS_GROUP); + my $hasMfaTOTPBypass = + OVH::Bastion::is_user_in_group(account => $sysself, group => OVH::Bastion::MFA_TOTP_BYPASS_GROUP); + + if ($mfaRequired eq 'password' && $hasMfaPasswordBypass) { + print STDERR "This host requires password MFA but your account has password MFA bypass, allowing...\n"; + } + elsif ($mfaRequired eq 'totp' && $hasMfaTOTPBypass) { + print STDERR "This host requires TOTP MFA but your account has TOTP MFA bypass, allowing...\n"; + } + elsif ($mfaRequired eq 'any' && $hasMfaPasswordBypass && $hasMfaTOTPBypass) { + print STDERR "This host requires MFA but your account has MFA bypass, allowing...\n"; + } + else { + osh_exit('KO_MFA_REQUIRED', "MFA is required for this host, which is not supported by rsync."); + } +} + +# now build the command + +my @cmd = qw{ ssh -x -oForwardAgent=no -oPermitLocalCommand=no -oClearAllForwardings=yes }; +push @cmd, ('-p', $port) if $port; +push @cmd, ('-l', $user) if $user; + +foreach my $key (@keys) { + push @cmd, ('-i', $key); +} + +push @cmd, "--", $ip, @$remainingOptions; + +print STDERR ">>> Hello $self, running rsync through the bastion on $machine...\n"; + +$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 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, rsync exited with return code " . $fnret->value->{'sysret'} . ".\n"; +} + +# don't use osh_exit() to avoid getting a footer +exit OVH::Bastion::EXIT_OK; diff --git a/bin/plugin/open/rsync.json b/bin/plugin/open/rsync.json new file mode 100644 index 000000000..64855ce98 --- /dev/null +++ b/bin/plugin/open/rsync.json @@ -0,0 +1,4 @@ +{ + "stealth_stdout": true, + "force_stderr": true +} diff --git a/bin/plugin/open/sftp b/bin/plugin/open/sftp index 9eb2520e7..16bec9073 100755 --- a/bin/plugin/open/sftp +++ b/bin/plugin/open/sftp @@ -13,10 +13,10 @@ use OVH::Bastion; use OVH::Bastion::Plugin qw( :DEFAULT ); use OVH::Bastion::Plugin::otherProtocol; -# stdout is used by scp, so ensure we output everything through stderr +# stdout is used by sftp, so ensure we output everything through stderr local $ENV{'FORCE_STDERR'} = 1; -# don't output fancy stuff, this can get digested by scp and we get garbage output +# don't output fancy stuff, this can get digested by sftp and we get garbage output local $ENV{'PLUGIN_QUIET'} = 1; my $remainingOptions = OVH::Bastion::Plugin::begin( @@ -278,7 +278,7 @@ if (not $ip) { osh_exit 'ERR_HOST_NOT_FOUND', "Sorry, couldn't resolve the host you specified ('$host'), aborting."; } -$port ||= 22; # scp uses 22 if not specified, so we need to test access to that port and not any port (aka undef) +$port ||= 22; # sftp uses 22 if not specified, so we need to test access to that port and not any port (aka undef) $user ||= $self; # same for user $fnret = OVH::Bastion::Plugin::otherProtocol::has_protocol_access( diff --git a/bin/shell/osh.pl b/bin/shell/osh.pl index 81e67951e..3c51fb1e8 100755 --- a/bin/shell/osh.pl +++ b/bin/shell/osh.pl @@ -1025,7 +1025,7 @@ sub main_exit { # do it themselves, and as they're accessing a remote asset, JIT MFA should apply to them too) my $pluginJitMfa = OVH::Bastion::plugin_config(plugin => $osh_command, key => "jit_mfa")->value; if ($pluginJitMfa) { - $fnret = do_plugin_jit_mfa(pluginJitMfa => $pluginJitMfa); + $fnret = do_plugin_jit_mfa(); # do_plugin_jit_mfa exits if needed, but just in case... main_exit(OVH::Bastion::EXIT_MFA_FAILED, "jit_mfa_failed", $fnret->msg) if !$fnret; } @@ -1796,10 +1796,13 @@ sub do_jit_mfa { return R('OK_VALIDATED', value => {mfaInfo => \%mfaInfo}); } +# check whether this plugin wants us to trigger a JIT MFA check depending on the +# specified user/host/ip, if this is configured in one of the matching bastion groups +# we are a part of (plugins such as sftp or scp will require us to do this, as they can't +# do it themselves, and as they're accessing a remote asset, JIT MFA should apply to them too) +# +# this func may exit sub do_plugin_jit_mfa { - my %params = @_; - my $pluginJitMfa = $params{'pluginJitMfa'}; ### XXX NOT USED - my $localfnret; if (!$host) { diff --git a/doc/sphinx-plugins-override/rsync.override.rst b/doc/sphinx-plugins-override/rsync.override.rst new file mode 100644 index 000000000..a95132002 --- /dev/null +++ b/doc/sphinx-plugins-override/rsync.override.rst @@ -0,0 +1,31 @@ +Transfer files from/to remote servers using rsync through the bastion +===================================================================== + +.. note:: + + This plugin should not be called manually, but passed as the --rsh option to rsync. + +Usage examples +-------------- + +To transfer all files from ``/srcdir`` to the ``remotehost``'s ``/dest/`` directory: + +.. code-block: none + + rsync -va --rsh "ssh -T BASTION_USER@BASTION_HOST -p BASTION_PORT -- --osh rsync --" /srcdir remoteuser@remotehost:/dest/ + +The ``-va`` options are just examples, you can use any option of ``rsync`` that you see fit. + +To transfer all remote files from ``/srcdir`` to the local ``/dest`` directory: + +.. code-block: none + + rsync -va --rsh "ssh -T BASTION_USER@BASTION_HOST -p BASTION_PORT -- --osh rsync --" remoteuser@remotehost:/srcdir /dest/ + +Please note that you need to be granted for uploading or downloading files +with ``rsync`` to/from the remote host, in addition to having the right to SSH to it. +For a group, the right should be added with ``--protocol rsync`` of the :doc:`/plugins/group-aclkeeper/groupAddServer` command. +For a personal access, the right should be added with ``--protocol rsync`` of the :doc:`/plugins/restricted/selfAddPersonalAccess` command. +:doc:`/plugins/open/selfListEgressKeys` + +You'll find more information and examples in :doc:`/using/sftp_scp_rsync`. diff --git a/doc/sphinx-plugins-override/scp.override.rst b/doc/sphinx-plugins-override/scp.override.rst index 786bec2b9..f1faf168e 100644 --- a/doc/sphinx-plugins-override/scp.override.rst +++ b/doc/sphinx-plugins-override/scp.override.rst @@ -25,4 +25,4 @@ with scp to/from the remote host, in addition to having the right to SSH to it. For a group, the right should be added with ``--scpup``/``--scpdown`` of the :doc:`/plugins/group-aclkeeper/groupAddServer` command. For a personal access, the right should be added with ``--scpup``/``--scpdown`` of the :doc:`/plugins/restricted/selfAddPersonalAccess` command. -You'll find more information and examples in :doc:`/using/sftp_scp`. +You'll find more information and examples in :doc:`/using/sftp_scp_rsync`. diff --git a/doc/sphinx-plugins-override/sftp.override.rst b/doc/sphinx-plugins-override/sftp.override.rst index 22daf4d37..8d5a7ae6c 100644 --- a/doc/sphinx-plugins-override/sftp.override.rst +++ b/doc/sphinx-plugins-override/sftp.override.rst @@ -28,4 +28,4 @@ For a group, the right should be added with ``--sftp`` of the :doc:`/plugins/gro For a personal access, the right should be added with ``--sftp`` of the :doc:`/plugins/restricted/selfAddPersonalAccess` command. :doc:`/plugins/open/selfListEgressKeys` -You'll find more information and examples in :doc:`/using/sftp_scp`. +You'll find more information and examples in :doc:`/using/sftp_scp_rsync`. diff --git a/doc/sphinx/index.rst b/doc/sphinx/index.rst index 0160c2115..9d7f32518 100644 --- a/doc/sphinx/index.rst +++ b/doc/sphinx/index.rst @@ -66,7 +66,7 @@ The unavoidable and iconic FAQ is also available under the **PRESENTATION** sect using/basics/index using/piv - using/sftp_scp + using/sftp_scp_rsync using/http_proxy using/api using/specific_ssh_clients_tutorials/index diff --git a/doc/sphinx/plugins/group-aclkeeper/groupAddServer.rst b/doc/sphinx/plugins/group-aclkeeper/groupAddServer.rst index b26080659..ccd600b13 100644 --- a/doc/sphinx/plugins/group-aclkeeper/groupAddServer.rst +++ b/doc/sphinx/plugins/group-aclkeeper/groupAddServer.rst @@ -9,7 +9,7 @@ Add an IP or IP block to a group's servers list .. admonition:: usage :class: cmdusage - --osh groupAddServer --group GROUP [OPTIONS] + --osh groupAddServer --group GROUP --host HOST --user USER|* --port PORT|* [OPTIONS] .. program:: groupAddServer @@ -23,40 +23,23 @@ Add an IP or IP block to a group's servers list Host(s) to add access to, either a HOST which will be resolved to an IP immediately, or an IP, or a whole network using the NET/CIDR notation -.. option:: --user USER - - Specify which remote user should be allowed to connect as. - + --user USER|PATTERN|* Specify which remote user should be allowed to connect as. Globbing characters '*' and '?' are supported, so you can specify a pattern that will be matched against the actual remote user name. -.. option:: --user-any - - Synonym of '--user *', allows connecting as any remote user. - -.. option:: --port PORT - - Remote port allowed to connect to - -.. option:: --port-any - - Allow access to any remote port - -.. option:: --scpup - - Allow SCP upload, you--bastion-->server (omit --user in this case) - -.. option:: --scpdown - - Allow SCP download, you<--bastion--server (omit --user in this case) - -.. option:: --sftp - - Allow usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) - -.. option:: --rsync - - Allow usage of rsync through the bastion - + To allow any user, use '--user *' (you might need to escape '*' from your shell) + --port PORT|* Remote port allowed to connect to + To allow any port, use '--port *' (you might need to escape '*' from your shell) +.. option:: --protocol PROTO + + Specify that a special protocol should be allowed for this HOST:PORT tuple, note that you + + must not specify --user in that case. However, for this protocol to be usable under a given + remote user, access to the USER@HOST:PORT tuple must also be allowed. + PROTO must be one of: + scpup allow SCP upload, you--bastion-->server + scpdown allow SCP download, you<--bastion--server + sftp allow usage of the SFTP subsystem, through the bastion + rsync allow usage of rsync, through the bastion .. option:: --force Don't try the ssh connection, just add the host to the group blindly @@ -80,5 +63,10 @@ Add an IP or IP block to a group's servers list Examples:: - --osh groupAddServer --group grp1 --host 203.0.113.0/24 --user-any --port-any --force --comment '"a whole network"' - --osh groupAddServer --group grp2 --host srv1.example.org --user root --port 22 + --osh groupAddServer --group grp1 --host 203.0.113.0/24 --user '*' --port '*' --force --ttl 1d12h --comment '"a whole network"' + --osh groupAddServer --group grp2 --host srv1.example.org --user data --port 22 + --osh groupAddServer --group grp2 --host srv1.example.org --user file --port 22 + +Example to allow using sftp to srv1.example.org using remote user 'data' or 'file', in addition to the above commands:: + + --osh groupAddServer --group grp2 --host srv1.example.org --port 22 --protocol sftp diff --git a/doc/sphinx/plugins/group-aclkeeper/groupDelServer.rst b/doc/sphinx/plugins/group-aclkeeper/groupDelServer.rst index f3a15309e..10ca738fc 100644 --- a/doc/sphinx/plugins/group-aclkeeper/groupDelServer.rst +++ b/doc/sphinx/plugins/group-aclkeeper/groupDelServer.rst @@ -9,7 +9,7 @@ Remove an IP or IP block from a group's server list .. admonition:: usage :class: cmdusage - --osh groupDelServer --group GROUP --host HOST [OPTIONS] + --osh groupDelServer --group GROUP --host HOST --user USER --port PORT [OPTIONS] .. program:: groupDelServer @@ -23,40 +23,22 @@ Remove an IP or IP block from a group's server list Host(s) to remove access from, either a HOST which will be resolved to an IP immediately, or an IP, or a whole network using the NET/CIDR notation -.. option:: --user USER - - Specify which remote user was allowed to connect as. - + --user USER|PATTERN|* Specify which remote user was allowed to connect as. Globbing characters '*' and '?' are supported, so you can specify a pattern that will be matched against the actual remote user name. -.. option:: --user-any - - Synonym of '--user *', allowed connecting as any remote user. - -.. option:: --port PORT - - Remote port that was allowed to connect to - -.. option:: --port-any - - Use when access was allowed to any remote port - -.. option:: --scpup - - Remove SCP upload right, you--bastion-->server (omit --user in this case) - -.. option:: --scpdown - - Remove SCP download right, you<--bastion--server (omit --user in this case) - -.. option:: --sftp - - Remove usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) - -.. option:: --rsync - - Remove usage of rsync through the bastion - + If any user was allowed, use '--user *' (you might need to escape '*' from your shell) + --port PORT|* Remote port that was allowed to connect to + If any port was allowed, use '--port *' (you might need to escape '*' from your shell) +.. option:: --protocol PROTO + + Specify that a special protocol allowance should be removed from this HOST:PORT tuple, note that you + + must not specify --user in that case. + PROTO must be one of: + scpup allow SCP upload, you--bastion-->server + scpdown allow SCP download, you<--bastion--server + sftp allow usage of the SFTP subsystem, through the bastion + rsync allow usage of rsync, through the bastion This command adds, to an existing bastion account, access to a given server, using the egress keys of the group. The list of eligible servers for a given group is given by ``groupListServers`` diff --git a/doc/sphinx/plugins/group-gatekeeper/groupAddGuestAccess.rst b/doc/sphinx/plugins/group-gatekeeper/groupAddGuestAccess.rst index bcd87b816..cf1e9f0ef 100644 --- a/doc/sphinx/plugins/group-gatekeeper/groupAddGuestAccess.rst +++ b/doc/sphinx/plugins/group-gatekeeper/groupAddGuestAccess.rst @@ -28,47 +28,30 @@ Add a specific group server access to an account Host(s) to add access to, either a HOST which will be resolved to an IP immediately, or an IP, or a whole network using the NET/CIDR notation -.. option:: --user USER - - Specify which remote user should be allowed to connect as. - + --user USER|PATTERN|* Specify which remote user should be allowed to connect as. Globbing characters '*' and '?' are supported, so you can specify a pattern that will be matched against the actual remote user name. -.. option:: --user-any - - Synonym of '--user *', allows connecting as any remote user. - -.. option:: --port PORT - - Remote port allowed to connect to - -.. option:: --port-any - - Allow access to any remote port - -.. option:: --scpup - - Allow SCP upload, you--bastion-->server (omit --user in this case) - -.. option:: --scpdown - - Allow SCP download, you<--bastion--server (omit --user in this case) - -.. option:: --sftp - - Allow usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) - -.. option:: --rsync - - Allow usage of rsync through the bastion - + To allow any user, use '--user *' (you might need to escape '*' from your shell) + --port PORT|* Remote port allowed to connect to + To allow any port, use '--port *' (you might need to escape '*' from your shell) +.. option:: --protocol PROTO + + Specify that a special protocol should be allowed for this HOST:PORT tuple, note that you + + must not specify --user in that case. However, for this protocol to be usable under a given + remote user, access to the USER@HOST:PORT tuple must also be allowed. + PROTO must be one of: + scpup allow SCP upload, you--bastion-->server + scpdown allow SCP download, you<--bastion--server + sftp allow usage of the SFTP subsystem, through the bastion + rsync allow usage of rsync, through the bastion .. option:: --ttl SECONDS|DURATION - specify a number of seconds after which the access will automatically expire + Specify a number of seconds after which the access will automatically expire .. option:: --comment '"ANY TEXT"' - add a comment alongside this access. + Add a comment alongside this access. Quote it twice as shown if you're under a shell. If omitted, we'll use the closest preexisting group access' comment as seen in groupListServers diff --git a/doc/sphinx/plugins/group-gatekeeper/groupDelGuestAccess.rst b/doc/sphinx/plugins/group-gatekeeper/groupDelGuestAccess.rst index 27c6c967b..f9f373fb6 100644 --- a/doc/sphinx/plugins/group-gatekeeper/groupDelGuestAccess.rst +++ b/doc/sphinx/plugins/group-gatekeeper/groupDelGuestAccess.rst @@ -14,53 +14,36 @@ Remove a specific group server access from an account .. program:: groupDelGuestAccess -.. option:: --group GROUP - - Specify which group to remove the guest access to ACCOUNT from - .. option:: --account ACCOUNT Bastion account remove the guest access from +.. option:: --group GROUP + + Specify which group to remove the guest access to ACCOUNT from + .. option:: --host HOST|IP|NET/CIDR Host(s) to remove access from, either a HOST which will be resolved to an IP immediately, or an IP, or a whole network using the NET/CIDR notation -.. option:: --user USER - - Specify which remote user was allowed to connect as. - + --user USER|PATTERN|* Specify which remote user was allowed to connect as. Globbing characters '*' and '?' are supported, so you can specify a pattern that will be matched against the actual remote user name. -.. option:: --user-any - - Synonym of '--user *', allowed connecting as any remote user. - -.. option:: --port PORT - - Remote port that was allowed to connect to - -.. option:: --port-any - - Use when access was allowed to any remote port - -.. option:: --scpup - - Remove SCP upload right, you--bastion-->server (omit --user in this case) - -.. option:: --scpdown - - Remove SCP download right, you<--bastion--server (omit --user in this case) - -.. option:: --sftp - - Remove usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) - -.. option:: --rsync - - Remove usage of rsync through the bastion - + If any user was allowed, use '--user *' (you might need to escape '*' from your shell) + --port PORT|* Remote port that was allowed to connect to + If any user was allowed, use '--port *' (you might need to escape '*' from your shell) +.. option:: --protocol PROTO + + Specify that a special protocol was allowed for this HOST:PORT tuple, note that you + + must not specify --user in that case. However, for this protocol to be usable under a given + remote user, access to the USER@HOST:PORT tuple must also be allowed. + PROTO must be one of: + scpup allow SCP upload, you--bastion-->server + scpdown allow SCP download, you<--bastion--server + sftp allow usage of the SFTP subsystem, through the bastion + rsync allow usage of rsync, through the bastion This command removes, from an existing bastion account, access to a given server, using the egress keys of the group. The list of such servers is given by ``groupListGuestAccesses`` diff --git a/doc/sphinx/plugins/open/index.rst b/doc/sphinx/plugins/open/index.rst index 3ef7dd6f3..4d571709b 100644 --- a/doc/sphinx/plugins/open/index.rst +++ b/doc/sphinx/plugins/open/index.rst @@ -17,6 +17,7 @@ open plugins mtr nc ping + rsync scp selfAddIngressKey selfDelIngressKey diff --git a/doc/sphinx/plugins/open/rsync.rst b/doc/sphinx/plugins/open/rsync.rst new file mode 100644 index 000000000..4cdf17cdc --- /dev/null +++ b/doc/sphinx/plugins/open/rsync.rst @@ -0,0 +1,35 @@ +====== +rsync +====== + +Transfer files from/to remote servers using rsync through the bastion +===================================================================== + +.. note:: + + This plugin should not be called manually, but passed as the --rsh option to rsync. + +Usage examples +-------------- + +To transfer all files from ``/srcdir`` to the ``remotehost``'s ``/dest/`` directory: + +.. code-block: none + + rsync -va --rsh "ssh -T BASTION_USER@BASTION_HOST -p BASTION_PORT -- --osh rsync --" /srcdir remoteuser@remotehost:/dest/ + +The ``-va`` options are just examples, you can use any option of ``rsync`` that you see fit. + +To transfer all remote files from ``/srcdir`` to the local ``/dest`` directory: + +.. code-block: none + + rsync -va --rsh "ssh -T BASTION_USER@BASTION_HOST -p BASTION_PORT -- --osh rsync --" remoteuser@remotehost:/srcdir /dest/ + +Please note that you need to be granted for uploading or downloading files +with ``rsync`` to/from the remote host, in addition to having the right to SSH to it. +For a group, the right should be added with ``--protocol rsync`` of the :doc:`/plugins/group-aclkeeper/groupAddServer` command. +For a personal access, the right should be added with ``--protocol rsync`` of the :doc:`/plugins/restricted/selfAddPersonalAccess` command. +:doc:`/plugins/open/selfListEgressKeys` + +You'll find more information and examples in :doc:`/using/sftp_scp_rsync`. diff --git a/doc/sphinx/plugins/open/scp.rst b/doc/sphinx/plugins/open/scp.rst index e91b21c48..14a4d80a4 100644 --- a/doc/sphinx/plugins/open/scp.rst +++ b/doc/sphinx/plugins/open/scp.rst @@ -29,4 +29,4 @@ with scp to/from the remote host, in addition to having the right to SSH to it. For a group, the right should be added with ``--scpup``/``--scpdown`` of the :doc:`/plugins/group-aclkeeper/groupAddServer` command. For a personal access, the right should be added with ``--scpup``/``--scpdown`` of the :doc:`/plugins/restricted/selfAddPersonalAccess` command. -You'll find more information and examples in :doc:`/using/sftp_scp`. +You'll find more information and examples in :doc:`/using/sftp_scp_rsync`. diff --git a/doc/sphinx/plugins/open/sftp.rst b/doc/sphinx/plugins/open/sftp.rst index 72b658b2d..6711b8396 100644 --- a/doc/sphinx/plugins/open/sftp.rst +++ b/doc/sphinx/plugins/open/sftp.rst @@ -32,4 +32,4 @@ For a group, the right should be added with ``--sftp`` of the :doc:`/plugins/gro For a personal access, the right should be added with ``--sftp`` of the :doc:`/plugins/restricted/selfAddPersonalAccess` command. :doc:`/plugins/open/selfListEgressKeys` -You'll find more information and examples in :doc:`/using/sftp_scp`. +You'll find more information and examples in :doc:`/using/sftp_scp_rsync`. diff --git a/doc/sphinx/plugins/restricted/accountAddPersonalAccess.rst b/doc/sphinx/plugins/restricted/accountAddPersonalAccess.rst index fe285dacc..bd9205cb3 100644 --- a/doc/sphinx/plugins/restricted/accountAddPersonalAccess.rst +++ b/doc/sphinx/plugins/restricted/accountAddPersonalAccess.rst @@ -9,7 +9,7 @@ Add a personal server access to an account .. admonition:: usage :class: cmdusage - --osh accountAddPersonalAccess --account ACCOUNT --host HOST [OPTIONS] + --osh accountAddPersonalAccess --account ACCOUNT --host HOST --user USER --port PORT [OPTIONS] .. program:: accountAddPersonalAccess @@ -23,43 +23,26 @@ Add a personal server access to an account Host(s) to add access to, either a HOST which will be resolved to an IP immediately, or an IP, or a whole network using the NET/CIDR notation -.. option:: --user USER - - Specify which remote user should be allowed to connect as. - + --user USER|PATTERN|* Specify which remote user should be allowed to connect as. Globbing characters '*' and '?' are supported, so you can specify a pattern that will be matched against the actual remote user name. -.. option:: --user-any - - Synonym of '--user *', allows connecting as any remote user. - -.. option:: --port PORT - - Remote port allowed to connect to - -.. option:: --port-any - - Allow access to any remote port - -.. option:: --scpup - - Allow SCP upload, you--bastion-->server (omit --user in this case) - -.. option:: --scpdown - - Allow SCP download, you<--bastion--server (omit --user in this case) - -.. option:: --sftp - - Allow usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) - -.. option:: --rsync - - Allow usage of rsync through the bastion - + To allow any user, use '--user *' (you might need to escape '*' from your shell) + --port PORT|* Remote port allowed to connect to + To allow any port, use '--port *' (you might need to escape '*' from your shell) +.. option:: --protocol PROTO + + Specify that a special protocol should be allowed for this HOST:PORT tuple, note that you + + must not specify --user in that case. However, for this protocol to be usable under a given + remote user, access to the USER@HOST:PORT tuple must also be allowed. + PROTO must be one of: + scpup allow SCP upload, you--bastion-->server + scpdown allow SCP download, you<--bastion--server + sftp allow usage of the SFTP subsystem, through the bastion + rsync allow usage of rsync, through the bastion .. option:: --force-key FINGERPRINT - Only use the key with the specified fingerprint to connect to the server (cf selfListEgressKeys) + Only use the key with the specified fingerprint to connect to the server (cf accountListEgressKeys) .. option:: --force-password HASH diff --git a/doc/sphinx/plugins/restricted/accountDelPersonalAccess.rst b/doc/sphinx/plugins/restricted/accountDelPersonalAccess.rst index 9d352ff20..7fc5e63d0 100644 --- a/doc/sphinx/plugins/restricted/accountDelPersonalAccess.rst +++ b/doc/sphinx/plugins/restricted/accountDelPersonalAccess.rst @@ -9,7 +9,7 @@ Remove a personal server access from an account .. admonition:: usage :class: cmdusage - --osh accountDelPersonalAccess --account ACCOUNT --host HOST [OPTIONS] + --osh accountDelPersonalAccess --account ACCOUNT --host HOST --user USER --port PORT [OPTIONS] .. program:: accountDelPersonalAccess @@ -23,37 +23,19 @@ Remove a personal server access from an account Host(s) to remove access from, either a HOST which will be resolved to an IP immediately, or an IP, or a whole network using the NET/CIDR notation -.. option:: --user USER - - Specify which remote user was allowed to connect as. - + --user USER|PATTERN|* Specify which remote user was allowed to connect as. Globbing characters '*' and '?' are supported, so you can specify a pattern that will be matched against the actual remote user name. -.. option:: --user-any - - Synonym of '--user *', allowed connecting as any remote user. - -.. option:: --port PORT - - Remote port that was allowed to connect to - -.. option:: --port-any - - Use when access was allowed to any remote port - -.. option:: --scpup - - Remove SCP upload right, you--bastion-->server (omit --user in this case) - -.. option:: --scpdown - - Remove SCP download right, you<--bastion--server (omit --user in this case) - -.. option:: --sftp - - Remove usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) - -.. option:: --rsync - - Remove usage of rsync through the bastion - + If any user was allowed, use '--user *' (you might need to escape '*' from your shell) + --port PORT|* Remote port that was allowed to connect to + If any port was allowed, use '--port *' (you might need to escape '*' from your shell) +.. option:: --protocol PROTO + + Specify that a special protocol allowance should be removed from this HOST:PORT tuple, note that you + + must not specify --user in that case. + PROTO must be one of: + scpup allow SCP upload, you--bastion-->server + scpdown allow SCP download, you<--bastion--server + sftp allow usage of the SFTP subsystem, through the bastion + rsync allow usage of rsync, through the bastion diff --git a/doc/sphinx/plugins/restricted/selfAddPersonalAccess.rst b/doc/sphinx/plugins/restricted/selfAddPersonalAccess.rst index 433872b23..68f72bd89 100644 --- a/doc/sphinx/plugins/restricted/selfAddPersonalAccess.rst +++ b/doc/sphinx/plugins/restricted/selfAddPersonalAccess.rst @@ -9,7 +9,7 @@ Add a personal server access to your account .. admonition:: usage :class: cmdusage - --osh selfAddPersonalAccess --host HOST [OPTIONS] + --osh selfAddPersonalAccess --host HOST --user USER --port PORT [OPTIONS] .. program:: selfAddPersonalAccess @@ -19,40 +19,23 @@ Add a personal server access to your account Host(s) to add access to, either a HOST which will be resolved to an IP immediately, or an IP, or a whole network using the NET/CIDR notation -.. option:: --user USER - - Specify which remote user should be allowed to connect as. - + --user USER|PATTERN|* Specify which remote user should be allowed to connect as. Globbing characters '*' and '?' are supported, so you can specify a pattern that will be matched against the actual remote user name. -.. option:: --user-any - - Synonym of '--user *', allows connecting as any remote user. - -.. option:: --port PORT - - Remote port allowed to connect to - -.. option:: --port-any - - Allow access to any remote port - -.. option:: --scpup - - Allow SCP upload, you--bastion-->server (omit --user in this case) - -.. option:: --scpdown - - Allow SCP download, you<--bastion--server (omit --user in this case) - -.. option:: --sftp - - Allow usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) - -.. option:: --rsync - - Allow usage of rsync through the bastion - + To allow any user, use '--user *' (you might need to escape '*' from your shell) + --port PORT|* Remote port allowed to connect to + To allow any port, use '--port *' (you might need to escape '*' from your shell) +.. option:: --protocol PROTO + + Specify that a special protocol should be allowed for this HOST:PORT tuple, note that you + + must not specify --user in that case. However, for this protocol to be usable under a given + remote user, access to the USER@HOST:PORT tuple must also be allowed. + PROTO must be one of: + scpup allow SCP upload, you--bastion-->server + scpdown allow SCP download, you<--bastion--server + sftp allow usage of the SFTP subsystem, through the bastion + rsync allow usage of rsync, through the bastion .. option:: --force Add the access without checking that the public SSH key is properly installed remotely diff --git a/doc/sphinx/plugins/restricted/selfDelPersonalAccess.rst b/doc/sphinx/plugins/restricted/selfDelPersonalAccess.rst index 2a0581f25..c8666a1cd 100644 --- a/doc/sphinx/plugins/restricted/selfDelPersonalAccess.rst +++ b/doc/sphinx/plugins/restricted/selfDelPersonalAccess.rst @@ -9,7 +9,7 @@ Remove a personal server access from your account .. admonition:: usage :class: cmdusage - --osh selfDelPersonalAccess --host HOST [OPTIONS] + --osh selfDelPersonalAccess --host HOST --user USER --port PORT [OPTIONS] .. program:: selfDelPersonalAccess @@ -19,37 +19,19 @@ Remove a personal server access from your account Host(s) to remove access from, either a HOST which will be resolved to an IP immediately, or an IP, or a whole network using the NET/CIDR notation -.. option:: --user USER - - Specify which remote user was allowed to connect as. - + --user USER|PATTERN|* Specify which remote user was allowed to connect as. Globbing characters '*' and '?' are supported, so you can specify a pattern that will be matched against the actual remote user name. -.. option:: --user-any - - Synonym of '--user *', allowed connecting as any remote user. - -.. option:: --port PORT - - Remote port that was allowed to connect to - -.. option:: --port-any - - Use when access was allowed to any remote port - -.. option:: --scpup - - Remove SCP upload right, you--bastion-->server (omit --user in this case) - -.. option:: --scpdown - - Remove SCP download right, you<--bastion--server (omit --user in this case) - -.. option:: --sftp - - Remove usage of the SFTP subsystem, you<--bastion-->server (omit --user in this case) - -.. option:: --rsync - - Remove usage of rsync through the bastion - + If any user was allowed, use '--user *' (you might need to escape '*' from your shell) + --port PORT|* Remote port that was allowed to connect to + If any port was allowed, use '--port *' (you might need to escape '*' from your shell) +.. option:: --protocol PROTO + + Specify that a special protocol allowance should be removed from this HOST:PORT tuple, note that you + + must not specify --user in that case. + PROTO must be one of: + scpup allow SCP upload, you--bastion-->server + scpdown allow SCP download, you<--bastion--server + sftp allow usage of the SFTP subsystem, through the bastion + rsync allow usage of rsync, through the bastion diff --git a/doc/sphinx/using/sftp_scp.rst b/doc/sphinx/using/sftp_scp_rsync.rst similarity index 60% rename from doc/sphinx/using/sftp_scp.rst rename to doc/sphinx/using/sftp_scp_rsync.rst index e9079647f..cef97369a 100644 --- a/doc/sphinx/using/sftp_scp.rst +++ b/doc/sphinx/using/sftp_scp_rsync.rst @@ -1,21 +1,25 @@ -================== -SFTP & SCP support -================== +========================= +SFTP, SCP & RSYNC support +========================= .. contents:: Introduction ============ -The Bastion's main goal is to secure ``ssh`` connections. However, one might also want to use ``sftp`` or ``scp`` through it. +The Bastion's main goal is to secure ``ssh`` connections. +However, one might also want to use ``sftp``, ``scp`` or ``rsync`` through it. -Its use is supported through the :doc:`/plugins/open/scp` and :doc:`/plugins/open/sftp` bastion plugins, -respectively, and documented as part of all the plugins. +Its use is supported through the :doc:`/plugins/open/scp`, :doc:`/plugins/open/sftp` and +:doc:`/plugins/open/rsync` bastion plugins, and documented as part of all the plugins. This additional documentation section gives some examples and outlines some common configuration errors. Prerequisites ============= +SFTP & SCP +---------- + The use of SFTP or SCP through the bastion requires an SFTP or SCP program that supports the **-S** option, and a shell to run the wrapper. This is the case on all operating systems using OpenSSH such as Linux or \*BSD. @@ -25,26 +29,34 @@ for Linux) environment, to have the OpenSSH version of ``scp`` or ``sftp`` and a Note that it won't work with Windows GUI apps, because there's no way to specify a wrapper (through **-S**), and no shell. For example, it won't work under WinSCP. +RSYNC +----- + +The use of RSYNC through the bastion only requires rsync to be installed locally and remotely, as is the +case for usage without the bastion. + Basic usage =========== -Please check the :doc:`/plugins/open/scp` and :doc:`/plugins/open/sftp` documentation to see how to use these. +Please check the :doc:`/plugins/open/scp`, :doc:`/plugins/open/sftp` and :doc:`/plugins/open/rsync` +documentation to see how to use these. Access model ============ .. note:: - Currently, to be able to use SFTP or SCP with a remote server, + Currently, to be able to use SFTP, SCP or RSYNC with a remote server, you first need to have a declared SSH access to it. This might change in a future version. Error message 1 --------------- -This is briefly explained in the :doc:`/plugins/open/scp`/:doc:`/plugins/open/sftp` documentation, -but having access rights to SSH to a machine is not enough to have the right to SCP to or from it, or use SFTP on it. -If you have the following error, then this is your problem: +This is briefly explained in the :doc:`/plugins/open/scp`/doc:`/plugins/open/sftp`/:doc:`/plugins/open/rsync` +documentation, but having access rights to SSH to a machine is not enough to have the right to SCP to or from it, +or use SFTP/RSYNC on it. +If you have the following error, then this is the problem you're having: :: @@ -52,18 +64,18 @@ If you have the following error, then this is your problem: The intersection between your rights for ssh and for scp needs to be at least one. When this happens, it means that you have at least one declared SSH access to this machine (through one or -several groups, or through personal accesses). You also have at least one declared SCP/SFTP access to it. +several groups, or through personal accesses). You also have at least one declared SCP/SFTP/RSYNC access to it. However **both accesses are declared through different means**, and more precisely different SSH keys. For example: -- You are a member of a group having this machine on one hand, and you have a declared SCP/SFTP access to this machine +- You are a member of a group having this machine on one hand, and you have a declared SCP/SFTP/RSYNC access to this machine using a personal access on the other hand. For SSH, the group key would be used, but for SCP/SFTP, your personal key - would be used. However, for technical reasons (that might be lifted in a future version), your SSH and SCP/SFTP access + would be used. However, for technical reasons (that might be lifted in a future version), your SSH and SCP/SFTP/RSYNC access must be declared with the same key, so in other words, using the same access mean (same group, or personal access). -- You are a member of group **A** having this machine, but SCP/SFTP access is declared in group **B**. +- You are a member of group **A** having this machine, but SCP/SFTP/RSYNC access is declared in group **B**. In that case, as previously, as two different keys are used, this won't work. -To declare an SCP or SFTP access, in addition to a preexisting SSH access, you should use either: +To declare an SCP/SFTP/RSYNC access, in addition to a preexisting SSH access, you should use either: - :doc:`/plugins/group-aclkeeper/groupAddServer`, if the SSH access is part of a group @@ -71,17 +83,21 @@ To declare an SCP or SFTP access, in addition to a preexisting SSH access, you s if the SSH access is personal (tied to an account) In both cases, where you would use the ``--user`` option to the command, to specify the remote user to use for -the SSH access being declared, you should replace it by either ``--scpdown``, ``--scpup`` or ``--sftp``, -to specify that you're about to add an SCP or SFTP access (not an SSH one), and which direction you want to allow. -For SCP ,you can allow both directions by using the command first with ``--scpdown``, then with ``--scpup``. -Note that for SFTP, you can't specify a direction, due to how the protocol works: you either have SFTP access (hence -being able to upload and download files), or you don't. +the SSH access being declared, you should replace it by either ``--protocol scpdown``, ``--protocol scpup``, +``--protocol sftp`` or ``--protocol rsync``, +to specify that you're about to add an SCP/SFTP/RSYNC access (and not a bare SSH one), and which direction you want +to allow in the case of SCP. + +For SCP, you can allow both directions by using the command first with ``--protocol scpdown``, +then with ``--protocol scpup``. +Note that for SFTP and RYSNC, you can't specify a direction, due to how these protocols work: you either have +SFTP/RSYNC access (hence being able to upload and download files), or you don't. For example, this is a valid command to add SFTP access to a machine which is part of a group: :: - bssh --osh groupAddServer --group mygroup --host scpserver.example.org --port 22 --sftp + bssh --osh groupAddServer --group mygroup --host scpserver.example.org --port 22 --protocol sftp Error message 2 --------------- diff --git a/docker/Dockerfile.tester b/docker/Dockerfile.tester index 3b47aab5c..a35b48d7e 100644 --- a/docker/Dockerfile.tester +++ b/docker/Dockerfile.tester @@ -2,7 +2,7 @@ FROM debian:bookworm LABEL maintainer="stephane.lesimple+bastion@ovhcloud.com" # install prerequisites -RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y jq netcat-traditional openssh-client procps bsdutils screen expect shellcheck libperl-critic-perl fping curl +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y jq netcat-traditional openssh-client procps bsdutils screen expect shellcheck libperl-critic-perl fping curl rsync # add our code COPY . /opt/bastion diff --git a/lib/perl/OVH/Bastion/Plugin/ACL.pm b/lib/perl/OVH/Bastion/Plugin/ACL.pm index eda63f295..9be5ee43b 100644 --- a/lib/perl/OVH/Bastion/Plugin/ACL.pm +++ b/lib/perl/OVH/Bastion/Plugin/ACL.pm @@ -25,7 +25,7 @@ sub check { if ($scpUp or $scpDown or $sftp) { return R('ERR_INCOMPATIBLE_PARAMETERS', msg => "Can't use --protocol with --scpup, --scpdown or --sftp"); } - if (!grep { $protocol eq $_ } qw{ scpup scpdown sftp rsync }) { + if (!grep { $protocol eq $_ } qw{ scpupload scpdownload sftp rsync }) { return R('ERR_INVALID_PARAMETER', msg => "The protocol '$protocol' is not supported, expected either scpup, scpdown, sftp or rsync"); } diff --git a/lib/perl/OVH/Bastion/Plugin/groupSetRole.pm b/lib/perl/OVH/Bastion/Plugin/groupSetRole.pm index 869fcacce..018733b2f 100644 --- a/lib/perl/OVH/Bastion/Plugin/groupSetRole.pm +++ b/lib/perl/OVH/Bastion/Plugin/groupSetRole.pm @@ -8,12 +8,14 @@ use lib dirname(__FILE__) . '/../../../../../lib/perl'; use OVH::Result; use OVH::Bastion; +# Called by the helper osh-groupSetRole, and also by act() below. sub preconditions { my %params = @_; - my ($self, $account, $group, $action, $type, $user, $userAny, $port, $portAny, $host, $ttl, $sudo, $silentoverride) - = @params{ - qw{ self account group action type user userAny port portAny host ttl sudo silentoverride } - }; + # common params: + my ($self, $account, $group, $action, $type, $sudo, $silentoverride) = + @params{qw{ self account group action type sudo silentoverride }}; + # params only used for adding/removing guest accesses: + my ($user, $port, $host, $ttl) = @params{qw{ user port host ttl }}; my $fnret; if (!$self || !$account || !$group || !$type || !$action) { @@ -34,15 +36,10 @@ sub preconditions { $type = $1; ## no critic (ProhibitCaptureWithoutTest) if ($type eq 'guest' && !$sudo) { + # Guest access require a host, user and port might be undef to say 'any', and a ttl can be provided too. + # In sudo mode, these are not used, because the osh-groupSetRole helper that calls us doesn't handle the guest + # access add by itself, the act() func of this package, directly called by the group(Del|Add)GuestAccess plugin, does. - # guest access need (user||user-any), host and (port||port-any) - # in sudo mode, these are not used, because the helper doesn't handle the guest access add by itself, the act() func of this package does - if (!($user xor $userAny)) { - return R('ERR_MISSING_PARAMETER', msg => "Require exactly one argument of user or user-any"); - } - if (!($port xor $portAny)) { - return R('ERR_MISSING_PARAMETER', msg => "Require exactly one argument of port or port-any"); - } if (not $host) { return R('ERR_MISSING_PARAMETER', msg => "Missing argument host for type guest"); } @@ -50,12 +47,12 @@ sub preconditions { $fnret = OVH::Bastion::is_valid_port(port => $port); $fnret or return $fnret; } - if ($user and $user !~ /^[a-zA-Z0-9!._-]+$/) { - return R('ERR_INVALID_PARAMETER', msg => "Invalid remote user ($user) specified"); + if ($user) { + $fnret = OVH::Bastion::is_valid_remote_user(user => $user, allowWildcards => 1); + $fnret or return $fnret; } if ($action eq 'add') { - # policy check for guest accesses: if group forces ttl, the account creation must comply $fnret = OVH::Bastion::group_config(group => $group, key => "guest_ttl_limit"); @@ -160,6 +157,10 @@ sub preconditions { ); } +# We handle the proper helper calls (osh-groupSetRole, osh-groupAddSymlinkToAccount, osh-accountAddGroupServer) to modify the roles as asked. +# Called by all the plugins that modify account roles on groups, and also by the groupCreate helper. +# This sub also calls itself in the case of group member add, +# if the account had guest group accesses before, so as to remove them. sub act { my %params = @_; my $fnret = preconditions(%params); @@ -171,8 +172,6 @@ sub act { @values{qw{ group shortGroup account type realm remoteaccount sysaccount }}; my ($action, $self, $user, $host, $port, $ttl, $comment) = @params{qw{ action self user host port ttl comment }}; - undef $user if $params{'userAny'}; - undef $port if $params{'portAny'}; my @command; osh_debug( @@ -216,9 +215,7 @@ sub act { action => 'del', type => 'guest', user => $access->{'user'}, - userAny => (defined $access->{'user'} ? 0 : 1), port => $access->{'port'}, - portAny => (defined $access->{'port'} ? 0 : 1), host => $access->{'ip'}, self => $self, ); diff --git a/lib/perl/OVH/Bastion/Plugin/otherProtocol.pm b/lib/perl/OVH/Bastion/Plugin/otherProtocol.pm index 267c71755..bd8923746 100644 --- a/lib/perl/OVH/Bastion/Plugin/otherProtocol.pm +++ b/lib/perl/OVH/Bastion/Plugin/otherProtocol.pm @@ -13,6 +13,7 @@ use OVH::Bastion; # and also that, using the same access way (the same egress ssh keys), that they are granted # for this host:port using another protocol than ssh (scp, sftp, rsync) # this requirement will be lifted once we add the "protocol type" to the whole access tuple data model +# while we're at it, return whether we found that this access requires MFA sub has_protocol_access { my %params = @_; my $account = $params{'account'}; @@ -67,13 +68,17 @@ sub has_protocol_access { msg => "Sorry, you have ssh access to $machine, but you need to be granted specifically for $protocol"); } - # get the keys we would try too + # get the keys we would try, along with an eventual mfaRequired flag + my $mfaRequired; foreach my $access (@{$fnret->value || []}) { foreach my $key (@{$access->{'sortedKeys'} || []}) { my $keyfile = $access->{'keys'}{$key}{'fullpath'}; $keys{$keyfile}++ if -r $keyfile; osh_debug("Checking access 2/2 keyfile: $keyfile"); } + if ($access->{'mfaRequired'} && $access->{'mfaRequired'} ne 'none') { + $mfaRequired = $access->{'mfaRequired'}; + } } # only use the key if it has been seen in both allow_deny() calls, this is to avoid @@ -94,7 +99,7 @@ sub has_protocol_access { . " The intersection between your rights for ssh and for $protocol needs to be at least one."); } - return R('OK', value => {keys => \@validKeys, machine => $machine}); + return R('OK', value => {keys => \@validKeys, machine => $machine, mfaRequired => $mfaRequired}); } 1; diff --git a/tests/functional/launch_tests_on_instance.sh b/tests/functional/launch_tests_on_instance.sh index 68710539c..8b9dee8ab 100755 --- a/tests/functional/launch_tests_on_instance.sh +++ b/tests/functional/launch_tests_on_instance.sh @@ -5,6 +5,9 @@ # shellcheck disable=SC2046 set -eu +# ensure a sparse '*' somewhere doesn't end up in us expanding it silently +set -f + basedir=$(readlink -f "$(dirname "$0")"/../..) # shellcheck source=lib/shell/functions.inc . "$basedir"/lib/shell/functions.inc @@ -424,7 +427,7 @@ run() # 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" $screen "$outdir/$basename.log" -D -m -fn -ln bash -c "set -f; $* ; echo \$? > $outdir/$basename.retval ; sleep $sleepafter" flock "$outdir/$basename.retval" true unset sleepafter @@ -682,7 +685,8 @@ runtests() grant accountRevokeCommand - for module in "$(dirname $0)"/tests.d/???-*.sh + # shellcheck disable=SC2044 + for module in $(find "$(dirname $0)/tests.d/" -mindepth 1 -maxdepth 1 -type f -name '???-*.sh' | sort) do module="$(readlink -f "$module")" modulename="$(basename "$module" .sh)" diff --git a/tests/functional/tests.d/340-selfaccesses.sh b/tests/functional/tests.d/340-selfaccesses.sh index 5a3409c51..a3f9b59f1 100644 --- a/tests/functional/tests.d/340-selfaccesses.sh +++ b/tests/functional/tests.d/340-selfaccesses.sh @@ -134,7 +134,7 @@ testsuite_selfaccesses() json .error_code ERR_INVALID_PARAMETER contain "IPv4 is /30 by this" - success selfAddPersonalAccess_constraints_ok $a0 --osh selfAddPersonalAccess --host 127.0.0.9 --user $account0 --port-any --ttl 1 --force + success selfAddPersonalAccess_constraints_ok $a0 --osh selfAddPersonalAccess --host 127.0.0.9 --user $account0 --port '*' --ttl 1 --force success selfAddPersonalAccess_delconfig $r0 "rm -f $opt_remote_etc_bastion/plugin.selfAddPersonalAccess.conf" @@ -150,7 +150,7 @@ testsuite_selfaccesses() json .error_code ERR_INVALID_PARAMETER contain "IPv4 is /30 by this" - success accountAddPersonalAccess_constaints_ok $a0 --osh accountAddPersonalAccess --host 127.0.0.9 --user $account1 --port-any --ttl 1 --account $account1 + success accountAddPersonalAccess_constaints_ok $a0 --osh accountAddPersonalAccess --host 127.0.0.9 --user $account1 --port '*' --ttl 1 --account $account1 success accountAddPersonalAccess_delconfig $r0 "rm -f $opt_remote_etc_bastion/plugin.accountAddPersonalAccess.conf" @@ -442,7 +442,7 @@ testsuite_selfaccesses() nocontain "already" json .command selfAddPersonalAccess .error_code ERR_MISSING_PARAMETER .value null - plgfail userportnoforce $a0 -osh selfAddPersonalAccess -h 127.0.0.4 --user-any --port 22 + plgfail userportnoforce $a0 -osh selfAddPersonalAccess -h 127.0.0.4 --user '*' --port 22 nocontain "already" contain REGEX "Couldn't connect to $account0@127.0.0.4 \\(ssh returned error (255|124)\\)" json .command selfAddPersonalAccess .error_code ERR_CONNECTION_FAILED .value null @@ -485,7 +485,7 @@ testsuite_selfaccesses() contain "Access to 127.0.0.4 " json .command selfDelPersonalAccess .error_code OK .value.ip 127.0.0.4 .value.port null .value.user null - success nousernoport_dupe $a0 -osh selfDelPersonalAccess -h 127.0.0.4 --user-any --port-any + success nousernoport_dupe $a0 -osh selfDelPersonalAccess -h 127.0.0.4 --user '*' --port '*' nocontain "no longer has a personal access" json .command selfDelPersonalAccess .error_code OK_NO_CHANGE .value null diff --git a/tests/functional/tests.d/395-mfa-scp-sftp-rsync.sh b/tests/functional/tests.d/395-mfa-scp-sftp-rsync.sh new file mode 100644 index 000000000..4c9b9fde3 --- /dev/null +++ b/tests/functional/tests.d/395-mfa-scp-sftp-rsync.sh @@ -0,0 +1,538 @@ +# 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() +{ + # 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 + 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 + + # scp + + ## detect recent scp + local scp_options="" + 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 + + grant selfAddPersonalAccess + grant selfDelPersonalAccess + + ### test personal ssh access, must fail without protocol access, must work with protocol access + + # scp + + success personal_scp_add_ssh_access $a0 --osh selfAddPersonalAccess -h 127.0.0.2 -u $shellaccount -p 22 --kbd-interactive + success personal_scp_add_scpup_access $a0 --osh selfAddPersonalAccess --host 127.0.0.2 --scpup --port 22 + + sleepafter 2 + run personal_scp_download_oldhelper_mustfail scp $scp_options -F $mytmpdir/ssh_config -S /tmp/scphelper -i $account0key1file $shellaccount@127.0.0.2:uptest /tmp/downloaded + retvalshouldbe 1 + contain "Sorry, you have ssh access to" + + run personal_scp_download_newwrapper_mustfail env BASTION_SCP_DEBUG=1 /tmp/scpwrapper -i $account0key1file $shellaccount@127.0.0.2:uptest /tmp/downloaded + retvalshouldbe 1 + contain "Sorry, you have ssh access to" + + success personal_scp_add_scpdown_access $a0 --osh selfAddPersonalAccess --host 127.0.0.2 --scpdown --port 22 + + sleepafter 2 + run personal_scp_download_oldhelper_badfile 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 personal_scp_download_newwrapper_badfile /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 invalidhostname_scp_oldhelper 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" + + run invalidhostname_scp_newwrapper /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 personal_scp_upload_oldhelper_ok 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 personal_scp_upload_newwrapper_ok /tmp/scpwrapper -i $account0key1file /etc/passwd $shellaccount@127.0.0.2:uptest + contain "through the bastion to" + contain "Done," + + success personal_scp_download_oldhelper_ok 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 personal_scp_download_newwrapper_ok /tmp/scpwrapper -i $account0key1file $shellaccount@127.0.0.2:uptest /tmp/downloaded + contain "through the bastion from" + contain "Done," + + success personal_scp_del_scpup_access $a0 --osh selfDelPersonalAccess --host 127.0.0.2 --scpup --port 22 + success personal_scp_del_scpdown_access $a0 --osh selfDelPersonalAccess --host 127.0.0.2 --scpdown --port 22 + + # sftp + + if [ "$COUNTONLY" != 1 ]; then + printf "ls\nexit\n" >"/tmp/sftpcommands" + fi + + run personal_sftp_use_oldhelper_mustfail sftp -F $mytmpdir/ssh_config -S /tmp/sftphelper -i $account0key1file $shellaccount@127.0.0.2 + retvalshouldbe 255 + contain "Sorry, you have ssh access to" + + run personal_sftp_use_newwrapper_mustfail /tmp/sftpwrapper -i $account0key1file $shellaccount@127.0.0.2 + retvalshouldbe 255 + contain "Sorry, you have ssh access to" + + success personal_sftp_add_sftp_access $a0 --osh selfAddPersonalAccess --host 127.0.0.2 --sftp --port 22 + + success personal_sftp_use_oldhelper_ok 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 personal_sftp_use_newwrapper_ok /tmp/sftpwrapper -b /tmp/sftpcommands -i $account0key1file $shellaccount@127.0.0.2 + contain 'sftp> ls' + contain 'uptest' + contain 'sftp> exit' + contain '>>> Done,' + + success personal_sftp_del_sftp_access $a0 --osh selfDelPersonalAccess --host 127.0.0.2 --sftp --port 22 + + # rsync + + run personal_rsync_use_mustfail rsync --rsh \"$a0 --osh rsync --\" /etc/passwd $shellaccount@127.0.0.2:/tmp/ + retvalshouldbe 2 + contain "Sorry, you have ssh access to" + + success personal_rsync_add_rsync_access $a0 --osh selfAddPersonalAccess --host 127.0.0.2 --protocol rsync --port 22 + + success personal_rsync_upload_ok rsync --rsh \"$a0 --osh rsync --\" /etc/passwd $shellaccount@127.0.0.2:rsync_file + nocontain "rsync:" + nocontain "rsync error:" + contain ">>> Hello" + contain ">>> Done," + + success personal_rsync_download_ok rsync --rsh \"$a0 --osh rsync --\" $shellaccount@127.0.0.2:rsync_file /tmp/downloaded + nocontain "rsync:" + nocontain "rsync error:" + contain ">>> Hello" + contain ">>> Done," + + success personal_rsync_del_rsync_access $a0 --osh selfDelPersonalAccess --host 127.0.0.2 --protocol rsync --port 22 + + ### test personal ssh access with group protocol access, must fail, and works only if group ssh access is added too + + 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 personalssh_groupprotocol_add_key_to_shellaccount $r0 "echo '$g1key' \>\> ~$shellaccount/.ssh/authorized_keys" + + # add server to group1 + success personalssh_groupprotocol_add_server_to_group $a0 --osh groupAddServer --group $group1 --host 127.0.0.2 --user $shellaccount --port 22 + + # scp + + run personalssh_groupprotocol_scp_download_mustfail /tmp/scpwrapper -i $account0key1file $shellaccount@127.0.0.2:passwd /tmp/ + retvalshouldbe 1 + contain 'MFA_TOKEN=notrequired' + contain 'need to be granted specifically for scpdownload' + nocontain '>>> Done' + + run personalssh_groupprotocol_scp_upload_mustfail /tmp/scpwrapper -i $account0key1file /etc/passwd $shellaccount@127.0.0.2: + retvalshouldbe 1 + contain 'MFA_TOKEN=notrequired' + contain 'need to be granted specifically for scpupload' + nocontain '>>> Done' + + success groupssh_groupprotocol_scp_add_scpup_access $a0 --osh groupAddServer --group $group1 --host 127.0.0.2 --scpup --port 22 + + success groupssh_groupprotocol_scp_upload_ok /tmp/scpwrapper -i $account0key1file /etc/passwd $shellaccount@127.0.0.2: + contain 'MFA_TOKEN=notrequired' + contain 'transferring your file through the bastion' + contain '>>> Done' + + run personalssh_groupprotocol_scp_download_mustfail /tmp/scpwrapper -i $account0key1file $shellaccount@127.0.0.2:passwd /tmp/ + retvalshouldbe 1 + contain 'MFA_TOKEN=notrequired' + contain 'need to be granted specifically for scpdownload' + nocontain '>>> Done' + + success groupssh_groupprotocol_scp_del_scpup_access $a0 --osh groupDelServer --group $group1 --host 127.0.0.2 --scpup --port 22 + + # sftp + + run personalssh_groupprotocol_sftp_download_mustfail /tmp/sftpwrapper -i $account0key1file sftp://$shellaccount@127.0.0.2//etc/passwd + retvalshouldbe 255 + contain 'MFA_TOKEN=notrequired' + contain 'need to be granted specifically for sftp' + nocontain '>>> Done' + + success groupssh_groupprotocol_sftp_add_sftp_access $a0 --osh groupAddServer --group $group1 --host 127.0.0.2 --protocol sftp --port 22 + + success groupssh_groupprotocol_sftp_use_ok /tmp/sftpwrapper -i $account0key1file sftp://$shellaccount@127.0.0.2//etc/passwd + contain 'MFA_TOKEN=notrequired' + contain 'Fetching /etc/passwd' + contain '>>> Done' + + success groupssh_groupprotocol_sftp_del_sftp_access $a0 --osh groupDelServer --group $group1 --host 127.0.0.2 --protocol sftp --port 22 + + # rsync + + run personalssh_groupprotocol_rsync_download_mustfail rsync --rsh \"$a0 --osh rsync --\" $shellaccount@127.0.0.2:/etc/passwd /tmp/ + retvalshouldbe 2 + contain 'need to be granted specifically for rsync' + nocontain '>>> Done' + + success groupssh_groupprotocol_rsync_add_rsync_access $a0 --osh groupAddServer --group $group1 --host 127.0.0.2 --protocol rsync --port 22 + + success groupssh_groupprotocol_rsync_use_ok rsync --rsh \"$a0 --osh rsync --\" $shellaccount@127.0.0.2:/etc/passwd /tmp/ + contain '>>> Hello' + contain '>>> Done,' + + success groupssh_groupprotocol_rsync_del_rsync_access $a0 --osh groupDelServer --group $group1 --host 127.0.0.2 --protocol rsync --port 22 + + ## set --personal-egress-mfa-required on this account, and add matching ssh/proto personal access: scp/sftp must request MFA, rsync must be denied + + grant accountModify + success personal_egress_mfa $a0 --osh accountModify --account $account0 --personal-egress-mfa-required password + + success personal_access_add_scpup $a0 --osh selfAddPersonalAccess --host 127.0.0.2 --port 22 --protocol scpupload + success personal_access_add_sftp $a0 --osh selfAddPersonalAccess --host 127.0.0.2 --port 22 --protocol sftp + success personal_access_add_rsync $a0 --osh selfAddPersonalAccess --host 127.0.0.2 --port 22 --protocol rsync + + # scp + + run account_mfa_scp_upload_mfa_fail /tmp/scpwrapper -i $account0key1file /etc/passwd $shellaccount@127.0.0.2: + retvalshouldbe 1 + nocontain 'MFA_TOKEN=notrequired' + contain 'entering MFA phase' + contain 'you need to setup the Multi-Factor Authentication for this plugin' + + # sftp + + run account_mfa_sftp_use_mfa_fail /tmp/sftpwrapper -i $account0key1file sftp://$shellaccount@127.0.0.2//etc/passwd + retvalshouldbe 1 + nocontain 'MFA_TOKEN=notrequired' + contain 'entering MFA phase' + contain 'you need to setup the Multi-Factor Authentication for this plugin' + + # rsync + + run account_mfa_rsync_use_mfa_fail rsync --rsh \"$a0 --osh rsync --\" $shellaccount@127.0.0.2:/etc/passwd /tmp/ + retvalshouldbe 2 + contain 'MFA is required for this host, which is not supported by rsync' + contain 'rsync error:' + + # reset --personal-egress-mfa-required on this account and remove protocol personal accesses + + success personal_egress_nomfa $a0 --osh accountModify --account $account0 --personal-egress-mfa-required none + revoke accountModify + + success personal_access_del_scpup $a0 --osh selfDelPersonalAccess --host 127.0.0.2 --port 22 --protocol scpupload + success personal_access_del_sftp $a0 --osh selfDelPersonalAccess --host 127.0.0.2 --port 22 --protocol sftp + success personal_access_del_rsync $a0 --osh selfDelPersonalAccess --host 127.0.0.2 --port 22 --protocol rsync + + ## set MFA required on group (and add back group protocol access), MFA should be asked by scp/sftp, and rsync should abort + + success group_need_mfa $a0 --osh groupModify --group $group1 --mfa-required password + success account_mfa_scp_add_scpup_access $a0 --osh groupAddServer --group $group1 --host 127.0.0.2 --protocol scpupload --port 22 + success account_mfa_sftp_add_sftp_access $a0 --osh groupAddServer --group $group1 --host 127.0.0.2 --protocol sftp --port 22 + success account_mfa_rsync_add_rsync_access $a0 --osh groupAddServer --group $group1 --host 127.0.0.2 --protocol rsync --port 22 + + # scp + + run group_mfa_scp_upload_mfa_fail /tmp/scpwrapper -i $account0key1file /etc/passwd $shellaccount@127.0.0.2: + retvalshouldbe 1 + nocontain 'MFA_TOKEN=notrequired' + contain 'entering MFA phase' + contain 'you need to setup the Multi-Factor Authentication for this plugin' + + # sftp + + run group_mfa_sftp_upload_mfa_fail /tmp/sftpwrapper -i $account0key1file sftp://$shellaccount@127.0.0.2//etc/passwd + retvalshouldbe 1 + nocontain 'MFA_TOKEN=notrequired' + contain 'entering MFA phase' + contain 'you need to setup the Multi-Factor Authentication for this plugin' + + # rsync + + run group_mfa_rsync_use_mfa_fail rsync --rsh \"$a0 --osh rsync --\" $shellaccount@127.0.0.2:/etc/passwd /tmp/ + retvalshouldbe 2 + nocontain 'MFA_TOKEN=' + contain 'MFA is required for this host, which is not supported by rsync' + contain 'rsync error:' + + ## keep MFA required on group, but setup MFA on our account, so we can test the MFA process + + # setup MFA on our account, step1 + run personal_mfa_setup_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 personal_mfa_setup_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' | timeout --foreground $default_timeout expect -f -" + retvalshouldbe 0 + unset a0_password_tmp + nocontain 'enter this:' + nocontain 'unchanged' + nocontain 'sorry' + json .command selfMFASetupPassword .error_code OK + + # scp + + script group_mfa_scp_upload_mfa_ok "echo 'set timeout $default_timeout; + spawn /tmp/scpwrapper -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' | timeout --foreground $default_timeout 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 + + script group_mfa_sftp_use_mfa_ok "echo 'set timeout $default_timeout; + spawn /tmp/sftpwrapper -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' | timeout --foreground $default_timeout 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 + + # rsync + + run group_mfa_rsync_use_mfa_unsupported rsync --rsh \"$a0 --osh rsync --\" $shellaccount@127.0.0.2:/etc/passwd /tmp/ + retvalshouldbe 2 + nocontain 'MFA_TOKEN=' + contain 'MFA is required for this host, which is not supported by rsync' + contain 'rsync error:' + + # provide invalid tokens manually + + for proto in scp sftp; do + run ${proto}_upload_bad_token_format $a0 --osh ${proto} --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,$(perl -e 'CORE::say time()-3600'),9f25d680b1bae2ef73abc3c62926ddb9c88f8ea1f4120b1125cc09720c74268b" + run ${proto}_upload_bad_token_expired $a0 --osh ${proto} --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,$(perl -e 'CORE::say time()+3600'),9f25d680b1bae2ef73abc3c62926ddb9c88f8ea1f4120b1125cc09720c74268b" + run ${proto}_upload_bad_token_future $a0 --osh ${proto} --host 127.0.0.2 --port 22 --user $shellaccount --mfa-token "$invalid_token" + retvalshouldbe 125 + json .error_code KO_MFA_FAILED_FUTURE_TOKEN + done + + # remove MFA from account + if [ "${capabilities[mfa]}" = 1 ] || [ "${capabilities[mfa-password]}" = 1 ]; then + script personal_mfa_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' | timeout --foreground $default_timeout expect -f -" + retvalshouldbe 0 + json .error_code OK .command selfMFAResetPassword + else + grant accountMFAResetPassword + success personal_mfa_reset_password $a0 --osh accountMFAResetPassword --account $account0 + fi + + ## set account as exempt from MFA, and see whether scp/sftp/rsync (that still require MFA as per the group) do work + + grant accountModify + success personal_mfa_set_exempt $a0 --osh accountModify --account $account0 --mfa-password-required bypass + + success scp_upload_mfa_exempt_oldhelper_ok /tmp/scpwrapper -i $account0key1file /etc/passwd $shellaccount@127.0.0.2: + nocontain 'MFA_TOKEN=v1' + contain 'MFA_TOKEN=notrequired' + contain 'skipping as your account is exempt from MFA' + + script sftp_upload_mfa_exempt_oldhelper_ok /tmp/sftpwrapper -i $account0key1file sftp://$shellaccount@127.0.0.2//etc/passwd + nocontain 'MFA_TOKEN=v1' + contain 'MFA_TOKEN=notrequired' + contain 'skipping as your account is exempt from MFA' + + sleepafter 2 + success scp_upload_mfa_exempt_oldwrapper_ok scp $scp_options -F $mytmpdir/ssh_config -S /tmp/scphelper -i $account0key1file /etc/passwd $shellaccount@127.0.0.2:uptest + contain 'skipping as your account is exempt from MFA' + contain "through the bastion to" + contain "Done," + + success sftp_use_mfa_exempt_oldwrapper_ok sftp -F $mytmpdir/ssh_config -b /tmp/sftpcommands -S /tmp/sftphelper -i $account0key1file $shellaccount@127.0.0.2 + contain 'skipping as your account is exempt from MFA' + contain 'sftp> ls' + contain 'uptest' + contain 'sftp> exit' + contain '>>> Done,' + + run rsync_use_mfa_exempt_ok rsync --rsh \"$a0 --osh rsync --\" $shellaccount@127.0.0.2:/etc/passwd /tmp/ + nocontain "rsync:" + nocontain "rsync error:" + contain "requires password MFA but your account has password MFA bypass, allowing" + contain ">>> Hello" + contain ">>> Done," + + # reset account setup + success personal_mfa_reset_policy $a0 --osh accountModify --account $account0 --mfa-password-required no + revoke accountModify + + # delete group1 + success groupDestroy $a0 --osh groupDestroy --group $group1 --no-confirm + + revoke selfAddPersonalAccess + revoke selfDelPersonalAccess +} + +testsuite_mfa_scp_sftp +unset -f testsuite_mfa_scp_sftp diff --git a/tests/functional/tests.d/395-mfa-scp-sftp.sh b/tests/functional/tests.d/395-mfa-scp-sftp.sh deleted file mode 100644 index 41cfa0189..000000000 --- a/tests/functional/tests.d/395-mfa-scp-sftp.sh +++ /dev/null @@ -1,445 +0,0 @@ -# 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() -{ - # 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 - 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 - - # scp - - ## detect recent scp - local scp_options="" - 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 - - grant selfAddPersonalAccess - grant selfDelPersonalAccess - - success a0_add_ssh_access $a0 --osh selfAddPersonalAccess -h 127.0.0.2 -u $shellaccount -p 22 --kbd-interactive - success a0_add_scp_up $a0 --osh selfAddPersonalAccess --host 127.0.0.2 --scpup --port 22 - - 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 a0_add_scp_down $a0 --osh selfAddPersonalAccess --host 127.0.0.2 --scpdown --port 22 - - 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_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" - - 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_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," - - success forscpremove1 $a0 --osh selfDelPersonalAccess --host 127.0.0.2 --scpup --port 22 - success forscpremove2 $a0 --osh selfDelPersonalAccess --host 127.0.0.2 --scpdown --port 22 - - # sftp - - 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 - - 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 - - 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 - - # scp: upload something (denied, not granted) - run scp_upload_denied /tmp/scpwrapper -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/scpwrapper -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/sftpwrapper -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/sftpwrapper -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/scpwrapper -i $account0key1file /etc/passwd $shellaccount@127.0.0.2: - retvalshouldbe 1 - nocontain 'MFA_TOKEN=notrequired' - contain '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/sftpwrapper -i $account0key1file sftp://$shellaccount@127.0.0.2//etc/passwd - retvalshouldbe 1 - nocontain 'MFA_TOKEN=notrequired' - contain '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/scpwrapper -i $account0key1file /etc/passwd $shellaccount@127.0.0.2: - retvalshouldbe 1 - nocontain 'MFA_TOKEN=notrequired' - contain '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/sftpwrapper -i $account0key1file sftp://$shellaccount@127.0.0.2//etc/passwd - retvalshouldbe 1 - nocontain 'MFA_TOKEN=notrequired' - contain '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' | timeout --foreground $default_timeout 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/scpwrapper -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' | timeout --foreground $default_timeout 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/sftpwrapper -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' | timeout --foreground $default_timeout 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,$(perl -e 'CORE::say time()-3600'),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,$(perl -e 'CORE::say time()+3600'),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' | timeout --foreground $default_timeout 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/scpwrapper -i $account0key1file /etc/passwd $shellaccount@127.0.0.2: - nocontain 'MFA_TOKEN=v1' - contain 'MFA_TOKEN=notrequired' - contain 'skipping as your account is exempt from MFA' - - # sftp: upload something after except from mfa - script sftp_upload_mfa_exempt_ok /tmp/sftpwrapper -i $account0key1file sftp://$shellaccount@127.0.0.2//etc/passwd - nocontain 'MFA_TOKEN=v1' - contain 'MFA_TOKEN=notrequired' - contain 'skipping as your account is exempt from MFA' - - # same but with old helpers - sleepafter 2 - success scp_upload_mfa_exempt_old_ok scp $scp_options -F $mytmpdir/ssh_config -S /tmp/scphelper -i $account0key1file /etc/passwd $shellaccount@127.0.0.2:uptest - contain 'skipping as your account is exempt from MFA' - contain "through the bastion to" - contain "Done," - - success sftp_list_mfa_exempt_old_ok sftp -F $mytmpdir/ssh_config -b /tmp/sftpcommands -S /tmp/sftphelper -i $account0key1file $shellaccount@127.0.0.2 - contain 'skipping as your account is exempt from MFA' - contain 'sftp> ls' - contain 'uptest' - contain 'sftp> exit' - contain '>>> Done,' - - # 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 - - revoke selfAddPersonalAccess - revoke selfDelPersonalAccess -} - -testsuite_mfa_scp_sftp -unset -f testsuite_mfa_scp_sftp