Adding UI elements
This commit is contained in:
parent
920d595c12
commit
8eeed821d3
|
@ -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):
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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""
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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():
|
||||||
|
|
Loading…
Reference in New Issue