From b8c2d42cad2bfeabd4a66879cacfccefd83728bb Mon Sep 17 00:00:00 2001 From: kevgliss Date: Wed, 16 Dec 2015 08:57:32 -0800 Subject: [PATCH] Closes #176 --- lemur/plugins/lemur_openssl/__init__.py | 5 + lemur/plugins/lemur_openssl/plugin.py | 130 ++++++++++++++++++ lemur/plugins/lemur_openssl/tests/conftest.py | 1 + .../lemur_openssl/tests/test_openssl.py | 63 +++++++++ setup.py | 3 + 5 files changed, 202 insertions(+) create mode 100644 lemur/plugins/lemur_openssl/__init__.py create mode 100644 lemur/plugins/lemur_openssl/plugin.py create mode 100644 lemur/plugins/lemur_openssl/tests/conftest.py create mode 100644 lemur/plugins/lemur_openssl/tests/test_openssl.py diff --git a/lemur/plugins/lemur_openssl/__init__.py b/lemur/plugins/lemur_openssl/__init__.py new file mode 100644 index 00000000..8ce5a7f3 --- /dev/null +++ b/lemur/plugins/lemur_openssl/__init__.py @@ -0,0 +1,5 @@ +try: + VERSION = __import__('pkg_resources') \ + .get_distribution(__name__).version +except Exception as e: + VERSION = 'unknown' diff --git a/lemur/plugins/lemur_openssl/plugin.py b/lemur/plugins/lemur_openssl/plugin.py new file mode 100644 index 00000000..2a1c9244 --- /dev/null +++ b/lemur/plugins/lemur_openssl/plugin.py @@ -0,0 +1,130 @@ +""" +.. module: lemur.plugins.lemur_openssl.plugin + :platform: Unix + :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more + :license: Apache, see LICENSE for more details. + +.. moduleauthor:: Kevin Glisson +""" +import subprocess + +from flask import current_app + +from lemur.utils import mktempfile, mktemppath +from lemur.plugins.bases import ExportPlugin +from lemur.plugins import lemur_openssl as openssl +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) + current_app.logger.debug(command) + stdout, stderr = p.communicate() + + if p.returncode != 0: + current_app.logger.debug(" ".join(command)) + current_app.logger.error(stderr) + raise Exception(stderr) + + +def create_pkcs12(cert, p12_tmp, key, alias, passphrase): + """ + Creates a pkcs12 formated file. + :param cert: + :param jks_tmp: + :param key: + :param alias: + :param passphrase: + """ + 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 cert_tmp: + with open(cert_tmp, 'w') as f: + f.write(cert) + + run_process([ + "openssl", + "pkcs12", + "-export", + "-name", alias, + "-in", cert_tmp, + "-inkey", key_tmp, + "-out", p12_tmp, + "-password", "pass:{}".format(passphrase) + ]) + + +class OpenSSLExportPlugin(ExportPlugin): + title = 'OpenSSL' + slug = 'openssl-export' + description = 'Is a loose interface to openssl and support various formats' + version = openssl.VERSION + + author = 'Kevin Glisson' + author_url = 'https://github.com/netflix/lemur' + + options = [ + { + 'name': 'type', + 'type': 'select', + 'required': True, + 'available': ['PKCS12 (.p12)'], + '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" + + type = self.get_option('type', options) + + with mktemppath() as output_tmp: + if type == 'PKCS12 (.p12)': + create_pkcs12(body, output_tmp, key, alias, passphrase) + extension = "p12" + else: + raise Exception("Unable to export, unsupported type: {0}".format(type)) + + with open(output_tmp, 'rb') as f: + raw = f.read() + + return extension, passphrase, raw diff --git a/lemur/plugins/lemur_openssl/tests/conftest.py b/lemur/plugins/lemur_openssl/tests/conftest.py new file mode 100644 index 00000000..0e1cd89f --- /dev/null +++ b/lemur/plugins/lemur_openssl/tests/conftest.py @@ -0,0 +1 @@ +from lemur.tests.conftest import * # noqa diff --git a/lemur/plugins/lemur_openssl/tests/test_openssl.py b/lemur/plugins/lemur_openssl/tests/test_openssl.py new file mode 100644 index 00000000..37aac9a3 --- /dev/null +++ b/lemur/plugins/lemur_openssl/tests/test_openssl.py @@ -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"" diff --git a/setup.py b/setup.py index 68b94089..f93e7ee4 100644 --- a/setup.py +++ b/setup.py @@ -74,6 +74,8 @@ docs_require = [ dev_requires = [ 'flake8>=2.0,<3.0', + 'invoke', + 'twine' ] @@ -164,6 +166,7 @@ setup( 'aws_source = lemur.plugins.lemur_aws.plugin:AWSSourcePlugin', 'email_notification = lemur.plugins.lemur_email.plugin:EmailNotificationPlugin', 'java_export = lemur.plugins.lemur_java.plugin:JavaExportPlugin' + 'openssl_export = lemur.plugins.lemur_openssl.plugin:OpenSSLExportPlugin' ], }, classifiers=[