diff options
author | Andreas Runfalk <andreas@runfalk.se> | 2017-05-10 13:46:27 +0200 |
---|---|---|
committer | Andreas Runfalk <andreas@runfalk.se> | 2017-05-10 13:46:27 +0200 |
commit | 63c76a64c5e42dbb9c9f21a62997135b64aa5b9a (patch) | |
tree | 9e3691d725855e660c02ade780d3c3888264d792 | |
parent | 418da2e366fdc9bdf8729c7b1a50563bc2890fd5 (diff) | |
download | certbot-loopia-63c76a64c5e42dbb9c9f21a62997135b64aa5b9a.zip certbot-loopia-63c76a64c5e42dbb9c9f21a62997135b64aa5b9a.tar.gz certbot-loopia-63c76a64c5e42dbb9c9f21a62997135b64aa5b9a.tar.bz2 |
Added self verification support and retry strategies
-rw-r--r-- | certbot_loopia.py | 126 |
1 files changed, 106 insertions, 20 deletions
diff --git a/certbot_loopia.py b/certbot_loopia.py index f6f0d98..7792802 100644 --- a/certbot_loopia.py +++ b/certbot_loopia.py @@ -1,16 +1,40 @@ import logging +import itertools +import re import zope.interface from acme.challenges import DNS01 +from acme.dns_resolver import DNS_AVAILABLE from acme.errors import DependencyError from acme.jose.b64 import b64encode from certbot.plugins.common import Plugin from certbot.interfaces import IAuthenticator, IPluginFactory +from datetime import datetime, timedelta from loopialib import DnsRecord, Loopia from time import sleep logger = logging.getLogger(__name__) + +def parse_time(time): + pattern = r"^(?:(?P<d>\d+)d)?(?:(?P<h>\d+)h)?(?:(?P<m>\d+)m)?(?:(?P<s>\d+)s?)?$" + multipliers = { + "d": 86400, + "h": 3600, + "m": 60, + "s": 1, + } + + match = re.match(pattern, time) + if match is None: + raise ValueError("Invalid time format") + + return sum( + int(v) * multipliers[k] + for k, v in match.groupdict().items() + if v is not None) + + class Domain(object): def __init__(self, subdomain=None, name=None, top_domain=None): self.subdomain = subdomain @@ -54,6 +78,37 @@ class LoopiaAuthenticator(Plugin): def add_parser_arguments(cls, add): add("user", action="store", help="Loopia API username.") add("password", action="store", help="Loopia API password.") + add( + "time-limit", + action="store", + default="30m", + help="Time limit for retries on local challenge verification.") + add( + "time-delay", + action="store", + default="1m", + help="Time before trying to verify challange locally.") + add( + "retry-interval", + action="store", + default="30s", + help="Time between each localverification retry.") + + @property + def time_limit(self): + return parse_time(self.conf("time-limit")) + + @property + def time_delay(self): + return parse_time(self.conf("time-delay")) + + @property + def retry_interval(self): + return parse_time(self.conf("retry-interval")) + + @property + def _can_self_verify(self): + return DNS_AVAILABLE def prepare(self): """ @@ -73,6 +128,22 @@ class LoopiaAuthenticator(Plugin): ns=self.option_namespace, opt="password")) + # Verify correctness of other options + try: + self.time_limit + except ValueError: + raise ValueError("Invalid format for time limit.") + + try: + self.time_delay + except ValueError: + raise ValueError("Invalid format for time delay.") + + try: + self.retry_interval + except ValueError: + raise ValueError("Invalid format for retry interval.") + self.loopia = Loopia(self.conf("user"), self.conf("password")) def more_info(self): @@ -86,6 +157,7 @@ class LoopiaAuthenticator(Plugin): """ Return a list of all supported challenge types of this plugin. """ + return [DNS01] def verify_challenge(self, achall, response): @@ -135,27 +207,41 @@ class LoopiaAuthenticator(Plugin): # Verify the result of adding the zone record. If the verification fails # it will retry twice after a delay since changes may not have # propagated yet. - tries = 20 - try: - for i in range(tries): - if self.verify_challenge(achall, response): - break - elif i < tries - 1: - seconds = 30 * (i + 1) - logger.info( - "Retrying self-verification in {} seconds.".format(seconds)) - sleep(seconds) - else: - # It is possible that the DNS change did not propagate - # yet, in which case we will make the authentication fail. - logger.warning( - "Unable to verify that the challenge was completed.") - return - except DependencyError: - logger.info( + if not self._can_self_verify: + logger.info(( "Self-verification is unavailable. Trying to authenticate in " - "60 seconds.") - sleep(60) + "{} seconds.").format(self.time_delay)) + sleep(self.time_delay) + return response + + start_time = datetime.now() + max_time = start_time + timedelta(seconds=self.time_limit) + for i in itertools.count(): + logger.debug("Self-verification try #{}".format(i + 1)) + + current_time = datetime.now() + if i == 0: + sleep(self.time_delay) + else: + # Sleep for the remaining time of the + delay = self.retry_interval - ( + current_time - start_time).total_seconds() + sleep(0 if delay < 0 else delay) + + if self.verify_challenge(achall, response): + break + elif current_time < max_time: + seconds = 30 * (i + 1) + logger.info( + "Retrying self-verification in {} seconds.".format(seconds)) + sleep(seconds) + else: + # It is possible that the DNS change did not propagate + # yet, in which case we will make the authentication fail. + logger.warning( + "Unable to verify that the challenge was completed.") + return + return response |