Fix unique dyn situation where zone does not match tld, and there's a deeper zone
This commit is contained in:
parent
0d4df75375
commit
3002945d55
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -39,5 +39,4 @@ retrying
|
||||||
six
|
six
|
||||||
SQLAlchemy-Utils
|
SQLAlchemy-Utils
|
||||||
tabulate
|
tabulate
|
||||||
tld
|
|
||||||
xmltodict
|
xmltodict
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue