Skip to content

Commit

Permalink
feat: add rsync support (fix #301)
Browse files Browse the repository at this point in the history
  • Loading branch information
speed47 committed Jul 5, 2024
1 parent 3d2cf21 commit 0994d3d
Show file tree
Hide file tree
Showing 28 changed files with 374 additions and 87 deletions.
5 changes: 4 additions & 1 deletion bin/plugin/group-aclkeeper/groupAddServer
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
Expand Down Expand Up @@ -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();
Expand Down
5 changes: 4 additions & 1 deletion bin/plugin/group-aclkeeper/groupDelServer
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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``
Expand Down Expand Up @@ -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();
Expand Down
5 changes: 4 additions & 1 deletion bin/plugin/group-gatekeeper/groupAddGuestAccess
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand All @@ -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
Expand Down Expand Up @@ -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();
Expand Down
5 changes: 4 additions & 1 deletion bin/plugin/group-gatekeeper/groupDelGuestAccess
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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``
Expand Down Expand Up @@ -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();
Expand Down
169 changes: 169 additions & 0 deletions bin/plugin/open/rsync
Original file line number Diff line number Diff line change
@@ -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;

4 changes: 2 additions & 2 deletions bin/plugin/open/scp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 4 additions & 1 deletion bin/plugin/restricted/accountAddPersonalAccess
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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();
Expand Down
5 changes: 4 additions & 1 deletion bin/plugin/restricted/accountDelPersonalAccess
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
);

Expand All @@ -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();
Expand Down
5 changes: 4 additions & 1 deletion bin/plugin/restricted/selfAddPersonalAccess
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
Expand Down Expand Up @@ -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();
Expand Down
5 changes: 4 additions & 1 deletion bin/plugin/restricted/selfDelPersonalAccess
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
);

Expand All @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion doc/sphinx-plugins-override/scp.override.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Loading

0 comments on commit 0994d3d

Please sign in to comment.