Refactor Acme plugin into AcmeChallenge objects, dns01
This commit is contained in:
parent
b91cebf245
commit
812e1dee92
|
@ -3,6 +3,7 @@ from flask_script import Manager
|
|||
import sys
|
||||
|
||||
from lemur.constants import SUCCESS_METRIC_STATUS
|
||||
from lemur.plugins.lemur_acme.acme_handlers import AcmeDnsHandler
|
||||
from lemur.dns_providers.service import get_all_dns_providers, set_domains
|
||||
from lemur.extensions import metrics, sentry
|
||||
from lemur.plugins.base import plugins
|
||||
|
@ -19,7 +20,7 @@ def get_all_zones():
|
|||
"""
|
||||
print("[+] Starting dns provider zone lookup and configuration.")
|
||||
dns_providers = get_all_dns_providers()
|
||||
acme_plugin = plugins.get("acme-issuer")
|
||||
acme_dns_handler = AcmeDnsHandler()
|
||||
|
||||
function = f"{__name__}.{sys._getframe().f_code.co_name}"
|
||||
log_data = {
|
||||
|
@ -29,7 +30,7 @@ def get_all_zones():
|
|||
|
||||
for dns_provider in dns_providers:
|
||||
try:
|
||||
zones = acme_plugin.get_all_zones(dns_provider)
|
||||
zones = acme_dns_handler.get_all_zones(dns_provider)
|
||||
set_domains(dns_provider, zones)
|
||||
except Exception as e:
|
||||
print("[+] Error with DNS Provider {}: {}".format(dns_provider.name, e))
|
||||
|
|
|
@ -229,6 +229,12 @@ class AcmeDnsHandler(AcmeHandler):
|
|||
current_app.logger.error(f"Unable to fetch DNS Providers: {e}")
|
||||
self.all_dns_providers = []
|
||||
|
||||
def get_all_zones(self, dns_provider):
|
||||
dns_provider_options = json.loads(dns_provider.credentials)
|
||||
account_number = dns_provider_options.get("account_id")
|
||||
dns_provider_plugin = self.get_dns_provider(dns_provider.provider_type)
|
||||
return dns_provider_plugin.get_zones(account_number=account_number)
|
||||
|
||||
def get_dns_challenges(self, host, authorizations):
|
||||
"""Get dns challenges for provided domain"""
|
||||
|
||||
|
@ -393,27 +399,8 @@ class AcmeDnsHandler(AcmeHandler):
|
|||
def finalize_authorizations(self, acme_client, authorizations):
|
||||
for authz_record in authorizations:
|
||||
self.complete_dns_challenge(acme_client, authz_record)
|
||||
for authz_record in authorizations:
|
||||
dns_challenges = authz_record.dns_challenge
|
||||
for dns_challenge in dns_challenges:
|
||||
dns_providers = self.dns_providers_for_domain.get(authz_record.host)
|
||||
for dns_provider in dns_providers:
|
||||
# Grab account number (For Route53)
|
||||
dns_provider_plugin = self.get_dns_provider(
|
||||
dns_provider.provider_type
|
||||
)
|
||||
dns_provider_options = json.loads(dns_provider.credentials)
|
||||
account_number = dns_provider_options.get("account_id")
|
||||
host_to_validate, _ = self.strip_wildcard(authz_record.host)
|
||||
host_to_validate = self.maybe_add_extension(
|
||||
host_to_validate, dns_provider_options
|
||||
)
|
||||
dns_provider_plugin.delete_txt_record(
|
||||
authz_record.change_id,
|
||||
account_number,
|
||||
dns_challenge.validation_domain_name(host_to_validate),
|
||||
dns_challenge.validation(acme_client.client.net.key),
|
||||
)
|
||||
|
||||
self.cleanup_dns_challenges(acme_client, authorizations)
|
||||
|
||||
return authorizations
|
||||
|
||||
|
|
|
@ -14,11 +14,15 @@ import OpenSSL
|
|||
from acme import challenges
|
||||
from flask import current_app
|
||||
|
||||
from lemur.exceptions import LemurException
|
||||
from lemur.dns_providers import service as dns_provider_service
|
||||
from lemur.extensions import metrics, sentry
|
||||
|
||||
from lemur.authorizations import service as authorization_service
|
||||
from lemur.exceptions import LemurException, InvalidConfiguration
|
||||
from lemur.plugins.base import plugins
|
||||
from lemur.destinations import service as destination_service
|
||||
from lemur.destinations.models import Destination
|
||||
from lemur.plugins.lemur_acme.acme_handlers import AcmeHandler
|
||||
from lemur.plugins.lemur_acme.acme_handlers import AcmeHandler, AcmeDnsHandler
|
||||
|
||||
|
||||
class AcmeChallengeMissmatchError(LemurException):
|
||||
|
@ -53,10 +57,11 @@ class AcmeChallenge(object):
|
|||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def cleanup(self, challenge, validation_target):
|
||||
def cleanup(self, challenge, acme_client, validation_target):
|
||||
"""
|
||||
Ideally the challenge should be cleaned up, after the validation is done
|
||||
:param challenge: Needed to identify the challenge to be removed
|
||||
:param acme_client: an already bootstrapped acme_client, to avoid passing all issuer_options and so on
|
||||
:param validation_target: Needed to remove the validation
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
@ -73,9 +78,9 @@ class AcmeHttpChallenge(AcmeChallenge):
|
|||
:param issuer_options:
|
||||
:return: :raise Exception:
|
||||
"""
|
||||
acme = AcmeHandler()
|
||||
self.acme = AcmeHandler()
|
||||
authority = issuer_options.get("authority")
|
||||
acme_client, registration = acme.setup_acme_client(authority)
|
||||
acme_client, registration = self.acme.setup_acme_client(authority)
|
||||
|
||||
orderr = acme_client.new_order(csr)
|
||||
|
||||
|
@ -149,15 +154,101 @@ class AcmeHttpChallenge(AcmeChallenge):
|
|||
|
||||
return response
|
||||
|
||||
def cleanup(self, challenge, validation_target):
|
||||
def cleanup(self, challenge, acme_client, validation_target):
|
||||
pass
|
||||
|
||||
|
||||
class AcmeDnsChallenge(AcmeChallenge):
|
||||
challengeType = challenges.DNS01
|
||||
|
||||
def __init__(self):
|
||||
self.dns_providers_for_domain = {}
|
||||
try:
|
||||
self.all_dns_providers = dns_provider_service.get_all_dns_providers()
|
||||
except Exception as e:
|
||||
metrics.send("AcmeHandler_init_error", "counter", 1)
|
||||
sentry.captureException()
|
||||
current_app.logger.error(f"Unable to fetch DNS Providers: {e}")
|
||||
self.all_dns_providers = []
|
||||
|
||||
def create_certificate(self, csr, issuer_options):
|
||||
"""
|
||||
Creates an ACME certificate.
|
||||
|
||||
:param csr:
|
||||
:param issuer_options:
|
||||
:return: :raise Exception:
|
||||
"""
|
||||
self.acme = AcmeDnsHandler()
|
||||
authority = issuer_options.get("authority")
|
||||
create_immediately = issuer_options.get("create_immediately", False)
|
||||
acme_client, registration = self.acme.setup_acme_client(authority)
|
||||
dns_provider = issuer_options.get("dns_provider", {})
|
||||
|
||||
if dns_provider:
|
||||
dns_provider_options = dns_provider.options
|
||||
credentials = json.loads(dns_provider.credentials)
|
||||
current_app.logger.debug(
|
||||
"Using DNS provider: {0}".format(dns_provider.provider_type)
|
||||
)
|
||||
dns_provider_plugin = __import__(
|
||||
dns_provider.provider_type, globals(), locals(), [], 1
|
||||
)
|
||||
account_number = credentials.get("account_id")
|
||||
provider_type = dns_provider.provider_type
|
||||
if 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)
|
||||
else:
|
||||
dns_provider = {}
|
||||
dns_provider_options = None
|
||||
account_number = None
|
||||
provider_type = None
|
||||
|
||||
domains = self.acme.get_domains(issuer_options)
|
||||
if not create_immediately:
|
||||
# Create pending authorizations that we'll need to do the creation
|
||||
dns_authorization = authorization_service.create(
|
||||
account_number, domains, provider_type
|
||||
)
|
||||
# Return id of the DNS Authorization
|
||||
return None, None, dns_authorization.id
|
||||
|
||||
authorizations = self.acme.get_authorizations(
|
||||
acme_client,
|
||||
account_number,
|
||||
domains,
|
||||
dns_provider_plugin,
|
||||
dns_provider_options,
|
||||
)
|
||||
self.acme.finalize_authorizations(
|
||||
acme_client,
|
||||
account_number,
|
||||
dns_provider_plugin,
|
||||
authorizations,
|
||||
dns_provider_options,
|
||||
)
|
||||
pem_certificate, pem_certificate_chain = self.acme.request_certificate(
|
||||
acme_client, authorizations, csr
|
||||
)
|
||||
# TODO add external ID (if possible)
|
||||
return pem_certificate, pem_certificate_chain, None
|
||||
|
||||
def deploy(self, challenge, acme_client, validation_target):
|
||||
pass
|
||||
|
||||
def cleanup(self, challenge, validation_target):
|
||||
pass
|
||||
def cleanup(self, authorizations, acme_client, validation_target):
|
||||
"""
|
||||
Best effort attempt to delete DNS challenges that may not have been deleted previously. This is usually called
|
||||
on an exception
|
||||
|
||||
:param authorizations: all the authorizations to be cleaned up
|
||||
:param acme_client: an already bootstrapped acme_client, to avoid passing all issuer_options and so on
|
||||
:param validation_target: Unused right now
|
||||
:return:
|
||||
"""
|
||||
acme = AcmeDnsHandler()
|
||||
acme.cleanup_dns_challenges(acme_client, authorizations)
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
.. moduleauthor:: Mikhail Khodorovskiy <mikhail.khodorovskiy@jivesoftware.com>
|
||||
.. moduleauthor:: Curtis Castrapel <ccastrapel@netflix.com>
|
||||
"""
|
||||
import json
|
||||
|
||||
import OpenSSL.crypto
|
||||
import josepy as jose
|
||||
|
@ -23,14 +22,13 @@ from flask import current_app
|
|||
|
||||
from lemur.authorizations import service as authorization_service
|
||||
from lemur.dns_providers import service as dns_provider_service
|
||||
from lemur.exceptions import InvalidConfiguration, UnknownProvider
|
||||
from lemur.exceptions import InvalidConfiguration
|
||||
from lemur.extensions import metrics, sentry
|
||||
|
||||
from lemur.plugins import lemur_acme as acme
|
||||
from lemur.plugins.bases import IssuerPlugin
|
||||
from lemur.plugins.lemur_acme import cloudflare, dyn, route53, ultradns, powerdns
|
||||
from lemur.plugins.lemur_acme.acme_handlers import AcmeHandler, AcmeDnsHandler
|
||||
from lemur.plugins.lemur_acme.challenge_types import AcmeHttpChallenge
|
||||
from lemur.plugins.lemur_acme.challenge_types import AcmeHttpChallenge, AcmeDnsChallenge
|
||||
|
||||
|
||||
class ACMEIssuerPlugin(IssuerPlugin):
|
||||
|
@ -84,28 +82,6 @@ class ACMEIssuerPlugin(IssuerPlugin):
|
|||
def __init__(self, *args, **kwargs):
|
||||
super(ACMEIssuerPlugin, self).__init__(*args, **kwargs)
|
||||
|
||||
def get_dns_provider(self, type):
|
||||
self.acme = AcmeDnsHandler()
|
||||
|
||||
provider_types = {
|
||||
"cloudflare": cloudflare,
|
||||
"dyn": dyn,
|
||||
"route53": route53,
|
||||
"ultradns": ultradns,
|
||||
"powerdns": powerdns
|
||||
}
|
||||
provider = provider_types.get(type)
|
||||
if not provider:
|
||||
raise UnknownProvider("No such DNS provider: {}".format(type))
|
||||
return provider
|
||||
|
||||
def get_all_zones(self, dns_provider):
|
||||
self.acme = AcmeDnsHandler()
|
||||
dns_provider_options = json.loads(dns_provider.credentials)
|
||||
account_number = dns_provider_options.get("account_id")
|
||||
dns_provider_plugin = self.get_dns_provider(dns_provider.provider_type)
|
||||
return dns_provider_plugin.get_zones(account_number=account_number)
|
||||
|
||||
def get_ordered_certificate(self, pending_cert):
|
||||
self.acme = AcmeDnsHandler()
|
||||
acme_client, registration = self.acme.setup_acme_client(pending_cert.authority)
|
||||
|
@ -154,6 +130,7 @@ class ACMEIssuerPlugin(IssuerPlugin):
|
|||
|
||||
def get_ordered_certificates(self, pending_certs):
|
||||
self.acme = AcmeDnsHandler()
|
||||
self.acme_dns_challenge = AcmeDnsChallenge()
|
||||
pending = []
|
||||
certs = []
|
||||
for pending_cert in pending_certs:
|
||||
|
@ -250,76 +227,23 @@ class ACMEIssuerPlugin(IssuerPlugin):
|
|||
}
|
||||
)
|
||||
# Ensure DNS records get deleted
|
||||
self.acme.cleanup_dns_challenges(
|
||||
entry["acme_client"], entry["authorizations"]
|
||||
self.acme_dns_challenge.cleanup(
|
||||
entry["authorizations"], entry["acme_client"]
|
||||
)
|
||||
return certs
|
||||
|
||||
def create_certificate(self, csr, issuer_options):
|
||||
"""
|
||||
Creates an ACME certificate.
|
||||
Creates an ACME certificate using the DNS-01 challenge.
|
||||
|
||||
:param csr:
|
||||
:param issuer_options:
|
||||
:return: :raise Exception:
|
||||
"""
|
||||
self.acme = AcmeDnsHandler()
|
||||
authority = issuer_options.get("authority")
|
||||
create_immediately = issuer_options.get("create_immediately", False)
|
||||
acme_client, registration = self.acme.setup_acme_client(authority)
|
||||
dns_provider = issuer_options.get("dns_provider", {})
|
||||
acme_dns_challenge = AcmeDnsChallenge()
|
||||
|
||||
if dns_provider:
|
||||
dns_provider_options = dns_provider.options
|
||||
credentials = json.loads(dns_provider.credentials)
|
||||
current_app.logger.debug(
|
||||
"Using DNS provider: {0}".format(dns_provider.provider_type)
|
||||
)
|
||||
dns_provider_plugin = __import__(
|
||||
dns_provider.provider_type, globals(), locals(), [], 1
|
||||
)
|
||||
account_number = credentials.get("account_id")
|
||||
provider_type = dns_provider.provider_type
|
||||
if 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)
|
||||
else:
|
||||
dns_provider = {}
|
||||
dns_provider_options = None
|
||||
account_number = None
|
||||
provider_type = None
|
||||
return acme_dns_challenge.create_certificate(csr, issuer_options)
|
||||
|
||||
domains = self.acme.get_domains(issuer_options)
|
||||
if not create_immediately:
|
||||
# Create pending authorizations that we'll need to do the creation
|
||||
dns_authorization = authorization_service.create(
|
||||
account_number, domains, provider_type
|
||||
)
|
||||
# Return id of the DNS Authorization
|
||||
return None, None, dns_authorization.id
|
||||
|
||||
authorizations = self.acme.get_authorizations(
|
||||
acme_client,
|
||||
account_number,
|
||||
domains,
|
||||
dns_provider_plugin,
|
||||
dns_provider_options,
|
||||
)
|
||||
self.acme.finalize_authorizations(
|
||||
acme_client,
|
||||
account_number,
|
||||
dns_provider_plugin,
|
||||
authorizations,
|
||||
dns_provider_options,
|
||||
)
|
||||
pem_certificate, pem_certificate_chain = self.acme.request_certificate(
|
||||
acme_client, authorizations, csr
|
||||
)
|
||||
# TODO add external ID (if possible)
|
||||
return pem_certificate, pem_certificate_chain, None
|
||||
|
||||
@staticmethod
|
||||
def create_authority(options):
|
||||
|
|
Loading…
Reference in New Issue