diff --git a/.gitignore b/.gitignore index 3235fc47..7505ec58 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,5 @@ pip-log.txt docs/_build .editorconfig .idea +test.conf +lemur/tests/tmp \ No newline at end of file diff --git a/.jshintignore b/.jshintignore index 82755159..55644736 100644 --- a/.jshintignore +++ b/.jshintignore @@ -1,4 +1,2 @@ -tests/ -lemur/static/lemur/scripts/lib/ -lemur/static/lemur/dist/ -lemur/static/lemur/vendor/ \ No newline at end of file +lemur/static//dist/ +lemur/static/app/vendor/ \ No newline at end of file diff --git a/.jshintrc b/.jshintrc index 40377ba2..c40849ed 100644 --- a/.jshintrc +++ b/.jshintrc @@ -3,13 +3,13 @@ "browser": true, "esnext": true, "bitwise": true, - "camelcase": true, + "camelcase": false, "curly": true, "eqeqeq": true, "immed": true, "indent": 2, "latedef": true, - "newcap": true, + "newcap": false, "noarg": true, "quotmark": "single", "regexp": true, @@ -19,6 +19,9 @@ "trailing": true, "smarttabs": true, "globals": { - "angular": false + "angular": false, + "moment": false, + "toaster": false, + "_": false } } diff --git a/.travis.yml b/.travis.yml index 83f4e22f..d4d11eaa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,26 @@ -language: node_js -node_js: - - '0.8' - - '0.10' +sudo: false +language: python +addons: + postgresql: "9.4" +python: + - "2.7" +cache: + directories: + - node_modules + - .pip_download_cache + - "$HOME/virtualenv/python2.7.9" +env: + global: + - PIP_DOWNLOAD_CACHE=".pip_download_cache" +install: + - make dev-postgres before_script: - - 'npm install -g bower grunt-cli' - - 'bower install' + - psql -c "create database lemur;" -U postgres + - psql -c "create user lemur with password 'lemur;'" -U postgres + - npm install -g bower +script: + - make test + +notifications: + email: + kglisson@netflix.com \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..dd2c2695 --- /dev/null +++ b/Makefile @@ -0,0 +1,85 @@ +NPM_ROOT = ./node_modules +STATIC_DIR = src/lemur/static/app + +develop: update-submodules setup-git + @echo "--> Installing dependencies" + npm install + pip install "setuptools>=0.9.8" + # order matters here, base package must install first + # this is temporary until the version we need is released + pip install -e 'git+https://git@github.com/pyca/cryptography.git#egg=cryptography-1.0.dev1' + pip install -e . + pip install "file://`pwd`#egg=lemur[dev]" + pip install "file://`pwd`#egg=lemur[tests]" + @echo "" + +dev-docs: + pip install -r docs/requirements.txt + +reset-db: + @echo "--> Dropping existing 'lemur' database" + dropdb lemur || true + @echo "--> Creating 'lemur' database" + createdb -E utf-8 lemur + @echo "--> Applying migrations" + lemur db upgrade + +setup-git: + @echo "--> Installing git hooks" + git config branch.autosetuprebase always + cd .git/hooks && ln -sf ../../hooks/* ./ + @echo "" + +clean: + @echo "--> Cleaning static cache" + ${NPM_ROOT}/.bin/gulp clean + @echo "--> Cleaning pyc files" + find . -name "*.pyc" -delete + @echo "" + +test: develop lint test-python + +testloop: develop + pip install pytest-xdist + py.test tests -f + +test-cli: + @echo "--> Testing CLI" + rm -rf test_cli + mkdir test_cli + cd test_cli && lemur create_config -c ./test.conf > /dev/null + cd test_cli && lemur -c ./test.conf db upgrade > /dev/null + cd test_cli && lemur -c ./test.conf help 2>&1 | grep start > /dev/null + rm -r test_cli + @echo "" + +test-js: + @echo "--> Running JavaScript tests" + npm test + @echo "" + +test-python: + @echo "--> Running Python tests" + py.test lemur/tests || exit 1 + @echo "" + +lint: lint-python lint-js + +lint-python: + @echo "--> Linting Python files" + PYFLAKES_NODOCTEST=1 flake8 lemur + @echo "" + +lint-js: + @echo "--> Linting JavaScript files" + npm run lint + @echo "" + +coverage: develop + coverage run --source=lemur -m py.test + coverage html + +publish: + python setup.py sdist bdist_wheel upload + +.PHONY: develop dev-postgres dev-docs setup-git build clean update-submodules test testloop test-cli test-js test-python lint lint-python lint-js coverage publish \ No newline at end of file diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..a77fbe3d --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,5 @@ +Jinja2>=2.3 +Pygments>=1.2 +Sphinx>=1.3 +docutils>=0.7 +markupsafe \ No newline at end of file diff --git a/gulp/build.js b/gulp/build.js index 4346a6de..54112294 100644 --- a/gulp/build.js +++ b/gulp/build.js @@ -26,9 +26,9 @@ var gulp = require('gulp'), imagemin = require('gulp-imagemin'), minifyHtml = require('gulp-minify-html'), bowerFiles = require('main-bower-files'), + karma = require('karma'), replace = require('gulp-replace'); - gulp.task('default', ['clean'], function () { gulp.start('fonts', 'styles'); }); @@ -37,6 +37,15 @@ gulp.task('clean', function (cb) { del(['.tmp', 'lemur/static/dist'], cb); }); +gulp.task('test', function (done) { + new karma.Server({ + configFile: __dirname + '/karma.conf.js', + singleRun: true + }, function() { + done(); + }).start(); +}); + gulp.task('dev:fonts', function () { var fileList = [ 'lemur/static/app/vendor/bower_components/bootstrap/dist/fonts/*', diff --git a/gulp/karma.conf.js b/gulp/karma.conf.js new file mode 100644 index 00000000..b9777c7a --- /dev/null +++ b/gulp/karma.conf.js @@ -0,0 +1,27 @@ +// Contents of: config/karma.conf.js +module.exports = function (config) { + config.set({ + basePath : '../', + + // Fix for "JASMINE is not supported anymore" warning + frameworks : ["jasmine"], + + files : [ + 'app/lib/angular/angular.js', + 'app/lib/angular/angular-*.js', + 'test/lib/angular/angular-mocks.js', + 'app/js/**/*.js', + 'test/unit/**/*.js' + ], + + autoWatch : true, + + browsers : ['Chrome'], + + junitReporter : { + outputFile : 'test_out/unit.xml', + suite : 'unit' + //... + } + }); +}; \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index a9da3d3c..1f79f902 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -27,5 +27,5 @@ gulp.task('default', function () { console.log(c.green + '-------------------------------------------' + c.reset); console.log(Object.keys(gulp.tasks).sort().join('\n')); console.log(''); - return; + }); diff --git a/hooks/pre-commit b/hooks/pre-commit old mode 100644 new mode 100755 index b41d5f35..5925a453 --- a/hooks/pre-commit +++ b/hooks/pre-commit @@ -29,14 +29,6 @@ def py_lint(files_modified): return report.total_errors != 0 -def js_lint(files_modified): - has_errors = False - if os.system('node_modules/.bin/jshint src/sentry'): - has_errors = True - - return has_errors - - def main(): from flake8.hooks import run @@ -46,7 +38,7 @@ def main(): files_modified = filter(lambda x: os.path.exists(x), files_modified) - if any((py_lint(files_modified), js_lint(files_modified))): + if py_lint(files_modified): return 1 return 0 diff --git a/lemur/__init__.py b/lemur/__init__.py index 59d8fff1..85bb846f 100644 --- a/lemur/__init__.py +++ b/lemur/__init__.py @@ -36,6 +36,7 @@ LEMUR_BLUEPRINTS = ( plugins_bp, ) + def create_app(config=None): app = factory.create_app(app_name=__name__, blueprints=LEMUR_BLUEPRINTS, config=config) configure_hook(app) @@ -61,4 +62,3 @@ def configure_hook(app): response = {'message': 'You are not allow to access this resource'} response.status_code = 403 return response - diff --git a/lemur/analyze/service.py b/lemur/analyze/service.py index 7b17d3db..8c384306 100644 --- a/lemur/analyze/service.py +++ b/lemur/analyze/service.py @@ -4,7 +4,7 @@ :license: Apache, see LICENSE for more details. .. moduleauthor:: Kevin Glisson """ -#def analyze(endpoints, truststores): +# def analyze(endpoints, truststores): # results = {"headings": ["Endpoint"], # "results": [], # "time": datetime.now().strftime("#Y%m%d %H:%M:%S")} @@ -37,7 +37,9 @@ # log.debug(e) # if 'hostname' in str(e): # tests.append('pass') -# result['details'].append("{}: This test passed ssl negotiation but failed hostname verification becuase the hostname is not included in the certificate".format(region)) +# result['details'].append( +# "{}: This test passed ssl negotiation but failed hostname verification because \ +# the hostname is not included in the certificate".format(region)) # elif 'certificate verify failed' in str(e): # tests.append('fail') # result['details'].append("{}: This test failed to verify the SSL certificate".format(region)) diff --git a/lemur/auth/service.py b/lemur/auth/service.py index 6386f6d0..807404b9 100644 --- a/lemur/auth/service.py +++ b/lemur/auth/service.py @@ -28,7 +28,7 @@ from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers from lemur.users import service as user_service from lemur.auth.permissions import CertificateOwnerNeed, CertificateCreatorNeed, \ - AuthorityCreatorNeed, AuthorityOwnerNeed, ViewRoleCredentialsNeed + AuthorityCreatorNeed, ViewRoleCredentialsNeed def base64url_decode(data): @@ -143,7 +143,6 @@ def fetch_token_header(token): raise jwt.DecodeError('Invalid header padding') - @identity_loaded.connect def on_identity_loaded(sender, identity): """ @@ -187,5 +186,3 @@ class AuthenticatedResource(Resource): def __init__(self): super(AuthenticatedResource, self).__init__() - - diff --git a/lemur/auth/views.py b/lemur/auth/views.py index fd7255ca..1ecbdeba 100644 --- a/lemur/auth/views.py +++ b/lemur/auth/views.py @@ -9,16 +9,16 @@ import jwt import base64 import requests -from flask import g, Blueprint, current_app, abort +from flask import g, Blueprint, current_app from flask.ext.restful import reqparse, Resource, Api from flask.ext.principal import Identity, identity_changed -from lemur.auth.permissions import admin_permission +from lemur.common.utils import get_psuedo_random_string + from lemur.users import service as user_service from lemur.roles import service as role_service -from lemur.certificates import service as cert_service -from lemur.auth.service import AuthenticatedResource, create_token, fetch_token_header, get_rsa_public_key +from lemur.auth.service import create_token, fetch_token_header, get_rsa_public_key mod = Blueprint('auth', __name__) @@ -203,7 +203,7 @@ class Ping(Resource): user = user_service.create( profile['email'], - cert_service.create_challenge(), + get_psuedo_random_string(), profile['email'], True, profile.get('thumbnailPhotoUrl'), @@ -222,7 +222,7 @@ class Ping(Resource): profile['email'], profile['email'], True, - profile.get('thumbnailPhotoUrl'), # incase profile isn't google+ enabled + profile.get('thumbnailPhotoUrl'), # Encase profile isn't google+ enabled roles ) @@ -234,5 +234,3 @@ class Ping(Resource): api.add_resource(Login, '/auth/login', endpoint='login') api.add_resource(Ping, '/auth/ping', endpoint='ping') - - diff --git a/lemur/authorities/service.py b/lemur/authorities/service.py index 25c42e8c..9c0abb95 100644 --- a/lemur/authorities/service.py +++ b/lemur/authorities/service.py @@ -19,6 +19,7 @@ import lemur.certificates.service as cert_service from lemur.plugins.base import plugins + def update(authority_id, active=None, roles=None): """ Update a an authority with new values. @@ -30,7 +31,7 @@ def update(authority_id, active=None, roles=None): """ authority = get(authority_id) if roles: - authority = database.update_list(authority, 'roles', Role, roles) + authority = database.update_list(authority, 'roles', Role, roles) if active: authority.active = active @@ -62,9 +63,9 @@ def create(kwargs): for r in issuer_roles: role = role_service.create( r['name'], - password=r['password'], - description="{0} auto generated role".format(kwargs.get('pluginName')), - username=r['username']) + 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': @@ -132,7 +133,7 @@ def get_authority_role(ca_name): """ if g.current_user.is_admin: authority = get_by_name(ca_name) - #TODO we should pick admin ca roles for admin + # TODO we should pick admin ca roles for admin return authority.roles[0] else: for role in g.current_user.roles: @@ -156,7 +157,7 @@ def render(args): if filt: terms = filt.split(';') - if 'active' in filt: # this is really weird but strcmp seems to not work here?? + if 'active' in filt: # this is really weird but strcmp seems to not work here?? query = query.filter(Authority.active == terms[1]) else: query = database.filter(query, Authority, terms) diff --git a/lemur/authorities/views.py b/lemur/authorities/views.py index cb1797ed..f449a837 100644 --- a/lemur/authorities/views.py +++ b/lemur/authorities/views.py @@ -183,8 +183,8 @@ class AuthoritiesList(AuthenticatedResource): self.reqparse.add_argument('caDescription', type=str, location='json', required=False) self.reqparse.add_argument('ownerEmail', type=str, location='json', required=True) self.reqparse.add_argument('caDN', type=dict, location='json', required=False) - self.reqparse.add_argument('validityStart', type=str, location='json', required=False) # TODO validate - self.reqparse.add_argument('validityEnd', type=str, location='json', required=False) # TODO validate + self.reqparse.add_argument('validityStart', type=str, location='json', required=False) # TODO validate + self.reqparse.add_argument('validityEnd', type=str, location='json', required=False) # TODO validate self.reqparse.add_argument('extensions', type=dict, location='json', required=False) self.reqparse.add_argument('pluginName', type=str, location='json', required=True) self.reqparse.add_argument('caType', type=str, location='json', required=False) diff --git a/lemur/certificates/exceptions.py b/lemur/certificates/exceptions.py index a9ed6e0a..83437704 100644 --- a/lemur/certificates/exceptions.py +++ b/lemur/certificates/exceptions.py @@ -53,6 +53,7 @@ class UnableToCreateCSR(LemurException): def __str__(self): return repr(self.data['message']) + class UnableToCreatePrivateKey(LemurException): def __init__(self): self.code = 500 @@ -63,6 +64,7 @@ class UnableToCreatePrivateKey(LemurException): def __str__(self): return repr(self.data['message']) + class MissingFiles(LemurException): def __init__(self, path): self.code = 500 @@ -84,4 +86,3 @@ class NoPersistanceFound(LemurException): def __str__(self): return repr(self.data['message']) - diff --git a/lemur/certificates/models.py b/lemur/certificates/models.py index 10e0900d..f807e1b6 100644 --- a/lemur/certificates/models.py +++ b/lemur/certificates/models.py @@ -21,7 +21,7 @@ from lemur.database import db from lemur.domains.models import Domain -from lemur.constants import SAN_NAMING_TEMPLATE, DEFAULT_NAMING_TEMPLATE, NONSTANDARD_NAMING_TEMPLATE +from lemur.constants import SAN_NAMING_TEMPLATE, DEFAULT_NAMING_TEMPLATE from lemur.models import certificate_associations, certificate_destination_associations @@ -51,6 +51,7 @@ def create_name(issuer, not_before, not_after, subject, san): # 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("-", "") + disallowed_chars = disallowed_chars.replace(".", "") temp = temp.replace('*', "WILDCARD") temp = temp.translate(None, disallowed_chars) # white space is silly too @@ -76,7 +77,7 @@ def cert_get_domains(cert): return the common name. :param cert: - :return: List of domainss + :return: List of domains """ domains = [] try: @@ -86,6 +87,7 @@ def cert_get_domains(cert): domains.append(entry) except Exception as e: current_app.logger.warning("Failed to get SubjectAltName: {0}".format(e)) + return domains @@ -110,6 +112,7 @@ def cert_is_san(cert): if len(cert_get_domains(cert)) > 1: return True + def cert_is_wildcard(cert): """ Determines if certificate is a wildcard certificate. @@ -121,6 +124,9 @@ def cert_is_wildcard(cert): if len(domains) == 1 and domains[0][0:1] == "*": return True + if cert.subject.get_attributes_for_oid(x509.OID_COMMON_NAME)[0].value[0:1] == "*": + return True + def cert_get_bitstrength(cert): """ @@ -197,8 +203,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'))) # TODO deprecate - csr_config = Column(Text()) # TODO deprecate + 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)) @@ -266,4 +272,3 @@ class Certificate(db.Model): def as_dict(self): return {c.name: getattr(self, c.name) for c in self.__table__.columns} - diff --git a/lemur/certificates/service.py b/lemur/certificates/service.py index b89cf159..f1997344 100644 --- a/lemur/certificates/service.py +++ b/lemur/certificates/service.py @@ -6,8 +6,6 @@ .. moduleauthor:: Kevin Glisson """ import arrow -import string -import random from sqlalchemy import func, or_ from flask import g, current_app @@ -27,7 +25,6 @@ from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import rsa - def get(cert_id): """ Retrieves certificate by it's ID. @@ -106,7 +103,6 @@ def mint(issuer_options): 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) @@ -212,8 +208,8 @@ def render(args): time_range = args.pop('time_range') destination_id = args.pop('destination_id') show = args.pop('show') - owner = args.pop('owner') - creator = args.pop('creator') # TODO we should enabling filtering by owner + # owner = args.pop('owner') + # creator = args.pop('creator') # TODO we should enabling filtering by owner filt = args.pop('filter') @@ -235,7 +231,7 @@ def render(args): if 'destination' in terms: query = query.filter(Certificate.destinations.any(Destination.id == terms[1])) - elif 'active' in filt: # this is really weird but strcmp seems to not work here?? + elif 'active' in filt: # this is really weird but strcmp seems to not work here?? query = query.filter(Certificate.active == terms[1]) else: query = database.filter(query, Certificate, terms) @@ -288,7 +284,7 @@ def create_csr(csr_config): x509.BasicConstraints(ca=False, path_length=None), critical=True, ) - #for k, v in csr_config.get('extensions', {}).items(): + # 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, @@ -354,16 +350,6 @@ def create_csr(csr_config): return csr, pem -def create_challenge(): - """ - Create a random and strongish csr challenge. - """ - challenge = ''.join(random.choice(string.ascii_uppercase) for x in range(6)) - challenge += ''.join(random.choice("~!@#$%^&*()_+") for x in range(6)) - challenge += ''.join(random.choice(string.ascii_lowercase) for x in range(6)) - challenge += ''.join(random.choice(string.digits) for x in range(6)) - return challenge - def stats(**kwargs): """ @@ -405,5 +391,3 @@ def stats(**kwargs): values.append(count) return {'labels': keys, 'values': values} - - diff --git a/lemur/certificates/sync.py b/lemur/certificates/sync.py index a3ec0d2f..b91af6b1 100644 --- a/lemur/certificates/sync.py +++ b/lemur/certificates/sync.py @@ -21,6 +21,7 @@ from lemur.certificates import service as cert_service from lemur.plugins.base import plugins from lemur.plugins.bases.source import SourcePlugin + def sync(): for plugin in plugins: new = 0 @@ -42,5 +43,4 @@ def sync(): # TODO associated cert with source # TODO update cert if found from different source - # TODO dissassociate source if missing - + # TODO disassociate source if missing diff --git a/lemur/certificates/verify.py b/lemur/certificates/verify.py index 4f3b0332..1e0febec 100644 --- a/lemur/certificates/verify.py +++ b/lemur/certificates/verify.py @@ -30,7 +30,7 @@ def ocsp_verify(cert_path, issuer_chain_path): url, err = p1.communicate() p2 = subprocess.Popen(['openssl', 'ocsp', '-issuer', issuer_chain_path, - '-cert', cert_path, "-url", url.strip()], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + '-cert', cert_path, "-url", url.strip()], stdout=subprocess.PIPE, stderr=subprocess.PIPE) message, err = p2.communicate() if 'error' in message or 'Error' in message: @@ -132,4 +132,4 @@ def verify_string(cert_string, issuer_string): def remove_tmp_file(file_path): - os.remove(file_path) \ No newline at end of file + os.remove(file_path) diff --git a/lemur/certificates/views.py b/lemur/certificates/views.py index f94011ce..df5f3737 100644 --- a/lemur/certificates/views.py +++ b/lemur/certificates/views.py @@ -51,7 +51,7 @@ def valid_authority(authority_options): """ Defends against invalid authorities - :param authority_name: + :param authority_options: :return: :raise ValueError: """ name = authority_options['name'] @@ -76,7 +76,7 @@ def pem_str(value, name): """ try: x509.load_pem_x509_certificate(str(value), default_backend()) - except Exception as e: + except Exception: raise ValueError("The parameter '{0}' needs to be a valid PEM string".format(name)) return value @@ -91,12 +91,11 @@ def private_key_str(value, name): """ try: serialization.load_pem_private_key(str(value), None, backend=default_backend()) - except Exception as e: + except Exception: raise ValueError("The parameter '{0}' needs to be a valid RSA private key".format(name)) return value - class CertificatesList(AuthenticatedResource): """ Defines the 'certificates' endpoint """ def __init__(self): @@ -274,8 +273,8 @@ class CertificatesList(AuthenticatedResource): self.reqparse.add_argument('destinations', type=list, default=[], location='json') self.reqparse.add_argument('elbs', type=list, location='json') self.reqparse.add_argument('owner', type=str, location='json') - self.reqparse.add_argument('validityStart', type=str, location='json') # parse date - self.reqparse.add_argument('validityEnd', type=str, location='json') # parse date + self.reqparse.add_argument('validityStart', type=str, location='json') # TODO validate + self.reqparse.add_argument('validityEnd', type=str, location='json') # TODO validate self.reqparse.add_argument('authority', type=valid_authority, location='json') self.reqparse.add_argument('description', type=str, location='json') self.reqparse.add_argument('country', type=str, location='json') diff --git a/lemur/common/health.py b/lemur/common/health.py index 11306de2..e5b6d509 100644 --- a/lemur/common/health.py +++ b/lemur/common/health.py @@ -10,6 +10,7 @@ from flask import Blueprint mod = Blueprint('healthCheck', __name__) + @mod.route('/healthcheck') def health(): - return 'ok' \ No newline at end of file + return 'ok' diff --git a/lemur/common/managers.py b/lemur/common/managers.py index 43079f46..4e693677 100644 --- a/lemur/common/managers.py +++ b/lemur/common/managers.py @@ -8,6 +8,7 @@ """ from flask import current_app + # inspired by https://github.com/getsentry/sentry class InstanceManager(object): def __init__(self, class_list=None, instances=True): @@ -61,4 +62,4 @@ class InstanceManager(object): continue self.cache = results - return results \ No newline at end of file + return results diff --git a/lemur/common/services/aws/ses.py b/lemur/common/services/aws/ses.py index 071437ca..8e5aeb4f 100644 --- a/lemur/common/services/aws/ses.py +++ b/lemur/common/services/aws/ses.py @@ -6,8 +6,8 @@ .. moduleauthor:: Kevin Glisson """ -from flask import current_app import boto.ses +from flask import current_app from lemur.templates.config import env @@ -22,8 +22,7 @@ def send(subject, data, email_type, recipients): :param recipients: """ conn = boto.connect_ses() - #jinja template depending on type + # jinja template depending on type template = env.get_template('{}.html'.format(email_type)) body = template.render(**data) conn.send_email(current_app.config.get("LEMUR_EMAIL"), subject, body, recipients, format='html') - diff --git a/lemur/common/utils.py b/lemur/common/utils.py index b0f1ded7..8380c579 100644 --- a/lemur/common/utils.py +++ b/lemur/common/utils.py @@ -6,16 +6,28 @@ .. moduleauthor:: Kevin Glisson """ +import string +import random from functools import wraps from flask import current_app from flask.ext.restful import marshal from flask.ext.restful.reqparse import RequestParser - from flask.ext.sqlalchemy import Pagination +def get_psuedo_random_string(): + """ + Create a random and strongish challenge. + """ + challenge = ''.join(random.choice(string.ascii_uppercase) for x in range(6)) # noqa + challenge += ''.join(random.choice("~!@#$%^&*()_+") for x in range(6)) # noqa + challenge += ''.join(random.choice(string.ascii_lowercase) for x in range(6)) + challenge += ''.join(random.choice(string.digits) for x in range(6)) # noqa + return challenge + + class marshal_items(object): def __init__(self, fields, envelope=None): self.fields = fields diff --git a/lemur/constants.py b/lemur/constants.py index 15d4bdee..3708bbec 100644 --- a/lemur/constants.py +++ b/lemur/constants.py @@ -6,5 +6,3 @@ SAN_NAMING_TEMPLATE = "SAN-{subject}-{issuer}-{not_before}-{not_after}" DEFAULT_NAMING_TEMPLATE = "{subject}-{issuer}-{not_before}-{not_after}" NONSTANDARD_NAMING_TEMPLATE = "{issuer}-{not_before}-{not_after}" - - diff --git a/lemur/database.py b/lemur/database.py index fe591ced..c2bb8f13 100644 --- a/lemur/database.py +++ b/lemur/database.py @@ -9,13 +9,11 @@ .. moduleauthor:: Kevin Glisson """ -from flask import current_app - from sqlalchemy import exc from sqlalchemy.sql import and_, or_ from lemur.extensions import db -from lemur.exceptions import AttrNotFound, IntegrityError, DuplicateError +from lemur.exceptions import AttrNotFound, DuplicateError def filter_none(kwargs): @@ -126,7 +124,7 @@ def get(model, value, field="id"): query = session_query(model) try: return query.filter(getattr(model, field) == value).one() - except: + except Exception: return @@ -178,8 +176,9 @@ def delete(model): :param model: """ - db.session.delete(model) - db.session.commit() + if model: + db.session.delete(model) + db.session.commit() def filter(query, model, terms): @@ -209,7 +208,7 @@ def sort(query, model, field, direction): direction = getattr(field, direction) query = query.order_by(direction()) return query - except AttributeError as e: + except AttributeError: raise AttrNotFound(field) @@ -274,6 +273,3 @@ def sort_and_page(query, model, args): query = sort(query, model, sort_by, sort_dir) return paginate(query, page, count) - - - diff --git a/lemur/decorators.py b/lemur/decorators.py index d1cb695b..70f8de6a 100644 --- a/lemur/decorators.py +++ b/lemur/decorators.py @@ -9,9 +9,10 @@ from flask import make_response, request, current_app from functools import update_wrapper +# this is only used for dev def crossdomain(origin=None, methods=None, headers=None, max_age=21600, attach_to_all=True, - automatic_options=True): + automatic_options=True): # pragma: no cover if methods is not None: methods = ', '.join(sorted(x.upper() for x in methods)) @@ -44,12 +45,10 @@ def crossdomain(origin=None, methods=None, headers=None, h['Access-Control-Allow-Origin'] = origin h['Access-Control-Allow-Methods'] = get_methods() h['Access-Control-Max-Age'] = str(max_age) - #if headers is not None: - h['Access-Control-Allow-Headers'] = "Origin, X-Requested-With, Content-Type, Accept, Authorization " # headers + h['Access-Control-Allow-Headers'] = "Origin, X-Requested-With, Content-Type, Accept, Authorization " h['Access-Control-Allow-Credentials'] = 'true' return resp f.provide_automatic_options = False return update_wrapper(wrapped_function, f) return decorator - diff --git a/lemur/default.conf.py b/lemur/default.conf.py index 1ca0cc72..c5f760fc 100644 --- a/lemur/default.conf.py +++ b/lemur/default.conf.py @@ -7,18 +7,13 @@ ADMINS = frozenset(['']) THREADS_PER_PAGE = 8 -############# -## General ## -############# +# General # These will need to be set to `True` if you are developing locally CORS = False debug = False -############# -## Logging ## -############# +# Logging LOG_LEVEL = "DEBUG" LOG_FILE = "lemur.log" - diff --git a/lemur/destinations/models.py b/lemur/destinations/models.py index 20f910bc..c4cc8a76 100644 --- a/lemur/destinations/models.py +++ b/lemur/destinations/models.py @@ -12,6 +12,7 @@ from lemur.database import db from lemur.plugins.base import plugins + class Destination(db.Model): __tablename__ = 'destinations' id = Column(Integer, primary_key=True) diff --git a/lemur/destinations/service.py b/lemur/destinations/service.py index d43d64d0..38dc600f 100644 --- a/lemur/destinations/service.py +++ b/lemur/destinations/service.py @@ -37,7 +37,7 @@ def update(destination_id, label, options, description): destination = get(destination_id) destination.label = label - description.options = options + destination.options = options destination.description = description return database.update(destination) @@ -107,4 +107,3 @@ def render(args): query = database.sort(query, Destination, sort_by, sort_dir) return database.paginate(query, page, count) - diff --git a/lemur/destinations/views.py b/lemur/destinations/views.py index 10c195ca..5d336e4a 100644 --- a/lemur/destinations/views.py +++ b/lemur/destinations/views.py @@ -229,7 +229,6 @@ class Destinations(AuthenticatedResource): return {'result': True} - class CertificateDestinations(AuthenticatedResource): """ Defines the 'certificate/', endpoint='account') -api.add_resource(CertificateDestinations, '/certificates//destinations', endpoint='certificateDestinations') - +api.add_resource(CertificateDestinations, '/certificates//destinations', + endpoint='certificateDestinations') diff --git a/lemur/domains/models.py b/lemur/domains/models.py index 14f52a3d..0bb62f65 100644 --- a/lemur/domains/models.py +++ b/lemur/domains/models.py @@ -24,4 +24,3 @@ class Domain(db.Model): blob = self.as_dict() blob['certificates'] = [x.id for x in self.certificate] return blob - diff --git a/lemur/domains/service.py b/lemur/domains/service.py index f9452bb3..b1e2d559 100644 --- a/lemur/domains/service.py +++ b/lemur/domains/service.py @@ -61,4 +61,3 @@ def render(args): query = database.sort(query, Domain, sort_by, sort_dir) return database.paginate(query, page, count) - diff --git a/lemur/elbs/models.py b/lemur/elbs/models.py index b334df8f..d57a9f1f 100644 --- a/lemur/elbs/models.py +++ b/lemur/elbs/models.py @@ -6,7 +6,7 @@ .. moduleauthor:: Kevin Glisson """ -from sqlalchemy import Column, BigInteger, String, ForeignKey, DateTime, PassiveDefault, func +from sqlalchemy import Column, BigInteger, String, DateTime, PassiveDefault, func from sqlalchemy.orm import relationship from lemur.database import db @@ -16,7 +16,7 @@ from lemur.listeners.models import Listener class ELB(db.Model): __tablename__ = 'elbs' id = Column(BigInteger, primary_key=True) - #account_id = Column(BigInteger, ForeignKey("accounts.id"), index=True) + # account_id = Column(BigInteger, ForeignKey("accounts.id"), index=True) region = Column(String(32)) name = Column(String(128)) vpc_id = Column(String(128)) diff --git a/lemur/elbs/service.py b/lemur/elbs/service.py index 8aa60ccb..d00110bf 100644 --- a/lemur/elbs/service.py +++ b/lemur/elbs/service.py @@ -14,6 +14,7 @@ from lemur import database from lemur.elbs.models import ELB from lemur.listeners.models import Listener + def get_all(account_id, elb_name): """ Retrieves all ELBs in a given account @@ -112,7 +113,7 @@ def stats(**kwargs): if kwargs.get('active') == 'true': query = query.join(ELB.listeners) - query = query.filter(Listener.certificate_id != None) + query = query.filter(Listener.certificate_id != None) # noqa items = query.group_by(attr).all() @@ -121,5 +122,3 @@ def stats(**kwargs): if key: results.append({"key": key, "y": count}) return results - - diff --git a/lemur/elbs/sync.py b/lemur/elbs/sync.py deleted file mode 100644 index f3d90ab0..00000000 --- a/lemur/elbs/sync.py +++ /dev/null @@ -1,72 +0,0 @@ - -""" -.. module: lemur.elbs.sync - :platform: Unix - :synopsis: This module attempts to sync with AWS and ensure that all elbs - currently available in AWS are available in Lemur as well - - :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.accounts import service as account_service -from lemur.elbs import service as elb_service -#from lemur.common.services.aws.elb import get_all_elbs, get_all_regions - - -def create_new(known, aws, account): - new = 0 - for elb in aws: - for n in known: - if elb.name == n.name: - break - else: - new += 1 - current_app.logger.debug("Creating {0}".format(elb.name)) - try: - elb_service.create(account, elb) - except AttributeError as e: - current_app.logger.exception(e) - return new - - -def remove_missing(known, aws): - deleted = 0 - for ke in known: - for elb in aws: - if elb.name == ke.name: - break - else: - deleted += 1 - current_app.logger.debug("Deleting {0}".format(ke.name)) - elb_service.delete(ke.id) - return deleted - - -def sync_all_elbs(): - for account in account_service.get_all(): - regions = get_all_regions() - for region in regions: - current_app.logger.info("Importing ELBs from '{0}/{1}/{2}'... ".format(account.account_number, account.label, region)) - try: - aws_elbs = get_all_elbs(account.account_number, region) - except Exception as e: - current_app.logger.error("Failed to get ELBS from '{0}/{1}/{2}' reason: {3}".format( - account.label, account.account_number, region, e.message) - ) - continue - - known_elbs = elb_service.get_by_region_and_account(region, account.id) - - new_elbs = create_new(known_elbs, aws_elbs, account) - current_app.logger.info( - "Created {0} new ELBs in '{1}/{2}/{3}'...".format( - new_elbs, account.account_number, account.label, region)) - - deleted_elbs = remove_missing(known_elbs, aws_elbs) - current_app.logger.info( - "Deleted {0} missing ELBs from '{1}/{2}/{3}'...".format( - deleted_elbs, account.account_number, account.label, region)) diff --git a/lemur/extensions.py b/lemur/extensions.py index 07101c4d..8c945f80 100644 --- a/lemur/extensions.py +++ b/lemur/extensions.py @@ -3,7 +3,6 @@ :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more :license: Apache, see LICENSE for more details. """ - from flask.ext.sqlalchemy import SQLAlchemy db = SQLAlchemy() @@ -15,4 +14,3 @@ bcrypt = Bcrypt() from flask.ext.principal import Principal principal = Principal() - diff --git a/lemur/factory.py b/lemur/factory.py index a606700a..f895a136 100644 --- a/lemur/factory.py +++ b/lemur/factory.py @@ -19,7 +19,6 @@ from logging.handlers import RotatingFileHandler from flask import Flask from lemur.common.health import mod as health -from lemur.exceptions import NoEncryptionKeyFound from lemur.extensions import db, migrate, principal @@ -161,7 +160,6 @@ def install_plugins(app): 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: diff --git a/lemur/listeners/models.py b/lemur/listeners/models.py index 8f83437d..b72c9b48 100644 --- a/lemur/listeners/models.py +++ b/lemur/listeners/models.py @@ -40,4 +40,3 @@ class Listener(db.Model): blob = self.as_dict() del blob['date_created'] return blob - diff --git a/lemur/listeners/service.py b/lemur/listeners/service.py index 25c19d8e..6f2ae596 100644 --- a/lemur/listeners/service.py +++ b/lemur/listeners/service.py @@ -18,7 +18,7 @@ from lemur.listeners.models import Listener from lemur.elbs import service as elb_service from lemur.certificates import service as certificate_service -#from lemur.common.services.aws.elb import update_listeners, create_new_listeners, delete_listeners +# from lemur.common.services.aws.elb import update_listeners, create_new_listeners, delete_listeners def verify_attachment(certificate_id, elb_account_number): @@ -60,7 +60,7 @@ def create(elb_id, instance_protocol, instance_port, load_balancer_port, load_ba cert = verify_attachment(certificate_id, account_number) listener_tuple = (load_balancer_port, instance_port, load_balancer_protocol, cert.get_art(account_number),) - create_new_listeners(account_number, elb.region, elb.name, [listener_tuple]) + # create_new_listeners(account_number, elb.region, elb.name, [listener_tuple]) return {'message': 'Listener has been created'} @@ -98,7 +98,7 @@ def update(listener_id, **kwargs): database.update(listener) listener_tuple = (listener.load_balancer_port, listener.instance_port, listener.load_balancer_protocol, arn,) - update_listeners(account_number, elb.region, elb.name, [listener_tuple], ports) + # update_listeners(account_number, elb.region, elb.name, [listener_tuple], ports) return {'message': 'Listener has been updated'} @@ -106,7 +106,7 @@ def update(listener_id, **kwargs): def delete(listener_id): # first try to delete the listener in aws listener = get(listener_id) - delete_listeners(listener.elb.account.account_number, listener.elb.region, listener.elb.name, [listener.load_balancer_port]) + # delete_listeners(listener.elb.account.account_number, listener.elb.region, listener.elb.name, [listener.load_balancer_port]) # cleanup operation in lemur database.delete(listener) @@ -149,7 +149,7 @@ def stats(**kwargs): query = query.filter(ELB.account_id == kwargs.get('account_id')) if kwargs.get('active') == 'true': - query = query.filter(Listener.certificate_id != None) + query = query.filter(Listener.certificate_id != None) # noqa items = query.group_by(attr).all() results = [] @@ -157,6 +157,3 @@ def stats(**kwargs): if key: results.append({"key": key, "y": count}) return results - - - diff --git a/lemur/manage.py b/lemur/manage.py index 8b255e33..c939fc58 100755 --- a/lemur/manage.py +++ b/lemur/manage.py @@ -20,19 +20,18 @@ from lemur.plugins.base import plugins from lemur.certificates.verify import verify_string from lemur.certificates import sync -from lemur.elbs.sync import sync_all_elbs from lemur import create_app # Needed to be imported so that SQLAlchemy create_all can find our models -from lemur.users.models import User -from lemur.roles.models import Role -from lemur.authorities.models import Authority -from lemur.certificates.models import Certificate -from lemur.destinations.models import Destination -from lemur.domains.models import Domain -from lemur.elbs.models import ELB -from lemur.listeners.models import Listener +from lemur.users.models import User # noqa +from lemur.roles.models import Role # noqa +from lemur.authorities.models import Authority # noqa +from lemur.certificates.models import Certificate # noqa +from lemur.destinations.models import Destination # noqa +from lemur.domains.models import Domain # noqa +from lemur.elbs.models import ELB # noqa +from lemur.listeners.models import Listener # noqa manager = Manager(create_app) manager.add_option('-c', '--config', dest='config') @@ -55,48 +54,42 @@ ADMINS = frozenset(['']) THREADS_PER_PAGE = 8 -############# -## General ## -############# +# General # These will need to be set to `True` if you are developing locally CORS = False debug = False +# this is the secret key used by flask session management +SECRET_KEY = '{flask_secret_key}' + # You should consider storing these separately from your config -LEMUR_SECRET_TOKEN = '{secret_token}' +LEMUR_TOKEN_SECRET = '{secret_token}' LEMUR_ENCRYPTION_KEY = '{encryption_key}' # this is a list of domains as regexes that only admins can issue LEMUR_RESTRICTED_DOMAINS = [] -################# -## Mail Server ## -################# +# Mail Server # Lemur currently only supports SES for sending email, this address # needs to be verified LEMUR_EMAIL = '' LEMUR_SECURITY_TEAM_EMAIL = [] -############# -## Logging ## -############# +# Logging LOG_LEVEL = "DEBUG" LOG_FILE = "lemur.log" -############## -## Database ## -############## +# Database -SQLALCHEMY_DATABASE_URI = '' +# modify this if you are not using a local database +SQLALCHEMY_DATABASE_URI = 'postgresql://lemur:lemur@localhost:5432/lemur' -######### -## AWS ## -######### +# AWS # Lemur will need STS assume role access to every destination you want to monitor #AWS_ACCOUNT_MAPPINGS = {{ @@ -129,6 +122,7 @@ SQLALCHEMY_DATABASE_URI = '' #VERSIGN_EMAIL = '' """ + @MigrateCommand.command def create(): database.db.create_all() @@ -178,7 +172,8 @@ def generate_settings(): """ output = CONFIG_TEMPLATE.format( encryption_key=base64.b64encode(os.urandom(KEY_LENGTH)), - secret_token=base64.b64encode(os.urandom(KEY_LENGTH)) + secret_token=base64.b64encode(os.urandom(KEY_LENGTH)), + flask_secret_key=base64.b64encode(os.urandom(KEY_LENGTH)), ) return output @@ -207,7 +202,7 @@ class Sync(Command): sys.stdout.write("[!] Starting to sync with AWS!\n") try: sync.aws() - #sync_all_elbs() + # sync_all_elbs() sys.stdout.write("[+] Finished syncing with AWS!\n") except Exception as e: sys.stdout.write("[-] Syncing with AWS failed!\n") @@ -480,8 +475,8 @@ def main(): manager.add_command("show_urls", ShowUrls()) manager.add_command("db", MigrateCommand) manager.add_command("init", InitializeApp()) - manager.add_command('create_user', CreateUser()) - manager.add_command('create_role', CreateRole()) + manager.add_command("create_user", CreateUser()) + manager.add_command("create_role", CreateRole()) manager.add_command("sync", Sync()) manager.run() diff --git a/lemur/migrations/versions/3b718f59b8ce_.py b/lemur/migrations/versions/3b718f59b8ce_.py new file mode 100644 index 00000000..1902e9d7 --- /dev/null +++ b/lemur/migrations/versions/3b718f59b8ce_.py @@ -0,0 +1,49 @@ +"""Refactors Accounts to Destinations + +Revision ID: 3b718f59b8ce +Revises: None +Create Date: 2015-07-09 17:44:55.626221 + +""" + +# revision identifiers, used by Alembic. +revision = '3b718f59b8ce' +down_revision = None + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.drop_table('certificate_account_associations') + op.drop_table('accounts') + op.add_column('destinations', sa.Column('plugin_name', sa.String(length=32), nullable=True)) + op.drop_index('ix_elbs_account_id', table_name='elbs') + op.drop_constraint(u'elbs_account_id_fkey', 'elbs', type_='foreignkey') + op.drop_column('elbs', 'account_id') + ### end Alembic commands ### + + +def downgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.add_column('elbs', sa.Column('account_id', sa.BIGINT(), autoincrement=False, nullable=True)) + op.create_foreign_key(u'elbs_account_id_fkey', 'elbs', 'accounts', ['account_id'], ['id']) + op.create_index('ix_elbs_account_id', 'elbs', ['account_id'], unique=False) + op.drop_column('destinations', 'plugin_name') + op.create_table('accounts', + sa.Column('id', sa.INTEGER(), server_default=sa.text(u"nextval('accounts_id_seq'::regclass)"), nullable=False), + sa.Column('account_number', sa.VARCHAR(length=32), autoincrement=False, nullable=True), + sa.Column('label', sa.VARCHAR(length=32), autoincrement=False, nullable=True), + sa.Column('notes', sa.TEXT(), autoincrement=False, nullable=True), + sa.PrimaryKeyConstraint('id', name=u'accounts_pkey'), + sa.UniqueConstraint('account_number', name=u'accounts_account_number_key'), + postgresql_ignore_search_path=False + ) + op.create_table('certificate_account_associations', + sa.Column('account_id', sa.INTEGER(), autoincrement=False, nullable=True), + sa.Column('certificate_id', sa.INTEGER(), autoincrement=False, nullable=True), + sa.ForeignKeyConstraint(['account_id'], [u'accounts.id'], name=u'certificate_account_associations_account_id_fkey', ondelete=u'CASCADE'), + sa.ForeignKeyConstraint(['certificate_id'], [u'certificates.id'], name=u'certificate_account_associations_certificate_id_fkey', ondelete=u'CASCADE') + ) + ### end Alembic commands ### diff --git a/lemur/models.py b/lemur/models.py index 493e1778..f12e49b6 100644 --- a/lemur/models.py +++ b/lemur/models.py @@ -14,17 +14,18 @@ from sqlalchemy import Column, Integer, ForeignKey from lemur.database import db certificate_associations = db.Table('certificate_associations', - Column('domain_id', Integer, ForeignKey('domains.id')), - Column('certificate_id', Integer, ForeignKey('certificates.id')) -) + Column('domain_id', Integer, ForeignKey('domains.id')), + Column('certificate_id', Integer, ForeignKey('certificates.id')) + ) certificate_destination_associations = db.Table('certificate_destination_associations', - Column('destination_id', Integer, ForeignKey('destinations.id', ondelete='cascade')), - Column('certificate_id', Integer, ForeignKey('certificates.id', ondelete='cascade')) -) + Column('destination_id', Integer, + ForeignKey('destinations.id', ondelete='cascade')), + Column('certificate_id', Integer, + ForeignKey('certificates.id', ondelete='cascade')) + ) roles_users = db.Table('roles_users', - Column('user_id', Integer, ForeignKey('users.id')), - Column('role_id', Integer, ForeignKey('roles.id')) -) - + Column('user_id', Integer, ForeignKey('users.id')), + Column('role_id', Integer, ForeignKey('roles.id')) + ) diff --git a/lemur/notifications.py b/lemur/notifications.py index 09bcfec7..055b6b42 100644 --- a/lemur/notifications.py +++ b/lemur/notifications.py @@ -58,7 +58,7 @@ def _find_superseded(domains): current_app.logger.info("Trying to resolve {0}".format(domain.name)) query = query.filter(Certificate.domains.any(Domain.name.in_([x.name for x in domains]))) - query = query.filter(Certificate.active == True) + query = query.filter(Certificate.active == True) # noqa query = query.filter(Certificate.not_after >= arrow.utcnow().format('YYYY-MM-DD')) ss_list.extend(query.all()) diff --git a/lemur/plugins/__init__.py b/lemur/plugins/__init__.py index d2656990..16450064 100644 --- a/lemur/plugins/__init__.py +++ b/lemur/plugins/__init__.py @@ -1,4 +1,4 @@ from __future__ import absolute_import -from lemur.plugins.base import * # NOQA -from lemur.plugins.bases import * # NOQA +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 index 7091b27b..107cbcf4 100644 --- a/lemur/plugins/base/__init__.py +++ b/lemur/plugins/base/__init__.py @@ -9,7 +9,7 @@ from __future__ import absolute_import, print_function from lemur.plugins.base.manager import PluginManager -from lemur.plugins.base.v1 import * # NOQA +from lemur.plugins.base.v1 import * # noqa plugins = PluginManager() register = plugins.register diff --git a/lemur/plugins/base/manager.py b/lemur/plugins/base/manager.py index 0ec270d0..12556e7d 100644 --- a/lemur/plugins/base/manager.py +++ b/lemur/plugins/base/manager.py @@ -8,6 +8,7 @@ from flask import current_app from lemur.common.managers import InstanceManager + # inspired by https://github.com/getsentry/sentry class PluginManager(InstanceManager): def __iter__(self): @@ -57,4 +58,3 @@ class PluginManager(InstanceManager): 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 index 2055577b..ce378b98 100644 --- a/lemur/plugins/base/v1.py +++ b/lemur/plugins/base/v1.py @@ -8,6 +8,7 @@ """ from threading import local + # stolen from https://github.com/getsentry/sentry/ class PluginMount(type): def __new__(cls, name, bases, attrs): diff --git a/lemur/plugins/bases/__init__.py b/lemur/plugins/bases/__init__.py index 2e501d35..044bb213 100644 --- a/lemur/plugins/bases/__init__.py +++ b/lemur/plugins/bases/__init__.py @@ -1,3 +1,3 @@ -from .destination import DestinationPlugin # NOQA -from .issuer import IssuerPlugin # NOQA -from .source import SourcePlugin \ No newline at end of file +from .destination import DestinationPlugin # noqa +from .issuer import IssuerPlugin # noqa +from .source import SourcePlugin # noqa diff --git a/lemur/plugins/bases/destination.py b/lemur/plugins/bases/destination.py index b3dcef5f..61c908eb 100644 --- a/lemur/plugins/bases/destination.py +++ b/lemur/plugins/bases/destination.py @@ -8,9 +8,9 @@ """ from lemur.plugins.base import Plugin + class DestinationPlugin(Plugin): type = 'destination' def upload(self): raise NotImplemented - diff --git a/lemur/plugins/bases/issuer.py b/lemur/plugins/bases/issuer.py index bfa7dbd6..29f44a97 100644 --- a/lemur/plugins/bases/issuer.py +++ b/lemur/plugins/bases/issuer.py @@ -8,6 +8,7 @@ """ from lemur.plugins.base import Plugin + class IssuerPlugin(Plugin): """ This is the base class from which all of the supported @@ -20,4 +21,3 @@ class IssuerPlugin(Plugin): def create_authority(self): raise NotImplemented - diff --git a/lemur/plugins/bases/source.py b/lemur/plugins/bases/source.py index a706acf2..9ddae1a0 100644 --- a/lemur/plugins/bases/source.py +++ b/lemur/plugins/bases/source.py @@ -8,6 +8,7 @@ """ from lemur.plugins.base import Plugin + class SourcePlugin(Plugin): type = 'source' @@ -16,4 +17,3 @@ class SourcePlugin(Plugin): def get_options(self): return {} - diff --git a/lemur/plugins/lemur_aws/__init__.py b/lemur/plugins/lemur_aws/__init__.py index d29488d2..e572596e 100644 --- a/lemur/plugins/lemur_aws/__init__.py +++ b/lemur/plugins/lemur_aws/__init__.py @@ -2,4 +2,4 @@ try: VERSION = __import__('pkg_resources') \ .get_distribution(__name__).version except Exception, e: - VERSION = 'unknown' \ No newline at end of file + VERSION = 'unknown' diff --git a/lemur/plugins/lemur_aws/elb.py b/lemur/plugins/lemur_aws/elb.py index 1edfd5b4..d71b5013 100644 --- a/lemur/plugins/lemur_aws/elb.py +++ b/lemur/plugins/lemur_aws/elb.py @@ -38,6 +38,7 @@ def is_valid(listener_tuple): return listener_tuple + def get_all_regions(): """ Retrieves all current EC2 regions. @@ -49,6 +50,7 @@ def get_all_regions(): regions.append(r.name) return regions + def get_all_elbs(account_number, region): """ Fetches all elb objects for a given account and region. @@ -74,7 +76,6 @@ def get_all_elbs(account_number, region): # return elbs - def attach_certificate(account_number, region, name, port, certificate_id): """ Attaches a certificate to a listener, throws exception @@ -137,4 +138,3 @@ def delete_listeners(account_number, region, name, ports): :return: """ return assume_service(account_number, 'elb', region).delete_load_balancer_listeners(name, ports) - diff --git a/lemur/plugins/lemur_aws/iam.py b/lemur/plugins/lemur_aws/iam.py index fa8e50e0..9279c577 100644 --- a/lemur/plugins/lemur_aws/iam.py +++ b/lemur/plugins/lemur_aws/iam.py @@ -1,5 +1,5 @@ """ -.. module: lemur.common.services.aws.iam +.. module: lemur.plugins.lemur_aws.iam :platform: Unix :synopsis: Contains helper functions for interactive with AWS IAM Apis. :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more @@ -19,21 +19,6 @@ def get_name_from_arn(arn): return arn.split("/", 1)[1] -def ssl_split(param_string): - """ - - :param param_string: - :return: - """ - output = {} - parts = str(param_string).split("/") - for part in parts: - if "=" in part: - key, value = part.split("=", 1) - output[key] = value - return output - - def upload_cert(account_number, cert, private_key, cert_chain=None): """ Upload a certificate to AWS @@ -44,7 +29,8 @@ def upload_cert(account_number, cert, private_key, cert_chain=None): :param cert_chain: :return: """ - return assume_service(account_number, 'iam').upload_server_cert(cert.name, str(cert.body), str(private_key), cert_chain=str(cert_chain)) + return assume_service(account_number, 'iam').upload_server_cert(cert.name, str(cert.body), str(private_key), + cert_chain=str(cert_chain)) def delete_cert(account_number, cert): @@ -109,5 +95,3 @@ def digest_aws_cert_response(response): chain = cert['certificate_chain'] return str(body), str(chain), - - diff --git a/lemur/plugins/lemur_aws/plugin.py b/lemur/plugins/lemur_aws/plugin.py index 35800f51..72304965 100644 --- a/lemur/plugins/lemur_aws/plugin.py +++ b/lemur/plugins/lemur_aws/plugin.py @@ -35,11 +35,11 @@ class AWSDestinationPlugin(DestinationPlugin): 'helpMessage': 'Must be a valid AWS account number!', } ] - #'elb': { - # 'name': {'type': 'name'}, - # 'region': {'type': 'str'}, - # 'port': {'type': 'int'} - #} + # 'elb': { + # 'name': {'type': 'name'}, + # 'region': {'type': 'str'}, + # 'port': {'type': 'int'} + # } def upload(self, cert, private_key, cert_chain, options, **kwargs): iam.upload_cert(find_value('accountNumber', options), cert, private_key, cert_chain=cert_chain) @@ -58,10 +58,22 @@ class AWSSourcePlugin(SourcePlugin): author = 'Kevin Glisson' author_url = 'https://github.com/netflix/lemur' - options = { - 'accountNumber': {'type': 'int'}, - 'pollRate': {'type': 'int', 'default': '60'} - } + options = [ + { + 'name': 'accountNumber', + 'type': 'int', + 'required': True, + 'validation': '/^[0-9]{12,12}$/', + 'helpMessage': 'Must be a valid AWS account number!', + }, + { + 'name': 'pollRate', + 'type': 'int', + 'required': False, + 'helpMessage': 'Rate in seconds to poll source for new information.', + 'default': '60', + } + ] def get_certificates(self, **kwargs): certs = [] diff --git a/lemur/plugins/lemur_aws/sts.py b/lemur/plugins/lemur_aws/sts.py index f53a545b..843cf0f8 100644 --- a/lemur/plugins/lemur_aws/sts.py +++ b/lemur/plugins/lemur_aws/sts.py @@ -25,17 +25,13 @@ def assume_service(account_number, service, region=None): elif service in 'elb': return boto.ec2.elb.connect_to_region( - region, - aws_access_key_id=role.credentials.access_key, - aws_secret_access_key=role.credentials.secret_key, - security_token=role.credentials.session_token) + region, + aws_access_key_id=role.credentials.access_key, + aws_secret_access_key=role.credentials.secret_key, + security_token=role.credentials.session_token) elif service in 'vpc': return boto.connect_vpc( - aws_access_key_id=role.credentials.access_key, - aws_secret_access_key=role.credentials.secret_key, - security_token=role.credentials.session_token) - - - - + aws_access_key_id=role.credentials.access_key, + aws_secret_access_key=role.credentials.secret_key, + security_token=role.credentials.session_token) diff --git a/lemur/plugins/lemur_aws/tests/test_iam.py b/lemur/plugins/lemur_aws/tests/test_iam.py new file mode 100644 index 00000000..bc86feb7 --- /dev/null +++ b/lemur/plugins/lemur_aws/tests/test_iam.py @@ -0,0 +1,31 @@ +from moto import mock_iam, mock_sts + +from lemur.certificates.models import Certificate + +from lemur.tests.certs import EXTERNAL_VALID_STR, PRIVATE_KEY_STR + + +def test_get_name_from_arn(): + from lemur.plugins.lemur_aws.iam import get_name_from_arn + arn = 'arn:aws:iam::123456789012:server-certificate/tttt2.netflixtest.net-NetflixInc-20150624-20150625' + assert get_name_from_arn(arn) == 'tttt2.netflixtest.net-NetflixInc-20150624-20150625' + + +@mock_sts() +@mock_iam() +def test_get_all_server_certs(app): + from lemur.plugins.lemur_aws.iam import upload_cert, get_all_server_certs + cert = Certificate(EXTERNAL_VALID_STR) + upload_cert('123456789012', cert, PRIVATE_KEY_STR) + certs = get_all_server_certs('123456789012') + assert len(certs) == 1 + + +@mock_sts() +@mock_iam() +def test_get_cert_from_arn(app): + from lemur.plugins.lemur_aws.iam import upload_cert, get_cert_from_arn + cert = Certificate(EXTERNAL_VALID_STR) + upload_cert('123456789012', cert, PRIVATE_KEY_STR) + body, chain = get_cert_from_arn('arn:aws:iam::123456789012:server-certificate/tttt2.netflixtest.net-NetflixInc-20150624-20150625') + assert body.replace('\n', '') == EXTERNAL_VALID_STR.replace('\n', '') diff --git a/lemur/plugins/lemur_cloudca/__init__.py b/lemur/plugins/lemur_cloudca/__init__.py index d29488d2..e572596e 100644 --- a/lemur/plugins/lemur_cloudca/__init__.py +++ b/lemur/plugins/lemur_cloudca/__init__.py @@ -2,4 +2,4 @@ try: VERSION = __import__('pkg_resources') \ .get_distribution(__name__).version except Exception, e: - VERSION = 'unknown' \ No newline at end of file + VERSION = 'unknown' diff --git a/lemur/plugins/lemur_cloudca/plugin.py b/lemur/plugins/lemur_cloudca/plugin.py index 68de48d3..229e58a7 100644 --- a/lemur/plugins/lemur_cloudca/plugin.py +++ b/lemur/plugins/lemur_cloudca/plugin.py @@ -23,7 +23,7 @@ from lemur.plugins import lemur_cloudca as cloudca from lemur.authorities import service as authority_service -API_ENDPOINT = '/v1/ca/netflix' # TODO this should be configurable +API_ENDPOINT = '/v1/ca/netflix' # TODO this should be configurable class CloudCAException(LemurException): @@ -72,7 +72,8 @@ def get_default_issuance(options): if not options.get('validityStart') and not options.get('validityEnd'): start = arrow.utcnow() options['validityStart'] = start.floor('second').isoformat() - options['validityEnd'] = start.replace(years=current_app.config.get('CLOUDCA_DEFAULT_VALIDITY')).ceil('second').isoformat() + options['validityEnd'] = start.replace(years=current_app.config.get('CLOUDCA_DEFAULT_VALIDITY'))\ + .ceil('second').isoformat() return options @@ -95,7 +96,8 @@ def convert_date_to_utc_time(date): :return: """ d = arrow.get(date) - return arrow.utcnow().replace(day=d.naive.day).replace(month=d.naive.month).replace(year=d.naive.year).replace(microsecond=0) + return arrow.utcnow().replace(day=d.naive.day).replace(month=d.naive.month).replace(year=d.naive.year)\ + .replace(microsecond=0) def process_response(response): @@ -152,7 +154,9 @@ class CloudCA(object): self.session.cert = current_app.config.get('CLOUDCA_PEM_PATH') self.ca_bundle = current_app.config.get('CLOUDCA_BUNDLE') else: - current_app.logger.warning("No CLOUDCA credentials found, lemur will be unable to request certificates from CLOUDCA") + current_app.logger.warning( + "No CLOUDCA credentials found, lemur will be unable to request certificates from CLOUDCA" + ) super(CloudCA, self).__init__(*args, **kwargs) @@ -203,7 +207,7 @@ class CloudCA(object): for ca in self.get(endpoint)['data']['caList']: try: authorities.append(ca['caName']) - except AttributeError as e: + except AttributeError: current_app.logger.error("No authority has been defined for {}".format(ca['caName'])) return authorities @@ -235,7 +239,8 @@ class CloudCAIssuerPlugin(IssuerPlugin, CloudCA): options['validityStart'] = convert_date_to_utc_time(options['validityStart']).isoformat() options['validityEnd'] = convert_date_to_utc_time(options['validityEnd']).isoformat() - response = self.session.post(self.url + endpoint, data=dumps(remove_none(options)), timeout=10, verify=self.ca_bundle) + response = self.session.post(self.url + endpoint, data=dumps(remove_none(options)), timeout=10, + verify=self.ca_bundle) json = process_response(response) roles = [] @@ -326,7 +331,8 @@ class CloudCASourcePlugin(SourcePlugin, CloudCA): :return: """ endpoint = '{0}/getCert'.format(API_ENDPOINT) - response = self.session.post(self.url + endpoint, data=dumps({'caName': ca_name}), timeout=10, verify=self.ca_bundle) + response = self.session.post(self.url + endpoint, data=dumps({'caName': ca_name}), timeout=10, + verify=self.ca_bundle) raw = process_response(response) certs = [] diff --git a/lemur/plugins/lemur_verisign/__init__.py b/lemur/plugins/lemur_verisign/__init__.py index d29488d2..e572596e 100644 --- a/lemur/plugins/lemur_verisign/__init__.py +++ b/lemur/plugins/lemur_verisign/__init__.py @@ -2,4 +2,4 @@ try: VERSION = __import__('pkg_resources') \ .get_distribution(__name__).version except Exception, e: - VERSION = 'unknown' \ No newline at end of file + VERSION = 'unknown' diff --git a/lemur/plugins/lemur_verisign/constants.py b/lemur/plugins/lemur_verisign/constants.py index 0f90ed98..b7ea6a53 100644 --- a/lemur/plugins/lemur_verisign/constants.py +++ b/lemur/plugins/lemur_verisign/constants.py @@ -55,4 +55,3 @@ F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== -----END CERTIFICATE----- """ - diff --git a/lemur/plugins/lemur_verisign/plugin.py b/lemur/plugins/lemur_verisign/plugin.py index eb00907d..5b2ee94a 100644 --- a/lemur/plugins/lemur_verisign/plugin.py +++ b/lemur/plugins/lemur_verisign/plugin.py @@ -16,6 +16,7 @@ from flask import current_app from lemur.plugins.bases import IssuerPlugin from lemur.plugins import lemur_verisign as verisign from lemur.plugins.lemur_verisign import constants +from lemur.common.utils import get_psuedo_random_string # https://support.venafi.com/entries/66445046-Info-VeriSign-Error-Codes @@ -58,9 +59,57 @@ VERISIGN_ERRORS = { } +def process_options(options): + """ + Processes and maps the incoming issuer options to fields/options that + verisign understands + + :param options: + :return: dict or valid verisign options + """ + data = { + 'challenge': get_psuedo_random_string(), + 'serverType': 'Apache', + 'certProductType': 'Server', + 'firstName': current_app.config.get("VERISIGN_FIRST_NAME"), + 'lastName': current_app.config.get("VERISIGN_LAST_NAME"), + 'signatureAlgorithm': 'sha256WithRSAEncryption', + 'email': current_app.config.get("VERISIGN_EMAIL") + } + + if options.get('validityEnd'): + end_date, period = get_default_issuance(options) + data['specificEndDate'] = end_date + data['validityPeriod'] = period + + return data + + +def get_default_issuance(options): + """ + Gets the default time range for certificates + + :param options: + :return: + """ + specific_end_date = arrow.get(options['validityEnd']).replace(days=-1).format("MM/DD/YYYY") + + now = arrow.utcnow() + then = arrow.get(options['validityEnd']) + + if then < now.replace(years=+1): + validity_period = '1Y' + elif then < now.replace(years=+2): + validity_period = '2Y' + else: + raise Exception("Verisign issued certificates cannot exceed two years in validity") + + return specific_end_date, validity_period + + def handle_response(content): """ - Helper function that helps with parsing responses from the Verisign API. + Helper function for parsing responses from the Verisign API. :param content: :return: :raise Exception: """ @@ -99,29 +148,8 @@ class VerisignIssuerPlugin(IssuerPlugin): """ url = current_app.config.get("VERISIGN_URL") + '/enroll' - data = { - 'csr': csr, - 'challenge': issuer_options['challenge'], - 'serverType': 'Apache', - 'certProductType': 'Server', - 'firstName': current_app.config.get("VERISIGN_FIRST_NAME"), - 'lastName': current_app.config.get("VERISIGN_LAST_NAME"), - 'signatureAlgorithm': 'sha256WithRSAEncryption', - 'email': current_app.config.get("VERISIGN_EMAIL") - } - - if issuer_options.get('validityEnd'): - data['specificEndDate'] = arrow.get(issuer_options['validityEnd']).replace(days=-1).format("MM/DD/YYYY") - - now = arrow.utcnow() - then = arrow.get(issuer_options['validityEnd']) - - if then < now.replace(years=+1): - data['validityPeriod'] = '1Y' - elif then < now.replace(years=+2): - data['validityPeriod'] = '2Y' - else: - raise Exception("Verisign issued certificates cannot exceed two years in validity") + data = process_options(issuer_options) + data['csr'] = csr current_app.logger.info("Requesting a new verisign certificate: {0}".format(data)) @@ -151,4 +179,3 @@ class VerisignIssuerPlugin(IssuerPlugin): url = current_app.config.get("VERISIGN_URL") + '/getTokens' response = self.session.post(url, headers={'content-type': 'application/x-www-form-urlencoded'}) return handle_response(response.content)['Response']['Order'] - diff --git a/lemur/plugins/views.py b/lemur/plugins/views.py index a1b7a000..eb774415 100644 --- a/lemur/plugins/views.py +++ b/lemur/plugins/views.py @@ -137,4 +137,3 @@ class PluginsTypeList(AuthenticatedResource): api.add_resource(PluginsList, '/plugins', endpoint='plugins') api.add_resource(PluginsTypeList, '/plugins/', endpoint='pluginType') - diff --git a/lemur/roles/models.py b/lemur/roles/models.py index 9df2a4fb..08781c6c 100644 --- a/lemur/roles/models.py +++ b/lemur/roles/models.py @@ -36,4 +36,3 @@ class Role(db.Model): def serialize(self): blob = self.as_dict() return blob - diff --git a/lemur/roles/service.py b/lemur/roles/service.py index 92c1011d..4e879143 100644 --- a/lemur/roles/service.py +++ b/lemur/roles/service.py @@ -15,6 +15,7 @@ from lemur import database from lemur.roles.models import Role from lemur.users.models import User + def update(role_id, name, description, users): """ Update a role @@ -122,4 +123,3 @@ def render(args): query = database.sort(query, Role, sort_by, sort_dir) return database.paginate(query, page, count) - diff --git a/lemur/static/app/angular/app.js b/lemur/static/app/angular/app.js index 3ed03188..2f0cc90a 100644 --- a/lemur/static/app/angular/app.js +++ b/lemur/static/app/angular/app.js @@ -65,11 +65,11 @@ lemur.factory('LemurRestangular', function (Restangular, $location, $auth) { RestangularConfigurer.setBaseUrl('http://localhost:5000/api/1'); RestangularConfigurer.setDefaultHttpFields({withCredentials: true}); - RestangularConfigurer.addResponseInterceptor(function (data, operation, what, url, response, deferred) { + RestangularConfigurer.addResponseInterceptor(function (data, operation) { var extractedData; // .. to look for getList operations - if (operation === "getList") { + if (operation === 'getList') { // .. and handle the data and meta data extractedData = data.items; extractedData.total = data.total; @@ -79,7 +79,7 @@ lemur.factory('LemurRestangular', function (Restangular, $location, $auth) { return extractedData; }); - RestangularConfigurer.addFullRequestInterceptor(function (element, operation, route, url, headers, params, httpConfig) { + RestangularConfigurer.addFullRequestInterceptor(function (element, operation, route, url, headers, params) { // We want to make sure the user is auth'd before any requests if (!$auth.isAuthenticated()) { $location.path('/login'); @@ -97,7 +97,7 @@ lemur.factory('LemurRestangular', function (Restangular, $location, $auth) { newParams.sortDir = params[item]; } else if (item.indexOf(f) > -1) { var key = regExp.exec(item)[1]; - newParams['filter'] = key + ";" + params[item]; + newParams.filter = key + ';' + params[item]; } else { newParams[item] = params[item]; } diff --git a/lemur/static/app/angular/authentication/services.js b/lemur/static/app/angular/authentication/services.js index 7230a41d..dddffeb4 100644 --- a/lemur/static/app/angular/authentication/services.js +++ b/lemur/static/app/angular/authentication/services.js @@ -28,7 +28,7 @@ angular.module('lemur') AuthenticationService.authenticate = function (provider) { $auth.authenticate(provider) .then( - function (user) { + function () { UserService.getCurrentUser(); $rootScope.$emit('user:login'); $location.url('/certificates'); @@ -41,7 +41,7 @@ angular.module('lemur') }); } ); - } + }; AuthenticationService.logout = function () { if (!$auth.isAuthenticated()) { @@ -56,7 +56,7 @@ angular.module('lemur') body: 'You have been successfully logged out.' }); $location.path('/'); - }) + }); }; }); diff --git a/lemur/static/app/angular/authorities/authority/authority.js b/lemur/static/app/angular/authorities/authority/authority.js index e5f4ffc2..419e49fd 100644 --- a/lemur/static/app/angular/authorities/authority/authority.js +++ b/lemur/static/app/angular/authorities/authority/authority.js @@ -19,10 +19,10 @@ angular.module('lemur') $scope.loading = false; $scope.create = function (authority) { WizardHandler.wizard().context.loading = true; - AuthorityService.create(authority).then(function (resposne) { + AuthorityService.create(authority).then(function () { WizardHandler.wizard().context.loading = false; $modalInstance.close(); - }) + }); }; PluginService.get('issuer').then(function (plugins) { diff --git a/lemur/static/app/angular/authorities/view/view.js b/lemur/static/app/angular/authorities/view/view.js index 0208307f..0ee16082 100644 --- a/lemur/static/app/angular/authorities/view/view.js +++ b/lemur/static/app/angular/authorities/view/view.js @@ -35,7 +35,7 @@ angular.module('lemur') $scope.getAuthorityStatus = function () { var def = $q.defer(); - def.resolve([{'title': 'Active', 'id': true}, {'title': 'Inactive', 'id': false}]) + def.resolve([{'title': 'Active', 'id': true}, {'title': 'Inactive', 'id': false}]); return def; }; diff --git a/lemur/static/app/angular/certificates/certificate/certificate.js b/lemur/static/app/angular/certificates/certificate/certificate.js index c2c47f0f..9a166050 100644 --- a/lemur/static/app/angular/certificates/certificate/certificate.js +++ b/lemur/static/app/angular/certificates/certificate/certificate.js @@ -16,7 +16,7 @@ angular.module('lemur') $scope.create = function (certificate) { WizardHandler.wizard().context.loading = true; - CertificateService.create(certificate).then(function (response) { + CertificateService.create(certificate).then(function () { WizardHandler.wizard().context.loading = false; $modalInstance.close(); }); diff --git a/lemur/static/app/angular/certificates/services.js b/lemur/static/app/angular/certificates/services.js index bb7eba53..504c2bcd 100644 --- a/lemur/static/app/angular/certificates/services.js +++ b/lemur/static/app/angular/certificates/services.js @@ -1,6 +1,5 @@ -/** - * Created by kglisson on 1/19/15. - */ +'use strict'; + angular.module('lemur') .service('CertificateApi', function (LemurRestangular, DomainService) { LemurRestangular.extendModel('certificates', function (obj) { @@ -102,7 +101,7 @@ angular.module('lemur') CertificateService.create = function (certificate) { certificate.attachSubAltName(); return CertificateApi.post(certificate).then( - function (response) { + function () { toaster.pop({ type: 'success', title: certificate.name, @@ -132,8 +131,8 @@ angular.module('lemur') }; CertificateService.upload = function (certificate) { - CertificateApi.customPOST(certificate, "upload").then( - function (response) { + CertificateApi.customPOST(certificate, 'upload').then( + function () { toaster.pop({ type: 'success', title: certificate.name, @@ -163,7 +162,7 @@ angular.module('lemur') certificate.privateKey = response.key; } }, - function (response) { + function () { toaster.pop({ type: 'error', title: certificate.name, diff --git a/lemur/static/app/angular/certificates/view/view.js b/lemur/static/app/angular/certificates/view/view.js index 230f9181..3c295f84 100644 --- a/lemur/static/app/angular/certificates/view/view.js +++ b/lemur/static/app/angular/certificates/view/view.js @@ -48,7 +48,7 @@ angular.module('lemur') $scope.getCertificateStatus = function () { var def = $q.defer(); - def.resolve([{'title': 'Active', 'id': true}, {'title': 'Inactive', 'id': false}]) + def.resolve([{'title': 'Active', 'id': true}, {'title': 'Inactive', 'id': false}]); return def; }; diff --git a/lemur/static/app/angular/components/filters.js b/lemur/static/app/angular/components/filters.js index 6229e25d..4e316fe7 100644 --- a/lemur/static/app/angular/components/filters.js +++ b/lemur/static/app/angular/components/filters.js @@ -1,3 +1,5 @@ +'use strict'; + angular.module('lemur'). filter('titleCase', function () { return function (str) { diff --git a/lemur/static/app/angular/dashboard/dashboard.js b/lemur/static/app/angular/dashboard/dashboard.js index 8e88d66b..f0d94eab 100644 --- a/lemur/static/app/angular/dashboard/dashboard.js +++ b/lemur/static/app/angular/dashboard/dashboard.js @@ -7,9 +7,8 @@ angular.module('lemur') controller: 'DashboardController' }); }) - .controller('DashboardController', function ($scope, $rootScope, $filter, $location, LemurRestangular, ngTableParams) { + .controller('DashboardController', function ($scope, $rootScope, $filter, $location, LemurRestangular) { - var baseStats = LemurRestangular.all('stats'); var baseAccounts = LemurRestangular.all('accounts'); baseAccounts.getList() @@ -78,16 +77,16 @@ angular.module('lemur') LemurRestangular.all('certificates').customGET('stats', {metric: 'issuer'}) .then(function (data) { - $scope.issuers = data['items']; + $scope.issuers = data.items; }); LemurRestangular.all('certificates').customGET('stats', {metric: 'bits'}) .then(function (data) { - $scope.bits = data['items']; + $scope.bits = data.items; }); LemurRestangular.all('certificates').customGET('stats', {metric: 'not_after'}) .then(function (data) { - $scope.expiring = {labels: data['items']['labels'], values: [data['items']['values']]}; + $scope.expiring = {labels: data.items.labels, values: [data.items.values]}; }); }); diff --git a/lemur/static/app/angular/destinations/destination/destination.js b/lemur/static/app/angular/destinations/destination/destination.js index 15fb2b42..7ef85dd1 100644 --- a/lemur/static/app/angular/destinations/destination/destination.js +++ b/lemur/static/app/angular/destinations/destination/destination.js @@ -32,7 +32,7 @@ angular.module('lemur') DestinationService.update(destination).then(function () { $modalInstance.close(); }); - } + }; $scope.cancel = function () { $modalInstance.dismiss('cancel'); diff --git a/lemur/static/app/angular/domains/services.js b/lemur/static/app/angular/domains/services.js index 3ac5579b..39c54b99 100644 --- a/lemur/static/app/angular/domains/services.js +++ b/lemur/static/app/angular/domains/services.js @@ -1,3 +1,5 @@ +'use strict'; + angular.module('lemur') .service('DomainApi', function (LemurRestangular) { return LemurRestangular.all('domains'); diff --git a/lemur/static/app/angular/elbs/services.js b/lemur/static/app/angular/elbs/services.js index 3ea3eadc..83d98dc5 100644 --- a/lemur/static/app/angular/elbs/services.js +++ b/lemur/static/app/angular/elbs/services.js @@ -1,5 +1,7 @@ +'use strict'; + angular.module('lemur') - .service('ELBApi', function (LemurRestangular, ListenerService) { + .service('ELBApi', function (LemurRestangular) { LemurRestangular.extendModel('elbs', function (obj) { return angular.extend(obj, { attachListener: function (listener) { diff --git a/lemur/static/app/angular/listeners/services.js b/lemur/static/app/angular/listeners/services.js index bd57c35f..ef141c17 100644 --- a/lemur/static/app/angular/listeners/services.js +++ b/lemur/static/app/angular/listeners/services.js @@ -1,3 +1,5 @@ +'use strict'; + angular.module('lemur') .service('ListenerApi', function (LemurRestangular) { return LemurRestangular.all('listeners'); diff --git a/lemur/static/app/angular/plugins/services.js b/lemur/static/app/angular/plugins/services.js new file mode 100644 index 00000000..ed54bff6 --- /dev/null +++ b/lemur/static/app/angular/plugins/services.js @@ -0,0 +1,14 @@ +'use strict'; + +angular.module('lemur') + .service('PluginApi', function (LemurRestangular) { + return LemurRestangular.all('plugins'); + }) + .service('PluginService', function (PluginApi) { + var PluginService = this; + PluginService.get = function (type) { + return PluginApi.customGETLIST(type).then(function (plugins) { + return plugins; + }); + }; + }); diff --git a/lemur/static/app/angular/roles/services.js b/lemur/static/app/angular/roles/services.js index 84e972c5..5b6d336b 100644 --- a/lemur/static/app/angular/roles/services.js +++ b/lemur/static/app/angular/roles/services.js @@ -1,3 +1,5 @@ +'use strict'; + angular.module('lemur') .service('RoleApi', function (LemurRestangular) { LemurRestangular.extendModel('roles', function (obj) { @@ -108,7 +110,7 @@ angular.module('lemur') role.username = response.username; } }, - function (response) { + function () { toaster.pop({ type: 'error', title: role.name, diff --git a/lemur/static/app/angular/users/user/user.js b/lemur/static/app/angular/users/user/user.js index 7a3524f2..99de4d0e 100644 --- a/lemur/static/app/angular/users/user/user.js +++ b/lemur/static/app/angular/users/user/user.js @@ -18,7 +18,7 @@ angular.module('lemur') UserService.update(user).then(function () { $modalInstance.close(); }); - } + }; $scope.cancel = function () { $modalInstance.dismiss('cancel'); diff --git a/lemur/tests/__init__.py b/lemur/tests/__init__.py index c293a1cc..c2cb93b4 100644 --- a/lemur/tests/__init__.py +++ b/lemur/tests/__init__.py @@ -1,4 +1,9 @@ import unittest + class LemurTestCase(unittest.TestCase): pass + + +class LemurPluginTestCase(LemurTestCase): + pass diff --git a/lemur/tests/certs.py b/lemur/tests/certs.py index ce2f123b..d71d2064 100644 --- a/lemur/tests/certs.py +++ b/lemur/tests/certs.py @@ -218,4 +218,4 @@ CSR_CONFIG = """ [alt_names] # Put your SANs here -""" \ No newline at end of file +""" diff --git a/lemur/tests/conf.py b/lemur/tests/conf.py new file mode 100644 index 00000000..30a0174f --- /dev/null +++ b/lemur/tests/conf.py @@ -0,0 +1,68 @@ + +# This is just Python which means you can inherit and tweak settings + +import os +_basedir = os.path.abspath(os.path.dirname(__file__)) + +ADMINS = frozenset(['']) + +THREADS_PER_PAGE = 8 + +# General + +# These will need to be set to `True` if you are developing locally +CORS = False +debug = False + +TESTING = True + +# this is the secret key used by flask session management +SECRET_KEY = 'I/dVhOZNSMZMqrFJa5tWli6VQccOGudKerq3eWPMSzQNmHHVhMAQfQ==' + +# You should consider storing these separately from your config +LEMUR_TOKEN_SECRET = 'test' +LEMUR_ENCRYPTION_KEY = 'jPd2xwxgVGXONqghHNq7/S761sffYSrT3UAgKwgtMxbqa0gmKYCfag==' + +# this is a list of domains as regexes that only admins can issue +LEMUR_RESTRICTED_DOMAINS = [] + +# Mail Server + +# Lemur currently only supports SES for sending email, this address +# needs to be verified +LEMUR_EMAIL = '' +LEMUR_SECURITY_TEAM_EMAIL = [] + +# Logging + +LOG_LEVEL = "DEBUG" +LOG_FILE = "lemur.log" + + +# Database + +# modify this if you are not using a local database +SQLALCHEMY_DATABASE_URI = 'postgresql://lemur:lemur@localhost:5432/lemur' + + +# AWS + +LEMUR_INSTANCE_PROFILE = 'Lemur' + +# Issuers + +# These will be dependent on which 3rd party that Lemur is +# configured to use. + +# CLOUDCA_URL = '' +# CLOUDCA_PEM_PATH = '' +# CLOUDCA_BUNDLE = '' + +# number of years to issue if not specified +# CLOUDCA_DEFAULT_VALIDITY = 2 + +VERISIGN_URL = 'http://example.com' +VERISIGN_PEM_PATH = '~/' +VERISIGN_FIRST_NAME = 'Jim' +VERISIGN_LAST_NAME = 'Bob' +VERSIGN_EMAIL = 'jim@example.com' diff --git a/lemur/tests/conftest.py b/lemur/tests/conftest.py index 849a3a7d..e722e695 100644 --- a/lemur/tests/conftest.py +++ b/lemur/tests/conftest.py @@ -1,3 +1,4 @@ +import os import pytest from lemur import create_app @@ -33,14 +34,11 @@ def app(): Creates a new Flask application for a test duration. Uses application factory `create_app`. """ - app = create_app() - app.config['TESTING'] = True - app.config['LEMUR_ENCRYPTION_KEY'] = 'test' - - ctx = app.app_context() + _app = create_app(os.path.dirname(os.path.realpath(__file__)) + '/conf.py') + ctx = _app.app_context() ctx.push() - yield app + yield _app ctx.pop() @@ -73,4 +71,3 @@ def session(db, request): @pytest.yield_fixture(scope="function") def client(app, session, client): yield client - diff --git a/lemur/tests/test_authorities.py b/lemur/tests/test_authorities.py index e66147e5..8e29dbd2 100644 --- a/lemur/tests/test_authorities.py +++ b/lemur/tests/test_authorities.py @@ -1,7 +1,6 @@ -import pytest -from lemur.authorities.views import * +from lemur.authorities.views import * # noqa -#def test_crud(session): +# def test_crud(session): # role = create('role1') # assert role.id > 0 # @@ -149,15 +148,3 @@ def test_admin_authorities_delete(client): 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 diff --git a/lemur/tests/test_certificates.py b/lemur/tests/test_certificates.py index 5990c09c..9e573ec6 100644 --- a/lemur/tests/test_certificates.py +++ b/lemur/tests/test_certificates.py @@ -1,8 +1,5 @@ import pytest -from lemur.certificates.views import * - -def test_valid_authority(session): - assert 1 == 2 +from lemur.certificates.views import * # noqa def test_pem_str(): @@ -40,18 +37,6 @@ def test_create_basic_csr(): assert name.value in csr_config.values() -def test_import_certificate(): - 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 @@ -59,19 +44,19 @@ def test_cert_get_cn(): assert cert_get_cn(INTERNAL_VALID_LONG_CERT) == 'long.lived.com' -def test_cert_get_domains(): +def test_cert_get_subAltDomains(): 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'] + assert cert_get_domains(INTERNAL_VALID_LONG_CERT) == [] + assert cert_get_domains(INTERNAL_VALID_SAN_CERT) == ['example2.long.com', 'example3.long.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_LONG_CERT) == None assert cert_is_san(INTERNAL_VALID_SAN_CERT) == True @@ -79,7 +64,7 @@ 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 + assert cert_is_wildcard(INTERNAL_VALID_LONG_CERT) == None def test_cert_get_bitstrength(): @@ -87,6 +72,7 @@ def test_cert_get_bitstrength(): 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 @@ -324,4 +310,3 @@ def test_admin_certificate_credentials_delete(client): def test_admin_certificate_credentials_patch(client): 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_crypto.py b/lemur/tests/test_crypto.py deleted file mode 100644 index e69de29b..00000000 diff --git a/lemur/tests/test_csr.py b/lemur/tests/test_csr.py deleted file mode 100644 index 25633a4a..00000000 --- a/lemur/tests/test_csr.py +++ /dev/null @@ -1,38 +0,0 @@ -TEST_CSR = """ - # 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 = "Operations" # 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} - """ diff --git a/lemur/tests/test_accounts.py b/lemur/tests/test_destinations.py similarity index 88% rename from lemur/tests/test_accounts.py rename to lemur/tests/test_destinations.py index c5dec77e..37da1299 100644 --- a/lemur/tests/test_accounts.py +++ b/lemur/tests/test_destinations.py @@ -1,15 +1,15 @@ -from lemur.destinations.service import * -from lemur.destinations.views import * +from lemur.destinations.service import * # noqa +from lemur.destinations.views import * # noqa from json import dumps def test_crud(session): - destination = create('111111', 'destination1') + destination = create('testdest', 'aws-destination', {}, description='destination1') assert destination.id > 0 - destination = update(destination.id, 11111, 'destination2') - assert destination.label == 'destination2' + destination = update(destination.id, 'testdest2', {}, 'destination2') + assert destination.label == 'testdest2' assert len(get_all()) == 1 @@ -40,6 +40,7 @@ def test_destination_patch(client): VALID_USER_HEADER_TOKEN = { 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'} + def test_auth_destination_get(client): assert client.get(api.url_for(Destinations, destination_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200 @@ -63,6 +64,7 @@ def test_auth_destination_patch(client): VALID_ADMIN_HEADER_TOKEN = { 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'} + def test_admin_destination_get(client): assert client.get(api.url_for(Destinations, destination_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 @@ -76,7 +78,7 @@ def test_admin_destination_put(client): def test_admin_destination_delete(client): - assert client.delete(api.url_for(Destinations, destination_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 500 + assert client.delete(api.url_for(Destinations, destination_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 def test_admin_destination_patch(client): @@ -119,13 +121,13 @@ def test_admin_destinations_get(client): def test_admin_destinations_crud(client): assert client.post(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 - data = {'destinationNumber': 111, 'label': 'test', 'comments': 'test'} - resp = client.post(api.url_for(DestinationsList), data=dumps(data), content_type='application/json', headers=VALID_ADMIN_HEADER_TOKEN) + data = {'plugin': {'slug': 'aws-destination', 'pluginOptions': {}}, 'label': 'test', 'description': 'test'} + resp = client.post(api.url_for(DestinationsList), data=dumps(data), content_type='application/json', headers=VALID_ADMIN_HEADER_TOKEN) assert resp.status_code == 200 assert client.get(api.url_for(Destinations, destination_id=resp.json['id']), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 resp = client.get(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN) assert resp.status_code == 200 - assert resp.json == {'items': [{'destinationNumber': 111, 'label': 'test', 'comments': 'test', 'id': 2}], 'total': 1} + assert resp.json['items'][0]['description'] == 'test' assert client.delete(api.url_for(Destinations, destination_id=2), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 resp = client.get(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN) assert resp.status_code == 200 diff --git a/lemur/tests/test_domains.py b/lemur/tests/test_domains.py index e8f94728..85909f72 100644 --- a/lemur/tests/test_domains.py +++ b/lemur/tests/test_domains.py @@ -1,4 +1,5 @@ -from lemur.domains.views import * +from lemur.domains.views import * # noqa + def test_domain_get(client): assert client.get(api.url_for(Domains, domain_id=1)).status_code == 401 @@ -23,6 +24,7 @@ def test_domain_patch(client): VALID_USER_HEADER_TOKEN = { 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'} + def test_auth_domain_get(client): assert client.get(api.url_for(Domains, domain_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200 @@ -46,6 +48,7 @@ def test_auth_domain_patch(client): VALID_ADMIN_HEADER_TOKEN = { 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'} + def test_admin_domain_get(client): assert client.get(api.url_for(Domains, domain_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 @@ -119,5 +122,6 @@ def test_certificate_domains_patch(client): 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): assert client.get(api.url_for(CertificateDomains, certificate_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 diff --git a/lemur/tests/test_elb.py b/lemur/tests/test_elb.py deleted file mode 100644 index bc4a10c7..00000000 --- a/lemur/tests/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/test_iam.py b/lemur/tests/test_iam.py deleted file mode 100644 index 2405f9b5..00000000 --- a/lemur/tests/test_iam.py +++ /dev/null @@ -1,35 +0,0 @@ -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 index 5eaec09b..e3472400 100644 --- a/lemur/tests/test_issuer_manager.py +++ b/lemur/tests/test_issuer_manager.py @@ -1,6 +1,6 @@ -from lemur.tests import LemurTestCase +# from lemur.tests import LemurTestCase -#class ManagerTestCase(LemurTestCase): +# class ManagerTestCase(LemurTestCase): # def test_validate_authority(self): # pass # diff --git a/lemur/tests/test_roles.py b/lemur/tests/test_roles.py index 7339aeb4..e14b6c61 100644 --- a/lemur/tests/test_roles.py +++ b/lemur/tests/test_roles.py @@ -1,6 +1,6 @@ from json import dumps -from lemur.roles.service import * -from lemur.roles.views import * +from lemur.roles.service import * # noqa +from lemur.roles.views import * # noqa def test_crud(session): diff --git a/lemur/users/models.py b/lemur/users/models.py index 08b341e7..a3a13b1e 100644 --- a/lemur/users/models.py +++ b/lemur/users/models.py @@ -84,5 +84,3 @@ class User(db.Model): listen(User, 'before_insert', hash_password) - - diff --git a/lemur/users/service.py b/lemur/users/service.py index c4eebcee..79b77a14 100644 --- a/lemur/users/service.py +++ b/lemur/users/service.py @@ -145,5 +145,3 @@ def render(args): query = database.sort(query, User, sort_by, sort_dir) return database.paginate(query, page, count) - - diff --git a/package.json b/package.json index b66140e1..f025204b 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "gulp-imagemin": "^0.6.2", "gulp-inject": "~1.0.1", "gulp-jshint": "^1.10.0", - "gulp-karma": "^0.0.4", "gulp-load-plugins": "^0.5.3", "gulp-minify-html": "~0.1.4", "gulp-ng-annotate": "~0.5.2", @@ -51,7 +50,9 @@ "main-bower-files": "^1.0.2", "require-dir": "~0.3.0", "streamqueue": "^0.1.1", - "uglify-save-license": "^0.4.1" + "uglify-save-license": "^0.4.1", + "karma": "~0.13.2", + "bower": "~1.4.1" }, "engines": { "node": ">=0.10.0" @@ -59,9 +60,13 @@ "scripts": { "postinstall": "bower install --allow-root", "pretest": "npm install && npm run build_static", - "build_static": "gulp dist", + "build_static": "gulp build", "prelint": "npm install", - "lint": "jshint app/", + "lint": "jshint lemur/static/app/", "test": "gulp test" + }, + "devDependencies": { + "jshint": "^2.8.0", + "karma-chrome-launcher": "^0.2.0" } } diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..fe08c7bf --- /dev/null +++ b/setup.cfg @@ -0,0 +1,12 @@ +[pytest] +python_files=test*.py +addopts=--tb=native -p no:doctest +norecursedirs=bin dist docs htmlcov script hooks node_modules .* {args} + +[flake8] +ignore = F999,E501,E128,E124,E402,W503,E731,F841 +max-line-length = 100 +exclude = .tox,.git,*/migrations/*,lemur/static/*,docs/* + +[wheel] +universal = 1 diff --git a/setup.py b/setup.py index 3685cb62..2caf245f 100644 --- a/setup.py +++ b/setup.py @@ -14,13 +14,14 @@ import os.path from distutils import log from distutils.core import Command from setuptools.command.develop import develop +from setuptools.command.install import install from setuptools.command.sdist import sdist from setuptools import setup from subprocess import check_output ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__))) -install_requires=[ +install_requires = [ 'Flask>=0.10.1', 'Flask-RESTful>=0.3.3', 'Flask-SQLAlchemy>=1.0.5', @@ -37,7 +38,7 @@ install_requires=[ 'six>=1.9.0', 'gunicorn>=19.3.0', 'pycrypto>=2.6.1', - 'cryptography>=0.9', + 'cryptography>=1.0dev', 'pyopenssl>=0.15.1', 'pyjwt>=1.0.1', 'xmltodict>=0.9.2' @@ -45,10 +46,10 @@ install_requires=[ tests_require = [ 'pyflakes', - 'moto', - 'nose', - 'pytest', - 'pytest-flask' + 'moto==0.4.6', + 'nose==1.3.7', + 'pytest==2.7.2', + 'pytest-flask==0.8.1' ] docs_require = [ @@ -56,6 +57,26 @@ docs_require = [ 'sphinxcontrib-httpdomain' ] +dev_requires = [ + 'flake8>=2.0,<2.1', +] + + +class SmartInstall(install): + """ + Installs Lemur into the Python environment. + If the package indicator is missing, this will also force a run of + `build_static` which is required for JavaScript assets and other things. + """ + def _needs_static(self): + return not os.path.exists(os.path.join(ROOT, 'lemur-package.json')) + + def run(self): + if self._needs_static(): + self.run_command('build_static') + install.run(self) + + class DevelopWithBuildStatic(develop): def install_for_development(self): self.run_command('build_static') @@ -79,7 +100,7 @@ class BuildStatic(Command): log.info("running [npm install --quiet]") check_output(['npm', 'install', '--quiet'], cwd=ROOT) - log.info("running [gulp buld]") + log.info("running [gulp build]") check_output([os.path.join(ROOT, 'node_modules', '.bin', 'gulp'), 'build'], cwd=ROOT) log.info("running [gulp package]") check_output([os.path.join(ROOT, 'node_modules', '.bin', 'gulp'), 'package'], cwd=ROOT) @@ -96,12 +117,15 @@ setup( install_requires=install_requires, extras_require={ 'tests': tests_require, - 'docs': docs_require + 'docs': docs_require, + 'dev': dev_requires, }, cmdclass={ 'build_static': BuildStatic, 'develop': DevelopWithBuildStatic, - 'sdist': SdistWithBuildStatic + 'sdist': SdistWithBuildStatic, + 'install': SmartInstall + }, entry_points={ 'console_scripts': [