Merge remote-tracking branch 'upstream/master' into elb-ssl-automation

This commit is contained in:
Jeremy Heffner 2015-08-20 15:46:11 -07:00
commit 96c3ab7f9d
18 changed files with 82 additions and 82 deletions

View File

@ -27,7 +27,10 @@ function browserSyncInit(baseDir, files, browser) {
browserSync.instance = browserSync.init(files, { browserSync.instance = browserSync.init(files, {
startPath: '/index.html', startPath: '/index.html',
server: { server: {
baseDir: baseDir baseDir: baseDir,
routes: {
'/bower_components': './bower_components'
}
}, },
browser: browser, browser: browser,
ghostMode: false ghostMode: false

View File

@ -16,25 +16,19 @@ operator_permission = Permission(RoleNeed('operator'))
admin_permission = Permission(RoleNeed('admin')) admin_permission = Permission(RoleNeed('admin'))
CertificateCreator = namedtuple('certificate', ['method', 'value']) CertificateCreator = namedtuple('certificate', ['method', 'value'])
CertificateCreatorNeed = partial(CertificateCreator, 'certificateView') CertificateCreatorNeed = partial(CertificateCreator, 'key')
CertificateOwner = namedtuple('certificate', ['method', 'value'])
CertificateOwnerNeed = partial(CertificateOwner, 'certificateView')
class ViewKeyPermission(Permission): class ViewKeyPermission(Permission):
def __init__(self, certificate_id, owner_id): def __init__(self, certificate_id, owner):
c_need = CertificateCreatorNeed(str(certificate_id)) c_need = CertificateCreatorNeed(str(certificate_id))
o_need = CertificateOwnerNeed(str(owner_id)) super(ViewKeyPermission, self).__init__(c_need, RoleNeed(owner), RoleNeed('admin'))
super(ViewKeyPermission, self).__init__(o_need, c_need, RoleNeed('admin'))
class UpdateCertificatePermission(Permission): class UpdateCertificatePermission(Permission):
def __init__(self, role_id, certificate_id): def __init__(self, certificate_id, owner):
c_need = CertificateCreatorNeed(str(certificate_id)) c_need = CertificateCreatorNeed(str(certificate_id))
o_need = CertificateOwnerNeed(str(role_id)) super(UpdateCertificatePermission, self).__init__(c_need, RoleNeed(owner), RoleNeed('admin'))
super(UpdateCertificatePermission, self).__init__(o_need, c_need, RoleNeed('admin'))
RoleUser = namedtuple('role', ['method', 'value']) RoleUser = namedtuple('role', ['method', 'value'])

View File

@ -29,7 +29,7 @@ from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers 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 CertificateCreatorNeed, \
AuthorityCreatorNeed, ViewRoleCredentialsNeed AuthorityCreatorNeed, ViewRoleCredentialsNeed
@ -165,7 +165,6 @@ def on_identity_loaded(sender, identity):
# identity with the roles that the user provides # identity with the roles that the user provides
if hasattr(user, 'roles'): if hasattr(user, 'roles'):
for role in user.roles: for role in user.roles:
identity.provides.add(CertificateOwnerNeed(role.id))
identity.provides.add(ViewRoleCredentialsNeed(role.id)) identity.provides.add(ViewRoleCredentialsNeed(role.id))
identity.provides.add(RoleNeed(role.name)) identity.provides.add(RoleNeed(role.name))

View File

@ -446,13 +446,14 @@ class CertificatePrivateKey(AuthenticatedResource):
role = role_service.get_by_name(cert.owner) role = role_service.get_by_name(cert.owner)
permission = ViewKeyPermission(certificate_id, hasattr(role, 'id')) if role:
permission = ViewKeyPermission(certificate_id, role.name)
if permission.can(): if permission.can():
response = make_response(jsonify(key=cert.private_key), 200) response = make_response(jsonify(key=cert.private_key), 200)
response.headers['cache-control'] = 'private, max-age=0, no-cache, no-store' response.headers['cache-control'] = 'private, max-age=0, no-cache, no-store'
response.headers['pragma'] = 'no-cache' response.headers['pragma'] = 'no-cache'
return response return response
return dict(message='You are not authorized to view this key'), 403 return dict(message='You are not authorized to view this key'), 403
@ -572,7 +573,7 @@ class Certificates(AuthenticatedResource):
cert = service.get(certificate_id) cert = service.get(certificate_id)
role = role_service.get_by_name(cert.owner) role = role_service.get_by_name(cert.owner)
permission = UpdateCertificatePermission(certificate_id, hasattr(role, 'id')) permission = UpdateCertificatePermission(certificate_id, role.name)
if permission.can(): if permission.can():
return service.update( return service.update(

View File

@ -9,10 +9,9 @@
.. 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 sqlalchemy.orm.exc import NoResultFound
from lemur.extensions import db from lemur.extensions import db
from lemur.exceptions import AttrNotFound, DuplicateError from lemur.exceptions import AttrNotFound, DuplicateError
@ -126,8 +125,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 Exception as e: except NoResultFound as e:
current_app.logger.exception(e)
return return

View File

@ -77,7 +77,6 @@ LEMUR_RESTRICTED_DOMAINS = []
LEMUR_EMAIL = '' LEMUR_EMAIL = ''
LEMUR_SECURITY_TEAM_EMAIL = [] LEMUR_SECURITY_TEAM_EMAIL = []
LEMUR_DEFAULT_EXPIRATION_NOTIFICATION_INTERVALS = [30, 15, 2]
# Logging # Logging
@ -172,18 +171,17 @@ def generate_settings():
@manager.option('-s', '--sources', dest='labels', default='', required=False) @manager.option('-s', '--sources', dest='labels', default='', required=False)
@manager.option('-l', '--list', dest='view', default=False, required=False) def sync_sources(labels):
def sync_sources(labels, view):
""" """
Attempts to run several methods Certificate discovery. This is Attempts to run several methods Certificate discovery. This is
run on a periodic basis and updates the Lemur datastore with the run on a periodic basis and updates the Lemur datastore with the
information it discovers. information it discovers.
""" """
if view: if not labels:
sys.stdout.write("Active\tLabel\tDescription\n") sys.stdout.write("Active\tLabel\tDescription\n")
for source in source_service.get_all(): for source in source_service.get_all():
sys.stdout.write( sys.stdout.write(
"[{active}]\t{label}\t{description}!\n".format( "{active}\t{label}\t{description}!\n".format(
label=source.label, label=source.label,
description=source.description, description=source.description,
active=source.active active=source.active

View File

@ -38,7 +38,10 @@ def _get_message_data(cert):
:return: :return:
""" """
cert_dict = cert.as_dict() cert_dict = cert.as_dict()
cert_dict['creator'] = cert.user.email
if cert.user:
cert_dict['creator'] = cert.user.email
cert_dict['domains'] = [x .name for x in cert.domains] cert_dict['domains'] = [x .name for x in cert.domains]
cert_dict['superseded'] = list(set([x.name for x in _find_superseded(cert) if cert.name != x])) cert_dict['superseded'] = list(set([x.name for x in _find_superseded(cert) if cert.name != x]))
return cert_dict return cert_dict
@ -56,13 +59,18 @@ def _deduplicate(messages):
for m, r, o in roll_ups: for m, r, o in roll_ups:
if r == targets: if r == targets:
m.append(data) for cert in m:
current_app.logger.info( if cert['body'] == data['body']:
"Sending expiration alert about {0} to {1}".format( break
data['name'], ",".join(targets))) else:
m.append(data)
current_app.logger.info(
"Sending expiration alert about {0} to {1}".format(
data['name'], ",".join(targets)))
break break
else: else:
roll_ups.append(([data], targets, options)) roll_ups.append(([data], targets, options))
return roll_ups return roll_ups

View File

@ -6,6 +6,7 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com> .. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
""" """
from boto.exception import BotoServerError
from lemur.plugins.bases import DestinationPlugin, SourcePlugin from lemur.plugins.bases import DestinationPlugin, SourcePlugin
from lemur.plugins.lemur_aws import iam, elb from lemur.plugins.lemur_aws import iam, elb
from lemur.plugins import lemur_aws as aws from lemur.plugins import lemur_aws as aws
@ -42,7 +43,11 @@ class AWSDestinationPlugin(DestinationPlugin):
# } # }
def upload(self, name, body, private_key, cert_chain, options, **kwargs): def upload(self, name, body, private_key, cert_chain, options, **kwargs):
iam.upload_cert(find_value('accountNumber', options), name, body, private_key, cert_chain=cert_chain) try:
iam.upload_cert(find_value('accountNumber', options), name, body, private_key, cert_chain=cert_chain)
except BotoServerError as e:
if e.error_code != 'EntityAlreadyExists':
raise Exception(e)
e = find_value('elb', options) e = find_value('elb', options)
if e: if e:

View File

@ -326,11 +326,11 @@ class CloudCASourcePlugin(SourcePlugin, CloudCA):
'pollRate': {'type': 'int', 'default': '60'} 'pollRate': {'type': 'int', 'default': '60'}
} }
def get_certificates(self, **kwargs): def get_certificates(self, options, **kwargs):
certs = [] certs = []
for authority in self.get_authorities(): for authority in self.get_authorities():
certs += self.get_cert(ca_name=authority) certs += self.get_cert(ca_name=authority)
return return certs
def get_cert(self, ca_name=None, cert_handle=None): def get_cert(self, ca_name=None, cert_handle=None):
""" """
@ -355,7 +355,7 @@ class CloudCASourcePlugin(SourcePlugin, CloudCA):
certs.append({ certs.append({
'public_certificate': cert, 'public_certificate': cert,
'intermediate_cert': "\n".join(intermediates), 'intermediate_certificate': "\n".join(intermediates),
'owner': c['ownerEmail'] 'owner': c['ownerEmail']
}) })

View File

@ -53,9 +53,9 @@ class EmailNotificationPlugin(ExpirationNotificationPlugin):
# jinja template depending on type # jinja template depending on type
template = env.get_template('{}.html'.format(event_type)) template = env.get_template('{}.html'.format(event_type))
body = template.render(**kwargs) body = template.render(dict(messages=message, hostname=current_app.config.get('LEMUR_HOSTNAME')))
s_type = current_app.config.get("LEMUR_EMAIL_SENDER").lower() s_type = current_app.config.get("LEMUR_EMAIL_SENDER", 'ses').lower()
if s_type == 'ses': if s_type == 'ses':
conn = boto.connect_ses() conn = boto.connect_ses()
conn.send_email(current_app.config.get("LEMUR_EMAIL"), subject, body, targets, format='html') conn.send_email(current_app.config.get("LEMUR_EMAIL"), subject, body, targets, format='html')

View File

@ -1,4 +1,5 @@
from jinja2 import Environment, PackageLoader import os
from jinja2 import Environment, FileSystemLoader
loader = PackageLoader('lemur') loader = FileSystemLoader(searchpath=os.path.dirname(os.path.realpath(__file__)))
env = Environment(loader=loader) env = Environment(loader=loader)

View File

@ -52,8 +52,13 @@
<span style="color: #29abe0">Notice: Your SSL certificates are expiring!</span> <span style="color: #29abe0">Notice: Your SSL certificates are expiring!</span>
<hr /> <hr />
</div> </div>
Lemur, Netflix's SSL management portal has noticed that the following certificates are expiring soon, if you rely on these certificates <p>
you should create new certificates to replace the certificates that are expiring. Visit https://lemur.netflix.com/#/certificates/create to reissue them. Lemur, Netflix's SSL management portal has noticed that the following certificates are expiring soon, if you rely on these certificates
you should create new certificates to replace the certificates that are expiring.
</p>
<p>
Visit https://{{ hostname }}/#/certificates/create to reissue them.
</p>
</td> </td>
</tr> </tr>
{% for message in messages %} {% for message in messages %}
@ -78,6 +83,12 @@
<tr> <tr>
<td>{{ message.creator }}</td> <td>{{ message.creator }}</td>
</tr> </tr>
<tr>
<td><strong>Description</strong></td>
</tr>
<tr>
<td>{{ message.description }}</td>
</tr>
<tr> <tr>
<td><strong>Not Before</strong></td> <td><strong>Not Before</strong></td>
</tr> </tr>
@ -104,20 +115,6 @@
<td>Unknown</td> <td>Unknown</td>
</tr> </tr>
{% endif %} {% endif %}
<tr>
<td><strong>Associated ELBs</strong></td>
</tr>
{% if message.listeners %}
{% for name in message.listeners %}
<tr>
<td>{{ name }}</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td>None</td>
</tr>
{% endif %}
<tr> <tr>
<td><strong>Potentially Superseded by</strong> (Lemur's best guess)</td> <td><strong>Potentially Superseded by</strong> (Lemur's best guess)</td>
</tr> </tr>
@ -139,7 +136,7 @@
</tr> </tr>
<tr> <tr>
<td style="padding-top: 0px" align="center" valign="top"> <td style="padding-top: 0px" align="center" valign="top">
<em style="font-style:italic; font-size: 12px; color: #aaa;">Lemur is broken regularly by <a style="color: #29abe0; text-decoration: none;" href="mailto:secops@netflix.com">Security Operations</a></em> <em style="font-style:italic; font-size: 12px; color: #aaa;">Lemur is broken regularly by Netflix</em>
</td> </td>
</tr> </tr>
</table> </table>

View File

@ -39,6 +39,7 @@ def _disassociate_certs_from_source(current_certificates, found_certificates, so
def sync_create(certificate, source): def sync_create(certificate, source):
cert = cert_service.import_certificate(**certificate) cert = cert_service.import_certificate(**certificate)
cert.description = "This certificate was automatically discovered by Lemur"
cert.sources.append(source) cert.sources.append(source)
sync_update_destination(cert, source) sync_update_destination(cert, source)
database.update(cert) database.update(cert)

View File

@ -107,7 +107,6 @@ angular.module('lemur')
title: certificate.name, title: certificate.name,
body: 'Successfully created!' body: 'Successfully created!'
}); });
$location.path('/certificates');
}, },
function (response) { function (response) {
toaster.pop({ toaster.pop({
@ -120,14 +119,21 @@ angular.module('lemur')
}; };
CertificateService.update = function (certificate) { CertificateService.update = function (certificate) {
return LemurRestangular.copy(certificate).put().then(function () { return LemurRestangular.copy(certificate).put().then(
toaster.pop({ function () {
type: 'success', toaster.pop({
title: certificate.name, type: 'success',
body: 'Successfully updated!' title: certificate.name,
body: 'Successfully updated!'
});
},
function (response) {
toaster.pop({
type: 'error',
title: certificate.name,
body: 'Failed to update ' + response.data.message
});
}); });
$location.path('certificates');
});
}; };
CertificateService.upload = function (certificate) { CertificateService.upload = function (certificate) {
@ -138,7 +144,6 @@ angular.module('lemur')
title: certificate.name, title: certificate.name,
body: 'Successfully uploaded!' body: 'Successfully uploaded!'
}); });
$location.path('/certificates');
}, },
function (response) { function (response) {
toaster.pop({ toaster.pop({

View File

@ -34,16 +34,6 @@ angular.module('lemur')
}); });
}); });
PluginService.getByType('destination').then(function (plugins) {
$scope.plugins = plugins;
_.each($scope.plugins, function (plugin) {
if (plugin.slug === $scope.destination.pluginName) {
plugin.pluginOptions = $scope.destination.destinationOptions;
$scope.destination.plugin = plugin;
}
});
});
$scope.save = function (destination) { $scope.save = function (destination) {
DestinationService.update(destination).then(function () { DestinationService.update(destination).then(function () {
$modalInstance.close(); $modalInstance.close();

View File

@ -21,7 +21,7 @@ angular.module('lemur')
}, { }, {
total: 0, // length of data total: 0, // length of data
getData: function ($defer, params) { getData: function ($defer, params) {
DomainApi.getList().then(function (data) { DomainApi.getList(params.url()).then(function (data) {
params.total(data.total); params.total(data.total);
$defer.resolve(data); $defer.resolve(data);
}); });

View File

@ -87,7 +87,7 @@
</div> </div>
<footer class="footer"> <footer class="footer">
<div class="container"> <div class="container">
<p class="text-muted">Lemur is maintained by <a href="mailto:secops@netflix.com">Security Operations</a>.</p> <p class="text-muted">Lemur is broken regularly by <a href="https://github.com/Netflix/lemur.git">Netflix</a>.</p>
</div> </div>
</footer> </footer>
</body> </body>

View File

@ -15,6 +15,6 @@ def get_key():
:return: :return:
""" """
try: try:
return current_app.config.get('LEMUR_ENCRYPTION_KEY') return current_app.config.get('LEMUR_ENCRYPTION_KEY').strip()
except RuntimeError: except RuntimeError:
return '' return ''