From 8cce2bb31ab96f6ce6edba95f54575576f2b1a40 Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy Date: Jun 08 2020 19:58:26 +0000 Subject: 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 Reviewed-By: Rob Crittenden Reviewed-By: Rob Crittenden --- diff --git a/ACI.txt b/ACI.txt index 541d4d2..9e2f043 100644 --- a/ACI.txt +++ b/ACI.txt @@ -179,7 +179,7 @@ aci: (targetfilter = "(objectclass=ipahostgroup)")(version 3.0;acl "permission:S 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";) 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 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 diff --git a/API.txt b/API.txt index 291ec1f..5354a33 100644 --- a/API.txt +++ b/API.txt @@ -1959,10 +1959,11 @@ output: Entry('result') output: Output('summary', type=[, ]) output: PrimaryKey('value') command: group_add_member/1 -args: 1,8,3 +args: 1,9,3 arg: Str('cn', cli_name='group_name') option: Flag('all', autofill=True, cli_name='all', default=False) option: Str('group*', alwaysask=True, cli_name='groups') +option: Str('idoverrideuser*', alwaysask=True, cli_name='idoverrideusers') option: Str('ipaexternalmember*', cli_name='external') option: Flag('no_members', autofill=True, default=False) option: Flag('raw', autofill=True, cli_name='raw', default=False) @@ -2000,7 +2001,7 @@ output: Output('result', type=[]) output: Output('summary', type=[, ]) output: PrimaryKey('value') command: group_find/1 -args: 1,34,4 +args: 1,36,4 arg: Str('criteria?') option: Flag('all', autofill=True, cli_name='all', default=False) 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: Int('gidnumber?', autofill=False, cli_name='gid') option: Str('group*', cli_name='groups') +option: Str('idoverrideuser*', cli_name='idoverrideusers') option: Str('in_group*', cli_name='in_groups') option: Str('in_hbacrule*', cli_name='in_hbacrules') 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_user*', cli_name='membermanager_users') 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: Principal('no_service*', cli_name='no_services') option: Str('no_user*', cli_name='no_users') @@ -2060,10 +2063,11 @@ output: Entry('result') output: Output('summary', type=[, ]) output: PrimaryKey('value') command: group_remove_member/1 -args: 1,8,3 +args: 1,9,3 arg: Str('cn', cli_name='group_name') option: Flag('all', autofill=True, cli_name='all', default=False) option: Str('group*', alwaysask=True, cli_name='groups') +option: Str('idoverrideuser*', alwaysask=True, cli_name='idoverrideusers') option: Str('ipaexternalmember*', cli_name='external') option: Flag('no_members', autofill=True, default=False) option: Flag('raw', autofill=True, cli_name='raw', default=False) @@ -2920,7 +2924,7 @@ output: Entry('result') output: Output('summary', type=[, ]) output: PrimaryKey('value') command: idoverrideuser_add/1 -args: 2,16,3 +args: 2,17,3 arg: Str('idviewcn', cli_name='idview') arg: Str('ipaanchoruuid', cli_name='anchor') option: Str('addattr*', cli_name='addattr') @@ -2933,6 +2937,7 @@ option: Str('homedirectory?', cli_name='homedir') option: Str('ipaoriginaluid?') option: Str('ipasshpubkey*', cli_name='sshpubkey') 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: Str('setattr*', cli_name='setattr') option: Str('uid?', cli_name='login') @@ -2943,11 +2948,12 @@ output: Entry('result') output: Output('summary', type=[, ]) output: PrimaryKey('value') command: idoverrideuser_add_cert/1 -args: 2,5,3 +args: 2,6,3 arg: Str('idviewcn', cli_name='idview') arg: Str('ipaanchoruuid', cli_name='anchor') option: Flag('all', autofill=True, cli_name='all', 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: Certificate('usercertificate+', alwaysask=True, cli_name='certificate') option: Str('version?') @@ -2965,7 +2971,7 @@ output: Output('result', type=[]) output: Output('summary', type=[, ]) output: ListOfPrimaryKeys('value') command: idoverrideuser_find/1 -args: 2,16,4 +args: 2,17,4 arg: Str('idviewcn', cli_name='idview') arg: Str('criteria?') 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('ipaoriginaluid?', autofill=False) 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('raw', autofill=True, cli_name='raw', default=False) option: Int('sizelimit?', autofill=False) @@ -2989,7 +2996,7 @@ output: ListOfEntries('result') output: Output('summary', type=[, ]) output: Output('truncated', type=[]) command: idoverrideuser_mod/1 -args: 2,19,3 +args: 2,20,3 arg: Str('idviewcn', cli_name='idview') arg: Str('ipaanchoruuid', cli_name='anchor') 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('ipasshpubkey*', autofill=False, cli_name='sshpubkey') 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: Str('rename?', cli_name='rename') option: Flag('rights', autofill=True, default=False) @@ -3015,11 +3023,12 @@ output: Entry('result') output: Output('summary', type=[, ]) output: PrimaryKey('value') command: idoverrideuser_remove_cert/1 -args: 2,5,3 +args: 2,6,3 arg: Str('idviewcn', cli_name='idview') arg: Str('ipaanchoruuid', cli_name='anchor') option: Flag('all', autofill=True, cli_name='all', 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: Certificate('usercertificate+', alwaysask=True, cli_name='certificate') option: Str('version?') @@ -3027,11 +3036,12 @@ output: Entry('result') output: Output('summary', type=[, ]) output: PrimaryKey('value') command: idoverrideuser_show/1 -args: 2,5,3 +args: 2,6,3 arg: Str('idviewcn', cli_name='idview') arg: Str('ipaanchoruuid', cli_name='anchor') option: Flag('all', autofill=True, cli_name='all', 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('rights', autofill=True, default=False) option: Str('version?') @@ -4147,12 +4157,13 @@ output: Entry('result') output: Output('summary', type=[, ]) output: PrimaryKey('value') command: role_add_member/1 -args: 1,9,3 +args: 1,10,3 arg: Str('cn', cli_name='name') option: Flag('all', autofill=True, cli_name='all', default=False) option: Str('group*', alwaysask=True, cli_name='groups') option: Str('host*', alwaysask=True, cli_name='hosts') 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('raw', autofill=True, cli_name='raw', default=False) option: Str('service*', alwaysask=True, cli_name='services') @@ -4213,12 +4224,13 @@ output: Entry('result') output: Output('summary', type=[, ]) output: PrimaryKey('value') command: role_remove_member/1 -args: 1,9,3 +args: 1,10,3 arg: Str('cn', cli_name='name') option: Flag('all', autofill=True, cli_name='all', default=False) option: Str('group*', alwaysask=True, cli_name='groups') option: Str('host*', alwaysask=True, cli_name='hosts') 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('raw', autofill=True, cli_name='raw', default=False) option: Str('service*', alwaysask=True, cli_name='services') diff --git a/VERSION.m4 b/VERSION.m4 index 15afded..ed0f20a 100644 --- a/VERSION.m4 +++ b/VERSION.m4 @@ -86,8 +86,8 @@ define(IPA_DATA_VERSION, 20100614120000) # # ######################################################## define(IPA_API_VERSION_MAJOR, 2) -define(IPA_API_VERSION_MINOR, 238) -# Last change: permission ipapermbindruletype=self +define(IPA_API_VERSION_MINOR, 239) +# Last change: allow ID overrides for users to be members of groups and roles ######################################################## diff --git a/ipaserver/plugins/baseldap.py b/ipaserver/plugins/baseldap.py index d186dd8..dfb4d34 100644 --- a/ipaserver/plugins/baseldap.py +++ b/ipaserver/plugins/baseldap.py @@ -120,6 +120,10 @@ global_output_params = ( Str('memberof_hbacrule?', 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?', label=_('Indirect Member users'), ), diff --git a/ipaserver/plugins/group.py b/ipaserver/plugins/group.py index 45b1f78..33984da 100644 --- a/ipaserver/plugins/group.py +++ b/ipaserver/plugins/group.py @@ -40,7 +40,7 @@ from .baseldap import ( LDAPRemoveMember, LDAPQuery, ) -from .idviews import remove_ipaobject_overrides +from .idviews import remove_ipaobject_overrides, handle_idoverride_memberof from . import baseldap from ipalib import _, ngettext from ipalib import errors @@ -204,10 +204,10 @@ class group(LDAPObject): ] uuid_attribute = 'ipauniqueid' attribute_members = { - 'member': ['user', 'group', 'service'], + 'member': ['user', 'group', 'service', 'idoverrideuser'], 'membermanager': ['user', 'group'], 'memberof': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'], - 'memberindirect': ['user', 'group', 'service'], + 'memberindirect': ['user', 'group', 'service', 'idoverrideuser'], 'memberofindirect': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'], } @@ -593,6 +593,12 @@ class group_add_member(LDAPAddMember): 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): assert isinstance(dn, DN) result = (completed, dn) diff --git a/ipaserver/plugins/role.py b/ipaserver/plugins/role.py index da30c5e..c634320 100644 --- a/ipaserver/plugins/role.py +++ b/ipaserver/plugins/role.py @@ -32,6 +32,8 @@ from .baseldap import ( LDAPRemoveReverseMember) from ipalib import api, Str, _, ngettext from ipalib import output +from ipapython.dn import DN +from .idviews import handle_idoverride_memberof __doc__ = _(""" Roles @@ -86,7 +88,8 @@ class role(LDAPObject): # 'memberindirect', 'memberofindirect', attribute_members = { - 'member': ['user', 'group', 'host', 'hostgroup', 'service'], + 'member': ['user', 'group', 'host', 'hostgroup', 'service', + 'idoverrideuser'], 'memberof': ['privilege'], } reverse_members = { @@ -198,6 +201,12 @@ class role_show(LDAPRetrieve): class role_add_member(LDAPAddMember): __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()