From f452a7ce68d891459c4a868f0e81f623fd5ec6e6 Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Sat, 11 May 2019 18:06:51 -0700 Subject: [PATCH] adding a new API for faster certificate lookup. The new API api/1/certificates/valid returns only non-expired (not_after >= today) certs which have auto-rotate enabled: cn is a required parameter: http://localhost:8000/api/1/certificates/valid?filter=cn;example.com cn can also be a database string wildcard ('%'): http://localhost:8000/api/1/certificates/valid?filter=cn;% owner is the additional parameter, and must be the email address of the owner: http://localhost:8000/api/1/certificates/valid?filter=cn;example.com&owner=hossein@example.com given owner and a database string wildcard ('%') one can retrieve all certs for that owner, which are still valid, and have auto-rotate enabled: http://localhost:8000/api/1/certificates/valid?filter=cn;%&owner=hossein@example.com --- lemur/certificates/service.py | 24 +++++++++ lemur/certificates/views.py | 97 +++++++++++++++++++++++++++++++++++ lemur/common/utils.py | 1 + 3 files changed, 122 insertions(+) diff --git a/lemur/certificates/service.py b/lemur/certificates/service.py index 8a1b74d2..815349ff 100644 --- a/lemur/certificates/service.py +++ b/lemur/certificates/service.py @@ -401,6 +401,30 @@ def query_name(certificate_name, args): return result +def query_common_name(common_name, args): + """ + Helper function that queries for not expired certificates by common name and owner which have auto-rotate enabled + + :param common_name: + :param args: + :return: + """ + owner = args.pop('owner') + if not owner: + owner = '%' + + # only not expired certificates + current_time = arrow.utcnow() + + result = Certificate.query.filter(Certificate.cn.ilike(common_name)) \ + .filter(Certificate.owner.ilike(owner))\ + .filter(Certificate.not_after >= current_time.format('YYYY-MM-DD')) \ + .filter(Certificate.rotation.is_(True))\ + .all() + + return result + + def create_csr(**csr_config): """ Given a list of domains create the appropriate csr diff --git a/lemur/certificates/views.py b/lemur/certificates/views.py index 17aa418f..48f6d672 100644 --- a/lemur/certificates/views.py +++ b/lemur/certificates/views.py @@ -37,6 +37,102 @@ mod = Blueprint('certificates', __name__) api = Api(mod) +class CertificatesListValid(AuthenticatedResource): + """ Defines the 'certificates/valid' endpoint """ + + def __init__(self): + self.reqparse = reqparse.RequestParser() + super(CertificatesListValid, self).__init__() + + @validate_schema(None, certificates_output_schema) + def get(self): + """ + .. http:get:: /certificates/valid/ + + The current list of not-expired certificates for a given common name, and owner + + **Example request**: + + .. sourcecode:: http + GET /certificates/valid?filter=cn;*.test.example.net&owner=joe@example.com + HTTP/1.1 + Host: example.com + Accept: application/json, text/javascript + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Vary: Accept + Content-Type: text/javascript + + { + "items": [{ + "status": null, + "cn": "*.test.example.net", + "chain": "", + "csr": "-----BEGIN CERTIFICATE REQUEST-----" + "authority": { + "active": true, + "owner": "secure@example.com", + "id": 1, + "description": "verisign test authority", + "name": "verisign" + }, + "owner": "joe@example.com", + "serial": "82311058732025924142789179368889309156", + "id": 2288, + "issuer": "SymantecCorporation", + "dateCreated": "2016-06-03T06:09:42.133769+00:00", + "notBefore": "2016-06-03T00:00:00+00:00", + "notAfter": "2018-01-12T23:59:59+00:00", + "destinations": [], + "bits": 2048, + "body": "-----BEGIN CERTIFICATE-----...", + "description": null, + "deleted": null, + "notifications": [{ + "id": 1 + }], + "signingAlgorithm": "sha256", + "user": { + "username": "jane", + "active": true, + "email": "jane@example.com", + "id": 2 + }, + "active": true, + "domains": [{ + "sensitive": false, + "id": 1090, + "name": "*.test.example.net" + }], + "replaces": [], + "replaced": [], + "name": "WILDCARD.test.example.net-SymantecCorporation-20160603-20180112", + "roles": [{ + "id": 464, + "description": "This is a google group based role created by Lemur", + "name": "joe@example.com" + }], + "san": null + }], + "total": 1 + } + + :reqheader Authorization: OAuth token to authenticate + :statuscode 200: no error + :statuscode 403: unauthenticated + + """ + parser = paginated_parser.copy() + args = parser.parse_args() + args['user'] = g.user + common_name = args['filter'].split(';')[1] + return service.query_common_name(common_name, args) + + class CertificatesNameQuery(AuthenticatedResource): """ Defines the 'certificates/name' endpoint """ @@ -1190,6 +1286,7 @@ class CertificateRevoke(AuthenticatedResource): api.add_resource(CertificateRevoke, '/certificates//revoke', endpoint='revokeCertificate') api.add_resource(CertificatesNameQuery, '/certificates/name/', endpoint='certificatesNameQuery') api.add_resource(CertificatesList, '/certificates', endpoint='certificates') +api.add_resource(CertificatesListValid, '/certificates/valid', endpoint='certificatesListValid') api.add_resource(Certificates, '/certificates/', endpoint='certificate') api.add_resource(CertificatesStats, '/certificates/stats', endpoint='certificateStats') api.add_resource(CertificatesUpload, '/certificates/upload', endpoint='certificateUpload') diff --git a/lemur/common/utils.py b/lemur/common/utils.py index 24ff5784..40f828f3 100644 --- a/lemur/common/utils.py +++ b/lemur/common/utils.py @@ -30,6 +30,7 @@ paginated_parser.add_argument('page', type=int, default=1, location='args') paginated_parser.add_argument('sortDir', type=str, dest='sort_dir', location='args') paginated_parser.add_argument('sortBy', type=str, dest='sort_by', location='args') paginated_parser.add_argument('filter', type=str, location='args') +paginated_parser.add_argument('owner', type=str, location='args') def get_psuedo_random_string():