First working iteration

This commit is contained in:
Hugo Peixoto 2016-10-03 19:53:53 +01:00
parent c4364f82fb
commit 1a5f09f4cf
2 changed files with 78 additions and 45 deletions

View file

@ -1,23 +1,21 @@
"""Route53 Let's Encrypt authenticator plugin."""
import os
import logging
import re
import subprocess
import time
import zope.component
import zope.interface
import boto3
from acme import challenges
from letsencrypt import errors
from letsencrypt import interfaces
from letsencrypt.plugins import common
logger = logging.getLogger(__name__)
TTL = 30
class Authenticator(common.Plugin):
zope.interface.implements(interfaces.IAuthenticator)
zope.interface.classProvides(interfaces.IPluginFactory)
@ -44,50 +42,85 @@ class Authenticator(common.Plugin):
responses.append(self._perform_single(achall))
return responses
def _find_zone(self, r53, domain):
return max(
(
zone for zone in r53.list_hosted_zones()["HostedZones"]
if (domain+".").endswith("."+zone["Name"])
),
key=lambda zone: len(zone["Name"]),
)
def _perform_single(self, achall):
# provision the TXT record, using the domain name given. Assumes the hosted zone exits, else fails the challenge
response, validation = achall.response_and_validation()
r53 = boto3.client('route53')
logger.info("Doing validation for " + response.domain)
listResponse = r53.list_hosted_zones_by_name(DNSName=response.domain)
matches = listResponse.HostedZones;
if matches.size != 0:
logger.error("Route53 returned " + mathces.size + " matching hosted zones. Expected exactly one. Auth canceled.")
return None
else:
r53.change_resource_record_sets(HostedZoneId=matches[0].Id,
ChangeBatch={
'Comment': 'Let\'s Entcrypt Change',
'Changes': [
{
'Action': 'UPSERT',
'ResourceRecordSet': {
'Name': achall.validation_domain_name(),
'Type': 'TXT',
'TTL': 300,
'ResourceRecords': [
{
'Value': validation
},
]
}
},
]
})
logger.info("Doing validation for " + achall.domain)
listResponse = r53.list_hosted_zones_by_name(DNSName=achall.domain)
if response.simple_verify(
achall.chall, achall.domain,
achall.account_key.public_key(), self.config.http01_port):
return response
else:
logger.error(
"Self-verify of challenge failed, authorization abandoned!")
try:
zone = self._find_zone(r53, achall.domain)
except ValueError as e:
logger.error("Unable to find matching Route53 zone for domain " + achall.domain)
return None
response, validation = achall.response_and_validation()
self._excute_r53_action(r53, achall, zone, validation, 'UPSERT', wait_for_change=True)
for _ in xrange(TTL*2):
if response.simple_verify(
achall.chall,
achall.domain,
achall.account_key.public_key(),
):
break
logger.info("Waiting for DNS propagation...")
time.sleep(1)
else:
logger.error("Unable to verify domain " + achall.domain)
return None
return response
def cleanup(self, achalls):
# pylint: disable=missing-docstring,no-self-use,unused-argument
#TODO:Cleanup record 
# pylint: disable=missing-docstring
r53 = boto3.client('route53')
#for achall in achalls:
# r53.delete_object(Bucket=self.conf('s3-bucket'), Key=achall.chall.path[1:])
for achall in achalls:
try:
zone = self._find_zone(r53, achall.domain)
except ValueError:
logger.warn("Unable to find zone for " + achall.domain + ". Skipping cleanup.")
continue
_, validation = achall.response_and_validation()
self._excute_r53_action(r53, achall, zone, validation, 'DELETE')
return None
def _excute_r53_action(self, r53, achall, zone, validation, action, wait_for_change=False):
response = r53.change_resource_record_sets(
HostedZoneId=zone["Id"],
ChangeBatch={
'Comment': 'Let\'s Encrypt ' + action,
'Changes': [
{
'Action': action,
'ResourceRecordSet': {
'Name': achall.validation_domain_name(achall.domain),
'Type': 'TXT',
'TTL': TTL,
'ResourceRecords': [
{
'Value': '"' + validation + '"',
},
],
},
},
],
},
)
if wait_for_change:
while r53.get_change(Id=response["ChangeInfo"]["Id"])["ChangeInfo"]["Status"] == "PENDING":
logger.info("Waiting for " + action + " to propagate...")
time.sleep(1)

View file

@ -6,8 +6,8 @@ from setuptools import find_packages
version = '0.1.3'
install_requires = [
'acme>=0.1.1',
'letsencrypt>=0.1.1',
'acme>=0.9.0.dev0',
'letsencrypt>=0.9.0.dev0',
'PyOpenSSL',
'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary?
'setuptools', # pkg_resources