Redesign subid feature

Subordinate ids are now handled by a new plugin class and stored in
separate entries in the cn=subids,cn=accounts subtree.

Signed-off-by: Christian Heimes <cheimes@redhat.com>
Reviewed-By: Francois Cami <fcami@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
This commit is contained in:
Christian Heimes
2021-04-14 15:21:18 +02:00
committed by Rob Crittenden
parent 1c4ae37293
commit c78d1341ad
26 changed files with 1384 additions and 586 deletions

12
ACI.txt
View File

@@ -61,7 +61,7 @@ aci: (targetattr = "cn || description || ipacertprofilestoreissued")(targetfilte
dn: cn=certprofiles,cn=ca,dc=ipa,dc=example
aci: (targetattr = "cn || createtimestamp || description || entryusn || ipacertprofilestoreissued || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipacertprofile)")(version 3.0;acl "permission:System: Read Certificate Profiles";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=ipaconfig,cn=etc,dc=ipa,dc=example
aci: (targetattr = "cn || createtimestamp || entryusn || ipacertificatesubjectbase || ipaconfigstring || ipacustomfields || ipadefaultemaildomain || ipadefaultloginshell || ipadefaultprimarygroup || ipadomainresolutionorder || ipagroupobjectclasses || ipagroupsearchfields || ipahomesrootdir || ipakrbauthzdata || ipamaxhostnamelength || ipamaxusernamelength || ipamigrationenabled || ipapwdexpadvnotify || ipasearchrecordslimit || ipasearchtimelimit || ipaselinuxusermapdefault || ipaselinuxusermaporder || ipauserauthtype || ipauserobjectclasses || ipausersearchfields || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipaguiconfig)")(version 3.0;acl "permission:System: Read Global Configuration";allow (compare,read,search) userdn = "ldap:///all";)
aci: (targetattr = "cn || createtimestamp || entryusn || ipacertificatesubjectbase || ipaconfigstring || ipacustomfields || ipadefaultemaildomain || ipadefaultloginshell || ipadefaultprimarygroup || ipadomainresolutionorder || ipagroupobjectclasses || ipagroupsearchfields || ipahomesrootdir || ipakrbauthzdata || ipamaxhostnamelength || ipamaxusernamelength || ipamigrationenabled || ipapwdexpadvnotify || ipasearchrecordslimit || ipasearchtimelimit || ipaselinuxusermapdefault || ipaselinuxusermaporder || ipauserauthtype || ipauserdefaultsubordinateid || ipauserobjectclasses || ipausersearchfields || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipaguiconfig)")(version 3.0;acl "permission:System: Read Global Configuration";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=costemplates,cn=accounts,dc=ipa,dc=example
aci: (targetfilter = "(objectclass=costemplate)")(version 3.0;acl "permission:System: Add Group Password Policy costemplate";allow (add) groupdn = "ldap:///cn=System: Add Group Password Policy costemplate,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=costemplates,cn=accounts,dc=ipa,dc=example
@@ -318,6 +318,14 @@ dn: cn=deleted users,cn=accounts,cn=provisioning,dc=ipa,dc=example
aci: (targetattr = "krblastpwdchange || krbpasswordexpiration || krbprincipalkey || userpassword")(target = "ldap:///uid=*,cn=deleted users,cn=accounts,cn=provisioning,dc=ipa,dc=example")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Reset Preserved User password";allow (read,search,write) groupdn = "ldap:///cn=System: Reset Preserved User password,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: dc=ipa,dc=example
aci: (target_to = "ldap:///cn=users,cn=accounts,dc=ipa,dc=example")(target_from = "ldap:///cn=deleted users,cn=accounts,cn=provisioning,dc=ipa,dc=example")(targetfilter = "(objectclass=nsContainer)")(version 3.0;acl "permission:System: Undelete User";allow (moddn) groupdn = "ldap:///cn=System: Undelete User,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=subids,cn=accounts,dc=ipa,dc=example
aci: (targetattr = "description || ipaowner")(targetfilter = "(objectclass=ipasubordinateidentry)")(version 3.0;acl "permission:System: Manage Subordinate Ids";allow (write) groupdn = "ldap:///cn=System: Manage Subordinate Ids,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=subids,cn=accounts,dc=ipa,dc=example
aci: (targetattr = "createtimestamp || description || entryusn || ipaowner || ipasubgidcount || ipasubgidnumber || ipasubuidcount || ipasubuidnumber || ipauniqueid || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipasubordinateidentry)")(version 3.0;acl "permission:System: Read Subordinate Id Attributes";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=subids,cn=accounts,dc=ipa,dc=example
aci: (targetattr = "numsubordinates")(target = "ldap:///cn=subids,cn=accounts,dc=ipa,dc=example")(version 3.0;acl "permission:System: Read Subordinate Id Count";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=subids,cn=accounts,dc=ipa,dc=example
aci: (targetfilter = "(objectclass=ipasubordinateidentry)")(version 3.0;acl "permission:System: Remove Subordinate Ids";allow (delete) groupdn = "ldap:///cn=System: Remove Subordinate Ids,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=sudocmds,cn=sudo,dc=ipa,dc=example
aci: (targetfilter = "(objectclass=ipasudocmd)")(version 3.0;acl "permission:System: Add Sudo Command";allow (add) groupdn = "ldap:///cn=System: Add Sudo Command,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=sudocmds,cn=sudo,dc=ipa,dc=example
@@ -375,7 +383,7 @@ aci: (targetattr = "audio || businesscategory || carlicense || departmentnumber
dn: dc=ipa,dc=example
aci: (targetattr = "cn || createtimestamp || entryusn || gecos || gidnumber || homedirectory || loginshell || modifytimestamp || objectclass || uid || uidnumber")(target = "ldap:///cn=users,cn=compat,dc=ipa,dc=example")(version 3.0;acl "permission:System: Read User Compat Tree";allow (compare,read,search) userdn = "ldap:///anyone";)
dn: cn=users,cn=accounts,dc=ipa,dc=example
aci: (targetattr = "ipasshpubkey || ipasubgidcount || ipasubgidnumber || ipasubuidcount || ipasubuidnumber || ipauniqueid || ipauserauthtype || userclass")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User IPA Attributes";allow (compare,read,search) userdn = "ldap:///all";)
aci: (targetattr = "ipasshpubkey || ipauniqueid || ipauserauthtype || userclass")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User IPA Attributes";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=users,cn=accounts,dc=ipa,dc=example
aci: (targetattr = "krbcanonicalname || krblastpwdchange || krbpasswordexpiration || krbprincipalaliases || krbprincipalexpiration || krbprincipalname || krbprincipaltype || nsaccountlock")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User Kerberos Attributes";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=users,cn=accounts,dc=ipa,dc=example

153
API.txt
View File

@@ -1076,7 +1076,7 @@ args: 0,1,1
option: Str('version?')
output: Output('result')
command: config_mod/1
args: 0,28,3
args: 0,29,3
option: Str('addattr*', cli_name='addattr')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('ca_renewal_master_server?', autofill=False)
@@ -1099,6 +1099,7 @@ option: Int('ipasearchtimelimit?', autofill=False, cli_name='searchtimelimit')
option: Str('ipaselinuxusermapdefault?', autofill=False)
option: Str('ipaselinuxusermaporder?', autofill=False)
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened', u'disabled'])
option: Bool('ipauserdefaultsubordinateid?', autofill=False, cli_name='user_default_subid')
option: Str('ipauserobjectclasses*', autofill=False, cli_name='userobjectclasses')
option: IA5Str('ipausersearchfields?', autofill=False, cli_name='usersearch')
option: Flag('raw', autofill=True, cli_name='raw', default=False)
@@ -4974,7 +4975,7 @@ output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
command: stageuser_add/1
args: 1,46,3
args: 1,45,3
arg: Str('uid', cli_name='login')
option: Str('addattr*', cli_name='addattr')
option: Flag('all', autofill=True, cli_name='all', default=False)
@@ -4992,7 +4993,6 @@ option: Str('givenname', cli_name='first')
option: Str('homedirectory?', cli_name='homedir')
option: Str('initials?', autofill=True)
option: Str('ipasshpubkey*', cli_name='sshpubkey')
option: Int('ipasubuidnumber?', cli_name='subuid')
option: Str('ipatokenradiusconfiglink?', cli_name='radius')
option: Str('ipatokenradiususername?', cli_name='radius_username')
option: StrEnum('ipauserauthtype*', cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
@@ -5099,14 +5099,13 @@ option: Str('in_group*', cli_name='in_groups')
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_subid*', cli_name='in_subids')
option: Str('in_sudorule*', cli_name='in_sudorules')
option: Str('initials?', autofill=False)
option: Str('ipanthomedirectory?', autofill=False, cli_name='smb_home_dir')
option: StrEnum('ipanthomedirectorydrive?', autofill=False, cli_name='smb_home_drive', values=[u'A:', u'B:', u'C:', u'D:', u'E:', u'F:', u'G:', u'H:', u'I:', u'J:', u'K:', u'L:', u'M:', u'N:', u'O:', u'P:', u'Q:', u'R:', u'S:', u'T:', u'U:', u'V:', u'W:', u'X:', u'Y:', u'Z:'])
option: Str('ipantlogonscript?', autofill=False, cli_name='smb_logon_script')
option: Str('ipantprofilepath?', autofill=False, cli_name='smb_profile_path')
option: Int('ipasubgidnumber?', autofill=False, cli_name='subgid')
option: Int('ipasubuidnumber?', autofill=False, cli_name='subuid')
option: Str('ipatokenradiusconfiglink?', autofill=False, cli_name='radius')
option: Str('ipatokenradiususername?', autofill=False, cli_name='radius_username')
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
@@ -5123,6 +5122,7 @@ option: Str('not_in_group*', cli_name='not_in_groups')
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_subid*', cli_name='not_in_subids')
option: Str('not_in_sudorule*', cli_name='not_in_sudorules')
option: Str('ou?', autofill=False, cli_name='orgunit')
option: Str('pager*', autofill=False)
@@ -5148,7 +5148,7 @@ output: ListOfEntries('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: Output('truncated', type=[<type 'bool'>])
command: stageuser_mod/1
args: 1,52,3
args: 1,51,3
arg: Str('uid', cli_name='login')
option: Str('addattr*', cli_name='addattr')
option: Flag('all', autofill=True, cli_name='all', default=False)
@@ -5170,7 +5170,6 @@ option: StrEnum('ipanthomedirectorydrive?', autofill=False, cli_name='smb_home_d
option: Str('ipantlogonscript?', autofill=False, cli_name='smb_logon_script')
option: Str('ipantprofilepath?', autofill=False, cli_name='smb_profile_path')
option: Str('ipasshpubkey*', autofill=False, cli_name='sshpubkey')
option: Int('ipasubuidnumber?', autofill=False, cli_name='subuid')
option: Str('ipatokenradiusconfiglink?', autofill=False, cli_name='radius')
option: Str('ipatokenradiususername?', autofill=False, cli_name='radius_username')
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
@@ -5263,6 +5262,100 @@ option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
command: subid_add/1
args: 1,8,3
arg: Str('ipauniqueid?', cli_name='id')
option: Str('addattr*', cli_name='addattr')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('description?', cli_name='desc')
option: Str('ipaowner', cli_name='owner')
option: Int('ipasubuidnumber?', cli_name='subuid')
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Str('setattr*', cli_name='setattr')
option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
command: subid_del/1
args: 1,2,3
arg: Str('ipauniqueid+', cli_name='id')
option: Flag('continue', autofill=True, cli_name='continue', default=False)
option: Str('version?')
output: Output('result', type=[<type 'dict'>])
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: ListOfPrimaryKeys('value')
command: subid_find/1
args: 1,11,4
arg: Str('criteria?')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('description?', autofill=False, cli_name='desc')
option: Str('ipaowner?', autofill=False, cli_name='owner')
option: Int('ipasubgidnumber?', autofill=False, cli_name='subgid')
option: Int('ipasubuidnumber?', autofill=False, cli_name='subuid')
option: Str('ipauniqueid?', autofill=False, cli_name='id')
option: Flag('pkey_only?', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Int('sizelimit?', autofill=False)
option: Int('timelimit?', autofill=False)
option: Str('version?')
output: Output('count', type=[<type 'int'>])
output: ListOfEntries('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: Output('truncated', type=[<type 'bool'>])
command: subid_generate/1
args: 0,4,3
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('ipaowner?', cli_name='owner')
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
command: subid_match/1
args: 1,7,4
arg: Str('criteria?')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Int('ipasubuidnumber', autofill=False, cli_name='subuid')
option: Flag('pkey_only?', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Int('sizelimit?', autofill=False)
option: Int('timelimit?', autofill=False)
option: Str('version?')
output: Output('count', type=[<type 'int'>])
output: ListOfEntries('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: Output('truncated', type=[<type 'bool'>])
command: subid_mod/1
args: 1,8,3
arg: Str('ipauniqueid', cli_name='id')
option: Str('addattr*', cli_name='addattr')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('delattr*', cli_name='delattr')
option: Str('description?', autofill=False, cli_name='desc')
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Flag('rights', autofill=True, default=False)
option: Str('setattr*', cli_name='setattr')
option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
command: subid_show/1
args: 1,4,3
arg: Str('ipauniqueid', cli_name='id')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Flag('rights', autofill=True, default=False)
option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
command: subid_stats/1
args: 0,3,2
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
command: sudocmd_add/1
args: 1,7,3
arg: Str('sudocmd', cli_name='command')
@@ -6062,7 +6155,7 @@ output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
command: user_add/1
args: 1,47,3
args: 1,46,3
arg: Str('uid', cli_name='login')
option: Str('addattr*', cli_name='addattr')
option: Flag('all', autofill=True, cli_name='all', default=False)
@@ -6079,7 +6172,6 @@ option: Str('givenname', cli_name='first')
option: Str('homedirectory?', cli_name='homedir')
option: Str('initials?', autofill=True)
option: Str('ipasshpubkey*', cli_name='sshpubkey')
option: Int('ipasubuidnumber?', cli_name='subuid')
option: Str('ipatokenradiusconfiglink?', cli_name='radius')
option: Str('ipatokenradiususername?', cli_name='radius_username')
option: StrEnum('ipauserauthtype*', cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
@@ -6161,16 +6253,6 @@ option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
command: user_auto_subid/1
args: 1,4,3
arg: Str('uid', cli_name='login')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
command: user_del/1
args: 1,3,3
arg: Str('uid+', cli_name='login')
@@ -6213,14 +6295,13 @@ option: Str('in_group*', cli_name='in_groups')
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_subid*', cli_name='in_subids')
option: Str('in_sudorule*', cli_name='in_sudorules')
option: Str('initials?', autofill=False)
option: Str('ipanthomedirectory?', autofill=False, cli_name='smb_home_dir')
option: StrEnum('ipanthomedirectorydrive?', autofill=False, cli_name='smb_home_drive', values=[u'A:', u'B:', u'C:', u'D:', u'E:', u'F:', u'G:', u'H:', u'I:', u'J:', u'K:', u'L:', u'M:', u'N:', u'O:', u'P:', u'Q:', u'R:', u'S:', u'T:', u'U:', u'V:', u'W:', u'X:', u'Y:', u'Z:'])
option: Str('ipantlogonscript?', autofill=False, cli_name='smb_logon_script')
option: Str('ipantprofilepath?', autofill=False, cli_name='smb_profile_path')
option: Int('ipasubgidnumber?', autofill=False, cli_name='subgid')
option: Int('ipasubuidnumber?', autofill=False, cli_name='subuid')
option: Str('ipatokenradiusconfiglink?', autofill=False, cli_name='radius')
option: Str('ipatokenradiususername?', autofill=False, cli_name='radius_username')
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
@@ -6237,6 +6318,7 @@ option: Str('not_in_group*', cli_name='not_in_groups')
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_subid*', cli_name='not_in_subids')
option: Str('not_in_sudorule*', cli_name='not_in_sudorules')
option: Bool('nsaccountlock?', autofill=False, cli_name='disabled', default=False)
option: Str('ou?', autofill=False, cli_name='orgunit')
@@ -6264,23 +6346,8 @@ output: Output('count', type=[<type 'int'>])
output: ListOfEntries('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: Output('truncated', type=[<type 'bool'>])
command: user_match_subid/1
args: 1,8,4
arg: Str('criteria?')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Int('ipasubuidnumber', autofill=False, cli_name='subuid')
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)
option: Int('timelimit?', autofill=False)
option: Str('version?')
output: Output('count', type=[<type 'int'>])
output: ListOfEntries('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: Output('truncated', type=[<type 'bool'>])
command: user_mod/1
args: 1,53,3
args: 1,52,3
arg: Str('uid', cli_name='login')
option: Str('addattr*', cli_name='addattr')
option: Flag('all', autofill=True, cli_name='all', default=False)
@@ -6302,7 +6369,6 @@ option: StrEnum('ipanthomedirectorydrive?', autofill=False, cli_name='smb_home_d
option: Str('ipantlogonscript?', autofill=False, cli_name='smb_logon_script')
option: Str('ipantprofilepath?', autofill=False, cli_name='smb_profile_path')
option: Str('ipasshpubkey*', autofill=False, cli_name='sshpubkey')
option: Int('ipasubuidnumber?', autofill=False, cli_name='subuid')
option: Str('ipatokenradiusconfiglink?', autofill=False, cli_name='radius')
option: Str('ipatokenradiususername?', autofill=False, cli_name='radius_username')
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
@@ -7138,6 +7204,15 @@ default: stageuser_remove_certmapdata/1
default: stageuser_remove_manager/1
default: stageuser_remove_principal/1
default: stageuser_show/1
default: subid/1
default: subid_add/1
default: subid_del/1
default: subid_find/1
default: subid_generate/1
default: subid_match/1
default: subid_mod/1
default: subid_show/1
default: subid_stats/1
default: sudocmd/1
default: sudocmd_add/1
default: sudocmd_del/1
@@ -7216,12 +7291,10 @@ default: user_add_cert/1
default: user_add_certmapdata/1
default: user_add_manager/1
default: user_add_principal/1
default: user_auto_subid/1
default: user_del/1
default: user_disable/1
default: user_enable/1
default: user_find/1
default: user_match_subid/1
default: user_mod/1
default: user_remove_cert/1
default: user_remove_certmapdata/1

View File

@@ -1,5 +1,9 @@
# Central management of subordinate user and group ids
## OUTDATED
**The design document does not reflect new implementation yet!**
Subordinate ids are a Linux Kernel feature to grant a user additional
user and group id ranges. Amongst others the feature can be used
by container runtime engies to implement rootless containers.
@@ -74,8 +78,10 @@ basic use cases. Some restrictions may be lifted in the future.
to the same value.
* counts are hard-coded to value 65536
* once assigned subids cannot be removed
* IPA does not support multiple subordinate id ranges. Contrary to
``/etc/subuid``, users are limited to one set of subordinate ids.
* IPA does not support multiple subordinate id ranges, yet. Contrary to
``/etc/subuid``, users are limited to one set of subordinate ids. The
limitation is implemented with a unique index on owner reference and
can be lifted in the future.
* subids are auto-assigned. Auto-assignment is currently emulated
until 389-DS has been extended to support DNA with step interval.
* subids are allocated from hard-coded range
@@ -118,20 +124,21 @@ to servers. The DNA plug-in guarantees uniqueness across servers.
### LDAP schema extension
The subordinate id feature introduces a new auxiliar object class
``ipaSubordinateId`` with four required attributes ``ipaSubUidNumber``,
``ipaSubUidCount``, ``ipaSubGidNumber``, and ``ipaSubGidCount``. The
attributes with ``number`` suffix store the start value of the interval.
The ``count`` attributes contain the size of the interval including the
start value. The maximum subid is
``ipaSubUidNumber + ipaSubUidCount - 1``.
``ipaSubordinateId`` with five required attributes ``ipaOwner``,
``ipaSubUidNumber``, ``ipaSubUidCount``, ``ipaSubGidNumber``, and
``ipaSubGidCount``. The attributes with ``number`` suffix store the
start value of the interval. The ``count`` attributes contain the
size of the interval including the start value. The maximum subid is
``ipaSubUidNumber + ipaSubUidCount - 1``. The ``ipaOwner`` attribute
is a reference to the owning user.
All four attributes are single-value ``INTEGER`` type with standard
integer matching rules. OIDs ``2.16.840.1.113730.3.8.23.8`` and
All count and number attributes are single-value ``INTEGER`` type with
standard integer matching rules. OIDs ``2.16.840.1.113730.3.8.23.8`` and
``2.16.840.1.113730.3.8.23.11`` are reserved for future use.
```raw
attributeTypes: (
2.16.840.1.113730.3.8.23.6
2.16.840.1.113730.3.8.23.7
NAME 'ipaSubUidNumber'
DESC 'Numerical subordinate user ID (range start value)'
EQUALITY integerMatch ORDERING integerOrderingMatch
@@ -139,7 +146,7 @@ attributeTypes: (
X-ORIGIN 'IPA v4.9'
)
attributeTypes: (
2.16.840.1.113730.3.8.23.7
2.16.840.1.113730.3.8.23.8
NAME 'ipaSubUidCount'
DESC 'Subordinate user ID count (range size)'
EQUALITY integerMatch ORDERING integerOrderingMatch
@@ -147,7 +154,7 @@ attributeTypes: (
X-ORIGIN 'IPA v4.9'
)
attributeTypes: (
2.16.840.1.113730.3.8.23.9
2.16.840.1.113730.3.8.23.10
NAME 'ipaSubGidNumber'
DESC 'Numerical subordinate group ID (range start value)'
EQUALITY integerMatch ORDERING integerOrderingMatch
@@ -155,7 +162,7 @@ attributeTypes: (
X-ORIGIN 'IPA v4.9'
)
attributeTypes: (
2.16.840.1.113730.3.8.23.10
2.16.840.1.113730.3.8.23.11
NAME 'ipaSubGidCount'
DESC 'Subordinate group ID count (range size)'
EQUALITY integerMatch ORDERING integerOrderingMatch
@@ -164,51 +171,96 @@ attributeTypes: (
)
```
The ``ipaSubordinateId`` object class is an auxiliar subclass of
The ``ipaOwner`` attribute is a single-value DN attribute that refers
to user entry that owns the subordinate ID entry. The proposal does not
reuse any of the existing attributes like ``owner`` or ``member``,
because they are all multi-valued.
```
attributeTypes: (
2.16.840.1.113730.3.8.23.13
NAME 'ipaOwner'
DESC 'Owner of an entry'
SUP distinguishedName
EQUALITY distinguishedNameMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
SINGLE-VALUE
X-ORIGIN 'IPA v4.9')
```
The ``ipaSubordinateId`` object class is an auxiliary subclass of
``top`` and requires all four subordinate id attributes as well as
``uidNumber``. It does not subclass ``posixAccount`` to make
the class reusable in idview overrides later.
``ipaOwner`` attribute``
```raw
objectClasses: (
2.16.840.1.113730.3.8.24.4
NAME 'ipaSubordinateId'
DESC 'Subordinate uid and gid for users'
SUP top AUXILIARY
MUST ( uidNumber $ ipaSubUidNumber $ ipaSubUidCount $ ipaSubGidNumber $ ipaSubGidCount )
SUP top
AUXILIARY
MUST ( ipaOwner $ ipaSubUidNumber $ ipaSubUidCount $ ipaSubGidNumber $ ipaSubGidCount )
X-ORIGIN 'IPA v4.9'
)
```
The ``ipaSubordinateGid`` and ``ipaSubordinateUid`` are defined for
future use. IPA always assumes the presence of ``ipaSubordinateId`` and
does not use these object classes.
future use. IPA always assumes the presence of ``ipaSubordinateId``.
```raw
objectClasses: (
2.16.840.1.113730.3.8.24.2
NAME 'ipaSubordinateUid'
DESC 'Subordinate uids for users, see subuid(5)'
SUP top AUXILIARY
MUST ( uidNumber $ ipaSubUidNumber $ ipaSubUidCount )
SUP top
AUXILIARY
MUST ( ipaOwner $ ipaSubUidNumber $ ipaSubUidCount )
X-ORIGIN 'IPA v4.9'
)
objectClasses: (
2.16.840.1.113730.3.8.24.3
NAME 'ipaSubordinateGid'
DESC 'Subordinate gids for users, see subgid(5)'
SUP top AUXILIARY
MUST ( uidNumber $ ipaSubGidNumber $ ipaSubGidCount )
SUP top
AUXILIARY
MUST ( ipaOwner $ ipaSubGidNumber $ ipaSubGidCount )
X-ORIGIN 'IPA v4.9'
)
```
### Index
Subordinate id entries have the structural object class
``ipaSubordinateIdEntry`` and one or more of the auxiliary object
classes ``ipaSubordinateId``, ``ipaSubordinateGid``, or
``ipaSubordinateUid``. ``ipaUniqueId`` is used as a primary key (RDN).
```raw
objectClasses: (
2.16.840.1.113730.3.8.24.5
NAME 'ipaSubordinateIdEntry'
DESC 'Subordinate uid and gid entry'
SUP top
STRUCTURAL
MUST ( ipaUniqueId ) MAY ( description ) X-ORIGIN 'IPA v4.9'
)
```
### cn=subids,cn=accounts,$SUFFIX
Subordiante ids and ACIs are stored in the new subtree
``cn=subids,cn=accounts,$SUFFIX``.
### Index, integrity, memberOf
The attributes ``ipaSubUidNumber`` and ``ipaSubGidNumber`` are index
for ``pres`` and ``eq`` with ``nsMatchingRule: integerOrderingMatch``
to enable efficient ``=``, ``>=``, and ``<=`` searches.
The attribute ``ipaOwner`` is indexed for ``pres`` and ``eq``. This DN
attribute is also checked for referential integrity and uniqueness
within the ``cn=subids,cn=accounts,$SUFFIX`` subtree. The memberOf
plugin creates back-references for ``ipaOwner`` references.
### Distributed numeric assignment (DNA) plug-in extension
Subordinate id auto-assignment requires an extension of 389-DS'
@@ -221,6 +273,85 @@ option ``dnaStepAttr`` (name is tentative) will tell the DNA plug-in
to use the value of entry attributes as step size.
## IPA plugins and commands
The config plugin has a new option to enable or disable generation of
subordinate id entries for new users:
```raw
$ ipa config-mod --user-default-subid=true
```
Subordinate ids are managed by a new plugin class. The ``subid-add``
and ``subid-del`` commands are hidden from command line. New subordinate
ids are generated and auto-assigned with ``subid-generate``.
```raw
$ ipa help subid
Topic commands:
subid-find Search for subordinate id.
subid-generate Generate and auto-assign subuid and subgid range to user entry
subid-match Match users by any subordinate uid in their range
subid-mod Modify a subordinate id.
subid-show Display information about a subordinate id.
subid-stats Subordinate id statistics
```
```raw
$ ipa subid-generate --owner testuser9
-----------------------------------------------------------
Added subordinate id "aa28f132-457c-488b-82e1-d123727e4f81"
-----------------------------------------------------------
Unique ID: aa28f132-457c-488b-82e1-d123727e4f81
Description: auto-assigned subid
Owner: testuser9
SubUID range start: 3922132992
SubUID range size: 65536
SubGID range start: 3922132992
SubGID range size: 65536
```
```raw
$ ipa subid-find --owner testuser9
------------------------
1 subordinate id matched
------------------------
Unique ID: aa28f132-457c-488b-82e1-d123727e4f81
Owner: testuser9
SubUID range start: 3922132992
SubUID range size: 65536
SubGID range start: 3922132992
SubGID range size: 65536
----------------------------
Number of entries returned 1
----------------------------
```
```raw
$ ipa -vv subid-stats
...
ipa: INFO: Response: {
"error": null,
"id": 0,
"principal": "admin@IPASUBID.TEST",
"result": {
"result": {
"assigned_subids": 20,
"baseid": 2147483648,
"dna_remaining": 4293394434,
"rangesize": 2147352576,
"remaining_subids": 65512
},
"summary": "65532 remaining subordinate id ranges"
},
"version": "4.10.0.dev"
}
-------------------------------------
65532 remaining subordinate id ranges
-------------------------------------
```
## Permissions, Privileges, Roles
### Self-servive RBAC
@@ -246,6 +377,13 @@ be modified or deleted.
* Privilege: *Subordinate ID Administrators*
* default privilege role: *User Administrator*
### Managed permissions
* *System: Read Subordinate Id Attributes* (all authenticated users)
* *System: Read Subordinate Id Count* (all authenticated usrs)
* *System: Manage Subordinate Ids* (User Administrators)
* *System: Remove Subordinate Ids* (User Administrators)
## Workflows
@@ -257,12 +395,12 @@ to assign subordinate ids to users.
Users with *User Administrator* role and members of the *admins* group
have permission to auto-assign new subordinate ids to any user. Auto
assignment can be performed with new ``user-auto-subid`` command on the
assignment can be performed with new ``subid-generate`` command on the
command line or with the *Auto assign subordinate ids* action in the
*Actions* drop-down menu in the web UI.
```shell
$ ipa user-auto-subid someusername
$ ipa subid-generate --owner myusername
```
### Self-service for group members
@@ -279,27 +417,14 @@ $ ipa role-add-member "Subordinate ID Selfservice User" --groups=ipausers
```
This allows members of ``ipausers`` to request subordinate ids with
the ``user-auto-subid`` command or the *Auto assign subordinate ids*
action in the web UI.
the ``subid-generate`` command or the *Auto assign subordinate ids*
action in the web UI (**TODO** not implemented yet). The command picks
the name of the current user principal automatically.
```shell
$ ipa user-auto-subid myusername
$ ipa subid-generate
```
### Auto assignment with user default object class
Admins can also enable auto-assignment of subordinate ids for all new
users by adding ``ipasubordinateid`` as a default user objectclass.
This can be accomplished in the web UI under "IPA Server" /
"Configuration" / "Default user objectclasses" or on the command line
with:
```shell
$ ipa config-mod --addattr="ipaUserObjectClasses=ipasubordinateid"
```
**NOTE:** The objectclass must be written all lower case.
### ipa-subid tool
Finally IPA includes a new tool for mass-assignment of subordinate ids.
@@ -355,26 +480,25 @@ gid range. The new command ``user-match-subid`` can be used to find a
user by any subordinate id in their range.
```raw
$ ipa user-match-subid --subuid=2153185287
User login: asmith
First name: Alice
Last name: Smith
...
SubUID range start: 2153185280
$ ipa subid-match --subuid=2147549183
------------------------
1 subordinate id matched
------------------------
Name: asmith-auto
Owner: asmith
SubUID range start: 2147483648
SubUID range size: 65536
SubGID range start: 2153185280
SubGID range start: 2147483648
SubGID range size: 65536
----------------------------
Number of entries returned 1
----------------------------
$ ipa user-match-subid --subuid=2153185279
User login: bjones
First name: Bob
Last name: Jones
...
SubUID range start: 2153119744
$ ipa user-match-subid --subuid=2147549184
Name: bjones-auto
Owner: bjones
SubUID range start: 2147549184
SubUID range size: 65536
SubGID range start: 2153119744
SubGID range start: 2147549184
SubGID range size: 65536
----------------------------
Number of entries returned 1
@@ -383,61 +507,54 @@ Number of entries returned 1
## SSSD integration
* base: ``cn=accounts,$SUFFIX`` / ``cn=users,cn=accounts,$SUFFIX``
* scope: ``SCOPE_SUBTREE`` (2) / ``SCOPE_ONELEVEL`` (1)
* user filter: should include ``(objectClass=posixAccount)``
* attributes: ``uidNumber ipaSubUidNumber ipaSubUidCount ipaSubGidNumber ipaSubGidCount``
SSSD can safely assume that only *user accounts* of type ``posixAccount``
have subordinate ids. In the first revision there are no other entries
with subordinate ids. The ``posixAccount`` object class has ``uid``
(user login name) and ``uidNumber`` (numeric user id) as mandatory
attributes. The ``uid`` attribute is guaranteed to be unique across
all user accounts in an IPA domain.
The ``uidNumber`` attribute is commonly unique, too. However it's
technically possible that an administrator has assigned the same
numeric user id to multiple users. Automatically assigned uid numbers
don't conflict. SSSD should treat multiple users with same numeric
user id as an error.
* search base: ``cn=subids,cn=accounts,$SUFFIX``
* scope: ``SCOPE_ONELEVEL`` (1)
* filter: ``(objectClass=ipaSubordinateId)``
* attributes: ``ipaOwner ipaSubUidNumber ipaSubUidCount ipaSubGidNumber ipaSubGidCount``
The attribute ``ipaSubUidNumber`` is always accompanied by
``ipaSubUidCount`` and ``ipaSubGidNumber`` is always accompanied
by ``ipaSubGidCount``. In revision 1 the presence of
``ipaSubUidNumber`` implies presence of the other three attributes.
All four subordinate id attributes and ``uidNumber`` are single-value
``INTEGER`` types. Any value outside of range of ``uint32_t`` must
treated as invalid. SSSD will never see the DNA magic value ``-1``
in ``cn=accounts,$SUFFIX`` subtree.
All four subordinate id attributes are single-value ``INTEGER`` types.
Any value outside of range of ``uint32_t`` must treated as invalid.
SSSD will never see the DNA magic value ``-1`` in
``cn=accounts,$SUFFIX`` subtree. In revision 1 each user subordinate
id entry is assigned to exactly one user and each user has either 0
or 1 subid.
IPA recommends that SSSD simply extends its existing query for user
accounts and requests the four subordinate attributes additionally to
RFC 2307 attributes ``rfc2307_user_map``. SSSD can directly take the
values and return them without further processing, e.g.
``uidNumber:ipaSubUidNumber:ipaSubUidCount`` for ``/etc/subuid``.
IPA recommends that SSSD uses LDAP deref controls for ``ipaOwner``
attribute to fetch ``uidNumber`` from the user object.
Filters for additional cases:
### Deref control example
* subuid filter (find user with subuid by numeric uid):
``&((objectClass=posixAccount)(ipaSubUidNumber=*)(uidNumber=$UID))``,
``(&(objectClass=ipaSubordinateId)(uidNumber=$UID))``, or similar
* subuid enumeration filter:
``&((objectClass=posixAccount)(ipaSubUidNumber=*)(uidNumber=*))``,
``(objectClass=ipaSubordinateId)``, or similar
* subgid filter (find user with subgid by numeric uid):
``&((objectClass=posixAccount)(ipaSubGidNumber=*)(uidNumber=$UID))``,
``(&(objectClass=ipaSubordinateId)(uidNumber=$UID))``, or similar
* subgid enumeration filter:
``&((objectClass=posixAccount)(ipaSubGidNumber=*)(uidNumber=*))``,
``(objectClass=ipaSubordinateId)``, or similar
```raw
$ ldapsearch -L -E '!deref=ipaOwner:uid,uidNumber' \
-b 'cn=subids,cn=accounts,dc=ipasubid,dc=test' \
'(ipaOwner=uid=testuser10,cn=users,cn=accounts,dc=ipasubid,dc=test)'
dn: ipauniqueid=35c02c93-3799-4551-a355-ebbf042e431c,cn=subids,cn=accounts,dc=ipasubid,dc=test
# control: 1.3.6.1.4.1.4203.666.5.16 false MIQAAABxMIQAAABrBAhpcGFPd25lcgQ3dWlk
PXRlc3R1c2VyMTAsY249dXNlcnMsY249YWNjb3VudHMsZGM9aXBhc3ViaWQsZGM9dGVzdKCEAAAAIj
CEAAAAHAQJdWlkTnVtYmVyMYQAAAALBAk2MjgwMDAwMTE=
# ipaOwner: <uidNumber=628000011>;uid=testuser10,cn=users,cn=accounts,dc=ipasubid,dc=test
ipaOwner: uid=testuser10,cn=users,cn=accounts,dc=ipasubid,dc=test
ipaUniqueID: 35c02c93-3799-4551-a355-ebbf042e431c
description: auto-assigned subid
objectClass: ipasubordinateidentry
objectClass: ipasubordinategid
objectClass: ipasubordinateuid
objectClass: ipasubordinateid
objectClass: top
ipaSubUidNumber: 3434020864
ipaSubGidNumber: 3434020864
ipaSubUidCount: 65536
ipaSubGidCount: 65536
```
## Implementation details
* The four subid attributes are not included in
``baseuser.default_attributes`` on purpose. The ``config-mod``
command does not permit removal of a user default objectclasses
when the class is the last provider of an attribute in
``default_attributes``.
* ``ipaSubordinateId`` object class does not subclass the other two
object classes. LDAP supports
``SUP ( ipaSubordinateGid $ ipaSubordinateUid )`` but 389-DS only
@@ -459,6 +576,13 @@ Filters for additional cases:
* Shared DNA configuration entries in ``cn=dna,cn=ipa,cn=etc,$SUFFIX``
are automatically removed by existing code. Server and replication
plug-ins search and delete entries by ``dnaHostname`` attribute.
* ``ipaSubordinateId`` entries no longer contains ``uidNumber``
attribute. I considered to use CoS plugin to provide ``uidNumber``
as virtual attribute. However it's not possible to
``objectClass: cosIndirectDefinition`` with
``cosIndirectSpecifier: ipaOwner`` and
``cosAttribute: uidNumber override`` for the task. Indexes and
searches don't work with virtual attributes.
### TODO
@@ -466,3 +590,23 @@ Filters for additional cases:
* remove ``fake_dna_plugin`` hack from ``baseuser`` plug-in.
* add custom range type for idranges and teach AD trust, sidgen, and
range overlap check code to deal with new range type.
#### user-del --preserve
Preserving a user with ``ipa user-del --preserve`` currently fails with
an ObjectclassViolation error (err=65). The problem is caused by
configuration of referential integrity postoperation plug-in. The
plug-in excludes the subtree
``nsslapd-pluginexcludeentryscope: cn=provisioning,$SUFFX``. Preserved
users are moved into the staging area of the provisioning subtree.
Since the ``ipaOwner`` DN target is now out of scope, the plug-in
attempts to delete references. However ``ipaOwner`` is a required
attribute, which triggers the objectclass violation.
Possible solutions
* Don't preserve subid entries
* Implement preserve feature for subid entries and move subids of
preserved users into
``cn=deleted subids,cn=accounts,cn=provisioning,$SUFFIX`` subtree.
* Change ``nsslapd-pluginexcludeentryscope`` setting

View File

@@ -5,15 +5,16 @@
##
dn: cn=schema
# subordinate ids
# range ceiling OIDs are reserved for future use (operational attribute?)
# object class requires uidNumber but does not subclass posixAccount so we
# can re-use the object class in idview overrides later.
attributeTypes: ( 2.16.840.1.113730.3.8.23.6 NAME 'ipaSubUidNumber' DESC 'Numerical subordinate user ID (range start value)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
attributeTypes: ( 2.16.840.1.113730.3.8.23.7 NAME 'ipaSubUidCount' DESC 'Subordinate user ID count (range size)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
# attributeTypes: ( 2.16.840.1.113730.3.8.23.8 NAME 'ipaSubUidCeiling' DESC 'Numerical subordinate user ID ceiling (largest value in range)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
attributeTypes: ( 2.16.840.1.113730.3.8.23.9 NAME 'ipaSubGidNumber' DESC 'Numerical subordinate group ID (range start value)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
attributeTypes: ( 2.16.840.1.113730.3.8.23.10 NAME 'ipaSubGidCount' DESC 'Subordinate group ID count (range size)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
# attributeTypes: ( 2.16.840.1.113730.3.8.23.11 NAME 'ipaSubGidCeiling' DESC 'Numerical subordinate user ID ceiling (largest value in range)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
objectClasses: (2.16.840.1.113730.3.8.24.2 NAME 'ipaSubordinateUid' DESC 'Subordinate uids for users, see subuid(5)' SUP top AUXILIARY MUST ( uidNumber $ ipaSubUidNumber $ ipaSubUidCount ) X-ORIGIN 'IPA v4.9')
objectClasses: (2.16.840.1.113730.3.8.24.3 NAME 'ipaSubordinateGid' DESC 'Subordinate gids for users, see subgid(5)' SUP top AUXILIARY MUST ( uidNumber $ ipaSubGidNumber $ ipaSubGidCount ) X-ORIGIN 'IPA v4.9')
objectClasses: (2.16.840.1.113730.3.8.24.4 NAME 'ipaSubordinateId' DESC 'Subordinate uid and gid for users' SUP top AUXILIARY MUST ( uidNumber $ ipaSubUidNumber $ ipaSubUidCount $ ipaSubGidNumber $ ipaSubGidCount ) X-ORIGIN 'IPA v4.9')
# range ceiling OIDs are reserved for future use
attributeTypes: ( 2.16.840.1.113730.3.8.23.7 NAME 'ipaSubUidNumber' DESC 'Numerical subordinate user ID (range start value)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
attributeTypes: ( 2.16.840.1.113730.3.8.23.8 NAME 'ipaSubUidCount' DESC 'Subordinate user ID count (range size)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
# attributeTypes: ( 2.16.840.1.113730.3.8.23.9 NAME 'ipaSubUidCeiling' DESC 'Numerical subordinate user ID ceiling (largest value in range)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
attributeTypes: ( 2.16.840.1.113730.3.8.23.10 NAME 'ipaSubGidNumber' DESC 'Numerical subordinate group ID (range start value)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
attributeTypes: ( 2.16.840.1.113730.3.8.23.11 NAME 'ipaSubGidCount' DESC 'Subordinate group ID count (range size)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
# attributeTypes: ( 2.16.840.1.113730.3.8.23.12 NAME 'ipaSubGidCeiling' DESC 'Numerical subordinate user ID ceiling (largest value in range)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
attributeTypes: ( 2.16.840.1.113730.3.8.23.13 NAME 'ipaOwner' DESC 'Owner of an entry' SUP distinguishedName EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
# attribute 2.16.840.1.113730.3.8.23.14 'ipaUserDefaultSubordinateId' is defined in 60ipaconfig.ldif
objectClasses: (2.16.840.1.113730.3.8.24.2 NAME 'ipaSubordinateUid' DESC 'Subordinate uids for users, see subuid(5)' SUP top AUXILIARY MUST ( ipaOwner $ ipaSubUidNumber $ ipaSubUidCount ) X-ORIGIN 'IPA v4.9')
objectClasses: (2.16.840.1.113730.3.8.24.3 NAME 'ipaSubordinateGid' DESC 'Subordinate gids for users, see subgid(5)' SUP top AUXILIARY MUST ( ipaOwner $ ipaSubGidNumber $ ipaSubGidCount ) X-ORIGIN 'IPA v4.9')
objectClasses: (2.16.840.1.113730.3.8.24.4 NAME 'ipaSubordinateId' DESC 'Subordinate uid and gid for users' SUP top AUXILIARY MUST ( ipaOwner $ ipaSubUidNumber $ ipaSubUidCount $ ipaSubGidNumber $ ipaSubGidCount ) X-ORIGIN 'IPA v4.9')
objectClasses: (2.16.840.1.113730.3.8.24.5 NAME 'ipaSubordinateIdEntry' DESC 'Subordinate uid and gid entry' SUP top STRUCTURAL MUST ( ipaUniqueId ) MAY ( description ) X-ORIGIN 'IPA v4.9')

View File

@@ -6,6 +6,7 @@
## ObjectClasses: 2.16.840.1.113730.3.8.2 - V1
## Attributes: 2.16.840.1.113730.3.8.3 - V2
## ObjectClasses: 2.16.840.1.113730.3.8.4 - V2
## Attributes: 2.16.840.1.113730.3.8.23 - V4 base attributes
dn: cn=schema
###############################################
##
@@ -45,11 +46,13 @@ attributeTypes: ( 2.16.840.1.113730.3.8.3.26 NAME 'ipaSELinuxUserMapDefault' DES
attributeTypes: ( 2.16.840.1.113730.3.8.3.27 NAME 'ipaSELinuxUserMapOrder' DESC 'Available SELinux user context ordering' EQUALITY caseIgnoreMatch ORDERING caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA v3')
## ipaMaxHostnameLength - maximum hostname length to allow
attributeTypes: ( 2.16.840.1.113730.3.8.1.28 NAME 'ipaMaxHostnameLength' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE)
# ipaUserDefaultSubordinateId - if TRUE new user entries gain subordinate id by default
attributeTypes: ( 2.16.840.1.113730.3.8.3.23.14 NAME 'ipaUserDefaultSubordinateId' DESC 'Enable adding user entries with subordinate id' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
###############################################
##
## ObjectClasses
##
## ipaGuiConfig - GUI config parameters objectclass
objectClasses: ( 2.16.840.1.113730.3.8.2.1 NAME 'ipaGuiConfig' AUXILIARY MAY ( ipaUserSearchFields $ ipaGroupSearchFields $ ipaSearchTimeLimit $ ipaSearchRecordsLimit $ ipaCustomFields $ ipaHomesRootDir $ ipaDefaultLoginShell $ ipaDefaultPrimaryGroup $ ipaMaxUsernameLength $ ipaPwdExpAdvNotify $ ipaUserObjectClasses $ ipaGroupObjectClasses $ ipaDefaultEmailDomain $ ipaMigrationEnabled $ ipaCertificateSubjectBase $ ipaSELinuxUserMapDefault $ ipaSELinuxUserMapOrder $ ipaKrbAuthzData $ ipaMaxHostnameLength) )
objectClasses: ( 2.16.840.1.113730.3.8.2.1 NAME 'ipaGuiConfig' AUXILIARY MAY ( ipaUserSearchFields $ ipaGroupSearchFields $ ipaSearchTimeLimit $ ipaSearchRecordsLimit $ ipaCustomFields $ ipaHomesRootDir $ ipaDefaultLoginShell $ ipaDefaultPrimaryGroup $ ipaMaxUsernameLength $ ipaPwdExpAdvNotify $ ipaUserObjectClasses $ ipaGroupObjectClasses $ ipaDefaultEmailDomain $ ipaMigrationEnabled $ ipaCertificateSubjectBase $ ipaSELinuxUserMapDefault $ ipaSELinuxUserMapOrder $ ipaKrbAuthzData $ ipaMaxHostnameLength $ ipaUserDefaultSubordinateId) )
## ipaConfigObject - Generic config strings object holder
objectClasses: (2.16.840.1.113730.3.8.4.13 NAME 'ipaConfigObject' DESC 'generic config object for IPA' AUXILIARY MAY ( ipaConfigString ) X-ORIGIN 'IPA v2' )

View File

@@ -29,12 +29,12 @@ dnaMagicRegen: -1
dnaFilter: (objectClass=ipaSubordinateId)
dnaScope: $SUFFIX
dnaThreshold: eval($SUBID_DNA_THRESHOLD)
# TODO: enable when 389-DS' DNA plugin supports dnaStepAttr
# dnaStepAttr: ipaSubUidCount
# dnaStepAttr: ipaSubGidCount
# dnaStepAllowedValues: eval($SUBID_COUNT)
dnaSharedCfgDN: cn=subordinate-ids,cn=dna,cn=ipa,cn=etc,$SUFFIX
dnaExcludeScope: cn=provisioning,$SUFFIX
# TODO: enable when 389-DS' DNA plugin supports dnaStepAttr
# dnaIntervalAttr: ipasubuidcount
# dnaIntervalAttr: ipasubgidcount
# dnaMaxInterval: eval($SUBID_COUNT)
# Enable the DNA plugin
dn: cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config

View File

@@ -8,4 +8,6 @@ memberofgroupattr: memberUser
-
add: memberofgroupattr
memberofgroupattr: memberHost
-
add: memberofgroupattr
memberofgroupattr: ipaOwner

View File

@@ -52,6 +52,7 @@ define([
'./serverconfig',
'./service',
'./stageuser',
'./subid',
'./sudo',
'./trust',
'./topology',

View File

@@ -103,7 +103,8 @@ var nav = {};
]
}
]
}
},
{ entity: 'subid' }
]
},
{

View File

@@ -123,6 +123,10 @@ return {
$type: 'checkbox',
name: 'ipamigrationenabled'
},
{
$type: 'checkbox',
name: 'ipauserdefaultsubordinateid'
},
{
$type: 'multivalued',
name: 'ipauserobjectclasses'

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2021 FreeIPA Contributors see COPYING for license
*/
define([
'dojo/on',
'./ipa',
'./jquery',
'./phases',
'./reg',
'./details',
'./search',
'./association',
'./entity'],
function(on, IPA, $, phases, reg) {
var exp = IPA.subid = {};
var make_spec = function() {
return {
name: 'subid',
facets: [
{
$type: 'search',
columns: [
'ipauniqueid',
'ipaowner',
'ipasubgidnumber',
'ipasubuidnumber'
]
},
{
$type: 'details',
sections: [
{
name: 'details',
fields: [
'ipauniqueid',
'description',
{
name: 'ipaowner',
label: '@i18n:objects.subid.ipaowner',
title: '@mo-param:subid:ipaowner:label'
},
{
name: 'ipasubgidnumber',
label: '@i18n:objects.subid.ipasubgidnumber',
title: '@mo-param:subid:ipasubgidnumber:label'
},
{
name: 'ipasubgidcount',
label: '@i18n:objects.subid.ipasubgidcount',
title: '@mo-param:subid:ipasubgidcount:label'
},
{
name: 'ipasubuidnumber',
label: '@i18n:objects.subid.ipasubuidnumber',
title: '@mo-param:subid:ipasubuidnumber:label'
},
{
name: 'ipasubuidcount',
label: '@i18n:objects.subid.ipasubuidcount',
title: '@mo-param:subid:ipasubuidcount:label'
}
]
}
]
}
],
adder_dialog: {
title: '@i18n:objects.subid.add',
method: 'generate',
fields: [
{
$type: 'entity_select',
name: 'ipaowner',
other_entity: 'user',
other_field: 'uid'
}
]
}
};};
exp.entity_spec = make_spec();
exp.register = function() {
var e = reg.entity;
e.register({type: 'subid', spec: exp.entity_spec});
};
phases.on('registration', exp.register);
return {};
});

View File

@@ -259,33 +259,6 @@ return {
}
]
},
{
name: 'subordinate',
label: '@i18n:objects.subordinate.identity',
fields: [
{
name: 'ipasubuidnumber',
label: '@i18n:objects.subordinate.subuidnumber',
read_only: true
},
{
name: 'ipasubuidcount',
label: '@i18n:objects.subordinate.subuidcount',
read_only: true
},
{
name: 'ipasubgidnumber',
label: '@i18n:objects.subordinate.subgidnumber',
read_only: true
},
{
name: 'ipasubgidcount',
label: '@i18n:objects.subordinate.subgidcount',
read_only: true
}
]
},
{
name: 'pwpolicy',
label: '@i18n:objects.pwpolicy.identity',
@@ -478,16 +451,6 @@ return {
enable_cond: ['is-locked'],
confirm_msg: '@i18n:objects.user.unlock_confirm'
},
{
$factory: IPA.object_action,
name: 'auto_subid',
method: 'auto_subid',
label: '@i18n:objects.user.auto_subid',
needs_confirm: true,
hide_cond: ['preserved-user'],
enable_cond: ['no-subid'],
confirm_msg: '@i18n:objects.user.auto_subid_confirm'
},
{
$type: 'automember_rebuild',
name: 'automember_rebuild',
@@ -500,20 +463,15 @@ return {
title: '@i18n:objects.cert.issue_for_user'
},
{
$factory: IPA.object_action,
name: 'auto_subid',
method: 'auto_subid',
label: '@i18n:objects.user.auto_subid',
needs_confirm: true,
$type: 'subid_generate',
hide_cond: ['preserved-user'],
enable_cond: ['no-subid'],
confirm_msg: '@i18n:objects.user.auto_subid_confirm'
enable_cond: ['no-subid']
}
],
header_actions: [
'reset_password', 'enable', 'disable', 'stage', 'undel',
'delete_active_user', 'delete', 'unlock', 'add_otptoken',
'automember_rebuild', 'request_cert', 'auto_subid'
'automember_rebuild', 'request_cert', 'subid_generate'
],
state: {
evaluators: [
@@ -532,7 +490,8 @@ return {
IPA.user.self_service_other_user_evaluator,
IPA.user.preserved_user_evaluator,
IPA.user.is_locked_evaluator,
IPA.cert.certificate_evaluator
IPA.cert.certificate_evaluator,
IPA.user.has_subid_evaluator
],
summary_conditions: [
{
@@ -593,6 +552,12 @@ return {
add_title: '@i18n:objects.user.add_into_sudo',
remove_method: 'remove_user',
remove_title: '@i18n:objects.user.remove_from_sudo'
},
{
$type: 'association',
name: 'memberof_subid',
associator: IPA.serial_associator,
read_only: true
}
],
standard_association_facets: {
@@ -1206,7 +1171,31 @@ IPA.user.is_locked_evaluator = function(spec) {
}
}
if (!user.ipasubuidnumber) {
that.notify_on_change(old_state);
};
return that;
};
IPA.user.has_subid_evaluator = function(spec) {
spec = spec || {};
spec.event = spec.event || 'post_load';
var that = IPA.state_evaluator(spec);
that.name = spec.name || 'has_subid_evaluator';
that.param = spec.param || 'memberof_subid';
/**
* Evaluates if user already has a subid
*/
that.on_event = function(data) {
var old_state = that.state;
that.state = [];
var value = that.adapter.load(data);
if (value.length === 0) {
that.state.push('no-subid');
}
@@ -1216,6 +1205,30 @@ IPA.user.is_locked_evaluator = function(spec) {
return that;
};
IPA.user.subid_generate_action = function(spec) {
spec = spec || {};
spec.name = spec.name || 'subid_generate';
spec.label = spec.label || '@i18n:objects.user.auto_subid';
spec.hide_cond = spec.hide_cond || ['preserved-user'];
spec.confirm_msg = spec.confirm_msg || '@i18n:objects.user.auto_subid_confirm';
var that = IPA.action(spec);
that.execute_action = function(facet) {
var subid_e = reg.entity.get('subid');
var dialog = subid_e.get_dialog('add');
dialog.open();
if (!IPA.is_selfservice) {
var owner = facet.get_pkey();
dialog.get_field('ipaowner').set_value([owner]);
}
};
return that;
};
exp.entity_spec = make_spec();
exp.register = function() {
var e = reg.entity;
@@ -1225,6 +1238,7 @@ exp.register = function() {
a.register('reset_password', IPA.user.reset_password_action);
a.register('add_otptoken', IPA.user.add_otptoken_action);
a.register('delete_active_user', IPA.user.delete_active_user_action);
a.register('subid_generate', IPA.user.subid_generate_action);
d.copy('password', 'user_password', {
factory: IPA.user.password_dialog,
pre_ops: [IPA.user.password_dialog_pre_op]

View File

@@ -109,3 +109,22 @@ default:nsslapd-plugin-depends-on-type: database
default:nsslapd-pluginId: NSUniqueAttr
default:nsslapd-pluginVersion: 1.1.0
default:nsslapd-pluginVendor: Fedora Project
dn: cn=ipaSubordinateIdEntry ipaOwner uniqueness,cn=plugins,cn=config
default:objectClass: top
default:objectClass: nsSlapdPlugin
default:objectClass: extensibleObject
default:cn: ipaSubordinateIdEntry ipaOwner uniqueness
default:nsslapd-pluginDescription: Enforce unique attribute values of ipaOwner
default:nsslapd-pluginPath: libattr-unique-plugin
default:nsslapd-pluginInitfunc: NSUniqueAttr_Init
default:nsslapd-pluginType: preoperation
default:nsslapd-pluginEnabled: on
default:uniqueness-attribute-name: ipaOwner
default:uniqueness-subtrees: cn=subids,cn=accounts,$SUFFIX
default:uniqueness-across-all-subtrees: on
default:uniqueness-subtree-entries-oc: ipaSubordinateIdEntry
default:nsslapd-plugin-depends-on-type: database
default:nsslapd-pluginId: NSUniqueAttr
default:nsslapd-pluginVersion: 1.1.0
default:nsslapd-pluginVendor: Fedora Project

View File

@@ -263,6 +263,14 @@ default:nsSystemIndex: false
add:nsIndexType: eq
add:nsIndexType: pres
dn: cn=ipaOwner,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
only:cn: ipaOwner
default:objectClass: nsIndex
default:objectClass: top
default:nsSystemIndex: false
add:nsIndexType: eq
add:nsIndexType: pres
dn: cn=ipasudorunas,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
only:cn: ipasudorunas
default:objectClass: nsIndex
@@ -425,6 +433,10 @@ default:nsSystemIndex: false
add:nsIndexType: eq
add:nsIndexType: pres
dn: cn=memberOf,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
only:cn: member
add:nsIndexType: sub
dn: cn=memberPrincipal,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
only:cn: memberPrincipal
default:objectClass: nsIndex

View File

@@ -21,3 +21,4 @@ add: referint-membership-attr: ipamemberca
add: referint-membership-attr: ipamembercertprofile
add: referint-membership-attr: ipalocation
add: referint-membership-attr: membermanager
add: referint-membership-attr: ipaowner

View File

@@ -1,5 +1,15 @@
# subordinate ids
# create memberOf attributes for ipaOwner
dn: cn=MemberOf Plugin,cn=plugins,cn=config
add: memberofgroupattr: ipaOwner
# container
dn: cn=subids,cn=accounts,$SUFFIX
default: objectClass: top
default: objectClass: nsContainer
default: cn: subids
# self-service RBAC
dn: cn=Subordinate ID Selfservice User,cn=roles,cn=accounts,$SUFFIX
default:objectClass: groupofnames
@@ -56,9 +66,9 @@ default:member: cn=Subordinate ID Administrators,cn=privileges,cn=pbac,$SUFFIX
# self-service permission when 389-DS' DNA plugin supports dnaStepAttr and
# fake_dna_plugin hack has been removed.
#
dn: $SUFFIX
add: aci: (targetfilter = "(objectclass=posixaccount)")(targattrfilters = "add=objectClass:(|(objectClass=ipasubordinateid)(objectClass=ipasubordinategid)(objectClass=ipasubordinateuid)) && ipasubuidnumber:(|(ipasubuidnumber>=eval($SUBID_RANGE_START))(ipasubuidnumber=-1)) && ipasubuidcount:(ipasubuidcount=eval($SUBID_COUNT)) && ipasubgidnumber:(|(ipasubgidnumber>=eval($SUBID_RANGE_START))(ipasubgidnumber=-1)) && ipasubgidcount:(ipasubgidcount=eval($SUBID_COUNT)), del=ipasubuidnumber:(!(ipasubuidnumber=*)) && ipasubuidcount:(!(ipasubuidcount=*)) && ipasubgidnumber:(!(ipasubgidnumber=*)) && ipasubgidcount:(!(ipasubgidcount=*))")(version 3.0;acl "selfservice: Add subordinate id";allow (write) userdn = "ldap:///self" and groupdn="ldap:///cn=Self-service subordinate ID,cn=permissions,cn=pbac,$SUFFIX";)
add: aci: (targetfilter = "(objectclass=posixaccount)")(targattrfilters = "add=objectClass:(|(objectClass=ipasubordinateid)(objectClass=ipasubordinategid)(objectClass=ipasubordinateuid)) && ipasubuidnumber:(|(ipasubuidnumber>=1)(ipasubuidnumber=-1)) && ipasubuidcount:(ipasubuidcount=eval($SUBID_COUNT)) && ipasubgidnumber:(|(ipasubgidnumber>=1)(ipasubgidnumber=-1)) && ipasubgidcount:(ipasubgidcount=eval($SUBID_COUNT)), del=ipasubuidnumber:(!(ipasubuidnumber=*)) && ipasubuidcount:(!(ipasubuidcount=*)) && ipasubgidnumber:(!(ipasubgidnumber=*)) && ipasubgidcount:(!(ipasubgidcount=*))")(version 3.0;acl "Add subordinate ids to any user";allow (write) groupdn="ldap:///cn=Subordinate ID Administrators,cn=privileges,cn=pbac,$SUFFIX";)
dn: cn=subids,cn=accounts,$SUFFIX
add: aci: (targetfilter = "(objectclass=ipasubordinateidentry)")(targetattr="description || ipaowner || ipauniqueid")(targattrfilters = "add=objectClass:(|(objectClass=top)(objectClass=ipasubordinateid)(objectClass=ipasubordinateidentry)(objectClass=ipasubordinategid)(objectClass=ipasubordinateuid)) && ipasubuidnumber:(|(ipasubuidnumber>=eval($SUBID_RANGE_START))(ipasubuidnumber=-1)) && ipasubuidcount:(ipasubuidcount=eval($SUBID_COUNT)) && ipasubgidnumber:(|(ipasubgidnumber>=eval($SUBID_RANGE_START))(ipasubgidnumber=-1)) && ipasubgidcount:(ipasubgidcount=eval($SUBID_COUNT)), del=ipasubuidnumber:(!(ipasubuidnumber=*)) && ipasubuidcount:(!(ipasubuidcount=*)) && ipasubgidnumber:(!(ipasubgidnumber=*)) && ipasubgidcount:(!(ipasubgidcount=*))")(version 3.0;acl "selfservice: Add subordinate id";allow (add, write) userattr = "ipaowner#SELFDN" and groupdn="ldap:///cn=Self-service subordinate ID,cn=permissions,cn=pbac,$SUFFIX";)
add: aci: (targetfilter = "(objectclass=ipasubordinateidentry)")(targetattr="description || ipaowner || ipauniqueid")(targattrfilters = "add=objectClass:(|(objectClass=top)(objectClass=ipasubordinateid)(objectClass=ipasubordinateidentry)(objectClass=ipasubordinategid)(objectClass=ipasubordinateuid)) && ipasubuidnumber:(|(ipasubuidnumber>=1)(ipasubuidnumber=-1)) && ipasubuidcount:(ipasubuidcount=eval($SUBID_COUNT)) && ipasubgidnumber:(|(ipasubgidnumber>=1)(ipasubgidnumber=-1)) && ipasubgidcount:(ipasubgidcount=eval($SUBID_COUNT)), del=ipasubuidnumber:(!(ipasubuidnumber=*)) && ipasubuidcount:(!(ipasubuidcount=*)) && ipasubgidnumber:(!(ipasubgidnumber=*)) && ipasubgidcount:(!(ipasubgidcount=*))")(version 3.0;acl "Add subordinate ids to any user";allow (add, write) groupdn="ldap:///cn=Subordinate ID Administrators,cn=privileges,cn=pbac,$SUFFIX";)
# DNA plugin and idrange configuration
dn: cn=subordinate-ids,cn=dna,cn=ipa,cn=etc,$SUFFIX
@@ -78,14 +88,14 @@ default: dnaMagicRegen: -1
default: dnaFilter: (objectClass=ipaSubordinateId)
default: dnaScope: $SUFFIX
default: dnaThreshold: eval($SUBID_DNA_THRESHOLD)
# TODO: enable when 389-DS' DNA plugin supports dnaStepAttr
# default: dnaStepAttr: ipaSubUidCount
# default: dnaStepAttr: ipaSubGidCount
# default: dnaStepAllowedValues: eval($SUBID_COUNT)
default: dnaSharedCfgDN: cn=subordinate-ids,cn=dna,cn=ipa,cn=etc,$SUFFIX
default: dnaExcludeScope: cn=provisioning,$SUFFIX
default: aci: (targetattr = "dnaNextRange || dnaNextValue || dnaMaxValue")(version 3.0;acl "permission:Modify DNA Range";allow (write) groupdn = "ldap:///cn=Modify DNA Range,cn=permissions,cn=pbac,$SUFFIX";)
default: aci: (targetattr = "cn || dnaMaxValue || dnaNextRange || dnaNextValue || dnaThreshold || dnaType || objectclass")(version 3.0;acl "permission:Read DNA Range";allow (read, search, compare) groupdn = "ldap:///cn=Read DNA Range,cn=permissions,cn=pbac,$SUFFIX";)
# TODO: enable when 389-DS' DNA plugin supports dnaStepAttr
# add: dnaIntervalAttr: ipasubuidcount
# add: dnaIntervalAttr: ipasubgidcount
# addifnew: dnaMaxInterval: eval($SUBID_COUNT)
add: aci: (targetattr = "dnaNextRange || dnaNextValue || dnaMaxValue")(version 3.0;acl "permission:Modify DNA Range";allow (write) groupdn = "ldap:///cn=Modify DNA Range,cn=permissions,cn=pbac,$SUFFIX";)
add: aci: (targetattr = "cn || dnaMaxValue || dnaNextRange || dnaNextValue || dnaThreshold || dnaType || objectclass")(version 3.0;acl "permission:Read DNA Range";allow (read, search, compare) groupdn = "ldap:///cn=Read DNA Range,cn=permissions,cn=pbac,$SUFFIX";)
dn: cn=${REALM}_subid_range,cn=ranges,cn=etc,$SUFFIX
default: objectClass: top

View File

@@ -131,6 +131,9 @@ DEFAULT_CONFIG = (
('container_ranges', DN(('cn', 'ranges'), ('cn', 'etc'))),
('container_dna', DN(('cn', 'dna'), ('cn', 'ipa'), ('cn', 'etc'))),
('container_dna_posix_ids', DN(('cn', 'posix-ids'), ('cn', 'dna'), ('cn', 'ipa'), ('cn', 'etc'))),
('container_dna_subordinate_ids', DN(
('cn', 'subordinate-ids'), ('cn', 'dna'), ('cn', 'ipa'), ('cn', 'etc')
)),
('container_realm_domains', DN(('cn', 'Realm Domains'), ('cn', 'ipa'), ('cn', 'etc'))),
('container_otp', DN(('cn', 'otp'))),
('container_radiusproxy', DN(('cn', 'radiusproxy'))),
@@ -148,6 +151,7 @@ DEFAULT_CONFIG = (
('container_certmaprules', DN(('cn', 'certmaprules'), ('cn', 'certmap'))),
('container_ca_renewal',
DN(('cn', 'ca_renewal'), ('cn', 'ipa'), ('cn', 'etc'))),
('container_subids', DN(('cn', 'subids'), ('cn', 'accounts'))),
# Ports, hosts, and URIs:
# Following values do not have any reasonable default.
@@ -355,4 +359,4 @@ SUBID_RANGE_START = 2 ** 31
SUBID_RANGE_MAX = (2 ** 32) - (2 * SUBID_COUNT)
SUBID_RANGE_SIZE = SUBID_RANGE_MAX - SUBID_RANGE_START
# threshold before DNA plugin requests a new range
SUBID_DNA_THRESHOLD = 500 * SUBID_COUNT
SUBID_DNA_THRESHOLD = 500

View File

@@ -10,7 +10,7 @@ from ipalib.facts import is_ipa_configured
from ipaplatform.paths import paths
from ipapython.admintool import AdminTool, ScriptError
from ipapython.dn import DN
from ipaserver.plugins.baseldap import DNA_MAGIC
from ipapython.version import API_VERSION
logger = logging.getLogger(__name__)
@@ -77,7 +77,7 @@ class IPASubids(AdminTool):
# only users with posixAccount
"(objectClass=posixAccount)",
# without subordinate ids
"(!(objectClass=ipaSubordinateId))",
f"(!(memberOf=*,cn=subids,cn=accounts,{api.env.basedn}))",
]
if groupinfo is not None:
filters.append(
@@ -89,7 +89,7 @@ class IPASubids(AdminTool):
def search_users(self, filters):
users_dn = DN(api.env.container_user, api.env.basedn)
attrs = ["objectclass", "uid", "uidnumber"]
attrs = ["objectclass", "uid"]
logger.debug("basedn: %s", users_dn)
logger.debug("attrs: %s", attrs)
@@ -116,7 +116,7 @@ class IPASubids(AdminTool):
api.finalize()
api.Backend.ldap2.connect()
self.ldap2 = api.Backend.ldap2
user_obj = api.Object["user"]
subid_generate = api.Command.subid_generate
dry_run = self.safe_options.dry_run
group_info = self.get_group_info()
@@ -136,11 +136,13 @@ class IPASubids(AdminTool):
i,
total
)
user_obj.set_subordinate_ids(
self.ldap2, entry.dn, entry, DNA_MAGIC
)
if not dry_run:
self.ldap2.update_entry(entry)
# TODO: check for duplicate entry (race condition)
# TODO: log new subid
subid_generate(
ipaowner=entry.single_value["uid"],
version=API_VERSION
)
if dry_run:
logger.info("Dry run mode, no user was modified")

View File

@@ -17,7 +17,7 @@ register = Registry()
@register()
class update_dna_shared_config(Updater):
dna_plugin_names = ('posix IDs',)
dna_plugin_names = ('posix IDs', 'Subordinate IDs')
dna_plugin_dn = DN(
('cn', 'Distributed Numeric Assignment Plugin'),

View File

@@ -121,6 +121,8 @@ global_output_params = (
Str('memberof_hbacrule?',
label='Member of HBAC rule',
),
Str('memberof_subid?',
label='Subordinate ids',),
Str('member_idoverrideuser?',
label=_('Member ID user overrides'),),
Str('memberindirect_idoverrideuser?',
@@ -795,7 +797,13 @@ class LDAPObject(Object):
dn = entry.dn
filter = self.backend.make_filter(
{'member': dn, 'memberuser': dn, 'memberhost': dn})
{
'member': dn,
'memberuser': dn,
'memberhost': dn,
'ipaowner': dn
}
)
try:
result = self.backend.get_entries(
self.api.env.basedn,

View File

@@ -17,10 +17,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import random
import six
from ipalib import api, errors, output, constants
from ipalib import api, errors, constants
from ipalib import (
Flag, Int, Password, Str, Bool, StrEnum, DateTime, DNParam)
from ipalib.parameters import Principal, Certificate
@@ -28,9 +27,9 @@ from ipalib.plugable import Registry
from .baseldap import (
DN, LDAPObject, LDAPCreate, LDAPUpdate, LDAPSearch, LDAPDelete,
LDAPRetrieve, LDAPAddAttribute, LDAPModAttribute, LDAPRemoveAttribute,
LDAPQuery, LDAPAddMember, LDAPRemoveMember,
LDAPAddMember, LDAPRemoveMember,
LDAPAddAttributeViaOption, LDAPRemoveAttributeViaOption,
add_missing_object_class, DNA_MAGIC, pkey_to_value, entry_to_dict
add_missing_object_class
)
from ipaserver.plugins.service import (validate_realm, normalize_principal)
from ipalib.request import context
@@ -162,7 +161,7 @@ class baseuser(LDAPObject):
possible_objectclasses = [
'meporiginentry', 'ipauserauthtypeclass', 'ipauser',
'ipatokenradiusproxyuser', 'ipacertmapobject',
'ipantuserattrs', 'ipasubordinateid',
'ipantuserattrs',
]
disallow_object_classes = ['krbticketpolicyaux']
permission_filter_objectclasses = ['posixaccount']
@@ -183,13 +182,13 @@ class baseuser(LDAPObject):
'krbprincipalname', 'loginshell',
'mail', 'telephonenumber', 'title', 'nsaccountlock',
'uidnumber', 'gidnumber', 'sshpubkeyfp',
'ipasubuidnumber', 'ipasubuidcount', 'ipasubgidnumber',
'ipasubgidcount',
]
uuid_attribute = 'ipauniqueid'
attribute_members = {
'manager': ['user'],
'memberof': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'],
'memberof': [
'group', 'netgroup', 'role', 'hbacrule', 'sudorule', 'subid'
],
'memberofindirect': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'],
}
allow_rename = True
@@ -432,41 +431,6 @@ class baseuser(LDAPObject):
'J:', 'K:', 'L:', 'M:', 'N:', 'O:', 'P:', 'Q:', 'R:',
'S:', 'T:', 'U:', 'V:', 'W:', 'X:', 'Y:', 'Z:'),
),
Int(
'ipasubuidnumber?',
label=_('SubUID range start'),
cli_name='subuid',
doc=_('Start value for subordinate user ID (subuid) range'),
minvalue=constants.SUBID_RANGE_START,
maxvalue=constants.SUBID_RANGE_MAX,
),
Int(
'ipasubuidcount?',
label=_('SubUID range size'),
cli_name='subuidcount',
doc=_('Subordinate user ID count'),
flags={'no_create', 'no_update', 'no_search'},
minvalue=constants.SUBID_COUNT,
maxvalue=constants.SUBID_COUNT,
),
Int(
'ipasubgidnumber?',
label=_('SubGID range start'),
cli_name='subgid',
doc=_('Start value for subordinate group ID (subgid) range'),
flags={'no_create', 'no_update'},
minvalue=constants.SUBID_RANGE_START,
maxvalue=constants.SUBID_RANGE_MAX,
),
Int(
'ipasubgidcount?',
label=_('SubGID range size'),
cli_name='subgidcount',
doc=_('Subordinate group ID count'),
flags={'no_create', 'no_update', 'no_search'},
minvalue=constants.SUBID_COUNT,
maxvalue=constants.SUBID_COUNT,
),
)
def normalize_and_validate_email(self, email, config=None):
@@ -564,131 +528,6 @@ class baseuser(LDAPObject):
except KeyError:
pass
def handle_subordinate_ids(self, ldap, dn, entry_attrs):
"""Handle ipaSubordinateId object class
"""
obj_classes = entry_attrs.get("objectclass")
new_subuid = entry_attrs.single_value.get("ipasubuidnumber")
new_subgid = entry_attrs.single_value.get("ipasubgidnumber")
# entry has object class ipaSubordinateId
# default to auto-assigment of subuids
if (
new_subuid is None
and obj_classes is not None
and self.has_objectclass(obj_classes, "ipasubordinateid")
):
new_subuid = DNA_MAGIC
# neither auto-assignment nor explicit assignment
if new_subuid is None:
# nothing to do
return False
# enforce subuid == subgid
if new_subgid is not None and new_subgid != new_subuid:
raise errors.ValidationError(
name="ipasubgidnumber",
error=_("subgidnumber must be equal to subuidnumber")
)
self.set_subordinate_ids(ldap, dn, entry_attrs, new_subuid)
return True
def set_subordinate_ids(self, ldap, dn, entry_attrs, subuid):
"""Set subuid value of an entry
Takes care of objectclass and sibbling attributes
"""
if "objectclass" in entry_attrs:
obj_classes = entry_attrs["objectclass"]
else:
_entry_attrs = ldap.get_entry(dn, ["objectclass"])
entry_attrs["objectclass"] = _entry_attrs["objectclass"]
obj_classes = entry_attrs["objectclass"]
if not self.has_objectclass(obj_classes, "ipasubordinateid"):
# could append ipasubordinategid and ipasubordinateuid, too
obj_classes.append("ipasubordinateid")
# XXX HACK, remove later
if subuid == DNA_MAGIC:
subuid = self._fake_dna_plugin(ldap, dn, entry_attrs)
entry_attrs["ipasubuidnumber"] = subuid
# enforice subuid == subgid for now
entry_attrs["ipasubgidnumber"] = subuid
# hard-coded constants
entry_attrs["ipasubuidcount"] = constants.SUBID_COUNT
entry_attrs["ipasubgidcount"] = constants.SUBID_COUNT
def get_subid_match_candidate_filter(
self, ldap, *, subuid, subgid, extra_filters=(), offset=None,
):
"""Create LDAP filter to locate matching/overlapping subids
"""
if subuid is None and subgid is None:
raise ValueError("subuid and subgid are both None")
if offset is None:
# assumes that no subordinate count is larger than SUBID_COUNT
offset = constants.SUBID_COUNT - 1
class_filters = "(objectclass=ipasubordinateid)"
subid_filters = []
if subuid is not None:
subid_filters.append(
ldap.combine_filters(
[
f"(ipasubuidnumber>={subuid - offset})",
f"(ipasubuidnumber<={subuid + offset})",
],
rules=ldap.MATCH_ALL
)
)
if subgid is not None:
subid_filters.append(
ldap.combine_filters(
[
f"(ipasubgidnumber>={subgid - offset})",
f"(ipasubgidnumber<={subgid + offset})",
],
rules=ldap.MATCH_ALL
)
)
subid_filters = ldap.combine_filters(
subid_filters, rules=ldap.MATCH_ANY
)
filters = [class_filters, subid_filters]
filters.extend(extra_filters)
return ldap.combine_filters(filters, rules=ldap.MATCH_ALL)
def _fake_dna_plugin(self, ldap, dn, entry_attrs):
"""XXX HACK, remove when 389-DS DNA plugin supports steps"""
uidnumber = entry_attrs.single_value.get("uidnumber")
if uidnumber is None:
entry = ldap.get_entry(dn, ["uidnumber"])
uidnumber = entry.single_value["uidnumber"]
uidnumber = int(uidnumber)
if uidnumber == DNA_MAGIC:
return (
3221225472
+ random.randint(1, 16382) * constants.SUBID_COUNT
)
if not hasattr(context, "idrange_ipabaseid"):
range_name = f"{self.api.env.realm}_id_range"
range = self.api.Command.idrange_show(range_name)["result"]
context.idrange_ipabaseid = int(range["ipabaseid"][0])
range_start = context.idrange_ipabaseid
assert uidnumber >= range_start
assert uidnumber < range_start + 2**14
return (uidnumber - range_start) * constants.SUBID_COUNT + 2**31
class baseuser_add(LDAPCreate):
"""
@@ -699,7 +538,6 @@ class baseuser_add(LDAPCreate):
assert isinstance(dn, DN)
set_krbcanonicalname(entry_attrs)
self.obj.convert_usercertificate_pre(entry_attrs)
self.obj.handle_subordinate_ids(ldap, dn, entry_attrs)
if entry_attrs.get('ipatokenradiususername', None):
add_missing_object_class(ldap, u'ipatokenradiusproxyuser', dn,
entry_attrs, update=False)
@@ -852,7 +690,6 @@ class baseuser_mod(LDAPUpdate):
self.check_objectclass(ldap, dn, entry_attrs)
self.obj.convert_usercertificate_pre(entry_attrs)
self.obj.handle_subordinate_ids(ldap, dn, entry_attrs)
self.preserve_krbprincipalname_pre(ldap, entry_attrs, *keys, **options)
update_samba_attrs(ldap, dn, entry_attrs, **options)
@@ -1133,98 +970,3 @@ class baseuser_remove_certmapdata(ModCertMapData,
LDAPRemoveAttribute):
__doc__ = _("Remove one or more certificate mappings from the user entry.")
msg_summary = _('Removed certificate mappings from user "%(value)s"')
class baseuser_auto_subid(LDAPQuery):
__doc__ = _("Auto-assign subuid and subgid range to user entry")
has_output = output.standard_entry
def execute(self, cn, **options):
ldap = self.obj.backend
dn = self.obj.get_dn(cn)
try:
entry_attrs = ldap.get_entry(
dn, ["objectclass", "ipasubuidnumber"]
)
except errors.NotFound:
raise self.obj.handle_not_found(cn)
if "ipasubuidnumber" in entry_attrs:
raise errors.AlreadyContainsValueError(attr="ipasubuidnumber")
self.obj.set_subordinate_ids(ldap, dn, entry_attrs, subuid=DNA_MAGIC)
ldap.update_entry(entry_attrs)
# fetch updated entry (use search display attribute to show subids)
if options.get('all', False):
attrs_list = ['*'] + self.obj.search_display_attributes
else:
attrs_list = set(self.obj.search_display_attributes)
attrs_list.update(entry_attrs.keys())
if options.get('no_members', False):
attrs_list.difference_update(self.obj.attribute_members)
attrs_list = list(attrs_list)
entry = self._exc_wrapper((cn,), options, ldap.get_entry)(
dn, attrs_list
)
entry_attrs = entry_to_dict(entry, **options)
entry_attrs['dn'] = dn
return dict(result=entry_attrs, value=pkey_to_value(cn, options))
class baseuser_match_subid(baseuser_find):
__doc__ = _("Match users by any subordinate uid in their range")
_subid_attrs = {
"ipasubuidnumber",
"ipasubuidcount",
"ipasubgidnumber",
"ipasubgidcount"
}
def get_options(self):
base_options = {p.name for p in self.obj.takes_params}
for option in super().get_options():
if option.name == "ipasubuidnumber":
yield option.clone(
label=_('SubUID match'),
doc=_('Match value for subordinate user ID'),
required=True,
)
elif option.name not in base_options:
# raw, version
yield option.clone()
def pre_callback(
self, ldap, filters, attrs_list, base_dn, scope, *args, **options
):
# search for candidates in range
# Code assumes that no subordinate count is larger than SUBID_COUNT
filters = self.obj.get_subid_match_candidate_filter(
ldap, subuid=options["ipasubuidnumber"], subgid=None,
)
# always include subid attributes
for missing in self._subid_attrs.difference(attrs_list):
attrs_list.append(missing)
return filters, base_dn, scope
def post_callback(self, ldap, entries, truncated, *args, **options):
# filter out mismatches manually
osubuid = options["ipasubuidnumber"]
new_entries = []
for entry in entries:
esubuid = int(entry.single_value["ipasubuidnumber"])
esubcount = int(entry.single_value["ipasubuidcount"])
minsubuid = esubuid
maxsubuid = esubuid + esubcount - 1
if minsubuid <= osubuid <= maxsubuid:
new_entries.append(entry)
entries[:] = new_entries
return truncated

View File

@@ -121,6 +121,7 @@ class config(LDAPObject):
'ipapwdexpadvnotify', 'ipaselinuxusermaporder',
'ipaselinuxusermapdefault', 'ipaconfigstring', 'ipakrbauthzdata',
'ipauserauthtype', 'ipadomainresolutionorder', 'ipamaxhostnamelength',
'ipauserdefaultsubordinateid',
]
container_dn = DN(('cn', 'ipaconfig'), ('cn', 'etc'))
permission_filter_objectclasses = ['ipaguiconfig']
@@ -142,7 +143,7 @@ class config(LDAPObject):
'ipasearchrecordslimit', 'ipasearchtimelimit',
'ipauserauthtype', 'ipauserobjectclasses',
'ipausersearchfields', 'ipacustomfields',
'ipamaxhostnamelength',
'ipamaxhostnamelength', 'ipauserdefaultsubordinateid',
},
},
}
@@ -261,6 +262,11 @@ class config(LDAPObject):
values=(u'password', u'radius', u'otp',
u'pkinit', u'hardened', u'disabled'),
),
Bool('ipauserdefaultsubordinateid?',
cli_name='user_default_subid',
label=_('Enable adding subids to new users'),
doc=_('Enable adding subids to new users'),
),
Str(
'ipa_master_server*',
label=_('IPA masters'),

View File

@@ -1547,7 +1547,7 @@ class i18n_messages(Command):
"Drive to mount a home directory"
),
},
"subordinate": {
"subid": {
"identity": _("Subordinate user and group id"),
"subuidnumber": _("Subordinate user id"),
"subuidcount": _("Subordinate user id count"),

608
ipaserver/plugins/subid.py Normal file
View File

@@ -0,0 +1,608 @@
#
# Copyright (C) 2021 FreeIPA Contributors see COPYING for license
#
import random
import uuid
from ipalib import api
from ipalib import constants
from ipalib import errors
from ipalib import output
from ipalib.plugable import Registry
from ipalib.parameters import Int, Str
from ipalib.request import context
from ipalib.text import _, ngettext
from ipapython.dn import DN
from .baseldap import (
LDAPObject,
LDAPCreate,
LDAPDelete,
LDAPUpdate,
LDAPSearch,
LDAPRetrieve,
LDAPQuery,
DNA_MAGIC,
)
__doc__ = _(
"""
Subordinate ids
Manage subordinate user and group ids for users
EXAMPLES:
Auto-assign a subordinate id range to current user
ipa subid-generate
Auto-assign a subordinate id range to user alice:
ipa subid-generate --owner=alice
Find subordinate ids for user alice:
ipa subid-find --owner=alice
Match entry by any subordinate uid in range:
ipa subid-match --subuid=2147483649
"""
)
register = Registry()
@register()
class subid(LDAPObject):
"""Subordinate id object."""
container_dn = api.env.container_subids
object_name = _("Subordinate id")
object_name_plural = _("Subordinate ids")
label = _("Subordinate ids")
label_singular = _("Subordinate id")
object_class = ["ipasubordinateidentry"]
possible_objectclasses = [
"ipasubordinategid",
"ipasubordinateuid",
"ipasubordinateid",
]
default_attributes = [
"ipauniqueid",
"ipaowner",
"ipasubuidnumber",
"ipasubuidcount",
"ipasubgidnumber",
"ipasubgidcount",
]
allow_rename = False
permission_filter_objectclasses_string = (
"(objectclass=ipasubordinateidentry)"
)
managed_permissions = {
# all authenticated principals can read subordinate id information
"System: Read Subordinate Id Attributes": {
"ipapermbindruletype": "all",
"ipapermright": {"read", "search", "compare"},
"ipapermtargetfilter": [
permission_filter_objectclasses_string,
],
"ipapermdefaultattr": {
"objectclass",
"ipauniqueid",
"description",
"ipaowner",
"ipasubuidnumber",
"ipasubuidcount",
"ipasubgidnumber",
"ipasubgidcount",
},
},
"System: Read Subordinate Id Count": {
"ipapermbindruletype": "all",
"ipapermright": {"read", "search", "compare"},
"ipapermtargetfilter": [],
"ipapermtarget": DN(container_dn, api.env.basedn),
"ipapermdefaultattr": {"numSubordinates"},
},
# user administrators can remove subordinate ids or update the
# ipaowner attribute. This enables user admins to remove users
# with assigned subids or move them to staging area (--preserve).
"System: Manage Subordinate Ids": {
"ipapermright": {"write"},
"ipapermtargetfilter": [
permission_filter_objectclasses_string,
],
"ipapermdefaultattr": {
"description",
"ipaowner", # allow user admins to preserve users
},
"default_privileges": {"User Administrators"},
},
"System: Remove Subordinate Ids": {
"ipapermright": {"delete"},
"ipapermtargetfilter": [
permission_filter_objectclasses_string,
],
"default_privileges": {"User Administrators"},
},
}
takes_params = (
Str(
"ipauniqueid",
cli_name="id",
label=_("Unique ID"),
primary_key=True,
flags={"optional_create"},
),
Str(
"description?",
cli_name="desc",
label=_("Description"),
doc=_("Subordinate id description"),
),
Str(
"ipaowner",
cli_name="owner",
label=_("Owner"),
doc=_("Owning user of subordinate id entry"),
flags={"no_update"},
),
Int(
"ipasubuidnumber?",
label=_("SubUID range start"),
cli_name="subuid",
doc=_("Start value for subordinate user ID (subuid) range"),
flags={"no_update"},
minvalue=constants.SUBID_RANGE_START,
maxvalue=constants.SUBID_RANGE_MAX,
),
Int(
"ipasubuidcount?",
label=_("SubUID range size"),
cli_name="subuidcount",
doc=_("Subordinate user ID count"),
flags={"no_create", "no_update", "no_search"}, # auto-assigned
minvalue=constants.SUBID_COUNT,
maxvalue=constants.SUBID_COUNT,
),
Int(
"ipasubgidnumber?",
label=_("SubGID range start"),
cli_name="subgid",
doc=_("Start value for subordinate group ID (subgid) range"),
flags={"no_create", "no_update"}, # auto-assigned
minvalue=constants.SUBID_RANGE_START,
maxvalue=constants.SUBID_RANGE_MAX,
),
Int(
"ipasubgidcount?",
label=_("SubGID range size"),
cli_name="subgidcount",
doc=_("Subordinate group ID count"),
flags={"no_create", "no_update", "no_search"}, # auto-assigned
minvalue=constants.SUBID_COUNT,
maxvalue=constants.SUBID_COUNT,
),
)
def fixup_objectclass(self, entry_attrs):
"""Add missing object classes to entry"""
has_subuid = "ipasubuidnumber" in entry_attrs
has_subgid = "ipasubgidnumber" in entry_attrs
candicates = set(self.object_class)
if has_subgid:
candicates.add("ipasubordinategid")
if has_subuid:
candicates.add("ipasubordinateuid")
if has_subgid and has_subuid:
candicates.add("ipasubordinateid")
entry_oc = entry_attrs.setdefault("objectclass", [])
current_oc = {x.lower() for x in entry_oc}
for oc in candicates.difference(current_oc):
entry_oc.append(oc)
def handle_duplicate_entry(self, *keys):
if hasattr(context, "subid_owner_dn"):
uid = context.subid_owner_dn[0].value
msg = _(
'%(oname)s with with name "%(pkey)s" or for user "%(uid)s" '
"already exists."
) % {
"uid": uid,
"pkey": keys[-1] if keys else "",
"oname": self.object_name,
}
raise errors.DuplicateEntry(message=msg) from None
else:
super().handle_duplicate_entry(*keys)
def convert_owner(self, entry_attrs, options):
"""Change owner from DN to uid string"""
if not options.get("raw", False) and "ipaowner" in entry_attrs:
userobj = self.api.Object.user
entry_attrs["ipaowner"] = [
userobj.get_primary_key_from_dn(entry_attrs["ipaowner"][0])
]
def get_owner_dn(self, *keys, **options):
"""Get owning user entry entry (username or DN)"""
owner = keys[-1]
userobj = self.api.Object.user
if isinstance(owner, DN):
# it's already a DN, validate it's either an active or preserved
# user. Ref integrity plugin checks that it's not a dangling DN.
user_dns = (
DN(userobj.active_container_dn, self.api.env.basedn),
DN(userobj.delete_container_dn, self.api.env.basedn),
)
if not owner.endswith(user_dns):
raise errors.ValidationError(
name="ipaowner",
error=_("'%(dn)s is not a valid user") % {"dn": owner},
)
return owner
# similar to user.get_either_dn() but with error reporting and
# returns an entry
ldap = self.backend
try:
active_dn = userobj.get_dn(owner, **options)
entry = ldap.get_entry(active_dn, attrs_list=[])
return entry.dn
except errors.NotFound:
# fall back to deleted user
try:
delete_dn = userobj.get_delete_dn(owner, **options)
entry = ldap.get_entry(delete_dn, attrs_list=[])
return entry.dn
except errors.NotFound:
raise userobj.handle_not_found(owner)
def handle_subordinate_ids(self, ldap, dn, entry_attrs):
"""Handle ipaSubordinateId object class"""
new_subuid = entry_attrs.single_value.get("ipasubuidnumber")
new_subgid = entry_attrs.single_value.get("ipasubgidnumber")
if new_subuid is None:
new_subuid = DNA_MAGIC
# enforce subuid == subgid
if new_subgid is not None and new_subgid != new_subuid:
raise errors.ValidationError(
name="ipasubgidnumber",
error=_("subgidnumber must be equal to subuidnumber"),
)
self.set_subordinate_ids(ldap, dn, entry_attrs, new_subuid)
return True
def set_subordinate_ids(self, ldap, dn, entry_attrs, subuid):
"""Set subuid value of an entry
Takes care of objectclass and sibbling attributes
"""
if "objectclass" not in entry_attrs:
_entry_attrs = ldap.get_entry(dn, ["objectclass"])
entry_attrs["objectclass"] = _entry_attrs["objectclass"]
# XXX HACK, remove later
if subuid == DNA_MAGIC:
subuid = self._fake_dna_plugin(ldap, dn, entry_attrs)
entry_attrs["ipasubuidnumber"] = subuid
# enforice subuid == subgid for now
entry_attrs["ipasubgidnumber"] = subuid
# hard-coded constants
entry_attrs["ipasubuidcount"] = constants.SUBID_COUNT
entry_attrs["ipasubgidcount"] = constants.SUBID_COUNT
self.fixup_objectclass(entry_attrs)
def get_subid_match_candidate_filter(
self,
ldap,
*,
subuid,
subgid,
extra_filters=(),
offset=None,
):
"""Create LDAP filter to locate matching/overlapping subids"""
if subuid is None and subgid is None:
raise ValueError("subuid and subgid are both None")
if offset is None:
# assumes that no subordinate count is larger than SUBID_COUNT
offset = constants.SUBID_COUNT - 1
class_filters = "(objectclass=ipasubordinateid)"
subid_filters = []
if subuid is not None:
subid_filters.append(
ldap.combine_filters(
[
f"(ipasubuidnumber>={subuid - offset})",
f"(ipasubuidnumber<={subuid + offset})",
],
rules=ldap.MATCH_ALL,
)
)
if subgid is not None:
subid_filters.append(
ldap.combine_filters(
[
f"(ipasubgidnumber>={subgid - offset})",
f"(ipasubgidnumber<={subgid + offset})",
],
rules=ldap.MATCH_ALL,
)
)
subid_filters = ldap.combine_filters(
subid_filters, rules=ldap.MATCH_ANY
)
filters = [class_filters, subid_filters]
filters.extend(extra_filters)
return ldap.combine_filters(filters, rules=ldap.MATCH_ALL)
def _fake_dna_plugin(self, ldap, dn, entry_attrs):
"""XXX HACK, remove when 389-DS DNA plugin supports steps"""
return (
constants.SUBID_RANGE_START
+ random.randint(1, 32764 - 2) * constants.SUBID_COUNT
)
@register()
class subid_add(LDAPCreate):
__doc__ = _("Add a new subordinate id.")
msg_summary = _('Added subordinate id "%(value)s"')
# internal command, use subid-auto to auto-assign subids
NO_CLI = True
def pre_callback(
self, ldap, dn, entry_attrs, attrs_list, *keys, **options
):
# XXX let ref integrity plugin validate DN?
owner_dn = self.obj.get_owner_dn(entry_attrs["ipaowner"], **options)
context.subid_owner_dn = owner_dn
entry_attrs["ipaowner"] = owner_dn
self.obj.handle_subordinate_ids(ldap, dn, entry_attrs)
attrs_list.append("objectclass")
return dn
def execute(self, ipauniqueid=None, **options):
if ipauniqueid is None:
ipauniqueid = str(uuid.uuid4())
return super().execute(ipauniqueid, **options)
def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
self.obj.convert_owner(entry_attrs, options)
return super(subid_add, self).post_callback(
ldap, dn, entry_attrs, *keys, **options
)
@register()
class subid_del(LDAPDelete):
__doc__ = _("Delete a subordinate id.")
msg_summary = _('Deleted subordinate id "%(value)s"')
# internal command, subids cannot be removed
NO_CLI = True
@register()
class subid_mod(LDAPUpdate):
__doc__ = _("Modify a subordinate id.")
msg_summary = _('Modified subordinate id "%(value)s"')
def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
self.obj.convert_owner(entry_attrs, options)
return super(subid_mod, self).post_callback(
ldap, dn, entry_attrs, *keys, **options
)
@register()
class subid_find(LDAPSearch):
__doc__ = _("Search for subordinate id.")
msg_summary = ngettext(
"%(count)d subordinate id matched",
"%(count)d subordinate ids matched",
0,
)
def pre_callback(
self, ldap, filters, attrs_list, base_dn, scope, *args, **options
):
attrs_list.append("objectclass")
return super(subid_find, self).pre_callback(
ldap, filters, attrs_list, base_dn, scope, *args, **options
)
def args_options_2_entry(self, *args, **options):
entry_attrs = super(subid_find, self).args_options_2_entry(
*args, **options
)
owner = entry_attrs.get("ipaowner")
if owner is not None:
owner_dn = self.obj.get_owner_dn(owner, **options)
entry_attrs["ipaowner"] = owner_dn
return entry_attrs
def post_callback(self, ldap, entries, truncated, *args, **options):
for entry in entries:
self.obj.convert_owner(entry, options)
return super(subid_find, self).post_callback(
ldap, entries, truncated, *args, **options
)
@register()
class subid_show(LDAPRetrieve):
__doc__ = _("Display information about a subordinate id.")
def pre_callback(self, ldap, dn, attrs_list, *keys, **options):
attrs_list.append("objectclass")
return super(subid_show, self).pre_callback(
ldap, dn, attrs_list, *keys, **options
)
def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
self.obj.convert_owner(entry_attrs, options)
return super(subid_show, self).post_callback(
ldap, dn, entry_attrs, *keys, **options
)
@register()
class subid_generate(LDAPQuery):
__doc__ = _(
"Generate and auto-assign subuid and subgid range to user entry"
)
has_output = output.standard_entry
takes_options = LDAPQuery.takes_options + (
Str(
"ipaowner?",
cli_name="owner",
label=_("Owner"),
doc=_("Owning user of subordinate id entry"),
),
)
def get_args(self):
return []
def execute(self, *keys, **options):
owner_uid = options.get("ipaowner")
# default to current user
if owner_uid is None:
owner_dn = DN(self.api.Backend.ldap2.conn.whoami_s()[4:])
# validate it's a user and not a service or host
owner_dn = self.obj.get_owner_dn(owner_dn)
owner_uid = owner_dn[0].value
return self.api.Command.subid_add(
description="auto-assigned subid",
ipaowner=owner_uid,
version=options["version"],
)
@register()
class subid_match(subid_find):
__doc__ = _("Match users by any subordinate uid in their range")
def get_options(self):
base_options = {p.name for p in self.obj.takes_params}
for option in super().get_options():
if option.name == "ipasubuidnumber":
yield option.clone(
label=_("SubUID match"),
doc=_("Match value for subordinate user ID"),
required=True,
)
elif option.name not in base_options:
# raw, version
yield option.clone()
def pre_callback(
self, ldap, filters, attrs_list, base_dn, scope, *args, **options
):
# search for candidates in range
# Code assumes that no subordinate count is larger than SUBID_COUNT
filters = self.obj.get_subid_match_candidate_filter(
ldap,
subuid=options["ipasubuidnumber"],
subgid=None,
)
attrs_list.extend(self.obj.default_attributes)
return filters, base_dn, scope
def post_callback(self, ldap, entries, truncated, *args, **options):
# filter out mismatches manually
osubuid = options["ipasubuidnumber"]
new_entries = []
for entry in entries:
esubuid = int(entry.single_value["ipasubuidnumber"])
esubcount = int(entry.single_value["ipasubuidcount"])
minsubuid = esubuid
maxsubuid = esubuid + esubcount - 1
if minsubuid <= osubuid <= maxsubuid:
new_entries.append(entry)
entries[:] = new_entries
return truncated
@register()
class subid_stats(LDAPQuery):
__doc__ = _("Subordinate id statistics")
takes_options = ()
has_output = (
output.summary,
output.Entry("result"),
)
def get_args(self):
return ()
def get_remaining_dna(self, ldap, **options):
base_dn = DN(
self.api.env.container_dna_subordinate_ids, self.api.env.basedn
)
entries, _truncated = ldap.find_entries(
"(objectClass=dnaSharedConfig)",
attrs_list=["dnaRemainingValues"],
base_dn=base_dn,
scope=ldap.SCOPE_ONELEVEL,
)
return sum(
int(entry.single_value["dnaRemainingValues"]) for entry in entries
)
def get_idrange(self, ldap, **options):
cn = f"{self.api.env.realm}_subid_range"
result = self.api.Command.idrange_show(cn, version=options["version"])
baseid = int(result["result"]["ipabaseid"][0])
rangesize = int(result["result"]["ipaidrangesize"][0])
return baseid, rangesize
def get_subid_assigned(self, ldap, **options):
dn = DN(self.api.env.container_subids, self.api.env.basedn)
entry = ldap.get_entry(dn=dn, attrs_list=["numSubordinates"])
return int(entry.single_value["numSubordinates"])
def execute(self, *keys, **options):
ldap = self.obj.backend
dna_remaining = self.get_remaining_dna(ldap, **options)
baseid, rangesize = self.get_idrange(ldap, **options)
assigned_subids = self.get_subid_assigned(ldap, **options)
remaining_subids = dna_remaining // constants.SUBID_COUNT
return dict(
summary=_("%(remaining)i remaining subordinate id ranges")
% {
"remaining": remaining_subids,
},
result=dict(
baseid=baseid,
rangesize=rangesize,
dna_remaining=dna_remaining,
assigned_subids=assigned_subids,
remaining_subids=remaining_subids,
),
)

View File

@@ -51,8 +51,6 @@ from .baseuser import (
baseuser_remove_principal,
baseuser_add_certmapdata,
baseuser_remove_certmapdata,
baseuser_auto_subid,
baseuser_match_subid,
)
from .idviews import remove_ipaobject_overrides
from ipalib.plugable import Registry
@@ -205,8 +203,6 @@ class user(baseuser):
'ipapermright': {'read', 'search', 'compare'},
'ipapermdefaultattr': {
'ipauniqueid', 'ipasshpubkey', 'ipauserauthtype', 'userclass',
'ipasubuidnumber', 'ipasubuidcount', 'ipasubgidnumber',
'ipasubgidcount',
},
'fixup_function': fix_addressbook_permission_bindrule,
},
@@ -670,6 +666,17 @@ class user_add(baseuser_add):
# if both randompassword and userpassword options were used
pass
# generate subid
default_subid = config.single_value.get(
'ipaUserDefaultSubordinateId', 'FALSE'
)
if default_subid == 'TRUE':
result = self.api.Command.subid_generate(
ipaowner=entry_attrs.single_value['uid'],
version=options['version']
)
entry_attrs["memberOf"].append(result['result']['dn'])
self.obj.get_preserved_attribute(entry_attrs, options)
self.post_common_callback(ldap, dn, entry_attrs, *keys, **options)
@@ -757,7 +764,9 @@ class user_del(baseuser_del):
# of OTP tokens.
check_protected_member(keys[-1])
if not options.get('preserve', False):
preserve = options.get('preserve', False)
if not preserve:
# Remove any ID overrides tied with this user
try:
remove_ipaobject_overrides(self.obj.backend, self.obj.api, dn)
@@ -780,6 +789,15 @@ class user_del(baseuser_del):
else:
self.api.Command.otptoken_del(token)
# XXX: preserving doesn't work yet, see subordinate-ids.md
# Delete all subid entries owned by this user.
results = self.api.Command.subid_find(ipaowner=owner)["result"]
for subid_entry in results:
subid_pkey = self.api.Object.subid.get_primary_key_from_dn(
subid_entry["dn"]
)
self.api.Command.subid_del(subid_pkey)
return dn
def execute(self, *keys, **options):
@@ -829,6 +847,7 @@ class user_mod(baseuser_mod):
self.pre_common_callback(ldap, dn, entry_attrs, attrs_list, *keys,
**options)
validate_nsaccountlock(entry_attrs)
# TODO: forward uidNumber changes and rename to subids
return dn
def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
@@ -1311,13 +1330,3 @@ class user_add_principal(baseuser_add_principal):
class user_remove_principal(baseuser_remove_principal):
__doc__ = _('Remove principal alias from the user entry')
msg_summary = _('Removed aliases from user "%(value)s"')
@register()
class user_auto_subid(baseuser_auto_subid):
__doc__ = baseuser_auto_subid.__doc__
@register()
class user_match_subid(baseuser_match_subid):
__doc__ = baseuser_match_subid.__doc__

View File

@@ -17,6 +17,7 @@ class TestSubordinateId(IntegrationTest):
topology = "star"
def _parse_result(self, result):
# ipa CLI should get an --outform json option
info = {}
for line in result.stdout_text.split("\n"):
line = line.strip()
@@ -42,39 +43,69 @@ class TestSubordinateId(IntegrationTest):
info[k] = set(v)
return info
def get_user(self, uid):
cmd = ["ipa", "user-show", "--all", "--raw", uid]
result = self.master.run_command(cmd)
return self._parse_result(result)
def assert_subid_info(self, uid, info):
assert info["ipauniqueid"]
basedn = self.master.domain.basedn
assert info["ipaowner"] == f"uid={uid},cn=users,cn=accounts,{basedn}"
assert info["ipasubuidnumber"] == info["ipasubuidnumber"]
assert info["ipasubuidnumber"] >= SUBID_RANGE_START
assert info["ipasubuidnumber"] <= SUBID_RANGE_MAX
assert info["ipasubuidcount"] == SUBID_COUNT
assert info["ipasubgidnumber"] == info["ipasubgidnumber"]
assert info["ipasubgidnumber"] == info["ipasubuidnumber"]
assert info["ipasubgidcount"] == SUBID_COUNT
def user_auto_subid(self, uid, **kwargs):
cmd = ["ipa", "user-auto-subid", uid]
def assert_subid(self, uid, *, match):
cmd = ["ipa", "subid-find", "--raw", "--owner", uid]
result = self.master.run_command(cmd, raiseonerr=False)
if not match:
assert result.returncode >= 1
if result.returncode == 1:
assert "0 subordinate ids matched" in result.stdout_text
elif result.returncode == 2:
assert "user not found" in result.stderr_text
return None
else:
assert result.returncode == 0
assert "1 subordinate id matched" in result.stdout_text
info = self._parse_result(result)
self.assert_subid_info(uid, info)
self.master.run_command(
["ipa", "subid-show", info["ipauniqueid"]]
)
return info
def subid_generate(self, uid, **kwargs):
cmd = ["ipa", "subid-generate"]
if uid is not None:
cmd.extend(("--owner", uid))
return self.master.run_command(cmd, **kwargs)
def test_auto_subid(self):
tasks.kinit_admin(self.master)
def test_auto_generate_subid(self):
uid = "testuser_auto1"
tasks.user_add(self.master, uid)
info = self.get_user(uid)
assert "ipasubuidcount" not in info
passwd = "Secret123"
tasks.create_active_user(self.master, uid, password=passwd)
self.user_auto_subid(uid)
info = self.get_user(uid)
assert "ipasubuidcount" in info
tasks.kinit_admin(self.master)
self.assert_subid(uid, match=False)
# add subid by name
self.subid_generate(uid)
info = self.assert_subid(uid, match=True)
# second generate fails due to unique index on ipaowner
result = self.subid_generate(uid, raiseonerr=False)
assert result.returncode > 0
assert f'for user "{uid}" already exists' in result.stderr_text
# check matching
subuid = info["ipasubuidnumber"]
for offset in (0, 1, 65535):
result = self.master.run_command(
["ipa", "user-match-subid", f"--subuid={subuid}", "--raw"]
["ipa", "subid-match", f"--subuid={subuid + offset}", "--raw"]
)
match = self._parse_result(result)
assert match["uid"] == uid
assert match["ipasubuidnumber"] == info["ipasubuidnumber"]
assert match["ipasubuidnumber"] >= SUBID_RANGE_START
assert match["ipasubuidnumber"] <= SUBID_RANGE_MAX
assert match["ipasubuidcount"] == SUBID_COUNT
assert match["ipasubgidnumber"] == info["ipasubgidnumber"]
assert match["ipasubgidnumber"] == match["ipasubuidnumber"]
assert match["ipasubgidcount"] == SUBID_COUNT
self.assert_subid_info(uid, match)
def test_ipa_subid_script(self):
tasks.kinit_admin(self.master)
@@ -85,34 +116,28 @@ class TestSubordinateId(IntegrationTest):
uid = f"testuser_script{i}"
users.append(uid)
tasks.user_add(self.master, uid)
info = self.get_user(uid)
assert "ipasubuidcount" not in info
self.assert_subid(uid, match=False)
cmd = [tool, "--verbose", "--group", "ipausers"]
self.master.run_command(cmd)
for uid in users:
info = self.get_user(uid)
assert info["ipasubuidnumber"] >= SUBID_RANGE_START
assert info["ipasubuidnumber"] <= SUBID_RANGE_MAX
assert info["ipasubuidnumber"] == info["ipasubgidnumber"]
assert info["ipasubuidcount"] == SUBID_COUNT
assert info["ipasubuidcount"] == info["ipasubgidcount"]
self.assert_subid(uid, match=True)
def test_subid_selfservice(self):
tasks.kinit_admin(self.master)
uid = "testuser_selfservice1"
uid1 = "testuser_selfservice1"
uid2 = "testuser_selfservice2"
password = "Secret123"
role = "Subordinate ID Selfservice User"
tasks.user_add(self.master, uid, password=password)
tasks.kinit_user(
self.master, uid, f"{password}\n{password}\n{password}\n"
)
info = self.get_user(uid)
assert "ipasubuidcount" not in info
result = self.user_auto_subid(uid, raiseonerr=False)
tasks.create_active_user(self.master, uid1, password=password)
tasks.create_active_user(self.master, uid2, password=password)
tasks.kinit_user(self.master, uid1, password=password)
self.assert_subid(uid1, match=False)
result = self.subid_generate(uid1, raiseonerr=False)
assert result.returncode > 0
result = self.subid_generate(None, raiseonerr=False)
assert result.returncode > 0
tasks.kinit_admin(self.master)
@@ -121,10 +146,14 @@ class TestSubordinateId(IntegrationTest):
)
try:
tasks.kinit_user(self.master, uid, password)
self.user_auto_subid(uid)
info = self.get_user(uid)
assert "ipasubuidcount" in info
tasks.kinit_user(self.master, uid1, password)
self.subid_generate(uid1)
self.assert_subid(uid1, match=True)
# add subid from whoami
tasks.kinit_as_user(self.master, uid2, password=password)
self.subid_generate(None)
self.assert_subid(uid2, match=True)
finally:
tasks.kinit_admin(self.master)
self.master.run_command(
@@ -140,45 +169,46 @@ class TestSubordinateId(IntegrationTest):
password = "Secret123"
# create user administrator
tasks.user_add(self.master, uid_useradmin, password=password)
tasks.create_active_user(
self.master, uid_useradmin, password=password
)
# add user to user admin group
tasks.kinit_admin(self.master)
self.master.run_command(
["ipa", "role-add-member", role, f"--users={uid_useradmin}"],
)
# kinit as user admin
tasks.kinit_user(
self.master,
uid_useradmin,
f"{password}\n{password}\n{password}\n",
)
tasks.kinit_user(self.master, uid_useradmin, password)
# create new user as user admin
tasks.user_add(self.master, uid)
# assign new subid to user (with useradmin credentials)
self.user_auto_subid(uid)
self.subid_generate(uid)
def test_subordinate_default_objclass(self):
# test that user admin can preserve and delete users with subids
self.master.run_command(["ipa", "user-del", "--preserve", uid])
# XXX does not work, see subordinate-ids.md
# subid should still exist
# self.assert_subid(uid, match=True)
# final delete should remove the user and subid
self.master.run_command(["ipa", "user-del", uid])
self.assert_subid(uid, match=False)
def tset_subid_auto_assign(self):
tasks.kinit_admin(self.master)
uid = "testuser_autoassign_user1"
result = self.master.run_command(
["ipa", "config-show", "--raw", "--all"]
self.master.run_command(
["ipa", "config-mod", "--user-default-subid=true"]
)
info = self._parse_result(result)
usercls = info["ipauserobjectclasses"]
assert "ipasubordinateid" not in usercls
cmd = [
"ipa",
"config-mod",
"--addattr",
"ipaUserObjectClasses=ipasubordinateid",
]
self.master.run_command(cmd)
uid = "testuser_usercls1"
try:
tasks.user_add(self.master, uid)
info = self.get_user(uid)
assert "ipasubuidcount" in info
self.assert_subid(uid, match=True)
finally:
self.master.run_command(
["ipa", "config-mod", "--user-default-subid=false"]
)
def test_idrange_subid(self):
tasks.kinit_admin(self.master)
@@ -199,3 +229,7 @@ class TestSubordinateId(IntegrationTest):
assert info["ipanttrusteddomainsid"].startswith(
"S-1-5-21-738065-838566-"
)
def test_subid_stats(self):
tasks.kinit_admin(self.master)
self.master.run_command(["ipa", "subid-stats"])