From eb9d14debc4276f422ae55d141e30246d5943067 Mon Sep 17 00:00:00 2001 From: Jan Cholasta Date: Thu, 30 Mar 2017 08:33:30 +0000 Subject: [PATCH] cert: defer cert-find result post-processing Rather than post-processing the results of each internal search, post-process the combined result. This avoids expensive per-certificate searches when cert-find is executed with the --all option on certificates which won't even be included in the combined result. https://pagure.io/freeipa/issue/6808 Reviewed-By: Stanislav Laznicka --- ipaserver/plugins/cert.py | 93 +++++++++++++++++++++++++++------------------ ipaserver/plugins/dogtag.py | 10 +++++ 2 files changed, 66 insertions(+), 37 deletions(-) diff --git a/ipaserver/plugins/cert.py b/ipaserver/plugins/cert.py index dfc7444ddbf31ac3c194e050af28220fc2a87a92..68402679cf0320e9c664ea89276f6c4332730a15 100644 --- a/ipaserver/plugins/cert.py +++ b/ipaserver/plugins/cert.py @@ -162,6 +162,11 @@ def normalize_pkidate(value): return datetime.datetime.strptime(value, PKIDATE_FORMAT) +def convert_pkidatetime(value): + value = datetime.datetime.fromtimestamp(int(value) // 1000) + return x509.format_datetime(value) + + def validate_csr(ugettext, csr): """ Ensure the CSR is base64-encoded and can be decoded by our PKCS#10 @@ -1296,18 +1301,7 @@ class cert_find(Search, CertMethod): return (DN(cert_obj.issuer), cert_obj.serial_number) - def _get_cert_obj(self, cert, all, raw, pkey_only): - obj = {'certificate': base64.b64encode(cert).decode('ascii')} - - full = not pkey_only and all - if not raw: - self.obj._parse(obj, full) - if not full: - del obj['certificate'] - - return obj - - def _cert_search(self, all, raw, pkey_only, **options): + def _cert_search(self, pkey_only, **options): result = collections.OrderedDict() try: @@ -1316,15 +1310,19 @@ class cert_find(Search, CertMethod): return result, False, False try: - key = self._get_cert_key(cert) + issuer, serial_number = self._get_cert_key(cert) except ValueError: return result, True, True - result[key] = self._get_cert_obj(cert, all, raw, pkey_only) + obj = {'serial_number': serial_number} + if not pkey_only: + obj['certificate'] = base64.b64encode(cert).decode('ascii') + + result[issuer, serial_number] = obj return result, False, True - def _ca_search(self, all, raw, pkey_only, exactly, **options): + def _ca_search(self, raw, pkey_only, exactly, **options): ra_options = {} for name in ('revocation_reason', 'issuer', @@ -1357,7 +1355,6 @@ class cert_find(Search, CertMethod): return result, False, complete ca_objs = self.api.Command.ca_find( - all=all, timelimit=0, sizelimit=0, )['result'] @@ -1377,24 +1374,16 @@ class cert_find(Search, CertMethod): obj = {'serial_number': serial_number} else: obj = ra_obj - if all: - obj.update(ra.get_certificate(str(serial_number))) if not raw: obj['issuer'] = issuer obj['subject'] = DN(ra_obj['subject']) + obj['valid_not_before'] = ( + convert_pkidatetime(obj['valid_not_before'])) + obj['valid_not_after'] = ( + convert_pkidatetime(obj['valid_not_after'])) obj['revoked'] = ( ra_obj['status'] in (u'REVOKED', u'REVOKED_EXPIRED')) - if all: - obj['certificate'] = ( - obj['certificate'].replace('\r\n', '')) - self.obj._parse(obj) - - if 'certificate_chain' in ca_obj: - cert = x509.load_certificate(obj['certificate']) - cert_der = cert.public_bytes(serialization.Encoding.DER) - obj['certificate_chain'] = ( - [cert_der] + ca_obj['certificate_chain']) obj['cacn'] = ca_obj['cn'][0] @@ -1402,7 +1391,7 @@ class cert_find(Search, CertMethod): return result, False, complete - def _ldap_search(self, all, raw, pkey_only, no_members, **options): + def _ldap_search(self, all, pkey_only, no_members, **options): ldap = self.api.Backend.ldap2 filters = [] @@ -1461,26 +1450,25 @@ class cert_find(Search, CertMethod): for attr in ('usercertificate', 'usercertificate;binary'): for cert in entry.get(attr, []): try: - key = self._get_cert_key(cert) + issuer, serial_number = self._get_cert_key(cert) except ValueError: truncated = True continue try: - obj = result[key] + obj = result[issuer, serial_number] except KeyError: - obj = self._get_cert_obj(cert, all, raw, pkey_only) - result[key] = obj + obj = {'serial_number': serial_number} + if not pkey_only and all: + obj['certificate'] = ( + base64.b64encode(cert).decode('ascii')) + result[issuer, serial_number] = obj if not pkey_only and (all or not no_members): owners = obj.setdefault('owner', []) if entry.dn not in owners: owners.append(entry.dn) - if not raw: - for obj in six.itervalues(result): - self.obj._fill_owners(obj) - return result, truncated, complete def execute(self, criteria=None, all=False, raw=False, pkey_only=False, @@ -1537,6 +1525,37 @@ class cert_find(Search, CertMethod): truncated = truncated or sub_truncated complete = complete or sub_complete + if not pkey_only: + ca_objs = {} + ra = self.api.Backend.ra + + for key, obj in six.iteritems(result): + if all and 'cacn' in obj: + _issuer, serial_number = key + cacn = obj['cacn'] + + try: + ca_obj = ca_objs[cacn] + except KeyError: + ca_obj = ca_objs[cacn] = ( + self.api.Command.ca_show(cacn, all=True)['result']) + + obj.update(ra.get_certificate(str(serial_number))) + if not raw: + obj['certificate'] = ( + obj['certificate'].replace('\r\n', '')) + + if 'certificate_chain' in ca_obj: + cert = x509.load_certificate(obj['certificate']) + cert_der = ( + cert.public_bytes(serialization.Encoding.DER)) + obj['certificate_chain'] = ( + [cert_der] + ca_obj['certificate_chain']) + + if not raw: + self.obj._parse(obj, all) + self.obj._fill_owners(obj) + result = list(six.itervalues(result)) if sizelimit > 0 and len(result) > sizelimit: if not truncated: diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py index d1dd707f145e0f58bfa721df55513d46c14358f2..3997531032746a22243a4219250af4172e9ae5b3 100644 --- a/ipaserver/plugins/dogtag.py +++ b/ipaserver/plugins/dogtag.py @@ -1945,6 +1945,16 @@ class ra(rabase.rabase, RestClient): if len(issuer_dn) == 1: response_request['issuer'] = unicode(issuer_dn[0].text) + not_valid_before = cert.xpath('NotValidBefore') + if len(not_valid_before) == 1: + response_request['valid_not_before'] = ( + unicode(not_valid_before[0].text)) + + not_valid_after = cert.xpath('NotValidAfter') + if len(not_valid_after) == 1: + response_request['valid_not_after'] = ( + unicode(not_valid_after[0].text)) + status = cert.xpath('Status') if len(status) == 1: response_request['status'] = unicode(status[0].text) -- 2.12.2