Merge pull request #154 from kevgliss/125-output-plugins
Initial work on #125
This commit is contained in:
commit
9e0b9d9dda
|
@ -32,7 +32,8 @@
|
|||
"angularjs-toaster": "~0.4.14",
|
||||
"ngletteravatar": "~3.0.1",
|
||||
"angular-ui-router": "~0.2.15",
|
||||
"angular-clipboard": "~1.1.1"
|
||||
"angular-clipboard": "~1.1.1",
|
||||
"angular-file-saver": "~1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-mocks": "~1.3",
|
||||
|
|
|
@ -77,6 +77,20 @@ def find_duplicates(cert_body):
|
|||
return Certificate.query.filter_by(body=cert_body).all()
|
||||
|
||||
|
||||
def export(cert, export_plugin):
|
||||
"""
|
||||
Exports a certificate to the requested format. This format
|
||||
may be a binary format.
|
||||
|
||||
:param export_plugin:
|
||||
:param cert:
|
||||
:return:
|
||||
"""
|
||||
plugin = plugins.get(export_plugin['slug'])
|
||||
|
||||
return plugin.export(cert.body, cert.chain, cert.private_key, export_plugin['pluginOptions'])
|
||||
|
||||
|
||||
def update(cert_id, owner, description, active, destinations, notifications, replaces):
|
||||
"""
|
||||
Updates a certificate
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
:license: Apache, see LICENSE for more details.
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
"""
|
||||
import os
|
||||
import requests
|
||||
import subprocess
|
||||
from OpenSSL import crypto
|
||||
|
@ -13,20 +12,7 @@ from cryptography import x509
|
|||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
from flask import current_app
|
||||
|
||||
from contextlib import contextmanager
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
|
||||
@contextmanager
|
||||
def mktempfile():
|
||||
with NamedTemporaryFile(delete=False) as f:
|
||||
name = f.name
|
||||
|
||||
try:
|
||||
yield name
|
||||
finally:
|
||||
os.unlink(name)
|
||||
from lemur.utils import mktempfile
|
||||
|
||||
|
||||
def ocsp_verify(cert_path, issuer_chain_path):
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
:license: Apache, see LICENSE for more details.
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
"""
|
||||
import base64
|
||||
from builtins import str
|
||||
|
||||
from flask import Blueprint, make_response, jsonify
|
||||
|
@ -775,10 +776,60 @@ class CertificatesReplacementsList(AuthenticatedResource):
|
|||
return service.get(certificate_id).replaces
|
||||
|
||||
|
||||
class CertificateExport(AuthenticatedResource):
|
||||
def __init__(self):
|
||||
self.reqparse = reqparse.RequestParser()
|
||||
super(CertificateExport, self).__init__()
|
||||
|
||||
def post(self, certificate_id):
|
||||
"""
|
||||
.. http:post:: /certificates/1/export
|
||||
|
||||
Export a certificate
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PUT /certificates/1/export HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
:statuscode 403: unauthenticated
|
||||
"""
|
||||
self.reqparse.add_argument('export', type=dict, required=True, location='json')
|
||||
args = self.reqparse.parse_args()
|
||||
|
||||
cert = service.get(certificate_id)
|
||||
role = role_service.get_by_name(cert.owner)
|
||||
|
||||
permission = UpdateCertificatePermission(certificate_id, getattr(role, 'name', None))
|
||||
|
||||
if permission.can():
|
||||
extension, passphrase, data = service.export(cert, args['export']['plugin'])
|
||||
# we take a hit in message size when b64 encoding
|
||||
return dict(extension=extension, passphrase=passphrase, data=base64.b64encode(data))
|
||||
|
||||
return dict(message='You are not authorized to export this certificate'), 403
|
||||
|
||||
|
||||
api.add_resource(CertificatesList, '/certificates', endpoint='certificates')
|
||||
api.add_resource(Certificates, '/certificates/<int:certificate_id>', endpoint='certificate')
|
||||
api.add_resource(CertificatesStats, '/certificates/stats', endpoint='certificateStats')
|
||||
api.add_resource(CertificatesUpload, '/certificates/upload', endpoint='certificateUpload')
|
||||
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(CertificatesReplacementsList, '/certificates/<int:certificate_id>/replacements', endpoint='replacements')
|
||||
|
|
|
@ -108,10 +108,11 @@ class IPlugin(local):
|
|||
"""
|
||||
return self.resource_links
|
||||
|
||||
def get_option(self, name, options):
|
||||
@staticmethod
|
||||
def get_option(name, options):
|
||||
for o in options:
|
||||
if o.get('name') == name:
|
||||
return o['value']
|
||||
return o.get('value')
|
||||
|
||||
|
||||
class Plugin(IPlugin):
|
||||
|
|
|
@ -2,3 +2,4 @@ from .destination import DestinationPlugin # noqa
|
|||
from .issuer import IssuerPlugin # noqa
|
||||
from .source import SourcePlugin # noqa
|
||||
from .notification import NotificationPlugin, ExpirationNotificationPlugin # noqa
|
||||
from .export import ExportPlugin # noqa
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
"""
|
||||
.. module: lemur.bases.export
|
||||
:platform: Unix
|
||||
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||
:license: Apache, see LICENSE for more details.
|
||||
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
"""
|
||||
from lemur.plugins.base import Plugin
|
||||
|
||||
|
||||
class ExportPlugin(Plugin):
|
||||
"""
|
||||
This is the base class from which all supported
|
||||
exporters will inherit from.
|
||||
"""
|
||||
type = 'export'
|
||||
|
||||
def export(self):
|
||||
raise NotImplemented
|
|
@ -0,0 +1,5 @@
|
|||
try:
|
||||
VERSION = __import__('pkg_resources') \
|
||||
.get_distribution(__name__).version
|
||||
except Exception as e:
|
||||
VERSION = 'unknown'
|
|
@ -0,0 +1,175 @@
|
|||
"""
|
||||
.. module: lemur.plugins.lemur_java.plugin
|
||||
:platform: Unix
|
||||
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||
:license: Apache, see LICENSE for more details.
|
||||
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
"""
|
||||
import subprocess
|
||||
|
||||
from flask import current_app
|
||||
|
||||
from lemur.utils import mktempfile, mktemppath
|
||||
from lemur.plugins.bases import ExportPlugin
|
||||
from lemur.plugins import lemur_java as java
|
||||
from lemur.common.utils import get_psuedo_random_string
|
||||
|
||||
|
||||
def run_process(command):
|
||||
"""
|
||||
Runs a given command with pOpen and wraps some
|
||||
error handling around it.
|
||||
:param command:
|
||||
:return:
|
||||
"""
|
||||
p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
stdout, stderr = p.communicate()
|
||||
|
||||
if p.returncode != 0:
|
||||
current_app.logger.debug(" ".join(command))
|
||||
current_app.logger.error(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):
|
||||
title = 'Java'
|
||||
slug = 'java-export'
|
||||
description = 'Attempts to generate a JKS keystore or truststore'
|
||||
version = java.VERSION
|
||||
|
||||
author = 'Kevin Glisson'
|
||||
author_url = 'https://github.com/netflix/lemur'
|
||||
|
||||
options = [
|
||||
{
|
||||
'name': 'type',
|
||||
'type': 'select',
|
||||
'required': True,
|
||||
'available': ['Java Key Store (JKS)'],
|
||||
'helpMessage': 'Choose the format you wish to export',
|
||||
},
|
||||
{
|
||||
'name': 'passphrase',
|
||||
'type': 'str',
|
||||
'required': False,
|
||||
'helpMessage': 'If no passphrase is given one will be generated for you, we highly recommend this. Minimum length is 8.',
|
||||
'validation': '^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&])[A-Za-z\d$@$!%*#?&]{8,}$'
|
||||
},
|
||||
{
|
||||
'name': 'alias',
|
||||
'type': 'str',
|
||||
'required': False,
|
||||
'helpMessage': 'Enter the alias you wish to use for the keystore.',
|
||||
}
|
||||
]
|
||||
|
||||
def export(self, body, chain, key, options, **kwargs):
|
||||
"""
|
||||
Generates a Java Keystore or Truststore
|
||||
|
||||
:param key:
|
||||
:param chain:
|
||||
:param body:
|
||||
:param options:
|
||||
:param kwargs:
|
||||
"""
|
||||
|
||||
if self.get_option('passphrase', options):
|
||||
passphrase = self.get_option('passphrase', options)
|
||||
else:
|
||||
passphrase = get_psuedo_random_string()
|
||||
|
||||
if self.get_option('alias', options):
|
||||
alias = self.get_option('alias', options)
|
||||
else:
|
||||
alias = "blah"
|
||||
|
||||
if not key:
|
||||
raise Exception("Unable to export, no private key found.")
|
||||
|
||||
with mktempfile() as cert_tmp:
|
||||
with open(cert_tmp, 'w') as f:
|
||||
f.write(body)
|
||||
|
||||
with mktempfile() as key_tmp:
|
||||
with open(key_tmp, 'w') as f:
|
||||
f.write(key)
|
||||
|
||||
# Create PKCS12 keystore from private key and public certificate
|
||||
with mktempfile() as p12_tmp:
|
||||
run_process([
|
||||
"openssl",
|
||||
"pkcs12",
|
||||
"-export",
|
||||
"-name", alias,
|
||||
"-in", cert_tmp,
|
||||
"-inkey", key_tmp,
|
||||
"-out", p12_tmp,
|
||||
"-password", "pass:{}".format(passphrase)
|
||||
])
|
||||
|
||||
# Convert PKCS12 keystore into a JKS keystore
|
||||
with mktemppath() as jks_tmp:
|
||||
run_process([
|
||||
"keytool",
|
||||
"-importkeystore",
|
||||
"-destkeystore", jks_tmp,
|
||||
"-srckeystore", p12_tmp,
|
||||
"-srcstoretype", "PKCS12",
|
||||
"-alias", alias,
|
||||
"-srcstorepass", passphrase,
|
||||
"-deststorepass", passphrase
|
||||
])
|
||||
|
||||
# Import leaf cert in to JKS keystore
|
||||
run_process([
|
||||
"keytool",
|
||||
"-importcert",
|
||||
"-file", cert_tmp,
|
||||
"-keystore", jks_tmp,
|
||||
"-alias", "{0}_cert".format(alias),
|
||||
"-storepass", passphrase,
|
||||
"-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:
|
||||
raw = f.read()
|
||||
|
||||
return "jks", passphrase, raw
|
|
@ -0,0 +1 @@
|
|||
from lemur.tests.conftest import * # noqa
|
|
@ -0,0 +1,63 @@
|
|||
PRIVATE_KEY_STR = b"""
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEAsXn+QZRATxryRmGXI4fdI+0a2oBwuVh8fC/9bcqX6c5eDmgc
|
||||
rj6esmc1hpIFxMM3DvkFXX6xISkU6B5fmYDEGZLi7NvcXF3+EoA/SCkP1MFlvqhn
|
||||
EvNhb0t1fBLs0i/0gfTS/FHBZY1ekHisd/sUetCDZ7F11RxMwws0Oc8bl7j1TpRc
|
||||
awXFAsh/aWwQOwFeyWU7TtZeAE7sMyWXInBg37tKk1wlv+mN+27WijI091+amkVy
|
||||
zIV6mA5OHfqbjuqV8uQflN8jE244Qr7shtSk7LpBpWf0M6dC7dXbuUctHFhqcDjy
|
||||
3IRUl+NisKRoMtq+a0uehfmpFNSUD7F4gdUtSwIDAQABAoIBAGITsZ+aBuPwVzzv
|
||||
x286MMoeyL1BR4oVzU1v09Rtpf/uLGo3vMnKDzc19A12+rseynl6wi1FyysxIb2Y
|
||||
s2oID9a2JrOQWLmus66TsuT01CvV6J0xQSzm1MyFXdqANuF84NlEa6hGoeK1+jFK
|
||||
jr0LQukP+9484oovxnfu5CCiRHRWNZmeuekuYhI1SJf343Tr6jwvyr6KZpnIy0Yt
|
||||
axuuIZdCfY9ZV2vFG89GwwgwVQrhf14Kv5vBMZrNh1lRGsr0Sqlx5cGkPRAy90lg
|
||||
HjrRMogrtXr3AR5Pk2qqAYXzZBU2EFhJ3k2njpwOzlSj0r0ZwTmejZ89cco0sW5j
|
||||
+eQ6aRECgYEA1tkNW75fgwU52Va5VETCzG8II/pZdqNygnoc3z8EutN+1w8f6Tr+
|
||||
PdpKSICW0z7Iq4f5k/4wrA5xw1vy5RBMH0ZP29GwHTvCPiTBboR9vWvxQvZn1jb9
|
||||
wvKa0RxE18KcF0YIyTnZMubkA17QTFlvCNyZg0iCqeyFYPyqVE+R4AkCgYEA03h1
|
||||
XrqECZDDbG9HLUdGbkZNk4VzTcF6dQ3GAPY8M/H7rw5BbvH0RZLOrzl46DDVzKTg
|
||||
B1VOReAHsxBKFdkqeq1A99CLDow6vHTIEG8DwxkA7/2QPkt8MybwdApUyYnQh5/v
|
||||
CxwkRt4Mm+EiYfn5iyL8yI+vaQSRToVO/3BND7MCgYAJQSpBJG8qzqPSR9kN1zRo
|
||||
5/N60ULfSGUbV7U8rJNAlPGmw+EFA+SFt4xxmRBmIxMzyFSo2k8waiLeXmyVD2Go
|
||||
CzhPaLXkXHmegajPYOelrCulTcXlRVMi/Z5LmaMhhCGDIyInwNUpSybROllQoJ2W
|
||||
zSHTtODj/usz5U5U+WR4OQKBgHQRosI6t2wUo96peTS18UdnmP7GeZINBuymga5X
|
||||
eJW+VLkxpuKBNOTW/lCYx+8Rlte7CyebP9oEa9VxtGgniTRKUeVy9lAm0bpMkt7K
|
||||
QBNebvBKiVhX0DS3Q7U9UmpIFUfLlcXQTW0ERYFtYZTLQpeGvZ5LlyiaFDM34jM7
|
||||
7WAXAoGANDPJdQLEuimCOAMx/xoecNWeZIP6ieB0hVBrwLNxsaZlkn1KodUMuvla
|
||||
VEowbtPRdc9o3VZRh4q9cEakssTvOD70hgUZCFcMarmc37RgRvvD2fsZmDZF6qd3
|
||||
QfHplREs9F0sW+eiirczG7up4XL+CA162TtZxW+2GAiQhwhE5jA=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
"""
|
||||
|
||||
EXTERNAL_VALID_STR = b"""
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID2zCCAsOgAwIBAgICA+0wDQYJKoZIhvcNAQELBQAwgZcxCzAJBgNVBAYTAlVT
|
||||
MRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlMb3MgR2F0b3MxDTALBgNV
|
||||
BAMMBHRlc3QxFjAUBgNVBAoMDU5ldGZsaXgsIEluYy4xEzARBgNVBAsMCk9wZXJh
|
||||
dGlvbnMxIzAhBgkqhkiG9w0BCQEWFGtnbGlzc29uQG5ldGZsaXguY29tMB4XDTE1
|
||||
MTEyMzIxNDIxMFoXDTE1MTEyNjIxNDIxMFowcjENMAsGA1UEAwwEdGVzdDEWMBQG
|
||||
A1UECgwNTmV0ZmxpeCwgSW5jLjETMBEGA1UECwwKT3BlcmF0aW9uczELMAkGA1UE
|
||||
BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCUxvcyBHYXRvczCC
|
||||
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALF5/kGUQE8a8kZhlyOH3SPt
|
||||
GtqAcLlYfHwv/W3Kl+nOXg5oHK4+nrJnNYaSBcTDNw75BV1+sSEpFOgeX5mAxBmS
|
||||
4uzb3Fxd/hKAP0gpD9TBZb6oZxLzYW9LdXwS7NIv9IH00vxRwWWNXpB4rHf7FHrQ
|
||||
g2exddUcTMMLNDnPG5e49U6UXGsFxQLIf2lsEDsBXsllO07WXgBO7DMllyJwYN+7
|
||||
SpNcJb/pjftu1ooyNPdfmppFcsyFepgOTh36m47qlfLkH5TfIxNuOEK+7IbUpOy6
|
||||
QaVn9DOnQu3V27lHLRxYanA48tyEVJfjYrCkaDLavmtLnoX5qRTUlA+xeIHVLUsC
|
||||
AwEAAaNVMFMwUQYDVR0fBEowSDBGoESgQoZAaHR0cDovL3Rlc3QuY2xvdWRjYS5j
|
||||
cmwubmV0ZmxpeC5jb20vdGVzdERlY3JpcHRpb25DQVJvb3QvY3JsLnBlbTANBgkq
|
||||
hkiG9w0BAQsFAAOCAQEAiHREBKg7zhlQ/N7hDIkxgodRSWD7CVbJGSCdkR3Pvr6+
|
||||
jHBVNTJUrYqy7sL2pIutoeiSTQEH65/Gbm30mOnNu+lvFKxTxzof6kNYv8cyc8sX
|
||||
eBuBfSrlTodPFSHXQIpOexZgA0f30LOuXegqzxgXkKg+uMXOez5Zo5pNjTUow0He
|
||||
oe+V1hfYYvL1rocCmBOkhIGWz7622FxKDawRtZTGVsGsMwMIWyvS3+KQ04K8yHhp
|
||||
bQOg9zZAoYQuHY1inKBnA0II8eW0hPpJrlZoSqN8Tp0NSBpFiUk3m7KNFP2kITIf
|
||||
tTneAgyUsgfDxNDifZryZSzg7MH31sTBcYaotSmTXw==
|
||||
-----END CERTIFICATE-----
|
||||
"""
|
||||
|
||||
|
||||
def test_export_certificate_to_jks(app):
|
||||
from lemur.plugins.base import plugins
|
||||
p = plugins.get('java-export')
|
||||
options = {'passphrase': 'test1234'}
|
||||
raw = p.export(EXTERNAL_VALID_STR, "", PRIVATE_KEY_STR, options)
|
||||
assert raw != b""
|
|
@ -15,7 +15,8 @@ var lemur = angular
|
|||
'mgo-angular-wizard',
|
||||
'satellizer',
|
||||
'ngLetterAvatar',
|
||||
'angular-clipboard'
|
||||
'angular-clipboard',
|
||||
'ngFileSaver'
|
||||
])
|
||||
.config(function ($stateProvider, $urlRouterProvider, $authProvider) {
|
||||
$urlRouterProvider.otherwise('/welcome');
|
||||
|
|
|
@ -1,6 +1,52 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('lemur')
|
||||
.controller('CertificateExportController', function ($scope, $modalInstance, CertificateApi, CertificateService, PluginService, FileSaver, Blob, 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 (response) {
|
||||
var byteCharacters = atob(response.data);
|
||||
var byteArrays = [];
|
||||
|
||||
for (var offset = 0; offset < byteCharacters.length; offset += 512) {
|
||||
var slice = byteCharacters.slice(offset, offset + 512);
|
||||
|
||||
var byteNumbers = new Array(slice.length);
|
||||
for (var i = 0; i < slice.length; i++) {
|
||||
byteNumbers[i] = slice.charCodeAt(i);
|
||||
}
|
||||
|
||||
var byteArray = new Uint8Array(byteNumbers);
|
||||
|
||||
byteArrays.push(byteArray);
|
||||
}
|
||||
|
||||
var blob = new Blob(byteArrays, {type: 'application/octet-stream'});
|
||||
FileSaver.saveAs(blob, certificate.name + '.' + response.extension);
|
||||
$scope.passphrase = response.passphrase;
|
||||
},
|
||||
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) {
|
||||
CertificateApi.get(editId).then(function (certificate) {
|
||||
CertificateService.getNotifications(certificate);
|
||||
|
|
|
@ -183,5 +183,9 @@ angular.module('lemur')
|
|||
return certificate.put();
|
||||
};
|
||||
|
||||
CertificateService.export = function (certificate) {
|
||||
return certificate.customPOST(certificate.exportOptions, 'export');
|
||||
};
|
||||
|
||||
return CertificateService;
|
||||
});
|
||||
|
|
|
@ -163,4 +163,18 @@ angular.module('lemur')
|
|||
$scope.certificateTable.reload();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.export = function (certificateId) {
|
||||
$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-heading">
|
||||
<div class="btn-group pull-right">
|
||||
<button data-placement="left" data-title="Create Certificate" bs-tooltip ng-click="create()"
|
||||
class="btn btn-primary">
|
||||
<button ng-click="create()" class="btn btn-primary">
|
||||
Create
|
||||
</button>
|
||||
<button data-placement="left" data-title="Import Certificate" bs-tooltip ng-click="import()"
|
||||
class="btn btn-info">
|
||||
<button ng-click="import()" class="btn btn-info">
|
||||
Import
|
||||
</button>
|
||||
</div>
|
||||
|
@ -48,6 +46,9 @@
|
|||
<button ng-model="certificate.toggle" class="btn btn-sm btn-info" btn-checkbox btn-checkbox-true="1"
|
||||
butn-checkbox-false="0">More
|
||||
</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>
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
@ -43,7 +43,7 @@ LOG_FILE = "lemur.log"
|
|||
|
||||
# modify this if you are not using a local database
|
||||
SQLALCHEMY_DATABASE_URI = 'postgresql://lemur:lemur@localhost:5432/lemur'
|
||||
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
|
||||
# AWS
|
||||
|
||||
|
|
|
@ -5,11 +5,41 @@
|
|||
:license: Apache, see LICENSE for more details.
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
"""
|
||||
import os
|
||||
import six
|
||||
from flask import current_app
|
||||
from cryptography.fernet import Fernet, MultiFernet
|
||||
import sqlalchemy.types as types
|
||||
|
||||
from contextlib import contextmanager
|
||||
import tempfile
|
||||
|
||||
|
||||
@contextmanager
|
||||
def mktempfile():
|
||||
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
name = f.name
|
||||
|
||||
try:
|
||||
yield name
|
||||
finally:
|
||||
try:
|
||||
os.unlink(name)
|
||||
except OSError as e:
|
||||
current_app.logger.debug("No file {0}".format(name))
|
||||
|
||||
|
||||
@contextmanager
|
||||
def mktemppath():
|
||||
try:
|
||||
path = os.path.join(tempfile._get_default_tempdir(), next(tempfile._get_candidate_names()))
|
||||
yield path
|
||||
finally:
|
||||
try:
|
||||
os.unlink(path)
|
||||
except OSError as e:
|
||||
current_app.logger.debug("No file {0}".format(path))
|
||||
|
||||
|
||||
def get_keys():
|
||||
"""
|
||||
|
@ -26,7 +56,7 @@ def get_keys():
|
|||
# the fact that there is not a current_app with a config at that point
|
||||
try:
|
||||
keys = current_app.config.get('LEMUR_ENCRYPTION_KEYS')
|
||||
except:
|
||||
except Exception:
|
||||
print("no encryption keys")
|
||||
return []
|
||||
|
||||
|
|
3
setup.py
3
setup.py
|
@ -25,7 +25,7 @@ ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__)))
|
|||
|
||||
install_requires = [
|
||||
'Flask==0.10.1',
|
||||
'Flask-RESTful==0.3.4',
|
||||
'Flask-RESTful==0.3.3',
|
||||
'Flask-SQLAlchemy==2.1',
|
||||
'Flask-Script==2.0.5',
|
||||
'Flask-Migrate==1.6.0',
|
||||
|
@ -154,6 +154,7 @@ setup(
|
|||
'aws_destination = lemur.plugins.lemur_aws.plugin:AWSDestinationPlugin',
|
||||
'aws_source = lemur.plugins.lemur_aws.plugin:AWSSourcePlugin',
|
||||
'email_notification = lemur.plugins.lemur_email.plugin:EmailNotificationPlugin',
|
||||
'java_export = lemur.plugins.lemur_java.plugin:JavaExportPlugin'
|
||||
],
|
||||
},
|
||||
classifiers=[
|
||||
|
|
Loading…
Reference in New Issue