Adding UI elements

This commit is contained in:
kevgliss 2015-11-27 13:27:14 -08:00
parent 920d595c12
commit 8eeed821d3
11 changed files with 139 additions and 35 deletions

View File

@ -77,22 +77,18 @@ def find_duplicates(cert_body):
return Certificate.query.filter_by(body=cert_body).all() return Certificate.query.filter_by(body=cert_body).all()
def export(cert_id, export_options): def export(cert, export_plugin):
""" """
Exports a certificate to the requested format. This format Exports a certificate to the requested format. This format
may be a binary format. may be a binary format.
:param export_options:
:param cert_id: :param export_plugin:
:param cert:
:return: :return:
""" """
cert = get(cert_id) plugin = plugins.get(export_plugin['slug'])
export_plugin = plugins.get(export_options['slug'])
data = export_plugin.export(cert.body, cert.key) return plugin.export(cert.body, cert.chain, cert.private_key, export_plugin['pluginOptions'])
if export_options.get('encrypted'):
pass
return data
def update(cert_id, owner, description, active, destinations, notifications, replaces): def update(cert_id, owner, description, active, destinations, notifications, replaces):

View File

@ -775,10 +775,10 @@ class CertificatesReplacementsList(AuthenticatedResource):
return service.get(certificate_id).replaces return service.get(certificate_id).replaces
class CertficiateExport(AuthenticatedResource): class CertificateExport(AuthenticatedResource):
def __init__(self): def __init__(self):
self.reqparse = reqparse.RequestParser() self.reqparse = reqparse.RequestParser()
super(CertficiateExport, self).__init__() super(CertificateExport, self).__init__()
def post(self, certificate_id): def post(self, certificate_id):
""" """
@ -808,6 +808,7 @@ class CertficiateExport(AuthenticatedResource):
:statuscode 200: no error :statuscode 200: no error
:statuscode 403: unauthenticated :statuscode 403: unauthenticated
""" """
self.reqparse.add_argument('export', type=dict, required=True, location='json')
args = self.reqparse.parse_args() args = self.reqparse.parse_args()
cert = service.get(certificate_id) cert = service.get(certificate_id)
@ -816,7 +817,7 @@ class CertficiateExport(AuthenticatedResource):
permission = UpdateCertificatePermission(certificate_id, getattr(role, 'name', None)) permission = UpdateCertificatePermission(certificate_id, getattr(role, 'name', None))
if permission.can(): if permission.can():
data = service.export(certificate_id) passphrase, data = service.export(cert, args['export']['plugin'])
response = make_response(data) response = make_response(data)
response.headers['content-type'] = 'application/octet-stream' response.headers['content-type'] = 'application/octet-stream'
return response return response
@ -829,5 +830,6 @@ api.add_resource(Certificates, '/certificates/<int:certificate_id>', endpoint='c
api.add_resource(CertificatesStats, '/certificates/stats', endpoint='certificateStats') api.add_resource(CertificatesStats, '/certificates/stats', endpoint='certificateStats')
api.add_resource(CertificatesUpload, '/certificates/upload', endpoint='certificateUpload') api.add_resource(CertificatesUpload, '/certificates/upload', endpoint='certificateUpload')
api.add_resource(CertificatePrivateKey, '/certificates/<int:certificate_id>/key', endpoint='privateKeyCertificates') api.add_resource(CertificatePrivateKey, '/certificates/<int:certificate_id>/key', endpoint='privateKeyCertificates')
api.add_resource(CertificateExport, '/certificates/<int:certificate_id>/export', endpoint='exportCertificate')
api.add_resource(NotificationCertificatesList, '/notifications/<int:notification_id>/certificates', endpoint='notificationCertificates') api.add_resource(NotificationCertificatesList, '/notifications/<int:notification_id>/certificates', endpoint='notificationCertificates')
api.add_resource(CertificatesReplacementsList, '/certificates/<int:certificate_id>/replacements', endpoint='replacements') api.add_resource(CertificatesReplacementsList, '/certificates/<int:certificate_id>/replacements', endpoint='replacements')

View File

@ -108,10 +108,11 @@ class IPlugin(local):
""" """
return self.resource_links return self.resource_links
def get_option(self, name, options): @staticmethod
def get_option(name, options):
for o in options: for o in options:
if o.get('name') == name: if o.get('name') == name:
return o['value'] return o.get('value')
class Plugin(IPlugin): class Plugin(IPlugin):

View File

@ -10,6 +10,10 @@ from lemur.plugins.base import Plugin
class ExportPlugin(Plugin): class ExportPlugin(Plugin):
"""
This is the base class from which all supported
exporters will inherit from.
"""
type = 'export' type = 'export'
def export(self): def export(self):

View File

@ -32,6 +32,26 @@ def run_process(command):
raise Exception(stderr) raise Exception(stderr)
def split_chain(chain):
"""
Split the chain into individual certificates for import into keystore
:param chain:
:return:
"""
certs = []
lines = chain.split('\n')
cert = []
for line in lines:
cert.append(line + '\n')
if line == '-----END CERTIFICATE-----':
certs.append("".join(cert))
cert = []
return certs
class JavaExportPlugin(ExportPlugin): class JavaExportPlugin(ExportPlugin):
title = 'Java' title = 'Java'
slug = 'java-export' slug = 'java-export'
@ -41,19 +61,20 @@ class JavaExportPlugin(ExportPlugin):
author = 'Kevin Glisson' author = 'Kevin Glisson'
author_url = 'https://github.com/netflix/lemur' author_url = 'https://github.com/netflix/lemur'
additional_options = [ options = [
{ {
'name': 'type', 'name': 'type',
'type': 'select', 'type': 'select',
'required': True, 'required': True,
'available': ['jks'], 'available': ['Java Key Store (JKS)'],
'helpMessage': 'Choose the format you wish to export', 'helpMessage': 'Choose the format you wish to export',
}, },
{ {
'name': 'passphrase', 'name': 'passphrase',
'type': 'str', 'type': 'str',
'required': False, 'required': False,
'helpMessage': 'If no passphrase is generated one will be generated for you.', 'helpMessage': 'If no passphrase is given one will be generated for you, we highly recommend this. Minimum length is 8.',
'validation': '^.{8}$'
}, },
{ {
'name': 'alias', 'name': 'alias',
@ -63,21 +84,27 @@ class JavaExportPlugin(ExportPlugin):
} }
] ]
@staticmethod def export(self, body, chain, key, options, **kwargs):
def export(body, key, options, **kwargs):
""" """
Generates a Java Keystore or Truststore Generates a Java Keystore or Truststore
:param key: :param key:
:param kwargs: :param chain:
:param body: :param body:
:param options: :param options:
:param kwargs:
""" """
if options.get('passphrase'):
passphrase = options['passphrase'] if self.get_option('passphrase', options):
passphrase = self.get_option('passphrase', options)
else: else:
passphrase = get_psuedo_random_string() passphrase = get_psuedo_random_string()
if self.get_option('alias', options):
alias = self.get_option('alias', options)
else:
alias = "blah"
with mktempfile() as cert_tmp: with mktempfile() as cert_tmp:
with open(cert_tmp, 'w') as f: with open(cert_tmp, 'w') as f:
f.write(body) f.write(body)
@ -91,7 +118,7 @@ class JavaExportPlugin(ExportPlugin):
"openssl", "openssl",
"pkcs12", "pkcs12",
"-export", "-export",
"-name", options.get('alias', 'cert'), "-name", alias,
"-in", cert_tmp, "-in", cert_tmp,
"-inkey", key_tmp, "-inkey", key_tmp,
"-out", p12_tmp, "-out", p12_tmp,
@ -106,23 +133,39 @@ class JavaExportPlugin(ExportPlugin):
"-destkeystore", jks_tmp, "-destkeystore", jks_tmp,
"-srckeystore", p12_tmp, "-srckeystore", p12_tmp,
"-srcstoretype", "PKCS12", "-srcstoretype", "PKCS12",
"-alias", options.get('alias', 'cert'), "-alias", alias,
"-srcstorepass", passphrase, "-srcstorepass", passphrase,
"-deststorepass", passphrase "-deststorepass", passphrase
]) ])
# Import signed cert in to JKS keystore # Import leaf cert in to JKS keystore
run_process([ run_process([
"keytool", "keytool",
"-importcert", "-importcert",
"-file", cert_tmp, "-file", cert_tmp,
"-keystore", jks_tmp, "-keystore", jks_tmp,
"-alias", "{0}_cert".format(options.get('alias'), 'cert'), "-alias", "{0}_cert".format(alias),
"-storepass", passphrase, "-storepass", passphrase,
"-noprompt" "-noprompt"
]) ])
# Import the entire chain
for idx, cert in enumerate(split_chain(chain)):
with mktempfile() as c_tmp:
with open(c_tmp, 'w') as f:
f.write(cert)
# Import signed cert in to JKS keystore
run_process([
"keytool",
"-importcert",
"-file", c_tmp,
"-keystore", jks_tmp,
"-alias", "{0}_cert_{1}".format(alias, idx),
"-storepass", passphrase,
"-noprompt"
])
with open(jks_tmp, 'rb') as f: with open(jks_tmp, 'rb') as f:
raw = f.read() raw = f.read()
return raw return passphrase, raw

View File

@ -59,5 +59,5 @@ def test_export_certificate_to_jks(app):
from lemur.plugins.base import plugins from lemur.plugins.base import plugins
p = plugins.get('java-export') p = plugins.get('java-export')
options = {'passphrase': 'test1234'} options = {'passphrase': 'test1234'}
raw = p.export(EXTERNAL_VALID_STR, PRIVATE_KEY_STR, options) raw = p.export(EXTERNAL_VALID_STR, "", PRIVATE_KEY_STR, options)
assert raw != b"" assert raw != b""

View File

@ -1,6 +1,39 @@
'use strict'; 'use strict';
angular.module('lemur') angular.module('lemur')
.controller('CertificateExportController', function ($scope, $modalInstance, CertificateApi, CertificateService, PluginService, toaster, editId) {
CertificateApi.get(editId).then(function (certificate) {
$scope.certificate = certificate;
});
PluginService.getByType('export').then(function (plugins) {
$scope.plugins = plugins;
});
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
$scope.save = function (certificate) {
CertificateService.export(certificate).then(
function () {
toaster.pop({
type: 'success',
title: certificate.name,
body: 'Successfully exported!'
});
$modalInstance.close();
},
function (response) {
toaster.pop({
type: 'error',
title: certificate.name,
body: 'Failed to export ' + response.data.message,
timeout: 100000
});
});
};
})
.controller('CertificateEditController', function ($scope, $modalInstance, CertificateApi, CertificateService, DestinationService, NotificationService, toaster, editId) { .controller('CertificateEditController', function ($scope, $modalInstance, CertificateApi, CertificateService, DestinationService, NotificationService, toaster, editId) {
CertificateApi.get(editId).then(function (certificate) { CertificateApi.get(editId).then(function (certificate) {
CertificateService.getNotifications(certificate); CertificateService.getNotifications(certificate);

View File

@ -183,5 +183,9 @@ angular.module('lemur')
return certificate.put(); return certificate.put();
}; };
CertificateService.export = function (certificate) {
return certificate.customPOST(certificate.exportOptions, 'export');
};
return CertificateService; return CertificateService;
}); });

View File

@ -163,4 +163,18 @@ angular.module('lemur')
$scope.certificateTable.reload(); $scope.certificateTable.reload();
}); });
}; };
$scope.export = function (certificateId) {
var modalInstance = $modal.open({
animation: true,
controller: 'CertificateExportController',
templateUrl: '/angular/certificates/certificate/export.tpl.html',
size: 'lg',
resolve: {
editId: function () {
return certificateId;
}
}
});
};
}); });

View File

@ -5,12 +5,10 @@
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<div class="btn-group pull-right"> <div class="btn-group pull-right">
<button data-placement="left" data-title="Create Certificate" bs-tooltip ng-click="create()" <button ng-click="create()" class="btn btn-primary">
class="btn btn-primary">
Create Create
</button> </button>
<button data-placement="left" data-title="Import Certificate" bs-tooltip ng-click="import()" <button ng-click="import()" class="btn btn-info">
class="btn btn-info">
Import Import
</button> </button>
</div> </div>
@ -48,6 +46,9 @@
<button ng-model="certificate.toggle" class="btn btn-sm btn-info" btn-checkbox btn-checkbox-true="1" <button ng-model="certificate.toggle" class="btn btn-sm btn-info" btn-checkbox btn-checkbox-true="1"
butn-checkbox-false="0">More butn-checkbox-false="0">More
</button> </button>
<button ng-click="export(certificate.id)" class="btn btn-sm btn-success">
Export
</button>
<button class="btn btn-sm btn-warning" ng-click="edit(certificate.id)">Edit</button> <button class="btn btn-sm btn-warning" ng-click="edit(certificate.id)">Edit</button>
</div> </div>
</td> </td>

View File

@ -23,7 +23,10 @@ def mktempfile():
try: try:
yield name yield name
finally: finally:
os.unlink(name) try:
os.unlink(name)
except OSError as e:
current_app.logger.debug("No file {0}".format(name))
@contextmanager @contextmanager
@ -32,7 +35,10 @@ def mktemppath():
path = os.path.join(tempfile._get_default_tempdir(), next(tempfile._get_candidate_names())) path = os.path.join(tempfile._get_default_tempdir(), next(tempfile._get_candidate_names()))
yield path yield path
finally: finally:
os.unlink(path) try:
os.unlink(path)
except OSError as e:
current_app.logger.debug("No file {0}".format(path))
def get_keys(): def get_keys():