From bee4204039dac9cd858e823b839183ba2cdbd216 Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy Date: Wed, 3 Jun 2020 13:04:46 +0300 Subject: [PATCH] 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 --- ACI.txt | 2 +- API.txt | 34 +++++++++++++++++++++++----------- VERSION.m4 | 4 ++-- ipaserver/plugins/baseldap.py | 4 ++++ ipaserver/plugins/group.py | 12 +++++++++--- ipaserver/plugins/role.py | 11 ++++++++++- 6 files changed, 49 insertions(+), 18 deletions(-) diff --git a/ACI.txt b/ACI.txt index 541d4d2f3..9e2f043c1 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 291ec1f9c..5354a33a0 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 aabde9e19..788db19e8 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 d186dd887..dfb4d34dc 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 45b1f7875..33984daf0 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 da30c5eb0..c63432087 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()