def rotate_endpoint_remove_cert(self, endpoint_id, certificate_id): function = f"{__name__}.{sys._getframe().f_code.co_name}" logger = logging.getLogger(function) if self.request.retries > 0: logger.warning( f"Retrying rotate_endpoint_remove_cert task as it failed before (retry {self.request.retries} of {self.max_retries})" ) endpoint = endpoint_service.get(endpoint_id) certificate = certificate_service.get(certificate_id) if not endpoint: # note: this can happen if this is scheduled twice logger.warning( "Could not detach cert because endpoint does not exist - maybe this task was scheduled twice." ) return if not certificate: logger.warning( "Could not detach cert because certificate does not exist - maybe this task was scheduled twice." ) return with red.lock(endpoint.name.rsplit("/", 1)[0], blocking_timeout=10): endpoint.source.plugin.remove_certificate(endpoint, certificate.name) # sync source if not is_task_scheduled(sync_source, (endpoint.source.label, )): sync_source.delay(endpoint.source.label)
def get(self, certificate_id): """ .. http:get:: /certificates/1/creator Get a certificate's creator **Example request**: .. sourcecode:: http GET /certificates/1/creator 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 { "id": 1, "active": false, "email": "*****@*****.**", "username": "******", "profileImage": null } :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error """ return certificate_service.get(certificate_id).user
def revoke(path, reason, commit): """ Revokes given certificate. """ if commit: print("[!] Running in COMMIT mode.") print("[+] Starting certificate revocation.") with open(path, 'r') as f: for c in f.readlines()[2:]: parts = c.split(' ') try: cert = get(int(parts[0].strip())) plugin = plugins.get(cert.authority.plugin_name) print('[+] Revoking certificate. Id: {0} Name: {1}'.format( cert.id, cert.name)) if commit: plugin.revoke_certificate(cert, reason) except Exception as e: sentry.captureException() metrics.send('certificate_revoke_failure', 'counter', 1) print( "[!] Failed to revoke certificates. Reason: {}".format(e))
def worker(data, commit, reason): parts = [x for x in data.split(" ") if x] try: cert = get(int(parts[0].strip())) plugin = plugins.get(cert.authority.plugin_name) print("[+] Revoking certificate. Id: {0} Name: {1}".format( cert.id, cert.name)) if commit: plugin.revoke_certificate(cert, reason) metrics.send( "certificate_revoke", "counter", 1, metric_tags={"status": SUCCESS_METRIC_STATUS}, ) except Exception as e: sentry.captureException() metrics.send( "certificate_revoke", "counter", 1, metric_tags={"status": FAILURE_METRIC_STATUS}, ) print("[!] Failed to revoke certificates. Reason: {}".format(e))
def test_delete_cert(session): from lemur.certificates.service import delete, get from lemur.tests.factories import CertificateFactory delete_this = CertificateFactory(name='DELETEME') session.commit() cert_exists = get(delete_this.id) # it needs to exist first assert cert_exists delete(delete_this.id) cert_exists = get(delete_this.id) # then not exist after delete assert not cert_exists
def put(self, certificate_id, data=None): """ .. http:put:: /certificates/1/revoke Revoke a certificate **Example request**: .. sourcecode:: http POST /certificates/1/revoke 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 { 'id': 1 } :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error :statuscode 403: unauthenticated """ cert = service.get(certificate_id) if not cert: return dict(message="Cannot find specified certificate"), 404 # allow creators if g.current_user != cert.user: owner_role = role_service.get_by_name(cert.owner) permission = CertificatePermission(owner_role, [x.name for x in cert.roles]) if not permission.can(): return dict(message='You are not authorized to revoke this certificate.'), 403 if not cert.external_id: return dict(message='Cannot revoke certificate. No external id found.'), 400 if cert.endpoints: return dict(message='Cannot revoke certificate. Endpoints are deployed with the given certificate.'), 403 plugin = plugins.get(cert.authority.plugin_name) plugin.revoke_certificate(cert, data) log_service.create(g.current_user, 'revoke_cert', certificate=cert) return dict(id=cert.id)
def delete(self, certificate_id, data=None): """ .. http:delete:: /certificates/1 Delete a certificate **Example request**: .. sourcecode:: http DELETE /certificates/1 HTTP/1.1 Host: example.com **Example response**: .. sourcecode:: http HTTP/1.1 204 OK :reqheader Authorization: OAuth token to authenticate :statuscode 204: no error :statuscode 403: unauthenticated :statuscode 404: certificate not found :statuscode 405: certificate deletion is disabled """ if not current_app.config.get("ALLOW_CERT_DELETION", False): return dict(message="Certificate deletion is disabled"), 405 cert = service.get(certificate_id) if not cert: return dict(message="Cannot find specified certificate"), 404 if cert.deleted: return dict(message="Certificate is already deleted"), 412 # allow creators if g.current_user != cert.user: owner_role = role_service.get_by_name(cert.owner) permission = CertificatePermission(owner_role, [x.name for x in cert.roles]) if not permission.can(): return ( dict(message= "You are not authorized to delete this certificate"), 403, ) service.update(certificate_id, deleted=True) log_service.create(g.current_user, "delete_cert", certificate=cert) return "Certificate deleted", 204
def test_cleanup_after_revoke(session, issuer_plugin, crypto_authority): from lemur.certificates.service import cleanup_after_revoke, get from lemur.tests.factories import CertificateFactory revoke_this = CertificateFactory(name="REVOKEME") session.commit() to_be_revoked = get(revoke_this.id) assert to_be_revoked to_be_revoked.notify = True to_be_revoked.rotation = True # Assuming the cert is revoked by corresponding issuer, update the records in lemur cleanup_after_revoke(to_be_revoked) revoked_cert = get(to_be_revoked.id) # then not exist after delete assert revoked_cert assert revoked_cert.status == "revoked" assert not revoked_cert.notify assert not revoked_cert.rotation assert not revoked_cert.destinations
def create_certificate(pending_certificate, certificate, user): """ Create and store a certificate with pending certificate's info Args: pending_certificate: PendingCertificate which will populate the certificate certificate: dict from Authority, which contains the body, chain and external id user: User that called this function, used as 'creator' of the certificate if it does not have an owner """ # check if the pending_certificate already has been resolved, in that case return # existing certificate maybe_resolved_cert = get(pending_certificate.id) if maybe_resolved_cert and maybe_resolved_cert.resolved and maybe_resolved_cert.resolved_cert_id: current_app.logger.warning( "Trying to resolve an already resolved certificate, returning existing resolved certificate" ) return certificate_service.get(maybe_resolved_cert.resolved_cert_id) certificate["owner"] = pending_certificate.owner data, errors = CertificateUploadInputSchema().load(certificate) if errors: raise Exception( "Unable to create certificate: {reasons}".format(reasons=errors)) data.update(vars(pending_certificate)) # Copy relationships, vars doesn't copy this without explicit fields data["notifications"] = list(pending_certificate.notifications) data["destinations"] = list(pending_certificate.destinations) data["sources"] = list(pending_certificate.sources) data["roles"] = list(pending_certificate.roles) data["replaces"] = list(pending_certificate.replaces) data["rotation_policy"] = pending_certificate.rotation_policy data["authority"] = pending_certificate.authority # Replace external id and chain with the one fetched from source data["external_id"] = certificate["external_id"] data["chain"] = certificate["chain"] creator = user_service.get_by_email(pending_certificate.owner) if not creator: # Owner of the pending certificate is not the creator, so use the current user who called # this as the creator (usually lemur) creator = user if pending_certificate.rename: # If generating name from certificate, remove the one from pending certificate del data["name"] data["creator"] = creator cert = certificate_service.import_certificate(**data) database.update(cert) return cert
def delete(self, certificate_id, data=None): """ .. http:delete:: /certificates/1 Delete a certificate **Example request**: .. sourcecode:: http DELETE /certificates/1 HTTP/1.1 Host: example.com **Example response**: .. sourcecode:: http HTTP/1.1 200 OK :reqheader Authorization: OAuth token to authenticate :statuscode 204: no error :statuscode 403: unauthenticated :statusoode 404: certificate not found """ cert = service.get(certificate_id) if not cert: return dict(message="Cannot find specified certificate"), 404 # allow creators if g.current_user != cert.user: owner_role = role_service.get_by_name(cert.owner) permission = CertificatePermission(owner_role, [x.name for x in cert.roles]) if not permission.can(): return dict( message='You are not authorized to delete this certificate' ), 403 if arrow.get(cert.not_after) > arrow.utcnow(): return dict( message= 'Certificate is still valid, only expired certificates can be deleted' ), 412 service.update(certificate_id, deleted=True) log_service.create(g.current_user, 'delete_cert', certificate=cert) return '', 204
def get(self, certificate_id): """ .. http:get:: /certificates/1/key Retrieves the private key for a given certificate **Example request**: .. sourcecode:: http GET /certificates/1/key 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 { "key": "-----BEGIN ...", } :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error :statuscode 403: unauthenticated """ cert = service.get(certificate_id) if not cert: return dict(message="Cannot find specified certificate"), 404 # allow creators if g.current_user != cert.user: owner_role = role_service.get_by_name(cert.owner) permission = CertificatePermission(owner_role, [x.name for x in cert.roles]) if not permission.can(): return dict( message='You are not authorized to view this key'), 403 log_service.create(g.current_user, 'key_view', certificate=cert) response = make_response(jsonify(key=cert.private_key), 200) response.headers[ 'cache-control'] = 'private, max-age=0, no-cache, no-store' response.headers['pragma'] = 'no-cache' return response
def delete(self, certificate_id, data=None): """ .. http:delete:: /certificates/1 Delete a certificate **Example request**: .. sourcecode:: http DELETE /certificates/1 HTTP/1.1 Host: example.com **Example response**: .. sourcecode:: http HTTP/1.1 204 OK :reqheader Authorization: OAuth token to authenticate :statuscode 204: no error :statuscode 403: unauthenticated :statuscode 404: certificate not found :statuscode 405: certificate deletion is disabled """ if not current_app.config.get('ALLOW_CERT_DELETION', False): return dict(message="Certificate deletion is disabled"), 405 cert = service.get(certificate_id) if not cert: return dict(message="Cannot find specified certificate"), 404 if cert.deleted: return dict(message="Certificate is already deleted"), 412 # allow creators if g.current_user != cert.user: owner_role = role_service.get_by_name(cert.owner) permission = CertificatePermission(owner_role, [x.name for x in cert.roles]) if not permission.can(): return dict(message='You are not authorized to delete this certificate'), 403 service.update(certificate_id, deleted=True) log_service.create(g.current_user, 'delete_cert', certificate=cert) return 'Certificate deleted', 204
def get(self, certificate_id): """ .. http:get:: /certificates/1/key Retrieves the private key for a given certificate **Example request**: .. sourcecode:: http GET /certificates/1/key 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 { "key": "----Begin ...", } :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error :statuscode 403: unauthenticated """ cert = service.get(certificate_id) if not cert: return dict(message="Cannot find specified certificate"), 404 role = role_service.get_by_name(cert.owner) permission = ViewKeyPermission(certificate_id, getattr(role, 'name', None)) if permission.can(): response = make_response(jsonify(key=cert.private_key), 200) response.headers[ 'cache-control'] = 'private, max-age=0, no-cache, no-store' response.headers['pragma'] = 'no-cache' return response return dict(message='You are not authorized to view this key'), 403
def get(self, certificate_id): """ .. http:get:: /certificates/1/replacements One certificate **Example request**: .. sourcecode:: http GET /certificates/1/replacements 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 [{ "id": 1, "name": "cert1", "description": "this is cert1", "bits": 2048, "deleted": false, "issuer": "ExampeInc.", "serial": "123450", "chain": "-----Begin ...", "body": "-----Begin ...", "san": true, "owner": "*****@*****.**", "active": true, "notBefore": "2015-06-05T17:09:39", "notAfter": "2015-06-10T17:09:39", "signingAlgorithm": "sha2", "cn": "example.com", "status": "unknown" }] :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error :statuscode 403: unauthenticated """ return service.get(certificate_id).replaces
def get(self, certificate_id): """ .. http:get:: /certificates/1/key Retrieves the private key for a given certificate **Example request**: .. sourcecode:: http GET /certificates/1/key 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 { "key": "-----BEGIN ..." } :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error :statuscode 403: unauthenticated """ cert = service.get(certificate_id) if not cert: return dict(message="Cannot find specified certificate"), 404 # allow creators if g.current_user != cert.user: owner_role = role_service.get_by_name(cert.owner) permission = CertificatePermission(owner_role, [x.name for x in cert.roles]) if not permission.can(): return dict(message='You are not authorized to view this key'), 403 log_service.create(g.current_user, 'key_view', certificate=cert) response = make_response(jsonify(key=cert.private_key), 200) response.headers['cache-control'] = 'private, max-age=0, no-cache, no-store' response.headers['pragma'] = 'no-cache' return response
def certificate_check_destination(cert_id, dest_id): """ This celery task checks a certificate, destination pair to verify that the certficate has been uploaded and uploads it if it hasn't :return: """ function = f"{__name__}.{sys._getframe().f_code.co_name}" logger = logging.getLogger(function) task_id = None if celery.current_task: task_id = celery.current_task.request.id log_data = { "task_id": task_id, } if task_id and is_task_active(function, task_id, None): logger.debug("Skipping task: Task is already active", extra=log_data) return log_data cert = certificate_service.get(cert_id) dest = destinations_service.get(dest_id) if not cert: raise RuntimeError( f"certificate (id={cert_id}) does not exist in database") # populate log data log_data["certificate"] = cert.name log_data["destination"] = str(dest) logger.debug("verifying certificate/destination pair", extra=log_data) uploaded = dest.plugin.verify(cert.name, dest.options) if not uploaded: logger.info("uploading certificate to destination", extra=log_data) dest.plugin.upload(cert.name, cert.body, cert.private_key, cert.chain, dest.options) logger.info("certificate uploaded to destination", extra=log_data) metrics.send(f"{function}.destination_missing_cert_resolved", "counter", 1) # at this point, the certificate MUST exist on the destination logger.debug("certificate/destination pair valid", extra=log_data) metrics.send(f"{function}.destination_certificate_valid", "counter", 1) return log_data
def get(self, certificate_id): """ .. http:get:: /certificates/1/key Retrieves the private key for a given certificate **Example request**: .. sourcecode:: http GET /certificates/1/key 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 { "key": "----Begin ...", } :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error :statuscode 403: unauthenticated """ cert = service.get(certificate_id) if not cert: return dict(message="Cannot find specified certificate"), 404 role = role_service.get_by_name(cert.owner) permission = ViewKeyPermission(certificate_id, getattr(role, 'name', None)) if permission.can(): response = make_response(jsonify(key=cert.private_key), 200) response.headers['cache-control'] = 'private, max-age=0, no-cache, no-store' response.headers['pragma'] = 'no-cache' return response return dict(message='You are not authorized to view this key'), 403
def worker(data, commit, reason): parts = [x for x in data.split(' ') if x] try: cert = get(int(parts[0].strip())) plugin = plugins.get(cert.authority.plugin_name) print('[+] Revoking certificate. Id: {0} Name: {1}'.format(cert.id, cert.name)) if commit: plugin.revoke_certificate(cert, reason) except Exception as e: sentry.captureException() metrics.send('certificate_revoke_failure', 'counter', 1) print( "[!] Failed to revoke certificates. Reason: {}".format( e ) )
def get(self, certificate_id): """ .. http:get:: /certificates/1/authority One authority for given certificate **Example request**: .. sourcecode:: http GET /certificates/1/authority 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 { "id": 1, "name": "authority1", "description": "this is authority1", "pluginName": null, "chain": "-----Begin ...", "body": "-----Begin ...", "active": true, "notBefore": "2015-06-05T17:09:39", "notAfter": "2015-06-10T17:09:39" "options": null } :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error :statuscode 403: unauthenticated """ cert = certificate_service.get(certificate_id) if not cert: return dict(message="Certificate not found"), 404 return cert.authority
def worker(data, commit, reason): parts = [x for x in data.split(' ') if x] try: cert = get(int(parts[0].strip())) plugin = plugins.get(cert.authority.plugin_name) print('[+] Revoking certificate. Id: {0} Name: {1}'.format(cert.id, cert.name)) if commit: plugin.revoke_certificate(cert, reason) metrics.send('certificate_revoke', 'counter', 1, metric_tags={'status': SUCCESS_METRIC_STATUS}) except Exception as e: sentry.captureException() metrics.send('certificate_revoke', 'counter', 1, metric_tags={'status': FAILURE_METRIC_STATUS}) print( "[!] Failed to revoke certificates. Reason: {}".format( e ) )
def post(self, certificate_id, data=None): """ .. http:post:: /certificates/1/export Export a certificate **Example request**: .. sourcecode:: http PUT /certificates/1/export HTTP/1.1 Host: example.com Accept: application/json, text/javascript { "export": { "plugin": { "pluginOptions": [{ "available": ["Java Key Store (JKS)"], "required": true, "type": "select", "name": "type", "helpMessage": "Choose the format you wish to export", "value": "Java Key Store (JKS)" }, { "required": false, "type": "str", "name": "passphrase", "validation": "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[$@$!%*#?&])[A-Za-z\\d$@$!%*#?&]{8,}$", "helpMessage": "If no passphrase is given one will be generated for you, we highly recommend this. Minimum length is 8." }, { "required": false, "type": "str", "name": "alias", "helpMessage": "Enter the alias you wish to use for the keystore." }], "version": "unknown", "description": "Attempts to generate a JKS keystore or truststore", "title": "Java", "author": "Kevin Glisson", "type": "export", "slug": "java-export" } } } **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Vary: Accept Content-Type: text/javascript { "data": "base64encodedstring", "passphrase": "UAWOHW#&@_%!tnwmxh832025", "extension": "jks" } :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error :statuscode 403: unauthenticated """ cert = service.get(certificate_id) if not cert: return dict(message="Cannot find specified certificate"), 404 plugin = data['plugin']['plugin_object'] if plugin.requires_key: if not cert.private_key: return dict( message= 'Unable to export certificate, plugin: {0} requires a private key but no key was found.' .format(plugin.slug)), 400 else: # allow creators if g.current_user != cert.user: owner_role = role_service.get_by_name(cert.owner) permission = CertificatePermission( owner_role, [x.name for x in cert.roles]) if not permission.can(): return dict( message= 'You are not authorized to export this certificate.' ), 403 options = data['plugin']['plugin_options'] log_service.create(g.current_user, 'key_view', certificate=cert) extension, passphrase, data = plugin.export(cert.body, cert.chain, cert.private_key, options) # we take a hit in message size when b64 encoding return dict(extension=extension, passphrase=passphrase, data=base64.b64encode(data).decode('utf-8'))
def post(self, certificate_id, data=None): """ .. http:post:: /certificates/1/export Export a certificate **Example request**: .. sourcecode:: http PUT /certificates/1/export HTTP/1.1 Host: example.com Accept: application/json, text/javascript { "export": { "plugin": { "pluginOptions": [{ "available": ["Java Key Store (JKS)"], "required": true, "type": "select", "name": "type", "helpMessage": "Choose the format you wish to export", "value": "Java Key Store (JKS)" }, { "required": false, "type": "str", "name": "passphrase", "validation": "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[$@$!%*#?&])[A-Za-z\\d$@$!%*#?&]{8,}$", "helpMessage": "If no passphrase is given one will be generated for you, we highly recommend this. Minimum length is 8." }, { "required": false, "type": "str", "name": "alias", "helpMessage": "Enter the alias you wish to use for the keystore." }], "version": "unknown", "description": "Attempts to generate a JKS keystore or truststore", "title": "Java", "author": "Kevin Glisson", "type": "export", "slug": "java-export" } } } **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Vary: Accept Content-Type: text/javascript { "data": "base64encodedstring", "passphrase": "UAWOHW#&@_%!tnwmxh832025", "extension": "jks" } :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error :statuscode 403: unauthenticated """ cert = service.get(certificate_id) if not cert: return dict(message="Cannot find specified certificate"), 404 plugin = data['plugin']['plugin_object'] if plugin.requires_key: if not cert.private_key: return dict( message='Unable to export certificate, plugin: {0} requires a private key but no key was found.'.format( plugin.slug)), 400 else: # allow creators if g.current_user != cert.user: owner_role = role_service.get_by_name(cert.owner) permission = CertificatePermission(owner_role, [x.name for x in cert.roles]) if not permission.can(): return dict(message='You are not authorized to export this certificate.'), 403 options = data['plugin']['plugin_options'] log_service.create(g.current_user, 'key_view', certificate=cert) extension, passphrase, data = plugin.export(cert.body, cert.chain, cert.private_key, options) # we take a hit in message size when b64 encoding return dict(extension=extension, passphrase=passphrase, data=base64.b64encode(data).decode('utf-8'))
def put(self, certificate_id, data=None): """ .. http:put:: /certificates/1 Update a certificate **Example request**: .. sourcecode:: http PUT /certificates/1 HTTP/1.1 Host: example.com Accept: application/json, text/javascript { "owner": "*****@*****.**", "active": false "notifications": [], "destinations": [], "replacements": [] } **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Vary: Accept Content-Type: text/javascript { "status": null, "cn": "*.test.example.net", "chain": "", "authority": { "active": true, "owner": "*****@*****.**", "id": 1, "description": "verisign test authority", "name": "verisign" }, "owner": "*****@*****.**", "serial": "82311058732025924142789179368889309156", "id": 2288, "issuer": "SymantecCorporation", "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": "******", "active": true, "email": "*****@*****.**", "id": 2 }, "active": true, "domains": [{ "sensitive": false, "id": 1090, "name": "*.test.example.net" }], "replaces": [], "name": "WILDCARD.test.example.net-SymantecCorporation-20160603-20180112", "roles": [{ "id": 464, "description": "This is a google group based role created by Lemur", "name": "*****@*****.**" }], "rotation": True, "rotationPolicy": {"name": "default"}, "san": null } :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error :statuscode 403: unauthenticated """ cert = service.get(certificate_id) if not cert: return dict(message="Cannot find specified certificate"), 404 # allow creators if g.current_user != cert.user: owner_role = role_service.get_by_name(cert.owner) permission = CertificatePermission(owner_role, [x.name for x in cert.roles]) if not permission.can(): return dict( message='You are not authorized to update this certificate' ), 403 for destination in data['destinations']: if destination.plugin.requires_key: if not cert.private_key: return dict( message= 'Unable to add destination: {0}. Certificate does not have required private key.' .format(destination.label)), 400 cert = service.update(certificate_id, **data) log_service.create(g.current_user, 'update_cert', certificate=cert) return cert
def get(self, certificate_id): """ .. http:get:: /certificates/1/replacements One certificate **Example request**: .. sourcecode:: http GET /certificates/1/replacements 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": "", "authority": { "active": true, "owner": "*****@*****.**", "id": 1, "description": "verisign test authority", "name": "verisign" }, "owner": "*****@*****.**", "serial": "82311058732025924142789179368889309156", "id": 2288, "issuer": "SymantecCorporation", "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": "******", "active": true, "email": "*****@*****.**", "id": 2 }, "active": true, "domains": [{ "sensitive": false, "id": 1090, "name": "*.test.example.net" }], "replaces": [], "replaced": [], "rotation": True, "rotationPolicy": {"name": "default"}, "name": "WILDCARD.test.example.net-SymantecCorporation-20160603-20180112", "roles": [{ "id": 464, "description": "This is a google group based role created by Lemur", "name": "*****@*****.**" }], "san": null }], "total": 1 } :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error :statuscode 403: unauthenticated """ return service.get(certificate_id).replaces
def put(self, certificate_id): """ .. http:put:: /certificates/1 Update a certificate **Example request**: .. sourcecode:: http PUT /certificates/1 HTTP/1.1 Host: example.com Accept: application/json, text/javascript { "owner": "*****@*****.**", "active": false "notifications": [], "destinations": [], "replacements": [] } **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Vary: Accept Content-Type: text/javascript { "id": 1, "name": "cert1", "description": "this is cert1", "bits": 2048, "deleted": false, "issuer": "ExampeInc.", "serial": "123450", "chain": "-----Begin ...", "body": "-----Begin ...", "san": true, "owner": "*****@*****.**", "active": false, "notBefore": "2015-06-05T17:09:39", "notAfter": "2015-06-10T17:09:39", "cn": "example.com", "status": "unknown", } :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error :statuscode 403: unauthenticated """ self.reqparse.add_argument('active', type=bool, location='json') self.reqparse.add_argument('owner', type=str, location='json') self.reqparse.add_argument('description', type=str, location='json') self.reqparse.add_argument('destinations', type=list, default=[], location='json') self.reqparse.add_argument('notifications', type=notification_list, default=[], location='json') self.reqparse.add_argument('replacements', type=list, default=[], location='json') args = self.reqparse.parse_args() cert = service.get(certificate_id) role = role_service.get_by_name(cert.owner) permission = UpdateCertificatePermission(certificate_id, getattr(role, 'name', None)) if permission.can(): return service.update(certificate_id, args['owner'], args['description'], args['active'], args['destinations'], args['notifications'], args['replacements']) return dict( message='You are not authorized to update this certificate'), 403
def put(self, certificate_id, data=None): """ .. http:put:: /certificates/1 Update a certificate **Example request**: .. sourcecode:: http PUT /certificates/1 HTTP/1.1 Host: example.com Accept: application/json, text/javascript { "owner": "*****@*****.**", "active": false "notifications": [], "destinations": [], "replacements": [] } **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Vary: Accept Content-Type: text/javascript { "status": null, "cn": "*.test.example.net", "chain": "", "authority": { "active": true, "owner": "*****@*****.**", "id": 1, "description": "verisign test authority", "name": "verisign" }, "owner": "*****@*****.**", "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": "******", "active": true, "email": "*****@*****.**", "id": 2 }, "active": true, "domains": [{ "sensitive": false, "id": 1090, "name": "*.test.example.net" }], "replaces": [], "name": "WILDCARD.test.example.net-SymantecCorporation-20160603-20180112", "roles": [{ "id": 464, "description": "This is a google group based role created by Lemur", "name": "*****@*****.**" }], "rotation": true, "rotationPolicy": {"name": "default"}, "san": null } :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error :statuscode 403: unauthenticated """ cert = service.get(certificate_id) if not cert: return dict(message="Cannot find specified certificate"), 404 # allow creators if g.current_user != cert.user: owner_role = role_service.get_by_name(cert.owner) permission = CertificatePermission(owner_role, [x.name for x in cert.roles]) if not permission.can(): return dict(message='You are not authorized to update this certificate'), 403 for destination in data['destinations']: if destination.plugin.requires_key: if not cert.private_key: return dict( message='Unable to add destination: {0}. Certificate does not have required private key.'.format( destination.label ) ), 400 cert = service.update(certificate_id, **data) log_service.create(g.current_user, 'update_cert', certificate=cert) return cert
def put(self, certificate_id, data=None): """ .. http:put:: /certificates/1 Update a certificate **Example request**: .. sourcecode:: http PUT /certificates/1 HTTP/1.1 Host: example.com Accept: application/json, text/javascript { "owner": "*****@*****.**", "active": false "notifications": [], "destinations": [], "replacements": [] } **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Vary: Accept Content-Type: text/javascript { "status": null, "cn": "*.test.example.net", "chain": "", "authority": { "active": true, "owner": "*****@*****.**", "id": 1, "description": "verisign test authority", "name": "verisign" }, "owner": "*****@*****.**", "serial": "82311058732025924142789179368889309156", "id": 2288, "issuer": "SymantecCorporation", "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": "******", "active": true, "email": "*****@*****.**", "id": 2 }, "active": true, "domains": [{ "sensitive": false, "id": 1090, "name": "*.test.example.net" }], "replaces": [], "name": "WILDCARD.test.example.net-SymantecCorporation-20160603-20180112", "roles": [{ "id": 464, "description": "This is a google group based role created by Lemur", "name": "*****@*****.**" }], "san": null } :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error :statuscode 403: unauthenticated """ cert = service.get(certificate_id) permission = CertificatePermission(cert.id, [x.name for x in cert.roles]) if permission.can(): return service.update( certificate_id, data['owner'], data['description'], data['active'], data['destinations'], data['notifications'], data['replacements'], data['roles'] ) return dict(message='You are not authorized to update this certificate'), 403
def get(self, certificate_id): """ .. http:get:: /certificates/1/authority One authority for given certificate **Example request**: .. sourcecode:: http GET /certificates/1/authority 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 { "name": "TestAuthority", "roles": [{ "id": 123, "name": "*****@*****.**" }, { "id": 564, "name": "TestAuthority_admin" }, { "id": 565, "name": "TestAuthority_operator" }], "options": null, "active": true, "authorityCertificate": { "body": "-----BEGIN CERTIFICATE-----IyMzU5MTVaMHk...", "status": true, "cn": "AcommonName", "description": "This is the ROOT certificate for the TestAuthority certificate authority.", "chain": "", "notBefore": "2016-06-02T00:00:15+00:00", "notAfter": "2023-06-02T23:59:15+00:00", "owner": "*****@*****.**", "user": { "username": "******", "active": true, "email": "*****@*****.**", "id": 3 }, "active": true, "bits": 2048, "id": 2235, "name": "TestAuthority" }, "owner": "*****@*****.**", "id": 43, "description": "This is the ROOT certificate for the TestAuthority certificate authority." } :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error :statuscode 403: unauthenticated """ cert = certificate_service.get(certificate_id) if not cert: return dict(message="Certificate not found."), 404 return cert.authority
def fetch_acme_cert(id, notify_reissue_cert_id=None): """ Attempt to get the full certificate for the pending certificate listed. Args: id: an id of a PendingCertificate notify_reissue_cert_id: ID of existing Certificate to use for reissue notifications, if supplied """ task_id = None if celery.current_task: task_id = celery.current_task.request.id function = f"{__name__}.{sys._getframe().f_code.co_name}" log_data = { "function": function, "message": "Resolving pending certificate {}".format(id), "task_id": task_id, "id": id, } current_app.logger.debug(log_data) if task_id and is_task_active(log_data["function"], task_id, (id, )): log_data["message"] = "Skipping task: Task is already active" current_app.logger.debug(log_data) return pending_certs = pending_certificate_service.get_pending_certs([id]) new = 0 failed = 0 wrong_issuer = 0 acme_certs = [] # We only care about certs using the acme-issuer plugin for cert in pending_certs: cert_authority = get_authority(cert.authority_id) if cert_authority.plugin_name == "acme-issuer": acme_certs.append(cert) else: wrong_issuer += 1 authority = plugins.get("acme-issuer") resolved_certs = authority.get_ordered_certificates(acme_certs) for cert in resolved_certs: real_cert = cert.get("cert") # It's necessary to reload the pending cert due to detached instance: http://sqlalche.me/e/bhk3 pending_cert = pending_certificate_service.get( cert.get("pending_cert").id) if not pending_cert or pending_cert.resolved: # pending_cert is cleared or it was resolved by another process log_data[ "message"] = "Pending certificate doesn't exist anymore. Was it resolved by another process?" current_app.logger.error(log_data) continue if real_cert: # If a real certificate was returned from issuer, then create it in Lemur and mark # the pending certificate as resolved final_cert = pending_certificate_service.create_certificate( pending_cert, real_cert, pending_cert.user) pending_certificate_service.update(cert.get("pending_cert").id, resolved_cert_id=final_cert.id) pending_certificate_service.update(cert.get("pending_cert").id, resolved=True) if notify_reissue_cert_id is not None: notify_reissue_cert = certificate_service.get( notify_reissue_cert_id) send_reissue_no_endpoints_notification(notify_reissue_cert, final_cert) # add metrics to metrics extension new += 1 else: failed += 1 error_log = copy.deepcopy(log_data) error_log["message"] = "Pending certificate creation failure" error_log["pending_cert_id"] = pending_cert.id error_log["last_error"] = cert.get("last_error") error_log["cn"] = pending_cert.cn if pending_cert.number_attempts > ACME_ADDITIONAL_ATTEMPTS: error_log["message"] = "Deleting pending certificate" send_pending_failure_notification( pending_cert, notify_owner=pending_cert.notify) if notify_reissue_cert_id is not None: send_reissue_failed_notification(pending_cert) # Mark the pending cert as resolved pending_certificate_service.update(cert.get("pending_cert").id, resolved=True) else: pending_certificate_service.increment_attempt(pending_cert) pending_certificate_service.update(cert.get("pending_cert").id, status=str( cert.get("last_error"))) # Add failed pending cert task back to queue fetch_acme_cert.delay(id, notify_reissue_cert_id) current_app.logger.error(error_log) log_data["message"] = "Complete" log_data["new"] = new log_data["failed"] = failed log_data["wrong_issuer"] = wrong_issuer current_app.logger.debug(log_data) metrics.send(f"{function}.resolved", "gauge", new) metrics.send(f"{function}.failed", "gauge", failed) metrics.send(f"{function}.wrong_issuer", "gauge", wrong_issuer) print( "[+] Certificates: New: {new} Failed: {failed} Not using ACME: {wrong_issuer}" .format(new=new, failed=failed, wrong_issuer=wrong_issuer)) return log_data
def get(self, certificate_id): """ .. http:get:: /certificates/1/replacements One certificate **Example request**: .. sourcecode:: http GET /certificates/1/replacements 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": "*****@*****.**", "id": 1, "description": "verisign test authority", "name": "verisign" }, "owner": "*****@*****.**", "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": "******", "active": true, "email": "*****@*****.**", "id": 2 }, "active": true, "domains": [{ "sensitive": false, "id": 1090, "name": "*.test.example.net" }], "replaces": [], "replaced": [], "rotation": true, "rotationPolicy": {"name": "default"}, "name": "WILDCARD.test.example.net-SymantecCorporation-20160603-20180112", "roles": [{ "id": 464, "description": "This is a google group based role created by Lemur", "name": "*****@*****.**" }], "san": null }], "total": 1 } :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error :statuscode 403: unauthenticated """ return service.get(certificate_id).replaces
def post(self, certificate_id): """ .. http:post:: /certificates/1/export Export a certificate **Example request**: .. sourcecode:: http PUT /certificates/1/export HTTP/1.1 Host: example.com Accept: application/json, text/javascript { "export": { "plugin": { "pluginOptions": [{ "available": ["Java Key Store (JKS)"], "required": true, "type": "select", "name": "type", "helpMessage": "Choose the format you wish to export", "value": "Java Key Store (JKS)" }, { "required": false, "type": "str", "name": "passphrase", "validation": "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[$@$!%*#?&])[A-Za-z\\d$@$!%*#?&]{8,}$", "helpMessage": "If no passphrase is given one will be generated for you, we highly recommend this. Minimum length is 8." }, { "required": false, "type": "str", "name": "alias", "helpMessage": "Enter the alias you wish to use for the keystore." }], "version": "unknown", "description": "Attempts to generate a JKS keystore or truststore", "title": "Java", "author": "Kevin Glisson", "type": "export", "slug": "java-export" } } } **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Vary: Accept Content-Type: text/javascript { "data": "base64encodedstring", "passphrase": "UAWOHW#&@_%!tnwmxh832025", "extension": "jks" } :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error :statuscode 403: unauthenticated """ self.reqparse.add_argument('export', type=dict, required=True, location='json') args = self.reqparse.parse_args() cert = service.get(certificate_id) role = role_service.get_by_name(cert.owner) permission = UpdateCertificatePermission(certificate_id, getattr(role, 'name', None)) if permission.can(): extension, passphrase, data = service.export(cert, args['export']['plugin']) # we take a hit in message size when b64 encoding return dict(extension=extension, passphrase=passphrase, data=base64.b64encode(data)) return dict(message='You are not authorized to export this certificate'), 403
def put(self, certificate_id, data=None): """ .. http:put:: /certificates/1 Update a certificate **Example request**: .. sourcecode:: http PUT /certificates/1 HTTP/1.1 Host: example.com Accept: application/json, text/javascript { "owner": "*****@*****.**", "active": false "notifications": [], "destinations": [], "replacements": [] } **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Vary: Accept Content-Type: text/javascript { "status": null, "cn": "*.test.example.net", "chain": "", "authority": { "active": true, "owner": "*****@*****.**", "id": 1, "description": "verisign test authority", "name": "verisign" }, "owner": "*****@*****.**", "serial": "82311058732025924142789179368889309156", "id": 2288, "issuer": "SymantecCorporation", "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": "******", "active": true, "email": "*****@*****.**", "id": 2 }, "active": true, "domains": [{ "sensitive": false, "id": 1090, "name": "*.test.example.net" }], "replaces": [], "name": "WILDCARD.test.example.net-SymantecCorporation-20160603-20180112", "roles": [{ "id": 464, "description": "This is a google group based role created by Lemur", "name": "*****@*****.**" }], "san": null } :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error :statuscode 403: unauthenticated """ cert = service.get(certificate_id) permission = CertificatePermission(cert.id, [x.name for x in cert.roles]) if permission.can(): return service.update(certificate_id, data['owner'], data['description'], data['active'], data['destinations'], data['notifications'], data['replacements'], data['roles']) return dict( message='You are not authorized to update this certificate'), 403
def put(self, certificate_id): """ .. http:put:: /certificates/1 Update a certificate **Example request**: .. sourcecode:: http PUT /certificates/1 HTTP/1.1 Host: example.com Accept: application/json, text/javascript { "owner": "*****@*****.**", "active": false "notifications": [], "destinations": [], "replacements": [] } **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Vary: Accept Content-Type: text/javascript { "id": 1, "name": "cert1", "description": "this is cert1", "bits": 2048, "deleted": false, "issuer": "ExampeInc.", "serial": "123450", "chain": "-----Begin ...", "body": "-----Begin ...", "san": true, "owner": "*****@*****.**", "active": false, "notBefore": "2015-06-05T17:09:39", "notAfter": "2015-06-10T17:09:39", "cn": "example.com", "status": "unknown", } :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error :statuscode 403: unauthenticated """ self.reqparse.add_argument('active', type=bool, location='json') self.reqparse.add_argument('owner', type=str, location='json') self.reqparse.add_argument('description', type=str, location='json') self.reqparse.add_argument('destinations', type=list, default=[], location='json') self.reqparse.add_argument('notifications', type=notification_list, default=[], location='json') self.reqparse.add_argument('replacements', type=list, default=[], location='json') args = self.reqparse.parse_args() cert = service.get(certificate_id) role = role_service.get_by_name(cert.owner) permission = UpdateCertificatePermission(certificate_id, getattr(role, 'name', None)) if permission.can(): return service.update( certificate_id, args['owner'], args['description'], args['active'], args['destinations'], args['notifications'], args['replacements'] ) return dict(message='You are not authorized to update this certificate'), 403
def post(self, certificate_id): """ .. http:post:: /certificates/1/export Export a certificate **Example request**: .. sourcecode:: http PUT /certificates/1/export HTTP/1.1 Host: example.com Accept: application/json, text/javascript { "export": { "plugin": { "pluginOptions": [{ "available": ["Java Key Store (JKS)"], "required": true, "type": "select", "name": "type", "helpMessage": "Choose the format you wish to export", "value": "Java Key Store (JKS)" }, { "required": false, "type": "str", "name": "passphrase", "validation": "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[$@$!%*#?&])[A-Za-z\\d$@$!%*#?&]{8,}$", "helpMessage": "If no passphrase is given one will be generated for you, we highly recommend this. Minimum length is 8." }, { "required": false, "type": "str", "name": "alias", "helpMessage": "Enter the alias you wish to use for the keystore." }], "version": "unknown", "description": "Attempts to generate a JKS keystore or truststore", "title": "Java", "author": "Kevin Glisson", "type": "export", "slug": "java-export" } } } **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Vary: Accept Content-Type: text/javascript { "data": "base64encodedstring", "passphrase": "UAWOHW#&@_%!tnwmxh832025", "extension": "jks" } :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error :statuscode 403: unauthenticated """ self.reqparse.add_argument('export', type=dict, required=True, location='json') args = self.reqparse.parse_args() cert = service.get(certificate_id) role = role_service.get_by_name(cert.owner) permission = UpdateCertificatePermission(certificate_id, getattr(role, 'name', None)) plugin = plugins.get(args['export']['plugin']['slug']) if plugin.requires_key: if permission.can(): extension, passphrase, data = plugin.export( cert.body, cert.chain, cert.private_key, args['export']['plugin']['pluginOptions']) else: return dict( message='You are not authorized to export this certificate' ), 403 else: extension, passphrase, data = plugin.export( cert.body, cert.chain, cert.private_key, args['export']['plugin']['pluginOptions']) # we take a hit in message size when b64 encoding return dict(extension=extension, passphrase=passphrase, data=base64.b64encode(data))