lemur/lemur/plugins/lemur_acme/route53.py
Hossein Shafagh 8d0007b9c0 fixing the private DNS zone issue.
Private hosted zones will never be visible to third-parties like LetsEncrypt, and Lemur should not consider them as authoritative zones.
This fix, make sure  they are not added to the  dns_provider table.
2020-04-24 15:48:06 -07:00

124 lines
3.9 KiB
Python

import time
from lemur.plugins.lemur_aws.sts import sts_client
@sts_client("route53")
def wait_for_dns_change(change_id, client=None):
_, change_id = change_id
while True:
response = client.get_change(Id=change_id)
if response["ChangeInfo"]["Status"] == "INSYNC":
return
time.sleep(5)
@sts_client("route53")
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:
raise ValueError("Unable to find a Route53 hosted zone for {}".format(domain))
return zones[0][1]
@sts_client("route53")
def get_zones(client=None):
paginator = client.get_paginator("list_hosted_zones")
zones = []
for page in paginator.paginate():
for zone in page["HostedZones"]:
if not zone["Config"]["PrivateZone"]:
zones.append(
zone["Name"][:-1]
) # We need [:-1] to strip out the trailing dot.
return zones
@sts_client("route53")
def change_txt_record(action, zone_id, domain, value, client=None):
current_txt_records = []
try:
current_records = client.list_resource_record_sets(
HostedZoneId=zone_id,
StartRecordName=domain,
StartRecordType="TXT",
MaxItems="1",
)["ResourceRecordSets"]
for record in current_records:
if record.get("Type") == "TXT":
current_txt_records.extend(record.get("ResourceRecords", []))
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.
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)})
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 = [
record
for record in current_txt_records
if not (record.get("Value") == '"{}"'.format(value))
]
action = "UPSERT"
response = client.change_resource_record_sets(
HostedZoneId=zone_id,
ChangeBatch={
"Changes": [
{
"Action": action,
"ResourceRecordSet": {
"Name": domain,
"Type": "TXT",
"TTL": 300,
"ResourceRecords": current_txt_records,
},
}
]
},
)
return response["ChangeInfo"]["Id"]
def create_txt_record(host, value, account_number):
zone_id = find_zone_id(host, account_number=account_number)
change_id = change_txt_record(
"UPSERT", zone_id, host, value, account_number=account_number
)
return zone_id, change_id
def delete_txt_record(change_ids, account_number, host, value):
for change_id in change_ids:
zone_id, _ = change_id
try:
change_txt_record(
"DELETE", zone_id, host, value, account_number=account_number
)
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