mirror of
https://github.com/ovh/the-bastion.git
synced 2025-01-10 17:30:51 +08:00
315 lines
14 KiB
Perl
315 lines
14 KiB
Perl
|
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;
|