summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndreas Runfalk <andreas@runfalk.se>2017-05-10 13:46:27 +0200
committerAndreas Runfalk <andreas@runfalk.se>2017-05-10 13:46:27 +0200
commit63c76a64c5e42dbb9c9f21a62997135b64aa5b9a (patch)
tree9e3691d725855e660c02ade780d3c3888264d792
parent418da2e366fdc9bdf8729c7b1a50563bc2890fd5 (diff)
downloadcertbot-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.py126
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