mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Add basic support for subordinate user/group ids
New LDAP object class "ipaUserSubordinate" with four new fields: - ipasubuidnumber / ipasubuidcount - ipasubgidnumber / ipasgbuidcount New self-service permission to add subids. New command user-auto-subid to auto-assign subid The code hard-codes counts to 65536, sets subgid equal to subuid, and does not allow removal of subids. There is also a hack that emulates a DNA plugin with step interval 65536 for testing. Work around problem with older SSSD clients that fail with unknown idrange type "ipa-local-subid", see: https://github.com/SSSD/sssd/issues/5571 Related: https://pagure.io/freeipa/issue/8361 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:
parent
fbee1549d3
commit
1c4ae37293
1
.gitignore
vendored
1
.gitignore
vendored
@ -199,6 +199,7 @@ install/tools/ipa-restore
|
|||||||
install/tools/ipa-server-certinstall
|
install/tools/ipa-server-certinstall
|
||||||
install/tools/ipa-server-install
|
install/tools/ipa-server-install
|
||||||
install/tools/ipa-server-upgrade
|
install/tools/ipa-server-upgrade
|
||||||
|
install/tools/ipa-subids
|
||||||
install/tools/ipa-winsync-migrate
|
install/tools/ipa-winsync-migrate
|
||||||
ipatests/i18n.py
|
ipatests/i18n.py
|
||||||
ipatests/ipa-run-tests
|
ipatests/ipa-run-tests
|
||||||
|
2
ACI.txt
2
ACI.txt
@ -375,7 +375,7 @@ aci: (targetattr = "audio || businesscategory || carlicense || departmentnumber
|
|||||||
dn: dc=ipa,dc=example
|
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";)
|
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
|
dn: cn=users,cn=accounts,dc=ipa,dc=example
|
||||||
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";)
|
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";)
|
||||||
dn: cn=users,cn=accounts,dc=ipa,dc=example
|
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";)
|
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
|
dn: cn=users,cn=accounts,dc=ipa,dc=example
|
||||||
|
47
API.txt
47
API.txt
@ -4974,7 +4974,7 @@ output: Entry('result')
|
|||||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||||
output: PrimaryKey('value')
|
output: PrimaryKey('value')
|
||||||
command: stageuser_add/1
|
command: stageuser_add/1
|
||||||
args: 1,45,3
|
args: 1,46,3
|
||||||
arg: Str('uid', cli_name='login')
|
arg: Str('uid', cli_name='login')
|
||||||
option: Str('addattr*', cli_name='addattr')
|
option: Str('addattr*', cli_name='addattr')
|
||||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||||
@ -4992,6 +4992,7 @@ option: Str('givenname', cli_name='first')
|
|||||||
option: Str('homedirectory?', cli_name='homedir')
|
option: Str('homedirectory?', cli_name='homedir')
|
||||||
option: Str('initials?', autofill=True)
|
option: Str('initials?', autofill=True)
|
||||||
option: Str('ipasshpubkey*', cli_name='sshpubkey')
|
option: Str('ipasshpubkey*', cli_name='sshpubkey')
|
||||||
|
option: Int('ipasubuidnumber?', cli_name='subuid')
|
||||||
option: Str('ipatokenradiusconfiglink?', cli_name='radius')
|
option: Str('ipatokenradiusconfiglink?', cli_name='radius')
|
||||||
option: Str('ipatokenradiususername?', cli_name='radius_username')
|
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'])
|
option: StrEnum('ipauserauthtype*', cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
|
||||||
@ -5080,7 +5081,7 @@ output: Output('result', type=[<type 'dict'>])
|
|||||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||||
output: ListOfPrimaryKeys('value')
|
output: ListOfPrimaryKeys('value')
|
||||||
command: stageuser_find/1
|
command: stageuser_find/1
|
||||||
args: 1,58,4
|
args: 1,60,4
|
||||||
arg: Str('criteria?')
|
arg: Str('criteria?')
|
||||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||||
option: Str('carlicense*', autofill=False)
|
option: Str('carlicense*', autofill=False)
|
||||||
@ -5104,6 +5105,8 @@ 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: 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('ipantlogonscript?', autofill=False, cli_name='smb_logon_script')
|
||||||
option: Str('ipantprofilepath?', autofill=False, cli_name='smb_profile_path')
|
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('ipatokenradiusconfiglink?', autofill=False, cli_name='radius')
|
||||||
option: Str('ipatokenradiususername?', autofill=False, cli_name='radius_username')
|
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'])
|
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
|
||||||
@ -5145,7 +5148,7 @@ output: ListOfEntries('result')
|
|||||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||||
output: Output('truncated', type=[<type 'bool'>])
|
output: Output('truncated', type=[<type 'bool'>])
|
||||||
command: stageuser_mod/1
|
command: stageuser_mod/1
|
||||||
args: 1,51,3
|
args: 1,52,3
|
||||||
arg: Str('uid', cli_name='login')
|
arg: Str('uid', cli_name='login')
|
||||||
option: Str('addattr*', cli_name='addattr')
|
option: Str('addattr*', cli_name='addattr')
|
||||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||||
@ -5167,6 +5170,7 @@ option: StrEnum('ipanthomedirectorydrive?', autofill=False, cli_name='smb_home_d
|
|||||||
option: Str('ipantlogonscript?', autofill=False, cli_name='smb_logon_script')
|
option: Str('ipantlogonscript?', autofill=False, cli_name='smb_logon_script')
|
||||||
option: Str('ipantprofilepath?', autofill=False, cli_name='smb_profile_path')
|
option: Str('ipantprofilepath?', autofill=False, cli_name='smb_profile_path')
|
||||||
option: Str('ipasshpubkey*', autofill=False, cli_name='sshpubkey')
|
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('ipatokenradiusconfiglink?', autofill=False, cli_name='radius')
|
||||||
option: Str('ipatokenradiususername?', autofill=False, cli_name='radius_username')
|
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'])
|
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
|
||||||
@ -6058,7 +6062,7 @@ output: Entry('result')
|
|||||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||||
output: PrimaryKey('value')
|
output: PrimaryKey('value')
|
||||||
command: user_add/1
|
command: user_add/1
|
||||||
args: 1,46,3
|
args: 1,47,3
|
||||||
arg: Str('uid', cli_name='login')
|
arg: Str('uid', cli_name='login')
|
||||||
option: Str('addattr*', cli_name='addattr')
|
option: Str('addattr*', cli_name='addattr')
|
||||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||||
@ -6075,6 +6079,7 @@ option: Str('givenname', cli_name='first')
|
|||||||
option: Str('homedirectory?', cli_name='homedir')
|
option: Str('homedirectory?', cli_name='homedir')
|
||||||
option: Str('initials?', autofill=True)
|
option: Str('initials?', autofill=True)
|
||||||
option: Str('ipasshpubkey*', cli_name='sshpubkey')
|
option: Str('ipasshpubkey*', cli_name='sshpubkey')
|
||||||
|
option: Int('ipasubuidnumber?', cli_name='subuid')
|
||||||
option: Str('ipatokenradiusconfiglink?', cli_name='radius')
|
option: Str('ipatokenradiusconfiglink?', cli_name='radius')
|
||||||
option: Str('ipatokenradiususername?', cli_name='radius_username')
|
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'])
|
option: StrEnum('ipauserauthtype*', cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
|
||||||
@ -6156,6 +6161,16 @@ option: Str('version?')
|
|||||||
output: Entry('result')
|
output: Entry('result')
|
||||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||||
output: PrimaryKey('value')
|
output: PrimaryKey('value')
|
||||||
|
command: 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
|
command: user_del/1
|
||||||
args: 1,3,3
|
args: 1,3,3
|
||||||
arg: Str('uid+', cli_name='login')
|
arg: Str('uid+', cli_name='login')
|
||||||
@ -6180,7 +6195,7 @@ output: Output('result', type=[<type 'bool'>])
|
|||||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||||
output: PrimaryKey('value')
|
output: PrimaryKey('value')
|
||||||
command: user_find/1
|
command: user_find/1
|
||||||
args: 1,61,4
|
args: 1,63,4
|
||||||
arg: Str('criteria?')
|
arg: Str('criteria?')
|
||||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||||
option: Str('carlicense*', autofill=False)
|
option: Str('carlicense*', autofill=False)
|
||||||
@ -6204,6 +6219,8 @@ 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: 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('ipantlogonscript?', autofill=False, cli_name='smb_logon_script')
|
||||||
option: Str('ipantprofilepath?', autofill=False, cli_name='smb_profile_path')
|
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('ipatokenradiusconfiglink?', autofill=False, cli_name='radius')
|
||||||
option: Str('ipatokenradiususername?', autofill=False, cli_name='radius_username')
|
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'])
|
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
|
||||||
@ -6247,8 +6264,23 @@ output: Output('count', type=[<type 'int'>])
|
|||||||
output: ListOfEntries('result')
|
output: ListOfEntries('result')
|
||||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||||
output: Output('truncated', type=[<type 'bool'>])
|
output: Output('truncated', type=[<type 'bool'>])
|
||||||
|
command: 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
|
command: user_mod/1
|
||||||
args: 1,52,3
|
args: 1,53,3
|
||||||
arg: Str('uid', cli_name='login')
|
arg: Str('uid', cli_name='login')
|
||||||
option: Str('addattr*', cli_name='addattr')
|
option: Str('addattr*', cli_name='addattr')
|
||||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||||
@ -6270,6 +6302,7 @@ option: StrEnum('ipanthomedirectorydrive?', autofill=False, cli_name='smb_home_d
|
|||||||
option: Str('ipantlogonscript?', autofill=False, cli_name='smb_logon_script')
|
option: Str('ipantlogonscript?', autofill=False, cli_name='smb_logon_script')
|
||||||
option: Str('ipantprofilepath?', autofill=False, cli_name='smb_profile_path')
|
option: Str('ipantprofilepath?', autofill=False, cli_name='smb_profile_path')
|
||||||
option: Str('ipasshpubkey*', autofill=False, cli_name='sshpubkey')
|
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('ipatokenradiusconfiglink?', autofill=False, cli_name='radius')
|
||||||
option: Str('ipatokenradiususername?', autofill=False, cli_name='radius_username')
|
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'])
|
option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
|
||||||
@ -7183,10 +7216,12 @@ default: user_add_cert/1
|
|||||||
default: user_add_certmapdata/1
|
default: user_add_certmapdata/1
|
||||||
default: user_add_manager/1
|
default: user_add_manager/1
|
||||||
default: user_add_principal/1
|
default: user_add_principal/1
|
||||||
|
default: user_auto_subid/1
|
||||||
default: user_del/1
|
default: user_del/1
|
||||||
default: user_disable/1
|
default: user_disable/1
|
||||||
default: user_enable/1
|
default: user_enable/1
|
||||||
default: user_find/1
|
default: user_find/1
|
||||||
|
default: user_match_subid/1
|
||||||
default: user_mod/1
|
default: user_mod/1
|
||||||
default: user_remove_cert/1
|
default: user_remove_cert/1
|
||||||
default: user_remove_certmapdata/1
|
default: user_remove_certmapdata/1
|
||||||
|
@ -229,7 +229,7 @@ fasttest: $(GENERATED_PYTHON_FILES) ipasetup.py
|
|||||||
--ignore $(abspath $(top_srcdir))/ipatests/test_integration \
|
--ignore $(abspath $(top_srcdir))/ipatests/test_integration \
|
||||||
--ignore $(abspath $(top_srcdir))/ipatests/test_xmlrpc
|
--ignore $(abspath $(top_srcdir))/ipatests/test_xmlrpc
|
||||||
|
|
||||||
fastlint: $(GENERATED_PYTHON_FILES) ipasetup.py
|
fastlint: $(GENERATED_PYTHON_FILES) ipasetup.py acilint apilint
|
||||||
if ! WITH_PYLINT
|
if ! WITH_PYLINT
|
||||||
@echo "ERROR: pylint not available"; exit 1
|
@echo "ERROR: pylint not available"; exit 1
|
||||||
endif
|
endif
|
||||||
|
@ -86,8 +86,8 @@ define(IPA_DATA_VERSION, 20100614120000)
|
|||||||
# #
|
# #
|
||||||
########################################################
|
########################################################
|
||||||
define(IPA_API_VERSION_MAJOR, 2)
|
define(IPA_API_VERSION_MAJOR, 2)
|
||||||
define(IPA_API_VERSION_MINOR, 242)
|
# Last change: add subordinate id feature
|
||||||
# Last change: add status options for cert-find
|
define(IPA_API_VERSION_MINOR, 243)
|
||||||
|
|
||||||
|
|
||||||
########################################################
|
########################################################
|
||||||
|
@ -19,3 +19,4 @@ FreeIPA design documentation
|
|||||||
hidden-replicas.md
|
hidden-replicas.md
|
||||||
disable-stale-users.md
|
disable-stale-users.md
|
||||||
ldapi-autobind-services.md
|
ldapi-autobind-services.md
|
||||||
|
subordinate-ids.md
|
||||||
|
468
doc/designs/subordinate-ids.md
Normal file
468
doc/designs/subordinate-ids.md
Normal file
@ -0,0 +1,468 @@
|
|||||||
|
# Central management of subordinate user and group ids
|
||||||
|
|
||||||
|
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.
|
||||||
|
Traditionally subordinate id ranges are configured in ``/etc/subuid``
|
||||||
|
and ``/etc/subgid``.
|
||||||
|
|
||||||
|
To make rootless containers in a large environment as easy as pie, IPA
|
||||||
|
gains the ability to centrally manage and assign subordinate id ranges.
|
||||||
|
SSSD and shadow-util are extended to read subordinate ids from IPA and
|
||||||
|
provide them to userspace tools.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Feature requests
|
||||||
|
|
||||||
|
* [FreeIPA feature request #8361](https://pagure.io/freeipa/issue/8361)
|
||||||
|
* [SSSD feature request #5197](https://github.com/SSSD/sssd/issues/5197)
|
||||||
|
* [shadow-util feature request #154](https://github.com/shadow-maint/shadow/issues/154)
|
||||||
|
* [389-DS RFE for DNA plugin rhbz#1938239](https://bugzilla.redhat.com/show_bug.cgi?id=1938239)
|
||||||
|
|
||||||
|
Man pages
|
||||||
|
|
||||||
|
* [man subuid(5)](https://man7.org/linux/man-pages/man5/subuid.5.html)
|
||||||
|
* [man subgid(5)](https://man7.org/linux/man-pages/man5/subgid.5.html)
|
||||||
|
* [man user_namespaces(7)](https://man7.org/linux/man-pages/man7/user_namespaces.7.html)
|
||||||
|
* [man newuidmap(1)](https://man7.org/linux/man-pages/man1/newuidmap.1.html)
|
||||||
|
|
||||||
|
Articles / blog posts
|
||||||
|
* [Basic Setup and Use of Podman in a Rootless environment](https://github.com/containers/podman/blob/master/docs/tutorials/rootless_tutorial.md)
|
||||||
|
* [How does rootless Podman work](https://opensource.com/article/19/2/how-does-rootless-podman-work)
|
||||||
|
|
||||||
|
## Design choices
|
||||||
|
|
||||||
|
Some design choices are owed to the circumstance that uids and gids
|
||||||
|
are limited datatypes. The Linux Kernel and userland defines
|
||||||
|
``uid_t`` and ``gid_t`` as unsigned 32bit integers (``uint32_t``), which
|
||||||
|
limits possible values for numeric user and group ids to
|
||||||
|
``0 .. 2^32-2``. ``(uid_t)-1`` is reserved for error reporting. On the
|
||||||
|
other hand the user ``nobody`` typically has uid 65534 / gid 65534. This
|
||||||
|
means we need to assign 65,536 subordinate ids to every user. The
|
||||||
|
theoretical maximum amount of subordinate ranges is less than 65,536
|
||||||
|
(``65536 * 65536 == 2^32``). [``logins.def``](https://man7.org/linux/man-pages/man5/login.defs.5.html)
|
||||||
|
also uses 65536 as default setting for ``SUB_UID_COUNT``.
|
||||||
|
|
||||||
|
The practical limit is far smaller. Subordinate ids should not overlap
|
||||||
|
with system accounts, local user accounts, IPA user accounts, and
|
||||||
|
mapped accounts from Active Directory. Therefore IPA uses the upper
|
||||||
|
half of the uid_t range (>= 2^31 == 2,147,483,648) for subordinate ids.
|
||||||
|
The high bit is rarely used. IPA limits general numeric ids
|
||||||
|
(``uidNumber``, ``gidNumber``, ID ranges) to maximum values of signed
|
||||||
|
32bit integer (2^31-1) for backwards compatibility with XML-RPC.
|
||||||
|
``logins.def`` defaults to ``SUB_UID_MAX`` 600,100,000.
|
||||||
|
|
||||||
|
A default subordinate id count of 65,536 and a total range of approx.
|
||||||
|
2.1 billion limits IPA to slightly more than 32,000 possible ranges. It
|
||||||
|
may sound like a lot of users, but there are much bigger installations
|
||||||
|
of IPA. For comparison Fedora Accounts has over 120,000 users stored in
|
||||||
|
IPA.
|
||||||
|
|
||||||
|
For that reason we treat subordinate id space as premium real estate
|
||||||
|
and don't auto-map or auto-assign subordinate ids by default. Instead
|
||||||
|
we give the admin several options to assign them manually, semi-manual,
|
||||||
|
or automatically.
|
||||||
|
|
||||||
|
### Revision 1 limitation
|
||||||
|
|
||||||
|
The first revision of the feature is deliberately limited and
|
||||||
|
restricted. We are aiming for a simple implementation that covers
|
||||||
|
basic use cases. Some restrictions may be lifted in the future.
|
||||||
|
|
||||||
|
* subuid and subgids cannot be set independently. They are always set
|
||||||
|
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.
|
||||||
|
* 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
|
||||||
|
``[2147483648..4294901767]`` (``2^31`` to ``2^32-1-65536``), which
|
||||||
|
is the upper 2.1 billion uids of ``uid_t`` (``uint32_t``). The range
|
||||||
|
can hold little 32,767 subordinate id ranges.
|
||||||
|
* Active Directory support is out of scope and may be provided in the
|
||||||
|
future.
|
||||||
|
|
||||||
|
### Subid assignment example
|
||||||
|
|
||||||
|
```
|
||||||
|
>>> import itertools
|
||||||
|
>>> def subids():
|
||||||
|
... for n in itertools.count(start=0):
|
||||||
|
... start = SUBID_RANGE_START + (n * SUBID_COUNT)
|
||||||
|
... last = start + SUBID_COUNT - 1
|
||||||
|
... yield (start, last)
|
||||||
|
...
|
||||||
|
>>> gen = subids()
|
||||||
|
>>> next(gen)
|
||||||
|
(2147483648, 2147549183)
|
||||||
|
>>> next(gen)
|
||||||
|
(2147549184, 2147614719)
|
||||||
|
>>> next(gen)
|
||||||
|
(2147614720, 2147680255)
|
||||||
|
```
|
||||||
|
|
||||||
|
The first user has 65565 subordinate ids from uid/gid ``2147483648``
|
||||||
|
to ``2147549183``, the next user has ``2147549184`` to ``2147614719``,
|
||||||
|
and so on. The range count includes the start value.
|
||||||
|
|
||||||
|
An installation with multiple servers, 389-DS'
|
||||||
|
[DNA](https://directory.fedoraproject.org/docs/389ds/design/dna-plugin.html)
|
||||||
|
plug-in takes care of delegating and assigning chunks of subid ranges
|
||||||
|
to servers. The DNA plug-in guarantees uniqueness across servers.
|
||||||
|
|
||||||
|
## LDAP
|
||||||
|
|
||||||
|
### 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``.
|
||||||
|
|
||||||
|
All four 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
|
||||||
|
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.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'
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
The ``ipaSubordinateId`` object class is an auxiliar 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.
|
||||||
|
|
||||||
|
```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 )
|
||||||
|
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.
|
||||||
|
|
||||||
|
```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 )
|
||||||
|
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'
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Index
|
||||||
|
|
||||||
|
The attributes ``ipaSubUidNumber`` and ``ipaSubGidNumber`` are index
|
||||||
|
for ``pres`` and ``eq`` with ``nsMatchingRule: integerOrderingMatch``
|
||||||
|
to enable efficient ``=``, ``>=``, and ``<=`` searches.
|
||||||
|
|
||||||
|
### Distributed numeric assignment (DNA) plug-in extension
|
||||||
|
|
||||||
|
Subordinate id auto-assignment requires an extension of 389-DS'
|
||||||
|
[DNA](https://directory.fedoraproject.org/docs/389ds/design/dna-plugin.html)
|
||||||
|
plug-in. The DNA plug-in is responsible for safely assigning unique
|
||||||
|
numeric ids across all replicas.
|
||||||
|
|
||||||
|
Currently the DNA plug-in only supports a step size of ``1``. A new
|
||||||
|
option ``dnaStepAttr`` (name is tentative) will tell the DNA plug-in
|
||||||
|
to use the value of entry attributes as step size.
|
||||||
|
|
||||||
|
|
||||||
|
## Permissions, Privileges, Roles
|
||||||
|
|
||||||
|
### Self-servive RBAC
|
||||||
|
|
||||||
|
The self-service permission enables users to request auto-assignment
|
||||||
|
of subordinate uid and gid ranges for themselves. Subordinate ids cannot
|
||||||
|
be modified or deleted.
|
||||||
|
|
||||||
|
* ACI: *selfservice: Add subordinate id*
|
||||||
|
* Permission: *Self-service subordinate ID*
|
||||||
|
* Privilege: *Subordinate ID Selfservice User*
|
||||||
|
* Role: *Subordinate ID Selfservice Users*
|
||||||
|
* role default member: n/a
|
||||||
|
|
||||||
|
### Administrator RBAC
|
||||||
|
|
||||||
|
The administrator permission allows privileged users to auto-assign
|
||||||
|
subordinate ids to users. Once assigned subordinate ids cannot
|
||||||
|
be modified or deleted.
|
||||||
|
|
||||||
|
* ACI: *Add subordinate ids to any user*
|
||||||
|
* Permission: *Manage subordinate ID*
|
||||||
|
* Privilege: *Subordinate ID Administrators*
|
||||||
|
* default privilege role: *User Administrator*
|
||||||
|
|
||||||
|
|
||||||
|
## Workflows
|
||||||
|
|
||||||
|
In the default configuration of IPA, neither existing users nor new
|
||||||
|
users will have subordinate ids assigned. There are a couple of ways
|
||||||
|
to assign subordinate ids to users.
|
||||||
|
|
||||||
|
### User administrator
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
### Self-service for group members
|
||||||
|
|
||||||
|
Ordinary users cannot self-service subordinate ids by default. Admins
|
||||||
|
can assign the new *Subordinate ID Selfservice User* to users group to
|
||||||
|
enable self-service for members of the group.
|
||||||
|
|
||||||
|
For example to enable self-service for all members of the default user
|
||||||
|
group ``ipausers``, do:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ 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.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ ipa user-auto-subid myusername
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
The command uses automatic LDAPI EXTERNAL bind when it's executed as
|
||||||
|
root user. Other it requires valid Kerberos TGT of an admin or user
|
||||||
|
administrator.
|
||||||
|
|
||||||
|
```raw
|
||||||
|
|
||||||
|
# /usr/libexec/ipa/ipa-subids --help
|
||||||
|
Usage: ipa-subids
|
||||||
|
|
||||||
|
Mass-assign subordinate ids
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--version show program's version number and exit
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
--group=GROUP Filter by group membership
|
||||||
|
--filter=USER_FILTER Raw LDAP filter
|
||||||
|
--dry-run Dry run mode.
|
||||||
|
--all-users All users
|
||||||
|
|
||||||
|
Logging and output options:
|
||||||
|
-v, --verbose print debugging information
|
||||||
|
-d, --debug alias for --verbose (deprecated)
|
||||||
|
-q, --quiet output only errors
|
||||||
|
--log-file=FILE log to the given file
|
||||||
|
|
||||||
|
# # /usr/libexec/ipa/ipa-subids --group ipausers
|
||||||
|
Processing user 'testsubordinated1' (1/15)
|
||||||
|
Processing user 'testsubordinated2' (2/15)
|
||||||
|
Processing user 'testsubordinated3' (3/15)
|
||||||
|
Processing user 'testsubordinated4' (4/15)
|
||||||
|
Processing user 'testsubordinated5' (5/15)
|
||||||
|
Processing user 'testsubordinated6' (6/15)
|
||||||
|
Processing user 'testsubordinated7' (7/15)
|
||||||
|
Processing user 'testsubordinated8' (8/15)
|
||||||
|
Processing user 'testsubordinated9' (9/15)
|
||||||
|
Processing user 'testsubordinated10' (10/15)
|
||||||
|
Processing user 'testsubordinated11' (11/15)
|
||||||
|
Processing user 'testsubordinated12' (12/15)
|
||||||
|
Processing user 'testsubordinated13' (13/15)
|
||||||
|
Processing user 'testsubordinated14' (14/15)
|
||||||
|
Processing user 'testsubordinated15' (15/15)
|
||||||
|
Processed 15 user(s)
|
||||||
|
The ipa-subids command was successful
|
||||||
|
```
|
||||||
|
|
||||||
|
### Find and match users by any subordinate id
|
||||||
|
|
||||||
|
The ``user-find`` command search by start value of subordinate uid and
|
||||||
|
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
|
||||||
|
SubUID range size: 65536
|
||||||
|
SubGID range start: 2153185280
|
||||||
|
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
|
||||||
|
SubUID range size: 65536
|
||||||
|
SubGID range start: 2153119744
|
||||||
|
SubGID range size: 65536
|
||||||
|
----------------------------
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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``.
|
||||||
|
|
||||||
|
Filters for additional cases:
|
||||||
|
|
||||||
|
* 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
|
||||||
|
|
||||||
|
## 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
|
||||||
|
auto-inherits from first object class.
|
||||||
|
* The idrange entry ``$REALM_subid_range`` has preconfigured base RIDs
|
||||||
|
and SID so idrange plug-in and sidgen task ignore the entry. It's the
|
||||||
|
simplest approach to ensure backwards compatibility with older IPA
|
||||||
|
server versions that don't know how to handle the new range.
|
||||||
|
The SID is ``S-1-5-21-738065-838566-$DOMAIN_HASH``. ``S-1-5-21``
|
||||||
|
is the well-known SID prefix for domain SIDs. ``738065-838566`` is
|
||||||
|
the decimal representation of the string ``IPA-SUB``. ``DOMAIN_HASH``
|
||||||
|
is the MURMUR-3 hash of the domain name for key ``0xdeadbeef``. SSSD
|
||||||
|
rejects SIDs unless they are prefixed with ``S-1-5-21`` (see
|
||||||
|
``sss_idmap.c:is_domain_sid()``).
|
||||||
|
* The new ``$REALM_subid_range`` entry uses range type ``ipa-ad-trust``
|
||||||
|
instead of range type ``ipa-local-subid`` for backwards compatibility
|
||||||
|
with older SSSD clients, see
|
||||||
|
[SSSD #5571](https://github.com/SSSD/sssd/issues/5571).
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
### TODO
|
||||||
|
|
||||||
|
* enable configuration for ``dnaStepAttr``
|
||||||
|
* 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.
|
@ -1361,6 +1361,7 @@ fi
|
|||||||
%{_libexecdir}/ipa/ipa-pki-wait-running
|
%{_libexecdir}/ipa/ipa-pki-wait-running
|
||||||
%{_libexecdir}/ipa/ipa-otpd
|
%{_libexecdir}/ipa/ipa-otpd
|
||||||
%{_libexecdir}/ipa/ipa-print-pac
|
%{_libexecdir}/ipa/ipa-print-pac
|
||||||
|
%{_libexecdir}/ipa/ipa-subids
|
||||||
%dir %{_libexecdir}/ipa/custodia
|
%dir %{_libexecdir}/ipa/custodia
|
||||||
%attr(755,root,root) %{_libexecdir}/ipa/custodia/ipa-custodia-dmldap
|
%attr(755,root,root) %{_libexecdir}/ipa/custodia/ipa-custodia-dmldap
|
||||||
%attr(755,root,root) %{_libexecdir}/ipa/custodia/ipa-custodia-pki-tomcat
|
%attr(755,root,root) %{_libexecdir}/ipa/custodia/ipa-custodia-pki-tomcat
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
## Attributes: 2.16.840.1.113730.3.8.3 - V2 base attributres
|
## Attributes: 2.16.840.1.113730.3.8.3 - V2 base attributres
|
||||||
## ObjectClasses: 2.16.840.1.113730.3.8.4 - V2 base objectclasses
|
## ObjectClasses: 2.16.840.1.113730.3.8.4 - V2 base objectclasses
|
||||||
## Attributes: 2.16.840.1.113730.3.8.23 - V4 base attributes
|
## Attributes: 2.16.840.1.113730.3.8.23 - V4 base attributes
|
||||||
|
## ObjectClasses: 2.16.840.1.113730.3.8.24 - V4 base objectclasses
|
||||||
##
|
##
|
||||||
dn: cn=schema
|
dn: cn=schema
|
||||||
attributeTypes: (2.16.840.1.113730.3.8.3.1 NAME 'ipaUniqueID' DESC 'Unique identifier' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
|
attributeTypes: (2.16.840.1.113730.3.8.3.1 NAME 'ipaUniqueID' DESC 'Unique identifier' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
|
||||||
|
19
install/share/60basev4.ldif
Normal file
19
install/share/60basev4.ldif
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
## IPA Base OID: 2.16.840.1.113730.3.8
|
||||||
|
##
|
||||||
|
## Attributes: 2.16.840.1.113730.3.8.23 - V4 base attributes
|
||||||
|
## ObjectClasses: 2.16.840.1.113730.3.8.24 - V4 base objectclasses
|
||||||
|
##
|
||||||
|
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')
|
@ -16,6 +16,7 @@ dist_app_DATA = \
|
|||||||
60ipaconfig.ldif \
|
60ipaconfig.ldif \
|
||||||
60basev2.ldif \
|
60basev2.ldif \
|
||||||
60basev3.ldif \
|
60basev3.ldif \
|
||||||
|
60basev4.ldif \
|
||||||
60ipadns.ldif \
|
60ipadns.ldif \
|
||||||
60ipapk11.ldif \
|
60ipapk11.ldif \
|
||||||
60certificate-profiles.ldif \
|
60certificate-profiles.ldif \
|
||||||
|
@ -167,6 +167,12 @@ objectClass: nsContainer
|
|||||||
objectClass: top
|
objectClass: top
|
||||||
cn: posix-ids
|
cn: posix-ids
|
||||||
|
|
||||||
|
dn: cn=subordinate-ids,cn=dna,cn=ipa,cn=etc,$SUFFIX
|
||||||
|
changetype: add
|
||||||
|
objectClass: nsContainer
|
||||||
|
objectClass: top
|
||||||
|
cn: subordinate-ids
|
||||||
|
|
||||||
dn: cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX
|
dn: cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX
|
||||||
changetype: add
|
changetype: add
|
||||||
objectClass: nsContainer
|
objectClass: nsContainer
|
||||||
@ -476,6 +482,22 @@ ipaBaseID: $IDSTART
|
|||||||
ipaIDRangeSize: $IDRANGE_SIZE
|
ipaIDRangeSize: $IDRANGE_SIZE
|
||||||
ipaRangeType: ipa-local
|
ipaRangeType: ipa-local
|
||||||
|
|
||||||
|
dn: cn=${REALM}_subid_range,cn=ranges,cn=etc,$SUFFIX
|
||||||
|
changetype: add
|
||||||
|
objectClass: top
|
||||||
|
objectClass: ipaIDrange
|
||||||
|
objectClass: ipaTrustedADDomainRange
|
||||||
|
cn: ${REALM}_subid_range
|
||||||
|
ipaBaseID: eval($SUBID_RANGE_START)
|
||||||
|
ipaIDRangeSize: eval($SUBID_RANGE_SIZE)
|
||||||
|
# HACK: RIDs to work around adtrust sidgen issue
|
||||||
|
ipaBaseRID: eval($SUBID_RANGE_START - $IDRANGE_SIZE)
|
||||||
|
# 738065-838566 = IPA-SUB
|
||||||
|
ipaNTTrustedDomainSID: S-1-5-21-738065-838566-$DOMAIN_HASH
|
||||||
|
# HACK: "ipa-local-subid" range type causes issues with older SSSD clients
|
||||||
|
# see https://github.com/SSSD/sssd/issues/5571
|
||||||
|
ipaRangeType: ipa-ad-trust
|
||||||
|
|
||||||
dn: cn=ca,$SUFFIX
|
dn: cn=ca,$SUFFIX
|
||||||
changetype: add
|
changetype: add
|
||||||
objectClass: nsContainer
|
objectClass: nsContainer
|
||||||
|
@ -16,6 +16,26 @@ dnaThreshold: 500
|
|||||||
dnaSharedCfgDN: cn=posix-ids,cn=dna,cn=ipa,cn=etc,$SUFFIX
|
dnaSharedCfgDN: cn=posix-ids,cn=dna,cn=ipa,cn=etc,$SUFFIX
|
||||||
dnaExcludeScope: cn=provisioning,$SUFFIX
|
dnaExcludeScope: cn=provisioning,$SUFFIX
|
||||||
|
|
||||||
|
dn: cn=Subordinate IDs,cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config
|
||||||
|
changetype: add
|
||||||
|
objectclass: top
|
||||||
|
objectclass: extensibleObject
|
||||||
|
cn: Subordinate IDs
|
||||||
|
dnaType: ipasubuidnumber
|
||||||
|
dnaType: ipasubgidnumber
|
||||||
|
dnaNextValue: eval($SUBID_RANGE_START)
|
||||||
|
dnaMaxValue: eval($SUBID_RANGE_MAX)
|
||||||
|
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
|
||||||
|
|
||||||
# Enable the DNA plugin
|
# Enable the DNA plugin
|
||||||
dn: cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config
|
dn: cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config
|
||||||
changetype: modify
|
changetype: modify
|
||||||
|
@ -38,6 +38,7 @@ dist_noinst_DATA = \
|
|||||||
ipa-pki-retrieve-key.in \
|
ipa-pki-retrieve-key.in \
|
||||||
ipa-pki-wait-running.in \
|
ipa-pki-wait-running.in \
|
||||||
ipa-acme-manage.in \
|
ipa-acme-manage.in \
|
||||||
|
ipa-subids.in \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
nodist_sbin_SCRIPTS = \
|
nodist_sbin_SCRIPTS = \
|
||||||
@ -78,6 +79,7 @@ nodist_app_SCRIPTS = \
|
|||||||
ipa-httpd-pwdreader \
|
ipa-httpd-pwdreader \
|
||||||
ipa-pki-retrieve-key \
|
ipa-pki-retrieve-key \
|
||||||
ipa-pki-wait-running \
|
ipa-pki-wait-running \
|
||||||
|
ipa-subids \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
PYTHON_SHEBANG = \
|
PYTHON_SHEBANG = \
|
||||||
|
8
install/tools/ipa-subids.in
Normal file
8
install/tools/ipa-subids.in
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
#
|
||||||
|
# Copyright (C) 2021 FreeIPA Contributors see COPYING for license
|
||||||
|
#
|
||||||
|
|
||||||
|
from ipaserver.install.ipa_subids import IPASubids
|
||||||
|
|
||||||
|
IPASubids.run_cli()
|
@ -259,6 +259,33 @@ 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',
|
name: 'pwpolicy',
|
||||||
label: '@i18n:objects.pwpolicy.identity',
|
label: '@i18n:objects.pwpolicy.identity',
|
||||||
@ -451,6 +478,16 @@ return {
|
|||||||
enable_cond: ['is-locked'],
|
enable_cond: ['is-locked'],
|
||||||
confirm_msg: '@i18n:objects.user.unlock_confirm'
|
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',
|
$type: 'automember_rebuild',
|
||||||
name: 'automember_rebuild',
|
name: 'automember_rebuild',
|
||||||
@ -461,12 +498,22 @@ return {
|
|||||||
$type: 'cert_request',
|
$type: 'cert_request',
|
||||||
hide_cond: ['preserved-user'],
|
hide_cond: ['preserved-user'],
|
||||||
title: '@i18n:objects.cert.issue_for_user'
|
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,
|
||||||
|
hide_cond: ['preserved-user'],
|
||||||
|
enable_cond: ['no-subid'],
|
||||||
|
confirm_msg: '@i18n:objects.user.auto_subid_confirm'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
header_actions: [
|
header_actions: [
|
||||||
'reset_password', 'enable', 'disable', 'stage', 'undel',
|
'reset_password', 'enable', 'disable', 'stage', 'undel',
|
||||||
'delete_active_user', 'delete', 'unlock', 'add_otptoken',
|
'delete_active_user', 'delete', 'unlock', 'add_otptoken',
|
||||||
'automember_rebuild', 'request_cert'
|
'automember_rebuild', 'request_cert', 'auto_subid'
|
||||||
],
|
],
|
||||||
state: {
|
state: {
|
||||||
evaluators: [
|
evaluators: [
|
||||||
@ -1159,6 +1206,10 @@ IPA.user.is_locked_evaluator = function(spec) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!user.ipasubuidnumber) {
|
||||||
|
that.state.push('no-subid');
|
||||||
|
}
|
||||||
|
|
||||||
that.notify_on_change(old_state);
|
that.notify_on_change(old_state);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -272,6 +272,24 @@ add:nsIndexType: eq
|
|||||||
add:nsIndexType: pres
|
add:nsIndexType: pres
|
||||||
add:nsIndexType: sub
|
add:nsIndexType: sub
|
||||||
|
|
||||||
|
dn: cn=ipaSubGidNumber,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
|
||||||
|
only:cn: ipaSubGidNumber
|
||||||
|
default:objectClass: nsIndex
|
||||||
|
default:objectClass: top
|
||||||
|
default:nsSystemIndex: false
|
||||||
|
add:nsIndexType: eq
|
||||||
|
add:nsIndexType: pres
|
||||||
|
add:nsMatchingRule: integerOrderingMatch
|
||||||
|
|
||||||
|
dn: cn=ipaSubUidNumber,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
|
||||||
|
only:cn: ipaSubUidNumber
|
||||||
|
default:objectClass: nsIndex
|
||||||
|
default:objectClass: top
|
||||||
|
default:nsSystemIndex: false
|
||||||
|
add:nsIndexType: eq
|
||||||
|
add:nsIndexType: pres
|
||||||
|
add:nsMatchingRule: integerOrderingMatch
|
||||||
|
|
||||||
dn: cn=ipasudorunasgroup,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
|
dn: cn=ipasudorunasgroup,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
|
||||||
only:cn: ipasudorunasgroup
|
only:cn: ipasudorunasgroup
|
||||||
default:objectClass: nsIndex
|
default:objectClass: nsIndex
|
||||||
|
102
install/updates/73-subid.update
Normal file
102
install/updates/73-subid.update
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
# subordinate ids
|
||||||
|
|
||||||
|
# self-service RBAC
|
||||||
|
dn: cn=Subordinate ID Selfservice User,cn=roles,cn=accounts,$SUFFIX
|
||||||
|
default:objectClass: groupofnames
|
||||||
|
default:objectClass: nestedgroup
|
||||||
|
default:objectClass: top
|
||||||
|
default:cn: Subordinate ID Selfservice User
|
||||||
|
default:description: User that can self-request subordiante ids
|
||||||
|
# default: member: cn=ipausers,cn=groups,cn=accounts,$SUFFIX
|
||||||
|
|
||||||
|
dn: cn=Subordinate ID Selfservice Users,cn=privileges,cn=pbac,$SUFFIX
|
||||||
|
default:objectClass: top
|
||||||
|
default:objectClass: groupofnames
|
||||||
|
default:objectClass: nestedgroup
|
||||||
|
default:cn: Subordinate ID Selfservice Users
|
||||||
|
default:description: Subordinate ID Selfservice User
|
||||||
|
default:member: cn=Subordinate ID Selfservice User,cn=roles,cn=accounts,$SUFFIX
|
||||||
|
|
||||||
|
dn: cn=Self-service subordinate ID,cn=permissions,cn=pbac,$SUFFIX
|
||||||
|
default:objectClass: top
|
||||||
|
default:objectClass: groupofnames
|
||||||
|
default:objectClass: ipapermission
|
||||||
|
default:cn: Self-service subordinate ID
|
||||||
|
default:ipapermissiontype: SYSTEM
|
||||||
|
default:member: cn=Subordinate ID Selfservice Users,cn=privileges,cn=pbac,$SUFFIX
|
||||||
|
|
||||||
|
# Administrator RBAC
|
||||||
|
dn: cn=Subordinate ID Administrators,cn=privileges,cn=pbac,$SUFFIX
|
||||||
|
default:objectClass: top
|
||||||
|
default:objectClass: groupofnames
|
||||||
|
default:objectClass: nestedgroup
|
||||||
|
default:cn: Subordinate ID Administrators
|
||||||
|
default:description: Subordinate ID Administrators
|
||||||
|
default:member: cn=User Administrator,cn=roles,cn=accounts,$SUFFIX
|
||||||
|
|
||||||
|
dn: cn=Manage subordinate ID,cn=permissions,cn=pbac,$SUFFIX
|
||||||
|
default:objectClass: top
|
||||||
|
default:objectClass: groupofnames
|
||||||
|
default:objectClass: ipapermission
|
||||||
|
default:cn: Manage subordinate ID
|
||||||
|
default:ipapermissiontype: SYSTEM
|
||||||
|
default:member: cn=Subordinate ID Administrators,cn=privileges,cn=pbac,$SUFFIX
|
||||||
|
|
||||||
|
# ACIs (in domain database root so they also apply to staging area)
|
||||||
|
#
|
||||||
|
# - allow users to request new subid with DNA_MAGIC value, subid count=65536,
|
||||||
|
# and subgid == subuid.
|
||||||
|
# - allow user admins to set subids. count=65536 and subgid == subuid
|
||||||
|
# properties are enforced as wel.
|
||||||
|
#
|
||||||
|
# The delete-when-empty check is required because IPA uses MOD_REPLACE to
|
||||||
|
# set attributes, see https://github.com/389ds/389-ds-base/issues/4597.
|
||||||
|
#
|
||||||
|
# TODO: remove (ipasubuidnumber>=eval($SUBID_RANGE_START) from
|
||||||
|
# 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";)
|
||||||
|
|
||||||
|
# DNA plugin and idrange configuration
|
||||||
|
dn: cn=subordinate-ids,cn=dna,cn=ipa,cn=etc,$SUFFIX
|
||||||
|
default: objectClass: nsContainer
|
||||||
|
default: objectClass: top
|
||||||
|
default: cn: subordinate-ids
|
||||||
|
|
||||||
|
dn: cn=Subordinate IDs,cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config
|
||||||
|
default: objectclass: top
|
||||||
|
default: objectclass: extensibleObject
|
||||||
|
default: cn: Subordinate IDs
|
||||||
|
default: dnaType: ipasubuidnumber
|
||||||
|
default: dnaType: ipasubgidnumber
|
||||||
|
default: dnaNextValue: eval($SUBID_RANGE_START)
|
||||||
|
default: dnaMaxValue: eval($SUBID_RANGE_MAX)
|
||||||
|
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";)
|
||||||
|
|
||||||
|
dn: cn=${REALM}_subid_range,cn=ranges,cn=etc,$SUFFIX
|
||||||
|
default: objectClass: top
|
||||||
|
default: objectClass: ipaIDrange
|
||||||
|
default: objectClass: ipaTrustedADDomainRange
|
||||||
|
default: cn: ${REALM}_subid_range
|
||||||
|
default: ipaBaseID: $SUBID_RANGE_START
|
||||||
|
default: ipaIDRangeSize: $SUBID_RANGE_SIZE
|
||||||
|
# HACK: RIDs to work around adtrust sidgen issue
|
||||||
|
default: ipaBaseRID: eval($SUBID_RANGE_START - $IDRANGE_SIZE)
|
||||||
|
default: ipaNTTrustedDomainSID: S-1-5-21-738065-838566-$DOMAIN_HASH
|
||||||
|
# HACK: "ipa-local-subid" range type causes issues with older SSSD clients
|
||||||
|
# see https://github.com/SSSD/sssd/issues/5571
|
||||||
|
default: ipaRangeType: ipa-ad-trust
|
@ -62,6 +62,7 @@ app_DATA = \
|
|||||||
71-idviews-sasl-mapping.update \
|
71-idviews-sasl-mapping.update \
|
||||||
72-domainlevels.update \
|
72-domainlevels.update \
|
||||||
73-custodia.update \
|
73-custodia.update \
|
||||||
|
73-subid.update \
|
||||||
73-winsync.update \
|
73-winsync.update \
|
||||||
73-certmap.update \
|
73-certmap.update \
|
||||||
75-user-trust-attributes.update \
|
75-user-trust-attributes.update \
|
||||||
|
@ -343,3 +343,16 @@ SOFTHSM_DNSSEC_TOKEN_LABEL = u'ipaDNSSEC'
|
|||||||
# Apache's mod_ssl SSLVerifyDepth value (Maximum depth of CA
|
# Apache's mod_ssl SSLVerifyDepth value (Maximum depth of CA
|
||||||
# Certificates in Client Certificate verification)
|
# Certificates in Client Certificate verification)
|
||||||
MOD_SSL_VERIFY_DEPTH = '5'
|
MOD_SSL_VERIFY_DEPTH = '5'
|
||||||
|
|
||||||
|
# subuid / subgid counts are hard-coded
|
||||||
|
# An interval of 65536 uids/gids is required to map nobody (65534).
|
||||||
|
SUBID_COUNT = 65536
|
||||||
|
|
||||||
|
# upper half of uid_t (uint32_t)
|
||||||
|
SUBID_RANGE_START = 2 ** 31
|
||||||
|
# theoretical max limit is UINT32_MAX-1 ((2 ** 32) - 2)
|
||||||
|
# We use a smaller value to keep the topmost subid interval unused.
|
||||||
|
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
|
||||||
|
@ -36,6 +36,7 @@ from ipaserver.install import service
|
|||||||
from ipaserver.install import installutils
|
from ipaserver.install import installutils
|
||||||
from ipaserver.install.replication import wait_for_task
|
from ipaserver.install.replication import wait_for_task
|
||||||
from ipalib import errors, api
|
from ipalib import errors, api
|
||||||
|
from ipalib.constants import SUBID_RANGE_START
|
||||||
from ipalib.util import normalize_zone
|
from ipalib.util import normalize_zone
|
||||||
from ipapython.dn import DN
|
from ipapython.dn import DN
|
||||||
from ipapython import ipachangeconf
|
from ipapython import ipachangeconf
|
||||||
@ -352,12 +353,19 @@ class ADTRUSTInstance(service.Service):
|
|||||||
DN(api.env.container_ranges, self.suffix),
|
DN(api.env.container_ranges, self.suffix),
|
||||||
ldap.SCOPE_ONELEVEL, "(objectclass=ipaDomainIDRange)")
|
ldap.SCOPE_ONELEVEL, "(objectclass=ipaDomainIDRange)")
|
||||||
|
|
||||||
# Filter out ranges where RID base is already set
|
ranges_with_no_rid_base = []
|
||||||
no_rid_base_set = lambda r: not any((
|
for entry in ranges:
|
||||||
r.single_value.get('ipaBaseRID'),
|
sv = entry.single_value
|
||||||
r.single_value.get('ipaSecondaryBaseRID')))
|
if sv.get('ipaBaseRID') or sv.get('ipaSecondaryBaseRID'):
|
||||||
|
# skip range where RID base is already set
|
||||||
|
continue
|
||||||
|
if sv.get('ipaRangeType') == 'ipa-local-subid':
|
||||||
|
# ignore subid ranges
|
||||||
|
continue
|
||||||
|
ranges_with_no_rid_base.append(entry)
|
||||||
|
|
||||||
ranges_with_no_rid_base = [r for r in ranges if no_rid_base_set(r)]
|
logger.debug(repr(ranges))
|
||||||
|
logger.debug(repr(ranges_with_no_rid_base))
|
||||||
|
|
||||||
# Return if no range is without RID base
|
# Return if no range is without RID base
|
||||||
if len(ranges_with_no_rid_base) == 0:
|
if len(ranges_with_no_rid_base) == 0:
|
||||||
@ -384,6 +392,17 @@ class ADTRUSTInstance(service.Service):
|
|||||||
"They have to differ at least by %d." % size)
|
"They have to differ at least by %d." % size)
|
||||||
raise RuntimeError("RID bases too close.\n")
|
raise RuntimeError("RID bases too close.\n")
|
||||||
|
|
||||||
|
# values above
|
||||||
|
if any(
|
||||||
|
v + size >= SUBID_RANGE_START
|
||||||
|
for v in (self.rid_base, self.secondary_rid_base)
|
||||||
|
):
|
||||||
|
self.print_msg(
|
||||||
|
"Ceiling of primary or secondary base is larger than "
|
||||||
|
f"start of subordinate id range {SUBID_RANGE_START}."
|
||||||
|
)
|
||||||
|
raise RuntimeError("RID bases overlap with SUBID range.\n")
|
||||||
|
|
||||||
# Modify the range
|
# Modify the range
|
||||||
# If the RID bases would cause overlap with some other range,
|
# If the RID bases would cause overlap with some other range,
|
||||||
# this will be detected by ipa-range-check DS plugin
|
# this will be detected by ipa-range-check DS plugin
|
||||||
|
@ -23,7 +23,6 @@ from __future__ import print_function, absolute_import
|
|||||||
import logging
|
import logging
|
||||||
import shutil
|
import shutil
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import fnmatch
|
import fnmatch
|
||||||
|
|
||||||
@ -46,6 +45,7 @@ from ipaserver.install import certs
|
|||||||
from ipaserver.install import replication
|
from ipaserver.install import replication
|
||||||
from ipaserver.install import sysupgrade
|
from ipaserver.install import sysupgrade
|
||||||
from ipaserver.install import upgradeinstance
|
from ipaserver.install import upgradeinstance
|
||||||
|
from ipaserver.install import ldapupdate
|
||||||
from ipalib import api
|
from ipalib import api
|
||||||
from ipalib import errors
|
from ipalib import errors
|
||||||
from ipalib import constants
|
from ipalib import constants
|
||||||
@ -66,6 +66,7 @@ IPA_SCHEMA_FILES = ("60kerberos.ldif",
|
|||||||
"60ipaconfig.ldif",
|
"60ipaconfig.ldif",
|
||||||
"60basev2.ldif",
|
"60basev2.ldif",
|
||||||
"60basev3.ldif",
|
"60basev3.ldif",
|
||||||
|
"60basev4.ldif",
|
||||||
"60ipapk11.ldif",
|
"60ipapk11.ldif",
|
||||||
"60ipadns.ldif",
|
"60ipadns.ldif",
|
||||||
"60certificate-profiles.ldif",
|
"60certificate-profiles.ldif",
|
||||||
@ -214,6 +215,8 @@ class DsInstance(service.Service):
|
|||||||
if realm_name:
|
if realm_name:
|
||||||
self.suffix = ipautil.realm_to_suffix(self.realm)
|
self.suffix = ipautil.realm_to_suffix(self.realm)
|
||||||
self.serverid = ipaldap.realm_to_serverid(self.realm)
|
self.serverid = ipaldap.realm_to_serverid(self.realm)
|
||||||
|
if self.domain is None:
|
||||||
|
self.domain = self.realm.lower()
|
||||||
self.__setup_sub_dict()
|
self.__setup_sub_dict()
|
||||||
else:
|
else:
|
||||||
self.suffix = DN()
|
self.suffix = DN()
|
||||||
@ -497,34 +500,22 @@ class DsInstance(service.Service):
|
|||||||
|
|
||||||
def __setup_sub_dict(self):
|
def __setup_sub_dict(self):
|
||||||
server_root = find_server_root()
|
server_root = find_server_root()
|
||||||
try:
|
self.sub_dict = ldapupdate.get_sub_dict(
|
||||||
idrange_size = self.idmax - self.idstart + 1
|
realm=self.realm,
|
||||||
except TypeError:
|
domain=self.domain,
|
||||||
idrange_size = None
|
suffix=self.suffix,
|
||||||
self.sub_dict = dict(
|
fqdn=self.fqdn,
|
||||||
FQDN=self.fqdn, SERVERID=self.serverid,
|
idstart=self.idstart,
|
||||||
|
idmax=self.idmax,
|
||||||
|
)
|
||||||
|
self.sub_dict.update(
|
||||||
|
DOMAIN_LEVEL=self.domainlevel,
|
||||||
|
SERVERID=self.serverid,
|
||||||
PASSWORD=self.dm_password,
|
PASSWORD=self.dm_password,
|
||||||
RANDOM_PASSWORD=ipautil.ipa_generate_password(),
|
RANDOM_PASSWORD=ipautil.ipa_generate_password(),
|
||||||
SUFFIX=self.suffix,
|
USER=DS_USER,
|
||||||
REALM=self.realm, USER=DS_USER,
|
|
||||||
SERVER_ROOT=server_root, DOMAIN=self.domain,
|
|
||||||
TIME=int(time.time()), IDSTART=self.idstart,
|
|
||||||
IDMAX=self.idmax, HOST=self.fqdn,
|
|
||||||
ESCAPED_SUFFIX=str(self.suffix),
|
|
||||||
GROUP=DS_GROUP,
|
GROUP=DS_GROUP,
|
||||||
IDRANGE_SIZE=idrange_size,
|
SERVER_ROOT=server_root,
|
||||||
DOMAIN_LEVEL=self.domainlevel,
|
|
||||||
MAX_DOMAIN_LEVEL=constants.MAX_DOMAIN_LEVEL,
|
|
||||||
MIN_DOMAIN_LEVEL=constants.MIN_DOMAIN_LEVEL,
|
|
||||||
STRIP_ATTRS=" ".join(replication.STRIP_ATTRS),
|
|
||||||
EXCLUDES='(objectclass=*) $ EXCLUDE ' +
|
|
||||||
' '.join(replication.EXCLUDES),
|
|
||||||
TOTAL_EXCLUDES='(objectclass=*) $ EXCLUDE ' +
|
|
||||||
' '.join(replication.TOTAL_EXCLUDES),
|
|
||||||
DEFAULT_SHELL=platformconstants.DEFAULT_SHELL,
|
|
||||||
DEFAULT_ADMIN_SHELL=platformconstants.DEFAULT_ADMIN_SHELL,
|
|
||||||
SELINUX_USERMAP_DEFAULT=platformconstants.SELINUX_USERMAP_DEFAULT,
|
|
||||||
SELINUX_USERMAP_ORDER=platformconstants.SELINUX_USERMAP_ORDER,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __create_instance(self):
|
def __create_instance(self):
|
||||||
|
154
ipaserver/install/ipa_subids.py
Normal file
154
ipaserver/install/ipa_subids.py
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
#
|
||||||
|
# Copyright (C) 2021 FreeIPA Contributors see COPYING for license
|
||||||
|
#
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from ipalib import api
|
||||||
|
from ipalib import errors
|
||||||
|
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
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class IPASubids(AdminTool):
|
||||||
|
command_name = "ipa-subids"
|
||||||
|
usage = "%prog [--group GROUP|--all-users]"
|
||||||
|
description = "Mass-assign subordinate ids to users"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_options(cls, parser):
|
||||||
|
super(IPASubids, cls).add_options(parser, debug_option=True)
|
||||||
|
parser.add_option(
|
||||||
|
"--group",
|
||||||
|
dest="group",
|
||||||
|
action="store",
|
||||||
|
default=None,
|
||||||
|
help="Updates members of a user group.",
|
||||||
|
)
|
||||||
|
parser.add_option(
|
||||||
|
"--all-users",
|
||||||
|
dest="all_users",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="Update all users.",
|
||||||
|
)
|
||||||
|
parser.add_option(
|
||||||
|
"--filter",
|
||||||
|
dest="user_filter",
|
||||||
|
action="store",
|
||||||
|
default="(!(nsaccountlock=TRUE))",
|
||||||
|
help="Additional raw LDAP filter (default: active users).",
|
||||||
|
)
|
||||||
|
parser.add_option(
|
||||||
|
"--dry-run",
|
||||||
|
dest="dry_run",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="Dry run mode.",
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate_options(self, neends_root=False):
|
||||||
|
super().validate_options(needs_root=True)
|
||||||
|
opt = self.safe_options
|
||||||
|
|
||||||
|
if opt.all_users and opt.group:
|
||||||
|
raise ScriptError("--group and --all-users are mutually exclusive")
|
||||||
|
if not opt.all_users and not opt.group:
|
||||||
|
raise ScriptError("Either --group or --all-users required")
|
||||||
|
|
||||||
|
def get_group_info(self):
|
||||||
|
assert api.isdone("finalize")
|
||||||
|
group = self.safe_options.group
|
||||||
|
if group is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
result = api.Command.group_show(group, no_members=True)
|
||||||
|
return result["result"]
|
||||||
|
except errors.NotFound:
|
||||||
|
raise ScriptError(f"Unknown users group '{group}'.")
|
||||||
|
|
||||||
|
def make_filter(self, groupinfo, user_filter):
|
||||||
|
filters = [
|
||||||
|
# only users with posixAccount
|
||||||
|
"(objectClass=posixAccount)",
|
||||||
|
# without subordinate ids
|
||||||
|
"(!(objectClass=ipaSubordinateId))",
|
||||||
|
]
|
||||||
|
if groupinfo is not None:
|
||||||
|
filters.append(
|
||||||
|
self.ldap2.make_filter({"memberof": groupinfo["dn"]})
|
||||||
|
)
|
||||||
|
if user_filter:
|
||||||
|
filters.append(user_filter)
|
||||||
|
return self.ldap2.combine_filters(filters, self.ldap2.MATCH_ALL)
|
||||||
|
|
||||||
|
def search_users(self, filters):
|
||||||
|
users_dn = DN(api.env.container_user, api.env.basedn)
|
||||||
|
attrs = ["objectclass", "uid", "uidnumber"]
|
||||||
|
|
||||||
|
logger.debug("basedn: %s", users_dn)
|
||||||
|
logger.debug("attrs: %s", attrs)
|
||||||
|
logger.debug("filter: %s", filters)
|
||||||
|
|
||||||
|
try:
|
||||||
|
entries = self.ldap2.get_entries(
|
||||||
|
base_dn=users_dn,
|
||||||
|
filter=filters,
|
||||||
|
attrs_list=attrs,
|
||||||
|
)
|
||||||
|
except errors.NotFound:
|
||||||
|
logger.debug("No entries found")
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
return entries
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
if not is_ipa_configured():
|
||||||
|
print("IPA is not configured.")
|
||||||
|
return 2
|
||||||
|
|
||||||
|
api.bootstrap(in_server=True, confdir=paths.ETC_IPA)
|
||||||
|
api.finalize()
|
||||||
|
api.Backend.ldap2.connect()
|
||||||
|
self.ldap2 = api.Backend.ldap2
|
||||||
|
user_obj = api.Object["user"]
|
||||||
|
|
||||||
|
dry_run = self.safe_options.dry_run
|
||||||
|
group_info = self.get_group_info()
|
||||||
|
filters = self.make_filter(
|
||||||
|
group_info, self.safe_options.user_filter
|
||||||
|
)
|
||||||
|
|
||||||
|
entries = self.search_users(filters)
|
||||||
|
total = len(entries)
|
||||||
|
logger.info("Found %i user(s) without subordinate ids", total)
|
||||||
|
|
||||||
|
total = len(entries)
|
||||||
|
for i, entry in enumerate(entries, start=1):
|
||||||
|
logger.info(
|
||||||
|
" Processing user '%s' (%i/%i)",
|
||||||
|
entry.single_value["uid"],
|
||||||
|
i,
|
||||||
|
total
|
||||||
|
)
|
||||||
|
user_obj.set_subordinate_ids(
|
||||||
|
self.ldap2, entry.dn, entry, DNA_MAGIC
|
||||||
|
)
|
||||||
|
if not dry_run:
|
||||||
|
self.ldap2.update_entry(entry)
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
logger.info("Dry run mode, no user was modified")
|
||||||
|
else:
|
||||||
|
logger.info("Updated %s user(s)", total)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
IPASubids.run_cli()
|
@ -32,6 +32,7 @@ import os
|
|||||||
import fnmatch
|
import fnmatch
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
from pysss_murmur import murmurhash3 # pylint: disable=no-name-in-module
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from ipapython import ipautil, ipaldap
|
from ipapython import ipautil, ipaldap
|
||||||
@ -53,6 +54,54 @@ UPDATES_DIR=paths.UPDATES_DIR
|
|||||||
UPDATE_SEARCH_TIME_LIMIT = 30 # seconds
|
UPDATE_SEARCH_TIME_LIMIT = 30 # seconds
|
||||||
|
|
||||||
|
|
||||||
|
def get_sub_dict(realm, domain, suffix, fqdn, idstart=None, idmax=None):
|
||||||
|
"""LDAP template substitution dict for installer and updater
|
||||||
|
"""
|
||||||
|
if idstart is None:
|
||||||
|
idrange_size = None
|
||||||
|
else:
|
||||||
|
idrange_size = idmax - idstart + 1
|
||||||
|
|
||||||
|
return dict(
|
||||||
|
REALM=realm,
|
||||||
|
DOMAIN=domain,
|
||||||
|
SUFFIX=suffix,
|
||||||
|
ESCAPED_SUFFIX=str(suffix),
|
||||||
|
FQDN=fqdn,
|
||||||
|
HOST=fqdn,
|
||||||
|
LIBARCH=paths.LIBARCH,
|
||||||
|
TIME=int(time.time()),
|
||||||
|
FIPS="#" if tasks.is_fips_enabled() else "",
|
||||||
|
# idstart, idmax, and idrange_size may be None
|
||||||
|
IDSTART=idstart,
|
||||||
|
IDMAX=idmax,
|
||||||
|
IDRANGE_SIZE=idrange_size,
|
||||||
|
SUBID_COUNT=constants.SUBID_COUNT,
|
||||||
|
SUBID_RANGE_START=constants.SUBID_RANGE_START,
|
||||||
|
SUBID_RANGE_SIZE=constants.SUBID_RANGE_SIZE,
|
||||||
|
SUBID_RANGE_MAX=constants.SUBID_RANGE_MAX,
|
||||||
|
SUBID_DNA_THRESHOLD=constants.SUBID_DNA_THRESHOLD,
|
||||||
|
DOMAIN_HASH=murmurhash3(domain, len(domain), 0xdeadbeef),
|
||||||
|
MAX_DOMAIN_LEVEL=constants.MAX_DOMAIN_LEVEL,
|
||||||
|
MIN_DOMAIN_LEVEL=constants.MIN_DOMAIN_LEVEL,
|
||||||
|
STRIP_ATTRS=" ".join(replication.STRIP_ATTRS),
|
||||||
|
EXCLUDES=(
|
||||||
|
'(objectclass=*) $ EXCLUDE ' + ' '.join(replication.EXCLUDES)
|
||||||
|
),
|
||||||
|
TOTAL_EXCLUDES=(
|
||||||
|
'(objectclass=*) $ EXCLUDE '
|
||||||
|
+ ' '.join(replication.TOTAL_EXCLUDES)
|
||||||
|
),
|
||||||
|
DEFAULT_SHELL=platformconstants.DEFAULT_SHELL,
|
||||||
|
DEFAULT_ADMIN_SHELL=platformconstants.DEFAULT_ADMIN_SHELL,
|
||||||
|
SELINUX_USERMAP_DEFAULT=platformconstants.SELINUX_USERMAP_DEFAULT,
|
||||||
|
SELINUX_USERMAP_ORDER=platformconstants.SELINUX_USERMAP_ORDER,
|
||||||
|
# uid / gid for autobind
|
||||||
|
NAMED_UID=platformconstants.NAMED_USER.uid,
|
||||||
|
NAMED_GID=platformconstants.NAMED_GROUP.gid,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def connect(ldapi=False, realm=None, fqdn=None):
|
def connect(ldapi=False, realm=None, fqdn=None):
|
||||||
"""Create a connection for updates"""
|
"""Create a connection for updates"""
|
||||||
if ldapi:
|
if ldapi:
|
||||||
@ -314,38 +363,33 @@ class LDAPUpdate:
|
|||||||
ldap_uri=self.ldapuri
|
ldap_uri=self.ldapuri
|
||||||
)
|
)
|
||||||
self.api.finalize()
|
self.api.finalize()
|
||||||
|
|
||||||
self.create_connection()
|
self.create_connection()
|
||||||
|
|
||||||
|
# get ipa-local domain idrange settings
|
||||||
|
domain_range = f"{self.api.env.realm}_id_range"
|
||||||
|
try:
|
||||||
|
result = self.api.Command.idrange_show(domain_range)["result"]
|
||||||
|
except errors.NotFound:
|
||||||
|
idstart = None
|
||||||
|
idmax = None
|
||||||
|
else:
|
||||||
|
idstart = int(result['ipabaseid'][0])
|
||||||
|
idrange_size = int(result['ipaidrangesize'][0])
|
||||||
|
idmax = idstart + idrange_size - 1
|
||||||
|
|
||||||
|
default_sub = get_sub_dict(
|
||||||
|
realm=api.env.realm,
|
||||||
|
domain=api.env.domain,
|
||||||
|
suffix=api.env.basedn,
|
||||||
|
fqdn=api.env.host,
|
||||||
|
idstart=idstart,
|
||||||
|
idmax=idmax,
|
||||||
|
)
|
||||||
replication_plugin = (
|
replication_plugin = (
|
||||||
installutils.get_replication_plugin_name(self.conn.get_entry)
|
installutils.get_replication_plugin_name(self.conn.get_entry)
|
||||||
)
|
)
|
||||||
|
default_sub["REPLICATION_PLUGIN"] = replication_plugin
|
||||||
|
|
||||||
default_sub = dict(
|
|
||||||
REALM=api.env.realm,
|
|
||||||
DOMAIN=api.env.domain,
|
|
||||||
SUFFIX=api.env.basedn,
|
|
||||||
ESCAPED_SUFFIX=str(api.env.basedn),
|
|
||||||
FQDN=api.env.host,
|
|
||||||
LIBARCH=paths.LIBARCH,
|
|
||||||
TIME=int(time.time()),
|
|
||||||
MIN_DOMAIN_LEVEL=str(constants.MIN_DOMAIN_LEVEL),
|
|
||||||
MAX_DOMAIN_LEVEL=str(constants.MAX_DOMAIN_LEVEL),
|
|
||||||
STRIP_ATTRS=" ".join(constants.REPL_AGMT_STRIP_ATTRS),
|
|
||||||
EXCLUDES="(objectclass=*) $ EXCLUDE %s" % (
|
|
||||||
" ".join(constants.REPL_AGMT_EXCLUDES)
|
|
||||||
),
|
|
||||||
TOTAL_EXCLUDES="(objectclass=*) $ EXCLUDE %s" % (
|
|
||||||
" ".join(constants.REPL_AGMT_TOTAL_EXCLUDES)
|
|
||||||
),
|
|
||||||
SELINUX_USERMAP_DEFAULT=platformconstants.SELINUX_USERMAP_DEFAULT,
|
|
||||||
SELINUX_USERMAP_ORDER=platformconstants.SELINUX_USERMAP_ORDER,
|
|
||||||
FIPS="#" if tasks.is_fips_enabled() else "",
|
|
||||||
# uid / gid for autobind
|
|
||||||
NAMED_UID=platformconstants.NAMED_USER.uid,
|
|
||||||
NAMED_GID=platformconstants.NAMED_GROUP.gid,
|
|
||||||
REPLICATION_PLUGIN=replication_plugin,
|
|
||||||
)
|
|
||||||
for k, v in default_sub.items():
|
for k, v in default_sub.items():
|
||||||
self.sub_dict.setdefault(k, v)
|
self.sub_dict.setdefault(k, v)
|
||||||
|
|
||||||
|
@ -17,9 +17,10 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import random
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from ipalib import api, errors
|
from ipalib import api, errors, output, constants
|
||||||
from ipalib import (
|
from ipalib import (
|
||||||
Flag, Int, Password, Str, Bool, StrEnum, DateTime, DNParam)
|
Flag, Int, Password, Str, Bool, StrEnum, DateTime, DNParam)
|
||||||
from ipalib.parameters import Principal, Certificate
|
from ipalib.parameters import Principal, Certificate
|
||||||
@ -27,13 +28,13 @@ from ipalib.plugable import Registry
|
|||||||
from .baseldap import (
|
from .baseldap import (
|
||||||
DN, LDAPObject, LDAPCreate, LDAPUpdate, LDAPSearch, LDAPDelete,
|
DN, LDAPObject, LDAPCreate, LDAPUpdate, LDAPSearch, LDAPDelete,
|
||||||
LDAPRetrieve, LDAPAddAttribute, LDAPModAttribute, LDAPRemoveAttribute,
|
LDAPRetrieve, LDAPAddAttribute, LDAPModAttribute, LDAPRemoveAttribute,
|
||||||
LDAPAddMember, LDAPRemoveMember,
|
LDAPQuery, LDAPAddMember, LDAPRemoveMember,
|
||||||
LDAPAddAttributeViaOption, LDAPRemoveAttributeViaOption,
|
LDAPAddAttributeViaOption, LDAPRemoveAttributeViaOption,
|
||||||
add_missing_object_class)
|
add_missing_object_class, DNA_MAGIC, pkey_to_value, entry_to_dict
|
||||||
|
)
|
||||||
from ipaserver.plugins.service import (validate_realm, normalize_principal)
|
from ipaserver.plugins.service import (validate_realm, normalize_principal)
|
||||||
from ipalib.request import context
|
from ipalib.request import context
|
||||||
from ipalib import _
|
from ipalib import _
|
||||||
from ipalib.constants import PATTERN_GROUPUSER_NAME
|
|
||||||
from ipapython import kerberos
|
from ipapython import kerberos
|
||||||
from ipapython.ipautil import ipa_generate_password, TMP_PWD_ENTROPY_BITS
|
from ipapython.ipautil import ipa_generate_password, TMP_PWD_ENTROPY_BITS
|
||||||
from ipapython.ipavalidate import Email
|
from ipapython.ipavalidate import Email
|
||||||
@ -161,7 +162,7 @@ class baseuser(LDAPObject):
|
|||||||
possible_objectclasses = [
|
possible_objectclasses = [
|
||||||
'meporiginentry', 'ipauserauthtypeclass', 'ipauser',
|
'meporiginentry', 'ipauserauthtypeclass', 'ipauser',
|
||||||
'ipatokenradiusproxyuser', 'ipacertmapobject',
|
'ipatokenradiusproxyuser', 'ipacertmapobject',
|
||||||
'ipantuserattrs'
|
'ipantuserattrs', 'ipasubordinateid',
|
||||||
]
|
]
|
||||||
disallow_object_classes = ['krbticketpolicyaux']
|
disallow_object_classes = ['krbticketpolicyaux']
|
||||||
permission_filter_objectclasses = ['posixaccount']
|
permission_filter_objectclasses = ['posixaccount']
|
||||||
@ -175,13 +176,15 @@ class baseuser(LDAPObject):
|
|||||||
'krbprincipalexpiration', 'usercertificate;binary',
|
'krbprincipalexpiration', 'usercertificate;binary',
|
||||||
'krbprincipalname', 'krbcanonicalname',
|
'krbprincipalname', 'krbcanonicalname',
|
||||||
'ipacertmapdata', 'ipantlogonscript', 'ipantprofilepath',
|
'ipacertmapdata', 'ipantlogonscript', 'ipantprofilepath',
|
||||||
'ipanthomedirectory', 'ipanthomedirectorydrive'
|
'ipanthomedirectory', 'ipanthomedirectorydrive',
|
||||||
]
|
]
|
||||||
search_display_attributes = [
|
search_display_attributes = [
|
||||||
'uid', 'givenname', 'sn', 'homedirectory', 'krbcanonicalname',
|
'uid', 'givenname', 'sn', 'homedirectory', 'krbcanonicalname',
|
||||||
'krbprincipalname', 'loginshell',
|
'krbprincipalname', 'loginshell',
|
||||||
'mail', 'telephonenumber', 'title', 'nsaccountlock',
|
'mail', 'telephonenumber', 'title', 'nsaccountlock',
|
||||||
'uidnumber', 'gidnumber', 'sshpubkeyfp',
|
'uidnumber', 'gidnumber', 'sshpubkeyfp',
|
||||||
|
'ipasubuidnumber', 'ipasubuidcount', 'ipasubgidnumber',
|
||||||
|
'ipasubgidcount',
|
||||||
]
|
]
|
||||||
uuid_attribute = 'ipauniqueid'
|
uuid_attribute = 'ipauniqueid'
|
||||||
attribute_members = {
|
attribute_members = {
|
||||||
@ -198,7 +201,7 @@ class baseuser(LDAPObject):
|
|||||||
|
|
||||||
takes_params = (
|
takes_params = (
|
||||||
Str('uid',
|
Str('uid',
|
||||||
pattern=PATTERN_GROUPUSER_NAME,
|
pattern=constants.PATTERN_GROUPUSER_NAME,
|
||||||
pattern_errmsg='may only include letters, numbers, _, -, . and $',
|
pattern_errmsg='may only include letters, numbers, _, -, . and $',
|
||||||
maxlength=255,
|
maxlength=255,
|
||||||
cli_name='login',
|
cli_name='login',
|
||||||
@ -429,6 +432,41 @@ class baseuser(LDAPObject):
|
|||||||
'J:', 'K:', 'L:', 'M:', 'N:', 'O:', 'P:', 'Q:', 'R:',
|
'J:', 'K:', 'L:', 'M:', 'N:', 'O:', 'P:', 'Q:', 'R:',
|
||||||
'S:', 'T:', 'U:', 'V:', 'W:', 'X:', 'Y:', 'Z:'),
|
'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):
|
def normalize_and_validate_email(self, email, config=None):
|
||||||
@ -526,6 +564,131 @@ class baseuser(LDAPObject):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
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):
|
class baseuser_add(LDAPCreate):
|
||||||
"""
|
"""
|
||||||
@ -536,6 +699,7 @@ class baseuser_add(LDAPCreate):
|
|||||||
assert isinstance(dn, DN)
|
assert isinstance(dn, DN)
|
||||||
set_krbcanonicalname(entry_attrs)
|
set_krbcanonicalname(entry_attrs)
|
||||||
self.obj.convert_usercertificate_pre(entry_attrs)
|
self.obj.convert_usercertificate_pre(entry_attrs)
|
||||||
|
self.obj.handle_subordinate_ids(ldap, dn, entry_attrs)
|
||||||
if entry_attrs.get('ipatokenradiususername', None):
|
if entry_attrs.get('ipatokenradiususername', None):
|
||||||
add_missing_object_class(ldap, u'ipatokenradiusproxyuser', dn,
|
add_missing_object_class(ldap, u'ipatokenradiusproxyuser', dn,
|
||||||
entry_attrs, update=False)
|
entry_attrs, update=False)
|
||||||
@ -688,6 +852,7 @@ class baseuser_mod(LDAPUpdate):
|
|||||||
|
|
||||||
self.check_objectclass(ldap, dn, entry_attrs)
|
self.check_objectclass(ldap, dn, entry_attrs)
|
||||||
self.obj.convert_usercertificate_pre(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)
|
self.preserve_krbprincipalname_pre(ldap, entry_attrs, *keys, **options)
|
||||||
update_samba_attrs(ldap, dn, entry_attrs, **options)
|
update_samba_attrs(ldap, dn, entry_attrs, **options)
|
||||||
|
|
||||||
@ -968,3 +1133,98 @@ class baseuser_remove_certmapdata(ModCertMapData,
|
|||||||
LDAPRemoveAttribute):
|
LDAPRemoveAttribute):
|
||||||
__doc__ = _("Remove one or more certificate mappings from the user entry.")
|
__doc__ = _("Remove one or more certificate mappings from the user entry.")
|
||||||
msg_summary = _('Removed certificate mappings from user "%(value)s"')
|
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
|
||||||
|
@ -205,6 +205,7 @@ class idrange(LDAPObject):
|
|||||||
# The commented range types are planned but not yet supported
|
# The commented range types are planned but not yet supported
|
||||||
range_types = {
|
range_types = {
|
||||||
u'ipa-local': unicode(_('local domain range')),
|
u'ipa-local': unicode(_('local domain range')),
|
||||||
|
# u'ipa-local-subid': unicode(_('local domain subid range')),
|
||||||
# u'ipa-ad-winsync': unicode(_('Active Directory winsync range')),
|
# u'ipa-ad-winsync': unicode(_('Active Directory winsync range')),
|
||||||
u'ipa-ad-trust': unicode(_('Active Directory domain range')),
|
u'ipa-ad-trust': unicode(_('Active Directory domain range')),
|
||||||
u'ipa-ad-trust-posix': unicode(_('Active Directory trust range with '
|
u'ipa-ad-trust-posix': unicode(_('Active Directory trust range with '
|
||||||
@ -221,10 +222,14 @@ class idrange(LDAPObject):
|
|||||||
Int('ipabaseid',
|
Int('ipabaseid',
|
||||||
cli_name='base_id',
|
cli_name='base_id',
|
||||||
label=_("First Posix ID of the range"),
|
label=_("First Posix ID of the range"),
|
||||||
|
minvalue=1,
|
||||||
|
maxvalue=Int.MAX_UINT32
|
||||||
),
|
),
|
||||||
Int('ipaidrangesize',
|
Int('ipaidrangesize',
|
||||||
cli_name='range_size',
|
cli_name='range_size',
|
||||||
label=_("Number of IDs in the range"),
|
label=_("Number of IDs in the range"),
|
||||||
|
minvalue=1,
|
||||||
|
maxvalue=Int.MAX_UINT32
|
||||||
),
|
),
|
||||||
Int('ipabaserid?',
|
Int('ipabaserid?',
|
||||||
cli_name='rid_base',
|
cli_name='rid_base',
|
||||||
@ -669,7 +674,10 @@ class idrange_mod(LDAPUpdate):
|
|||||||
except errors.NotFound:
|
except errors.NotFound:
|
||||||
raise self.obj.handle_not_found(*keys)
|
raise self.obj.handle_not_found(*keys)
|
||||||
|
|
||||||
if old_attrs['iparangetype'][0] == 'ipa-local':
|
if (
|
||||||
|
old_attrs['iparangetype'][0] in {'ipa-local', 'ipa-local-subid'}
|
||||||
|
or old_attrs['cn'][0] == f'{self.api.env.realm}_subid_range'
|
||||||
|
):
|
||||||
raise errors.ExecutionError(
|
raise errors.ExecutionError(
|
||||||
message=_('This command can not be used to change ID '
|
message=_('This command can not be used to change ID '
|
||||||
'allocation for local IPA domain. Run '
|
'allocation for local IPA domain. Run '
|
||||||
|
@ -1547,6 +1547,13 @@ class i18n_messages(Command):
|
|||||||
"Drive to mount a home directory"
|
"Drive to mount a home directory"
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
"subordinate": {
|
||||||
|
"identity": _("Subordinate user and group id"),
|
||||||
|
"subuidnumber": _("Subordinate user id"),
|
||||||
|
"subuidcount": _("Subordinate user id count"),
|
||||||
|
"subgidnumber": _("Subordinate group id"),
|
||||||
|
"subgidcount": _("Subordinate group id count"),
|
||||||
|
},
|
||||||
"trustconfig": {
|
"trustconfig": {
|
||||||
"options": _("Options"),
|
"options": _("Options"),
|
||||||
},
|
},
|
||||||
@ -1570,6 +1577,11 @@ class i18n_messages(Command):
|
|||||||
"add_into_sudo": _(
|
"add_into_sudo": _(
|
||||||
"Add user '${primary_key}' into sudo rules"
|
"Add user '${primary_key}' into sudo rules"
|
||||||
),
|
),
|
||||||
|
"auto_subid": _("Auto assign subordinate ids"),
|
||||||
|
"auto_subid_confirm": _(
|
||||||
|
"Are you sure you want to auto-assign a subordinate id "
|
||||||
|
"to user ${object}?"
|
||||||
|
),
|
||||||
"contact": _("Contact Settings"),
|
"contact": _("Contact Settings"),
|
||||||
"delete_mode": _("Delete mode"),
|
"delete_mode": _("Delete mode"),
|
||||||
"employee": _("Employee Information"),
|
"employee": _("Employee Information"),
|
||||||
|
@ -50,7 +50,10 @@ from .baseuser import (
|
|||||||
baseuser_add_principal,
|
baseuser_add_principal,
|
||||||
baseuser_remove_principal,
|
baseuser_remove_principal,
|
||||||
baseuser_add_certmapdata,
|
baseuser_add_certmapdata,
|
||||||
baseuser_remove_certmapdata)
|
baseuser_remove_certmapdata,
|
||||||
|
baseuser_auto_subid,
|
||||||
|
baseuser_match_subid,
|
||||||
|
)
|
||||||
from .idviews import remove_ipaobject_overrides
|
from .idviews import remove_ipaobject_overrides
|
||||||
from ipalib.plugable import Registry
|
from ipalib.plugable import Registry
|
||||||
from .baseldap import (
|
from .baseldap import (
|
||||||
@ -202,6 +205,8 @@ class user(baseuser):
|
|||||||
'ipapermright': {'read', 'search', 'compare'},
|
'ipapermright': {'read', 'search', 'compare'},
|
||||||
'ipapermdefaultattr': {
|
'ipapermdefaultattr': {
|
||||||
'ipauniqueid', 'ipasshpubkey', 'ipauserauthtype', 'userclass',
|
'ipauniqueid', 'ipasshpubkey', 'ipauserauthtype', 'userclass',
|
||||||
|
'ipasubuidnumber', 'ipasubuidcount', 'ipasubgidnumber',
|
||||||
|
'ipasubgidcount',
|
||||||
},
|
},
|
||||||
'fixup_function': fix_addressbook_permission_bindrule,
|
'fixup_function': fix_addressbook_permission_bindrule,
|
||||||
},
|
},
|
||||||
@ -1306,3 +1311,13 @@ class user_add_principal(baseuser_add_principal):
|
|||||||
class user_remove_principal(baseuser_remove_principal):
|
class user_remove_principal(baseuser_remove_principal):
|
||||||
__doc__ = _('Remove principal alias from the user entry')
|
__doc__ = _('Remove principal alias from the user entry')
|
||||||
msg_summary = _('Removed aliases from user "%(value)s"')
|
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__
|
||||||
|
@ -298,3 +298,15 @@ jobs:
|
|||||||
template: *ci-master-latest
|
template: *ci-master-latest
|
||||||
timeout: 3600
|
timeout: 3600
|
||||||
topology: *master_1repl
|
topology: *master_1repl
|
||||||
|
|
||||||
|
fedora-latest/test_subids:
|
||||||
|
requires: [fedora-latest/build]
|
||||||
|
priority: 100
|
||||||
|
job:
|
||||||
|
class: RunPytest
|
||||||
|
args:
|
||||||
|
build_url: '{fedora-latest/build_url}'
|
||||||
|
test_suite: test_integration/test_subids.py
|
||||||
|
template: *ci-master-latest
|
||||||
|
timeout: 3600
|
||||||
|
topology: *master_1repl
|
||||||
|
201
ipatests/test_integration/test_subids.py
Normal file
201
ipatests/test_integration/test_subids.py
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
#
|
||||||
|
# Copyright (C) 2021 FreeIPA Contributors see COPYING for license
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Tests for subordinate ids
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
|
||||||
|
from ipalib.constants import SUBID_COUNT, SUBID_RANGE_START, SUBID_RANGE_MAX
|
||||||
|
from ipaplatform.paths import paths
|
||||||
|
from ipatests.pytest_ipa.integration import tasks
|
||||||
|
from ipatests.test_integration.base import IntegrationTest
|
||||||
|
|
||||||
|
|
||||||
|
class TestSubordinateId(IntegrationTest):
|
||||||
|
num_replicas = 0
|
||||||
|
topology = "star"
|
||||||
|
|
||||||
|
def _parse_result(self, result):
|
||||||
|
info = {}
|
||||||
|
for line in result.stdout_text.split("\n"):
|
||||||
|
line = line.strip()
|
||||||
|
if line:
|
||||||
|
if ":" not in line:
|
||||||
|
continue
|
||||||
|
k, v = line.split(":", 1)
|
||||||
|
k = k.strip()
|
||||||
|
v = v.strip()
|
||||||
|
try:
|
||||||
|
v = int(v, 10)
|
||||||
|
except ValueError:
|
||||||
|
if v == "FALSE":
|
||||||
|
v = False
|
||||||
|
elif v == "TRUE":
|
||||||
|
v = True
|
||||||
|
info.setdefault(k.lower(), []).append(v)
|
||||||
|
|
||||||
|
for k, v in info.items():
|
||||||
|
if len(v) == 1:
|
||||||
|
info[k] = v[0]
|
||||||
|
else:
|
||||||
|
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 user_auto_subid(self, uid, **kwargs):
|
||||||
|
cmd = ["ipa", "user-auto-subid", uid]
|
||||||
|
return self.master.run_command(cmd, **kwargs)
|
||||||
|
|
||||||
|
def test_auto_subid(self):
|
||||||
|
tasks.kinit_admin(self.master)
|
||||||
|
uid = "testuser_auto1"
|
||||||
|
tasks.user_add(self.master, uid)
|
||||||
|
info = self.get_user(uid)
|
||||||
|
assert "ipasubuidcount" not in info
|
||||||
|
|
||||||
|
self.user_auto_subid(uid)
|
||||||
|
info = self.get_user(uid)
|
||||||
|
assert "ipasubuidcount" in info
|
||||||
|
|
||||||
|
subuid = info["ipasubuidnumber"]
|
||||||
|
result = self.master.run_command(
|
||||||
|
["ipa", "user-match-subid", f"--subuid={subuid}", "--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
|
||||||
|
|
||||||
|
def test_ipa_subid_script(self):
|
||||||
|
tasks.kinit_admin(self.master)
|
||||||
|
|
||||||
|
tool = os.path.join(paths.LIBEXEC_IPA_DIR, "ipa-subids")
|
||||||
|
users = []
|
||||||
|
for i in range(1, 11):
|
||||||
|
uid = f"testuser_script{i}"
|
||||||
|
users.append(uid)
|
||||||
|
tasks.user_add(self.master, uid)
|
||||||
|
info = self.get_user(uid)
|
||||||
|
assert "ipasubuidcount" not in info
|
||||||
|
|
||||||
|
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"]
|
||||||
|
|
||||||
|
def test_subid_selfservice(self):
|
||||||
|
tasks.kinit_admin(self.master)
|
||||||
|
|
||||||
|
uid = "testuser_selfservice1"
|
||||||
|
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)
|
||||||
|
assert result.returncode > 0
|
||||||
|
|
||||||
|
tasks.kinit_admin(self.master)
|
||||||
|
self.master.run_command(
|
||||||
|
["ipa", "role-add-member", role, "--groups=ipausers"]
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
tasks.kinit_user(self.master, uid, password)
|
||||||
|
self.user_auto_subid(uid)
|
||||||
|
info = self.get_user(uid)
|
||||||
|
assert "ipasubuidcount" in info
|
||||||
|
finally:
|
||||||
|
tasks.kinit_admin(self.master)
|
||||||
|
self.master.run_command(
|
||||||
|
["ipa", "role-remove-member", role, "--groups=ipausers"]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_subid_useradmin(self):
|
||||||
|
tasks.kinit_admin(self.master)
|
||||||
|
|
||||||
|
uid_useradmin = "testuser_usermgr_mgr1"
|
||||||
|
role = "User Administrator"
|
||||||
|
uid = "testuser_usermgr_user1"
|
||||||
|
password = "Secret123"
|
||||||
|
|
||||||
|
# create user administrator
|
||||||
|
tasks.user_add(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",
|
||||||
|
)
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
def test_subordinate_default_objclass(self):
|
||||||
|
tasks.kinit_admin(self.master)
|
||||||
|
|
||||||
|
result = self.master.run_command(
|
||||||
|
["ipa", "config-show", "--raw", "--all"]
|
||||||
|
)
|
||||||
|
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"
|
||||||
|
tasks.user_add(self.master, uid)
|
||||||
|
info = self.get_user(uid)
|
||||||
|
assert "ipasubuidcount" in info
|
||||||
|
|
||||||
|
def test_idrange_subid(self):
|
||||||
|
tasks.kinit_admin(self.master)
|
||||||
|
|
||||||
|
range_name = f"{self.master.domain.realm}_subid_range"
|
||||||
|
|
||||||
|
result = self.master.run_command(
|
||||||
|
["ipa", "idrange-show", range_name, "--raw"]
|
||||||
|
)
|
||||||
|
info = self._parse_result(result)
|
||||||
|
|
||||||
|
# see https://github.com/SSSD/sssd/issues/5571
|
||||||
|
assert info["iparangetype"] == "ipa-ad-trust"
|
||||||
|
assert info["ipabaseid"] == SUBID_RANGE_START
|
||||||
|
assert info["ipaidrangesize"] == SUBID_RANGE_MAX - SUBID_RANGE_START
|
||||||
|
assert info["ipabaserid"] < SUBID_RANGE_START
|
||||||
|
assert "ipasecondarybaserid" not in info
|
||||||
|
assert info["ipanttrusteddomainsid"].startswith(
|
||||||
|
"S-1-5-21-738065-838566-"
|
||||||
|
)
|
@ -24,6 +24,7 @@ Test the `ipaserver/plugins/idrange.py` module, and XML-RPC in general.
|
|||||||
import six
|
import six
|
||||||
|
|
||||||
from ipalib import api, errors, messages
|
from ipalib import api, errors, messages
|
||||||
|
from ipalib import constants
|
||||||
from ipaplatform import services
|
from ipaplatform import services
|
||||||
from ipatests.test_xmlrpc.xmlrpc_test import Declarative, fuzzy_uuid
|
from ipatests.test_xmlrpc.xmlrpc_test import Declarative, fuzzy_uuid
|
||||||
from ipatests.test_xmlrpc import objectclasses
|
from ipatests.test_xmlrpc import objectclasses
|
||||||
@ -46,6 +47,12 @@ rid_shift = 0
|
|||||||
for idrange in api.Command['idrange_find']()['result']:
|
for idrange in api.Command['idrange_find']()['result']:
|
||||||
size = int(idrange['ipaidrangesize'][0])
|
size = int(idrange['ipaidrangesize'][0])
|
||||||
base_id = int(idrange['ipabaseid'][0])
|
base_id = int(idrange['ipabaseid'][0])
|
||||||
|
rtype = idrange['iparangetype'][0]
|
||||||
|
|
||||||
|
if rtype == 'ipa-local-subid' or base_id == constants.SUBID_RANGE_START:
|
||||||
|
# ignore subordinate id range. It would push values beyond uint32_t.
|
||||||
|
# There is plenty of space below SUBUID_RANGE_START.
|
||||||
|
continue
|
||||||
|
|
||||||
id_end = base_id + size
|
id_end = base_id + size
|
||||||
rid_end = 0
|
rid_end = 0
|
||||||
|
Loading…
Reference in New Issue
Block a user