Adding a toy certificate authority. (#378)
This commit is contained in:
parent
eefff8497a
commit
54b888bb08
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
13
lemur/common/missing.py
Normal 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()
|
@ -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.')
|
||||
|
5
lemur/plugins/lemur_cryptography/__init__.py
Normal file
5
lemur/plugins/lemur_cryptography/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
try:
|
||||
VERSION = __import__('pkg_resources') \
|
||||
.get_distribution(__name__).version
|
||||
except Exception as e:
|
||||
VERSION = 'unknown'
|
132
lemur/plugins/lemur_cryptography/plugin.py
Normal file
132
lemur/plugins/lemur_cryptography/plugin.py
Normal 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
|
1
lemur/plugins/lemur_cryptography/tests/conftest.py
Normal file
1
lemur/plugins/lemur_cryptography/tests/conftest.py
Normal file
@ -0,0 +1 @@
|
||||
from lemur.tests.conftest import * # noqa
|
3
setup.py
3
setup.py
@ -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=[
|
||||
|
Loading…
Reference in New Issue
Block a user