From e24b15133df177260572c086e7a8e1d1cf53e97d Mon Sep 17 00:00:00 2001 From: badra001 Date: Thu, 17 Feb 2022 16:04:49 -0500 Subject: [PATCH 1/2] add heritage txt functions --- CHANGELOG.md | 3 + code/ddns-lambda.py | 162 +++++++++++++++++++++++++++++++++++++------- version.tf | 2 +- 3 files changed, 142 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 992ab33..e6035f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,3 +26,6 @@ - Added additional "sleep" timer to reduce Route 53 API limit (5 per sec). I also reduced the amount of API's which were duplicate. Also, added random 10 or 20 seconds sleep timer to reduce the probability of API limit. - Route 53 SDK will auto-retry up to 5 times, however, by adding random up to 10 seconds (running) or up to 20 seconds (terminate/stop) will spread out the calls if multiple instances are launched OR terminated/stopped. Testing performed up to 30 instance started/stopped. +* 0.0.21 -- 2022-02-17 + - update code 0.0.12 + - add heritage functions, but not include anywhere to call them diff --git a/code/ddns-lambda.py b/code/ddns-lambda.py index e3ba763..521dac1 100755 --- a/code/ddns-lambda.py +++ b/code/ddns-lambda.py @@ -1,7 +1,7 @@ """ DDNS Lambda Python3 Script (revised) -The script will retrieve the information from the EC2 instance or from the DynamoDB Table entry. +The script will retrieve the information from the EC2 instance or from the DynamoDB Table entry. IF the instance is running, it will create the DynamoDB Item with the instance data. * PrivateIpAddress * PrivateDnsName @@ -14,31 +14,31 @@ 3. DNS support enabled on the VPC 4. Reverse Lookup zone (Route 53 PHZ) for the subnet is associated with the VPC. -It will then itereate through the Tags and based upon the Tag combination, +It will then itereate through the Tags and based upon the Tag combination, construct the A/PTR record to be created (running) or deleted (shutting down) -The following tag combination will result in the A/PTR record. +The following tag combination will result in the A/PTR record. The order matters since first match will skip the rest of the condition. 1. If Custom hostname Tag AND Custom Zone Tags exist and are valid - hostname: Custom Hostname - zonename: Custom Zonename 2. If Custom hostname Tag is valid AND there's NO Custom Zone Tag AND VPC Domain name is Valid - hostname: Custom Hostname -- zonename: VPC Domain name given via DHCP option +- zonename: VPC Domain name given via DHCP option (if custom hostname value contains fqdn, the zone is ignored) 3. If Name Tag is valid (both hostname and zonename portion) -- hostname: Name (minus the zone name) +- hostname: Name (minus the zone name) - zonename: Name (minus the host name) 4. If Name Tag is valid (only hostname portion) AND Custom Zone Tag is valid -- hostname: Name (minus the zone name) +- hostname: Name (minus the zone name) - zonename: Custom Zonename -5. If Name Tag is valid (only hostname portion) AND VPC Domain name is Valid -- hostname: Name (minus the zone name) +5. If Name Tag is valid (only hostname portion) AND VPC Domain name is Valid +- hostname: Name (minus the zone name) - zonename: VPC Domain name given via DHCP option 6. Custom Zone Tags is valid AND (Name hostname and custom hostname not Valid) - hostname: ip-1-2-3-4 format - zonename: Custom Zonename -7. No Custom Tags +7. No Custom Tags - hostname: ip-1-2-3-4 format - zonename: VPC Domain name given via DHCP option 8. If no match above then exit out script (no A/PTR record) @@ -65,12 +65,14 @@ import os import ipaddress from botocore.exceptions import ClientError +from collections import OrderedDict +from pprint import pformat # Setting Global Variables LOGGER = logging.getLogger() ACCOUNT = None REGION = None -VERSION = '0.0.11' +VERSION = '0.0.12' # Adjust the logging level [logging.INFO, logging.DEBUG, logging.WARNING, etc] LOGGER.setLevel(logging.DEBUG) @@ -179,8 +181,8 @@ def lambda_handler( if state == 'running': LOGGER.debug("sleeping for maximum {} seconds {}".format(SLEEPTIME, lineno())) - # wait increment and wait until maximum sleeptime - i = 1 + # wait increment and wait until maximum sleeptime + i = 1 while i < SLEEPTIME: LOGGER.debug("waiting count: %s", str(i) + lineno()) time.sleep(1) @@ -197,8 +199,8 @@ def lambda_handler( # if key attributes are found, then break out of the loop if all([t_private_ip, t_private_dns_name, t_subnet_id, t_vpc_id]): - LOGGER.debug ("instance data found, exiting while loop: " - "%s", t_private_dns_name + ","+ t_private_ip + ","+ t_subnet_id + ","+ t_vpc_id + lineno()) + LOGGER.debug("instance data found, exiting while loop: " + "%s", t_private_dns_name + "," + t_private_ip + "," + t_subnet_id + "," + t_vpc_id + lineno()) break except: LOGGER.info("no instance data, repeat check: %s", lineno()) @@ -360,7 +362,7 @@ def lambda_handler( # the VPC. Obtain the zone name, and check if there is a Private Hosted Zone # associated with the VPC. If so, it will set the zone name to be used later. has_dhcp_dns_zone_associated_vpc = False - + # store verified valid dns zones so to speed up the script. valid_dns_zones = [] @@ -396,7 +398,7 @@ def lambda_handler( if tag.get('Key').lstrip().lower() == TAGKEY_ZONE.lower(): LOGGER.debug("Zone Tag key: %s", tag.get('Key') + lineno()) - + # pause 1s to spread out API calls time.sleep(1) custom_zone_name = tag.get('Value').lstrip().lower() @@ -407,7 +409,7 @@ def lambda_handler( LOGGER.debug("Checking if custom_zone_name is valid: %s", str(custom_zone_name) + lineno()) - + # check if the zone is already validated, if not check if custom_zone_name in valid_dns_zones: LOGGER.debug("custom_zone_name already valid: %s", str( @@ -455,7 +457,7 @@ def lambda_handler( elif is_valid_zone(route53, cname_domain_suffix, hosted_zones, vpc_id, private_hosted_zone_collection): LOGGER.debug("cname domain is valid: %s", cname_domain_suffix + lineno()) - valid_dns_zones.append(cname_domain_suffix) + valid_dns_zones.append(cname_domain_suffix) has_valid_cname_tag = True else: LOGGER.debug("cname domain is not valid: %s", @@ -521,7 +523,7 @@ def lambda_handler( if name_domain_suffix in valid_dns_zones: has_valid_Name_tag_zonename = True LOGGER.debug("name_domain_suffix already valid: %s", str( - name_domain_suffix) + lineno()) + name_domain_suffix) + lineno()) elif is_valid_zone(route53, name_domain_suffix, hosted_zones, vpc_id, private_hosted_zone_collection): valid_dns_zones.append(name_domain_suffix) has_valid_Name_tag_zonename = True @@ -567,7 +569,8 @@ def lambda_handler( else: # none of the use-casem and no suitable zone to create the A record LOGGER.info("No DHCP Associated for VPC and no custom tags. Exiting Script") # nothing to do, exit out script - caller_response.append ('No DHCP Associated for VPC and no custom tags. Exiting Script') + caller_response.append( + 'No DHCP Associated for VPC and no custom tags. Exiting Script') return caller_response # put together the FQDN of the dns name... @@ -627,7 +630,7 @@ def lambda_handler( else: # not running so delete the records try: - + # pause 1 before deleting to avoid API limit time.sleep(1) delete_resource_record( @@ -1154,10 +1157,11 @@ def is_valid_hostname(hostname): except: LOGGER.info("unexpected error. %s\n", str(sys.exc_info()[0]) + lineno()) + def is_valid_zone(route53, zonename, hosted_zones, vpc_id, private_hosted_zone_collection): """ This function checks to see whether the zone "name" entered - is valid (PHZ zone exists and is associated with the VPC + is valid (PHZ zone exists and is associated with the VPC where instance is lauched in) :param zonename: :param vpc_id: @@ -1165,11 +1169,11 @@ def is_valid_zone(route53, zonename, hosted_zones, vpc_id, private_hosted_zone_c :param private_hosted_zone_collection: :return: """ - + LOGGER.debug("in function is_valid_zone") LOGGER.debug("Looking to validate zone: %s", zonename + lineno()) - + try: # check if the zone is PHZ if zonename.lower() in private_hosted_zone_collection: @@ -1396,3 +1400,113 @@ def get_subnet_cidr_block(client, subnet_id): return response['Subnets'][0]['CidrBlock'] except: LOGGER.info("unexpected error. %s\n", str(sys.exc_info()[0]) + lineno()) + + +def initialize_heritage(application_name, version='null', items={}): + """ + Initialize the heritage datastructure (dict). + :param str application_name: The application name. Shoud not have spaces. An empty application name will return an empty dict. + :param str version: A version of the specific implementation that created this. Versions are primarily for documenting what created the record TXT record. + :param dict(str) items: A dict of key/value pairs to set on initialization. They key of version is not permitted here. + :return dict(str): dict with the application name, version, and items ready for use + """ + if application_name != '' and appication_name is not none: + return { + 'application_name': str(appname), + 'version': str(version), + 'items': OrderedDict(items), + } + else: + return {} + + +def dump_heritage(data): + """ + Dump the heritage dict into a string. + :param dict(string): Dictionary containing heritage data + :return dict(string): string format of dict + """ + return pformat(data) + + +def add_heritage_item(data, key, value): + """ + Add a key/value pair to the heritage dict. + :param dict(string) data: Dictionary containing heritage data + :param str key: The key for the key/value pair + :param str value: The value for the key/value pair + :return: This adds the key/value pair to the heritage dict items. There is no return value. + """ + data['items'][str(key)] = str(value) + + +def add_heritage_item_timestamp(data, key): + """ + Add an epoch timestamp to the named field in key. + :param dict(string) data: Dictionary containing heritage data + :param str key: The key for the key to contain the timestamp + :return: This adds the key and current timestamp to the heritage dict items. There is no return value. + """ + data['items'][str(key)] = int(datetime.now().timestamp()) + + +def format_heritage(data): + """ + Return the TXT record format of the heritage data structure. This is of the format + heritage={app},{app}/version={version},{app}/{key}={value},... + + :param dict(string) data: Dictionary containing heritage data + :return str: This returns a string with the formatted heritage data comma separated + """ + appname = data['application_name'] + output = ['heritage={},{}/version={}'.format(appname, appname, data['version'])] + for k, v in data['items'].items(): + output.append('{}/{}={}'.format(appname, k, v)) + return ','.join(output) + + +def parse_heritage(info): + """ + Take a TXT record and parse it into a heritage dict. + :param str info: string with TXT record of heritage data + :return dict(str): Heritage dict + kv_results={} + kv=info.split(',') +# print(kv) + header=kv.pop(0).split('=') + + if header[0]!='heritage': + return kv_results + else: + appname=header[1] + kv_results['application_name']=appname + try: + for item in kv: + k,v=item.split('=',2) +# print('appname',appname,'k',k,'v',v) + if appname+'/' in k: + nk=k.replace(appname+'/','') + kv_results[nk]=v +# 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 kv_results + except: + return {} + +# heritage examples to incorporate +# h=initialize_heritage('dynr53','0.0.9') +# pprint(h) +# add_heritage_item(h,'instance_id','i-123123123123') +# add_heritage_item(h,'account_id','123123123123') +# add_heritage_item(h,'region','west') +# add_heritage_item_timestamp(h,'create_time') +# txt=format_heritage(h) +# print(txt) +# nh=parse_heritage(txt) +# print(dump_heritage(nh)) +# nh=parse_heritage('bob'+txt) +# print(dump_heritage(nh)) diff --git a/version.tf b/version.tf index 56bf4db..3ca2fc8 100644 --- a/version.tf +++ b/version.tf @@ -1,3 +1,3 @@ locals { - _module_version = "0.0.20" + _module_version = "0.0.21" } From e7099b33a5710329f4de59423c0f8c0ef532ad16 Mon Sep 17 00:00:00 2001 From: badra001 Date: Thu, 17 Feb 2022 16:05:04 -0500 Subject: [PATCH 2/2] add heritage txt functions --- code/ddns-lambda.zip | Bin 9907 -> 11046 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/code/ddns-lambda.zip b/code/ddns-lambda.zip index c0c6635beef93ce75cad7f7268c73fd56fe77825..89902c60c845d6d3df835a905e43ddaa685677f1 100644 GIT binary patch literal 11046 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 literal 9907 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 bW&a12sVl+4{qHv9f2I9jSNM;nK|uTuOY|y8