Merge branch 'master' into linuxdst

This commit is contained in:
Curtis
2018-06-12 07:40:40 -07:00
committed by GitHub
147 changed files with 2008 additions and 312 deletions

View File

@@ -1,7 +1,7 @@
"""
.. module: lemur.plugins.base
:platform: Unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>

View File

@@ -1,6 +1,6 @@
"""
.. module: lemur.plugins.base.manager
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson (kglisson@netflix.com)

View File

@@ -1,7 +1,7 @@
"""
.. module: lemur.plugins.base.v1
:platform: Unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>

View File

@@ -1,7 +1,7 @@
"""
.. module: lemur.plugins.bases.destination
:platform: Unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>

View File

@@ -1,7 +1,7 @@
"""
.. module: lemur.plugins.bases.export
:platform: Unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>

View File

@@ -1,7 +1,7 @@
"""
.. module: lemur.plugins.bases.issuer
:platform: Unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
@@ -25,7 +25,7 @@ class IssuerPlugin(Plugin):
def revoke_certificate(self, certificate, comments):
raise NotImplementedError
def get_ordered_certificate(self, order_id):
def get_ordered_certificate(self, certificate):
raise NotImplementedError
def cancel_ordered_certificate(self, pending_cert, **kwargs):

View File

@@ -1,7 +1,7 @@
"""
.. module: lemur.plugins.bases.metric
:platform: Unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>

View File

@@ -1,7 +1,7 @@
"""
.. module: lemur.plugins.bases.notification
:platform: Unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>

View File

@@ -1,7 +1,7 @@
"""
.. module: lemur.plugins.bases.source
:platform: Unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>

View File

@@ -1,6 +1,6 @@
import time
import CloudFlare
import CloudFlare
from flask import current_app

View File

@@ -0,0 +1,131 @@
import time
import dns
import dns.exception
import dns.name
import dns.query
import dns.resolver
from dyn.tm.errors import DynectCreateError
from dyn.tm.session import DynectSession
from dyn.tm.zones import Node, Zone
from flask import current_app
from tld import get_tld
def get_dynect_session():
dynect_session = DynectSession(
current_app.config.get('ACME_DYN_CUSTOMER_NAME', ''),
current_app.config.get('ACME_DYN_USERNAME', ''),
current_app.config.get('ACME_DYN_PASSWORD', ''),
)
return dynect_session
def _has_dns_propagated(name, token):
txt_records = []
try:
dns_resolver = dns.resolver.Resolver()
dns_resolver.nameservers = [get_authoritative_nameserver(name)]
dns_response = dns_resolver.query(name, 'TXT')
for rdata in dns_response:
for txt_record in rdata.strings:
txt_records.append(txt_record.decode("utf-8"))
except dns.exception.DNSException:
return False
for txt_record in txt_records:
if txt_record == token:
return True
return False
def wait_for_dns_change(change_id, account_number=None):
fqdn, token = change_id
while True:
status = _has_dns_propagated(fqdn, token)
current_app.logger.debug("Record status for fqdn: {}: {}".format(fqdn, status))
if status:
break
time.sleep(20)
return
def create_txt_record(domain, token, account_number):
get_dynect_session()
zone_name = get_tld('http://' + domain)
zone_parts = len(zone_name.split('.'))
node_name = '.'.join(domain.split('.')[:-zone_parts])
fqdn = "{0}.{1}".format(node_name, zone_name)
zone = Zone(zone_name)
try:
zone.add_record(node_name, record_type='TXT', txtdata="\"{}\"".format(token), ttl=5)
except DynectCreateError:
delete_txt_record(None, None, domain, token)
zone.add_record(node_name, record_type='TXT', txtdata="\"{}\"".format(token), ttl=5)
node = zone.get_node(node_name)
zone.publish()
current_app.logger.debug("TXT record created: {0}".format(fqdn))
change_id = (fqdn, token)
return change_id
def delete_txt_record(change_id, account_number, domain, token):
get_dynect_session()
if not domain:
current_app.logger.debug("delete_txt_record: No domain passed")
return
zone_name = get_tld('http://' + domain)
zone_parts = len(zone_name.split('.'))
node_name = '.'.join(domain.split('.')[:-zone_parts])
fqdn = "{0}.{1}".format(node_name, zone_name)
zone = Zone(zone_name)
node = Node(zone_name, fqdn)
all_txt_records = node.get_all_records_by_type('TXT')
for txt_record in all_txt_records:
if txt_record.txtdata == ("{}".format(token)):
current_app.logger.debug("Deleting TXT record name: {0}".format(fqdn))
txt_record.delete()
zone.publish()
def get_authoritative_nameserver(domain):
n = dns.name.from_text(domain)
depth = 2
default = dns.resolver.get_default_resolver()
nameserver = default.nameservers[0]
last = False
while not last:
s = n.split(depth)
last = s[0].to_unicode() == u'@'
sub = s[1]
query = dns.message.make_query(sub, dns.rdatatype.NS)
response = dns.query.udp(query, nameserver)
rcode = response.rcode()
if rcode != dns.rcode.NOERROR:
if rcode == dns.rcode.NXDOMAIN:
raise Exception('%s does not exist.' % sub)
else:
raise Exception('Error %s' % dns.rcode.to_text(rcode))
if len(response.authority) > 0:
rrset = response.authority[0]
else:
rrset = response.answer[0]
rr = rrset[0]
if rr.rdtype != dns.rdatatype.SOA:
authority = rr.target
nameserver = default.query(authority).rrset[0].to_text()
depth += 1
return nameserver

View File

@@ -2,38 +2,44 @@
.. module: lemur.plugins.lemur_acme.plugin
:platform: Unix
:synopsis: This module is responsible for communicating with an ACME CA.
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
Snippets from https://raw.githubusercontent.com/alex/letsencrypt-aws/master/letsencrypt-aws.py
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
.. moduleauthor:: Mikhail Khodorovskiy <mikhail.khodorovskiy@jivesoftware.com>
.. moduleauthor:: Curtis Castrapel <ccastrapel@netflix.com>
"""
import josepy as jose
from flask import current_app
from acme.client import Client
from acme import messages
from acme import challenges
from lemur.common.utils import generate_private_key
import datetime
import json
import time
import OpenSSL.crypto
import josepy as jose
from acme import challenges, messages
from acme.client import BackwardsCompatibleClientV2, ClientNetwork
from acme.messages import Error as AcmeError
from acme.errors import PollError, WildcardUnsupportedError
from botocore.exceptions import ClientError
from flask import current_app
from lemur.common.utils import validate_conf
from lemur.plugins.bases import IssuerPlugin
from lemur.authorizations import service as authorization_service
from lemur.common.utils import generate_private_key
from lemur.dns_providers import service as dns_provider_service
from lemur.exceptions import InvalidAuthority, InvalidConfiguration, UnknownProvider
from lemur.plugins import lemur_acme as acme
from lemur.plugins.bases import IssuerPlugin
from lemur.plugins.lemur_acme import cloudflare, dyn, route53
def find_dns_challenge(authz):
for combo in authz.body.resolved_combinations:
if (
len(combo) == 1 and
isinstance(combo[0].chall, challenges.DNS01)
):
yield combo[0]
def find_dns_challenge(authorizations):
dns_challenges = []
for authz in authorizations:
for combo in authz.body.challenges:
if isinstance(combo.chall, challenges.DNS01):
dns_challenges.append(combo)
return dns_challenges
class AuthorizationRecord(object):
@@ -44,85 +50,90 @@ class AuthorizationRecord(object):
self.change_id = change_id
def start_dns_challenge(acme_client, account_number, host, dns_provider):
def maybe_remove_wildcard(host):
return host.replace("*.", "")
def start_dns_challenge(acme_client, account_number, host, dns_provider, order):
current_app.logger.debug("Starting DNS challenge for {0}".format(host))
authz = acme_client.request_domain_challenges(host)
[dns_challenge] = find_dns_challenge(authz)
dns_challenges = find_dns_challenge(order.authorizations)
change_ids = []
change_id = dns_provider.create_txt_record(
dns_challenge.validation_domain_name(host),
dns_challenge.validation(acme_client.key),
account_number
)
for dns_challenge in find_dns_challenge(order.authorizations):
change_id = dns_provider.create_txt_record(
dns_challenge.validation_domain_name(maybe_remove_wildcard(host)),
dns_challenge.validation(acme_client.client.net.key),
account_number
)
change_ids.append(change_id)
return AuthorizationRecord(
host,
authz,
dns_challenge,
change_id,
order.authorizations,
dns_challenges,
change_ids
)
def complete_dns_challenge(acme_client, account_number, authz_record, dns_provider):
dns_provider.wait_for_dns_change(authz_record.change_id, account_number=account_number)
current_app.logger.debug("Finalizing DNS challenge for {0}".format(authz_record.authz[0].body.identifier.value))
for change_id in authz_record.change_id:
dns_provider.wait_for_dns_change(change_id, account_number=account_number)
response = authz_record.dns_challenge.response(acme_client.key)
for dns_challenge in authz_record.dns_challenge:
verified = response.simple_verify(
authz_record.dns_challenge.chall,
authz_record.host,
acme_client.key.public_key()
)
response = dns_challenge.response(acme_client.client.net.key)
if not verified:
raise ValueError("Failed verification")
verified = response.simple_verify(
dns_challenge.chall,
authz_record.host,
acme_client.client.net.key.public_key()
)
acme_client.answer_challenge(authz_record.dns_challenge, response)
if not verified:
raise ValueError("Failed verification")
time.sleep(5)
acme_client.answer_challenge(dns_challenge, response)
def request_certificate(acme_client, authorizations, csr):
cert_response, _ = acme_client.poll_and_request_issuance(
jose.util.ComparableX509(
OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_PEM,
csr
)
),
authzrs=[authz_record.authz for authz_record in authorizations],
)
def request_certificate(acme_client, authorizations, csr, order):
for authorization in authorizations:
for authz in authorization.authz:
authorization_resource, _ = acme_client.poll(authz)
pem_certificate = OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, cert_response.body
).decode('utf-8')
deadline = datetime.datetime.now() + datetime.timedelta(seconds=90)
orderr = acme_client.finalize_order(order, deadline)
pem_certificate = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
orderr.fullchain_pem)).decode()
pem_certificate_chain = orderr.fullchain_pem[len(pem_certificate):].lstrip()
pem_certificate_chain = "\n".join(
OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert.decode("utf-8"))
for cert in acme_client.fetch_chain(cert_response)
).decode('utf-8')
current_app.logger.debug("{0} {1}".format(type(pem_certificate). type(pem_certificate_chain)))
current_app.logger.debug("{0} {1}".format(type(pem_certificate), type(pem_certificate_chain)))
return pem_certificate, pem_certificate_chain
def setup_acme_client():
email = current_app.config.get('ACME_EMAIL')
tel = current_app.config.get('ACME_TEL')
directory_url = current_app.config.get('ACME_DIRECTORY_URL')
contact = ('mailto:{}'.format(email), 'tel:{}'.format(tel))
def setup_acme_client(authority):
if not authority.options:
raise InvalidAuthority("Invalid authority. Options not set")
options = {}
for option in json.loads(authority.options):
options[option["name"]] = option.get("value")
email = options.get('email', current_app.config.get('ACME_EMAIL'))
tel = options.get('telephone', current_app.config.get('ACME_TEL'))
directory_url = options.get('acme_url', current_app.config.get('ACME_DIRECTORY_URL'))
key = jose.JWKRSA(key=generate_private_key('RSA2048'))
current_app.logger.debug("Connecting with directory at {0}".format(directory_url))
client = Client(directory_url, key)
registration = client.register(
messages.NewRegistration.from_data(email=email)
)
net = ClientNetwork(key, account=None)
client = BackwardsCompatibleClientV2(net, key, directory_url)
registration = client.new_account_and_tos(messages.NewRegistration.from_data(email=email))
current_app.logger.debug("Connected: {0}".format(registration.uri))
client.agree_to_tos(registration)
return client, registration
@@ -143,23 +154,25 @@ def get_domains(options):
return domains
def get_authorizations(acme_client, account_number, domains, dns_provider):
def get_authorizations(acme_client, order, order_info, dns_provider):
authorizations = []
try:
for domain in domains:
authz_record = start_dns_challenge(acme_client, account_number, domain, dns_provider)
authorizations.append(authz_record)
for domain in order_info.domains:
authz_record = start_dns_challenge(acme_client, order_info.account_number, domain, dns_provider, order)
authorizations.append(authz_record)
return authorizations
for authz_record in authorizations:
complete_dns_challenge(acme_client, account_number, authz_record, dns_provider)
finally:
for authz_record in authorizations:
dns_challenge = authz_record.dns_challenge
def finalize_authorizations(acme_client, account_number, dns_provider, authorizations):
for authz_record in authorizations:
complete_dns_challenge(acme_client, account_number, authz_record, dns_provider)
for authz_record in authorizations:
dns_challenges = authz_record.dns_challenge
for dns_challenge in dns_challenges:
dns_provider.delete_txt_record(
authz_record.change_id,
account_number,
dns_challenge.validation_domain_name(authz_record.host),
dns_challenge.validation(acme_client.key)
dns_challenge.validation_domain_name(maybe_remove_wildcard(authz_record.host)),
dns_challenge.validation(acme_client.client.net.key)
)
return authorizations
@@ -171,24 +184,139 @@ class ACMEIssuerPlugin(IssuerPlugin):
description = 'Enables the creation of certificates via ACME CAs (including Let\'s Encrypt)'
version = acme.VERSION
author = 'Kevin Glisson'
author = 'Netflix'
author_url = 'https://github.com/netflix/lemur.git'
def __init__(self, *args, **kwargs):
required_vars = [
'ACME_DIRECTORY_URL',
'ACME_TEL',
'ACME_EMAIL',
'ACME_AWS_ACCOUNT_NUMBER',
'ACME_ROOT'
]
options = [
{
'name': 'acme_url',
'type': 'str',
'required': True,
'validation': '/^http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+$/',
'helpMessage': 'Must be a valid web url starting with http[s]://',
},
{
'name': 'telephone',
'type': 'str',
'default': '',
'helpMessage': 'Telephone to use'
},
{
'name': 'email',
'type': 'str',
'default': '',
'validation': '/^?([-a-zA-Z0-9.`?{}]+@\w+\.\w+)$/',
'helpMessage': 'Email to use'
},
{
'name': 'certificate',
'type': 'textarea',
'default': '',
'validation': '/^-----BEGIN CERTIFICATE-----/',
'helpMessage': 'Certificate to use'
},
]
validate_conf(current_app, required_vars)
self.dns_provider_name = current_app.config.get('ACME_DNS_PROVIDER', 'route53')
current_app.logger.debug("Using DNS provider: {0}".format(self.dns_provider_name))
self.dns_provider = __import__(self.dns_provider_name, globals(), locals(), [], 1)
def __init__(self, *args, **kwargs):
super(ACMEIssuerPlugin, self).__init__(*args, **kwargs)
def get_dns_provider(self, type):
provider_types = {
'cloudflare': cloudflare,
'dyn': dyn,
'route53': route53,
}
provider = provider_types.get(type)
if not provider:
raise UnknownProvider("No such DNS provider: {}".format(type))
return provider
def get_ordered_certificate(self, pending_cert):
acme_client, registration = setup_acme_client(pending_cert.authority)
order_info = authorization_service.get(pending_cert.external_id)
dns_provider = dns_provider_service.get(pending_cert.dns_provider_id)
dns_provider_type = self.get_dns_provider(dns_provider.provider_type)
try:
authorizations = get_authorizations(
acme_client, order_info.account_number, order_info.domains, dns_provider_type)
except ClientError:
current_app.logger.error("Unable to resolve pending cert: {}".format(pending_cert.name), exc_info=True)
return False
authorizations = finalize_authorizations(
acme_client, order_info.account_number, dns_provider_type, authorizations)
pem_certificate, pem_certificate_chain = request_certificate(acme_client, authorizations, pending_cert.csr)
cert = {
'body': "\n".join(str(pem_certificate).splitlines()),
'chain': "\n".join(str(pem_certificate_chain).splitlines()),
'external_id': str(pending_cert.external_id)
}
return cert
def get_ordered_certificates(self, pending_certs):
pending = []
certs = []
for pending_cert in pending_certs:
try:
acme_client, registration = setup_acme_client(pending_cert.authority)
order_info = authorization_service.get(pending_cert.external_id)
dns_provider = dns_provider_service.get(pending_cert.dns_provider_id)
dns_provider_type = self.get_dns_provider(dns_provider.provider_type)
try:
order = acme_client.new_order(pending_cert.csr)
except WildcardUnsupportedError:
raise Exception("The currently selected ACME CA endpoint does"
" not support issuing wildcard certificates.")
authorizations = get_authorizations(acme_client, order, order_info, dns_provider_type)
pending.append({
"acme_client": acme_client,
"account_number": order_info.account_number,
"dns_provider_type": dns_provider_type,
"authorizations": authorizations,
"pending_cert": pending_cert,
"order": order,
})
except (ClientError, ValueError, Exception):
current_app.logger.error("Unable to resolve pending cert: {}".format(pending_cert), exc_info=True)
certs.append({
"cert": False,
"pending_cert": pending_cert,
})
for entry in pending:
try:
entry["authorizations"] = finalize_authorizations(
entry["acme_client"],
entry["account_number"],
entry["dns_provider_type"],
entry["authorizations"],
)
pem_certificate, pem_certificate_chain = request_certificate(
entry["acme_client"],
entry["authorizations"],
entry["pending_cert"].csr,
entry["order"]
)
cert = {
'body': "\n".join(str(pem_certificate).splitlines()),
'chain': "\n".join(str(pem_certificate_chain).splitlines()),
'external_id': str(entry["pending_cert"].external_id)
}
certs.append({
"cert": cert,
"pending_cert": entry["pending_cert"],
})
except (PollError, AcmeError, Exception):
current_app.logger.error("Unable to resolve pending cert: {}".format(pending_cert), exc_info=True)
certs.append({
"cert": False,
"pending_cert": entry["pending_cert"],
})
return certs
def create_certificate(self, csr, issuer_options):
"""
Creates an ACME certificate.
@@ -197,11 +325,37 @@ class ACMEIssuerPlugin(IssuerPlugin):
:param issuer_options:
:return: :raise Exception:
"""
current_app.logger.debug("Requesting a new acme certificate: {0}".format(issuer_options))
acme_client, registration = setup_acme_client()
account_number = current_app.config.get('ACME_AWS_ACCOUNT_NUMBER')
authority = issuer_options.get('authority')
create_immediately = issuer_options.get('create_immediately', False)
acme_client, registration = setup_acme_client(authority)
dns_provider = issuer_options.get('dns_provider')
if not dns_provider:
raise InvalidConfiguration("DNS Provider setting is required for ACME certificates.")
credentials = json.loads(dns_provider.credentials)
current_app.logger.debug("Using DNS provider: {0}".format(dns_provider.provider_type))
dns_provider_type = __import__(dns_provider.provider_type, globals(), locals(), [], 1)
account_number = credentials.get("account_id")
if dns_provider.provider_type == 'route53' and not account_number:
error = "Route53 DNS Provider {} does not have an account number configured.".format(dns_provider.name)
current_app.logger.error(error)
raise InvalidConfiguration(error)
domains = get_domains(issuer_options)
authorizations = get_authorizations(acme_client, account_number, domains, self.dns_provider)
if not create_immediately:
# Create pending authorizations that we'll need to do the creation
authz_domains = []
for d in domains:
if type(d) == str:
authz_domains.append(d)
else:
authz_domains.append(d.value)
dns_authorization = authorization_service.create(account_number, authz_domains, dns_provider.provider_type)
# Return id of the DNS Authorization
return None, None, dns_authorization.id
authorizations = get_authorizations(acme_client, account_number, domains, dns_provider_type)
finalize_authorizations(acme_client, account_number, dns_provider_type, authorizations)
pem_certificate, pem_certificate_chain = request_certificate(acme_client, authorizations, csr)
# TODO add external ID (if possible)
return pem_certificate, pem_certificate_chain, None
@@ -216,4 +370,15 @@ class ACMEIssuerPlugin(IssuerPlugin):
:return:
"""
role = {'username': '', 'password': '', 'name': 'acme'}
return current_app.config.get('ACME_ROOT'), "", [role]
plugin_options = options.get('plugin', {}).get('plugin_options')
if not plugin_options:
error = "Invalid options for lemur_acme plugin: {}".format(options)
current_app.logger.error(error)
raise InvalidConfiguration(error)
# Define static acme_root based off configuration variable by default. However, if user has passed a
# certificate, use this certificate as the root.
acme_root = current_app.config.get('ACME_ROOT')
for option in plugin_options:
if option.get('name') == 'certificate':
acme_root = option.get('value')
return acme_root, "", [role]

View File

@@ -1,4 +1,5 @@
import time
from lemur.plugins.lemur_aws.sts import sts_client
@@ -58,21 +59,23 @@ def change_txt_record(action, zone_id, domain, value, client=None):
def create_txt_record(host, value, account_number):
zone_id = find_zone_id(host, account_number=account_number)
change_id = change_txt_record(
"CREATE",
"UPSERT",
zone_id,
host,
value,
account_number=account_number
)
return zone_id, change_id
def delete_txt_record(change_id, account_number, host, value):
zone_id, _ = change_id
change_txt_record(
"DELETE",
zone_id,
host,
value,
account_number=account_number
)
def delete_txt_record(change_ids, account_number, host, value):
for change_id in change_ids:
zone_id, _ = change_id
change_txt_record(
"DELETE",
zone_id,
host,
value,
account_number=account_number
)

View File

@@ -1,4 +1,305 @@
import unittest
def test_get_certificates(app):
from lemur.plugins.base import plugins
p = plugins.get('acme-issuer')
from mock import MagicMock, Mock, patch
from lemur.plugins.lemur_acme import plugin
class TestAcme(unittest.TestCase):
def setUp(self):
self.ACMEIssuerPlugin = plugin.ACMEIssuerPlugin()
@patch('lemur.plugins.lemur_acme.plugin.len', return_value=1)
def test_find_dns_challenge(self, mock_len):
assert mock_len
from acme import challenges
c = challenges.DNS01()
mock_authz = Mock()
mock_authz.body.resolved_combinations = []
mock_entry = Mock()
mock_entry.chall = c
mock_authz.body.resolved_combinations.append(mock_entry)
result = yield plugin.find_dns_challenge(mock_authz)
self.assertEqual(result, mock_entry)
def test_authz_record(self):
a = plugin.AuthorizationRecord("host", "authz", "challenge", "id")
self.assertEqual(type(a), plugin.AuthorizationRecord)
@patch('acme.client.Client')
@patch('lemur.plugins.lemur_acme.plugin.current_app')
@patch('lemur.plugins.lemur_acme.plugin.len', return_value=1)
@patch('lemur.plugins.lemur_acme.plugin.find_dns_challenge')
def test_start_dns_challenge(self, mock_find_dns_challenge, mock_len, mock_app, mock_acme):
assert mock_len
mock_order = Mock()
mock_app.logger.debug = Mock()
mock_authz = Mock()
mock_authz.body.resolved_combinations = []
mock_entry = MagicMock()
from acme import challenges
c = challenges.DNS01()
mock_entry.chall = c
mock_authz.body.resolved_combinations.append(mock_entry)
mock_acme.request_domain_challenges = Mock(return_value=mock_authz)
mock_dns_provider = Mock()
mock_dns_provider.create_txt_record = Mock(return_value=1)
values = [mock_entry]
iterable = mock_find_dns_challenge.return_value
iterator = iter(values)
iterable.__iter__.return_value = iterator
result = plugin.start_dns_challenge(mock_acme, "accountid", "host", mock_dns_provider, mock_order)
self.assertEqual(type(result), plugin.AuthorizationRecord)
@patch('acme.client.Client')
@patch('lemur.plugins.lemur_acme.plugin.current_app')
def test_complete_dns_challenge_success(self, mock_current_app, mock_acme):
mock_dns_provider = Mock()
mock_dns_provider.wait_for_dns_change = Mock(return_value=True)
mock_authz = Mock()
mock_authz.dns_challenge.response = Mock()
mock_authz.dns_challenge.response.simple_verify = Mock(return_value=True)
mock_authz.authz = []
mock_authz_record = Mock()
mock_authz_record.body.identifier.value = "test"
mock_authz.authz.append(mock_authz_record)
mock_authz.change_id = []
mock_authz.change_id.append("123")
mock_authz.dns_challenge = []
dns_challenge = Mock()
mock_authz.dns_challenge.append(dns_challenge)
plugin.complete_dns_challenge(mock_acme, "accountid", mock_authz, mock_dns_provider)
@patch('acme.client.Client')
@patch('lemur.plugins.lemur_acme.plugin.current_app')
def test_complete_dns_challenge_fail(self, mock_current_app, mock_acme):
mock_dns_provider = Mock()
mock_dns_provider.wait_for_dns_change = Mock(return_value=True)
mock_authz = Mock()
mock_authz.dns_challenge.response = Mock()
mock_authz.dns_challenge.response.simple_verify = Mock(return_value=False)
mock_authz.authz = []
mock_authz_record = Mock()
mock_authz_record.body.identifier.value = "test"
mock_authz.authz.append(mock_authz_record)
mock_authz.change_id = []
mock_authz.change_id.append("123")
mock_authz.dns_challenge = []
dns_challenge = Mock()
mock_authz.dns_challenge.append(dns_challenge)
self.assertRaises(
ValueError,
plugin.complete_dns_challenge(mock_acme, "accountid", mock_authz, mock_dns_provider)
)
@patch('acme.client.Client')
@patch('OpenSSL.crypto', return_value="mock_cert")
@patch('josepy.util.ComparableX509')
@patch('lemur.plugins.lemur_acme.plugin.find_dns_challenge')
@patch('lemur.plugins.lemur_acme.plugin.current_app')
def test_request_certificate(self, mock_current_app, mock_find_dns_challenge, mock_jose, mock_crypto, mock_acme):
mock_cert_response = Mock()
mock_cert_response.body = "123"
mock_cert_response_full = [mock_cert_response, True]
mock_acme.poll_and_request_issuance = Mock(return_value=mock_cert_response_full)
mock_authz = []
mock_authz_record = MagicMock()
mock_authz_record.authz = Mock()
mock_authz.append(mock_authz_record)
mock_acme.fetch_chain = Mock(return_value="mock_chain")
mock_crypto.dump_certificate = Mock(return_value=b'chain')
mock_order = Mock()
plugin.request_certificate(mock_acme, [], "mock_csr", mock_order)
def test_setup_acme_client_fail(self):
mock_authority = Mock()
mock_authority.options = []
with self.assertRaises(Exception):
plugin.setup_acme_client(mock_authority)
@patch('lemur.plugins.lemur_acme.plugin.BackwardsCompatibleClientV2')
@patch('lemur.plugins.lemur_acme.plugin.current_app')
def test_setup_acme_client_success(self, mock_current_app, mock_acme):
mock_authority = Mock()
mock_authority.options = '[{"name": "mock_name", "value": "mock_value"}]'
mock_client = Mock()
mock_registration = Mock()
mock_registration.uri = "http://test.com"
mock_client.register = mock_registration
mock_client.agree_to_tos = Mock(return_value=True)
mock_acme.return_value = mock_client
result_client, result_registration = plugin.setup_acme_client(mock_authority)
assert result_client
assert result_registration
@patch('lemur.plugins.lemur_acme.plugin.current_app')
def test_get_domains_single(self, mock_current_app):
options = {
"common_name": "test.netflix.net"
}
result = plugin.get_domains(options)
self.assertEqual(result, [options["common_name"]])
@patch('lemur.plugins.lemur_acme.plugin.current_app')
def test_get_domains_multiple(self, mock_current_app):
options = {
"common_name": "test.netflix.net",
"extensions": {
"sub_alt_names": {
"names": [
"test2.netflix.net",
"test3.netflix.net"
]
}
}
}
result = plugin.get_domains(options)
self.assertEqual(result, [options["common_name"], "test2.netflix.net", "test3.netflix.net"])
@patch('lemur.plugins.lemur_acme.plugin.start_dns_challenge', return_value="test")
def test_get_authorizations(self, mock_start_dns_challenge):
mock_order = Mock()
mock_order.body.identifiers = []
mock_domain = Mock()
mock_order.body.identifiers.append(mock_domain)
mock_order_info = Mock()
mock_order_info.account_number = 1
mock_order_info.domains = ["test.fakedomain.net"]
result = plugin.get_authorizations("acme_client", mock_order, mock_order_info, "dns_provider")
self.assertEqual(result, ["test"])
@patch('lemur.plugins.lemur_acme.plugin.complete_dns_challenge', return_value="test")
def test_finalize_authorizations(self, mock_complete_dns_challenge):
mock_authz = []
mock_authz_record = MagicMock()
mock_authz_record.authz = Mock()
mock_authz_record.change_id = 1
mock_authz_record.dns_challenge.validation_domain_name = Mock()
mock_authz_record.dns_challenge.validation = Mock()
mock_authz.append(mock_authz_record)
mock_dns_provider = Mock()
mock_dns_provider.delete_txt_record = Mock()
mock_acme_client = Mock()
result = plugin.finalize_authorizations(mock_acme_client, "account_number", mock_dns_provider, mock_authz)
self.assertEqual(result, mock_authz)
@patch('lemur.plugins.lemur_acme.plugin.current_app')
def test_create_authority(self, mock_current_app):
mock_current_app.config = Mock()
options = {
"plugin": {
"plugin_options": [{
"name": "certificate",
"value": "123"
}]
}
}
acme_root, b, role = self.ACMEIssuerPlugin.create_authority(options)
self.assertEqual(acme_root, "123")
self.assertEqual(b, "")
self.assertEqual(role, [{'username': '', 'password': '', 'name': 'acme'}])
@patch('lemur.plugins.lemur_acme.plugin.current_app')
@patch('lemur.plugins.lemur_acme.dyn.current_app')
@patch('lemur.plugins.lemur_acme.cloudflare.current_app')
def test_get_dns_provider(self, mock_current_app_cloudflare, mock_current_app_dyn, mock_current_app):
provider = plugin.ACMEIssuerPlugin()
route53 = provider.get_dns_provider("route53")
assert route53
cloudflare = provider.get_dns_provider("cloudflare")
assert cloudflare
dyn = provider.get_dns_provider("dyn")
assert dyn
@patch('lemur.plugins.lemur_acme.plugin.setup_acme_client')
@patch('lemur.plugins.lemur_acme.plugin.current_app')
@patch('lemur.plugins.lemur_acme.plugin.authorization_service')
@patch('lemur.plugins.lemur_acme.plugin.dns_provider_service')
@patch('lemur.plugins.lemur_acme.plugin.get_authorizations')
@patch('lemur.plugins.lemur_acme.plugin.finalize_authorizations')
@patch('lemur.plugins.lemur_acme.plugin.request_certificate')
def test_get_ordered_certificate(
self, mock_request_certificate, mock_finalize_authorizations, mock_get_authorizations,
mock_dns_provider_service, mock_authorization_service, mock_current_app, mock_acme):
mock_client = Mock()
mock_acme.return_value = (mock_client, "")
mock_request_certificate.return_value = ("pem_certificate", "chain")
mock_cert = Mock()
mock_cert.external_id = 1
provider = plugin.ACMEIssuerPlugin()
provider.get_dns_provider = Mock()
result = provider.get_ordered_certificate(mock_cert)
self.assertEqual(
result,
{
'body': "pem_certificate",
'chain': "chain",
'external_id': "1"
}
)
@patch('lemur.plugins.lemur_acme.plugin.setup_acme_client')
@patch('lemur.plugins.lemur_acme.plugin.current_app')
@patch('lemur.plugins.lemur_acme.plugin.authorization_service')
@patch('lemur.plugins.lemur_acme.plugin.dns_provider_service')
@patch('lemur.plugins.lemur_acme.plugin.get_authorizations')
@patch('lemur.plugins.lemur_acme.plugin.finalize_authorizations')
@patch('lemur.plugins.lemur_acme.plugin.request_certificate')
def test_get_ordered_certificates(
self, mock_request_certificate, mock_finalize_authorizations, mock_get_authorizations,
mock_dns_provider_service, mock_authorization_service, mock_current_app, mock_acme):
mock_client = Mock()
mock_acme.return_value = (mock_client, "")
mock_request_certificate.return_value = ("pem_certificate", "chain")
mock_cert = Mock()
mock_cert.external_id = 1
mock_cert2 = Mock()
mock_cert2.external_id = 2
provider = plugin.ACMEIssuerPlugin()
provider.get_dns_provider = Mock()
result = provider.get_ordered_certificates([mock_cert, mock_cert2])
self.assertEqual(len(result), 2)
self.assertEqual(result[0]['cert'], {'body': 'pem_certificate', 'chain': 'chain', 'external_id': '1'})
self.assertEqual(result[1]['cert'], {'body': 'pem_certificate', 'chain': 'chain', 'external_id': '2'})
@patch('lemur.plugins.lemur_acme.plugin.setup_acme_client')
@patch('lemur.plugins.lemur_acme.plugin.dns_provider_service')
@patch('lemur.plugins.lemur_acme.plugin.current_app')
@patch('lemur.plugins.lemur_acme.plugin.get_authorizations')
@patch('lemur.plugins.lemur_acme.plugin.finalize_authorizations')
@patch('lemur.plugins.lemur_acme.plugin.request_certificate')
@patch('lemur.plugins.lemur_acme.plugin.authorization_service')
def test_create_certificate(self, mock_authorization_service, mock_request_certificate, mock_finalize_authorizations, mock_get_authorizations,
mock_current_app, mock_dns_provider_service, mock_acme):
provider = plugin.ACMEIssuerPlugin()
mock_authority = Mock()
mock_client = Mock()
mock_acme.return_value = (mock_client, "")
mock_dns_provider = Mock()
mock_dns_provider.credentials = '{"account_id": 1}'
mock_dns_provider.provider_type = "route53"
mock_dns_provider_service.get.return_value = mock_dns_provider
issuer_options = {
'authority': mock_authority,
'dns_provider': mock_dns_provider,
"common_name": "test.netflix.net"
}
csr = "123"
mock_request_certificate.return_value = ("pem_certificate", "chain")
result = provider.create_certificate(csr, issuer_options)
assert result

View File

@@ -1,7 +1,7 @@
"""
.. module: lemur.plugins.lemur_atlas.plugin
:platform: Unix
:copyright: (c) 2016 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>

View File

@@ -2,7 +2,7 @@
.. module: lemur.plugins.lemur_aws.iam
:platform: Unix
:synopsis: Contains helper functions for interactive with AWS IAM Apis.
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""

View File

@@ -1,7 +1,7 @@
"""
.. module: lemur.plugins.lemur_aws.plugin
:platform: Unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
Terraform example to setup the destination bucket:

View File

@@ -2,7 +2,7 @@
.. module: lemur.plugins.lemur_aws.s3
:platform: Unix
:synopsis: Contains helper functions for interactive with AWS S3 Apis.
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""

View File

@@ -1,7 +1,7 @@
"""
.. module: lemur.plugins.lemur_aws.sts
:platform: Unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""

View File

@@ -2,7 +2,7 @@
.. module: lemur.plugins.lemur_cfssl.plugin
:platform: Unix
:synopsis: This module is responsible for communicating with the CFSSL private CA.
:copyright: (c) 2016 by Thomson Reuters
:copyright: (c) 2018 by Thomson Reuters
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Charles Hendrie <chad.hendrie@tr.com>

View File

@@ -1,7 +1,7 @@
"""
.. module: lemur.plugins.lemur_cryptography.plugin
:platform: Unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>

View File

@@ -325,8 +325,9 @@ class DigiCertIssuerPlugin(IssuerPlugin):
response = self.session.put(create_url, data=json.dumps({'comments': comments}))
return handle_response(response)
def get_ordered_certificate(self, order_id):
def get_ordered_certificate(self, pending_cert):
""" Retrieve a certificate via order id """
order_id = pending_cert.external_id
base_url = current_app.config.get('DIGICERT_URL')
try:
certificate_id = get_certificate_id(self.session, base_url, order_id)

View File

@@ -150,11 +150,7 @@ def test_signature_hash(app):
signature_hash('sdfdsf')
def test_issuer_plugin_create_certificate():
import requests_mock
from lemur.plugins.lemur_digicert.plugin import DigiCertIssuerPlugin
pem_fixture = """\
def test_issuer_plugin_create_certificate(certificate_="""\
-----BEGIN CERTIFICATE-----
abc
-----END CERTIFICATE-----
@@ -164,7 +160,11 @@ def
-----BEGIN CERTIFICATE-----
ghi
-----END CERTIFICATE-----
"""
"""):
import requests_mock
from lemur.plugins.lemur_digicert.plugin import DigiCertIssuerPlugin
pem_fixture = certificate_
subject = DigiCertIssuerPlugin()
adapter = requests_mock.Adapter()

View File

@@ -1,7 +1,7 @@
"""
.. module: lemur.plugins.lemur_email.plugin
:platform: Unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>

View File

@@ -1,7 +1,7 @@
"""
.. module: lemur.plugins.lemur_java.plugin
:platform: Unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>

View File

@@ -1,7 +1,7 @@
"""
.. module: lemur.plugins.lemur_kubernetes.plugin
:platform: Unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.

View File

@@ -1,7 +1,7 @@
"""
.. module: lemur.plugins.lemur_openssl.plugin
:platform: Unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>

View File

@@ -2,7 +2,7 @@
.. module: lemur.plugins.lemur_sftp.plugin
:platform: Unix
:synopsis: Allow the uploading of certificates to SFTP.
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
Allow the uploading of certificates to SFTP.

View File

@@ -1,7 +1,7 @@
"""
.. module: lemur.plugins.lemur_slack.plugin
:platform: Unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Harm Weites <harm@weites.com>

View File

@@ -2,7 +2,7 @@
.. module: lemur.plugins.lemur_verisign.plugin
:platform: Unix
:synopsis: This module is responsible for communicating with the VeriSign VICE 2.0 API.
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>

View File

@@ -1,7 +1,7 @@
"""
.. module: service
:platform: Unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""

View File

@@ -2,7 +2,7 @@
.. module: lemur.plugins.utils
:platform: Unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>

View File

@@ -2,7 +2,7 @@
.. module: lemur.plugins.views
:platform: Unix
:synopsis: This module contains all of the accounts view code.
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""