diff --git a/bin/plugin/open/rsync b/bin/plugin/open/rsync new file mode 100755 index 000000000..81be13af2 --- /dev/null +++ b/bin/plugin/open/rsync @@ -0,0 +1,150 @@ +#! /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 scp, 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; # 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 => '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"; + +#print STDERR join('^', @cmd) . "\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/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/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/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..d8834534b --- /dev/null +++ b/doc/sphinx/plugins/open/rsync.rst @@ -0,0 +1,4 @@ +====== +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/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