After adding {user|usergroup|group|hostgroup} in {service|host} tables which contains entites which can create/retrieve keytab, the ipa {host|service} show --all command stopped working and fails with internal server error.
ipa {host|service} show --all
hostname
ipa: ERROR: an internal error has occurred
See output of the command.
freeipa-server-4.6.90.dev201712141305+gitd7426ccbe-0.fc27.x86_64 freeipa-client-4.6.90.dev201712141305+gitd7426ccbe-0.fc27.x86_64 389-ds-base-1.3.7.8-1.fc27.x86_64 pki-ca-10.5.1-2.fc27.noarch krb5-server-1.15.2-4.fc27.x86_64
/var/log/httpd: [Thu Dec 14 15:41:58.046629 2017] [:error] [pid 42244:tid 140290998327040] [client 10.37.171.49:33298] failed to set perms (3140) on file (/var/run/ipa/ccaches/admin@DOM-IPA.EXAMPLE.COM)!, referer: https://ipa.example.com/ipa/xml [Thu Dec 14 15:41:58.100255 2017] [wsgi:error] [pid 42241:tid 140290791048960] [remote 10.37.171.49:33298] ipa: ERROR: non-public: AttributeError: 'tuple' object has no attribute 'decode' [Thu Dec 14 15:41:58.100316 2017] [wsgi:error] [pid 42241:tid 140290791048960] [remote 10.37.171.49:33298] Traceback (most recent call last): [Thu Dec 14 15:41:58.100322 2017] [wsgi:error] [pid 42241:tid 140290791048960] [remote 10.37.171.49:33298] File "/usr/lib/python3.6/site-packages/ipaserver/rpcserver.py", line 370, in wsgi_execute [Thu Dec 14 15:41:58.100327 2017] [wsgi:error] [pid 42241:tid 140290791048960] [remote 10.37.171.49:33298] result = command(args, options) [Thu Dec 14 15:41:58.100332 2017] [wsgi:error] [pid 42241:tid 140290791048960] [remote 10.37.171.49:33298] File "/usr/lib/python3.6/site-packages/ipalib/frontend.py", line 450, in call [Thu Dec 14 15:41:58.100336 2017] [wsgi:error] [pid 42241:tid 140290791048960] [remote 10.37.171.49:33298] return self.__do_call(*args, options) [Thu Dec 14 15:41:58.100341 2017] [wsgi:error] [pid 42241:tid 140290791048960] [remote 10.37.171.49:33298] File "/usr/lib/python3.6/site-packages/ipalib/frontend.py", line 478, in __do_call [Thu Dec 14 15:41:58.100345 2017] [wsgi:error] [pid 42241:tid 140290791048960] [remote 10.37.171.49:33298] ret = self.run(args, options) [Thu Dec 14 15:41:58.100349 2017] [wsgi:error] [pid 42241:tid 140290791048960] [remote 10.37.171.49:33298] File "/usr/lib/python3.6/site-packages/ipalib/frontend.py", line 800, in run [Thu Dec 14 15:41:58.100353 2017] [wsgi:error] [pid 42241:tid 140290791048960] [remote 10.37.171.49:33298] return self.execute(*args, options) [Thu Dec 14 15:41:58.100357 2017] [wsgi:error] [pid 42241:tid 140290791048960] [remote 10.37.171.49:33298] File "/usr/lib/python3.6/site-packages/ipaserver/plugins/baseldap.py", line 1335, in execute [Thu Dec 14 15:41:58.100402 2017] [wsgi:error] [pid 42241:tid 140290791048960] [remote 10.37.171.49:33298] self.obj.convert_attribute_members(entry_attrs, keys, *options) [Thu Dec 14 15:41:58.100408 2017] [wsgi:error] [pid 42241:tid 140290791048960] [remote 10.37.171.49:33298] File "/usr/lib/python3.6/site-packages/ipaserver/plugins/baseldap.py", line 657, in convert_attribute_members [Thu Dec 14 15:41:58.100413 2017] [wsgi:error] [pid 42241:tid 140290791048960] [remote 10.37.171.49:33298] memberdn = DN(member.decode('utf-8')) [Thu Dec 14 15:41:58.100420 2017] [wsgi:error] [pid 42241:tid 140290791048960] [remote 10.37.171.49:33298] AttributeError: 'tuple' object has no attribute 'decode' [Thu Dec 14 15:41:58.100431 2017] [wsgi:error] [pid 42241:tid 140290791048960] [remote 10.37.171.49:33298] [Thu Dec 14 15:41:58.100683 2017] [wsgi:error] [pid 42241:tid 140290791048960] [remote 10.37.171.49:33298] ipa: INFO: [jsonserver_session] admin@DOM-IPA.EXAMPLE.COM: host_show/1('ipa.example.com', all=True, version='2.229'): InternalError
Metadata Update from @fbarreto: - Issue assigned to fbarreto
The error calling from api.Command['host_show'](u'vm-171-203.abc.idm.lab.eng.brq', all=True):
api.Command['host_show'](u'vm-171-203.abc.idm.lab.eng.brq', all=True)
[root@vm-171-203 fbarreto]# python -m pdb init_ipa.py > /home/fbarreto/init_ipa.py(1)<module>() -> from ipalib import api (Pdb) c ipa: ERROR: unable to convert the attribute u'ipaAllowedToPerform;write_keys' value 'vm-171-203.abc.idm.lab.eng.brq' to type <class 'ipapython.dn.DN'> Traceback (most recent call last): File "/usr/lib64/python2.7/pdb.py", line 1314, in main pdb._runscript(mainpyfile) File "/usr/lib64/python2.7/pdb.py", line 1233, in _runscript self.run(statement) File "/usr/lib64/python2.7/bdb.py", line 400, in run exec cmd in globals, locals File "<string>", line 1, in <module> File "init_ipa.py", line 1, in <module> from ipalib import api File "/usr/lib/python2.7/site-packages/ipalib/frontend.py", line 450, in __call__ return self.__do_call(*args, **options) File "/usr/lib/python2.7/site-packages/ipalib/frontend.py", line 478, in __do_call ret = self.run(*args, **options) File "/usr/lib/python2.7/site-packages/ipalib/frontend.py", line 800, in run return self.execute(*args, **options) File "/usr/lib/python2.7/site-packages/ipaserver/plugins/baseldap.py", line 1333, in execute self, ldap, entry_attrs.dn, entry_attrs, *keys, **options) File "/usr/lib/python2.7/site-packages/ipaserver/plugins/host.py", line 1134, in post_callback rename_ipaallowedtoperform_from_ldap(entry_attrs, options) File "/usr/lib/python2.7/site-packages/ipaserver/plugins/service.py", line 345, in rename_ipaallowedtoperform_from_ldap entry_attrs[new_name] = entry_attrs.pop(name) File "/usr/lib64/python2.7/_abcoll.py", line 519, in pop value = self[key] File "/usr/lib/python2.7/site-packages/ipapython/ipaldap.py", line 457, in __getitem__ return self._get_nice(name) File "/usr/lib/python2.7/site-packages/ipapython/ipaldap.py", line 432, in _get_nice self._sync_attr(name) File "/usr/lib/python2.7/site-packages/ipapython/ipaldap.py", line 324, in _sync_attr error=e, dn=self._dn)) ValueError: unable to convert the attribute u'ipaAllowedToPerform;write_keys' value 'vm-171-203.abc.idm.lab.eng.brq' to type <class 'ipapython.dn.DN'> in LDAP entry 'fqdn=vm-171-203.abc.idm.lab.eng.brq,cn=computers,cn=accounts,dc=dom-171-203,dc=abc,dc=idm,dc=lab,dc=eng,dc=brq' Uncaught exception. Entering post mortem debugging Running 'cont' or 'step' will restart the program > /usr/lib/python2.7/site-packages/ipapython/ipaldap.py(324)_sync_attr() -> error=e, dn=self._dn))
Metadata Update from @fbarreto: - Assignee reset
I need some help to figure out what's happening here.
After running the command ipa host-allow-create-keytab vm-171-203.abc.idm.lab.eng.brq --users=teste3.
ipa host-allow-create-keytab vm-171-203.abc.idm.lab.eng.brq --users=teste3
The attribute ipaAllowedToPerform;write_keys is added in fqdn=vm-171-203.abc.idm.lab.eng.brq,cn=computers,cn=accounts,dc=dom-171-203,dc=abc,dc=idm,dc=lab,dc=eng,dc=brq with the value of the user DN: uid=teste3,cn=users,cn=accounts,dc=dom-171-203,dc=abc,dc=idm,dc=lab,dc=eng,dc=brq
ipaAllowedToPerform;write_keys
fqdn=vm-171-203.abc.idm.lab.eng.brq,cn=computers,cn=accounts,dc=dom-171-203,dc=abc,dc=idm,dc=lab,dc=eng,dc=brq
uid=teste3,cn=users,cn=accounts,dc=dom-171-203,dc=abc,dc=idm,dc=lab,dc=eng,dc=brq
However, as it's showing in the log posted above, the value that is returned in baseldap.py is the hostname of the VM (vm-171-203.abc.idm.lab.eng.brq). And that is why the value cannot be converted to a DN type.
Any idea?
There is definitely an error:
# ipa console (Custom IPA interactive Python console) >>> api.Command.host_show('wiki.xs.ipa.cool', all=True, raw=True)['result']['ipaAllowedToPerform;write_keys'] ('host/wiki.xs.ipa.cool@XS.IPA.COOL',) >>> now exiting InteractiveConsole... # ldapsearch -Y GSSAPI -LLL -o ldif-wrap=no -b fqdn=wiki.xs.ipa.cool,cn=computers,cn=accounts,dc=xs,dc=ipa,dc=cool 'ipaAllowedToPerform;write_keys' SASL/GSSAPI authentication started SASL username: admin@XS.IPA.COOL SASL SSF: 256 SASL data security layer installed. dn: fqdn=wiki.xs.ipa.cool,cn=computers,cn=accounts,dc=xs,dc=ipa,dc=cool ipaAllowedToPerform;write_keys: uid=ab,cn=users,cn=accounts,dc=xs,dc=ipa,dc=cool
If you enable debugging for ipapython.ipaldap._debug_log_ldap = True, you'll see that we get it converted to that value even before the LDAPClient._convert_result:
ipapython.ipaldap._debug_log_ldap = True
LDAPClient._convert_result
[Fri Dec 22 10:43:17.616065 2017] [wsgi:error] [pid 20799:tid 140030109005568] [remote XX.XX.XX.XX:40516] ipa: DEBUG: ldap.result: [LDAPEntry(ipapython.dn.DN('fqdn=wiki.xs.ipa.cool,cn=computers,cn=accounts,dc=xs,dc=ipa,dc=cool'), {'objectClass': [b'ipaobject', b'nshost', b'ipahost', b'pkiuser', b'ipaservice', b'krbprincipalaux', b'krbprincipal', b'ieee802device', b'ipasshhost', b'top', b'ipaSshGroupOfPubKeys', b'ipaallowedoperations'], 'cn': [b'wiki.xs.ipa.cool'], 'serverHostName': [b'wiki'], 'ipaUniqueID': [b'2c67b830-d5ba-11e7-8e6a-001a4a62eb77'], 'ipaAllowedToPerform;write_keys': [b'host/wiki.xs.ipa.cool@XS.IPA.COOL'], 'krbPwdPolicyReference': [b'cn=Default Host Password Policy,cn=computers,cn=accounts,dc=xs,dc=ipa,dc=cool'], 'fqdn': [b'wiki.xs.ipa.cool'], 'krbprincipalname': [b'host/wiki.xs.ipa.cool@XS.IPA.COOL'], 'krbcanonicalname': [b'host/wiki.xs.ipa.cool@XS.IPA.COOL'], 'managedby': [b'fqdn=wiki.xs.ipa.cool,cn=computers,cn=accounts,dc=xs,dc=ipa,dc=cool']})]
The result value we get to the _convert_result comes directly from calling to a Python LDAP library. Indeed, if I add a bit more instrumentation to LDAPClient.find_entries to see what is returned, I can see that we get wrong value back from self.conn.result3():
_convert_result
LDAPClient.find_entries
self.conn.result3()
try: id = self.conn.search_ext( str(base_dn), scope, filter, attrs_list, serverctrls=sctrls, timeout=time_limit, sizelimit=size_limit ) while True: result = self.conn.result3(id, 0) if _debug_log_ldap: logger.debug('find_entries result: %s', result)
And the corresponding log looks like this:
[Fri Dec 22 10:53:09.659252 2017] [wsgi:error] [pid 21468:tid 139866694240000] [remote XX.XX.XX.XX:40524] ipa: DEBUG: find_entries result: (100, [('fqdn=wiki.xs.ipa.cool,cn=computers,cn=accounts,dc=xs,dc=ipa,dc=cool', {'objectClass': [b'ipaobject', b'nshost', b'ipahost', b'pkiuser', b'ipaservice', b'krbprincipalaux', b'krbprincipal', b'ieee802device', b'ipasshhost', b'top', b'ipaSshGroupOfPubKeys', b'ipaallowedoperations'], 'cn': [b'wiki.xs.ipa.cool'], 'serverHostName': [b'wiki'], 'ipaUniqueID': [b'2c67b830-d5ba-11e7-8e6a-001a4a62eb77'], 'ipaAllowedToPerform;write_keys': [b'host/wiki.xs.ipa.cool@XS.IPA.COOL'], 'krbPwdPolicyReference': [b'cn=Default Host Password Policy,cn=computers,cn=accounts,dc=xs,dc=ipa,dc=cool'], 'fqdn': [b'wiki.xs.ipa.cool'], 'krbprincipalname': [b'host/wiki.xs.ipa.cool@XS.IPA.COOL'], 'managedby': [b'fqdn=wiki.xs.ipa.cool,cn=computers,cn=accounts,dc=xs,dc=ipa,dc=cool'], 'krbcanonicalname': [b'host/wiki.xs.ipa.cool@XS.IPA.COOL']})], 5, [])
I'm running with python3-ldap-3.0.0-0.1.b1.fc27.x86_64. @cheimes, could you please look into why python3-ldap could be doing such conversion?
python3-ldap-3.0.0-0.1.b1.fc27.x86_64
Note that I couldn't reproduce it with a regular python3-ldap search script based on the Demo/sasl-bind.py from the python3-ldap distribution where I added
result = l.search_s('fqdn=wiki.xs.ipa.cool,cn=computers,cn=accounts,dc=xs,dc=ipa,dc=cool', ldap.SCOPE_SUBTREE, '(objectclass=*)', '*') pprint.pprint(result)
# python3 sasl_bind.py ******************** b'GSSAPI' ******************** Sucessfully bound using SASL mechanism: b'GSSAPI' Result of Who Am I? ext. op: 'dn: uid=admin,cn=users,cn=accounts,dc=xs,dc=ipa,dc=cool' OPT_X_SASL_USERNAME 'admin@XS.IPA.COOL' [('fqdn=wiki.xs.ipa.cool,cn=computers,cn=accounts,dc=xs,dc=ipa,dc=cool', {'cn': [b'wiki.xs.ipa.cool'], 'fqdn': [b'wiki.xs.ipa.cool'], 'ipaAllowedToPerform;write_keys': [b'uid=ab,cn=users,cn=accounts,dc=xs,dc' b'=ipa,dc=cool'], 'ipaUniqueID': [b'2c67b830-d5ba-11e7-8e6a-001a4a62eb77'], 'krbCanonicalName': [b'host/wiki.xs.ipa.cool@XS.IPA.COOL'], 'krbPrincipalName': [b'host/wiki.xs.ipa.cool@XS.IPA.COOL'], 'krbPwdPolicyReference': [b'cn=Default Host Password Policy,cn=computers,cn=' b'accounts,dc=xs,dc=ipa,dc=cool'], 'managedBy': [b'fqdn=wiki.xs.ipa.cool,cn=computers,cn=accounts,dc=xs,dc=ipa,' b'dc=cool'], 'objectClass': [b'ipaobject', b'nshost', b'ipahost', b'pkiuser', b'ipaservice', b'krbprincipalaux', b'krbprincipal', b'ieee802device', b'ipasshhost', b'top', b'ipaSshGroupOfPubKeys', b'ipaallowedoperations'], 'serverHostName': [b'wiki']})]
I tried also with a paged search like Demo/paged_search_ext_s.py does and it also works just fine, returning me a proper DN. So it looks like we have a bug somewhere in between.
Demo/paged_search_ext_s.py
Reading @abbra's comment, it seems it might be python3 related. @fbarreto do we encounter this bug also with python 2 e.g., IPA 4.5?
@pvoborni no, I was not able to reproduce it with ipa 4.5 (py2), only 4.6. As you pointed it out, it seems to be related to py3 changes.
Metadata Update from @pvoborni: - Issue tagged with: py3
There's a hack to run the current freeIPA version built with Python3 in Python2 mode: rpm -e --nodeps python3-mod_wsgi and restart httpd. That way you'd check whether it's really Python3-related (if you have the common mod_wsgi installed ofc).
rpm -e --nodeps python3-mod_wsgi
I also tried with LDAP2 backend with Python 3. The LDAP2 backend returns correct value.
Python 3.6.4 (default, Jan 17 2018, 12:00:56) [GCC 7.2.1 20170915 (Red Hat 7.2.1-2)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import ipalib >>> from ipapython.dn import DN >>> ipalib.api.bootstrap(in_server=True) >>> ipalib.api.finalize() >>> ipalib.api.Backend.ldap2.connect() >>> dn = DN('fqdn=vm-171-019....') >>> ipalib.api.Backend.ldap2.get_entry(dn, ['ipaAllowedToPerform;write_keys']) LDAPEntry(ipapython.dn.DN('fqdn=vm-171-019...'), {'ipaallowedtoperform;write_keys': [b'uid=admin,cn=users,cn=accounts,dc=...']})
Metadata Update from @rcritten: - Issue priority set to: normal - Issue set to the milestone: FreeIPA 4.6.4
Most likely a dup of https://pagure.io/freeipa/issue/7497
I confirmed that the issue is due to use of self.obj.convert_attribute_members() in LDAPAddMember.execute(). It converts the DN to a user-friendly format for printing purposes.
self.obj.convert_attribute_members()
LDAPAddMember.execute()
A fix, probably, would be to find all the places that expect (wrongly) DN value in such member-based attributes. In the example below member_service is a virtual attribute generated from a member attribute. It contains principals that are members of the group but a code we have in both tests and plugins expects DN here.
member_service
member
ipaAllowedToPerform;write_keys has the same issue because ipa host-allow-create-keytab is built using the same LDAPAddMember class. We need to review all code paths that use LDAP*Member classes.
ipa host-allow-create-keytab
LDAPAddMember
LDAP*Member
# ipa -e in_server=True console (Custom IPA interactive Python console) >>> api.Command.group_add_member(u'some-group', service=[u'HTTP/nyx.xs.ipa.cool']) ipa: ERROR: BEFORE::addmember::group entry: LDAPEntry(ipapython.dn.DN('cn=some-group,cn=groups,cn=accounts,dc=xs,dc=ipa,dc=cool'), {'member': [b'krbprincipalname=HTTP/nyx.xs.ipa.cool@XS.IPA.COOL,cn=services,cn=accounts,dc=xs,dc=ipa,dc=cool'], 'cn': [b'some-group'], 'gidnumber': [b'1536000079'], 'memberof': []}) ipa: ERROR: AFTER::addmember::group entry: LDAPEntry(ipapython.dn.DN('cn=some-group,cn=groups,cn=accounts,dc=xs,dc=ipa,dc=cool'), {'cn': [b'some-group'], 'gidnumber': [b'1536000079'], 'member_service': [ipapython.kerberos.Principal('HTTP/nyx.xs.ipa.cool@XS.IPA.COOL')]}) {'completed': 1, 'failed': {'member': {'user': [], 'group': [], 'service': []}}, 'result': {'cn': ['some-group'], 'gidnumber': ['1536000079'], 'member_service': [ipapython.kerberos.Principal('HTTP/nyx.xs.ipa.cool@XS.IPA.COOL')], 'dn': ipapython.dn.DN('cn=some-group,cn=groups,cn=accounts,dc=xs,dc=ipa,dc=cool')}, 'messages': [{'type': 'warning', 'name': 'VersionMissing', 'message': "API Version number was not sent, forward compatibility not guaranteed. Assuming server's API version, 2.229", 'code': 13001, 'data': {'server_version': '2.229'}}]} >>>
Metadata Update from @abbra: - Issue assigned to abbra
So, my current idea is that this happens because LDAPEntry class is implemented on top of collections.MutableMapping and enforces schema checks for each element that is added to the entry. However, we transform elements in violation with the schema because at this point we are already not planning to use the entry to marshal into an LDAP message. We treat the entry as a dictionary.
collections.MutableMapping
I scratched my idea but I instrumented _sync_attr() code in ipapython/ipaldap.py and it showed that we definitely get a weird data from python-ldap. At some point I got a Python GC crash (line numbers in ipaldap.py are wrong because I have added logging statements there):
Fatal Python error: GC object already tracked Current thread 0x00007fa75f02f700 (most recent call first): File "/usr/lib/python3.6/site-packages/ipapython/ipaldap.py", line 326 in _sync_attr File "/usr/lib/python3.6/site-packages/ipapython/ipaldap.py", line 463 in _get_nice File "/usr/lib/python3.6/site-packages/ipapython/ipaldap.py", line 488 in __getitem__ File "/usr/lib64/python3.6/_collections_abc.py", line 854 in setdefault File "/usr/lib/python3.6/site-packages/ipaserver/plugins/service.py", line 594 in populate_krbcanonicalname File "/usr/lib/python3.6/site-packages/ipaserver/plugins/service.py", line 835 in post_callback File "/usr/lib/python3.6/site-packages/ipaserver/plugins/baseldap.py", line 1337 in execute File "/usr/lib/python3.6/site-packages/ipalib/frontend.py", line 800 in run File "/usr/lib/python3.6/site-packages/ipalib/frontend.py", line 478 in __do_call File "/usr/lib/python3.6/site-packages/ipalib/frontend.py", line 450 in __call__ File "/usr/lib/python3.6/site-packages/ipaserver/rpcserver.py", line 369 in wsgi_execute File "/usr/lib/python3.6/site-packages/ipaserver/rpcserver.py", line 429 in __call__ File "/usr/lib/python3.6/site-packages/ipaserver/rpcserver.py", line 473 in __call__ File "/usr/lib/python3.6/site-packages/ipaserver/rpcserver.py", line 821 in __call__ File "/usr/lib/python3.6/site-packages/ipaserver/rpcserver.py", line 279 in route File "/usr/lib/python3.6/site-packages/ipaserver/rpcserver.py", line 267 in __call__ File "/usr/share/ipa/wsgi.py", line 59 in application
For a typical service_show API operation we get two LDAP searches: a search for the IPA config entry and a search for the LDAP entry. I modified _sync_attr() to print out content of self._raw, self._nice, and self._sync dictionaries we keep in each entry. I also print the LDAPEntry representation itself. The request starts like this:
_sync_attr()
self._raw
self._nice
self._sync
ipa: DEBUG: WSGI wsgi_dispatch.__call__: ipa: DEBUG: WSGI jsonserver_session.__call__: ipa: DEBUG: Created connection context.ldap2_140130699223224 ipa: DEBUG: WSGI jsonserver.__call__: ipa: DEBUG: WSGI WSGIExecutioner.__call__: ipa: DEBUG: raw: service_show('unittest/nyx.xs.ipa.cool', all=True, version='2.229' ) ipa: DEBUG: service_show(ipapython.kerberos.Principal('unittest/nyx.xs.ipa.cool@XS. IPA.COOL'), rights=False, all=True, raw=False, version='2.229', no_members=False) ipa: DEBUG: ldap.result: [LDAPEntry(ipapython.dn.DN('cn=ipaConfig,cn=etc,dc=xs,dc=i pa,dc=cool'), {'objectClass': [b'nsContainer', b'top', b'ipaGuiConfig', b'ipaConfigObject', b'ipaUserAuthTypeClass', b'ipaNameResolutionData'], 'ipaUserSearchFields': [b'uid,givenname,sn,telephonenumber,ou,title'], 'ipaGroupSearchFields': [b'cn,description'], 'ipaSearchTimeLimit': [b'2'], 'ipaSearchRecordsLimit': [b'100'], 'ipaHomesRootDir': [b'/home'], 'ipaDefaultLoginShell': [b'/bin/sh'], 'ipaDefaultPrimaryGroup': [b'ipausers'], 'ipaMaxUsernameLength': [b'32'], 'ipaPwdExpAdvNotify': [b'4'], 'ipaGroupObjectClasses': [b'top', b'groupofnames', b'nestedgroup', b'ipausergroup', b'ipaobject'], 'ipaUserObjectClasses': [b'top', b'person', b'organizationalperson', b'inetorgperson', b'inetuser', b'posixaccount', b'krbprincipalaux', b'krbticketpolicyaux', b'ipaobject', b'ipasshuser'], 'ipaDefaultEmailDomain': [b'xs.ipa.cool'], 'ipaMigrationEnabled': [b'FALSE'], 'ipaConfigString': [b'AllowNThash'], 'ipaSELinuxUserMapOrder': [b'guest_u:s0$xguest_u:s0$user_u:s0$staff_u:s0-s0:c0.c1023$unconfined_u:s0-s0:c0.c1023'], 'ipaSELinuxUserMapDefault': [b'unconfined_u:s0-s0:c0.c1023'], 'cn': [b'ipaConfig'], 'ipaCertificateSubjectBase': [b'O=XS.IPA.COOL'], 'ipaKrbAuthzData': [b'MS-PAC', b'nfs:NONE']})] ipa: ERROR: _sync_attr(ipaSearchTimeLimit) _nice: {'objectClass': None, 'ipaUserSearchFields': None, 'ipaGroupSearchFields': None, 'ipaSearchTimeLimit': [], 'ipaSearchRecordsLimit': None, 'ipaHomesRootDir': None, 'ipaDefaultLoginShell': None, 'ipaDefaultPrimaryGroup': None, 'ipaMaxUsernameLength': None, 'ipaPwdExpAdvNotify': None, 'ipaGroupObjectClasses': None, 'ipaUserObjectClasses': None, 'ipaDefaultEmailDomain': None, 'ipaMigrationEnabled': None, 'ipaConfigString': None, 'ipaSELinuxUserMapOrder': None, 'ipaSELinuxUserMapDefault': None, 'cn': None, 'ipaCertificateSubjectBase': None, 'ipaKrbAuthzData': None} _raw: {'objectClass': [b'nsContainer', b'top', b'ipaGuiConfig', b'ipaConfigObject' , b'ipaUserAuthTypeClass', b'ipaNameResolutionData'], 'ipaUserSearchFields': [b'uid,givenname,sn,telephonenumber,ou,title'], 'ipaGroupSearchFields': [b'cn,description'], 'ipaSearchTimeLimit': [b'2'], 'ipaSearchRecordsLimit': [b'100'], 'ipaHomesRootDir': [b'/home'], 'ipaDefaultLoginShell': [b'/bin/sh'], 'ipaDefaultPrimaryGroup': [b'ipausers'], 'ipaMaxUsernameLength': [b'32'], 'ipaPwdExpAdvNotify': [b'4'], 'ipaGroupObjectClasses': [b'top', b'groupofnames', b'nestedgroup', b'ipausergroup', b'ipaobject'], 'ipaUserObjectClasses': [b'top', b'person', b'organizationalperson', b'inetorgperson', b'inetuser', b'posixaccount', b'krbprincipalaux', b'krbticketpolicyaux', b'ipaobject', b'ipasshuser'], 'ipaDefaultEmailDomain': [b'xs.ipa.cool'], 'ipaMigrationEnabled': [b'FALSE'], 'ipaConfigString': [b'AllowNThash'], 'ipaSELinuxUserMapOrder': [b'guest_u:s0$xguest_u:s0$user_u:s0$staff_u:s0-s0:c0.c1023$unconfined_u:s0-s0:c0.c1023'], 'ipaSELinuxUserMapDefault': [b'unconfined_u:s0-s0:c0.c1023'], 'cn': [b'ipaConfig'], 'ipaCertificateSubjectBase': [b'O=XS.IPA.COOL'], 'ipaKrbAuthzData': [b'MS-PAC', b'nfs:NONE']} _sync = {} ipa: ERROR: For an attr (ipaSearchTimeLimit): nice = [] raw = [b'2'] nice_adds = set() nice_dels = set() raw_adds = {b'2'} raw_dels = set() ipa: ERROR: _sync_attr(ipaSearchRecordsLimit) _nice: {'objectClass': None, 'ipaUserSearchFields': None, 'ipaGroupSearchFields': None, 'ipaSearchTimeLimit': ['2'], 'ipaSearchRecordsLimit': [], 'ipaHomesRootDir': None, 'ipaDefaultLoginShell': None, 'ipaDefaultPrimaryGroup': None, 'ipaMaxUsernameLength': None, 'ipaPwdExpAdvNotify': None, 'ipaGroupObjectClasses': None, 'ipaUserObjectClasses': None, 'ipaDefaultEmailDomain': None, 'ipaMigrationEnabled': None, 'ipaConfigString': None, 'ipaSELinuxUserMapOrder': None, 'ipaSELinuxUserMapDefault': None, 'cn': None, 'ipaCertificateSubjectBase': None, 'ipaKrbAuthzData': None} _raw: {'objectClass': [b'nsContainer', b'top', b'ipaGuiConfig', b'ipaConfigObject', b'ipaUserAuthTypeClass', b'ipaNameResolutionData'], 'ipaUserSearchFields': [b'uid,givenname,sn,telephonenumber,ou,title'], 'ipaGroupSearchFields': [b'cn,description'], 'ipaSearchTimeLimit': [b'2'], 'ipaSearchRecordsLimit': [b'100'], 'ipaHomesRootDir': [b'/home'], 'ipaDefaultLoginShell': [b'/bin/sh'], 'ipaDefaultPrimaryGroup': [b'ipausers'], 'ipaMaxUsernameLength': [b'32'], 'ipaPwdExpAdvNotify': [b'4'], 'ipaGroupObjectClasses': [b'top', b'groupofnames', b'nestedgroup', b'ipausergroup', b'ipaobject'], 'ipaUserObjectClasses': [b'top', b'person', b'organizationalperson', b'inetorgperson', b'inetuser', b'posixaccount', b'krbprincipalaux', b'krbticketpolicyaux', b'ipaobject', b'ipasshuser'], 'ipaDefaultEmailDomain': [b'xs.ipa.cool'], 'ipaMigrationEnabled': [b'FALSE'], 'ipaConfigString': [b'AllowNThash'], 'ipaSELinuxUserMapOrder': [b'guest_u:s0$xguest_u:s0$user_u:s0$staff_u:s0-s0:c0.c1023$unconfined_u:s0-s0:c0.c1023'], 'ipaSELinuxUserMapDefault': [b'unconfined_u:s0-s0:c0.c1023'], 'cn': [b'ipaConfig'], 'ipaCertificateSubjectBase': [b'O=XS.IPA.COOL'], 'ipaKrbAuthzData': [b'MS-PAC', b'nfs:NONE']} _sync = {'ipaSearchTimeLimit': (['2'], [b'2'])} ipa: ERROR: For an attr (ipaSearchRecordsLimit): nice = [] raw = [b'100'] nice_adds = set() nice_dels = set() raw_adds = {b'100'} raw_dels = set()
So we can see that for each attribute value from LDAP entry, there is a conversion to be done. During the conversion LDAPEntry syncronizes raw and 'nice' values. Raw is the value as it was returned by python-ldap. 'nice' is our Python representation of the same value -- for example, for a krbprincipalname=foo@BAR.Z we convert it to KrbPrincipal() instance.
Now, I don't even get to the point where actual LDAPEntry for my service unittest/nyx.xs.ipa.cool is printed, unlike the IPA config entry above. This is because we fail conversion from raw to nice for ipaAllowedToPerform;write_keys attribute. And we fail that because python-ldap seems to present us with a completely wrong object -- instead of a bytes object we got list object [('krbprincipalname', 'unittest/nyx.xs.ipa.cool@XS.IPA.COOL', 1)]. Not only it is a different object, it also refers to a completely different attribute.
unittest/nyx.xs.ipa.cool
ipa: ERROR: non-public: TypeError: ipaAllowedToPerform;write_keys[0] value must be bytes, got list object [('krbprincipalname', 'unittest/nyx.xs.ipa.cool@XS.IPA.COOL', 1)] Traceback (most recent call last): File "/usr/lib/python3.6/site-packages/ipaserver/rpcserver.py", line 369, in wsgi_execute result = command(*args, **options) File "/usr/lib/python3.6/site-packages/ipalib/frontend.py", line 450, in __call__ return self.__do_call(*args, **options) File "/usr/lib/python3.6/site-packages/ipalib/frontend.py", line 478, in __do_call ret = self.run(*args, **options) File "/usr/lib/python3.6/site-packages/ipalib/frontend.py", line 800, in run return self.execute(*args, **options) File "/usr/lib/python3.6/site-packages/ipaserver/plugins/baseldap.py", line 1324, in execute dn, attrs_list File "/usr/lib/python3.6/site-packages/ipaserver/plugins/baseldap.py", line 1104, in wrapped return func(*call_args, **call_kwargs) File "/usr/lib/python3.6/site-packages/ipapython/ipaldap.py", line 1552, in get_entry size_limit=size_limit, get_effective_rights=get_effective_rights, File "/usr/lib/python3.6/site-packages/ipapython/ipaldap.py", line 1364, in get_entries **kwargs) File "/usr/lib/python3.6/site-packages/ipapython/ipaldap.py", line 1459, in find_entries res_list = self._convert_result(res_list) File "/usr/lib/python3.6/site-packages/ipapython/ipaldap.py", line 995, in _convert_result ipa_entry.raw[attr] = original_values File "/usr/lib/python3.6/site-packages/ipapython/ipaldap.py", line 606, in __setitem__ self._entry._set_raw(name, value) File "/usr/lib/python3.6/site-packages/ipapython/ipaldap.py", line 433, in _set_raw name, i, item.__class__.__name__, item) TypeError: ipaAllowedToPerform;write_keys[0] value must be bytes, got list object [('krbprincipalname', 'unittest/nyx.xs.ipa.cool@XS.IPA.COOL', 1)] ipa: INFO: [jsonserver_session] admin@XS.IPA.COOL: service_show/1('unittest/nyx.xs.ipa.cool', all=True, version='2.229'): InternalError ipa: DEBUG: Destroyed connection context.ldap2_140130699223224
_convert_result() does the following, where original_attrs and original_dn are elements of the list of tuples passed by the caller -- they come straight from python-ldap:
_convert_result()
original_attrs
original_dn
def find_entries(): .... while True: result = self.conn.result3(id, 0) objtype, res_list, _res_id, res_ctrls = result if objtype == ldap.RES_SEARCH_RESULT: break res_list = self._convert_result(res_list) if res_list: res.append(res_list[0]) .... def _convert_result(): .... ipa_entry = LDAPEntry(self, DN(original_dn)) for attr, original_values in original_attrs.items(): ipa_entry.raw[attr] = original_values ipa_entry.reset_modlist() ipa_result.append(ipa_entry) ....
We seem to fail inside the latter loop -- notice that we set ipaAllowedToPerform;write_keys attribute name to [('krbprincipalname', 'unittest/nyx.xs.ipa.cool@XS.IPA.COOL', 1)]. Not sure how this could be happening unless original_attrs.items() are references messed up.
[('krbprincipalname', 'unittest/nyx.xs.ipa.cool@XS.IPA.COOL', 1)]
original_attrs.items()
I found the culprit. In fact, I found a combination of three different bugs that lead to the issue.
--all
For --all option, FreeIPA's LDAP wrapper creates a search with both * and all the keys it expects. This should be fixed in FreeIPA.
*
[ 'krbcanonicalname', 'krbprincipalauthind', 'managedby', 'krbprincipalname', '*', 'usercertificate', 'ipakrbauthzdata', 'memberof', 'ipaallowedtoperform', ]
389-DS behaves particularly strange with LDAP searches that have both a wildcard * and an explicit ipaallowedtoperform attribute in the attribute list. The ipaallowedtoperform attributes are returned twice as two separate attribute entries. This looks like a bug to me and should be fixed in 389-DS. Or is it caused by one of our plugins?
ipaallowedtoperform
ldapsearch -vv -H ldap://$(hostname) -D "cn=Directory Manager" -w password -b krbprincipalname=aservice/master.ipa.example@IPA.EXAMPLE,cn=services,cn=accounts,dc=ipa,dc=example \* ipaallowedtoperform
# extended LDIF # # LDAPv3 # base <krbprincipalname=aservice/master.ipa.example@IPA.EXAMPLE,cn=services,cn=accounts,dc=ipa,dc=example> with scope subtree # filter: (objectclass=*) # requesting: * ipaallowedtoperform # # aservice/master.ipa.example@IPA.EXAMPLE, services, accounts, dom-023.abc.idm.lab.eng.brq.redhat.com dn: krbprincipalname=aservice/master.ipa.example@IPA.EXAMPLE,cn=services,cn=accounts,dc=ipa,dc=example ipaAllowedToPerform;write_keys: uid=tuser,cn=users,cn=accounts,dc=ipa,dc=example ipaAllowedToPerform;write_keys: uid=admin,cn=users,cn=accounts,dc=ipa,dc=example krbCanonicalName: aservice/master.ipa.example@IPA.EXAMPLE objectClass: krbprincipal objectClass: krbprincipalaux objectClass: krbticketpolicyaux objectClass: ipaobject objectClass: ipaservice objectClass: pkiuser objectClass: ipakrbprincipal objectClass: top objectClass: ipaallowedoperations managedBy: fqdn=master.ipa.example,cn=computers,cn=accounts,dc=ipa,dc=example ipaKrbPrincipalAlias: aservice/master.ipa.example@IPA.EXAMPLE krbPrincipalName: aservice/master.ipa.example@IPA.EXAMPLE ipaUniqueID: 9a24241e-5f65-11e8-9368-001a4a2314fd krbPwdPolicyReference: cn=Default Service Password Policy,cn=services,cn=accounts,dc=ipa,dc=example ipaAllowedToPerform;write_keys: uid=tuser,cn=users,cn=accounts,dc=ipa,dc=example ipaAllowedToPerform;write_keys: uid=admin,cn=users,cn=accounts,dc=ipa,dc=example
python-ldap has a reference counting bug in rare cases. The case is only triggered when an LDAP server returns multiple attribute entries with the same name. An LDAP result contains a sequence of attributes, each item has an attribute name entry and a sequence of values. python-ldap code has extra code to handle a sequence with more than one attribute with the same name. However the code for this special case was both buggy and never triggered in any test case.
When python-ldap iterates over the attributes, it checks if an attribute name is already in the result dict. That was never the case before. In this special case, ipaAllowedToPerform;write_keys was already in the dict. The code retrieves the result list with PyDict_GetItem and later decreases the reference counter of the list. But that is wrong. PyDict_GetItem returns a borrowed reference, not a new reference. The ref count of the list dropped to zero and a broken object was returned to Python space. Later on when the object was accessed and the memory location was modified in the mean time, the interpreter crashed with a segfault.
PyDict_GetItem
See https://github.com/python-ldap/python-ldap/pull/219 and https://github.com/python-ldap/python-ldap/issues/218
The --all query may not be a bug after all. An LDAP query with ['*'] attribute list does not return operational attributes. Operational attributes are only returned with a query like ['*', '+'] or when they are spelled out explicitly.
['*']
['*', '+']
master:
Metadata Update from @cheimes: - Issue assigned to cheimes (was: abbra)
Miro has created a fixed build of python3-pyldap for F27, see https://bugzilla.redhat.com/show_bug.cgi?id=1582786
I'll push https://github.com/freeipa/freeipa/pull/1965 once the package is available.
Metadata Update from @rcritten: - Issue set to the milestone: FreeIPA 4.6.5 (was: FreeIPA 4.6.4)
ipa-4-6:
Metadata Update from @cheimes: - Issue close_status updated to: fixed - Issue status updated to: Closed (was: Open)
Log in to comment on this ticket.