Merge branch 'master' into add-pending-certificate-upload

This commit is contained in:
Hossein Shafagh 2019-04-22 11:47:02 -07:00 committed by GitHub
commit 9b38761153
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 260 additions and 329 deletions

View File

@ -85,7 +85,9 @@ def parse_cert_chain(pem_chain):
:param pem_chain: string
:return: List of parsed certificates
"""
return [parse_certificate(cert) for cert in split_pem(pem_chain) if pem_chain]
if pem_chain is None:
return []
return [parse_certificate(cert) for cert in split_pem(pem_chain) if cert]
def parse_csr(csr):

View File

@ -1,246 +0,0 @@
"""
.. module: lemur.plugins.lemur_java.plugin
:platform: Unix
:copyright: (c) 2018 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 cryptography.fernet import Fernet
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 parse_certificate
from lemur.common.defaults import common_name
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)
current_app.logger.error(stdout)
raise Exception(stderr)
def split_chain(chain):
"""
Split the chain into individual certificates for import into keystore
:param chain:
:return:
"""
certs = []
if not 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
def create_truststore(cert, chain, jks_tmp, alias, passphrase):
assert isinstance(cert, str)
assert isinstance(chain, str)
with mktempfile() as cert_tmp:
with open(cert_tmp, 'w') as f:
f.write(cert)
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"
])
def create_keystore(cert, chain, jks_tmp, key, alias, passphrase):
assert isinstance(cert, str)
assert isinstance(chain, str)
assert isinstance(key, str)
# Create PKCS12 keystore from private key and public certificate
with mktempfile() as cert_tmp:
with open(cert_tmp, 'w') as f:
if chain:
f.writelines([key.strip() + "\n", cert.strip() + "\n", chain.strip() + "\n"])
else:
f.writelines([key.strip() + "\n", cert.strip() + "\n"])
with mktempfile() as p12_tmp:
run_process([
"openssl",
"pkcs12",
"-export",
"-nodes",
"-name", alias,
"-in", cert_tmp,
"-out", p12_tmp,
"-password", "pass:{}".format(passphrase)
])
# Convert PKCS12 keystore into a JKS keystore
run_process([
"keytool",
"-importkeystore",
"-destkeystore", jks_tmp,
"-srckeystore", p12_tmp,
"-srcstoretype", "pkcs12",
"-deststoretype", "JKS",
"-alias", alias,
"-srcstorepass", passphrase,
"-deststorepass", passphrase
])
class JavaTruststoreExportPlugin(ExportPlugin):
title = 'Java Truststore (JKS)'
slug = 'java-truststore-jks'
description = 'Attempts to generate a JKS truststore'
requires_key = False
version = java.VERSION
author = 'Kevin Glisson'
author_url = 'https://github.com/netflix/lemur'
options = [
{
'name': 'alias',
'type': 'str',
'required': False,
'helpMessage': 'Enter the alias you wish to use for the truststore.',
},
{
'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': ''
},
]
def export(self, body, chain, key, options, **kwargs):
"""
Generates a Java Truststore
:param key:
:param chain:
:param body:
:param options:
:param kwargs:
"""
if self.get_option('alias', options):
alias = self.get_option('alias', options)
else:
alias = "blah"
if self.get_option('passphrase', options):
passphrase = self.get_option('passphrase', options)
else:
passphrase = Fernet.generate_key().decode('utf-8')
with mktemppath() as jks_tmp:
create_truststore(body, chain, jks_tmp, alias, passphrase)
with open(jks_tmp, 'rb') as f:
raw = f.read()
return "jks", passphrase, raw
class JavaKeystoreExportPlugin(ExportPlugin):
title = 'Java Keystore (JKS)'
slug = 'java-keystore-jks'
description = 'Attempts to generate a JKS keystore'
version = java.VERSION
author = 'Kevin Glisson'
author_url = 'https://github.com/netflix/lemur'
options = [
{
'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': ''
},
{
'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
:param key:
:param chain:
:param body:
:param options:
:param kwargs:
"""
if self.get_option('passphrase', options):
passphrase = self.get_option('passphrase', options)
else:
passphrase = Fernet.generate_key().decode('utf-8')
if self.get_option('alias', options):
alias = self.get_option('alias', options)
else:
alias = common_name(parse_certificate(body))
with mktemppath() as jks_tmp:
create_keystore(body, chain, jks_tmp, key, alias, passphrase)
with open(jks_tmp, 'rb') as f:
raw = f.read()
return "jks", passphrase, raw

View File

@ -1,63 +0,0 @@
import pytest
from lemur.tests.vectors import INTERNAL_CERTIFICATE_A_STR, INTERNAL_PRIVATE_KEY_A_STR
@pytest.mark.skip(reason="no way of currently testing this")
def test_export_truststore(app):
from lemur.plugins.base import plugins
p = plugins.get('java-truststore-jks')
options = [{'name': 'passphrase', 'value': 'test1234'}]
actual = p.export(INTERNAL_CERTIFICATE_A_STR, "", "", options)
assert actual[0] == 'jks'
assert actual[1] == 'test1234'
assert isinstance(actual[2], bytes)
@pytest.mark.skip(reason="no way of currently testing this")
def test_export_truststore_default_password(app):
from lemur.plugins.base import plugins
p = plugins.get('java-truststore-jks')
options = []
actual = p.export(INTERNAL_CERTIFICATE_A_STR, "", "", options)
assert actual[0] == 'jks'
assert isinstance(actual[1], str)
assert isinstance(actual[2], bytes)
@pytest.mark.skip(reason="no way of currently testing this")
def test_export_keystore(app):
from lemur.plugins.base import plugins
p = plugins.get('java-keystore-jks')
options = [{'name': 'passphrase', 'value': 'test1234'}]
with pytest.raises(Exception):
p.export(INTERNAL_CERTIFICATE_A_STR, "", "", options)
actual = p.export(INTERNAL_CERTIFICATE_A_STR, "", INTERNAL_PRIVATE_KEY_A_STR, options)
assert actual[0] == 'jks'
assert actual[1] == 'test1234'
assert isinstance(actual[2], bytes)
@pytest.mark.skip(reason="no way of currently testing this")
def test_export_keystore_default_password(app):
from lemur.plugins.base import plugins
p = plugins.get('java-keystore-jks')
options = []
with pytest.raises(Exception):
p.export(INTERNAL_CERTIFICATE_A_STR, "", "", options)
actual = p.export(INTERNAL_CERTIFICATE_A_STR, "", INTERNAL_PRIVATE_KEY_A_STR, options)
assert actual[0] == 'jks'
assert isinstance(actual[1], str)
assert isinstance(actual[2], bytes)

View File

@ -0,0 +1,140 @@
"""
.. module: lemur.plugins.lemur_jks.plugin
:platform: Unix
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Marti Raudsepp <marti@juffo.org>
"""
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import serialization
from jks import PrivateKeyEntry, KeyStore, TrustedCertEntry
from lemur.common.defaults import common_name
from lemur.common.utils import parse_certificate, parse_cert_chain, parse_private_key
from lemur.plugins import lemur_jks as jks
from lemur.plugins.bases import ExportPlugin
def cert_chain_as_der(cert, chain):
"""Return a certificate and its chain in a list format, as expected by pyjks."""
certs = [parse_certificate(cert)]
certs.extend(parse_cert_chain(chain))
# certs (list) A list of certificates, as byte strings. The first one should be the one belonging to the private
# key, the others the chain (in correct order).
return [cert.public_bytes(encoding=serialization.Encoding.DER) for cert in certs]
def create_truststore(cert, chain, alias, passphrase):
entries = []
for idx, cert_bytes in enumerate(cert_chain_as_der(cert, chain)):
# The original cert gets name <ALIAS>_cert, first chain element is <ALIAS>_cert_1, etc.
cert_alias = alias + '_cert' + ('_{}'.format(idx) if idx else '')
entries.append(TrustedCertEntry.new(cert_alias, cert_bytes))
return KeyStore.new('jks', entries).saves(passphrase)
def create_keystore(cert, chain, key, alias, passphrase):
certs_bytes = cert_chain_as_der(cert, chain)
key_bytes = parse_private_key(key).private_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
entry = PrivateKeyEntry.new(alias, certs_bytes, key_bytes)
return KeyStore.new('jks', [entry]).saves(passphrase)
class JavaTruststoreExportPlugin(ExportPlugin):
title = 'Java Truststore (JKS)'
slug = 'java-truststore-jks'
description = 'Generates a JKS truststore'
requires_key = False
version = jks.VERSION
author = 'Marti Raudsepp'
author_url = 'https://github.com/intgr'
options = [
{
'name': 'alias',
'type': 'str',
'required': False,
'helpMessage': 'Enter the alias you wish to use for the truststore.',
},
{
'name': 'passphrase',
'type': 'str',
'required': False,
'helpMessage': 'If no passphrase is given one will be generated for you, we highly recommend this.',
'validation': ''
},
]
def export(self, body, chain, key, options, **kwargs):
"""
Generates a Java Truststore
"""
if self.get_option('alias', options):
alias = self.get_option('alias', options)
else:
alias = common_name(parse_certificate(body))
if self.get_option('passphrase', options):
passphrase = self.get_option('passphrase', options)
else:
passphrase = Fernet.generate_key().decode('utf-8')
raw = create_truststore(body, chain, alias, passphrase)
return 'jks', passphrase, raw
class JavaKeystoreExportPlugin(ExportPlugin):
title = 'Java Keystore (JKS)'
slug = 'java-keystore-jks'
description = 'Generates a JKS keystore'
version = jks.VERSION
author = 'Marti Raudsepp'
author_url = 'https://github.com/intgr'
options = [
{
'name': 'passphrase',
'type': 'str',
'required': False,
'helpMessage': 'If no passphrase is given one will be generated for you, we highly recommend this.',
'validation': ''
},
{
'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
"""
if self.get_option('passphrase', options):
passphrase = self.get_option('passphrase', options)
else:
passphrase = Fernet.generate_key().decode('utf-8')
if self.get_option('alias', options):
alias = self.get_option('alias', options)
else:
alias = common_name(parse_certificate(body))
raw = create_keystore(body, chain, key, alias, passphrase)
return 'jks', passphrase, raw

View File

@ -0,0 +1,96 @@
import pytest
from jks import KeyStore, TrustedCertEntry, PrivateKeyEntry
from lemur.tests.vectors import INTERNAL_CERTIFICATE_A_STR, SAN_CERT_STR, INTERMEDIATE_CERT_STR, ROOTCA_CERT_STR, \
SAN_CERT_KEY
def test_export_truststore(app):
from lemur.plugins.base import plugins
p = plugins.get('java-truststore-jks')
options = [
{'name': 'passphrase', 'value': 'hunter2'},
{'name': 'alias', 'value': 'AzureDiamond'},
]
chain = INTERMEDIATE_CERT_STR + '\n' + ROOTCA_CERT_STR
ext, password, raw = p.export(SAN_CERT_STR, chain, SAN_CERT_KEY, options)
assert ext == 'jks'
assert password == 'hunter2'
assert isinstance(raw, bytes)
ks = KeyStore.loads(raw, 'hunter2')
assert ks.store_type == 'jks'
# JKS lower-cases alias strings
assert ks.entries.keys() == {'azurediamond_cert', 'azurediamond_cert_1', 'azurediamond_cert_2'}
assert isinstance(ks.entries['azurediamond_cert'], TrustedCertEntry)
def test_export_truststore_defaults(app):
from lemur.plugins.base import plugins
p = plugins.get('java-truststore-jks')
options = []
ext, password, raw = p.export(INTERNAL_CERTIFICATE_A_STR, '', '', options)
assert ext == 'jks'
assert isinstance(password, str)
assert isinstance(raw, bytes)
ks = KeyStore.loads(raw, password)
assert ks.store_type == 'jks'
# JKS lower-cases alias strings
assert ks.entries.keys() == {'acommonname_cert'}
assert isinstance(ks.entries['acommonname_cert'], TrustedCertEntry)
def test_export_keystore(app):
from lemur.plugins.base import plugins
p = plugins.get('java-keystore-jks')
options = [
{'name': 'passphrase', 'value': 'hunter2'},
{'name': 'alias', 'value': 'AzureDiamond'},
]
chain = INTERMEDIATE_CERT_STR + '\n' + ROOTCA_CERT_STR
with pytest.raises(Exception):
p.export(INTERNAL_CERTIFICATE_A_STR, chain, '', options)
ext, password, raw = p.export(SAN_CERT_STR, chain, SAN_CERT_KEY, options)
assert ext == 'jks'
assert password == 'hunter2'
assert isinstance(raw, bytes)
ks = KeyStore.loads(raw, password)
assert ks.store_type == 'jks'
# JKS lower-cases alias strings
assert ks.entries.keys() == {'azurediamond'}
entry = ks.entries['azurediamond']
assert isinstance(entry, PrivateKeyEntry)
assert len(entry.cert_chain) == 3 # Cert and chain were provided
def test_export_keystore_defaults(app):
from lemur.plugins.base import plugins
p = plugins.get('java-keystore-jks')
options = []
with pytest.raises(Exception):
p.export(INTERNAL_CERTIFICATE_A_STR, '', '', options)
ext, password, raw = p.export(SAN_CERT_STR, '', SAN_CERT_KEY, options)
assert ext == 'jks'
assert isinstance(password, str)
assert isinstance(raw, bytes)
ks = KeyStore.loads(raw, password)
assert ks.store_type == 'jks'
assert ks.entries.keys() == {'san.example.org'}
entry = ks.entries['san.example.org']
assert isinstance(entry, PrivateKeyEntry)
assert len(entry.cert_chain) == 1 # Only cert itself, no chain was provided

View File

@ -18,7 +18,7 @@ invoke==1.2.0
mccabe==0.6.1 # via flake8
nodeenv==1.3.3
pkginfo==1.5.0.1 # via twine
pre-commit==1.15.1
pre-commit==1.15.2
pycodestyle==2.3.1 # via flake8
pyflakes==1.6.0 # via flake8
pygments==2.3.1 # via readme-renderer
@ -30,7 +30,7 @@ six==1.12.0 # via bleach, cfgv, pre-commit, readme-renderer
toml==0.10.0 # via pre-commit
tqdm==4.31.1 # via twine
twine==1.13.0
urllib3==1.24.1 # via requests
urllib3==1.24.2 # via requests
virtualenv==16.4.3 # via pre-commit
webencodings==0.5.1 # via bleach
zipp==0.3.3 # via importlib-metadata

View File

@ -17,12 +17,12 @@ babel==2.6.0 # via sphinx
bcrypt==3.1.6
billiard==3.6.0.0
blinker==1.4
boto3==1.9.130
botocore==1.12.130
boto3==1.9.134
botocore==1.12.134
celery[redis]==4.3.0
certifi==2019.3.9
certsrv==2.1.1
cffi==1.12.2
cffi==1.12.3
chardet==3.0.4
click==7.0
cloudflare==2.1.0
@ -97,7 +97,7 @@ sphinxcontrib-serializinghtml==1.1.3 # via sphinx
sqlalchemy-utils==0.33.11
sqlalchemy==1.3.3
tabulate==0.8.3
urllib3==1.24.1
urllib3==1.24.2
vine==1.3.0
werkzeug==0.15.2
xmltodict==0.12.0

View File

@ -8,11 +8,11 @@ asn1crypto==0.24.0 # via cryptography
atomicwrites==1.3.0 # via pytest
attrs==19.1.0 # via pytest
aws-xray-sdk==0.95 # via moto
boto3==1.9.130 # via moto
boto3==1.9.134 # via moto
boto==2.49.0 # via moto
botocore==1.12.130 # via boto3, moto, s3transfer
botocore==1.12.134 # via boto3, moto, s3transfer
certifi==2019.3.9 # via requests
cffi==1.12.2 # via cryptography
cffi==1.12.3 # via cryptography
chardet==3.0.4 # via requests
click==7.0 # via flask
coverage==4.5.3
@ -20,7 +20,7 @@ cryptography==2.6.1 # via moto
docker-pycreds==0.4.0 # via docker
docker==3.7.2 # via moto
docutils==0.14 # via botocore
ecdsa==0.13 # via python-jose
ecdsa==0.13.2 # via python-jose
factory-boy==2.11.1
faker==1.0.5
flask==1.0.2 # via pytest-flask
@ -40,12 +40,12 @@ nose==1.3.7
pbr==5.1.3 # via mock
pluggy==0.9.0 # via pytest
py==1.8.0 # via pytest
pyaml==18.11.0 # via moto
pyaml==19.4.1 # via moto
pycparser==2.19 # via cffi
pycryptodome==3.8.1 # via python-jose
pyflakes==2.1.1
pytest-flask==0.14.0
pytest-mock==1.10.3
pytest-mock==1.10.4
pytest==4.4.1
python-dateutil==2.8.0 # via botocore, faker, freezegun, moto
python-jose==2.0.2 # via moto
@ -57,7 +57,7 @@ responses==0.10.6 # via moto
s3transfer==0.2.0 # via boto3
six==1.12.0 # via cryptography, docker, docker-pycreds, faker, freezegun, mock, moto, pytest, python-dateutil, python-jose, requests-mock, responses, websocket-client
text-unidecode==1.2 # via faker
urllib3==1.24.1 # via botocore, requests
urllib3==1.24.2 # via botocore, requests
websocket-client==0.56.0 # via docker
werkzeug==0.15.2 # via flask, moto, pytest-flask
wrapt==1.11.1 # via aws-xray-sdk

View File

@ -47,3 +47,4 @@ SQLAlchemy-Utils
tabulate
xmltodict
pyyaml>=4.2b1 #high severity alert
pyjks

View File

@ -15,12 +15,12 @@ asyncpool==1.0
bcrypt==3.1.6 # via flask-bcrypt, paramiko
billiard==3.6.0.0 # via celery
blinker==1.4 # via flask-mail, flask-principal, raven
boto3==1.9.130
botocore==1.12.130
boto3==1.9.134
botocore==1.12.134
celery[redis]==4.3.0
certifi==2019.3.9
certsrv==2.1.1
cffi==1.12.2 # via bcrypt, cryptography, pynacl
cffi==1.12.3 # via bcrypt, cryptography, pynacl
chardet==3.0.4 # via requests
click==7.0 # via flask
cloudflare==2.1.0
@ -82,7 +82,8 @@ six==1.12.0
sqlalchemy-utils==0.33.11
sqlalchemy==1.3.3 # via alembic, flask-sqlalchemy, marshmallow-sqlalchemy, sqlalchemy-utils
tabulate==0.8.3
urllib3==1.24.1 # via botocore, requests
urllib3==1.24.2 # via botocore, requests
vine==1.3.0 # via amqp, celery
werkzeug==0.15.2 # via flask
xmltodict==0.12.0
pyjks==18.0.0

View File

@ -143,8 +143,8 @@ setup(
'aws_s3 = lemur.plugins.lemur_aws.plugin:S3DestinationPlugin',
'email_notification = lemur.plugins.lemur_email.plugin:EmailNotificationPlugin',
'slack_notification = lemur.plugins.lemur_slack.plugin:SlackNotificationPlugin',
'java_truststore_export = lemur.plugins.lemur_java.plugin:JavaTruststoreExportPlugin',
'java_keystore_export = lemur.plugins.lemur_java.plugin:JavaKeystoreExportPlugin',
'java_truststore_export = lemur.plugins.lemur_jks.plugin:JavaTruststoreExportPlugin',
'java_keystore_export = lemur.plugins.lemur_jks.plugin:JavaKeystoreExportPlugin',
'openssl_export = lemur.plugins.lemur_openssl.plugin:OpenSSLExportPlugin',
'atlas_metric = lemur.plugins.lemur_atlas.plugin:AtlasMetricPlugin',
'kubernetes_destination = lemur.plugins.lemur_kubernetes.plugin:KubernetesDestinationPlugin',