pgreco / rpms / ipa

Forked from forks/areguera/rpms/ipa 4 years ago
Clone

Blame SOURCES/0069-cert-speed-up-cert-find.patch

403b09
From be32fd1d727fc8398dd51fa0fd3f404ef451281a Mon Sep 17 00:00:00 2001
403b09
From: Jan Cholasta <jcholast@redhat.com>
403b09
Date: Mon, 1 Aug 2016 09:53:39 +0200
403b09
Subject: [PATCH] cert: speed up cert-find
403b09
403b09
Use issuer+serial rather than raw DER blob to identify certificates in
403b09
cert-find's intermediate result.
403b09
403b09
Restructure the code to make it (hopefully) easier to follow.
403b09
403b09
https://fedorahosted.org/freeipa/ticket/6098
403b09
403b09
Reviewed-By: Martin Basti <mbasti@redhat.com>
403b09
Reviewed-By: Pavel Vomacka <pvomacka@redhat.com>
403b09
---
403b09
 ipaserver/plugins/cert.py | 398 +++++++++++++++++++++++++---------------------
403b09
 1 file changed, 216 insertions(+), 182 deletions(-)
403b09
403b09
diff --git a/ipaserver/plugins/cert.py b/ipaserver/plugins/cert.py
403b09
index 06041d3083565e8d093b610473d6083111d406d2..47dccf15a4010f2766642aedd2cc16e0a1eb1dd4 100644
403b09
--- a/ipaserver/plugins/cert.py
403b09
+++ b/ipaserver/plugins/cert.py
403b09
@@ -21,6 +21,7 @@
403b09
 
403b09
 import base64
403b09
 import binascii
403b09
+import collections
403b09
 import datetime
403b09
 import os
403b09
 
403b09
@@ -295,18 +296,24 @@ class BaseCertObject(Object):
403b09
         ),
403b09
     )
403b09
 
403b09
-    def _parse(self, obj):
403b09
-        cert = x509.load_certificate(obj['certificate'])
403b09
-        obj['subject'] = DN(unicode(cert.subject))
403b09
-        obj['issuer'] = DN(unicode(cert.issuer))
403b09
-        obj['valid_not_before'] = unicode(cert.valid_not_before_str)
403b09
-        obj['valid_not_after'] = unicode(cert.valid_not_after_str)
403b09
-        obj['md5_fingerprint'] = unicode(
403b09
-            nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0])
403b09
-        obj['sha1_fingerprint'] = unicode(
403b09
-            nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0])
403b09
-        obj['serial_number'] = cert.serial_number
403b09
-        obj['serial_number_hex'] = u'0x%X' % cert.serial_number
403b09
+    def _parse(self, obj, full=True):
403b09
+        cert = obj.get('certificate')
403b09
+        if cert is not None:
403b09
+            cert = x509.load_certificate(cert)
403b09
+            obj['subject'] = DN(unicode(cert.subject))
403b09
+            obj['issuer'] = DN(unicode(cert.issuer))
403b09
+            obj['serial_number'] = cert.serial_number
403b09
+            if full:
403b09
+                obj['valid_not_before'] = unicode(cert.valid_not_before_str)
403b09
+                obj['valid_not_after'] = unicode(cert.valid_not_after_str)
403b09
+                obj['md5_fingerprint'] = unicode(
403b09
+                    nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0])
403b09
+                obj['sha1_fingerprint'] = unicode(
403b09
+                    nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0])
403b09
+
403b09
+        serial_number = obj.get('serial_number')
403b09
+        if serial_number is not None:
403b09
+            obj['serial_number_hex'] = u'0x%X' % serial_number
403b09
 
403b09
 
403b09
 class BaseCertMethod(Method):
403b09
@@ -691,10 +698,14 @@ class cert(BaseCertObject):
403b09
             yield self.api.Object[name]
403b09
 
403b09
     def _fill_owners(self, obj):
403b09
+        dns = obj.pop('owner', None)
403b09
+        if dns is None:
403b09
+            return
403b09
+
403b09
         for owner in self._owners():
403b09
             container_dn = DN(owner.container_dn, self.api.env.basedn)
403b09
             name = 'owner_' + owner.name
403b09
-            for dn in obj['owner']:
403b09
+            for dn in dns:
403b09
                 if dn.endswith(container_dn, 1):
403b09
                     value = owner.get_primary_key_from_dn(dn)
403b09
                     obj.setdefault(name, []).append(value)
403b09
@@ -776,9 +787,7 @@ class cert_show(Retrieve, CertMethod, VirtualCommand):
403b09
             result['certificate'] = result['certificate'].replace('\r\n', '')
403b09
             self.obj._parse(result)
403b09
             result['revoked'] = ('revocation_reason' in result)
403b09
-            if 'owner' in result:
403b09
-                self.obj._fill_owners(result)
403b09
-                del result['owner']
403b09
+            self.obj._fill_owners(result)
403b09
 
403b09
         if hostname:
403b09
             # If we have a hostname we want to verify that the subject
403b09
@@ -984,36 +993,171 @@ class cert_find(Search, CertMethod):
403b09
                 label=owner.object_name,
403b09
             )
403b09
 
403b09
-    def execute(self, criteria=None, all=False, raw=False, pkey_only=False,
403b09
-                no_members=True, timelimit=None, sizelimit=None, **options):
403b09
-        ca_options = {'cacn',
403b09
-                      'revocation_reason',
403b09
-                      'issuer',
403b09
-                      'subject',
403b09
-                      'min_serial_number', 'max_serial_number',
403b09
-                      'exactly',
403b09
-                      'validnotafter_from', 'validnotafter_to',
403b09
-                      'validnotbefore_from', 'validnotbefore_to',
403b09
-                      'issuedon_from', 'issuedon_to',
403b09
-                      'revokedon_from', 'revokedon_to'}
403b09
-        ldap_options = {prefix + owner.name
403b09
-                        for owner in self.obj._owners()
403b09
-                        for prefix in ('', 'no_')}
403b09
-        has_ca_options = (
403b09
-            any(name in options for name in ca_options - {'exactly'}) or
403b09
-            options['exactly'])
403b09
-        has_ldap_options = any(name in options for name in ldap_options)
403b09
-        has_cert_option = 'certificate' in options
403b09
+    def _get_cert_key(self, cert):
403b09
+        nss_cert = x509.load_certificate(cert, x509.DER)
403b09
+
403b09
+        return (DN(unicode(nss_cert.issuer)), nss_cert.serial_number)
403b09
+
403b09
+    def _get_cert_obj(self, cert, all, raw, pkey_only):
403b09
+        obj = {'certificate': unicode(base64.b64encode(cert))}
403b09
+
403b09
+        full = not pkey_only and all
403b09
+        if not raw:
403b09
+            self.obj._parse(obj, full)
403b09
+        if not full:
403b09
+            del obj['certificate']
403b09
+
403b09
+        return obj
403b09
+
403b09
+    def _cert_search(self, all, raw, pkey_only, **options):
403b09
+        result = collections.OrderedDict()
403b09
+
403b09
+        try:
403b09
+            cert = options['certificate']
403b09
+        except KeyError:
403b09
+            return result, False, False
403b09
+
403b09
+        key = self._get_cert_key(cert)
403b09
+
403b09
+        result[key] = self._get_cert_obj(cert, all, raw, pkey_only)
403b09
+
403b09
+        return result, False, True
403b09
+
403b09
+    def _ca_search(self, all, raw, pkey_only, sizelimit, exactly, **options):
403b09
+        ra_options = {}
403b09
+        for name in ('revocation_reason',
403b09
+                     'issuer',
403b09
+                     'subject',
403b09
+                     'min_serial_number', 'max_serial_number',
403b09
+                     'validnotafter_from', 'validnotafter_to',
403b09
+                     'validnotbefore_from', 'validnotbefore_to',
403b09
+                     'issuedon_from', 'issuedon_to',
403b09
+                     'revokedon_from', 'revokedon_to'):
403b09
+            try:
403b09
+                value = options[name]
403b09
+            except KeyError:
403b09
+                continue
403b09
+            if isinstance(value, datetime.datetime):
403b09
+                value = value.strftime(PKIDATE_FORMAT)
403b09
+            elif isinstance(value, DN):
403b09
+                value = unicode(value)
403b09
+            ra_options[name] = value
403b09
+        if sizelimit:
403b09
+            ra_options['sizelimit'] = sizelimit
403b09
+        if exactly:
403b09
+            ra_options['exactly'] = True
403b09
+
403b09
+        result = collections.OrderedDict()
403b09
+        complete = bool(ra_options)
403b09
 
403b09
         try:
403b09
             ca_enabled_check()
403b09
         except errors.NotFound:
403b09
-            if has_ca_options:
403b09
+            if ra_options:
403b09
                 raise
403b09
-            ca_enabled = False
403b09
+            return result, False, complete
403b09
+
403b09
+        ra = self.api.Backend.ra
403b09
+        for ra_obj in ra.find(ra_options):
403b09
+            issuer = DN(ra_obj['issuer'])
403b09
+            serial_number = ra_obj['serial_number']
403b09
+
403b09
+            if pkey_only:
403b09
+                obj = {'serial_number': serial_number}
403b09
+            else:
403b09
+                obj = ra_obj
403b09
+                obj['issuer'] = issuer
403b09
+                obj['subject'] = DN(ra_obj['subject'])
403b09
+                del obj['serial_number_hex']
403b09
+
403b09
+                if all:
403b09
+                    ra_obj = ra.get_certificate(str(serial_number))
403b09
+                    if not raw:
403b09
+                        obj['certificate'] = (
403b09
+                            ra_obj['certificate'].replace('\r\n', ''))
403b09
+                        self.obj._parse(obj)
403b09
+
403b09
+            result[issuer, serial_number] = obj
403b09
+
403b09
+        return result, False, complete
403b09
+
403b09
+    def _ldap_search(self, all, raw, pkey_only, no_members, timelimit,
403b09
+                     sizelimit, **options):
403b09
+        ldap = self.api.Backend.ldap2
403b09
+
403b09
+        filters = []
403b09
+        for owner in self.obj._owners():
403b09
+            for prefix, rule in (('', ldap.MATCH_ALL),
403b09
+                                 ('no_', ldap.MATCH_NONE)):
403b09
+                try:
403b09
+                    value = options[prefix + owner.name]
403b09
+                except KeyError:
403b09
+                    continue
403b09
+
403b09
+                filter = ldap.make_filter_from_attr(
403b09
+                    'objectclass',
403b09
+                    owner.object_class,
403b09
+                    ldap.MATCH_ALL)
403b09
+                if filter not in filters:
403b09
+                    filters.append(filter)
403b09
+
403b09
+                filter = ldap.make_filter_from_attr(
403b09
+                    owner.primary_key.name,
403b09
+                    value,
403b09
+                    rule)
403b09
+                filters.append(filter)
403b09
+
403b09
+        cert = options.get('certificate')
403b09
+        if cert is not None:
403b09
+            filter = ldap.make_filter_from_attr('usercertificate', cert)
403b09
+            filters.append(filter)
403b09
+
403b09
+        result = collections.OrderedDict()
403b09
+        complete = bool(filters)
403b09
+
403b09
+        if cert is None:
403b09
+            filter = '(usercertificate=*)'
403b09
+            filters.append(filter)
403b09
+
403b09
+        filter = ldap.combine_filters(filters, ldap.MATCH_ALL)
403b09
+        try:
403b09
+            entries, truncated = ldap.find_entries(
403b09
+                base_dn=self.api.env.basedn,
403b09
+                filter=filter,
403b09
+                attrs_list=['usercertificate'],
403b09
+                time_limit=timelimit,
403b09
+                size_limit=sizelimit,
403b09
+            )
403b09
+        except errors.EmptyResult:
403b09
+            entries = []
403b09
+            truncated = False
403b09
         else:
403b09
-            ca_enabled = True
403b09
+            truncated = bool(truncated)
403b09
+
403b09
+        for entry in entries:
403b09
+            for attr in ('usercertificate', 'usercertificate;binary'):
403b09
+                for cert in entry.get(attr, []):
403b09
+                    key = self._get_cert_key(cert)
403b09
+
403b09
+                    try:
403b09
+                        obj = result[key]
403b09
+                    except KeyError:
403b09
+                        obj = self._get_cert_obj(cert, all, raw, pkey_only)
403b09
+                        result[key] = obj
403b09
 
403b09
+                    if not pkey_only and (all or not no_members):
403b09
+                        owners = obj.setdefault('owner', [])
403b09
+                        if entry.dn not in owners:
403b09
+                            owners.append(entry.dn)
403b09
+
403b09
+        if not raw:
403b09
+            for obj in six.itervalues(result):
403b09
+                self.obj._fill_owners(obj)
403b09
+
403b09
+        return result, truncated, complete
403b09
+
403b09
+    def execute(self, criteria=None, all=False, raw=False, pkey_only=False,
403b09
+                no_members=True, timelimit=None, sizelimit=None, **options):
403b09
         if 'cacn' in options:
403b09
             ca_obj = api.Command.ca_show(options['cacn'])['result']
403b09
             ca_sdn = unicode(ca_obj['ipacasubjectdn'][0])
403b09
@@ -1028,153 +1172,43 @@ class cert_find(Search, CertMethod):
403b09
         if criteria is not None:
403b09
             return dict(result=[], count=0, truncated=False)
403b09
 
403b09
-        obj_seq = []
403b09
-        obj_dict = {}
403b09
+        result = collections.OrderedDict()
403b09
         truncated = False
403b09
-
403b09
-        if has_cert_option:
403b09
-            cert = options['certificate']
403b09
-            obj = {'certificate': unicode(base64.b64encode(cert))}
403b09
-            obj_seq.append(obj)
403b09
-            obj_dict[cert] = obj
403b09
-
403b09
-        if ca_enabled:
403b09
-            ra_options = {}
403b09
-            for name, value in options.items():
403b09
-                if name not in ca_options:
403b09
-                    continue
403b09
-                if isinstance(value, datetime.datetime):
403b09
-                    value = value.strftime(PKIDATE_FORMAT)
403b09
-                elif isinstance(value, DN):
403b09
-                    value = unicode(value)
403b09
-                ra_options[name] = value
403b09
-            if sizelimit is not None:
403b09
-                if sizelimit != 0:
403b09
-                    ra_options['sizelimit'] = sizelimit
403b09
-                sizelimit = 0
403b09
-                has_ca_options = True
403b09
-
403b09
-            for ra_obj in self.Backend.ra.find(ra_options):
403b09
-                obj = {}
403b09
-                if ((not pkey_only and all) or
403b09
-                        not no_members or
403b09
-                        not has_ca_options or
403b09
-                        has_ldap_options or
403b09
-                        has_cert_option):
403b09
-                    ra_obj.update(
403b09
-                        self.Backend.ra.get_certificate(
403b09
-                            str(ra_obj['serial_number'])))
403b09
-                    cert = base64.b64decode(ra_obj['certificate'])
403b09
-                    try:
403b09
-                        obj = obj_dict[cert]
403b09
-                    except KeyError:
403b09
-                        if has_cert_option:
403b09
-                            continue
403b09
-                        obj = {}
403b09
-                        obj_seq.append(obj)
403b09
-                        obj_dict[cert] = obj
403b09
+        complete = False
403b09
+
403b09
+        for sub_search in (self._cert_search,
403b09
+                           self._ca_search,
403b09
+                           self._ldap_search):
403b09
+            sub_result, sub_truncated, sub_complete = sub_search(
403b09
+                all=all,
403b09
+                raw=raw,
403b09
+                pkey_only=pkey_only,
403b09
+                no_members=no_members,
403b09
+                timelimit=timelimit,
403b09
+                sizelimit=sizelimit,
403b09
+                **options)
403b09
+
403b09
+            if sub_complete:
403b09
+                sizelimit = None
403b09
+
403b09
+                for key in tuple(result):
403b09
+                    if key not in sub_result:
403b09
+                        del result[key]
403b09
+
403b09
+            for key, sub_obj in six.iteritems(sub_result):
403b09
+                try:
403b09
+                    obj = result[key]
403b09
+                except KeyError:
403b09
+                    if complete:
403b09
+                        continue
403b09
+                    result[key] = sub_obj
403b09
                 else:
403b09
-                    obj_seq.append(obj)
403b09
-                obj.update(ra_obj)
403b09
-
403b09
-        if ((not pkey_only and all) or
403b09
-                not no_members or
403b09
-                not has_ca_options or
403b09
-                has_ldap_options or
403b09
-                has_cert_option):
403b09
-            ldap = self.api.Backend.ldap2
403b09
+                    obj.update(sub_obj)
403b09
 
403b09
-            filters = []
403b09
-            if 'certificate' in options:
403b09
-                cert_filter = ldap.make_filter_from_attr(
403b09
-                    'usercertificate', options['certificate'])
403b09
-            else:
403b09
-                cert_filter = '(usercertificate=*)'
403b09
-            filters.append(cert_filter)
403b09
-            for owner in self.obj._owners():
403b09
-                oc_filter = ldap.make_filter_from_attr(
403b09
-                    'objectclass', owner.object_class, ldap.MATCH_ALL)
403b09
-                for prefix, rule in (('', ldap.MATCH_ALL),
403b09
-                                     ('no_', ldap.MATCH_NONE)):
403b09
-                    value = options.get(prefix + owner.name)
403b09
-                    if value is None:
403b09
-                        continue
403b09
-                    pkey_filter = ldap.make_filter_from_attr(
403b09
-                        owner.primary_key.name, value, rule)
403b09
-                    filters.append(oc_filter)
403b09
-                    filters.append(pkey_filter)
403b09
-            filter = ldap.combine_filters(filters, ldap.MATCH_ALL)
403b09
+            truncated = truncated or sub_truncated
403b09
+            complete = complete or sub_complete
403b09
 
403b09
-            try:
403b09
-                entries, truncated = ldap.find_entries(
403b09
-                    base_dn=self.api.env.basedn,
403b09
-                    filter=filter,
403b09
-                    attrs_list=['usercertificate'],
403b09
-                    time_limit=timelimit,
403b09
-                    size_limit=sizelimit,
403b09
-                )
403b09
-            except errors.EmptyResult:
403b09
-                entries, truncated = [], False
403b09
-            for entry in entries:
403b09
-                seen = set()
403b09
-                for attr in ('usercertificate', 'usercertificate;binary'):
403b09
-                    for cert in entry.get(attr, []):
403b09
-                        if cert in seen:
403b09
-                            continue
403b09
-                        seen.add(cert)
403b09
-                        try:
403b09
-                            obj = obj_dict[cert]
403b09
-                        except KeyError:
403b09
-                            if has_ca_options or has_cert_option:
403b09
-                                continue
403b09
-                            obj = {
403b09
-                                'certificate': unicode(base64.b64encode(cert))}
403b09
-                            obj_seq.append(obj)
403b09
-                            obj_dict[cert] = obj
403b09
-                        obj.setdefault('owner', []).append(entry.dn)
403b09
-
403b09
-        result = []
403b09
-        for obj in obj_seq:
403b09
-            if has_ldap_options and 'owner' not in obj:
403b09
-                continue
403b09
-            if not pkey_only:
403b09
-                if not raw:
403b09
-                    if 'certificate' in obj:
403b09
-                        obj['certificate'] = (
403b09
-                            obj['certificate'].replace('\r\n', ''))
403b09
-                        self.obj._parse(obj)
403b09
-                        if not all:
403b09
-                            del obj['certificate']
403b09
-                            del obj['valid_not_before']
403b09
-                            del obj['valid_not_after']
403b09
-                            del obj['md5_fingerprint']
403b09
-                            del obj['sha1_fingerprint']
403b09
-                    if 'subject' in obj:
403b09
-                        obj['subject'] = DN(obj['subject'])
403b09
-                    if 'issuer' in obj:
403b09
-                        obj['issuer'] = DN(obj['issuer'])
403b09
-                    if 'status' in obj:
403b09
-                        obj['revoked'] = (
403b09
-                            obj['status'] in (u'REVOKED', u'REVOKED_EXPIRED'))
403b09
-                    if 'owner' in obj:
403b09
-                        if all or not no_members:
403b09
-                            self.obj._fill_owners(obj)
403b09
-                        del obj['owner']
403b09
-                else:
403b09
-                    if 'certificate' in obj:
403b09
-                        if not all:
403b09
-                            del obj['certificate']
403b09
-                    if 'owner' in obj:
403b09
-                        if not all and no_members:
403b09
-                            del obj['owner']
403b09
-            else:
403b09
-                if 'serial_number' in obj:
403b09
-                    serial_number = obj['serial_number']
403b09
-                    obj.clear()
403b09
-                    obj['serial_number'] = serial_number
403b09
-                else:
403b09
-                    obj.clear()
403b09
-            result.append(obj)
403b09
+        result = list(six.itervalues(result))
403b09
 
403b09
         ret = dict(
403b09
             result=result
403b09
-- 
403b09
2.7.4
403b09