Oh!, the plugin version that we get installing it with apt
is not the latest one!
Also I was reading the code from here https://certbot-dns-route53.readthedocs.io/en/latest/_modules/certbot_dns_route53/dns_route53.html (which look to be the latest one) and the installed plugin code, and I didnât realize it! indeed, in my unknowing of python I missed the line for achall in achalls
in the web code that also was the one that I pasted in my comment, all looks to be working as you said.
This is the actual code that we have in the plugin:
"""Certbot Route53 authenticator plugin."""
import collections
import logging
import time
import boto3
import zope.interface
from botocore.exceptions import NoCredentialsError, ClientError
from certbot import errors
from certbot import interfaces
from certbot.plugins import dns_common
logger = logging.getLogger(__name__)
INSTRUCTIONS = (
"To use certbot-dns-route53, configure credentials as described at "
"https://boto3.readthedocs.io/en/latest/guide/configuration.html#best-practices-for-configuring-credentials " # pylint: disable=line-too-long
"and add the necessary permissions for Route53 access.")
@zope.interface.implementer(interfaces.IAuthenticator)
@zope.interface.provider(interfaces.IPluginFactory)
class Authenticator(dns_common.DNSAuthenticator):
"""Route53 Authenticator
This authenticator solves a DNS01 challenge by uploading the answer to AWS
Route53.
"""
description = ("Obtain certificates using a DNS TXT record (if you are using AWS Route53 for "
"DNS).")
ttl = 10
def __init__(self, *args, **kwargs):
super(Authenticator, self).__init__(*args, **kwargs)
self.r53 = boto3.client("route53")
self._resource_records = collections.defaultdict(list)
def more_info(self): # pylint: disable=missing-docstring,no-self-use
return "Solve a DNS01 challenge using AWS Route53"
def _setup_credentials(self):
pass
def _perform(self, domain, validation_domain_name, validation):
try:
change_id = self._change_txt_record("UPSERT", validation_domain_name, validation)
self._wait_for_change(change_id)
except (NoCredentialsError, ClientError) as e:
logger.debug('Encountered error during perform: %s', e, exc_info=True)
raise errors.PluginError("\n".join([str(e), INSTRUCTIONS]))
def _cleanup(self, domain, validation_domain_name, validation):
try:
self._change_txt_record("DELETE", validation_domain_name, validation)
except (NoCredentialsError, ClientError) as e:
logger.debug('Encountered error during cleanup: %s', e, exc_info=True)
def _find_zone_id_for_domain(self, domain):
"""Find the zone id responsible a given FQDN.
That is, the id for the zone whose name is the longest parent of the
domain.
"""
paginator = self.r53.get_paginator("list_hosted_zones")
zones = []
target_labels = domain.rstrip(".").split(".")
for page in paginator.paginate():
for zone in page["HostedZones"]:
if zone["Config"]["PrivateZone"]:
continue
candidate_labels = zone["Name"].rstrip(".").split(".")
if candidate_labels == target_labels[-len(candidate_labels):]:
zones.append((zone["Name"], zone["Id"]))
if not zones:
raise errors.PluginError(
"Unable to find a Route53 hosted zone for {0}".format(domain)
)
# Order the zones that are suffixes for our desired to domain by
# length, this puts them in an order like:
# ["foo.bar.baz.com", "bar.baz.com", "baz.com", "com"]
# And then we choose the first one, which will be the most specific.
zones.sort(key=lambda z: len(z[0]), reverse=True)
return zones[0][1]
def _change_txt_record(self, action, validation_domain_name, validation):
zone_id = self._find_zone_id_for_domain(validation_domain_name)
rrecords = self._resource_records[validation_domain_name]
challenge = {"Value": '"{0}"'.format(validation)}
if action == "DELETE":
# Remove the record being deleted from the list of tracked records
rrecords.remove(challenge)
if rrecords:
# Need to update instead, as we're not deleting the rrset
action = "UPSERT"
else:
# Create a new list containing the record to use with DELETE
rrecords = [challenge]
else:
rrecords.append(challenge)
response = self.r53.change_resource_record_sets(
HostedZoneId=zone_id,
ChangeBatch={
"Comment": "certbot-dns-route53 certificate validation " + action,
"Changes": [
{
"Action": action,
"ResourceRecordSet": {
"Name": validation_domain_name,
"Type": "TXT",
"TTL": self.ttl,
"ResourceRecords": rrecords,
}
}
]
}
)
return response["ChangeInfo"]["Id"]
def _wait_for_change(self, change_id):
"""Wait for a change to be propagated to all Route53 DNS servers.
https://docs.aws.amazon.com/Route53/latest/APIReference/API_GetChange.html
"""
for unused_n in range(0, 120):
response = self.r53.get_change(Id=change_id)
if response["ChangeInfo"]["Status"] == "INSYNC":
return
time.sleep(5)
raise errors.PluginError(
"Timed out waiting for Route53 change. Current status: %s" %
response["ChangeInfo"]["Status"])
This is the version that we got installed with apt certbout-route53-plugin
in Ubuntu 16.04 LTS
dpkg-query -s python3-certbot-dns-route53
Package: python3-certbot-dns-route53
Status: install ok installed
Priority: optional
Section: python
Installed-Size: 44
Maintainer: Debian Let's Encrypt Team <team+letsencrypt@tracker.debian.org>
Architecture: all
Source: python-certbot-dns-route53
Version: 0.23.0-2+ubuntu16.04.1+certbot+1
Depends: python3-acme (>= 0.22.0~), python3-boto3, python3-certbot, python3-mock, python3-pkg-resources, python3-zope.interface, python3:any (>= 3.3.2-2~)
Enhances: certbot
Description: Route53 DNS plugin for Certbot
The objective of Certbot, Let's Encrypt, and the ACME (Automated
Certificate Management Environment) protocol is to make it possible
to set up an HTTPS server and have it automatically obtain a
browser-trusted certificate, without any human intervention. This is
accomplished by running a certificate management agent on the web
server.
.
This agent is used to:
.
- Automatically prove to the Let's Encrypt CA that you control the website
- Obtain a browser-trusted certificate and set it up on your web server
- Keep track of when your certificate is going to expire, and renew it
- Help you revoke the certificate if that ever becomes necessary.
.
This package contains the Route53 DNS plugin to the main application.
Homepage: https://certbot.eff.org/
and the certbot
version
$ certbot --version
certbot 0.26.1
$ dpkg-query -s certbot
Package: certbot
Status: install ok installed
Priority: optional
Section: web
Installed-Size: 48
Maintainer: Debian Let's Encrypt <team+letsencrypt@tracker.debian.org>
Architecture: all
Source: python-certbot
Version: 0.26.1-1+ubuntu16.04.1+certbot+2
Replaces: letsencrypt
Provides: letsencrypt
Depends: python3-certbot (= 0.26.1-1+ubuntu16.04.1+certbot+2), init-system-helpers (>= 1.18~), python3:any
Suggests: python3-certbot-apache, python3-certbot-nginx, python-certbot-doc
Breaks: letsencrypt (<= 0.6.0)
Conffiles:
/etc/cron.d/certbot 0b97d70db8c43d86fcdc565590414c79
/etc/letsencrypt/cli.ini dc5a5672c8f966a968ac0c98c447c14e
/etc/logrotate.d/certbot a815a66a333f2637c00055fcd44b02d4
Description: automatically configure HTTPS using Let's Encrypt
The objective of Certbot, Let's Encrypt, and the ACME (Automated
Certificate Management Environment) protocol is to make it possible
to set up an HTTPS server and have it automatically obtain a
browser-trusted certificate, without any human intervention. This is
accomplished by running a certificate management agent on the web
server.
.
This agent is used to:
.
- Automatically prove to the Let's Encrypt CA that you control the website
- Obtain a browser-trusted certificate and set it up on your web server
- Keep track of when your certificate is going to expire, and renew it
- Help you revoke the certificate if that ever becomes necessary.
.
This package contains the main application, including the standalone
and the manual authenticators.
Homepage: https://certbot.eff.org/
This week I am in a small holidays , but at beginning of the next week I will update the plugin code and test it again.
Thanks