Initial workon the digicert high issuance api. (#531)
This commit is contained in:
parent
734233257c
commit
b46ff4158a
|
@ -50,3 +50,17 @@ def is_weekend(date):
|
|||
"""
|
||||
if date.weekday() > 5:
|
||||
return True
|
||||
|
||||
|
||||
def validate_conf(app, required_vars):
|
||||
"""
|
||||
Ensures that the given fields are set in the applications conf.
|
||||
|
||||
:param app:
|
||||
:param required_vars: list
|
||||
"""
|
||||
for var in required_vars:
|
||||
if not app.config.get(var):
|
||||
raise Exception("Required variable {var} is not set, ensure that it is set in Lemur's configuration file".format(
|
||||
var=var
|
||||
))
|
||||
|
|
|
@ -19,6 +19,7 @@ from logging.handlers import RotatingFileHandler
|
|||
|
||||
from flask import Flask
|
||||
from lemur.common.health import mod as health
|
||||
from lemur.common.utils import validate_conf
|
||||
from lemur.extensions import db, migrate, principal, smtp_mail, metrics
|
||||
|
||||
|
||||
|
@ -28,6 +29,16 @@ DEFAULT_BLUEPRINTS = (
|
|||
|
||||
API_VERSION = 1
|
||||
|
||||
REQUIRED_VARIABLES = [
|
||||
'LEMUR_SECURITY_TEAM_EMAIL',
|
||||
'LEMUR_DEFAULT_ORGANIZATIONAL_UNIT',
|
||||
'LEMUR_DEFAULT_ORGANIZATION',
|
||||
'LEMUR_DEFAULT_LOCATION',
|
||||
'LEMUR_DEFAULT_COUNTRY',
|
||||
'LEMUR_DEFAULT_STATE',
|
||||
'SQLALCHEMY_DATABASE_URI'
|
||||
]
|
||||
|
||||
|
||||
def create_app(app_name=None, blueprints=None, config=None):
|
||||
"""
|
||||
|
@ -104,29 +115,7 @@ def configure_app(app, config=None):
|
|||
else:
|
||||
app.config.from_object(from_file(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'default.conf.py')))
|
||||
|
||||
validate_conf(app)
|
||||
|
||||
|
||||
def validate_conf(app):
|
||||
"""
|
||||
There are a few configuration variables that are 'required' by Lemur. Here
|
||||
we validate those required variables are set.
|
||||
"""
|
||||
required_vars = [
|
||||
'LEMUR_SECURITY_TEAM_EMAIL',
|
||||
'LEMUR_DEFAULT_ORGANIZATIONAL_UNIT',
|
||||
'LEMUR_DEFAULT_ORGANIZATION',
|
||||
'LEMUR_DEFAULT_LOCATION',
|
||||
'LEMUR_DEFAULT_COUNTRY',
|
||||
'LEMUR_DEFAULT_STATE',
|
||||
'SQLALCHEMY_DATABASE_URI'
|
||||
]
|
||||
|
||||
for var in required_vars:
|
||||
if not app.config.get(var):
|
||||
raise Exception("Required variable {var} is not set, ensure that it is set in Lemur's configuration file".format(
|
||||
var=var
|
||||
))
|
||||
validate_conf(app, REQUIRED_VARIABLES)
|
||||
|
||||
|
||||
def configure_extensions(app):
|
||||
|
|
|
@ -27,6 +27,8 @@ from lemur.plugins.bases import IssuerPlugin, SourcePlugin
|
|||
|
||||
from lemur.plugins import lemur_digicert as digicert
|
||||
|
||||
from lemur.common.utils import validate_conf
|
||||
|
||||
|
||||
def signature_hash(signing_algorithm):
|
||||
"""Converts Lemur's signing algorithm into a format DigiCert understands.
|
||||
|
@ -81,7 +83,22 @@ def get_issuance(options):
|
|||
return validity_years
|
||||
|
||||
|
||||
def process_options(options, csr):
|
||||
def get_additional_names(options):
|
||||
"""
|
||||
Return a list of strings to be added to a SAN certificates.
|
||||
|
||||
:param options:
|
||||
:return:
|
||||
"""
|
||||
names = []
|
||||
# add SANs if present
|
||||
if options.get('extensions', 'sub_alt_names'):
|
||||
for san in options['extensions']['sub_alt_names']['names']:
|
||||
names.append(san['value'])
|
||||
return names
|
||||
|
||||
|
||||
def map_fields(options, csr):
|
||||
"""Set the incoming issuer options to DigiCert fields/options.
|
||||
|
||||
:param options:
|
||||
|
@ -102,14 +119,7 @@ def process_options(options, csr):
|
|||
},
|
||||
}
|
||||
|
||||
# add SANs if present
|
||||
if options.get('extensions', 'sub_alt_names'):
|
||||
dns_names = []
|
||||
for san in options['extensions']['sub_alt_names']['names']:
|
||||
dns_names.append(san['value'])
|
||||
|
||||
data['certificate']['dns_names'] = dns_names
|
||||
|
||||
data['certificate']['dns_names'] = get_additional_names(options)
|
||||
validity_years = get_issuance(options)
|
||||
data['custom_expiration_date'] = options['validity_end'].format('YYYY-MM-DD')
|
||||
data['validity_years'] = validity_years
|
||||
|
@ -117,6 +127,31 @@ def process_options(options, csr):
|
|||
return data
|
||||
|
||||
|
||||
def map_cis_fields(options, csr):
|
||||
"""
|
||||
MAP issuer options to DigiCert CIS fields/options.
|
||||
|
||||
:param options:
|
||||
:param csr:
|
||||
:return:
|
||||
"""
|
||||
data = {
|
||||
"common_name": options['common_name'],
|
||||
"additional_dns_names": get_additional_names(options),
|
||||
"csr": csr,
|
||||
"signature_hash": signature_hash(options.get('signing_algorithm')),
|
||||
"validity": {
|
||||
"valid_to": options['validity_end'].format('YYYY-MM-DD')
|
||||
},
|
||||
"organization": {
|
||||
"name": options['organization'],
|
||||
"units": [options['organizational_unit']]
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def handle_response(response):
|
||||
"""
|
||||
Handle the DigiCert API response and any errors it might have experienced.
|
||||
|
@ -125,41 +160,40 @@ def handle_response(response):
|
|||
"""
|
||||
metrics.send('digicert_status_code_{0}'.format(response.status_code), 'counter', 1)
|
||||
|
||||
if response.status_code not in [200, 201, 302, 301]:
|
||||
if response.status_code > 399:
|
||||
raise Exception(response.json()['message'])
|
||||
|
||||
return response.json()
|
||||
|
||||
|
||||
def verify_configuration():
|
||||
"""Verify that needed configuration variables are set before plugin startup."""
|
||||
if not current_app.config.get('DIGICERT_API_KEY'):
|
||||
raise Exception("No Digicert API key found. Ensure that 'DIGICERT_API_KEY' is set in the Lemur conf.")
|
||||
|
||||
if not current_app.config.get('DIGICERT_URL'):
|
||||
raise Exception("No Digicert URL found. Ensure that 'DIGICERT_URL' is set in the Lemur conf.")
|
||||
|
||||
if not current_app.config.get('DIGICERT_ORG_ID'):
|
||||
raise Exception("No Digicert organization ID found. Ensure that 'DIGICERT_ORG_ID' is set in Lemur conf.")
|
||||
|
||||
if not current_app.config.get('DIGICERT_ROOT'):
|
||||
raise Exception("No Digicert root found. Ensure that 'DIGICERT_ROOT' is set in the Lemur conf.")
|
||||
|
||||
if not current_app.config.get('DIGICERT_INTERMEDIATE'):
|
||||
raise Exception("No Digicert intermediate found. Ensure that 'DIGICERT_INTERMEDIATE is set in Lemur conf.")
|
||||
|
||||
|
||||
@retry(stop_max_attempt_number=10, wait_fixed=10000)
|
||||
def get_certificate_id(session, base_url, order_id):
|
||||
"""Retrieve certificate order id from Digicert API."""
|
||||
order_url = "{0}/services/v2/order/certificate/{1}".format(base_url, order_id)
|
||||
response_data = handle_response(session.get(order_url))
|
||||
if response_data['status'] != 'issued':
|
||||
metrics.send('digicert_retries', 'counter', 1)
|
||||
raise Exception("Order not in issued state.")
|
||||
|
||||
return response_data['certificate']['id']
|
||||
|
||||
|
||||
@retry(stop_max_attempt_number=10, wait_fixed=10000)
|
||||
def get_cis_certificate(session, base_url, order_id):
|
||||
"""Retrieve certificate order id from Digicert API."""
|
||||
certificate_url = '{0}/platform/cis/certificate/{1}'.format(base_url, order_id)
|
||||
session.headers.update(
|
||||
{'Accept': 'application/x-pem-file'}
|
||||
)
|
||||
response = session.get(certificate_url)
|
||||
|
||||
if response.status_code == 404:
|
||||
metrics.send('digicert_retries', 'counter', 1)
|
||||
raise Exception("Order not in issued state.")
|
||||
|
||||
return response.content
|
||||
|
||||
|
||||
class DigiCertSourcePlugin(SourcePlugin):
|
||||
"""Wrap the Digicert Certifcate API."""
|
||||
title = 'DigiCert'
|
||||
|
@ -172,12 +206,19 @@ class DigiCertSourcePlugin(SourcePlugin):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize source with appropriate details."""
|
||||
verify_configuration()
|
||||
required_vars = [
|
||||
'DIGICERT_API_KEY',
|
||||
'DIGICERT_URL',
|
||||
'DIGICERT_ORG_ID',
|
||||
'DIGICERT_ROOT',
|
||||
'DIGICERT_INTERMEDIATE'
|
||||
]
|
||||
validate_conf(current_app, required_vars)
|
||||
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update(
|
||||
{
|
||||
'X-DC-DEVKEY': current_app.config.get('DIGICERT_API_KEY'),
|
||||
'X-DC-DEVKEY': current_app.config['DIGICERT_API_KEY'],
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
)
|
||||
|
@ -190,7 +231,6 @@ class DigiCertSourcePlugin(SourcePlugin):
|
|||
|
||||
class DigiCertIssuerPlugin(IssuerPlugin):
|
||||
"""Wrap the Digicert Issuer API."""
|
||||
|
||||
title = 'DigiCert'
|
||||
slug = 'digicert-issuer'
|
||||
description = "Enables the creation of certificates by"
|
||||
|
@ -202,12 +242,20 @@ class DigiCertIssuerPlugin(IssuerPlugin):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize the issuer with the appropriate details."""
|
||||
verify_configuration()
|
||||
required_vars = [
|
||||
'DIGICERT_API_KEY',
|
||||
'DIGICERT_URL',
|
||||
'DIGICERT_ORG_ID',
|
||||
'DIGICERT_ROOT',
|
||||
'DIGICERT_INTERMEDIATE'
|
||||
]
|
||||
|
||||
validate_conf(current_app, required_vars)
|
||||
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update(
|
||||
{
|
||||
'X-DC-DEVKEY': current_app.config.get('DIGICERT_API_KEY'),
|
||||
'X-DC-DEVKEY': current_app.config['DIGICERT_API_KEY'],
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
)
|
||||
|
@ -225,7 +273,7 @@ class DigiCertIssuerPlugin(IssuerPlugin):
|
|||
|
||||
# make certificate request
|
||||
determinator_url = "{0}/services/v2/order/certificate/ssl".format(base_url)
|
||||
data = process_options(issuer_options, csr)
|
||||
data = map_fields(issuer_options, csr)
|
||||
response = self.session.post(determinator_url, data=json.dumps(data))
|
||||
order_id = response.json()['id']
|
||||
|
||||
|
@ -249,3 +297,67 @@ class DigiCertIssuerPlugin(IssuerPlugin):
|
|||
"""
|
||||
role = {'username': '', 'password': '', 'name': 'digicert'}
|
||||
return current_app.config.get('DIGICERT_ROOT'), "", [role]
|
||||
|
||||
|
||||
class DigiCertCISIssuerPlugin(IssuerPlugin):
|
||||
"""Wrap the Digicert Certificate Issuing API."""
|
||||
title = 'DigiCert CIS'
|
||||
slug = 'digicert-cis-issuer'
|
||||
description = "Enables the creation of certificates by the DigiCert CIS REST API."
|
||||
version = digicert.VERSION
|
||||
|
||||
author = 'Kevin Glisson'
|
||||
author_url = 'https://github.com/netflix/lemur.git'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize the issuer with the appropriate details."""
|
||||
required_vars = [
|
||||
'DIGICERT_CIS_API_KEY',
|
||||
'DIGICERT_CIS_URL',
|
||||
'DIGICERT_CIS_ORG_ID',
|
||||
'DIGICERT_CIS_ROOT',
|
||||
'DIGICERT_CIS_INTERMEDIATE',
|
||||
'DIGICERT_CIS_PROFILE_NAME'
|
||||
]
|
||||
|
||||
validate_conf(current_app, required_vars)
|
||||
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update(
|
||||
{
|
||||
'X-DC-DEVKEY': current_app.config['DIGICERT_CIS_API_KEY'],
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
)
|
||||
|
||||
super(DigiCertCISIssuerPlugin, self).__init__(*args, **kwargs)
|
||||
|
||||
def create_certificate(self, csr, issuer_options):
|
||||
"""Create a DigiCert certificate."""
|
||||
base_url = current_app.config.get('DIGICERT_CIS_URL')
|
||||
|
||||
# make certificate request
|
||||
create_url = '{0}/platform/cis/certificate'
|
||||
|
||||
data = map_cis_fields(issuer_options, csr)
|
||||
response = self.session.post(create_url, data=json.dumps(data))
|
||||
order_id = response.json()['id']
|
||||
|
||||
# retrieve certificate
|
||||
certificate_pem = get_cis_certificate(self.session, base_url, order_id)
|
||||
end_entity, intermediate, root = pem.parse(certificate_pem)
|
||||
return str(end_entity), str(intermediate)
|
||||
|
||||
@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:
|
||||
"""
|
||||
role = {'username': '', 'password': '', 'name': 'digicert'}
|
||||
return current_app.config.get('DIGICERT_CIS_ROOT'), "", [role]
|
||||
|
|
|
@ -5,8 +5,8 @@ from freezegun import freeze_time
|
|||
from lemur.tests.vectors import CSR_STR
|
||||
|
||||
|
||||
def test_process_options(app):
|
||||
from lemur.plugins.lemur_digicert.plugin import process_options
|
||||
def test_map_fields(app):
|
||||
from lemur.plugins.lemur_digicert.plugin import map_fields
|
||||
|
||||
names = ['one.example.com', 'two.example.com', 'three.example.com']
|
||||
|
||||
|
@ -23,7 +23,7 @@ def test_process_options(app):
|
|||
'validity_start': arrow.get(2016, 10, 30)
|
||||
}
|
||||
|
||||
data = process_options(options, CSR_STR)
|
||||
data = map_fields(options, CSR_STR)
|
||||
|
||||
assert data == {
|
||||
'certificate': {
|
||||
|
@ -38,6 +38,40 @@ def test_process_options(app):
|
|||
}
|
||||
|
||||
|
||||
def test_map_cis_fields(app):
|
||||
from lemur.plugins.lemur_digicert.plugin import map_cis_fields
|
||||
|
||||
names = ['one.example.com', 'two.example.com', 'three.example.com']
|
||||
|
||||
options = {
|
||||
'common_name': 'example.com',
|
||||
'owner': 'bob@example.com',
|
||||
'description': 'test certificate',
|
||||
'extensions': {
|
||||
'sub_alt_names': {
|
||||
'names': [{'name_type': 'DNSName', 'value': x} for x in names]
|
||||
}
|
||||
},
|
||||
'organization': 'Example, Inc.',
|
||||
'organizational_unit': 'Example Org',
|
||||
'validity_end': arrow.get(2017, 5, 7),
|
||||
'validity_start': arrow.get(2016, 10, 30)
|
||||
}
|
||||
|
||||
data = map_cis_fields(options, CSR_STR)
|
||||
|
||||
assert data == {
|
||||
'common_name': 'example.com',
|
||||
'csr': CSR_STR,
|
||||
'additional_dns_names': names,
|
||||
'signature_hash': 'sha256',
|
||||
'organization': {'name': 'Example, Inc.', 'units': ['Example Org']},
|
||||
'validity': {
|
||||
'valid_to': arrow.get(2017, 5, 7).format('YYYY-MM-DD')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_issuance():
|
||||
from lemur.plugins.lemur_digicert.plugin import get_issuance
|
||||
|
||||
|
|
Loading…
Reference in New Issue