[WIP] - 422 elb rotate (#493)

* Initial work on certificate rotation.

* Adding ability to get additional certificate info.

* - Adding endpoint rotation.
- Removes the g requirement from all services to enable easier testing.
This commit is contained in:
kevgliss 2016-11-18 11:27:46 -08:00 committed by GitHub
parent 6fd47edbe3
commit d45e7d6b85
27 changed files with 393 additions and 390 deletions

View File

@ -8,8 +8,6 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
from flask import g
from lemur import database
from lemur.extensions import metrics
from lemur.authorities.models import Authority
@ -53,13 +51,14 @@ def mint(**kwargs):
elif len(values) == 4:
body, private_key, chain, roles = values
roles = create_authority_roles(roles, kwargs['owner'], kwargs['plugin']['plugin_object'].title)
roles = create_authority_roles(roles, kwargs['owner'], kwargs['plugin']['plugin_object'].title, None)
return body, private_key, chain, roles
def create_authority_roles(roles, owner, plugin_title):
def create_authority_roles(roles, owner, plugin_title, creator):
"""
Creates all of the necessary authority roles.
:param creator:
:param roles:
:return:
"""
@ -75,7 +74,7 @@ def create_authority_roles(roles, owner, plugin_title):
# the user creating the authority should be able to administer it
if role.username == 'admin':
g.current_user.roles.append(role)
creator.roles.append(role)
role_objs.append(role)
@ -95,10 +94,9 @@ def create(**kwargs):
"""
Creates a new authority.
"""
kwargs['creator'] = g.user.email
body, private_key, chain, roles = mint(**kwargs)
g.user.roles = list(set(list(g.user.roles) + roles))
kwargs['creator'].roles = list(set(list(kwargs['creator'].roles) + roles))
kwargs['body'] = body
kwargs['private_key'] = private_key
@ -114,7 +112,7 @@ def create(**kwargs):
authority = Authority(**kwargs)
authority = database.create(authority)
g.user.authorities.append(authority)
kwargs['creator'].authorities.append(authority)
metrics.send('authority_created', 'counter', 1, metric_tags=dict(owner=authority.owner))
return authority
@ -151,14 +149,14 @@ def get_by_name(authority_name):
return database.get(Authority, authority_name, field='name')
def get_authority_role(ca_name):
def get_authority_role(ca_name, creator):
"""
Attempts to get the authority role for a given ca uses current_user
as a basis for accomplishing that.
:param ca_name:
"""
if g.current_user.is_admin:
if creator.is_admin:
return role_service.get_by_name("{0}_admin".format(ca_name))
else:
return role_service.get_by_name("{0}_operator".format(ca_name))
@ -181,12 +179,12 @@ def render(args):
query = database.filter(query, Authority, terms)
# 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:
if not args['user'].is_admin:
authority_ids = []
for authority in g.current_user.authorities:
for authority in args['user'].authorities:
authority_ids.append(authority.id)
for role in g.current_user.roles:
for role in args['user'].roles:
for authority in role.authorities:
authority_ids.append(authority.id)
query = query.filter(Authority.id.in_(authority_ids))

View File

@ -5,7 +5,7 @@
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
from flask import Blueprint
from flask import Blueprint, g
from flask.ext.restful import reqparse, Api
from lemur.common.utils import paginated_parser
@ -107,6 +107,7 @@ class AuthoritiesList(AuthenticatedResource):
"""
parser = paginated_parser.copy()
args = parser.parse_args()
args['user'] = g.current_user
return service.render(args)
@validate_schema(authority_input_schema, authority_output_schema)

View File

@ -5,16 +5,16 @@
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
import datetime
import arrow
import lemur.common.utils
from flask import current_app
from sqlalchemy.orm import relationship
from sqlalchemy.sql.expression import case
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy import event, Integer, ForeignKey, String, DateTime, PassiveDefault, func, Column, Text, Boolean
from sqlalchemy import event, Integer, ForeignKey, String, PassiveDefault, func, Column, Text, Boolean
import lemur.common.utils
from lemur.database import db
from lemur.models import certificate_associations, certificate_source_associations, \
certificate_destination_associations, certificate_notification_associations, \
@ -22,6 +22,8 @@ from lemur.models import certificate_associations, certificate_source_associatio
from lemur.plugins.base import plugins
from lemur.utils import Vault
from sqlalchemy_utils.types.arrow import ArrowType
from lemur.common import defaults
from lemur.domains.models import Domain
@ -53,9 +55,9 @@ class Certificate(db.Model):
cn = Column(String(128))
deleted = Column(Boolean, index=True)
not_before = Column(DateTime)
not_after = Column(DateTime)
date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False)
not_before = Column(ArrowType)
not_after = Column(ArrowType)
date_created = Column(ArrowType, PassiveDefault(func.now()), nullable=False)
signing_algorithm = Column(String(128))
status = Column(String(128))
@ -120,16 +122,41 @@ class Certificate(db.Model):
def active(self):
return self.notify
@property
def organization(self):
cert = lemur.common.utils.parse_certificate(self.body)
return defaults.organization(cert)
@property
def organizational_unit(self):
cert = lemur.common.utils.parse_certificate(self.body)
return defaults.organizational_unit(cert)
@property
def country(self):
cert = lemur.common.utils.parse_certificate(self.body)
return defaults.country(cert)
@property
def state(self):
cert = lemur.common.utils.parse_certificate(self.body)
return defaults.state(cert)
@property
def location(self):
cert = lemur.common.utils.parse_certificate(self.body)
return defaults.location(cert)
@hybrid_property
def expired(self):
if self.not_after <= datetime.datetime.now():
if self.not_after <= arrow.utcnow():
return True
@expired.expression
def expired(cls):
return case(
[
(cls.now_after <= datetime.datetime.now(), True)
(cls.now_after <= arrow.utcnow(), True)
],
else_=False
)

View File

@ -8,7 +8,7 @@
import arrow
from sqlalchemy import func, or_
from flask import g, current_app
from flask import current_app
from lemur import database
from lemur.extensions import metrics
@ -201,11 +201,7 @@ def upload(**kwargs):
cert = database.create(cert)
try:
g.user.certificates.append(cert)
except AttributeError:
current_app.logger.debug("No user to associate uploaded certificate to.")
kwargs['creator'].certificates.append(cert)
return database.update(cert)
@ -213,7 +209,6 @@ def create(**kwargs):
"""
Creates a new certificate.
"""
kwargs['creator'] = g.user.email
cert_body, private_key, cert_chain = mint(**kwargs)
kwargs['body'] = cert_body
kwargs['private_key'] = private_key
@ -228,7 +223,7 @@ def create(**kwargs):
cert = Certificate(**kwargs)
g.user.certificates.append(cert)
kwargs['creator'].certificates.append(cert)
cert.authority = kwargs['authority']
database.commit()
@ -286,10 +281,10 @@ def render(args):
query = database.filter(query, Certificate, terms)
if show:
sub_query = database.session_query(Role.name).filter(Role.user_id == g.user.id).subquery()
sub_query = database.session_query(Role.name).filter(Role.user_id == args['user'].id).subquery()
query = query.filter(
or_(
Certificate.user_id == g.user.id,
Certificate.user_id == args['user'].id,
Certificate.owner.in_(sub_query)
)
)
@ -476,10 +471,9 @@ def calculate_reissue_range(start, end):
new_start = arrow.utcnow().date()
new_end = new_start + span
return new_start, new_end
return new_start, arrow.get(new_end)
# TODO pull the OU, O, CN, etc + other extensions.
def get_certificate_primitives(certificate):
"""
Retrieve key primitive from a certificate such that the certificate
@ -491,6 +485,7 @@ def get_certificate_primitives(certificate):
start, end = calculate_reissue_range(certificate.not_before, certificate.not_after)
names = [{'name_type': 'DNSName', 'value': x.name} for x in certificate.domains]
# TODO pull additional extensions
extensions = {
'sub_alt_names': {
'names': names
@ -506,5 +501,31 @@ def get_certificate_primitives(certificate):
destinations=certificate.destinations,
roles=certificate.roles,
extensions=extensions,
owner=certificate.owner
owner=certificate.owner,
organization=certificate.organization,
organizational_unit=certificate.organizational_unit,
country=certificate.country,
state=certificate.state,
location=certificate.location
)
def reissue_certificate(certificate, replace=None, user=None):
"""
Reissue certificate with the same properties of the given certificate.
:param certificate:
:return:
"""
primitives = get_certificate_primitives(certificate)
if not user:
primitives['creator'] = certificate.user
else:
primitives['creator'] = user
new_cert = create(**primitives)
if replace:
certificate.notify = False
return new_cert

View File

@ -8,7 +8,7 @@
import base64
from builtins import str
from flask import Blueprint, make_response, jsonify
from flask import Blueprint, make_response, jsonify, g
from flask.ext.restful import reqparse, Api
from lemur.common.schema import validate_schema
@ -129,6 +129,7 @@ class CertificatesList(AuthenticatedResource):
parser.add_argument('show', type=str, location='args')
args = parser.parse_args()
args['user'] = g.user
return service.render(args)
@validate_schema(certificate_input_schema, certificate_output_schema)
@ -265,6 +266,7 @@ class CertificatesList(AuthenticatedResource):
authority_permission = AuthorityPermission(data['authority'].id, roles)
if authority_permission.can():
data['creator'] = g.user
return service.create(**data)
return dict(message="You are not authorized to use {0}".format(data['authority'].name)), 403
@ -371,6 +373,7 @@ class CertificatesUpload(AuthenticatedResource):
"""
if data.get('destinations'):
if data.get('private_key'):
data['creator'] = g.user
return service.upload(**data)
else:
raise Exception("Private key must be provided in order to upload certificate to AWS")
@ -740,6 +743,7 @@ class NotificationCertificatesList(AuthenticatedResource):
args = parser.parse_args()
args['notification_id'] = notification_id
args['user'] = g.current_user
return service.render(args)

View File

@ -58,6 +58,61 @@ def common_name(cert):
)[0].value.strip()
def organization(cert):
"""
Attempt to get the organization name from a given certificate.
:param cert:
:return:
"""
return cert.subject.get_attributes_for_oid(
x509.OID_ORGANIZATION_NAME
)[0].value.strip()
def organizational_unit(cert):
"""
Attempt to get the organization unit from a given certificate.
:param cert:
:return:
"""
return cert.subject.get_attributes_for_oid(
x509.OID_ORGANIZATIONAL_UNIT_NAME
)[0].value.strip()
def country(cert):
"""
Attempt to get the country from a given certificate.
:param cert:
:return:
"""
return cert.subject.get_attributes_for_oid(
x509.OID_COUNTRY_NAME
)[0].value.strip()
def state(cert):
"""
Attempt to get the from a given certificate.
:param cert:
:return:
"""
return cert.subject.get_attributes_for_oid(
x509.OID_STATE_OR_PROVINCE_NAME
)[0].value.strip()
def location(cert):
"""
Attempt to get the location name from a given certificate.
:param cert:
:return:
"""
return cert.subject.get_attributes_for_oid(
x509.OID_LOCALITY_NAME
)[0].value.strip()
def domains(cert):
"""
Attempts to get an domains listed in a certificate.

View File

@ -274,6 +274,9 @@ def sort_and_page(query, model, args):
page = args.pop('page')
count = args.pop('count')
if args.get('user'):
user = args.pop('user')
query = find_all(query, model, args)
if sort_by and sort_dir:

View File

@ -8,6 +8,8 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
from flask import current_app
from lemur import database
from lemur.extensions import metrics
from lemur.endpoints.models import Endpoint, Policy, Cipher
@ -96,6 +98,17 @@ def update(endpoint_id, **kwargs):
return endpoint
def rotate_certificate(endpoint, new_cert):
"""Rotates a certificate on a given endpoint."""
try:
endpoint.source.plugin.update_endpoint(endpoint, new_cert)
endpoint.certificate = new_cert
except Exception as e:
metrics.send('rotate_failure', 'counter', 1, tags={'endpoint': endpoint.name})
current_app.logger.exception(e)
raise e
def render(args):
"""
Helper that helps us render the REST Api responses.

View File

@ -5,7 +5,7 @@
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
from flask import Blueprint
from flask import Blueprint, g
from flask.ext.restful import reqparse, Api
from lemur.common.utils import paginated_parser
@ -63,6 +63,7 @@ class EndpointsList(AuthenticatedResource):
"""
parser = paginated_parser.copy()
args = parser.parse_args()
args['user'] = g.current_user
return service.render(args)

View File

@ -1,5 +1,6 @@
from __future__ import unicode_literals # at top of module
import arrow
from datetime import datetime, timedelta
from collections import Counter
@ -28,11 +29,7 @@ from lemur.certificates import service as cert_service
from lemur.authorities import service as authority_service
from lemur.notifications import service as notification_service
from lemur.certificates.service import get_name_from_arn
from lemur.certificates.verify import verify_string
from lemur.plugins.lemur_aws import elb
from lemur.sources import service as source_service
from lemur import create_app
@ -487,253 +484,122 @@ def unicode_(data):
return data
class RotateELBs(Command):
def print_certificate_details(details):
"""
Rotates existing certificates to a new one on an ELB
Print the certificate details with formatting.
:param details:
:return:
"""
option_list = (
Option('-e', '--elb-list', dest='elb_list', required=True),
Option('-p', '--chain-path', dest='chain_path'),
Option('-c', '--cert-name', dest='cert_name'),
Option('-a', '--cert-prefix', dest='cert_prefix'),
Option('-d', '--description', dest='description')
sys.stdout.write("[+] Re-issuing certificate with the following details: \n")
sys.stdout.write(
"[+] Common Name: {common_name}\n"
"[+] Subject Alternate Names: {sans}\n"
"[+] Authority: {authority_name}\n"
"[+] Validity Start: {validity_start}\n"
"[+] Validity End: {validity_end}\n"
"[+] Organization: {organization}\n"
"[+] Organizational Unit: {organizational_unit}\n"
"[+] Country: {country}\n"
"[+] State: {state}\n"
"[+] Location: {location}\n".format(
common_name=details['common_name'],
sans=",".join(x['value'] for x in details['extensions']['sub_alt_names']['names']),
authority_name=details['authority'].name,
validity_start=details['validity_start'].isoformat(),
validity_end=details['validity_end'].isoformat(),
organization=details['organization'],
organizational_unit=details['organizational_unit'],
country=details['country'],
state=details['state'],
location=details['location']
)
)
def run(self, elb_list, chain_path, cert_name, cert_prefix, description):
for e in open(elb_list, 'r').readlines():
elb_name, account_id, region, from_port, to_port, protocol = e.strip().split(',')
if cert_name:
arn = "arn:aws:iam::{0}:server-certificate/{1}".format(account_id, cert_name)
else:
# if no cert name is provided we need to discover it
listeners = elb.get_listeners(account_id, region, elb_name)
# get the listener we care about
for listener in listeners:
if listener[0] == int(from_port) and listener[1] == int(to_port):
arn = listener[4]
name = get_name_from_arn(arn)
certificate = cert_service.get_by_name(name)
break
else:
sys.stdout.write("[-] Could not find ELB {0}".format(elb_name))
continue
if not certificate:
sys.stdout.write("[-] Could not find certificate {0} in Lemur".format(name))
continue
dests = []
for d in certificate.destinations:
dests.append({'id': d.id})
nots = []
for n in certificate.notifications:
nots.append({'id': n.id})
new_certificate = database.clone(certificate)
if cert_prefix:
new_certificate.name = "{0}-{1}".format(cert_prefix, new_certificate.name)
new_certificate.chain = open(chain_path, 'r').read()
new_certificate.description = "{0} - {1}".format(new_certificate.description, description)
new_certificate = database.create(new_certificate)
database.update_list(new_certificate, 'destinations', Destination, dests)
database.update_list(new_certificate, 'notifications', Notification, nots)
database.update(new_certificate)
arn = new_certificate.get_arn(account_id)
elb.update_listeners(account_id, region, elb_name, [(from_port, to_port, protocol, arn)], [from_port])
sys.stdout.write("[+] Updated {0} to use {1}\n".format(elb_name, new_certificate.name))
class ProvisionELB(Command):
class RotateCertificate(Command):
"""
Creates and provisions a certificate on an ELB based on command line arguments
Rotates certificate on all endpoints managed by Lemur.
"""
option_list = (
Option('-d', '--dns', dest='dns', action='append', required=True, type=unicode_),
Option('-e', '--elb', dest='elb_name', required=True, type=unicode_),
Option('-o', '--owner', dest='owner', type=unicode_),
Option('-a', '--authority', dest='authority', required=True, type=unicode_),
Option('-s', '--description', dest='description', default=u'Command line provisioned keypair', type=unicode_),
Option('-t', '--destination', dest='destinations', action='append', type=unicode_, required=True),
Option('-n', '--notification', dest='notifications', action='append', type=unicode_, default=[]),
Option('-r', '--region', dest='region', default=u'us-east-1', type=unicode_),
Option('-p', '--dport', '--port', dest='dport', default=7002),
Option('--src-port', '--source-port', '--sport', dest='sport', default=443),
Option('--dry-run', dest='dryrun', action='store_true')
Option('-n', '--cert-name', dest='new_cert_name'),
Option('-o', '--old-cert-name', dest='old_cert_name', required=True),
Option('-c', '--commit', dest='commit', action='store_true', default=False)
)
def configure_user(self, owner):
from flask import g
import lemur.users.service
def run(self, new_cert_name, old_cert_name, commit):
from lemur.certificates.service import get_by_name, reissue_certificate, get_certificate_primitives
from lemur.endpoints.service import rotate_certificate
# grab the user
g.user = lemur.users.service.get_by_username(owner)
# get the first user by default
if not g.user:
g.user = lemur.users.service.get_all()[0]
old_cert = get_by_name(old_cert_name)
return g.user.username
def build_cert_options(self, destinations, notifications, description, owner, dns, authority):
from sqlalchemy.orm.exc import NoResultFound
from lemur.certificates.views import valid_authority
import sys
# convert argument lists to arrays, or empty sets
destinations = self.get_destinations(destinations)
if not destinations:
sys.stderr.write("Valid destinations provided\n")
if not old_cert:
sys.stdout.write("[-] No certificate found with name: {0}\n".format(old_cert_name))
sys.exit(1)
# get the primary CN
common_name = dns[0]
if new_cert_name:
new_cert = get_by_name(new_cert_name)
# If there are more than one fqdn, add them as alternate names
extensions = {}
if len(dns) > 1:
extensions['subAltNames'] = {'names': map(lambda x: {'nameType': 'DNSName', 'value': x}, dns)}
try:
authority = valid_authority({"name": authority})
except NoResultFound:
sys.stderr.write("Invalid authority specified: '{}'\naborting\n".format(authority))
if not new_cert:
sys.stdout.write("[-] No certificate found with name: {0}\n".format(old_cert_name))
sys.exit(1)
options = {
# Convert from the Destination model to the JSON input expected further in the code
'destinations': map(lambda x: {'id': x.id, 'label': x.label}, destinations),
'description': description,
'notifications': notifications,
'commonName': common_name,
'extensions': extensions,
'authority': authority,
'owner': owner,
# defaults:
'organization': current_app.config.get('LEMUR_DEFAULT_ORGANIZATION'),
'organizationalUnit': current_app.config.get('LEMUR_DEFAULT_ORGANIZATIONAL_UNIT'),
'country': current_app.config.get('LEMUR_DEFAULT_COUNTRY'),
'state': current_app.config.get('LEMUR_DEFAULT_STATE'),
'location': current_app.config.get('LEMUR_DEFAULT_LOCATION')
}
if commit:
sys.stdout.write("[!] Running in COMMIT mode.\n")
return options
if not new_cert_name:
sys.stdout.write("[!] No new certificate provided. Attempting to re-issue old certificate: {0}.\n".format(old_cert_name))
def get_destinations(self, destination_names):
from lemur.destinations import service
details = get_certificate_primitives(old_cert)
print_certificate_details(details)
destinations = []
if commit:
new_cert = reissue_certificate(old_cert, replace=True)
sys.stdout.write("[+] Issued new certificate named: {0}\n".format(new_cert.name))
for destination_name in destination_names:
destination = service.get_by_label(destination_name)
sys.stdout.write("[+] Done! \n")
if not destination:
sys.stderr.write("Invalid destination specified: '{}'\nAborting...\n".format(destination_name))
sys.exit(1)
if len(old_cert.endpoints) > 0:
for endpoint in old_cert.endpoints:
sys.stdout.write("[+] Certificate found deployed onto {0}\n ".format(endpoint.name))
sys.stdout.write("[+] Rotating certificate from: {0} to: {1}\n ".format(old_cert_name, new_cert.name))
destinations.append(service.get_by_label(destination_name))
if commit:
rotate_certificate(endpoint, new_cert_name)
return destinations
def check_duplicate_listener(self, elb_name, region, account, sport, dport):
from lemur.plugins.lemur_aws import elb
listeners = elb.get_listeners(account, region, elb_name)
for listener in listeners:
if listener[0] == sport and listener[1] == dport:
return True
return False
def get_destination_account(self, destinations):
for destination in self.get_destinations(destinations):
if destination.plugin_name == 'aws-destination':
account_number = destination.plugin.get_option('accountNumber', destination.options)
return account_number
sys.stderr.write("No destination AWS account provided, failing\n")
sys.exit(1)
def run(self, dns, elb_name, owner, authority, description, notifications, destinations, region, dport, sport,
dryrun):
from lemur.certificates import service
from lemur.plugins.lemur_aws import elb
from boto.exception import BotoServerError
# configure the owner if we can find it, or go for default, and put it in the global
owner = self.configure_user(owner)
# make a config blob from the command line arguments
cert_options = self.build_cert_options(
destinations=destinations,
notifications=notifications,
description=description,
owner=owner,
dns=dns,
authority=authority)
aws_account = self.get_destination_account(destinations)
if dryrun:
import json
cert_options['authority'] = cert_options['authority'].name
sys.stdout.write('Will create certificate using options: {}\n'
.format(json.dumps(cert_options, sort_keys=True, indent=2)))
sys.stdout.write('Will create listener {}->{} HTTPS using the new certificate to elb {}\n'
.format(sport, dport, elb_name))
sys.exit(0)
if self.check_duplicate_listener(elb_name, region, aws_account, sport, dport):
sys.stderr.write("ELB {} already has a listener {}->{}\nAborting...\n".format(elb_name, sport, dport))
sys.exit(1)
# create the certificate
try:
sys.stdout.write('Creating certificate for {}\n'.format(cert_options['commonName']))
cert = service.create(**cert_options)
except Exception as e:
if e.message == 'Duplicate certificate: a certificate with the same common name exists already':
sys.stderr.write("Certificate already exists named: {}\n".format(dns[0]))
sys.exit(1)
raise e
cert_arn = cert.get_arn(aws_account)
sys.stderr.write('cert arn: {}\n'.format(cert_arn))
sys.stderr.write('Configuring elb {} from port {} to port {} in region {} with cert {}\n'
.format(elb_name, sport, dport, region, cert_arn))
delay = 1
done = False
retries = 5
while not done and retries > 0:
try:
elb.create_new_listeners(aws_account, region, elb_name, [(sport, dport, 'HTTPS', cert_arn)])
except BotoServerError as bse:
# if the server returns ad error, the certificate
if bse.error_code == 'CertificateNotFound':
sys.stderr.write('Certificate not available yet in the AWS account, waiting {}, {} retries left\n'
.format(delay, retries))
time.sleep(delay)
delay *= 2
retries -= 1
elif bse.error_code == 'DuplicateListener':
sys.stderr.write('ELB {} already has a listener {}->{}'.format(elb_name, sport, dport))
sys.exit(1)
sys.stdout.write("[+] Done! \n")
else:
raise bse
else:
done = True
sys.stdout.write("[!] Certificate not found on any existing endpoints. Nothing to rotate.\n")
class ReissueCertificate(Command):
"""
Reissues a certificate based on a given certificate.
"""
option_list = (
Option('-o', '--old-cert-name', dest='old_cert_name', required=True),
Option('-c', '--commit', dest='commit', action='store_true', default=False)
)
def run(self, old_cert_name, commit):
from lemur.certificates.service import get_by_name, reissue_certificate, get_certificate_primitives
old_cert = get_by_name(old_cert_name)
if not old_cert:
sys.stdout.write("[-] No certificate found with name: {0}\n".format(old_cert_name))
sys.exit(1)
if commit:
sys.stdout.write("[!] Running in COMMIT mode.\n")
details = get_certificate_primitives(old_cert)
print_certificate_details(details)
if commit:
new_cert = reissue_certificate(old_cert, replace=True)
sys.stdout.write("[+] Issued new certificate named: {0}\n".format(new_cert.name))
sys.stdout.write("[+] Done! \n")
@manager.command
@ -791,11 +657,33 @@ class Report(Command):
)
def run(self, name, duration):
end = datetime.utcnow()
start = end - timedelta(days=duration)
if name == 'authority':
self.certificates_issued(name, start, end)
elif name == 'activeFQDNS':
self.active_fqdns()
@staticmethod
def active_fqdns():
"""
Generates a report that gives the number of active fqdns, but root domain.
:return:
"""
from lemur.certificates.service import get_all_certs
sys.stdout.write("FQDN, Root Domain, Issuer, Total Length (days), Time until expiration (days)\n")
for cert in get_all_certs():
if not cert.expired:
now = arrow.utcnow()
ttl = now - cert.not_before
total_length = cert.not_after - cert.not_before
for fqdn in cert.domains:
root_domain = ".".join(fqdn.name.split('.')[-2:])
sys.stdout.write(", ".join([fqdn.name, root_domain, cert.issuer, str(total_length.days), str(ttl.days)]) + "\n")
@staticmethod
def certificates_issued(name=None, start=None, end=None):
"""
@ -938,10 +826,10 @@ def main():
manager.add_command("create_user", CreateUser())
manager.add_command("reset_password", ResetPassword())
manager.add_command("create_role", CreateRole())
manager.add_command("provision_elb", ProvisionELB())
manager.add_command("rotate_elbs", RotateELBs())
manager.add_command("sources", Sources())
manager.add_command("report", Report())
manager.add_command("rotate_certificate", RotateCertificate())
manager.add_command("reissue_certificate", ReissueCertificate())
manager.run()
if __name__ == "__main__":

View File

@ -103,6 +103,7 @@ def describe_load_balancer_types(policies, **kwargs):
return kwargs['client'].describe_load_balancer_policy_types(PolicyTypeNames=policies)
@sts_client('elb')
def attach_certificate(account_number, region, name, port, certificate_id):
"""
Attaches a certificate to a listener, throws exception
@ -115,69 +116,3 @@ def attach_certificate(account_number, region, name, port, certificate_id):
:param certificate_id:
"""
return assume_service(account_number, 'elb', region).set_lb_listener_SSL_certificate(name, port, certificate_id)
# def create_new_listeners(account_number, region, name, listeners=None):
# """
# Creates a new listener and attaches it to the ELB.
#
# :param account_number:
# :param region:
# :param name:
# :param listeners:
# :return:
# """
# listeners = [is_valid(x) for x in listeners]
# return assume_service(account_number, 'elb', region).create_load_balancer_listeners(name, listeners=listeners)
#
#
# def update_listeners(account_number, region, name, listeners, ports):
# """
# We assume that a listener with a specified port already exists. We can then
# delete the old listener on the port and create a new one in it's place.
#
# If however we are replacing a listener e.g. changing a port from 80 to 443 we need
# to make sure we kept track of which ports we needed to delete so that we don't create
# two listeners (one 80 and one 443)
#
# :param account_number:
# :param region:
# :param name:
# :param listeners:
# :param ports:
# """
# # you cannot update a listeners port/protocol instead we remove the only one and
# # create a new one in it's place
# listeners = [is_valid(x) for x in listeners]
#
# assume_service(account_number, 'elb', region).delete_load_balancer_listeners(name, ports)
# return create_new_listeners(account_number, region, name, listeners=listeners)
#
#
# def delete_listeners(account_number, region, name, ports):
# """
# Deletes a listener from an ELB.
#
# :param account_number:
# :param region:
# :param name:
# :param ports:
# :return:
# """
# return assume_service(account_number, 'elb', region).delete_load_balancer_listeners(name, ports)
#
#
# def get_listeners(account_number, region, name):
# """
# Gets the listeners configured on an elb and returns a array of tuples
#
# :param account_number:
# :param region:
# :param name:
# :return: list of tuples
# """
#
# conn = assume_service(account_number, 'elb', region)
# elbs = conn.get_all_load_balancers(load_balancer_names=[name])
# if elbs:
# return elbs[0].listeners

View File

@ -80,6 +80,20 @@ def get_cert_from_arn(arn):
return digest_aws_cert_response(response)
def create_arn_from_cert(account_number, region, certificate_name):
"""
Create an ARN from a certificate.
:param account_number:
:param region:
:param certificate_name:
:return:
"""
return "arn:aws:iam:{region}:{account_number}:{certificate_name}".format(
region=region,
account_number=account_number,
certificate_name=certificate_name)
def digest_aws_cert_response(response):
"""
Processes an AWS certifcate response and retrieves the certificate body and chain.

View File

@ -36,9 +36,7 @@ from flask import current_app
from boto.exception import BotoServerError
from lemur.plugins.bases import DestinationPlugin, SourcePlugin
from lemur.plugins.lemur_aws.ec2 import get_regions
from lemur.plugins.lemur_aws.elb import get_all_elbs, describe_load_balancer_policies, attach_certificate
from lemur.plugins.lemur_aws import iam, s3
from lemur.plugins.lemur_aws import iam, s3, elb, ec2
from lemur.plugins import lemur_aws as aws
@ -77,7 +75,10 @@ class AWSDestinationPlugin(DestinationPlugin):
e = self.get_option('elb', options)
if e:
attach_certificate(kwargs['accountNumber'], ['region'], e['name'], e['port'], e['certificateId'])
iam.attach_certificate(kwargs['accountNumber'], ['region'], e['name'], e['port'], e['certificateId'])
def deploy(self, elb_name, account, region, certificate):
pass
class AWSSourcePlugin(SourcePlugin):
@ -124,15 +125,15 @@ class AWSSourcePlugin(SourcePlugin):
regions = self.get_option('regions', options)
if not regions:
regions = get_regions(account_number=account_number)
regions = ec2.get_regions(account_number=account_number)
else:
regions = regions.split(',')
for region in regions:
elbs = get_all_elbs(account_number=account_number, region=region)
elbs = elb.get_all_elbs(account_number=account_number, region=region)
current_app.logger.info("Describing load balancers in {0}-{1}".format(account_number, region))
for elb in elbs:
for listener in elb['ListenerDescriptions']:
for e in elbs:
for listener in e['ListenerDescriptions']:
if not listener['Listener'].get('SSLCertificateId'):
continue
@ -140,21 +141,29 @@ class AWSSourcePlugin(SourcePlugin):
continue
endpoint = dict(
name=elb['LoadBalancerName'],
dnsname=elb['DNSName'],
type='elb',
name=e['LoadBalancerName'],
dnsname=e['DNSName'],
type='e',
port=listener['Listener']['LoadBalancerPort'],
certificate_name=iam.get_name_from_arn(listener['Listener']['SSLCertificateId'])
)
if listener['PolicyNames']:
policy = describe_load_balancer_policies(elb['LoadBalancerName'], listener['PolicyNames'], account_number=account_number, region=region)
policy = e.describe_load_balancer_policies(e['LoadBalancerName'], listener['PolicyNames'], account_number=account_number, region=region)
endpoint['policy'] = format_elb_cipher_policy(policy)
endpoints.append(endpoint)
return endpoints
def update_endpoint(self, options, endpoint, certificate):
account_number = self.get_option('accountNumber', options)
regions = self.get_option('regions', options)
for region in regions:
arn = iam.create_arn_from_cert(account_number, region, certificate.name)
elb.attach_certificate(account_number, region, certificate.name, endpoint.port, arn)
def clean(self, options, **kwargs):
account_number = self.get_option('accountNumber', options)
certificates = self.get_certificates(options)

View File

@ -15,7 +15,7 @@ def test_get_name_from_arn():
@mock_iam()
def test_get_all_server_certs(app):
from lemur.plugins.lemur_aws.iam import upload_cert, get_all_server_certs
upload_cert('123456789012', 'testCert', EXTERNAL_VALID_STR.decode('utf-8'), PRIVATE_KEY_STR.decode('utf-8'))
upload_cert('123456789012', 'testCert', EXTERNAL_VALID_STR, PRIVATE_KEY_STR)
certs = get_all_server_certs('123456789012')
assert len(certs) == 1
@ -24,6 +24,6 @@ def test_get_all_server_certs(app):
@mock_iam()
def test_get_cert_from_arn(app):
from lemur.plugins.lemur_aws.iam import upload_cert, get_cert_from_arn
upload_cert('123456789012', 'testCert', EXTERNAL_VALID_STR.decode('utf-8'), PRIVATE_KEY_STR.decode('utf-8'))
upload_cert('123456789012', 'testCert', EXTERNAL_VALID_STR, PRIVATE_KEY_STR)
body, chain = get_cert_from_arn('arn:aws:iam::123456789012:server-certificate/testCert')
assert body.replace('\n', '') == EXTERNAL_VALID_STR.decode('utf-8').replace('\n', '')
assert body.replace('\n', '') == EXTERNAL_VALID_STR.replace('\n', '')

View File

@ -92,7 +92,7 @@ def process_options(options, csr):
"certificate":
{
"common_name": options['common_name'],
"csr": csr.decode('utf-8'),
"csr": csr,
"signature_hash":
signature_hash(options.get('signing_algorithm')),
},

View File

@ -27,7 +27,7 @@ def test_process_options(app):
assert data == {
'certificate': {
'csr': CSR_STR.decode('utf-8'),
'csr': CSR_STR,
'common_name': 'example.com',
'dns_names': names,
'signature_hash': 'sha256'

View File

@ -83,8 +83,7 @@ class RolesList(AuthenticatedResource):
parser.add_argument('id', type=str, location='args')
args = parser.parse_args()
if not g.current_user.is_admin:
args['user_id'] = g.current_user.id
args['user'] = g.current_user
return service.render(args)
@admin_permission.require(http_exception=403)

View File

@ -5,7 +5,7 @@
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
import datetime
import arrow
from flask import current_app
@ -69,6 +69,8 @@ def certificate_create(certificate, source):
if errors:
raise Exception("Unable to import certificate: {reasons}".format(reasons=errors))
data['creator'] = certificate['creator']
cert = cert_service.import_certificate(**data)
cert.description = "This certificate was automatically discovered by Lemur"
cert.sources.append(source)
@ -187,7 +189,7 @@ def sync(source, user):
sync_certificates(source, user)
sync_endpoints(source)
source.last_run = datetime.datetime.utcnow()
source.last_run = arrow.utcnow()
database.update(source)

View File

@ -14,6 +14,7 @@ from lemur.sources.models import Source
from lemur.notifications.models import Notification
from lemur.users.models import User
from lemur.roles.models import Role
from lemur.endpoints.models import Policy, Endpoint
from .vectors import INTERNAL_VALID_SAN_STR, PRIVATE_KEY_STR
@ -227,6 +228,10 @@ class PolicyFactory(BaseFactory):
"""Policy Factory."""
name = Sequence(lambda n: 'endpoint{0}'.format(n))
class Meta:
"""Factory Configuration."""
model = Policy
class EndpointFactory(BaseFactory):
"""Endpoint Factory."""
@ -237,4 +242,8 @@ class EndpointFactory(BaseFactory):
port = FuzzyInteger(0, high=65535)
policy = SubFactory(PolicyFactory)
certificate = SubFactory(CertificateFactory)
destination = SubFactory(DestinationFactory)
source = SubFactory(SourceFactory)
class Meta:
"""Factory Configuration."""
model = Endpoint

View File

@ -14,3 +14,6 @@ class TestSourcePlugin(SourcePlugin):
def get_certificates(self):
return
def update_endpoint(self, endpoint, certificate):
return

View File

@ -37,9 +37,9 @@ def test_user_authority(session, client, authority, role, user, issuer_plugin):
assert client.get(api.url_for(AuthoritiesList), headers=user['token']).json['total'] == 0
def test_create_authority(issuer_plugin, logged_in_admin):
def test_create_authority(issuer_plugin, user):
from lemur.authorities.service import create
authority = create(plugin={'plugin_object': issuer_plugin, 'slug': issuer_plugin.slug}, owner='jim@example.com', type='root')
authority = create(plugin={'plugin_object': issuer_plugin, 'slug': issuer_plugin.slug}, owner='jim@example.com', type='root', creator=user['user'])
assert authority.authority_certificate
@ -47,7 +47,7 @@ def test_create_authority(issuer_plugin, logged_in_admin):
(VALID_USER_HEADER_TOKEN, 0),
(VALID_ADMIN_HEADER_TOKEN, 3)
])
def test_admin_authority(client, authority, token, count):
def test_admin_authority(client, authority, issuer_plugin, token, count):
assert client.get(api.url_for(AuthoritiesList), headers=token).json['total'] == count

View File

@ -30,13 +30,18 @@ def test_get_certificate_primitives(certificate):
},
'destinations': [],
'roles': [],
'validity_end': datetime.date(year=2021, month=5, day=7),
'validity_start': datetime.date(year=2016, month=10, day=30)
'validity_end': arrow.get(2021, 5, 7),
'validity_start': arrow.get(2016, 10, 30),
'country': 'US',
'location': 'A place',
'organization': 'Example',
'organizational_unit': 'Operations',
'state': 'CA'
}
with freeze_time(datetime.date(year=2016, month=10, day=30)):
primitives = get_certificate_primitives(certificate)
assert data == primitives
assert len(primitives) == 14
def test_certificate_edit_schema(session):
@ -354,18 +359,24 @@ def test_mint_certificate(issuer_plugin, authority, logged_in_admin):
assert cert_body == INTERNAL_VALID_LONG_STR, INTERNAL_VALID_SAN_STR
def test_create_certificate(issuer_plugin, authority, logged_in_admin):
def test_create_certificate(issuer_plugin, authority, user):
from lemur.certificates.service import create
cert = create(authority=authority, csr=CSR_STR, owner='joe@example.com')
assert str(cert.not_after) == '2040-01-01 20:30:52'
assert str(cert.not_before) == '2015-06-26 20:30:52'
cert = create(authority=authority, csr=CSR_STR, owner='joe@example.com', creator=user['user'])
assert str(cert.not_after) == '2040-01-01T20:30:52+00:00'
assert str(cert.not_before) == '2015-06-26T20:30:52+00:00'
assert cert.issuer == 'Example'
assert cert.name == 'long.lived.com-Example-20150626-20400101'
cert = create(authority=authority, csr=CSR_STR, owner='joe@example.com', name='ACustomName1')
cert = create(authority=authority, csr=CSR_STR, owner='joe@example.com', name='ACustomName1', creator=user['user'])
assert cert.name == 'ACustomName1'
def test_reissue_certificate(issuer_plugin, authority, certificate, logged_in_admin):
from lemur.certificates.service import reissue_certificate
new_cert = reissue_certificate(certificate)
assert new_cert
def test_create_csr():
from lemur.certificates.service import create_csr
@ -381,34 +392,34 @@ def test_create_csr():
assert private_key
def test_import(logged_in_user):
def test_import(user):
from lemur.certificates.service import import_certificate
cert = import_certificate(body=INTERNAL_VALID_LONG_STR, chain=INTERNAL_VALID_SAN_STR, private_key=PRIVATE_KEY_STR)
assert str(cert.not_after) == '2040-01-01 20:30:52'
assert str(cert.not_before) == '2015-06-26 20:30:52'
assert cert.issuer == 'Example'
assert cert.name == 'long.lived.com-Example-20150626-20400101-1'
cert = import_certificate(body=INTERNAL_VALID_LONG_STR, chain=INTERNAL_VALID_SAN_STR, private_key=PRIVATE_KEY_STR, owner='joe@example.com', name='ACustomName2')
assert cert.name == 'ACustomName2'
def test_upload(logged_in_user):
from lemur.certificates.service import upload
cert = upload(body=INTERNAL_VALID_LONG_STR, chain=INTERNAL_VALID_SAN_STR, private_key=PRIVATE_KEY_STR, owner='joe@example.com')
assert str(cert.not_after) == '2040-01-01 20:30:52'
assert str(cert.not_before) == '2015-06-26 20:30:52'
cert = import_certificate(body=INTERNAL_VALID_LONG_STR, chain=INTERNAL_VALID_SAN_STR, private_key=PRIVATE_KEY_STR, creator=user['user'])
assert str(cert.not_after) == '2040-01-01T20:30:52+00:00'
assert str(cert.not_before) == '2015-06-26T20:30:52+00:00'
assert cert.issuer == 'Example'
assert cert.name == 'long.lived.com-Example-20150626-20400101-2'
cert = upload(body=INTERNAL_VALID_LONG_STR, chain=INTERNAL_VALID_SAN_STR, private_key=PRIVATE_KEY_STR, owner='joe@example.com', name='ACustomName')
cert = import_certificate(body=INTERNAL_VALID_LONG_STR, chain=INTERNAL_VALID_SAN_STR, private_key=PRIVATE_KEY_STR, owner='joe@example.com', name='ACustomName2', creator=user['user'])
assert cert.name == 'ACustomName2'
def test_upload(user):
from lemur.certificates.service import upload
cert = upload(body=INTERNAL_VALID_LONG_STR, chain=INTERNAL_VALID_SAN_STR, private_key=PRIVATE_KEY_STR, owner='joe@example.com', creator=user['user'])
assert str(cert.not_after) == '2040-01-01T20:30:52+00:00'
assert str(cert.not_before) == '2015-06-26T20:30:52+00:00'
assert cert.issuer == 'Example'
assert cert.name == 'long.lived.com-Example-20150626-20400101-3'
cert = upload(body=INTERNAL_VALID_LONG_STR, chain=INTERNAL_VALID_SAN_STR, private_key=PRIVATE_KEY_STR, owner='joe@example.com', name='ACustomName', creator=user['user'])
assert 'ACustomName' in cert.name
# verify upload with a private key as a str
def test_upload_private_key_str(logged_in_user):
def test_upload_private_key_str(user):
from lemur.certificates.service import upload
cert = upload(body=INTERNAL_VALID_LONG_STR, chain=INTERNAL_VALID_SAN_STR, private_key=PRIVATE_KEY_STR.decode('utf-8'), owner='joe@example.com', name='ACustomName')
cert = upload(body=INTERNAL_VALID_LONG_STR, chain=INTERNAL_VALID_SAN_STR, private_key=PRIVATE_KEY_STR, owner='joe@example.com', name='ACustomName', creator=user['user'])
assert cert

View File

@ -1,11 +1,21 @@
import pytest
from lemur.endpoints.views import * # noqa
from lemur.tests.factories import EndpointFactory, CertificateFactory
from .vectors import VALID_ADMIN_HEADER_TOKEN, VALID_USER_HEADER_TOKEN
def test_rotate_certificate(client, source_plugin):
from lemur.endpoints.service import rotate_certificate
new_certificate = CertificateFactory()
endpoint = EndpointFactory()
rotate_certificate(endpoint, new_certificate)
assert endpoint.certificate == new_certificate
@pytest.mark.parametrize("token,status", [
(VALID_USER_HEADER_TOKEN, 404),
(VALID_ADMIN_HEADER_TOKEN, 404),

View File

@ -110,7 +110,7 @@ def test_role_put_with_data_and_user(client, session):
}
assert client.put(api.url_for(Roles, role_id=role.id), data=json.dumps(data), headers=headers).status_code == 200
assert client.get(api.url_for(RolesList), data={}, headers=headers).json['total'] == 1
assert client.get(api.url_for(RolesList), data={}, headers=headers).json['total'] > 1
@pytest.mark.parametrize("token,status", [

View File

@ -18,7 +18,7 @@ def validate_source_schema(client):
assert not errors
def test_create_certificate(source):
def test_create_certificate(user, source):
from lemur.sources.service import certificate_create
with pytest.raises(Exception):
@ -27,7 +27,8 @@ def test_create_certificate(source):
data = {
'body': INTERNAL_VALID_WILDCARD_STR,
'private_key': INTERNAL_PRIVATE_KEY_A_STR,
'owner': 'bob@example.com'
'owner': 'bob@example.com',
'creator': user['user']
}
cert = certificate_create(data, source)

View File

@ -8,7 +8,6 @@ def test_private_key(session):
from lemur.common.validators import private_key
private_key(PRIVATE_KEY_STR)
private_key(PRIVATE_KEY_STR.decode('utf-8'))
with pytest.raises(ValidationError):
private_key('invalid_private_key')

View File

@ -12,7 +12,7 @@ VALID_ADMIN_HEADER_TOKEN = {
}
INTERNAL_VALID_LONG_STR = b"""
INTERNAL_VALID_LONG_STR = """
-----BEGIN CERTIFICATE-----
MIID1zCCAr+gAwIBAgIBATANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCVVMx
CzAJBgNVBAgMAkNBMRAwDgYDVQQHDAdBIHBsYWNlMRcwFQYDVQQDDA5sb25nLmxp
@ -40,7 +40,7 @@ h0S8LN4iv/+vNFPNiM1z9X/SZgfbwZXrLsSi
INTERNAL_VALID_LONG_CERT = parse_certificate(INTERNAL_VALID_LONG_STR)
INTERNAL_INVALID_STR = b"""
INTERNAL_INVALID_STR = """
-----BEGIN CERTIFICATE-----
MIIEFTCCAv2gAwIBAgICA+gwDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAlVT
MQswCQYDVQQIDAJDQTEQMA4GA1UEBwwHQSBwbGFjZTEXMBUGA1UEAwwObG9uZy5s
@ -69,7 +69,7 @@ kP+oGWtHvhteUAe8Gloo5NchZJ0/BqlYRCD5aAHcmbXRsDid9mO4ADU=
INTERNAL_INVALID_CERT = parse_certificate(INTERNAL_INVALID_STR)
INTERNAL_VALID_SAN_STR = b"""
INTERNAL_VALID_SAN_STR = """
-----BEGIN CERTIFICATE-----
MIIESjCCAzKgAwIBAgICA+kwDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAlVT
MQswCQYDVQQIDAJDQTEQMA4GA1UEBwwHQSBwbGFjZTEXMBUGA1UEAwwObG9uZy5s
@ -99,7 +99,7 @@ YBrY/duF15YpoMKAlFhDBh6R9/nb5kI2n3pY6I5h6LEYfLStazXbIu61M8zu9TM/
INTERNAL_VALID_SAN_CERT = parse_certificate(INTERNAL_VALID_SAN_STR)
INTERNAL_VALID_WILDCARD_STR = b"""
INTERNAL_VALID_WILDCARD_STR = """
-----BEGIN CERTIFICATE-----
MIIEHDCCAwSgAwIBAgICA+owDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAlVT
MQswCQYDVQQIDAJDQTEQMA4GA1UEBwwHQSBwbGFjZTEXMBUGA1UEAwwObG9uZy5s
@ -128,7 +128,7 @@ S0Xb3ZauZJQI7OdHeUPDRVq+8hcG77sopN9pEYrIH08oxvLX2US3GqrowjOxthRa
INTERNAL_VALID_WILDCARD_CERT = parse_certificate(INTERNAL_VALID_WILDCARD_STR)
EXTERNAL_VALID_STR = b"""
EXTERNAL_VALID_STR = """
-----BEGIN CERTIFICATE-----
MIIFHzCCBAegAwIBAgIQGFWCciDWzbOej/TbAJN0WzANBgkqhkiG9w0BAQsFADCB
pDELMAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8w
@ -163,7 +163,7 @@ Bs63gULVCqWygt5KEbv990m/XGuRMaXuHzHCHB4v5LRM30FiFmqCzyD8d+btzW9B
EXTERNAL_CERT = parse_certificate(EXTERNAL_VALID_STR)
PRIVATE_KEY_STR = b"""
PRIVATE_KEY_STR = """
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAnEjM0cQevlDjT6mDMtTo8N1ovAyKbfVEp0ketCPC4hLkStms
q9ETIyyerARIMv4SEhKqS4E7HIg6ccGkwv1ja5E/b2jHMH4ht1dEXnfM2yh0Mwvk
@ -193,7 +193,7 @@ XKxcRgm/Va4QMEAnec0qXfdTVJaJiAW0bdKwKRRrrbwcTdNRGibdng==
-----END RSA PRIVATE KEY-----
"""
INTERNAL_CERTIFICATE_A_STR = b"""
INTERNAL_CERTIFICATE_A_STR = """
-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIBATANBgkqhkiG9w0BAQsFADB5MQswCQYDVQQGEwJVUzET
MBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJTG9zIEdhdG9zMRYwFAYDVQQK
@ -218,7 +218,7 @@ L0Ew8hy0GG3nZ6uXLW7q
"""
INTERNAL_PRIVATE_KEY_A_STR = b"""
INTERNAL_PRIVATE_KEY_A_STR = """
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAtkyvL6EqSgYSJX11635Hb8FBG/8Wey6C2KtG7M+GXvGCsSmf
NqQMeZdfW9Avxelkstp5/K+ilVJJ2TJRelu1yVUUkQcrP7imgf7CxKQAnPz2oXQI
@ -249,7 +249,7 @@ o6ynBW1bG+qfjx9GyThgudvRtB+0vTSShrT5GftLCyMtOiYSHkGEvMOGFBuowzoz
"""
CSR_STR = b"""
CSR_STR = """
-----BEGIN CERTIFICATE REQUEST-----
MIIC1zCCAb8CAQAwczEUMBIGA1UEAwwLQUNvbW1vbk5hbWUxFTATBgNVBAoMDG9y
Z2FuaXphdGlvbjEOMAwGA1UECwwFZ3VuaXQxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
@ -271,7 +271,7 @@ PiFAxlc0tVjlLqQ=
"""
CSR_PEM_STR = b"""
CSR_PEM_STR = """
-----BEGIN RSA PRIVATE KEY-----
MIIEpgIBAAKCAQEAzZ2PgKflffhIPzwLXe26tuw+7Q72y9dAMrDVEms8u4Cch3yc
hN6Il5tvM4xTc0UN+aobAhaVoPDN+haXT/XyBRvbFV1f8nvmDQZGdmkyYkZ2xK2X