Adding a toy certificate authority. (#378)

This commit is contained in:
kevgliss 2016-06-29 09:05:39 -07:00 committed by GitHub
parent eefff8497a
commit 54b888bb08
10 changed files with 180 additions and 11 deletions

View File

@ -7,14 +7,14 @@
"""
from flask import current_app
from marshmallow import fields, validates_schema
from marshmallow import fields, validates_schema, pre_load
from marshmallow import validate
from marshmallow.exceptions import ValidationError
from lemur.schemas import PluginInputSchema, PluginOutputSchema, ExtensionSchema, AssociatedAuthoritySchema, AssociatedRoleSchema
from lemur.users.schemas import UserNestedOutputSchema
from lemur.common.schema import LemurInputSchema, LemurOutputSchema
from lemur.common import validators
from lemur.common import validators, missing
class AuthorityInputSchema(LemurInputSchema):
@ -60,6 +60,10 @@ class AuthorityInputSchema(LemurInputSchema):
if not data.get('parent'):
raise ValidationError("If generating a subca parent 'authority' must be specified.")
@pre_load
def ensure_dates(self, data):
return missing.dates(data)
class AuthorityUpdateSchema(LemurInputSchema):
owner = fields.Email(required=True)

View File

@ -44,9 +44,17 @@ def mint(**kwargs):
Creates the authority based on the plugin provided.
"""
issuer = kwargs['plugin']['plugin_object']
body, chain, roles = issuer.create_authority(kwargs)
values = issuer.create_authority(kwargs)
# support older plugins
if len(values) == 3:
body, chain, roles = values
private_key = None
elif len(values) == 4:
body, private_key, chain, roles = values
roles = create_authority_roles(roles, kwargs['owner'], kwargs['plugin']['plugin_object'].title)
return body, chain, roles
return body, private_key, chain, roles
def create_authority_roles(roles, owner, plugin_title):
@ -88,9 +96,10 @@ def create(**kwargs):
Creates a new authority.
"""
kwargs['creator'] = g.user.email
body, chain, roles = mint(**kwargs)
body, private_key, chain, roles = mint(**kwargs)
kwargs['body'] = body
kwargs['private_key'] = private_key
kwargs['chain'] = chain
if kwargs.get('roles'):
@ -172,6 +181,9 @@ def render(args):
# we make sure that a user can only use an authority they either own are are a member of - admins can see all
if not g.current_user.is_admin:
authority_ids = []
for authority in g.current_user.authorities:
authority_ids.append(authority.id)
for role in g.current_user.roles:
for authority in role.authorities:
authority_ids.append(authority.id)

View File

@ -6,7 +6,7 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
from flask import current_app
from marshmallow import fields, validates_schema, post_load
from marshmallow import fields, validates_schema, post_load, pre_load
from marshmallow.exceptions import ValidationError
from lemur.schemas import AssociatedAuthoritySchema, AssociatedDestinationSchema, AssociatedCertificateSchema, \
@ -20,7 +20,7 @@ from lemur.domains.schemas import DomainNestedOutputSchema
from lemur.users.schemas import UserNestedOutputSchema
from lemur.common.schema import LemurInputSchema, LemurOutputSchema
from lemur.common import validators
from lemur.common import validators, missing
from lemur.notifications import service as notification_service
@ -68,6 +68,10 @@ class CertificateInputSchema(CertificateSchema):
def validate_dates(self, data):
validators.dates(data)
@pre_load
def ensure_dates(self, data):
return missing.dates(data)
class CertificateEditInputSchema(CertificateSchema):
active = fields.Boolean()

13
lemur/common/missing.py Normal file
View File

@ -0,0 +1,13 @@
import arrow
def dates(data):
# ensure that validity_start and validity_end are always set
if not(data.get('validity_start') and data.get('validity_end')):
if data.get('validity_years'):
num_years = data['validity_years']
now = arrow.utcnow()
then = now.replace(years=+int(num_years))
data['validity_start'] = now.isoformat()
data['validity_end'] = then.isoformat()

View File

@ -94,9 +94,6 @@ def dates(data):
if not data.get('validity_end') and data.get('validity_start'):
raise ValidationError('If validity end is specified so must validity start.')
if data.get('validity_end') and data.get('validity_years'):
raise ValidationError('Cannot specify both validity end and validity years.')
if data.get('validity_start') and data.get('validity_end'):
if not data['validity_start'] < data['validity_end']:
raise ValidationError('Validity start must be before validity end.')

View File

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

View File

@ -0,0 +1,132 @@
"""
.. module: lemur.plugins.lemur_cryptography.plugin
:platform: Unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
import uuid
from flask import current_app
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from lemur.plugins.bases import IssuerPlugin
from lemur.plugins import lemur_cryptography as cryptography_issuer
def build_root_certificate(options):
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
subject = issuer = x509.Name([
x509.NameAttribute(x509.OID_COUNTRY_NAME, options['country']),
x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, options['state']),
x509.NameAttribute(x509.OID_LOCALITY_NAME, options['location']),
x509.NameAttribute(x509.OID_ORGANIZATION_NAME, options['organization']),
x509.NameAttribute(x509.OID_COMMON_NAME, options['organizational_unit'])
])
builder = x509.CertificateBuilder(
subject_name=subject,
issuer_name=issuer,
public_key=private_key.public_key(),
not_valid_after=options['validity_end'],
not_valid_before=options['validity_start'],
serial_number=options['first_serial']
)
builder.add_extension(x509.SubjectAlternativeName([x509.DNSName(options['common_name'])]), critical=False)
cert = builder.sign(private_key, hashes.SHA256(), default_backend())
cert_pem = cert.public_bytes(
encoding=serialization.Encoding.PEM
)
private_key_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL, # would like to use PKCS8 but AWS ELBs don't like it
encryption_algorithm=serialization.NoEncryption()
)
return cert_pem, private_key_pem
def issue_certificate(csr, options):
csr = x509.load_pem_x509_csr(csr, default_backend())
builder = x509.CertificateBuilder(
issuer_name=x509.Name([
x509.NameAttribute(
x509.OID_ISSUER_ALTERNATIVE_NAME,
options['authority'].authority_certificate.issuer
)]
),
subject_name=csr.subject,
public_key=csr.public_key(),
not_valid_before=options['validity_start'],
not_valid_after=options['validity_end'],
extensions=csr.extensions)
# TODO figure out a better way to increment serial
builder = builder.serial_number(int(uuid.uuid4()))
private_key = serialization.load_pem_private_key(
options['authority'].authority_certificate.private_key,
password=None,
backend=default_backend()
)
cert = builder.sign(private_key, hashes.SHA256(), default_backend())
return cert.public_bytes(
encoding=serialization.Encoding.PEM
)
class CryptographyIssuerPlugin(IssuerPlugin):
title = 'Cryptography'
slug = 'cryptography-issuer'
description = 'Enables the creation and signing of self-signed certificates'
version = cryptography_issuer.VERSION
author = 'Kevin Glisson'
author_url = 'https://github.com/netflix/lemur.git'
def create_certificate(self, csr, options):
"""
Creates a certificate.
:param csr:
:param options:
:return: :raise Exception:
"""
current_app.logger.debug("Issuing new cryptography certificate with options: {0}".format(options))
cert = issue_certificate(csr, options)
return cert, ""
@staticmethod
def create_authority(options):
"""
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:
"""
current_app.logger.debug("Issuing new cryptography authority with options: {0}".format(options))
cert, private_key = build_root_certificate(options)
roles = [
{'username': '', 'password': '', 'name': options['name'] + '_admin'},
{'username': '', 'password': '', 'name': options['name'] + '_operator'}
]
return cert, private_key, "", roles

View File

@ -0,0 +1 @@
from lemur.tests.conftest import * # noqa

View File

@ -178,7 +178,8 @@ setup(
'java_keystore_export = lemur.plugins.lemur_java.plugin:JavaKeystoreExportPlugin',
'openssl_export = lemur.plugins.lemur_openssl.plugin:OpenSSLExportPlugin',
'atlas_metric = lemur.plugins.lemur_atlas.plugin:AtlasMetricPlugin',
'kubernetes_destination = lemur.plugins.lemur_kubernetes.plugin:KubernetesDestinationPlugin'
'kubernetes_destination = lemur.plugins.lemur_kubernetes.plugin:KubernetesDestinationPlugin',
'cryptography_issuer = lemur.plugins.lemur_cryptography.plugin:CryptographyIssuerPlugin',
],
},
classifiers=[