the-bastion/lib/perl/OVH/Bastion/Plugin/groupSetRole.pm

315 lines
14 KiB
Perl
Raw Normal View History

2020-10-16 00:32:37 +08:00
package OVH::Bastion::Plugin::groupSetRole;
# vim: set filetype=perl ts=4 sw=4 sts=4 et:
use common::sense;
use File::Basename;
use lib dirname(__FILE__) . '/../../../../../lib/perl';
use OVH::Result;
use OVH::Bastion;
sub preconditions {
my %params = @_;
my ($self, $account, $group, $action, $type, $user, $userAny, $port, $portAny, $host, $ttl, $sudo, $silentoverride) =
@params{qw{ self account group action type user userAny port portAny host ttl sudo silentoverride }};
my $fnret;
if (!$self || !$account || !$group || !$type || !$action) {
return R('ERR_MISSING_PARAMETER', msg => "Missing argument self[$self], account[$account], group[$group], type[$type] or action[$action]");
}
if (!grep { $action eq $_ } qw{ add del }) {
return R('ERR_INVALID_PARAMETER', msg => "Action should be add or del");
}
# a regex is overkill here but we need it for untaint
if ($type !~ /^(owner|gatekeeper|aclkeeper|member|guest)$/) { ## no critic (ProhibitFixedStringMatches)
return R('ERR_INVALID_PARAMETER', msg => "Type should be either owner, gatekeeper, aclkeeper, member or guest");
}
# untaint it:
$type = $1; ## no critic (ProhibitCaptureWithoutTest)
if ($type eq 'guest' && !$sudo) {
# guest access need (user||user-any), host and (port||port-any)
# in sudo mode, these are not used, because the helper doesn't handle the guest access add by itself, the do() func of this package does
if (!($user xor $userAny)) {
return R('ERR_MISSING_PARAMETER', msg => "Require exactly one argument of user or user-any");
}
if (!($port xor $portAny)) {
return R('ERR_MISSING_PARAMETER', msg => "Require exactly one argument of port or port-any");
}
if (not $host) {
return R('ERR_MISSING_PARAMETER', msg => "Missing argument host for type guest");
}
if ($port) {
$fnret = OVH::Bastion::is_valid_port(port => $port);
$fnret or return $fnret;
}
if ($user and $user !~ /^[a-zA-Z0-9!._-]+$/) {
return R('ERR_INVALID_PARAMETER', msg => "Invalid remote user ($user) specified");
}
# policy check for guest accesses: if group forces ttl, the account creation must comply
$fnret = OVH::Bastion::group_config(group => $group, key => "guest_ttl_limit");
# if this config key is not set, no policy enforce has been requested, otherwise, check it:
if ($fnret) {
my $max = $fnret->value();
if (!$ttl) {
return R('ERR_INVALID_PARAMETER',
msg => "This group requires guest accesses to have a TTL set, to a duration of "
. OVH::Bastion::duration2human(seconds => $max)->value->{'duration'}
. " or less");
}
if ($ttl > $max) {
return R('ERR_INVALID_PARAMETER',
msg => "The TTL you specified is invalid, this group requires guest accesses to have a TTL of "
. OVH::Bastion::duration2human(seconds => $max)->value->{'duration'}
. " maximum");
}
}
}
$fnret = OVH::Bastion::is_valid_group_and_existing(group => $group, groupType => "key");
$fnret or return $fnret;
# get returned untainted value
$group = $fnret->value->{'group'};
my $shortGroup = $fnret->value->{'shortGroup'};
$fnret = OVH::Bastion::is_bastion_account_valid_and_existing(account => $account);
$fnret or return $fnret;
# get returned untainted value
$account = $fnret->value->{'account'};
my $realm = $fnret->value->{'realm'};
my $remoteaccount = $fnret->value->{'remoteaccount'};
my $sysaccount = $fnret->value->{'sysaccount'};
if ($self eq 'root' && $< == 0) {
osh_debug("called by root, allowing anyway");
}
else {
my $neededright = 'unknown';
if (grep { $type eq $_ } qw{ owner gatekeeper aclkeeper }) {
$neededright = "owner";
$fnret = OVH::Bastion::is_group_owner(account => $self, group => $shortGroup, superowner => 1, sudo => $sudo);
if (!$fnret) {
osh_debug("user $self not an owner of $shortGroup");
return R('ERR_NOT_GROUP_OWNER', msg => "Sorry, you're not an owner of group $shortGroup, which is needed to change its $type list");
}
# if account is from a realm, he can't be owner/gk/aclk
if (defined $realm) {
return R('ERR_REALM_USER', msg => "Sorry, $account is from another realm, this account can't be $type");
}
}
elsif (grep { $type eq $_ } qw{ member guest }) {
$neededright = "gatekeeper";
$fnret = OVH::Bastion::is_group_gatekeeper(account => $self, group => $shortGroup, superowner => 1, sudo => $sudo);
if (!$fnret) {
osh_debug("user $self not a gk of $shortGroup");
return R('ERR_NOT_GROUP_GATEKEEPER', msg => "Sorry, you're not a gatekeeper of group $shortGroup, which is needed to change its $type list");
}
}
else {
return R('ERR_INTERNAL', msg => "Unknown type $type");
}
if ($fnret->value() and $fnret->value()->{'superowner'} and not $silentoverride) {
osh_warn "SUPER OWNER OVERRIDE: You're not a $neededright of the group $shortGroup,";
osh_warn "but allowing because you're a superowner. This has been logged.";
OVH::Bastion::syslogFormatted(
criticity => 'info',
type => 'security',
fields => [['type', 'superowner-override'], ['account', $params{'self'}], ['plugin', $params{'scriptName'}], ['params', $params{'savedArgs'}],]
);
}
}
return R('OK',
value => {group => $group, shortGroup => $shortGroup, account => $account, type => $type, realm => $realm, remoteaccount => $remoteaccount, sysaccount => $sysaccount});
}
sub act {
my %params = @_;
my $fnret = preconditions(%params);
$fnret or return $fnret;
# get returned untainted value
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) = @params{qw{ action self user host port ttl }};
undef $user if $params{'userAny'};
undef $port if $params{'portAny'};
my @command;
osh_debug("groupSetRole::act, $action $type $group/$account ($sysaccount/$realm/$remoteaccount) $user\@$host:$port ttl=$ttl");
# 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')) {
@command = qw{ sudo -n -u root -- /usr/bin/env perl -T };
push @command, $OVH::Bastion::BASEPATH . '/bin/helper/osh-groupSetRole';
push @command, '--type', $type;
push @command, '--group', $group;
push @command, '--account', $account, '--action', $action;
$fnret = OVH::Bastion::helper(cmd => \@command);
$fnret or return $fnret;
}
if ($type eq 'member') {
# in that case, we also need to handle the symlink
@command = qw{ sudo -n -u allowkeeper -- /usr/bin/env perl -T };
push @command, $OVH::Bastion::BASEPATH . '/bin/helper/osh-groupAddSymlinkToAccount';
push @command, '--group', $group; # must be first params, forced in sudoers.d
push @command, '--account', $account;
push @command, '--action', $action;
$fnret = OVH::Bastion::helper(cmd => \@command);
$fnret or return $fnret;
if ($fnret->err eq 'OK_NO_CHANGE') {
# make the error msg user friendly
$fnret->{'msg'} = "Account $account was already " . ($action eq 'del' ? 'not ' : '') . "a member of $shortGroup, nothing to do";
}
}
elsif ($type eq 'guest') {
# 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 = $host;
$port and $machine .= ":$port";
$user and $machine = $user . '@' . $machine;
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,
);
if (not $fnret) {
osh_debug("groupSetRole::act, it doesn't! $fnret");
return R('ERR_GROUP_HAS_NO_ACCESS',
msg => "The group $shortGroup doesn't have access to $machine, so you can't add a guest group access "
. "to it (first add it to the group if applicable, with groupAddServer)");
}
}
# 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;
$fnret = OVH::Bastion::helper(cmd => \@command);
$fnret or return $fnret;
if ($fnret->err eq 'OK_NO_CHANGE') {
if ($action eq 'add') {
osh_info "Account $account already had access to $machine through $shortGroup";
}
else {
osh_info "Account $account didn't have access to $machine through $shortGroup";
}
}
else {
if ($action eq 'add') {
osh_info "Account $account has now access to the group key of $shortGroup, but does NOT";
osh_info "automatically inherits access to any of the group's servers, only to $machine,";
osh_info "and any other(s) $shortGroup group server(s) previously granted to $account.";
osh_info "This access will expire in " . OVH::Bastion::duration2human(seconds => $ttl)->value->{'human'} if $ttl;
}
else {
osh_info "Access to $machine through group $shortGroup was removed from account $account";
}
}
if ($action eq 'del') {
# if the guest group access file of this account is now empty, we should remove the account from the group
# but ONLY if the account doesn't have regular member access to the group too.
my $accessesFound = 0;
if (!$realm) {
# in non-realm mode, just check the account itself
$fnret = OVH::Bastion::get_acl_way(way => 'groupguest', group => $shortGroup, account => $account);
$fnret or return $fnret;
$accessesFound += @{$fnret->value};
}
else {
# in realm-mode, we need to check that all the other remote accounts no longer have access either, before removing the key
$fnret = OVH::Bastion::get_remote_accounts_from_realm(realm => $realm);
$fnret or return $fnret;
foreach my $pRemoteaccount (@{$fnret->value}) {
$fnret = OVH::Bastion::get_acl_way(way => 'groupguest', group => $shortGroup, account => "$realm/$pRemoteaccount");
$accessesFound += @{$fnret->value};
last if $accessesFound > 0;
}
}
if ($accessesFound == 0 && !OVH::Bastion::is_group_member(group => $shortGroup, account => $account)) {
osh_debug "No guest access remains to group $shortGroup for account $account, removing group key access";
#
# remove account from group
#
@command = qw{ sudo -n -u root -- /usr/bin/env perl -T };
push @command, $OVH::Bastion::BASEPATH . '/bin/helper/osh-groupSetRole';
push @command, '--type', 'guest';
push @command, '--group', $group;
push @command, '--account', $account;
push @command, '--action', 'del';
$fnret = OVH::Bastion::helper(cmd => \@command);
$fnret or return $fnret;
if (!$realm) {
osh_info "No guest access to servers of group $shortGroup remained for account $account, removed group key access";
}
else {
osh_info "No guest access to servers of group $shortGroup remained for realm $realm, removed group key access";
}
}
}
else {
osh_info "\nYou can view ${account}'s guest accesses to $shortGroup with the following command:";
my $bastionName = OVH::Bastion::config('bastionName')->value();
osh_info "$bastionName --osh groupListGuestAccesses --account $account --group $shortGroup";
}
}
if ($fnret) {
OVH::Bastion::syslogFormatted(
severity => 'info',
type => 'membership',
fields => [
['action', $action],
['type', $type],
['group', $shortGroup],
['account', $account],
['self', $self],
['user', $user],
['host', $host],
['port', $port],
['ttl', $ttl],
]
);
}
return $fnret;
}
1;