#7324 {host,service}-show --all fails after adding allow {create,retrieve} keytab
Closed: fixed 2 years ago Opened 2 years ago by pvomacka.

Issue

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.

Steps to Reproduce

  1. ipa host-allow-create-keytab hostname --users=tuser
  2. ipa host-show hostname --all

Actual behavior

ipa: ERROR: an internal error has occurred

Expected behavior

See output of the command.

Version/Release/Distribution

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

Additional info:

/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

2 years ago

The error calling from 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

2 years ago

Metadata Update from @fbarreto:
- Issue assigned to fbarreto

2 years ago

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.

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

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:

[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():

                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?

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.

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

2 years ago

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).

I also tried with LDAP2 backend with Python 3. The LDAP2 backend returns correct value.

  • ipa host-allow-create-keytab vm-171-019... --users=admin
  • sudo python3
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 @fbarreto:
- Assignee reset

2 years ago

Metadata Update from @rcritten:
- Issue priority set to: normal
- Issue set to the milestone: FreeIPA 4.6.4

2 years ago

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.

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.

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 -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

2 years ago

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.

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:

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.

 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:

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.

I found the culprit. In fact, I found a combination of three different bugs that lead to the issue.

Bad behavior with --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',
]

Duplicated attribute in LDAP result

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?

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

reference counting bug in python-ldap

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.

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:

  • 59ea580 Require python-ldap >= 3.1.0

Metadata Update from @cheimes:
- Issue assigned to cheimes (was: abbra)

2 years ago

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.

master:

  • 9b8bb85 Add test case for allow-create-keytab

Metadata Update from @rcritten:
- Issue set to the milestone: FreeIPA 4.6.5 (was: FreeIPA 4.6.4)

2 years ago

ipa-4-6:

  • 13567ef Add test case for allow-create-keytab
  • 5bc650a Require python-ldap with fix for ref counting bug
  • b1df5fe Use freeipa/ci-ipa-4-6-f27 for PR-CI

Metadata Update from @cheimes:
- Issue close_status updated to: fixed
- Issue status updated to: Closed (was: Open)

2 years ago

master:

  • ae256fa Update selinux-policy minimal requirement

Login to comment on this ticket.

Metadata