def fetch_all_pending_acme_certs(): """Instantiate celery workers to resolve all pending Acme certificates""" pending_certs = pending_certificate_service.get_unresolved_pending_certs() function = f"{__name__}.{sys._getframe().f_code.co_name}" log_data = { "function": function, "message": "Starting job.", } current_app.logger.debug(log_data) # 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": if datetime.now(timezone.utc) - cert.last_updated > timedelta( minutes=5): log_data["message"] = "Triggering job for cert {}".format( cert.name) log_data["cert_name"] = cert.name log_data["cert_id"] = cert.id current_app.logger.debug(log_data) fetch_acme_cert.delay(cert.id) red.set(f'{function}.last_success', int(time.time())) metrics.send(f"{function}.success", 'counter', 1)
def fetch_all_pending_acme_certs(): """Instantiate celery workers to resolve all pending Acme certificates""" function = f"{__name__}.{sys._getframe().f_code.co_name}" task_id = None if celery.current_task: task_id = celery.current_task.request.id log_data = { "function": function, "message": "Starting job.", "task_id": task_id, } if task_id and is_task_active(function, task_id, None): log_data["message"] = "Skipping task: Task is already active" current_app.logger.debug(log_data) return current_app.logger.debug(log_data) pending_certs = pending_certificate_service.get_unresolved_pending_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": if datetime.now(timezone.utc) - cert.last_updated > timedelta(minutes=5): log_data["message"] = "Triggering job for cert {}".format(cert.name) log_data["cert_name"] = cert.name log_data["cert_id"] = cert.id current_app.logger.debug(log_data) fetch_acme_cert.delay(cert.id) metrics.send(f"{function}.success", "counter", 1) return log_data
def fetch_all_pending_acme_certs(): """Instantiate celery workers to resolve all pending Acme certificates""" pending_certs = pending_certificate_service.get_unresolved_pending_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': if cert.last_updated == cert.date_created or datetime.now( timezone.utc) - cert.last_updated > timedelta(minutes=3): fetch_acme_cert.delay(cert.id)
def revoke_certificate(self, certificate, comments): """Revoke an EJBCA certificate.""" base_url = current_app.config.get("EJBCA_URL") authority = get_authority(certificate.authority_id) authority_const = authority.name.upper().replace('-', '_') cert_body = certificate.body x509 = load_certificate(FILETYPE_PEM, cert_body) issuer = x509.get_issuer() issuer_dn = get_subject_dn_string(issuer.get_components()) # create certificate revocation request hex_serial = hex(int(certificate.serial))[2:] cert_serial_hex = str(hex_serial) create_url = "{0}/ejbca/ejbca-rest-api/v1/certificate/{1}/{2}/revoke".format( base_url, issuer_dn, cert_serial_hex) # print(create_url) session = requests.Session() session.mount('https://', HttpsAdapter()) session.cert = current_app.config.get( f'EJBCA_PEM_PATH_{authority_const}') session.verify = current_app.config.get("EJBCA_TRUSTSTORE") session.hooks = dict(response=log_status_code) metrics.send("ejbca_revoke_certificate", "counter", 1) reason = comments.get('crl_reason') or comments.get( 'crlReason', 'unspecified') reason_dict = { 'unspecified': 'UNSPECIFIED', 'keyCompromise': 'KEY_COMPROMISE', 'cACompromise': 'CA_COMPROMISE', 'affiliationChanged': 'AFFILIATION_CHANGED', 'superseded': 'SUPERSEDED', 'cessationOfOperation': 'CESSATION_OF_OPERATION', 'certificateHold': 'CERTIFICATE_HOLD', 'removeFromCRL': 'REMOVE_FROM_CRL', 'privilegeWithdrawn': 'PRIVILEGES_WITHDRAWN', 'aACompromise': 'AA_COMPROMISE', } reason = reason_dict.get(reason, reason) response = session.put(create_url, params={'reason': reason}) # print(response) extra_logger.info(f'{certificate} was revoked with reason {reason}') return handle_response(response)
def fetch_all_acme(): """ Attempt to get full certificates for each pending certificate listed with the acme-issuer. This is more efficient for acme-issued certificates because it will configure all of the DNS challenges prior to resolving any certificates. """ pending_certs = pending_certificate_service.get_pending_certs('all') user = user_service.get_by_username('lemur') 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 real_cert: # If a real certificate was returned from issuer, then create it in Lemur and delete # the pending certificate pending_certificate_service.create_certificate( pending_cert, real_cert, user) pending_certificate_service.delete_by_id(pending_cert.id) # add metrics to metrics extension new += 1 else: pending_certificate_service.increment_attempt(pending_cert) failed += 1 print( "[+] Certificates: New: {new} Failed: {failed} Not using ACME: {wrong_issuer}" .format(new=new, failed=failed, wrong_issuer=wrong_issuer))
def fetch_all_pending_acme_certs(): """Instantiate celery workers to resolve all pending Acme certificates""" pending_certs = pending_certificate_service.get_unresolved_pending_certs() log_data = { "function": "{}.{}".format(__name__, sys._getframe().f_code.co_name), "message": "Starting job." } current_app.logger.debug(log_data) # 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': if datetime.now(timezone.utc) - cert.last_updated > timedelta(minutes=5): log_data["message"] = "Triggering job for cert {}".format(cert.name) log_data["cert_name"] = cert.name log_data["cert_id"] = cert.id current_app.logger.debug(log_data) fetch_acme_cert.delay(cert.id)
def fetch_all_pending_acme_certs(): """Instantiate celery workers to resolve all pending Acme certificates""" pending_certs = pending_certificate_service.get_unresolved_pending_certs() log_data = { "function": "{}.{}".format(__name__, sys._getframe().f_code.co_name), "message": "Starting job." } current_app.logger.debug(log_data) # 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': if datetime.now(timezone.utc) - cert.last_updated > timedelta(minutes=5): log_data["message"] = "Triggering job for cert {}".format(cert.name) log_data["cert_name"] = cert.name log_data["cert_id"] = cert.id current_app.logger.debug(log_data) fetch_acme_cert.delay(cert.id)
def fetch_all_pending_certs(): """Instantiate celery workers to resolve all certificates""" function = f"{__name__}.{sys._getframe().f_code.co_name}" task_id = None if celery.current_task: task_id = celery.current_task.request.id log_data = { "function": function, "message": "Starting fetching all pending certs.", "task_id": task_id, } if task_id and is_task_active(function, task_id, None): log_data["message"] = "Skipping task: Task is already active" current_app.logger.debug(log_data) return current_app.logger.debug(log_data) pending_certs = pending_certificate_service.get_unresolved_pending_certs() for cert in pending_certs: cert_authority = get_authority(cert.authority_id) # handle only certs from non-acme issuers, as acme certs are specially # taken care of in the `fetach_all_pending_acme_certs` function if cert_authority.plugin_name != "acme-issuer": log_data["message"] = "Triggering job for cert {}".format( cert.name) log_data["cert_name"] = cert.name log_data["cert_id"] = cert.id current_app.logger.debug(log_data) fetch_cert.delay(cert.id) metrics.send(f"{function}.success", "counter", 1) return log_data
def fetch_acme_cert(id): """ Attempt to get the full certificate for the pending certificate listed. Args: id: an id of a PendingCertificate """ 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) # 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) # 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) 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 fetch_acme_cert(id): """ Attempt to get the full certificate for the pending certificate listed. Args: id: an id of a PendingCertificate """ log_data = { "function": "{}.{}".format(__name__, sys._getframe().f_code.co_name), "message": "Resolving pending certificate {}".format(id) } current_app.logger.debug(log_data) 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: 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=True ) pending_certificate_service.update( cert.get("pending_cert").id, resolved_cert_id=final_cert.id ) # 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 > 4: error_log["message"] = "Deleting pending certificate" send_pending_failure_notification(pending_cert, notify_owner=pending_cert.notify) # 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) 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) print( "[+] Certificates: New: {new} Failed: {failed} Not using ACME: {wrong_issuer}".format( new=new, failed=failed, wrong_issuer=wrong_issuer ) )
def fetch_all_acme(): """ Attempt to get full certificates for each pending certificate listed with the acme-issuer. This is more efficient for acme-issued certificates because it will configure all of the DNS challenges prior to resolving any certificates. """ log_data = { "function": "{}.{}".format(__name__, sys._getframe().f_code.co_name) } pending_certs = pending_certificate_service.get_unresolved_pending_certs() 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 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( pending_cert.id, resolved=True ) pending_certificate_service.update( pending_cert.id, resolved_cert_id=final_cert.id ) # 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 > 4: error_log["message"] = "Marking pending certificate as resolved" send_pending_failure_notification(pending_cert, notify_owner=pending_cert.notify) # Mark "resolved" as True pending_certificate_service.update( 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")) ) 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) print( "[+] Certificates: New: {new} Failed: {failed} Not using ACME: {wrong_issuer}".format( new=new, failed=failed, wrong_issuer=wrong_issuer ) )
def fetch_cert(id): """ Attempt to get the full certificate for the pending certificate listed. Args: id: an id of a PendingCertificate """ 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 for cert in pending_certs: cert_authority = get_authority(cert.authority_id) if cert_authority.plugin_name == "acme-issuer": current_app.logger.warning( "Skipping acme cert (use `fetch_acme_cert()` instead).") continue plugin = plugins.get(cert_authority.plugin_name) real_cert = plugin.get_ordered_certificate(cert) if real_cert: # If a real certificate was returned from issuer, then create it in # Lemur and mark the pending certificate as resolved # Ideally, this should be a db transaction that would check resolved status # before creating a new one final_cert = pending_certificate_service.create_certificate( cert, real_cert, cert.user) pending_certificate_service.update(cert.id, resolved_cert_id=final_cert.id) pending_certificate_service.update(cert.id, resolved=True) # add metrics to metrics extension new += 1 else: # Double check the pending certificate still exists in the database # before updating the failed attempt counter if not pending_certificate_service.get(cert.id): current_app.logger.info( f"Not incrementing failed attempt counter because {cert.name} was cancelled before." ) else: pending_certificate_service.increment_attempt(cert) failed += 1 log_data["message"] = "Complete" log_data["new"] = new log_data["failed"] = failed current_app.logger.debug(log_data) metrics.send(f"{function}.resolved", "gauge", new) metrics.send(f"{function}.failed", "gauge", failed) print(f"[+] Certificates: New: {new} Failed: {failed}") return log_data
def get_ordered_certificates(self, pending_certs): pending = [] certs = [] for pending_cert in pending_certs: rejected = False expired = False try: authority = get_authority(pending_cert.authority_id) authority_name = authority.name.upper() # print("AUTHORITYNAME**** " + authority_name) session = requests.Session() session.mount('https://', HttpsAdapter()) session.cert = current_app.config.get( "EJBCA_PEM_PATH_{0}".format(authority_name)) session.verify = current_app.config.get("EJBCA_TRUSTSTORE") session.hooks = dict(response=log_status_code) transport = Transport(session=session) url = current_app.config.get( "EJBCA_URL") + "/ejbca/ejbcaws/ejbcaws?wsdl" client = Client(url, transport=transport) csr_x509 = load_certificate_request(FILETYPE_PEM, pending_cert.csr) # get SubjectDN string from CSR subject_dn = get_subject_dn_string( csr_x509.get_subject().get_components()) # print("*****DN:" + subject_dn) end_entity_username = pending_cert.name if end_entity_username is None: end_entity_username = "******" # Strip -[digit]+ from pending cert name to obtain end entity username end_entity_username = re.sub('-\d+$', '', end_entity_username) response = client.service.getRemainingNumberOfApprovals( pending_cert.external_id) num_remaining = response current_app.logger.debug( f"Remaining check: {str(num_remaining)}") if num_remaining == -1: # print("Rejected!") rejected = True certs.append({ "cert": False, "pending_cert": pending_cert, "last_error": "Request was rejected", "rejected": rejected, "expired": expired }) elif num_remaining > 0: current_app.logger.debug( f"Remaining Approvals: {num_remaining}") # print("Approvals Remaining") remain_message = "Remaining approvals: " + str( num_remaining) certs.append({ "cert": False, "pending_cert": pending_cert, "last_error": remain_message, "rejected": rejected, "expired": expired }) elif num_remaining == 0: #ready to issue cert csr_b64 = dump_certificate_request(FILETYPE_PEM, csr_x509) csr_b64 = csr_b64.decode() request_data = { 'arg0': end_entity_username, 'arg1': 'foo123', 'arg2': csr_b64, 'arg3': None, 'arg4': 'CERTIFICATE' } try: response = client.service.pkcs10Request(**request_data) # print(response) # print(response.data) cert_data_str = response.data.decode("utf-8") # print("CERT DATA") # print(cert_data_str) #cert_data = base64.b64decode(cert_data_str).decode("utf-8") cert_data_str.replace('\\n', '\n') # print("decoded:") # print(cert_data_str) external_id = None #reconstruct certificate from json array pem = "-----BEGIN CERTIFICATE-----\n" pem += cert_data_str pem += "\n-----END CERTIFICATE-----" #authority = get_authority(pending_cert.authority_id) #authority_name = authority.name.upper() chain = current_app.config.get( "EJBCA_INTERMEDIATE_{0}".format(authority_name), current_app.config.get("EJBCA_INTERMEDIATE")) cert = { "body": pem, "chain": "\n".join(str(chain).splitlines()), "external_id": str(pending_cert.external_id), "authority_id": str(pending_cert.authority_id), } certs.append({ "cert": cert, "pending_cert": pending_cert }) except zeep.exceptions.Fault as fault: sentry.captureException() metrics.send( "get_ordered_certificates_pending_creation_error", "counter", 1) current_app.logger.error( f"Unable to resolve EJBCA pending cert: {pending_cert}", exc_info=True) certs.append({ "cert": False, "pending_cert": pending_cert, "last_error": fault.message, "rejected": rejected, "expired": expired }) except zeep.exceptions.Fault as fault: strdet = fault.detail[0] m = re.search('^\{.*\}(.*)$', strdet.tag) exceptname = m.group(1) if (exceptname == "ApprovalRequestExpiredException"): expired = True certs.append({ "cert": False, "pending_cert": pending_cert, "last_error": fault.message, "rejected": rejected, "expired": expired }) return certs
def fetch_acme_cert(id): """ Attempt to get the full certificate for the pending certificate listed. Args: id: an id of a PendingCertificate """ log_data = { "function": "{}.{}".format(__name__, sys._getframe().f_code.co_name) } pending_certs = pending_certificate_service.get_pending_certs([id]) user = user_service.get_by_username('lemur') 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 real_cert: # If a real certificate was returned from issuer, then create it in Lemur and delete # the pending certificate pending_certificate_service.create_certificate( pending_cert, real_cert, user) pending_certificate_service.delete_by_id(pending_cert.id) # 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 > 4: error_log["message"] = "Deleting pending certificate" send_pending_failure_notification( pending_cert, notify_owner=pending_cert.notify) pending_certificate_service.delete( pending_certificate_service.cancel(pending_cert)) 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) 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) print( "[+] Certificates: New: {new} Failed: {failed} Not using ACME: {wrong_issuer}" .format(new=new, failed=failed, wrong_issuer=wrong_issuer))