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)
2021-02-17 22:38:59 +08:00
# in sudo mode, these are not used, because the helper doesn't handle the guest access add by itself, the act() func of this package does
2020-10-16 00:32:37 +08:00
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" ) ;
}
2021-02-17 22:37:47 +08:00
if ( $ action eq 'add' ) {
2020-10-16 00:32:37 +08:00
2021-02-17 22:37:47 +08:00
# 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" ) ;
}
2020-10-16 00:32:37 +08:00
}
}
}
$ 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" ;
2020-11-23 05:05:45 +08:00
$ fnret = OVH::Bastion:: is_group_owner ( account = > $ self , group = > $ shortGroup , superowner = > 1 , sudo = > $ sudo ) ;
2020-10-16 00:32:37 +08:00
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" ;
2020-11-23 05:05:45 +08:00
$ fnret = OVH::Bastion:: is_group_gatekeeper ( account = > $ self , group = > $ shortGroup , superowner = > 1 , sudo = > $ sudo ) ;
2020-10-16 00:32:37 +08:00
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 ( ) } ;
2021-02-17 22:38:59 +08:00
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 } } ;
2020-10-16 00:32:37 +08:00
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' ) {
2021-02-15 19:19:11 +08:00
if ( $ action eq 'add' && OVH::Bastion:: is_group_guest ( group = > $ shortGroup , account = > $ account , sudo = > $ params { 'sudo' } ) ) {
fix: guests: get rid of ghost guest accesses in corner cases
Adding a guest access to a member of a group is now denied, to avoid having
dangling guest accesses when their membership is revoked. In effect, they
could no longer access the group servers, even as guest, because they no longer
had access to the group key, but their previous guest accesses were still
visible in groupListGuestAccesses, causing possible confusion.
We now also revoke all guest accesses of an account to a group, if any,
when it's being set as a member of this group, so that when/if the account
membership is revoked, we don't end up with the same ghost guest accesses as above.
2020-12-10 17:57:14 +08:00
2021-02-17 22:38:59 +08:00
# if the user is a guest, must remove all their guest accesses first
fix: guests: get rid of ghost guest accesses in corner cases
Adding a guest access to a member of a group is now denied, to avoid having
dangling guest accesses when their membership is revoked. In effect, they
could no longer access the group servers, even as guest, because they no longer
had access to the group key, but their previous guest accesses were still
visible in groupListGuestAccesses, causing possible confusion.
We now also revoke all guest accesses of an account to a group, if any,
when it's being set as a member of this group, so that when/if the account
membership is revoked, we don't end up with the same ghost guest accesses as above.
2020-12-10 17:57:14 +08:00
$ fnret = OVH::Bastion:: get_acl_way ( way = > 'groupguest' , group = > $ shortGroup , account = > $ account ) ;
2020-12-23 18:23:55 +08:00
if ( $ fnret && $ fnret - > value && @ { $ fnret - > value } ) {
fix: guests: get rid of ghost guest accesses in corner cases
Adding a guest access to a member of a group is now denied, to avoid having
dangling guest accesses when their membership is revoked. In effect, they
could no longer access the group servers, even as guest, because they no longer
had access to the group key, but their previous guest accesses were still
visible in groupListGuestAccesses, causing possible confusion.
We now also revoke all guest accesses of an account to a group, if any,
when it's being set as a member of this group, so that when/if the account
membership is revoked, we don't end up with the same ghost guest accesses as above.
2020-12-10 17:57:14 +08:00
osh_warn ( "This account was previously a guest of this group, with the following accesses:" ) ;
my @ acl = @ { $ fnret - > value } ;
OVH::Bastion:: print_acls ( acls = > [ { type = > 'group-guest' , group = > $ shortGroup , acl = > \ @ acl } ] ) ;
osh_info ( "\nCleaning these guest accesses before granting membership..." ) ;
# foreach guest access, delete
foreach my $ access ( @ acl ) {
my $ machine = $ access - > { 'ip' } ;
$ machine . = ':' . $ access - > { 'port' } if defined $ access - > { 'port' } ;
$ machine = $ access - > { 'user' } . '@' . $ machine if defined $ access - > { 'user' } ;
$ fnret = OVH::Bastion::Plugin::groupSetRole:: act (
account = > $ account ,
group = > $ shortGroup ,
action = > 'del' ,
type = > 'guest' ,
user = > $ access - > { 'user' } ,
userAny = > ( defined $ access - > { 'user' } ? 0 : 1 ) ,
port = > $ access - > { 'port' } ,
portAny = > ( defined $ access - > { 'port' } ? 0 : 1 ) ,
host = > $ access - > { 'ip' } ,
self = > $ self ,
) ;
if ( ! $ fnret ) {
osh_warn ( "Failed removing guest access to $machine, proceeding anyway..." ) ;
warn_syslog ( "Failed removing guest access to $machine in group $shortGroup for $account, before granting this account full membership on behalf of $self: "
. $ fnret - > msg ) ;
}
}
}
}
# then, for add and del, we need to handle the symlink
2020-10-16 00:32:37 +08:00
@ 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 ;
2020-11-23 05:05:45 +08:00
2020-10-16 00:32:37 +08:00
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)" ) ;
}
2021-02-17 22:38:59 +08:00
# if no comment was specified for this guest access, reuse the one from the matching group ACL entry
$ comment || = $ fnret - > value - > { 'comment' } ;
2020-10-16 00:32:37 +08:00
}
fix: guests: get rid of ghost guest accesses in corner cases
Adding a guest access to a member of a group is now denied, to avoid having
dangling guest accesses when their membership is revoked. In effect, they
could no longer access the group servers, even as guest, because they no longer
had access to the group key, but their previous guest accesses were still
visible in groupListGuestAccesses, causing possible confusion.
We now also revoke all guest accesses of an account to a group, if any,
when it's being set as a member of this group, so that when/if the account
membership is revoked, we don't end up with the same ghost guest accesses as above.
2020-12-10 17:57:14 +08:00
# If the account is already a member, can't add/del them as guest
2021-02-15 19:19:11 +08:00
if ( OVH::Bastion:: is_group_member ( group = > $ shortGroup , account = > $ account , sudo = > $ params { 'sudo' } ) ) {
fix: guests: get rid of ghost guest accesses in corner cases
Adding a guest access to a member of a group is now denied, to avoid having
dangling guest accesses when their membership is revoked. In effect, they
could no longer access the group servers, even as guest, because they no longer
had access to the group key, but their previous guest accesses were still
visible in groupListGuestAccesses, causing possible confusion.
We now also revoke all guest accesses of an account to a group, if any,
when it's being set as a member of this group, so that when/if the account
membership is revoked, we don't end up with the same ghost guest accesses as above.
2020-12-10 17:57:14 +08:00
return R ( 'ERR_MEMBER_CANNOT_BE_GUEST' , msg = > "Can't $action $account as a guest of group $shortGroup, they're already a member!" ) ;
}
2020-10-16 00:32:37 +08:00
# 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 ;
2021-02-17 22:38:59 +08:00
push @ command , '--comment' , $ comment if $ comment ;
2020-10-16 00:32:37 +08:00
$ 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 ;
}
}
2021-02-15 19:19:11 +08:00
if ( $ accessesFound == 0 && ! OVH::Bastion:: is_group_member ( group = > $ shortGroup , account = > $ account , sudo = > $ params { 'sudo' } ) ) {
2020-10-16 00:32:37 +08:00
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 ] ,
2021-02-17 22:38:59 +08:00
[ 'comment' , $ comment || '' ] ,
2020-10-16 00:32:37 +08:00
]
) ;
}
return $ fnret ;
}
1 ;