From 39ad270dad9b7fdfb06f98aa16b09984bc413661 Mon Sep 17 00:00:00 2001 From: Kevin Glisson Date: Wed, 24 Jun 2015 16:48:40 -0700 Subject: [PATCH 01/31] Adding in some initial tests --- lemur/database.py | 7 +- lemur/exceptions.py | 8 ++ lemur/extensions.py | 36 +++++++- lemur/factory.py | 6 ++ lemur/tests/__init__.py | 12 --- lemur/tests/certificates/test_certificates.py | 87 ------------------ lemur/tests/conftest.py | 91 +++++++++++++++++++ lemur/tests/constants.py | 51 ----------- lemur/tests/elbs/__init__.py | 0 lemur/tests/elbs/test_elbs.py | 5 - lemur/tests/listeners/__init__.py | 0 lemur/tests/services/__init__.py | 0 lemur/tests/services/test_elb.py | 51 ----------- lemur/tests/services/test_iam.py | 37 -------- lemur/tests/services/test_issuer_manager.py | 23 ----- lemur/tests/services/test_s3.py | 27 ------ lemur/tests/test_accounts.py | 53 +++++++++++ lemur/tests/test_certificates.py | 85 +++++++++++++++++ .../__init__.py => test_crypto.py} | 0 lemur/tests/{certificates => }/test_csr.py | 0 lemur/tests/test_elb.py | 51 +++++++++++ lemur/tests/test_iam.py | 35 +++++++ lemur/tests/test_issuer_manager.py | 16 ++++ .../test_pack/challenge.txt | 0 .../test_pack/csr_config.txt | 0 .../{certificates => }/test_pack/private.key | 0 .../{certificates => }/test_pack/request.csr | 0 .../{certificates => }/test_pack/server.crt | 0 setup.py | 3 +- 29 files changed, 386 insertions(+), 298 deletions(-) delete mode 100644 lemur/tests/certificates/test_certificates.py create mode 100644 lemur/tests/conftest.py delete mode 100644 lemur/tests/constants.py delete mode 100644 lemur/tests/elbs/__init__.py delete mode 100644 lemur/tests/elbs/test_elbs.py delete mode 100644 lemur/tests/listeners/__init__.py delete mode 100644 lemur/tests/services/__init__.py delete mode 100644 lemur/tests/services/test_elb.py delete mode 100644 lemur/tests/services/test_iam.py delete mode 100644 lemur/tests/services/test_issuer_manager.py delete mode 100644 lemur/tests/services/test_s3.py create mode 100644 lemur/tests/test_accounts.py create mode 100644 lemur/tests/test_certificates.py rename lemur/tests/{certificates/__init__.py => test_crypto.py} (100%) rename lemur/tests/{certificates => }/test_csr.py (100%) create mode 100644 lemur/tests/test_elb.py create mode 100644 lemur/tests/test_iam.py create mode 100644 lemur/tests/test_issuer_manager.py rename lemur/tests/{certificates => }/test_pack/challenge.txt (100%) rename lemur/tests/{certificates => }/test_pack/csr_config.txt (100%) rename lemur/tests/{certificates => }/test_pack/private.key (100%) rename lemur/tests/{certificates => }/test_pack/request.csr (100%) rename lemur/tests/{certificates => }/test_pack/server.crt (100%) diff --git a/lemur/database.py b/lemur/database.py index d2a7c742..fe591ced 100644 --- a/lemur/database.py +++ b/lemur/database.py @@ -15,7 +15,7 @@ from sqlalchemy import exc from sqlalchemy.sql import and_, or_ from lemur.extensions import db -from lemur.exceptions import AttrNotFound, IntegrityError +from lemur.exceptions import AttrNotFound, IntegrityError, DuplicateError def filter_none(kwargs): @@ -153,9 +153,10 @@ def create(model): try: db.session.add(model) commit() - db.session.refresh(model) except exc.IntegrityError as e: - raise IntegrityError(e.orig.diag.message_detail) + raise DuplicateError(e.orig.diag.message_detail) + + db.session.refresh(model) return model diff --git a/lemur/exceptions.py b/lemur/exceptions.py index f36bda21..42543a59 100644 --- a/lemur/exceptions.py +++ b/lemur/exceptions.py @@ -11,6 +11,14 @@ class LemurException(Exception): current_app.logger.error(self) +class DuplicateError(LemurException): + def __init__(self, key): + self.key = key + + def __str__(self): + return repr("Duplicate found! Could not create: {0}".format(self.key)) + + class AuthenticationFailedException(LemurException): def __init__(self, remote_ip, user_agent): self.remote_ip = remote_ip diff --git a/lemur/extensions.py b/lemur/extensions.py index 07101c4d..101432e8 100644 --- a/lemur/extensions.py +++ b/lemur/extensions.py @@ -4,7 +4,41 @@ :license: Apache, see LICENSE for more details. """ -from flask.ext.sqlalchemy import SQLAlchemy +from flask.ext.sqlalchemy import SQLAlchemy, SignallingSession, SessionBase + + +class _SignallingSession(SignallingSession): + """A subclass of `SignallingSession` that allows for `binds` to be specified + in the `options` keyword arguments. + + """ + def __init__(self, db, autocommit=False, autoflush=True, **options): + self.app = db.get_app() + self._model_changes = {} + self.emit_modification_signals = \ + self.app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] + + bind = options.pop('bind', None) + if bind is None: + bind = db.engine + + binds = options.pop('binds', None) + if binds is None: + binds = db.get_binds(self.app) + + SessionBase.__init__(self, + autocommit=autocommit, + autoflush=autoflush, + bind=bind, + binds=binds, + **options) + + +class _SQLAlchemy(SQLAlchemy): + """A subclass of `SQLAlchemy` that uses `_SignallingSession`.""" + def create_session(self, options): + return _SignallingSession(self, **options) + db = SQLAlchemy() from flask.ext.migrate import Migrate diff --git a/lemur/factory.py b/lemur/factory.py index e0470ae2..a4863976 100644 --- a/lemur/factory.py +++ b/lemur/factory.py @@ -51,6 +51,12 @@ def create_app(app_name=None, blueprints=None, config=None): configure_blueprints(app, blueprints) configure_extensions(app) configure_logging(app) + + @app.teardown_appcontext + def teardown(exception=None): + if db.session: + db.session.remove() + return app diff --git a/lemur/tests/__init__.py b/lemur/tests/__init__.py index 0b6502e9..c293a1cc 100644 --- a/lemur/tests/__init__.py +++ b/lemur/tests/__init__.py @@ -1,16 +1,4 @@ import unittest -from nose.tools import eq_ - -from lemur import app - -test_app = app.test_client() - -HEADERS = {'Content-Type': 'application/json'} - - -def check_content_type(headers): - eq_(headers['Content-Type'], 'application/json') - class LemurTestCase(unittest.TestCase): pass diff --git a/lemur/tests/certificates/test_certificates.py b/lemur/tests/certificates/test_certificates.py deleted file mode 100644 index 1480818c..00000000 --- a/lemur/tests/certificates/test_certificates.py +++ /dev/null @@ -1,87 +0,0 @@ -import os -import shutil -import boto - -from lemur import app -from lemur.tests import LemurTestCase -from lemur.tests.constants import TEST_CERT, TEST_KEY - -from moto import mock_iam, mock_sts, mock_s3 - - -class CertificateTestCase(LemurTestCase): - def test_create_challenge(self): - from lemur.certificates.service import create_challenge - self.assertTrue(len(create_challenge()) >= 24) - - def test_hash_domains(self): - from lemur.certificates.service import hash_domains - h = hash_domains(['netflix.com', 'www.netflix.com', 'movies.netflix.com']) - self.assertEqual('c9c83253b46c7c1245c100ed3f7045eb', h) - - def test_create_csr(self): - from lemur.certificates.service import create_csr - from lemur.tests.certificates.test_csr import TEST_CSR - path = create_csr(['netflix.com'], TEST_CSR) - files = len(os.listdir(path)) - self.assertEqual(files, 4) - shutil.rmtree(path) - - def test_create_san_csr(self): - from lemur.certificates.service import create_csr - from lemur.tests.certificates.test_csr import TEST_CSR - path = create_csr(['netflix.com', 'www.netflix.com'], TEST_CSR) - files = len(os.listdir(path)) - self.assertEqual(files, 4) - shutil.rmtree(path) - - def test_create_path(self): - from lemur.certificates.service import create_path - path = create_path("blah") - self.assertIn('blah', path) - shutil.rmtree(path) - - @mock_s3 - @mock_sts - @mock_iam - def test_save_cert(self): - from lemur.certificates.service import save_cert - from lemur.common.services.aws.iam import get_all_server_certs - conn = boto.connect_s3() - bucket = conn.create_bucket(app.config.get('S3_BUCKET')) - cert = save_cert(TEST_CERT, TEST_KEY, None, "blah", "blah", [1]) - count = 0 - for key in bucket.list(): - count += 1 - - self.assertEqual(count, 4) - certs = get_all_server_certs('1111') - self.assertEqual(len(certs), 1) - -# @mock_s3 -# @mock_sts -# @mock_iam -# def test_upload_cert(self): -# from lemur.certificates.service import upload -# from lemur.common.services.aws.iam import get_all_server_certs -# conn = boto.connect_s3() -# bucket = conn.create_bucket(app.config.get('S3_BUCKET')) -# -# cert_up = {"public_cert": TEST_CERT, "private_key": TEST_KEY, "owner": "test@example.com", "accounts_ids": ['1111']} -# -# cert_name = upload(**cert_up) -# valid_name = 'AHB-dfdsflkj.net-NetflixInc-20140525-20150525' -# self.assertEqual(cert_name, valid_name) -# -# app.logger.debug(cert_name) -# count = 0 -# -# for key in bucket.list(): -# count += 1 -# -# self.assertEqual(count, 2) -# certs = get_all_server_certs('179727101194') -# self.assertEqual(len(certs), 1) -# -# -# diff --git a/lemur/tests/conftest.py b/lemur/tests/conftest.py new file mode 100644 index 00000000..135b5ca7 --- /dev/null +++ b/lemur/tests/conftest.py @@ -0,0 +1,91 @@ +import pytest + +from lemur import create_app +from lemur.database import db as _db + +from flask.ext.sqlalchemy import SignallingSession + +from sqlalchemy import event + + +def pytest_addoption(parser): + parser.addoption("--runslow", action="store_true", help="run slow tests") + + +def pytest_runtest_setup(item): + if 'slow' in item.keywords and not item.config.getoption("--runslow"): + pytest.skip("need --runslow option to run") + + if "incremental" in item.keywords: + previousfailed = getattr(item.parent, "_previousfailed", None) + if previousfailed is not None: + pytest.xfail("previous test failed ({0})".format(previousfailed.name)) + + +def pytest_runtest_makereport(item, call): + if "incremental" in item.keywords: + if call.excinfo is not None: + parent = item.parent + parent._previousfailed = item + + +@pytest.yield_fixture(scope="session") +def app(): + """ + Creates a new Flask application for a test duration. + Uses application factory `create_app`. + """ + app = create_app() + + ctx = app.app_context() + ctx.push() + + yield app + + ctx.pop() + + +@pytest.yield_fixture(scope="session") +def db(): + _db.create_all() + + yield _db + + _db.drop_all() + + +@pytest.yield_fixture(scope="function") +def session(app, db): + """ + Creates a new database session with (with working transaction) + for test duration. + """ + connection = _db.engine.connect() + transaction = connection.begin() + + options = dict(bind=connection) + session = _db.create_scoped_session(options=options) + + # then each time that SAVEPOINT ends, reopen it + @event.listens_for(SignallingSession, "after_transaction_end") + def restart_savepoint(session, transaction): + if transaction.nested and not transaction._parent.nested: + + # ensure that state is expired the way + # session.commit() at the top level normally does + # (optional step) + session.expire_all() + + session.begin_nested() + + # pushing new Flask application context for multiple-thread + # tests to work + + _db.session = session + + yield session + + # the code after the yield statement works as a teardown + transaction.rollback() + connection.close() + session.remove() diff --git a/lemur/tests/constants.py b/lemur/tests/constants.py deleted file mode 100644 index 8632e76a..00000000 --- a/lemur/tests/constants.py +++ /dev/null @@ -1,51 +0,0 @@ -TEST_KEY = """-----BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEAvNudwW+UeQqkpY71MIdEg501AFlPKuOXG2xU8DZhvZS6dKv+ -kDmIWdEqodDgkQiy0jyTgTwxwRqDSw96R6ZgrXefUoJJo66aCsosTBZtVaE85f1L -bj2+3U678c+rekUdkrnGcGCo6b8QtdvBpiDy2clneox8tSvmffAdcR1uCv/790/k -PzQ/djWDX9JcBRyDkcTJwYC0/ek7URvA/+MXmgUL13T+gWKqduaKuIBlFetonDjn -nO11QUBiusIuHV62wzKn8m5Nc+4XoaBR0YWMFn/g6qXDYrwfCsMpka7vSWJFv5Ff -yf+7kY3wU4xIwU2vXlIDcCsdUu6b/pYoQ0YOsQIDAQABAoIBAGbFH6iWnnXrq8MH -8zcQNOFmF+RztRgCt0TOA76f6TowB/LbcXBsTl2J7CgYMUvbLuwm2KHX7r9FPTMI -XiNFT5C16rYMfiQbLGo4sDhLb/3L+wawem6oHQfzA2VH++lSWRByFaEriF+CgIZl -6pALl/uZlLzkXCx+kjPwCSV3vV0wFkDnNs6+wPrz2IhkePsuC8J0QKQLlwsES2It -Gizzhpehdv9lc9MyZC//1QlD9gMDl5ok5Bt1Xm2c12XUEEcLlKQkJxiOrBOfXPmV -PHCdLc7gZO30hc6dyQ1SSnLpywhz/a0ir2GMvkMbS5hculpcZmwEcdZl1HYD8ObP -yOMbPE0CgYEA4LVGJKGtbM8RiBB0MstxNstMYVJ4mXB0lSQ0RazdO3S3ojn+oLpF -b2pvV6m9WnHiCGigWkzhqtGGCo6aqE0MoiR4jTN8GhiZz4ggDDaVgc4Px5reUD+r -tRsTpBHseGQ+ODGgkMI8eJYkdyqkECkYjAOrdy6uorvgxUAZecRIfJMCgYEA1yhM -7NidTNRuA+huS5GcQwQweTM6P1qF7Kfk1JYQMVu4gibLZiLHlWCyHI9lrbI7IaMm -g/4jXXoewv7IvyrrSEFulkPeVWxCe3mjfQ8JANfUj4kuR915LSn4lX2pbUgUS66K -vJSUJtnzLUmb8khLEcOmDbmTFZl8D/bTHFFZlisCgYAeelfWNhuoq3lMRDcOgKuN -bAujE6WJ4kfdxrhUTvr+ynjxxv3zXPB4CS6q7Dnjn5ix3UcKmGzvV1Xf7rGpbDHv -eBTlyfrmKzoJfQQjw++JWKKpRycqKUin2tFSKqAxQB90Tb7ig4XiMTMm+qCgFILg -0sqZ8rn7FpKJDoWmD2ppgwKBgG2Dl9QeVcKbhfv7PNi+HvmFkl6+knFY1D4nHzSN -xWQ6OWoV8QXlwgzokQA0hR6qT6rJbntUyg90b1/1a5zSbbvzgiR+GxcD6bsLqQmo -s354XTtKKgJuWpWAfYUp1ylGvP3gs8FVJyu3WC2+/9+MqJk8KrNlt9YQr7M4gTAy -wBTNAoGAGU7Po4uI3xDKGLLK/ot3D3P8U9ByfeLlrUZtTz1PASsMOr92bkXmUPlE -DYUd5uFfwwlvbMNT1Ooeyrzg3bARd9B6ATyMkOaJeGoQwFAI468iucnm9rNXB+/t -U2rbIi1pXSm8zSNEY85tf6C8DU/5YbcAPf47a2UYhwCpYAJfMk0= ------END RSA PRIVATE KEY-----""" - -TEST_CERT = """-----BEGIN CERTIFICATE----- -MIIDcDCCAlgCCQC8msHu/aa61zANBgkqhkiG9w0BAQUFADB6MQswCQYDVQQGEwJV -UzETMBEGA1UECBMKQ0FMSUZPUk5JQTESMBAGA1UEBxMJTG9zIEdhdG9zMRYwFAYD -VQQKEw1OZXRmbGl4LCBJbmMuMRMwEQYDVQQLEwpPcGVyYXRpb25zMRUwEwYDVQQD -EwxkZmRzZmxrai5uZXQwHhcNMTQwNTI1MTczMDMzWhcNMTUwNTI1MTczMDMzWjB6 -MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ0FMSUZPUk5JQTESMBAGA1UEBxMJTG9z -IEdhdG9zMRYwFAYDVQQKEw1OZXRmbGl4LCBJbmMuMRMwEQYDVQQLEwpPcGVyYXRp -b25zMRUwEwYDVQQDEwxkZmRzZmxrai5uZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IB -DwAwggEKAoIBAQC8253Bb5R5CqSljvUwh0SDnTUAWU8q45cbbFTwNmG9lLp0q/6Q -OYhZ0Sqh0OCRCLLSPJOBPDHBGoNLD3pHpmCtd59SgkmjrpoKyixMFm1VoTzl/Utu -Pb7dTrvxz6t6RR2SucZwYKjpvxC128GmIPLZyWd6jHy1K+Z98B1xHW4K//v3T+Q/ -ND92NYNf0lwFHIORxMnBgLT96TtRG8D/4xeaBQvXdP6BYqp25oq4gGUV62icOOec -7XVBQGK6wi4dXrbDMqfybk1z7hehoFHRhYwWf+DqpcNivB8KwymRru9JYkW/kV/J -/7uRjfBTjEjBTa9eUgNwKx1S7pv+lihDRg6xAgMBAAEwDQYJKoZIhvcNAQEFBQAD -ggEBAJHwa4l2iSiFBb6wVFBJEWEt31qp+njiVCoTg2OJzCT60Xb26hkrsiTldIIh -eB9+y+fwdfwopzWhkNbIOlCfudx/uxtpor8/3BRbjSlNwDUg2L8pfAircJMFLQUM -O6nqPOBWCe8hXwe9FQM/oFOavf/AAw/FED+892xlytjirK9u3B28O20W11+fY7hp -8LQVBrMoVxFeLWmmwETAltJ7HEYutplRzYTM0vLBARl4Vd5kLJlY3j2Dp1ZpRGcg -CrQp26UD/oaAPGtiZQSC4LJ+4JfOuuqbm3CI24QMCh9rxv3ZoOQnFuC+7cZgqrat -V4bxCrVvWhrrDSgy9+A80NVzQ3k= ------END CERTIFICATE-----""" - - diff --git a/lemur/tests/elbs/__init__.py b/lemur/tests/elbs/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/lemur/tests/elbs/test_elbs.py b/lemur/tests/elbs/test_elbs.py deleted file mode 100644 index 1d31fd12..00000000 --- a/lemur/tests/elbs/test_elbs.py +++ /dev/null @@ -1,5 +0,0 @@ -import os -import shutil -from lemur import app -from lemur.tests import LemurTestCase - diff --git a/lemur/tests/listeners/__init__.py b/lemur/tests/listeners/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/lemur/tests/services/__init__.py b/lemur/tests/services/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/lemur/tests/services/test_elb.py b/lemur/tests/services/test_elb.py deleted file mode 100644 index 0303f77e..00000000 --- a/lemur/tests/services/test_elb.py +++ /dev/null @@ -1,51 +0,0 @@ -import boto -from lemur.tests import LemurTestCase - -from moto import mock_elb, mock_sts - - -class ELBTestCase(LemurTestCase): - @mock_sts - @mock_elb - def test_add_listener(self): - from lemur.common.services.aws.elb import create_new_listeners - conn = boto.connect_elb() - zones = ['us-east-1a', 'us-east-1b'] - ports = [(80, 8080, 'http')] - conn.create_load_balancer('my-lb', zones, ports) - create_new_listeners('111', 'us-east-1', 'my-lb', listeners=[('443', '80', 'HTTP')]) - balancer = conn.get_all_load_balancers()[0] - self.assertEqual(balancer.name, "my-lb") - self.assertEqual(len(balancer.listeners), 2) - - @mock_sts - @mock_elb - def test_update_listener(self): - from lemur.common.services.aws.elb import update_listeners - conn = boto.connect_elb() - zones = ['us-east-1a', 'us-east-1b'] - ports = [(80, 8080, 'http')] - conn.create_load_balancer('my-lb', zones, ports) - update_listeners('111', 'us-east-1', 'my-lb', listeners=[('80', '7001', 'http')]) - balancer = conn.get_all_load_balancers()[0] - listener = balancer.listeners[0] - self.assertEqual(listener.load_balancer_port, 80) - self.assertEqual(listener.instance_port, 7001) - self.assertEqual(listener.protocol, "HTTP") - - @mock_sts - @mock_elb - def test_set_certificate(self): - from lemur.common.services.aws.elb import attach_certificate - conn = boto.connect_elb() - zones = ['us-east-1a', 'us-east-1b'] - ports = [(443, 7001, 'https', 'sslcert')] - conn.create_load_balancer('my-lb', zones, ports) - attach_certificate('1111', 'us-east-1', 'my-lb', 443, 'somecert') - balancer = conn.get_all_load_balancers()[0] - listener = balancer.listeners[0] - self.assertEqual(listener.load_balancer_port, 443) - self.assertEqual(listener.instance_port, 7001) - self.assertEqual(listener.protocol, "HTTPS") - self.assertEqual(listener.ssl_certificate_id, 'somecert') - diff --git a/lemur/tests/services/test_iam.py b/lemur/tests/services/test_iam.py deleted file mode 100644 index 68315e1b..00000000 --- a/lemur/tests/services/test_iam.py +++ /dev/null @@ -1,37 +0,0 @@ -from lemur import app -from lemur.tests import LemurTestCase -from lemur.tests.constants import TEST_CERT, TEST_KEY - -from lemur.certificates.models import Certificate - -from moto import mock_iam, mock_sts - - -class IAMTestCase(LemurTestCase): - @mock_sts - @mock_iam - def test_get_all_server_certs(self): - from lemur.common.services.aws.iam import upload_cert, get_all_server_certs - cert = Certificate(TEST_CERT) - upload_cert('1111', cert, TEST_KEY) - certs = get_all_server_certs('1111') - self.assertEquals(len(certs), 1) - - @mock_sts - @mock_iam - def test_get_server_cert(self): - from lemur.common.services.aws.iam import upload_cert, get_cert_from_arn - cert = Certificate(TEST_CERT) - upload_cert('1111', cert, TEST_KEY) - body, chain = get_cert_from_arn('arn:aws:iam::123456789012:server-certificate/AHB-dfdsflkj.net-NetflixInc-20140525-20150525') - self.assertTrue(body) - - @mock_sts - @mock_iam - def test_upload_server_cert(self): - from lemur.common.services.aws.iam import upload_cert - cert = Certificate(TEST_CERT) - response = upload_cert('1111', cert, TEST_KEY) - self.assertEquals(response['upload_server_certificate_response']['upload_server_certificate_result']['server_certificate_metadata']['server_certificate_name'], 'AHB-dfdsflkj.net-NetflixInc-20140525-20150525') - - diff --git a/lemur/tests/services/test_issuer_manager.py b/lemur/tests/services/test_issuer_manager.py deleted file mode 100644 index 6408438d..00000000 --- a/lemur/tests/services/test_issuer_manager.py +++ /dev/null @@ -1,23 +0,0 @@ -from lemur import app -from lemur.tests import LemurTestCase -from lemur.tests.constants import TEST_CERT, TEST_KEY - -from lemur.certificates.models import Certificate - -from moto import mock_iam, mock_sts - - -class ManagerTestCase(LemurTestCase): - def test_validate_authority(self): - pass - - def test_get_all_authorities(self): - from lemur.common.services.issuers.manager import get_all_authorities - authorities = get_all_authorities() - self.assertEqual(len(authorities), 3) - - def test_get_all_issuers(self): - from lemur.common.services.issuers.manager import get_all_issuers - issuers = get_all_issuers() - self.assertEqual(len(issuers) > 1) - diff --git a/lemur/tests/services/test_s3.py b/lemur/tests/services/test_s3.py deleted file mode 100644 index f5de669f..00000000 --- a/lemur/tests/services/test_s3.py +++ /dev/null @@ -1,27 +0,0 @@ -import boto - -from lemur.tests import LemurTestCase -from lemur.tests.constants import TEST_CERT - -from lemur.certificates.models import Certificate - -from moto import mock_s3 - - -class S3TestCase(LemurTestCase): - @mock_s3 - def test_save(self): - from lemur.common.services.aws.s3 import save - conn = boto.connect_s3() - - cert = Certificate(TEST_CERT) - - buck = conn.create_bucket('test') - path = save(cert, 'private_key', None, 'csr_config', 'challenge') - self.assertEqual(path, 'lemur/{}/{}/'.format(cert.issuer, cert.name)) - - count = 0 - for key in buck.list(): - count += 1 - - self.assertEqual(count, 4) diff --git a/lemur/tests/test_accounts.py b/lemur/tests/test_accounts.py new file mode 100644 index 00000000..19f5bd8d --- /dev/null +++ b/lemur/tests/test_accounts.py @@ -0,0 +1,53 @@ + +import pytest +from lemur.accounts.service import * +from lemur.exceptions import DuplicateError + +from lemur.accounts.views import * + +#def test_crud(session): +# account = create('111111', 'account1') +# assert account.id > 0 +# +# account = update(account.id, 11111, 'account2') +# assert account.label == 'account2' +# +# assert len(get_all()) == 1 +# +# delete(1) +# assert len(get_all()) == 0 +# + +#def test_duplicate(session): +# account = create('111111', 'account1') +# assert account.id > 0 +# +# with pytest.raises(DuplicateError): +# account = create('111111', 'account1') + + +def test_basic_user_views(client): + pass + + +def test_admin_user_views(client): + pass + +def test_unauthenticated_views(client): + assert client.get(api.url_for(Accounts, account_id=1)).status_code == 401 + assert client.post(api.url_for(Accounts, account_id=1), {}).status_code == 405 + assert client.put(api.url_for(Accounts, account_id=1), {}).status_code == 401 + assert client.delete(api.url_for(Accounts, account_id=1)).status_code == 401 + assert client.patch(api.url_for(Accounts, account_id=1), {}).status_code == 405 + + assert client.get(api.url_for(AccountsList)).status_code == 401 + assert client.post(api.url_for(AccountsList), {}).status_code == 401 + assert client.put(api.url_for(AccountsList), {}).status_code == 405 + assert client.delete(api.url_for(AccountsList)).status_code == 405 + assert client.patch(api.url_for(Accounts), {}).status_code == 405 + + assert client.get(api.url_for(CertificateAccounts, certificate_id=1)).status_code == 401 + assert client.post(api.url_for(CertificateAccounts), {}).status_code == 405 + assert client.put(api.url_for(CertificateAccounts), {}).status_code == 405 + assert client.delete(api.url_for(CertificateAccounts)).status_code == 405 + assert client.patch(api.url_for(CertificateAccounts), {}).status_code == 405 diff --git a/lemur/tests/test_certificates.py b/lemur/tests/test_certificates.py new file mode 100644 index 00000000..e24251c5 --- /dev/null +++ b/lemur/tests/test_certificates.py @@ -0,0 +1,85 @@ +import os +import shutil + +import boto +from moto import mock_iam, mock_sts, mock_s3 + +from lemur.tests import LemurTestCase + + +#class CertificateTestCase(LemurTestCase): +# def test_create_challenge(self): +# from lemur.certificates.service import create_challenge +# self.assertTrue(len(create_challenge()) >= 24) +# +# def test_hash_domains(self): +# from lemur.certificates.service import hash_domains +# h = hash_domains(['netflix.com', 'www.netflix.com', 'movies.netflix.com']) +# self.assertEqual('c9c83253b46c7c1245c100ed3f7045eb', h) +# +# def test_create_csr(self): +# from lemur.certificates.service import create_csr +# from lemur.tests.test_csr import TEST_CSR +# path = create_csr(['netflix.com'], TEST_CSR) +# files = len(os.listdir(path)) +# self.assertEqual(files, 4) +# shutil.rmtree(path) +# +# def test_create_san_csr(self): +# from lemur.certificates.service import create_csr +# from lemur.tests.test_csr import TEST_CSR +# path = create_csr(['netflix.com', 'www.netflix.com'], TEST_CSR) +# files = len(os.listdir(path)) +# self.assertEqual(files, 4) +# shutil.rmtree(path) +# +# def test_create_path(self): +# from lemur.certificates.service import create_path +# path = create_path("blah") +# self.assertIn('blah', path) +# shutil.rmtree(path) +# +# @mock_s3 +# @mock_sts +# @mock_iam +# def test_save_cert(self): +# from lemur.certificates.service import save_cert +# from lemur.common.services.aws.iam import get_all_server_certs +# conn = boto.connect_s3() +# bucket = conn.create_bucket(app.config.get('S3_BUCKET')) +# cert = save_cert(TEST_CERT, TEST_KEY, None, "blah", "blah", [1]) +# count = 0 +# for key in bucket.list(): +# count += 1 +# +# self.assertEqual(count, 4) +# certs = get_all_server_certs('1111') +# self.assertEqual(len(certs), 1) +# +## @mock_s3 +## @mock_sts +## @mock_iam +## def test_upload_cert(self): +## from lemur.certificates.service import upload +## from lemur.common.services.aws.iam import get_all_server_certs +## conn = boto.connect_s3() +## bucket = conn.create_bucket(app.config.get('S3_BUCKET')) +## +## cert_up = {"public_cert": TEST_CERT, "private_key": TEST_KEY, "owner": "test@example.com", "accounts_ids": ['1111']} +## +## cert_name = upload(**cert_up) +## valid_name = 'AHB-dfdsflkj.net-NetflixInc-20140525-20150525' +## self.assertEqual(cert_name, valid_name) +## +## app.logger.debug(cert_name) +## count = 0 +## +## for key in bucket.list(): +## count += 1 +## +## self.assertEqual(count, 2) +## certs = get_all_server_certs('179727101194') +## self.assertEqual(len(certs), 1) +## +## +## diff --git a/lemur/tests/certificates/__init__.py b/lemur/tests/test_crypto.py similarity index 100% rename from lemur/tests/certificates/__init__.py rename to lemur/tests/test_crypto.py diff --git a/lemur/tests/certificates/test_csr.py b/lemur/tests/test_csr.py similarity index 100% rename from lemur/tests/certificates/test_csr.py rename to lemur/tests/test_csr.py diff --git a/lemur/tests/test_elb.py b/lemur/tests/test_elb.py new file mode 100644 index 00000000..bc4a10c7 --- /dev/null +++ b/lemur/tests/test_elb.py @@ -0,0 +1,51 @@ +import boto +from lemur.tests import LemurTestCase + +from moto import mock_elb, mock_sts + + +#class ELBTestCase(LemurTestCase): +# @mock_sts +# @mock_elb +# def test_add_listener(self): +# from lemur.common.services.aws.elb import create_new_listeners +# conn = boto.connect_elb() +# zones = ['us-east-1a', 'us-east-1b'] +# ports = [(80, 8080, 'http')] +# conn.create_load_balancer('my-lb', zones, ports) +# create_new_listeners('111', 'us-east-1', 'my-lb', listeners=[('443', '80', 'HTTP')]) +# balancer = conn.get_all_load_balancers()[0] +# self.assertEqual(balancer.name, "my-lb") +# self.assertEqual(len(balancer.listeners), 2) +# +# @mock_sts +# @mock_elb +# def test_update_listener(self): +# from lemur.common.services.aws.elb import update_listeners +# conn = boto.connect_elb() +# zones = ['us-east-1a', 'us-east-1b'] +# ports = [(80, 8080, 'http')] +# conn.create_load_balancer('my-lb', zones, ports) +# update_listeners('111', 'us-east-1', 'my-lb', listeners=[('80', '7001', 'http')]) +# balancer = conn.get_all_load_balancers()[0] +# listener = balancer.listeners[0] +# self.assertEqual(listener.load_balancer_port, 80) +# self.assertEqual(listener.instance_port, 7001) +# self.assertEqual(listener.protocol, "HTTP") +# +# @mock_sts +# @mock_elb +# def test_set_certificate(self): +# from lemur.common.services.aws.elb import attach_certificate +# conn = boto.connect_elb() +# zones = ['us-east-1a', 'us-east-1b'] +# ports = [(443, 7001, 'https', 'sslcert')] +# conn.create_load_balancer('my-lb', zones, ports) +# attach_certificate('1111', 'us-east-1', 'my-lb', 443, 'somecert') +# balancer = conn.get_all_load_balancers()[0] +# listener = balancer.listeners[0] +# self.assertEqual(listener.load_balancer_port, 443) +# self.assertEqual(listener.instance_port, 7001) +# self.assertEqual(listener.protocol, "HTTPS") +# self.assertEqual(listener.ssl_certificate_id, 'somecert') +# diff --git a/lemur/tests/test_iam.py b/lemur/tests/test_iam.py new file mode 100644 index 00000000..2405f9b5 --- /dev/null +++ b/lemur/tests/test_iam.py @@ -0,0 +1,35 @@ +from lemur.tests import LemurTestCase + +from lemur.certificates.models import Certificate + +from moto import mock_iam, mock_sts + + +#class IAMTestCase(LemurTestCase): +# @mock_sts +# @mock_iam +# def test_get_all_server_certs(self): +# from lemur.common.services.aws.iam import upload_cert, get_all_server_certs +# cert = Certificate(TEST_CERT) +# upload_cert('1111', cert, TEST_KEY) +# certs = get_all_server_certs('1111') +# self.assertEquals(len(certs), 1) +# +# @mock_sts +# @mock_iam +# def test_get_server_cert(self): +# from lemur.common.services.aws.iam import upload_cert, get_cert_from_arn +# cert = Certificate(TEST_CERT) +# upload_cert('1111', cert, TEST_KEY) +# body, chain = get_cert_from_arn('arn:aws:iam::123456789012:server-certificate/AHB-dfdsflkj.net-NetflixInc-20140525-20150525') +# self.assertTrue(body) +# +# @mock_sts +# @mock_iam +# def test_upload_server_cert(self): +# from lemur.common.services.aws.iam import upload_cert +# cert = Certificate(TEST_CERT) +# response = upload_cert('1111', cert, TEST_KEY) +# self.assertEquals(response['upload_server_certificate_response']['upload_server_certificate_result']['server_certificate_metadata']['server_certificate_name'], 'AHB-dfdsflkj.net-NetflixInc-20140525-20150525') +# +# diff --git a/lemur/tests/test_issuer_manager.py b/lemur/tests/test_issuer_manager.py new file mode 100644 index 00000000..5eaec09b --- /dev/null +++ b/lemur/tests/test_issuer_manager.py @@ -0,0 +1,16 @@ +from lemur.tests import LemurTestCase + +#class ManagerTestCase(LemurTestCase): +# def test_validate_authority(self): +# pass +# +# def test_get_all_authorities(self): +# from lemur.common.services.issuers.manager import get_all_authorities +# authorities = get_all_authorities() +# self.assertEqual(len(authorities), 3) +# +# def test_get_all_issuers(self): +# from lemur.common.services.issuers.manager import get_all_issuers +# issuers = get_all_issuers() +# self.assertEqual(len(issuers) > 1) +# diff --git a/lemur/tests/certificates/test_pack/challenge.txt b/lemur/tests/test_pack/challenge.txt similarity index 100% rename from lemur/tests/certificates/test_pack/challenge.txt rename to lemur/tests/test_pack/challenge.txt diff --git a/lemur/tests/certificates/test_pack/csr_config.txt b/lemur/tests/test_pack/csr_config.txt similarity index 100% rename from lemur/tests/certificates/test_pack/csr_config.txt rename to lemur/tests/test_pack/csr_config.txt diff --git a/lemur/tests/certificates/test_pack/private.key b/lemur/tests/test_pack/private.key similarity index 100% rename from lemur/tests/certificates/test_pack/private.key rename to lemur/tests/test_pack/private.key diff --git a/lemur/tests/certificates/test_pack/request.csr b/lemur/tests/test_pack/request.csr similarity index 100% rename from lemur/tests/certificates/test_pack/request.csr rename to lemur/tests/test_pack/request.csr diff --git a/lemur/tests/certificates/test_pack/server.crt b/lemur/tests/test_pack/server.crt similarity index 100% rename from lemur/tests/certificates/test_pack/server.crt rename to lemur/tests/test_pack/server.crt diff --git a/setup.py b/setup.py index ad3815a0..5e07860b 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,8 @@ install_requires=[ tests_require = [ 'pyflakes', 'moto', - 'nose' + 'nose', + 'pytest' ] docs_require = [ From 37669b906cc25b2ed6b72ba0f1d7b409e5dde0a3 Mon Sep 17 00:00:00 2001 From: Kevin Glisson Date: Wed, 24 Jun 2015 16:51:44 -0700 Subject: [PATCH 02/31] Fixes an issue where the issuer has special chars in the name. AWS dislikes special chars in certificate names so we strip them out here. In general we want to have the name tracked by Lemur be the same as what is uploaded to various destinations. --- lemur/certificates/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lemur/certificates/models.py b/lemur/certificates/models.py index 81947bfc..1f7ffc0c 100644 --- a/lemur/certificates/models.py +++ b/lemur/certificates/models.py @@ -116,8 +116,10 @@ def cert_get_issuer(cert): :param cert: :return: Issuer """ + delchars = ''.join(c for c in map(chr, range(256)) if not c.isalnum()) try: - return cert.subject.get_attributes_for_oid(x509.OID_ORGANIZATION_NAME)[0].value + issuer = str(cert.subject.get_attributes_for_oid(x509.OID_ORGANIZATION_NAME)[0].value) + return issuer.translate(None, delchars) except Exception as e: current_app.logger.error("Unable to get issuer! {0}".format(e)) From 02854226543bd3349455b7ca91c24999216957c2 Mon Sep 17 00:00:00 2001 From: Kevin Glisson Date: Thu, 25 Jun 2015 13:43:42 -0700 Subject: [PATCH 03/31] Adding some structure for authenticated tests --- lemur/accounts/views.py | 2 +- lemur/auth/service.py | 3 +-- lemur/tests/conftest.py | 45 ++++++++++++++++++++++++++---------- lemur/tests/test_accounts.py | 13 +++-------- 4 files changed, 38 insertions(+), 25 deletions(-) diff --git a/lemur/accounts/views.py b/lemur/accounts/views.py index b3304af4..2729ec47 100644 --- a/lemur/accounts/views.py +++ b/lemur/accounts/views.py @@ -181,7 +181,7 @@ class Accounts(AuthenticatedResource): @marshal_items(FIELDS) def put(self, account_id): """ - .. http:post:: /accounts/1 + .. http:put:: /accounts/1 Updates an account diff --git a/lemur/auth/service.py b/lemur/auth/service.py index 0675f640..facad7c4 100644 --- a/lemur/auth/service.py +++ b/lemur/auth/service.py @@ -96,9 +96,8 @@ def login_required(f): response.status_code = 401 return response - token = request.headers.get('Authorization').split()[1] - try: + token = request.headers.get('Authorization').split()[1] payload = jwt.decode(token, current_app.config['TOKEN_SECRET']) except jwt.DecodeError: return dict(message='Token is invalid'), 403 diff --git a/lemur/tests/conftest.py b/lemur/tests/conftest.py index 135b5ca7..2d680850 100644 --- a/lemur/tests/conftest.py +++ b/lemur/tests/conftest.py @@ -1,9 +1,11 @@ import pytest +from flask import current_app + from lemur import create_app -from lemur.database import db as _db from flask.ext.sqlalchemy import SignallingSession +from flask.ext.principal import Identity, identity_changed from sqlalchemy import event @@ -45,26 +47,45 @@ def app(): ctx.pop() -@pytest.yield_fixture(scope="session") -def db(): - _db.create_all() - - yield _db - - _db.drop_all() +@pytest.yield_fixture(scope="function") +def unauth_client(app): + with app.test_client() as client: + yield client @pytest.yield_fixture(scope="function") -def session(app, db): +def auth_client(app): + with app.test_client() as client: + yield client + + +@pytest.yield_fixture(scope="function") +def admin_client(app): + with app.test_client() as client: + yield client + + + +@pytest.yield_fixture(scope="session") +def database(app): + app.db.create_all() + + yield app.db + + app.db.drop_all() + + +@pytest.yield_fixture(scope="function") +def session(database): """ Creates a new database session with (with working transaction) for test duration. """ - connection = _db.engine.connect() + connection = database.engine.connect() transaction = connection.begin() options = dict(bind=connection) - session = _db.create_scoped_session(options=options) + session = database.create_scoped_session(options=options) # then each time that SAVEPOINT ends, reopen it @event.listens_for(SignallingSession, "after_transaction_end") @@ -81,7 +102,7 @@ def session(app, db): # pushing new Flask application context for multiple-thread # tests to work - _db.session = session + database.session = session yield session diff --git a/lemur/tests/test_accounts.py b/lemur/tests/test_accounts.py index 19f5bd8d..3d1de94d 100644 --- a/lemur/tests/test_accounts.py +++ b/lemur/tests/test_accounts.py @@ -40,14 +40,7 @@ def test_unauthenticated_views(client): assert client.delete(api.url_for(Accounts, account_id=1)).status_code == 401 assert client.patch(api.url_for(Accounts, account_id=1), {}).status_code == 405 - assert client.get(api.url_for(AccountsList)).status_code == 401 - assert client.post(api.url_for(AccountsList), {}).status_code == 401 - assert client.put(api.url_for(AccountsList), {}).status_code == 405 - assert client.delete(api.url_for(AccountsList)).status_code == 405 - assert client.patch(api.url_for(Accounts), {}).status_code == 405 +VALID_TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI' - assert client.get(api.url_for(CertificateAccounts, certificate_id=1)).status_code == 401 - assert client.post(api.url_for(CertificateAccounts), {}).status_code == 405 - assert client.put(api.url_for(CertificateAccounts), {}).status_code == 405 - assert client.delete(api.url_for(CertificateAccounts)).status_code == 405 - assert client.patch(api.url_for(CertificateAccounts), {}).status_code == 405 +def test_auth_account_get(auth_client): + assert auth_client.get(api.url_for(Accounts, account_id=1), headers={'Authorization': 'Basic ' + VALID_TOKEN}).status_code == 200 \ No newline at end of file From f28d3a54c5356e9f4fa5a11fee54a05efe92377e Mon Sep 17 00:00:00 2001 From: Kevin Glisson Date: Thu, 25 Jun 2015 13:50:46 -0700 Subject: [PATCH 04/31] API change in cryptography --- lemur/certificates/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lemur/certificates/views.py b/lemur/certificates/views.py index 3de200d5..4bc317f1 100644 --- a/lemur/certificates/views.py +++ b/lemur/certificates/views.py @@ -90,7 +90,7 @@ def private_key_str(value, name): :return: :raise ValueError: """ try: - serialization.load_pem_private_key(str(value), backend=default_backend()) + serialization.load_pem_private_key(str(value), None, backend=default_backend()) except Exception as e: raise ValueError("The parameter '{0}' needs to be a valid RSA private key".format(name)) return value From bea8e6f2a31947eba394e168125b8076c3622d99 Mon Sep 17 00:00:00 2001 From: Kevin Glisson Date: Thu, 25 Jun 2015 18:05:52 -0700 Subject: [PATCH 05/31] Adding more tests to the accounts model --- lemur/extensions.py | 36 +-------- lemur/tests/conftest.py | 90 ++++++++------------- lemur/tests/test_accounts.py | 150 +++++++++++++++++++++++++++-------- 3 files changed, 152 insertions(+), 124 deletions(-) diff --git a/lemur/extensions.py b/lemur/extensions.py index 101432e8..07101c4d 100644 --- a/lemur/extensions.py +++ b/lemur/extensions.py @@ -4,41 +4,7 @@ :license: Apache, see LICENSE for more details. """ -from flask.ext.sqlalchemy import SQLAlchemy, SignallingSession, SessionBase - - -class _SignallingSession(SignallingSession): - """A subclass of `SignallingSession` that allows for `binds` to be specified - in the `options` keyword arguments. - - """ - def __init__(self, db, autocommit=False, autoflush=True, **options): - self.app = db.get_app() - self._model_changes = {} - self.emit_modification_signals = \ - self.app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] - - bind = options.pop('bind', None) - if bind is None: - bind = db.engine - - binds = options.pop('binds', None) - if binds is None: - binds = db.get_binds(self.app) - - SessionBase.__init__(self, - autocommit=autocommit, - autoflush=autoflush, - bind=bind, - binds=binds, - **options) - - -class _SQLAlchemy(SQLAlchemy): - """A subclass of `SQLAlchemy` that uses `_SignallingSession`.""" - def create_session(self, options): - return _SignallingSession(self, **options) - +from flask.ext.sqlalchemy import SQLAlchemy db = SQLAlchemy() from flask.ext.migrate import Migrate diff --git a/lemur/tests/conftest.py b/lemur/tests/conftest.py index 2d680850..254a7e27 100644 --- a/lemur/tests/conftest.py +++ b/lemur/tests/conftest.py @@ -1,13 +1,9 @@ import pytest -from flask import current_app - from lemur import create_app - -from flask.ext.sqlalchemy import SignallingSession -from flask.ext.principal import Identity, identity_changed - -from sqlalchemy import event +from lemur.database import db as _db +from lemur.users import service as user_service +from lemur.roles import service as role_service def pytest_addoption(parser): @@ -38,6 +34,7 @@ def app(): Uses application factory `create_app`. """ app = create_app() + app.config['TESTING'] = True ctx = app.app_context() ctx.push() @@ -47,66 +44,45 @@ def app(): ctx.pop() -@pytest.yield_fixture(scope="function") -def unauth_client(app): - with app.test_client() as client: - yield client - - -@pytest.yield_fixture(scope="function") -def auth_client(app): - with app.test_client() as client: - yield client - - -@pytest.yield_fixture(scope="function") -def admin_client(app): - with app.test_client() as client: - yield client - - @pytest.yield_fixture(scope="session") -def database(app): - app.db.create_all() +def db(app, request): + _db.drop_all() + _db.create_all() - yield app.db + _db.app = app - app.db.drop_all() + yield _db + + _db.drop_all() @pytest.yield_fixture(scope="function") -def session(database): +def session(db, request): """ Creates a new database session with (with working transaction) for test duration. """ - connection = database.engine.connect() - transaction = connection.begin() - - options = dict(bind=connection) - session = database.create_scoped_session(options=options) - - # then each time that SAVEPOINT ends, reopen it - @event.listens_for(SignallingSession, "after_transaction_end") - def restart_savepoint(session, transaction): - if transaction.nested and not transaction._parent.nested: - - # ensure that state is expired the way - # session.commit() at the top level normally does - # (optional step) - session.expire_all() - - session.begin_nested() - - # pushing new Flask application context for multiple-thread - # tests to work - - database.session = session - + db.session.begin_nested() yield session + db.session.rollback() + + +@pytest.yield_fixture(scope="session") +def default_user(db): + user = user_service.create('user', 'test', 'user@example.com', True, None, []) + yield user + + +@pytest.yield_fixture(scope="session") +def admin_user(db): + admin_role = role_service.create('admin') + admin = user_service.create('admin', 'admin', 'admin@example.com', True, None, [admin_role]) + yield admin + + +@pytest.yield_fixture(scope="function") +def client(app): + with app.test_client() as client: + yield client - # the code after the yield statement works as a teardown - transaction.rollback() - connection.close() - session.remove() diff --git a/lemur/tests/test_accounts.py b/lemur/tests/test_accounts.py index 3d1de94d..08239afb 100644 --- a/lemur/tests/test_accounts.py +++ b/lemur/tests/test_accounts.py @@ -1,46 +1,132 @@ - -import pytest from lemur.accounts.service import * -from lemur.exceptions import DuplicateError - from lemur.accounts.views import * -#def test_crud(session): -# account = create('111111', 'account1') -# assert account.id > 0 -# -# account = update(account.id, 11111, 'account2') -# assert account.label == 'account2' -# -# assert len(get_all()) == 1 -# -# delete(1) -# assert len(get_all()) == 0 -# - -#def test_duplicate(session): -# account = create('111111', 'account1') -# assert account.id > 0 -# -# with pytest.raises(DuplicateError): -# account = create('111111', 'account1') +from json import dumps -def test_basic_user_views(client): - pass +def test_crud(session): + account = create('111111', 'account1') + assert account.id > 0 + + account = update(account.id, 11111, 'account2') + assert account.label == 'account2' + + assert len(get_all()) == 1 + + delete(1) + assert len(get_all()) == 0 -def test_admin_user_views(client): - pass - -def test_unauthenticated_views(client): +def test_account_get(client): assert client.get(api.url_for(Accounts, account_id=1)).status_code == 401 + + +def test_account_post(client): assert client.post(api.url_for(Accounts, account_id=1), {}).status_code == 405 + + +def test_account_put(client): assert client.put(api.url_for(Accounts, account_id=1), {}).status_code == 401 + + +def test_account_delete(client): assert client.delete(api.url_for(Accounts, account_id=1)).status_code == 401 + + +def test_account_patch(client): assert client.patch(api.url_for(Accounts, account_id=1), {}).status_code == 405 -VALID_TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI' -def test_auth_account_get(auth_client): - assert auth_client.get(api.url_for(Accounts, account_id=1), headers={'Authorization': 'Basic ' + VALID_TOKEN}).status_code == 200 \ No newline at end of file +VALID_USER_HEADER_TOKEN = { + 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'} + +def test_auth_account_get(client, default_user): + assert client.get(api.url_for(Accounts, account_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200 + + +def test_auth_account_post_(client, default_user): + assert client.post(api.url_for(Accounts, account_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_account_put(client, default_user): + assert client.put(api.url_for(Accounts, account_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 403 + + +def test_auth_account_delete(client, default_user): + assert client.delete(api.url_for(Accounts, account_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 403 + + +def test_auth_account_patch(client, default_user): + assert client.patch(api.url_for(Accounts, account_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +VALID_ADMIN_HEADER_TOKEN = { + 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'} + +def test_admin_account_get(client, admin_user): + assert client.get(api.url_for(Accounts, account_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 + + +def test_admin_account_post(client, admin_user): + assert client.post(api.url_for(Accounts, account_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_account_put(client, admin_user): + assert client.put(api.url_for(Accounts, account_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 + + +def test_admin_account_delete(client, admin_user): + assert client.delete(api.url_for(Accounts, account_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 500 + + +def test_admin_account_patch(client, admin_user): + assert client.patch(api.url_for(Accounts, account_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_accounts_get(client): + assert client.get(api.url_for(AccountsList)).status_code == 401 + + +def test_accounts_post(client): + assert client.post(api.url_for(AccountsList), {}).status_code == 401 + + +def test_accounts_put(client): + assert client.put(api.url_for(AccountsList), {}).status_code == 405 + + +def test_accounts_delete(client): + assert client.delete(api.url_for(AccountsList)).status_code == 405 + + +def test_accounts_patch(client): + assert client.patch(api.url_for(AccountsList), {}).status_code == 405 + + +def test_auth_accounts_get(client, default_user): + assert client.get(api.url_for(AccountsList), headers=VALID_USER_HEADER_TOKEN).status_code == 200 + + +def test_auth_accounts_post(client, default_user): + assert client.post(api.url_for(AccountsList), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 403 + + +def test_admin_accounts_get(client, admin_user): + resp = client.get(api.url_for(AccountsList), headers=VALID_ADMIN_HEADER_TOKEN) + assert resp.status_code == 200 + assert resp.json == {'items': [], 'total': 0} + + +def test_admin_accounts_crud(client, admin_user): + assert client.post(api.url_for(AccountsList), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 + data = {'accountNumber': 111, 'label': 'test', 'comments': 'test'} + resp = client.post(api.url_for(AccountsList), data=dumps(data), content_type='application/json', headers=VALID_ADMIN_HEADER_TOKEN) + assert resp.status_code == 200 + assert client.get(api.url_for(Accounts, account_id=resp.json['id']), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 + resp = client.get(api.url_for(AccountsList), headers=VALID_ADMIN_HEADER_TOKEN) + assert resp.status_code == 200 + assert resp.json == {'items': [{'accountNumber': 111, 'label': 'test', 'comments': 'test', 'id': 2}], 'total': 1} + assert client.delete(api.url_for(Accounts, account_id=2), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 + resp = client.get(api.url_for(AccountsList), headers=VALID_ADMIN_HEADER_TOKEN) + assert resp.status_code == 200 + assert resp.json == {'items': [], 'total': 0} From 6aa1a12ef6e571e9d77fa226965e05da41d8909c Mon Sep 17 00:00:00 2001 From: Kevin Glisson Date: Thu, 25 Jun 2015 18:06:47 -0700 Subject: [PATCH 06/31] Removing netflix specific role --- lemur/auth/permissions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lemur/auth/permissions.py b/lemur/auth/permissions.py index 16d686b7..137b650b 100644 --- a/lemur/auth/permissions.py +++ b/lemur/auth/permissions.py @@ -13,7 +13,7 @@ from flask.ext.principal import Permission, RoleNeed # Permissions operator_permission = Permission(RoleNeed('operator')) -admin_permission = Permission(RoleNeed('secops@netflix.com')) +admin_permission = Permission(RoleNeed('admin')) CertificateCreator = namedtuple('certificate', ['method', 'value']) CertificateCreatorNeed = partial(CertificateCreator, 'certificateView') From 6b5383633d14d1ede72ae16c2a44b505c4a53e5d Mon Sep 17 00:00:00 2001 From: Kevin Glisson Date: Thu, 25 Jun 2015 18:07:21 -0700 Subject: [PATCH 07/31] Removing duplicated commit --- lemur/users/service.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lemur/users/service.py b/lemur/users/service.py index f3fd84ba..c4eebcee 100644 --- a/lemur/users/service.py +++ b/lemur/users/service.py @@ -31,8 +31,7 @@ def create(username, password, email, active, profile_picture, roles): profile_picture=profile_picture, role=roles ) - user = database.create(user) - return database.update(user) + return database.create(user) def update(user_id, username, email, active, profile_picture, roles): From 8a6abc6f82bd0b3fd074694746f008b1113d8983 Mon Sep 17 00:00:00 2001 From: Kevin Glisson Date: Thu, 25 Jun 2015 18:08:04 -0700 Subject: [PATCH 08/31] Adding for handling proxy-based errors --- lemur/auth/service.py | 6 +++++- lemur/common/utils.py | 7 +++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lemur/auth/service.py b/lemur/auth/service.py index facad7c4..5fd20f42 100644 --- a/lemur/auth/service.py +++ b/lemur/auth/service.py @@ -98,6 +98,10 @@ def login_required(f): try: token = request.headers.get('Authorization').split()[1] + except Exception as e: + return dict(message='Token is invalid'), 403 + + try: payload = jwt.decode(token, current_app.config['TOKEN_SECRET']) except jwt.DecodeError: return dict(message='Token is invalid'), 403 @@ -108,7 +112,7 @@ def login_required(f): g.current_user = user_service.get(payload['sub']) - if not g.current_user.id: + if not g.current_user: return dict(message='You are not logged in'), 403 # Tell Flask-Principal the identity changed diff --git a/lemur/common/utils.py b/lemur/common/utils.py index 6b23b3d8..55f411e2 100644 --- a/lemur/common/utils.py +++ b/lemur/common/utils.py @@ -45,11 +45,14 @@ class marshal_items(object): return marshal(resp, self.fields) except Exception as e: + current_app.logger.exception(e) # this is a little weird hack to respect flask restful parsing errors on marshaled functions if hasattr(e, 'code'): - return {'message': e.data['message']}, 400 + if hasattr(e, 'data'): + return {'message': e.data['message']}, 400 + else: + return {'message': 'unknown'}, 400 else: - current_app.logger.exception(e) return {'message': e.message}, 400 return wrapper From 9f2088061574241e860bc060d315e9ef89ce0d67 Mon Sep 17 00:00:00 2001 From: Kevin Glisson Date: Fri, 26 Jun 2015 08:09:10 -0700 Subject: [PATCH 09/31] Adding domain module tests --- lemur/tests/test_domains.py | 123 ++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 lemur/tests/test_domains.py diff --git a/lemur/tests/test_domains.py b/lemur/tests/test_domains.py new file mode 100644 index 00000000..2f9b1a7f --- /dev/null +++ b/lemur/tests/test_domains.py @@ -0,0 +1,123 @@ +from lemur.domains.views import * + +def test_domain_get(client): + assert client.get(api.url_for(Domains, domain_id=1)).status_code == 401 + + +def test_domain_post(client): + assert client.post(api.url_for(Domains, domain_id=1), {}).status_code == 405 + + +def test_domain_put(client): + assert client.put(api.url_for(Domains, domain_id=1), {}).status_code == 405 + + +def test_domain_delete(client): + assert client.delete(api.url_for(Domains, domain_id=1)).status_code == 405 + + +def test_domain_patch(client): + assert client.patch(api.url_for(Domains, domain_id=1), {}).status_code == 405 + + +VALID_USER_HEADER_TOKEN = { + 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'} + +def test_auth_domain_get(client, default_user): + assert client.get(api.url_for(Domains, domain_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200 + + +def test_auth_domain_post_(client, default_user): + assert client.post(api.url_for(Domains, domain_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_domain_put(client, default_user): + assert client.put(api.url_for(Domains, domain_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_domain_delete(client, default_user): + assert client.delete(api.url_for(Domains, domain_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_domain_patch(client, default_user): + assert client.patch(api.url_for(Domains, domain_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +VALID_ADMIN_HEADER_TOKEN = { + 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'} + +def test_admin_domain_get(client, admin_user): + assert client.get(api.url_for(Domains, domain_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 + + +def test_admin_domain_post(client, admin_user): + assert client.post(api.url_for(Domains, domain_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_domain_put(client, admin_user): + assert client.put(api.url_for(Domains, domain_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_domain_delete(client, admin_user): + assert client.delete(api.url_for(Domains, domain_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_domain_patch(client, admin_user): + assert client.patch(api.url_for(Domains, domain_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_domains_get(client): + assert client.get(api.url_for(DomainsList)).status_code == 401 + + +def test_domains_post(client): + assert client.post(api.url_for(DomainsList), {}).status_code == 405 + + +def test_domains_put(client): + assert client.put(api.url_for(DomainsList), {}).status_code == 405 + + +def test_domains_delete(client): + assert client.delete(api.url_for(DomainsList)).status_code == 405 + + +def test_domains_patch(client): + assert client.patch(api.url_for(DomainsList), {}).status_code == 405 + + +def test_auth_domains_get(client, default_user): + assert client.get(api.url_for(DomainsList), headers=VALID_USER_HEADER_TOKEN).status_code == 200 + + +def test_admin_domains_get(client, admin_user): + resp = client.get(api.url_for(DomainsList), headers=VALID_ADMIN_HEADER_TOKEN) + assert resp.status_code == 200 + assert resp.json == {'items': [], 'total': 0} + + +def test_certificate_domains_get(client): + assert client.get(api.url_for(CertificateDomains, certificate_id=1)).status_code == 401 + + +def test_certificate_domains_post(client): + assert client.post(api.url_for(CertificateDomains, certificate_id=1), {}).status_code == 405 + + +def test_certificate_domains_put(client): + assert client.put(api.url_for(CertificateDomains, certificate_id=1), {}).status_code == 405 + + +def test_certificate_domains_delete(client): + assert client.delete(api.url_for(CertificateDomains, certificate_id=1)).status_code == 405 + + +def test_certificate_domains_patch(client): + assert client.patch(api.url_for(CertificateDomains, certificate_id=1), {}).status_code == 405 + + +def test_auth_certificate_domains_get(client, default_user): + assert client.get(api.url_for(CertificateDomains, certificate_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200 + +def test_admin_certificate_domains_get(client, admin_user): + assert client.get(api.url_for(CertificateDomains, certificate_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 From 57ec9c068a1e09a5356246c28313d9bbcfd90c71 Mon Sep 17 00:00:00 2001 From: Kevin Glisson Date: Fri, 26 Jun 2015 10:31:55 -0700 Subject: [PATCH 10/31] Adding role tests --- lemur/tests/conftest.py | 22 +-- lemur/tests/test_accounts.py | 28 ++-- lemur/tests/test_domains.py | 28 ++-- lemur/tests/test_roles.py | 311 +++++++++++++++++++++++++++++++++++ 4 files changed, 345 insertions(+), 44 deletions(-) create mode 100644 lemur/tests/test_roles.py diff --git a/lemur/tests/conftest.py b/lemur/tests/conftest.py index 254a7e27..50bfd144 100644 --- a/lemur/tests/conftest.py +++ b/lemur/tests/conftest.py @@ -35,6 +35,7 @@ def app(): """ app = create_app() app.config['TESTING'] = True + app.config['LEMUR_ENCRYPTION_KEY'] = 'test' ctx = app.app_context() ctx.push() @@ -52,10 +53,12 @@ def db(app, request): _db.app = app + user = user_service.create('user', 'test', 'user@example.com', True, None, []) + admin_role = role_service.create('admin') + admin = user_service.create('admin', 'admin', 'admin@example.com', True, None, [admin_role]) + _db.session.commit() yield _db - _db.drop_all() - @pytest.yield_fixture(scope="function") def session(db, request): @@ -68,21 +71,8 @@ def session(db, request): db.session.rollback() -@pytest.yield_fixture(scope="session") -def default_user(db): - user = user_service.create('user', 'test', 'user@example.com', True, None, []) - yield user - - -@pytest.yield_fixture(scope="session") -def admin_user(db): - admin_role = role_service.create('admin') - admin = user_service.create('admin', 'admin', 'admin@example.com', True, None, [admin_role]) - yield admin - - @pytest.yield_fixture(scope="function") -def client(app): +def client(app, session): with app.test_client() as client: yield client diff --git a/lemur/tests/test_accounts.py b/lemur/tests/test_accounts.py index 08239afb..2712947c 100644 --- a/lemur/tests/test_accounts.py +++ b/lemur/tests/test_accounts.py @@ -40,46 +40,46 @@ def test_account_patch(client): VALID_USER_HEADER_TOKEN = { 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'} -def test_auth_account_get(client, default_user): +def test_auth_account_get(client): assert client.get(api.url_for(Accounts, account_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200 -def test_auth_account_post_(client, default_user): +def test_auth_account_post_(client): assert client.post(api.url_for(Accounts, account_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 -def test_auth_account_put(client, default_user): +def test_auth_account_put(client): assert client.put(api.url_for(Accounts, account_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 403 -def test_auth_account_delete(client, default_user): +def test_auth_account_delete(client): assert client.delete(api.url_for(Accounts, account_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 403 -def test_auth_account_patch(client, default_user): +def test_auth_account_patch(client): assert client.patch(api.url_for(Accounts, account_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 VALID_ADMIN_HEADER_TOKEN = { 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'} -def test_admin_account_get(client, admin_user): +def test_admin_account_get(client): assert client.get(api.url_for(Accounts, account_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 -def test_admin_account_post(client, admin_user): +def test_admin_account_post(client): assert client.post(api.url_for(Accounts, account_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 -def test_admin_account_put(client, admin_user): +def test_admin_account_put(client): assert client.put(api.url_for(Accounts, account_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 -def test_admin_account_delete(client, admin_user): +def test_admin_account_delete(client): assert client.delete(api.url_for(Accounts, account_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 500 -def test_admin_account_patch(client, admin_user): +def test_admin_account_patch(client): assert client.patch(api.url_for(Accounts, account_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 @@ -103,21 +103,21 @@ def test_accounts_patch(client): assert client.patch(api.url_for(AccountsList), {}).status_code == 405 -def test_auth_accounts_get(client, default_user): +def test_auth_accounts_get(client): assert client.get(api.url_for(AccountsList), headers=VALID_USER_HEADER_TOKEN).status_code == 200 -def test_auth_accounts_post(client, default_user): +def test_auth_accounts_post(client): assert client.post(api.url_for(AccountsList), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 403 -def test_admin_accounts_get(client, admin_user): +def test_admin_accounts_get(client): resp = client.get(api.url_for(AccountsList), headers=VALID_ADMIN_HEADER_TOKEN) assert resp.status_code == 200 assert resp.json == {'items': [], 'total': 0} -def test_admin_accounts_crud(client, admin_user): +def test_admin_accounts_crud(client): assert client.post(api.url_for(AccountsList), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 data = {'accountNumber': 111, 'label': 'test', 'comments': 'test'} resp = client.post(api.url_for(AccountsList), data=dumps(data), content_type='application/json', headers=VALID_ADMIN_HEADER_TOKEN) diff --git a/lemur/tests/test_domains.py b/lemur/tests/test_domains.py index 2f9b1a7f..9d57f142 100644 --- a/lemur/tests/test_domains.py +++ b/lemur/tests/test_domains.py @@ -23,46 +23,46 @@ def test_domain_patch(client): VALID_USER_HEADER_TOKEN = { 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'} -def test_auth_domain_get(client, default_user): +def test_auth_domain_get(client): assert client.get(api.url_for(Domains, domain_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200 -def test_auth_domain_post_(client, default_user): +def test_auth_domain_post_(client): assert client.post(api.url_for(Domains, domain_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 -def test_auth_domain_put(client, default_user): +def test_auth_domain_put(client): assert client.put(api.url_for(Domains, domain_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 -def test_auth_domain_delete(client, default_user): +def test_auth_domain_delete(client): assert client.delete(api.url_for(Domains, domain_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 405 -def test_auth_domain_patch(client, default_user): +def test_auth_domain_patch(client): assert client.patch(api.url_for(Domains, domain_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 VALID_ADMIN_HEADER_TOKEN = { 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'} -def test_admin_domain_get(client, admin_user): +def test_admin_domain_get(client): assert client.get(api.url_for(Domains, domain_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 -def test_admin_domain_post(client, admin_user): +def test_admin_domain_post(client): assert client.post(api.url_for(Domains, domain_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 -def test_admin_domain_put(client, admin_user): +def test_admin_domain_put(client): assert client.put(api.url_for(Domains, domain_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 -def test_admin_domain_delete(client, admin_user): +def test_admin_domain_delete(client): assert client.delete(api.url_for(Domains, domain_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 -def test_admin_domain_patch(client, admin_user): +def test_admin_domain_patch(client): assert client.patch(api.url_for(Domains, domain_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 @@ -86,11 +86,11 @@ def test_domains_patch(client): assert client.patch(api.url_for(DomainsList), {}).status_code == 405 -def test_auth_domains_get(client, default_user): +def test_auth_domains_get(client): assert client.get(api.url_for(DomainsList), headers=VALID_USER_HEADER_TOKEN).status_code == 200 -def test_admin_domains_get(client, admin_user): +def test_admin_domains_get(client): resp = client.get(api.url_for(DomainsList), headers=VALID_ADMIN_HEADER_TOKEN) assert resp.status_code == 200 assert resp.json == {'items': [], 'total': 0} @@ -116,8 +116,8 @@ def test_certificate_domains_patch(client): assert client.patch(api.url_for(CertificateDomains, certificate_id=1), {}).status_code == 405 -def test_auth_certificate_domains_get(client, default_user): +def test_auth_certificate_domains_get(client): assert client.get(api.url_for(CertificateDomains, certificate_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200 -def test_admin_certificate_domains_get(client, admin_user): +def test_admin_certificate_domains_get(client): assert client.get(api.url_for(CertificateDomains, certificate_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 diff --git a/lemur/tests/test_roles.py b/lemur/tests/test_roles.py new file mode 100644 index 00000000..b40e0772 --- /dev/null +++ b/lemur/tests/test_roles.py @@ -0,0 +1,311 @@ +from json import dumps +from lemur.roles.service import * +from lemur.roles.views import * + + +def test_crud(session): + role = create('role1') + assert role.id > 0 + + role = update(role.id, 'role_new', None, []) + assert role.name == 'role_new' + delete(role.id) + assert get(role.id) == None + + +def test_role_get(client): + assert client.get(api.url_for(Roles, role_id=1)).status_code == 401 + + +def test_role_post(client): + assert client.post(api.url_for(Roles, role_id=1), {}).status_code == 405 + + +def test_role_put(client): + assert client.put(api.url_for(Roles, role_id=1), {}).status_code == 401 + + +def test_role_delete(client): + assert client.delete(api.url_for(Roles, role_id=1)).status_code == 401 + + +def test_role_patch(client): + assert client.patch(api.url_for(Roles, role_id=1), {}).status_code == 405 + + +def test_roles_get(client): + assert client.get(api.url_for(RolesList)).status_code == 401 + + +def test_roles_post(client): + assert client.post(api.url_for(RolesList), {}).status_code == 401 + + +def test_roles_put(client): + assert client.put(api.url_for(RolesList), {}).status_code == 405 + + +def test_roles_delete(client): + assert client.delete(api.url_for(RolesList)).status_code == 405 + + +def test_roles_patch(client): + assert client.patch(api.url_for(RolesList), {}).status_code == 405 + + +def test_role_credentials_get(client): + assert client.get(api.url_for(RoleViewCredentials, role_id=1)).status_code == 401 + + +def test_role_credentials_post(client): + assert client.post(api.url_for(RoleViewCredentials, role_id=1), {}).status_code == 405 + + +def test_role_credentials_put(client): + assert client.put(api.url_for(RoleViewCredentials, role_id=1), {}).status_code == 405 + + +def test_role_credentials_delete(client): + assert client.delete(api.url_for(RoleViewCredentials, role_id=1)).status_code == 405 + + +def test_role_credentials_patch(client): + assert client.patch(api.url_for(RoleViewCredentials, role_id=1), {}).status_code == 405 + + +def test_user_roles_get(client): + assert client.get(api.url_for(UserRolesList, user_id=1)).status_code == 401 + + +def test_user_roles_post(client): + assert client.post(api.url_for(UserRolesList, user_id=1), {}).status_code == 405 + + +def test_user_roles_put(client): + assert client.put(api.url_for(UserRolesList, user_id=1), {}).status_code == 405 + + +def test_user_roles_delete(client): + assert client.delete(api.url_for(UserRolesList, user_id=1)).status_code == 405 + + +def test_user_roles_patch(client): + assert client.patch(api.url_for(UserRolesList, user_id=1), {}).status_code == 405 + + +def test_authority_roles_get(client): + assert client.get(api.url_for(AuthorityRolesList, authority_id=1)).status_code == 401 + + +def test_authority_roles_post(client): + assert client.post(api.url_for(AuthorityRolesList, authority_id=1), {}).status_code == 405 + + +def test_authority_roles_put(client): + assert client.put(api.url_for(AuthorityRolesList, authority_id=1), {}).status_code == 405 + + +def test_authority_roles_delete(client): + assert client.delete(api.url_for(AuthorityRolesList, authority_id=1)).status_code == 405 + + +def test_authority_roles_patch(client): + assert client.patch(api.url_for(AuthorityRolesList, authority_id=1), {}).status_code == 405 + + +VALID_USER_HEADER_TOKEN = { + 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'} + + +def test_auth_role_get(client): + assert client.get(api.url_for(Roles, role_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 400 + + +def test_auth_role_post_(client): + assert client.post(api.url_for(Roles, role_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_role_put(client): + assert client.put(api.url_for(Roles, role_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 400 + + +def test_auth_role_delete(client): + assert client.delete(api.url_for(Roles, role_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 403 + + +def test_auth_role_patch(client): + assert client.patch(api.url_for(Roles, role_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_roles_get(client): + assert client.get(api.url_for(RolesList), headers=VALID_USER_HEADER_TOKEN).status_code == 200 + + +def test_auth_roles_post(client): + assert client.post(api.url_for(RolesList), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 403 + + +def test_auth_role_credentials_get(client): + assert client.get(api.url_for(RoleViewCredentials, role_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 403 + + +def test_auth_role_credentials_post(client): + assert client.post(api.url_for(RoleViewCredentials, role_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_role_credentials_put(client): + assert client.put(api.url_for(RoleViewCredentials, role_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_role_credentials_delete(client): + assert client.delete(api.url_for(RoleViewCredentials, role_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_role_credentials_patch(client): + assert client.patch(api.url_for(RoleViewCredentials, role_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_user_roles_get(client): + assert client.get(api.url_for(UserRolesList, user_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200 + + +def test_auth_user_roles_post(client): + assert client.post(api.url_for(UserRolesList, user_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_user_roles_put(client): + assert client.put(api.url_for(UserRolesList, user_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_user_roles_delete(client): + assert client.delete(api.url_for(UserRolesList, user_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_user_roles_patch(client): + assert client.patch(api.url_for(UserRolesList, user_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_authority_roles_get(client): + assert client.get(api.url_for(AuthorityRolesList, authority_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200 + + +def test_auth_authority_roles_post(client): + assert client.post(api.url_for(AuthorityRolesList, authority_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_authority_roles_put(client): + assert client.put(api.url_for(AuthorityRolesList, authority_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_authority_roles_delete(client): + assert client.delete(api.url_for(AuthorityRolesList, authority_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_authority_roles_patch(client): + assert client.patch(api.url_for(AuthorityRolesList, authority_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +VALID_ADMIN_HEADER_TOKEN = { + 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'} + + +def test_admin_role_get(client): + assert client.get(api.url_for(Roles, role_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 + + +def test_admin_role_post(client): + assert client.post(api.url_for(Roles, role_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_role_put(client): + assert client.put(api.url_for(Roles, role_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 + + +def test_admin_role_delete(client): + assert client.delete(api.url_for(Roles, role_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 + + +def test_admin_role_patch(client): + assert client.patch(api.url_for(Roles, role_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_roles_get(client): + resp = client.get(api.url_for(RolesList), headers=VALID_ADMIN_HEADER_TOKEN) + assert resp.status_code == 200 + assert resp.json['total'] > 0 + + +def test_admin_role_credentials_get(client): + assert client.get(api.url_for(RolesList), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 + + +def test_admin_role_credentials_post(client): + assert client.post(api.url_for(RolesList), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 + + +def test_admin_role_credentials_put(client): + assert client.put(api.url_for(RolesList), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_role_credentials_delete(client): + assert client.delete(api.url_for(RolesList), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_role_credentials_patch(client): + assert client.patch(api.url_for(RolesList), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_user_roles_get(client): + assert client.get(api.url_for(UserRolesList, user_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 + + +def test_admin_user_roles_post(client): + assert client.post(api.url_for(UserRolesList, user_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_user_roles_put(client): + assert client.put(api.url_for(UserRolesList, user_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_user_roles_delete(client): + assert client.delete(api.url_for(UserRolesList, user_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_user_roles_patch(client): + assert client.patch(api.url_for(UserRolesList, user_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_authority_roles_get(client): + assert client.get(api.url_for(AuthorityRolesList, authority_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 + + +def test_admin_authority_roles_post(client): + assert client.post(api.url_for(AuthorityRolesList, authority_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_authority_roles_put(client): + assert client.put(api.url_for(AuthorityRolesList, authority_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_authority_roles_delete(client): + assert client.delete(api.url_for(AuthorityRolesList, authority_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_authority_roles_patch(client): + assert client.patch(api.url_for(AuthorityRolesList, authority_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_roles_crud(client): + assert client.post(api.url_for(RolesList), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 + data = {'name': 'role', 'description': 'test'} + resp = client.post(api.url_for(RolesList), data=dumps(data), content_type='application/json', headers=VALID_ADMIN_HEADER_TOKEN) + assert resp.status_code == 200 + role_id = resp.json['id'] + assert client.get(api.url_for(Roles, role_id=role_id), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 + resp = client.get(api.url_for(RolesList), headers=VALID_ADMIN_HEADER_TOKEN) + assert resp.status_code == 200 + assert resp.json['total'] == 2 + assert client.delete(api.url_for(Roles, role_id=role_id), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 + resp = client.get(api.url_for(RolesList), headers=VALID_ADMIN_HEADER_TOKEN) + assert resp.status_code == 200 + assert resp.json['total'] == 1 From 7ab3e27c79e45d6a853e22d0fe0068832a3a337b Mon Sep 17 00:00:00 2001 From: Kevin Glisson Date: Fri, 26 Jun 2015 16:16:13 -0700 Subject: [PATCH 11/31] Starting add certificate tests --- lemur/tests/certs.py | 183 ++++++++++++++ lemur/tests/test_certificates.py | 416 +++++++++++++++++++++++++------ 2 files changed, 517 insertions(+), 82 deletions(-) create mode 100644 lemur/tests/certs.py diff --git a/lemur/tests/certs.py b/lemur/tests/certs.py new file mode 100644 index 00000000..29e62705 --- /dev/null +++ b/lemur/tests/certs.py @@ -0,0 +1,183 @@ +from cryptography import x509 +from cryptography.hazmat.backends import default_backend + +INTERNAL_VALID_LONG_STR = """ +-----BEGIN CERTIFICATE----- +MIID1zCCAr+gAwIBAgIBATANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCVVMx +CzAJBgNVBAgMAkNBMRAwDgYDVQQHDAdBIHBsYWNlMRcwFQYDVQQDDA5sb25nLmxp +dmVkLmNvbTEQMA4GA1UECgwHRXhhbXBsZTETMBEGA1UECwwKT3BlcmF0aW9uczEe +MBwGCSqGSIb3DQEJARYPamltQGV4YW1wbGUuY29tMB4XDTE1MDYyNjIwMzA1MloX +DTQwMDEwMTIwMzA1MlowgYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEQMA4G +A1UEBwwHQSBwbGFjZTEXMBUGA1UEAwwObG9uZy5saXZlZC5jb20xEDAOBgNVBAoM +B0V4YW1wbGUxEzARBgNVBAsMCk9wZXJhdGlvbnMxHjAcBgkqhkiG9w0BCQEWD2pp +bUBleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKeg +sqb0HI10i2eRSx3pLeA7JoGdUpud7hy3bGws/1HgOSpRMin9Y65DEpVq2Ia9oir7 +XOJLpSTEIulnBkgDHNOsdKVYHDR6k0gUisnIKSl2C3IgKHpCouwiOvvVPwd3PExg +17+d7KLBIu8LpG28wkXKFU8vSz5i7H4i/XCEChnKJ4oGJuGAJJM4Zn022U156pco +97aEAc9ZXR/1dm2njr4XxCXmrnKCYTElfRhLkmxtv+mCi6eV//5d12z7mY3dTBkQ +EG2xpb5DQ+ITQ8BzsKcPX80rz8rTzgYFwaV3gUg38+bgka/JGJq8HgBuNnHv5CeT +1T/EoZTRYW2oPfOgQK8CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B +Af8EBAMCAQYwHQYDVR0OBBYEFIuDY73dQIhj2nnd4DG2SvseHVVaMA0GCSqGSIb3 +DQEBCwUAA4IBAQBk/WwfoWYdS0M8rz5tJda/cMdYFSugUbTn6JJdmHuw6RmiKzKG +8NzfSqBR6m8MWdSTuAZ/chsUZH9YEIjS9tAH9/FfUFBrsUE7TXaUgpNBm4DBLLfl +fj5xDmEyj17JPN/C36amQ9eU5BNesdCx9EkdWLyVJaM50HFRo71W0/FrpKZyKK68 +XPhd1z9w/xgfCfYhe7PjEmrmNPN5Tgk5TyXW+UUhOepDctAv2DBetptcx+gHrtW+ +Ygk1wptlt/tg7uUmstmXZA4vTPx83f4P3KSS3XHIYFIyGFWUDs23C20K6mmW1iXa +h0S8LN4iv/+vNFPNiM1z9X/SZgfbwZXrLsSi +-----END CERTIFICATE----- +""" +INTERNAL_VALID_LONG_CERT = x509.load_pem_x509_certificate(INTERNAL_VALID_LONG_STR, default_backend()) + + +INTERNAL_INVALID_STR = """ +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgICA+gwDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAlVT +MQswCQYDVQQIDAJDQTEQMA4GA1UEBwwHQSBwbGFjZTEXMBUGA1UEAwwObG9uZy5s +aXZlZC5jb20xEDAOBgNVBAoMB0V4YW1wbGUxEzARBgNVBAsMCk9wZXJhdGlvbnMx +HjAcBgkqhkiG9w0BCQEWD2ppbUBleGFtcGxlLmNvbTAeFw0xNTA2MjYyMDM2NDha +Fw0xNTA2MjcyMDM2NDhaMGkxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEQMA4G +A1UEBxMHQSBwbGFjZTEQMA4GA1UEChMHRXhhbXBsZTETMBEGA1UECxMKT3BlcmF0 +aW9uczEUMBIGA1UEAxMLZXhwaXJlZC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCcSMzRxB6+UONPqYMy1Ojw3Wi8DIpt9USnSR60I8LiEuRK2ayr +0RMjLJ6sBEgy/hISEqpLgTsciDpxwaTC/WNrkT9vaMcwfiG3V0Red8zbKHQzC+Ty +cLRg9wbC3v613kaIZCQCoE7Aouru9WbVPmuRoasfztrgksWmH9infQbL4TDcmcxo +qGaMn4ajQTVAD63CKnut+CULZIMBREBVlSTLiOO7qZdTrd+vjtLWvdXVPcWLSBrd +Vpu3YnhqqTte+DMzQHwY7A2s3fu4Cg4H4npzcR+0H1H/B5z64kxqZq9FWGIcZcz7 +0xXeHN9UUKPDSTgsjtIzKTaIOe9eML3jGSU7AgMBAAGjgaIwgZ8wDAYDVR0TAQH/ +BAIwADAOBgNVHQ8BAf8EBAMCBaAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwEwHQYD +VR0OBBYEFKwBYaxCLxK0csmV319rbRdqDllWMEgGA1UdHwRBMD8wPaA7oDmGN2h0 +dHA6Ly90ZXN0LmNsb3VkY2EuY3JsLm5ldGZsaXguY29tL2xvbmdsaXZlZENBL2Ny +bC5wZW0wDQYJKoZIhvcNAQELBQADggEBADFngqsMsGnNBWknphLDvnoWu5MTrpsD +AgN0bktv5ACKRWhi/qtCmkEf6TieecRMwpQNMpE50dko3LGGdWlZRCI8wdH/zrw2 +8MnOeCBxuS1nB4muUGjbf4LIbtuwoHSESrkfmuKjGGK9JTszLL6Hb9YnoFefeg8L +T7W3s8mm5bVHhQM7J9tV6dz/sVDmpOSuzL8oZkqeKP+lWU6ytaohFFpbdzaxWipU +3+GobVe4vRqoF1kwuhQ8YbMbXWDK6zlrT9pjFABcQ/b5nveiW93JDQUbjmVccx/u +kP+oGWtHvhteUAe8Gloo5NchZJ0/BqlYRCD5aAHcmbXRsDid9mO4ADU= +-----END CERTIFICATE----- +""" +INTERNAL_INVALID_CERT = x509.load_pem_x509_certificate(INTERNAL_INVALID_STR, default_backend()) + + +INTERNAL_VALID_SAN_STR = """ +-----BEGIN CERTIFICATE----- +MIIESjCCAzKgAwIBAgICA+kwDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAlVT +MQswCQYDVQQIDAJDQTEQMA4GA1UEBwwHQSBwbGFjZTEXMBUGA1UEAwwObG9uZy5s +aXZlZC5jb20xEDAOBgNVBAoMB0V4YW1wbGUxEzARBgNVBAsMCk9wZXJhdGlvbnMx +HjAcBgkqhkiG9w0BCQEWD2ppbUBleGFtcGxlLmNvbTAeFw0xNTA2MjYyMDU5MDZa +Fw0yMDAxMDEyMDU5MDZaMG0xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEQMA4G +A1UEBxMHQSBwbGFjZTEQMA4GA1UEChMHRXhhbXBsZTETMBEGA1UECxMKT3BlcmF0 +aW9uczEYMBYGA1UEAxMPc2FuLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA2Nq5zFh2WiqtNIPssdSwQ9/00j370VcKPlOATLqK24Q+ +dr2hWP1WlZJ0NOoPefhoIysccs2tRivosTpViRAzNJXigBHhxe8ger0QhVW6AXIp +ov327N689TgY4GzRrwqavjz8cqussIcnEUr4NLLsU5AvXE7e3WxYkkskzO497UOI +uCBtWdCXZ4cAGhtVkkA5uQHfPsLmgRVoUmdMDt5ZmA8HhLX4X6vkT3oGIhdGCw6T +W+Cu7PfYlSaggSBbBniU0YKTFLfGLkYFZN/b6bxzvt6CTJLoVFAYXyLJwUvd3EAm +u23HgUflIyZNG3xVPml/lah0OIX7RtSigXUSLm7lYwIDAQABo4HTMIHQMAwGA1Ud +EwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMB +MC8GA1UdEQQoMCaCEWV4YW1wbGUyLmxvbmcuY29tghFleGFtcGxlMy5sb25nLmNv +bTAdBgNVHQ4EFgQUiiIyclcBIfJ5PE3OCcTXwzJAM+0wSAYDVR0fBEEwPzA9oDug +OYY3aHR0cDovL3Rlc3QuY2xvdWRjYS5jcmwubmV0ZmxpeC5jb20vbG9uZ2xpdmVk +Q0EvY3JsLnBlbTANBgkqhkiG9w0BAQsFAAOCAQEAgcTioq70B/aPWovNTy+84wLw +VX1q6bCdH3FJwAv2rc28CHp5mCGdR6JqfT/H/CbfRwT1Yh/5i7T5kEVyz+Dp3+p+ +AJ2xauHrTvWn0QHQYbUWICwkuZ7VTI9nd0Fry1FQI1EeKiCmyrzNljiN2l+GZw6i +NJUpVNtwRyWRzB+yIx2E9wyydqDFH+sROuQok7EgzlQileitPrF4RrkfIhQp2/ki +YBrY/duF15YpoMKAlFhDBh6R9/nb5kI2n3pY6I5h6LEYfLStazXbIu61M8zu9TM/ ++t5Oz6rmcjohL22+sEmmRz86dQZlrBBUxX0kCQj6OAFB4awtRd4fKtkCkZhvhQ== +-----END CERTIFICATE----- +""" +INTERNAL_VALID_SAN_CERT = x509.load_pem_x509_certificate(INTERNAL_VALID_SAN_STR, default_backend()) + + +INTERNAL_VALID_WILDCARD_STR = """ +-----BEGIN CERTIFICATE----- +MIIEHDCCAwSgAwIBAgICA+owDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAlVT +MQswCQYDVQQIDAJDQTEQMA4GA1UEBwwHQSBwbGFjZTEXMBUGA1UEAwwObG9uZy5s +aXZlZC5jb20xEDAOBgNVBAoMB0V4YW1wbGUxEzARBgNVBAsMCk9wZXJhdGlvbnMx +HjAcBgkqhkiG9w0BCQEWD2ppbUBleGFtcGxlLmNvbTAeFw0xNTA2MjYyMTEzMTBa +Fw0yMDAxMDEyMTEzMTBaMHAxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEQMA4G +A1UEBxMHQSBwbGFjZTEQMA4GA1UEChMHRXhhbXBsZTETMBEGA1UECxMKT3BlcmF0 +aW9uczEbMBkGA1UEAxQSKi50ZXN0LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA0T7OEY9FxMIdhe1CwLc+TbDeSfDN6KRHlp0I9MwK +3Pre7A1+1vmRzLiS5qAdOh3Oexelmgdkn/fZUFI+IqEVJwmeUiq13Kib3BFnVtbB +N1RdT7rZF24Bqwygf1DHAekEBYdvu4dGD/gYKsLYsSMD7g6glUuhTbgR871updcV +USYJ801y640CcHjai8UCLxpqtkP/Alob+/KDczUHbhdxYgmH34aQgxC8zg+uzuq6 +bIqUAc6SctI+6ArXOqri7wSMgZUnogpF4R5QbCnlDfSzNcNxJFtGp8cy7CNWebMd +IWgBYwee8i8S6Q90B2QUFD9EGG2pEZldpudTxWUpq0tWmwIDAQABo4GiMIGfMAwG +A1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMBYGA1UdJQEB/wQMMAoGCCsGAQUF +BwMBMB0GA1UdDgQWBBTH2KIECrqPHMbsVysGv7ggkYYZGDBIBgNVHR8EQTA/MD2g +O6A5hjdodHRwOi8vdGVzdC5jbG91ZGNhLmNybC5uZXRmbGl4LmNvbS9sb25nbGl2 +ZWRDQS9jcmwucGVtMA0GCSqGSIb3DQEBCwUAA4IBAQBjjfur2B6BcdIQIouwhXGk +IFE5gUYMK5S8Crf/lpMxwHdWK8QM1BpJu9gIo6VoM8uFVa8qlY8LN0SyNyWw+qU5 +Jc8X/qCeeJwXEyXY3dIYRT/1aj7FCc7EFn1j6pcHPD6/0M2z0Zmj+1rWNBJdcYor +pCy27OgRoJKZ6YhEYekzwIPeFPL6irIN9xKPnfH0b2cnYa/g56DyGmyKH2Kkhz0A +UGniiUh4bAUuppbtSIvUTsRsJuPYOqHC3h8791JZ/3Sr5uB7QbCdz9K14c9zi6Z1 +S0Xb3ZauZJQI7OdHeUPDRVq+8hcG77sopN9pEYrIH08oxvLX2US3GqrowjOxthRa +-----END CERTIFICATE----- +""" +INTERNAL_VALID_WILDCARD_CERT = x509.load_pem_x509_certificate(INTERNAL_VALID_WILDCARD_STR, default_backend()) + + +EXTERNAL_VALID_STR = """ +-----BEGIN CERTIFICATE----- +MIIFHzCCBAegAwIBAgIQGFWCciDWzbOej/TbAJN0WzANBgkqhkiG9w0BAQsFADCB +pDELMAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8w +HQYDVQQLExZGT1IgVEVTVCBQVVJQT1NFUyBPTkxZMR8wHQYDVQQLExZTeW1hbnRl +YyBUcnVzdCBOZXR3b3JrMTQwMgYDVQQDEytTeW1hbnRlYyBDbGFzcyAzIFNlY3Vy +ZSBTZXJ2ZXIgVEVTVCBDQSAtIEc0MB4XDTE1MDYyNDAwMDAwMFoXDTE1MDYyNTIz +NTk1OVowgYMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDQUxJRk9STklBMRIwEAYD +VQQHDAlMb3MgR2F0b3MxFjAUBgNVBAoMDU5ldGZsaXgsIEluYy4xEzARBgNVBAsM +Ck9wZXJhdGlvbnMxHjAcBgNVBAMMFXR0dHQyLm5ldGZsaXh0ZXN0Lm5ldDCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALwMY/yod9YGLKLCzbbsSUBWm4ZC +DfcgbUNL3JLtZaFCaOeUPLa4YNqty+9ACXBLYPNMm+dgsRHix8N2uwtZrGazHILK +qey96eSTosPsvKFt0KLNpUl8GC/YxA69L128SJgFaaq5Dr2Mp3NP0rt0RIz5luPj +Oae0hkGOS8uS0dySlAmfOw2OsJY3gCw5UHcmpcCHpO2f7uU+tWKmgfz4U/PpQ0kz +WVJno+JhcaXIximtiLreCNF1LpraAjrcZJ+ySJwYaLaYMiJoFkdXUtKJcyqmkbA3 +Splt7N4Hb8c+5aXv225uQYCh0HXQeMyBotlaIrAddP5obrtjxhXBxB4ysEcCAwEA +AaOCAWowggFmMCAGA1UdEQQZMBeCFXR0dHQyLm5ldGZsaXh0ZXN0Lm5ldDAJBgNV +HRMEAjAAMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB +BQUHAwIwYQYDVR0gBFowWDBWBgZngQwBAgIwTDAjBggrBgEFBQcCARYXaHR0cHM6 +Ly9kLnN5bWNiLmNvbS9jcHMwJQYIKwYBBQUHAgIwGRoXaHR0cHM6Ly9kLnN5bWNi +LmNvbS9ycGEwHwYDVR0jBBgwFoAUNI9UtT8KH1K6nLJl7bqLCGcZ4AQwKwYDVR0f +BCQwIjAgoB6gHIYaaHR0cDovL3NzLnN5bWNiLmNvbS9zcy5jcmwwVwYIKwYBBQUH +AQEESzBJMB8GCCsGAQUFBzABhhNodHRwOi8vc3Muc3ltY2QuY29tMCYGCCsGAQUF +BzAChhpodHRwOi8vc3Muc3ltY2IuY29tL3NzLmNydDANBgkqhkiG9w0BAQsFAAOC +AQEAQuIfyBltvCZ9orqNdS6PUo2PaeUgJzkmdDwbDVd7rTwbZIwGZXZjeKseqMSb +L+r/jN6DWrScVylleiz0N/D0lSUhC609dQKuicGpy3yQaXwhfYZ6duxrW3Ii/+Vz +pFv7DnG3JPZjIXCmVhQVIv/8oaV0bfUF/1mrWRFwZiBILxa7iaycRhjusJEVRtzN +Ot/qkLluHO0wbEHnASV4P9Y5NuR/bliuFS/DeRczofNS78jJuZrGvl2AqS/19Hvm +Bs63gULVCqWygt5KEbv990m/XGuRMaXuHzHCHB4v5LRM30FiFmqCzyD8d+btzW9B +1hZ5s3rj+a6UwvpinKJoPfgkgg== +-----END CERTIFICATE----- +""" +EXTERNAL_CERT = x509.load_pem_x509_certificate(EXTERNAL_VALID_STR, default_backend()) + + +PRIVATE_KEY_STR = """ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAnEjM0cQevlDjT6mDMtTo8N1ovAyKbfVEp0ketCPC4hLkStms +q9ETIyyerARIMv4SEhKqS4E7HIg6ccGkwv1ja5E/b2jHMH4ht1dEXnfM2yh0Mwvk +8nC0YPcGwt7+td5GiGQkAqBOwKLq7vVm1T5rkaGrH87a4JLFph/Yp30Gy+Ew3JnM +aKhmjJ+Go0E1QA+twip7rfglC2SDAURAVZUky4jju6mXU63fr47S1r3V1T3Fi0ga +3Vabt2J4aqk7XvgzM0B8GOwNrN37uAoOB+J6c3EftB9R/wec+uJMamavRVhiHGXM ++9MV3hzfVFCjw0k4LI7SMyk2iDnvXjC94xklOwIDAQABAoIBAGeykly5MeD70OgB +xPEMfoebkav88jklnekVxk6mz9+rw1i6+CyFLJqRN7NRoApdtOXTBrXUyMEUzxq9 +7zIGaVptZNbqggh2GK8LM20vNnlQbVGVmdMX30fbgNv6lK1eEBTdxVsMvVRqhVIK ++LGTmlJmICKZ4XdTS9v/k4UGm2TZPCt2pvrNzIpT7TIm2QybCbZoOPY8SHx0U8c5 +lmtdqmIsy2JPNSOsOCiJgzQIvkR/fMGWFgNE4fEHsHAfubgpK97TGzwLiFRmlTb+ +QUDaz0YbwhF+5bQjHtaGUGATcg5bvV1UWBUvp+g4gRIfwzG+3PAGacYE/djouAdG +PHbxuCkCgYEAz/LsgMgsaV3arlounviSwc8wG9WcI5gbYw5qwX0P57ZoxS7EBAGu +yYtudurJrU9SfsSV44GL11UzBcAGOeS0btddrcMiNBhc7fY7P/1xaufQ3GjG06/v +kH4gOjzsGSTJliZ709g4J6hnMCxz0O0PS31Qg5cBD8UG8xO7/AV0is0CgYEAwGWy +A6YPinpZuenaxrivM5AcVDWmj7aeC29M63l/GY+O5LQH2PKVESH0vL5PvG3LkrCR +SUbaMKdKR0wnZsJ89z21eZ54ydUgj41bZJczl8drxcY0GSajj6XZXGTUjtoVrWsB +A0kJbjsrpd+8J316Y9iCgpopmbVd965pUHe4ACcCgYAamJlDB1cWytgzQHmB/4zV +mOgwRyvHKacnDir9QD+OhTf1MDwFvylZwamJMBJHRkPozr/U7zaxfcYe0CZ7tRKW +spjapoBzZUJNdRay4nllEO0Xo5b6cCAVvOvmRvBzbs8Rky53M8pK2DEKakUNzaQN +JaPskJ2kJLD02etLGm+DaQKBgQCTI/NNmQ2foUzHw1J+0jWjoJ4ZxOI6XLZoFlnk +aInMuZ7Vx92MjJF2hdqPEpkWiX28FO839EjgFsDW4CXuD+XUjEwi1BCagzWgs8Hm +n0Bk3q3MlnW3mnZSYMtoPvDUw3L6qrAenBfrRrNt6zsRlIQqoiXFzjLsi+luh+Oh +F74P1wKBgQCPQGKLUcfAvjIcZp4ECH0K8sBEmoEf8pceuALZ3H5vneYDzqMDIceo +t5Gpocpt77LJnNiszXSerj/KjX2MflY5xUXeekWowLVTBOK5+CZ8+XBIgBt1hIG3 +XKxcRgm/Va4QMEAnec0qXfdTVJaJiAW0bdKwKRRrrbwcTdNRGibdng== +-----END RSA PRIVATE KEY----- +""" \ No newline at end of file diff --git a/lemur/tests/test_certificates.py b/lemur/tests/test_certificates.py index e24251c5..3201287a 100644 --- a/lemur/tests/test_certificates.py +++ b/lemur/tests/test_certificates.py @@ -1,85 +1,337 @@ -import os -import shutil +import pytest +from lemur.certificates.views import * -import boto -from moto import mock_iam, mock_sts, mock_s3 - -from lemur.tests import LemurTestCase +#def test_crud(session): +# role = create('role1') +# assert role.id > 0 +# +# role = update(role.id, 'role_new', None, []) +# assert role.name == 'role_new' +# delete(role.id) +# assert get(role.id) == None -#class CertificateTestCase(LemurTestCase): -# def test_create_challenge(self): -# from lemur.certificates.service import create_challenge -# self.assertTrue(len(create_challenge()) >= 24) -# -# def test_hash_domains(self): -# from lemur.certificates.service import hash_domains -# h = hash_domains(['netflix.com', 'www.netflix.com', 'movies.netflix.com']) -# self.assertEqual('c9c83253b46c7c1245c100ed3f7045eb', h) -# -# def test_create_csr(self): -# from lemur.certificates.service import create_csr -# from lemur.tests.test_csr import TEST_CSR -# path = create_csr(['netflix.com'], TEST_CSR) -# files = len(os.listdir(path)) -# self.assertEqual(files, 4) -# shutil.rmtree(path) -# -# def test_create_san_csr(self): -# from lemur.certificates.service import create_csr -# from lemur.tests.test_csr import TEST_CSR -# path = create_csr(['netflix.com', 'www.netflix.com'], TEST_CSR) -# files = len(os.listdir(path)) -# self.assertEqual(files, 4) -# shutil.rmtree(path) -# -# def test_create_path(self): -# from lemur.certificates.service import create_path -# path = create_path("blah") -# self.assertIn('blah', path) -# shutil.rmtree(path) -# -# @mock_s3 -# @mock_sts -# @mock_iam -# def test_save_cert(self): -# from lemur.certificates.service import save_cert -# from lemur.common.services.aws.iam import get_all_server_certs -# conn = boto.connect_s3() -# bucket = conn.create_bucket(app.config.get('S3_BUCKET')) -# cert = save_cert(TEST_CERT, TEST_KEY, None, "blah", "blah", [1]) -# count = 0 -# for key in bucket.list(): -# count += 1 -# -# self.assertEqual(count, 4) -# certs = get_all_server_certs('1111') -# self.assertEqual(len(certs), 1) -# -## @mock_s3 -## @mock_sts -## @mock_iam -## def test_upload_cert(self): -## from lemur.certificates.service import upload -## from lemur.common.services.aws.iam import get_all_server_certs -## conn = boto.connect_s3() -## bucket = conn.create_bucket(app.config.get('S3_BUCKET')) -## -## cert_up = {"public_cert": TEST_CERT, "private_key": TEST_KEY, "owner": "test@example.com", "accounts_ids": ['1111']} -## -## cert_name = upload(**cert_up) -## valid_name = 'AHB-dfdsflkj.net-NetflixInc-20140525-20150525' -## self.assertEqual(cert_name, valid_name) -## -## app.logger.debug(cert_name) -## count = 0 -## -## for key in bucket.list(): -## count += 1 -## -## self.assertEqual(count, 2) -## certs = get_all_server_certs('179727101194') -## self.assertEqual(len(certs), 1) -## -## -## +def test_valid_authority(session): + assert 1 == 2 + + +def test_pem_str(): + from lemur.tests.certs import INTERNAL_VALID_LONG_STR + assert pem_str(INTERNAL_VALID_LONG_STR, 'test') == INTERNAL_VALID_LONG_STR + + with pytest.raises(ValueError): + pem_str('sdfsdfds', 'test') + + +def test_private_key_str(): + from lemur.tests.certs import PRIVATE_KEY_STR + assert private_key_str(PRIVATE_KEY_STR, 'test') == PRIVATE_KEY_STR + + with pytest.raises(ValueError): + private_key_str('dfsdfsdf', 'test') + + +def test_create_csr(): + assert 1 == 2 + + +def test_create_path(): + assert 1 == 2 + + +def test_load_ssl_pack(): + assert 1 == 2 + + +def test_delete_ssl_path(): + assert 1 == 2 + + +def test_import_certificate(session): + assert 1 == 2 + + +def test_mint(): + assert 1 == 2 + + +def test_disassociate_aws_account(): + assert 1 == 2 + + +def test_cert_get_cn(): + from lemur.tests.certs import INTERNAL_VALID_LONG_CERT + from lemur.certificates.models import cert_get_cn + + assert cert_get_cn(INTERNAL_VALID_LONG_CERT) == 'long.lived.com' + + +def test_cert_get_domains(): + from lemur.tests.certs import INTERNAL_VALID_SAN_CERT, INTERNAL_VALID_LONG_CERT + from lemur.certificates.models import cert_get_domains + + assert cert_get_domains(INTERNAL_VALID_LONG_CERT) == ['long.lived.com'] + assert cert_get_domains(INTERNAL_VALID_SAN_CERT) == ['example2.long.com', 'example3.long.com', 'san.example.com'] + + +def test_cert_is_san(): + from lemur.tests.certs import INTERNAL_VALID_SAN_CERT, INTERNAL_VALID_LONG_CERT + from lemur.certificates.models import cert_is_san + + assert cert_is_san(INTERNAL_VALID_LONG_CERT) == False + assert cert_is_san(INTERNAL_VALID_SAN_CERT) == True + + +def test_cert_is_wildcard(): + from lemur.tests.certs import INTERNAL_VALID_WILDCARD_CERT, INTERNAL_VALID_LONG_CERT + from lemur.certificates.models import cert_is_wildcard + assert cert_is_wildcard(INTERNAL_VALID_WILDCARD_CERT) == True + assert cert_is_wildcard(INTERNAL_VALID_LONG_CERT) == False + + +def test_cert_get_bitstrength(): + from lemur.tests.certs import INTERNAL_VALID_LONG_CERT + from lemur.certificates.models import cert_get_bitstrength + assert cert_get_bitstrength(INTERNAL_VALID_LONG_CERT) == 2048 + +def test_cert_get_issuer(): + from lemur.tests.certs import INTERNAL_VALID_LONG_CERT + from lemur.certificates.models import cert_get_issuer + assert cert_get_issuer(INTERNAL_VALID_LONG_CERT) == 'Example' + + +def test_get_name_from_arn(): + from lemur.certificates.models import get_name_from_arn + arn = 'arn:aws:iam::11111111:server-certificate/mycertificate' + assert get_name_from_arn(arn) == 'mycertificate' + + +def test_get_account_number(): + from lemur.certificates.models import get_account_number + arn = 'arn:aws:iam::11111111:server-certificate/mycertificate' + assert get_account_number(arn) == '11111111' + + +def test_create_name(): + from lemur.certificates.models import create_name + from datetime import datetime + assert create_name( + 'Example Inc,', + datetime(2015, 5, 7, 0, 0, 0), + datetime(2015, 5, 12, 0, 0, 0), + 'example.com', + False + ) == 'example.com-ExampleInc-20150507-20150512' + assert create_name( + 'Example Inc,', + datetime(2015, 5, 7, 0, 0, 0), + datetime(2015, 5, 12, 0, 0, 0), + 'example.com', + True + ) == 'SAN-example.com-ExampleInc-20150507-20150512' + +def test_is_expired(): + assert 1 == 2 + + +def test_certificate_get(client): + assert client.get(api.url_for(Certificates, certificate_id=1)).status_code == 401 + + +def test_certificate_post(client): + assert client.post(api.url_for(Certificates, certificate_id=1), {}).status_code == 405 + + +def test_certificate_put(client): + assert client.put(api.url_for(Certificates, certificate_id=1), {}).status_code == 401 + + +def test_certificate_delete(client): + assert client.delete(api.url_for(Certificates, certificate_id=1)).status_code == 405 + + +def test_certificate_patch(client): + assert client.patch(api.url_for(Certificates, certificate_id=1), {}).status_code == 405 + + +def test_certificates_get(client): + assert client.get(api.url_for(CertificatesList)).status_code == 401 + + +def test_certificates_post(client): + assert client.post(api.url_for(CertificatesList), {}).status_code == 401 + + +def test_certificates_put(client): + assert client.put(api.url_for(CertificatesList), {}).status_code == 405 + + +def test_certificates_delete(client): + assert client.delete(api.url_for(CertificatesList)).status_code == 405 + + +def test_certificates_patch(client): + assert client.patch(api.url_for(CertificatesList), {}).status_code == 405 + + +def test_certificate_credentials_get(client): + assert client.get(api.url_for(CertificatePrivateKey, certificate_id=1)).status_code == 401 + + +def test_certificate_credentials_post(client): + assert client.post(api.url_for(CertificatePrivateKey, certificate_id=1), {}).status_code == 405 + + +def test_certificate_credentials_put(client): + assert client.put(api.url_for(CertificatePrivateKey, certificate_id=1), {}).status_code == 405 + + +def test_certificate_credentials_delete(client): + assert client.delete(api.url_for(CertificatePrivateKey, certificate_id=1)).status_code == 405 + + +def test_certificate_credentials_patch(client): + assert client.patch(api.url_for(CertificatePrivateKey, certificate_id=1), {}).status_code == 405 + + +def test_certificates_upload_get(client): + assert client.get(api.url_for(CertificatesUpload)).status_code == 405 + + +def test_certificates_upload_post(client): + assert client.post(api.url_for(CertificatesUpload), {}).status_code == 401 + + +def test_certificates_upload_put(client): + assert client.put(api.url_for(CertificatesUpload), {}).status_code == 405 + + +def test_certificates_upload_delete(client): + assert client.delete(api.url_for(CertificatesUpload)).status_code == 405 + + +def test_certificates_upload_patch(client): + assert client.patch(api.url_for(CertificatesUpload), {}).status_code == 405 + + +VALID_USER_HEADER_TOKEN = { + 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'} + + +def test_auth_certificate_get(client): + assert client.get(api.url_for(Certificates, certificate_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200 + + +def test_auth_certificate_post_(client): + assert client.post(api.url_for(Certificates, certificate_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_certificate_put(client): + assert client.put(api.url_for(Certificates, certificate_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 400 + + +def test_auth_certificate_delete(client): + assert client.delete(api.url_for(Certificates, certificate_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_certificate_patch(client): + assert client.patch(api.url_for(Certificates, certificate_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_certificates_get(client): + assert client.get(api.url_for(CertificatesList), headers=VALID_USER_HEADER_TOKEN).status_code == 200 + + +def test_auth_certificates_post(client): + assert client.post(api.url_for(CertificatesList), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 400 + + +def test_auth_certificate_credentials_get(client): + assert client.get(api.url_for(CertificatePrivateKey, certificate_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 404 + + +def test_auth_certificate_credentials_post(client): + assert client.post(api.url_for(CertificatePrivateKey, certificate_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_certificate_credentials_put(client): + assert client.put(api.url_for(CertificatePrivateKey, certificate_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_certificate_credentials_delete(client): + assert client.delete(api.url_for(CertificatePrivateKey, certificate_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_certificate_credentials_patch(client): + assert client.patch(api.url_for(CertificatePrivateKey, certificate_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_certificates_upload_get(client): + assert client.get(api.url_for(CertificatesUpload), headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_certificates_upload_post(client): + assert client.post(api.url_for(CertificatesUpload), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 400 + + +def test_auth_certificates_upload_put(client): + assert client.put(api.url_for(CertificatesUpload), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_certificates_upload_delete(client): + assert client.delete(api.url_for(CertificatesUpload), headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_certificates_upload_patch(client): + assert client.patch(api.url_for(CertificatesUpload), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +VALID_ADMIN_HEADER_TOKEN = { + 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'} + + +def test_admin_certificate_get(client): + assert client.get(api.url_for(Certificates, certificate_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 + + +def test_admin_certificate_post(client): + assert client.post(api.url_for(Certificates, certificate_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_certificate_put(client): + assert client.put(api.url_for(Certificates, certificate_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 + + +def test_admin_certificate_delete(client): + assert client.delete(api.url_for(Certificates, certificate_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_certificate_patch(client): + assert client.patch(api.url_for(Certificates, certificate_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_certificates_get(client): + resp = client.get(api.url_for(CertificatesList), headers=VALID_ADMIN_HEADER_TOKEN) + assert resp.status_code == 200 + assert resp.json['total'] == 0 + + +def test_admin_certificate_credentials_get(client): + assert client.get(api.url_for(CertificatePrivateKey, certificate_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 404 + + +def test_admin_certificate_credentials_post(client): + assert client.post(api.url_for(CertificatePrivateKey, certificate_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_certificate_credentials_put(client): + assert client.put(api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_certificate_credentials_delete(client): + assert client.delete(api.url_for(CertificatePrivateKey, certificate_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_certificate_credentials_patch(client): + assert client.patch(api.url_for(CertificatePrivateKey, certificate_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + From 096d88bc9b148dbe9f15c6919b9e2e3871a457af Mon Sep 17 00:00:00 2001 From: Kevin Glisson Date: Fri, 26 Jun 2015 16:17:22 -0700 Subject: [PATCH 12/31] Ensuring a 404 is returned when we can't find the specified certificate --- lemur/certificates/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lemur/certificates/views.py b/lemur/certificates/views.py index 4bc317f1..1312d665 100644 --- a/lemur/certificates/views.py +++ b/lemur/certificates/views.py @@ -437,6 +437,9 @@ class CertificatePrivateKey(AuthenticatedResource): :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, hasattr(role, 'id')) From bc0f9534c23924478366d93abe202b381552fce5 Mon Sep 17 00:00:00 2001 From: Kevin Glisson Date: Fri, 26 Jun 2015 16:18:31 -0700 Subject: [PATCH 13/31] Refactoring 'create_name' out of our certificate class, fixed an issuer were key size was being calculated and removing unused functions --- lemur/certificates/models.py | 116 +++++++++++++---------------------- 1 file changed, 43 insertions(+), 73 deletions(-) diff --git a/lemur/certificates/models.py b/lemur/certificates/models.py index 1f7ffc0c..47af84e5 100644 --- a/lemur/certificates/models.py +++ b/lemur/certificates/models.py @@ -26,6 +26,36 @@ from lemur.constants import SAN_NAMING_TEMPLATE, DEFAULT_NAMING_TEMPLATE, NONSTA from lemur.models import certificate_associations, certificate_account_associations +def create_name(issuer, not_before, not_after, common_name, san): + """ + Create a name for our certificate. A naming standard + is based on a series of templates. The name includes + useful information such as Common Name, Validation dates, + and Issuer. + + :rtype : str + :return: + """ + delchars = ''.join(c for c in map(chr, range(256)) if not c.isalnum()) + # aws doesn't allow special chars + subject = common_name.replace('*', "WILDCARD") + issuer = issuer.translate(None, delchars) + + if san: + t = SAN_NAMING_TEMPLATE + else: + t = DEFAULT_NAMING_TEMPLATE + + temp = t.format( + subject=subject, + issuer=issuer, + not_before=not_before.strftime('%Y%m%d'), + not_after=not_after.strftime('%Y%m%d') + ) + + return temp + + def cert_get_cn(cert): """ Attempts to get a sane common name from a given certificate. @@ -33,12 +63,9 @@ def cert_get_cn(cert): :param cert: :return: Common name or None """ - try: - return cert.subject.get_attributes_for_oid( - x509.OID_COMMON_NAME - )[0].value.strip() - except Exception as e: - current_app.logger.error("Unable to get CN! {0}".format(e)) + return cert.subject.get_attributes_for_oid( + x509.OID_COMMON_NAME + )[0].value.strip() def cert_get_domains(cert): @@ -48,17 +75,21 @@ def cert_get_domains(cert): return the common name. :param cert: - :return: List of domains + :return: List of domainss """ domains = [] try: ext = cert.extensions.get_extension_for_oid(x509.OID_SUBJECT_ALTERNATIVE_NAME) - entries = ext.get_values_for(x509.DNSName) + entries = ext.value.get_values_for_type(x509.DNSName) for entry in entries: - domains.append(entry.split(":")[1].strip(", ")) + domains.append(entry) except Exception as e: current_app.logger.warning("Failed to get SubjectAltName: {0}".format(e)) - domains.append(cert_get_cn(cert)) + + # do a simple check to make sure it's a real domain + common_name = cert_get_cn(cert) + if '.' in common_name: + domains.append(common_name) return domains @@ -106,7 +137,7 @@ def cert_get_bitstrength(cert): :param cert: :return: Integer """ - return cert.public_key().key_size * 8 + return cert.public_key().key_size def cert_get_issuer(cert): @@ -124,20 +155,6 @@ def cert_get_issuer(cert): current_app.logger.error("Unable to get issuer! {0}".format(e)) -def cert_is_internal(cert): - """ - Uses an internal resource in order to determine if - a given certificate was issued by an 'internal' certificate - authority. - - :param cert: - :return: Bool - """ - if cert_get_issuer(cert) in current_app.config.get('INTERNAL_CA', []): - return True - return False - - def cert_get_not_before(cert): """ Gets the naive datetime of the certificates 'not_before' field. @@ -225,49 +242,11 @@ class Certificate(db.Model): self.san = cert_is_san(cert) self.not_before = cert_get_not_before(cert) self.not_after = cert_get_not_after(cert) - self.name = self.create_name + self.name = create_name(self.issuer, self.not_before, self.not_after, self.cn, self.san) for domain in cert_get_domains(cert): self.domains.append(Domain(name=domain)) - @property - def create_name(self): - """ - Create a name for our certificate. A naming standard - is based on a series of templates. The name includes - useful information such as Common Name, Validation dates, - and Issuer. - - :rtype : str - :return: - """ - # aws doesn't allow special chars - if self.cn: - subject = self.cn.replace('*', "WILDCARD") - - if self.san: - t = SAN_NAMING_TEMPLATE - else: - t = DEFAULT_NAMING_TEMPLATE - - temp = t.format( - subject=subject, - issuer=self.issuer, - not_before=self.not_before.strftime('%Y%m%d'), - not_after=self.not_after.strftime('%Y%m%d') - ) - - else: - t = NONSTANDARD_NAMING_TEMPLATE - - temp = t.format( - issuer=self.issuer, - not_before=self.not_before.strftime('%Y%m%d'), - not_after=self.not_after.strftime('%Y%m%d') - ) - - return temp - @property def is_expired(self): if self.not_after < datetime.datetime.now(): @@ -298,12 +277,3 @@ class Certificate(db.Model): def as_dict(self): return {c.name: getattr(self, c.name) for c in self.__table__.columns} - def serialize(self): - blob = self.as_dict() - # TODO this should be done with relationships - user = user_service.get(self.user_id) - if user: - blob['creator'] = user.username - - return blob - From 9def00d1a2edfc4e66e06197724571b40d21103a Mon Sep 17 00:00:00 2001 From: Kevin Glisson Date: Mon, 29 Jun 2015 12:36:27 -0700 Subject: [PATCH 14/31] Adding basic authority tests. --- lemur/authorities/service.py | 9 +- lemur/authorities/views.py | 6 +- lemur/tests/test_authorities.py | 163 ++++++++++++++++++++++++++++++++ 3 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 lemur/tests/test_authorities.py diff --git a/lemur/authorities/service.py b/lemur/authorities/service.py index d7d82a48..84861665 100644 --- a/lemur/authorities/service.py +++ b/lemur/authorities/service.py @@ -57,11 +57,14 @@ def create(kwargs): cert = cert_service.save_cert(cert_body, None, intermediate, None, None, None) cert.user = g.current_user - # we create and attach any roles that cloudCA gives us + # we create and attach any roles that the issuer gives us role_objs = [] for r in issuer_roles: - role = role_service.create(r['name'], password=r['password'], description="CloudCA auto generated role", - username=r['username']) + role = role_service.create( + r['name'], + password=r['password'], + description="{0} auto generated role".format(kwargs.get('pluginName')), + username=r['username']) # the user creating the authority should be able to administer it if role.username == 'admin': g.current_user.roles.append(role) diff --git a/lemur/authorities/views.py b/lemur/authorities/views.py index e973a10c..cb1797ed 100644 --- a/lemur/authorities/views.py +++ b/lemur/authorities/views.py @@ -365,7 +365,11 @@ class CertificateAuthority(AuthenticatedResource): :statuscode 200: no error :statuscode 403: unauthenticated """ - return certificate_service.get(certificate_id).authority + cert = certificate_service.get(certificate_id) + if not cert: + return dict(message="Certificate not found"), 404 + + return cert.authority api.add_resource(AuthoritiesList, '/authorities', endpoint='authorities') api.add_resource(Authorities, '/authorities/', endpoint='authority') diff --git a/lemur/tests/test_authorities.py b/lemur/tests/test_authorities.py new file mode 100644 index 00000000..e55db48a --- /dev/null +++ b/lemur/tests/test_authorities.py @@ -0,0 +1,163 @@ +import pytest +from lemur.authorities.views import * + +#def test_crud(session): +# role = create('role1') +# assert role.id > 0 +# +# role = update(role.id, 'role_new', None, []) +# assert role.name == 'role_new' +# delete(role.id) +# assert get(role.id) == None + + +def test_authority_get(client): + assert client.get(api.url_for(Authorities, authority_id=1)).status_code == 401 + + +def test_authority_post(client): + assert client.post(api.url_for(Authorities, authority_id=1), {}).status_code == 405 + + +def test_authority_put(client): + assert client.put(api.url_for(Authorities, authority_id=1), {}).status_code == 401 + + +def test_authority_delete(client): + assert client.delete(api.url_for(Authorities, authority_id=1)).status_code == 405 + + +def test_authority_patch(client): + assert client.patch(api.url_for(Authorities, authority_id=1), {}).status_code == 405 + + +def test_authorities_get(client): + assert client.get(api.url_for(AuthoritiesList)).status_code == 401 + + +def test_authorities_post(client): + assert client.post(api.url_for(AuthoritiesList), {}).status_code == 401 + + +def test_authorities_put(client): + assert client.put(api.url_for(AuthoritiesList), {}).status_code == 405 + + +def test_authorities_delete(client): + assert client.delete(api.url_for(AuthoritiesList)).status_code == 405 + + +def test_authorities_patch(client): + assert client.patch(api.url_for(AuthoritiesList), {}).status_code == 405 + + +def test_certificate_authorities_get(client): + assert client.get(api.url_for(AuthoritiesList)).status_code == 401 + + +def test_certificate_authorities_post(client): + assert client.post(api.url_for(AuthoritiesList), {}).status_code == 401 + + +def test_certificate_authorities_put(client): + assert client.put(api.url_for(AuthoritiesList), {}).status_code == 405 + + +def test_certificate_authorities_delete(client): + assert client.delete(api.url_for(AuthoritiesList)).status_code == 405 + + +def test_certificate_authorities_patch(client): + assert client.patch(api.url_for(AuthoritiesList), {}).status_code == 405 + + +VALID_USER_HEADER_TOKEN = { + 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'} + + +def test_auth_authority_get(client): + assert client.get(api.url_for(Authorities, authority_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200 + + +def test_auth_authority_post_(client): + assert client.post(api.url_for(Authorities, authority_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_authority_put(client): + assert client.put(api.url_for(Authorities, authority_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 400 + + +def test_auth_authority_delete(client): + assert client.delete(api.url_for(Authorities, authority_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_authority_patch(client): + assert client.patch(api.url_for(Authorities, authority_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + + +def test_auth_authorities_get(client): + assert client.get(api.url_for(AuthoritiesList), headers=VALID_USER_HEADER_TOKEN).status_code == 200 + + +def test_auth_authorities_post(client): + assert client.post(api.url_for(AuthoritiesList), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 400 + + +def test_auth_certificates_authorities_get(client): + assert client.get(api.url_for(CertificateAuthority, certificate_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 404 + + +VALID_ADMIN_HEADER_TOKEN = { + 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'} + + +def test_admin_authority_get(client): + assert client.get(api.url_for(Authorities, authority_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 + + +def test_admin_authority_post(client): + assert client.post(api.url_for(Authorities, authority_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_authority_put(client): + assert client.put(api.url_for(Authorities, authority_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 + + +def test_admin_authority_delete(client): + assert client.delete(api.url_for(Authorities, authority_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_authority_patch(client): + assert client.patch(api.url_for(Authorities, authority_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_authorities_get(client): + assert client.get(api.url_for(AuthoritiesList), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 + + +def test_admin_authorities_post(client): + assert client.post(api.url_for(AuthoritiesList), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 + + +def test_admin_authorities_put(client): + assert client.put(api.url_for(AuthoritiesList), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_authorities_delete(client): + assert client.delete(api.url_for(AuthoritiesList), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_certificate_authorities_get(client): + assert client.get(api.url_for(CertificateAuthority, certificate_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 404 + + +def test_admin_certificate_authorities_post(client): + assert client.post(api.url_for(CertificateAuthority, certficate_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_certificate_authorities_put(client): + assert client.put(api.url_for(CertificateAuthority, certificate_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + + +def test_admin_certificate_authorities_delete(client): + assert client.delete(api.url_for(CertificateAuthority, certificate_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 From 7123e77edfee988b1fefb12fbef5173cf7cd7dee Mon Sep 17 00:00:00 2001 From: Kevin Glisson Date: Mon, 29 Jun 2015 13:51:52 -0700 Subject: [PATCH 15/31] Extending certificate tests. --- lemur/__init__.py | 40 ++++++++++++++++---------------- lemur/tests/certs.py | 38 ++++++++++++++++++++++++++++++ lemur/tests/test_certificates.py | 9 ++++++- 3 files changed, 66 insertions(+), 21 deletions(-) diff --git a/lemur/__init__.py b/lemur/__init__.py index 015ad459..39432438 100644 --- a/lemur/__init__.py +++ b/lemur/__init__.py @@ -12,28 +12,28 @@ from flask import jsonify from lemur import factory -from lemur.users.views import mod as users -from lemur.roles.views import mod as roles -from lemur.auth.views import mod as auth -from lemur.domains.views import mod as domains -from lemur.elbs.views import mod as elbs -from lemur.accounts.views import mod as accounts -from lemur.authorities.views import mod as authorities -from lemur.listeners.views import mod as listeners -from lemur.certificates.views import mod as certificates -from lemur.status.views import mod as status +from lemur.users.views import mod as users_bp +from lemur.roles.views import mod as roles_bp +from lemur.auth.views import mod as auth_bp +from lemur.domains.views import mod as domains_bp +from lemur.elbs.views import mod as elbs_bp +from lemur.accounts.views import mod as accounts_bp +from lemur.authorities.views import mod as authorities_bp +from lemur.listeners.views import mod as listeners_bp +from lemur.certificates.views import mod as certificates_bp +from lemur.status.views import mod as status_bp LEMUR_BLUEPRINTS = ( - users, - roles, - auth, - domains, - elbs, - accounts, - authorities, - listeners, - certificates, - status + users_bp, + roles_bp, + auth_bp, + domains_bp, + elbs_bp, + accounts_bp, + authorities_bp, + listeners_bp, + certificates_bp, + status_bp ) def create_app(config=None): diff --git a/lemur/tests/certs.py b/lemur/tests/certs.py index 29e62705..ce2f123b 100644 --- a/lemur/tests/certs.py +++ b/lemur/tests/certs.py @@ -180,4 +180,42 @@ F74P1wKBgQCPQGKLUcfAvjIcZp4ECH0K8sBEmoEf8pceuALZ3H5vneYDzqMDIceo t5Gpocpt77LJnNiszXSerj/KjX2MflY5xUXeekWowLVTBOK5+CZ8+XBIgBt1hIG3 XKxcRgm/Va4QMEAnec0qXfdTVJaJiAW0bdKwKRRrrbwcTdNRGibdng== -----END RSA PRIVATE KEY----- +""" + +CSR_CONFIG = """ + # Configuration for standard CSR generation for Netflix + # Used for procuring VeriSign certificates + # Author: jbob + # Contact: security@example.com + + [ req ] + # Use a 2048 bit private key + default_bits = 2048 + default_keyfile = key.pem + prompt = no + encrypt_key = no + + # base request + distinguished_name = req_distinguished_name + + # extensions + # Uncomment the following line if you are requesting a SAN cert + #req_extensions = req_ext + + # distinguished_name + [ req_distinguished_name ] + countryName = "US" # C= + stateOrProvinceName = "CALIFORNIA" # ST= + localityName = "A place" # L= + organizationName = "Example, Inc." # O= + organizationalUnitName = "Operations" # OU= + # This is the hostname/subject name on the certificate + commonName = "example.net" # CN= + + [ req_ext ] + # Uncomment the following line if you are requesting a SAN cert + #subjectAltName = @alt_names + + [alt_names] + # Put your SANs here """ \ No newline at end of file diff --git a/lemur/tests/test_certificates.py b/lemur/tests/test_certificates.py index 3201287a..4470149d 100644 --- a/lemur/tests/test_certificates.py +++ b/lemur/tests/test_certificates.py @@ -1,4 +1,6 @@ +import os import pytest +from mock import mock_open, patch from lemur.certificates.views import * #def test_crud(session): @@ -32,7 +34,12 @@ def test_private_key_str(): def test_create_csr(): - assert 1 == 2 + from lemur.tests.certs import CSR_CONFIG + from lemur.certificates.service import create_csr + m = mock_open() + with patch('lemur.certificates.service.open', m, create=True): + path = create_csr(CSR_CONFIG) + assert path == '' def test_create_path(): From 8cbc6b8325c08bb3a72932c7e67c6476f7d29edb Mon Sep 17 00:00:00 2001 From: kevgliss Date: Thu, 2 Jul 2015 12:10:09 -0700 Subject: [PATCH 16/31] Initial work at removing openssl --- lemur/certificates/service.py | 173 ++++++++---------- lemur/common/services/issuers/issuer.py | 3 - .../issuers/plugins/cloudca/cloudca.py | 9 - .../issuers/plugins/cloudca/constants.py | 27 --- .../issuers/plugins/verisign/constants.py | 39 ---- .../issuers/plugins/verisign/verisign.py | 33 ---- lemur/manage.py | 1 - 7 files changed, 78 insertions(+), 207 deletions(-) delete mode 100644 lemur/common/services/issuers/plugins/cloudca/constants.py diff --git a/lemur/certificates/service.py b/lemur/certificates/service.py index 354c9868..9f4fc1f3 100644 --- a/lemur/certificates/service.py +++ b/lemur/certificates/service.py @@ -5,13 +5,9 @@ :license: Apache, see LICENSE for more details. .. moduleauthor:: Kevin Glisson """ -import os import arrow import string import random -import hashlib -import datetime -import subprocess from sqlalchemy import func, or_ from flask import g, current_app @@ -21,8 +17,6 @@ from lemur.common.services.aws import iam from lemur.common.services.issuers.manager import get_plugin_by_name from lemur.certificates.models import Certificate -from lemur.certificates.exceptions import UnableToCreateCSR, \ - UnableToCreatePrivateKey, MissingFiles from lemur.accounts.models import Account from lemur.accounts import service as account_service @@ -30,6 +24,11 @@ from lemur.authorities.models import Authority from lemur.roles.models import Role +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import rsa + def get(cert_id): """ @@ -128,23 +127,17 @@ def mint(issuer_options): authority = issuer_options['authority'] issuer = get_plugin_by_name(authority.plugin_name) - # NOTE if we wanted to support more issuers it might make sense to - # push CSR creation down to the plugin - path = create_csr(issuer.get_csr_config(issuer_options)) - challenge, csr, csr_config, private_key = load_ssl_pack(path) - issuer_options['challenge'] = challenge + csr, private_key = create_csr(issuer_options) + + issuer_options['challenge'] = create_challenge() issuer_options['creator'] = g.user.email cert_body, cert_chain = issuer.create_certificate(csr, issuer_options) - cert = save_cert(cert_body, private_key, cert_chain, challenge, csr_config, issuer_options.get('accounts')) + cert = save_cert(cert_body, private_key, cert_chain, issuer_options.get('accounts')) cert.user = g.user cert.authority = authority database.update(cert) - - # securely delete pack after saving it to RDS and IAM (if applicable) - delete_ssl_pack(path) - return cert, private_key, cert_chain, @@ -302,93 +295,83 @@ def create_csr(csr_config): :param csr_config: """ + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + backend=default_backend() + ) - # we create a no colliding file name - path = create_path(hashlib.md5(csr_config).hexdigest()) + builder = x509.CertificateSigningRequestBuilder() + builder = builder.subject_name(x509.Name([ + x509.NameAttribute(x509.OID_COMMON_NAME, csr_config['commonName']), + x509.NameAttribute(x509.OID_ORGANIZATION_NAME, csr_config['organization']), + x509.NameAttribute(x509.OID_ORGANIZATIONAL_UNIT_NAME, csr_config['organizationalUnit']), + x509.NameAttribute(x509.OID_COUNTRY_NAME, csr_config['country']), + x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, csr_config['state']), + x509.NameAttribute(x509.OID_LOCALITY_NAME, csr_config['location']) + ])) - challenge = create_challenge() - challenge_path = os.path.join(path, 'challenge.txt') + builder = builder.add_extension( + x509.BasicConstraints(ca=False, path_length=None), critical=True, + ) - with open(challenge_path, 'w') as c: - c.write(challenge) + for name in csr_config['extensions']['subAltNames']['names']: + builder.add_extension( + x509.SubjectAlternativeName(x509.DNSName, name['value']) + ) - csr_path = os.path.join(path, 'csr_config.txt') +# TODO support more CSR options +# csr_config['extensions']['keyUsage'] +# builder.add_extension( +# x509.KeyUsage( +# digital_signature=digital_signature, +# content_commitment=content_commitment, +# key_encipherment=key_enipherment, +# data_encipherment=data_encipherment, +# key_agreement=key_agreement, +# key_cert_sign=key_cert_sign, +# crl_sign=crl_sign, +# encipher_only=enchipher_only, +# decipher_only=decipher_only +# ), critical=True +# ) +# +# # we must maintain our own list of OIDs here +# builder.add_extension( +# x509.ExtendedKeyUsage( +# server_authentication=server_authentication, +# email= +# ) +# ) +# +# builder.add_extension( +# x509.AuthorityInformationAccess() +# ) +# +# builder.add_extension( +# x509.AuthorityKeyIdentifier() +# ) +# +# builder.add_extension( +# x509.SubjectKeyIdentifier() +# ) +# +# builder.add_extension( +# x509.CRLDistributionPoints() +# ) - with open(csr_path, 'w') as f: - f.write(csr_config) + request = builder.sign( + private_key, hashes.SHA256(), default_backend() + ) - #TODO use cloudCA to seed a -rand file for each call - #TODO replace openssl shell calls with cryptograph - with open('/dev/null', 'w') as devnull: - code = subprocess.call(['openssl', 'genrsa', - '-out', os.path.join(path, 'private.key'), '2048'], - stdout=devnull, stderr=devnull) - - if code != 0: - raise UnableToCreatePrivateKey(code) - - with open('/dev/null', 'w') as devnull: - code = subprocess.call(['openssl', 'req', '-new', '-sha256', '-nodes', - '-config', csr_path, "-key", os.path.join(path, 'private.key'), - "-out", os.path.join(path, 'request.csr')], stdout=devnull, stderr=devnull) - - if code != 0: - raise UnableToCreateCSR(code) - - return path + # here we try and support arbitrary oids + for oid in csr_config['extensions']['custom']: + builder.add_extension( + x509.ObjectIdentifier(oid) + ) -def create_path(domain_hash): - """ - - :param domain_hash: - :return: - """ - path = os.path.join('/tmp', domain_hash) - - try: - os.mkdir(path) - except OSError as e: - now = datetime.datetime.now() - path = os.path.join('/tmp', "{}.{}".format(domain_hash, now.strftime('%s'))) - os.mkdir(path) - current_app.logger.warning(e) - - current_app.logger.debug("Writing ssl files to: {}".format(path)) - return path - - -def load_ssl_pack(path): - """ - Loads the information created by openssl to be used by other functions. - - :param path: - """ - if len(os.listdir(path)) != 4: - raise MissingFiles(path) - - with open(os.path.join(path, 'challenge.txt')) as c: - challenge = c.read() - - with open(os.path.join(path, 'request.csr')) as r: - csr = r.read() - - with open(os.path.join(path, 'csr_config.txt')) as config: - csr_config = config.read() - - with open(os.path.join(path, 'private.key')) as key: - private_key = key.read() - - return (challenge, csr, csr_config, private_key,) - - -def delete_ssl_pack(path): - """ - Removes the temporary files associated with CSR creation. - - :param path: - """ - subprocess.check_call(['srm', '-r', path]) + return request.public_bytes("PEM"), private_key.public_bytes("PEM") def create_challenge(): diff --git a/lemur/common/services/issuers/issuer.py b/lemur/common/services/issuers/issuer.py index 4950a9b9..937c3f69 100644 --- a/lemur/common/services/issuers/issuer.py +++ b/lemur/common/services/issuers/issuer.py @@ -27,6 +27,3 @@ class Issuer(object): def get_authorities(self): raise NotImplementedError - def get_csr_config(self): - raise NotImplementedError - diff --git a/lemur/common/services/issuers/plugins/cloudca/cloudca.py b/lemur/common/services/issuers/plugins/cloudca/cloudca.py index d6612b4e..2e03d778 100644 --- a/lemur/common/services/issuers/plugins/cloudca/cloudca.py +++ b/lemur/common/services/issuers/plugins/cloudca/cloudca.py @@ -261,15 +261,6 @@ class CloudCA(Issuer): return cert, "".join(intermediates), - def get_csr_config(self, issuer_options): - """ - Get a valid CSR for use with CloudCA - - :param issuer_options: - :return: - """ - return cloudca.constants.CSR_CONFIG.format(**issuer_options) - def random(self, length=10): """ Uses CloudCA as a decent source of randomness. diff --git a/lemur/common/services/issuers/plugins/cloudca/constants.py b/lemur/common/services/issuers/plugins/cloudca/constants.py deleted file mode 100644 index 229910bf..00000000 --- a/lemur/common/services/issuers/plugins/cloudca/constants.py +++ /dev/null @@ -1,27 +0,0 @@ -CSR_CONFIG = """ - # Configuration for standard CSR generation for Netflix - # Used for procuring CloudCA certificates - # Author: kglisson - # Contact: secops@netflix.com - - [ req ] - # Use a 2048 bit private key - default_bits = 2048 - default_keyfile = key.pem - prompt = no - encrypt_key = no - - # base request - distinguished_name = req_distinguished_name - - # distinguished_name - [ req_distinguished_name ] - countryName = "{country}" # C= - stateOrProvinceName = "{state}" # ST= - localityName = "{location}" # L= - organizationName = "{organization}" # O= - organizationalUnitName = "{organizationalUnit}" # OU= - # This is the hostname/subject name on the certificate - commonName = "{commonName}" # CN= - """ - diff --git a/lemur/common/services/issuers/plugins/verisign/constants.py b/lemur/common/services/issuers/plugins/verisign/constants.py index e5d84c49..7382dc38 100644 --- a/lemur/common/services/issuers/plugins/verisign/constants.py +++ b/lemur/common/services/issuers/plugins/verisign/constants.py @@ -1,42 +1,3 @@ -CSR_CONFIG = """ - # Configuration for standard CSR generation for Netflix - # Used for procuring VeriSign certificates - # Author: jachan - # Contact: cloudsecurity@netflix.com - - [ req ] - # Use a 2048 bit private key - default_bits = 2048 - default_keyfile = key.pem - prompt = no - encrypt_key = no - - # base request - distinguished_name = req_distinguished_name - - # extensions - # Uncomment the following line if you are requesting a SAN cert - {is_san_comment}req_extensions = req_ext - - # distinguished_name - [ req_distinguished_name ] - countryName = "US" # C= - stateOrProvinceName = "CALIFORNIA" # ST= - localityName = "Los Gatos" # L= - organizationName = "Netflix, Inc." # O= - organizationalUnitName = "{OU}" # OU= - # This is the hostname/subject name on the certificate - commonName = "{DNS[0]}" # CN= - - [ req_ext ] - # Uncomment the following line if you are requesting a SAN cert - {is_san_comment}subjectAltName = @alt_names - - [alt_names] - # Put your SANs here - {DNS_LINES} - """ - VERISIGN_INTERMEDIATE = """ -----BEGIN CERTIFICATE----- MIIFFTCCA/2gAwIBAgIQKC4nkXkzkuQo8iGnTsk3rjANBgkqhkiG9w0BAQsFADCB diff --git a/lemur/common/services/issuers/plugins/verisign/verisign.py b/lemur/common/services/issuers/plugins/verisign/verisign.py index 2b3ca1cd..7f3a2867 100644 --- a/lemur/common/services/issuers/plugins/verisign/verisign.py +++ b/lemur/common/services/issuers/plugins/verisign/verisign.py @@ -129,39 +129,6 @@ class Verisign(Issuer): cert = self.handle_response(response.content)['Response']['Certificate'] return cert, verisign.constants.VERISIGN_INTERMEDIATE, - def get_csr_config(self, issuer_options): - """ - Used to generate a valid CSR for the given Certificate Authority. - - :param issuer_options: - :return: :raise InsufficientDomains: - """ - domains = [] - - if issuer_options.get('commonName'): - domains.append(issuer_options.get('commonName')) - - if issuer_options.get('extensions'): - for n in issuer_options['extensions']['subAltNames']['names']: - if n['value']: - domains.append(n['value']) - - is_san_comment = "#" - - dns_lines = [] - if len(domains) < 1: - raise InsufficientDomains - - elif len(domains) > 1: - is_san_comment = "" - for domain_line in list(set(domains)): - dns_lines.append("DNS.{} = {}".format(len(dns_lines) + 1, domain_line)) - - return verisign.constants.CSR_CONFIG.format( - is_san_comment=is_san_comment, - OU=issuer_options.get('organizationalUnit', 'Operations'), - DNS=domains, - DNS_LINES="\n".join(dns_lines)) @staticmethod def create_authority(options): diff --git a/lemur/manage.py b/lemur/manage.py index 2f4aee15..93fe8775 100755 --- a/lemur/manage.py +++ b/lemur/manage.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python import os import sys import base64 From 95bab9331d695be80080088125bc1da866af534f Mon Sep 17 00:00:00 2001 From: kevgliss Date: Fri, 3 Jul 2015 10:30:17 -0700 Subject: [PATCH 17/31] Enabling CSR generation and reducing complexity of encryption/decrypting the 'key' dir. --- lemur/__init__.py | 2 - lemur/auth/views.py | 19 ---- lemur/certificates/service.py | 112 ++++++++++--------- lemur/common/crypto.py | 185 ------------------------------- lemur/manage.py | 154 ++++++++++++------------- lemur/tests/conftest.py | 1 + lemur/tests/test_certificates.py | 34 +++--- lemur/tests/test_crypto.py | 0 8 files changed, 156 insertions(+), 351 deletions(-) delete mode 100644 lemur/common/crypto.py delete mode 100644 lemur/tests/test_crypto.py diff --git a/lemur/__init__.py b/lemur/__init__.py index 39432438..aada8abe 100644 --- a/lemur/__init__.py +++ b/lemur/__init__.py @@ -8,8 +8,6 @@ """ -from flask import jsonify - from lemur import factory from lemur.users.views import mod as users_bp diff --git a/lemur/auth/views.py b/lemur/auth/views.py index 06e77f77..fd7255ca 100644 --- a/lemur/auth/views.py +++ b/lemur/auth/views.py @@ -14,8 +14,6 @@ from flask import g, Blueprint, current_app, abort from flask.ext.restful import reqparse, Resource, Api from flask.ext.principal import Identity, identity_changed -from lemur.common.crypto import unlock - from lemur.auth.permissions import admin_permission from lemur.users import service as user_service from lemur.roles import service as role_service @@ -234,24 +232,7 @@ class Ping(Resource): return dict(token=create_token(user)) -class Unlock(AuthenticatedResource): - def __init__(self): - self.reqparse = reqparse.RequestParser() - super(Unlock, self).__init__() - - @admin_permission.require(http_exception=403) - def post(self): - self.reqparse.add_argument('password', type=str, required=True, location='json') - args = self.reqparse.parse_args() - unlock(args['password']) - return { - "message": "You have successfully unlocked this Lemur instance", - "type": "success" - } - - api.add_resource(Login, '/auth/login', endpoint='login') api.add_resource(Ping, '/auth/ping', endpoint='ping') -api.add_resource(Unlock, '/auth/unlock', endpoint='unlock') diff --git a/lemur/certificates/service.py b/lemur/certificates/service.py index 9f4fc1f3..04f3b697 100644 --- a/lemur/certificates/service.py +++ b/lemur/certificates/service.py @@ -26,10 +26,11 @@ from lemur.roles.models import Role from cryptography import x509 from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import rsa + def get(cert_id): """ Retrieves certificate by it's ID. @@ -145,7 +146,7 @@ def import_certificate(**kwargs): """ Uploads already minted certificates and pulls the required information into Lemur. - This is to be used for certificates that are reated outside of Lemur but + This is to be used for certificates that are created outside of Lemur but should still be tracked. Internally this is used to bootstrap Lemur with external @@ -315,64 +316,71 @@ def create_csr(csr_config): x509.BasicConstraints(ca=False, path_length=None), critical=True, ) - for name in csr_config['extensions']['subAltNames']['names']: - builder.add_extension( - x509.SubjectAlternativeName(x509.DNSName, name['value']) - ) + for k, v in csr_config.get('extensions', {}).items(): + if k == 'subAltNames': + builder = builder.add_extension( + x509.SubjectAlternativeName([x509.DNSName(n) for n in v]), critical=True, + ) -# TODO support more CSR options -# csr_config['extensions']['keyUsage'] -# builder.add_extension( -# x509.KeyUsage( -# digital_signature=digital_signature, -# content_commitment=content_commitment, -# key_encipherment=key_enipherment, -# data_encipherment=data_encipherment, -# key_agreement=key_agreement, -# key_cert_sign=key_cert_sign, -# crl_sign=crl_sign, -# encipher_only=enchipher_only, -# decipher_only=decipher_only -# ), critical=True -# ) -# -# # we must maintain our own list of OIDs here -# builder.add_extension( -# x509.ExtendedKeyUsage( -# server_authentication=server_authentication, -# email= -# ) -# ) -# -# builder.add_extension( -# x509.AuthorityInformationAccess() -# ) -# -# builder.add_extension( -# x509.AuthorityKeyIdentifier() -# ) -# -# builder.add_extension( -# x509.SubjectKeyIdentifier() -# ) -# -# builder.add_extension( -# x509.CRLDistributionPoints() -# ) + # TODO support more CSR options, none of the authorities support these atm + # builder.add_extension( + # x509.KeyUsage( + # digital_signature=digital_signature, + # content_commitment=content_commitment, + # key_encipherment=key_enipherment, + # data_encipherment=data_encipherment, + # key_agreement=key_agreement, + # key_cert_sign=key_cert_sign, + # crl_sign=crl_sign, + # encipher_only=enchipher_only, + # decipher_only=decipher_only + # ), critical=True + # ) + # + # # we must maintain our own list of OIDs here + # builder.add_extension( + # x509.ExtendedKeyUsage( + # server_authentication=server_authentication, + # email= + # ) + # ) + # + # builder.add_extension( + # x509.AuthorityInformationAccess() + # ) + # + # builder.add_extension( + # x509.AuthorityKeyIdentifier() + # ) + # + # builder.add_extension( + # x509.SubjectKeyIdentifier() + # ) + # + # builder.add_extension( + # x509.CRLDistributionPoints() + # ) + # + # builder.add_extension( + # x509.ObjectIdentifier(oid) + # ) request = builder.sign( private_key, hashes.SHA256(), default_backend() ) - # here we try and support arbitrary oids - for oid in csr_config['extensions']['custom']: - builder.add_extension( - x509.ObjectIdentifier(oid) - ) + # serialize our private key and CSR + pem = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption() + ) + csr = request.public_bytes( + encoding=serialization.Encoding.PEM + ) - return request.public_bytes("PEM"), private_key.public_bytes("PEM") - + return csr, pem def create_challenge(): """ diff --git a/lemur/common/crypto.py b/lemur/common/crypto.py deleted file mode 100644 index 80b03e42..00000000 --- a/lemur/common/crypto.py +++ /dev/null @@ -1,185 +0,0 @@ -""" -.. module: lemur.common.crypto - :platform: Unix - :synopsis: This module contains all cryptographic function's in Lemur - :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more - :license: Apache, see LICENSE for more details. -.. moduleauthor:: Kevin Glisson - -""" -import os -import ssl -import StringIO -import functools -from Crypto import Random -from Crypto.Cipher import AES -from hashlib import sha512 - -from flask import current_app - -from lemur.factory import create_app - - -old_init = ssl.SSLSocket.__init__ - -@functools.wraps(old_init) -def ssl_bug(self, *args, **kwargs): - kwargs['ssl_version'] = ssl.PROTOCOL_TLSv1 - old_init(self, *args, **kwargs) - -ssl.SSLSocket.__init__ = ssl_bug - - -def derive_key_and_iv(password, salt, key_length, iv_length): - """ - Derives the key and iv from the password and salt. - - :param password: - :param salt: - :param key_length: - :param iv_length: - :return: key, iv - """ - d = d_i = '' - - while len(d) < key_length + iv_length: - d_i = sha512(d_i + password + salt).digest() - d += d_i - - return d[:key_length], d[key_length:key_length+iv_length] - - -def encrypt(in_file, out_file, password, key_length=32): - """ - Encrypts a file. - - :param in_file: - :param out_file: - :param password: - :param key_length: - """ - bs = AES.block_size - salt = Random.new().read(bs - len('Salted__')) - key, iv = derive_key_and_iv(password, salt, key_length, bs) - cipher = AES.new(key, AES.MODE_CBC, iv) - out_file.write('Salted__' + salt) - finished = False - while not finished: - chunk = in_file.read(1024 * bs) - if len(chunk) == 0 or len(chunk) % bs != 0: - padding_length = bs - (len(chunk) % bs) - chunk += padding_length * chr(padding_length) - finished = True - out_file.write(cipher.encrypt(chunk)) - - -def decrypt(in_file, out_file, password, key_length=32): - """ - Decrypts a file. - - :param in_file: - :param out_file: - :param password: - :param key_length: - :raise ValueError: - """ - bs = AES.block_size - salt = in_file.read(bs)[len('Salted__'):] - key, iv = derive_key_and_iv(password, salt, key_length, bs) - cipher = AES.new(key, AES.MODE_CBC, iv) - next_chunk = '' - finished = False - while not finished: - chunk, next_chunk = next_chunk, cipher.decrypt(in_file.read(1024 * bs)) - if len(next_chunk) == 0: - padding_length = ord(chunk[-1]) - if padding_length < 1 or padding_length > bs: - raise ValueError("bad decrypt pad (%d)" % padding_length) - # all the pad-bytes must be the same - if chunk[-padding_length:] != (padding_length * chr(padding_length)): - # this is similar to the bad decrypt:evp_enc.c from openssl program - raise ValueError("bad decrypt") - chunk = chunk[:-padding_length] - finished = True - out_file.write(chunk) - - -def encrypt_string(string, password): - """ - Encrypts a string. - - :param string: - :param password: - :return: - """ - in_file = StringIO.StringIO(string) - enc_file = StringIO.StringIO() - encrypt(in_file, enc_file, password) - enc_file.seek(0) - return enc_file.read() - - -def decrypt_string(string, password): - """ - Decrypts a string. - - :param string: - :param password: - :return: - """ - in_file = StringIO.StringIO(string) - out_file = StringIO.StringIO() - decrypt(in_file, out_file, password) - out_file.seek(0) - return out_file.read() - - -def lock(password): - """ - Encrypts Lemur's KEY_PATH. This directory can be used to store secrets needed for normal - Lemur operation. This is especially useful for storing secrets needed for communication - with third parties (e.g. external certificate authorities). - - Lemur does not assume anything about the contents of the directory and will attempt to - encrypt all files contained within. Currently this has only been tested against plain - text files. - - :param password: - """ - dest_dir = os.path.join(current_app.config.get("KEY_PATH"), "encrypted") - - if not os.path.exists(dest_dir): - current_app.logger.debug("Creating encryption directory: {0}".format(dest_dir)) - os.makedirs(dest_dir) - - for root, dirs, files in os.walk(os.path.join(current_app.config.get("KEY_PATH"), 'decrypted')): - for f in files: - source = os.path.join(root, f) - dest = os.path.join(dest_dir, f + ".enc") - with open(source, 'rb') as in_file, open(dest, 'wb') as out_file: - encrypt(in_file, out_file, password) - - -def unlock(password): - """ - Decrypts Lemur's KEY_PATH, allowing lemur to use the secrets within. - - This reverses the :func:`lock` function. - - :param password: - """ - dest_dir = os.path.join(current_app.config.get("KEY_PATH"), "decrypted") - source_dir = os.path.join(current_app.config.get("KEY_PATH"), "encrypted") - - if not os.path.exists(dest_dir): - current_app.logger.debug("Creating decryption directory: {0}".format(dest_dir)) - os.makedirs(dest_dir) - - for root, dirs, files in os.walk(source_dir): - for f in files: - source = os.path.join(source_dir, f) - dest = os.path.join(dest_dir, ".".join(f.split(".")[:-1])) - with open(source, 'rb') as in_file, open(dest, 'wb') as out_file: - current_app.logger.debug("Writing file: {0} Source: {1}".format(dest, source)) - decrypt(in_file, out_file, password) - diff --git a/lemur/manage.py b/lemur/manage.py index 93fe8775..33ad5cae 100755 --- a/lemur/manage.py +++ b/lemur/manage.py @@ -3,6 +3,8 @@ import sys import base64 from gunicorn.config import make_settings +from cryptography.fernet import Fernet + from flask import current_app from flask.ext.script import Manager, Command, Option, Group, prompt_pass from flask.ext.migrate import Migrate, MigrateCommand, stamp @@ -19,7 +21,6 @@ from lemur.certificates import sync from lemur.elbs.sync import sync_all_elbs from lemur import create_app -from lemur.common.crypto import encrypt, decrypt, lock, unlock # Needed to be imported so that SQLAlchemy create_all can find our models from lemur.users.models import User @@ -132,78 +133,6 @@ def create(): stamp(revision='head') -@manager.command -def lock(): - """ - Encrypts all of the files in the `keys` directory with the password - given. This is a useful function to ensure that you do no check in - your key files into source code in clear text. - - :return: - """ - password = prompt_pass("Please enter the encryption password") - lock(password) - sys.stdout.write("[+] Lemur keys have been encrypted!\n") - - -@manager.command -def unlock(): - """ - Decrypts all of the files in the `keys` directory with the password - given. This is most commonly used during the startup sequence of Lemur - allowing it to go from source code to something that can communicate - with external services. - - :return: - """ - password = prompt_pass("Please enter the encryption password") - unlock(password) - sys.stdout.write("[+] Lemur keys have been unencrypted!\n") - - -@manager.command -def encrypt_file(source): - """ - Utility to encrypt sensitive files, Lemur will decrypt these - files when admin enters the correct password. - - Uses AES-256-CBC encryption - """ - dest = source + ".encrypted" - password = prompt_pass("Please enter the encryption password") - password1 = prompt_pass("Please confirm the encryption password") - if password != password1: - sys.stdout.write("[!] Encryption passwords do not match!\n") - return - - with open(source, 'rb') as in_file, open(dest, 'wb') as out_file: - encrypt(in_file, out_file, password) - - sys.stdout.write("[+] Writing encryption files... {0}!\n".format(dest)) - - -@manager.command -def decrypt_file(source): - """ - Utility to decrypt, Lemur will decrypt these - files when admin enters the correct password. - - Assumes AES-256-CBC encryption - """ - # cleanup extensions a bit - if ".encrypted" in source: - dest = ".".join(source.split(".")[:-1]) + ".decrypted" - else: - dest = source + ".decrypted" - - password = prompt_pass("Please enter the encryption password") - - with open(source, 'rb') as in_file, open(dest, 'wb') as out_file: - decrypt(in_file, out_file, password) - - sys.stdout.write("[+] Writing decrypted files... {0}!\n".format(dest)) - - @manager.command def check_revoked(): """ @@ -491,7 +420,84 @@ def create_config(config_path=None): with open(config_path, 'w') as f: f.write(config) - sys.stdout.write("Created a new configuration file {0}\n".format(config_path)) + sys.stdout.write("[+] Created a new configuration file {0}\n".format(config_path)) + + +@manager.command +def lock(path=None): + """ + Encrypts a given path. This directory can be used to store secrets needed for normal + Lemur operation. This is especially useful for storing secrets needed for communication + with third parties (e.g. external certificate authorities). + + Lemur does not assume anything about the contents of the directory and will attempt to + encrypt all files contained within. Currently this has only been tested against plain + text files. + + Path defaults ~/.lemur/keys + + :param: path + """ + if not path: + path = os.path.expanduser('~/.lemur/keys') + + dest_dir = os.path.join(path, "encrypted") + sys.stdout.write("[!] Generating a new key...\n") + + key = Fernet.generate_key() + + if not os.path.exists(dest_dir): + sys.stdout.write("[+] Creating encryption directory: {0}\n".format(dest_dir)) + os.makedirs(dest_dir) + + for root, dirs, files in os.walk(os.path.join(path, 'decrypted')): + for f in files: + source = os.path.join(root, f) + dest = os.path.join(dest_dir, f + ".enc") + with open(source, 'rb') as in_file, open(dest, 'wb') as out_file: + f = Fernet(key) + data = f.encrypt(in_file.read()) + out_file.write(data) + sys.stdout.write("[+] Writing file: {0} Source: {1}\n".format(dest, source)) + + sys.stdout.write("[+] Keys have been encrypted with key {0}\n".format(key)) + + +@manager.command +def unlock(path=None): + """ + Decrypts all of the files in a given directory with provided password. + This is most commonly used during the startup sequence of Lemur + allowing it to go from source code to something that can communicate + with external services. + + Path defaults ~/.lemur/keys + + :param: path + """ + key = prompt_pass("[!] Please enter the encryption password") + + if not path: + path = os.path.expanduser('~/.lemur/keys') + + dest_dir = os.path.join(path, "decrypted") + source_dir = os.path.join(path, "encrypted") + + if not os.path.exists(dest_dir): + sys.stdout.write("[+] Creating decryption directory: {0}\n".format(dest_dir)) + os.makedirs(dest_dir) + + for root, dirs, files in os.walk(source_dir): + for f in files: + source = os.path.join(source_dir, f) + dest = os.path.join(dest_dir, ".".join(f.split(".")[:-1])) + with open(source, 'rb') as in_file, open(dest, 'wb') as out_file: + f = Fernet(key) + data = f.decrypt(in_file.read()) + out_file.write(data) + sys.stdout.write("[+] Writing file: {0} Source: {1}\n".format(dest, source)) + + sys.stdout.write("[+] Keys have been unencrypted!\n") def main(): diff --git a/lemur/tests/conftest.py b/lemur/tests/conftest.py index 50bfd144..c4f1a4a8 100644 --- a/lemur/tests/conftest.py +++ b/lemur/tests/conftest.py @@ -1,3 +1,4 @@ +import os import pytest from lemur import create_app diff --git a/lemur/tests/test_certificates.py b/lemur/tests/test_certificates.py index 4470149d..4dc23a6c 100644 --- a/lemur/tests/test_certificates.py +++ b/lemur/tests/test_certificates.py @@ -1,6 +1,4 @@ -import os import pytest -from mock import mock_open, patch from lemur.certificates.views import * #def test_crud(session): @@ -33,25 +31,23 @@ def test_private_key_str(): private_key_str('dfsdfsdf', 'test') -def test_create_csr(): - from lemur.tests.certs import CSR_CONFIG +def test_create_basic_csr(): from lemur.certificates.service import create_csr - m = mock_open() - with patch('lemur.certificates.service.open', m, create=True): - path = create_csr(CSR_CONFIG) - assert path == '' + csr_config = dict( + commonName=u'example.com', + organization=u'Example, Inc.', + organizationalUnit=u'Operations', + country=u'US', + state=u'CA', + location=u'A place', + extensions=dict(subAltNames=[u'test.example.com', u'test2.example.com']) + ) + csr, pem = create_csr(csr_config) - -def test_create_path(): - assert 1 == 2 - - -def test_load_ssl_pack(): - assert 1 == 2 - - -def test_delete_ssl_path(): - assert 1 == 2 + private_key = serialization.load_pem_private_key(pem, password=None, backend=default_backend()) + csr = x509.load_pem_x509_csr(csr, default_backend()) + for name in csr.subject: + assert name.value in csr_config.values() def test_import_certificate(session): diff --git a/lemur/tests/test_crypto.py b/lemur/tests/test_crypto.py deleted file mode 100644 index e69de29b..00000000 From b17e12bed4f22659680acab8b28fcd5f28614017 Mon Sep 17 00:00:00 2001 From: kevgliss Date: Fri, 3 Jul 2015 12:59:48 -0700 Subject: [PATCH 18/31] Doc fix --- lemur/certificates/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lemur/certificates/service.py b/lemur/certificates/service.py index 04f3b697..7600b059 100644 --- a/lemur/certificates/service.py +++ b/lemur/certificates/service.py @@ -183,7 +183,7 @@ def save_cert(cert_body, private_key, cert_chain, challenge, csr_config, account :param cert_chain: :param challenge: :param csr_config: - :param account_ids: + :param accounts: """ cert = Certificate(cert_body, private_key, challenge, cert_chain, csr_config) # if we have an AWS accounts lets upload them From 3f49bb95ffdac79fa9267ff5840b668d2ef60a5f Mon Sep 17 00:00:00 2001 From: kevgliss Date: Sat, 4 Jul 2015 12:47:57 -0700 Subject: [PATCH 19/31] Starting to move to new plugin architecture. --- lemur/authorities/service.py | 4 +- lemur/certificates/service.py | 5 +- lemur/certificates/sync.py | 5 +- lemur/common/managers.py | 64 ++++++++++ lemur/common/services/issuers/manager.py | 37 ------ .../issuers/plugins/cloudca/constants.py | 27 ---- lemur/factory.py | 27 +++- lemur/manage.py | 41 +----- lemur/plugins/__init__.py | 4 + lemur/plugins/base/__init__.py | 16 +++ lemur/plugins/base/manager.py | 59 +++++++++ lemur/plugins/base/v1.py | 117 ++++++++++++++++++ lemur/plugins/bases/__init__.py | 2 + lemur/plugins/bases/destination.py | 13 ++ .../issuers => plugins/bases}/issuer.py | 11 +- .../__init__.py => plugins/bases/source.py} | 0 .../plugins => plugins/lemur_aws}/__init__.py | 0 lemur/plugins/lemur_aws/aws.py | 0 .../lemur_cloudca}/__init__.py | 0 .../lemur_cloudca/plugin.py} | 23 +--- .../lemur_verisign}/__init__.py | 0 .../lemur_verisign}/constants.py | 40 ------ .../lemur_verisign/plugin.py} | 54 ++------ setup.py | 4 + 24 files changed, 327 insertions(+), 226 deletions(-) create mode 100644 lemur/common/managers.py delete mode 100644 lemur/common/services/issuers/manager.py delete mode 100644 lemur/common/services/issuers/plugins/cloudca/constants.py create mode 100644 lemur/plugins/__init__.py create mode 100644 lemur/plugins/base/__init__.py create mode 100644 lemur/plugins/base/manager.py create mode 100644 lemur/plugins/base/v1.py create mode 100644 lemur/plugins/bases/__init__.py create mode 100644 lemur/plugins/bases/destination.py rename lemur/{common/services/issuers => plugins/bases}/issuer.py (78%) rename lemur/{common/services/issuers/__init__.py => plugins/bases/source.py} (100%) rename lemur/{common/services/issuers/plugins => plugins/lemur_aws}/__init__.py (100%) create mode 100644 lemur/plugins/lemur_aws/aws.py rename lemur/{common/services/issuers/plugins/cloudca => plugins/lemur_cloudca}/__init__.py (100%) rename lemur/{common/services/issuers/plugins/cloudca/cloudca.py => plugins/lemur_cloudca/plugin.py} (95%) rename lemur/{common/services/issuers/plugins/verisign => plugins/lemur_verisign}/__init__.py (100%) rename lemur/{common/services/issuers/plugins/verisign => plugins/lemur_verisign}/constants.py (81%) rename lemur/{common/services/issuers/plugins/verisign/verisign.py => plugins/lemur_verisign/plugin.py} (83%) diff --git a/lemur/authorities/service.py b/lemur/authorities/service.py index d7d82a48..19cfc881 100644 --- a/lemur/authorities/service.py +++ b/lemur/authorities/service.py @@ -17,7 +17,7 @@ from lemur.roles import service as role_service from lemur.roles.models import Role import lemur.certificates.service as cert_service -from lemur.common.services.issuers.manager import get_plugin_by_name +from lemur.plugins.base import plugins def update(authority_id, active=None, roles=None): """ @@ -49,7 +49,7 @@ def create(kwargs): :return: """ - issuer = get_plugin_by_name(kwargs.get('pluginName')) + issuer = plugins.get(kwargs.get('pluginName')) kwargs['creator'] = g.current_user.email cert_body, intermediate, issuer_roles = issuer.create_authority(kwargs) diff --git a/lemur/certificates/service.py b/lemur/certificates/service.py index 354c9868..7c3bc9af 100644 --- a/lemur/certificates/service.py +++ b/lemur/certificates/service.py @@ -18,8 +18,7 @@ from flask import g, current_app from lemur import database from lemur.common.services.aws import iam -from lemur.common.services.issuers.manager import get_plugin_by_name - +from lemur.plugins.base import plugins from lemur.certificates.models import Certificate from lemur.certificates.exceptions import UnableToCreateCSR, \ UnableToCreatePrivateKey, MissingFiles @@ -127,7 +126,7 @@ def mint(issuer_options): """ authority = issuer_options['authority'] - issuer = get_plugin_by_name(authority.plugin_name) + issuer = plugins.get(authority.plugin_name) # NOTE if we wanted to support more issuers it might make sense to # push CSR creation down to the plugin path = create_csr(issuer.get_csr_config(issuer_options)) diff --git a/lemur/certificates/sync.py b/lemur/certificates/sync.py index 3de59982..92438aa4 100644 --- a/lemur/certificates/sync.py +++ b/lemur/certificates/sync.py @@ -30,8 +30,7 @@ from lemur.certificates.models import Certificate, get_name_from_arn from lemur.common.services.aws.iam import get_all_server_certs from lemur.common.services.aws.iam import get_cert_from_arn -from lemur.common.services.issuers.manager import get_plugin_by_name - +from lemur.plugins.base import plugins def aws(): """ @@ -101,7 +100,7 @@ def cloudca(): """ user = user_service.get_by_email('lemur@nobody') # sync all new certificates/authorities not created through lemur - issuer = get_plugin_by_name('cloudca') + issuer = plugins.get('cloudca') authorities = issuer.get_authorities() total = 0 new = 1 diff --git a/lemur/common/managers.py b/lemur/common/managers.py new file mode 100644 index 00000000..43079f46 --- /dev/null +++ b/lemur/common/managers.py @@ -0,0 +1,64 @@ +""" +.. module: lemur.common.managers + :platform: Unix + :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more + :license: Apache, see LICENSE for more details. + +.. moduleauthor:: Kevin Glisson +""" +from flask import current_app + +# inspired by https://github.com/getsentry/sentry +class InstanceManager(object): + def __init__(self, class_list=None, instances=True): + if class_list is None: + class_list = [] + self.instances = instances + self.update(class_list) + + def get_class_list(self): + return self.class_list + + def add(self, class_path): + self.cache = None + self.class_list.append(class_path) + + def remove(self, class_path): + self.cache = None + self.class_list.remove(class_path) + + def update(self, class_list): + """ + Updates the class list and wipes the cache. + """ + self.cache = None + self.class_list = class_list + + def all(self): + """ + Returns a list of cached instances. + """ + class_list = list(self.get_class_list()) + if not class_list: + self.cache = [] + return [] + + if self.cache is not None: + return self.cache + + results = [] + for cls_path in class_list: + module_name, class_name = cls_path.rsplit('.', 1) + try: + module = __import__(module_name, {}, {}, class_name) + cls = getattr(module, class_name) + if self.instances: + results.append(cls()) + else: + results.append(cls) + except Exception: + current_app.logger.exception('Unable to import %s', cls_path) + continue + self.cache = results + + return results \ No newline at end of file diff --git a/lemur/common/services/issuers/manager.py b/lemur/common/services/issuers/manager.py deleted file mode 100644 index 4a5982c3..00000000 --- a/lemur/common/services/issuers/manager.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -.. module: lemur.common.services.issuers.manager - :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more - :license: Apache, see LICENSE for more details. - -.. moduleauthor:: Kevin Glisson (kglisson@netflix.com) -""" -import pkgutil -from importlib import import_module - -from flask import current_app - -from lemur.common.services.issuers import plugins - -# TODO make the plugin dir configurable -def get_plugin_by_name(plugin_name): - """ - Fetches a given plugin by it's name. We use a known location for issuer plugins and attempt - to load it such that it can be used for issuing certificates. - - :param plugin_name: - :return: a plugin `class` :raise Exception: Generic error whenever the plugin specified can not be found. - """ - for importer, modname, ispkg in pkgutil.iter_modules(plugins.__path__): - try: - issuer = import_module('lemur.common.services.issuers.plugins.{0}.{0}'.format(modname)) - if issuer.__name__ == plugin_name: - # we shouldn't return bad issuers - issuer_obj = issuer.init() - return issuer_obj - except Exception as e: - current_app.logger.warn("Issuer {0} was unable to be imported: {1}".format(modname, e)) - - else: - raise Exception("Could not find the specified plugin: {0}".format(plugin_name)) - - diff --git a/lemur/common/services/issuers/plugins/cloudca/constants.py b/lemur/common/services/issuers/plugins/cloudca/constants.py deleted file mode 100644 index 229910bf..00000000 --- a/lemur/common/services/issuers/plugins/cloudca/constants.py +++ /dev/null @@ -1,27 +0,0 @@ -CSR_CONFIG = """ - # Configuration for standard CSR generation for Netflix - # Used for procuring CloudCA certificates - # Author: kglisson - # Contact: secops@netflix.com - - [ req ] - # Use a 2048 bit private key - default_bits = 2048 - default_keyfile = key.pem - prompt = no - encrypt_key = no - - # base request - distinguished_name = req_distinguished_name - - # distinguished_name - [ req_distinguished_name ] - countryName = "{country}" # C= - stateOrProvinceName = "{state}" # ST= - localityName = "{location}" # L= - organizationName = "{organization}" # O= - organizationalUnitName = "{organizationalUnit}" # OU= - # This is the hostname/subject name on the certificate - commonName = "{commonName}" # CN= - """ - diff --git a/lemur/factory.py b/lemur/factory.py index e0470ae2..a5586f70 100644 --- a/lemur/factory.py +++ b/lemur/factory.py @@ -12,6 +12,7 @@ import os import imp import errno +import pkg_resources from logging import Formatter from logging.handlers import RotatingFileHandler @@ -51,6 +52,7 @@ def create_app(app_name=None, blueprints=None, config=None): configure_blueprints(app, blueprints) configure_extensions(app) configure_logging(app) + install_plugins(app) return app @@ -91,7 +93,7 @@ def configure_app(app, config=None): elif os.path.isfile(os.path.expanduser("~/.lemur/lemur.conf.py")): app.config.from_object(from_file(os.path.expanduser("~/.lemur/lemur.conf.py"))) else: - app.config.from_object(from_file(os.path.join(os.getcwd(), 'default.conf.py'))) + app.config.from_object(from_file(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'default.conf.py'))) @@ -136,3 +138,26 @@ def configure_logging(app): app.logger.setLevel(app.config.get('LOG_LEVEL', 'DEBUG')) app.logger.addHandler(handler) + +def install_plugins(app): + """ + Installs new issuers that are not currently bundled with Lemur. + + :param settings: + :return: + """ + from lemur.plugins.base import register + # entry_points={ + # 'lemur.plugins': [ + # 'verisign = lemur_verisign.plugin:VerisignPlugin' + # ], + # }, + for ep in pkg_resources.iter_entry_points('lemur.plugins'): + try: + plugin = ep.load() + except Exception: + import sys + import traceback + app.logger.error("Failed to load plugin %r:\n%s\n" % (ep.name, traceback.format_exc())) + else: + register(plugin) diff --git a/lemur/manage.py b/lemur/manage.py index 2f4aee15..9e553b5f 100755 --- a/lemur/manage.py +++ b/lemur/manage.py @@ -333,7 +333,7 @@ class InitializeApp(Command): else: sys.stdout.write("[-] Default user has already been created, skipping...!\n") - if current_app.app.config.get('AWS_ACCOUNT_MAPPINGS'): + if current_app.config.get('AWS_ACCOUNT_MAPPINGS'): for account_name, account_number in current_app.config.get('AWS_ACCOUNT_MAPPINGS').items(): account = account_service.get_by_account_number(account_number) @@ -346,45 +346,6 @@ class InitializeApp(Command): sys.stdout.write("[/] Done!\n") - -#def install_issuers(settings): -# """ -# Installs new issuers that are not currently bundled with Lemur. -# -# :param settings: -# :return: -# """ -# from lemur.issuers import register -# # entry_points={ -# # 'lemur.issuers': [ -# # 'verisign = lemur_issuers.issuers:VerisignPlugin' -# # ], -# # }, -# installed_apps = list(settings.INSTALLED_APPS) -# for ep in pkg_resources.iter_entry_points('lemur.apps'): -# try: -# issuer = ep.load() -# except Exception: -# import sys -# import traceback -# -# sys.stderr.write("Failed to load app %r:\n%s\n" % (ep.name, traceback.format_exc())) -# else: -# installed_apps.append(ep.module_name) -# settings.INSTALLED_APPS = tuple(installed_apps) -# -# for ep in pkg_resources.iter_entry_points('lemur.issuers'): -# try: -# issuer = ep.load() -# except Exception: -# import sys -# import traceback -# -# sys.stderr.write("Failed to load issuer %r:\n%s\n" % (ep.name, traceback.format_exc())) -# else: -# register(issuer) - - class CreateUser(Command): """ This command allows for the creation of a new user within Lemur diff --git a/lemur/plugins/__init__.py b/lemur/plugins/__init__.py new file mode 100644 index 00000000..d2656990 --- /dev/null +++ b/lemur/plugins/__init__.py @@ -0,0 +1,4 @@ +from __future__ import absolute_import + +from lemur.plugins.base import * # NOQA +from lemur.plugins.bases import * # NOQA diff --git a/lemur/plugins/base/__init__.py b/lemur/plugins/base/__init__.py new file mode 100644 index 00000000..7091b27b --- /dev/null +++ b/lemur/plugins/base/__init__.py @@ -0,0 +1,16 @@ +""" +.. module: lemur.plugins.base + :platform: Unix + :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more + :license: Apache, see LICENSE for more details. + +.. moduleauthor:: Kevin Glisson +""" +from __future__ import absolute_import, print_function + +from lemur.plugins.base.manager import PluginManager +from lemur.plugins.base.v1 import * # NOQA + +plugins = PluginManager() +register = plugins.register +unregister = plugins.unregister diff --git a/lemur/plugins/base/manager.py b/lemur/plugins/base/manager.py new file mode 100644 index 00000000..32234cdc --- /dev/null +++ b/lemur/plugins/base/manager.py @@ -0,0 +1,59 @@ +""" +.. module: lemur.plugins.base.manager + :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more + :license: Apache, see LICENSE for more details. + +.. moduleauthor:: Kevin Glisson (kglisson@netflix.com) +""" +from flask import current_app +from lemur.common.managers import InstanceManager + + +# inspired by https://github.com/getsentry/sentry +class PluginManager(InstanceManager): + def __iter__(self): + return iter(self.all()) + + def __len__(self): + return sum(1 for i in self.all()) + + def all(self, version=1): + for plugin in sorted(super(PluginManager, self).all(), key=lambda x: x.get_title()): + if not plugin.is_enabled(): + continue + if version is not None and plugin.__version__ != version: + continue + yield plugin + + def get(self, slug): + for plugin in self.all(version=1): + if plugin.slug == slug: + return plugin + for plugin in self.all(version=2): + if plugin.slug == slug: + return plugin + raise KeyError(slug) + + def first(self, func_name, *args, **kwargs): + version = kwargs.pop('version', 1) + for plugin in self.all(version=version): + try: + result = getattr(plugin, func_name)(*args, **kwargs) + except Exception as e: + current_app.logger.error('Error processing %s() on %r: %s', func_name, plugin.__class__, e, extra={ + 'func_arg': args, + 'func_kwargs': kwargs, + }, exc_info=True) + continue + + if result is not None: + return result + + def register(self, cls): + self.add('%s.%s' % (cls.__module__, cls.__name__)) + return cls + + def unregister(self, cls): + self.remove('%s.%s' % (cls.__module__, cls.__name__)) + return cls + diff --git a/lemur/plugins/base/v1.py b/lemur/plugins/base/v1.py new file mode 100644 index 00000000..448e6d95 --- /dev/null +++ b/lemur/plugins/base/v1.py @@ -0,0 +1,117 @@ +""" +.. module: lemur.plugins.base.v1 + :platform: Unix + :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more + :license: Apache, see LICENSE for more details. + +.. moduleauthor:: Kevin Glisson +""" +from threading import local + +# stolen from https://github.com/getsentry/sentry/ +class PluginMount(type): + def __new__(cls, name, bases, attrs): + new_cls = type.__new__(cls, name, bases, attrs) + if IPlugin in bases: + return new_cls + if new_cls.title is None: + new_cls.title = new_cls.__name__ + if not new_cls.slug: + new_cls.slug = new_cls.title.replace(' ', '-').lower() + return new_cls + + +class IPlugin(local): + """ + Plugin interface. Should not be inherited from directly. + A plugin should be treated as if it were a singleton. The owner does not + control when or how the plugin gets instantiated, nor is it guaranteed that + it will happen, or happen more than once. + >>> from lemur.plugins import Plugin + >>> + >>> class MyPlugin(Plugin): + >>> def get_title(self): + >>> return 'My Plugin' + As a general rule all inherited methods should allow ``**kwargs`` to ensure + ease of future compatibility. + """ + # Generic plugin information + title = None + slug = None + description = None + version = None + author = None + author_url = None + resource_links = () + + # Configuration specifics + conf_key = None + conf_title = None + + # Global enabled state + enabled = True + can_disable = True + + def is_enabled(self, project=None): + """ + Returns a boolean representing if this plugin is enabled. + If ``project`` is passed, it will limit the scope to that project. + >>> plugin.is_enabled() + """ + if not self.enabled: + return False + if not self.can_disable: + return True + + return True + + def get_conf_key(self): + """ + Returns a string representing the configuration keyspace prefix for this plugin. + """ + if not self.conf_key: + self.conf_key = self.get_conf_title().lower().replace(' ', '_') + return self.conf_key + + def get_conf_title(self): + """ + Returns a string representing the title to be shown on the configuration page. + """ + return self.conf_title or self.get_title() + + def get_title(self): + """ + Returns the general title for this plugin. + >>> plugin.get_title() + """ + return self.title + + def get_description(self): + """ + Returns the description for this plugin. This is shown on the plugin configuration + page. + >>> plugin.get_description() + """ + return self.description + + def get_resource_links(self): + """ + Returns a list of tuples pointing to various resources for this plugin. + >>> def get_resource_links(self): + >>> return [ + >>> ('Documentation', 'http://sentry.readthedocs.org'), + >>> ('Bug Tracker', 'https://github.com/getsentry/sentry/issues'), + >>> ('Source', 'https://github.com/getsentry/sentry'), + >>> ] + """ + return self.resource_links + + +class Plugin(IPlugin): + """ + A plugin should be treated as if it were a singleton. The owner does not + control when or how the plugin gets instantiated, nor is it guaranteed that + it will happen, or happen more than once. + """ + __version__ = 1 + __metaclass__ = PluginMount diff --git a/lemur/plugins/bases/__init__.py b/lemur/plugins/bases/__init__.py new file mode 100644 index 00000000..d43aa85e --- /dev/null +++ b/lemur/plugins/bases/__init__.py @@ -0,0 +1,2 @@ +from .destination import DestinationPlugin # NOQA +from .issuer import IssuerPlugin # NOQA \ No newline at end of file diff --git a/lemur/plugins/bases/destination.py b/lemur/plugins/bases/destination.py new file mode 100644 index 00000000..b95d2c7e --- /dev/null +++ b/lemur/plugins/bases/destination.py @@ -0,0 +1,13 @@ +""" +.. module: lemur.bases.destination + :platform: Unix + :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more + :license: Apache, see LICENSE for more details. + +.. moduleauthor:: Kevin Glisson +""" +from lemur.plugins.base import Plugin + +class DestinationPlugin(Plugin): + pass + diff --git a/lemur/common/services/issuers/issuer.py b/lemur/plugins/bases/issuer.py similarity index 78% rename from lemur/common/services/issuers/issuer.py rename to lemur/plugins/bases/issuer.py index 4950a9b9..d3ac9989 100644 --- a/lemur/common/services/issuers/issuer.py +++ b/lemur/plugins/bases/issuer.py @@ -1,23 +1,18 @@ """ -.. module: authority +.. module: lemur.bases.issuer :platform: Unix :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more :license: Apache, see LICENSE for more details. .. moduleauthor:: Kevin Glisson """ -from flask import current_app +from lemur.plugins.base import Plugin - -class Issuer(object): +class IssuerPlugin(Plugin): """ This is the base class from which all of the supported issuers will inherit from. """ - - def __init__(self): - self.dry_run = current_app.config.get('DRY_RUN') - def create_certificate(self): raise NotImplementedError diff --git a/lemur/common/services/issuers/__init__.py b/lemur/plugins/bases/source.py similarity index 100% rename from lemur/common/services/issuers/__init__.py rename to lemur/plugins/bases/source.py diff --git a/lemur/common/services/issuers/plugins/__init__.py b/lemur/plugins/lemur_aws/__init__.py similarity index 100% rename from lemur/common/services/issuers/plugins/__init__.py rename to lemur/plugins/lemur_aws/__init__.py diff --git a/lemur/plugins/lemur_aws/aws.py b/lemur/plugins/lemur_aws/aws.py new file mode 100644 index 00000000..e69de29b diff --git a/lemur/common/services/issuers/plugins/cloudca/__init__.py b/lemur/plugins/lemur_cloudca/__init__.py similarity index 100% rename from lemur/common/services/issuers/plugins/cloudca/__init__.py rename to lemur/plugins/lemur_cloudca/__init__.py diff --git a/lemur/common/services/issuers/plugins/cloudca/cloudca.py b/lemur/plugins/lemur_cloudca/plugin.py similarity index 95% rename from lemur/common/services/issuers/plugins/cloudca/cloudca.py rename to lemur/plugins/lemur_cloudca/plugin.py index d6612b4e..f1ca11bc 100644 --- a/lemur/common/services/issuers/plugins/cloudca/cloudca.py +++ b/lemur/plugins/lemur_cloudca/plugin.py @@ -18,10 +18,8 @@ from requests.adapters import HTTPAdapter from flask import current_app from lemur.exceptions import LemurException -from lemur.common.services.issuers.issuer import Issuer - -from lemur.common.services.issuers.plugins import cloudca - +from lemur.plugins.bases import IssuerPlugin +from lemur.plugins import lemur_cloudca as cloudca from lemur.authorities import service as authority_service @@ -144,7 +142,7 @@ def get_auth_data(ca_name): raise CloudCAException("You do not have the required role to issue certificates from {0}".format(ca_name)) -class CloudCA(Issuer): +class CloudCAPlugin(IssuerPlugin): title = 'CloudCA' slug = 'cloudca' description = 'Enables the creation of certificates from the cloudca API.' @@ -164,7 +162,7 @@ class CloudCA(Issuer): else: current_app.logger.warning("No CLOUDCA credentials found, lemur will be unable to request certificates from CLOUDCA") - super(CloudCA, self).__init__(*args, **kwargs) + super(CloudCAPlugin, self).__init__(*args, **kwargs) def create_authority(self, options): """ @@ -261,15 +259,6 @@ class CloudCA(Issuer): return cert, "".join(intermediates), - def get_csr_config(self, issuer_options): - """ - Get a valid CSR for use with CloudCA - - :param issuer_options: - :return: - """ - return cloudca.constants.CSR_CONFIG.format(**issuer_options) - def random(self, length=10): """ Uses CloudCA as a decent source of randomness. @@ -340,7 +329,3 @@ class CloudCA(Issuer): response = self.session.get(self.url + endpoint, timeout=10, verify=self.ca_bundle) return process_response(response) - -def init(): - return CloudCA() - diff --git a/lemur/common/services/issuers/plugins/verisign/__init__.py b/lemur/plugins/lemur_verisign/__init__.py similarity index 100% rename from lemur/common/services/issuers/plugins/verisign/__init__.py rename to lemur/plugins/lemur_verisign/__init__.py diff --git a/lemur/common/services/issuers/plugins/verisign/constants.py b/lemur/plugins/lemur_verisign/constants.py similarity index 81% rename from lemur/common/services/issuers/plugins/verisign/constants.py rename to lemur/plugins/lemur_verisign/constants.py index e5d84c49..541ee769 100644 --- a/lemur/common/services/issuers/plugins/verisign/constants.py +++ b/lemur/plugins/lemur_verisign/constants.py @@ -1,42 +1,3 @@ -CSR_CONFIG = """ - # Configuration for standard CSR generation for Netflix - # Used for procuring VeriSign certificates - # Author: jachan - # Contact: cloudsecurity@netflix.com - - [ req ] - # Use a 2048 bit private key - default_bits = 2048 - default_keyfile = key.pem - prompt = no - encrypt_key = no - - # base request - distinguished_name = req_distinguished_name - - # extensions - # Uncomment the following line if you are requesting a SAN cert - {is_san_comment}req_extensions = req_ext - - # distinguished_name - [ req_distinguished_name ] - countryName = "US" # C= - stateOrProvinceName = "CALIFORNIA" # ST= - localityName = "Los Gatos" # L= - organizationName = "Netflix, Inc." # O= - organizationalUnitName = "{OU}" # OU= - # This is the hostname/subject name on the certificate - commonName = "{DNS[0]}" # CN= - - [ req_ext ] - # Uncomment the following line if you are requesting a SAN cert - {is_san_comment}subjectAltName = @alt_names - - [alt_names] - # Put your SANs here - {DNS_LINES} - """ - VERISIGN_INTERMEDIATE = """ -----BEGIN CERTIFICATE----- MIIFFTCCA/2gAwIBAgIQKC4nkXkzkuQo8iGnTsk3rjANBgkqhkiG9w0BAQsFADCB @@ -70,7 +31,6 @@ J+71/xuzAYN6 -----END CERTIFICATE----- """ - VERISIGN_ROOT = """ -----BEGIN CERTIFICATE----- MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw diff --git a/lemur/common/services/issuers/plugins/verisign/verisign.py b/lemur/plugins/lemur_verisign/plugin.py similarity index 83% rename from lemur/common/services/issuers/plugins/verisign/verisign.py rename to lemur/plugins/lemur_verisign/plugin.py index 2b3ca1cd..e3881272 100644 --- a/lemur/common/services/issuers/plugins/verisign/verisign.py +++ b/lemur/plugins/lemur_verisign/plugin.py @@ -1,5 +1,5 @@ """ -.. module: lemur.common.services.issuers.plugins.verisign.verisign +.. module: lemur.plugins.lemur_verisign.verisign :platform: Unix :synopsis: This module is responsible for communicating with the VeriSign VICE 2.0 API. :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more @@ -13,10 +13,9 @@ import xmltodict from flask import current_app -from lemur.common.services.issuers.issuer import Issuer -from lemur.common.services.issuers.plugins import verisign - -from lemur.certificates.exceptions import InsufficientDomains +from lemur.plugins.bases import IssuerPlugin +from lemur.plugins import lemur_verisign as verisign +from lemur.plugins.lemur_verisign import constants # https://support.venafi.com/entries/66445046-Info-VeriSign-Error-Codes @@ -58,7 +57,7 @@ VERISIGN_ERRORS = { } -class Verisign(Issuer): +class VerisignPlugin(IssuerPlugin): title = 'VeriSign' slug = 'verisign' description = 'Enables the creation of certificates by the VICE2.0 verisign API.' @@ -70,7 +69,7 @@ class Verisign(Issuer): def __init__(self, *args, **kwargs): self.session = requests.Session() self.session.cert = current_app.config.get('VERISIGN_PEM_PATH') - super(Verisign, self).__init__(*args, **kwargs) + super(VerisignPlugin, self).__init__(*args, **kwargs) @staticmethod def handle_response(content): @@ -127,41 +126,7 @@ class Verisign(Issuer): response = self.session.post(url, data=data) cert = self.handle_response(response.content)['Response']['Certificate'] - return cert, verisign.constants.VERISIGN_INTERMEDIATE, - - def get_csr_config(self, issuer_options): - """ - Used to generate a valid CSR for the given Certificate Authority. - - :param issuer_options: - :return: :raise InsufficientDomains: - """ - domains = [] - - if issuer_options.get('commonName'): - domains.append(issuer_options.get('commonName')) - - if issuer_options.get('extensions'): - for n in issuer_options['extensions']['subAltNames']['names']: - if n['value']: - domains.append(n['value']) - - is_san_comment = "#" - - dns_lines = [] - if len(domains) < 1: - raise InsufficientDomains - - elif len(domains) > 1: - is_san_comment = "" - for domain_line in list(set(domains)): - dns_lines.append("DNS.{} = {}".format(len(dns_lines) + 1, domain_line)) - - return verisign.constants.CSR_CONFIG.format( - is_san_comment=is_san_comment, - OU=issuer_options.get('organizationalUnit', 'Operations'), - DNS=domains, - DNS_LINES="\n".join(dns_lines)) + return cert, constants.VERISIGN_INTERMEDIATE, @staticmethod def create_authority(options): @@ -173,7 +138,7 @@ class Verisign(Issuer): :return: """ role = {'username': '', 'password': '', 'name': 'verisign'} - return verisign.constants.VERISIGN_ROOT, "", [role] + return constants.VERISIGN_ROOT, "", [role] def get_available_units(self): """ @@ -189,6 +154,3 @@ class Verisign(Issuer): def get_authorities(self): pass - -def init(): - return Verisign() diff --git a/setup.py b/setup.py index ad3815a0..1d55623e 100644 --- a/setup.py +++ b/setup.py @@ -103,6 +103,10 @@ setup( 'console_scripts': [ 'lemur = lemur.manage:main', ], + 'lemur.plugins': [ + 'verisign = lemur.plugins.lemur_verisign.plugin:VerisignPlugin', + 'cloudca = lemur.plugins.lemur_cloudca.plugin:CloudCAPlugin', + ], }, classifiers=[ 'Framework :: Flask', From c59bf3f257c71e9507d1f55a718a414442c5fd25 Mon Sep 17 00:00:00 2001 From: kevgliss Date: Mon, 6 Jul 2015 10:53:12 -0700 Subject: [PATCH 20/31] Fixing tests --- lemur/certificates/service.py | 10 ++-- lemur/tests/conftest.py | 6 +-- lemur/tests/test_accounts.py | 22 ++++---- lemur/tests/test_authorities.py | 30 +++++------ lemur/tests/test_certificates.py | 65 ++++++++++-------------- lemur/tests/test_domains.py | 26 +++++----- lemur/tests/test_pack/challenge.txt | 1 - lemur/tests/test_pack/csr_config.txt | 38 -------------- lemur/tests/test_pack/private.key | 27 ---------- lemur/tests/test_pack/request.csr | 17 ------- lemur/tests/test_pack/server.crt | 21 -------- lemur/tests/test_roles.py | 76 ++++++++++++++-------------- setup.py | 3 +- 13 files changed, 112 insertions(+), 230 deletions(-) delete mode 100644 lemur/tests/test_pack/challenge.txt delete mode 100644 lemur/tests/test_pack/csr_config.txt delete mode 100644 lemur/tests/test_pack/private.key delete mode 100644 lemur/tests/test_pack/request.csr delete mode 100644 lemur/tests/test_pack/server.crt diff --git a/lemur/certificates/service.py b/lemur/certificates/service.py index 7600b059..581e2388 100644 --- a/lemur/certificates/service.py +++ b/lemur/certificates/service.py @@ -316,11 +316,11 @@ def create_csr(csr_config): x509.BasicConstraints(ca=False, path_length=None), critical=True, ) - for k, v in csr_config.get('extensions', {}).items(): - if k == 'subAltNames': - builder = builder.add_extension( - x509.SubjectAlternativeName([x509.DNSName(n) for n in v]), critical=True, - ) + #for k, v in csr_config.get('extensions', {}).items(): + # if k == 'subAltNames': + # builder = builder.add_extension( + # x509.SubjectAlternativeName([x509.DNSName(n) for n in v]), critical=True, + # ) # TODO support more CSR options, none of the authorities support these atm # builder.add_extension( diff --git a/lemur/tests/conftest.py b/lemur/tests/conftest.py index c4f1a4a8..b0a777aa 100644 --- a/lemur/tests/conftest.py +++ b/lemur/tests/conftest.py @@ -46,7 +46,6 @@ def app(): ctx.pop() - @pytest.yield_fixture(scope="session") def db(app, request): _db.drop_all() @@ -73,7 +72,6 @@ def session(db, request): @pytest.yield_fixture(scope="function") -def client(app, session): - with app.test_client() as client: - yield client +def client(app, session, client): + yield client diff --git a/lemur/tests/test_accounts.py b/lemur/tests/test_accounts.py index 2712947c..7665bfc6 100644 --- a/lemur/tests/test_accounts.py +++ b/lemur/tests/test_accounts.py @@ -22,11 +22,11 @@ def test_account_get(client): def test_account_post(client): - assert client.post(api.url_for(Accounts, account_id=1), {}).status_code == 405 + assert client.post(api.url_for(Accounts, account_id=1), data={}).status_code == 405 def test_account_put(client): - assert client.put(api.url_for(Accounts, account_id=1), {}).status_code == 401 + assert client.put(api.url_for(Accounts, account_id=1), data={}).status_code == 401 def test_account_delete(client): @@ -34,7 +34,7 @@ def test_account_delete(client): def test_account_patch(client): - assert client.patch(api.url_for(Accounts, account_id=1), {}).status_code == 405 + assert client.patch(api.url_for(Accounts, account_id=1), data={}).status_code == 405 VALID_USER_HEADER_TOKEN = { @@ -45,7 +45,7 @@ def test_auth_account_get(client): def test_auth_account_post_(client): - assert client.post(api.url_for(Accounts, account_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.post(api.url_for(Accounts, account_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 def test_auth_account_put(client): @@ -57,7 +57,7 @@ def test_auth_account_delete(client): def test_auth_account_patch(client): - assert client.patch(api.url_for(Accounts, account_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.patch(api.url_for(Accounts, account_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 VALID_ADMIN_HEADER_TOKEN = { @@ -68,7 +68,7 @@ def test_admin_account_get(client): def test_admin_account_post(client): - assert client.post(api.url_for(Accounts, account_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + assert client.post(api.url_for(Accounts, account_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 def test_admin_account_put(client): @@ -80,7 +80,7 @@ def test_admin_account_delete(client): def test_admin_account_patch(client): - assert client.patch(api.url_for(Accounts, account_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + assert client.patch(api.url_for(Accounts, account_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 def test_accounts_get(client): @@ -88,11 +88,11 @@ def test_accounts_get(client): def test_accounts_post(client): - assert client.post(api.url_for(AccountsList), {}).status_code == 401 + assert client.post(api.url_for(AccountsList), data={}).status_code == 401 def test_accounts_put(client): - assert client.put(api.url_for(AccountsList), {}).status_code == 405 + assert client.put(api.url_for(AccountsList), data={}).status_code == 405 def test_accounts_delete(client): @@ -100,7 +100,7 @@ def test_accounts_delete(client): def test_accounts_patch(client): - assert client.patch(api.url_for(AccountsList), {}).status_code == 405 + assert client.patch(api.url_for(AccountsList), data={}).status_code == 405 def test_auth_accounts_get(client): @@ -108,7 +108,7 @@ def test_auth_accounts_get(client): def test_auth_accounts_post(client): - assert client.post(api.url_for(AccountsList), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 403 + assert client.post(api.url_for(AccountsList), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 403 def test_admin_accounts_get(client): diff --git a/lemur/tests/test_authorities.py b/lemur/tests/test_authorities.py index e55db48a..e66147e5 100644 --- a/lemur/tests/test_authorities.py +++ b/lemur/tests/test_authorities.py @@ -16,11 +16,11 @@ def test_authority_get(client): def test_authority_post(client): - assert client.post(api.url_for(Authorities, authority_id=1), {}).status_code == 405 + assert client.post(api.url_for(Authorities, authority_id=1), data={}).status_code == 405 def test_authority_put(client): - assert client.put(api.url_for(Authorities, authority_id=1), {}).status_code == 401 + assert client.put(api.url_for(Authorities, authority_id=1), data={}).status_code == 401 def test_authority_delete(client): @@ -28,7 +28,7 @@ def test_authority_delete(client): def test_authority_patch(client): - assert client.patch(api.url_for(Authorities, authority_id=1), {}).status_code == 405 + assert client.patch(api.url_for(Authorities, authority_id=1), data={}).status_code == 405 def test_authorities_get(client): @@ -36,11 +36,11 @@ def test_authorities_get(client): def test_authorities_post(client): - assert client.post(api.url_for(AuthoritiesList), {}).status_code == 401 + assert client.post(api.url_for(AuthoritiesList), data={}).status_code == 401 def test_authorities_put(client): - assert client.put(api.url_for(AuthoritiesList), {}).status_code == 405 + assert client.put(api.url_for(AuthoritiesList), data={}).status_code == 405 def test_authorities_delete(client): @@ -48,7 +48,7 @@ def test_authorities_delete(client): def test_authorities_patch(client): - assert client.patch(api.url_for(AuthoritiesList), {}).status_code == 405 + assert client.patch(api.url_for(AuthoritiesList), data={}).status_code == 405 def test_certificate_authorities_get(client): @@ -56,11 +56,11 @@ def test_certificate_authorities_get(client): def test_certificate_authorities_post(client): - assert client.post(api.url_for(AuthoritiesList), {}).status_code == 401 + assert client.post(api.url_for(AuthoritiesList), data={}).status_code == 401 def test_certificate_authorities_put(client): - assert client.put(api.url_for(AuthoritiesList), {}).status_code == 405 + assert client.put(api.url_for(AuthoritiesList), data={}).status_code == 405 def test_certificate_authorities_delete(client): @@ -68,7 +68,7 @@ def test_certificate_authorities_delete(client): def test_certificate_authorities_patch(client): - assert client.patch(api.url_for(AuthoritiesList), {}).status_code == 405 + assert client.patch(api.url_for(AuthoritiesList), data={}).status_code == 405 VALID_USER_HEADER_TOKEN = { @@ -80,7 +80,7 @@ def test_auth_authority_get(client): def test_auth_authority_post_(client): - assert client.post(api.url_for(Authorities, authority_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.post(api.url_for(Authorities, authority_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 def test_auth_authority_put(client): @@ -92,7 +92,7 @@ def test_auth_authority_delete(client): def test_auth_authority_patch(client): - assert client.patch(api.url_for(Authorities, authority_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.patch(api.url_for(Authorities, authority_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 def test_auth_authorities_get(client): @@ -100,7 +100,7 @@ def test_auth_authorities_get(client): def test_auth_authorities_post(client): - assert client.post(api.url_for(AuthoritiesList), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 400 + assert client.post(api.url_for(AuthoritiesList), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 400 def test_auth_certificates_authorities_get(client): @@ -116,7 +116,7 @@ def test_admin_authority_get(client): def test_admin_authority_post(client): - assert client.post(api.url_for(Authorities, authority_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + assert client.post(api.url_for(Authorities, authority_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 def test_admin_authority_put(client): @@ -136,11 +136,11 @@ def test_admin_authorities_get(client): def test_admin_authorities_post(client): - assert client.post(api.url_for(AuthoritiesList), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 + assert client.post(api.url_for(AuthoritiesList), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 def test_admin_authorities_put(client): - assert client.put(api.url_for(AuthoritiesList), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + assert client.put(api.url_for(AuthoritiesList), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 def test_admin_authorities_delete(client): diff --git a/lemur/tests/test_certificates.py b/lemur/tests/test_certificates.py index 4dc23a6c..5990c09c 100644 --- a/lemur/tests/test_certificates.py +++ b/lemur/tests/test_certificates.py @@ -1,16 +1,6 @@ import pytest from lemur.certificates.views import * -#def test_crud(session): -# role = create('role1') -# assert role.id > 0 -# -# role = update(role.id, 'role_new', None, []) -# assert role.name == 'role_new' -# delete(role.id) -# assert get(role.id) == None - - def test_valid_authority(session): assert 1 == 2 @@ -50,7 +40,7 @@ def test_create_basic_csr(): assert name.value in csr_config.values() -def test_import_certificate(session): +def test_import_certificate(): assert 1 == 2 @@ -133,20 +123,17 @@ def test_create_name(): True ) == 'SAN-example.com-ExampleInc-20150507-20150512' -def test_is_expired(): - assert 1 == 2 - def test_certificate_get(client): assert client.get(api.url_for(Certificates, certificate_id=1)).status_code == 401 def test_certificate_post(client): - assert client.post(api.url_for(Certificates, certificate_id=1), {}).status_code == 405 + assert client.post(api.url_for(Certificates, certificate_id=1), data={}).status_code == 405 def test_certificate_put(client): - assert client.put(api.url_for(Certificates, certificate_id=1), {}).status_code == 401 + assert client.put(api.url_for(Certificates, certificate_id=1), data={}).status_code == 401 def test_certificate_delete(client): @@ -154,7 +141,7 @@ def test_certificate_delete(client): def test_certificate_patch(client): - assert client.patch(api.url_for(Certificates, certificate_id=1), {}).status_code == 405 + assert client.patch(api.url_for(Certificates, certificate_id=1), data={}).status_code == 405 def test_certificates_get(client): @@ -162,11 +149,11 @@ def test_certificates_get(client): def test_certificates_post(client): - assert client.post(api.url_for(CertificatesList), {}).status_code == 401 + assert client.post(api.url_for(CertificatesList), data={}).status_code == 401 def test_certificates_put(client): - assert client.put(api.url_for(CertificatesList), {}).status_code == 405 + assert client.put(api.url_for(CertificatesList), data={}).status_code == 405 def test_certificates_delete(client): @@ -174,7 +161,7 @@ def test_certificates_delete(client): def test_certificates_patch(client): - assert client.patch(api.url_for(CertificatesList), {}).status_code == 405 + assert client.patch(api.url_for(CertificatesList), data={}).status_code == 405 def test_certificate_credentials_get(client): @@ -182,11 +169,11 @@ def test_certificate_credentials_get(client): def test_certificate_credentials_post(client): - assert client.post(api.url_for(CertificatePrivateKey, certificate_id=1), {}).status_code == 405 + assert client.post(api.url_for(CertificatePrivateKey, certificate_id=1), data={}).status_code == 405 def test_certificate_credentials_put(client): - assert client.put(api.url_for(CertificatePrivateKey, certificate_id=1), {}).status_code == 405 + assert client.put(api.url_for(CertificatePrivateKey, certificate_id=1), data={}).status_code == 405 def test_certificate_credentials_delete(client): @@ -194,7 +181,7 @@ def test_certificate_credentials_delete(client): def test_certificate_credentials_patch(client): - assert client.patch(api.url_for(CertificatePrivateKey, certificate_id=1), {}).status_code == 405 + assert client.patch(api.url_for(CertificatePrivateKey, certificate_id=1), data={}).status_code == 405 def test_certificates_upload_get(client): @@ -202,11 +189,11 @@ def test_certificates_upload_get(client): def test_certificates_upload_post(client): - assert client.post(api.url_for(CertificatesUpload), {}).status_code == 401 + assert client.post(api.url_for(CertificatesUpload), data={}).status_code == 401 def test_certificates_upload_put(client): - assert client.put(api.url_for(CertificatesUpload), {}).status_code == 405 + assert client.put(api.url_for(CertificatesUpload), data={}).status_code == 405 def test_certificates_upload_delete(client): @@ -214,7 +201,7 @@ def test_certificates_upload_delete(client): def test_certificates_upload_patch(client): - assert client.patch(api.url_for(CertificatesUpload), {}).status_code == 405 + assert client.patch(api.url_for(CertificatesUpload), data={}).status_code == 405 VALID_USER_HEADER_TOKEN = { @@ -226,7 +213,7 @@ def test_auth_certificate_get(client): def test_auth_certificate_post_(client): - assert client.post(api.url_for(Certificates, certificate_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.post(api.url_for(Certificates, certificate_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 def test_auth_certificate_put(client): @@ -238,7 +225,7 @@ def test_auth_certificate_delete(client): def test_auth_certificate_patch(client): - assert client.patch(api.url_for(Certificates, certificate_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.patch(api.url_for(Certificates, certificate_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 def test_auth_certificates_get(client): @@ -246,7 +233,7 @@ def test_auth_certificates_get(client): def test_auth_certificates_post(client): - assert client.post(api.url_for(CertificatesList), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 400 + assert client.post(api.url_for(CertificatesList), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 400 def test_auth_certificate_credentials_get(client): @@ -254,11 +241,11 @@ def test_auth_certificate_credentials_get(client): def test_auth_certificate_credentials_post(client): - assert client.post(api.url_for(CertificatePrivateKey, certificate_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.post(api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 def test_auth_certificate_credentials_put(client): - assert client.put(api.url_for(CertificatePrivateKey, certificate_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.put(api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 def test_auth_certificate_credentials_delete(client): @@ -266,7 +253,7 @@ def test_auth_certificate_credentials_delete(client): def test_auth_certificate_credentials_patch(client): - assert client.patch(api.url_for(CertificatePrivateKey, certificate_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.patch(api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 def test_auth_certificates_upload_get(client): @@ -274,11 +261,11 @@ def test_auth_certificates_upload_get(client): def test_auth_certificates_upload_post(client): - assert client.post(api.url_for(CertificatesUpload), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 400 + assert client.post(api.url_for(CertificatesUpload), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 400 def test_auth_certificates_upload_put(client): - assert client.put(api.url_for(CertificatesUpload), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.put(api.url_for(CertificatesUpload), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 def test_auth_certificates_upload_delete(client): @@ -286,7 +273,7 @@ def test_auth_certificates_upload_delete(client): def test_auth_certificates_upload_patch(client): - assert client.patch(api.url_for(CertificatesUpload), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.patch(api.url_for(CertificatesUpload), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 VALID_ADMIN_HEADER_TOKEN = { @@ -298,7 +285,7 @@ def test_admin_certificate_get(client): def test_admin_certificate_post(client): - assert client.post(api.url_for(Certificates, certificate_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + assert client.post(api.url_for(Certificates, certificate_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 def test_admin_certificate_put(client): @@ -310,7 +297,7 @@ def test_admin_certificate_delete(client): def test_admin_certificate_patch(client): - assert client.patch(api.url_for(Certificates, certificate_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + assert client.patch(api.url_for(Certificates, certificate_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 def test_admin_certificates_get(client): @@ -324,7 +311,7 @@ def test_admin_certificate_credentials_get(client): def test_admin_certificate_credentials_post(client): - assert client.post(api.url_for(CertificatePrivateKey, certificate_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + assert client.post(api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 def test_admin_certificate_credentials_put(client): @@ -336,5 +323,5 @@ def test_admin_certificate_credentials_delete(client): def test_admin_certificate_credentials_patch(client): - assert client.patch(api.url_for(CertificatePrivateKey, certificate_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + assert client.patch(api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 diff --git a/lemur/tests/test_domains.py b/lemur/tests/test_domains.py index 9d57f142..e8f94728 100644 --- a/lemur/tests/test_domains.py +++ b/lemur/tests/test_domains.py @@ -5,11 +5,11 @@ def test_domain_get(client): def test_domain_post(client): - assert client.post(api.url_for(Domains, domain_id=1), {}).status_code == 405 + assert client.post(api.url_for(Domains, domain_id=1), data={}).status_code == 405 def test_domain_put(client): - assert client.put(api.url_for(Domains, domain_id=1), {}).status_code == 405 + assert client.put(api.url_for(Domains, domain_id=1), data={}).status_code == 405 def test_domain_delete(client): @@ -17,7 +17,7 @@ def test_domain_delete(client): def test_domain_patch(client): - assert client.patch(api.url_for(Domains, domain_id=1), {}).status_code == 405 + assert client.patch(api.url_for(Domains, domain_id=1), data={}).status_code == 405 VALID_USER_HEADER_TOKEN = { @@ -28,7 +28,7 @@ def test_auth_domain_get(client): def test_auth_domain_post_(client): - assert client.post(api.url_for(Domains, domain_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.post(api.url_for(Domains, domain_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 def test_auth_domain_put(client): @@ -40,7 +40,7 @@ def test_auth_domain_delete(client): def test_auth_domain_patch(client): - assert client.patch(api.url_for(Domains, domain_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.patch(api.url_for(Domains, domain_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 VALID_ADMIN_HEADER_TOKEN = { @@ -51,7 +51,7 @@ def test_admin_domain_get(client): def test_admin_domain_post(client): - assert client.post(api.url_for(Domains, domain_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + assert client.post(api.url_for(Domains, domain_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 def test_admin_domain_put(client): @@ -63,7 +63,7 @@ def test_admin_domain_delete(client): def test_admin_domain_patch(client): - assert client.patch(api.url_for(Domains, domain_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + assert client.patch(api.url_for(Domains, domain_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 def test_domains_get(client): @@ -71,11 +71,11 @@ def test_domains_get(client): def test_domains_post(client): - assert client.post(api.url_for(DomainsList), {}).status_code == 405 + assert client.post(api.url_for(DomainsList), data={}).status_code == 405 def test_domains_put(client): - assert client.put(api.url_for(DomainsList), {}).status_code == 405 + assert client.put(api.url_for(DomainsList), data={}).status_code == 405 def test_domains_delete(client): @@ -83,7 +83,7 @@ def test_domains_delete(client): def test_domains_patch(client): - assert client.patch(api.url_for(DomainsList), {}).status_code == 405 + assert client.patch(api.url_for(DomainsList), data={}).status_code == 405 def test_auth_domains_get(client): @@ -101,11 +101,11 @@ def test_certificate_domains_get(client): def test_certificate_domains_post(client): - assert client.post(api.url_for(CertificateDomains, certificate_id=1), {}).status_code == 405 + assert client.post(api.url_for(CertificateDomains, certificate_id=1), data={}).status_code == 405 def test_certificate_domains_put(client): - assert client.put(api.url_for(CertificateDomains, certificate_id=1), {}).status_code == 405 + assert client.put(api.url_for(CertificateDomains, certificate_id=1), data={}).status_code == 405 def test_certificate_domains_delete(client): @@ -113,7 +113,7 @@ def test_certificate_domains_delete(client): def test_certificate_domains_patch(client): - assert client.patch(api.url_for(CertificateDomains, certificate_id=1), {}).status_code == 405 + assert client.patch(api.url_for(CertificateDomains, certificate_id=1), data={}).status_code == 405 def test_auth_certificate_domains_get(client): diff --git a/lemur/tests/test_pack/challenge.txt b/lemur/tests/test_pack/challenge.txt deleted file mode 100644 index 21c7ddb9..00000000 --- a/lemur/tests/test_pack/challenge.txt +++ /dev/null @@ -1 +0,0 @@ -KRPZPA&*!_~%dbnuzf153594 \ No newline at end of file diff --git a/lemur/tests/test_pack/csr_config.txt b/lemur/tests/test_pack/csr_config.txt deleted file mode 100644 index 0704e5bc..00000000 --- a/lemur/tests/test_pack/csr_config.txt +++ /dev/null @@ -1,38 +0,0 @@ - - # Configuration for standard CSR generation for Netflix - # Used for procuring VeriSign certificates - # Author: jachan - # Contact: cloudsecurity@netflix.com - - [ req ] - # Use a 2048 bit private key - default_bits = 2048 - default_keyfile = key.pem - prompt = no - encrypt_key = no - - # base request - distinguished_name = req_distinguished_name - - # extensions - # Uncomment the following line if you are requesting a SAN cert - #req_extensions = req_ext - - # distinguished_name - [ req_distinguished_name ] - countryName = "US" # C= - stateOrProvinceName = "CALIFORNIA" # ST= - localityName = "Los Gatos" # L= - organizationName = "Netflix, Inc." # O= - organizationalUnitName = "Operations" # OU= - # This is the hostname/subject name on the certificate - commonName = "dfdsflkj.net" # CN= - - [ req_ext ] - # Uncomment the following line if you are requesting a SAN cert - #subjectAltName = @alt_names - - [alt_names] - # Put your SANs here - - \ No newline at end of file diff --git a/lemur/tests/test_pack/private.key b/lemur/tests/test_pack/private.key deleted file mode 100644 index a70fee5f..00000000 --- a/lemur/tests/test_pack/private.key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEAvNudwW+UeQqkpY71MIdEg501AFlPKuOXG2xU8DZhvZS6dKv+ -kDmIWdEqodDgkQiy0jyTgTwxwRqDSw96R6ZgrXefUoJJo66aCsosTBZtVaE85f1L -bj2+3U678c+rekUdkrnGcGCo6b8QtdvBpiDy2clneox8tSvmffAdcR1uCv/790/k -PzQ/djWDX9JcBRyDkcTJwYC0/ek7URvA/+MXmgUL13T+gWKqduaKuIBlFetonDjn -nO11QUBiusIuHV62wzKn8m5Nc+4XoaBR0YWMFn/g6qXDYrwfCsMpka7vSWJFv5Ff -yf+7kY3wU4xIwU2vXlIDcCsdUu6b/pYoQ0YOsQIDAQABAoIBAGbFH6iWnnXrq8MH -8zcQNOFmF+RztRgCt0TOA76f6TowB/LbcXBsTl2J7CgYMUvbLuwm2KHX7r9FPTMI -XiNFT5C16rYMfiQbLGo4sDhLb/3L+wawem6oHQfzA2VH++lSWRByFaEriF+CgIZl -6pALl/uZlLzkXCx+kjPwCSV3vV0wFkDnNs6+wPrz2IhkePsuC8J0QKQLlwsES2It -Gizzhpehdv9lc9MyZC//1QlD9gMDl5ok5Bt1Xm2c12XUEEcLlKQkJxiOrBOfXPmV -PHCdLc7gZO30hc6dyQ1SSnLpywhz/a0ir2GMvkMbS5hculpcZmwEcdZl1HYD8ObP -yOMbPE0CgYEA4LVGJKGtbM8RiBB0MstxNstMYVJ4mXB0lSQ0RazdO3S3ojn+oLpF -b2pvV6m9WnHiCGigWkzhqtGGCo6aqE0MoiR4jTN8GhiZz4ggDDaVgc4Px5reUD+r -tRsTpBHseGQ+ODGgkMI8eJYkdyqkECkYjAOrdy6uorvgxUAZecRIfJMCgYEA1yhM -7NidTNRuA+huS5GcQwQweTM6P1qF7Kfk1JYQMVu4gibLZiLHlWCyHI9lrbI7IaMm -g/4jXXoewv7IvyrrSEFulkPeVWxCe3mjfQ8JANfUj4kuR915LSn4lX2pbUgUS66K -vJSUJtnzLUmb8khLEcOmDbmTFZl8D/bTHFFZlisCgYAeelfWNhuoq3lMRDcOgKuN -bAujE6WJ4kfdxrhUTvr+ynjxxv3zXPB4CS6q7Dnjn5ix3UcKmGzvV1Xf7rGpbDHv -eBTlyfrmKzoJfQQjw++JWKKpRycqKUin2tFSKqAxQB90Tb7ig4XiMTMm+qCgFILg -0sqZ8rn7FpKJDoWmD2ppgwKBgG2Dl9QeVcKbhfv7PNi+HvmFkl6+knFY1D4nHzSN -xWQ6OWoV8QXlwgzokQA0hR6qT6rJbntUyg90b1/1a5zSbbvzgiR+GxcD6bsLqQmo -s354XTtKKgJuWpWAfYUp1ylGvP3gs8FVJyu3WC2+/9+MqJk8KrNlt9YQr7M4gTAy -wBTNAoGAGU7Po4uI3xDKGLLK/ot3D3P8U9ByfeLlrUZtTz1PASsMOr92bkXmUPlE -DYUd5uFfwwlvbMNT1Ooeyrzg3bARd9B6ATyMkOaJeGoQwFAI468iucnm9rNXB+/t -U2rbIi1pXSm8zSNEY85tf6C8DU/5YbcAPf47a2UYhwCpYAJfMk0= ------END RSA PRIVATE KEY----- diff --git a/lemur/tests/test_pack/request.csr b/lemur/tests/test_pack/request.csr deleted file mode 100644 index 7b2eecf5..00000000 --- a/lemur/tests/test_pack/request.csr +++ /dev/null @@ -1,17 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIICvzCCAacCAQAwejELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNBTElGT1JOSUEx -EjAQBgNVBAcTCUxvcyBHYXRvczEWMBQGA1UEChMNTmV0ZmxpeCwgSW5jLjETMBEG -A1UECxMKT3BlcmF0aW9uczEVMBMGA1UEAxMMZGZkc2Zsa2oubmV0MIIBIjANBgkq -hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvNudwW+UeQqkpY71MIdEg501AFlPKuOX -G2xU8DZhvZS6dKv+kDmIWdEqodDgkQiy0jyTgTwxwRqDSw96R6ZgrXefUoJJo66a -CsosTBZtVaE85f1Lbj2+3U678c+rekUdkrnGcGCo6b8QtdvBpiDy2clneox8tSvm -ffAdcR1uCv/790/kPzQ/djWDX9JcBRyDkcTJwYC0/ek7URvA/+MXmgUL13T+gWKq -duaKuIBlFetonDjnnO11QUBiusIuHV62wzKn8m5Nc+4XoaBR0YWMFn/g6qXDYrwf -CsMpka7vSWJFv5Ffyf+7kY3wU4xIwU2vXlIDcCsdUu6b/pYoQ0YOsQIDAQABoAAw -DQYJKoZIhvcNAQEFBQADggEBAE8b0+IYGiR64Me/L0/njYvSR5WR4EnjW99Sc8X5 -k93zpk4hExrZhrlkDBA/jUHhBZcPNV9w/YkhSu5ubPjRp9gRM2d4B9gGJFAs+bwe -LS9hCOxWIMKgvaBMEDQFcwqAv6kEJzmrIa7LtWS39wNfdko2hANtm7z9qskc8bPr -265+Z48DwSNCF4RPhVp9eDifjHrj0I//GMXYa92uvgj1BlPo/SGMS+XFQF779p2b -622HmUCop3pYeIyYd6rirvl9+KwqvIhm2MqHk62eHOK7Bn/FPev8OUDeV6pIvvSV -UxsEHjjLm0V/lOD65lROc7dTq4jO5PkpoKnFQDgV5v0Bf/k= ------END CERTIFICATE REQUEST----- diff --git a/lemur/tests/test_pack/server.crt b/lemur/tests/test_pack/server.crt deleted file mode 100644 index 64ec5358..00000000 --- a/lemur/tests/test_pack/server.crt +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDcDCCAlgCCQC8msHu/aa61zANBgkqhkiG9w0BAQUFADB6MQswCQYDVQQGEwJV -UzETMBEGA1UECBMKQ0FMSUZPUk5JQTESMBAGA1UEBxMJTG9zIEdhdG9zMRYwFAYD -VQQKEw1OZXRmbGl4LCBJbmMuMRMwEQYDVQQLEwpPcGVyYXRpb25zMRUwEwYDVQQD -EwxkZmRzZmxrai5uZXQwHhcNMTQwNTI1MTczMDMzWhcNMTUwNTI1MTczMDMzWjB6 -MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ0FMSUZPUk5JQTESMBAGA1UEBxMJTG9z -IEdhdG9zMRYwFAYDVQQKEw1OZXRmbGl4LCBJbmMuMRMwEQYDVQQLEwpPcGVyYXRp -b25zMRUwEwYDVQQDEwxkZmRzZmxrai5uZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IB -DwAwggEKAoIBAQC8253Bb5R5CqSljvUwh0SDnTUAWU8q45cbbFTwNmG9lLp0q/6Q -OYhZ0Sqh0OCRCLLSPJOBPDHBGoNLD3pHpmCtd59SgkmjrpoKyixMFm1VoTzl/Utu -Pb7dTrvxz6t6RR2SucZwYKjpvxC128GmIPLZyWd6jHy1K+Z98B1xHW4K//v3T+Q/ -ND92NYNf0lwFHIORxMnBgLT96TtRG8D/4xeaBQvXdP6BYqp25oq4gGUV62icOOec -7XVBQGK6wi4dXrbDMqfybk1z7hehoFHRhYwWf+DqpcNivB8KwymRru9JYkW/kV/J -/7uRjfBTjEjBTa9eUgNwKx1S7pv+lihDRg6xAgMBAAEwDQYJKoZIhvcNAQEFBQAD -ggEBAJHwa4l2iSiFBb6wVFBJEWEt31qp+njiVCoTg2OJzCT60Xb26hkrsiTldIIh -eB9+y+fwdfwopzWhkNbIOlCfudx/uxtpor8/3BRbjSlNwDUg2L8pfAircJMFLQUM -O6nqPOBWCe8hXwe9FQM/oFOavf/AAw/FED+892xlytjirK9u3B28O20W11+fY7hp -8LQVBrMoVxFeLWmmwETAltJ7HEYutplRzYTM0vLBARl4Vd5kLJlY3j2Dp1ZpRGcg -CrQp26UD/oaAPGtiZQSC4LJ+4JfOuuqbm3CI24QMCh9rxv3ZoOQnFuC+7cZgqrat -V4bxCrVvWhrrDSgy9+A80NVzQ3k= ------END CERTIFICATE----- diff --git a/lemur/tests/test_roles.py b/lemur/tests/test_roles.py index b40e0772..7339aeb4 100644 --- a/lemur/tests/test_roles.py +++ b/lemur/tests/test_roles.py @@ -18,11 +18,11 @@ def test_role_get(client): def test_role_post(client): - assert client.post(api.url_for(Roles, role_id=1), {}).status_code == 405 + assert client.post(api.url_for(Roles, role_id=1), data={}).status_code == 405 def test_role_put(client): - assert client.put(api.url_for(Roles, role_id=1), {}).status_code == 401 + assert client.put(api.url_for(Roles, role_id=1), data={}).status_code == 401 def test_role_delete(client): @@ -30,7 +30,7 @@ def test_role_delete(client): def test_role_patch(client): - assert client.patch(api.url_for(Roles, role_id=1), {}).status_code == 405 + assert client.patch(api.url_for(Roles, role_id=1), data={}).status_code == 405 def test_roles_get(client): @@ -38,11 +38,11 @@ def test_roles_get(client): def test_roles_post(client): - assert client.post(api.url_for(RolesList), {}).status_code == 401 + assert client.post(api.url_for(RolesList), data={}).status_code == 401 def test_roles_put(client): - assert client.put(api.url_for(RolesList), {}).status_code == 405 + assert client.put(api.url_for(RolesList), data={}).status_code == 405 def test_roles_delete(client): @@ -50,7 +50,7 @@ def test_roles_delete(client): def test_roles_patch(client): - assert client.patch(api.url_for(RolesList), {}).status_code == 405 + assert client.patch(api.url_for(RolesList), data={}).status_code == 405 def test_role_credentials_get(client): @@ -58,11 +58,11 @@ def test_role_credentials_get(client): def test_role_credentials_post(client): - assert client.post(api.url_for(RoleViewCredentials, role_id=1), {}).status_code == 405 + assert client.post(api.url_for(RoleViewCredentials, role_id=1), data={}).status_code == 405 def test_role_credentials_put(client): - assert client.put(api.url_for(RoleViewCredentials, role_id=1), {}).status_code == 405 + assert client.put(api.url_for(RoleViewCredentials, role_id=1), data={}).status_code == 405 def test_role_credentials_delete(client): @@ -70,7 +70,7 @@ def test_role_credentials_delete(client): def test_role_credentials_patch(client): - assert client.patch(api.url_for(RoleViewCredentials, role_id=1), {}).status_code == 405 + assert client.patch(api.url_for(RoleViewCredentials, role_id=1), data={}).status_code == 405 def test_user_roles_get(client): @@ -78,11 +78,11 @@ def test_user_roles_get(client): def test_user_roles_post(client): - assert client.post(api.url_for(UserRolesList, user_id=1), {}).status_code == 405 + assert client.post(api.url_for(UserRolesList, user_id=1), data={}).status_code == 405 def test_user_roles_put(client): - assert client.put(api.url_for(UserRolesList, user_id=1), {}).status_code == 405 + assert client.put(api.url_for(UserRolesList, user_id=1), data={}).status_code == 405 def test_user_roles_delete(client): @@ -90,7 +90,7 @@ def test_user_roles_delete(client): def test_user_roles_patch(client): - assert client.patch(api.url_for(UserRolesList, user_id=1), {}).status_code == 405 + assert client.patch(api.url_for(UserRolesList, user_id=1), data={}).status_code == 405 def test_authority_roles_get(client): @@ -98,11 +98,11 @@ def test_authority_roles_get(client): def test_authority_roles_post(client): - assert client.post(api.url_for(AuthorityRolesList, authority_id=1), {}).status_code == 405 + assert client.post(api.url_for(AuthorityRolesList, authority_id=1), data={}).status_code == 405 def test_authority_roles_put(client): - assert client.put(api.url_for(AuthorityRolesList, authority_id=1), {}).status_code == 405 + assert client.put(api.url_for(AuthorityRolesList, authority_id=1), data={}).status_code == 405 def test_authority_roles_delete(client): @@ -110,7 +110,7 @@ def test_authority_roles_delete(client): def test_authority_roles_patch(client): - assert client.patch(api.url_for(AuthorityRolesList, authority_id=1), {}).status_code == 405 + assert client.patch(api.url_for(AuthorityRolesList, authority_id=1), data={}).status_code == 405 VALID_USER_HEADER_TOKEN = { @@ -122,7 +122,7 @@ def test_auth_role_get(client): def test_auth_role_post_(client): - assert client.post(api.url_for(Roles, role_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.post(api.url_for(Roles, role_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 def test_auth_role_put(client): @@ -134,7 +134,7 @@ def test_auth_role_delete(client): def test_auth_role_patch(client): - assert client.patch(api.url_for(Roles, role_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.patch(api.url_for(Roles, role_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 def test_auth_roles_get(client): @@ -142,7 +142,7 @@ def test_auth_roles_get(client): def test_auth_roles_post(client): - assert client.post(api.url_for(RolesList), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 403 + assert client.post(api.url_for(RolesList), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 403 def test_auth_role_credentials_get(client): @@ -150,11 +150,11 @@ def test_auth_role_credentials_get(client): def test_auth_role_credentials_post(client): - assert client.post(api.url_for(RoleViewCredentials, role_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.post(api.url_for(RoleViewCredentials, role_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 def test_auth_role_credentials_put(client): - assert client.put(api.url_for(RoleViewCredentials, role_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.put(api.url_for(RoleViewCredentials, role_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 def test_auth_role_credentials_delete(client): @@ -162,7 +162,7 @@ def test_auth_role_credentials_delete(client): def test_auth_role_credentials_patch(client): - assert client.patch(api.url_for(RoleViewCredentials, role_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.patch(api.url_for(RoleViewCredentials, role_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 def test_auth_user_roles_get(client): @@ -170,11 +170,11 @@ def test_auth_user_roles_get(client): def test_auth_user_roles_post(client): - assert client.post(api.url_for(UserRolesList, user_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.post(api.url_for(UserRolesList, user_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 def test_auth_user_roles_put(client): - assert client.put(api.url_for(UserRolesList, user_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.put(api.url_for(UserRolesList, user_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 def test_auth_user_roles_delete(client): @@ -182,7 +182,7 @@ def test_auth_user_roles_delete(client): def test_auth_user_roles_patch(client): - assert client.patch(api.url_for(UserRolesList, user_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.patch(api.url_for(UserRolesList, user_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 def test_auth_authority_roles_get(client): @@ -190,11 +190,11 @@ def test_auth_authority_roles_get(client): def test_auth_authority_roles_post(client): - assert client.post(api.url_for(AuthorityRolesList, authority_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.post(api.url_for(AuthorityRolesList, authority_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 def test_auth_authority_roles_put(client): - assert client.put(api.url_for(AuthorityRolesList, authority_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.put(api.url_for(AuthorityRolesList, authority_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 def test_auth_authority_roles_delete(client): @@ -202,7 +202,7 @@ def test_auth_authority_roles_delete(client): def test_auth_authority_roles_patch(client): - assert client.patch(api.url_for(AuthorityRolesList, authority_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 + assert client.patch(api.url_for(AuthorityRolesList, authority_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 VALID_ADMIN_HEADER_TOKEN = { @@ -214,7 +214,7 @@ def test_admin_role_get(client): def test_admin_role_post(client): - assert client.post(api.url_for(Roles, role_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + assert client.post(api.url_for(Roles, role_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 def test_admin_role_put(client): @@ -226,7 +226,7 @@ def test_admin_role_delete(client): def test_admin_role_patch(client): - assert client.patch(api.url_for(Roles, role_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + assert client.patch(api.url_for(Roles, role_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 def test_admin_roles_get(client): @@ -240,11 +240,11 @@ def test_admin_role_credentials_get(client): def test_admin_role_credentials_post(client): - assert client.post(api.url_for(RolesList), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 + assert client.post(api.url_for(RolesList), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 def test_admin_role_credentials_put(client): - assert client.put(api.url_for(RolesList), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + assert client.put(api.url_for(RolesList), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 def test_admin_role_credentials_delete(client): @@ -252,7 +252,7 @@ def test_admin_role_credentials_delete(client): def test_admin_role_credentials_patch(client): - assert client.patch(api.url_for(RolesList), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + assert client.patch(api.url_for(RolesList), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 def test_admin_user_roles_get(client): @@ -260,11 +260,11 @@ def test_admin_user_roles_get(client): def test_admin_user_roles_post(client): - assert client.post(api.url_for(UserRolesList, user_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + assert client.post(api.url_for(UserRolesList, user_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 def test_admin_user_roles_put(client): - assert client.put(api.url_for(UserRolesList, user_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + assert client.put(api.url_for(UserRolesList, user_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 def test_admin_user_roles_delete(client): @@ -272,7 +272,7 @@ def test_admin_user_roles_delete(client): def test_admin_user_roles_patch(client): - assert client.patch(api.url_for(UserRolesList, user_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + assert client.patch(api.url_for(UserRolesList, user_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 def test_admin_authority_roles_get(client): @@ -280,11 +280,11 @@ def test_admin_authority_roles_get(client): def test_admin_authority_roles_post(client): - assert client.post(api.url_for(AuthorityRolesList, authority_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + assert client.post(api.url_for(AuthorityRolesList, authority_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 def test_admin_authority_roles_put(client): - assert client.put(api.url_for(AuthorityRolesList, authority_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + assert client.put(api.url_for(AuthorityRolesList, authority_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 def test_admin_authority_roles_delete(client): @@ -292,7 +292,7 @@ def test_admin_authority_roles_delete(client): def test_admin_authority_roles_patch(client): - assert client.patch(api.url_for(AuthorityRolesList, authority_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 + assert client.patch(api.url_for(AuthorityRolesList, authority_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 def test_admin_roles_crud(client): diff --git a/setup.py b/setup.py index 5e07860b..d5cf38bc 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,8 @@ tests_require = [ 'pyflakes', 'moto', 'nose', - 'pytest' + 'pytest', + 'pytest-flask' ] docs_require = [ From 6d384f342f51292fd0f6169f83219bdb04c6d484 Mon Sep 17 00:00:00 2001 From: kevgliss Date: Tue, 7 Jul 2015 15:32:55 -0700 Subject: [PATCH 21/31] adding test utils --- docs/developer/plugins/index.rst | 43 +++++++++++++++++++------------- lemur/plugins/bases/issuer.py | 5 +--- lemur/pytest.py | 0 3 files changed, 27 insertions(+), 21 deletions(-) create mode 100644 lemur/pytest.py diff --git a/docs/developer/plugins/index.rst b/docs/developer/plugins/index.rst index b7cdcd2a..6bfbd205 100644 --- a/docs/developer/plugins/index.rst +++ b/docs/developer/plugins/index.rst @@ -1,11 +1,11 @@ Writing a Plugin ================ -**The plugin interface is a work in progress.** - Several interfaces exist for extending Lemur: -* Issuers (lemur.issuers) +* Issuer (lemur.plugins.base.issuer) +* Destination (lemur.plugins.base.destination) +* Source (lemur.plugins.base.source) Structure --------- @@ -29,9 +29,9 @@ if you want to pull the version using pkg_resources (which is what we recommend) Inside of ``plugin.py``, you'll declare your Plugin class:: import lemur_pluginname - from lemur.common.services.issuers.plugins import Issuer + from lemur.plugins.base.issuer import IssuerPlugin - class PluginName(Plugin): + class PluginName(IssuerPlugin): title = 'Plugin Name' slug = 'pluginname' description = 'My awesome plugin!' @@ -55,27 +55,36 @@ And you'll register it via ``entry_points`` in your ``setup.py``:: ) -That's it! Users will be able to install your plugin via ``pip install `` and configure it -via the web interface based on the hooks you enabled. +That's it! Users will be able to install your plugin via ``pip install ``. + +Interfaces +========== + +Lemur has several different plugin interfaces that are used to extend Lemur, each of them require +that you subclass and override their functions in order for your plugin to function. -Permissions -=========== +Issuer +------ -As described in the plugin interface, Lemur provides a suite of permissions. +Issuer plugins are to be used when you want to allow Lemur to use external services to create certificates. +In the simple case this means that you have one Certificate Authority and you ask it for certificates given a +few parameters. In a more advanced case this could mean that this third party not only allows you to create certifcates +but also allows you to create Certificate Authorities and Sub Certificate Authorities. -In most cases, a admin (that is, if User.is_admin is ``True``), will be granted implicit permissions -on everything. +The `IssuerPlugin` interface only required that you implement one function:: -This page attempts to describe those permissions, and the contextual objects along with them. + def create_certificate(self, options): + # requests.get('a third party') -.. data:: add_project +Lemur will pass a dictionary of all possible options for certificate creation. - Controls whether a user can create a new project. +Optionally the `IssuerPlugin` exposes another function for authority create:: - :: + def create_authority(self, options): + # request.get('a third party') - >>> has_perm('add_project', user) +If implemented this function will be used to allow users to create external Certificate Authorities. Testing diff --git a/lemur/plugins/bases/issuer.py b/lemur/plugins/bases/issuer.py index d3ac9989..b7cb14a4 100644 --- a/lemur/plugins/bases/issuer.py +++ b/lemur/plugins/bases/issuer.py @@ -17,10 +17,7 @@ class IssuerPlugin(Plugin): raise NotImplementedError def create_authority(self): - raise NotImplementedError - - def get_authorities(self): - raise NotImplementedError + raise NotImplemented def get_csr_config(self): raise NotImplementedError diff --git a/lemur/pytest.py b/lemur/pytest.py new file mode 100644 index 00000000..e69de29b From f660450043628150f40abdb3a7c6595c0122f6bb Mon Sep 17 00:00:00 2001 From: kevgliss Date: Tue, 7 Jul 2015 17:23:46 -0700 Subject: [PATCH 22/31] Aligning config variables --- lemur/auth/service.py | 6 +++--- lemur/certificates/service.py | 13 +++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lemur/auth/service.py b/lemur/auth/service.py index 5fd20f42..6386f6d0 100644 --- a/lemur/auth/service.py +++ b/lemur/auth/service.py @@ -72,13 +72,13 @@ def create_token(user): :param user: :return: """ - expiration_delta = timedelta(days=int(current_app.config.get('TOKEN_EXPIRATION', 1))) + expiration_delta = timedelta(days=int(current_app.config.get('LEMUR_TOKEN_EXPIRATION', 1))) payload = { 'sub': user.id, 'iat': datetime.now(), 'exp': datetime.now() + expiration_delta } - token = jwt.encode(payload, current_app.config['TOKEN_SECRET']) + token = jwt.encode(payload, current_app.config['LEMUR_TOKEN_SECRET']) return token.decode('unicode_escape') @@ -102,7 +102,7 @@ def login_required(f): return dict(message='Token is invalid'), 403 try: - payload = jwt.decode(token, current_app.config['TOKEN_SECRET']) + payload = jwt.decode(token, current_app.config['LEMUR_TOKEN_SECRET']) except jwt.DecodeError: return dict(message='Token is invalid'), 403 except jwt.ExpiredSignatureError: diff --git a/lemur/certificates/service.py b/lemur/certificates/service.py index fb52a812..5cfb6484 100644 --- a/lemur/certificates/service.py +++ b/lemur/certificates/service.py @@ -301,14 +301,15 @@ def create_csr(csr_config): backend=default_backend() ) + # TODO When we figure out a better way to validate these options they should be parsed as unicode builder = x509.CertificateSigningRequestBuilder() builder = builder.subject_name(x509.Name([ - x509.NameAttribute(x509.OID_COMMON_NAME, csr_config['commonName']), - x509.NameAttribute(x509.OID_ORGANIZATION_NAME, csr_config['organization']), - x509.NameAttribute(x509.OID_ORGANIZATIONAL_UNIT_NAME, csr_config['organizationalUnit']), - x509.NameAttribute(x509.OID_COUNTRY_NAME, csr_config['country']), - x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, csr_config['state']), - x509.NameAttribute(x509.OID_LOCALITY_NAME, csr_config['location']) + x509.NameAttribute(x509.OID_COMMON_NAME, unicode(csr_config['commonName'])), + x509.NameAttribute(x509.OID_ORGANIZATION_NAME, unicode(csr_config['organization'])), + x509.NameAttribute(x509.OID_ORGANIZATIONAL_UNIT_NAME, unicode(csr_config['organizationalUnit'])), + x509.NameAttribute(x509.OID_COUNTRY_NAME, unicode(csr_config['country'])), + x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, unicode(csr_config['state'])), + x509.NameAttribute(x509.OID_LOCALITY_NAME, unicode(csr_config['location'])), ])) builder = builder.add_extension( From 6a18b01e4ec13e7e6c5bc5c04e651a1b9b344d61 Mon Sep 17 00:00:00 2001 From: kevgliss Date: Wed, 8 Jul 2015 16:34:21 -0700 Subject: [PATCH 23/31] Ensuring that we can use the gulp watcher and that fonts are copied over to the tmp dir --- gulp/server.js | 2 +- gulp/watch.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gulp/server.js b/gulp/server.js index 1f00c599..7ee20381 100644 --- a/gulp/server.js +++ b/gulp/server.js @@ -38,7 +38,7 @@ function browserSyncInit(baseDir, files, browser) { gulp.task('serve', ['watch'], function () { browserSyncInit([ '.tmp', - 'app' + 'lemur/static/app' ], [ '.tmp/*.html', '.tmp/styles/**/*.css', diff --git a/gulp/watch.js b/gulp/watch.js index 9d6f894c..460a935b 100644 --- a/gulp/watch.js +++ b/gulp/watch.js @@ -3,7 +3,7 @@ var gulp = require('gulp'); -gulp.task('watch', ['dev:styles', 'dev:scripts', 'dev:inject'] ,function () { +gulp.task('watch', ['dev:styles', 'dev:scripts', 'dev:inject', 'dev:fonts'] ,function () { gulp.watch('app/styles/**/*.less', ['dev:styles']); gulp.watch('app/styles/**/*.css', ['dev:styles']); gulp.watch('app/**/*.js', ['dev:scripts']); From 002f83092d684872e2793b4ec8e0afe6f4d92e7a Mon Sep 17 00:00:00 2001 From: kevgliss Date: Wed, 8 Jul 2015 16:37:48 -0700 Subject: [PATCH 24/31] Changing the signature of save_cert, we don't create a csr_config anymore so it doesn't make sense to store it. Additionally 'challenge' is a verisign specific thing and should be factored out. We have stopped saving it as well. --- lemur/authorities/service.py | 5 +++-- lemur/certificates/service.py | 9 ++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lemur/authorities/service.py b/lemur/authorities/service.py index 9bed0eea..f414d6a8 100644 --- a/lemur/authorities/service.py +++ b/lemur/authorities/service.py @@ -54,7 +54,7 @@ def create(kwargs): kwargs['creator'] = g.current_user.email cert_body, intermediate, issuer_roles = issuer.create_authority(kwargs) - cert = cert_service.save_cert(cert_body, None, intermediate, None, None, None) + cert = cert_service.save_cert(cert_body, None, intermediate, None) cert.user = g.current_user # we create and attach any roles that the issuer gives us @@ -65,9 +65,11 @@ def create(kwargs): password=r['password'], description="{0} auto generated role".format(kwargs.get('pluginName')), username=r['username']) + # the user creating the authority should be able to administer it if role.username == 'admin': g.current_user.roles.append(role) + role_objs.append(role) authority = Authority( @@ -80,7 +82,6 @@ def create(kwargs): roles=role_objs ) - # do this last encase we need to roll back/abort database.update(cert) authority = database.create(authority) diff --git a/lemur/certificates/service.py b/lemur/certificates/service.py index 5cfb6484..3b3b2089 100644 --- a/lemur/certificates/service.py +++ b/lemur/certificates/service.py @@ -173,7 +173,7 @@ def import_certificate(**kwargs): return cert -def save_cert(cert_body, private_key, cert_chain, challenge, csr_config, accounts): +def save_cert(cert_body, private_key, cert_chain, accounts): """ Determines if the certificate needs to be uploaded to AWS or other services. @@ -184,7 +184,7 @@ def save_cert(cert_body, private_key, cert_chain, challenge, csr_config, account :param csr_config: :param accounts: """ - cert = Certificate(cert_body, private_key, challenge, cert_chain, csr_config) + cert = Certificate(cert_body, private_key, cert_chain) # if we have an AWS accounts lets upload them if accounts: for account in accounts: @@ -204,8 +204,6 @@ def upload(**kwargs): kwargs.get('public_cert'), kwargs.get('private_key'), kwargs.get('intermediate_cert'), - None, - None, kwargs.get('accounts') ) @@ -223,6 +221,7 @@ def create(**kwargs): cert.owner = kwargs['owner'] database.create(cert) + cert.description = kwargs['description'] g.user.certificates.append(cert) database.update(g.user) return cert @@ -372,7 +371,7 @@ def create_csr(csr_config): # serialize our private key and CSR pem = private_key.private_bytes( encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.PKCS8, + format=serialization.PrivateFormat.TraditionalOpenSSL, # would like to use PKCS8 but AWS ELBs don't like it encryption_algorithm=serialization.NoEncryption() ) From 5156371913e63ebcf26e867f8eafb1ac5d6d69a2 Mon Sep 17 00:00:00 2001 From: kevgliss Date: Wed, 8 Jul 2015 16:39:00 -0700 Subject: [PATCH 25/31] Modify the naming structure for certificates. AWS is pretty picky about what is a valid name. --- lemur/certificates/models.py | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/lemur/certificates/models.py b/lemur/certificates/models.py index 47af84e5..5a46a015 100644 --- a/lemur/certificates/models.py +++ b/lemur/certificates/models.py @@ -20,13 +20,12 @@ from sqlalchemy_utils import EncryptedType from lemur.database import db from lemur.domains.models import Domain -from lemur.users import service as user_service from lemur.constants import SAN_NAMING_TEMPLATE, DEFAULT_NAMING_TEMPLATE, NONSTANDARD_NAMING_TEMPLATE from lemur.models import certificate_associations, certificate_account_associations -def create_name(issuer, not_before, not_after, common_name, san): +def create_name(issuer, not_before, not_after, subject, san): """ Create a name for our certificate. A naming standard is based on a series of templates. The name includes @@ -36,11 +35,6 @@ def create_name(issuer, not_before, not_after, common_name, san): :rtype : str :return: """ - delchars = ''.join(c for c in map(chr, range(256)) if not c.isalnum()) - # aws doesn't allow special chars - subject = common_name.replace('*', "WILDCARD") - issuer = issuer.translate(None, delchars) - if san: t = SAN_NAMING_TEMPLATE else: @@ -53,7 +47,14 @@ def create_name(issuer, not_before, not_after, common_name, san): not_after=not_after.strftime('%Y%m%d') ) - return temp + # NOTE we may want to give more control over naming + # aws doesn't allow special chars except '-' + disallowed_chars = ''.join(c for c in map(chr, range(256)) if not c.isalnum()) + disallowed_chars = disallowed_chars.replace("-", "") + temp = temp.replace('*', "WILDCARD") + temp = temp.translate(None, disallowed_chars) + # white space is silly too + return temp.replace(" ", "-") def cert_get_cn(cert): @@ -85,11 +86,6 @@ def cert_get_domains(cert): domains.append(entry) except Exception as e: current_app.logger.warning("Failed to get SubjectAltName: {0}".format(e)) - - # do a simple check to make sure it's a real domain - common_name = cert_get_cn(cert) - if '.' in common_name: - domains.append(common_name) return domains @@ -111,11 +107,8 @@ def cert_is_san(cert): :param cert: :return: Bool """ - domains = cert_get_domains(cert) - if len(domains) > 1: + if len(cert_get_domains(cert)) > 1: return True - return False - def cert_is_wildcard(cert): """ @@ -127,7 +120,6 @@ def cert_is_wildcard(cert): domains = cert_get_domains(cert) if len(domains) == 1 and domains[0][0:1] == "*": return True - return False def cert_get_bitstrength(cert): @@ -205,8 +197,8 @@ class Certificate(db.Model): owner = Column(String(128)) body = Column(Text()) private_key = Column(EncryptedType(String, os.environ.get('LEMUR_ENCRYPTION_KEY'))) - challenge = Column(EncryptedType(String, os.environ.get('LEMUR_ENCRYPTION_KEY'))) - csr_config = Column(Text()) + challenge = Column(EncryptedType(String, os.environ.get('LEMUR_ENCRYPTION_KEY'))) # TODO deprecate + csr_config = Column(Text()) # TODO deprecate status = Column(String(128)) deleted = Column(Boolean, index=True) name = Column(String(128)) @@ -227,13 +219,11 @@ class Certificate(db.Model): domains = relationship("Domain", secondary=certificate_associations, backref="certificate") elb_listeners = relationship("Listener", lazy='dynamic', backref='certificate') - def __init__(self, body, private_key=None, challenge=None, chain=None, csr_config=None): + def __init__(self, body, private_key=None, chain=None): self.body = body # We encrypt the private_key on creation self.private_key = private_key self.chain = chain - self.csr_config = csr_config - self.challenge = challenge cert = x509.load_pem_x509_certificate(str(self.body), default_backend()) self.bits = cert_get_bitstrength(cert) self.issuer = cert_get_issuer(cert) From 1a2e437b33d090d2ae1dbc46bcc3eae403a16250 Mon Sep 17 00:00:00 2001 From: kevgliss Date: Wed, 8 Jul 2015 16:40:46 -0700 Subject: [PATCH 26/31] Factoring out 'dry' run. This doesn't really make sense to have as we don't have a concept of a pre-flight request. Plugin tests should mock out their particular dependencies. --- lemur/plugins/lemur_cloudca/plugin.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lemur/plugins/lemur_cloudca/plugin.py b/lemur/plugins/lemur_cloudca/plugin.py index f1ca11bc..8d42cb3b 100644 --- a/lemur/plugins/lemur_cloudca/plugin.py +++ b/lemur/plugins/lemur_cloudca/plugin.py @@ -306,9 +306,6 @@ class CloudCAPlugin(IssuerPlugin): :param data: :return: """ - if self.dry_run: - endpoint += '?dry_run=1' - data = dumps(dict(data.items() + get_auth_data(data['caName']).items())) # we set a low timeout, if cloudca is down it shouldn't bring down @@ -323,9 +320,6 @@ class CloudCAPlugin(IssuerPlugin): :param endpoint: :return: """ - if self.dry_run: - endpoint += '?dry_run=1' - response = self.session.get(self.url + endpoint, timeout=10, verify=self.ca_bundle) return process_response(response) From bc6202adf79fe16d1106de4a2608f97f03acc8c5 Mon Sep 17 00:00:00 2001 From: kevgliss Date: Wed, 8 Jul 2015 16:41:45 -0700 Subject: [PATCH 27/31] Refactoring out static methods and removing the old SHA1 intermediate certificates. --- lemur/plugins/lemur_verisign/constants.py | 65 +---------------------- lemur/plugins/lemur_verisign/plugin.py | 41 +++++++------- 2 files changed, 23 insertions(+), 83 deletions(-) diff --git a/lemur/plugins/lemur_verisign/constants.py b/lemur/plugins/lemur_verisign/constants.py index 541ee769..0f90ed98 100644 --- a/lemur/plugins/lemur_verisign/constants.py +++ b/lemur/plugins/lemur_verisign/constants.py @@ -1,5 +1,4 @@ -VERISIGN_INTERMEDIATE = """ ------BEGIN CERTIFICATE----- +VERISIGN_INTERMEDIATE = """-----BEGIN CERTIFICATE----- MIIFFTCCA/2gAwIBAgIQKC4nkXkzkuQo8iGnTsk3rjANBgkqhkiG9w0BAQsFADCB yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMTk5OSBWZXJp @@ -31,8 +30,7 @@ J+71/xuzAYN6 -----END CERTIFICATE----- """ -VERISIGN_ROOT = """ ------BEGIN CERTIFICATE----- +VERISIGN_ROOT = """-----BEGIN CERTIFICATE----- MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu @@ -58,62 +56,3 @@ TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== -----END CERTIFICATE----- """ -OLD_VERISIGN_INTERMEDIATE = """ ------BEGIN CERTIFICATE----- -MIIFlTCCBH2gAwIBAgIQLP62CQ7ireLp/CI3JPG2vzANBgkqhkiG9w0BAQUFADCB -yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL -ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMTk5OSBWZXJp -U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW -ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 -aG9yaXR5IC0gRzMwHhcNMTAwMjA4MDAwMDAwWhcNMjAwMjA3MjM1OTU5WjCBtTEL -MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW -ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2UgYXQg -aHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykxMDEvMC0GA1UEAxMmVmVy -aVNpZ24gQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzMwggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQCxh4QfwgxF9byrJZenraI+nLr2wTm4i8rCrFbG -5btljkRPTc5v7QlK1K9OEJxoiy6Ve4mbE8riNDTB81vzSXtig0iBdNGIeGwCU/m8 -f0MmV1gzgzszChew0E6RJK2GfWQS3HRKNKEdCuqWHQsV/KNLO85jiND4LQyUhhDK -tpo9yus3nABINYYpUHjoRWPNGUFP9ZXse5jUxHGzUL4os4+guVOc9cosI6n9FAbo -GLSa6Dxugf3kzTU2s1HTaewSulZub5tXxYsU5w7HnO1KVGrJTcW/EbGuHGeBy0RV -M5l/JJs/U0V/hhrzPPptf4H1uErT9YU3HLWm0AnkGHs4TvoPAgMBAAGjggGIMIIB -hDASBgNVHRMBAf8ECDAGAQH/AgEAMHAGA1UdIARpMGcwZQYLYIZIAYb4RQEHFwMw -VjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL2NwczAqBggr -BgEFBQcCAjAeGhxodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhMA4GA1UdDwEB -/wQEAwIBBjBtBggrBgEFBQcBDARhMF+hXaBbMFkwVzBVFglpbWFnZS9naWYwITAf -MAcGBSsOAwIaBBSP5dMahqyNjmvDz4Bq1EgYLHsZLjAlFiNodHRwOi8vbG9nby52 -ZXJpc2lnbi5jb20vdnNsb2dvLmdpZjAoBgNVHREEITAfpB0wGzEZMBcGA1UEAxMQ -VmVyaVNpZ25NUEtJLTItNjAdBgNVHQ4EFgQUDURcFlNEwYJ+HSCrJfQBY9i+eaUw -NAYDVR0fBC0wKzApoCegJYYjaHR0cDovL2NybC52ZXJpc2lnbi5jb20vcGNhMy1n -My5jcmwwDQYJKoZIhvcNAQEFBQADggEBAHREFQzFWA4YY+3z8CjDeuuSSG/ghSBJ -olwwlpIX4IjoeYuzT864Hzk2tTeEeODf4YFIVsSxah8nUsGdpgVTUGPPoUJOMXvn -8wJeBSlUDXBwv3td5XbPIPXHy6vmIS6phYRetZUgq1CDTI/pvtWZKXTGM/eYXlLF -6QDvXevUHQjfb3cqQvfLljws85xLxbNFmz7cy9YmiLOd5n+gFC6X5hzSDO7+DDMi -o//+4Q/nk/UId1UCsobqYWVmqs017AmyiAPO/v3sGncYYQY2BMYgla74dZfeDNu4 -MXA68Mb6ZdlkhGEmZYVBcOmkaKs+P+SggTofsK27BlpugAtNWjEy5JY= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEOzCCA6SgAwIBAgIQSsnqCI7m94zHpfn6OaSTljANBgkqhkiG9w0BAQUFADBf -MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT -LkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw -HhcNMTEwNjA5MDAwMDAwWhcNMjExMTA3MjM1OTU5WjCByjELMAkGA1UEBhMCVVMx -FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz -dCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMTk5OSBWZXJpU2lnbiwgSW5jLiAtIEZv -ciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAz -IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzMwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLupxS/HgfGh5vGzdzvfjJa5QS -ME/wNkf10JEK9RfIpWHBFkBN+4phkOV2IMERBn2rLG6m9RFBjvotrSphWaRnJkzQ -6LxSW3AgBFjResmkabyDF2StBYu80FjOjYz16/BCSQudlydnMm7hrpMVHHC8IE0v -GN6SiOhshVcRGul+4yYRVKJFllWDyjCJ6NzYo+0qgD9/eWVXPhUgZggvlZO/qkcv -qEaX8BLi/sIKK1Hmdua3RrfiDabMqMNMWVWJ5uhTXBzqnfBiFgunyV8M8N7Cds6v -92ry+kGmojMUyeV6Y9OeYjfVhWWeDuZTJHQbXh0SU1vHLOeDSTsVropouVeXAgMB -AAGjggEGMIIBAjAPBgNVHRMBAf8EBTADAQH/MD0GA1UdIAQ2MDQwMgYEVR0gADAq -MCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy52ZXJpc2lnbi5jb20vY3BzMDEGA1Ud -HwQqMCgwJqAkoCKGIGh0dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMuY3JsMA4G -A1UdDwEB/wQEAwIBBjBtBggrBgEFBQcBDARhMF+hXaBbMFkwVzBVFglpbWFnZS9n -aWYwITAfMAcGBSsOAwIaBBSP5dMahqyNjmvDz4Bq1EgYLHsZLjAlFiNodHRwOi8v -bG9nby52ZXJpc2lnbi5jb20vdnNsb2dvLmdpZjANBgkqhkiG9w0BAQUFAAOBgQBl -2Sr58sJgybnqQQfKNrcYL2iu/gMk5mdU7nTDLNn1M8Fetw6Tz3iejrImFBFT0cjC -EiG0PXsq2BzUS2TsiU+/lYeH3pVk9HPGF9+9GZCX6GmBEmlmStMkQA5ZdRWwRHQX -op4GYNOwg7jdL+afe2dcFqFH284ueQXZ8fT4PuJKoQ== ------END CERTIFICATE----- -""" diff --git a/lemur/plugins/lemur_verisign/plugin.py b/lemur/plugins/lemur_verisign/plugin.py index c31f499f..59adaeaa 100644 --- a/lemur/plugins/lemur_verisign/plugin.py +++ b/lemur/plugins/lemur_verisign/plugin.py @@ -53,10 +53,28 @@ VERISIGN_ERRORS = { "0x6013": "only supports DSA keys with (2048, 256) as the bit lengths of the prime parameter pair (p, q), other DSA key sizes will get this error", "0x600d": "RSA key size < 2A048", "0x4828": "Verisign certificates can be at most two years in length", - "0x3043": "Certificates must have a validity of at least 1 day" + "0x3043": "Certificates must have a validity of at least 1 day", + "0x950b": "CSR: Invalid State", } +def handle_response(content): + """ + Helper function that helps with parsing responses from the Verisign API. + :param content: + :return: :raise Exception: + """ + d = xmltodict.parse(content) + global VERISIGN_ERRORS + if d.get('Error'): + status_code = d['Error']['StatusCode'] + elif d.get('Response'): + status_code = d['Response']['StatusCode'] + if status_code in VERISIGN_ERRORS.keys(): + raise Exception(VERISIGN_ERRORS[status_code]) + return d + + class VerisignPlugin(IssuerPlugin): title = 'VeriSign' slug = 'verisign' @@ -71,23 +89,6 @@ class VerisignPlugin(IssuerPlugin): self.session.cert = current_app.config.get('VERISIGN_PEM_PATH') super(VerisignPlugin, self).__init__(*args, **kwargs) - @staticmethod - def handle_response(content): - """ - Helper function that helps with parsing responses from the Verisign API. - :param content: - :return: :raise Exception: - """ - d = xmltodict.parse(content) - global VERISIGN_ERRORS - if d.get('Error'): - status_code = d['Error']['StatusCode'] - elif d.get('Response'): - status_code = d['Response']['StatusCode'] - if status_code in VERISIGN_ERRORS.keys(): - raise Exception(VERISIGN_ERRORS[status_code]) - return d - def create_certificate(self, csr, issuer_options): """ Creates a Verisign certificate. @@ -125,7 +126,7 @@ class VerisignPlugin(IssuerPlugin): current_app.logger.info("Requesting a new verisign certificate: {0}".format(data)) response = self.session.post(url, data=data) - cert = self.handle_response(response.content)['Response']['Certificate'] + cert = handle_response(response.content)['Response']['Certificate'] return cert, constants.VERISIGN_INTERMEDIATE, @staticmethod @@ -149,5 +150,5 @@ class VerisignPlugin(IssuerPlugin): """ url = current_app.config.get("VERISIGN_URL") + '/getTokens' response = self.session.post(url, headers={'content-type': 'application/x-www-form-urlencoded'}) - return self.handle_response(response.content)['Response']['Order'] + return handle_response(response.content)['Response']['Order'] From 67dc12347e576241e79895824150f0a2a307834d Mon Sep 17 00:00:00 2001 From: kevgliss Date: Wed, 8 Jul 2015 16:42:57 -0700 Subject: [PATCH 28/31] Removing verisign specific frontend code, we also give some more hints to user on how to make SAN certificates. --- .../certificate/tracking.tpl.html | 38 +++++-------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/lemur/static/app/angular/certificates/certificate/tracking.tpl.html b/lemur/static/app/angular/certificates/certificate/tracking.tpl.html index 025f41d2..4dab6044 100644 --- a/lemur/static/app/angular/certificates/certificate/tracking.tpl.html +++ b/lemur/static/app/angular/certificates/certificate/tracking.tpl.html @@ -20,40 +20,20 @@

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

-
+
- + class="form-control" typeahead-wait-ms="100" typeahead-template-url="angular/authorities/authority/select.tpl.html" required>
-
-
- -
-
- - - - -
- - - - - -
{{ domain.value }} - -
-
-
-
-
+
@@ -61,19 +41,19 @@
-
- +

You must enter a common name

-