Skip to content

Commit

Permalink
feat: add rsync support to --protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
speed47 committed Sep 5, 2024
1 parent cbd945a commit b120d4a
Showing 23 changed files with 863 additions and 687 deletions.
150 changes: 150 additions & 0 deletions bin/plugin/open/rsync
Original file line number Diff line number Diff line change
@@ -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;
11 changes: 7 additions & 4 deletions bin/shell/osh.pl
Original file line number Diff line number Diff line change
@@ -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) {
2 changes: 1 addition & 1 deletion doc/sphinx-plugins-override/scp.override.rst
Original file line number Diff line number Diff line change
@@ -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`.
2 changes: 1 addition & 1 deletion doc/sphinx-plugins-override/sftp.override.rst
Original file line number Diff line number Diff line change
@@ -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`.
2 changes: 1 addition & 1 deletion doc/sphinx/index.rst
Original file line number Diff line number Diff line change
@@ -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
58 changes: 23 additions & 35 deletions doc/sphinx/plugins/group-aclkeeper/groupAddServer.rst
Original file line number Diff line number Diff line change
@@ -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
48 changes: 15 additions & 33 deletions doc/sphinx/plugins/group-aclkeeper/groupDelServer.rst
Original file line number Diff line number Diff line change
@@ -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``
1 change: 1 addition & 0 deletions doc/sphinx/plugins/open/index.rst
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@ open plugins
mtr
nc
ping
rsync
scp
selfAddIngressKey
selfDelIngressKey
4 changes: 4 additions & 0 deletions doc/sphinx/plugins/open/rsync.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
======
rsync
======

2 changes: 1 addition & 1 deletion doc/sphinx/plugins/open/scp.rst
Original file line number Diff line number Diff line change
@@ -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`.
2 changes: 1 addition & 1 deletion doc/sphinx/plugins/open/sftp.rst
Original file line number Diff line number Diff line change
@@ -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`.
Loading

0 comments on commit b120d4a

Please sign in to comment.