Merge branch 'master' into key_type_column

This commit is contained in:
Hossein Shafagh 2020-09-15 10:51:36 -07:00 committed by GitHub
commit 477f2fa1c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 317 additions and 13 deletions

View File

@ -654,12 +654,19 @@ Active Directory Certificate Services Plugin
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:
@ -672,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

@ -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 @@
# #
# 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.7.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

View File

@ -18,14 +18,14 @@ 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.2 # 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.1 # 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==3.0.1 # via -r requirements-tests.in factory-boy==3.0.1 # via -r requirements-tests.in
faker==4.1.2 # via -r requirements-tests.in, factory-boy faker==4.1.3 # via -r requirements-tests.in, factory-boy
fakeredis==1.4.3 # 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==1.0.0 # via -r requirements-tests.in freezegun==1.0.0 # via -r requirements-tests.in
@ -43,10 +43,10 @@ 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 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
@ -62,9 +62,9 @@ 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.3.1 # 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
@ -88,7 +88,7 @@ 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,7 +4,7 @@
# #
# pip-compile --no-index --output-file=requirements.txt requirements.in # pip-compile --no-index --output-file=requirements.txt requirements.in
# #
acme==1.7.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

View File

@ -153,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=[