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;