From f0a1f084b64bd0ca570999594f8e25d4e8808938 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 6 Nov 2019 06:58:42 +0100 Subject: [PATCH] Add group membership management A group membership manager is a user or a group that can add members to a group or remove members from a group or host group. Fixes: https://pagure.io/freeipa/issue/8114 Signed-off-by: Christian Heimes Reviewed-By: Alexander Bokovoy --- ACI.txt | 8 +- API.txt | 64 +++++++++++++- VERSION.m4 | 5 +- doc/designs/membermanager.md | 130 ++++++++++++++++++++++++++++ install/share/60basev2.ldif | 7 +- install/share/default-aci.ldif | 13 +++ install/share/indices.ldif | 9 ++ install/ui/src/freeipa/entity.js | 3 +- install/ui/src/freeipa/group.js | 14 +++ install/ui/src/freeipa/hostgroup.js | 16 ++++ install/updates/20-aci.update | 8 ++ install/updates/20-indices.update | 8 ++ install/updates/25-referint.update | 1 + ipaserver/plugins/baseldap.py | 5 ++ ipaserver/plugins/group.py | 56 +++++++++++- ipaserver/plugins/hostgroup.py | 61 +++++++++++-- ipaserver/plugins/internal.py | 33 +++++++ 17 files changed, 421 insertions(+), 20 deletions(-) create mode 100644 doc/designs/membermanager.md diff --git a/ACI.txt b/ACI.txt index 5ad8d12a3..3dcf84fd6 100644 --- a/ACI.txt +++ b/ACI.txt @@ -99,7 +99,7 @@ aci: (targetattr = "ipaexternalmember")(targetfilter = "(objectclass=ipaexternal dn: cn=groups,cn=accounts,dc=ipa,dc=example aci: (targetattr = "member")(targetfilter = "(&(!(cn=admins))(objectclass=ipausergroup))")(version 3.0;acl "permission:System: Modify Group Membership";allow (write) groupdn = "ldap:///cn=System: Modify Group Membership,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=groups,cn=accounts,dc=ipa,dc=example -aci: (targetattr = "cn || description || gidnumber || ipauniqueid || mepmanagedby || objectclass")(targetfilter = "(|(objectclass=ipausergroup)(objectclass=posixgroup))")(version 3.0;acl "permission:System: Modify Groups";allow (write) groupdn = "ldap:///cn=System: Modify Groups,cn=permissions,cn=pbac,dc=ipa,dc=example";) +aci: (targetattr = "cn || description || gidnumber || ipauniqueid || membermanager || mepmanagedby || objectclass")(targetfilter = "(|(objectclass=ipausergroup)(objectclass=posixgroup))")(version 3.0;acl "permission:System: Modify Groups";allow (write) groupdn = "ldap:///cn=System: Modify Groups,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=groups,cn=accounts,dc=ipa,dc=example aci: (targetattr = "ipaexternalmember")(targetfilter = "(|(objectclass=ipausergroup)(objectclass=posixgroup))")(version 3.0;acl "permission:System: Read External Group Membership";allow (compare,read,search) userdn = "ldap:///all";) dn: dc=ipa,dc=example @@ -109,7 +109,7 @@ aci: (targetattr = "member || memberhost || memberof || memberuid || memberuser" dn: dc=ipa,dc=example aci: (targetattr = "cn || createtimestamp || entryusn || gidnumber || memberuid || modifytimestamp || objectclass")(target = "ldap:///cn=groups,cn=*,cn=views,cn=compat,dc=ipa,dc=example")(version 3.0;acl "permission:System: Read Group Views Compat Tree";allow (compare,read,search) userdn = "ldap:///anyone";) dn: cn=groups,cn=accounts,dc=ipa,dc=example -aci: (targetattr = "businesscategory || cn || createtimestamp || description || entryusn || gidnumber || ipaexternalmember || ipantsecurityidentifier || ipauniqueid || mepmanagedby || modifytimestamp || o || objectclass || ou || owner || seealso")(targetfilter = "(|(objectclass=ipausergroup)(objectclass=posixgroup))")(version 3.0;acl "permission:System: Read Groups";allow (compare,read,search) userdn = "ldap:///anyone";) +aci: (targetattr = "businesscategory || cn || createtimestamp || description || entryusn || gidnumber || ipaexternalmember || ipantsecurityidentifier || ipauniqueid || membermanager || mepmanagedby || modifytimestamp || o || objectclass || ou || owner || seealso")(targetfilter = "(|(objectclass=ipausergroup)(objectclass=posixgroup))")(version 3.0;acl "permission:System: Read Groups";allow (compare,read,search) userdn = "ldap:///anyone";) dn: cn=groups,cn=accounts,dc=ipa,dc=example aci: (targetfilter = "(|(objectclass=ipausergroup)(objectclass=posixgroup))")(version 3.0;acl "permission:System: Remove Groups";allow (delete) groupdn = "ldap:///cn=System: Remove Groups,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=hbac,dc=ipa,dc=example @@ -169,11 +169,11 @@ aci: (targetfilter = "(objectclass=ipahostgroup)")(version 3.0;acl "permission:S dn: cn=hostgroups,cn=accounts,dc=ipa,dc=example aci: (targetattr = "member")(targetfilter = "(&(!(cn=ipaservers))(objectclass=ipahostgroup))")(version 3.0;acl "permission:System: Modify Hostgroup Membership";allow (write) groupdn = "ldap:///cn=System: Modify Hostgroup Membership,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=hostgroups,cn=accounts,dc=ipa,dc=example -aci: (targetattr = "cn || description")(targetfilter = "(objectclass=ipahostgroup)")(version 3.0;acl "permission:System: Modify Hostgroups";allow (write) groupdn = "ldap:///cn=System: Modify Hostgroups,cn=permissions,cn=pbac,dc=ipa,dc=example";) +aci: (targetattr = "cn || description || membermanager")(targetfilter = "(objectclass=ipahostgroup)")(version 3.0;acl "permission:System: Modify Hostgroups";allow (write) groupdn = "ldap:///cn=System: Modify Hostgroups,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=hostgroups,cn=accounts,dc=ipa,dc=example aci: (targetattr = "member || memberhost || memberof || memberuser")(targetfilter = "(objectclass=ipahostgroup)")(version 3.0;acl "permission:System: Read Hostgroup Membership";allow (compare,read,search) userdn = "ldap:///all";) dn: cn=hostgroups,cn=accounts,dc=ipa,dc=example -aci: (targetattr = "businesscategory || cn || createtimestamp || description || entryusn || ipauniqueid || modifytimestamp || o || objectclass || ou || owner || seealso")(targetfilter = "(objectclass=ipahostgroup)")(version 3.0;acl "permission:System: Read Hostgroups";allow (compare,read,search) userdn = "ldap:///all";) +aci: (targetattr = "businesscategory || cn || createtimestamp || description || entryusn || ipauniqueid || membermanager || modifytimestamp || o || objectclass || ou || owner || seealso")(targetfilter = "(objectclass=ipahostgroup)")(version 3.0;acl "permission:System: Read Hostgroups";allow (compare,read,search) userdn = "ldap:///all";) dn: cn=hostgroups,cn=accounts,dc=ipa,dc=example aci: (targetfilter = "(objectclass=ipahostgroup)")(version 3.0;acl "permission:System: Remove Hostgroups";allow (delete) groupdn = "ldap:///cn=System: Remove Hostgroups,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=views,cn=accounts,dc=ipa,dc=example diff --git a/API.txt b/API.txt index 9ba175c04..a919e3a63 100644 --- a/API.txt +++ b/API.txt @@ -1972,6 +1972,18 @@ option: Str('version?') output: Output('completed', type=[]) output: Output('failed', type=[]) output: Entry('result') +command: group_add_member_manager/1 +args: 1,6,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: Flag('no_members', autofill=True, default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Str('user*', alwaysask=True, cli_name='users') +option: Str('version?') +output: Output('completed', type=[]) +output: Output('failed', type=[]) +output: Entry('result') command: group_del/1 args: 1,2,3 arg: Str('cn+', cli_name='group_name') @@ -1988,7 +2000,7 @@ output: Output('result', type=[]) output: Output('summary', type=[, ]) output: PrimaryKey('value') command: group_find/1 -args: 1,30,4 +args: 1,34,4 arg: Str('criteria?') option: Flag('all', autofill=True, cli_name='all', default=False) option: Str('cn?', autofill=False, cli_name='group_name') @@ -2001,6 +2013,8 @@ option: Str('in_hbacrule*', cli_name='in_hbacrules') option: Str('in_netgroup*', cli_name='in_netgroups') option: Str('in_role*', cli_name='in_roles') 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: Flag('no_members', autofill=True, default=True) option: Principal('no_service*', cli_name='no_services') @@ -2011,6 +2025,8 @@ option: Str('not_in_hbacrule*', cli_name='not_in_hbacrules') option: Str('not_in_netgroup*', cli_name='not_in_netgroups') option: Str('not_in_role*', cli_name='not_in_roles') option: Str('not_in_sudorule*', cli_name='not_in_sudorules') +option: Str('not_membermanager_group*', cli_name='not_membermanager_groups') +option: Str('not_membermanager_user*', cli_name='not_membermanager_users') option: Flag('pkey_only?', autofill=True, default=False) option: Flag('posix', autofill=True, cli_name='posix', default=False) option: Flag('private', autofill=True, cli_name='private', default=False) @@ -2057,6 +2073,18 @@ option: Str('version?') output: Output('completed', type=[]) output: Output('failed', type=[]) output: Entry('result') +command: group_remove_member_manager/1 +args: 1,6,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: Flag('no_members', autofill=True, default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Str('user*', alwaysask=True, cli_name='users') +option: Str('version?') +output: Output('completed', type=[]) +output: Output('failed', type=[]) +output: Entry('result') command: group_show/1 args: 1,5,3 arg: Str('cn', cli_name='group_name') @@ -2708,6 +2736,18 @@ option: Str('version?') output: Output('completed', type=[]) output: Output('failed', type=[]) output: Entry('result') +command: hostgroup_add_member_manager/1 +args: 1,6,3 +arg: Str('cn', cli_name='hostgroup_name') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Str('group*', alwaysask=True, cli_name='groups') +option: Flag('no_members', autofill=True, default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Str('user*', alwaysask=True, cli_name='users') +option: Str('version?') +output: Output('completed', type=[]) +output: Output('failed', type=[]) +output: Entry('result') command: hostgroup_del/1 args: 1,2,3 arg: Str('cn+', cli_name='hostgroup_name') @@ -2717,7 +2757,7 @@ output: Output('result', type=[]) output: Output('summary', type=[, ]) output: ListOfPrimaryKeys('value') command: hostgroup_find/1 -args: 1,21,4 +args: 1,25,4 arg: Str('criteria?') option: Flag('all', autofill=True, cli_name='all', default=False) option: Str('cn?', autofill=False, cli_name='hostgroup_name') @@ -2728,6 +2768,8 @@ option: Str('in_hbacrule*', cli_name='in_hbacrules') option: Str('in_hostgroup*', cli_name='in_hostgroups') option: Str('in_netgroup*', cli_name='in_netgroups') 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_host*', cli_name='no_hosts') option: Str('no_hostgroup*', cli_name='no_hostgroups') option: Flag('no_members', autofill=True, default=True) @@ -2735,6 +2777,8 @@ option: Str('not_in_hbacrule*', cli_name='not_in_hbacrules') option: Str('not_in_hostgroup*', cli_name='not_in_hostgroups') option: Str('not_in_netgroup*', cli_name='not_in_netgroups') option: Str('not_in_sudorule*', cli_name='not_in_sudorules') +option: Str('not_membermanager_group*', cli_name='not_membermanager_groups') +option: Str('not_membermanager_user*', cli_name='not_membermanager_users') option: Flag('pkey_only?', autofill=True, default=False) option: Flag('raw', autofill=True, cli_name='raw', default=False) option: Int('sizelimit?', autofill=False) @@ -2771,6 +2815,18 @@ option: Str('version?') output: Output('completed', type=[]) output: Output('failed', type=[]) output: Entry('result') +command: hostgroup_remove_member_manager/1 +args: 1,6,3 +arg: Str('cn', cli_name='hostgroup_name') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Str('group*', alwaysask=True, cli_name='groups') +option: Flag('no_members', autofill=True, default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Str('user*', alwaysask=True, cli_name='users') +option: Str('version?') +output: Output('completed', type=[]) +output: Output('failed', type=[]) +output: Entry('result') command: hostgroup_show/1 args: 1,5,3 arg: Str('cn', cli_name='hostgroup_name') @@ -6739,11 +6795,13 @@ default: env/1 default: group/1 default: group_add/1 default: group_add_member/1 +default: group_add_member_manager/1 default: group_del/1 default: group_detach/1 default: group_find/1 default: group_mod/1 default: group_remove_member/1 +default: group_remove_member_manager/1 default: group_show/1 default: hbacrule/1 default: hbacrule_add/1 @@ -6796,10 +6854,12 @@ default: host_show/1 default: hostgroup/1 default: hostgroup_add/1 default: hostgroup_add_member/1 +default: hostgroup_add_member_manager/1 default: hostgroup_del/1 default: hostgroup_find/1 default: hostgroup_mod/1 default: hostgroup_remove_member/1 +default: hostgroup_remove_member_manager/1 default: hostgroup_show/1 default: i18n_messages/1 default: idoverridegroup/1 diff --git a/VERSION.m4 b/VERSION.m4 index 4fe91e4bb..36c1e1db2 100644 --- a/VERSION.m4 +++ b/VERSION.m4 @@ -86,9 +86,8 @@ define(IPA_DATA_VERSION, 20100614120000) # # ######################################################## define(IPA_API_VERSION_MAJOR, 2) -define(IPA_API_VERSION_MINOR, 234) -# Last change: Added new auth indicators to ipauserauthtype and krbprincipalauthind. -# Converted krbprincipalauthind from Str() to StrEnum() +define(IPA_API_VERSION_MINOR, 235) +# Last change: Add memberManager to groups. ######################################################## # Following values are auto-generated from values above diff --git a/doc/designs/membermanager.md b/doc/designs/membermanager.md new file mode 100644 index 000000000..ff6e65a08 --- /dev/null +++ b/doc/designs/membermanager.md @@ -0,0 +1,130 @@ +# Member Manager for group membership + +## Overview + +A member manager is a principal that is able to manage members of a +group. Member managers are able to add new members to a group or remove +existing members from a group. They cannot modify additional attributes +of a group as a part of the member manager role. + +Member management is implemented for *user groups* and *host groups*. +Membership can be managed by users or user groups. Member managers are +independent from members. A principal can be a member manager of a +group without being a member of a group. + + +## Use Cases + +An administrator can use member management feature to delegate some +control over user groups and host groups to users. For example a +project manager is now able to add new team members to a project group. + +A NFS admin with member management capability for a host group is able +to indirectly influence an HBAC rules and control which hosts can +connect to an NFS file share. + +## Implementation + +The user group commands and host group commands are extended to handle +member managers. The plugin classes grow two additional sub commands, +one for adding and one for removing member managers. The show command +prints member manager users and member manager groups. The find command +can search by member manager. + +Member managers are stored in a new LDAP attribute ``memberManager`` +with OID 2.16.840.1.113730.3.8.23.1. It is multi-valued and contains +DNs of users and groups which can manage members of the group. The +attribute can be added to entries with object class ``ipaUserGroup`` +or ``ipaHostGroup``. The attribute is indexed and its membership +controlled by referential integrity postoperation plugin. +New userattr ACIs grant principals with user DN or group DN in +``memberManager`` write permission to the ``member`` attribute of the +group. + +The ``memberManager`` attribute is protected by the generic read and +modify permissions for each type of group. It is readable by everybody +with ``System: Read Groups`` / ``System: Read Hostgroups`` permission +and writable by everybody with ``System: Modify Groups`` / +``System: Modify Hostgroups`` permission. + + +## Examples + +Add example user and groups: + +``` +$ kinit admin +$ ipa user-add john --first John --last Doe --random +$ ipa user-add tom --first Tom --last Doe --random +$ ipa group-add project +$ ipa group-add project_admins +``` + +Make user and group member managers: + +``` +$ ipa group-add-member-manager project --users=john +$ ipa group-add-member-manager project --groups=project_admins +``` + +Show group: + +``` +$ ipa group-show project + Group name: project + GID: 787600003 + Membership managed by groups: project_admins + Membership managed by users: john +``` + +Find groups by member managers: + +``` +$ ipa group-find --membermanager-users=john +--------------- +1 group matched +--------------- + Group name: project + GID: 787600003 +---------------------------- +Number of entries returned 1 +---------------------------- +$ ipa group-find --membermanager-groups=project_admins +--------------- +1 group matched +--------------- + Group name: project + GID: 787600003 +---------------------------- +Number of entries returned 1 +---------------------------- +``` + +Use member management capability: + +``` +$ kinit john +$ ipa group-add-member project --users=tom + Group name: project + GID: 787600003 + Member users: tom + Membership managed by groups: project_admins + Membership managed by users: john +------------------------- +Number of members added 1 +------------------------- +``` + +Remove member management capability: + +``` +$ kinit admin +$ ipa group-remove-member-manager project --groups=project_admins + Group name: project + GID: 787600003 + Member users: tom + Membership managed by users: john +--------------------------- +Number of members removed 1 +--------------------------- +``` diff --git a/install/share/60basev2.ldif b/install/share/60basev2.ldif index 00712ddda..94b4376ac 100644 --- a/install/share/60basev2.ldif +++ b/install/share/60basev2.ldif @@ -2,6 +2,7 @@ ## ## Attributes: 2.16.840.1.113730.3.8.3 - V2 base attributres ## ObjectClasses: 2.16.840.1.113730.3.8.4 - V2 base objectclasses +## Attributes: 2.16.840.1.113730.3.8.23 - V4 base attributes ## dn: cn=schema attributeTypes: (2.16.840.1.113730.3.8.3.1 NAME 'ipaUniqueID' DESC 'Unique identifier' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' ) @@ -21,8 +22,10 @@ objectClasses: (2.16.840.1.113730.3.8.4.14 NAME 'ipaEntitlement' DESC 'IPA Entit objectClasses: (2.16.840.1.113730.3.8.4.15 NAME 'ipaPermission' DESC 'IPA Permission objectclass' AUXILIARY MAY ( ipaPermissionType ) X-ORIGIN 'IPA v2' ) objectClasses: (2.16.840.1.113730.3.8.4.2 NAME 'ipaService' DESC 'IPA service objectclass' AUXILIARY MAY ( memberOf $ managedBy $ ipaKrbAuthzData) X-ORIGIN 'IPA v2' ) objectClasses: (2.16.840.1.113730.3.8.4.3 NAME 'nestedGroup' DESC 'Group that supports nesting' SUP groupOfNames STRUCTURAL MAY memberOf X-ORIGIN 'IPA v2' ) -objectClasses: (2.16.840.1.113730.3.8.4.4 NAME 'ipaUserGroup' DESC 'IPA user group object class' SUP nestedGroup STRUCTURAL X-ORIGIN 'IPA v2' ) -objectClasses: (2.16.840.1.113730.3.8.4.5 NAME 'ipaHostGroup' DESC 'IPA host group object class' SUP nestedGroup STRUCTURAL X-ORIGIN 'IPA v2' ) +# Same for memberManager except it's actually v4 attribute +attributeTypes: (2.16.840.1.113730.3.8.23.1 NAME 'memberManager' DESC 'DNs of entries allowed to manage group membership' SUP distinguishedName EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'IPA v4') +objectClasses: (2.16.840.1.113730.3.8.4.4 NAME 'ipaUserGroup' DESC 'IPA user group object class' SUP nestedGroup STRUCTURAL MAY memberManager X-ORIGIN 'IPA v2' ) +objectClasses: (2.16.840.1.113730.3.8.4.5 NAME 'ipaHostGroup' DESC 'IPA host group object class' SUP nestedGroup STRUCTURAL MAY memberManager X-ORIGIN 'IPA v2' ) attributeTypes: (2.16.840.1.113730.3.8.3.5 NAME 'memberUser' DESC 'Reference to a principal that performs an action (usually user).' SUP distinguishedName EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'IPA v2' ) attributeTypes: (2.16.840.1.113730.3.8.3.6 NAME 'userCategory' DESC 'Additional classification for users' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' ) attributeTypes: (2.16.840.1.113730.3.8.3.7 NAME 'memberHost' DESC 'Reference to a device where the operation takes place (usually host).' SUP distinguishedName EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'IPA v2' ) diff --git a/install/share/default-aci.ldif b/install/share/default-aci.ldif index dd15cbe56..d60358b8d 100644 --- a/install/share/default-aci.ldif +++ b/install/share/default-aci.ldif @@ -70,6 +70,19 @@ changetype: modify add: aci aci: (targetattr = "krbPrincipalKey || krbLastPwdChange")(target = "ldap:///fqdn=*,cn=computers,cn=accounts,$SUFFIX")(version 3.0;acl "Admins can manage host keytab";allow (write) groupdn = "ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";) +# Allow member managers to modify members of user groups +dn: cn=groups,cn=accounts,$SUFFIX +changetype: modify +add: aci +aci: (targetattr = "member")(targetfilter = "(objectclass=ipaUserGroup)")(version 3.0; acl "Allow member managers to modify members of user groups"; allow (write) userattr = "memberManager#USERDN" or userattr = "memberManager#GROUPDN";) + +# Allow member managers to modify members of a host group +dn: cn=hostgroups,cn=accounts,$SUFFIX +changetype: modify +add: aci +aci: (targetattr = "member")(targetfilter = "(objectclass=ipaHostGroup)")(version 3.0; acl "Allow member managers to modify members of host groups"; allow (write) userattr = "memberManager#USERDN" or userattr = "memberManager#GROUPDN";) + + # This is used for the host/service one-time passwordn and keytab indirectors. # We can do a query on a DN to see if an attribute exists. dn: cn=accounts,$SUFFIX diff --git a/install/share/indices.ldif b/install/share/indices.ldif index 570c494f2..f5c3829fc 100644 --- a/install/share/indices.ldif +++ b/install/share/indices.ldif @@ -417,3 +417,12 @@ objectClass: top objectClass: nsIndex nsSystemIndex: false nsIndexType: eq + +dn: cn=memberManager,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config +changetype: add +cn: memberManager +objectClass: top +objectClass: nsIndex +nsSystemIndex: false +nsIndexType: eq +nsIndexType: pres diff --git a/install/ui/src/freeipa/entity.js b/install/ui/src/freeipa/entity.js index a7901df93..5c2982b72 100644 --- a/install/ui/src/freeipa/entity.js +++ b/install/ui/src/freeipa/entity.js @@ -442,7 +442,8 @@ exp.entity_builder = IPA.entity_builder = function(entity) { 'member', 'settings', 'memberof', - 'managedby' + 'managedby', + 'membermanager' ]; /** diff --git a/install/ui/src/freeipa/group.js b/install/ui/src/freeipa/group.js index 341154352..1a4352431 100644 --- a/install/ui/src/freeipa/group.js +++ b/install/ui/src/freeipa/group.js @@ -151,6 +151,20 @@ return { } ] }, + { + $type: 'association', + name: 'membermanager_group', + associator: IPA.serial_associator, + add_title: '@i18n:objects.group.add_membermanager_group', + remove_title: '@i18n:objects.group.remove_membermanager_group' + }, + { + $type: 'association', + name: 'membermanager_user', + associator: IPA.serial_associator, + add_title: '@i18n:objects.group.add_membermanager_user', + remove_title: '@i18n:objects.group.remove_membermanager_user' + }, { $type: 'association', name: 'memberof_group', diff --git a/install/ui/src/freeipa/hostgroup.js b/install/ui/src/freeipa/hostgroup.js index 5d599a785..763605194 100644 --- a/install/ui/src/freeipa/hostgroup.js +++ b/install/ui/src/freeipa/hostgroup.js @@ -63,15 +63,31 @@ return { } ] }, + { + $type: 'association', + name: 'membermanager_group', + associator: IPA.serial_associator, + add_title: '@i18n:objects.hostgroup.add_membermanager_group', + remove_title: '@i18n:objects.hostgroup.remove_membermanager_group' + }, + { + $type: 'association', + name: 'membermanager_user', + associator: IPA.serial_associator, + add_title: '@i18n:objects.hostgroup.add_membermanager_user', + remove_title: '@i18n:objects.hostgroup.remove_membermanager_user' + }, { $type: 'association', name: 'member_host', + associator: IPA.serial_associator, add_title: '@i18n:objects.hostgroup.add_hosts', remove_title: '@i18n:objects.hostgroup.remove_hosts' }, { $type: 'association', name: 'member_hostgroup', + associator: IPA.serial_associator, add_title: '@i18n:objects.hostgroup.add_hostgroups', remove_title: '@i18n:objects.hostgroup.remove_hostgroups' }, diff --git a/install/updates/20-aci.update b/install/updates/20-aci.update index 3c08781cd..1f8fa8310 100644 --- a/install/updates/20-aci.update +++ b/install/updates/20-aci.update @@ -132,6 +132,14 @@ add:aci: (targetfilter="(|(objectclass=ipaHost)(objectclass=ipaService))")(targe dn: $SUFFIX add:aci:(targetattr = "usercertificate")(version 3.0;acl "selfservice:Users can manage their own X.509 certificates";allow (write) userdn = "ldap:///self";) +# Allow member managers to modify members of user groups +dn: cn=groups,cn=accounts,$SUFFIX +add:aci: (targetattr = "member")(targetfilter = "(objectclass=ipaUserGroup)")(version 3.0; acl "Allow member managers to modify members of user groups"; allow (write) userattr = "memberManager#USERDN";) + +# Allow member managers to modify members of host groups +dn: cn=hostgroups,cn=accounts,$SUFFIX +add:aci: (targetattr = "member")(targetfilter = "(objectclass=ipaHostGroup)")(version 3.0; acl "Allow member managers to modify members of host groups"; allow (write) userattr = "memberManager#USERDN";) + # Hosts can add and delete their own services dn: cn=services,cn=accounts,$SUFFIX remove:aci: (target = "ldap:///krbprincipalname=*/($$dn)@$REALM,cn=services,cn=accounts,$SUFFIX")(targetfilter = "(objectClass=ipaKrbPrincipal)")(version 3.0;acl "Hosts can add own services"; allow(add) userdn="ldap:///fqdn=($$dn),cn=computers,cn=accounts,$SUFFIX";) diff --git a/install/updates/20-indices.update b/install/updates/20-indices.update index 6798f50c8..ed5029fd1 100644 --- a/install/updates/20-indices.update +++ b/install/updates/20-indices.update @@ -380,3 +380,11 @@ default: objectClass: top default: objectClass: nsIndex default: nsSystemIndex: false default: nsIndexType: eq + +dn: cn=memberManager,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config +default: cn: memberManager +default: objectClass: top +default: objectClass: nsIndex +default: nsSystemIndex: false +default: nsIndexType: eq +default: nsIndexType: pres diff --git a/install/updates/25-referint.update b/install/updates/25-referint.update index b887ede9c..89bc5ef91 100644 --- a/install/updates/25-referint.update +++ b/install/updates/25-referint.update @@ -20,3 +20,4 @@ add: referint-membership-attr: ipaallowedtarget add: referint-membership-attr: ipamemberca add: referint-membership-attr: ipamembercertprofile add: referint-membership-attr: ipalocation +add: referint-membership-attr: membermanager diff --git a/ipaserver/plugins/baseldap.py b/ipaserver/plugins/baseldap.py index eda7f4491..298f12ec1 100644 --- a/ipaserver/plugins/baseldap.py +++ b/ipaserver/plugins/baseldap.py @@ -564,6 +564,11 @@ class LDAPObject(Object): 'memberofindirect': ( 'Indirect Member Of', None, 'not_in_indirect_' ), + 'membermanager': ( + 'Group membership managed by', + 'membermanager_', + 'not_membermanager_' + ), } label = _('Entry') label_singular = _('Entry') diff --git a/ipaserver/plugins/group.py b/ipaserver/plugins/group.py index 9eaf06761..60a79dc6b 100644 --- a/ipaserver/plugins/group.py +++ b/ipaserver/plugins/group.py @@ -114,6 +114,15 @@ EXAMPLES: Display information about a named group. ipa group-show localadmins +Group membership managers are users or groups that can add members to a +group or remove members from a group. + + Allow user "test2" to add or remove members from group "localadmins": + ipa group-add-member-manager --users=test2 localadmins + + Revoke membership management rights for user "test2" from "localadmins": + ipa group-remove-member-manager --users=test2 localadmins + External group membership is designed to allow users from trusted domains to be mapped to local POSIX groups in order to actually use IPA resources. External members should be added to groups that specifically created as @@ -159,6 +168,22 @@ ipaexternalmember_param = Str('ipaexternalmember*', ) +group_output_params = ( + Str( + 'membermanager_group', + label='Membership managed by groups', + ), + Str( + 'membermanager_user', + label='Membership managed by users', + ), + Str( + 'membermanager', + label=_('Failed membermanager'), + ), +) + + @register() class group(LDAPObject): """ @@ -175,10 +200,12 @@ class group(LDAPObject): default_attributes = [ 'cn', 'description', 'gidnumber', 'member', 'memberof', 'memberindirect', 'memberofindirect', 'ipaexternalmember', + 'membermanager', ] uuid_attribute = 'ipauniqueid' attribute_members = { 'member': ['user', 'group', 'service'], + 'membermanager': ['user', 'group'], 'memberof': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'], 'memberindirect': ['user', 'group', 'service'], 'memberofindirect': ['group', 'netgroup', 'role', 'hbacrule', @@ -194,7 +221,7 @@ class group(LDAPObject): 'businesscategory', 'cn', 'description', 'gidnumber', 'ipaexternalmember', 'ipauniqueid', 'mepmanagedby', 'o', 'objectclass', 'ou', 'owner', 'seealso', - 'ipantsecurityidentifier' + 'ipantsecurityidentifier', 'membermanager', }, }, 'System: Read Group Membership': { @@ -248,7 +275,7 @@ class group(LDAPObject): 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'cn', 'description', 'gidnumber', 'ipauniqueid', - 'mepmanagedby', 'objectclass' + 'mepmanagedby', 'objectclass', 'membermanager', }, 'replaces': [ '(targetattr = "cn || description || gidnumber || objectclass || mepmanagedby || ipauniqueid")(target = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Groups";allow (write) groupdn = "ldap:///cn=Modify Groups,cn=permissions,cn=pbac,$SUFFIX";)', @@ -316,6 +343,7 @@ class group(LDAPObject): class group_add(LDAPCreate): __doc__ = _('Create a new group.') + has_output_params = LDAPCreate.has_output_params + group_output_params msg_summary = _('Added group "%(value)s"') takes_options = LDAPCreate.takes_options + ( @@ -397,6 +425,7 @@ class group_del(LDAPDelete): class group_mod(LDAPUpdate): __doc__ = _('Modify a group.') + has_output_params = LDAPUpdate.has_output_params + group_output_params msg_summary = _('Modified group "%(value)s"') takes_options = LDAPUpdate.takes_options + ( @@ -468,8 +497,9 @@ class group_mod(LDAPUpdate): class group_find(LDAPSearch): __doc__ = _('Search for groups.') - member_attributes = ['member', 'memberof'] + member_attributes = ['member', 'memberof', 'membermanager'] + has_output_params = LDAPSearch.has_output_params + group_output_params msg_summary = ngettext( '%(count)d group matched', '%(count)d groups matched', 0 ) @@ -538,6 +568,8 @@ class group_find(LDAPSearch): class group_show(LDAPRetrieve): __doc__ = _('Display information about a named group.') + has_output_params = LDAPRetrieve.has_output_params + group_output_params + def post_callback(self, ldap, dn, entry_attrs, *keys, **options): assert isinstance(dn, DN) if ('ipaexternalmember' in entry_attrs and @@ -736,3 +768,21 @@ class group_detach(LDAPQuery): result=True, value=pkey_to_value(keys[0], options), ) + + +@register() +class group_add_member_manager(LDAPAddMember): + __doc__ = _('Add users that can manage members of this group.') + + has_output_params = LDAPAddMember.has_output_params + group_output_params + member_attributes = ['membermanager'] + + +@register() +class group_remove_member_manager(LDAPRemoveMember): + __doc__ = _('Remove users that can manage members of this group.') + + has_output_params = ( + LDAPRemoveMember.has_output_params + group_output_params + ) + member_attributes = ['membermanager'] diff --git a/ipaserver/plugins/hostgroup.py b/ipaserver/plugins/hostgroup.py index 341b3077b..6293ea797 100644 --- a/ipaserver/plugins/hostgroup.py +++ b/ipaserver/plugins/hostgroup.py @@ -58,6 +58,12 @@ EXAMPLES: Display a host group: ipa hostgroup-show baltimore + Add a member manager: + ipa hostgroup-add-member-manager --users=user1 baltimore + + Remove a member manager + ipa hostgroup-remove-member-manager --users=user1 baltimore + Delete a hostgroup: ipa hostgroup-del baltimore """) @@ -75,6 +81,22 @@ register = Registry() PROTECTED_HOSTGROUPS = (u'ipaservers',) +hostgroup_output_params = ( + Str( + 'membermanager_group', + label='Membership managed by groups', + ), + Str( + 'membermanager_user', + label='Membership managed by users', + ), + Str( + 'membermanager', + label=_('Failed membermanager'), + ), +) + + @register() class hostgroup(LDAPObject): """ @@ -86,12 +108,14 @@ class hostgroup(LDAPObject): object_class = ['ipaobject', 'ipahostgroup'] permission_filter_objectclasses = ['ipahostgroup'] search_attributes = ['cn', 'description', 'member', 'memberof'] - default_attributes = ['cn', 'description', 'member', 'memberof', - 'memberindirect', 'memberofindirect', + default_attributes = [ + 'cn', 'description', 'member', 'memberof', 'memberindirect', + 'memberofindirect', 'membermanager', ] uuid_attribute = 'ipauniqueid' attribute_members = { 'member': ['host', 'hostgroup'], + 'membermanager': ['user', 'group'], 'memberof': ['hostgroup', 'netgroup', 'hbacrule', 'sudorule'], 'memberindirect': ['host', 'hostgroup'], 'memberofindirect': ['hostgroup', 'hbacrule', 'sudorule'], @@ -103,7 +127,7 @@ class hostgroup(LDAPObject): 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'businesscategory', 'cn', 'description', 'ipauniqueid', 'o', - 'objectclass', 'ou', 'owner', 'seealso', + 'objectclass', 'ou', 'owner', 'seealso', 'membermanager', }, }, 'System: Read Hostgroup Membership': { @@ -135,7 +159,7 @@ class hostgroup(LDAPObject): }, 'System: Modify Hostgroups': { 'ipapermright': {'write'}, - 'ipapermdefaultattr': {'cn', 'description'}, + 'ipapermdefaultattr': {'cn', 'description', 'membermanager'}, 'replaces': [ '(targetattr = "cn || description")(target = "ldap:///cn=*,cn=hostgroups,cn=accounts,$SUFFIX")(version 3.0; acl "permission:Modify Hostgroups";allow (write) groupdn = "ldap:///cn=Modify Hostgroups,cn=permissions,cn=pbac,$SUFFIX";)', ], @@ -194,6 +218,7 @@ class hostgroup(LDAPObject): class hostgroup_add(LDAPCreate): __doc__ = _('Add a new hostgroup.') + has_output_params = LDAPCreate.has_output_params + hostgroup_output_params msg_summary = _('Added hostgroup "%(value)s"') def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): @@ -248,6 +273,7 @@ class hostgroup_del(LDAPDelete): class hostgroup_mod(LDAPUpdate): __doc__ = _('Modify a hostgroup.') + has_output_params = LDAPUpdate.has_output_params + hostgroup_output_params msg_summary = _('Modified hostgroup "%(value)s"') def post_callback(self, ldap, dn, entry_attrs, *keys, **options): @@ -260,7 +286,8 @@ class hostgroup_mod(LDAPUpdate): class hostgroup_find(LDAPSearch): __doc__ = _('Search for hostgroups.') - member_attributes = ['member', 'memberof'] + member_attributes = ['member', 'memberof', 'membermanager'] + has_output_params = LDAPSearch.has_output_params + hostgroup_output_params msg_summary = ngettext( '%(count)d hostgroup matched', '%(count)d hostgroups matched', 0 ) @@ -277,6 +304,10 @@ class hostgroup_find(LDAPSearch): class hostgroup_show(LDAPRetrieve): __doc__ = _('Display information about a hostgroup.') + has_output_params = ( + LDAPRetrieve.has_output_params + hostgroup_output_params + ) + def post_callback(self, ldap, dn, entry_attrs, *keys, **options): assert isinstance(dn, DN) self.obj.suppress_netgroup_memberof(ldap, dn, entry_attrs) @@ -313,3 +344,23 @@ class hostgroup_remove_member(LDAPRemoveMember): assert isinstance(dn, DN) self.obj.suppress_netgroup_memberof(ldap, dn, entry_attrs) return (completed, dn) + + +@register() +class hostgroup_add_member_manager(LDAPAddMember): + __doc__ = _('Add users that can manage members of this hostgroup.') + + has_output_params = ( + LDAPAddMember.has_output_params + hostgroup_output_params + ) + member_attributes = ['membermanager'] + + +@register() +class hostgroup_remove_member_manager(LDAPRemoveMember): + __doc__ = _('Remove users that can manage members of this hostgroup.') + + has_output_params = ( + LDAPRemoveMember.has_output_params + hostgroup_output_params + ) + member_attributes = ['membermanager'] diff --git a/ipaserver/plugins/internal.py b/ipaserver/plugins/internal.py index 8b7356e36..6ea5c99ac 100644 --- a/ipaserver/plugins/internal.py +++ b/ipaserver/plugins/internal.py @@ -301,6 +301,7 @@ class i18n_messages(Command): "managedby": _("${primary_key} is managed by:"), "member": _("${primary_key} members:"), "memberof": _("${primary_key} is a member of:"), + "membermanager": _("${primary_key} member managers:"), }, "facets": { "details": _("Settings"), @@ -814,6 +815,22 @@ class i18n_messages(Command): "add_users": _( "Add users into user group '${primary_key}'" ), + "add_membermanager_group": _( + "Add groups as member managers for user group " + "'${primary_key}'" + ), + "remove_membermanager_group": _( + "Remove groups from member managers for user group " + "'${primary_key}'" + ), + "add_membermanager_user": _( + "Add users as member managers for user group " + "'${primary_key}'" + ), + "remove_membermanager_user": _( + "Remove users from member managers for user group " + "'${primary_key}'" + ), "details": _("Group Settings"), "external": _("External"), "groups": _("Groups"), @@ -1026,6 +1043,22 @@ class i18n_messages(Command): "add_into_sudo": _( "Add host group '${primary_key}' into sudo rules" ), + "add_membermanager_group": _( + "Add groups as member managers for host group " + "'${primary_key}'" + ), + "remove_membermanager_group": _( + "Remove groups from member managers for host group " + "'${primary_key}'" + ), + "add_membermanager_user": _( + "Add users as member managers for host group " + "'${primary_key}'" + ), + "remove_membermanager_user": _( + "Remove users from member managers for host group " + "'${primary_key}'" + ), "host_group": _("Host Groups"), "identity": _("Host Group Settings"), "remove": _("Remove host groups"),