From 6f0d3b9d02ba3ef3951d8401b06b89cc757535ea Mon Sep 17 00:00:00 2001 From: "Gangwoo \"Peter\" Cho" Date: Mon, 18 Apr 2022 09:24:21 -0400 Subject: [PATCH] added heritage validation before delete and --- code/ddns-lambda.py | 730 +++++++++++++------------ code/sample-event.running-minium.json | 7 + code/sample-event.stopped-minimum.json | 7 + 3 files changed, 380 insertions(+), 364 deletions(-) create mode 100644 code/sample-event.running-minium.json create mode 100644 code/sample-event.stopped-minimum.json diff --git a/code/ddns-lambda.py b/code/ddns-lambda.py index 1892c12..e92f886 100755 --- a/code/ddns-lambda.py +++ b/code/ddns-lambda.py @@ -72,7 +72,7 @@ LOGGER = logging.getLogger() ACCOUNT = None REGION = None -VERSION = '0.2.3' +VERSION = '0.3.0' # Read Env variables DEBUG_LOG_LEVEL = os.environ.get('DebugLogLevel', 'INFO') @@ -715,7 +715,7 @@ def lambda_handler( LOGGER.info("instance: %s, no custom tags - use default.", instance_id) final_private_hostname = private_host_name final_hosted_zone_name = private_hosted_zone_name - else: # none of the use-case and no suitable zone to create the A record + else: LOGGER.error( "instance: %s, No DHCP Associated for VPC and no custom tags. Exiting Script", instance_id) # nothing to do, exit out script @@ -753,7 +753,7 @@ def lambda_handler( " %s", str(heritage_value) + lineno()) delete_records = True - get_rr = False + # get_rr = False # Create OR Delete the A / PTR Record if state == 'running': @@ -790,8 +790,8 @@ def lambda_handler( try: if len(heritage) > 0: - LOGGER.debug("Creating heritage TXT resource records %s", - final_private_hostname + lineno()) + LOGGER.debug("Creating heritage TXT resource records %s, with a value of %s", + final_private_hostname, str(heritage_value) + lineno()) create_response = create_resource_record( route53, instance_id, @@ -854,8 +854,8 @@ def lambda_handler( try: if reverse_zone_associated and len(heritage) > 0: - LOGGER.debug("Creating heritage TXT resource records %s", - reversed_ip_address + lineno()) + LOGGER.debug("Creating heritage TXT resource records %s, with a value of %s", + str(reversed_ip_address), str(heritage_value) + lineno()) create_response = create_resource_record( route53, instance_id, @@ -886,226 +886,41 @@ def lambda_handler( instance_id, str(err) + lineno()) else: # not running so delete the records - # delete A record - try: - LOGGER.debug("Deleting A record %s", final_private_hostname + lineno()) - response_text = 'Delete A record in zone id: ' + str(final_hosted_zone_id) + \ - ' for hosted zone ' + str(final_private_hostname) + \ - '.' + str(final_hosted_zone_name) + ' with value: ' + \ - str(private_ip) - - delete_response = new_delete_resource_record( - route53, - instance_id, - final_hosted_zone_id, - final_private_hostname, - final_hosted_zone_name, - 'A', - private_ip - ) - - if delete_response == 'NoSuchHostedZone': - delete_records = False - caller_response.append("Failed, no such zone: " + response_text) - LOGGER.info("instance: %s, NoSuchHostedZone: %s", - instance_id, response_text + lineno()) - elif delete_response == 'InvalidChangeBatch-RecordNotFound': - caller_response.append("Failed, Record Not Found: " + response_text) - LOGGER.info("instance: %s, InvalidChangeBatch-RecordNotFound: %s", - instance_id, response_text + lineno()) - elif delete_response == 'InvalidChangeBatch-RecordDoNotMatch': - delete_records = False - caller_response.append( - "Failed, requested delete do not match existing record: " + response_text) - LOGGER.info("instance: %s, InvalidChangeBatch-RecordDoNotMatch: %s", - instance_id, response_text + lineno()) - elif delete_response == {}: - delete_records = False - caller_response.append( - "Failed, could NOT delete Record: " + response_text) - LOGGER.info("instance: %s, Failed, could NOT delete Record: %s", - instance_id, response_text + lineno()) - else: - LOGGER.info("instance: %s, Success: %s", - instance_id, response_text + lineno()) - caller_response.append("Success: " + response_text) - except BaseException as err: - delete_records = False - LOGGER.error("instance: %s, unexpected error. %s\n", - instance_id, str(err) + lineno()) - - # delete TXT record associated with A record - try: - # pause 1 before deleting to avoid API limit - if get_rr: - # time.sleep(1) - heritage_value = new_get_resource_record( - route53, - instance_id, - final_hosted_zone_id, - final_private_hostname, - final_hosted_zone_name, - 'TXT', - heritage_value - ) - if len(heritage) > 0: - LOGGER.debug("Deleting heritage TXT resource records %s", - final_private_hostname + lineno()) - response_text = 'Delete TXT record in zone id: ' + str(final_hosted_zone_id) + \ - ' for hosted zone ' + str(final_private_hostname) + \ - '.' + str(final_hosted_zone_name) + ' with value: ' + \ - str(heritage_value) - - delete_response = new_delete_resource_record( - route53, - instance_id, - final_hosted_zone_id, - final_private_hostname, - final_hosted_zone_name, - 'TXT', - heritage_value - ) - if delete_response == 'NoSuchHostedZone': - delete_records = False - caller_response.append("Failed, no such zone: " + response_text) - LOGGER.info("instance: %s, NoSuchHostedZone: %s", - instance_id, response_text + lineno()) - elif delete_response == 'InvalidChangeBatch-RecordNotFound': - caller_response.append("Failed, Record Not Found: " + response_text) - LOGGER.info("instance: %s, InvalidChangeBatch-RecordNotFound: %s", - instance_id, response_text + lineno()) - elif delete_response == 'InvalidChangeBatch-RecordDoNotMatch': - delete_records = False - caller_response.append( - "Failed, requested delete do not match existing record: " + response_text) - LOGGER.info("instance: %s, InvalidChangeBatch-RecordDoNotMatch: %s", - instance_id, response_text + lineno()) - elif delete_response == {}: - delete_records = False - caller_response.append( - "Failed, could NOT delete Record: " + response_text) - LOGGER.info("instance: %s, Failed Could NOT delete Record: %s", - instance_id, response_text + lineno()) - else: - caller_response.append("Success: " + response_text) - LOGGER.info("instance: %s, Success: %s", - instance_id, response_text + lineno()) - except BaseException as err: - delete_records = False - LOGGER.error("instance: %s, unexpected error. %s\n", - instance_id, str(err) + lineno()) - - # delete PTR record - try: - # pause 1 before deleting to avoid API limit - # time.sleep(1) - - LOGGER.debug("Deleting PTR record %s", reversed_ip_address + lineno()) - response_text = 'Delete PTR record in zone id: ' + str(reverse_lookup_zone_id) + \ - ' for hosted zone ' + str(reversed_ip_address) + \ - str(private_dns_name) + ' with value: ' + \ - str(final_private_dns_name) - - delete_response = new_delete_resource_record( - route53, - instance_id, - reverse_lookup_zone_id, - reversed_ip_address, - 'in-addr.arpa', - 'PTR', - final_private_dns_name - ) - if delete_response == 'NoSuchHostedZone': - delete_records = False - caller_response.append("Failed, no such zone: " + response_text) - LOGGER.info("instance: %s, NoSuchHostedZone: %s", - instance_id, response_text + lineno()) - elif delete_response == 'InvalidChangeBatch-RecordNotFound': - caller_response.append("Failed, Record Not Found: " + response_text) - LOGGER.info("instance: %s, InvalidChangeBatch-RecordNotFound: %s", - instance_id, response_text + lineno()) - elif delete_response == 'InvalidChangeBatch-RecordDoNotMatch': - delete_records = False - caller_response.append( - "Failed, requested delete do not match existing record: " + response_text) - LOGGER.info("instance: %s, InvalidChangeBatch-RecordDoNotMatch: %s", - instance_id, response_text + lineno()) - elif delete_response == {}: - delete_records = False - caller_response.append( - "Failed, could NOT delete Record: " + response_text) - LOGGER.info("instance: %s, Failed could NOT delete Record: %s", - instance_id, response_text + lineno()) - else: - caller_response.append("Success: " + response_text) - LOGGER.info("instance: %s, Success: %s", - instance_id, response_text + lineno()) - except BaseException as err: - delete_records = False - LOGGER.error("instance: %s, unexpected error. %s\n", - instance_id, str(err) + lineno()) + # Process and delete A record and associated TXT record + process_response = process_delete_records( + route53, + instance_id, + final_hosted_zone_id, + final_private_hostname, + final_hosted_zone_name, + 'A', + private_ip, + heritage_value + ) - try: - if get_rr: - # pause 1 before deleting to avoid API limit - # time.sleep(1) - heritage_value = new_get_resource_record( - route53, - instance_id, - reverse_lookup_zone_id, - reversed_ip_address, - 'in-addr.arpa', - 'TXT', - heritage_value - ) - if len(heritage) > 0: - LOGGER.debug("Deleting heritage TXT resource records %s", - reversed_ip_address + lineno()) - response_text = 'Delete TXT record in zone id: ' + str(reverse_lookup_zone_id) + \ - ' for hosted zone ' + str(reversed_ip_address) + str(private_dns_name) + \ - ' with value: ' + str(heritage_value) + # only true if existing delete_records and the delete_success from the subroutine is true + delete_records = delete_records and process_response['delete_success'] + # append to the lsit + caller_response = caller_response + process_response['msg'] - delete_response = new_delete_resource_record( - route53, - instance_id, - reverse_lookup_zone_id, - reversed_ip_address, - 'in-addr.arpa', - 'TXT', - heritage_value - ) - if delete_response == 'NoSuchHostedZone': - delete_records = False - caller_response.append("Failed, no such zone: " + response_text) - LOGGER.info("instance: %s, NoSuchHostedZone: %s", - instance_id, response_text + lineno()) - elif delete_response == 'InvalidChangeBatch-RecordNotFound': - caller_response.append("Failed, Record Not Found: " + response_text) - LOGGER.info("instance: %s, InvalidChangeBatch-RecordNotFound: %s", - instance_id, response_text + lineno()) - elif delete_response == 'InvalidChangeBatch-RecordDoNotMatch': - delete_records = False - caller_response.append( - "Failed, requested delete do not match existing record: " + response_text) - LOGGER.info("instance: %s, InvalidChangeBatch-RecordDoNotMatch: %s", - instance_id, response_text + lineno()) - elif delete_response == {}: - delete_records = False - caller_response.append( - "Failed, could NOT delete Record: " + response_text) - LOGGER.info("instance: %s, Failed could NOT delete Record: %s", - instance_id, response_text + lineno()) - else: - caller_response.append("Success: " + response_text) - LOGGER.info("instance: %s, Success: %s", - instance_id, response_text + lineno()) - except BaseException as err: - delete_records = False - LOGGER.error("instance: %s, unexpected error. %s\n", - instance_id, str(err) + lineno()) + # Process and delete PTR record and associated TXT record + process_response = process_delete_records( + route53, + instance_id, + reverse_lookup_zone_id, + reversed_ip_address, + 'in-addr.arpa.', + 'PTR', + final_private_dns_name, + heritage_value + ) + # only true if existing delete_records and the delete_success from the subroutine is true + delete_records = delete_records and process_response['delete_success'] + # append to the lsit + caller_response = caller_response + process_response['msg'] - # Create the CNAME record only if it has passed the check + # Process the CNAME record only if it has passed the check if has_valid_cname_tag: LOGGER.debug("cname record is valid - creating CNAME record:" " %s", str(cname_host_name) + "." + str(cname_domain_suffix) + lineno()) @@ -1155,8 +970,8 @@ def lambda_handler( try: if len(heritage) > 0: - LOGGER.debug("Creating heritage TXT resource records %s", - TXT_RR_PREFIX + '.' + cname_host_name + lineno()) + LOGGER.debug("Creating heritage TXT resource records %s, with value of %s", + TXT_RR_PREFIX + '.' + cname_host_name, str(heritage_value) + lineno()) create_response = create_resource_record( route53, instance_id, @@ -1189,117 +1004,24 @@ def lambda_handler( LOGGER.error("instance: %s, unexpected error. %s\n", instance_id, str(err) + lineno()) + # not running, so process delete CNAME and associated TXT record else: - # delete the CNAME record - try: - LOGGER.debug("deleting CNAME record %s", lineno()) - response_text = 'Delete CNAME record in zone id: ' + str(cname_domain_suffix_id) + \ - ' for hosted zone ' + str(cname_host_name) + '.' + \ - str(cname_domain_suffix) + ' with value: ' + \ - str(final_private_dns_name) - - # pause 1 before deleting to avoid API limit - # time.sleep(1) - delete_response = new_delete_resource_record( - route53, - instance_id, - cname_domain_suffix_id, - cname_host_name, - cname_domain_suffix, - 'CNAME', - final_private_dns_name - ) - - if delete_response == 'NoSuchHostedZone': - delete_records = False - caller_response.append("Failed, no such zone: " + response_text) - LOGGER.info("instance: %s, NoSuchHostedZone: %s", - instance_id, response_text + lineno()) - elif delete_response == 'InvalidChangeBatch-RecordNotFound': - caller_response.append("Failed, Record Not Found: " + response_text) - LOGGER.info("instance: %s, InvalidChangeBatch-RecordNotFound: %s", - instance_id, response_text + lineno()) - elif delete_response == 'InvalidChangeBatch-RecordDoNotMatch': - delete_records = False - caller_response.append( - "Failed, requested delete do not match existing record: " + response_text) - LOGGER.info("instance: %s, InvalidChangeBatch-RecordDoNotMatch: %s", - instance_id, response_text + lineno()) - elif delete_response == {}: - delete_records = False - caller_response.append( - "Failed, could NOT delete Record: " + response_text) - LOGGER.info("instance: %s, Failed could NOT delete Record: %s", - instance_id, response_text + lineno()) - else: - caller_response.append("Success: " + response_text) - LOGGER.info("instance: %s, Success: %s", - instance_id, response_text + lineno()) - except BaseException as err: - delete_records = False - LOGGER.error("instance: %s, unexpected error. %s\n", - instance_id, str(err) + lineno()) - - # delete the CNAME txt record - try: - if get_rr: - heritage_value = new_get_resource_record( - route53, - instance_id, - cname_domain_suffix_id, - TXT_RR_PREFIX + '.' + cname_host_name, - cname_domain_suffix, - 'TXT', - heritage_value - ) - if len(heritage) > 0: - LOGGER.debug("Deleting heritage TXT resource records %s", - TXT_RR_PREFIX + '.' + cname_host_name + lineno()) - response_text = 'Delete TXT for CNAME record in zone id: ' + str(cname_domain_suffix_id) \ - + ' for hosted zone ' + str(TXT_RR_PREFIX) + '.' + str(cname_host_name) \ - + '.' + str(cname_domain_suffix) + ' with value: ' \ - + str(heritage_value) - - delete_response = new_delete_resource_record( - route53, - instance_id, - cname_domain_suffix_id, - TXT_RR_PREFIX + '.' + cname_host_name, - cname_domain_suffix, - 'TXT', - heritage_value - ) + # Process and delete CNAME record and associated TXT record + process_response = process_delete_records( + route53, + instance_id, + cname_domain_suffix_id, + cname_host_name, + cname_domain_suffix, + 'CNAME', + final_private_dns_name, + heritage_value + ) - if delete_response == 'NoSuchHostedZone': - delete_records = False - caller_response.append("Failed, no such zone: " + response_text) - LOGGER.info("instance: %s, NoSuchHostedZone: %s", - instance_id, response_text + lineno()) - elif delete_response == 'InvalidChangeBatch-RecordNotFound': - caller_response.append( - "Failed, Record Not Found: " + response_text) - LOGGER.info("instance: %s, InvalidChangeBatch-RecordNotFound: %s", - instance_id, response_text + lineno()) - elif delete_response == 'InvalidChangeBatch-RecordDoNotMatch': - delete_records = False - caller_response.append( - "Failed, requested delete do not match existing record: " + response_text) - LOGGER.info("instance: %s, InvalidChangeBatch-RecordDoNotMatch: %s", - instance_id, response_text + lineno()) - elif delete_response == {}: - delete_records = False - caller_response.append( - "Failed, could NOT delete Record: " + response_text) - LOGGER.info("instance: %s, Failed could NOT delete Record: %s", - instance_id, response_text + lineno()) - else: - caller_response.append("Success: " + response_text) - LOGGER.info("instance: %s, Success: %s", - instance_id, response_text + lineno()) - except BaseException as err: - delete_records = False - LOGGER.error("instance: %s, unexpected error. %s\n", - instance_id, str(err) + lineno()) + # only true if existing delete_records and the delete_success from the subroutine is true + delete_records = delete_records and process_response['delete_success'] + # append to the lsit + caller_response = caller_response + process_response['msg'] # Clean up DynamoDB after deleting records if state != 'running': @@ -1427,7 +1149,7 @@ def new_list_hosted_zones(client, instance_id): sns_msg['boto3_method'] = 'list_hosted_zones' sns_msg['message'] = 'list_hosted_zones timed out' publish_to_sns(get_sns_client(), json.dumps(sns_msg)) - LOGGER.info("instance: %s, sending sns message %s", + LOGGER.info("instance: %s, sending sns message %s", instance_id, json.dumps(sns_msg) + lineno()) except: LOGGER.info("instance: %s, error: %s", instance_id, @@ -1753,7 +1475,7 @@ def new_change_resource_recordset(client, instance_id, zone_id, host_name, hoste if i >= MAX_API_RETRY: LOGGER.error("instance: %s, change_resource_record_sets exceeded max retry of %s", - instance_id, MAX_API_RETRY + lineno()) + instance_id, str(MAX_API_RETRY) + lineno()) if update_response == {}: if SNS_ENABLE: @@ -1767,7 +1489,7 @@ def new_change_resource_recordset(client, instance_id, zone_id, host_name, hoste sns_msg['change_resource_record_sets'] = { 'HostedZoneId': zone_id, 'ChangeBatch': change_batch} publish_to_sns(get_sns_client(), json.dumps(sns_msg)) - LOGGER.info("instance: %s, sending sns message %s", + LOGGER.info("instance: %s, sending sns message %s", instance_id, json.dumps(sns_msg) + lineno()) except: LOGGER.info("instance: %s, error: %s", instance_id, @@ -1888,29 +1610,43 @@ def new_get_resource_record(client, instance_id, zone_id, host_name, hosted_zone :param str hosted_zone_name: :param str record_type: :param str unused: Placeholder for same calling parameters as delete_resource_record(); unused - :return str value: Value of record if found, None if not + :return str value: Value of record if found, empty "' if not found """ i = 0 - value = None + value = '' while i < MAX_API_RETRY: try: - LOGGER.debug("Getting %s record %s in zone %s" - " %s", record_type, host_name, hosted_zone_name, lineno()) + LOGGER.debug("Getting %s record type for %s", + record_type, host_name + lineno()) + if host_name[-1] != '.': host_name = host_name + '.' + + LOGGER.debug("list_resource_record_sets looking for record %s in zone %s", + str(host_name), str(hosted_zone_name) + lineno()) + response = client.list_resource_record_sets( HostedZoneId=zone_id, - StartRecordName=host_name, + StartRecordName=host_name + hosted_zone_name, StartRecordType=record_type, - MaxItems=1) + MaxItems='1') - if len(response) > 0: - rr_set = response['ResourceRecordSets'][0] - if rr_set['Name'] == host_name and rr_set['Type'] == record_type: - value = rr_set['ResourceRecords'][0]['Value'] + LOGGER.debug("list_resource_record_sets response: %s", json.dumps(response) + lineno()) + for rr_set in response['ResourceRecordSets']: + rr_name = rr_set['Name'] + # check if the return value matches the record, if not ignore + # if the record isn't there, it returns the list_resource_record_sets returns the next record + if rr_name == (host_name + hosted_zone_name): + value = rr_set['ResourceRecords'][0]['Value'] + LOGGER.debug("list_resource_record_sets returned value. %s", + str(value) + lineno()) + else: + LOGGER.debug("list_resource_record_sets returned different record ignoring. %s", + str(rr_name) + lineno()) + LOGGER.debug( "list_resource_record_sets returned without error. %s", lineno()) break @@ -1934,7 +1670,7 @@ def new_get_resource_record(client, instance_id, zone_id, host_name, hosted_zone if i >= MAX_API_RETRY: LOGGER.error("instance: %s, list_resource_record_sets exceeded max retry of %s", - instance_id, MAX_API_RETRY + lineno()) + instance_id, str(MAX_API_RETRY) + lineno()) if SNS_ENABLE: try: sns_msg = {} @@ -1944,13 +1680,13 @@ def new_get_resource_record(client, instance_id, zone_id, host_name, hosted_zone sns_msg['boto3_method'] = 'list_resource_record_sets' sns_msg['message'] = 'list_resource_record_sets timed out' publish_to_sns(get_sns_client(), json.dumps(sns_msg)) - LOGGER.info("instance: %s, sending sns message %s", + LOGGER.info("instance: %s, sending sns message %s", instance_id, json.dumps(sns_msg) + lineno()) except: LOGGER.info("instance: %s, error: %s", instance_id, str(sys.exc_info()[0]) + lineno()) - return value + return str(value) # def delete_resource_record(client, zone_id, host_name, hosted_zone_name, record_type, value): @@ -2089,7 +1825,7 @@ def new_delete_resource_record(client, instance_id, zone_id, host_name, hosted_z if i >= MAX_API_RETRY: LOGGER.error("instance: %s, change_resource_record_sets exceeded max retry of %s", - instance_id, MAX_API_RETRY + lineno()) + instance_id, str(MAX_API_RETRY) + lineno()) if (delete_response == {} or delete_response == "InvalidChangeBatch-RecordDoNotMatch"): if SNS_ENABLE: @@ -2103,7 +1839,7 @@ def new_delete_resource_record(client, instance_id, zone_id, host_name, hosted_z sns_msg['change_resource_record_sets'] = { 'HostedZoneId': zone_id, 'ChangeBatch': change_batch} publish_to_sns(get_sns_client(), json.dumps(sns_msg)) - LOGGER.info("instance: %s, sending sns message %s", + LOGGER.info("instance: %s, sending sns message %s", instance_id, json.dumps(sns_msg) + lineno()) except: LOGGER.info("instance: %s, error: %s", instance_id, @@ -2525,7 +2261,7 @@ def new_get_hosted_zone_properties(client, instance_id, zone_id): if hosted_zone_properties == {}: LOGGER.error("instance: %s, get_hosted_zone exceeded max retry of %s", - instance_id, MAX_API_RETRY + lineno()) + instance_id, str(MAX_API_RETRY) + lineno()) if SNS_ENABLE: try: sns_msg = {} @@ -2535,7 +2271,7 @@ def new_get_hosted_zone_properties(client, instance_id, zone_id): sns_msg['boto3_method'] = 'get_hosted_zone' sns_msg['message'] = 'get_hosted_zone timed out' publish_to_sns(get_sns_client(), json.dumps(sns_msg)) - LOGGER.info("instance: %s, sending sns message %s", + LOGGER.info("instance: %s, sending sns message %s", instance_id, json.dumps(sns_msg) + lineno()) except: LOGGER.info("instance: %s, error: %s", instance_id, @@ -2651,29 +2387,59 @@ def parse_heritage(info): :param str info: string with TXT record of heritage data :return dict(str): Heritage dict """ + + LOGGER.debug("parsing heritage value: %s", + str(info) + lineno()) + + # return empty dictionary if non-string passed it + if not (isinstance(info,str)): + LOGGER.error("heritage parsing error value: non-string value passed in: %s", + str(info) + lineno()) + return {} + + # check if not empty string and then remove leading and trailing quotes + if len(info) > 1: + # remove beginning quote + if info[0] == '"': + info = info[1:] + + # remove ending quote + if info[-1] == '"': + info = info[:-1] + kv_results = {} kv = info.split(',') -# print(kv) + LOGGER.debug("heritage split result: %s", str(kv) + lineno()) header = kv.pop(0).split('=') if header[0] != 'heritage': + LOGGER.debug("heritage analysis: does not contain heritage header, returning: %s", + str(kv_results) + lineno()) return kv_results else: appname = header[1] kv_results['application_name'] = appname + LOGGER.debug("heritage analysis: assigning application_name: %s", + str(appname) + lineno()) + try: for item in kv: k, v = item.split('=', 2) -# print('appname',appname,'k',k,'v',v) + LOGGER.debug("heritage item: key: %s, value: %s", + str(k), str(v) + lineno()) + # print('appname',appname,'k',k,'v',v) if appname + '/' in k: nk = k.replace(appname + '/', '') kv_results[nk] = v -# print('nk',nk) + # print('nk',nk) if kv_results.get('version') is None: # version=kv_result.pop('version') # else: version = 'null' -# return initialize_heritage(appname,version,kv_results) + # return initialize_heritage(appname,version,kv_results) + + LOGGER.debug("heritage parsed dictionary: %s", + str(kv_results) + lineno()) return kv_results except: return {} @@ -2699,13 +2465,22 @@ def get_heritage_item(data, key): :return: The value of the heritage item if it exists, and None if not """ - result = None - items = data.get('items', {}) - if len(items): - v = items.get(key, None) - result = v - return result + # result = None + # items = data.get('items', {}) + # if len(items): + # v = items.get(key, None) + # result = v + # return result + if not isinstance(data,dict): + LOGGER.debug("get_heritage_item: not valid dictionary: %s is a class of %s", + str(data), str(type(data)) + lineno()) + return None + else: + result = data.get(key, None) + LOGGER.debug("get_heritage_item: getting key %s value %s", + str(key), str(result) + lineno()) + return result def compare_heritage(data, key, value): """ @@ -2756,3 +2531,230 @@ def publish_to_sns(client, message): LOGGER.debug("Unexpected error: %s", str(err) + lineno()) else: LOGGER.debug("No SNS Topic specified, ignoring") + +def process_delete_records(route53, instance_id, zone_id, + record_name, zone_name, record_type, record_value, heritage_value): + + """ + Consolidate all of the logic to evaluate the process deletion for A/PTR/AAAA record and associated TXT record. + :param route53: + :param instance_id: + :param zone_id: + :param record_name: + :param zone_name: + :param record_type: + :param record_value: + :return response: # dictionary of 'delete_success' and 'msg' + """ + + response = {} + response_delete_success = True + response_msg = [] + + LOGGER.info("instance: %s, Delete %s Record. Checking TXT record association for %s in zone %s", + instance_id, record_type, record_name, zone_name + lineno()) + + # if record type is CNAME, we need to add the TXT RR prefix + if record_type == 'CNAME': + txt_record_name = TXT_RR_PREFIX + '.' + record_name + else: + txt_record_name = record_name + + heritage_value = new_get_resource_record( + route53, + instance_id, + zone_id, + txt_record_name, + zone_name, + "TXT", + heritage_value + ) + + # Return the dictionary of the value with comma separated + heritage = parse_heritage(heritage_value) + LOGGER.debug("heritage parsed data in string format: %s", str(heritage) + lineno()) + + # check if the TXT record was created by the Lambda as match instance-id + if verify_heritage_owner(heritage, HERITAGE_TAG): + LOGGER.debug("TXT record was created by Lambda DDNS %s", HERITAGE_TAG + lineno()) + heritage_own = True + else: + LOGGER.info("TXT record was not created by Lambda DDNS %s", HERITAGE_TAG + lineno()) + heritage_own = False + + if compare_heritage(heritage, 'instance_id', instance_id): + LOGGER.debug("TXT record matches instance_id: %s", instance_id + lineno()) + heritage_instance_match = True + else: + LOGGER.info("TXT record does not match instance_id: %s", instance_id + lineno()) + heritage_instance_match = False + + # delete A/PTR/AAAA/CNAME record + if heritage_own and heritage_instance_match: + try: + + LOGGER.info("Deleting %s resource record %s, in zone %s, with a value of %s", + record_type, record_name, zone_name, record_value + lineno()) + + response_text = 'Delete ' + record_type + \ + ' record in zone id: ' + zone_id + \ + ' for record ' + record_name + \ + ' in zone named ' + zone_name + \ + ' with value: ' + record_value + + delete_response = new_delete_resource_record( + route53, + instance_id, + zone_id, + record_name, + zone_name, + record_type, + record_value + ) + + if delete_response == 'NoSuchHostedZone': + response_delete_success = False + response_msg.append("Failed, no such zone: " + response_text) + LOGGER.info("instance: %s, NoSuchHostedZone: %s", + instance_id, response_text + lineno()) + elif delete_response == 'InvalidChangeBatch-RecordNotFound': + response_msg.append("Failed, Record Not Found: " + response_text) + LOGGER.info("instance: %s, InvalidChangeBatch-RecordNotFound: %s", + instance_id, response_text + lineno()) + elif delete_response == 'InvalidChangeBatch-RecordDoNotMatch': + response_delete_success = False + response_msg.append( + "Failed, requested delete do not match existing record: " + response_text) + LOGGER.info("instance: %s, InvalidChangeBatch-RecordDoNotMatch: %s", + instance_id, response_text + lineno()) + elif delete_response == {}: + response_delete_success = False + response_msg.append( + "Failed, could NOT delete Record: " + response_text) + LOGGER.info("instance: %s, Failed, could NOT delete Record: %s", + instance_id, response_text + lineno()) + else: + LOGGER.info("instance: %s, Success: %s", + instance_id, response_text + lineno()) + response_msg.append("Success: " + response_text) + + except BaseException as err: + response_delete_success = False + LOGGER.error("instance: %s, unexpected error. %s\n", + instance_id, str(err) + lineno()) + + else: + response_delete_success = False + response_msg.append("Failed, the TXT record for the " + record_type + + " record does not match expected value. Will not delete the " + record_type + " record.") + LOGGER.error("instance: %s, the TXT record for the %s record does not match expected value. Will not delete the %s record. %s\n", + instance_id, record_type, record_type, lineno()) + if SNS_ENABLE: + try: + sns_msg = {} + sns_msg['instance_id'] = instance_id + sns_msg['account_id'] = get_caller_account_id() + sns_msg['message'] = 'TXT record does not match. Will not delete the A/PTR/CNAME/AAAA record.' + + sns_heritage = {} + sns_heritage['record_type'] = record_type + sns_heritage['record_name'] = record_name + sns_heritage['zone_name'] = zone_name + sns_heritage['zone_id'] = zone_id + sns_heritage['heritage_value'] = heritage_value + + sns_msg['heritage']=sns_heritage + publish_to_sns(get_sns_client(), json.dumps(sns_msg)) + LOGGER.info("instance: %s, sending sns message %s", instance_id, + json.dumps(sns_msg) + lineno()) + except: + LOGGER.info("instance: %s, error: %s", instance_id, + str(sys.exc_info()[0]) + lineno()) + + + # delete TXT record associated with A/PTR/CNAME/AAAA record + if heritage_own and heritage_instance_match: + try: + LOGGER.info("Deleting heritage TXT resource record %s, in the zone %s, with value of %s", + txt_record_name, zone_name, str(heritage_value) + lineno()) + + response_text = 'Delete ' + 'TXT' + \ + ' record in zone id: ' + zone_id + \ + ' for record ' + txt_record_name + \ + ' in zone named ' + zone_name + \ + ' with value: ' + str(heritage_value) + + delete_response = new_delete_resource_record( + route53, + instance_id, + zone_id, + txt_record_name, + zone_name, + 'TXT', + str(heritage_value)) + + if delete_response == 'NoSuchHostedZone': + response_delete_success = False + response_msg.append("Failed, no such zone: " + response_text) + LOGGER.info("instance: %s, NoSuchHostedZone: %s", + instance_id, response_text + lineno()) + elif delete_response == 'InvalidChangeBatch-RecordNotFound': + response_msg.append("Failed, Record Not Found: " + response_text) + LOGGER.info("instance: %s, InvalidChangeBatch-RecordNotFound: %s", + instance_id, response_text + lineno()) + elif delete_response == 'InvalidChangeBatch-RecordDoNotMatch': + response_delete_success = False + response_msg.append( + "Failed, requested delete do not match existing record: " + response_text) + LOGGER.info("instance: %s, InvalidChangeBatch-RecordDoNotMatch: %s", + instance_id, response_text + lineno()) + elif delete_response == {}: + response_delete_success = False + response_msg.append( + "Failed, could NOT delete Record: " + response_text) + LOGGER.info("instance: %s, Failed Could NOT delete Record: %s", + instance_id, response_text + lineno()) + else: + response_msg.append("Success: " + response_text) + LOGGER.info("instance: %s, Success: %s", + instance_id, response_text + lineno()) + except BaseException as err: + response_delete_success = False + LOGGER.error("instance: %s, unexpected error. %s\n", + instance_id, str(err) + lineno()) + else: + response_delete_success = False + response_msg.append("Failed, the TXT record for " + record_type + + " does not match expected value. Will not delete the TXT record.") + LOGGER.error("instance: %s, the TXT record for the %s does not match expected value. Will not delete TXT record. %s", + instance_id, record_type, lineno()) + + if SNS_ENABLE: + try: + sns_msg = {} + sns_msg['instance_id'] = instance_id + sns_msg['account_id'] = get_caller_account_id() + sns_msg['message'] = 'TXT record does not match. Will not delete the TXT record.' + + sns_heritage = {} + sns_heritage['record_type'] = 'TXT' + sns_heritage['record_name'] = txt_record_name + sns_heritage['zone_name'] = zone_name + sns_heritage['zone_id'] = zone_id + sns_heritage['heritage_value'] = heritage_value + + sns_msg['heritage']=sns_heritage + publish_to_sns(get_sns_client(), json.dumps(sns_msg)) + LOGGER.info("instance: %s, sending sns message %s", instance_id, + json.dumps(sns_msg) + lineno()) + + except: + LOGGER.info("instance: %s, error: %s", instance_id, + str(sys.exc_info()[0]) + lineno()) + + + # create a dictionary to return + response['delete_success'] = response_delete_success + response['msg'] = response_msg + + return response diff --git a/code/sample-event.running-minium.json b/code/sample-event.running-minium.json new file mode 100644 index 0000000..0068fe4 --- /dev/null +++ b/code/sample-event.running-minium.json @@ -0,0 +1,7 @@ +{ + "region": "us-gov-west-1", + "detail": { + "instance-id": "i-06eaa7e7ec1ca43dd", + "state": "running" + } +} diff --git a/code/sample-event.stopped-minimum.json b/code/sample-event.stopped-minimum.json new file mode 100644 index 0000000..72ce4d4 --- /dev/null +++ b/code/sample-event.stopped-minimum.json @@ -0,0 +1,7 @@ +{ + "region": "us-gov-west-1", + "detail": { + "instance-id": "i-06eaa7e7ec1ca43dd", + "state": "stopped" + } +} \ No newline at end of file