Fix unique dyn situation where zone does not match tld, and there's a deeper zone

This commit is contained in:
Curtis Castrapel 2018-06-13 14:08:39 -07:00
parent 0d4df75375
commit 3002945d55
6 changed files with 110 additions and 58 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 DynectCreateError, 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():
@ -51,19 +50,43 @@ def wait_for_dns_change(change_id, account_number=None):
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:
zone.add_record(node_name, record_type='TXT', txtdata="\"{}\"".format(token), ttl=5) # Delete all stale ACME TXT records
except DynectCreateError: delete_acme_txt_records(domain)
delete_txt_record(None, None, domain, token) except DynectGetError as e:
zone.add_record(node_name, record_type='TXT', txtdata="\"{}\"".format(token), ttl=5) if (
node = zone.get_node(node_name) "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.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 +99,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,40 +115,70 @@ 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):
n = dns.name.from_text(domain) if current_app.config.get('ACME_DYN_GET_AUTHORATATIVE_NAMESERVER'):
n = dns.name.from_text(domain)
depth = 2 depth = 2
default = dns.resolver.get_default_resolver() default = dns.resolver.get_default_resolver()
nameserver = default.nameservers[0] nameserver = default.nameservers[0]
last = False last = False
while not last: while not last:
s = n.split(depth) s = n.split(depth)
last = s[0].to_unicode() == u'@' last = s[0].to_unicode() == u'@'
sub = s[1] sub = s[1]
query = dns.message.make_query(sub, dns.rdatatype.NS) query = dns.message.make_query(sub, dns.rdatatype.NS)
response = dns.query.udp(query, nameserver) response = dns.query.udp(query, nameserver)
rcode = response.rcode() rcode = response.rcode()
if rcode != dns.rcode.NOERROR: if rcode != dns.rcode.NOERROR:
if rcode == dns.rcode.NXDOMAIN: if rcode == dns.rcode.NXDOMAIN:
raise Exception('%s does not exist.' % sub) raise Exception('%s does not exist.' % sub)
else:
raise Exception('Error %s' % dns.rcode.to_text(rcode))
if len(response.authority) > 0:
rrset = response.authority[0]
else: else:
raise Exception('Error %s' % dns.rcode.to_text(rcode)) rrset = response.answer[0]
if len(response.authority) > 0: rr = rrset[0]
rrset = response.authority[0] if rr.rdtype != dns.rdatatype.SOA:
else: authority = rr.target
rrset = response.answer[0] nameserver = default.query(authority).rrset[0].to_text()
rr = rrset[0] depth += 1
if rr.rdtype != dns.rdatatype.SOA:
authority = rr.target
nameserver = default.query(authority).rrset[0].to_text()
depth += 1 return nameserver
else:
return nameserver return "8.8.8.8"

View File

@ -11,20 +11,20 @@ cfgv==1.1.0 # via pre-commit
chardet==3.0.4 # via requests chardet==3.0.4 # via requests
flake8==3.5.0 flake8==3.5.0
identify==1.1.0 # via pre-commit identify==1.1.0 # via pre-commit
idna==2.6 # via requests idna==2.7 # via requests
invoke==1.0.0 invoke==1.0.0
mccabe==0.6.1 # via flake8 mccabe==0.6.1 # via flake8
nodeenv==1.3.0 nodeenv==1.3.1
pkginfo==1.4.2 # via twine pkginfo==1.4.2 # via twine
pre-commit==1.10.2 pre-commit==1.10.2
pycodestyle==2.3.1 # via flake8 pycodestyle==2.3.1 # via flake8
pyflakes==1.6.0 # via flake8 pyflakes==1.6.0 # via flake8
pyyaml==3.12 # via aspy.yaml, pre-commit pyyaml==3.12 # via aspy.yaml, pre-commit
requests-toolbelt==0.8.0 # via twine requests-toolbelt==0.8.0 # via twine
requests==2.18.4 # via requests-toolbelt, twine requests==2.19.0 # via requests-toolbelt, twine
six==1.11.0 # via cfgv, pre-commit six==1.11.0 # via cfgv, pre-commit
toml==0.9.4 # via pre-commit toml==0.9.4 # via pre-commit
tqdm==4.23.4 # via twine tqdm==4.23.4 # via twine
twine==1.11.0 twine==1.11.0
urllib3==1.22 # via requests urllib3==1.23 # via requests
virtualenv==16.0.0 # via pre-commit virtualenv==16.0.0 # via pre-commit

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.0
alabaster==0.7.10 # via sphinx alabaster==0.7.10 # 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.36
botocore==1.10.32 botocore==1.10.36
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

View File

@ -8,9 +8,9 @@ asn1crypto==0.24.0 # via cryptography
atomicwrites==1.1.5 # via pytest atomicwrites==1.1.5 # via pytest
attrs==18.1.0 # via pytest attrs==18.1.0 # via pytest
aws-xray-sdk==0.95 # via moto aws-xray-sdk==0.95 # via moto
boto3==1.7.36 # via moto boto3==1.7.37 # via moto
boto==2.48.0 # via moto boto==2.48.0 # via moto
botocore==1.10.36 # via boto3, moto, s3transfer botocore==1.10.37 # via boto3, moto, s3transfer
certifi==2018.4.16 # via requests certifi==2018.4.16 # via requests
cffi==1.11.5 # via cryptography cffi==1.11.5 # via cryptography
chardet==3.0.4 # via requests chardet==3.0.4 # via requests
@ -25,7 +25,7 @@ factory-boy==2.11.1
faker==0.8.15 faker==0.8.15
flask==1.0.2 # via pytest-flask flask==1.0.2 # via pytest-flask
freezegun==0.3.10 freezegun==0.3.10
idna==2.6 # via cryptography, requests idna==2.7 # via cryptography, requests
itsdangerous==0.24 # via flask itsdangerous==0.24 # via flask
jinja2==2.10 # via flask, moto jinja2==2.10 # via flask, moto
jmespath==0.9.3 # via boto3, botocore jmespath==0.9.3 # via boto3, botocore
@ -49,12 +49,12 @@ python-dateutil==2.6.1 # via botocore, faker, freezegun, moto
pytz==2018.4 # via moto pytz==2018.4 # via moto
pyyaml==3.12 # via pyaml pyyaml==3.12 # via pyaml
requests-mock==1.5.0 requests-mock==1.5.0
requests==2.18.4 # via aws-xray-sdk, docker, moto, requests-mock, responses requests==2.19.0 # via aws-xray-sdk, docker, moto, requests-mock, responses
responses==0.9.0 # via moto responses==0.9.0 # via moto
s3transfer==0.1.13 # via boto3 s3transfer==0.1.13 # via boto3
six==1.11.0 # via cryptography, docker, docker-pycreds, faker, freezegun, mock, more-itertools, moto, pytest, python-dateutil, requests-mock, responses, websocket-client six==1.11.0 # via cryptography, docker, docker-pycreds, faker, freezegun, mock, more-itertools, moto, pytest, python-dateutil, requests-mock, responses, websocket-client
text-unidecode==1.2 # via faker text-unidecode==1.2 # via faker
urllib3==1.22 # via requests urllib3==1.23 # via requests
websocket-client==0.48.0 # via docker websocket-client==0.48.0 # via docker
werkzeug==0.14.1 # via flask, moto, pytest-flask werkzeug==0.14.1 # via flask, moto, pytest-flask
wrapt==1.10.11 # via aws-xray-sdk wrapt==1.10.11 # via aws-xray-sdk

View File

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

View File

@ -4,7 +4,7 @@
# #
# pip-compile --no-index --output-file requirements.txt requirements.in # pip-compile --no-index --output-file requirements.txt requirements.in
# #
acme==0.25.0 acme==0.25.1
alembic-autogenerate-enums==0.0.2 alembic-autogenerate-enums==0.0.2
alembic==0.9.9 # via flask-migrate alembic==0.9.9 # via flask-migrate
aniso8601==3.0.0 # via flask-restful aniso8601==3.0.0 # via flask-restful
@ -13,8 +13,8 @@ asn1crypto==0.24.0 # via cryptography
asyncpool==1.0 asyncpool==1.0
bcrypt==3.1.4 # via flask-bcrypt, paramiko bcrypt==3.1.4 # via flask-bcrypt, paramiko
blinker==1.4 # via flask-mail, flask-principal, raven blinker==1.4 # via flask-mail, flask-principal, raven
boto3==1.7.36 boto3==1.7.37
botocore==1.10.36 # via boto3, s3transfer botocore==1.10.37 # via boto3, s3transfer
certifi==2018.4.16 certifi==2018.4.16
cffi==1.11.5 # via bcrypt, cryptography, pynacl cffi==1.11.5 # via bcrypt, cryptography, pynacl
click==6.7 # via flask click==6.7 # via flask
@ -74,6 +74,5 @@ six==1.11.0
sqlalchemy-utils==0.33.3 sqlalchemy-utils==0.33.3
sqlalchemy==1.2.8 # via alembic, flask-sqlalchemy, marshmallow-sqlalchemy, sqlalchemy-utils sqlalchemy==1.2.8 # via alembic, flask-sqlalchemy, marshmallow-sqlalchemy, sqlalchemy-utils
tabulate==0.8.2 tabulate==0.8.2
tld==0.7.10
werkzeug==0.14.1 # via flask werkzeug==0.14.1 # via flask
xmltodict==0.11.0 xmltodict==0.11.0