From 8977c5ddbfba801a6cbd5ad73d5ba5634e1a7449 Mon Sep 17 00:00:00 2001 From: kevgliss Date: Sat, 29 Aug 2015 12:02:50 -0700 Subject: [PATCH 01/10] Ensuring notifications follow owner --- lemur/certificates/service.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lemur/certificates/service.py b/lemur/certificates/service.py index 8a1e20fa..6fb2f634 100644 --- a/lemur/certificates/service.py +++ b/lemur/certificates/service.py @@ -85,11 +85,22 @@ def update(cert_id, owner, description, active, destinations, notifications): :param active: :return: """ + from lemur.notifications import service as notification_service cert = get(cert_id) - cert.owner = owner cert.active = active cert.description = description + # we might have to create new notifications if the owner changes + if owner != cert.owner: + for n in cert.notifications: + notification_name = "DEFAULT_{0}".format(cert.owner.split('@')[0].upper()) + if n.name == notification_name: + cert.notifications.remove(n) + + cert.owner = owner + notification_name = "DEFAULT_{0}".format(cert.owner.split('@')[0].upper()) + notifications = notification_service.create_default_expiration_notifications(notification_name, owner) + database.update_list(cert, 'notifications', Notification, notifications) database.update_list(cert, 'destinations', Destination, destinations) From 8d09d865b14bf0581c577bc524abdcbfb80a1e4d Mon Sep 17 00:00:00 2001 From: kevgliss Date: Sat, 29 Aug 2015 11:48:39 -0700 Subject: [PATCH 02/10] Closes #57 --- lemur/certificates/service.py | 4 ++++ lemur/certificates/views.py | 4 +++- .../app/angular/certificates/certificate/upload.tpl.html | 9 +++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lemur/certificates/service.py b/lemur/certificates/service.py index 6fb2f634..1416d372 100644 --- a/lemur/certificates/service.py +++ b/lemur/certificates/service.py @@ -179,6 +179,10 @@ def upload(**kwargs): kwargs.get('intermediate_cert'), ) + # we override the generated name if one is provided + if kwargs.get('name'): + cert.name = kwargs['name'] + cert.description = kwargs.get('description') cert.owner = kwargs['owner'] diff --git a/lemur/certificates/views.py b/lemur/certificates/views.py index b6cb597d..4643dedc 100644 --- a/lemur/certificates/views.py +++ b/lemur/certificates/views.py @@ -332,7 +332,8 @@ class CertificatesUpload(AuthenticatedResource): "intermediateCert": "---Begin Public...", "privateKey": "---Begin Private..." "destinations": [], - "notifications": [] + "notifications": [], + "name": "cert1" } **Example response**: @@ -373,6 +374,7 @@ class CertificatesUpload(AuthenticatedResource): """ self.reqparse.add_argument('description', type=str, location='json') self.reqparse.add_argument('owner', type=str, required=True, location='json') + self.reqparse.add_argument('name', type=str, location='json') self.reqparse.add_argument('publicCert', type=pem_str, required=True, dest='public_cert', location='json') self.reqparse.add_argument('destinations', type=list, default=[], dest='destinations', location='json') self.reqparse.add_argument('notifications', type=list, default=[], dest='notifications', location='json') diff --git a/lemur/static/app/angular/certificates/certificate/upload.tpl.html b/lemur/static/app/angular/certificates/certificate/upload.tpl.html index 6ba63232..928e923e 100644 --- a/lemur/static/app/angular/certificates/certificate/upload.tpl.html +++ b/lemur/static/app/angular/certificates/certificate/upload.tpl.html @@ -18,6 +18,15 @@ email.

+
+ +
+ +
+
- -

You must give a short description about this authority will be used for, it should contain only alphanumeric characters

+ +

You must give a short description about this authority will be used for

- -

You must enter a common name

+ +

You must enter a common name and it must be less than 64 characters in length

- -

You must give a short description about this authority will be used for, this description should only include alphanumeric characters

+ +

You must give a short description about this authority will be used for.

- -

You must enter a common name

+ +

You must enter a common name and it must be less than 64 characters

diff --git a/lemur/static/app/angular/certificates/certificate/upload.tpl.html b/lemur/static/app/angular/certificates/certificate/upload.tpl.html index 928e923e..55dc850f 100644 --- a/lemur/static/app/angular/certificates/certificate/upload.tpl.html +++ b/lemur/static/app/angular/certificates/certificate/upload.tpl.html @@ -33,8 +33,8 @@ Description
- -

You must give a short description about this authority will be used for, this description should only include alphanumeric characters

+ +

You must give a short description about this authority will be used for.

Date: Wed, 2 Sep 2015 09:19:06 -0700 Subject: [PATCH 07/10] Adding command to fetch and publish verisign units --- lemur/manage.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/lemur/manage.py b/lemur/manage.py index 4b7ff6fb..b21e60a7 100755 --- a/lemur/manage.py +++ b/lemur/manage.py @@ -4,6 +4,8 @@ import os import sys import base64 import time +import requests +import json from gunicorn.config import make_settings from cryptography.fernet import Fernet @@ -680,6 +682,38 @@ class ProvisionELB(Command): done = True +@manager.command +def publish_verisign_units(): + """ + Simple function that queries verisign for API units and posts the mertics to + Atlas API for other teams to consume. + :return: + """ + from lemur.plugins import plugins + v = plugins.get('verisign-issuer') + units = v.get_available_units() + + metrics = {} + for item in units: + if item['@type'] in metrics.keys(): + metrics[item['@type']] += int(item['@remaining']) + else: + metrics.update({item['@type']: int(item['@remaining'])}) + + for name, value in metrics.items(): + metric = [ + { + "timestamp": 1321351651, + "type": "GAUGE", + "name": "Symantec {0} Unit Count".format(name), + "tags": {}, + "value": value + } + ] + + requests.post('http://localhost:8078/metrics', data=json.dumps(metric)) + + def main(): manager.add_command("start", LemurServer()) manager.add_command("runserver", Server(host='127.0.0.1')) From fe7b075f7bf7a734f6e7bef1ae025864aa9b8880 Mon Sep 17 00:00:00 2001 From: kevgliss Date: Wed, 26 Aug 2015 14:16:34 -0700 Subject: [PATCH 08/10] rely on stable version of cryptography instead of dev --- Makefile | 2 -- setup.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Makefile b/Makefile index dd2c2695..03369342 100644 --- a/Makefile +++ b/Makefile @@ -6,8 +6,6 @@ develop: update-submodules setup-git npm install pip install "setuptools>=0.9.8" # order matters here, base package must install first - # this is temporary until the version we need is released - pip install -e 'git+https://git@github.com/pyca/cryptography.git#egg=cryptography-1.0.dev1' pip install -e . pip install "file://`pwd`#egg=lemur[dev]" pip install "file://`pwd`#egg=lemur[tests]" diff --git a/setup.py b/setup.py index ea7ef326..0e15ea76 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ install_requires = [ 'six==1.9.0', 'gunicorn==19.3.0', 'pycrypto==2.6.1', - 'cryptography>=1.0dev', + 'cryptography==1.0', 'pyopenssl==0.15.1', 'pyjwt==1.0.1', 'xmltodict==0.9.2', From 45158c64a2b6b9f6a97edb63c8205c262f059149 Mon Sep 17 00:00:00 2001 From: kevgliss Date: Thu, 27 Aug 2015 11:53:37 -0700 Subject: [PATCH 09/10] cleaning up temporary file creation --- lemur/certificates/verify.py | 79 +++++++++++++++--------------------- 1 file changed, 32 insertions(+), 47 deletions(-) diff --git a/lemur/certificates/verify.py b/lemur/certificates/verify.py index 1e0febec..432c487c 100644 --- a/lemur/certificates/verify.py +++ b/lemur/certificates/verify.py @@ -6,14 +6,28 @@ .. moduleauthor:: Kevin Glisson """ import os -import re -import hashlib import requests import subprocess from OpenSSL import crypto +from cryptography import x509 +from cryptography.hazmat.backends import default_backend from flask import current_app +from contextlib import contextmanager +from tempfile import NamedTemporaryFile + + +@contextmanager +def mktempfile(): + with NamedTemporaryFile(delete=False) as f: + fi = f + + try: + yield fi + finally: + os.unlink(fi.name) + def ocsp_verify(cert_path, issuer_chain_path): """ @@ -53,27 +67,18 @@ def crl_verify(cert_path): :return: True if certificate is valid, False otherwise :raise Exception: If certificate does not have CRL """ - s = "(http(s)?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}/\S*?$)" - regex = re.compile(s, re.MULTILINE) + with open(cert_path, 'rt') as c: + cert = x509.load_pem_x509_certificate(c.read(), default_backend()) - x509 = crypto.load_certificate(crypto.FILETYPE_PEM, open(cert_path, 'rt').read()) - for x in range(x509.get_extension_count()): - ext = x509.get_extension(x) - if ext.get_short_name() == 'crlDistributionPoints': - r = regex.search(ext.get_data()) - points = r.groups() - break - else: - raise Exception("Certificate does not have a CRL distribution point") - - for point in points: - if point: - response = requests.get(point) - crl = crypto.load_crl(crypto.FILETYPE_ASN1, response.content) - revoked = crl.get_revoked() - for r in revoked: - if x509.get_serial_number() == r.get_serial(): - return + distribution_points = cert.extensions.get_extension_for_oid(x509.OID_CRL_DISTRIBUTION_POINTS).value + for p in distribution_points: + point = p.full_name[0].value + response = requests.get(point) + crl = crypto.load_crl(crypto.FILETYPE_ASN1, response.content) # TODO this should be switched to cryptography when support exists + revoked = crl.get_revoked() + for r in revoked: + if cert.serial == r.get_serial(): + return return True @@ -99,22 +104,6 @@ def verify(cert_path, issuer_chain_path): raise Exception("Failed to verify") -def make_tmp_file(string): - """ - Creates a temporary file for a given string - - :param string: - :return: Full file path to created file - """ - m = hashlib.md5() - m.update(string) - hexdigest = m.hexdigest() - path = os.path.join(os.path.dirname(os.path.abspath(__file__)), hexdigest) - with open(path, 'w') as f: - f.write(string) - return path - - def verify_string(cert_string, issuer_string): """ Verify a certificate given only it's string value @@ -123,13 +112,9 @@ def verify_string(cert_string, issuer_string): :param issuer_string: :return: True if valid, False otherwise """ - cert_path = make_tmp_file(cert_string) - issuer_path = make_tmp_file(issuer_string) - status = verify(cert_path, issuer_path) - remove_tmp_file(cert_path) - remove_tmp_file(issuer_path) + with mktempfile() as cert_tmp: + cert_tmp.write(cert_string) + with mktempfile() as issuer_tmp: + issuer_tmp.write(issuer_string) + status = verify(cert_tmp.path, issuer_tmp.path) return status - - -def remove_tmp_file(file_path): - os.remove(file_path) From 3b109ec578e2f979ae7dabdf538ca9047a4a68ce Mon Sep 17 00:00:00 2001 From: kevgliss Date: Wed, 2 Sep 2015 09:12:05 -0700 Subject: [PATCH 10/10] Cleaning up temporary file creation, and revocation checking --- lemur/certificates/verify.py | 14 ++++++++------ lemur/manage.py | 28 ++++++++++++++++------------ 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/lemur/certificates/verify.py b/lemur/certificates/verify.py index 432c487c..79afdf50 100644 --- a/lemur/certificates/verify.py +++ b/lemur/certificates/verify.py @@ -21,12 +21,12 @@ from tempfile import NamedTemporaryFile @contextmanager def mktempfile(): with NamedTemporaryFile(delete=False) as f: - fi = f + name = f.name try: - yield fi + yield name finally: - os.unlink(fi.name) + os.unlink(name) def ocsp_verify(cert_path, issuer_chain_path): @@ -113,8 +113,10 @@ def verify_string(cert_string, issuer_string): :return: True if valid, False otherwise """ with mktempfile() as cert_tmp: - cert_tmp.write(cert_string) + with open(cert_tmp, 'w') as f: + f.write(cert_string) with mktempfile() as issuer_tmp: - issuer_tmp.write(issuer_string) - status = verify(cert_tmp.path, issuer_tmp.path) + with open(issuer_tmp, 'w') as f: + f.write(issuer_string) + status = verify(cert_tmp, issuer_tmp) return status diff --git a/lemur/manage.py b/lemur/manage.py index b21e60a7..42137576 100755 --- a/lemur/manage.py +++ b/lemur/manage.py @@ -148,12 +148,15 @@ def check_revoked(): as `unknown`. """ for cert in cert_service.get_all_certs(): - if cert.chain: - status = verify_string(cert.body, cert.chain) - else: - status = verify_string(cert.body, "") + try: + if cert.chain: + status = verify_string(cert.body, cert.chain) + else: + status = verify_string(cert.body, "") - cert.status = 'valid' if status else "invalid" + cert.status = 'valid' if status else 'invalid' + except Exception as e: + cert.status = 'unknown' database.update(cert) @@ -183,7 +186,7 @@ def generate_settings(): return output -@manager.option('-s', '--sources', dest='labels', default='', required=False) +@manager.option('-s', '--sources', dest='labels') def sync_sources(labels): """ Attempts to run several methods Certificate discovery. This is @@ -209,13 +212,14 @@ def sync_sources(labels): try: sync_lock.acquire(timeout=10) # wait up to 10 seconds - if labels: - sys.stdout.write("[+] Staring to sync sources: {labels}!\n".format(labels=labels)) - labels = labels.split(",") - else: - sys.stdout.write("[+] Starting to sync ALL sources!\n") + sys.stdout.write("[+] Staring to sync sources: {labels}!\n".format(labels=labels)) + labels = labels.split(",") + + if labels[0] == 'all': + sync() + else: + sync(labels=labels) - sync(labels=labels) sys.stdout.write( "[+] Finished syncing sources. Run Time: {time}\n".format( time=(time.time() - start_time)