2016-06-28 00:57:53 +02:00
|
|
|
import time
|
2018-05-07 18:58:24 +02:00
|
|
|
|
2016-06-28 00:57:53 +02:00
|
|
|
from lemur.plugins.lemur_aws.sts import sts_client
|
|
|
|
|
|
|
|
|
2019-05-16 16:57:02 +02:00
|
|
|
@sts_client("route53")
|
2018-02-22 17:17:28 +01:00
|
|
|
def wait_for_dns_change(change_id, client=None):
|
2016-06-28 00:57:53 +02:00
|
|
|
_, change_id = change_id
|
|
|
|
|
|
|
|
while True:
|
|
|
|
response = client.get_change(Id=change_id)
|
|
|
|
if response["ChangeInfo"]["Status"] == "INSYNC":
|
|
|
|
return
|
|
|
|
time.sleep(5)
|
|
|
|
|
|
|
|
|
2019-05-16 16:57:02 +02:00
|
|
|
@sts_client("route53")
|
2016-06-28 00:57:53 +02:00
|
|
|
def find_zone_id(domain, client=None):
|
|
|
|
paginator = client.get_paginator("list_hosted_zones")
|
|
|
|
zones = []
|
|
|
|
for page in paginator.paginate():
|
|
|
|
for zone in page["HostedZones"]:
|
|
|
|
if domain.endswith(zone["Name"]) or (domain + ".").endswith(zone["Name"]):
|
|
|
|
if not zone["Config"]["PrivateZone"]:
|
|
|
|
zones.append((zone["Name"], zone["Id"]))
|
|
|
|
|
|
|
|
if not zones:
|
2019-05-16 16:57:02 +02:00
|
|
|
raise ValueError("Unable to find a Route53 hosted zone for {}".format(domain))
|
2017-11-13 19:19:54 +01:00
|
|
|
return zones[0][1]
|
2016-06-28 00:57:53 +02:00
|
|
|
|
|
|
|
|
2019-05-16 16:57:02 +02:00
|
|
|
@sts_client("route53")
|
2018-08-13 23:22:59 +02:00
|
|
|
def get_zones(client=None):
|
|
|
|
paginator = client.get_paginator("list_hosted_zones")
|
|
|
|
zones = []
|
|
|
|
for page in paginator.paginate():
|
|
|
|
for zone in page["HostedZones"]:
|
2020-04-25 00:48:06 +02:00
|
|
|
if not zone["Config"]["PrivateZone"]:
|
|
|
|
zones.append(
|
|
|
|
zone["Name"][:-1]
|
|
|
|
) # We need [:-1] to strip out the trailing dot.
|
2018-08-13 23:22:59 +02:00
|
|
|
return zones
|
|
|
|
|
|
|
|
|
2019-05-16 16:57:02 +02:00
|
|
|
@sts_client("route53")
|
2016-06-28 00:57:53 +02:00
|
|
|
def change_txt_record(action, zone_id, domain, value, client=None):
|
2018-06-20 01:27:58 +02:00
|
|
|
current_txt_records = []
|
|
|
|
try:
|
2018-06-20 19:33:35 +02:00
|
|
|
current_records = client.list_resource_record_sets(
|
2018-06-20 01:27:58 +02:00
|
|
|
HostedZoneId=zone_id,
|
|
|
|
StartRecordName=domain,
|
2019-05-16 16:57:02 +02:00
|
|
|
StartRecordType="TXT",
|
|
|
|
MaxItems="1",
|
|
|
|
)["ResourceRecordSets"]
|
2018-06-20 19:33:35 +02:00
|
|
|
|
|
|
|
for record in current_records:
|
2019-05-16 16:57:02 +02:00
|
|
|
if record.get("Type") == "TXT":
|
2018-06-20 19:33:35 +02:00
|
|
|
current_txt_records.extend(record.get("ResourceRecords", []))
|
2018-06-20 01:27:58 +02:00
|
|
|
except Exception as e:
|
|
|
|
# Current Resource Record does not exist
|
|
|
|
if "NoSuchHostedZone" not in str(type(e)):
|
|
|
|
raise
|
|
|
|
# For some reason TXT records need to be
|
|
|
|
# manually quoted.
|
2018-08-13 23:22:59 +02:00
|
|
|
seen = False
|
|
|
|
for record in current_txt_records:
|
|
|
|
for k, v in record.items():
|
|
|
|
if '"{}"'.format(value) == v:
|
|
|
|
seen = True
|
|
|
|
if not seen:
|
|
|
|
current_txt_records.append({"Value": '"{}"'.format(value)})
|
2018-06-20 01:27:58 +02:00
|
|
|
|
|
|
|
if action == "DELETE" and len(current_txt_records) > 1:
|
|
|
|
# If we want to delete one record out of many, we'll update the record to not include the deleted value instead.
|
|
|
|
# This allows us to support concurrent issuance.
|
|
|
|
current_txt_records = [
|
2019-05-16 16:57:02 +02:00
|
|
|
record
|
|
|
|
for record in current_txt_records
|
|
|
|
if not (record.get("Value") == '"{}"'.format(value))
|
2018-06-20 01:27:58 +02:00
|
|
|
]
|
|
|
|
action = "UPSERT"
|
|
|
|
|
2016-06-28 00:57:53 +02:00
|
|
|
response = client.change_resource_record_sets(
|
|
|
|
HostedZoneId=zone_id,
|
|
|
|
ChangeBatch={
|
|
|
|
"Changes": [
|
|
|
|
{
|
|
|
|
"Action": action,
|
|
|
|
"ResourceRecordSet": {
|
|
|
|
"Name": domain,
|
|
|
|
"Type": "TXT",
|
|
|
|
"TTL": 300,
|
2018-06-20 01:27:58 +02:00
|
|
|
"ResourceRecords": current_txt_records,
|
2019-05-16 16:57:02 +02:00
|
|
|
},
|
2016-06-28 00:57:53 +02:00
|
|
|
}
|
|
|
|
]
|
2019-05-16 16:57:02 +02:00
|
|
|
},
|
2016-06-28 00:57:53 +02:00
|
|
|
)
|
|
|
|
return response["ChangeInfo"]["Id"]
|
|
|
|
|
|
|
|
|
2017-11-13 19:19:54 +01:00
|
|
|
def create_txt_record(host, value, account_number):
|
2017-02-16 22:24:05 +01:00
|
|
|
zone_id = find_zone_id(host, account_number=account_number)
|
2016-06-28 00:57:53 +02:00
|
|
|
change_id = change_txt_record(
|
2019-05-16 16:57:02 +02:00
|
|
|
"UPSERT", zone_id, host, value, account_number=account_number
|
2016-06-28 00:57:53 +02:00
|
|
|
)
|
2018-06-12 00:40:15 +02:00
|
|
|
|
2016-06-28 00:57:53 +02:00
|
|
|
return zone_id, change_id
|
|
|
|
|
|
|
|
|
2018-06-12 00:40:15 +02:00
|
|
|
def delete_txt_record(change_ids, account_number, host, value):
|
|
|
|
for change_id in change_ids:
|
|
|
|
zone_id, _ = change_id
|
2018-08-13 23:22:59 +02:00
|
|
|
try:
|
|
|
|
change_txt_record(
|
2019-05-16 16:57:02 +02:00
|
|
|
"DELETE", zone_id, host, value, account_number=account_number
|
2018-08-13 23:22:59 +02:00
|
|
|
)
|
|
|
|
except Exception as e:
|
|
|
|
if "but it was not found" in e.response.get("Error", {}).get("Message"):
|
|
|
|
# We tried to delete a record that doesn't exist. We'll ignore this error.
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
raise
|