Merge branch 'master' into master

This commit is contained in:
Hossein Shafagh 2020-09-15 12:14:13 -07:00 committed by GitHub
commit 87a85dd3b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 653 additions and 155 deletions

View File

@ -20,6 +20,8 @@ cache:
env: env:
global: global:
- PIP_DOWNLOAD_CACHE=".pip_download_cache" - PIP_DOWNLOAD_CACHE=".pip_download_cache"
# The following line is a temporary workaround for this issue: https://github.com/pypa/setuptools/issues/2230
- SETUPTOOLS_USE_DISTUTILS=stdlib
# do not load /etc/boto.cfg with Python 3 incompatible plugin # do not load /etc/boto.cfg with Python 3 incompatible plugin
# https://github.com/travis-ci/travis-ci/issues/5246#issuecomment-166460882 # https://github.com/travis-ci/travis-ci/issues/5246#issuecomment-166460882
- BOTO_CONFIG=/doesnotexist - BOTO_CONFIG=/doesnotexist

View File

@ -50,8 +50,10 @@ reset-db:
setup-git: setup-git:
@echo "--> Installing git hooks" @echo "--> Installing git hooks"
git config branch.autosetuprebase always if [ -d .git/hooks ]; then \
cd .git/hooks && ln -sf ../../hooks/* ./ git config branch.autosetuprebase always; \
cd .git/hooks && ln -sf ../../hooks/* ./; \
fi
@echo "" @echo ""
clean: clean:

View File

@ -172,15 +172,16 @@ Specifying the `SQLALCHEMY_MAX_OVERFLOW` to 0 will enforce limit to not create c
PUBLIC_CA_MAX_VALIDITY_DAYS = 365 PUBLIC_CA_MAX_VALIDITY_DAYS = 365
.. data:: DEFAULT_MAX_VALIDITY_DAYS .. data:: DEFAULT_VALIDITY_DAYS
:noindex: :noindex:
Use this config to override the default limit of 1095 days (3 years) of validity. Any CA which is not listed in Use this config to override the default validity of 365 days for certificates offered through Lemur UI. Any CA which
PUBLIC_CA_AUTHORITY_NAMES will be using this validity to display date range on UI. Below example overrides the is not listed in PUBLIC_CA_AUTHORITY_NAMES will be using this value as default validity to be displayed on UI. Please
default validity of 1095 days and sets it to 365 days. note that this config is used for cert issuance only through Lemur UI. Below example overrides the default validity
of 365 days and sets it to 1095 days (3 years).
:: ::
DEFAULT_MAX_VALIDITY_DAYS = 365 DEFAULT_VALIDITY_DAYS = 1095
.. data:: DEBUG_DUMP .. data:: DEBUG_DUMP
@ -652,13 +653,20 @@ Active Directory Certificate Services Plugin
:noindex: :noindex:
Template to be used for certificate issuing. Usually display name w/o spaces Template to be used for certificate issuing. Usually display name w/o spaces
.. data:: ADCS_TEMPLATE_<upper(authority.name)>
:noindex:
If there is a config variable ADCS_TEMPLATE_<upper(authority.name)> take the value as Cert template else default to ADCS_TEMPLATE to be compatible with former versions. Template to be used for certificate issuing. Usually display name w/o spaces
.. data:: ADCS_START .. data:: ADCS_START
:noindex: :noindex:
Used in ADCS-Sourceplugin. Minimum id of the first certificate to be returned. ID is increased by one until ADCS_STOP. Missing cert-IDs are ignored
.. data:: ADCS_STOP .. data:: ADCS_STOP
:noindex: :noindex:
Used for ADCS-Sourceplugin. Maximum id of the certificates returned.
.. data:: ADCS_ISSUING .. data:: ADCS_ISSUING
:noindex: :noindex:
@ -671,6 +679,68 @@ Active Directory Certificate Services Plugin
Contains the root cert of the CA Contains the root cert of the CA
Entrust Plugin
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Enables the creation of Entrust certificates. You need to set the API access up with Entrust support. Check the information in the Entrust Portal as well.
Certificates are created as "SERVER_AND_CLIENT_AUTH".
Caution: Sometimes the entrust API does not respond in a timely manner. This error is handled and reported by the plugin. Should this happen you just have to hit the create button again after to create a valid certificate.
The following parameters have to be set in the configuration files.
.. data:: ENTRUST_URL
:noindex:
This is the url for the Entrust API. Refer to the API documentation.
.. data:: ENTRUST_API_CERT
:noindex:
Path to the certificate file in PEM format. This certificate is created in the onboarding process. Refer to the API documentation.
.. data:: ENTRUST_API_KEY
:noindex:
Path to the key file in RSA format. This certificate is created in the onboarding process. Refer to the API documentation. Caution: the request library cannot handle encrypted keys. The keyfile therefore has to contain the unencrypted key. Please put this in a secure location on the server.
.. data:: ENTRUST_API_USER
:noindex:
String with the API user. This user is created in the onboarding process. Refer to the API documentation.
.. data:: ENTRUST_API_PASS
:noindex:
String with the password for the API user. This password is created in the onboarding process. Refer to the API documentation.
.. data:: ENTRUST_NAME
:noindex:
String with the name that should appear as certificate owner in the Entrust portal. Refer to the API documentation.
.. data:: ENTRUST_EMAIL
:noindex:
String with the email address that should appear as certificate contact email in the Entrust portal. Refer to the API documentation.
.. data:: ENTRUST_PHONE
:noindex:
String with the phone number that should appear as certificate contact in the Entrust portal. Refer to the API documentation.
.. data:: ENTRUST_ISSUING
:noindex:
Contains the issuing cert of the CA
.. data:: ENTRUST_ROOT
:noindex:
Contains the root cert of the CA
.. data:: ENTRUST_PRODUCT_<upper(authority.name)>
:noindex:
If there is a config variable ENTRUST_PRODUCT_<upper(authority.name)> take the value as cert product name else default to "STANDARD_SSL". Refer to the API documentation for valid products names.
Verisign Issuer Plugin Verisign Issuer Plugin
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~

View File

@ -23,6 +23,7 @@ from lemur.common.schema import LemurInputSchema, LemurOutputSchema
from lemur.common import validators, missing from lemur.common import validators, missing
from lemur.common.fields import ArrowDateTime from lemur.common.fields import ArrowDateTime
from lemur.constants import CERTIFICATE_KEY_TYPES
class AuthorityInputSchema(LemurInputSchema): class AuthorityInputSchema(LemurInputSchema):
@ -56,11 +57,12 @@ class AuthorityInputSchema(LemurInputSchema):
type = fields.String(validate=validate.OneOf(["root", "subca"]), missing="root") type = fields.String(validate=validate.OneOf(["root", "subca"]), missing="root")
parent = fields.Nested(AssociatedAuthoritySchema) parent = fields.Nested(AssociatedAuthoritySchema)
signing_algorithm = fields.String( signing_algorithm = fields.String(
validate=validate.OneOf(["sha256WithRSA", "sha1WithRSA"]), validate=validate.OneOf(["sha256WithRSA", "sha1WithRSA",
"sha256WithECDSA", "SHA384withECDSA", "SHA512withECDSA"]),
missing="sha256WithRSA", missing="sha256WithRSA",
) )
key_type = fields.String( key_type = fields.String(
validate=validate.OneOf(["RSA2048", "RSA4096"]), missing="RSA2048" validate=validate.OneOf(CERTIFICATE_KEY_TYPES), missing="RSA2048"
) )
key_name = fields.String() key_name = fields.String()
sensitivity = fields.String( sensitivity = fields.String(
@ -110,6 +112,7 @@ class RootAuthorityCertificateOutputSchema(LemurOutputSchema):
not_after = fields.DateTime() not_after = fields.DateTime()
not_before = fields.DateTime() not_before = fields.DateTime()
max_issuance_days = fields.Integer() max_issuance_days = fields.Integer()
default_validity_days = fields.Integer()
owner = fields.Email() owner = fields.Email()
status = fields.Boolean() status = fields.Boolean()
user = fields.Nested(UserNestedOutputSchema) user = fields.Nested(UserNestedOutputSchema)
@ -135,7 +138,7 @@ class AuthorityNestedOutputSchema(LemurOutputSchema):
owner = fields.Email() owner = fields.Email()
plugin = fields.Nested(PluginOutputSchema) plugin = fields.Nested(PluginOutputSchema)
active = fields.Boolean() active = fields.Boolean()
authority_certificate = fields.Nested(RootAuthorityCertificateOutputSchema, only=["max_issuance_days"]) authority_certificate = fields.Nested(RootAuthorityCertificateOutputSchema, only=["max_issuance_days", "default_validity_days"])
authority_update_schema = AuthorityUpdateSchema() authority_update_schema = AuthorityUpdateSchema()

View File

@ -9,9 +9,10 @@ from datetime import timedelta
import arrow import arrow
from cryptography import x509 from cryptography import x509
from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.asymmetric import rsa, ec
from flask import current_app from flask import current_app
from idna.core import InvalidCodepoint from idna.core import InvalidCodepoint
from lemur.common.utils import get_key_type_from_ec_curve
from sqlalchemy import ( from sqlalchemy import (
event, event,
Integer, Integer,
@ -302,6 +303,8 @@ class Certificate(db.Model):
return "RSA{key_size}".format( return "RSA{key_size}".format(
key_size=self.parsed_cert.public_key().key_size key_size=self.parsed_cert.public_key().key_size
) )
elif isinstance(self.parsed_cert.public_key(), ec.EllipticCurvePublicKey):
return get_key_type_from_ec_curve(self.parsed_cert.public_key().curve.name)
@property @property
def validity_remaining(self): def validity_remaining(self):
@ -317,7 +320,13 @@ class Certificate(db.Model):
if self.name.lower() in [ca.lower() for ca in public_CA]: if self.name.lower() in [ca.lower() for ca in public_CA]:
return current_app.config.get("PUBLIC_CA_MAX_VALIDITY_DAYS", 397) return current_app.config.get("PUBLIC_CA_MAX_VALIDITY_DAYS", 397)
return current_app.config.get("DEFAULT_MAX_VALIDITY_DAYS", 1095) # 3 years default @property
def default_validity_days(self):
public_CA = current_app.config.get("PUBLIC_CA_AUTHORITY_NAMES", [])
if self.name.lower() in [ca.lower() for ca in public_CA]:
return current_app.config.get("PUBLIC_CA_MAX_VALIDITY_DAYS", 397)
return current_app.config.get("DEFAULT_VALIDITY_DAYS", 365) # 1 year default
@property @property
def subject(self): def subject(self):

View File

@ -148,6 +148,13 @@ class CertificateInputSchema(CertificateCreationSchema):
data["extensions"]["subAltNames"]["names"] = [] data["extensions"]["subAltNames"]["names"] = []
data["extensions"]["subAltNames"]["names"] = csr_sans data["extensions"]["subAltNames"]["names"] = csr_sans
common_name = cert_utils.get_cn_from_csr(data["csr"])
if common_name:
data["common_name"] = common_name
key_type = cert_utils.get_key_type_from_csr(data["csr"])
if key_type:
data["key_type"] = key_type
return missing.convert_validity_years(data) return missing.convert_validity_years(data)

View File

@ -12,6 +12,8 @@ Utils to parse certificate data.
from cryptography import x509 from cryptography import x509
from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends import default_backend
from marshmallow.exceptions import ValidationError from marshmallow.exceptions import ValidationError
from cryptography.hazmat.primitives.asymmetric import rsa, ec
from lemur.common.utils import get_key_type_from_ec_curve
def get_sans_from_csr(data): def get_sans_from_csr(data):
@ -39,3 +41,45 @@ def get_sans_from_csr(data):
pass pass
return sub_alt_names return sub_alt_names
def get_cn_from_csr(data):
"""
Fetches common name (CN) from CSR.
Works with any kind of SubjectAlternativeName
:param data: PEM-encoded string with CSR
:return: the common name
"""
try:
request = x509.load_pem_x509_csr(data.encode("utf-8"), default_backend())
except Exception:
raise ValidationError("CSR presented is not valid.")
common_name = request.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)
return common_name[0].value
def get_key_type_from_csr(data):
"""
Fetches key_type from CSR.
Works with any kind of SubjectAlternativeName
:param data: PEM-encoded string with CSR
:return: key_type
"""
try:
request = x509.load_pem_x509_csr(data.encode("utf-8"), default_backend())
except Exception:
raise ValidationError("CSR presented is not valid.")
try:
if isinstance(request.public_key(), rsa.RSAPublicKey):
return "RSA{key_size}".format(
key_size=request.public_key().key_size
)
elif isinstance(request.public_key(), ec.EllipticCurvePublicKey):
return get_key_type_from_ec_curve(request.public_key().curve.name)
else:
raise Exception("Unsupported key type")
except NotImplemented:
raise NotImplemented()

View File

@ -114,6 +114,39 @@ def get_authority_key(body):
return authority_key.hex() return authority_key.hex()
def get_key_type_from_ec_curve(curve_name):
"""
Give an EC curve name, return the matching key_type.
:param: curve_name
:return: key_type
"""
_CURVE_TYPES = {
ec.SECP192R1().name: "ECCPRIME192V1",
ec.SECP256R1().name: "ECCPRIME256V1",
ec.SECP224R1().name: "ECCSECP224R1",
ec.SECP384R1().name: "ECCSECP384R1",
ec.SECP521R1().name: "ECCSECP521R1",
ec.SECP256K1().name: "ECCSECP256K1",
ec.SECT163K1().name: "ECCSECT163K1",
ec.SECT233K1().name: "ECCSECT233K1",
ec.SECT283K1().name: "ECCSECT283K1",
ec.SECT409K1().name: "ECCSECT409K1",
ec.SECT571K1().name: "ECCSECT571K1",
ec.SECT163R2().name: "ECCSECT163R2",
ec.SECT233R1().name: "ECCSECT233R1",
ec.SECT283R1().name: "ECCSECT283R1",
ec.SECT409R1().name: "ECCSECT409R1",
ec.SECT571R1().name: "ECCSECT571R2",
}
if curve_name in _CURVE_TYPES.keys():
return _CURVE_TYPES[curve_name]
else:
return None
def generate_private_key(key_type): def generate_private_key(key_type):
""" """
Generates a new private key based on key_type. Generates a new private key based on key_type.
@ -128,11 +161,11 @@ def generate_private_key(key_type):
""" """
_CURVE_TYPES = { _CURVE_TYPES = {
"ECCPRIME192V1": ec.SECP192R1(), "ECCPRIME192V1": ec.SECP192R1(), # duplicate
"ECCPRIME256V1": ec.SECP256R1(), "ECCPRIME256V1": ec.SECP256R1(), # duplicate
"ECCSECP192R1": ec.SECP192R1(), "ECCSECP192R1": ec.SECP192R1(), # duplicate
"ECCSECP224R1": ec.SECP224R1(), "ECCSECP224R1": ec.SECP224R1(),
"ECCSECP256R1": ec.SECP256R1(), "ECCSECP256R1": ec.SECP256R1(), # duplicate
"ECCSECP384R1": ec.SECP384R1(), "ECCSECP384R1": ec.SECP384R1(),
"ECCSECP521R1": ec.SECP521R1(), "ECCSECP521R1": ec.SECP521R1(),
"ECCSECP256K1": ec.SECP256K1(), "ECCSECP256K1": ec.SECP256K1(),

View File

@ -152,18 +152,6 @@ def dates(data):
data["authority"].authority_certificate.not_after data["authority"].authority_certificate.not_after
) )
) )
# Allow no more than PUBLIC_CA_MAX_VALIDITY_DAYS (Default: 397) days of validity
# for certs issued by public CA
# The list of public issuers can be managed through a config named PUBLIC_CA
public_CA = current_app.config.get("PUBLIC_CA_AUTHORITY_NAMES", [])
if data["authority"].name.lower() in [ca.lower() for ca in public_CA]:
max_validity_days = current_app.config.get("PUBLIC_CA_MAX_VALIDITY_DAYS", 397)
if (
(data.get("validity_end").date() - data.get("validity_start").date()).days
> max_validity_days
):
raise ValidationError("Certificate cannot be valid for more than " +
str(max_validity_days) + " days")
return data return data

View File

@ -40,7 +40,10 @@ class ADCSIssuerPlugin(IssuerPlugin):
adcs_user = current_app.config.get("ADCS_USER") adcs_user = current_app.config.get("ADCS_USER")
adcs_pwd = current_app.config.get("ADCS_PWD") adcs_pwd = current_app.config.get("ADCS_PWD")
adcs_auth_method = current_app.config.get("ADCS_AUTH_METHOD") adcs_auth_method = current_app.config.get("ADCS_AUTH_METHOD")
adcs_template = current_app.config.get("ADCS_TEMPLATE") # if there is a config variable ADCS_TEMPLATE_<upper(authority.name)> take the value as Cert template
# else default to ADCS_TEMPLATE to be compatible with former versions
authority = issuer_options.get("authority").name.upper()
adcs_template = current_app.config.get("ADCS_TEMPLATE_{0}".format(authority), current_app.config.get("ADCS_TEMPLATE"))
ca_server = Certsrv( ca_server = Certsrv(
adcs_server, adcs_user, adcs_pwd, auth_method=adcs_auth_method adcs_server, adcs_user, adcs_pwd, auth_method=adcs_auth_method
) )

View File

@ -18,8 +18,9 @@ import json
import arrow import arrow
import pem import pem
import requests import requests
import sys
from cryptography import x509 from cryptography import x509
from flask import current_app from flask import current_app, g
from lemur.common.utils import validate_conf from lemur.common.utils import validate_conf
from lemur.extensions import metrics from lemur.extensions import metrics
from lemur.plugins import lemur_digicert as digicert from lemur.plugins import lemur_digicert as digicert
@ -129,6 +130,9 @@ def map_fields(options, csr):
data["validity_years"] = determine_validity_years(options.get("validity_years")) data["validity_years"] = determine_validity_years(options.get("validity_years"))
elif options.get("validity_end"): elif options.get("validity_end"):
data["custom_expiration_date"] = determine_end_date(options.get("validity_end")).format("YYYY-MM-DD") data["custom_expiration_date"] = determine_end_date(options.get("validity_end")).format("YYYY-MM-DD")
# check if validity got truncated. If resultant validity is not equal to requested validity, it just got truncated
if data["custom_expiration_date"] != options.get("validity_end").format("YYYY-MM-DD"):
log_validity_truncation(options, f"{__name__}.{sys._getframe().f_code.co_name}")
else: else:
data["validity_years"] = determine_validity_years(0) data["validity_years"] = determine_validity_years(0)
@ -154,6 +158,9 @@ def map_cis_fields(options, csr):
validity_end = determine_end_date(arrow.utcnow().shift(years=options["validity_years"])) validity_end = determine_end_date(arrow.utcnow().shift(years=options["validity_years"]))
elif options.get("validity_end"): elif options.get("validity_end"):
validity_end = determine_end_date(options.get("validity_end")) validity_end = determine_end_date(options.get("validity_end"))
# check if validity got truncated. If resultant validity is not equal to requested validity, it just got truncated
if validity_end != options.get("validity_end"):
log_validity_truncation(options, f"{__name__}.{sys._getframe().f_code.co_name}")
else: else:
validity_end = determine_end_date(False) validity_end = determine_end_date(False)
@ -179,6 +186,18 @@ def map_cis_fields(options, csr):
return data return data
def log_validity_truncation(options, function):
log_data = {
"cn": options["common_name"],
"creator": g.user.username
}
metrics.send("digicert_validity_truncated", "counter", 1, metric_tags=log_data)
log_data["function"] = function
log_data["message"] = "Digicert Plugin truncated the validity of certificate"
current_app.logger.info(log_data)
def handle_response(response): def handle_response(response):
""" """
Handle the DigiCert API response and any errors it might have experienced. Handle the DigiCert API response and any errors it might have experienced.

View File

@ -0,0 +1,5 @@
"""Set the version information."""
try:
VERSION = __import__("pkg_resources").get_distribution(__name__).version
except Exception as e:
VERSION = "unknown"

View File

@ -0,0 +1,228 @@
from lemur.plugins.bases import IssuerPlugin, SourcePlugin
import arrow
import requests
import json
from lemur.plugins import lemur_entrust as ENTRUST
from flask import current_app
from lemur.extensions import metrics
from lemur.common.utils import validate_conf
def log_status_code(r, *args, **kwargs):
"""
Is a request hook that logs all status codes to the ENTRUST api.
:param r:
:param args:
:param kwargs:
:return:
"""
metrics.send("ENTRUST_status_code_{}".format(r.status_code), "counter", 1)
def determine_end_date(end_date):
"""
Determine appropriate end date
:param end_date:
:return: validity_end
"""
# ENTRUST only allows 13 months of max certificate duration
max_validity_end = arrow.utcnow().shift(years=1, months=+1).format('YYYY-MM-DD')
if not end_date:
end_date = max_validity_end
if end_date > max_validity_end:
end_date = max_validity_end
return end_date
def process_options(options):
"""
Processes and maps the incoming issuer options to fields/options that
Entrust understands
:param options:
:return: dict of valid entrust options
"""
# if there is a config variable ENTRUST_PRODUCT_<upper(authority.name)>
# take the value as Cert product-type
# else default to "STANDARD_SSL"
authority = options.get("authority").name.upper()
product_type = current_app.config.get("ENTRUST_PRODUCT_{0}".format(authority), "STANDARD_SSL")
if options.get("validity_end"):
validity_end = determine_end_date(options.get("validity_end"))
else:
validity_end = determine_end_date(False)
tracking_data = {
"requesterName": current_app.config.get("ENTRUST_NAME"),
"requesterEmail": current_app.config.get("ENTRUST_EMAIL"),
"requesterPhone": current_app.config.get("ENTRUST_PHONE")
}
data = {
"signingAlg": "SHA-2",
"eku": "SERVER_AND_CLIENT_AUTH",
"certType": product_type,
"certExpiryDate": validity_end,
"tracking": tracking_data
}
return data
def handle_response(my_response):
"""
Helper function for parsing responses from the Entrust API.
:param content:
:return: :raise Exception:
"""
msg = {
200: "The request had the validateOnly flag set to true and validation was successful.",
201: "Certificate created",
202: "Request accepted and queued for approval",
400: "Invalid request parameters",
404: "Unknown jobId",
429: "Too many requests"
}
try:
d = json.loads(my_response.content)
except Exception as e:
# catch an empty jason object here
d = {'errors': 'No detailled message'}
s = my_response.status_code
if s > 399:
raise Exception("ENTRUST error: {0}\n{1}".format(msg.get(s, s), d['errors']))
current_app.logger.info("Response: {0}, {1} ".format(s, d))
return d
class EntrustIssuerPlugin(IssuerPlugin):
title = "ENTRUST"
slug = "entrust-issuer"
description = "Enables the creation of certificates by ENTRUST"
version = ENTRUST.VERSION
author = "sirferl"
author_url = "https://github.com/sirferl/lemur"
def __init__(self, *args, **kwargs):
"""Initialize the issuer with the appropriate details."""
required_vars = [
"ENTRUST_API_CERT",
"ENTRUST_API_KEY",
"ENTRUST_API_USER",
"ENTRUST_API_PASS",
"ENTRUST_URL",
"ENTRUST_ROOT",
"ENTRUST_NAME",
"ENTRUST_EMAIL",
"ENTRUST_PHONE",
"ENTRUST_ISSUING",
]
validate_conf(current_app, required_vars)
self.session = requests.Session()
cert_file = current_app.config.get("ENTRUST_API_CERT")
key_file = current_app.config.get("ENTRUST_API_KEY")
user = current_app.config.get("ENTRUST_API_USER")
password = current_app.config.get("ENTRUST_API_PASS")
self.session.cert = (cert_file, key_file)
self.session.auth = (user, password)
self.session.hooks = dict(response=log_status_code)
# self.session.config['keep_alive'] = False
super(EntrustIssuerPlugin, self).__init__(*args, **kwargs)
def create_certificate(self, csr, issuer_options):
"""
Creates an Entrust certificate.
:param csr:
:param issuer_options:
:return: :raise Exception:
"""
current_app.logger.info(
"Requesting options: {0}".format(issuer_options)
)
url = current_app.config.get("ENTRUST_URL") + "/certificates"
data = process_options(issuer_options)
data["csr"] = csr
try:
response = self.session.post(url, json=data, timeout=(15, 40))
except requests.exceptions.Timeout:
raise Exception("Timeout for POST")
except requests.exceptions.RequestException as e:
raise Exception("Error for POST {0}".format(e))
response_dict = handle_response(response)
external_id = response_dict['trackingId']
cert = response_dict['endEntityCert']
chain = response_dict['chainCerts'][1]
current_app.logger.info(
"Received Chain: {0}".format(chain)
)
return cert, chain, external_id
def revoke_certificate(self, certificate, comments):
"""Revoke a Digicert certificate."""
base_url = current_app.config.get("ENTRUST_URL")
# make certificate revoke request
revoke_url = "{0}/certificates/{1}/revocations".format(
base_url, certificate.external_id
)
metrics.send("entrust_revoke_certificate", "counter", 1)
if comments == '' or not comments:
comments = "revoked via API"
data = {
"crlReason": "superseded",
"revocationComment": comments
}
response = self.session.post(revoke_url, json=data)
data = handle_response(response)
@staticmethod
def create_authority(options):
"""Create an authority.
Creates an authority, this authority is then used by Lemur to
allow a user to specify which Certificate Authority they want
to sign their certificate.
:param options:
:return:
"""
entrust_root = current_app.config.get("ENTRUST_ROOT")
entrust_issuing = current_app.config.get("ENTRUST_ISSUING")
role = {"username": "", "password": "", "name": "entrust"}
current_app.logger.info("Creating Auth: {0} {1}".format(options, entrust_issuing))
return entrust_root, "", [role]
def get_ordered_certificate(self, order_id):
raise NotImplementedError("Not implemented\n", self, order_id)
def canceled_ordered_certificate(self, pending_cert, **kwargs):
raise NotImplementedError("Not implemented\n", self, pending_cert, **kwargs)
class EntrustSourcePlugin(SourcePlugin):
title = "ENTRUST"
slug = "entrust-source"
description = "Enables the collecion of certificates"
version = ENTRUST.VERSION
author = "sirferl"
author_url = "https://github.com/sirferl/lemur"
def get_certificates(self, options, **kwargs):
# Not needed for ENTRUST
raise NotImplementedError("Not implemented\n", self, options, **kwargs)
def get_endpoints(self, options, **kwargs):
# There are no endpoints in ENTRUST
raise NotImplementedError("Not implemented\n", self, options, **kwargs)

View File

@ -4,7 +4,7 @@
Signing Algorithm Signing Algorithm
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<select class="form-control" ng-model="authority.signingAlgorithm" ng-options="option for option in ['sha1WithRSA', 'sha256WithRSA']" ng-init="authority.signingAlgorithm = 'sha256WithRSA'"></select> <select class="form-control" ng-model="authority.signingAlgorithm" ng-options="option for option in ['sha1WithRSA', 'sha256WithRSA', 'sha256WithECDSA', 'SHA384withECDSA', 'SHA512withECDSA']" ng-init="authority.signingAlgorithm = 'sha256WithRSA'"></select>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">

View File

@ -107,7 +107,6 @@ angular.module('lemur')
startingDay: 1 startingDay: 1
}; };
$scope.open1 = function() { $scope.open1 = function() {
$scope.popup1.opened = true; $scope.popup1.opened = true;
}; };
@ -140,6 +139,14 @@ angular.module('lemur')
); );
$scope.create = function (certificate) { $scope.create = function (certificate) {
if(certificate.validityType === 'customDates' &&
(!certificate.validityStart || !certificate.validityEnd)) { // these are not mandatory fields in schema, thus handling validation in js
return showMissingDateError();
}
if(certificate.validityType === 'defaultDays') {
populateValidityDateAsPerDefault(certificate);
}
WizardHandler.wizard().context.loading = true; WizardHandler.wizard().context.loading = true;
CertificateService.create(certificate).then( CertificateService.create(certificate).then(
function () { function () {
@ -164,6 +171,30 @@ angular.module('lemur')
}); });
}; };
function showMissingDateError() {
let error = {};
error.message = '';
error.reasons = {};
error.reasons.validityRange = 'Valid start and end dates are needed, else select Default option';
toaster.pop({
type: 'error',
title: 'Validation Error',
body: 'lemur-bad-request',
bodyOutputType: 'directive',
directiveData: error,
timeout: 100000
});
}
function populateValidityDateAsPerDefault(certificate) {
// calculate start and end date as per default validity
let startDate = new Date(), endDate = new Date();
endDate.setDate(startDate.getDate() + certificate.authority.authorityCertificate.defaultValidityDays);
certificate.validityStart = startDate;
certificate.validityEnd = endDate;
}
$scope.templates = [ $scope.templates = [
{ {
'name': 'Client Certificate', 'name': 'Client Certificate',
@ -277,6 +308,14 @@ angular.module('lemur')
}; };
$scope.create = function (certificate) { $scope.create = function (certificate) {
if(certificate.validityType === 'customDates' &&
(!certificate.validityStart || !certificate.validityEnd)) { // these are not mandatory fields in schema, thus handling validation in js
return showMissingDateError();
}
if(certificate.validityType === 'defaultDays') {
populateValidityDateAsPerDefault(certificate);
}
WizardHandler.wizard().context.loading = true; WizardHandler.wizard().context.loading = true;
CertificateService.create(certificate).then( CertificateService.create(certificate).then(
function () { function () {
@ -301,6 +340,30 @@ angular.module('lemur')
}); });
}; };
function showMissingDateError() {
let error = {};
error.message = '';
error.reasons = {};
error.reasons.validityRange = 'Valid start and end dates are needed, else select Default option';
toaster.pop({
type: 'error',
title: 'Validation Error',
body: 'lemur-bad-request',
bodyOutputType: 'directive',
directiveData: error,
timeout: 100000
});
}
function populateValidityDateAsPerDefault(certificate) {
// calculate start and end date as per default validity
let startDate = new Date(), endDate = new Date();
endDate.setDate(startDate.getDate() + certificate.authority.authorityCertificate.defaultValidityDays);
certificate.validityStart = startDate;
certificate.validityEnd = endDate;
}
$scope.templates = [ $scope.templates = [
{ {
'name': 'Client Certificate', 'name': 'Client Certificate',

View File

@ -20,7 +20,7 @@
name="certificate signing request" name="certificate signing request"
ng-model="certificate.csr" ng-model="certificate.csr"
placeholder="PEM encoded string..." class="form-control" placeholder="PEM encoded string..." class="form-control"
ng-pattern="/^-----BEGIN CERTIFICATE REQUEST-----/"></textarea> ng-pattern="/(^-----BEGIN CERTIFICATE REQUEST-----[\S\s]*-----END CERTIFICATE REQUEST-----)|(^-----BEGIN NEW CERTIFICATE REQUEST-----[\S\s]*-----END NEW CERTIFICATE REQUEST-----)/"></textarea>
<p ng-show="trackingForm.csr.$invalid && !trackingForm.csr.$pristine" <p ng-show="trackingForm.csr.$invalid && !trackingForm.csr.$pristine"
class="help-block">Enter a valid certificate signing request.</p> class="help-block">Enter a valid certificate signing request.</p>

View File

@ -96,7 +96,7 @@
Certificate Authority Certificate Authority
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<ui-select class="input-md" ng-model="certificate.authority" theme="bootstrap" title="choose an authority"> <ui-select class="input-md" ng-model="certificate.authority" theme="bootstrap" title="choose an authority" ng-change="clearDates()">
<ui-select-match placeholder="select an authority...">{{$select.selected.name}}</ui-select-match> <ui-select-match placeholder="select an authority...">{{$select.selected.name}}</ui-select-match>
<ui-select-choices class="form-control" repeat="authority in authorities" <ui-select-choices class="form-control" repeat="authority in authorities"
refresh="getAuthoritiesByName($select.search)" refresh="getAuthoritiesByName($select.search)"
@ -133,22 +133,20 @@
</div> </div>
<div class="form-group" ng-hide="certificate.authority.plugin.slug == 'acme-issuer'"> <div class="form-group" ng-hide="certificate.authority.plugin.slug == 'acme-issuer'">
<label class="control-label col-sm-2" <label class="control-label col-sm-2"
uib-tooltip="If no date is selected Lemur attempts to issue a 1 year certificate"> uib-tooltip="You can select custom date range; however, we recommend continuing with default validity.">
Validity Range <span class="glyphicon glyphicon-question-sign"></span> Validity Range <span class="glyphicon glyphicon-question-sign"></span>
</label> </label>
<div class="col-sm-2"> <div class="col-sm-4">
<select ng-model="certificate.validityYears" class="form-control"> <div class="btn-group btn-group-toggle" data-toggle="buttons">
<option value="">-</option> <label class="btn btn-info" ng-model="certificate.validityType" uib-btn-radio="'defaultDays'" ng-click="clearDates()">
<option value="1">1 year</option> Default ({{certificate.authority.authorityCertificate.defaultValidityDays}} days)</label>
</select> <label class="btn btn-info" ng-model="certificate.validityType" uib-btn-radio="'customDates'" ng-change="clearDates()">Custom</label>
</div>
</div> </div>
<span style="padding-top: 15px" class="text-center col-sm-1"> <div class="col-sm-3" ng-if="certificate.validityType==='customDates'">
<strong>or</strong>
</span>
<div class="col-sm-3">
<div class="input-group"> <div class="input-group">
<input type="text" class="form-control" <input type="text" class="form-control"
uib-tooltip="yyyy/MM/dd" uib-tooltip="Start Date (yyyy/MM/dd)"
uib-datepicker-popup="yyyy/MM/dd" uib-datepicker-popup="yyyy/MM/dd"
ng-model="certificate.validityStart" ng-model="certificate.validityStart"
ng-change="certificate.setValidityEndDateRange(certificate.validityStart)" ng-change="certificate.setValidityEndDateRange(certificate.validityStart)"
@ -159,6 +157,7 @@
min-date="certificate.authority.authorityCertificate.notBefore" min-date="certificate.authority.authorityCertificate.notBefore"
alt-input-formats="altInputFormats" alt-input-formats="altInputFormats"
placeholder="Start Date" placeholder="Start Date"
readonly="true"
/> />
<span class="input-group-btn"> <span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open1()"><i <button type="button" class="btn btn-default" ng-click="open1()"><i
@ -166,10 +165,10 @@
</span> </span>
</div> </div>
</div> </div>
<div class="col-sm-3"> <div class="col-sm-3" ng-if="certificate.validityType==='customDates'">
<div class="input-group"> <div class="input-group">
<input type="text" class="form-control" <input type="text" class="form-control"
uib-tooltip="yyyy/MM/dd" uib-tooltip="End Date (yyyy/MM/dd)"
uib-datepicker-popup="yyyy/MM/dd" uib-datepicker-popup="yyyy/MM/dd"
ng-model="certificate.validityEnd" ng-model="certificate.validityEnd"
is-open="popup2.opened" is-open="popup2.opened"
@ -179,6 +178,7 @@
min-date="certificate.authority.authorityCertificate.minValidityEnd" min-date="certificate.authority.authorityCertificate.minValidityEnd"
alt-input-formats="altInputFormats" alt-input-formats="altInputFormats"
placeholder="End Date" placeholder="End Date"
readonly="true"
/> />
<span class="input-group-btn"> <span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open2()"><i <button type="button" class="btn btn-default" ng-click="open2()"><i
@ -186,10 +186,6 @@
</span> </span>
</div> </div>
</div> </div>
<div class="col-sm-1">
<button uib-tooltip="Clear Validity" ng-click="clearDates()" class="btn btn-default"><i
class="glyphicon glyphicon-remove"></i></button>
</div>
</div> </div>
<div class="form-group" ng-show="certificate.authority.plugin.slug == 'acme-issuer'"> <div class="form-group" ng-show="certificate.authority.plugin.slug == 'acme-issuer'">
<label class="control-label col-sm-2"> <label class="control-label col-sm-2">

View File

@ -167,17 +167,19 @@ angular.module('lemur')
}, },
setValidityEndDateRange: function (value) { setValidityEndDateRange: function (value) {
// clear selected validity end date as we are about to calculate new range // clear selected validity end date as we are about to calculate new range
if(this.validityEnd) { this.validityEnd = '';
this.validityEnd = '';
}
// Minimum end date will be same as selected start date // Minimum end date will be same as selected start date
this.authority.authorityCertificate.minValidityEnd = value; this.authority.authorityCertificate.minValidityEnd = value;
// Move max end date by maxIssuanceDays if(!this.authority.authorityCertificate || !this.authority.authorityCertificate.maxIssuanceDays) {
let endDate = new Date(value); this.authority.authorityCertificate.maxValidityEnd = this.authority.authorityCertificate.notAfter;
endDate.setDate(endDate.getDate() + this.authority.authorityCertificate.maxIssuanceDays); } else {
this.authority.authorityCertificate.maxValidityEnd = endDate; // Move max end date by maxIssuanceDays
let endDate = new Date(value);
endDate.setDate(endDate.getDate() + this.authority.authorityCertificate.maxIssuanceDays);
this.authority.authorityCertificate.maxValidityEnd = endDate;
}
} }
}); });
}); });
@ -195,7 +197,7 @@ angular.module('lemur')
CertificateService.create = function (certificate) { CertificateService.create = function (certificate) {
certificate.attachSubAltName(); certificate.attachSubAltName();
certificate.attachCustom(); certificate.attachCustom();
if (certificate.validityYears === '') { // if a user de-selects validity years we ignore it if (certificate.validityYears === '') { // if a user de-selects validity years we ignore it - might not be needed anymore
delete certificate.validityYears; delete certificate.validityYears;
} }
return CertificateApi.post(certificate); return CertificateApi.post(certificate);
@ -281,6 +283,9 @@ angular.module('lemur')
certificate.authority.authorityCertificate.minValidityEnd = defaults.authority.authorityCertificate.notBefore; certificate.authority.authorityCertificate.minValidityEnd = defaults.authority.authorityCertificate.notBefore;
certificate.authority.authorityCertificate.maxValidityEnd = defaults.authority.authorityCertificate.notAfter; certificate.authority.authorityCertificate.maxValidityEnd = defaults.authority.authorityCertificate.notAfter;
// pre-select validity type radio button to default days
certificate.validityType = 'defaultDays';
if (certificate.dnsProviderId) { if (certificate.dnsProviderId) {
certificate.dnsProvider = {id: certificate.dnsProviderId}; certificate.dnsProvider = {id: certificate.dnsProviderId};
} }

View File

@ -147,17 +147,19 @@ angular.module('lemur')
}, },
setValidityEndDateRange: function (value) { setValidityEndDateRange: function (value) {
// clear selected validity end date as we are about to calculate new range // clear selected validity end date as we are about to calculate new range
if(this.validityEnd) { this.validityEnd = '';
this.validityEnd = '';
}
// Minimum end date will be same as selected start date // Minimum end date will be same as selected start date
this.authority.authorityCertificate.minValidityEnd = value; this.authority.authorityCertificate.minValidityEnd = value;
// Move max end date by maxIssuanceDays if(!this.authority.authorityCertificate || !this.authority.authorityCertificate.maxIssuanceDays) {
let endDate = new Date(value); this.authority.authorityCertificate.maxValidityEnd = this.authority.authorityCertificate.notAfter;
endDate.setDate(endDate.getDate() + this.authority.authorityCertificate.maxIssuanceDays); } else {
this.authority.authorityCertificate.maxValidityEnd = endDate; // Move max end date by maxIssuanceDays
let endDate = new Date(value);
endDate.setDate(endDate.getDate() + this.authority.authorityCertificate.maxIssuanceDays);
this.authority.authorityCertificate.maxValidityEnd = endDate;
}
} }
}); });
}); });

View File

@ -34,6 +34,29 @@ def test_authority_input_schema(client, role, issuer_plugin, logged_in_user):
assert not errors assert not errors
def test_authority_input_schema_ecc(client, role, issuer_plugin, logged_in_user):
from lemur.authorities.schemas import AuthorityInputSchema
input_data = {
"name": "Example Authority",
"owner": "jim@example.com",
"description": "An example authority.",
"commonName": "An Example Authority",
"plugin": {
"slug": "test-issuer",
"plugin_options": [{"name": "test", "value": "blah"}],
},
"type": "root",
"signingAlgorithm": "sha256WithECDSA",
"keyType": "ECCPRIME256V1",
"sensitivity": "medium",
}
data, errors = AuthorityInputSchema().load(input_data)
assert not errors
def test_user_authority(session, client, authority, role, user, issuer_plugin): def test_user_authority(session, client, authority, role, user, issuer_plugin):
u = user["user"] u = user["user"]
u.roles.append(role) u.roles.append(role)

View File

@ -11,6 +11,12 @@ from lemur.tests.vectors import (
) )
def test_get_key_type_from_ec_curve():
from lemur.common.utils import get_key_type_from_ec_curve
assert get_key_type_from_ec_curve("secp256r1") == "ECCPRIME256V1"
def test_generate_private_key(): def test_generate_private_key():
from lemur.common.utils import generate_private_key from lemur.common.utils import generate_private_key

View File

@ -39,7 +39,7 @@
"gulp-uglify": "^2.0.0", "gulp-uglify": "^2.0.0",
"gulp-useref": "^3.1.2", "gulp-useref": "^3.1.2",
"gulp-util": "^3.0.1", "gulp-util": "^3.0.1",
"http-proxy": "~1.16.2", "http-proxy": ">=1.18.1",
"jshint-stylish": "^2.2.1", "jshint-stylish": "^2.2.1",
"karma": "^4.4.1", "karma": "^4.4.1",
"karma-jasmine": "^1.1.0", "karma-jasmine": "^1.1.0",

View File

@ -11,7 +11,7 @@ cffi==1.14.0 # via cryptography
cfgv==3.1.0 # via pre-commit cfgv==3.1.0 # via pre-commit
chardet==3.0.4 # via requests chardet==3.0.4 # via requests
colorama==0.4.3 # via twine colorama==0.4.3 # via twine
cryptography==3.0 # via secretstorage cryptography==3.1 # via secretstorage
distlib==0.3.0 # via virtualenv distlib==0.3.0 # via virtualenv
docutils==0.16 # via readme-renderer docutils==0.16 # via readme-renderer
filelock==3.0.12 # via virtualenv filelock==3.0.12 # via virtualenv
@ -22,9 +22,9 @@ invoke==1.4.1 # via -r requirements-dev.in
jeepney==0.4.3 # via keyring, secretstorage jeepney==0.4.3 # via keyring, secretstorage
keyring==21.2.0 # via twine keyring==21.2.0 # via twine
mccabe==0.6.1 # via flake8 mccabe==0.6.1 # via flake8
nodeenv==1.4.0 # via -r requirements-dev.in, pre-commit nodeenv==1.5.0 # via -r requirements-dev.in, pre-commit
pkginfo==1.5.0.1 # via twine pkginfo==1.5.0.1 # via twine
pre-commit==2.6.0 # via -r requirements-dev.in pre-commit==2.7.1 # via -r requirements-dev.in
pycodestyle==2.3.1 # via flake8 pycodestyle==2.3.1 # via flake8
pycparser==2.20 # via cffi pycparser==2.20 # via cffi
pyflakes==1.6.0 # via flake8 pyflakes==1.6.0 # via flake8

View File

@ -4,35 +4,35 @@
# #
# pip-compile --no-index --output-file=requirements-docs.txt requirements-docs.in # pip-compile --no-index --output-file=requirements-docs.txt requirements-docs.in
# #
acme==1.6.0 # via -r requirements.txt acme==1.8.0 # via -r requirements.txt
alabaster==0.7.12 # via sphinx alabaster==0.7.12 # via sphinx
alembic-autogenerate-enums==0.0.2 # via -r requirements.txt alembic-autogenerate-enums==0.0.2 # via -r requirements.txt
alembic==1.4.2 # via -r requirements.txt, flask-migrate alembic==1.4.2 # via -r requirements.txt, flask-migrate
amqp==2.5.2 # via -r requirements.txt, kombu amqp==2.5.2 # via -r requirements.txt, kombu
aniso8601==8.0.0 # via -r requirements.txt, flask-restful aniso8601==8.0.0 # via -r requirements.txt, flask-restful
arrow==0.15.8 # via -r requirements.txt arrow==0.16.0 # via -r requirements.txt
asyncpool==1.0 # via -r requirements.txt asyncpool==1.0 # via -r requirements.txt
babel==2.8.0 # via sphinx babel==2.8.0 # via sphinx
bcrypt==3.1.7 # via -r requirements.txt, flask-bcrypt, paramiko bcrypt==3.1.7 # via -r requirements.txt, flask-bcrypt, paramiko
beautifulsoup4==4.9.1 # via -r requirements.txt, cloudflare beautifulsoup4==4.9.1 # via -r requirements.txt, cloudflare
billiard==3.6.3.0 # via -r requirements.txt, celery billiard==3.6.3.0 # via -r requirements.txt, celery
blinker==1.4 # via -r requirements.txt, flask-mail, flask-principal, raven blinker==1.4 # via -r requirements.txt, flask-mail, flask-principal, raven
boto3==1.14.33 # via -r requirements.txt boto3==1.14.61 # via -r requirements.txt
botocore==1.17.33 # via -r requirements.txt, boto3, s3transfer botocore==1.17.61 # via -r requirements.txt, boto3, s3transfer
celery[redis]==4.4.2 # via -r requirements.txt celery[redis]==4.4.2 # via -r requirements.txt
certifi==2020.6.20 # via -r requirements.txt, requests certifi==2020.6.20 # via -r requirements.txt, requests
certsrv==2.1.1 # via -r requirements.txt certsrv==2.1.1 # via -r requirements.txt
cffi==1.14.0 # via -r requirements.txt, bcrypt, cryptography, pynacl cffi==1.14.0 # via -r requirements.txt, bcrypt, cryptography, pynacl
chardet==3.0.4 # via -r requirements.txt, requests chardet==3.0.4 # via -r requirements.txt, requests
click==7.1.1 # via -r requirements.txt, flask click==7.1.1 # via -r requirements.txt, flask
cloudflare==2.8.9 # via -r requirements.txt cloudflare==2.8.13 # via -r requirements.txt
cryptography==3.0 # via -r requirements.txt, acme, josepy, paramiko, pyopenssl, requests cryptography==3.1 # via -r requirements.txt, acme, josepy, paramiko, pyopenssl, requests
dnspython3==1.15.0 # via -r requirements.txt dnspython3==1.15.0 # via -r requirements.txt
dnspython==1.15.0 # via -r requirements.txt, dnspython3 dnspython==1.15.0 # via -r requirements.txt, dnspython3
docutils==0.15.2 # via -r requirements.txt, botocore, sphinx docutils==0.15.2 # via -r requirements.txt, botocore, sphinx
dyn==1.8.1 # via -r requirements.txt dyn==1.8.1 # via -r requirements.txt
flask-bcrypt==0.7.1 # via -r requirements.txt flask-bcrypt==0.7.1 # via -r requirements.txt
flask-cors==3.0.8 # via -r requirements.txt flask-cors==3.0.9 # via -r requirements.txt
flask-mail==0.9.1 # via -r requirements.txt flask-mail==0.9.1 # via -r requirements.txt
flask-migrate==2.5.3 # via -r requirements.txt flask-migrate==2.5.3 # via -r requirements.txt
flask-principal==0.4.0 # via -r requirements.txt flask-principal==0.4.0 # via -r requirements.txt
@ -46,7 +46,7 @@ gunicorn==20.0.4 # via -r requirements.txt
hvac==0.10.5 # via -r requirements.txt hvac==0.10.5 # via -r requirements.txt
idna==2.9 # via -r requirements.txt, requests idna==2.9 # via -r requirements.txt, requests
imagesize==1.2.0 # via sphinx imagesize==1.2.0 # via sphinx
inflection==0.5.0 # via -r requirements.txt inflection==0.5.1 # via -r requirements.txt
itsdangerous==1.1.0 # via -r requirements.txt, flask itsdangerous==1.1.0 # via -r requirements.txt, flask
javaobj-py3==0.4.0.1 # via -r requirements.txt, pyjks javaobj-py3==0.4.0.1 # via -r requirements.txt, pyjks
jinja2==2.11.2 # via -r requirements.txt, flask, sphinx jinja2==2.11.2 # via -r requirements.txt, flask, sphinx
@ -62,9 +62,9 @@ marshmallow-sqlalchemy==0.23.1 # via -r requirements.txt
marshmallow==2.20.4 # via -r requirements.txt, marshmallow-sqlalchemy marshmallow==2.20.4 # via -r requirements.txt, marshmallow-sqlalchemy
ndg-httpsclient==0.5.1 # via -r requirements.txt ndg-httpsclient==0.5.1 # via -r requirements.txt
packaging==20.3 # via sphinx packaging==20.3 # via sphinx
paramiko==2.7.1 # via -r requirements.txt paramiko==2.7.2 # via -r requirements.txt
pem==20.1.0 # via -r requirements.txt pem==20.1.0 # via -r requirements.txt
psycopg2==2.8.5 # via -r requirements.txt psycopg2==2.8.6 # via -r requirements.txt
pyasn1-modules==0.2.8 # via -r requirements.txt, pyjks, python-ldap pyasn1-modules==0.2.8 # via -r requirements.txt, pyjks, python-ldap
pyasn1==0.4.8 # via -r requirements.txt, ndg-httpsclient, pyasn1-modules, pyjks, python-ldap pyasn1==0.4.8 # via -r requirements.txt, ndg-httpsclient, pyasn1-modules, pyjks, python-ldap
pycparser==2.20 # via -r requirements.txt, cffi pycparser==2.20 # via -r requirements.txt, cffi
@ -92,7 +92,7 @@ six==1.15.0 # via -r requirements.txt, acme, bcrypt, cryptography,
snowballstemmer==2.0.0 # via sphinx snowballstemmer==2.0.0 # via sphinx
soupsieve==2.0.1 # via -r requirements.txt, beautifulsoup4 soupsieve==2.0.1 # via -r requirements.txt, beautifulsoup4
sphinx-rtd-theme==0.5.0 # via -r requirements-docs.in sphinx-rtd-theme==0.5.0 # via -r requirements-docs.in
sphinx==3.2.0 # via -r requirements-docs.in, sphinx-rtd-theme, sphinxcontrib-httpdomain sphinx==3.2.1 # via -r requirements-docs.in, sphinx-rtd-theme, sphinxcontrib-httpdomain
sphinxcontrib-applehelp==1.0.2 # via sphinx sphinxcontrib-applehelp==1.0.2 # via sphinx
sphinxcontrib-devhelp==1.0.2 # via sphinx sphinxcontrib-devhelp==1.0.2 # via sphinx
sphinxcontrib-htmlhelp==1.0.3 # via sphinx sphinxcontrib-htmlhelp==1.0.3 # via sphinx

View File

@ -5,30 +5,30 @@
# pip-compile --no-index --output-file=requirements-tests.txt requirements-tests.in # pip-compile --no-index --output-file=requirements-tests.txt requirements-tests.in
# #
appdirs==1.4.3 # via black appdirs==1.4.3 # via black
attrs==19.3.0 # via black, jsonschema, pytest attrs==19.3.0 # via jsonschema, pytest
aws-sam-translator==1.22.0 # via cfn-lint aws-sam-translator==1.22.0 # via cfn-lint
aws-xray-sdk==2.5.0 # via moto aws-xray-sdk==2.5.0 # via moto
bandit==1.6.2 # via -r requirements-tests.in bandit==1.6.2 # via -r requirements-tests.in
black==19.10b0 # via -r requirements-tests.in black==20.8b1 # via -r requirements-tests.in
boto3==1.14.33 # via aws-sam-translator, moto boto3==1.14.61 # via aws-sam-translator, moto
boto==2.49.0 # via moto boto==2.49.0 # via moto
botocore==1.17.33 # via aws-xray-sdk, boto3, moto, s3transfer botocore==1.17.61 # via aws-xray-sdk, boto3, moto, s3transfer
certifi==2020.6.20 # via requests certifi==2020.6.20 # via requests
cffi==1.14.0 # via cryptography cffi==1.14.0 # via cryptography
cfn-lint==0.29.5 # via moto cfn-lint==0.29.5 # via moto
chardet==3.0.4 # via requests chardet==3.0.4 # via requests
click==7.1.1 # via black, flask click==7.1.2 # via black, flask
coverage==5.2.1 # via -r requirements-tests.in coverage==5.3 # via -r requirements-tests.in
cryptography==3.0 # via moto, sshpubkeys cryptography==3.1 # via moto, python-jose, sshpubkeys
decorator==4.4.2 # via networkx decorator==4.4.2 # via networkx
docker==4.2.0 # via moto docker==4.2.0 # via moto
docutils==0.15.2 # via botocore docutils==0.15.2 # via botocore
ecdsa==0.15 # via python-jose, sshpubkeys ecdsa==0.14.1 # via moto, python-jose, sshpubkeys
factory-boy==2.12.0 # via -r requirements-tests.in factory-boy==3.0.1 # via -r requirements-tests.in
faker==4.1.1 # via -r requirements-tests.in, factory-boy faker==4.1.3 # via -r requirements-tests.in, factory-boy
fakeredis==1.4.1 # via -r requirements-tests.in fakeredis==1.4.3 # via -r requirements-tests.in
flask==1.1.2 # via pytest-flask flask==1.1.2 # via pytest-flask
freezegun==0.3.15 # via -r requirements-tests.in freezegun==1.0.0 # via -r requirements-tests.in
future==0.18.2 # via aws-xray-sdk future==0.18.2 # via aws-xray-sdk
gitdb==4.0.4 # via gitpython gitdb==4.0.4 # via gitpython
gitpython==3.1.1 # via bandit gitpython==3.1.1 # via bandit
@ -43,10 +43,11 @@ jsonpatch==1.25 # via cfn-lint
jsonpickle==1.4 # via aws-xray-sdk jsonpickle==1.4 # via aws-xray-sdk
jsonpointer==2.0 # via jsonpatch jsonpointer==2.0 # via jsonpatch
jsonschema==3.2.0 # via aws-sam-translator, cfn-lint jsonschema==3.2.0 # via aws-sam-translator, cfn-lint
markupsafe==1.1.1 # via jinja2 markupsafe==1.1.1 # via jinja2, moto
mock==4.0.2 # via moto mock==4.0.2 # via moto
more-itertools==8.2.0 # via pytest more-itertools==8.2.0 # via moto, pytest
moto==1.3.14 # via -r requirements-tests.in moto==1.3.16 # via -r requirements-tests.in
mypy-extensions==0.4.3 # via black
networkx==2.4 # via cfn-lint networkx==2.4 # via cfn-lint
nose==1.3.7 # via -r requirements-tests.in nose==1.3.7 # via -r requirements-tests.in
packaging==20.3 # via pytest packaging==20.3 # via pytest
@ -60,10 +61,10 @@ pyflakes==2.2.0 # via -r requirements-tests.in
pyparsing==2.4.7 # via packaging pyparsing==2.4.7 # via packaging
pyrsistent==0.16.0 # via jsonschema pyrsistent==0.16.0 # via jsonschema
pytest-flask==1.0.0 # via -r requirements-tests.in pytest-flask==1.0.0 # via -r requirements-tests.in
pytest-mock==3.2.0 # via -r requirements-tests.in pytest-mock==3.3.1 # via -r requirements-tests.in
pytest==6.0.1 # via -r requirements-tests.in, pytest-flask, pytest-mock pytest==6.0.2 # via -r requirements-tests.in, pytest-flask, pytest-mock
python-dateutil==2.8.1 # via botocore, faker, freezegun, moto python-dateutil==2.8.1 # via botocore, faker, freezegun, moto
python-jose==3.1.0 # via moto python-jose[cryptography]==3.1.0 # via moto
pytz==2019.3 # via moto pytz==2019.3 # via moto
pyyaml==5.3.1 # via -r requirements-tests.in, bandit, cfn-lint, moto pyyaml==5.3.1 # via -r requirements-tests.in, bandit, cfn-lint, moto
redis==3.5.3 # via fakeredis redis==3.5.3 # via fakeredis
@ -73,20 +74,21 @@ requests==2.24.0 # via docker, moto, requests-mock, responses
responses==0.10.12 # via moto responses==0.10.12 # via moto
rsa==4.0 # via python-jose rsa==4.0 # via python-jose
s3transfer==0.3.3 # via boto3 s3transfer==0.3.3 # via boto3
six==1.15.0 # via aws-sam-translator, bandit, cfn-lint, cryptography, docker, ecdsa, fakeredis, freezegun, jsonschema, moto, packaging, pyrsistent, python-dateutil, python-jose, requests-mock, responses, stevedore, websocket-client six==1.15.0 # via aws-sam-translator, bandit, cfn-lint, cryptography, docker, ecdsa, fakeredis, jsonschema, moto, packaging, pyrsistent, python-dateutil, python-jose, requests-mock, responses, stevedore, websocket-client
smmap==3.0.2 # via gitdb smmap==3.0.2 # via gitdb
sortedcontainers==2.1.0 # via fakeredis sortedcontainers==2.1.0 # via fakeredis
sshpubkeys==3.1.0 # via moto sshpubkeys==3.1.0 # via moto
stevedore==1.32.0 # via bandit stevedore==1.32.0 # via bandit
text-unidecode==1.3 # via faker text-unidecode==1.3 # via faker
toml==0.10.0 # via black, pytest toml==0.10.1 # via black, pytest
typed-ast==1.4.1 # via black typed-ast==1.4.1 # via black
typing-extensions==3.7.4.3 # via black
urllib3==1.25.8 # via botocore, requests urllib3==1.25.8 # via botocore, requests
websocket-client==0.57.0 # via docker websocket-client==0.57.0 # via docker
werkzeug==1.0.1 # via flask, moto, pytest-flask werkzeug==1.0.1 # via flask, moto, pytest-flask
wrapt==1.12.1 # via aws-xray-sdk wrapt==1.12.1 # via aws-xray-sdk
xmltodict==0.12.0 # via moto xmltodict==0.12.0 # via moto
zipp==3.1.0 # via importlib-metadata zipp==3.1.0 # via importlib-metadata, moto
# The following packages are considered to be unsafe in a requirements file: # The following packages are considered to be unsafe in a requirements file:
# setuptools # setuptools

View File

@ -4,33 +4,33 @@
# #
# pip-compile --no-index --output-file=requirements.txt requirements.in # pip-compile --no-index --output-file=requirements.txt requirements.in
# #
acme==1.6.0 # via -r requirements.in acme==1.8.0 # via -r requirements.in
alembic-autogenerate-enums==0.0.2 # via -r requirements.in alembic-autogenerate-enums==0.0.2 # via -r requirements.in
alembic==1.4.2 # via flask-migrate alembic==1.4.2 # via flask-migrate
amqp==2.5.2 # via kombu amqp==2.5.2 # via kombu
aniso8601==8.0.0 # via flask-restful aniso8601==8.0.0 # via flask-restful
arrow==0.15.8 # via -r requirements.in arrow==0.16.0 # via -r requirements.in
asyncpool==1.0 # via -r requirements.in asyncpool==1.0 # via -r requirements.in
bcrypt==3.1.7 # via flask-bcrypt, paramiko bcrypt==3.1.7 # via flask-bcrypt, paramiko
beautifulsoup4==4.9.1 # via cloudflare beautifulsoup4==4.9.1 # via cloudflare
billiard==3.6.3.0 # via celery billiard==3.6.3.0 # via celery
blinker==1.4 # via flask-mail, flask-principal, raven blinker==1.4 # via flask-mail, flask-principal, raven
boto3==1.14.33 # via -r requirements.in boto3==1.14.61 # via -r requirements.in
botocore==1.17.33 # via -r requirements.in, boto3, s3transfer botocore==1.17.61 # via -r requirements.in, boto3, s3transfer
celery[redis]==4.4.2 # via -r requirements.in celery[redis]==4.4.2 # via -r requirements.in
certifi==2020.6.20 # via -r requirements.in, requests certifi==2020.6.20 # via -r requirements.in, requests
certsrv==2.1.1 # via -r requirements.in certsrv==2.1.1 # via -r requirements.in
cffi==1.14.0 # via bcrypt, cryptography, pynacl cffi==1.14.0 # via bcrypt, cryptography, pynacl
chardet==3.0.4 # via requests chardet==3.0.4 # via requests
click==7.1.1 # via flask click==7.1.1 # via flask
cloudflare==2.8.9 # via -r requirements.in cloudflare==2.8.13 # via -r requirements.in
cryptography==3.0 # via -r requirements.in, acme, josepy, paramiko, pyopenssl, requests cryptography==3.1 # via -r requirements.in, acme, josepy, paramiko, pyopenssl, requests
dnspython3==1.15.0 # via -r requirements.in dnspython3==1.15.0 # via -r requirements.in
dnspython==1.15.0 # via dnspython3 dnspython==1.15.0 # via dnspython3
docutils==0.15.2 # via botocore docutils==0.15.2 # via botocore
dyn==1.8.1 # via -r requirements.in dyn==1.8.1 # via -r requirements.in
flask-bcrypt==0.7.1 # via -r requirements.in flask-bcrypt==0.7.1 # via -r requirements.in
flask-cors==3.0.8 # via -r requirements.in flask-cors==3.0.9 # via -r requirements.in
flask-mail==0.9.1 # via -r requirements.in flask-mail==0.9.1 # via -r requirements.in
flask-migrate==2.5.3 # via -r requirements.in flask-migrate==2.5.3 # via -r requirements.in
flask-principal==0.4.0 # via -r requirements.in flask-principal==0.4.0 # via -r requirements.in
@ -43,7 +43,7 @@ future==0.18.2 # via -r requirements.in
gunicorn==20.0.4 # via -r requirements.in gunicorn==20.0.4 # via -r requirements.in
hvac==0.10.5 # via -r requirements.in hvac==0.10.5 # via -r requirements.in
idna==2.9 # via requests idna==2.9 # via requests
inflection==0.5.0 # via -r requirements.in inflection==0.5.1 # via -r requirements.in
itsdangerous==1.1.0 # via flask itsdangerous==1.1.0 # via flask
javaobj-py3==0.4.0.1 # via pyjks javaobj-py3==0.4.0.1 # via pyjks
jinja2==2.11.2 # via -r requirements.in, flask jinja2==2.11.2 # via -r requirements.in, flask
@ -58,9 +58,9 @@ markupsafe==1.1.1 # via jinja2, mako
marshmallow-sqlalchemy==0.23.1 # via -r requirements.in marshmallow-sqlalchemy==0.23.1 # via -r requirements.in
marshmallow==2.20.4 # via -r requirements.in, marshmallow-sqlalchemy marshmallow==2.20.4 # via -r requirements.in, marshmallow-sqlalchemy
ndg-httpsclient==0.5.1 # via -r requirements.in ndg-httpsclient==0.5.1 # via -r requirements.in
paramiko==2.7.1 # via -r requirements.in paramiko==2.7.2 # via -r requirements.in
pem==20.1.0 # via -r requirements.in pem==20.1.0 # via -r requirements.in
psycopg2==2.8.5 # via -r requirements.in psycopg2==2.8.6 # via -r requirements.in
pyasn1-modules==0.2.8 # via pyjks, python-ldap pyasn1-modules==0.2.8 # via pyjks, python-ldap
pyasn1==0.4.8 # via ndg-httpsclient, pyasn1-modules, pyjks, python-ldap pyasn1==0.4.8 # via ndg-httpsclient, pyasn1-modules, pyjks, python-ldap
pycparser==2.20 # via cffi pycparser==2.20 # via cffi

View File

@ -9,30 +9,18 @@ Is a TLS management and orchestration tool.
""" """
from __future__ import absolute_import from __future__ import absolute_import
import sys
import json
import os.path
import datetime import datetime
import json
import logging
import os.path
import sys
from subprocess import check_output
from distutils import log from setuptools import Command
from distutils.core import Command from setuptools import setup, find_packages
from setuptools.command.develop import develop from setuptools.command.develop import develop
from setuptools.command.install import install from setuptools.command.install import install
from setuptools.command.sdist import sdist from setuptools.command.sdist import sdist
from setuptools import setup, find_packages
from subprocess import check_output
import pip
if tuple(map(int, pip.__version__.split('.'))) >= (19, 3, 0):
from pip._internal.network.session import PipSession
from pip._internal.req import parse_requirements
elif tuple(map(int, pip.__version__.split('.'))) >= (10, 0, 0):
from pip._internal.download import PipSession
from pip._internal.req import parse_requirements
else:
from pip.download import PipSession
from pip.req import parse_requirements
ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__))) ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__)))
@ -44,21 +32,18 @@ about = {}
with open(os.path.join(ROOT, 'lemur', '__about__.py')) as f: with open(os.path.join(ROOT, 'lemur', '__about__.py')) as f:
exec(f.read(), about) # nosec: about file is benign exec(f.read(), about) # nosec: about file is benign
install_requires_g = parse_requirements("requirements.txt", session=PipSession()) # Parse requirements files
tests_require_g = parse_requirements("requirements-tests.txt", session=PipSession()) with open('requirements.txt') as f:
docs_require_g = parse_requirements("requirements-docs.txt", session=PipSession()) install_requirements = f.read().splitlines()
dev_requires_g = parse_requirements("requirements-dev.txt", session=PipSession())
if tuple(map(int, pip.__version__.split('.'))) >= (20, 1): with open('requirements-tests.txt') as f:
install_requires = [str(ir.requirement) for ir in install_requires_g] tests_requirements = f.read().splitlines()
tests_require = [str(ir.requirement) for ir in tests_require_g]
docs_require = [str(ir.requirement) for ir in docs_require_g] with open('requirements-docs.txt') as f:
dev_requires = [str(ir.requirement) for ir in dev_requires_g] docs_requirements = f.read().splitlines()
else:
install_requires = [str(ir.req) for ir in install_requires_g] with open('requirements-dev.txt') as f:
tests_require = [str(ir.req) for ir in tests_require_g] dev_requirements = f.read().splitlines()
docs_require = [str(ir.req) for ir in docs_require_g]
dev_requires = [str(ir.req) for ir in dev_requires_g]
class SmartInstall(install): class SmartInstall(install):
@ -67,6 +52,7 @@ class SmartInstall(install):
If the package indicator is missing, this will also force a run of If the package indicator is missing, this will also force a run of
`build_static` which is required for JavaScript assets and other things. `build_static` which is required for JavaScript assets and other things.
""" """
def _needs_static(self): def _needs_static(self):
return not os.path.exists(os.path.join(ROOT, 'lemur/static/dist')) return not os.path.exists(os.path.join(ROOT, 'lemur/static/dist'))
@ -105,16 +91,16 @@ class BuildStatic(Command):
pass pass
def run(self): def run(self):
log.info("running [npm install --quiet] in {0}".format(ROOT)) logging.info("running [npm install --quiet] in {0}".format(ROOT))
try: try:
check_output(['npm', 'install', '--quiet'], cwd=ROOT) check_output(['npm', 'install', '--quiet'], cwd=ROOT)
log.info("running [gulp build]") logging.info("running [gulp build]")
check_output([os.path.join(ROOT, 'node_modules', '.bin', 'gulp'), 'build'], cwd=ROOT) check_output([os.path.join(ROOT, 'node_modules', '.bin', 'gulp'), 'build'], cwd=ROOT)
log.info("running [gulp package]") logging.info("running [gulp package]")
check_output([os.path.join(ROOT, 'node_modules', '.bin', 'gulp'), 'package'], cwd=ROOT) check_output([os.path.join(ROOT, 'node_modules', '.bin', 'gulp'), 'package'], cwd=ROOT)
except Exception as e: except Exception as e:
log.warn("Unable to build static content") logging.warn("Unable to build static content")
setup( setup(
@ -128,11 +114,11 @@ setup(
packages=find_packages(), packages=find_packages(),
include_package_data=True, include_package_data=True,
zip_safe=False, zip_safe=False,
install_requires=install_requires, install_requires=install_requirements,
extras_require={ extras_require={
'tests': tests_require, 'tests': tests_requirements,
'docs': docs_require, 'docs': docs_requirements,
'dev': dev_requires, 'dev': dev_requirements,
}, },
cmdclass={ cmdclass={
'build_static': BuildStatic, 'build_static': BuildStatic,
@ -167,7 +153,9 @@ setup(
'vault_source = lemur.plugins.lemur_vault_dest.plugin:VaultSourcePlugin', 'vault_source = lemur.plugins.lemur_vault_dest.plugin:VaultSourcePlugin',
'vault_desination = lemur.plugins.lemur_vault_dest.plugin:VaultDestinationPlugin', 'vault_desination = lemur.plugins.lemur_vault_dest.plugin:VaultDestinationPlugin',
'adcs_issuer = lemur.plugins.lemur_adcs.plugin:ADCSIssuerPlugin', 'adcs_issuer = lemur.plugins.lemur_adcs.plugin:ADCSIssuerPlugin',
'adcs_source = lemur.plugins.lemur_adcs.plugin:ADCSSourcePlugin' 'adcs_source = lemur.plugins.lemur_adcs.plugin:ADCSSourcePlugin',
'entrust_issuer = lemur.plugins.lemur_entrust.plugin:EntrustIssuerPlugin',
'entrust_source = lemur.plugins.lemur_entrust.plugin:EntrustSourcePlugin'
], ],
}, },
classifiers=[ classifiers=[