Initial workon the digicert high issuance api. (#531)

This commit is contained in:
kevgliss 2016-11-28 10:50:58 -08:00 committed by GitHub
parent 734233257c
commit b46ff4158a
4 changed files with 209 additions and 60 deletions

View File

@ -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
))

View File

@ -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):

View File

@ -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]

View File

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