Merge branch 'master' into acme_validation_dns_provider_option

This commit is contained in:
Curtis 2018-06-19 16:45:25 -07:00 committed by GitHub
commit d50c9c7748
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 108 additions and 49 deletions

View File

@ -5,11 +5,10 @@ import dns.exception
import dns.name import dns.name
import dns.query import dns.query
import dns.resolver import dns.resolver
from dyn.tm.errors import DynectCreateError from dyn.tm.errors import DynectGetError
from dyn.tm.session import DynectSession from dyn.tm.session import DynectSession
from dyn.tm.zones import Node, Zone from dyn.tm.zones import Node, Zone, get_all_zones
from flask import current_app from flask import current_app
from tld import get_tld
def get_dynect_session(): def get_dynect_session():
@ -42,28 +41,55 @@ def _has_dns_propagated(name, token):
def wait_for_dns_change(change_id, account_number=None): def wait_for_dns_change(change_id, account_number=None):
fqdn, token = change_id fqdn, token = change_id
while True: number_of_attempts = 10
for attempts in range(0, number_of_attempts):
status = _has_dns_propagated(fqdn, token) status = _has_dns_propagated(fqdn, token)
current_app.logger.debug("Record status for fqdn: {}: {}".format(fqdn, status)) current_app.logger.debug("Record status for fqdn: {}: {}".format(fqdn, status))
if status: if status:
break break
time.sleep(20) time.sleep(20)
if not status:
raise Exception("Unable to query DNS token for fqdn {}.".format(fqdn))
return return
def get_zone_name(domain):
zones = get_all_zones()
zone_name = ""
for z in zones:
if domain.endswith(z.name):
# Find the most specific zone possible for the domain
# Ex: If fqdn is a.b.c.com, there is a zone for c.com,
# and a zone for b.c.com, we want to use b.c.com.
if z.name.count(".") > zone_name.count("."):
zone_name = z.name
if not zone_name:
raise Exception("No Dyn zone found for domain: {}".format(domain))
return zone_name
def create_txt_record(domain, token, account_number): def create_txt_record(domain, token, account_number):
get_dynect_session() get_dynect_session()
zone_name = get_tld('http://' + domain) zone_name = get_zone_name(domain)
zone_parts = len(zone_name.split('.')) zone_parts = len(zone_name.split('.'))
node_name = '.'.join(domain.split('.')[:-zone_parts]) node_name = '.'.join(domain.split('.')[:-zone_parts])
fqdn = "{0}.{1}".format(node_name, zone_name) fqdn = "{0}.{1}".format(node_name, zone_name)
zone = Zone(zone_name) zone = Zone(zone_name)
try: try:
# Delete all stale ACME TXT records
delete_acme_txt_records(domain)
except DynectGetError as e:
if (
"No such zone." in e.message or
"Host is not in this zone" in e.message or
"Host not found in this zone" in e.message
):
current_app.logger.debug("Unable to delete ACME TXT records. They probably don't exist yet: {}".format(e))
else:
raise
zone.add_record(node_name, record_type='TXT', txtdata="\"{}\"".format(token), ttl=5) zone.add_record(node_name, record_type='TXT', txtdata="\"{}\"".format(token), ttl=5)
except DynectCreateError:
delete_txt_record(None, None, domain, token)
zone.add_record(node_name, record_type='TXT', txtdata="\"{}\"".format(token), ttl=5)
node = zone.get_node(node_name)
zone.publish() zone.publish()
current_app.logger.debug("TXT record created: {0}".format(fqdn)) current_app.logger.debug("TXT record created: {0}".format(fqdn))
change_id = (fqdn, token) change_id = (fqdn, token)
@ -76,7 +102,7 @@ def delete_txt_record(change_id, account_number, domain, token):
current_app.logger.debug("delete_txt_record: No domain passed") current_app.logger.debug("delete_txt_record: No domain passed")
return return
zone_name = get_tld('http://' + domain) zone_name = get_zone_name(domain)
zone_parts = len(zone_name.split('.')) zone_parts = len(zone_name.split('.'))
node_name = '.'.join(domain.split('.')[:-zone_parts]) node_name = '.'.join(domain.split('.')[:-zone_parts])
fqdn = "{0}.{1}".format(node_name, zone_name) fqdn = "{0}.{1}".format(node_name, zone_name)
@ -92,7 +118,35 @@ def delete_txt_record(change_id, account_number, domain, token):
zone.publish() zone.publish()
def delete_acme_txt_records(domain):
get_dynect_session()
if not domain:
current_app.logger.debug("delete_acme_txt_records: No domain passed")
return
acme_challenge_string = "_acme-challenge"
if not domain.startswith(acme_challenge_string):
current_app.logger.debug(
"delete_acme_txt_records: Domain {} doesn't start with string {}. "
"Cowardly refusing to delete TXT records".format(domain, acme_challenge_string))
return
zone_name = get_zone_name(domain)
zone_parts = len(zone_name.split('.'))
node_name = '.'.join(domain.split('.')[:-zone_parts])
fqdn = "{0}.{1}".format(node_name, zone_name)
zone = Zone(zone_name)
node = Node(zone_name, fqdn)
all_txt_records = node.get_all_records_by_type('TXT')
for txt_record in all_txt_records:
current_app.logger.debug("Deleting TXT record name: {0}".format(fqdn))
txt_record.delete()
zone.publish()
def get_authoritative_nameserver(domain): def get_authoritative_nameserver(domain):
if current_app.config.get('ACME_DYN_GET_AUTHORATATIVE_NAMESERVER'):
n = dns.name.from_text(domain) n = dns.name.from_text(domain)
depth = 2 depth = 2
@ -129,3 +183,5 @@ def get_authoritative_nameserver(domain):
depth += 1 depth += 1
return nameserver return nameserver
else:
return "8.8.8.8"

View File

@ -116,8 +116,8 @@ def request_certificate(acme_client, authorizations, csr, order):
try: try:
orderr = acme_client.finalize_order(order, deadline) orderr = acme_client.finalize_order(order, deadline)
except: except AcmeError:
current_app.logger.error("Unable to finalize ACME order: {}".format(order), exc_info=True) current_app.logger.error("Unable to resolve Acme order: {}".format(order), exc_info=True)
raise raise
pem_certificate = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, pem_certificate = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
@ -407,3 +407,7 @@ class ACMEIssuerPlugin(IssuerPlugin):
if option.get('name') == 'certificate': if option.get('name') == 'certificate':
acme_root = option.get('value') acme_root = option.get('value')
return acme_root, "", [role] return acme_root, "", [role]
def cancel_ordered_certificate(self, pending_cert, **kwargs):
# Needed to override issuer function.
pass

View File

@ -4,7 +4,7 @@
# #
# pip-compile --no-index --output-file requirements-docs.txt requirements-docs.in # pip-compile --no-index --output-file requirements-docs.txt requirements-docs.in
# #
acme==0.24.0 acme==0.25.1
alabaster==0.7.11 # via sphinx alabaster==0.7.11 # via sphinx
alembic-autogenerate-enums==0.0.2 alembic-autogenerate-enums==0.0.2
alembic==0.9.9 alembic==0.9.9
@ -15,8 +15,8 @@ asyncpool==1.0
babel==2.6.0 # via sphinx babel==2.6.0 # via sphinx
bcrypt==3.1.4 bcrypt==3.1.4
blinker==1.4 blinker==1.4
boto3==1.7.32 boto3==1.7.39
botocore==1.10.32 botocore==1.10.37
certifi==2018.4.16 certifi==2018.4.16
cffi==1.11.5 cffi==1.11.5
click==6.7 click==6.7
@ -27,7 +27,7 @@ dnspython==1.15.0
docutils==0.14 docutils==0.14
dyn==1.8.1 dyn==1.8.1
flask-bcrypt==0.7.1 flask-bcrypt==0.7.1
flask-cors==3.0.4 flask-cors==3.0.6
flask-mail==0.9.1 flask-mail==0.9.1
flask-migrate==2.1.1 flask-migrate==2.1.1
flask-principal==0.4.0 flask-principal==0.4.0
@ -37,7 +37,7 @@ flask-sqlalchemy==2.3.2
flask==0.12 flask==0.12
future==0.16.0 future==0.16.0
gunicorn==19.8.1 gunicorn==19.8.1
idna==2.6 idna==2.7
imagesize==1.0.0 # via sphinx imagesize==1.0.0 # via sphinx
inflection==0.3.1 inflection==0.3.1
itsdangerous==0.24 itsdangerous==0.24
@ -54,7 +54,7 @@ mock==2.0.0
ndg-httpsclient==0.5.0 ndg-httpsclient==0.5.0
packaging==17.1 # via sphinx packaging==17.1 # via sphinx
paramiko==2.4.1 paramiko==2.4.1
pbr==4.0.3 pbr==4.0.4
pem==17.1.0 pem==17.1.0
psycopg2==2.7.4 psycopg2==2.7.4
pyasn1-modules==0.2.1 pyasn1-modules==0.2.1
@ -65,12 +65,13 @@ pyjwt==1.6.4
pynacl==1.2.1 pynacl==1.2.1
pyopenssl==17.2.0 pyopenssl==17.2.0
pyparsing==2.2.0 # via packaging pyparsing==2.2.0 # via packaging
pyrfc3339==1.0 pyrfc3339==1.1
python-dateutil==2.7.3 python-dateutil==2.7.3
python-editor==1.0.3 python-editor==1.0.3
pytz==2018.4 pytz==2018.4
pyyaml==3.12 pyyaml==3.12
raven[flask]==6.9.0 raven[flask]==6.9.0
requests-toolbelt==0.8.0
requests[security]==2.11.1 requests[security]==2.11.1
retrying==1.3.3 retrying==1.3.3
s3transfer==0.1.13 s3transfer==0.1.13
@ -83,6 +84,5 @@ sphinxcontrib-websupport==1.1.0 # via sphinx
sqlalchemy-utils==0.33.3 sqlalchemy-utils==0.33.3
sqlalchemy==1.2.8 sqlalchemy==1.2.8
tabulate==0.8.2 tabulate==0.8.2
tld==0.7.10
werkzeug==0.14.1 werkzeug==0.14.1
xmltodict==0.11.0 xmltodict==0.11.0

View File

@ -39,5 +39,4 @@ retrying
six six
SQLAlchemy-Utils SQLAlchemy-Utils
tabulate tabulate
tld
xmltodict xmltodict