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
|
import sys
|
||||||
|
|
||||||
from lemur.constants import SUCCESS_METRIC_STATUS
|
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.dns_providers.service import get_all_dns_providers, set_domains
|
||||||
from lemur.extensions import metrics, sentry
|
from lemur.extensions import metrics, sentry
|
||||||
from lemur.plugins.base import plugins
|
from lemur.plugins.base import plugins
|
||||||
|
@ -19,7 +20,7 @@ def get_all_zones():
|
||||||
"""
|
"""
|
||||||
print("[+] Starting dns provider zone lookup and configuration.")
|
print("[+] Starting dns provider zone lookup and configuration.")
|
||||||
dns_providers = get_all_dns_providers()
|
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}"
|
function = f"{__name__}.{sys._getframe().f_code.co_name}"
|
||||||
log_data = {
|
log_data = {
|
||||||
|
@ -29,7 +30,7 @@ def get_all_zones():
|
||||||
|
|
||||||
for dns_provider in dns_providers:
|
for dns_provider in dns_providers:
|
||||||
try:
|
try:
|
||||||
zones = acme_plugin.get_all_zones(dns_provider)
|
zones = acme_dns_handler.get_all_zones(dns_provider)
|
||||||
set_domains(dns_provider, zones)
|
set_domains(dns_provider, zones)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("[+] Error with DNS Provider {}: {}".format(dns_provider.name, 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}")
|
current_app.logger.error(f"Unable to fetch DNS Providers: {e}")
|
||||||
self.all_dns_providers = []
|
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):
|
def get_dns_challenges(self, host, authorizations):
|
||||||
"""Get dns challenges for provided domain"""
|
"""Get dns challenges for provided domain"""
|
||||||
|
|
||||||
|
@ -393,27 +399,8 @@ class AcmeDnsHandler(AcmeHandler):
|
||||||
def finalize_authorizations(self, acme_client, authorizations):
|
def finalize_authorizations(self, acme_client, authorizations):
|
||||||
for authz_record in authorizations:
|
for authz_record in authorizations:
|
||||||
self.complete_dns_challenge(acme_client, authz_record)
|
self.complete_dns_challenge(acme_client, authz_record)
|
||||||
for authz_record in authorizations:
|
|
||||||
dns_challenges = authz_record.dns_challenge
|
self.cleanup_dns_challenges(acme_client, authorizations)
|
||||||
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),
|
|
||||||
)
|
|
||||||
|
|
||||||
return authorizations
|
return authorizations
|
||||||
|
|
||||||
|
|
|
@ -14,11 +14,15 @@ import OpenSSL
|
||||||
from acme import challenges
|
from acme import challenges
|
||||||
from flask import current_app
|
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.plugins.base import plugins
|
||||||
from lemur.destinations import service as destination_service
|
from lemur.destinations import service as destination_service
|
||||||
from lemur.destinations.models import Destination
|
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):
|
class AcmeChallengeMissmatchError(LemurException):
|
||||||
|
@ -53,10 +57,11 @@ class AcmeChallenge(object):
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
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
|
Ideally the challenge should be cleaned up, after the validation is done
|
||||||
:param challenge: Needed to identify the challenge to be removed
|
: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
|
:param validation_target: Needed to remove the validation
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
@ -73,9 +78,9 @@ class AcmeHttpChallenge(AcmeChallenge):
|
||||||
:param issuer_options:
|
:param issuer_options:
|
||||||
:return: :raise Exception:
|
:return: :raise Exception:
|
||||||
"""
|
"""
|
||||||
acme = AcmeHandler()
|
self.acme = AcmeHandler()
|
||||||
authority = issuer_options.get("authority")
|
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)
|
orderr = acme_client.new_order(csr)
|
||||||
|
|
||||||
|
@ -149,15 +154,101 @@ class AcmeHttpChallenge(AcmeChallenge):
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def cleanup(self, challenge, validation_target):
|
def cleanup(self, challenge, acme_client, validation_target):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AcmeDnsChallenge(AcmeChallenge):
|
class AcmeDnsChallenge(AcmeChallenge):
|
||||||
challengeType = challenges.DNS01
|
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):
|
def deploy(self, challenge, acme_client, validation_target):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def cleanup(self, challenge, validation_target):
|
def cleanup(self, authorizations, acme_client, validation_target):
|
||||||
pass
|
"""
|
||||||
|
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:: Mikhail Khodorovskiy <mikhail.khodorovskiy@jivesoftware.com>
|
||||||
.. moduleauthor:: Curtis Castrapel <ccastrapel@netflix.com>
|
.. moduleauthor:: Curtis Castrapel <ccastrapel@netflix.com>
|
||||||
"""
|
"""
|
||||||
import json
|
|
||||||
|
|
||||||
import OpenSSL.crypto
|
import OpenSSL.crypto
|
||||||
import josepy as jose
|
import josepy as jose
|
||||||
|
@ -23,14 +22,13 @@ from flask import current_app
|
||||||
|
|
||||||
from lemur.authorizations import service as authorization_service
|
from lemur.authorizations import service as authorization_service
|
||||||
from lemur.dns_providers import service as dns_provider_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.extensions import metrics, sentry
|
||||||
|
|
||||||
from lemur.plugins import lemur_acme as acme
|
from lemur.plugins import lemur_acme as acme
|
||||||
from lemur.plugins.bases import IssuerPlugin
|
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.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):
|
class ACMEIssuerPlugin(IssuerPlugin):
|
||||||
|
@ -84,28 +82,6 @@ class ACMEIssuerPlugin(IssuerPlugin):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(ACMEIssuerPlugin, self).__init__(*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):
|
def get_ordered_certificate(self, pending_cert):
|
||||||
self.acme = AcmeDnsHandler()
|
self.acme = AcmeDnsHandler()
|
||||||
acme_client, registration = self.acme.setup_acme_client(pending_cert.authority)
|
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):
|
def get_ordered_certificates(self, pending_certs):
|
||||||
self.acme = AcmeDnsHandler()
|
self.acme = AcmeDnsHandler()
|
||||||
|
self.acme_dns_challenge = AcmeDnsChallenge()
|
||||||
pending = []
|
pending = []
|
||||||
certs = []
|
certs = []
|
||||||
for pending_cert in pending_certs:
|
for pending_cert in pending_certs:
|
||||||
|
@ -250,76 +227,23 @@ class ACMEIssuerPlugin(IssuerPlugin):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
# Ensure DNS records get deleted
|
# Ensure DNS records get deleted
|
||||||
self.acme.cleanup_dns_challenges(
|
self.acme_dns_challenge.cleanup(
|
||||||
entry["acme_client"], entry["authorizations"]
|
entry["authorizations"], entry["acme_client"]
|
||||||
)
|
)
|
||||||
return certs
|
return certs
|
||||||
|
|
||||||
def create_certificate(self, csr, issuer_options):
|
def create_certificate(self, csr, issuer_options):
|
||||||
"""
|
"""
|
||||||
Creates an ACME certificate.
|
Creates an ACME certificate using the DNS-01 challenge.
|
||||||
|
|
||||||
:param csr:
|
:param csr:
|
||||||
:param issuer_options:
|
:param issuer_options:
|
||||||
:return: :raise Exception:
|
:return: :raise Exception:
|
||||||
"""
|
"""
|
||||||
self.acme = AcmeDnsHandler()
|
acme_dns_challenge = AcmeDnsChallenge()
|
||||||
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:
|
return acme_dns_challenge.create_certificate(csr, issuer_options)
|
||||||
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
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_authority(options):
|
def create_authority(options):
|
||||||
|
|
Loading…
Reference in New Issue