From 0994d3dfca944fe6b087c5243a335c7b88338b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lesimple?= Date: Fri, 5 Jul 2024 12:15:57 +0000 Subject: [PATCH] feat: add rsync support (fix #301) --- bin/plugin/group-aclkeeper/groupAddServer | 5 +- bin/plugin/group-aclkeeper/groupDelServer | 5 +- .../group-gatekeeper/groupAddGuestAccess | 5 +- .../group-gatekeeper/groupDelGuestAccess | 5 +- bin/plugin/open/rsync | 169 ++++++++++++++++++ bin/plugin/open/scp | 4 +- .../restricted/accountAddPersonalAccess | 5 +- .../restricted/accountDelPersonalAccess | 5 +- bin/plugin/restricted/selfAddPersonalAccess | 5 +- bin/plugin/restricted/selfDelPersonalAccess | 5 +- 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 | 4 + .../group-aclkeeper/groupDelServer.rst | 4 + .../group-gatekeeper/groupAddGuestAccess.rst | 4 + .../group-gatekeeper/groupDelGuestAccess.rst | 4 + doc/sphinx/plugins/open/index.rst | 1 + doc/sphinx/plugins/open/scp.rst | 2 +- doc/sphinx/plugins/open/sftp.rst | 2 +- .../restricted/accountAddPersonalAccess.rst | 4 + .../restricted/accountDelPersonalAccess.rst | 4 + .../restricted/selfAddPersonalAccess.rst | 4 + .../restricted/selfDelPersonalAccess.rst | 4 + .../{sftp_scp.rst => sftp_scp_rsync.rst} | 56 +++--- lib/perl/OVH/Bastion/Plugin.pm | 134 +++++++++----- lib/perl/OVH/Bastion/Plugin/ACL.pm | 13 +- lib/perl/OVH/Bastion/allowdeny.inc | 2 +- 28 files changed, 374 insertions(+), 87 deletions(-) create mode 100755 bin/plugin/open/rsync rename doc/sphinx/using/{sftp_scp.rst => sftp_scp_rsync.rst} (62%) diff --git a/bin/plugin/group-aclkeeper/groupAddServer b/bin/plugin/group-aclkeeper/groupAddServer index 0e8d12561..088aea895 100755 --- a/bin/plugin/group-aclkeeper/groupAddServer +++ b/bin/plugin/group-aclkeeper/groupAddServer @@ -20,6 +20,7 @@ my $remainingOptions = OVH::Bastion::Plugin::begin( "scpup" => \my $scpUp, "scpdown" => \my $scpDown, "sftp" => \my $sftp, + "rsync" => \my $rsync, "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, @@ -43,6 +44,7 @@ Usage: --osh SCRIPT_NAME --group GROUP [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 --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) @@ -71,7 +73,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-aclkeeper/groupDelServer b/bin/plugin/group-aclkeeper/groupDelServer index e54161e6a..b2874e3c0 100755 --- a/bin/plugin/group-aclkeeper/groupDelServer +++ b/bin/plugin/group-aclkeeper/groupDelServer @@ -20,6 +20,7 @@ my $remainingOptions = OVH::Bastion::Plugin::begin( "scpup" => \my $scpUp, "scpdown" => \my $scpDown, "sftp" => \my $sftp, + "rsync" => \my $rsync, "force" => \my $force, }, helptext => <<'EOF', @@ -39,6 +40,7 @@ Usage: --osh SCRIPT_NAME --group GROUP --host HOST [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 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`` @@ -66,7 +68,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/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/open/rsync b/bin/plugin/open/rsync new file mode 100755 index 000000000..07e6d8b28 --- /dev/null +++ b/bin/plugin/open/rsync @@ -0,0 +1,169 @@ +#! /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 ); + +# don't output fancy stuff, this can get digested by rsync and we get garbage output +local $ENV{'PLUGIN_QUIET'} = 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 +); + +# stdout is used by rsync, so ensure we output everything through stderr +local $ENV{'FORCE_STDERR'} = 1; + +# 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); +} + +if (ref $remainingOptions eq 'ARRAY' && @$remainingOptions && $remainingOptions->[0] eq 'rsync') { + ; # ok +} +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."; +} + +my $machine = $ip; +$machine = "$user\@$ip" if $user; +$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 +$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"); + } +} + +osh_debug("Checking access 2/2 of !rsync to $user of $machine..."); +$fnret = OVH::Bastion::is_access_granted( + account => $self, + user => '!rsync', + 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 rsync"; +} + +# 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"); + } +} + +# 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 + # !rsync 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 rsync but by different and distinct means (distinct keys)." + . " The intersection between your rights for ssh and for rsync needs to be at least one."); +} + +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/plugin/open/scp b/bin/plugin/open/scp index 0c606e8b8..1b91942fc 100755 --- a/bin/plugin/open/scp +++ b/bin/plugin/open/scp @@ -319,10 +319,10 @@ foreach my $access (@{$fnret->value || []}) { } } -osh_debug("Checking access 2/2 of $self to $userToCheck of $machine..."); +osh_debug("Checking access 2/2 of $self to $user of $machine..."); $fnret = OVH::Bastion::is_access_granted( account => $self, - user => $userToCheck, + user => $user, ipfrom => $ENV{'OSH_IP_FROM'}, ip => $ip, port => $port, diff --git a/bin/plugin/restricted/accountAddPersonalAccess b/bin/plugin/restricted/accountAddPersonalAccess index dbb9acfc2..45a70a118 100755 --- a/bin/plugin/restricted/accountAddPersonalAccess +++ b/bin/plugin/restricted/accountAddPersonalAccess @@ -21,6 +21,7 @@ my $remainingOptions = OVH::Bastion::Plugin::begin( "scpup" => \my $scpUp, "scpdown" => \my $scpDown, "sftp" => \my $sftp, + "rsync" => \my $rsync, "force-key=s" => \my $forceKey, "force-password=s" => \my $forcePassword, "ttl=s" => \my $ttl, @@ -43,6 +44,7 @@ Usage: --osh SCRIPT_NAME --account ACCOUNT --host HOST [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 --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 accountListPasswords) --ttl SECONDS|DURATION Specify a number of seconds (or a duration string, such as "1d7h8m") after which the access will automatically expire @@ -94,7 +96,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/accountDelPersonalAccess b/bin/plugin/restricted/accountDelPersonalAccess index 25316a865..16c180216 100755 --- a/bin/plugin/restricted/accountDelPersonalAccess +++ b/bin/plugin/restricted/accountDelPersonalAccess @@ -20,6 +20,7 @@ my $remainingOptions = OVH::Bastion::Plugin::begin( "scpup" => \my $scpUp, "scpdown" => \my $scpDown, "sftp" => \my $sftp, + "rsync" => \my $rsync, }, helptext => <<'EOF', Remove a personal server access from an account @@ -38,6 +39,7 @@ Usage: --osh SCRIPT_NAME --account ACCOUNT --host HOST [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 EOF ); @@ -55,7 +57,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/selfAddPersonalAccess b/bin/plugin/restricted/selfAddPersonalAccess index 4af46c3ec..709616e9d 100755 --- a/bin/plugin/restricted/selfAddPersonalAccess +++ b/bin/plugin/restricted/selfAddPersonalAccess @@ -20,6 +20,7 @@ my $remainingOptions = OVH::Bastion::Plugin::begin( "scpup" => \my $scpUp, "scpdown" => \my $scpDown, "sftp" => \my $sftp, + "rsync" => \my $rsync, "force-key=s" => \my $forceKey, "force-password=s" => \my $forcePassword, "force" => \my $force, @@ -42,6 +43,7 @@ Usage: --osh SCRIPT_NAME --host HOST [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 --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) @@ -90,7 +92,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/selfDelPersonalAccess b/bin/plugin/restricted/selfDelPersonalAccess index f49cf5a1e..03639ace2 100755 --- a/bin/plugin/restricted/selfDelPersonalAccess +++ b/bin/plugin/restricted/selfDelPersonalAccess @@ -19,6 +19,7 @@ my $remainingOptions = OVH::Bastion::Plugin::begin( "scpup" => \my $scpUp, "scpdown" => \my $scpDown, "sftp" => \my $sftp, + "rsync" => \my $rsync, }, helptext => <<'EOF', Remove a personal server access from your account @@ -36,6 +37,7 @@ Usage: --osh SCRIPT_NAME --host HOST [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 EOF ); @@ -53,7 +55,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/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 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/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/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 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/doc/sphinx/using/sftp_scp.rst b/doc/sphinx/using/sftp_scp_rsync.rst similarity index 62% rename from doc/sphinx/using/sftp_scp.rst rename to doc/sphinx/using/sftp_scp_rsync.rst index e9079647f..6fa8c277c 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,11 +83,13 @@ 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 ``--scpdown``, ``--scpup``, ``--sftp`` or ``--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 ``--scpdown``, then with ``--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: diff --git a/lib/perl/OVH/Bastion/Plugin.pm b/lib/perl/OVH/Bastion/Plugin.pm index f3320a08a..d4718c38f 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; } +# validate user/host/ip/port when specified, undef them otherwise (instead of '') +# this sets the vars user/host/ip/port exported to the plugins, called in begin() below. +# can also be called by the plugins themselves to change/validate the user/host/ip/port vars, +# from arguments they might have received themselves. +# when init=1, make the assumption than user/host/ip/port are already set and just validate them +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"; diff --git a/lib/perl/OVH/Bastion/Plugin/ACL.pm b/lib/perl/OVH/Bastion/Plugin/ACL.pm index b94c87fba..1ccc057df 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, $rsync) = + @params{qw{ port portAny user userAny scpUp scpDown sftp rsync }}; if ($user and $userAny) { return R('ERR_INCOMPATIBLE_PARAMETERS', @@ -24,13 +24,13 @@ sub check { . "if you want to grant both, please do it in two separate commands"); } - if ($sftp and ($scpUp or $scpDown)) { + if ($sftp and ($scpUp or $scpDown or $rsync)) { 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 --rsync at a time, " + . "if you want to grant several of those protocols, please do it in separate commands"); } - if (($scpUp or $scpDown or $sftp) and ($user or $userAny)) { + if (($scpUp or $scpDown or $sftp or $rsync) and ($user or $userAny)) { 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 " @@ -39,6 +39,7 @@ sub check { $user = '!scpupload' if $scpUp; $user = '!scpdownload' if $scpDown; $user = '!sftp' if $sftp; + $user = '!rsync' if $rsync; if (not $user and not $userAny) { return R('ERR_MISSING_PARAMETER', diff --git a/lib/perl/OVH/Bastion/allowdeny.inc b/lib/perl/OVH/Bastion/allowdeny.inc index 11f7dbdc4..23f7a3e38 100644 --- a/lib/perl/OVH/Bastion/allowdeny.inc +++ b/lib/perl/OVH/Bastion/allowdeny.inc @@ -951,7 +951,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 ((grep { $user eq $_ } qw{ !scpupload !scpdownload !sftp !rsync }) || ($user =~ /[*?]/)) { return R('OK_MAGIC_USER', msg => "Didn't really test the connection, as the specified user is special"); }