Merge pull request #24 from kevgliss/ci

Adding Travis CI Support
This commit is contained in:
kevgliss 2015-07-23 13:05:35 -07:00
commit c04e23d3ea
103 changed files with 694 additions and 568 deletions

2
.gitignore vendored
View File

@ -26,3 +26,5 @@ pip-log.txt
docs/_build docs/_build
.editorconfig .editorconfig
.idea .idea
test.conf
lemur/tests/tmp

View File

@ -1,4 +1,2 @@
tests/ lemur/static//dist/
lemur/static/lemur/scripts/lib/ lemur/static/app/vendor/
lemur/static/lemur/dist/
lemur/static/lemur/vendor/

View File

@ -3,13 +3,13 @@
"browser": true, "browser": true,
"esnext": true, "esnext": true,
"bitwise": true, "bitwise": true,
"camelcase": true, "camelcase": false,
"curly": true, "curly": true,
"eqeqeq": true, "eqeqeq": true,
"immed": true, "immed": true,
"indent": 2, "indent": 2,
"latedef": true, "latedef": true,
"newcap": true, "newcap": false,
"noarg": true, "noarg": true,
"quotmark": "single", "quotmark": "single",
"regexp": true, "regexp": true,
@ -19,6 +19,9 @@
"trailing": true, "trailing": true,
"smarttabs": true, "smarttabs": true,
"globals": { "globals": {
"angular": false "angular": false,
"moment": false,
"toaster": false,
"_": false
} }
} }

View File

@ -1,7 +1,26 @@
language: node_js sudo: false
node_js: language: python
- '0.8' addons:
- '0.10' 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: before_script:
- 'npm install -g bower grunt-cli' - psql -c "create database lemur;" -U postgres
- 'bower install' - psql -c "create user lemur with password 'lemur;'" -U postgres
- npm install -g bower
script:
- make test
notifications:
email:
kglisson@netflix.com

85
Makefile Normal file
View File

@ -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

5
docs/requirements.txt Normal file
View File

@ -0,0 +1,5 @@
Jinja2>=2.3
Pygments>=1.2
Sphinx>=1.3
docutils>=0.7
markupsafe

View File

@ -26,9 +26,9 @@ var gulp = require('gulp'),
imagemin = require('gulp-imagemin'), imagemin = require('gulp-imagemin'),
minifyHtml = require('gulp-minify-html'), minifyHtml = require('gulp-minify-html'),
bowerFiles = require('main-bower-files'), bowerFiles = require('main-bower-files'),
karma = require('karma'),
replace = require('gulp-replace'); replace = require('gulp-replace');
gulp.task('default', ['clean'], function () { gulp.task('default', ['clean'], function () {
gulp.start('fonts', 'styles'); gulp.start('fonts', 'styles');
}); });
@ -37,6 +37,15 @@ gulp.task('clean', function (cb) {
del(['.tmp', 'lemur/static/dist'], 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 () { gulp.task('dev:fonts', function () {
var fileList = [ var fileList = [
'lemur/static/app/vendor/bower_components/bootstrap/dist/fonts/*', 'lemur/static/app/vendor/bower_components/bootstrap/dist/fonts/*',

27
gulp/karma.conf.js Normal file
View File

@ -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'
//...
}
});
};

View File

@ -27,5 +27,5 @@ gulp.task('default', function () {
console.log(c.green + '-------------------------------------------' + c.reset); console.log(c.green + '-------------------------------------------' + c.reset);
console.log(Object.keys(gulp.tasks).sort().join('\n')); console.log(Object.keys(gulp.tasks).sort().join('\n'));
console.log(''); console.log('');
return;
}); });

10
hooks/pre-commit Normal file → Executable file
View File

@ -29,14 +29,6 @@ def py_lint(files_modified):
return report.total_errors != 0 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(): def main():
from flake8.hooks import run from flake8.hooks import run
@ -46,7 +38,7 @@ def main():
files_modified = filter(lambda x: os.path.exists(x), files_modified) 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 1
return 0 return 0

View File

@ -36,6 +36,7 @@ LEMUR_BLUEPRINTS = (
plugins_bp, plugins_bp,
) )
def create_app(config=None): def create_app(config=None):
app = factory.create_app(app_name=__name__, blueprints=LEMUR_BLUEPRINTS, config=config) app = factory.create_app(app_name=__name__, blueprints=LEMUR_BLUEPRINTS, config=config)
configure_hook(app) configure_hook(app)
@ -61,4 +62,3 @@ def configure_hook(app):
response = {'message': 'You are not allow to access this resource'} response = {'message': 'You are not allow to access this resource'}
response.status_code = 403 response.status_code = 403
return response return response

View File

@ -4,7 +4,7 @@
:license: Apache, see LICENSE for more details. :license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com> .. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
""" """
#def analyze(endpoints, truststores): # def analyze(endpoints, truststores):
# results = {"headings": ["Endpoint"], # results = {"headings": ["Endpoint"],
# "results": [], # "results": [],
# "time": datetime.now().strftime("#Y%m%d %H:%M:%S")} # "time": datetime.now().strftime("#Y%m%d %H:%M:%S")}
@ -37,7 +37,9 @@
# log.debug(e) # log.debug(e)
# if 'hostname' in str(e): # if 'hostname' in str(e):
# tests.append('pass') # 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): # elif 'certificate verify failed' in str(e):
# tests.append('fail') # tests.append('fail')
# result['details'].append("{}: This test failed to verify the SSL certificate".format(region)) # result['details'].append("{}: This test failed to verify the SSL certificate".format(region))

View File

@ -28,7 +28,7 @@ from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers
from lemur.users import service as user_service from lemur.users import service as user_service
from lemur.auth.permissions import CertificateOwnerNeed, CertificateCreatorNeed, \ from lemur.auth.permissions import CertificateOwnerNeed, CertificateCreatorNeed, \
AuthorityCreatorNeed, AuthorityOwnerNeed, ViewRoleCredentialsNeed AuthorityCreatorNeed, ViewRoleCredentialsNeed
def base64url_decode(data): def base64url_decode(data):
@ -143,7 +143,6 @@ def fetch_token_header(token):
raise jwt.DecodeError('Invalid header padding') raise jwt.DecodeError('Invalid header padding')
@identity_loaded.connect @identity_loaded.connect
def on_identity_loaded(sender, identity): def on_identity_loaded(sender, identity):
""" """
@ -187,5 +186,3 @@ class AuthenticatedResource(Resource):
def __init__(self): def __init__(self):
super(AuthenticatedResource, self).__init__() super(AuthenticatedResource, self).__init__()

View File

@ -9,16 +9,16 @@ import jwt
import base64 import base64
import requests 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.restful import reqparse, Resource, Api
from flask.ext.principal import Identity, identity_changed 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.users import service as user_service
from lemur.roles import service as role_service from lemur.roles import service as role_service
from lemur.certificates import service as cert_service from lemur.auth.service import create_token, fetch_token_header, get_rsa_public_key
from lemur.auth.service import AuthenticatedResource, create_token, fetch_token_header, get_rsa_public_key
mod = Blueprint('auth', __name__) mod = Blueprint('auth', __name__)
@ -203,7 +203,7 @@ class Ping(Resource):
user = user_service.create( user = user_service.create(
profile['email'], profile['email'],
cert_service.create_challenge(), get_psuedo_random_string(),
profile['email'], profile['email'],
True, True,
profile.get('thumbnailPhotoUrl'), profile.get('thumbnailPhotoUrl'),
@ -222,7 +222,7 @@ class Ping(Resource):
profile['email'], profile['email'],
profile['email'], profile['email'],
True, True,
profile.get('thumbnailPhotoUrl'), # incase profile isn't google+ enabled profile.get('thumbnailPhotoUrl'), # Encase profile isn't google+ enabled
roles roles
) )
@ -234,5 +234,3 @@ class Ping(Resource):
api.add_resource(Login, '/auth/login', endpoint='login') api.add_resource(Login, '/auth/login', endpoint='login')
api.add_resource(Ping, '/auth/ping', endpoint='ping') api.add_resource(Ping, '/auth/ping', endpoint='ping')

View File

@ -19,6 +19,7 @@ import lemur.certificates.service as cert_service
from lemur.plugins.base import plugins from lemur.plugins.base import plugins
def update(authority_id, active=None, roles=None): def update(authority_id, active=None, roles=None):
""" """
Update a an authority with new values. Update a an authority with new values.
@ -30,7 +31,7 @@ def update(authority_id, active=None, roles=None):
""" """
authority = get(authority_id) authority = get(authority_id)
if roles: if roles:
authority = database.update_list(authority, 'roles', Role, roles) authority = database.update_list(authority, 'roles', Role, roles)
if active: if active:
authority.active = active authority.active = active
@ -62,9 +63,9 @@ def create(kwargs):
for r in issuer_roles: for r in issuer_roles:
role = role_service.create( role = role_service.create(
r['name'], r['name'],
password=r['password'], password=r['password'],
description="{0} auto generated role".format(kwargs.get('pluginName')), description="{0} auto generated role".format(kwargs.get('pluginName')),
username=r['username']) username=r['username'])
# the user creating the authority should be able to administer it # the user creating the authority should be able to administer it
if role.username == 'admin': if role.username == 'admin':
@ -132,7 +133,7 @@ def get_authority_role(ca_name):
""" """
if g.current_user.is_admin: if g.current_user.is_admin:
authority = get_by_name(ca_name) 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] return authority.roles[0]
else: else:
for role in g.current_user.roles: for role in g.current_user.roles:
@ -156,7 +157,7 @@ def render(args):
if filt: if filt:
terms = filt.split(';') 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]) query = query.filter(Authority.active == terms[1])
else: else:
query = database.filter(query, Authority, terms) query = database.filter(query, Authority, terms)

View File

@ -183,8 +183,8 @@ class AuthoritiesList(AuthenticatedResource):
self.reqparse.add_argument('caDescription', type=str, location='json', required=False) 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('ownerEmail', type=str, location='json', required=True)
self.reqparse.add_argument('caDN', type=dict, location='json', required=False) 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('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('validityEnd', type=str, location='json', required=False) # TODO validate
self.reqparse.add_argument('extensions', type=dict, location='json', required=False) 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('pluginName', type=str, location='json', required=True)
self.reqparse.add_argument('caType', type=str, location='json', required=False) self.reqparse.add_argument('caType', type=str, location='json', required=False)

View File

@ -53,6 +53,7 @@ class UnableToCreateCSR(LemurException):
def __str__(self): def __str__(self):
return repr(self.data['message']) return repr(self.data['message'])
class UnableToCreatePrivateKey(LemurException): class UnableToCreatePrivateKey(LemurException):
def __init__(self): def __init__(self):
self.code = 500 self.code = 500
@ -63,6 +64,7 @@ class UnableToCreatePrivateKey(LemurException):
def __str__(self): def __str__(self):
return repr(self.data['message']) return repr(self.data['message'])
class MissingFiles(LemurException): class MissingFiles(LemurException):
def __init__(self, path): def __init__(self, path):
self.code = 500 self.code = 500
@ -84,4 +86,3 @@ class NoPersistanceFound(LemurException):
def __str__(self): def __str__(self):
return repr(self.data['message']) return repr(self.data['message'])

View File

@ -21,7 +21,7 @@ from lemur.database import db
from lemur.domains.models import Domain 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 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 '-' # aws doesn't allow special chars except '-'
disallowed_chars = ''.join(c for c in map(chr, range(256)) if not c.isalnum()) 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("-", "")
disallowed_chars = disallowed_chars.replace(".", "")
temp = temp.replace('*', "WILDCARD") temp = temp.replace('*', "WILDCARD")
temp = temp.translate(None, disallowed_chars) temp = temp.translate(None, disallowed_chars)
# white space is silly too # white space is silly too
@ -76,7 +77,7 @@ def cert_get_domains(cert):
return the common name. return the common name.
:param cert: :param cert:
:return: List of domainss :return: List of domains
""" """
domains = [] domains = []
try: try:
@ -86,6 +87,7 @@ def cert_get_domains(cert):
domains.append(entry) domains.append(entry)
except Exception as e: except Exception as e:
current_app.logger.warning("Failed to get SubjectAltName: {0}".format(e)) current_app.logger.warning("Failed to get SubjectAltName: {0}".format(e))
return domains return domains
@ -110,6 +112,7 @@ def cert_is_san(cert):
if len(cert_get_domains(cert)) > 1: if len(cert_get_domains(cert)) > 1:
return True return True
def cert_is_wildcard(cert): def cert_is_wildcard(cert):
""" """
Determines if certificate is a wildcard certificate. 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] == "*": if len(domains) == 1 and domains[0][0:1] == "*":
return True return True
if cert.subject.get_attributes_for_oid(x509.OID_COMMON_NAME)[0].value[0:1] == "*":
return True
def cert_get_bitstrength(cert): def cert_get_bitstrength(cert):
""" """
@ -197,8 +203,8 @@ class Certificate(db.Model):
owner = Column(String(128)) owner = Column(String(128))
body = Column(Text()) body = Column(Text())
private_key = Column(EncryptedType(String, os.environ.get('LEMUR_ENCRYPTION_KEY'))) private_key = Column(EncryptedType(String, os.environ.get('LEMUR_ENCRYPTION_KEY')))
challenge = Column(EncryptedType(String, os.environ.get('LEMUR_ENCRYPTION_KEY'))) # TODO deprecate challenge = Column(EncryptedType(String, os.environ.get('LEMUR_ENCRYPTION_KEY'))) # TODO deprecate
csr_config = Column(Text()) # TODO deprecate csr_config = Column(Text()) # TODO deprecate
status = Column(String(128)) status = Column(String(128))
deleted = Column(Boolean, index=True) deleted = Column(Boolean, index=True)
name = Column(String(128)) name = Column(String(128))
@ -266,4 +272,3 @@ class Certificate(db.Model):
def as_dict(self): def as_dict(self):
return {c.name: getattr(self, c.name) for c in self.__table__.columns} return {c.name: getattr(self, c.name) for c in self.__table__.columns}

View File

@ -6,8 +6,6 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com> .. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
""" """
import arrow import arrow
import string
import random
from sqlalchemy import func, or_ from sqlalchemy import func, or_
from flask import g, current_app from flask import g, current_app
@ -27,7 +25,6 @@ from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.asymmetric import rsa
def get(cert_id): def get(cert_id):
""" """
Retrieves certificate by it's ID. Retrieves certificate by it's ID.
@ -106,7 +103,6 @@ def mint(issuer_options):
csr, private_key = create_csr(issuer_options) csr, private_key = create_csr(issuer_options)
issuer_options['challenge'] = create_challenge()
issuer_options['creator'] = g.user.email issuer_options['creator'] = g.user.email
cert_body, cert_chain = issuer.create_certificate(csr, issuer_options) cert_body, cert_chain = issuer.create_certificate(csr, issuer_options)
@ -212,8 +208,8 @@ def render(args):
time_range = args.pop('time_range') time_range = args.pop('time_range')
destination_id = args.pop('destination_id') destination_id = args.pop('destination_id')
show = args.pop('show') show = args.pop('show')
owner = args.pop('owner') # owner = args.pop('owner')
creator = args.pop('creator') # TODO we should enabling filtering by owner # creator = args.pop('creator') # TODO we should enabling filtering by owner
filt = args.pop('filter') filt = args.pop('filter')
@ -235,7 +231,7 @@ def render(args):
if 'destination' in terms: if 'destination' in terms:
query = query.filter(Certificate.destinations.any(Destination.id == terms[1])) 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]) query = query.filter(Certificate.active == terms[1])
else: else:
query = database.filter(query, Certificate, terms) query = database.filter(query, Certificate, terms)
@ -288,7 +284,7 @@ def create_csr(csr_config):
x509.BasicConstraints(ca=False, path_length=None), critical=True, 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': # if k == 'subAltNames':
# builder = builder.add_extension( # builder = builder.add_extension(
# x509.SubjectAlternativeName([x509.DNSName(n) for n in v]), critical=True, # x509.SubjectAlternativeName([x509.DNSName(n) for n in v]), critical=True,
@ -354,16 +350,6 @@ def create_csr(csr_config):
return csr, pem 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): def stats(**kwargs):
""" """
@ -405,5 +391,3 @@ def stats(**kwargs):
values.append(count) values.append(count)
return {'labels': keys, 'values': values} return {'labels': keys, 'values': values}

View File

@ -21,6 +21,7 @@ from lemur.certificates import service as cert_service
from lemur.plugins.base import plugins from lemur.plugins.base import plugins
from lemur.plugins.bases.source import SourcePlugin from lemur.plugins.bases.source import SourcePlugin
def sync(): def sync():
for plugin in plugins: for plugin in plugins:
new = 0 new = 0
@ -42,5 +43,4 @@ def sync():
# TODO associated cert with source # TODO associated cert with source
# TODO update cert if found from different source # TODO update cert if found from different source
# TODO dissassociate source if missing # TODO disassociate source if missing

View File

@ -30,7 +30,7 @@ def ocsp_verify(cert_path, issuer_chain_path):
url, err = p1.communicate() url, err = p1.communicate()
p2 = subprocess.Popen(['openssl', 'ocsp', '-issuer', issuer_chain_path, 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() message, err = p2.communicate()
if 'error' in message or 'Error' in message: if 'error' in message or 'Error' in message:

View File

@ -51,7 +51,7 @@ def valid_authority(authority_options):
""" """
Defends against invalid authorities Defends against invalid authorities
:param authority_name: :param authority_options:
:return: :raise ValueError: :return: :raise ValueError:
""" """
name = authority_options['name'] name = authority_options['name']
@ -76,7 +76,7 @@ def pem_str(value, name):
""" """
try: try:
x509.load_pem_x509_certificate(str(value), default_backend()) 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)) raise ValueError("The parameter '{0}' needs to be a valid PEM string".format(name))
return value return value
@ -91,12 +91,11 @@ def private_key_str(value, name):
""" """
try: try:
serialization.load_pem_private_key(str(value), None, backend=default_backend()) 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)) raise ValueError("The parameter '{0}' needs to be a valid RSA private key".format(name))
return value return value
class CertificatesList(AuthenticatedResource): class CertificatesList(AuthenticatedResource):
""" Defines the 'certificates' endpoint """ """ Defines the 'certificates' endpoint """
def __init__(self): def __init__(self):
@ -274,8 +273,8 @@ class CertificatesList(AuthenticatedResource):
self.reqparse.add_argument('destinations', type=list, default=[], location='json') self.reqparse.add_argument('destinations', type=list, default=[], location='json')
self.reqparse.add_argument('elbs', type=list, location='json') self.reqparse.add_argument('elbs', type=list, location='json')
self.reqparse.add_argument('owner', type=str, 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('validityStart', type=str, location='json') # TODO validate
self.reqparse.add_argument('validityEnd', type=str, location='json') # parse date 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('authority', type=valid_authority, location='json')
self.reqparse.add_argument('description', type=str, location='json') self.reqparse.add_argument('description', type=str, location='json')
self.reqparse.add_argument('country', type=str, location='json') self.reqparse.add_argument('country', type=str, location='json')

View File

@ -10,6 +10,7 @@ from flask import Blueprint
mod = Blueprint('healthCheck', __name__) mod = Blueprint('healthCheck', __name__)
@mod.route('/healthcheck') @mod.route('/healthcheck')
def health(): def health():
return 'ok' return 'ok'

View File

@ -8,6 +8,7 @@
""" """
from flask import current_app from flask import current_app
# inspired by https://github.com/getsentry/sentry # inspired by https://github.com/getsentry/sentry
class InstanceManager(object): class InstanceManager(object):
def __init__(self, class_list=None, instances=True): def __init__(self, class_list=None, instances=True):

View File

@ -6,8 +6,8 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com> .. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
""" """
from flask import current_app
import boto.ses import boto.ses
from flask import current_app
from lemur.templates.config import env from lemur.templates.config import env
@ -22,8 +22,7 @@ def send(subject, data, email_type, recipients):
:param recipients: :param recipients:
""" """
conn = boto.connect_ses() conn = boto.connect_ses()
#jinja template depending on type # jinja template depending on type
template = env.get_template('{}.html'.format(email_type)) template = env.get_template('{}.html'.format(email_type))
body = template.render(**data) body = template.render(**data)
conn.send_email(current_app.config.get("LEMUR_EMAIL"), subject, body, recipients, format='html') conn.send_email(current_app.config.get("LEMUR_EMAIL"), subject, body, recipients, format='html')

View File

@ -6,16 +6,28 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com> .. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
""" """
import string
import random
from functools import wraps from functools import wraps
from flask import current_app from flask import current_app
from flask.ext.restful import marshal from flask.ext.restful import marshal
from flask.ext.restful.reqparse import RequestParser from flask.ext.restful.reqparse import RequestParser
from flask.ext.sqlalchemy import Pagination 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): class marshal_items(object):
def __init__(self, fields, envelope=None): def __init__(self, fields, envelope=None):
self.fields = fields self.fields = fields

View File

@ -6,5 +6,3 @@
SAN_NAMING_TEMPLATE = "SAN-{subject}-{issuer}-{not_before}-{not_after}" SAN_NAMING_TEMPLATE = "SAN-{subject}-{issuer}-{not_before}-{not_after}"
DEFAULT_NAMING_TEMPLATE = "{subject}-{issuer}-{not_before}-{not_after}" DEFAULT_NAMING_TEMPLATE = "{subject}-{issuer}-{not_before}-{not_after}"
NONSTANDARD_NAMING_TEMPLATE = "{issuer}-{not_before}-{not_after}" NONSTANDARD_NAMING_TEMPLATE = "{issuer}-{not_before}-{not_after}"

View File

@ -9,13 +9,11 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com> .. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
""" """
from flask import current_app
from sqlalchemy import exc from sqlalchemy import exc
from sqlalchemy.sql import and_, or_ from sqlalchemy.sql import and_, or_
from lemur.extensions import db from lemur.extensions import db
from lemur.exceptions import AttrNotFound, IntegrityError, DuplicateError from lemur.exceptions import AttrNotFound, DuplicateError
def filter_none(kwargs): def filter_none(kwargs):
@ -126,7 +124,7 @@ def get(model, value, field="id"):
query = session_query(model) query = session_query(model)
try: try:
return query.filter(getattr(model, field) == value).one() return query.filter(getattr(model, field) == value).one()
except: except Exception:
return return
@ -178,8 +176,9 @@ def delete(model):
:param model: :param model:
""" """
db.session.delete(model) if model:
db.session.commit() db.session.delete(model)
db.session.commit()
def filter(query, model, terms): def filter(query, model, terms):
@ -209,7 +208,7 @@ def sort(query, model, field, direction):
direction = getattr(field, direction) direction = getattr(field, direction)
query = query.order_by(direction()) query = query.order_by(direction())
return query return query
except AttributeError as e: except AttributeError:
raise AttrNotFound(field) raise AttrNotFound(field)
@ -274,6 +273,3 @@ def sort_and_page(query, model, args):
query = sort(query, model, sort_by, sort_dir) query = sort(query, model, sort_by, sort_dir)
return paginate(query, page, count) return paginate(query, page, count)

View File

@ -9,9 +9,10 @@ from flask import make_response, request, current_app
from functools import update_wrapper from functools import update_wrapper
# this is only used for dev
def crossdomain(origin=None, methods=None, headers=None, def crossdomain(origin=None, methods=None, headers=None,
max_age=21600, attach_to_all=True, max_age=21600, attach_to_all=True,
automatic_options=True): automatic_options=True): # pragma: no cover
if methods is not None: if methods is not None:
methods = ', '.join(sorted(x.upper() for x in methods)) 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-Origin'] = origin
h['Access-Control-Allow-Methods'] = get_methods() h['Access-Control-Allow-Methods'] = get_methods()
h['Access-Control-Max-Age'] = str(max_age) 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 "
h['Access-Control-Allow-Headers'] = "Origin, X-Requested-With, Content-Type, Accept, Authorization " # headers
h['Access-Control-Allow-Credentials'] = 'true' h['Access-Control-Allow-Credentials'] = 'true'
return resp return resp
f.provide_automatic_options = False f.provide_automatic_options = False
return update_wrapper(wrapped_function, f) return update_wrapper(wrapped_function, f)
return decorator return decorator

View File

@ -7,18 +7,13 @@ ADMINS = frozenset([''])
THREADS_PER_PAGE = 8 THREADS_PER_PAGE = 8
############# # General
## General ##
#############
# These will need to be set to `True` if you are developing locally # These will need to be set to `True` if you are developing locally
CORS = False CORS = False
debug = False debug = False
############# # Logging
## Logging ##
#############
LOG_LEVEL = "DEBUG" LOG_LEVEL = "DEBUG"
LOG_FILE = "lemur.log" LOG_FILE = "lemur.log"

View File

@ -12,6 +12,7 @@ from lemur.database import db
from lemur.plugins.base import plugins from lemur.plugins.base import plugins
class Destination(db.Model): class Destination(db.Model):
__tablename__ = 'destinations' __tablename__ = 'destinations'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)

View File

@ -37,7 +37,7 @@ def update(destination_id, label, options, description):
destination = get(destination_id) destination = get(destination_id)
destination.label = label destination.label = label
description.options = options destination.options = options
destination.description = description destination.description = description
return database.update(destination) return database.update(destination)
@ -107,4 +107,3 @@ def render(args):
query = database.sort(query, Destination, sort_by, sort_dir) query = database.sort(query, Destination, sort_by, sort_dir)
return database.paginate(query, page, count) return database.paginate(query, page, count)

View File

@ -229,7 +229,6 @@ class Destinations(AuthenticatedResource):
return {'result': True} return {'result': True}
class CertificateDestinations(AuthenticatedResource): class CertificateDestinations(AuthenticatedResource):
""" Defines the 'certificate/<int:certificate_id/destinations'' endpoint """ """ Defines the 'certificate/<int:certificate_id/destinations'' endpoint """
def __init__(self): def __init__(self):
@ -274,5 +273,5 @@ class CertificateDestinations(AuthenticatedResource):
api.add_resource(DestinationsList, '/destinations', endpoint='destinations') api.add_resource(DestinationsList, '/destinations', endpoint='destinations')
api.add_resource(Destinations, '/destinations/<int:destination_id>', endpoint='account') api.add_resource(Destinations, '/destinations/<int:destination_id>', endpoint='account')
api.add_resource(CertificateDestinations, '/certificates/<int:certificate_id>/destinations', endpoint='certificateDestinations') api.add_resource(CertificateDestinations, '/certificates/<int:certificate_id>/destinations',
endpoint='certificateDestinations')

View File

@ -24,4 +24,3 @@ class Domain(db.Model):
blob = self.as_dict() blob = self.as_dict()
blob['certificates'] = [x.id for x in self.certificate] blob['certificates'] = [x.id for x in self.certificate]
return blob return blob

View File

@ -61,4 +61,3 @@ def render(args):
query = database.sort(query, Domain, sort_by, sort_dir) query = database.sort(query, Domain, sort_by, sort_dir)
return database.paginate(query, page, count) return database.paginate(query, page, count)

View File

@ -6,7 +6,7 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com> .. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
""" """
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 sqlalchemy.orm import relationship
from lemur.database import db from lemur.database import db
@ -16,7 +16,7 @@ from lemur.listeners.models import Listener
class ELB(db.Model): class ELB(db.Model):
__tablename__ = 'elbs' __tablename__ = 'elbs'
id = Column(BigInteger, primary_key=True) 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)) region = Column(String(32))
name = Column(String(128)) name = Column(String(128))
vpc_id = Column(String(128)) vpc_id = Column(String(128))

View File

@ -14,6 +14,7 @@ from lemur import database
from lemur.elbs.models import ELB from lemur.elbs.models import ELB
from lemur.listeners.models import Listener from lemur.listeners.models import Listener
def get_all(account_id, elb_name): def get_all(account_id, elb_name):
""" """
Retrieves all ELBs in a given account Retrieves all ELBs in a given account
@ -112,7 +113,7 @@ def stats(**kwargs):
if kwargs.get('active') == 'true': if kwargs.get('active') == 'true':
query = query.join(ELB.listeners) 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() items = query.group_by(attr).all()
@ -121,5 +122,3 @@ def stats(**kwargs):
if key: if key:
results.append({"key": key, "y": count}) results.append({"key": key, "y": count})
return results return results

View File

@ -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 <kglisson@netflix.com>
"""
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))

View File

@ -3,7 +3,6 @@
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details. :license: Apache, see LICENSE for more details.
""" """
from flask.ext.sqlalchemy import SQLAlchemy from flask.ext.sqlalchemy import SQLAlchemy
db = SQLAlchemy() db = SQLAlchemy()
@ -15,4 +14,3 @@ bcrypt = Bcrypt()
from flask.ext.principal import Principal from flask.ext.principal import Principal
principal = Principal() principal = Principal()

View File

@ -19,7 +19,6 @@ from logging.handlers import RotatingFileHandler
from flask import Flask from flask import Flask
from lemur.common.health import mod as health from lemur.common.health import mod as health
from lemur.exceptions import NoEncryptionKeyFound
from lemur.extensions import db, migrate, principal from lemur.extensions import db, migrate, principal
@ -161,7 +160,6 @@ def install_plugins(app):
try: try:
plugin = ep.load() plugin = ep.load()
except Exception: except Exception:
import sys
import traceback import traceback
app.logger.error("Failed to load plugin %r:\n%s\n" % (ep.name, traceback.format_exc())) app.logger.error("Failed to load plugin %r:\n%s\n" % (ep.name, traceback.format_exc()))
else: else:

View File

@ -40,4 +40,3 @@ class Listener(db.Model):
blob = self.as_dict() blob = self.as_dict()
del blob['date_created'] del blob['date_created']
return blob return blob

View File

@ -18,7 +18,7 @@ from lemur.listeners.models import Listener
from lemur.elbs import service as elb_service from lemur.elbs import service as elb_service
from lemur.certificates import service as certificate_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): 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) cert = verify_attachment(certificate_id, account_number)
listener_tuple = (load_balancer_port, instance_port, load_balancer_protocol, cert.get_art(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'} return {'message': 'Listener has been created'}
@ -98,7 +98,7 @@ def update(listener_id, **kwargs):
database.update(listener) database.update(listener)
listener_tuple = (listener.load_balancer_port, listener.instance_port, listener.load_balancer_protocol, arn,) 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'} return {'message': 'Listener has been updated'}
@ -106,7 +106,7 @@ def update(listener_id, **kwargs):
def delete(listener_id): def delete(listener_id):
# first try to delete the listener in aws # first try to delete the listener in aws
listener = get(listener_id) 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 # cleanup operation in lemur
database.delete(listener) database.delete(listener)
@ -149,7 +149,7 @@ def stats(**kwargs):
query = query.filter(ELB.account_id == kwargs.get('account_id')) query = query.filter(ELB.account_id == kwargs.get('account_id'))
if kwargs.get('active') == 'true': 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() items = query.group_by(attr).all()
results = [] results = []
@ -157,6 +157,3 @@ def stats(**kwargs):
if key: if key:
results.append({"key": key, "y": count}) results.append({"key": key, "y": count})
return results return results

View File

@ -20,19 +20,18 @@ from lemur.plugins.base import plugins
from lemur.certificates.verify import verify_string from lemur.certificates.verify import verify_string
from lemur.certificates import sync from lemur.certificates import sync
from lemur.elbs.sync import sync_all_elbs
from lemur import create_app from lemur import create_app
# Needed to be imported so that SQLAlchemy create_all can find our models # Needed to be imported so that SQLAlchemy create_all can find our models
from lemur.users.models import User from lemur.users.models import User # noqa
from lemur.roles.models import Role from lemur.roles.models import Role # noqa
from lemur.authorities.models import Authority from lemur.authorities.models import Authority # noqa
from lemur.certificates.models import Certificate from lemur.certificates.models import Certificate # noqa
from lemur.destinations.models import Destination from lemur.destinations.models import Destination # noqa
from lemur.domains.models import Domain from lemur.domains.models import Domain # noqa
from lemur.elbs.models import ELB from lemur.elbs.models import ELB # noqa
from lemur.listeners.models import Listener from lemur.listeners.models import Listener # noqa
manager = Manager(create_app) manager = Manager(create_app)
manager.add_option('-c', '--config', dest='config') manager.add_option('-c', '--config', dest='config')
@ -55,48 +54,42 @@ ADMINS = frozenset([''])
THREADS_PER_PAGE = 8 THREADS_PER_PAGE = 8
############# # General
## General ##
#############
# These will need to be set to `True` if you are developing locally # These will need to be set to `True` if you are developing locally
CORS = False CORS = False
debug = 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 # You should consider storing these separately from your config
LEMUR_SECRET_TOKEN = '{secret_token}' LEMUR_TOKEN_SECRET = '{secret_token}'
LEMUR_ENCRYPTION_KEY = '{encryption_key}' LEMUR_ENCRYPTION_KEY = '{encryption_key}'
# this is a list of domains as regexes that only admins can issue # this is a list of domains as regexes that only admins can issue
LEMUR_RESTRICTED_DOMAINS = [] LEMUR_RESTRICTED_DOMAINS = []
################# # Mail Server
## Mail Server ##
#################
# Lemur currently only supports SES for sending email, this address # Lemur currently only supports SES for sending email, this address
# needs to be verified # needs to be verified
LEMUR_EMAIL = '' LEMUR_EMAIL = ''
LEMUR_SECURITY_TEAM_EMAIL = [] LEMUR_SECURITY_TEAM_EMAIL = []
############# # Logging
## Logging ##
#############
LOG_LEVEL = "DEBUG" LOG_LEVEL = "DEBUG"
LOG_FILE = "lemur.log" 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 # Lemur will need STS assume role access to every destination you want to monitor
#AWS_ACCOUNT_MAPPINGS = {{ #AWS_ACCOUNT_MAPPINGS = {{
@ -129,6 +122,7 @@ SQLALCHEMY_DATABASE_URI = ''
#VERSIGN_EMAIL = '' #VERSIGN_EMAIL = ''
""" """
@MigrateCommand.command @MigrateCommand.command
def create(): def create():
database.db.create_all() database.db.create_all()
@ -178,7 +172,8 @@ def generate_settings():
""" """
output = CONFIG_TEMPLATE.format( output = CONFIG_TEMPLATE.format(
encryption_key=base64.b64encode(os.urandom(KEY_LENGTH)), 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 return output
@ -207,7 +202,7 @@ class Sync(Command):
sys.stdout.write("[!] Starting to sync with AWS!\n") sys.stdout.write("[!] Starting to sync with AWS!\n")
try: try:
sync.aws() sync.aws()
#sync_all_elbs() # sync_all_elbs()
sys.stdout.write("[+] Finished syncing with AWS!\n") sys.stdout.write("[+] Finished syncing with AWS!\n")
except Exception as e: except Exception as e:
sys.stdout.write("[-] Syncing with AWS failed!\n") sys.stdout.write("[-] Syncing with AWS failed!\n")
@ -480,8 +475,8 @@ def main():
manager.add_command("show_urls", ShowUrls()) manager.add_command("show_urls", ShowUrls())
manager.add_command("db", MigrateCommand) manager.add_command("db", MigrateCommand)
manager.add_command("init", InitializeApp()) manager.add_command("init", InitializeApp())
manager.add_command('create_user', CreateUser()) manager.add_command("create_user", CreateUser())
manager.add_command('create_role', CreateRole()) manager.add_command("create_role", CreateRole())
manager.add_command("sync", Sync()) manager.add_command("sync", Sync())
manager.run() manager.run()

View File

@ -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 ###

View File

@ -14,17 +14,18 @@ from sqlalchemy import Column, Integer, ForeignKey
from lemur.database import db from lemur.database import db
certificate_associations = db.Table('certificate_associations', certificate_associations = db.Table('certificate_associations',
Column('domain_id', Integer, ForeignKey('domains.id')), Column('domain_id', Integer, ForeignKey('domains.id')),
Column('certificate_id', Integer, ForeignKey('certificates.id')) Column('certificate_id', Integer, ForeignKey('certificates.id'))
) )
certificate_destination_associations = db.Table('certificate_destination_associations', certificate_destination_associations = db.Table('certificate_destination_associations',
Column('destination_id', Integer, ForeignKey('destinations.id', ondelete='cascade')), Column('destination_id', Integer,
Column('certificate_id', Integer, ForeignKey('certificates.id', ondelete='cascade')) ForeignKey('destinations.id', ondelete='cascade')),
) Column('certificate_id', Integer,
ForeignKey('certificates.id', ondelete='cascade'))
)
roles_users = db.Table('roles_users', roles_users = db.Table('roles_users',
Column('user_id', Integer, ForeignKey('users.id')), Column('user_id', Integer, ForeignKey('users.id')),
Column('role_id', Integer, ForeignKey('roles.id')) Column('role_id', Integer, ForeignKey('roles.id'))
) )

View File

@ -58,7 +58,7 @@ def _find_superseded(domains):
current_app.logger.info("Trying to resolve {0}".format(domain.name)) 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.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')) query = query.filter(Certificate.not_after >= arrow.utcnow().format('YYYY-MM-DD'))
ss_list.extend(query.all()) ss_list.extend(query.all())

View File

@ -1,4 +1,4 @@
from __future__ import absolute_import from __future__ import absolute_import
from lemur.plugins.base import * # NOQA from lemur.plugins.base import * # noqa
from lemur.plugins.bases import * # NOQA from lemur.plugins.bases import * # noqa

View File

@ -9,7 +9,7 @@
from __future__ import absolute_import, print_function from __future__ import absolute_import, print_function
from lemur.plugins.base.manager import PluginManager from lemur.plugins.base.manager import PluginManager
from lemur.plugins.base.v1 import * # NOQA from lemur.plugins.base.v1 import * # noqa
plugins = PluginManager() plugins = PluginManager()
register = plugins.register register = plugins.register

View File

@ -8,6 +8,7 @@
from flask import current_app from flask import current_app
from lemur.common.managers import InstanceManager from lemur.common.managers import InstanceManager
# inspired by https://github.com/getsentry/sentry # inspired by https://github.com/getsentry/sentry
class PluginManager(InstanceManager): class PluginManager(InstanceManager):
def __iter__(self): def __iter__(self):
@ -57,4 +58,3 @@ class PluginManager(InstanceManager):
def unregister(self, cls): def unregister(self, cls):
self.remove('%s.%s' % (cls.__module__, cls.__name__)) self.remove('%s.%s' % (cls.__module__, cls.__name__))
return cls return cls

View File

@ -8,6 +8,7 @@
""" """
from threading import local from threading import local
# stolen from https://github.com/getsentry/sentry/ # stolen from https://github.com/getsentry/sentry/
class PluginMount(type): class PluginMount(type):
def __new__(cls, name, bases, attrs): def __new__(cls, name, bases, attrs):

View File

@ -1,3 +1,3 @@
from .destination import DestinationPlugin # NOQA from .destination import DestinationPlugin # noqa
from .issuer import IssuerPlugin # NOQA from .issuer import IssuerPlugin # noqa
from .source import SourcePlugin from .source import SourcePlugin # noqa

View File

@ -8,9 +8,9 @@
""" """
from lemur.plugins.base import Plugin from lemur.plugins.base import Plugin
class DestinationPlugin(Plugin): class DestinationPlugin(Plugin):
type = 'destination' type = 'destination'
def upload(self): def upload(self):
raise NotImplemented raise NotImplemented

View File

@ -8,6 +8,7 @@
""" """
from lemur.plugins.base import Plugin from lemur.plugins.base import Plugin
class IssuerPlugin(Plugin): class IssuerPlugin(Plugin):
""" """
This is the base class from which all of the supported This is the base class from which all of the supported
@ -20,4 +21,3 @@ class IssuerPlugin(Plugin):
def create_authority(self): def create_authority(self):
raise NotImplemented raise NotImplemented

View File

@ -8,6 +8,7 @@
""" """
from lemur.plugins.base import Plugin from lemur.plugins.base import Plugin
class SourcePlugin(Plugin): class SourcePlugin(Plugin):
type = 'source' type = 'source'
@ -16,4 +17,3 @@ class SourcePlugin(Plugin):
def get_options(self): def get_options(self):
return {} return {}

View File

@ -38,6 +38,7 @@ def is_valid(listener_tuple):
return listener_tuple return listener_tuple
def get_all_regions(): def get_all_regions():
""" """
Retrieves all current EC2 regions. Retrieves all current EC2 regions.
@ -49,6 +50,7 @@ def get_all_regions():
regions.append(r.name) regions.append(r.name)
return regions return regions
def get_all_elbs(account_number, region): def get_all_elbs(account_number, region):
""" """
Fetches all elb objects for a given account and region. Fetches all elb objects for a given account and region.
@ -74,7 +76,6 @@ def get_all_elbs(account_number, region):
# return elbs # return elbs
def attach_certificate(account_number, region, name, port, certificate_id): def attach_certificate(account_number, region, name, port, certificate_id):
""" """
Attaches a certificate to a listener, throws exception Attaches a certificate to a listener, throws exception
@ -137,4 +138,3 @@ def delete_listeners(account_number, region, name, ports):
:return: :return:
""" """
return assume_service(account_number, 'elb', region).delete_load_balancer_listeners(name, ports) return assume_service(account_number, 'elb', region).delete_load_balancer_listeners(name, ports)

View File

@ -1,5 +1,5 @@
""" """
.. module: lemur.common.services.aws.iam .. module: lemur.plugins.lemur_aws.iam
:platform: Unix :platform: Unix
:synopsis: Contains helper functions for interactive with AWS IAM Apis. :synopsis: Contains helper functions for interactive with AWS IAM Apis.
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more :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] 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): def upload_cert(account_number, cert, private_key, cert_chain=None):
""" """
Upload a certificate to AWS Upload a certificate to AWS
@ -44,7 +29,8 @@ def upload_cert(account_number, cert, private_key, cert_chain=None):
:param cert_chain: :param cert_chain:
:return: :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): def delete_cert(account_number, cert):
@ -109,5 +95,3 @@ def digest_aws_cert_response(response):
chain = cert['certificate_chain'] chain = cert['certificate_chain']
return str(body), str(chain), return str(body), str(chain),

View File

@ -35,11 +35,11 @@ class AWSDestinationPlugin(DestinationPlugin):
'helpMessage': 'Must be a valid AWS account number!', 'helpMessage': 'Must be a valid AWS account number!',
} }
] ]
#'elb': { # 'elb': {
# 'name': {'type': 'name'}, # 'name': {'type': 'name'},
# 'region': {'type': 'str'}, # 'region': {'type': 'str'},
# 'port': {'type': 'int'} # 'port': {'type': 'int'}
#} # }
def upload(self, cert, private_key, cert_chain, options, **kwargs): def upload(self, cert, private_key, cert_chain, options, **kwargs):
iam.upload_cert(find_value('accountNumber', options), cert, private_key, cert_chain=cert_chain) 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 = 'Kevin Glisson'
author_url = 'https://github.com/netflix/lemur' author_url = 'https://github.com/netflix/lemur'
options = { options = [
'accountNumber': {'type': 'int'}, {
'pollRate': {'type': 'int', 'default': '60'} '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): def get_certificates(self, **kwargs):
certs = [] certs = []

View File

@ -25,17 +25,13 @@ def assume_service(account_number, service, region=None):
elif service in 'elb': elif service in 'elb':
return boto.ec2.elb.connect_to_region( return boto.ec2.elb.connect_to_region(
region, region,
aws_access_key_id=role.credentials.access_key, aws_access_key_id=role.credentials.access_key,
aws_secret_access_key=role.credentials.secret_key, aws_secret_access_key=role.credentials.secret_key,
security_token=role.credentials.session_token) security_token=role.credentials.session_token)
elif service in 'vpc': elif service in 'vpc':
return boto.connect_vpc( return boto.connect_vpc(
aws_access_key_id=role.credentials.access_key, aws_access_key_id=role.credentials.access_key,
aws_secret_access_key=role.credentials.secret_key, aws_secret_access_key=role.credentials.secret_key,
security_token=role.credentials.session_token) security_token=role.credentials.session_token)

View File

@ -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', '')

View File

@ -23,7 +23,7 @@ from lemur.plugins import lemur_cloudca as cloudca
from lemur.authorities import service as authority_service 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): class CloudCAException(LemurException):
@ -72,7 +72,8 @@ def get_default_issuance(options):
if not options.get('validityStart') and not options.get('validityEnd'): if not options.get('validityStart') and not options.get('validityEnd'):
start = arrow.utcnow() start = arrow.utcnow()
options['validityStart'] = start.floor('second').isoformat() 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 return options
@ -95,7 +96,8 @@ def convert_date_to_utc_time(date):
:return: :return:
""" """
d = arrow.get(date) 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): def process_response(response):
@ -152,7 +154,9 @@ class CloudCA(object):
self.session.cert = current_app.config.get('CLOUDCA_PEM_PATH') self.session.cert = current_app.config.get('CLOUDCA_PEM_PATH')
self.ca_bundle = current_app.config.get('CLOUDCA_BUNDLE') self.ca_bundle = current_app.config.get('CLOUDCA_BUNDLE')
else: 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) super(CloudCA, self).__init__(*args, **kwargs)
@ -203,7 +207,7 @@ class CloudCA(object):
for ca in self.get(endpoint)['data']['caList']: for ca in self.get(endpoint)['data']['caList']:
try: try:
authorities.append(ca['caName']) authorities.append(ca['caName'])
except AttributeError as e: except AttributeError:
current_app.logger.error("No authority has been defined for {}".format(ca['caName'])) current_app.logger.error("No authority has been defined for {}".format(ca['caName']))
return authorities return authorities
@ -235,7 +239,8 @@ class CloudCAIssuerPlugin(IssuerPlugin, CloudCA):
options['validityStart'] = convert_date_to_utc_time(options['validityStart']).isoformat() options['validityStart'] = convert_date_to_utc_time(options['validityStart']).isoformat()
options['validityEnd'] = convert_date_to_utc_time(options['validityEnd']).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) json = process_response(response)
roles = [] roles = []
@ -326,7 +331,8 @@ class CloudCASourcePlugin(SourcePlugin, CloudCA):
:return: :return:
""" """
endpoint = '{0}/getCert'.format(API_ENDPOINT) 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) raw = process_response(response)
certs = [] certs = []

View File

@ -55,4 +55,3 @@ F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt
TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
-----END CERTIFICATE----- -----END CERTIFICATE-----
""" """

View File

@ -16,6 +16,7 @@ from flask import current_app
from lemur.plugins.bases import IssuerPlugin from lemur.plugins.bases import IssuerPlugin
from lemur.plugins import lemur_verisign as verisign from lemur.plugins import lemur_verisign as verisign
from lemur.plugins.lemur_verisign import constants 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 # 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): 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: :param content:
:return: :raise Exception: :return: :raise Exception:
""" """
@ -99,29 +148,8 @@ class VerisignIssuerPlugin(IssuerPlugin):
""" """
url = current_app.config.get("VERISIGN_URL") + '/enroll' url = current_app.config.get("VERISIGN_URL") + '/enroll'
data = { data = process_options(issuer_options)
'csr': csr, 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")
current_app.logger.info("Requesting a new verisign certificate: {0}".format(data)) 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' url = current_app.config.get("VERISIGN_URL") + '/getTokens'
response = self.session.post(url, headers={'content-type': 'application/x-www-form-urlencoded'}) response = self.session.post(url, headers={'content-type': 'application/x-www-form-urlencoded'})
return handle_response(response.content)['Response']['Order'] return handle_response(response.content)['Response']['Order']

View File

@ -137,4 +137,3 @@ class PluginsTypeList(AuthenticatedResource):
api.add_resource(PluginsList, '/plugins', endpoint='plugins') api.add_resource(PluginsList, '/plugins', endpoint='plugins')
api.add_resource(PluginsTypeList, '/plugins/<plugin_type>', endpoint='pluginType') api.add_resource(PluginsTypeList, '/plugins/<plugin_type>', endpoint='pluginType')

View File

@ -36,4 +36,3 @@ class Role(db.Model):
def serialize(self): def serialize(self):
blob = self.as_dict() blob = self.as_dict()
return blob return blob

View File

@ -15,6 +15,7 @@ from lemur import database
from lemur.roles.models import Role from lemur.roles.models import Role
from lemur.users.models import User from lemur.users.models import User
def update(role_id, name, description, users): def update(role_id, name, description, users):
""" """
Update a role Update a role
@ -122,4 +123,3 @@ def render(args):
query = database.sort(query, Role, sort_by, sort_dir) query = database.sort(query, Role, sort_by, sort_dir)
return database.paginate(query, page, count) return database.paginate(query, page, count)

View File

@ -65,11 +65,11 @@ lemur.factory('LemurRestangular', function (Restangular, $location, $auth) {
RestangularConfigurer.setBaseUrl('http://localhost:5000/api/1'); RestangularConfigurer.setBaseUrl('http://localhost:5000/api/1');
RestangularConfigurer.setDefaultHttpFields({withCredentials: true}); RestangularConfigurer.setDefaultHttpFields({withCredentials: true});
RestangularConfigurer.addResponseInterceptor(function (data, operation, what, url, response, deferred) { RestangularConfigurer.addResponseInterceptor(function (data, operation) {
var extractedData; var extractedData;
// .. to look for getList operations // .. to look for getList operations
if (operation === "getList") { if (operation === 'getList') {
// .. and handle the data and meta data // .. and handle the data and meta data
extractedData = data.items; extractedData = data.items;
extractedData.total = data.total; extractedData.total = data.total;
@ -79,7 +79,7 @@ lemur.factory('LemurRestangular', function (Restangular, $location, $auth) {
return extractedData; 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 // We want to make sure the user is auth'd before any requests
if (!$auth.isAuthenticated()) { if (!$auth.isAuthenticated()) {
$location.path('/login'); $location.path('/login');
@ -97,7 +97,7 @@ lemur.factory('LemurRestangular', function (Restangular, $location, $auth) {
newParams.sortDir = params[item]; newParams.sortDir = params[item];
} else if (item.indexOf(f) > -1) { } else if (item.indexOf(f) > -1) {
var key = regExp.exec(item)[1]; var key = regExp.exec(item)[1];
newParams['filter'] = key + ";" + params[item]; newParams.filter = key + ';' + params[item];
} else { } else {
newParams[item] = params[item]; newParams[item] = params[item];
} }

View File

@ -28,7 +28,7 @@ angular.module('lemur')
AuthenticationService.authenticate = function (provider) { AuthenticationService.authenticate = function (provider) {
$auth.authenticate(provider) $auth.authenticate(provider)
.then( .then(
function (user) { function () {
UserService.getCurrentUser(); UserService.getCurrentUser();
$rootScope.$emit('user:login'); $rootScope.$emit('user:login');
$location.url('/certificates'); $location.url('/certificates');
@ -41,7 +41,7 @@ angular.module('lemur')
}); });
} }
); );
} };
AuthenticationService.logout = function () { AuthenticationService.logout = function () {
if (!$auth.isAuthenticated()) { if (!$auth.isAuthenticated()) {
@ -56,7 +56,7 @@ angular.module('lemur')
body: 'You have been successfully logged out.' body: 'You have been successfully logged out.'
}); });
$location.path('/'); $location.path('/');
}) });
}; };
}); });

View File

@ -19,10 +19,10 @@ angular.module('lemur')
$scope.loading = false; $scope.loading = false;
$scope.create = function (authority) { $scope.create = function (authority) {
WizardHandler.wizard().context.loading = true; WizardHandler.wizard().context.loading = true;
AuthorityService.create(authority).then(function (resposne) { AuthorityService.create(authority).then(function () {
WizardHandler.wizard().context.loading = false; WizardHandler.wizard().context.loading = false;
$modalInstance.close(); $modalInstance.close();
}) });
}; };
PluginService.get('issuer').then(function (plugins) { PluginService.get('issuer').then(function (plugins) {

View File

@ -35,7 +35,7 @@ angular.module('lemur')
$scope.getAuthorityStatus = function () { $scope.getAuthorityStatus = function () {
var def = $q.defer(); 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; return def;
}; };

View File

@ -16,7 +16,7 @@ angular.module('lemur')
$scope.create = function (certificate) { $scope.create = function (certificate) {
WizardHandler.wizard().context.loading = true; WizardHandler.wizard().context.loading = true;
CertificateService.create(certificate).then(function (response) { CertificateService.create(certificate).then(function () {
WizardHandler.wizard().context.loading = false; WizardHandler.wizard().context.loading = false;
$modalInstance.close(); $modalInstance.close();
}); });

View File

@ -1,6 +1,5 @@
/** 'use strict';
* Created by kglisson on 1/19/15.
*/
angular.module('lemur') angular.module('lemur')
.service('CertificateApi', function (LemurRestangular, DomainService) { .service('CertificateApi', function (LemurRestangular, DomainService) {
LemurRestangular.extendModel('certificates', function (obj) { LemurRestangular.extendModel('certificates', function (obj) {
@ -102,7 +101,7 @@ angular.module('lemur')
CertificateService.create = function (certificate) { CertificateService.create = function (certificate) {
certificate.attachSubAltName(); certificate.attachSubAltName();
return CertificateApi.post(certificate).then( return CertificateApi.post(certificate).then(
function (response) { function () {
toaster.pop({ toaster.pop({
type: 'success', type: 'success',
title: certificate.name, title: certificate.name,
@ -132,8 +131,8 @@ angular.module('lemur')
}; };
CertificateService.upload = function (certificate) { CertificateService.upload = function (certificate) {
CertificateApi.customPOST(certificate, "upload").then( CertificateApi.customPOST(certificate, 'upload').then(
function (response) { function () {
toaster.pop({ toaster.pop({
type: 'success', type: 'success',
title: certificate.name, title: certificate.name,
@ -163,7 +162,7 @@ angular.module('lemur')
certificate.privateKey = response.key; certificate.privateKey = response.key;
} }
}, },
function (response) { function () {
toaster.pop({ toaster.pop({
type: 'error', type: 'error',
title: certificate.name, title: certificate.name,

View File

@ -48,7 +48,7 @@ angular.module('lemur')
$scope.getCertificateStatus = function () { $scope.getCertificateStatus = function () {
var def = $q.defer(); 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; return def;
}; };

View File

@ -1,3 +1,5 @@
'use strict';
angular.module('lemur'). angular.module('lemur').
filter('titleCase', function () { filter('titleCase', function () {
return function (str) { return function (str) {

View File

@ -7,9 +7,8 @@ angular.module('lemur')
controller: 'DashboardController' 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'); var baseAccounts = LemurRestangular.all('accounts');
baseAccounts.getList() baseAccounts.getList()
@ -78,16 +77,16 @@ angular.module('lemur')
LemurRestangular.all('certificates').customGET('stats', {metric: 'issuer'}) LemurRestangular.all('certificates').customGET('stats', {metric: 'issuer'})
.then(function (data) { .then(function (data) {
$scope.issuers = data['items']; $scope.issuers = data.items;
}); });
LemurRestangular.all('certificates').customGET('stats', {metric: 'bits'}) LemurRestangular.all('certificates').customGET('stats', {metric: 'bits'})
.then(function (data) { .then(function (data) {
$scope.bits = data['items']; $scope.bits = data.items;
}); });
LemurRestangular.all('certificates').customGET('stats', {metric: 'not_after'}) LemurRestangular.all('certificates').customGET('stats', {metric: 'not_after'})
.then(function (data) { .then(function (data) {
$scope.expiring = {labels: data['items']['labels'], values: [data['items']['values']]}; $scope.expiring = {labels: data.items.labels, values: [data.items.values]};
}); });
}); });

View File

@ -32,7 +32,7 @@ angular.module('lemur')
DestinationService.update(destination).then(function () { DestinationService.update(destination).then(function () {
$modalInstance.close(); $modalInstance.close();
}); });
} };
$scope.cancel = function () { $scope.cancel = function () {
$modalInstance.dismiss('cancel'); $modalInstance.dismiss('cancel');

View File

@ -1,3 +1,5 @@
'use strict';
angular.module('lemur') angular.module('lemur')
.service('DomainApi', function (LemurRestangular) { .service('DomainApi', function (LemurRestangular) {
return LemurRestangular.all('domains'); return LemurRestangular.all('domains');

View File

@ -1,5 +1,7 @@
'use strict';
angular.module('lemur') angular.module('lemur')
.service('ELBApi', function (LemurRestangular, ListenerService) { .service('ELBApi', function (LemurRestangular) {
LemurRestangular.extendModel('elbs', function (obj) { LemurRestangular.extendModel('elbs', function (obj) {
return angular.extend(obj, { return angular.extend(obj, {
attachListener: function (listener) { attachListener: function (listener) {

View File

@ -1,3 +1,5 @@
'use strict';
angular.module('lemur') angular.module('lemur')
.service('ListenerApi', function (LemurRestangular) { .service('ListenerApi', function (LemurRestangular) {
return LemurRestangular.all('listeners'); return LemurRestangular.all('listeners');

View File

@ -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;
});
};
});

View File

@ -1,3 +1,5 @@
'use strict';
angular.module('lemur') angular.module('lemur')
.service('RoleApi', function (LemurRestangular) { .service('RoleApi', function (LemurRestangular) {
LemurRestangular.extendModel('roles', function (obj) { LemurRestangular.extendModel('roles', function (obj) {
@ -108,7 +110,7 @@ angular.module('lemur')
role.username = response.username; role.username = response.username;
} }
}, },
function (response) { function () {
toaster.pop({ toaster.pop({
type: 'error', type: 'error',
title: role.name, title: role.name,

View File

@ -18,7 +18,7 @@ angular.module('lemur')
UserService.update(user).then(function () { UserService.update(user).then(function () {
$modalInstance.close(); $modalInstance.close();
}); });
} };
$scope.cancel = function () { $scope.cancel = function () {
$modalInstance.dismiss('cancel'); $modalInstance.dismiss('cancel');

View File

@ -1,4 +1,9 @@
import unittest import unittest
class LemurTestCase(unittest.TestCase): class LemurTestCase(unittest.TestCase):
pass pass
class LemurPluginTestCase(LemurTestCase):
pass

68
lemur/tests/conf.py Normal file
View File

@ -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'

View File

@ -1,3 +1,4 @@
import os
import pytest import pytest
from lemur import create_app from lemur import create_app
@ -33,14 +34,11 @@ def app():
Creates a new Flask application for a test duration. Creates a new Flask application for a test duration.
Uses application factory `create_app`. Uses application factory `create_app`.
""" """
app = create_app() _app = create_app(os.path.dirname(os.path.realpath(__file__)) + '/conf.py')
app.config['TESTING'] = True ctx = _app.app_context()
app.config['LEMUR_ENCRYPTION_KEY'] = 'test'
ctx = app.app_context()
ctx.push() ctx.push()
yield app yield _app
ctx.pop() ctx.pop()
@ -73,4 +71,3 @@ def session(db, request):
@pytest.yield_fixture(scope="function") @pytest.yield_fixture(scope="function")
def client(app, session, client): def client(app, session, client):
yield client yield client

View File

@ -1,7 +1,6 @@
import pytest from lemur.authorities.views import * # noqa
from lemur.authorities.views import *
#def test_crud(session): # def test_crud(session):
# role = create('role1') # role = create('role1')
# assert role.id > 0 # assert role.id > 0
# #
@ -149,15 +148,3 @@ def test_admin_authorities_delete(client):
def test_admin_certificate_authorities_get(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 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

View File

@ -1,8 +1,5 @@
import pytest import pytest
from lemur.certificates.views import * from lemur.certificates.views import * # noqa
def test_valid_authority(session):
assert 1 == 2
def test_pem_str(): def test_pem_str():
@ -40,18 +37,6 @@ def test_create_basic_csr():
assert name.value in csr_config.values() 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(): def test_cert_get_cn():
from lemur.tests.certs import INTERNAL_VALID_LONG_CERT from lemur.tests.certs import INTERNAL_VALID_LONG_CERT
from lemur.certificates.models import cert_get_cn 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' 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.tests.certs import INTERNAL_VALID_SAN_CERT, INTERNAL_VALID_LONG_CERT
from lemur.certificates.models import cert_get_domains 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_LONG_CERT) == []
assert cert_get_domains(INTERNAL_VALID_SAN_CERT) == ['example2.long.com', 'example3.long.com', 'san.example.com'] assert cert_get_domains(INTERNAL_VALID_SAN_CERT) == ['example2.long.com', 'example3.long.com']
def test_cert_is_san(): def test_cert_is_san():
from lemur.tests.certs import INTERNAL_VALID_SAN_CERT, INTERNAL_VALID_LONG_CERT from lemur.tests.certs import INTERNAL_VALID_SAN_CERT, INTERNAL_VALID_LONG_CERT
from lemur.certificates.models import cert_is_san 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 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.tests.certs import INTERNAL_VALID_WILDCARD_CERT, INTERNAL_VALID_LONG_CERT
from lemur.certificates.models import cert_is_wildcard from lemur.certificates.models import cert_is_wildcard
assert cert_is_wildcard(INTERNAL_VALID_WILDCARD_CERT) == True 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(): def test_cert_get_bitstrength():
@ -87,6 +72,7 @@ def test_cert_get_bitstrength():
from lemur.certificates.models import cert_get_bitstrength from lemur.certificates.models import cert_get_bitstrength
assert cert_get_bitstrength(INTERNAL_VALID_LONG_CERT) == 2048 assert cert_get_bitstrength(INTERNAL_VALID_LONG_CERT) == 2048
def test_cert_get_issuer(): def test_cert_get_issuer():
from lemur.tests.certs import INTERNAL_VALID_LONG_CERT from lemur.tests.certs import INTERNAL_VALID_LONG_CERT
from lemur.certificates.models import cert_get_issuer 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): 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 assert client.patch(api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405

View File

@ -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}
"""

View File

@ -1,15 +1,15 @@
from lemur.destinations.service import * from lemur.destinations.service import * # noqa
from lemur.destinations.views import * from lemur.destinations.views import * # noqa
from json import dumps from json import dumps
def test_crud(session): def test_crud(session):
destination = create('111111', 'destination1') destination = create('testdest', 'aws-destination', {}, description='destination1')
assert destination.id > 0 assert destination.id > 0
destination = update(destination.id, 11111, 'destination2') destination = update(destination.id, 'testdest2', {}, 'destination2')
assert destination.label == 'destination2' assert destination.label == 'testdest2'
assert len(get_all()) == 1 assert len(get_all()) == 1
@ -40,6 +40,7 @@ def test_destination_patch(client):
VALID_USER_HEADER_TOKEN = { VALID_USER_HEADER_TOKEN = {
'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'} 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'}
def test_auth_destination_get(client): def test_auth_destination_get(client):
assert client.get(api.url_for(Destinations, destination_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200 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 = { VALID_ADMIN_HEADER_TOKEN = {
'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'} 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'}
def test_admin_destination_get(client): def test_admin_destination_get(client):
assert client.get(api.url_for(Destinations, destination_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 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): 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): def test_admin_destination_patch(client):
@ -119,13 +121,13 @@ def test_admin_destinations_get(client):
def test_admin_destinations_crud(client): def test_admin_destinations_crud(client):
assert client.post(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 assert client.post(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400
data = {'destinationNumber': 111, 'label': 'test', 'comments': 'test'} 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) 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 resp.status_code == 200
assert client.get(api.url_for(Destinations, destination_id=resp.json['id']), headers=VALID_ADMIN_HEADER_TOKEN).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) resp = client.get(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN)
assert resp.status_code == 200 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 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) resp = client.get(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN)
assert resp.status_code == 200 assert resp.status_code == 200

View File

@ -1,4 +1,5 @@
from lemur.domains.views import * from lemur.domains.views import * # noqa
def test_domain_get(client): def test_domain_get(client):
assert client.get(api.url_for(Domains, domain_id=1)).status_code == 401 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 = { VALID_USER_HEADER_TOKEN = {
'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'} 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'}
def test_auth_domain_get(client): def test_auth_domain_get(client):
assert client.get(api.url_for(Domains, domain_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200 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 = { VALID_ADMIN_HEADER_TOKEN = {
'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'} 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'}
def test_admin_domain_get(client): def test_admin_domain_get(client):
assert client.get(api.url_for(Domains, domain_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 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): 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 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): 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 assert client.get(api.url_for(CertificateDomains, certificate_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200

View File

@ -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')
#

View File

@ -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')
#
#

View File

@ -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): # def test_validate_authority(self):
# pass # pass
# #

View File

@ -1,6 +1,6 @@
from json import dumps from json import dumps
from lemur.roles.service import * from lemur.roles.service import * # noqa
from lemur.roles.views import * from lemur.roles.views import * # noqa
def test_crud(session): def test_crud(session):

View File

@ -84,5 +84,3 @@ class User(db.Model):
listen(User, 'before_insert', hash_password) listen(User, 'before_insert', hash_password)

View File

@ -145,5 +145,3 @@ def render(args):
query = database.sort(query, User, sort_by, sort_dir) query = database.sort(query, User, sort_by, sort_dir)
return database.paginate(query, page, count) return database.paginate(query, page, count)

View File

@ -28,7 +28,6 @@
"gulp-imagemin": "^0.6.2", "gulp-imagemin": "^0.6.2",
"gulp-inject": "~1.0.1", "gulp-inject": "~1.0.1",
"gulp-jshint": "^1.10.0", "gulp-jshint": "^1.10.0",
"gulp-karma": "^0.0.4",
"gulp-load-plugins": "^0.5.3", "gulp-load-plugins": "^0.5.3",
"gulp-minify-html": "~0.1.4", "gulp-minify-html": "~0.1.4",
"gulp-ng-annotate": "~0.5.2", "gulp-ng-annotate": "~0.5.2",
@ -51,7 +50,9 @@
"main-bower-files": "^1.0.2", "main-bower-files": "^1.0.2",
"require-dir": "~0.3.0", "require-dir": "~0.3.0",
"streamqueue": "^0.1.1", "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": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@ -59,9 +60,13 @@
"scripts": { "scripts": {
"postinstall": "bower install --allow-root", "postinstall": "bower install --allow-root",
"pretest": "npm install && npm run build_static", "pretest": "npm install && npm run build_static",
"build_static": "gulp dist", "build_static": "gulp build",
"prelint": "npm install", "prelint": "npm install",
"lint": "jshint app/", "lint": "jshint lemur/static/app/",
"test": "gulp test" "test": "gulp test"
},
"devDependencies": {
"jshint": "^2.8.0",
"karma-chrome-launcher": "^0.2.0"
} }
} }

12
setup.cfg Normal file
View File

@ -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

View File

@ -14,13 +14,14 @@ import os.path
from distutils import log from distutils import log
from distutils.core import Command from distutils.core import Command
from setuptools.command.develop import develop from setuptools.command.develop import develop
from setuptools.command.install import install
from setuptools.command.sdist import sdist from setuptools.command.sdist import sdist
from setuptools import setup from setuptools import setup
from subprocess import check_output from subprocess import check_output
ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__))) ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__)))
install_requires=[ install_requires = [
'Flask>=0.10.1', 'Flask>=0.10.1',
'Flask-RESTful>=0.3.3', 'Flask-RESTful>=0.3.3',
'Flask-SQLAlchemy>=1.0.5', 'Flask-SQLAlchemy>=1.0.5',
@ -37,7 +38,7 @@ install_requires=[
'six>=1.9.0', 'six>=1.9.0',
'gunicorn>=19.3.0', 'gunicorn>=19.3.0',
'pycrypto>=2.6.1', 'pycrypto>=2.6.1',
'cryptography>=0.9', 'cryptography>=1.0dev',
'pyopenssl>=0.15.1', 'pyopenssl>=0.15.1',
'pyjwt>=1.0.1', 'pyjwt>=1.0.1',
'xmltodict>=0.9.2' 'xmltodict>=0.9.2'
@ -45,10 +46,10 @@ install_requires=[
tests_require = [ tests_require = [
'pyflakes', 'pyflakes',
'moto', 'moto==0.4.6',
'nose', 'nose==1.3.7',
'pytest', 'pytest==2.7.2',
'pytest-flask' 'pytest-flask==0.8.1'
] ]
docs_require = [ docs_require = [
@ -56,6 +57,26 @@ docs_require = [
'sphinxcontrib-httpdomain' '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): class DevelopWithBuildStatic(develop):
def install_for_development(self): def install_for_development(self):
self.run_command('build_static') self.run_command('build_static')
@ -79,7 +100,7 @@ class BuildStatic(Command):
log.info("running [npm install --quiet]") log.info("running [npm install --quiet]")
check_output(['npm', 'install', '--quiet'], cwd=ROOT) 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) check_output([os.path.join(ROOT, 'node_modules', '.bin', 'gulp'), 'build'], cwd=ROOT)
log.info("running [gulp package]") log.info("running [gulp package]")
check_output([os.path.join(ROOT, 'node_modules', '.bin', 'gulp'), 'package'], cwd=ROOT) check_output([os.path.join(ROOT, 'node_modules', '.bin', 'gulp'), 'package'], cwd=ROOT)
@ -96,12 +117,15 @@ setup(
install_requires=install_requires, install_requires=install_requires,
extras_require={ extras_require={
'tests': tests_require, 'tests': tests_require,
'docs': docs_require 'docs': docs_require,
'dev': dev_requires,
}, },
cmdclass={ cmdclass={
'build_static': BuildStatic, 'build_static': BuildStatic,
'develop': DevelopWithBuildStatic, 'develop': DevelopWithBuildStatic,
'sdist': SdistWithBuildStatic 'sdist': SdistWithBuildStatic,
'install': SmartInstall
}, },
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [