Support adding user ID overrides as group and role members

Second part of adding support to manage IPA as a user from a trusted
Active Directory forest.

Treat user ID overrides as members of groups and roles.

For example, adding an Active Directory user ID override as a member of
'admins' group would make it equivalent to built-in FreeIPA 'admin'
user.

We already support self-service operations by Active Directory users if
their user ID override does exist. When Active Directory user
authenticates with GSSAPI against the FreeIPA LDAP server, its Kerberos
principal is automatically mapped to the user's ID override in the
Default Trust View. LDAP server's access control plugin uses membership
information of the corresponding LDAP entry to decide how access can be
allowed.

With the change, users from trusted Active Directory forests can
manage FreeIPA resources if the groups are part of appropriate roles or
their ID overrides are members of the roles themselves.

Fixes: https://pagure.io/freeipa/issue/7255

Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
This commit is contained in:
Alexander Bokovoy 2020-06-03 13:04:46 +03:00 committed by Rob Crittenden
parent 973e0c04e4
commit bee4204039
6 changed files with 49 additions and 18 deletions

View File

@ -179,7 +179,7 @@ aci: (targetfilter = "(objectclass=ipahostgroup)")(version 3.0;acl "permission:S
dn: cn=views,cn=accounts,dc=ipa,dc=example dn: cn=views,cn=accounts,dc=ipa,dc=example
aci: (targetattr = "cn || createtimestamp || description || entryusn || gidnumber || ipaanchoruuid || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipaGroupOverride)")(version 3.0;acl "permission:System: Read Group ID Overrides";allow (compare,read,search) userdn = "ldap:///all";) aci: (targetattr = "cn || createtimestamp || description || entryusn || gidnumber || ipaanchoruuid || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipaGroupOverride)")(version 3.0;acl "permission:System: Read Group ID Overrides";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=views,cn=accounts,dc=ipa,dc=example dn: cn=views,cn=accounts,dc=ipa,dc=example
aci: (targetattr = "createtimestamp || description || entryusn || gecos || gidnumber || homedirectory || ipaanchoruuid || ipaoriginaluid || ipasshpubkey || loginshell || modifytimestamp || objectclass || uid || uidnumber || usercertificate")(targetfilter = "(objectclass=ipaUserOverride)")(version 3.0;acl "permission:System: Read User ID Overrides";allow (compare,read,search) userdn = "ldap:///all";) aci: (targetattr = "createtimestamp || description || entryusn || gecos || gidnumber || homedirectory || ipaanchoruuid || ipaoriginaluid || ipasshpubkey || loginshell || memberof || modifytimestamp || objectclass || uid || uidnumber || usercertificate")(targetfilter = "(objectclass=ipaUserOverride)")(version 3.0;acl "permission:System: Read User ID Overrides";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=ranges,cn=etc,dc=ipa,dc=example dn: cn=ranges,cn=etc,dc=ipa,dc=example
aci: (targetattr = "cn || createtimestamp || entryusn || ipabaseid || ipabaserid || ipaidrangesize || ipanttrusteddomainsid || iparangetype || ipasecondarybaserid || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipaidrange)")(version 3.0;acl "permission:System: Read ID Ranges";allow (compare,read,search) userdn = "ldap:///all";) aci: (targetattr = "cn || createtimestamp || entryusn || ipabaseid || ipabaserid || ipaidrangesize || ipanttrusteddomainsid || iparangetype || ipasecondarybaserid || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipaidrange)")(version 3.0;acl "permission:System: Read ID Ranges";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=views,cn=accounts,dc=ipa,dc=example dn: cn=views,cn=accounts,dc=ipa,dc=example

34
API.txt
View File

@ -1959,10 +1959,11 @@ output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value') output: PrimaryKey('value')
command: group_add_member/1 command: group_add_member/1
args: 1,8,3 args: 1,9,3
arg: Str('cn', cli_name='group_name') arg: Str('cn', cli_name='group_name')
option: Flag('all', autofill=True, cli_name='all', default=False) option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('group*', alwaysask=True, cli_name='groups') option: Str('group*', alwaysask=True, cli_name='groups')
option: Str('idoverrideuser*', alwaysask=True, cli_name='idoverrideusers')
option: Str('ipaexternalmember*', cli_name='external') option: Str('ipaexternalmember*', cli_name='external')
option: Flag('no_members', autofill=True, default=False) option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False) option: Flag('raw', autofill=True, cli_name='raw', default=False)
@ -2000,7 +2001,7 @@ output: Output('result', type=[<type 'bool'>])
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value') output: PrimaryKey('value')
command: group_find/1 command: group_find/1
args: 1,34,4 args: 1,36,4
arg: Str('criteria?') arg: Str('criteria?')
option: Flag('all', autofill=True, cli_name='all', default=False) option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('cn?', autofill=False, cli_name='group_name') option: Str('cn?', autofill=False, cli_name='group_name')
@ -2008,6 +2009,7 @@ option: Str('description?', autofill=False, cli_name='desc')
option: Flag('external', autofill=True, cli_name='external', default=False) option: Flag('external', autofill=True, cli_name='external', default=False)
option: Int('gidnumber?', autofill=False, cli_name='gid') option: Int('gidnumber?', autofill=False, cli_name='gid')
option: Str('group*', cli_name='groups') option: Str('group*', cli_name='groups')
option: Str('idoverrideuser*', cli_name='idoverrideusers')
option: Str('in_group*', cli_name='in_groups') option: Str('in_group*', cli_name='in_groups')
option: Str('in_hbacrule*', cli_name='in_hbacrules') option: Str('in_hbacrule*', cli_name='in_hbacrules')
option: Str('in_netgroup*', cli_name='in_netgroups') option: Str('in_netgroup*', cli_name='in_netgroups')
@ -2016,6 +2018,7 @@ option: Str('in_sudorule*', cli_name='in_sudorules')
option: Str('membermanager_group*', cli_name='membermanager_groups') option: Str('membermanager_group*', cli_name='membermanager_groups')
option: Str('membermanager_user*', cli_name='membermanager_users') option: Str('membermanager_user*', cli_name='membermanager_users')
option: Str('no_group*', cli_name='no_groups') option: Str('no_group*', cli_name='no_groups')
option: Str('no_idoverrideuser*', cli_name='no_idoverrideusers')
option: Flag('no_members', autofill=True, default=True) option: Flag('no_members', autofill=True, default=True)
option: Principal('no_service*', cli_name='no_services') option: Principal('no_service*', cli_name='no_services')
option: Str('no_user*', cli_name='no_users') option: Str('no_user*', cli_name='no_users')
@ -2060,10 +2063,11 @@ output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value') output: PrimaryKey('value')
command: group_remove_member/1 command: group_remove_member/1
args: 1,8,3 args: 1,9,3
arg: Str('cn', cli_name='group_name') arg: Str('cn', cli_name='group_name')
option: Flag('all', autofill=True, cli_name='all', default=False) option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('group*', alwaysask=True, cli_name='groups') option: Str('group*', alwaysask=True, cli_name='groups')
option: Str('idoverrideuser*', alwaysask=True, cli_name='idoverrideusers')
option: Str('ipaexternalmember*', cli_name='external') option: Str('ipaexternalmember*', cli_name='external')
option: Flag('no_members', autofill=True, default=False) option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False) option: Flag('raw', autofill=True, cli_name='raw', default=False)
@ -2920,7 +2924,7 @@ output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value') output: PrimaryKey('value')
command: idoverrideuser_add/1 command: idoverrideuser_add/1
args: 2,16,3 args: 2,17,3
arg: Str('idviewcn', cli_name='idview') arg: Str('idviewcn', cli_name='idview')
arg: Str('ipaanchoruuid', cli_name='anchor') arg: Str('ipaanchoruuid', cli_name='anchor')
option: Str('addattr*', cli_name='addattr') option: Str('addattr*', cli_name='addattr')
@ -2933,6 +2937,7 @@ option: Str('homedirectory?', cli_name='homedir')
option: Str('ipaoriginaluid?') option: Str('ipaoriginaluid?')
option: Str('ipasshpubkey*', cli_name='sshpubkey') option: Str('ipasshpubkey*', cli_name='sshpubkey')
option: Str('loginshell?', cli_name='shell') option: Str('loginshell?', cli_name='shell')
option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False) option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Str('setattr*', cli_name='setattr') option: Str('setattr*', cli_name='setattr')
option: Str('uid?', cli_name='login') option: Str('uid?', cli_name='login')
@ -2943,11 +2948,12 @@ output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value') output: PrimaryKey('value')
command: idoverrideuser_add_cert/1 command: idoverrideuser_add_cert/1
args: 2,5,3 args: 2,6,3
arg: Str('idviewcn', cli_name='idview') arg: Str('idviewcn', cli_name='idview')
arg: Str('ipaanchoruuid', cli_name='anchor') arg: Str('ipaanchoruuid', cli_name='anchor')
option: Flag('all', autofill=True, cli_name='all', default=False) option: Flag('all', autofill=True, cli_name='all', default=False)
option: Flag('fallback_to_ldap?', autofill=True, default=False) option: Flag('fallback_to_ldap?', autofill=True, default=False)
option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False) option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Certificate('usercertificate+', alwaysask=True, cli_name='certificate') option: Certificate('usercertificate+', alwaysask=True, cli_name='certificate')
option: Str('version?') option: Str('version?')
@ -2965,7 +2971,7 @@ output: Output('result', type=[<type 'dict'>])
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: ListOfPrimaryKeys('value') output: ListOfPrimaryKeys('value')
command: idoverrideuser_find/1 command: idoverrideuser_find/1
args: 2,16,4 args: 2,17,4
arg: Str('idviewcn', cli_name='idview') arg: Str('idviewcn', cli_name='idview')
arg: Str('criteria?') arg: Str('criteria?')
option: Flag('all', autofill=True, cli_name='all', default=False) option: Flag('all', autofill=True, cli_name='all', default=False)
@ -2977,6 +2983,7 @@ option: Str('homedirectory?', autofill=False, cli_name='homedir')
option: Str('ipaanchoruuid?', autofill=False, cli_name='anchor') option: Str('ipaanchoruuid?', autofill=False, cli_name='anchor')
option: Str('ipaoriginaluid?', autofill=False) option: Str('ipaoriginaluid?', autofill=False)
option: Str('loginshell?', autofill=False, cli_name='shell') option: Str('loginshell?', autofill=False, cli_name='shell')
option: Flag('no_members', autofill=True, default=True)
option: Flag('pkey_only?', autofill=True, default=False) option: Flag('pkey_only?', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False) option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Int('sizelimit?', autofill=False) option: Int('sizelimit?', autofill=False)
@ -2989,7 +2996,7 @@ output: ListOfEntries('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: Output('truncated', type=[<type 'bool'>]) output: Output('truncated', type=[<type 'bool'>])
command: idoverrideuser_mod/1 command: idoverrideuser_mod/1
args: 2,19,3 args: 2,20,3
arg: Str('idviewcn', cli_name='idview') arg: Str('idviewcn', cli_name='idview')
arg: Str('ipaanchoruuid', cli_name='anchor') arg: Str('ipaanchoruuid', cli_name='anchor')
option: Str('addattr*', cli_name='addattr') option: Str('addattr*', cli_name='addattr')
@ -3003,6 +3010,7 @@ option: Str('homedirectory?', autofill=False, cli_name='homedir')
option: Str('ipaoriginaluid?', autofill=False) option: Str('ipaoriginaluid?', autofill=False)
option: Str('ipasshpubkey*', autofill=False, cli_name='sshpubkey') option: Str('ipasshpubkey*', autofill=False, cli_name='sshpubkey')
option: Str('loginshell?', autofill=False, cli_name='shell') option: Str('loginshell?', autofill=False, cli_name='shell')
option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False) option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Str('rename?', cli_name='rename') option: Str('rename?', cli_name='rename')
option: Flag('rights', autofill=True, default=False) option: Flag('rights', autofill=True, default=False)
@ -3015,11 +3023,12 @@ output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value') output: PrimaryKey('value')
command: idoverrideuser_remove_cert/1 command: idoverrideuser_remove_cert/1
args: 2,5,3 args: 2,6,3
arg: Str('idviewcn', cli_name='idview') arg: Str('idviewcn', cli_name='idview')
arg: Str('ipaanchoruuid', cli_name='anchor') arg: Str('ipaanchoruuid', cli_name='anchor')
option: Flag('all', autofill=True, cli_name='all', default=False) option: Flag('all', autofill=True, cli_name='all', default=False)
option: Flag('fallback_to_ldap?', autofill=True, default=False) option: Flag('fallback_to_ldap?', autofill=True, default=False)
option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False) option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Certificate('usercertificate+', alwaysask=True, cli_name='certificate') option: Certificate('usercertificate+', alwaysask=True, cli_name='certificate')
option: Str('version?') option: Str('version?')
@ -3027,11 +3036,12 @@ output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value') output: PrimaryKey('value')
command: idoverrideuser_show/1 command: idoverrideuser_show/1
args: 2,5,3 args: 2,6,3
arg: Str('idviewcn', cli_name='idview') arg: Str('idviewcn', cli_name='idview')
arg: Str('ipaanchoruuid', cli_name='anchor') arg: Str('ipaanchoruuid', cli_name='anchor')
option: Flag('all', autofill=True, cli_name='all', default=False) option: Flag('all', autofill=True, cli_name='all', default=False)
option: Flag('fallback_to_ldap?', autofill=True, default=False) option: Flag('fallback_to_ldap?', autofill=True, default=False)
option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False) option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Flag('rights', autofill=True, default=False) option: Flag('rights', autofill=True, default=False)
option: Str('version?') option: Str('version?')
@ -4147,12 +4157,13 @@ output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value') output: PrimaryKey('value')
command: role_add_member/1 command: role_add_member/1
args: 1,9,3 args: 1,10,3
arg: Str('cn', cli_name='name') arg: Str('cn', cli_name='name')
option: Flag('all', autofill=True, cli_name='all', default=False) option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('group*', alwaysask=True, cli_name='groups') option: Str('group*', alwaysask=True, cli_name='groups')
option: Str('host*', alwaysask=True, cli_name='hosts') option: Str('host*', alwaysask=True, cli_name='hosts')
option: Str('hostgroup*', alwaysask=True, cli_name='hostgroups') option: Str('hostgroup*', alwaysask=True, cli_name='hostgroups')
option: Str('idoverrideuser*', alwaysask=True, cli_name='idoverrideusers')
option: Flag('no_members', autofill=True, default=False) option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False) option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Str('service*', alwaysask=True, cli_name='services') option: Str('service*', alwaysask=True, cli_name='services')
@ -4213,12 +4224,13 @@ output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value') output: PrimaryKey('value')
command: role_remove_member/1 command: role_remove_member/1
args: 1,9,3 args: 1,10,3
arg: Str('cn', cli_name='name') arg: Str('cn', cli_name='name')
option: Flag('all', autofill=True, cli_name='all', default=False) option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('group*', alwaysask=True, cli_name='groups') option: Str('group*', alwaysask=True, cli_name='groups')
option: Str('host*', alwaysask=True, cli_name='hosts') option: Str('host*', alwaysask=True, cli_name='hosts')
option: Str('hostgroup*', alwaysask=True, cli_name='hostgroups') option: Str('hostgroup*', alwaysask=True, cli_name='hostgroups')
option: Str('idoverrideuser*', alwaysask=True, cli_name='idoverrideusers')
option: Flag('no_members', autofill=True, default=False) option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False) option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Str('service*', alwaysask=True, cli_name='services') option: Str('service*', alwaysask=True, cli_name='services')

View File

@ -86,8 +86,8 @@ define(IPA_DATA_VERSION, 20100614120000)
# # # #
######################################################## ########################################################
define(IPA_API_VERSION_MAJOR, 2) define(IPA_API_VERSION_MAJOR, 2)
define(IPA_API_VERSION_MINOR, 238) define(IPA_API_VERSION_MINOR, 239)
# Last change: permission ipapermbindruletype=self # Last change: allow ID overrides for users to be members of groups and roles
######################################################## ########################################################

View File

@ -120,6 +120,10 @@ global_output_params = (
Str('memberof_hbacrule?', Str('memberof_hbacrule?',
label='Member of HBAC rule', label='Member of HBAC rule',
), ),
Str('member_idoverrideuser?',
label=_('Member ID user overrides'),),
Str('memberindirect_idoverrideuser?',
label=_('Indirect Member ID user overrides'),),
Str('memberindirect_user?', Str('memberindirect_user?',
label=_('Indirect Member users'), label=_('Indirect Member users'),
), ),

View File

@ -40,7 +40,7 @@ from .baseldap import (
LDAPRemoveMember, LDAPRemoveMember,
LDAPQuery, LDAPQuery,
) )
from .idviews import remove_ipaobject_overrides from .idviews import remove_ipaobject_overrides, handle_idoverride_memberof
from . import baseldap from . import baseldap
from ipalib import _, ngettext from ipalib import _, ngettext
from ipalib import errors from ipalib import errors
@ -204,10 +204,10 @@ class group(LDAPObject):
] ]
uuid_attribute = 'ipauniqueid' uuid_attribute = 'ipauniqueid'
attribute_members = { attribute_members = {
'member': ['user', 'group', 'service'], 'member': ['user', 'group', 'service', 'idoverrideuser'],
'membermanager': ['user', 'group'], 'membermanager': ['user', 'group'],
'memberof': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'], 'memberof': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'],
'memberindirect': ['user', 'group', 'service'], 'memberindirect': ['user', 'group', 'service', 'idoverrideuser'],
'memberofindirect': ['group', 'netgroup', 'role', 'hbacrule', 'memberofindirect': ['group', 'netgroup', 'role', 'hbacrule',
'sudorule'], 'sudorule'],
} }
@ -593,6 +593,12 @@ class group_add_member(LDAPAddMember):
takes_options = (ipaexternalmember_param,) takes_options = (ipaexternalmember_param,)
def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
assert isinstance(dn, DN)
handle_idoverride_memberof(self, ldap, dn, found, not_found,
*keys, **options)
return dn
def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
assert isinstance(dn, DN) assert isinstance(dn, DN)
result = (completed, dn) result = (completed, dn)

View File

@ -32,6 +32,8 @@ from .baseldap import (
LDAPRemoveReverseMember) LDAPRemoveReverseMember)
from ipalib import api, Str, _, ngettext from ipalib import api, Str, _, ngettext
from ipalib import output from ipalib import output
from ipapython.dn import DN
from .idviews import handle_idoverride_memberof
__doc__ = _(""" __doc__ = _("""
Roles Roles
@ -86,7 +88,8 @@ class role(LDAPObject):
# 'memberindirect', 'memberofindirect', # 'memberindirect', 'memberofindirect',
attribute_members = { attribute_members = {
'member': ['user', 'group', 'host', 'hostgroup', 'service'], 'member': ['user', 'group', 'host', 'hostgroup', 'service',
'idoverrideuser'],
'memberof': ['privilege'], 'memberof': ['privilege'],
} }
reverse_members = { reverse_members = {
@ -198,6 +201,12 @@ class role_show(LDAPRetrieve):
class role_add_member(LDAPAddMember): class role_add_member(LDAPAddMember):
__doc__ = _('Add members to a role.') __doc__ = _('Add members to a role.')
def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
assert isinstance(dn, DN)
handle_idoverride_memberof(self, ldap, dn, found, not_found,
*keys, **options)
return dn
@register() @register()