Merge branch 'master' into linuxdst
This commit is contained in:
@@ -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>
|
||||
|
@@ -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)
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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):
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import time
|
||||
import CloudFlare
|
||||
|
||||
import CloudFlare
|
||||
from flask import current_app
|
||||
|
||||
|
||||
|
131
lemur/plugins/lemur_acme/dyn.py
Normal file
131
lemur/plugins/lemur_acme/dyn.py
Normal 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
|
@@ -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]
|
||||
|
@@ -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
|
||||
)
|
||||
|
@@ -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
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
"""
|
||||
|
@@ -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:
|
||||
|
@@ -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>
|
||||
"""
|
||||
|
@@ -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>
|
||||
"""
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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)
|
||||
|
@@ -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()
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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.
|
||||
|
||||
|
||||
|
@@ -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>
|
||||
|
@@ -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.
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
"""
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
"""
|
||||
|
Reference in New Issue
Block a user