pull/592/merge
Jonah 2 months ago committed by GitHub
commit a05c413947
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -17,19 +17,22 @@ use OVH::Bastion::Helper;
# Fetch command options
my $fnret;
my ($result, @optwarns);
my ($account, $group, $ip, $user, $port, $action, $ttl, $comment, $forceKey);
my ($account, $group, $ip, $user, $port, $proxyIp, $proxyPort, $proxyUser, $action, $ttl, $comment, $forceKey);
eval {
local $SIG{__WARN__} = sub { push @optwarns, shift };
$result = GetOptions(
"account=s" => sub { $account //= $_[1] },
"group=s" => sub { $group //= $_[1] },
"ip=s" => sub { $ip //= $_[1] },
"user=s" => sub { $user //= $_[1] },
"port=i" => sub { $port //= $_[1] },
"action=s" => sub { $action //= $_[1] },
"ttl=i" => sub { $ttl //= $_[1] },
"comment=s" => sub { $comment //= $_[1] },
"force-key=s" => sub { $forceKey //= $_[1] },
"account=s" => sub { $account //= $_[1] },
"group=s" => sub { $group //= $_[1] },
"ip=s" => sub { $ip //= $_[1] },
"user=s" => sub { $user //= $_[1] },
"port=i" => sub { $port //= $_[1] },
"action=s" => sub { $action //= $_[1] },
"ttl=i" => sub { $ttl //= $_[1] },
"comment=s" => sub { $comment //= $_[1] },
"force-key=s" => sub { $forceKey //= $_[1] },
"proxy-ip=s" => sub { $proxyIp //= $_[1] },
"proxy-port=i" => sub { $proxyPort //= $_[1] },
"proxy-user=s" => sub { $proxyUser //= $_[1] },
);
};
if ($@) { die $@ }
@ -57,17 +60,28 @@ if (not grep { $action eq $_ } qw{ add del }) {
#<PARAMS:ACTION
#>CODE
$fnret = OVH::Bastion::load_configuration();
$fnret or main_exit(OVH::Bastion::EXIT_CONFIGURATION_FAILURE, "configuration_failure", $fnret->msg);
my $config = $fnret->value;
if ($action eq 'add' && $proxyIp && !$config->{'egressProxyJumpAllowed'}) {
HEXIT('ERR_INVALID_PARAMETER', msg => "ProxyJump egress connections are disabled by policy");
}
# access_modify validates all its parameters, don't do it ourselves here for clarity
$fnret = OVH::Bastion::access_modify(
way => 'groupguest',
account => $account,
group => $group,
action => $action,
user => $user,
ip => $ip,
port => $port,
ttl => $ttl,
comment => $comment,
forceKey => $forceKey
way => 'groupguest',
account => $account,
group => $group,
action => $action,
user => $user,
ip => $ip,
port => $port,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
ttl => $ttl,
comment => $comment,
forceKey => $forceKey
);
HEXIT($fnret);

@ -32,7 +32,10 @@ use OVH::Bastion::Helper;
# Fetch command options
my $fnret;
my ($result, @optwarns);
my ($account, $ip, $user, $port, $action, $ttl, $forceKey, $forcePassword, $target, $comment);
my (
$account, $ip, $user, $port, $action, $ttl, $forceKey,
$forcePassword, $target, $comment, $proxyIp, $proxyPort, $proxyUser
);
eval {
local $SIG{__WARN__} = sub { push @optwarns, shift };
$result = GetOptions(
@ -46,6 +49,9 @@ eval {
"force-password=s" => sub { $forcePassword //= $_[1] },
"target=s" => sub { $target //= $_[1] },
"comment=s" => sub { $comment //= $_[1] },
"proxy-ip=s" => sub { $proxyIp //= $_[1] },
"proxy-port=i" => sub { $proxyPort //= $_[1] },
"proxy-user=s" => sub { $proxyUser //= $_[1] },
);
};
if ($@) { die $@ }
@ -81,7 +87,22 @@ if (not grep { $action eq $_ } qw{ add del }) {
#<PARAMS:ACTION
#>CODE
my $machine = OVH::Bastion::machine_display(ip => $ip, port => $port, user => $user)->value;
$fnret = OVH::Bastion::load_configuration();
$fnret or main_exit(OVH::Bastion::EXIT_CONFIGURATION_FAILURE, "configuration_failure", $fnret->msg);
my $config = $fnret->value;
if ($action eq 'add' && $proxyIp && !$config->{'egressProxyJumpAllowed'}) {
HEXIT('ERR_INVALID_PARAMETER', msg => "ProxyJump egress connections are disabled by policy");
}
my $machine = OVH::Bastion::machine_display(
ip => $ip,
port => $port,
user => $user,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser
)->value;
my $plugin = ($target eq 'self' ? 'self' : 'account') . 'AddPersonalAccess';
@ -115,19 +136,25 @@ $fnret = OVH::Bastion::access_modify(
forcePassword => $forcePassword,
comment => $comment,
widestV4Prefix => $widestV4Prefix,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
);
if ($fnret->err eq 'OK') {
my $ttlmsg = $ttl ? ' (expires in ' . OVH::Bastion::duration2human(seconds => $ttl)->value->{'human'} . ')' : '';
HEXIT(
'OK',
value => {
action => $action,
account => $account,
ip => $ip,
user => $user,
port => $port,
ttl => $ttl,
comment => $comment
action => $action,
account => $account,
ip => $ip,
user => $user,
port => $port,
ttl => $ttl,
comment => $comment,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
},
msg => $action eq 'add'
? "Access to $machine was added to account $account$ttlmsg"

@ -19,7 +19,10 @@ use OVH::Bastion::Helper;
# Fetch command options
my $fnret;
my ($result, @optwarns);
my ($group, $user, $ip, $port, $action, $force, $forcePassword, $forceKey, $ttl, $comment);
my (
$group, $user, $ip, $port, $action, $force, $forcePassword,
$forceKey, $ttl, $comment, $proxyIp, $proxyPort, $proxyUser
);
eval {
local $SIG{__WARN__} = sub { push @optwarns, shift };
$result = GetOptions(
@ -33,6 +36,9 @@ eval {
"force-key=s" => sub { $forceKey //= $_[1] },
"ttl=i" => sub { $ttl //= $_[1] },
"comment=s" => sub { $comment //= $_[1] },
"proxy-ip=s" => sub { $proxyIp //= $_[1] },
"proxy-port=i" => sub { $proxyPort //= $_[1] },
"proxy-user=s" => sub { $proxyUser //= $_[1] },
);
};
@ -86,7 +92,22 @@ $fnret = OVH::Bastion::Helper::acquire_lock($lock_fh);
$fnret or HEXIT($fnret);
#>CODE
my $machine = OVH::Bastion::machine_display(ip => $ip, port => $port, user => $user)->value;
$fnret = OVH::Bastion::load_configuration();
$fnret or main_exit(OVH::Bastion::EXIT_CONFIGURATION_FAILURE, "configuration_failure", $fnret->msg);
my $config = $fnret->value;
if ($action eq 'add' && $proxyIp && !$config->{'egressProxyJumpAllowed'}) {
HEXIT('ERR_INVALID_PARAMETER', msg => "ProxyJump egress connections are disabled by policy");
}
my $machine = OVH::Bastion::machine_display(
ip => $ip,
port => $port,
user => $user,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser
)->value;
# access_modify validates all its parameters, don't do it ourselves here for clarity
$fnret = OVH::Bastion::access_modify(
@ -100,6 +121,9 @@ $fnret = OVH::Bastion::access_modify(
forceKey => $forceKey,
ttl => $ttl,
comment => $comment,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
);
if ($fnret->err eq 'OK') {
my $ttlmsg = $ttl ? ' (expires in ' . OVH::Bastion::duration2human(seconds => $ttl)->value->{'human'} . ')' : '';
@ -114,7 +138,10 @@ if ($fnret->err eq 'OK') {
forcePassword => $forcePassword,
forceKey => $forceKey,
ttl => $ttl,
comment => $comment
comment => $comment,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
},
msg => $action eq 'add'
? "Entry $machine was added to group $shortGroup$ttlmsg"

@ -21,6 +21,9 @@ my $remainingOptions = OVH::Bastion::Plugin::begin(
"force-password=s" => \my $forcePassword,
"ttl=s" => \my $ttl,
"comment=s" => \my $comment,
"proxy-host=s" => \my $proxyHost,
"proxy-port=s" => \my $proxyPort,
"proxy-user=s" => \my $proxyUser,
# undocumented/compatibility:
"user-any" => \my $userAny,
"port-any" => \my $portAny,
@ -33,28 +36,32 @@ Add an IP or IP block to a group's servers list
Usage: --osh SCRIPT_NAME --group GROUP --host HOST --user USER|* --port PORT|* [OPTIONS]
--group GROUP Specify which group this machine should be added to
--host HOST|IP|SUBNET Host(s) to add access to, either a HOST which will be resolved to an IP immediately,
or an IP, or a whole subnet using the PREFIX/SIZE notation
--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.
To allow any user, use '--user *' (you might need to escape '*' from your shell)
--port PORT|* Remote port allowed to connect to
To allow any port, use '--port *' (you might need to escape '*' from your shell)
--protocol PROTO Specify that a special protocol should be allowed for this HOST:PORT tuple, note that you
must not specify --user in that case. However, for this protocol to be usable under a given
remote user, access to the USER@HOST:PORT tuple must also be allowed.
PROTO must be one of:
scpup allow SCP upload, you--bastion-->server
scpdown allow SCP download, you<--bastion--server
sftp allow usage of the SFTP subsystem, through the bastion
rsync allow usage of rsync, through the bastion
--force Don't try the ssh connection, just add the host to the group blindly
--force-key FINGERPRINT Only use the key with the specified fingerprint to connect to the server (cf groupInfo)
--force-password HASH Only use the password with the specified hash to connect to the server (cf groupListPasswords)
--ttl SECONDS|DURATION Specify a number of seconds (or a duration string, such as "1d7h8m") after which the access will automatically expire
--comment "'ANY TEXT'" Add a comment alongside this server. Quote it twice as shown if you're under a shell.
--group GROUP Specify which group this machine should be added to
--host HOST|IP|SUBNET Host(s) to add access to, either a HOST which will be resolved to an IP immediately,
or an IP, or a whole subnet using the PREFIX/SIZE notation
--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.
To allow any user, use '--user *' (you might need to escape '*' from your shell)
--port PORT|* Remote port allowed to connect to
To allow any port, use '--port *' (you might need to escape '*' from your shell)
--protocol PROTO Specify that a special protocol should be allowed for this HOST:PORT tuple, note that you
must not specify --user in that case. However, for this protocol to be usable under a given
remote user, access to the USER@HOST:PORT tuple must also be allowed.
PROTO must be one of:
scpup allow SCP upload, you--bastion-->server
scpdown allow SCP download, you<--bastion--server
sftp allow usage of the SFTP subsystem, through the bastion
rsync allow usage of rsync, through the bastion
--force Don't try the ssh connection, just add the host to the group blindly
--force-key FINGERPRINT Only use the key with the specified fingerprint to connect to the server (cf groupInfo)
--force-password HASH Only use the password with the specified hash to connect to the server (cf groupListPasswords)
--ttl SECONDS|DURATION Specify a number of seconds (or a duration string, such as "1d7h8m") after which the access will automatically expire
--comment "'ANY TEXT'" Add a comment alongside this server. Quote it twice as shown if you're under a shell.
--proxy-host HOST|IP Use this host as a proxy/jump host to reach the target server
--proxy-port PORT Proxy host port to connect to (mandatory when --proxy-host is specified)
--proxy-user USER|PATTERN|* Proxy user to connect as (mandatory when --proxy-host is specified).
Globbing characters '*' and '?' are supported for pattern matching.
Examples::
@ -76,22 +83,36 @@ if (not $group or not $ip) {
"Missing mandatory parameter 'host' or 'group' (or host didn't resolve correctly)";
}
my $proxyIp;
if ($proxyHost) {
$fnret =
OVH::Bastion::validate_proxy_params(proxyHost => $proxyHost, proxyPort => $proxyPort, proxyUser => $proxyUser);
$fnret or osh_exit $fnret;
$proxyIp = $fnret->value->{'proxyIp'};
$proxyPort = $fnret->value->{'proxyPort'};
$proxyUser = $fnret->value->{'proxyUser'};
}
$fnret = OVH::Bastion::Plugin::ACL::check(
user => $user,
userAny => $userAny,
port => $port,
portAny => $portAny,
scpUp => $scpUp,
scpDown => $scpDown,
sftp => $sftp,
protocol => $protocol,
user => $user,
userAny => $userAny,
port => $port,
portAny => $portAny,
scpUp => $scpUp,
scpDown => $scpDown,
sftp => $sftp,
protocol => $protocol,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
);
if (!$fnret) {
help();
osh_exit($fnret);
}
$user = $fnret->value->{'user'};
$port = $fnret->value->{'port'};
$user = $fnret->value->{'user'};
$port = $fnret->value->{'port'};
$proxyUser = $fnret->value->{'proxyUser'};
$fnret = OVH::Bastion::is_valid_group_and_existing(group => $group, groupType => "key");
$fnret or osh_exit($fnret);
@ -138,7 +159,10 @@ if (not $force) {
port => $port,
ip => $ip,
forceKey => $forceKey,
forcePassword => $forcePassword
forcePassword => $forcePassword,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
);
if ($fnret->is_ok and $fnret->err ne 'OK') {
# we have something to say, say it
@ -170,5 +194,8 @@ push @command, '--force-key', $forceKey if $forceKey;
push @command, '--force-password', $forcePassword if $forcePassword;
push @command, '--ttl', $ttl if $ttl;
push @command, '--comment', $comment if $comment;
push @command, '--proxy-ip', $proxyIp if $proxyIp;
push @command, '--proxy-port', $proxyPort if $proxyPort;
push @command, '--proxy-user', $proxyUser if $proxyUser;
osh_exit OVH::Bastion::helper(cmd => \@command);

@ -4,14 +4,22 @@
"groupAddServer +--group" , {"ac" : ["<GROUP>"]},
"groupAddServer +--group +\\S+" , {"ac" : ["--host"]},
"groupAddServer +--group +\\S+ +--host" , {"pr" : ["<HOST>", "<IP>", "<IP/MASK>"]},
"groupAddServer +--group +\\S+ +--host +\\S+" , {"ac" : ["--port", "--port-any"]},
"groupAddServer +--group +\\S+ +--host +\\S+ +--port" , {"pr" : ["<PORT>"]},
"groupAddServer +--group +\\S+ +--host +\\S+ +--port(-any| +\\d+)" , {"ac" : ["--user", "--user-any"]},
"groupAddServer +--group +\\S+ +--host +\\S+ +--port(-any| +\\d+) +--user" , {"pr" : ["<USER>"]},
"groupAddServer +--group +\\S+ +--host +\\S+ +--port(-any| +\\d+) +--user(-any| +\\S+)" , {"ac" : ["<enter>", "--force-password", "--force"]},
"groupAddServer +--group +\\S+ +--host +\\S+ +--port(-any| +\\d+) +--user(-any| +\\S+) +--force-password" , {"pr" : ["<HASH>"]},
"groupAddServer +--group +\\S+ +--host +\\S+ +--port(-any| +\\d+) +--user(-any| +\\S+) +--force-password +\\S+" , {"ac" : ["<enter>", "--force"]},
"groupAddServer +--group +\\S+ +--host +\\S+ +--port(-any| +\\d+) +--user(-any| +\\S+) +--force-password +\\S+ +--force" , {"pr" : ["<enter>"]}
"groupAddServer +--group +\\S+ +--host +\\S+" , {"ac" : ["<enter>", "--user", "--port"]},
"groupAddServer +--group +\\S+ +--host +\\S+ +.*--user" , {"pr" : ["<USER>"]},
"groupAddServer +--group +\\S+ +--host +\\S+ +.*--port" , {"pr" : ["<PORT>"]},
"groupAddServer +--group +\\S+ +--host +\\S+ +--user +\\S+" , {"ac" : ["<enter>", "--port"]},
"groupAddServer +--group +\\S+ +--host +\\S+ +--port +\\S+" , {"ac" : ["<enter>", "--user"]},
"groupAddServer +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+" , {"ac" : ["--proxy-host", "--force-key", "--force-password", "--ttl", "--comment", "--force", "<enter>"]},
"groupAddServer +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host" , {"pr" : ["<PROXY_HOST>", "<PROXY_IP>"]},
"groupAddServer +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+" , {"ac" : ["--proxy-port"]},
"groupAddServer +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port" , {"pr" : ["<PROXY_PORT>"]},
"groupAddServer +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+" , {"ac" : ["--proxy-user"]},
"groupAddServer +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+ +.*--proxy-user" , {"pr" : ["<PROXY_USER>"]},
"groupAddServer +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+ +.*--proxy-user +\\S+" , {"ac" : ["--force-key", "--force-password", "--ttl", "--comment", "--force", "<enter>"]},
"groupAddServer +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--ttl" , {"pr" : ["<SECONDS>"]},
"groupAddServer +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--force-key" , {"pr" : ["<FINGERPRINT>"]},
"groupAddServer +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--force-password" , {"pr" : ["<HASH>"]},
"groupAddServer +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--comment" , {"pr" : ["<COMMENT>"]}
],
"master_only": true
}

@ -14,9 +14,12 @@ my $remainingOptions = OVH::Bastion::Plugin::begin(
header => "removing a server from a group",
userAllowWildcards => 1,
options => {
"group=s" => \my $group,
"protocol=s" => \my $protocol,
"force" => \my $force,
"group=s" => \my $group,
"protocol=s" => \my $protocol,
"force" => \my $force,
"proxy-host=s" => \my $proxyHost,
"proxy-port=s" => \my $proxyPort,
"proxy-user=s" => \my $proxyUser,
# undocumented/compatibility:
"user-any" => \my $userAny,
"port-any" => \my $portAny,
@ -45,6 +48,10 @@ Usage: --osh SCRIPT_NAME --group GROUP --host HOST --user USER --port PORT [OPTI
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
--proxy-host HOST|IP Specify which host was used as a proxy/jump host to reach the target server
--proxy-port PORT Proxy port that was used to reach the target server
--proxy-user USER|PATTERN|* Proxy user that was configured for this access (mandatory when --proxy-host is specified).
Globbing characters '*' and '?' are supported for pattern matching.
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``
@ -65,22 +72,36 @@ if (not $group or not $ip) {
"Missing mandatory parameter 'host' or 'group' (or host didn't resolve correctly)";
}
my $proxyIp;
if ($proxyHost) {
$fnret =
OVH::Bastion::validate_proxy_params(proxyHost => $proxyHost, proxyPort => $proxyPort, proxyUser => $proxyUser);
$fnret or osh_exit $fnret;
$proxyIp = $fnret->value->{'proxyIp'};
$proxyPort = $fnret->value->{'proxyPort'};
$proxyUser = $fnret->value->{'proxyUser'};
}
$fnret = OVH::Bastion::Plugin::ACL::check(
user => $user,
userAny => $userAny,
port => $port,
portAny => $portAny,
scpUp => $scpUp,
scpDown => $scpDown,
sftp => $sftp,
protocol => $protocol,
user => $user,
userAny => $userAny,
port => $port,
portAny => $portAny,
scpUp => $scpUp,
scpDown => $scpDown,
sftp => $sftp,
protocol => $protocol,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
);
if (!$fnret) {
help();
osh_exit($fnret);
}
$user = $fnret->value->{'user'};
$port = $fnret->value->{'port'};
$user = $fnret->value->{'user'};
$port = $fnret->value->{'port'};
$proxyUser = $fnret->value->{'proxyUser'};
$fnret = OVH::Bastion::is_valid_group_and_existing(group => $group, groupType => "key");
$fnret or osh_exit($fnret);
@ -100,10 +121,13 @@ $fnret
my @command = qw{ sudo -n -u };
push @command, ($group, '--', '/usr/bin/env', 'perl', '-T', $OVH::Bastion::BASEPATH . '/bin/helper/osh-groupAddServer');
push @command, '--group', $group;
push @command, '--action', 'del';
push @command, '--ip', $ip;
push @command, '--user', $user if $user;
push @command, '--port', $port if $port;
push @command, '--group', $group;
push @command, '--action', 'del';
push @command, '--ip', $ip;
push @command, '--user', $user if $user;
push @command, '--port', $port if $port;
push @command, '--proxy-ip', $proxyIp if $proxyIp;
push @command, '--proxy-port', $proxyPort if $proxyPort;
push @command, '--proxy-user', $proxyUser if $proxyUser;
osh_exit OVH::Bastion::helper(cmd => \@command);

@ -4,11 +4,18 @@
"groupDelServer +--group" , {"ac" : ["<GROUP>"]},
"groupDelServer +--group +\\S+" , {"ac" : ["--host"]},
"groupDelServer +--group +\\S+ +--host" , {"pr" : ["<HOST>", "<IP>", "<IP/MASK>"]},
"groupDelServer +--group +\\S+ +--host +\\S+" , {"ac" : ["--port", "--port-any"]},
"groupDelServer +--group +\\S+ +--host +\\S+ +--port" , {"pr" : ["<PORT>"]},
"groupDelServer +--group +\\S+ +--host +\\S+ +--port(-any| +\\d+)" , {"ac" : ["--user", "--user-any"]},
"groupDelServer +--group +\\S+ +--host +\\S+ +--port(-any| +\\d+) +--user" , {"pr" : ["<USER>"]},
"groupDelServer +--group +\\S+ +--host +\\S+ +--port(-any| +\\d+) +--user(-any| +\\S+)" , {"pr" : ["<enter>", "--force"]}
"groupDelServer +--group +\\S+ +--host +\\S+" , {"ac" : ["<enter>", "--user", "--port"]},
"groupDelServer +--group +\\S+ +--host +\\S+ +.*--user" , {"pr" : ["<USER>"]},
"groupDelServer +--group +\\S+ +--host +\\S+ +.*--port" , {"pr" : ["<PORT>"]},
"groupDelServer +--group +\\S+ +--host +\\S+ +--user +\\S+" , {"ac" : ["<enter>", "--port"]},
"groupDelServer +--group +\\S+ +--host +\\S+ +--port +\\S+" , {"ac" : ["<enter>", "--user"]},
"groupDelServer +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+" , {"ac" : ["--proxy-host", "<enter>"]},
"groupDelServer +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host" , {"pr" : ["<PROXY_HOST>", "<PROXY_IP>"]},
"groupDelServer +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+" , {"ac" : ["--proxy-port"]},
"groupDelServer +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port" , {"pr" : ["<PROXY_PORT>"]},
"groupDelServer +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+" , {"ac" : ["--proxy-user"]},
"groupDelServer +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+ +.*--proxy-user" , {"pr" : ["<PROXY_USER>"]},
"groupDelServer +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+ +.*--proxy-user +\\S+" , {"pr" : ["<enter>"]}
],
"master_only": true
}

@ -15,11 +15,14 @@ my $remainingOptions = OVH::Bastion::Plugin::begin(
header => "add access to one server of a group to an account",
userAllowWildcards => 1,
options => {
"group=s" => \my $group,
"protocol=s" => \my $protocol,
"account=s" => \my $account,
"ttl=s" => \my $ttl,
"comment=s" => \my $comment,
"group=s" => \my $group,
"protocol=s" => \my $protocol,
"account=s" => \my $account,
"ttl=s" => \my $ttl,
"comment=s" => \my $comment,
"proxy-host=s" => \my $proxyHost,
"proxy-port=s" => \my $proxyPort,
"proxy-user=s" => \my $proxyUser,
# undocumented/compatibility:
"user-any" => \my $userAny,
"port-any" => \my $portAny,
@ -32,28 +35,32 @@ Add a specific group server access to an account
Usage: --osh SCRIPT_NAME --group GROUP --account ACCOUNT [OPTIONS]
--account ACCOUNT Name of the other bastion account to add access to, they'll be given access to the GROUP key
--group GROUP Group to add the guest access to, note that this group should already have access
to the USER/HOST/PORT tuple you'll specify with the options below.
--host HOST|IP|SUBNET Host(s) to add access to, either a HOST which will be resolved to an IP immediately,
or an IP, or a whole subnet using the PREFIX/SIZE notation
--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.
To allow any user, use '--user *' (you might need to escape '*' from your shell)
--port PORT|* Remote port allowed to connect to
To allow any port, use '--port *' (you might need to escape '*' from your shell)
--protocol PROTO Specify that a special protocol should be allowed for this HOST:PORT tuple, note that you
must not specify --user in that case. However, for this protocol to be usable under a given
remote user, access to the USER@HOST:PORT tuple must also be allowed.
PROTO must be one of:
scpupload allow SCP upload, you--bastion-->server
scpdownload allow SCP download, you<--bastion--server
sftp allow usage of the SFTP subsystem, through the bastion
rsync allow usage of rsync, through the bastion
--ttl SECONDS|DURATION Specify a number of seconds after which the access will automatically expire
--comment '"ANY TEXT"' Add a comment alongside this access. Quote it twice as shown if you're under a shell.
If omitted, we'll use the closest preexisting group access' comment as seen in groupListServers
--account ACCOUNT Name of the other bastion account to add access to, they'll be given access to the GROUP key
--group GROUP Group to add the guest access to, note that this group should already have access
to the USER/HOST/PORT tuple you'll specify with the options below.
--host HOST|IP|SUBNET Host(s) to add access to, either a HOST which will be resolved to an IP immediately,
or an IP, or a whole subnet using the PREFIX/SIZE notation
--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.
To allow any user, use '--user *' (you might need to escape '*' from your shell)
--port PORT|* Remote port allowed to connect to
To allow any port, use '--port *' (you might need to escape '*' from your shell)
--protocol PROTO Specify that a special protocol should be allowed for this HOST:PORT tuple, note that you
must not specify --user in that case. However, for this protocol to be usable under a given
remote user, access to the USER@HOST:PORT tuple must also be allowed.
PROTO must be one of:
scpupload allow SCP upload, you--bastion-->server
scpdownload allow SCP download, you<--bastion--server
sftp allow usage of the SFTP subsystem, through the bastion
rsync allow usage of rsync, through the bastion
--ttl SECONDS|DURATION Specify a number of seconds after which the access will automatically expire
--comment '"ANY TEXT"' Add a comment alongside this access. Quote it twice as shown if you're under a shell.
If omitted, we'll use the closest preexisting group access' comment as seen in groupListServers
--proxy-host HOST|IP Use this host as a proxy/jump host to reach the target server
--proxy-port PORT Proxy host port to connect to (mandatory when --proxy-host is specified)
--proxy-user USER|PATTERN|* Proxy user to connect as (mandatory when --proxy-host is specified).
Globbing characters '*' and '?' are supported for pattern matching.
This command adds, to an existing bastion account, access to the egress keys of a group,
but only to accessing one or several given servers, instead of all the servers of this group.
@ -76,22 +83,36 @@ if (not $ip and $host) {
"Specified host ($host) didn't resolve correctly, fix your DNS or specify the IP instead";
}
my $proxyIp;
if ($proxyHost) {
$fnret =
OVH::Bastion::validate_proxy_params(proxyHost => $proxyHost, proxyPort => $proxyPort, proxyUser => $proxyUser);
$fnret or osh_exit $fnret;
$proxyIp = $fnret->value->{'proxyIp'};
$proxyPort = $fnret->value->{'proxyPort'};
$proxyUser = $fnret->value->{'proxyUser'};
}
$fnret = OVH::Bastion::Plugin::ACL::check(
user => $user,
userAny => $userAny,
port => $port,
portAny => $portAny,
scpUp => $scpUp,
scpDown => $scpDown,
sftp => $sftp,
protocol => $protocol,
user => $user,
userAny => $userAny,
port => $port,
portAny => $portAny,
scpUp => $scpUp,
scpDown => $scpDown,
sftp => $sftp,
protocol => $protocol,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
);
if (!$fnret) {
help();
osh_exit($fnret);
}
$user = $fnret->value->{'user'};
$port = $fnret->value->{'port'};
$user = $fnret->value->{'user'};
$port = $fnret->value->{'port'};
$proxyUser = $fnret->value->{'proxyUser'};
if (defined $ttl) {
$fnret = OVH::Bastion::is_valid_ttl(ttl => $ttl);
@ -108,6 +129,9 @@ $fnret = OVH::Bastion::Plugin::groupSetRole::act(
user => $user,
port => $port,
host => ($ip || $host),
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
ttl => $ttl,
comment => $comment,
sudo => 0,

@ -11,7 +11,15 @@
"groupAddGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +.*--port" , {"pr" : ["<PORT>"]},
"groupAddGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +--user +\\S+" , {"ac" : ["<enter>", "--port"]},
"groupAddGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +--port +\\S+" , {"ac" : ["<enter>", "--user"]},
"groupAddGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+" , {"pr" : ["<enter>"]}
"groupAddGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+" , {"ac" : ["--proxy-host", "--ttl", "--comment", "<enter>"]},
"groupAddGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host" , {"pr" : ["<PROXY_HOST>", "<PROXY_IP>"]},
"groupAddGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+" , {"ac" : ["--proxy-port"]},
"groupAddGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port" , {"pr" : ["<PROXY_PORT>"]},
"groupAddGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+" , {"ac" : ["--proxy-user"]},
"groupAddGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+ +.*--proxy-user" , {"pr" : ["<PROXY_USER>"]},
"groupAddGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+ +.*--proxy-user +\\S+" , {"ac" : ["--ttl", "--comment", "<enter>"]},
"groupAddGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--ttl" , {"pr" : ["<SECONDS>"]},
"groupAddGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--comment" , {"pr" : ["<COMMENT>"]}
],
"master_only": true
}

@ -15,9 +15,12 @@ my $remainingOptions = OVH::Bastion::Plugin::begin(
header => "remove access from one server of a group from an account",
userAllowWildcards => 1,
options => {
"group=s" => \my $group,
"protocol=s" => \my $protocol,
"account=s" => \my $account,
"group=s" => \my $group,
"protocol=s" => \my $protocol,
"account=s" => \my $account,
"proxy-host=s" => \my $proxyHost,
"proxy-port=s" => \my $proxyPort,
"proxy-user=s" => \my $proxyUser,
# undocumented/compatibility:
"user-any" => \my $userAny,
"port-any" => \my $portAny,
@ -30,24 +33,28 @@ Remove a specific group server access from an account
Usage: --osh SCRIPT_NAME --group GROUP --account ACCOUNT [OPTIONS]
--account ACCOUNT Bastion account remove the guest access from
--group GROUP Specify which group to remove the guest access to ACCOUNT from
--host HOST|IP|SUBNET Host(s) to remove access from, either a HOST which will be resolved to an IP immediately,
or an IP, or a whole subnet using the PREFIX/SIZE notation
--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.
If any user was allowed, use '--user *' (you might need to escape '*' from your shell)
--port PORT|* Remote port that was allowed to connect to
If any user was allowed, use '--port *' (you might need to escape '*' from your shell)
--protocol PROTO Specify that a special protocol was allowed for this HOST:PORT tuple, note that you
must not specify --user in that case. However, for this protocol to be usable under a given
remote user, access to the USER@HOST:PORT tuple must also be allowed.
PROTO must be one of:
scpupload allow SCP upload, you--bastion-->server
scpdownload allow SCP download, you<--bastion--server
sftp allow usage of the SFTP subsystem, through the bastion
rsync allow usage of rsync, through the bastion
--account ACCOUNT Bastion account remove the guest access from
--group GROUP Specify which group to remove the guest access to ACCOUNT from
--host HOST|IP|SUBNET Host(s) to remove access from, either a HOST which will be resolved to an IP immediately,
or an IP, or a whole subnet using the PREFIX/SIZE notation
--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.
If any user was allowed, use '--user *' (you might need to escape '*' from your shell)
--port PORT|* Remote port that was allowed to connect to
If any user was allowed, use '--port *' (you might need to escape '*' from your shell)
--protocol PROTO Specify that a special protocol was allowed for this HOST:PORT tuple, note that you
must not specify --user in that case. However, for this protocol to be usable under a given
remote user, access to the USER@HOST:PORT tuple must also be allowed.
PROTO must be one of:
scpupload allow SCP upload, you--bastion-->server
scpdownload allow SCP download, you<--bastion--server
sftp allow usage of the SFTP subsystem, through the bastion
rsync allow usage of rsync, through the bastion
--proxy-host HOST|IP Use this host as a proxy/jump host to reach the target server
--proxy-port PORT Proxy host port to connect to (mandatory when --proxy-host is specified)
--proxy-user USER|PATTERN|* Proxy user to connect as (mandatory when --proxy-host is specified).
Globbing characters '*' and '?' are supported for pattern matching.
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,22 +76,36 @@ if (not $ip and $host) {
"Specified host ($host) didn't resolve correctly, fix your DNS or specify the IP instead";
}
my $proxyIp;
if ($proxyHost) {
$fnret =
OVH::Bastion::validate_proxy_params(proxyHost => $proxyHost, proxyPort => $proxyPort, proxyUser => $proxyUser);
$fnret or osh_exit $fnret;
$proxyIp = $fnret->value->{'proxyIp'};
$proxyPort = $fnret->value->{'proxyPort'};
$proxyUser = $fnret->value->{'proxyUser'};
}
$fnret = OVH::Bastion::Plugin::ACL::check(
user => $user,
userAny => $userAny,
port => $port,
portAny => $portAny,
scpUp => $scpUp,
scpDown => $scpDown,
sftp => $sftp,
protocol => $protocol,
user => $user,
userAny => $userAny,
port => $port,
portAny => $portAny,
scpUp => $scpUp,
scpDown => $scpDown,
sftp => $sftp,
protocol => $protocol,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
);
if (!$fnret) {
help();
osh_exit($fnret);
}
$user = $fnret->value->{'user'};
$port = $fnret->value->{'port'};
$user = $fnret->value->{'user'};
$port = $fnret->value->{'port'};
$proxyUser = $fnret->value->{'proxyUser'};
$fnret = OVH::Bastion::Plugin::groupSetRole::act(
account => $account,
@ -94,6 +115,9 @@ $fnret = OVH::Bastion::Plugin::groupSetRole::act(
user => $user,
port => $port,
host => ($ip || $host),
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
sudo => 0,
silentoverride => 0,
self => $self,

@ -11,7 +11,13 @@
"groupDelGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +.*--port" , {"pr" : ["<PORT>"]},
"groupDelGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +--user +\\S+" , {"ac" : ["<enter>", "--port"]},
"groupDelGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +--port +\\S+" , {"ac" : ["<enter>", "--user"]},
"groupDelGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+" , {"pr" : ["<enter>"]}
"groupDelGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+" , {"ac" : ["--proxy-host", "<enter>"]},
"groupDelGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host" , {"pr" : ["<PROXY_HOST>", "<PROXY_IP>"]},
"groupDelGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+" , {"ac" : ["--proxy-port"]},
"groupDelGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port" , {"pr" : ["<PROXY_PORT>"]},
"groupDelGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+" , {"ac" : ["--proxy-user"]},
"groupDelGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+ +.*--proxy-user" , {"pr" : ["<PROXY_USER>"]},
"groupDelGuestAccess +--account +\\S+ +--group +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+ +.*--proxy-user +\\S+" , {"pr" : ["<enter>"]}
],
"master_only": true
}

@ -23,8 +23,11 @@ my ($scpCmd);
my $remainingOptions = OVH::Bastion::Plugin::begin(
argv => \@ARGV,
header => undef,
options => {'scp-cmd=s' => \$scpCmd},
help => \&help,
options => {
'scp-cmd=s' => \$scpCmd,
'proxy-jump=s' => \my $proxyJump,
},
help => \&help,
);
sub help {
@ -65,10 +68,11 @@ REMOTE_HOST=""
REMOTE_PORT=22
REMOTE_USER=""
REMOTE_PATH=""
PROXY_JUMP=""
usage() {
cat >&2 <<EOF
Usage: $0 [-p] [-q] [-r] [-T] [-v] [-l limit] [-i identity_file] [-P port] [-o ssh_option] source target
Usage: $0 [-p] [-q] [-r] [-T] [-v] [-l limit] [-i identity_file] [-P port] [-J jumphost] [-o ssh_option] source target
Please refer to the scp manpage for details about each command-line option above.
EOF
@ -90,6 +94,13 @@ while [ -n "${1:-}" ]; do
BASTION_SCP_EXTRA_ARGS="$BASTION_SCP_EXTRA_ARGS $1 $2"
fi
shift 2;;
"-J")
if [ -z "${2:-}" ]; then
echo "scpwrapper: missing argument after '$1'" >&2
exit 1
fi
PROXY_JUMP="$2"
shift 2;;
"-P"|"-l")
if [ -z "${2:-}" ]; then
echo "scpwrapper: missing argument after '$1'" >&2
@ -170,7 +181,11 @@ fi
# shellcheck disable=SC2086
[ "$BASTION_SCP_DEBUG" = 1 ] && set -x
$BASTION_CMD -t $BASTION_SSH_EXTRA_ARGS -- --osh scp --host "$REMOTE_HOST" --port "$REMOTE_PORT" --user "$REMOTE_USER" --generate-mfa-token | tee "$t"
if [ -n "$PROXY_JUMP" ]; then
$BASTION_CMD -t $BASTION_SSH_EXTRA_ARGS -- --osh scp --host "$REMOTE_HOST" --port "$REMOTE_PORT" --user "$REMOTE_USER" -J "$PROXY_JUMP" --generate-mfa-token | tee "$t"
else
$BASTION_CMD -t $BASTION_SSH_EXTRA_ARGS -- --osh scp --host "$REMOTE_HOST" --port "$REMOTE_PORT" --user "$REMOTE_USER" --generate-mfa-token | tee "$t"
fi
[ "$BASTION_SCP_DEBUG" = 1 ] && set +x
token=$(grep -Eo '^MFA_TOKEN=[a-zA-Z0-9,]+' "$t" | tail -n 1 | cut -d= -f2)
@ -229,7 +244,11 @@ scpcmd=$(echo "$3" | sed -e 's/#/##/g;s/ /#/g')
# and go
[ "$BASTION_SCP_DEBUG" = 1 ] && set -x
EOF
echo "exec $BASTION_CMD -T \$sshcmdline $BASTION_SSH_EXTRA_ARGS -- --user \"\$REMOTE_USER\" --port \"\$REMOTE_PORT\" --host \"\$REMOTE_HOST\" --osh scp --scp-cmd \"\$scpcmd\" --mfa-token $token" >> "$t"
if [ -n "$PROXY_JUMP" ]; then
echo "exec $BASTION_CMD -T \$sshcmdline $BASTION_SSH_EXTRA_ARGS -- --user \"\$REMOTE_USER\" --port \"\$REMOTE_PORT\" --host \"\$REMOTE_HOST\" -J \"$PROXY_JUMP\" --osh scp --proxy-jump \"$PROXY_JUMP\" --scp-cmd \"\$scpcmd\" --mfa-token $token" >> "$t"
else
echo "exec $BASTION_CMD -T \$sshcmdline $BASTION_SSH_EXTRA_ARGS -- --user \"\$REMOTE_USER\" --port \"\$REMOTE_PORT\" --host \"\$REMOTE_HOST\" --osh scp --scp-cmd \"\$scpcmd\" --mfa-token $token" >> "$t"
fi
chmod +x "$t"
# don't use exec below, because we need the trap to be executed on exit
@ -290,6 +309,40 @@ EOF
# code
#
my $fnret;
my $proxyIp = undef;
my $proxyPort = 22;
my $proxyUser = undef;
# Parse proxyjump args if specified
if ($proxyJump) {
if ($proxyJump =~ /^(?:([a-zA-Z0-9._@!-]{1,128})@)?(\[?[a-zA-Z0-9._-]+\]?)(?::(\d+))?$/) {
$proxyUser = $1 if $1;
$proxyIp = $2;
$proxyPort = $3 if $3;
osh_debug("parsed proxyjump: host=$proxyIp port=$proxyPort user=$proxyUser");
}
else {
osh_exit 'ERR_INVALID_PARAMETER', "Invalid proxyjump specification '$proxyJump', should be [user@]host[:port]";
}
$fnret = OVH::Bastion::get_ip(host => $proxyIp, allowSubnets => 0);
if (!$fnret) {
if ($fnret->err eq 'ERR_DNS_DISABLED') {
osh_exit 'ERR_DNS_DISABLED', $fnret->msg;
}
elsif ($fnret->err eq 'ERR_IP_VERSION_DISABLED') {
osh_exit 'ERR_IP_VERSION_DISABLED', $fnret->msg;
}
else {
osh_exit 'ERR_HOST_NOT_FOUND', $fnret->msg;
}
}
$proxyIp = $fnret->value->{'ip'};
osh_debug("Proxyjump host resolved to IP: $proxyIp");
if ($proxyUser && !OVH::Bastion::is_valid_remote_user(user => $proxyUser, allowWildcards => 0)) {
osh_exit 'EXIT_INVALID_REMOTE_USER', 'invalid_proxy_user', "Proxy user name '$proxyUser' seems invalid";
}
}
if (not $host) {
help();
@ -320,13 +373,19 @@ if ($decoded =~ m{[\`\$\;><\|\&]}) {
$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
if ($proxyIp) {
$proxyUser ||= $user;
}
$fnret = OVH::Bastion::Plugin::otherProtocol::has_protocol_access(
account => $self,
user => $user,
ip => $ip,
port => $port,
protocol => $protocol,
account => $self,
user => $user,
ip => $ip,
port => $port,
protocol => $protocol,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
);
$fnret or osh_exit($fnret);
@ -342,6 +401,27 @@ foreach my $key (@keys) {
push @cmd, ('-i', $key);
}
# Add ProxyCommand if proxy is specified
if ($proxyIp) {
# Build ProxyCommand similar to how osh.pl does it
my @proxyCommand = ('ssh');
push @proxyCommand, '-o', 'ForwardAgent=no', '-o', 'PermitLocalCommand=no', '-o', 'ClearAllForwardings=yes';
# Add the same SSH keys to the proxy command
foreach my $key (@keys) {
push @proxyCommand, ('-i', $key);
}
push @proxyCommand, '-p', $proxyPort if $proxyPort && $proxyPort != 22;
push @proxyCommand, '-l', $proxyUser, '-W', '%h:%p', $proxyIp;
# Quote arguments that contain spaces and build the command string
my $proxyCommandStr = join(' ', map { /\s/ ? "'$_'" : $_ } @proxyCommand);
push @cmd, '-o', "ProxyCommand=$proxyCommandStr";
osh_debug("ProxyCommand: $proxyCommandStr");
}
push @cmd, "--", $ip, $decoded;
print STDERR ">>> Hello $self, transferring your file through the bastion "

@ -14,18 +14,21 @@ my $remainingOptions = OVH::Bastion::Plugin::begin(
argv => \@ARGV,
header => "your past sessions list",
options => {
"detailed" => \my $detailed,
"id=s" => \my $id,
"type=s" => \my $type,
"allowed" => \my $allowed,
"denied" => \my $denied,
"after=s" => \my $after,
"before=s" => \my $before,
"from=s" => \my $from,
"via=s" => \my $via,
"via-port=i" => \my $viaPort,
"to-port=i" => \my $toPort,
"limit=i" => \my $limit,
"detailed" => \my $detailed,
"id=s" => \my $id,
"type=s" => \my $type,
"allowed" => \my $allowed,
"denied" => \my $denied,
"after=s" => \my $after,
"before=s" => \my $before,
"from=s" => \my $from,
"via=s" => \my $via,
"via-port=i" => \my $viaPort,
"to-port=i" => \my $toPort,
"limit=i" => \my $limit,
"proxy-user=s" => \my $proxyUser,
"proxy-ip=s" => \my $proxyIp,
"proxy-port=i" => \my $proxyPort,
},
helptext => <<'EOF',
List the few past sessions of your account
@ -47,6 +50,9 @@ Usage: --osh SCRIPT_NAME [OPTIONS]
--user USER Only sessions connecting using remote USER
--via HOST Only sessions that connected through bastion IP HOST
--via-port PORT Only sessions that connected through bastion PORT
--proxy-user USER Only sessions that used proxy USER
--proxy-ip HOST Only sessions that used proxy IP
--proxy-port PORT Only sessions that used proxy PORT
Note that only the sessions that happened on this precise bastion instance will be shown,
not the sessions from its possible cluster siblings.
@ -95,7 +101,10 @@ $fnret = OVH::Bastion::log_access_get(
bastionport => $viaPort,
toPort => $toPort,
user => $user,
limit => $limit
limit => $limit,
proxyuser => $proxyUser,
proxyip => $proxyIp,
proxyport => $proxyPort,
);
$fnret or osh_exit $fnret;
@ -148,6 +157,12 @@ else {
$r->{user} || $r->{ipto} || $r->{portto} || $r->{hostto}
? sprintf(' to %s@%s:%s(%s)', $r->{'user'}, $r->{'ipto'}, $r->{'portto'}, $r->{'hostto'})
: '';
if ($r->{proxyip}) {
$to .= sprintf(' via proxy %s@%s:%s(%s)',
$r->{'proxyuser'}, $r->{'proxyip'}, $r->{'proxyport'}, $r->{'proxyhost'});
}
$r->{params} = undef if ($r->{cmdtype} ne 'osh');
$r->{returnvalue} = $r->{comment} if $r->{returnvalue} < 0;
@ -177,6 +192,7 @@ else {
from => {ip => $r->{ipfrom}, host => $r->{hostfrom}, port => $r->{portfrom}},
via => {ip => $r->{bastionip}, port => $r->{bastionport}, user => $r->{account}},
to => {ip => $r->{ipto}, port => $r->{portto}, host => $r->{hostto}},
proxy => {ip => $r->{proxyip}, port => $r->{proxyport}, host => $r->{proxyhost}},
timestamp_started => $r->{timestamp} + $r->{timestampusec} / 1_000_000,
timestamp_ended => $r->{timestampend} + $r->{timestampendusec} / 1_000_000,
type => $r->{cmdtype},

@ -21,6 +21,9 @@ my $remainingOptions = OVH::Bastion::Plugin::begin(
"force-password=s" => \my $forcePassword,
"ttl=s" => \my $ttl,
"comment=s" => \my $comment,
"proxy-host=s" => \my $proxyHost,
"proxy-port=s" => \my $proxyPort,
"proxy-user=s" => \my $proxyUser,
# undocumented/compatibility:
"user-any" => \my $userAny,
"port-any" => \my $portAny,
@ -54,6 +57,10 @@ Usage: --osh SCRIPT_NAME --account ACCOUNT --host HOST --user USER --port PORT [
--force-password HASH Only use the password with the specified hash to connect to the server (cf accountListPasswords)
--ttl SECONDS|DURATION Specify a number of seconds (or a duration string, such as "1d7h8m") after which the access will automatically expire
--comment "'ANY TEXT'" Add a comment alongside this server. Quote it twice as shown if you're under a shell.
--proxy-host HOST|IP Use this host as a proxy/jump host to reach the target server
--proxy-port PORT Proxy host port to connect to (mandatory when --proxy-host is specified)
--proxy-user USER|PATTERN|* Proxy user to connect as (mandatory when --proxy-host is specified).
Globbing characters '*' and '?' are supported for pattern matching.
The access will work only if one of the account's personal egress public key has been copied to the remote server.
To get the list of an account's personal egress public keys, see ``accountListEgressKeyss`` and ``selfListEgressKeys``.
@ -94,22 +101,36 @@ if (!$ip) {
osh_exit 'ERR_MISSING_PARAMETER', "Missing parameter 'host' or didn't resolve correctly";
}
my $proxyIp;
if ($proxyHost) {
$fnret =
OVH::Bastion::validate_proxy_params(proxyHost => $proxyHost, proxyPort => $proxyPort, proxyUser => $proxyUser);
$fnret or osh_exit $fnret;
$proxyIp = $fnret->value->{'proxyIp'};
$proxyPort = $fnret->value->{'proxyPort'};
$proxyUser = $fnret->value->{'proxyUser'};
}
$fnret = OVH::Bastion::Plugin::ACL::check(
user => $user,
userAny => $userAny,
port => $port,
portAny => $portAny,
scpUp => $scpUp,
scpDown => $scpDown,
sftp => $sftp,
protocol => $protocol,
user => $user,
userAny => $userAny,
port => $port,
portAny => $portAny,
scpUp => $scpUp,
scpDown => $scpDown,
sftp => $sftp,
protocol => $protocol,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
);
if (!$fnret) {
help();
osh_exit($fnret);
}
$user = $fnret->value->{'user'};
$port = $fnret->value->{'port'};
$user = $fnret->value->{'user'};
$port = $fnret->value->{'port'};
$proxyUser = $fnret->value->{'proxyUser'};
if (defined $ttl) {
$fnret = OVH::Bastion::is_valid_ttl(ttl => $ttl);
@ -155,6 +176,14 @@ if ($pluginConfig && $pluginConfig->{'self_remote_user_only'}) {
}
}
if ($pluginConfig && $pluginConfig->{'self_remote_user_only'}) {
if ($proxyIp && (!$proxyUser || $proxyUser ne $account)) {
osh_exit('ERR_INVALID_PARAMETER',
msg => "This bastion policy forces the remote user of personal accesses to match\n"
. "the account name: you may retry with --proxy-user $account");
}
}
# if no comment is specified, but we're adding the server by hostname,
# use it to craft a comment
if (!$comment && $host ne $ip) {
@ -176,6 +205,9 @@ $fnret = OVH::Bastion::access_modify(
forcePassword => $forcePassword,
comment => $comment,
widestV4Prefix => ($pluginConfig ? $pluginConfig->{'widest_v4_prefix'} : undef),
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
);
$fnret or osh_exit($fnret);
@ -194,5 +226,8 @@ push @command, '--force-key', $forceKey if $forceKey;
push @command, '--force-password', $forcePassword if $forcePassword;
push @command, '--ttl', $ttl if $ttl;
push @command, '--comment', $comment if $comment;
push @command, '--proxy-ip', $proxyIp if $proxyIp;
push @command, '--proxy-port', $proxyPort if $proxyPort;
push @command, '--proxy-user', $proxyUser if $proxyUser;
osh_exit OVH::Bastion::helper(cmd => \@command);

@ -9,17 +9,17 @@
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +.*--port" , {"pr" : ["<PORT>"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--user +\\S+" , {"ac" : ["<enter>", "--port"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--port +\\S+" , {"ac" : ["<enter>", "--user"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+" , {"ac" : ["--force-key","--force-password","--ttl","<enter>"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +--ttl" , {"pr" : ["<SECONDS>"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +--ttl \\S+" , {"ac" : ["--force-key","--force-password","<enter>"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +--ttl \\S+ --force-key" , {"pr" : ["<FINGERPRINT>"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +--ttl \\S+ --force-password" , {"pr" : ["<HASH>"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +--ttl \\S+ --force-(key|password) \\S+" , {"pr" : ["<enter>"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +--force-key" , {"pr" : ["<FINGERPRINT>"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +--force-password" , {"pr" : ["<HASH>"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +--force-(key|password) +\\S+" , {"ac" : ["--ttl","<enter>"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +--force-(key|password) +\\S+ +--ttl" , {"pr" : ["<SECONDS>"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +--force-(key|password) +\\S+ +--ttl +\\S+" , {"pr" : ["<enter>"]}
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+" , {"ac" : ["--proxy-host", "--force-key", "--force-password", "--ttl", "--comment", "<enter>"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host" , {"pr" : ["<PROXY_HOST>", "<PROXY_IP>"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+" , {"ac" : ["--proxy-port"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port" , {"pr" : ["<PROXY_PORT>"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+" , {"ac" : ["--proxy-user"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+ +.*--proxy-user" , {"pr" : ["<PROXY_USER>"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+ +.*--proxy-user +\\S+" , {"ac" : ["--force-key", "--force-password", "--ttl", "--comment", "<enter>"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--ttl" , {"pr" : ["<SECONDS>"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--force-key" , {"pr" : ["<FINGERPRINT>"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--force-password" , {"pr" : ["<HASH>"]},
"accountAddPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--comment" , {"pr" : ["<COMMENT>"]}
],
"master_only": true
}

@ -14,8 +14,11 @@ my $remainingOptions = OVH::Bastion::Plugin::begin(
header => "removing personal access to a server from an account",
userAllowWildcards => 1,
options => {
"account=s" => \my $account,
"protocol=s" => \my $protocol,
"account=s" => \my $account,
"protocol=s" => \my $protocol,
"proxy-host=s" => \my $proxyHost,
"proxy-port=s" => \my $proxyPort,
"proxy-user=s" => \my $proxyUser,
# undocumented/compatibility:
"user-any" => \my $userAny,
"port-any" => \my $portAny,
@ -44,6 +47,10 @@ Usage: --osh SCRIPT_NAME --account ACCOUNT --host HOST --user USER --port PORT [
scpdownload allow SCP download, you<--bastion--server
sftp allow usage of the SFTP subsystem, through the bastion
rsync allow usage of rsync, through the bastion
--proxy-host HOST|IP Specify which host was used as a proxy/jump host to reach the target server
--proxy-port PORT Proxy port that was used to reach the target server
--proxy-user USER|PATTERN|* Proxy user that was configured for this access (mandatory when --proxy-host is specified).
Globbing characters '*' and '?' are supported for pattern matching.
EOF
);
@ -54,22 +61,36 @@ if (!$ip) {
osh_exit 'ERR_MISSING_PARAMETER', "Missing parameter 'host' or didn't resolve correctly";
}
my $proxyIp;
if ($proxyHost) {
$fnret =
OVH::Bastion::validate_proxy_params(proxyHost => $proxyHost, proxyPort => $proxyPort, proxyUser => $proxyUser);
$fnret or osh_exit $fnret;
$proxyIp = $fnret->value->{'proxyIp'};
$proxyPort = $fnret->value->{'proxyPort'};
$proxyUser = $fnret->value->{'proxyUser'};
}
$fnret = OVH::Bastion::Plugin::ACL::check(
user => $user,
userAny => $userAny,
port => $port,
portAny => $portAny,
scpUp => $scpUp,
scpDown => $scpDown,
sftp => $sftp,
protocol => $protocol,
user => $user,
userAny => $userAny,
port => $port,
portAny => $portAny,
scpUp => $scpUp,
scpDown => $scpDown,
sftp => $sftp,
protocol => $protocol,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
);
if (!$fnret) {
help();
osh_exit($fnret);
}
$user = $fnret->value->{'user'};
$port = $fnret->value->{'port'};
$user = $fnret->value->{'user'};
$port = $fnret->value->{'port'};
$proxyUser = $fnret->value->{'proxyUser'};
if (not $account) {
help();
@ -82,11 +103,14 @@ $account = $fnret->value->{'account'};
my @command = qw{ sudo -n -u allowkeeper -- /usr/bin/env perl -T };
push @command, $OVH::Bastion::BASEPATH . '/bin/helper/osh-accountModifyPersonalAccess';
push @command, '--target', 'any';
push @command, '--action', 'del';
push @command, '--account', $account;
push @command, '--ip', $ip;
push @command, '--user', $user if $user;
push @command, '--port', $port if $port;
push @command, '--target', 'any';
push @command, '--action', 'del';
push @command, '--account', $account;
push @command, '--ip', $ip;
push @command, '--user', $user if $user;
push @command, '--port', $port if $port;
push @command, '--proxy-ip', $proxyIp if $proxyIp;
push @command, '--proxy-port', $proxyPort if $proxyPort;
push @command, '--proxy-user', $proxyUser if $proxyUser;
osh_exit OVH::Bastion::helper(cmd => \@command);

@ -9,7 +9,13 @@
"accountDelPersonalAccess +--account +\\S+ +--host +\\S+ +.*--port" , {"pr" : ["<PORT>"]},
"accountDelPersonalAccess +--account +\\S+ +--host +\\S+ +--user +\\S+" , {"ac" : ["<enter>", "--port"]},
"accountDelPersonalAccess +--account +\\S+ +--host +\\S+ +--port +\\S+" , {"ac" : ["<enter>", "--user"]},
"accountDelPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+" , {"pr" : ["<enter>"]}
"accountDelPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+" , {"ac" : ["--proxy-host", "<enter>"]},
"accountDelPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host" , {"pr" : ["<PROXY_HOST>", "<PROXY_IP>"]},
"accountDelPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+" , {"ac" : ["--proxy-port"]},
"accountDelPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port" , {"pr" : ["<PROXY_PORT>"]},
"accountDelPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+" , {"ac" : ["--proxy-user"]},
"accountDelPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+ +.*--proxy-user" , {"pr" : ["<PROXY_USER>"]},
"accountDelPersonalAccess +--account +\\S+ +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+ +.*--proxy-user +\\S+" , {"pr" : ["<enter>"]}
],
"master_only": true
}

@ -21,6 +21,9 @@ my $remainingOptions = OVH::Bastion::Plugin::begin(
"force" => \my $force,
"ttl=s" => \my $ttl,
"comment=s" => \my $comment,
"proxy-host=s" => \my $proxyHost,
"proxy-port=s" => \my $proxyPort,
"proxy-user=s" => \my $proxyUser,
# undocumented/compatibility:
"user-any" => \my $userAny,
"port-any" => \my $portAny,
@ -54,6 +57,11 @@ Usage: --osh SCRIPT_NAME --host HOST --user USER --port PORT [OPTIONS]
--force-password HASH Only use the password with the specified hash to connect to the server (cf selfListPasswords)
--ttl SECONDS|DURATION Specify a number of seconds (or a duration string, such as "1d7h8m") after which the access will automatically expire
--comment "'ANY TEXT'" Add a comment alongside this server. Quote it twice as shown if you're under a shell.
--proxy-host HOST|IP Use this host as a proxy/jump host to reach the target server
--proxy-port PORT Proxy host port to connect to (mandatory when --proxy-host is specified)
--proxy-user USER|PATTERN|* Proxy user to connect as (mandatory when --proxy-host is specified).
Globbing characters '*' and '?' are supported for pattern matching.
When connecting via SSH (not plugins), defaults to --user value for convenience.
EOF
);
@ -90,22 +98,36 @@ if (!$ip) {
osh_exit 'ERR_MISSING_PARAMETER', "Missing parameter 'host' or didn't resolve correctly";
}
my $proxyIp;
if ($proxyHost) {
$fnret =
OVH::Bastion::validate_proxy_params(proxyHost => $proxyHost, proxyPort => $proxyPort, proxyUser => $proxyUser);
$fnret or osh_exit $fnret;
$proxyIp = $fnret->value->{'proxyIp'};
$proxyPort = $fnret->value->{'proxyPort'};
$proxyUser = $fnret->value->{'proxyUser'};
}
$fnret = OVH::Bastion::Plugin::ACL::check(
user => $user,
userAny => $userAny,
port => $port,
portAny => $portAny,
scpUp => $scpUp,
scpDown => $scpDown,
sftp => $sftp,
protocol => $protocol,
user => $user,
userAny => $userAny,
port => $port,
portAny => $portAny,
scpUp => $scpUp,
scpDown => $scpDown,
sftp => $sftp,
protocol => $protocol,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
);
if (!$fnret) {
help();
osh_exit($fnret);
}
$user = $fnret->value->{'user'};
$port = $fnret->value->{'port'};
$user = $fnret->value->{'user'};
$port = $fnret->value->{'port'};
$proxyUser = $fnret->value->{'proxyUser'};
if (defined $ttl) {
$fnret = OVH::Bastion::is_valid_ttl(ttl => $ttl);
@ -142,6 +164,14 @@ if ($pluginConfig && $pluginConfig->{'self_remote_user_only'}) {
}
}
if ($pluginConfig && $pluginConfig->{'self_remote_user_only'}) {
if ($proxyIp && (!$proxyUser || $proxyUser ne $self)) {
osh_exit('ERR_INVALID_PARAMETER',
msg => "This bastion policy forces the remote user of personal accesses to match\n"
. "the account name: you may retry with --proxy-user $self");
}
}
# if no comment is specified, but we're adding the server by hostname,
# use it to craft a comment
if (!$comment && $host ne $ip) {
@ -162,6 +192,9 @@ $fnret = OVH::Bastion::access_modify(
forceKey => $forceKey,
forcePassword => $forcePassword,
comment => $comment,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
widestV4Prefix => ($pluginConfig ? $pluginConfig->{'widest_v4_prefix'} : undef),
);
$fnret or osh_exit($fnret);
@ -173,7 +206,10 @@ if (not $force) {
port => $port,
ip => $ip,
forceKey => $forceKey,
forcePassword => $forcePassword
forcePassword => $forcePassword,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
);
if ($fnret->is_ok and $fnret->err ne 'OK') {
# we have something to say, say it
@ -200,5 +236,8 @@ push @command, '--force-key', $forceKey if $forceKey;
push @command, '--force-password', $forcePassword if $forcePassword;
push @command, '--ttl', $ttl if $ttl;
push @command, '--comment', $comment if $comment;
push @command, '--proxy-ip', $proxyIp if $proxyIp;
push @command, '--proxy-port', $proxyPort if $proxyPort;
push @command, '--proxy-user', $proxyUser if $proxyUser;
osh_exit OVH::Bastion::helper(cmd => \@command);

@ -7,17 +7,17 @@
"selfAddPersonalAccess +--host +\\S+ +.*--port" , {"pr" : ["<PORT>"]},
"selfAddPersonalAccess +--host +\\S+ +--user +\\S+" , {"ac" : ["<enter>", "--port"]},
"selfAddPersonalAccess +--host +\\S+ +--port +\\S+" , {"ac" : ["<enter>", "--user"]},
"selfAddPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+" , {"ac" : ["--force-key","--force-password","--ttl","<enter>"]},
"selfAddPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +--ttl" , {"pr" : ["<SECONDS>"]},
"selfAddPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +--ttl +\\S+" , {"ac" : ["--force-key","--force-password","<enter>"]},
"selfAddPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +--ttl +\\S+ --force-key" , {"pr" : ["<FINGERPRINT>"]},
"selfAddPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +--ttl +\\S+ --force-password" , {"pr" : ["<HASH>"]},
"selfAddPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +--ttl +\\S+ --force-(key|password) \\S+" , {"pr" : ["<enter>"]},
"selfAddPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +--force-key" , {"pr" : ["<FINGERPRINT>"]},
"selfAddPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +--force-password" , {"pr" : ["<HASH>"]},
"selfAddPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +--force-(key|password) +\\S+" , {"ac" : ["--ttl","<enter>"]},
"selfAddPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +--force-(key|password) +\\S+ +--ttl" , {"pr" : ["<SECONDS>"]},
"selfAddPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +--force-(key|password) +\\S+ +--ttl +\\S+" , {"ac" : ["<enter>"]}
"selfAddPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+" , {"ac" : ["--proxy-host", "--force-key", "--force-password", "--ttl", "--force", "--comment", "<enter>"]},
"selfAddPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host" , {"pr" : ["<PROXY_HOST>", "<PROXY_IP>"]},
"selfAddPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+" , {"ac" : ["--proxy-port"]},
"selfAddPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port" , {"pr" : ["<PROXY_PORT>"]},
"selfAddPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+" , {"ac" : ["--proxy-user"]},
"selfAddPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+ +.*--proxy-user" , {"pr" : ["<PROXY_USER>"]},
"selfAddPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+ +.*--proxy-user +\\S+" , {"ac" : ["--force-key", "--force-password", "--ttl", "--force", "--comment", "<enter>"]},
"selfAddPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--ttl" , {"pr" : ["<SECONDS>"]},
"selfAddPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--force-key" , {"pr" : ["<FINGERPRINT>"]},
"selfAddPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--force-password" , {"pr" : ["<HASH>"]},
"selfAddPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--comment" , {"pr" : ["<COMMENT>"]}
],
"master_only": true
}

@ -14,7 +14,10 @@ my $remainingOptions = OVH::Bastion::Plugin::begin(
header => "removing personal access to a server from an account",
userAllowWildcards => 1,
options => {
"protocol=s" => \my $protocol,
"protocol=s" => \my $protocol,
"proxy-host=s" => \my $proxyHost,
"proxy-port=s" => \my $proxyPort,
"proxy-user=s" => \my $proxyUser,
# undocumented/compatibility:
"user-any" => \my $userAny,
"port-any" => \my $portAny,
@ -42,6 +45,10 @@ Usage: --osh SCRIPT_NAME --host HOST --user USER --port PORT [OPTIONS]
scpdownload allow SCP download, you<--bastion--server
sftp allow usage of the SFTP subsystem, through the bastion
rsync allow usage of rsync, through the bastion
--proxy-host HOST|IP Specify which host was used as a proxy/jump host to reach the target server
--proxy-port PORT Proxy port that was used to reach the target server
--proxy-user USER|PATTERN|* Proxy user that was configured for this access (mandatory when --proxy-host is specified).
Globbing characters '*' and '?' are supported for pattern matching.
EOF
);
@ -52,30 +59,47 @@ if (!$ip) {
osh_exit 'ERR_MISSING_PARAMETER', "Missing parameter 'host' or didn't resolve correctly";
}
my $proxyIp;
if ($proxyHost) {
$fnret =
OVH::Bastion::validate_proxy_params(proxyHost => $proxyHost, proxyPort => $proxyPort, proxyUser => $proxyUser);
$fnret or osh_exit $fnret;
$proxyIp = $fnret->value->{'proxyIp'};
$proxyPort = $fnret->value->{'proxyPort'};
$proxyUser = $fnret->value->{'proxyUser'};
}
$fnret = OVH::Bastion::Plugin::ACL::check(
user => $user,
userAny => $userAny,
port => $port,
portAny => $portAny,
scpUp => $scpUp,
scpDown => $scpDown,
sftp => $sftp,
protocol => $protocol,
user => $user,
userAny => $userAny,
port => $port,
portAny => $portAny,
scpUp => $scpUp,
scpDown => $scpDown,
sftp => $sftp,
protocol => $protocol,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
);
if (!$fnret) {
help();
osh_exit($fnret);
}
$user = $fnret->value->{'user'};
$port = $fnret->value->{'port'};
$user = $fnret->value->{'user'};
$port = $fnret->value->{'port'};
$proxyUser = $fnret->value->{'proxyUser'};
my @command = qw{ sudo -n -u allowkeeper -- /usr/bin/env perl -T };
push @command, $OVH::Bastion::BASEPATH . '/bin/helper/osh-accountModifyPersonalAccess';
push @command, '--target', 'self';
push @command, '--action', 'del';
push @command, '--account', $self;
push @command, '--ip', $ip;
push @command, '--user', $user if $user;
push @command, '--port', $port if $port;
push @command, '--target', 'self';
push @command, '--action', 'del';
push @command, '--account', $self;
push @command, '--ip', $ip;
push @command, '--user', $user if $user;
push @command, '--port', $port if $port;
push @command, '--proxy-ip', $proxyIp if $proxyIp;
push @command, '--proxy-port', $proxyPort if $proxyPort;
push @command, '--proxy-user', $proxyUser if $proxyUser;
osh_exit OVH::Bastion::helper(cmd => \@command);

@ -7,7 +7,13 @@
"selfDelPersonalAccess +--host +\\S+ +.*--port" , {"pr" : ["<PORT>"]},
"selfDelPersonalAccess +--host +\\S+ +--user +\\S+" , {"ac" : ["<enter>", "--port"]},
"selfDelPersonalAccess +--host +\\S+ +--port +\\S+" , {"ac" : ["<enter>", "--user"]},
"selfDelPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+" , {"pr" : ["<enter>"]}
"selfDelPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+" , {"ac" : ["--proxy-host", "<enter>"]},
"selfDelPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host" , {"pr" : ["<PROXY_HOST>", "<PROXY_IP>"]},
"selfDelPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+" , {"ac" : ["--proxy-port"]},
"selfDelPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port" , {"pr" : ["<PROXY_PORT>"]},
"selfDelPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+" , {"ac" : ["--proxy-user"]},
"selfDelPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+ +.*--proxy-user" , {"pr" : ["<PROXY_USER>"]},
"selfDelPersonalAccess +--host +\\S+ +--(port|user) +\\S+ +--(port|user) +\\S+ +.*--proxy-host +\\S+ +.*--proxy-port +\\S+ +.*--proxy-user +\\S+" , {"pr" : ["<enter>"]}
],
"master_only": true
}

@ -12,11 +12,14 @@ use OVH::Bastion::Plugin qw( :DEFAULT help );
# globally allow sys_getpw* and sys_getgr* cache use
$ENV{'PW_GR_CACHE'} = 1;
my (@ignoreGroups, $ignorePersonal, $showWildcards);
my ($proxyUser, $proxyHost, $proxyPort, @ignoreGroups, $ignorePersonal, $showWildcards);
my $remainingOptions = OVH::Bastion::Plugin::begin(
argv => \@ARGV,
header => "who has access to this?",
options => {
"proxy-user=s" => \$proxyUser,
"proxy-host=s" => \$proxyHost,
"proxy-port=i" => \$proxyPort,
"ignore-group=s" => \@ignoreGroups,
"ignore-private" => \$ignorePersonal, # deprecated name
"ignore-personal" => \$ignorePersonal,
@ -30,6 +33,9 @@ Usage: --osh SCRIPT_NAME --host SERVER [OPTIONS]
--host SERVER List declared accesses to this server
--user USER Remote user allowed (if not specified, ignore user specifications)
--port PORT Remote port allowed (if not specified, ignore port specifications)
--proxy-user USER Proxy user allowed (if egress connection goes through a proxyjump)
--proxy-host HOST Proxy host allowed (if egress connection goes through a proxyjump)
--proxy-port PORT Proxy port allowed (if egress connection goes through a proxyjump)
--ignore-personal Don't check accounts' personal accesses (i.e. only check groups)
--ignore-group GROUP Ignore accesses by this group, if you know GROUP public key is in fact
not present on remote server but bastion thinks it is
@ -54,6 +60,16 @@ if (!$ip) {
osh_exit 'ERR_MISSING_PARAMETER', "Missing parameter host (or it didn't resolve correctly)";
}
my $proxyIp;
if ($proxyHost) {
$fnret =
OVH::Bastion::validate_proxy_params(proxyHost => $proxyHost, proxyPort => $proxyPort, proxyUser => $proxyUser);
$fnret or osh_exit $fnret;
$proxyIp = $fnret->value->{'proxyIp'};
$proxyPort = $fnret->value->{'proxyPort'};
$proxyUser = $fnret->value->{'proxyUser'};
}
$fnret = OVH::Bastion::get_account_list();
$fnret or osh_exit $fnret;
@ -67,14 +83,18 @@ sub process_account {
my $account = $params{'account'};
$fnret = OVH::Bastion::is_access_granted(
account => $account,
user => $user,
ip => $ip,
ipfrom => $ENV{'OSH_IP_FROM'},
port => $port,
cache => 1,
ignorePort => ($port ? 0 : 1), # return accesses without checking for the specified port
ignoreUser => ($user ? 0 : 1), # return accesses without checking for the specified remote user
account => $account,
user => $user,
ip => $ip,
ipfrom => $ENV{'OSH_IP_FROM'},
port => $port,
proxyUser => $proxyUser,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
cache => 1,
ignorePort => ($port ? 0 : 1), # return accesses without checking for the specified port
ignoreUser => ($user ? 0 : 1), # return accesses without checking for the specified remote user
ignoreProxyUser => ($proxyUser ? 0 : 1), # return accesses without checking for the specified proxy user
);
if ($fnret) {
my $byPersonal = 0;

@ -2,9 +2,12 @@
"interactive": [
"whoHasAccessTo" , {"ac" : ["--host"]},
"whoHasAccessTo +--host" , {"pr" : ["<IP/HOST>"]},
"whoHasAccessTo +--host +\\S+ +(.*--(user|port|ignore-group) +\\S+| +.*--(ignore-wildcard|ignore-private))?$" , {"ac" : ["--user","--port","--ignore-wildcard","--ignore-private","--ignore-group","<enter>"]},
"whoHasAccessTo +--host +\\S+ +.*--user" , {"pr" : ["<USER>"]},
"whoHasAccessTo +--host +\\S+ +.*--port" , {"pr" : ["<PORT>"]},
"whoHasAccessTo +--host +\\S+ +.*--ignore-group" , {"ac" : ["<GROUP>"]}
"whoHasAccessTo +--host +\\S+" , {"ac" : ["--user", "--port", "--proxy-host", "--proxy-port", "--proxy-user", "--ignore-group", "--ignore-personal", "--show-wildcards", "<enter>"]},
"whoHasAccessTo +--host +\\S+ +.*--user" , {"pr" : ["<USER>"]},
"whoHasAccessTo +--host +\\S+ +.*--port" , {"pr" : ["<PORT>"]},
"whoHasAccessTo +--host +\\S+ +.*--proxy-host" , {"pr" : ["<PROXY_HOST>", "<PROXY_IP>"]},
"whoHasAccessTo +--host +\\S+ +.*--proxy-port" , {"pr" : ["<PROXY_PORT>"]},
"whoHasAccessTo +--host +\\S+ +.*--proxy-user" , {"pr" : ["<PROXY_USER>"]},
"whoHasAccessTo +--host +\\S+ +.*--ignore-group" , {"ac" : ["<GROUP>"]}
]
}

@ -70,7 +70,11 @@ sub main_exit {
plugin => undef,
params => join('^', @ARGV),
comment => $comment,
uniqid => $log_uniq_id
uniqid => $log_uniq_id,
proxyuser => undef,
proxyip => undef,
proxyhost => undef,
proxyport => undef
) if (not defined $log_db_name or not defined $log_insert_id);
my $R = R($retcode eq OVH::Bastion::EXIT_OK ? 'OK' : 'KO_' . uc($comment), msg => $msg);
@ -268,9 +272,47 @@ osh_debug("user-passed options : $realOptions");
# Command params
#
# special case: check if this is a ProxyJump connection that should be executed directly
if ($ENV{'OSH_PROXYJUMP_CONNECTION'}) {
delete $ENV{'OSH_PROXYJUMP_CONNECTION'}; # make sure nothing else gets interpreted as proxyjump
# check if poxyJump connections are allowed
# This condition should never be true if proxyJump isn't allowed, but let's double check
if (!$config->{'egressProxyJumpAllowed'}) {
main_exit OVH::Bastion::EXIT_ACCESS_DENIED, "proxyjump_not_allowed",
"Sorry $self, egress proxy-jump connections have been disabled by policy";
}
osh_debug("Detected ProxyJump connection, executing command directly");
# Execute the proxy command directly without further validation
if ($realOptions) {
# Parse the command to extract program and arguments
my @cmd_parts = split(/\s+/, $realOptions);
if (!@cmd_parts) {
main_exit(OVH::Bastion::EXIT_EXEC_FAILED, "proxy_parsing_failed", "Failed to parse proxy command");
}
# Remove "exec" if it's the first argument (the ssh subprocess puts that there)
if ($cmd_parts[0] eq 'exec') {
shift @cmd_parts;
}
# this should never happen, but just in case...
if ($cmd_parts[0] ne 'ssh') {
main_exit(OVH::Bastion::EXIT_EXEC_FAILED, "proxy_no_ssh_cmd", "Proxy command must start with 'ssh'");
}
osh_debug("Executing proxy command parts: " . join(' ', @cmd_parts));
exec(@cmd_parts)
or main_exit(OVH::Bastion::EXIT_EXEC_FAILED, "proxy_exec_failed", "Failed to execute proxy command: $!");
}
else {
main_exit(OVH::Bastion::EXIT_EXEC_FAILED, "proxy_no_cmd", "No proxy command provided");
}
}
my $port = 22; # can be override by special port
my @toExecute;
# special case: mosh, in that case we have something like this in $realOptions
# mosh-server 'new' '-s' '-c' '256' '-l' 'LANG=en_US.UTF-8' '-l' 'LANGUAGE=en_US' '--' '--osh' 'info'
if (defined $realOptions && $realOptions =~ /^mosh-server (.+?) '--' (.*)/) {
@ -382,6 +424,7 @@ my $remainingOptions;
"generate-mfa-token" => \my $generateMfaToken,
"mfa-token=s" => \my $mfaToken,
"term-passthrough" => \my $termPassthrough,
"J=s" => \my $proxyJump,
);
if (not defined $realOptions) {
help();
@ -522,7 +565,11 @@ if ($interactive and not $ENV{'OSH_IN_INTERACTIVE_SESSION'}) {
plugin => undef,
params => undef,
comment => undef,
uniqid => $log_uniq_id
uniqid => $log_uniq_id,
proxyuser => undef,
proxyip => undef,
proxyhost => undef,
proxyport => undef
);
if ($logret) {
@ -576,6 +623,7 @@ else {
}
else {
$host = shift(@{$remainingOptions});
osh_debug("After shift, remainingOptions " . join('/', @$remainingOptions));
if ($host eq '-osh' || $host eq '--osh') {
# special case when using -osh without argument
@ -599,6 +647,45 @@ else {
}
}
my $proxyIp = undef;
my $proxyPort = undef;
my $proxyUser = $user; # user might be undef. We'll handle that later
# Parse proxyjump args if specified
if ($proxyJump) {
if ($proxyJump =~ /^(?:([a-zA-Z0-9._@!-]{1,128})@)?(\[?[a-zA-Z0-9._-]+\]?)(?::(\d+))?$/) {
$proxyUser = $1 if $1;
$proxyIp = $2;
$proxyPort = $3 ? $3 : 22;
osh_debug("parsed proxyjump: host=$proxyIp port=$proxyPort user=$proxyUser");
}
else {
main_exit OVH::Bastion::EXIT_INVALID_PROXYJUMP, 'invalid_proxyjump',
"Invalid proxyjump specification '$proxyJump', should be [user@]host[:port]";
}
$fnret = OVH::Bastion::get_ip(host => $proxyIp, allowSubnets => 0);
if (!$fnret) {
if ($fnret->err eq 'ERR_DNS_DISABLED') {
main_exit OVH::Bastion::EXIT_DNS_DISABLED, 'dns_disabled', $fnret->msg;
}
elsif ($fnret->err eq 'ERR_IP_VERSION_DISABLED') {
main_exit OVH::Bastion::EXIT_IP_VERSION_DISABLED, 'ip_version_disabled', $fnret->msg;
}
else {
main_exit OVH::Bastion::EXIT_HOST_NOT_FOUND, 'host_not_found', $fnret->msg;
}
}
osh_debug("Proxyjump host $proxyIp resolved to IP " . $fnret->value->{'ip'});
$proxyIp = $fnret->value->{'ip'};
if ($proxyUser && !OVH::Bastion::is_valid_remote_user(user => $proxyUser, allowWildcards => 0)) {
main_exit OVH::Bastion::EXIT_INVALID_REMOTE_USER, 'invalid_proxy_user',
"Proxy user name '$proxyUser' seems invalid";
}
$ENV{'OSH_PROXYJUMP_CONNECTION'} = 1;
}
# for plugins (osh_command), do a first check with allowWildcards, it'll be re-done in Plugin::start with
# either allowWildcards set to 0 or 1 depending on the plugin configuration that we don't have at this stage yet
if ($user && !OVH::Bastion::is_valid_remote_user(user => $user, allowWildcards => ($osh_command ? 1 : 0))) {
@ -813,6 +900,8 @@ osh_debug("self : "
. (defined $host ? $host : '<undef>') . "\n"
. "port : "
. (defined $port ? $port : '<undef>') . "\n"
. "proxyJump : "
. (defined $proxyJump ? $proxyJump : '<undef>') . "\n"
. "verbose : "
. (defined $verbose ? $verbose : '<undef>') . "\n"
. "tty : "
@ -824,6 +913,10 @@ osh_debug("self : "
. "\n");
my $hostto = OVH::Bastion::ip2host($host)->value || $host;
my $proxyhost;
if (defined $proxyIp) {
$proxyhost = OVH::Bastion::ip2host($proxyIp)->value || $proxyIp;
}
# Special case: adminSudo for ssh connection as another user
if ($sshAs) {
@ -845,7 +938,11 @@ if ($sshAs) {
plugin => undef,
params => join(' ', @$remainingOptions),
comment => undef,
uniqid => $log_uniq_id
uniqid => $log_uniq_id,
proxyuser => $proxyUser,
proxyip => $proxyIp,
proxyhost => $proxyhost,
proxyport => $proxyPort
);
if (!$fnret) {
main_exit OVH::Bastion::EXIT_RESTRICTED_COMMAND, "sshas_denied",
@ -966,7 +1063,11 @@ if ($osh_command) {
plugin => $osh_command,
params => join(' ', @$remainingOptions),
comment => 'plugin-' . ($fnret->value ? $fnret->value->{'type'} : 'UNDEF'),
uniqid => $log_uniq_id
uniqid => $log_uniq_id,
proxyuser => $proxyUser,
proxyip => $proxyIp,
proxyhost => $proxyhost,
proxyport => $proxyPort
);
if ($logret) {
@ -1089,6 +1190,9 @@ if ($osh_command) {
debug => $osh_debug,
tty => $tty,
notty => $notty,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
stealth_stdout => OVH::Bastion::plugin_config(
plugin => $osh_command,
key => "stealth_stdout"
@ -1152,13 +1256,25 @@ if (!$quiet) {
# do that here, cause sometimes we do not want to pass user to osh
$user = $user || $config->{'defaultLogin'} || $remoteself || $sysself;
# if we have a proxyIp but no proxyUser, set it to $user
$proxyUser = $user if ($proxyIp && !$proxyUser);
# log request
osh_debug("final request : " . "$user\@$ip -p $port -- $command'\n");
my $displayLine = sprintf("%s => %s => %s",
my $displayLine = sprintf(
"%s => %s => %s",
OVH::Bastion::machine_display(ip => $hostfrom, port => $portfrom)->value,
OVH::Bastion::machine_display(ip => $bastionhost, port => $bastionport, user => $self)->value,
OVH::Bastion::machine_display(ip => $hostto, port => $port, user => $user)->value,
OVH::Bastion::machine_display(
ip => $hostto,
port => $port,
user => $user,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
)->value,
);
if (!$quiet) {
@ -1172,12 +1288,15 @@ if ($fnret and $fnret->value() =~ /yes/) {
}
else {
$fnret = OVH::Bastion::is_access_granted(
account => $self,
user => $user,
ipfrom => $ipfrom,
ip => $ip,
port => $port,
details => 1
account => $self,
user => $user,
ipfrom => $ipfrom,
ip => $ip,
port => $port,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
details => 1
);
}
@ -1206,7 +1325,11 @@ if (!$fnret) {
portto => $port,
user => $user,
params => $command,
uniqid => $log_uniq_id
uniqid => $log_uniq_id,
proxyuser => $proxyUser,
proxyip => $proxyIp,
proxyhost => $proxyhost,
proxyport => $proxyPort
);
if (!$logret) {
osh_warn($logret);
@ -1236,7 +1359,10 @@ my $ttyrec_fnret = OVH::Bastion::build_ttyrec_cmdline_part1of2(
remoteaccount => $remoteself,
debug => $osh_debug,
tty => $tty,
notty => $notty
notty => $notty,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser
);
main_exit(OVH::Bastion::EXIT_TTYREC_CMDLINE_FAILED, "ttyrec_failed", $ttyrec_fnret->msg) if !$ttyrec_fnret;
@ -1399,6 +1525,7 @@ else {
@ttyrec = @{$ttyrec_fnret->value->{'cmd'}};
# SSH PASSWORD AUTOLOGIN
# TODO: how tf does this work??? And how to proxyjump with this?
if ($userPasswordClue) {
push @preferredAuths, 'keyboard-interactive';
@ -1452,7 +1579,7 @@ else {
if ($fnret) {
# add the -i key1 -i key2 etc. returned by get_details_from_access_array()
push @command, @{$fnret->value->{'sshKeysArgs'}};
# updathe the JIT MFA flag
# update the JIT MFA flag
$JITMFARequired = $fnret->value->{'mfaRequired'};
}
else {
@ -1470,6 +1597,39 @@ else {
push @command, '-T' if $notty;
push @command, '-o', "ConnectTimeout=$timeout" if $timeout;
if ($proxyJump) {
# check if poxyJump connections are allowed
if (!$config->{'egressProxyJumpAllowed'}) {
main_exit OVH::Bastion::EXIT_ACCESS_DENIED, "proxyjump_not_allowed",
"Sorry $self, egress proxy-jump connections have been disabled by policy";
}
# Build ProxyCommand with same options as main SSH command
my @proxyCommand = ('ssh');
push @proxyCommand, '-o', 'PreferredAuthentications=' . (join(',', @preferredAuths));
# Add the same SSH keys to the proxy command
if ($fnret && $fnret->value->{'sshKeysArgs'}) {
push @proxyCommand, @{$fnret->value->{'sshKeysArgs'}};
}
push @proxyCommand, '-p', $proxyPort if $proxyPort && $proxyPort != 22;
push @proxyCommand, '-l', $proxyUser, '-W', '%h:%p', $proxyIp;
if ($verbose) {
foreach (1 .. $verbose) {
push @proxyCommand, '-v';
}
}
push @proxyCommand, '-o', "ConnectTimeout=$timeout" if $timeout;
# Quote arguments that contain spaces and build the command string
my $proxyCommandStr = join(' ', map { /\s/ ? "'$_'" : $_ } @proxyCommand);
push @command, '-o', "ProxyCommand=$proxyCommandStr";
osh_debug("ProxyCommand: $proxyCommandStr");
}
if (not $quiet) {
$fnret =
OVH::Bastion::account_config(account => $self, key => OVH::Bastion::OPT_ACCOUNT_IDLE_IGNORE, public => 1);
@ -1591,7 +1751,11 @@ my $logret = OVH::Bastion::log_access_insert(
user => $user,
params => join(' ', @ttyrec),
ttyrecfile => $saveFile,
uniqid => $log_uniq_id
uniqid => $log_uniq_id,
proxyuser => $proxyUser,
proxyip => $proxyIp,
proxyhost => $proxyhost,
proxyport => $proxyPort
);
if (!$logret) {
osh_warn($logret);
@ -1905,12 +2069,14 @@ sub do_plugin_jit_mfa {
my $remoteuser = $user || $config->{'defaultLogin'} || $remoteself || $sysself;
$localfnret = OVH::Bastion::is_access_granted(
account => $self,
user => $user,
ipfrom => $ipfrom,
ip => $ip,
port => $port,
details => 1
account => $self,
user => $user,
ipfrom => $ipfrom,
ip => $ip,
port => $port,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
details => 1
);
if (!$localfnret) {

@ -123,6 +123,11 @@
# EXAMPLE: "-s -p 40000:49999"
"moshCommandLine": "",
#
# egressProxyJumpAllowed (boolean)
# DESC: If set to ``true``, ProxyJump (``-J``) egress connections will be allowed.
# DEFAULT: false
"egressProxyJumpAllowed": false,
#
###########################
# > Global network policies
# >> Those options can set a few global network policies to be applied bastion-wide.
@ -508,7 +513,7 @@
"sshClientHasOptionE": false,
#
# sshAddKeysToAgentAllowed (boolean)
# DESC: Set to ``true`` if you want to allow to spawn an ssh-agent and forward it over the egress session when specifically requested with the '--forward-agent' or '-x' flag, with the egress key added to the agent. Useful if you need the ssh-key for authentication on other systems (another jumpserver for example).
# DESC: Set to ``true`` if you want to allow to spawn an ssh-agent and forward it over the egress session when specifically requested with the '--forward-agent' or '-x' flag, with the egress key added to the agent. Useful if you need the ssh-key for authentication on other systems (another jumpserver for example).
# DEFAULT: false
"sshAddKeysToAgentAllowed": false
}

@ -104,6 +104,7 @@ use constant {
EXIT_ACCOUNT_FROZEN => 131,
EXIT_DNS_DISABLED => 132,
EXIT_IP_VERSION_DISABLED => 133,
EXIT_INVALID_PROXYJUMP => 134,
};
use constant {
@ -761,16 +762,63 @@ sub is_valid_remote_user {
return R('ERR_INVALID_PARAMETER', msg => "Specified user doesn't seem to be valid");
}
sub validate_proxy_params {
my %params = @_;
my $proxyHost = $params{'proxyHost'};
my $proxyPort = $params{'proxyPort'};
my $proxyUser = $params{'proxyUser'};
my $allowWildcards = $params{'allowWildcards'} // 1;
my $proxyIp;
my $fnret;
if ($proxyHost) {
$fnret = OVH::Bastion::is_valid_ip(ip => $proxyHost, allowSubnets => 0);
if ($fnret) {
$proxyIp = $fnret->value->{'ip'};
}
else {
$fnret = OVH::Bastion::get_ip(host => $proxyHost);
$fnret or return $fnret;
$proxyIp = $fnret->value->{'ip'};
}
}
if ($proxyPort) {
$fnret = OVH::Bastion::is_valid_port(port => $proxyPort);
$fnret or return $fnret;
$proxyPort = $fnret->value;
}
if ($proxyUser) {
$fnret = OVH::Bastion::is_valid_remote_user(user => $proxyUser, allowWildcards => $allowWildcards);
$fnret or return $fnret;
$proxyUser = $fnret->value;
}
return R('OK', value => {proxyIp => $proxyIp, proxyPort => $proxyPort, proxyUser => $proxyUser});
}
sub machine_display {
my %params = @_;
my $ip = $params{'ip'};
my $port = $params{'port'};
my $user = $params{'user'};
my %params = @_;
my $ip = $params{'ip'};
my $port = $params{'port'};
my $user = $params{'user'};
my $proxyIp = $params{'proxyIp'};
my $proxyPort = $params{'proxyPort'};
my $proxyUser = $params{'proxyUser'};
my $machine = (index($ip, ':') >= 0 ? "[$ip]" : $ip);
$machine .= ":$port" if $port;
$machine = $user . '@' . $machine if $user;
if ($proxyIp) {
my $proxy = (index($proxyIp, ':') >= 0 ? "[$proxyIp]" : $proxyIp);
$proxy .= ":$proxyPort" if $proxyPort;
$proxy = $proxyUser . '@' . $proxy if $proxyUser;
$machine = "$machine via $proxy";
}
return R('OK', value => $machine);
}
@ -1142,15 +1190,24 @@ sub build_ttyrec_cmdline_part1of2 {
$params{'ip'} = 'v6[' . $params{'ip'} . ']';
}
# handle ipv6 for proxyIp
if ($params{'proxyIp'} && index($params{'proxyIp'}, ':') >= 0) {
$params{'proxyIp'} =~ tr/:/./;
$params{'proxyIp'} = 'v6[' . $params{'proxyIp'} . ']';
}
# build ttyrec filename format
my $bastionName = OVH::Bastion::config('bastionName')->value;
my $ttyrecFilenameFormat = OVH::Bastion::config('ttyrecFilenameFormat')->value;
$ttyrecFilenameFormat =~ s/&bastionname/$bastionName/g;
$ttyrecFilenameFormat =~ s/&uniqid/$params{'uniqid'}/g if $params{'uniqid'};
$ttyrecFilenameFormat =~ s/&ip/$params{'ip'}/g if $params{'ip'};
$ttyrecFilenameFormat =~ s/&port/$params{'port'}/g if defined $params{'port'};
$ttyrecFilenameFormat =~ s/&user/$params{'user'}/g if defined $params{'user'};
$ttyrecFilenameFormat =~ s/&account/$params{'account'}/g if $params{'account'};
$ttyrecFilenameFormat =~ s/&uniqid/$params{'uniqid'}/g if $params{'uniqid'};
$ttyrecFilenameFormat =~ s/&ip/$params{'ip'}/g if $params{'ip'};
$ttyrecFilenameFormat =~ s/&port/$params{'port'}/g if defined $params{'port'};
$ttyrecFilenameFormat =~ s/&user/$params{'user'}/g if defined $params{'user'};
$ttyrecFilenameFormat =~ s/&account/$params{'account'}/g if $params{'account'};
$ttyrecFilenameFormat =~ s/&proxyip/$params{'proxyIp'}/g if defined $params{'proxyIp'};
$ttyrecFilenameFormat =~ s/&proxyport/$params{'proxyPort'}/g if defined $params{'proxyPort'};
$ttyrecFilenameFormat =~ s/&proxyuser/$params{'proxyUser'}/g if defined $params{'proxyUser'};
if ($ttyrecFilenameFormat =~ /&(bastionname|uniqid|ip|port|user|account)/) {
@ -1159,6 +1216,14 @@ sub build_ttyrec_cmdline_part1of2 {
msg => "Missing bastionname, uniqid, ip, port, user or account in ttyrec cmdline building");
}
# if there is no proxyIp, remove the via part and all proxy placeholders
if (!defined $params{'proxyIp'}) {
$ttyrecFilenameFormat =~ s/\.via//g;
$ttyrecFilenameFormat =~ s/\.&proxyuser//g;
$ttyrecFilenameFormat =~ s/\.&proxyip//g;
$ttyrecFilenameFormat =~ s/\.&proxyport//g;
}
# ensure there are no '/'
$ttyrecFilenameFormat =~ tr{/}{_};
@ -1169,7 +1234,14 @@ sub build_ttyrec_cmdline_part1of2 {
$saveDir .= "/" . $params{'remoteaccount'};
mkdir($saveDir);
}
$saveDir .= "/" . $params{'ip'};
# format it like via-$proxyIp-$ip so that it's sorted by proxyIp first, then by ip if you list the directory
if ($params{'proxyIp'}) {
$saveDir .= "/via-" . $params{'proxyIp'} . "-" . $params{'ip'};
}
else {
$saveDir .= "/" . $params{'ip'};
}
mkdir($saveDir);
my $saveFileFormat = "$saveDir/$ttyrecFilenameFormat";

@ -9,8 +9,8 @@ use OVH::Bastion;
sub check {
my %params = @_;
my ($port, $portAny, $user, $userAny, $scpUp, $scpDown, $sftp, $protocol) =
@params{qw{ port portAny user userAny scpUp scpDown sftp protocol }};
my ($port, $portAny, $user, $userAny, $scpUp, $scpDown, $sftp, $protocol, $proxyIp, $proxyPort, $proxyUser) =
@params{qw{ port portAny user userAny scpUp scpDown sftp protocol proxyIp proxyPort proxyUser }};
if ($user and $userAny) {
return R('ERR_INCOMPATIBLE_PARAMETERS',
@ -84,11 +84,52 @@ sub check {
);
}
# now, remap port and user '*' back to undef
undef $user if $user eq '*';
undef $port if $port eq '*';
# check proxy-host and proxy-port parameters
osh_debug("Checking proxy parameters: proxyIp='$proxyIp' proxyPort='$proxyPort' proxyUser='$proxyUser'");
if ($proxyIp) {
if (!$proxyPort) {
return R('ERR_MISSING_PARAMETER', msg => "When --proxy-host is specified, --proxy-port becomes mandatory");
}
# validate proxy ip
my $fntret = OVH::Bastion::is_valid_ip(ip => $proxyIp, allowSubnets => 0);
if (!$fntret) {
return R('ERR_INVALID_PARAMETER', msg => "Proxy host IP '$proxyIp' is invalid: " . $fntret->msg);
}
if (!$proxyUser) {
return R('ERR_MISSING_PARAMETER', msg => "When --proxy-host is specified, --proxy-user becomes mandatory");
}
}
return R('OK', value => {user => $user, port => $port, protocol => $protocol});
if ($proxyPort) {
if (!$proxyIp) {
return R('ERR_MISSING_PARAMETER', msg => "When --proxy-port is specified, --proxy-host becomes mandatory");
}
# validate proxy port
my $fnret = OVH::Bastion::is_valid_port(port => $proxyPort);
if (!$fnret) {
return R('ERR_INVALID_PARAMETER', msg => "Proxy port '$proxyPort' is invalid: " . $fnret->msg);
}
}
# now, remap port and user '*' back to undef
undef $user if $user eq '*';
undef $port if $port eq '*';
undef $proxyUser if $proxyUser eq '*';
return R(
'OK',
value => {
user => $user,
port => $port,
protocol => $protocol,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser
}
);
}
1;

@ -170,13 +170,17 @@ sub act {
my %values = %{$fnret->value()};
my ($group, $shortGroup, $account, $type, $realm, $remoteaccount, $sysaccount) =
@values{qw{ group shortGroup account type realm remoteaccount sysaccount }};
my ($action, $self, $user, $host, $port, $ttl, $comment) = @params{qw{ action self user host port ttl comment }};
my ($action, $self, $user, $host, $port, $proxyIp, $proxyPort, $proxyUser, $ttl, $comment) =
@params{qw{ action self user host port proxyIp proxyPort proxyUser ttl comment }};
my @command;
osh_debug(
"groupSetRole::act, $action $type $group/$account ($sysaccount/$realm/$remoteaccount) $user\@$host:$port ttl=$ttl"
);
my $debug_msg =
"groupSetRole::act, $action $type $group/$account ($sysaccount/$realm/$remoteaccount) $user\@$host:$port ttl=$ttl";
if ($proxyIp) {
$debug_msg .= " via proxy $proxyUser\@$proxyIp:$proxyPort";
}
osh_debug($debug_msg);
# add/del system user to system group except if we're removing a guest access (will be done after if needed)
if (!($type eq 'guest' and $action eq 'del')) {
@ -207,19 +211,25 @@ sub act {
# foreach guest access, delete
foreach my $access (@acl) {
my $machine = OVH::Bastion::machine_display(
ip => $access->{'ip'},
port => $access->{'port'},
user => $access->{'user'}
ip => $access->{'ip'},
port => $access->{'port'},
user => $access->{'user'},
proxyIp => $access->{'proxyIp'},
proxyPort => $access->{'proxyPort'},
proxyUser => $access->{'proxyUser'},
)->value;
$fnret = OVH::Bastion::Plugin::groupSetRole::act(
account => $account,
group => $shortGroup,
action => 'del',
type => 'guest',
user => $access->{'user'},
port => $access->{'port'},
host => $access->{'ip'},
self => $self,
account => $account,
group => $shortGroup,
action => 'del',
type => 'guest',
user => $access->{'user'},
port => $access->{'port'},
host => $access->{'ip'},
proxyIp => $access->{'proxyIp'},
proxyPort => $access->{'proxyPort'},
proxyUser => $access->{'proxyUser'},
self => $self,
);
if (!$fnret) {
osh_warn("Failed removing guest access to $machine, proceeding anyway...");
@ -253,18 +263,28 @@ sub act {
# in that case, we need to handle the add/del of the guest access to $user@$host:$port
# check if group has access to $user@$ip:$port
my $machine = OVH::Bastion::machine_display(ip => $host, port => $port, user => $user)->value;
my $machine = OVH::Bastion::machine_display(
ip => $host,
port => $port,
user => $user,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser
)->value;
osh_debug(
"groupSetRole::act, checking if group $group has access to $machine to $action $type access to $account");
if ($action eq 'add') {
$fnret = OVH::Bastion::is_access_way_granted(
way => 'group',
group => $shortGroup,
user => $user,
port => $port,
ip => $host,
way => 'group',
group => $shortGroup,
user => $user,
port => $port,
ip => $host,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
);
if (not $fnret) {
osh_debug("groupSetRole::act, it doesn't! $fnret");
@ -287,14 +307,17 @@ sub act {
# Add/Del user access to user@host:port with group key
@command = qw{ sudo -n -u allowkeeper -- /usr/bin/env perl -T };
push @command, $OVH::Bastion::BASEPATH . '/bin/helper/osh-accountAddGroupServer';
push @command, '--group', $group; # must be first params, forced in sudoers.d
push @command, '--account', $account;
push @command, '--action', $action;
push @command, '--ip', $host;
push @command, '--user', $user if $user;
push @command, '--port', $port if $port;
push @command, '--ttl', $ttl if $ttl;
push @command, '--comment', $comment if $comment;
push @command, '--group', $group; # must be first params, forced in sudoers.d
push @command, '--account', $account;
push @command, '--action', $action;
push @command, '--ip', $host;
push @command, '--user', $user if $user;
push @command, '--port', $port if $port;
push @command, '--proxy-ip', $proxyIp if $proxyIp;
push @command, '--proxy-port', $proxyPort if $proxyPort;
push @command, '--proxy-user', $proxyUser if $proxyUser;
push @command, '--ttl', $ttl if $ttl;
push @command, '--comment', $comment if $comment;
$fnret = OVH::Bastion::helper(cmd => \@command);
$fnret or return $fnret;
@ -388,16 +411,19 @@ sub act {
severity => 'info',
type => 'membership',
fields => [
['action', $action],
['type', $type],
['group', $shortGroup],
['account', $account],
['self', $self],
['user', $user],
['host', $host],
['port', $port],
['ttl', $ttl],
['comment', $comment || ''],
['action', $action],
['type', $type],
['group', $shortGroup],
['account', $account],
['self', $self],
['user', $user],
['host', $host],
['port', $port],
['proxyIp', $proxyIp],
['proxyPort', $proxyPort],
['proxyUser', $proxyUser],
['ttl', $ttl],
['comment', $comment || ''],
]
);
}

@ -15,29 +15,42 @@ use OVH::Bastion;
# 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'};
my $user = $params{'user'};
my $ipfrom = $params{'ipfrom'} || $ENV{'OSH_IP_FROM'};
my $ip = $params{'ip'};
my $port = $params{'port'};
my $protocol = $params{'protocol'};
my %params = @_;
my $account = $params{'account'};
my $user = $params{'user'};
my $ipfrom = $params{'ipfrom'} || $ENV{'OSH_IP_FROM'};
my $ip = $params{'ip'};
my $port = $params{'port'};
my $proxyIp = $params{'proxyIp'};
my $proxyPort = $params{'proxyPort'};
my $proxyUser = $params{'proxyUser'};
my $protocol = $params{'protocol'};
if (!$account || !$ipfrom || !$ip || !$protocol || !$user || !$port) {
return R('ERR_MISSING_PARAMETERS', msg => "Missing mandatory parameters for has_protocol_access");
}
my $machine = OVH::Bastion::machine_display(ip => $ip, port => $port, user => $user)->value;
my $machine = OVH::Bastion::machine_display(
ip => $ip,
port => $port,
user => $user,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser
)->value;
my %keys;
osh_debug("Checking access 1/2 of $account to $machine...");
my $fnret = OVH::Bastion::is_access_granted(
account => $account,
user => $user,
ipfrom => $ipfrom,
ip => $ip,
port => $port,
details => 1
account => $account,
user => $user,
ipfrom => $ipfrom,
ip => $ip,
port => $port,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
details => 1
);
if (not $fnret) {
@ -60,6 +73,9 @@ sub has_protocol_access {
ipfrom => $ipfrom,
ip => $ip,
port => $port,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
exactUserMatch => 1,
details => 1
);

@ -77,13 +77,18 @@ sub is_access_way_granted {
my $exactUserMatch = $params{'exactUserMatch'}; # $user must be explicitly allowed (user wildcards in grantfile will be ignored)
my $exactMatch = $params{'exactMatch'}; # sets exactIpMatch exactPortMatch and exactUserMatch
my $ignoreUser = $params{'ignoreUser'}; # ignore remote user COMPLETELY (plop@, or root@, or <nil>@ will all match)
my $ignorePort = $params{'ignorePort'}; # ignore port COMPLETELY (port 22, 2345, or port-wildcard will all match)
my $ignoreUser = $params{'ignoreUser'}; # ignore remote user COMPLETELY (plop@, or root@, or <nil>@ will all match)
my $ignorePort = $params{'ignorePort'}; # ignore port COMPLETELY (port 22, 2345, or port-wildcard will all match)
my $ignoreProxyUser = $params{'ignoreProxyUser'}; # ignore proxy user COMPLETELY (if egress connection goes through a proxyjump)
my $wantedUser = $params{'user'}; # if undef, means we look for a user-any allow
my $wantedIp = $params{'ip'}; # can be a single IP or a subnet
my $wantedPort = $params{'port'}; # if undef, means we look for a port-any allow
my $wantedProxyIp = $params{'proxyIp'}; # can be a single IP, no support for subnet
my $wantedProxyPort = $params{'proxyPort'}; # must be exact if defined, no support for wildcard
my $wantedProxyUser = $params{'proxyUser'}; # if undef, means we look for a user-any allow
my $way = $params{'way'}; # personal|group|groupguest|legacy
my $group = $params{'group'}; # only meaningful and needed if type=group or type=groupguest
my $account = $params{'account'}; # only meaningful and needed if type=personal or type=groupguest
@ -93,17 +98,23 @@ sub is_access_way_granted {
$exactIpMatch = $exactPortMatch = $exactUserMatch = 1 if $exactMatch;
# normalize '*' to undef
undef $wantedUser if (defined $wantedUser && $wantedUser eq '*');
undef $wantedPort if (defined $wantedPort && $wantedPort eq '*');
undef $wantedUser if (defined $wantedUser && $wantedUser eq '*');
undef $wantedPort if (defined $wantedPort && $wantedPort eq '*');
undef $wantedProxyUser if (defined $wantedProxyUser && $wantedProxyUser eq '*');
# 'group', 'account', and 'way' parameters are only useful to, and checked by, get_acl_way()
$fnret = OVH::Bastion::get_acl_way(way => $way, account => $account, group => $group);
$fnret or return $fnret;
my @acl = @{$fnret->value || []};
osh_debug(
"checking way $way/$account/$group with ignorePort=$ignorePort ignoreUser=$ignoreUser exactIpMatch=$exactIpMatch exactPortMatch=$exactPortMatch exactUserMatch=$exactUserMatch"
);
my $check_debug_msg =
"checking way $way/$account/$group with ignorePort=$ignorePort ignoreUser=$ignoreUser ignoreProxyUser=$ignoreProxyUser exactIpMatch=$exactIpMatch exactPortMatch=$exactPortMatch exactUserMatch=$exactUserMatch";
osh_debug($check_debug_msg);
# make sure both proxyIp and proxyPort are defined or undefined
if (defined $wantedProxyIp && !defined $wantedProxyPort) {
return R('ERR_INVALID_PARAMETER', msg => "If proxyIp is given, proxyPort must be given too");
}
my %match;
foreach my $entry (@acl) {
@ -111,14 +122,30 @@ sub is_access_way_granted {
undef $entry->{'user'} if (defined $entry->{'user'} && $entry->{'user'} eq '*');
undef $entry->{'port'} if (defined $entry->{'port'} && $entry->{'port'} eq '*');
osh_debug("checking wanted "
. ($wantedUser // '<u>') . '@'
. ($wantedIp // '<u>') . ':'
. ($wantedPort // '<u>')
. ' against '
. ($entry->{'user'} // '<u>') . '@'
. ($entry->{'ip'} // '<u>') . ':'
. ($entry->{'port'} // '<u>'));
$check_debug_msg =
"checking wanted "
. ($wantedUser // '<u>') . '@'
. ($wantedIp // '<u>') . ':'
. ($wantedPort // '<u>')
. ' against '
. ($entry->{'user'} // '<u>') . '@'
. ($entry->{'ip'} // '<u>') . ':'
. ($entry->{'port'} // '<u>');
if (defined $entry->{'proxyIp'}) {
undef $entry->{'proxyUser'} if (defined $entry->{'proxyUser'} && $entry->{'proxyUser'} eq '*');
$check_debug_msg .=
" with wanted proxy "
. ($wantedProxyUser // '<u>') . '@'
. ($wantedProxyIp // '<u>') . ':'
. ($wantedProxyPort // '<u>')
. " against "
. ($entry->{'proxyUser'} // '<u>') . '@'
. ($entry->{'proxyIp'} // '<u>') . ':'
. ($entry->{'proxyPort'} // '<u>');
}
osh_debug($check_debug_msg);
$entry->{'ip'} or next; # can't be empty
@ -209,6 +236,54 @@ sub is_access_way_granted {
}
}
# check proxy user if we have a proxy ip
if (defined $wantedProxyIp && !$ignoreProxyUser) {
if ($exactUserMatch) {
# we want an exact match
if (not defined $entry->{'proxyUser'}) {
if (not defined $wantedProxyUser) {
; # both undefined ? ok
}
else {
next; # if only one of two is undef, it's not an exact match
}
}
else {
if (not defined $wantedProxyUser) {
next; # if only one of two is undef, it's not an exact match
}
else {
next if ($wantedProxyUser ne $entry->{'proxyUser'}); # both defined but unequal, not a match
}
}
}
else {
# we don't want an exact match (aka proxy-user-any allowed)
if (not defined $entry->{'proxyUser'}) {
; # it's a wildcard, will always match
}
else {
if (not defined $wantedProxyUser) {
next; # we want a wildcard, but we don't have it
}
else {
# handle the case where $entry->{'proxyUser'} contains wildcards such as '?' or '*'
if (index($entry->{'proxyUser'}, '*') >= 0 || index($entry->{'proxyUser'}, '?') >= 0) {
# turn wildcards into a regexp
my $entryUserRe = quotemeta($entry->{'proxyUser'});
$entryUserRe =~ s{\\\?}{.}g;
$entryUserRe =~ s{\\\*}{.*}g;
next if ($wantedProxyUser !~ /^$entryUserRe$/);
}
else {
# doesn't contain a wildcard, simple comparison
next if ($wantedProxyUser ne $entry->{'proxyUser'}); # both defined but unequal, not a match
}
}
}
}
}
# then, check IP
my $isIPv6 = (index($entry->{'ip'}, ':') != -1);
my $isNetblock = (index($entry->{'ip'}, '/') != -1);
@ -217,12 +292,32 @@ sub is_access_way_granted {
if ($exactIpMatch && !$isIPv6) {
next if ($entry->{'ip'} ne $wantedIp);
# Case 1: proxy parameters are requested
if (defined $wantedProxyIp) {
# if proxy parameters are requested, the entry must have proxy info
if (!defined $entry->{'proxyIp'} || !defined $entry->{'proxyPort'}) {
next;
}
# check proxy IP match
next if ($entry->{'proxyIp'} ne $wantedProxyIp);
# check proxy port match
next if ($entry->{'proxyPort'} ne $wantedProxyPort);
}
# Case 2: no proxy requested, but entry has proxy info - should not match
elsif (defined $entry->{'proxyIp'} || defined $entry->{'proxyPort'}) {
next; # entry has proxy info but no proxy was requested
}
# here, we got a perfect match
%match = (
forceKey => $entry->{'forceKey'},
forcePassword => $entry->{'forcePassword'},
ip => $entry->{'ip'},
comment => $entry->{'userComment'},
proxyIp => $entry->{'proxyIp'},
proxyPort => $entry->{'proxyPort'},
size => undef, # not needed
);
last; # perfect match, don't search further
@ -236,12 +331,37 @@ sub is_access_way_granted {
my $ipCheck = Net::Netmask->new2($entry->{'ip'});
if ($ipCheck && ($isNetblock ? $ipCheck->contains($wantedIp) : $ipCheck->match($wantedIp))) {
osh_debug("... we got a subnet match!");
if (not defined $match{'size'} or $ipCheck->size() < $match{'size'}) {
# check proxy parameters
my $proxyMatches = 1;
# Case 1: proxy parameters are requested
if (defined $wantedProxyIp) {
# if proxy parameters are requested, the entry must have proxy info
if (!defined $entry->{'proxyIp'} || !defined $entry->{'proxyPort'}) {
$proxyMatches = 0;
}
else {
# check proxy IP match
$proxyMatches = 0 if ($entry->{'proxyIp'} ne $wantedProxyIp);
# check proxy port match
$proxyMatches = 0 if ($proxyMatches && $entry->{'proxyPort'} ne $wantedProxyPort);
}
}
# Case 2: no proxy requested, but entry has proxy info - should not match
elsif (defined $entry->{'proxyIp'} || defined $entry->{'proxyPort'}) {
$proxyMatches = 0;
}
if ($proxyMatches && (not defined $match{'size'} or $ipCheck->size() < $match{'size'})) {
%match = (
forceKey => $entry->{'forceKey'},
forcePassword => $entry->{'forcePassword'},
ip => $entry->{'ip'},
comment => $entry->{'userComment'},
proxyIp => $entry->{'proxyIp'},
proxyPort => $entry->{'proxyPort'},
size => $ipCheck->size(),
);
last if $match{'size'} == 1; # we won't get better than this
@ -252,14 +372,41 @@ sub is_access_way_granted {
# it's a single ipv4, so a simple strcmp() does the trick
if ($entry->{'ip'} eq $wantedIp) {
osh_debug("... we got a singleip match!");
%match = (
forceKey => $entry->{'forceKey'},
forcePassword => $entry->{'forcePassword'},
ip => $entry->{'ip'},
comment => $entry->{'userComment'},
size => 1,
);
last;
# check proxy parameters
my $proxyMatches = 1;
# Case 1: proxy parameters are requested
if (defined $wantedProxyIp) {
# if proxy parameters are requested, the entry must have proxy info
if (!defined $entry->{'proxyIp'} && !defined $entry->{'proxyPort'}) {
$proxyMatches = 0;
}
else {
# check proxy IP match
next if ($entry->{'proxyIp'} ne $wantedProxyIp);
# check proxy port match
next if ($entry->{'proxyPort'} ne $wantedProxyPort);
}
}
# Case 2: no proxy requested, but entry has proxy info - should not match
elsif (defined $entry->{'proxyIp'} || defined $entry->{'proxyPort'}) {
$proxyMatches = 0;
}
if ($proxyMatches) {
%match = (
forceKey => $entry->{'forceKey'},
forcePassword => $entry->{'forcePassword'},
ip => $entry->{'ip'},
comment => $entry->{'userComment'},
proxyIp => $entry->{'proxyIp'},
proxyPort => $entry->{'proxyPort'},
size => 1,
);
last;
}
}
}
}
@ -273,6 +420,8 @@ sub is_access_way_granted {
forceKey => $match{'forceKey'},
forcePassword => $match{'forcePassword'},
comment => $match{'comment'},
proxyIp => $match{'proxyIp'},
proxyPort => $match{'proxyPort'},
}
);
}
@ -581,7 +730,7 @@ sub print_acls {
# also take this opportunity to remember the longest field for each column
my @printRows;
my @jsonRows;
my @columnNames = qw( IP PORT USER ACCESS-BY ADDED-BY ADDED-AT EXPIRY? COMMENT FORCED-KEY FORCED-PASSWORD);
my @columnNames = qw( IP PORT USER ACCESS-BY ADDED-BY ADDED-AT EXPIRY? COMMENT FORCED-KEY FORCED-PASSWORD VIA);
my @printColumnLength = map { length } @columnNames;
foreach my $contextAcl (@$acls) {
my $type = $contextAcl->{'type'};
@ -597,6 +746,13 @@ sub print_acls {
$addedDate = substr($addedDate, 0, 10);
my $forceKey = $entry->{'forceKey'} || '-';
my $forcePassword = $entry->{'forcePassword'} || '-';
my $via = '-';
if ($entry->{'proxyIp'}) {
$via =
($entry->{'proxyUser'} ? $entry->{'proxyUser'} : '*') . "\@$entry->{'proxyIp'}:$entry->{'proxyPort'}";
}
my $expiry =
$entry->{'expiry'} ? (duration2human(seconds => ($entry->{'expiry'} - time()))->value->{'human'}) : '-';
@ -610,7 +766,8 @@ sub print_acls {
$entry->{'user'} ? $entry->{'user'} : '*', $accessType,
$addedBy, $addedDate,
$expiry, $entry->{'userComment'} || '-',
$forceKey, $forcePassword
$forceKey, $forcePassword,
$via
);
# if we have includes or excludes, match fields against the built regex
@ -729,6 +886,10 @@ sub is_access_granted {
my $listOnly = $params{'listOnly'}; # don't open the files, just return file names
my $noexec = $params{'noexec'}; # passed to is_valid_public_key
my $proxyIp = $params{'proxyIp'}; # if defined, means we look for a proxyIp match too
my $proxyPort = $params{'proxyPort'}; # if defined, means we look for a proxyPort match too
my $proxyUser = $params{'proxyUser'}; # if undef, means we look for a proxy-user wildcard allow
my $details = delete $params{'details'}; # if set, look for and return ssh keys + config data along with allowed accesses
delete $params{'way'}; # WE specify this parameter, not our caller
@ -745,6 +906,14 @@ sub is_access_granted {
msg => "Can't connect you to $ip as it's part of the forbidden networks of this bastion (see --osh info)")
if $fnret->is_ok;
if (defined $proxyIp) {
$fnret = _is_in_any_net(ip => $proxyIp, networks => $forbiddenNetworks);
return R('KO_ACCESS_DENIED',
msg =>
"Can't connect you to $ip via proxy $proxyIp as it's part of the forbidden networks of this bastion (see --osh info)"
) if $fnret->is_ok;
}
# 0b/3 check if we're not outside of the bastion allowed networks, if we are, just bail out
my $allowedNetworks = OVH::Bastion::config('allowedNetworks')->value;
if (@$allowedNetworks) {
@ -752,6 +921,14 @@ sub is_access_granted {
return R('KO_ACCESS_DENIED',
msg => "Can't connect you to $ip as it's not part of the allowed networks of this bastion (see --osh info)")
if $fnret->is_ko;
if (defined $proxyIp) {
$fnret = _is_in_any_net(ip => $proxyIp, networks => $allowedNetworks);
return R('KO_ACCESS_DENIED',
msg =>
"Can't connect you to $ip via proxy $proxyIp as it's not part of the allowed networks of this bastion (see --osh info)"
) if $fnret->is_ko;
}
}
# 0c/3 check if there are more complex "ingressToEgressRules" defined, and potentially bail out whether needed
@ -775,6 +952,19 @@ sub is_access_granted {
warn("Denied access due to potential configuration error in ingressToEgressRules (rule #$ruleNb, egress");
return R('KO_ACCESS_DENIED', msg => "Error checking ingressToEgressRules, warn your bastion admin!");
}
if (defined $proxyIp) {
my $fnret2 = _is_in_any_net(ip => $proxyIp, networks => $outNets);
if ($fnret2->is_err) {
warn(
"Denied access due to potential configuration error in ingressToEgressRules (rule #$ruleNb, egress proxy"
);
return R('KO_ACCESS_DENIED', msg => "Error checking ingressToEgressRules, warn your bastion admin!");
}
if ($fnret2->is_ok) {
# proxy egress matches too, we consider the egress matches
$fnret = $fnret2;
}
}
if ($policy eq 'ALLOW-EXCLUSIVE') {
if ($fnret->is_ok) {
@ -929,7 +1119,14 @@ sub is_access_granted {
return R('OK', value => \@grants) if @grants;
my $machine = OVH::Bastion::machine_display(ip => $ip, port => $port, user => $user)->value;
my $machine = OVH::Bastion::machine_display(
ip => $ip,
port => $port,
user => $user,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser
)->value;
return R('KO_ACCESS_DENIED', msg => "Access denied for $account to $machine");
}
@ -938,9 +1135,12 @@ sub ssh_test_access_way {
my $account = $params{'account'};
my $group = $params{'group'};
my $port = $params{'port'};
my $ip = $params{'ip'};
my $user = $params{'user'};
my $port = $params{'port'};
my $ip = $params{'ip'};
my $user = $params{'user'};
my $proxyIp = $params{'proxyIp'};
my $proxyPort = $params{'proxyPort'};
my $proxyUser = $params{'proxyUser'};
my $forceKey = $params{'forceKey'};
my $fnret;
@ -962,6 +1162,21 @@ sub ssh_test_access_way {
$port = $fnret->value;
}
if (defined $proxyIp) {
$fnret = OVH::Bastion::is_valid_ip(ip => $proxyIp, allowSubnets => 0);
$fnret or return $fnret;
$proxyIp = $fnret->value->{'ip'};
$fnret = OVH::Bastion::is_valid_port(port => $proxyPort);
$fnret or return $fnret;
$proxyPort = $fnret->value;
$proxyUser = OVH::Bastion::get_user_from_env()->value if not $proxyUser; # no proxyUser or account ? get from env then
$fnret = OVH::Bastion::is_valid_remote_user(user => $proxyUser, allowWildcards => 1);
$fnret or return $fnret;
$proxyUser = $fnret->value;
}
$user = OVH::Bastion::config("defaultLogin")->value if not $user;
$user = $account if not $user; # defaultLogin empty means the user himself
$user = OVH::Bastion::get_user_from_env()->value if not $user; # no user or account ? get from env then
@ -1029,9 +1244,35 @@ sub ssh_test_access_way {
# add port when specified
push @command, ("-p", $port) if $port;
push @command, "-l", $user, $ip, '-T', '--', 'true';
push @command, "-l", $user, $ip;
# add proxyjump when specified
if (defined $proxyIp) {
my @proxyCommand = qw{ ssh -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no };
push @proxyCommand, '-o', 'PreferredAuthentications=' . $preferredAuthentications;
foreach (@keyList) {
push @proxyCommand, "-i", $_;
}
push @proxyCommand, "-p", $proxyPort if $proxyPort && $proxyPort != 22;
push @proxyCommand, '-l', $proxyUser, '-W', '%h:%p', $proxyIp;
my $proxyCommandStr = join(' ', map { /\s/ ? "'$_'" : $_ } @proxyCommand);
osh_debug("Testing with ProxyCommand: $proxyCommandStr");
push @command, '-o', "ProxyCommand=$proxyCommandStr";
$ENV{'OSH_PROXYJUMP_CONNECTION'} = 1;
}
push @command, '-T', '--', 'true';
osh_info("Testing connection to $user\@$ip, please wait...");
my $connection_info_msg = "Testing connection to $user\@$ip";
if (defined $proxyIp) {
$connection_info_msg .= " via proxy $proxyIp";
}
$connection_info_msg .= ", please wait...";
osh_info($connection_info_msg);
$fnret = OVH::Bastion::execute(cmd => \@command, noisy_stderr => 1);
$fnret or return $fnret;
@ -1291,8 +1532,9 @@ sub _get_acl_from_file {
my @entries;
foreach my $line (@lines) {
my (
$ip, $user, $port, $comment, $forceKey, $forcePassword,
$expiry, $addedBy, $addedDate, $extra, $comment, $userComment
$ip, $user, $port, $comment, $forceKey,
$forcePassword, $expiry, $addedBy, $addedDate, $extra,
$comment, $userComment, $proxyIp, $proxyPort, $proxyUser
);
# extract comment if any
@ -1382,6 +1624,33 @@ sub _get_acl_from_file {
$forcePassword = $fnret->value->{'hash'};
osh_debug("found a valid forced password <$forcePassword>");
}
if ($comment =~ s/# PROXYHOST=(\S+)//) {
$fnret = OVH::Bastion::is_valid_ip(ip => $1, allowSubnets => 0);
if (!$fnret) {
osh_debug("skipping line <$line> because invalid proxy host IP ($1) found");
next;
}
$proxyIp = $fnret->value->{'ip'};
osh_debug("found a valid proxy host <$proxyIp>");
}
if ($comment =~ s/# PROXYPORT=(\d+)//) {
$fnret = OVH::Bastion::is_valid_port(port => $1);
if (!$fnret) {
osh_debug("skipping line <$line> because invalid proxy port ($1) found");
next;
}
$proxyPort = $fnret->value;
osh_debug("found a valid proxy port <$proxyPort>");
}
if ($comment =~ s/# PROXYUSER=(\S+)//) {
$fnret = OVH::Bastion::is_valid_remote_user(user => $1, allowWildcards => 1);
if (!$fnret) {
osh_debug("skipping line <$line> because invalid proxy user ($1) found");
next;
}
$proxyUser = $fnret->value;
osh_debug("found a valid proxy user <$proxyUser>");
}
if ($comment =~ s/# COMMENT=<([^>]+)>//) {
$userComment = $1;
}
@ -1404,6 +1673,9 @@ sub _get_acl_from_file {
addedDate => $addedDate,
userComment => $userComment,
comment => $extra,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser
};
}

@ -340,6 +340,10 @@ sub access_modify {
my $forceKey = $params{'forceKey'};
my $forcePassword = $params{'forcePassword'};
my $proxyIp = $params{'proxyIp'};
my $proxyPort = $params{'proxyPort'};
my $proxyUser = $params{'proxyUser'};
my $dryrun = $params{'dryrun'}; # don't do anything, just check params and prereqs
my $sudo = $params{'sudo'}; # passed as-is to subs we use
@ -368,8 +372,9 @@ sub access_modify {
# normalize * into undef
# also, due to how plugins work, sometimes user and port are just '', make them undef in those cases
undef $user if (defined $user && ($user eq '*' || $user eq ''));
undef $port if (defined $port && ($port eq '*' || $port eq ''));
undef $user if (defined $user && ($user eq '*' || $user eq ''));
undef $port if (defined $port && ($port eq '*' || $port eq ''));
undef $proxyUser if (defined $proxyUser && ($proxyUser eq '*' || $proxyUser eq ''));
# check way
if ($way eq 'personal') {
@ -469,6 +474,35 @@ sub access_modify {
}
}
# check proxy host
if (defined $proxyIp) {
$fnret = OVH::Bastion::is_valid_ip(ip => $proxyIp, allowSubnets => 0);
return $fnret unless $fnret;
$proxyIp = $fnret->value->{'ip'};
}
# check proxy port
if (defined $proxyPort) {
$fnret = OVH::Bastion::is_valid_port(port => $proxyPort);
return $fnret unless $fnret;
$proxyPort = $fnret->value;
}
# check proxy user
if (defined $proxyUser) {
$fnret = OVH::Bastion::is_valid_remote_user(user => $proxyUser, allowWildcards => 1);
return $fnret unless $fnret;
$proxyUser = $fnret->value;
}
# Validate proxy configuration: if PROXYHOST is specified, PROXYPORT must be too
if (defined $proxyIp && !defined $proxyPort) {
return R('ERR_INVALID_PARAMETER', msg => "When specifying a proxy host, proxy port must also be specified");
}
if (!defined $proxyIp && defined $proxyPort) {
return R('ERR_INVALID_PARAMETER', msg => "When specifying a proxy port, proxy host must also be specified");
}
# check if the caller has the right to make the change they're asking
# ... 1. either $> is allowkeeper and $ENV{'SUDO_USER'} is the requesting account
# ... 2. or $> is $grouptomodify and $ENV{'SUDO_USER'} is the requesting account
@ -549,6 +583,9 @@ sub access_modify {
way => $way,
group => $shortGroup,
account => $account,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
exactMatch => 1, # we're checking if the exact right we're asked to modify exists or not
);
osh_debug("... result is $fnret");
@ -631,6 +668,15 @@ sub access_modify {
if ($ttl) {
$entry .= " # EXPIRY=" . (time() + $ttl);
}
if ($proxyIp) {
$entry .= " # PROXYHOST=" . $proxyIp;
}
if ($proxyPort) {
$entry .= " # PROXYPORT=" . $proxyPort;
}
if ($proxyUser) {
$entry .= " # PROXYUSER=" . $proxyUser;
}
if ($comment) {
$comment =~ s{[#<>\\"']}{_}g;
$entry .= " # COMMENT=<" . $comment . ">";
@ -652,7 +698,14 @@ sub access_modify {
else {
return R('ERR_CANNOT_OPEN_FILE', msg => "Error opening $file: $!");
}
my $machine = OVH::Bastion::machine_display(ip => $ip, port => $port, user => $user)->value;
my $machine = OVH::Bastion::machine_display(
ip => $ip,
port => $port,
user => $user,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
)->value;
my $ttlmsg =
$ttl ? (' (expires in ' . OVH::Bastion::duration2human(seconds => $ttl)->value->{'human'} . ')') : '';
$returnmsg = "Access to $machine successfully added$ttlmsg";
@ -663,9 +716,35 @@ sub access_modify {
my $found = 0;
while (my $line = <$fh_file>) {
if ($line =~ m{^\Q$entry\E(\s|$)}) {
chomp $line;
$line = "# $line # $comment\n";
$found++;
# now verify that proxy options match too
my $shouldDelete = 1;
if (defined $proxyIp) {
$shouldDelete = 0 unless $line =~ m{\# PROXYHOST=\Q$proxyIp\E(\s|$)};
}
elsif ($line =~ m{\# PROXYHOST=}) {
$shouldDelete = 0;
}
if ($shouldDelete && defined $proxyPort) {
$shouldDelete = 0 unless $line =~ m{\# PROXYPORT=\Q$proxyPort\E(\s|$)};
}
elsif ($shouldDelete && $line =~ m{\# PROXYPORT=}) {
$shouldDelete = 0;
}
if ($shouldDelete && defined $proxyUser) {
$shouldDelete = 0 unless $line =~ m{\# PROXYUSER=\Q$proxyUser\E(\s|$)};
}
elsif ($shouldDelete && $line =~ m{\# PROXYUSER=}) {
$shouldDelete = 0;
}
if ($shouldDelete) {
chomp $line;
$line = "# $line # $comment\n";
$found++;
}
}
$newFile .= $line;
}
@ -677,7 +756,14 @@ sub access_modify {
if (open(my $fh_file, '>', $file)) {
print $fh_file $newFile;
close($fh_file);
my $machine = OVH::Bastion::machine_display(ip => $ip, port => $port, user => $user)->value;
my $machine = OVH::Bastion::machine_display(
ip => $ip,
port => $port,
user => $user,
proxyIp => $proxyIp,
proxyPort => $proxyPort,
proxyUser => $proxyUser,
)->value;
$returnmsg = "Access to $machine successfully removed";
}
else {
@ -704,6 +790,9 @@ sub access_modify {
['force_key', $params{'forceKey'}],
['force_password', $params{'forcePassword'}],
['comment', $params{'comment'}],
['proxy_ip', $params{'proxyIp'}],
['proxy_port', $params{'proxyPort'}],
['proxy_user', $params{'proxyUser'}],
]
);
return R('OK', msg => $returnmsg) if $returnmsg;

@ -156,7 +156,8 @@ sub load_configuration {
{name => 'syslogDescription', default => 'bastion', validre => qr/^([a-zA-Z0-9_.-]+)$/},
{
name => 'ttyrecFilenameFormat',
default => '%Y-%m-%d.%H-%M-%S.#usec#.&uniqid.&account.&user.&ip.&port.ttyrec',
default =>
'%Y-%m-%d.%H-%M-%S.#usec#.&uniqid.&account.&user.&ip.&port.via.&proxyuser.&proxyip.&proxyport.ttyrec',
validre => qr/^([a-zA-Z0-9%&#_.-]+)$/
},
{name => 'accountExpiredMessage', default => '', validre => qr/^(.*)$/, emptyok => 1},
@ -277,6 +278,7 @@ sub load_configuration {
interactiveModeAllowed readOnlySlaveMode sshClientHasOptionE ingressKeysFromAllowOverride
moshAllowed debug keyboardInteractiveAllowed passwordAllowed telnetAllowed remoteCommandEscapeByDefault
accountExternalValidationDenyOnFailure ingressRequirePIV IPv6Allowed sshAddKeysToAgentAllowed
egressProxyJumpAllowed
}
],
}

@ -223,6 +223,29 @@ EOS
# endof version==0
if ($user_version == 1) {
# add proxy columns for proxyjump feature
my $table = ($sqltype eq 'local' ? "connections" : "connections_summary");
$dbh->do("ALTER TABLE $table ADD COLUMN proxyuser TEXT")
or return R('KO', msg => "adding proxyuser column to $table");
$dbh->do("ALTER TABLE $table ADD COLUMN proxyip TEXT")
or return R('KO', msg => "adding proxyip column to $table");
$dbh->do("ALTER TABLE $table ADD COLUMN proxyhost TEXT")
or return R('KO', msg => "adding proxyhost column to $table");
$dbh->do("ALTER TABLE $table ADD COLUMN proxyport INTEGER")
or return R('KO', msg => "adding proxyport column to $table");
$dbh->do("PRAGMA user_version=2")
or return R('KO', msg => "setting user_version to 2");
$user_version = 2;
}
# endof version==1
if ($user_version == 2) {
; # insert here future schema modifications
}
@ -254,6 +277,10 @@ sub _sql_log_insert_file {
my $timestampusec = $params{'timestampusec'};
my $uniqid = $params{'uniqid'};
my $sqltype = $params{'sqltype'};
my $proxyuser = $params{'proxyuser'};
my $proxyip = $params{'proxyip'};
my $proxyhost = $params{'proxyhost'};
my $proxyport = $params{'proxyport'};
if ($sqltype ne 'local' and $sqltype ne 'global') {
return R('ERR_INVALID_PARAMETER', msg => "Invalid parameter sqltype");
@ -304,20 +331,24 @@ sub _sql_log_insert_file {
my @execute;
if ($sqltype eq 'local') {
@execute = (
$uniqid, $timestamp, $timestampusec, $account, $cmdtype, $allowed, $hostfrom,
$ipfrom, $portfrom, $bastionip, $bastionport, $hostto, $ipto, $portto,
$user, $plugin, $params, $comment, $ttyrecfile
$uniqid, $timestamp, $timestampusec, $account, $cmdtype, $allowed,
$hostfrom, $ipfrom, $portfrom, $bastionip, $bastionport, $hostto,
$ipto, $portto, $user, $plugin, $params, $comment,
$ttyrecfile, $proxyuser, $proxyip, $proxyhost, $proxyport
);
$prepare =
"INSERT INTO connections"
. "(uniqid,timestamp,timestampusec,account,cmdtype,allowed,hostfrom,ipfrom,portfrom,bastionip,bastionport,hostto,ipto,portto,user,plugin,params,comment,ttyrecfile)"
. "(uniqid,timestamp,timestampusec,account,cmdtype,allowed,hostfrom,ipfrom,portfrom,bastionip,bastionport,hostto,ipto,portto,user,plugin,params,comment,ttyrecfile,proxyuser,proxyip,proxyhost,proxyport)"
. 'VALUES ('
. ('?,' x (@execute - 1)) . '?)';
}
elsif ($sqltype eq 'global') {
@execute = ($uniqid, $timestamp, $account, $cmdtype, $allowed, $ipfrom, $ipto, $portto, $user, $plugin);
@execute = (
$uniqid, $timestamp, $account, $cmdtype, $allowed, $ipfrom, $ipto,
$portto, $user, $plugin, $proxyuser, $proxyhost, $proxyip, $proxyport
);
$prepare =
"INSERT INTO connections_summary (uniqid,timestamp,account,cmdtype,allowed,ipfrom,ipto,portto,user,plugin)"
"INSERT INTO connections_summary (uniqid,timestamp,account,cmdtype,allowed,ipfrom,ipto,portto,user,plugin,proxyuser,proxyip,proxyhost,proxyport)"
. 'VALUES ('
. ('?,' x (@execute - 1)) . '?)';
}
@ -405,6 +436,9 @@ sub log_access_insert {
if (not defined $params{'hostfrom'} and defined $params{'ipfrom'}) {
$params{'hostfrom'} = OVH::Bastion::ip2host($params{'ipfrom'})->value;
}
if (not defined $params{'proxyhost'} and defined $params{'proxyip'}) {
$params{'proxyhost'} = OVH::Bastion::ip2host($params{'proxyip'})->value;
}
my ($timestamp, $timestampusec) = Time::HiRes::gettimeofday();
$params{'timestamp'} = $timestamp;
@ -475,6 +509,10 @@ sub log_access_insert {
['accountsql', $accountsql],
['comment', $params{'comment'}],
['params', $params{'params'}],
['proxyuser', $params{'proxyuser'}],
['proxyip', $params{'proxyip'}],
['proxyhost', $params{'proxyhost'}],
['proxyport', $params{'proxyport'}],
);
if (ref $custom eq 'ARRAY') {
foreach my $item (@$custom) {
@ -771,6 +809,9 @@ sub _sql_log_fetch_from_file {
my $portto = $params{'portto'};
my $bastionip = $params{'bastionip'};
my $bastionport = $params{'bastionport'};
my $proxyuser = $params{'proxyuser'};
my $proxyip = $params{'proxyip'};
my $proxyport = $params{'proxyport'};
my $user = $params{'user'};
my $plugin = $params{'plugin'};
my $before = $params{'before'};
@ -828,6 +869,18 @@ sub _sql_log_fetch_from_file {
push @conditions, "bastionport = ?";
push @execvalues, $bastionport;
}
if ($proxyuser) {
push @conditions, "proxyuser = ?";
push @execvalues, $proxyuser;
}
if ($proxyip) {
push @conditions, "proxyip = ?";
push @execvalues, $proxyip;
}
if ($proxyport) {
push @conditions, "proxyport = ?";
push @execvalues, $proxyport;
}
if ($before) {
push @conditions, "timestamp <= ?";
push @execvalues, $before;

@ -0,0 +1,479 @@
# vim: set filetype=sh ts=4 sw=4 sts=4 et:
# shellcheck shell=bash
# shellcheck disable=SC2317,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_proxyjump()
{
# Create test accounts
success a0_create_a1 $a0 --osh accountCreate --always-active --account $account1 --uid $uid1 --public-key "\"$(cat $account1key1file.pub)\""
json .error_code OK .command accountCreate
success a0_create_a2 $a0 --osh accountCreate --always-active --account $account2 --uid $uid2 --public-key "\"$(cat $account2key1file.pub)\""
json .error_code OK .command accountCreate
# Create a test group
success a0_create_group1 $a0 --osh groupCreate --group $group1 --owner $account1 --algo ed25519 --size 256
json .error_code OK .command groupCreate
# Test that proxy parameters are rejected while the feature is disabled
run selfAddPersonalAccess_proxy_feature_disabled $a0 --osh selfAddPersonalAccess --host 192.168.1.100 --user testuser --port 22 --proxy-host 10.0.0.1 --proxy-port 22 --proxy-user testuser --force
retvalshouldbe 100
contain "ProxyJump egress connections are disabled by policy"
json .error_code ERR_INVALID_PARAMETER
# now enable the proxyjump feature
configchg 's=^\\\\x22egressProxyJumpAllowed\\\\x22.+=\\\\x22egressProxyJumpAllowed\\\\x22:true,='
#
# Test selfAddPersonalAccess with proxy parameters
#
# Test basic proxy parameter
success selfAddPersonalAccess_with_proxy_host $a0 --osh selfAddPersonalAccess --host 192.168.1.100 --user testuser --port 22 --proxy-host 10.0.0.1 --proxy-port 22 --proxy-user testuser --force
json .command selfAddPersonalAccess .error_code OK .value.ip 192.168.1.100 .value.user testuser .value.port 22 .value.proxyIp 10.0.0.1 .value.proxyPort 22
# Test with hostname as proxy-host
success selfAddPersonalAccess_with_proxy_hostname $a0 --osh selfAddPersonalAccess --host 192.168.1.102 --user testuser --port 22 --proxy-host localhost --proxy-port 22 --proxy-user testuser --force
json .command selfAddPersonalAccess .error_code OK .value.ip 192.168.1.102 .value.user testuser .value.port 22 .value.proxyIp 127.0.0.1
# Test invalid proxy-host
plgfail selfAddPersonalAccess_invalid_proxy_host $a0 --osh selfAddPersonalAccess --host 192.168.1.103 --user testuser --port 22 --proxy-host "invalid..host..name" --proxy-port 22 --proxy-user testuser --force
json .command selfAddPersonalAccess .error_code ERR_HOST_NOT_FOUND
# Test proxy-port without proxy-host
plgfail selfAddPersonalAccess_proxy_port_without_host $a0 --osh selfAddPersonalAccess --host 192.168.1.104 --user testuser --port 22 --proxy-port 2222 --force
json .command selfAddPersonalAccess .error_code ERR_MISSING_PARAMETER
# Test proxy-host without proxy-port
plgfail selfAddPersonalAccess_proxy_host_without_port $a0 --osh selfAddPersonalAccess --host 192.168.1.107 --user testuser --port 22 --proxy-host 10.0.0.1 --proxy-user testuser --force
json .command selfAddPersonalAccess .error_code ERR_MISSING_PARAMETER
# Test invalid proxy-port
plgfail selfAddPersonalAccess_invalid_proxy_port $a0 --osh selfAddPersonalAccess --host 192.168.1.105 --user testuser --port 22 --proxy-host 10.0.0.1 --proxy-port abc --proxy-user testuser --force
json .command selfAddPersonalAccess .error_code ERR_INVALID_PARAMETER
# Test invalid proxy-port (out of range)
plgfail selfAddPersonalAccess_proxy_port_out_of_range $a0 --osh selfAddPersonalAccess --host 192.168.1.106 --user testuser --port 22 --proxy-host 10.0.0.1 --proxy-port 99999 --proxy-user testuser --force
json .command selfAddPersonalAccess .error_code ERR_INVALID_PARAMETER
# Test proxy-host and proxy-port without proxy-user (should fail)
plgfail selfAddPersonalAccess_proxy_without_user $a0 --osh selfAddPersonalAccess --host 192.168.1.108 --user testuser --port 22 --proxy-host 10.0.0.1 --proxy-port 22 --force
json .command selfAddPersonalAccess .error_code ERR_MISSING_PARAMETER
contain "When --proxy-host is specified, --proxy-user becomes mandatory"
#
# Test selfAddPersonalAccess with proxy-user parameter
#
# Test basic proxy-user parameter
success selfAddPersonalAccess_with_proxy_user $a0 --osh selfAddPersonalAccess --host 192.168.1.110 --user testuser --port 22 --proxy-host 10.0.0.1 --proxy-port 22 --proxy-user proxyuser --force
json .command selfAddPersonalAccess .error_code OK .value.ip 192.168.1.110 .value.user testuser .value.port 22 .value.proxyIp 10.0.0.1 .value.proxyPort 22 .value.proxyUser proxyuser
# Test proxy-user wildcard
success selfAddPersonalAccess_with_proxy_user_wildcard $a0 --osh selfAddPersonalAccess --host 192.168.1.111 --user testuser --port 22 --proxy-host 10.0.0.1 --proxy-port 22 --proxy-user '*' --force
json .command selfAddPersonalAccess .error_code OK .value.ip 192.168.1.111 .value.user testuser .value.port 22 .value.proxyIp 10.0.0.1 .value.proxyPort 22
# Test invalid proxy-user
plgfail selfAddPersonalAccess_invalid_proxy_user $a0 --osh selfAddPersonalAccess --host 192.168.1.112 --user testuser --port 22 --proxy-host 10.0.0.1 --proxy-port 22 --proxy-user "inväliduse{r" --force
json .command selfAddPersonalAccess .error_code ERR_INVALID_PARAMETER
# Clean up proxy-user test entries
success selfDelPersonalAccess_with_proxy_user $a0 --osh selfDelPersonalAccess --host 192.168.1.110 --user testuser --port 22 --proxy-host 10.0.0.1 --proxy-port 22 --proxy-user proxyuser
json .command selfDelPersonalAccess .error_code OK
success selfDelPersonalAccess_with_proxy_user_wildcard $a0 --osh selfDelPersonalAccess --host 192.168.1.111 --user testuser --port 22 --proxy-host 10.0.0.1 --proxy-port 22 --proxy-user '*'
json .command selfDelPersonalAccess .error_code OK
#
# Test accountAddPersonalAccess with proxy parameters
#
# Test basic proxy-host parameter
success accountAddPersonalAccess_with_proxy_host $a0 --osh accountAddPersonalAccess --account $account2 --host 192.168.2.100 --user testuser --port 22 --proxy-host 10.0.0.2 --proxy-port 22 --proxy-user testuser
json .command accountAddPersonalAccess .error_code OK .value.ip 192.168.2.100 .value.user testuser .value.port 22 .value.proxyIp 10.0.0.2
# Test with proxy hostname
success accountAddPersonalAccess_with_proxy_port $a0 --osh accountAddPersonalAccess --account $account2 --host 192.168.2.101 --user testuser --port 22 --proxy-host localhost --proxy-port 3333 --proxy-user testuser
json .command accountAddPersonalAccess .error_code OK .value.ip 192.168.2.101 .value.user testuser .value.port 22 .value.proxyIp 127.0.0.1 .value.proxyPort 3333
# Test proxy-port without proxy-host
plgfail accountAddPersonalAccess_proxy_port_without_host $a0 --osh accountAddPersonalAccess --account $account2 --host 192.168.2.102 --user testuser --port 22 --proxy-port 3333
json .command accountAddPersonalAccess .error_code ERR_MISSING_PARAMETER
# Test proxy-host without proxy-port
plgfail accountAddPersonalAccess_proxy_host_without_port $a0 --osh accountAddPersonalAccess --account $account2 --host 192.168.2.102 --user testuser --port 22 --proxy-host 10.0.0.2 --proxy-user testuser
json .command accountAddPersonalAccess .error_code ERR_MISSING_PARAMETER
# Test proxy-host and proxy-port without proxy-user (should fail)
plgfail accountAddPersonalAccess_proxy_without_user $a0 --osh accountAddPersonalAccess --account $account2 --host 192.168.2.103 --user testuser --port 22 --proxy-host 10.0.0.2 --proxy-port 22
json .command accountAddPersonalAccess .error_code ERR_MISSING_PARAMETER
contain "When --proxy-host is specified, --proxy-user becomes mandatory"
#
# Test accountAddPersonalAccess with proxy-user parameter
#
# Test basic proxy-user parameter
success accountAddPersonalAccess_with_proxy_user $a0 --osh accountAddPersonalAccess --account $account2 --host 192.168.2.110 --user testuser --port 22 --proxy-host 10.0.0.2 --proxy-port 22 --proxy-user proxyuser
json .command accountAddPersonalAccess .error_code OK .value.ip 192.168.2.110 .value.user testuser .value.port 22 .value.proxyIp 10.0.0.2 .value.proxyPort 22 .value.proxyUser proxyuser
# Test proxy-user wildcard
success accountAddPersonalAccess_with_proxy_user_wildcard $a0 --osh accountAddPersonalAccess --account $account2 --host 192.168.2.111 --user testuser --port 22 --proxy-host 10.0.0.2 --proxy-port 22 --proxy-user '*'
json .command accountAddPersonalAccess .error_code OK .value.ip 192.168.2.111 .value.user testuser .value.port 22 .value.proxyIp 10.0.0.2 .value.proxyPort 22
# Clean up proxy-user test entries
success accountDelPersonalAccess_with_proxy_user $a0 --osh accountDelPersonalAccess --account $account2 --host 192.168.2.110 --user testuser --port 22 --proxy-host 10.0.0.2 --proxy-port 22 --proxy-user proxyuser
json .command accountDelPersonalAccess .error_code OK
success accountDelPersonalAccess_with_proxy_user_wildcard $a0 --osh accountDelPersonalAccess --account $account2 --host 192.168.2.111 --user testuser --port 22 --proxy-host 10.0.0.2 --proxy-port 22 --proxy-user '*'
json .command accountDelPersonalAccess .error_code OK
#
# Test groupAddServer with proxy parameters
#
# Test basic proxy-host parameter
success groupAddServer_with_proxy_host $a1 --osh groupAddServer --group $group1 --host 192.168.3.100 --user testuser --port 22 --proxy-host 10.0.0.3 --proxy-port 22 --proxy-user testuser --force
json .command groupAddServer .error_code OK .value.ip 192.168.3.100 .value.user testuser .value.port 22 .value.proxyIp 10.0.0.3 .value.proxyPort 22
# Test with proxy hostname
success groupAddServer_with_proxy_port $a1 --osh groupAddServer --group $group1 --host 192.168.3.101 --user testuser --port 22 --proxy-host localhost --proxy-port 4444 --proxy-user testuser --force
json .command groupAddServer .error_code OK .value.ip 192.168.3.101 .value.user testuser .value.port 22 .value.proxyIp 127.0.0.1 .value.proxyPort 4444
# Test proxy-port without proxy-host
plgfail groupAddServer_proxy_port_without_host $a1 --osh groupAddServer --group $group1 --host 192.168.3.102 --user testuser --port 22 --proxy-port 4444 --force
json .command groupAddServer .error_code ERR_MISSING_PARAMETER
# Test proxy-host without proxy-port
plgfail groupAddServer_proxy_host_without_port $a1 --osh groupAddServer --group $group1 --host 192.168.3.102 --user testuser --port 22 --proxy-host 10.0.0.3 --proxy-user testuser --force
json .command groupAddServer .error_code ERR_MISSING_PARAMETER
# Test invalid proxy-host
plgfail groupAddServer_invalid_proxy_host $a1 --osh groupAddServer --group $group1 --host 192.168.3.103 --user testuser --port 22 --proxy-host "bad...hostname" --proxy-port 22 --proxy-user testuser --force
json .command groupAddServer .error_code ERR_HOST_NOT_FOUND
# Test proxy-host and proxy-port without proxy-user (should fail)
plgfail groupAddServer_proxy_without_user $a1 --osh groupAddServer --group $group1 --host 192.168.3.104 --user testuser --port 22 --proxy-host 10.0.0.3 --proxy-port 22 --force
json .command groupAddServer .error_code ERR_MISSING_PARAMETER
contain "When --proxy-host is specified, --proxy-user becomes mandatory"
#
# Test groupAddServer with proxy-user parameter
#
# Test basic proxy-user parameter
success groupAddServer_with_proxy_user $a1 --osh groupAddServer --group $group1 --host 192.168.3.110 --user testuser --port 22 --proxy-host 10.0.0.3 --proxy-port 22 --proxy-user proxyuser --force
json .command groupAddServer .error_code OK .value.ip 192.168.3.110 .value.user testuser .value.port 22 .value.proxyIp 10.0.0.3 .value.proxyPort 22 .value.proxyUser proxyuser
# Test proxy-user wildcard
success groupAddServer_with_proxy_user_wildcard $a1 --osh groupAddServer --group $group1 --host 192.168.3.111 --user testuser --port 22 --proxy-host 10.0.0.3 --proxy-port 22 --proxy-user '*' --force
json .command groupAddServer .error_code OK .value.ip 192.168.3.111 .value.user testuser .value.port 22 .value.proxyIp 10.0.0.3 .value.proxyPort 22
# Clean up proxy-user test entries
success groupDelServer_with_proxy_user $a1 --osh groupDelServer --group $group1 --host 192.168.3.110 --user testuser --port 22 --proxy-host 10.0.0.3 --proxy-port 22 --proxy-user proxyuser
json .command groupDelServer .error_code OK
success groupDelServer_with_proxy_user_wildcard $a1 --osh groupDelServer --group $group1 --host 192.168.3.111 --user testuser --port 22 --proxy-host 10.0.0.3 --proxy-port 22 --proxy-user '*'
json .command groupDelServer .error_code OK
#
# Test deletion of accesses with proxy parameters
#
# Delete selfAddPersonalAccess entries with missing proxy-port
plgfail selfDelPersonalAccess_without_proxy_port $a0 --osh selfDelPersonalAccess --host 192.168.1.100 --user testuser --port 22 --proxy-host 10.0.0.1 --proxy-user testuser
json .command selfDelPersonalAccess .error_code ERR_MISSING_PARAMETER
# Delete selfAddPersonalAccess entries with missing proxy-host
plgfail selfDelPersonalAccess_without_proxy_host $a0 --osh selfDelPersonalAccess --host 192.168.1.100 --user testuser --port 22 --proxy-port 22
json .command selfDelPersonalAccess .error_code ERR_MISSING_PARAMETER
# Delete selfAddPersonalAccess entries with missing proxy-user
plgfail selfDelPersonalAccess_without_proxy_user $a0 --osh selfDelPersonalAccess --host 192.168.1.100 --user testuser --port 22 --proxy-host 10.0.0.1 --proxy-port 22
json .command selfDelPersonalAccess .error_code ERR_MISSING_PARAMETER
contain "When --proxy-host is specified, --proxy-user becomes mandatory"
# Delete selfAddPersonalAccess entries
success selfDelPersonalAccess_with_proxy $a0 --osh selfDelPersonalAccess --host 192.168.1.100 --user testuser --port 22 --proxy-host 10.0.0.1 --proxy-port 22 --proxy-user testuser
json .command selfDelPersonalAccess .error_code OK
success selfDelPersonalAccess_with_proxy_hostname $a0 --osh selfDelPersonalAccess --host 192.168.1.102 --user testuser --port 22 --proxy-host localhost --proxy-port 22 --proxy-user testuser
json .command selfDelPersonalAccess .error_code OK
# Delete accountAddPersonalAccess entries with missing proxy-port
plgfail accountDelPersonalAccess_without_proxy_port $a0 --osh accountDelPersonalAccess --account $account2 --host 192.168.2.100 --user testuser --port 22 --proxy-host 10.0.0.2 --proxy-user testuser
json .command accountDelPersonalAccess .error_code ERR_MISSING_PARAMETER
# Delete accountAddPersonalAccess entries with missing proxy-host
plgfail accountDelPersonalAccess_without_proxy_host $a0 --osh accountDelPersonalAccess --account $account2 --host 192.168.2.100 --user testuser --port 22 --proxy-port 22
json .command accountDelPersonalAccess .error_code ERR_MISSING_PARAMETER
# Delete accountAddPersonalAccess entries with missing proxy-user
plgfail accountDelPersonalAccess_without_proxy_user $a0 --osh accountDelPersonalAccess --account $account2 --host 192.168.2.100 --user testuser --port 22 --proxy-host 10.0.0.2 --proxy-port 22
json .command accountDelPersonalAccess .error_code ERR_MISSING_PARAMETER
contain "When --proxy-host is specified, --proxy-user becomes mandatory"
# Delete accountAddPersonalAccess entries
success accountDelPersonalAccess_with_proxy $a0 --osh accountDelPersonalAccess --account $account2 --host 192.168.2.100 --user testuser --port 22 --proxy-host 10.0.0.2 --proxy-port 22 --proxy-user testuser
json .command accountDelPersonalAccess .error_code OK
success accountDelPersonalAccess_with_proxy_hostname $a0 --osh accountDelPersonalAccess --account $account2 --host 192.168.2.101 --user testuser --port 22 --proxy-host localhost --proxy-port 3333 --proxy-user testuser
json .command accountDelPersonalAccess .error_code OK
# Delete groupAddServer entries with missing proxy-port
plgfail groupDelServer_without_proxy_port $a1 --osh groupDelServer --group $group1 --host 192.168.3.100 --user testuser --port 22 --proxy-host 10.0.0.3 --proxy-user testuser
json .command groupDelServer .error_code ERR_MISSING_PARAMETER
# Delete groupAddServer entries with missing proxy-host
plgfail groupDelServer_without_proxy_host $a1 --osh groupDelServer --group $group1 --host 192.168.3.100 --user testuser --port 22 --proxy-port 22
json .command groupDelServer .error_code ERR_MISSING_PARAMETER
# Delete groupAddServer entries with missing proxy-user
plgfail groupDelServer_without_proxy_user $a1 --osh groupDelServer --group $group1 --host 192.168.3.100 --user testuser --port 22 --proxy-host 10.0.0.3 --proxy-port 22
json .command groupDelServer .error_code ERR_MISSING_PARAMETER
contain "When --proxy-host is specified, --proxy-user becomes mandatory"
# Delete groupAddServer entries
success groupDelServer_with_proxy $a1 --osh groupDelServer --group $group1 --host 192.168.3.100 --user testuser --port 22 --proxy-host 10.0.0.3 --proxy-port 22 --proxy-user testuser
json .command groupDelServer .error_code OK
success groupDelServer_with_proxy_hostname $a1 --osh groupDelServer --group $group1 --host 192.168.3.101 --user testuser --port 22 --proxy-host localhost --proxy-port 4444 --proxy-user testuser
json .command groupDelServer .error_code OK
#
# Test that proxy information is displayed in access lists
#
# Add self access with proxy
success add_access_for_list_check $a0 --osh selfAddPersonalAccess --host 192.168.1.200 --user listtest --port 2222 --proxy-host 10.0.0.5 --proxy-port 5555 --proxy-user listtest --force
json .command selfAddPersonalAccess .error_code OK
# Check that selfListAccesses shows the proxy information
success selfListAccesses_shows_proxy $a0 --osh selfListAccesses
json .command selfListAccesses .error_code OK
json .value[0].acl[0].ip 192.168.1.200
json .value[0].acl[0].port 2222
json .value[0].acl[0].user listtest
json .value[0].acl[0].proxyIp 10.0.0.5
json .value[0].acl[0].proxyPort 5555
json .value[0].acl[0].proxyUser listtest
# Clean up
success cleanup_list_test $a0 --osh selfDelPersonalAccess --host 192.168.1.200 --user listtest --port 2222 --proxy-host 10.0.0.5 --proxy-port 5555 --proxy-user listtest
json .command selfDelPersonalAccess .error_code OK # Add self access with proxy and proxy-user
success add_access_with_proxy_user_for_list_check $a0 --osh selfAddPersonalAccess --host 192.168.1.201 --user listtest --port 2222 --proxy-host 10.0.0.5 --proxy-port 5555 --proxy-user proxyuser --force
json .command selfAddPersonalAccess .error_code OK
# Check that selfListAccesses shows the proxy-user information
success selfListAccesses_shows_proxy_user $a0 --osh selfListAccesses
json .command selfListAccesses .error_code OK
json .value[0].acl[0].ip 192.168.1.201
json .value[0].acl[0].port 2222
json .value[0].acl[0].user listtest
json .value[0].acl[0].proxyIp 10.0.0.5
json .value[0].acl[0].proxyPort 5555
json .value[0].acl[0].proxyUser proxyuser
# Clean up
success cleanup_list_test_with_proxy_user $a0 --osh selfDelPersonalAccess --host 192.168.1.201 --user listtest --port 2222 --proxy-host 10.0.0.5 --proxy-port 5555 --proxy-user proxyuser
json .command selfDelPersonalAccess .error_code OK
# Add account access with proxy
success add_account_access_for_list_check $a0 --osh accountAddPersonalAccess --account $account2 --host 192.168.2.100 --user testuser --port 22 --proxy-host 10.0.0.2 --proxy-port 22 --proxy-user testuser
json .command accountAddPersonalAccess .error_code OK
# Check that accountListAccesses shows the proxy information
success accountListAccesses_shows_proxy $a0 --osh accountListAccesses --account $account2
json .command accountListAccesses .error_code OK
json .value[0].acl[0].ip 192.168.2.100
json .value[0].acl[0].port 22
json .value[0].acl[0].user testuser
json .value[0].acl[0].proxyIp 10.0.0.2
json .value[0].acl[0].proxyPort 22
json .value[0].acl[0].proxyUser testuser
# Clean up
success cleanup_account_list_test $a0 --osh accountDelPersonalAccess --account $account2 --host 192.168.2.100 --user testuser --port 22 --proxy-host 10.0.0.2 --proxy-port 22 --proxy-user testuser
json .command accountDelPersonalAccess .error_code OK
# Add account access with proxy and proxy-user
success add_account_access_with_proxy_user_for_list_check $a0 --osh accountAddPersonalAccess --account $account2 --host 192.168.2.200 --user testuser --port 22 --proxy-host 10.0.0.2 --proxy-port 22 --proxy-user proxyuser
json .command accountAddPersonalAccess .error_code OK
# Check that accountListAccesses shows the proxy-user information
success accountListAccesses_shows_proxy_user $a0 --osh accountListAccesses --account $account2
json .command accountListAccesses .error_code OK
json .value[0].acl[0].ip 192.168.2.200
json .value[0].acl[0].port 22
json .value[0].acl[0].user testuser
json .value[0].acl[0].proxyIp 10.0.0.2
json .value[0].acl[0].proxyPort 22
json .value[0].acl[0].proxyUser proxyuser
# Clean up
success cleanup_account_list_test_with_proxy_user $a0 --osh accountDelPersonalAccess --account $account2 --host 192.168.2.200 --user testuser --port 22 --proxy-host 10.0.0.2 --proxy-port 22 --proxy-user proxyuser
json .command accountDelPersonalAccess .error_code OK
# Add group server with proxy
success add_group_server_for_list_check $a1 --osh groupAddServer --group $group1 --host 192.168.3.100 --user testuser --port 22 --proxy-host 10.0.0.3 --proxy-port 22 --proxy-user testuser --force
json .command groupAddServer .error_code OK
# Check that groupListServers shows the proxy information
success groupListServers_shows_proxy $a1 --osh groupListServers --group $group1
json .command groupListServers .error_code OK
json .value[0].ip 192.168.3.100
json .value[0].port 22
json .value[0].user testuser
json .value[0].proxyIp 10.0.0.3
json .value[0].proxyPort 22
json .value[0].proxyUser testuser
# Clean up
success cleanup_group_list_test $a1 --osh groupDelServer --group $group1 --host 192.168.3.100 --user testuser --port 22 --proxy-host 10.0.0.3 --proxy-port 22 --proxy-user testuser
json .command groupDelServer .error_code OK
# Add group server with proxy and proxy-user
success add_group_server_with_proxy_user_for_list_check $a1 --osh groupAddServer --group $group1 --host 192.168.3.200 --user testuser --port 22 --proxy-host 10.0.0.3 --proxy-port 22 --proxy-user proxyuser --force
json .command groupAddServer .error_code OK
# Check that groupListServers shows the proxy-user information
success groupListServers_shows_proxy_user $a1 --osh groupListServers --group $group1
json .command groupListServers .error_code OK
json .value[0].ip 192.168.3.200
json .value[0].port 22
json .value[0].user testuser
json .value[0].proxyIp 10.0.0.3
json .value[0].proxyPort 22
json .value[0].proxyUser proxyuser
# Clean up
success cleanup_group_list_test_with_proxy_user $a1 --osh groupDelServer --group $group1 --host 192.168.3.200 --user testuser --port 22 --proxy-host 10.0.0.3 --proxy-port 22 --proxy-user proxyuser
json .command groupDelServer .error_code OK
#
# Test groupAddGuestAccess with proxy parameters
#
# Add a server to the group first so we can test guest access
success a1_add_server_to_g1 $a1 --osh groupAddServer --group $group1 --host 192.168.4.100 --user testuser --port 22 --proxy-host 10.0.0.4 --proxy-port 22 --proxy-user testuser --force
json .command groupAddServer .error_code OK
# Test basic proxy parameter with groupAddGuestAccess
success groupAddGuestAccess_with_proxy_host $a1 --osh groupAddGuestAccess --group $group1 --account $account2 --host 192.168.4.100 --user testuser --port 22 --proxy-host 10.0.0.4 --proxy-port 22 --proxy-user testuser
json .command groupAddGuestAccess .error_code OK
# Test with hostname as proxy-host
success a1_add_server_with_proxy_hostname $a1 --osh groupAddServer --group $group1 --host 192.168.4.101 --user testuser --port 22 --proxy-host localhost --proxy-port 2222 --proxy-user testuser --force
json .command groupAddServer .error_code OK
success groupAddGuestAccess_with_proxy_hostname $a1 --osh groupAddGuestAccess --group $group1 --account $account2 --host 192.168.4.101 --user testuser --port 22 --proxy-host localhost --proxy-port 2222 --proxy-user testuser
json .command groupAddGuestAccess .error_code OK
# Test proxy-port without proxy-host
plgfail groupAddGuestAccess_proxy_port_without_host $a1 --osh groupAddGuestAccess --group $group1 --account $account2 --host 192.168.4.102 --user testuser --port 22 --proxy-port 2222
json .command groupAddGuestAccess .error_code ERR_MISSING_PARAMETER
# Test proxy-host without proxy-port
plgfail groupAddGuestAccess_proxy_host_without_port $a1 --osh groupAddGuestAccess --group $group1 --account $account2 --host 192.168.4.103 --user testuser --port 22 --proxy-host 10.0.0.4 --proxy-user testuser
json .command groupAddGuestAccess .error_code ERR_MISSING_PARAMETER
# Test proxy-host and proxy-port without proxy-user
plgfail groupAddGuestAccess_proxy_without_user $a1 --osh groupAddGuestAccess --group $group1 --account $account2 --host 192.168.4.104 --user testuser --port 22 --proxy-host 10.0.0.4 --proxy-port 22
json .command groupAddGuestAccess .error_code ERR_MISSING_PARAMETER
# Test invalid proxy-host
plgfail groupAddGuestAccess_invalid_proxy_host $a1 --osh groupAddGuestAccess --group $group1 --account $account2 --host 192.168.4.105 --user testuser --port 22 --proxy-host "badhostnäim" --proxy-port 22 --proxy-user testuser
json .command groupAddGuestAccess .error_code ERR_HOST_NOT_FOUND
# Test guest access that requires group to have access to proxy params
success a1_add_server_no_proxy $a1 --osh groupAddServer --group $group1 --host 192.168.4.110 --user testuser --port 22 --force
json .command groupAddServer .error_code OK
plgfail groupAddGuestAccess_group_no_access_to_proxy $a1 --osh groupAddGuestAccess --group $group1 --account $account2 --host 192.168.4.110 --user testuser --port 22 --proxy-host 10.0.0.4 --proxy-port 22 --proxy-user testuser
json .command groupAddGuestAccess .error_code ERR_GROUP_HAS_NO_ACCESS
#
# Test groupDelGuestAccess with proxy parameters
#
# Delete with missing proxy-port
plgfail groupDelGuestAccess_without_proxy_port $a1 --osh groupDelGuestAccess --group $group1 --account $account2 --host 192.168.4.100 --user testuser --port 22 --proxy-host 10.0.0.4 --proxy-user testuser
json .command groupDelGuestAccess .error_code ERR_MISSING_PARAMETER
# Delete with missing proxy-host
plgfail groupDelGuestAccess_without_proxy_host $a1 --osh groupDelGuestAccess --group $group1 --account $account2 --host 192.168.4.100 --user testuser --port 22 --proxy-port 22
json .command groupDelGuestAccess .error_code ERR_MISSING_PARAMETER
# Delete with missing proxy-user
plgfail groupDelGuestAccess_without_proxy_user $a1 --osh groupDelGuestAccess --group $group1 --account $account2 --host 192.168.4.100 --user testuser --port 22 --proxy-host 10.0.0.4 --proxy-port 22
json .command groupDelGuestAccess .error_code ERR_MISSING_PARAMETER
contain "When --proxy-host is specified, --proxy-user becomes mandatory"
# Delete guest access with proxy
success groupDelGuestAccess_with_proxy $a1 --osh groupDelGuestAccess --group $group1 --account $account2 --host 192.168.4.100 --user testuser --port 22 --proxy-host 10.0.0.4 --proxy-port 22 --proxy-user testuser
json .command groupDelGuestAccess .error_code OK
success groupDelGuestAccess_with_proxy_hostname $a1 --osh groupDelGuestAccess --group $group1 --account $account2 --host 192.168.4.101 --user testuser --port 22 --proxy-host localhost --proxy-port 2222 --proxy-user testuser
json .command groupDelGuestAccess .error_code OK
#
# Test that proxy information is displayed in groupListGuestAccesses
#
# Add guest access with proxy for list check
success a1_add_server_for_guest_list_check $a1 --osh groupAddServer --group $group1 --host 192.168.4.200 --user listtest --port 2222 --proxy-host 10.0.0.6 --proxy-port 6666 --proxy-user listtest --force
json .command groupAddServer .error_code OK
success add_guest_access_for_list_check $a1 --osh groupAddGuestAccess --group $group1 --account $account2 --host 192.168.4.200 --user listtest --port 2222 --proxy-host 10.0.0.6 --proxy-port 6666 --proxy-user listtest
json .command groupAddGuestAccess .error_code OK
# Check that groupListGuestAccesses shows the proxy information
success groupListGuestAccesses_shows_proxy $a1 --osh groupListGuestAccesses --group $group1 --account $account2
json .command groupListGuestAccesses .error_code OK .value[0].ip 192.168.4.200 .value[0].port 2222 .value[0].user listtest .value[0].proxyIp 10.0.0.6 .value[0].proxyPort 6666 .value[0].proxyUser listtest
# Clean up
success cleanup_guest_list_test $a1 --osh groupDelGuestAccess --group $group1 --account $account2 --host 192.168.4.200 --user listtest --port 2222 --proxy-host 10.0.0.6 --proxy-port 6666 --proxy-user listtest
json .command groupDelGuestAccess .error_code OK
# Add guest access with proxy-user for list check
success a1_add_server_with_proxy_user_for_guest_list $a1 --osh groupAddServer --group $group1 --host 192.168.4.201 --user listtest --port 2222 --proxy-host 10.0.0.6 --proxy-port 6666 --proxy-user proxyuser --force
json .command groupAddServer .error_code OK
success add_guest_access_with_proxy_user_for_list_check $a1 --osh groupAddGuestAccess --group $group1 --account $account2 --host 192.168.4.201 --user listtest --port 2222 --proxy-host 10.0.0.6 --proxy-port 6666 --proxy-user proxyuser
json .command groupAddGuestAccess .error_code OK
# Clean up
success cleanup_guest_list_test_with_proxy_user $a1 --osh groupDelGuestAccess --group $group1 --account $account2 --host 192.168.4.201 --user listtest --port 2222 --proxy-host 10.0.0.6 --proxy-port 6666 --proxy-user proxyuser
json .command groupDelGuestAccess .error_code OK
# Clean up servers added for testing
success cleanup_server_192_168_4_100 $a1 --osh groupDelServer --group $group1 --host 192.168.4.100 --user testuser --port 22 --proxy-host 10.0.0.4 --proxy-port 22 --proxy-user testuser
json .command groupDelServer .error_code OK
success cleanup_server_192_168_4_101 $a1 --osh groupDelServer --group $group1 --host 192.168.4.101 --user testuser --port 22 --proxy-host localhost --proxy-port 2222 --proxy-user testuser
json .command groupDelServer .error_code OK
success cleanup_server_192_168_4_110 $a1 --osh groupDelServer --group $group1 --host 192.168.4.110 --user testuser --port 22
json .command groupDelServer .error_code OK
success cleanup_server_192_168_4_200 $a1 --osh groupDelServer --group $group1 --host 192.168.4.200 --user listtest --port 2222 --proxy-host 10.0.0.6 --proxy-port 6666 --proxy-user listtest
json .command groupDelServer .error_code OK
success cleanup_server_192_168_4_201 $a1 --osh groupDelServer --group $group1 --host 192.168.4.201 --user listtest --port 2222 --proxy-host 10.0.0.6 --proxy-port 6666 --proxy-user proxyuser
json .command groupDelServer .error_code OK
success a0_delete_group1 $a0 --osh groupDelete --group $group1 --no-confirm
json .error_code OK .command groupDelete
success a0_delete_a1 $a0 --osh accountDelete --account $account1 --no-confirm
json .error_code OK .command accountDelete
success a0_delete_a2 $a0 --osh accountDelete --account $account2 --no-confirm
json .error_code OK .command accountDelete
}
testsuite_proxyjump
unset -f testsuite_proxyjump

@ -159,6 +159,68 @@ cmp_deeply(
"build_ttyrec_cmdline_part2of2 cmd"
);
# Test ttyrec with proxy parameters
$fnret = OVH::Bastion::build_ttyrec_cmdline_part1of2(
ip => "192.168.1.100",
port => 22,
user => "targetuser",
account => "bastionuser",
uniqid => 'cafed00dcafe',
home => "/home/randomuser",
proxyIp => "10.0.0.1",
proxyPort => 2222,
proxyUser => "jumpi",
);
cmp_deeply(
$fnret->value->{'saveFile'},
re(
qr{^\Q/home/randomuser/ttyrec/via-10.0.0.1-192.168.1.100/20\E\d\d-\d\d-\d\d.\d\d\-\d\d\-\d\d\.\d{6}\Q.cafed00dcafe.bastionuser.targetuser.192.168.1.100.22.via.jumpi.10.0.0.1.2222.ttyrec\E$}
),
"build_ttyrec_cmdline_part1of2 with proxy saveFile"
);
cmp_deeply(
$fnret->value->{'cmd'},
[
'ttyrec',
'-f',
$fnret->value->{'saveFile'},
'-F',
'/home/randomuser/ttyrec/via-10.0.0.1-192.168.1.100/%Y-%m-%d.%H-%M-%S.#usec#.cafed00dcafe.bastionuser.targetuser.192.168.1.100.22.via.jumpi.10.0.0.1.2222.ttyrec'
],
"build_ttyrec_cmdline_part1of2 with proxy cmd"
);
# Test ttyrec with IPv6 proxy
$fnret = OVH::Bastion::build_ttyrec_cmdline_part1of2(
ip => "192.168.1.200",
port => 22,
user => "targetuser",
account => "bastionuser",
uniqid => 'cafed00dcafe',
home => "/home/randomuser",
proxyIp => "2001:db8::1",
proxyPort => 22,
proxyUser => "jumpi",
);
cmp_deeply(
$fnret->value->{'saveFile'},
re(
qr{^\Q/home/randomuser/ttyrec/via-v6[2001.db8..1]-192.168.1.200/20\E\d\d-\d\d-\d\d.\d\d\-\d\d\-\d\d\.\d{6}\Q.cafed00dcafe.bastionuser.targetuser.192.168.1.200.22.via.jumpi.v6[2001.db8..1].22.ttyrec\E$}
),
"build_ttyrec_cmdline_part1of2 with IPv6 proxy saveFile"
);
cmp_deeply(
$fnret->value->{'cmd'},
[
'ttyrec',
'-f',
$fnret->value->{'saveFile'},
'-F',
'/home/randomuser/ttyrec/via-v6[2001.db8..1]-192.168.1.200/%Y-%m-%d.%H-%M-%S.#usec#.cafed00dcafe.bastionuser.targetuser.192.168.1.200.22.via.jumpi.v6[2001.db8..1].22.ttyrec'
],
"build_ttyrec_cmdline_part1of2 with IPv6 proxy cmd"
);
is(OVH::Bastion::config("bastionName")->value, "mock", "bastion name is mocked");
ok(OVH::Bastion::is_account_valid(account => "azerty")->is_ok, "is_account_valid('azerty')");

@ -0,0 +1,311 @@
#! /usr/bin/env perl
# vim: set filetype=perl ts=4 sw=4 sts=4 et:
use common::sense;
use Test::More;
use File::Basename;
use lib dirname(__FILE__) . '/../../../lib/perl';
use OVH::Bastion;
use OVH::Result;
OVH::Bastion::enable_mocking();
OVH::Bastion::set_mock_data(
{
"accounts" => {
"me" => {
"uid" => 99982,
"gid" => 99982,
"personal_accesses" => [
"me\@192.0.2.10:22",
"me\@192.0.2.11",
"me\@192.0.2.20:22 # PROXYHOST=10.0.0.1 # PROXYPORT=2222",
"me\@192.0.2.21:80 # PROXYHOST=10.0.0.1 # PROXYPORT=2222",
"me\@192.0.2.30:22 # PROXYHOST=10.0.0.2 # PROXYPORT=3333",
"me\@192.0.2.50 # PROXYHOST=10.0.0.4 # PROXYPORT=4444",
"192.0.2.60:22 # PROXYHOST=10.0.0.5 # PROXYPORT=5555",
"192.0.2.61:22 # PROXYHOST=10.0.0.5 # PROXYPORT=5555 # PROXYUSER=proxyuser",
"192.0.2.62:22 # PROXYHOST=10.0.0.5 # PROXYPORT=5555",
"192.0.2.63:22 # PROXYHOST=10.0.0.5 # PROXYPORT=5555 # PROXYUSER=admin*",
"192.0.2.64:22 # PROXYHOST=10.0.0.5 # PROXYPORT=5555 # PROXYUSER=user?",
"198.51.100.0/24:22 # PROXYHOST=10.0.0.1 # PROXYPORT=2222",
"198.51.200.0/24:22 # PROXYHOST=10.0.0.1 # PROXYPORT=2222 # PROXYUSER=netuser",
# IPv6 entries
"me\@[2001:db8::10]:22",
"me\@[2001:db8::11]",
"me\@[2001:db8::20]:22 # PROXYHOST=2001:db8:cafe::1 # PROXYPORT=2222",
"me\@[2001:db8::30]:80 # PROXYHOST=2001:db8:cafe::2 # PROXYPORT=3333",
"[2001:db8::40]:22 # PROXYHOST=2001:db8:cafe::4 # PROXYPORT=4444",
"[2001:db8::41]:22 # PROXYHOST=2001:db8:cafe::4 # PROXYPORT=4444 # PROXYUSER=ipv6user",
"[2001:aaaa::/64]:22 # PROXYHOST=2001:db8:cafe::1 # PROXYPORT=2222",
],
},
},
}
);
OVH::Bastion::load_configuration(
mock_data => {
bastionName => "mock",
}
);
my %want; # truth table
my $undef = '_none_'; # can't use undef as a hash key, so we'll use this special value instead
# Test 1: Regular access without proxy - should work as before
$want{"192.0.2.10"}{"22"}{"me"}{$undef}{$undef} = 'OK';
$want{"192.0.2.10"}{"22"}{"me"}{"10.0.0.1"}{"2222"} = 'KO_ACCESS_DENIED'; # proxy requested but not configured
$want{"192.0.2.11"}{"22"}{"me"}{$undef}{$undef} = 'OK';
$want{"192.0.2.11"}{"80"}{"me"}{$undef}{$undef} = 'OK';
# Test 2: Access with specific proxy (no PROXYUSER in ACL) - should work with any proxy-user
$want{"192.0.2.20"}{"22"}{"me"}{$undef}{$undef} = 'KO_ACCESS_DENIED'; # proxy required but not provided
$want{"192.0.2.20"}{"22"}{"me"}{"10.0.0.1"}{"2222"} = 'OK'; # no proxy-user specified
$want{"192.0.2.20"}{"22"}{"me"}{"10.0.0.1"}{"2222"}{"anyuser"} = 'OK'; # no PROXYUSER in ACL = accepts any proxy-user
$want{"192.0.2.20"}{"22"}{"me"}{"10.0.0.1"}{"2222"}{"testuser"} = 'OK'; # no PROXYUSER in ACL = accepts any proxy-user
$want{"192.0.2.20"}{"22"}{"me"}{"10.0.0.1"}{"2222"}{$undef} = 'OK'; # no PROXYUSER in ACL = accepts undef too
$want{"192.0.2.20"}{"22"}{"me"}{"10.0.0.1"}{"3333"} = 'KO_ACCESS_DENIED'; # wrong proxy port
$want{"192.0.2.20"}{"22"}{"me"}{"10.0.0.2"}{"2222"} = 'KO_ACCESS_DENIED'; # wrong proxy IP
$want{"192.0.2.20"}{"22"}{"me"}{"10.0.0.1"}{$undef} = 'KO_ACCESS_DENIED'; # proxy IP without port
# Test 3: Different proxy configuration (no PROXYUSER in ACL) - accepts any proxy-user
$want{"192.0.2.30"}{"22"}{"me"}{"10.0.0.2"}{"3333"} = 'OK';
$want{"192.0.2.30"}{"22"}{"me"}{"10.0.0.2"}{"3333"}{"anyuser"} = 'OK'; # no PROXYUSER in ACL
$want{"192.0.2.30"}{"22"}{"me"}{"10.0.0.2"}{"3333"}{$undef} = 'OK'; # no PROXYUSER in ACL
$want{"192.0.2.30"}{"22"}{"me"}{"10.0.0.1"}{"2222"} = 'KO_ACCESS_DENIED'; # wrong proxy
# Test 4: Subnet access with proxy (no PROXYUSER in ACL) - accepts any proxy-user
$want{"198.51.100.100"}{"22"}{"me"}{"10.0.0.1"}{"2222"} = 'OK'; # subnet match with proxy
$want{"198.51.100.100"}{"22"}{"me"}{"10.0.0.1"}{"2222"}{"anyuser"} = 'OK'; # no PROXYUSER in ACL
$want{"198.51.100.100"}{"22"}{"me"}{"10.0.0.1"}{"2222"}{$undef} = 'OK'; # no PROXYUSER in ACL
$want{"198.51.100.200"}{"22"}{"me"}{"10.0.0.1"}{"2222"} = 'OK'; # subnet match with proxy
$want{"198.51.100.200"}{"22"}{"me"}{"10.0.0.1"}{"2222"}{"testuser"} = 'OK'; # no PROXYUSER in ACL
$want{"198.51.100.100"}{"22"}{"me"}{"10.0.0.1"}{"3333"} = 'KO_ACCESS_DENIED'; # subnet match, wrong proxy port
$want{"198.51.100.100"}{"22"}{"me"}{$undef}{$undef} = 'KO_ACCESS_DENIED'; # subnet match but no proxy requested when proxy required
# Test 5: Port wildcard with proxy (no PROXYUSER in ACL) - accepts any proxy-user
$want{"192.0.2.50"}{"22"}{"me"}{"10.0.0.4"}{"4444"} = 'OK'; # port wildcard with proxy
$want{"192.0.2.50"}{"22"}{"me"}{"10.0.0.4"}{"4444"}{"anyuser"} = 'OK'; # no PROXYUSER in ACL
$want{"192.0.2.50"}{"22"}{"me"}{"10.0.0.4"}{"4444"}{$undef} = 'OK'; # no PROXYUSER in ACL
$want{"192.0.2.50"}{"80"}{"me"}{"10.0.0.4"}{"4444"} = 'OK'; # port wildcard with proxy
$want{"192.0.2.50"}{"80"}{"me"}{"10.0.0.4"}{"4444"}{"testuser"} = 'OK'; # no PROXYUSER in ACL
$want{"192.0.2.50"}{"22"}{"me"}{"10.0.0.4"}{"5555"} = 'KO_ACCESS_DENIED'; # wrong proxy port
# Test 6: User wildcard with proxy (no PROXYUSER in ACL) - accepts any proxy-user
$want{"192.0.2.60"}{"22"}{"root"}{"10.0.0.5"}{"5555"} = 'OK'; # user wildcard, no proxy-user specified
$want{"192.0.2.60"}{"22"}{"root"}{"10.0.0.5"}{"5555"}{"anyuser"} = 'OK'; # no PROXYUSER in ACL
$want{"192.0.2.60"}{"22"}{"root"}{"10.0.0.5"}{"5555"}{$undef} = 'OK'; # no PROXYUSER in ACL
$want{"192.0.2.60"}{"22"}{"admin"}{"10.0.0.5"}{"5555"} = 'OK'; # user wildcard should match any user with exact proxy
$want{"192.0.2.60"}{"22"}{"admin"}{"10.0.0.5"}{"5555"}{"testuser"} = 'OK'; # no PROXYUSER in ACL
$want{"192.0.2.60"}{"22"}{"root"}{"10.0.0.5"}{"6666"} = 'KO_ACCESS_DENIED'; # user wildcard but wrong proxy port
# Test 6b: User wildcard with proxy and specific proxy-user
$want{"192.0.2.61"}{"22"}{"root"}{"10.0.0.5"}{"5555"}{"proxyuser"} = 'OK'; # user wildcard, specific proxy-user match
$want{"192.0.2.61"}{"22"}{"admin"}{"10.0.0.5"}{"5555"}{"proxyuser"} = 'OK'; # user wildcard, specific proxy-user match
$want{"192.0.2.61"}{"22"}{"root"}{"10.0.0.5"}{"5555"}{"wronguser"} = 'KO_ACCESS_DENIED'; # user wildcard, wrong proxy-user
$want{"192.0.2.61"}{"22"}{"root"}{"10.0.0.5"}{"5555"}{$undef} = 'KO_ACCESS_DENIED'; # user wildcard, missing proxy-user
# Test 6c: User wildcard with proxy but no proxy-user in ACL (allows any proxy-user)
$want{"192.0.2.62"}{"22"}{"root"}{"10.0.0.5"}{"5555"}{"anyuser"} = 'OK'; # no proxy-user in ACL = wildcard
$want{"192.0.2.62"}{"22"}{"admin"}{"10.0.0.5"}{"5555"}{"otheruser"} = 'OK'; # no proxy-user in ACL = wildcard
$want{"192.0.2.62"}{"22"}{"root"}{"10.0.0.5"}{"5555"}{$undef} = 'OK'; # no proxy-user in ACL = wildcard, undef also allowed
# Test 6d: User wildcard with proxy-user pattern (admin*)
$want{"192.0.2.63"}{"22"}{"root"}{"10.0.0.5"}{"5555"}{"admin"} = 'OK'; # proxy-user pattern match
$want{"192.0.2.63"}{"22"}{"admin"}{"10.0.0.5"}{"5555"}{"admin123"} = 'OK'; # proxy-user pattern match
$want{"192.0.2.63"}{"22"}{"root"}{"10.0.0.5"}{"5555"}{"root"} = 'KO_ACCESS_DENIED'; # proxy-user pattern no match
$want{"192.0.2.63"}{"22"}{"root"}{"10.0.0.5"}{"5555"}{$undef} = 'KO_ACCESS_DENIED'; # missing proxy-user
# Test 6e: User wildcard with proxy-user pattern (user?)
$want{"192.0.2.64"}{"22"}{"root"}{"10.0.0.5"}{"5555"}{"user1"} = 'OK'; # proxy-user pattern match
$want{"192.0.2.64"}{"22"}{"admin"}{"10.0.0.5"}{"5555"}{"userA"} = 'OK'; # proxy-user pattern match
$want{"192.0.2.64"}{"22"}{"root"}{"10.0.0.5"}{"5555"}{"user"} = 'KO_ACCESS_DENIED'; # proxy-user pattern no match (too short)
$want{"192.0.2.64"}{"22"}{"root"}{"10.0.0.5"}{"5555"}{"user12"} = 'KO_ACCESS_DENIED'; # proxy-user pattern no match (too long)
$want{"192.0.2.64"}{"22"}{"root"}{"10.0.0.5"}{"5555"}{$undef} = 'KO_ACCESS_DENIED'; # missing proxy-user
# Test 7: Subnet with proxy-user
$want{"198.51.200.100"}{"22"}{"me"}{"10.0.0.1"}{"2222"}{"netuser"} = 'OK'; # subnet match with specific proxy-user
$want{"198.51.200.200"}{"22"}{"me"}{"10.0.0.1"}{"2222"}{"netuser"} = 'OK'; # subnet match with specific proxy-user
$want{"198.51.200.100"}{"22"}{"me"}{"10.0.0.1"}{"2222"}{"other"} = 'KO_ACCESS_DENIED'; # subnet match, wrong proxy-user
$want{"198.51.200.100"}{"22"}{"me"}{"10.0.0.1"}{"2222"}{$undef} = 'KO_ACCESS_DENIED'; # subnet match, missing proxy-user
# Test 8: Negative cases - hosts not in ACL
$want{"192.0.2.99"}{"22"}{"me"}{$undef}{$undef} = 'KO_ACCESS_DENIED';
$want{"192.0.2.99"}{"22"}{"me"}{"10.0.0.1"}{"2222"} = 'KO_ACCESS_DENIED';
# IPv6 Tests
# Test 9: Regular IPv6 access without proxy - should work as before
$want{"2001:db8::10"}{"22"}{"me"}{$undef}{$undef} = 'OK';
$want{"2001:db8::10"}{"22"}{"me"}{"2001:db8:cafe::1"}{"2222"} = 'KO_ACCESS_DENIED'; # proxy requested but not configured
$want{"2001:db8::11"}{"22"}{"me"}{$undef}{$undef} = 'OK';
$want{"2001:db8::11"}{"80"}{"me"}{$undef}{$undef} = 'OK';
# Test 10: IPv6 access with specific proxy (no PROXYUSER in ACL) - accepts any proxy-user
$want{"2001:db8::20"}{"22"}{"me"}{$undef}{$undef} = 'KO_ACCESS_DENIED'; # proxy required but not provided
$want{"2001:db8::20"}{"22"}{"me"}{"2001:db8:cafe::1"}{"2222"} = 'OK';
$want{"2001:db8::20"}{"22"}{"me"}{"2001:db8:cafe::1"}{"2222"}{"anyuser"} = 'OK'; # no PROXYUSER in ACL
$want{"2001:db8::20"}{"22"}{"me"}{"2001:db8:cafe::1"}{"2222"}{$undef} = 'OK'; # no PROXYUSER in ACL
$want{"2001:db8::20"}{"22"}{"me"}{"2001:db8:cafe::1"}{"3333"} = 'KO_ACCESS_DENIED'; # wrong proxy port
$want{"2001:db8::20"}{"22"}{"me"}{"2001:db8:cafe::2"}{"2222"} = 'KO_ACCESS_DENIED'; # wrong proxy IP
# Test 11: IPv6 different proxy configuration (no PROXYUSER in ACL) - accepts any proxy-user
$want{"2001:db8::30"}{"80"}{"me"}{"2001:db8:cafe::2"}{"3333"} = 'OK';
$want{"2001:db8::30"}{"80"}{"me"}{"2001:db8:cafe::2"}{"3333"}{"anyuser"} = 'OK'; # no PROXYUSER in ACL
$want{"2001:db8::30"}{"80"}{"me"}{"2001:db8:cafe::2"}{"3333"}{$undef} = 'OK'; # no PROXYUSER in ACL
$want{"2001:db8::30"}{"80"}{"me"}{"2001:db8:cafe::1"}{"2222"} = 'KO_ACCESS_DENIED'; # wrong proxy
# Test 12: IPv6 user wildcard with proxy (no PROXYUSER in ACL) - accepts any proxy-user
$want{"2001:db8::40"}{"22"}{"root"}{"2001:db8:cafe::4"}{"4444"} = 'OK'; # user wildcard, no proxy-user specified
$want{"2001:db8::40"}{"22"}{"root"}{"2001:db8:cafe::4"}{"4444"}{"anyuser"} = 'OK'; # no PROXYUSER in ACL
$want{"2001:db8::40"}{"22"}{"root"}{"2001:db8:cafe::4"}{"4444"}{$undef} = 'OK'; # no PROXYUSER in ACL
$want{"2001:db8::40"}{"22"}{"admin"}{"2001:db8:cafe::4"}{"4444"} = 'OK'; # user wildcard should match any user with exact proxy
$want{"2001:db8::40"}{"22"}{"admin"}{"2001:db8:cafe::4"}{"4444"}{"testuser"} = 'OK'; # no PROXYUSER in ACL
$want{"2001:db8::40"}{"22"}{"root"}{"2001:db8:cafe::4"}{"5555"} = 'KO_ACCESS_DENIED'; # user wildcard but wrong proxy port
# Test 12b: IPv6 user wildcard with proxy and specific proxy-user
$want{"2001:db8::41"}{"22"}{"root"}{"2001:db8:cafe::4"}{"4444"}{"ipv6user"} = 'OK'; # IPv6 user wildcard, specific proxy-user match
$want{"2001:db8::41"}{"22"}{"admin"}{"2001:db8:cafe::4"}{"4444"}{"ipv6user"} = 'OK'; # IPv6 user wildcard, specific proxy-user match
$want{"2001:db8::41"}{"22"}{"root"}{"2001:db8:cafe::4"}{"4444"}{"wronguser"} = 'KO_ACCESS_DENIED'; # IPv6 user wildcard, wrong proxy-user
$want{"2001:db8::41"}{"22"}{"root"}{"2001:db8:cafe::4"}{"4444"}{$undef} = 'KO_ACCESS_DENIED'; # IPv6 user wildcard, missing proxy-user
# Test 13: IPv6 subnet access with proxy (no PROXYUSER in ACL) - accepts any proxy-user
$want{"2001:aaaa::100"}{"22"}{"me"}{"2001:db8:cafe::1"}{"2222"} = 'OK'; # subnet match with proxy
$want{"2001:aaaa::100"}{"22"}{"me"}{"2001:db8:cafe::1"}{"2222"}{"anyuser"} = 'OK'; # no PROXYUSER in ACL
$want{"2001:aaaa::100"}{"22"}{"me"}{"2001:db8:cafe::1"}{"2222"}{$undef} = 'OK'; # no PROXYUSER in ACL
$want{"2001:aaaa::200"}{"22"}{"me"}{"2001:db8:cafe::1"}{"2222"} = 'OK'; # subnet match with proxy
$want{"2001:aaaa::200"}{"22"}{"me"}{"2001:db8:cafe::1"}{"2222"}{"testuser"} = 'OK'; # no PROXYUSER in ACL
$want{"2001:aaaa::100"}{"22"}{"me"}{"2001:db8:cafe::1"}{"3333"} = 'KO_ACCESS_DENIED'; # subnet match, wrong proxy port
$want{"2001:aaaa::100"}{"22"}{"me"}{$undef}{$undef} = 'KO_ACCESS_DENIED'; # subnet match but no proxy requested when proxy required
# Test 14: IPv6 negative cases - hosts not in ACL
$want{"2001:ffff::999"}{"22"}{"me"}{$undef}{$undef} = 'KO_ACCESS_DENIED';
$want{"2001:ffff::999"}{"22"}{"me"}{"2001:db8:cafe::1"}{"2222"} = 'KO_ACCESS_DENIED';
# Run all the tests
foreach my $ip (
qw{
192.0.2.10
192.0.2.11
192.0.2.20
192.0.2.30
198.51.100.100
198.51.100.200
192.0.2.50
192.0.2.60
192.0.2.61
192.0.2.62
192.0.2.63
192.0.2.64
192.0.2.99
198.51.200.100
198.51.200.200
2001:db8::10
2001:db8::11
2001:db8::20
2001:db8::30
2001:db8::40
2001:db8::41
2001:aaaa::100
2001:aaaa::200
2001:ffff::999
}
)
{
foreach my $port (qw{22 80}) {
foreach my $user (qw{me root admin}) {
foreach my $proxyIp (
$undef, "10.0.0.1", "10.0.0.2", "10.0.0.3",
"10.0.0.4", "10.0.0.5", "2001:db8:cafe::1", "2001:db8:cafe::2",
"2001:db8:cafe::4"
)
{
foreach my $proxyPort ($undef, "2222", "3333", "1234", "4444", "5555", "6666") {
foreach my $proxyUser (
$undef, "proxyuser", "anyuser", "otheruser", "wronguser", "testuser",
"admin", "admin123", "root", "user1", "userA", "user",
"user12", "netuser", "other", "ipv6user"
)
{
# Skip combinations that don't make sense
next
if (!defined $proxyIp || $proxyIp eq $undef) && (defined $proxyPort && $proxyPort ne $undef);
next
if (!defined $proxyIp || $proxyIp eq $undef) && (defined $proxyUser && $proxyUser ne $undef);
my $expected =
$want{$ip}{$port}{$user}{$proxyIp // $undef}{$proxyPort // $undef}{$proxyUser // $undef};
next unless defined $expected;
my %params = (
ipfrom => "127.0.0.1",
account => "me",
user => $user,
ip => $ip,
port => $port,
);
# Add proxy parameters if they are defined
if (defined $proxyIp && $proxyIp ne $undef) { ## no critic (ProhibitDeepNests)
$params{proxyIp} = $proxyIp;
}
if (defined $proxyPort && $proxyPort ne $undef) { ## no critic (ProhibitDeepNests)
$params{proxyPort} = $proxyPort;
}
if (defined $proxyUser && $proxyUser ne $undef) { ## no critic (ProhibitDeepNests)
$params{proxyUser} = $proxyUser;
}
my $result = OVH::Bastion::is_access_granted(%params);
my $test_desc = sprintf(
"is_access_granted with %s@%s:%s proxy=%s@%s:%s",
$user, $ip, $port,
$proxyUser // '<none>',
$proxyIp // '<none>',
$proxyPort // '<none>'
);
is($result->err, $expected, $test_desc);
# If access is granted, verify proxy information is returned
_verify_proxy_information($expected, $proxyIp, $proxyPort, $proxyUser, $result, $test_desc);
}
}
}
}
}
}
sub _verify_proxy_information { ## no critic (ProhibitManyArgs)
my ($expected, $proxyIp, $proxyPort, $proxyUser, $result, $test_desc) = @_;
# Early return if access is not granted or no proxy expected
return if $expected ne 'OK';
return if !defined $proxyIp || $proxyIp eq $undef;
my $value = $result->value;
return if ref $value ne 'ARRAY' || @$value == 0;
# Check if any of the returned grants has proxy info
my $found_proxy = 0;
foreach my $grant (@$value) {
next if !defined $grant->{proxyIp};
next if $grant->{proxyIp} ne $proxyIp;
$found_proxy = 1;
if (defined $proxyPort && $proxyPort ne $undef) {
is($grant->{proxyPort}, $proxyPort, "$test_desc - proxy port returned");
}
if (defined $proxyUser && $proxyUser ne $undef) {
# Note: proxyUser in grant might be undef if wildcard, so we only check if explicitly set
if (defined $grant->{proxyUser}) {
ok(1, "$test_desc - proxy user returned");
}
}
last;
}
ok($found_proxy, "$test_desc - proxy IP returned in grant");
return;
}
done_testing();
Loading…
Cancel
Save