From d233a12a5ec54248403aba95e90f841260f73c7f Mon Sep 17 00:00:00 2001 From: Daniel Dayley Date: Fri, 6 Dec 2019 11:14:19 -0700 Subject: [PATCH] I feel like I'm writing more bugs than features --- ifxtool | 29 ++++++------ plugins/bluecat.py | 9 +++- plugins/f5.py | 114 ++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 126 insertions(+), 26 deletions(-) diff --git a/ifxtool b/ifxtool index 3079275..b73d7af 100755 --- a/ifxtool +++ b/ifxtool @@ -61,15 +61,22 @@ class IFXTool(): # Resolve subject names for item in self.namespace.subjects : try: - quicklookup = socket.gethostbyname(item) - if quicklookup != item: + quicklookup = ipaddress.ip_address(item) + subjecttuple = socket.gethostbyname_ex(item) + # Can't make an IP out of item + except ValueError : + try : + # Lookup with system subjecttuple = socket.gethostbyname_ex(item) - else : - subjecttuple = socket.gethostbyaddr(item) - except (socket.gaierror,socket.herror) : - self.warninfo.append("Unable to resolve " + item) - self.namespace.subjects.remove(item) - continue + except socket.gaierror : + # Not a valid host or IP + self.warninfo.append("Unable to resolve " + item) + #self.namespace.subjects.remove(item) + #continue + subjecttuple = [item,[],['']] + # Can't make IPv4 out of it + except socket.gaierror : + subjecttuple = [ipaddress.ip_address(item).reverse_pointer,[],[str(item)]] # Convert mutable types to immutable (to be used as dictionary keys) immutablesubjecttuple = tuple([subjecttuple[0],tuple(subjecttuple[1]),tuple(subjecttuple[2])]) self._dnsmaps.update({immutablesubjecttuple:item}) @@ -109,10 +116,6 @@ class IFXTool(): else : return '' - def get_dc_from_ip(self,ip) : - """Given an IP, return the datacenter the IP resides in.""" - pass - def print_results(self,dataset) : """Returns the submitted dataset""" finalresult = {} @@ -125,7 +128,6 @@ class IFXTool(): for subject in self._subjecttuples : subjectname = self._dnsmaps[subject] subjectdataset = {} - # for plugin in [x for x in dataset if x != 'status'] : for plugin in [x for x in dataset if x != 'error' and x != 'warn'] : subjectdataset.update({plugin:dataset[plugin][subject]}) finalresult.update({subjectname:subjectdataset}) @@ -141,6 +143,7 @@ if __name__ == "__main__": # Load Plugins plugindir="plugins" pluginfiles = [x for x in os.listdir(plugindir) if x != "servicebase.py" and x.endswith('.py')] + pluginfiles = sorted(pluginfiles) importlib.import_module(plugindir) delegates = [] for pluginfile in pluginfiles : diff --git a/plugins/bluecat.py b/plugins/bluecat.py index 72527bd..f367562 100644 --- a/plugins/bluecat.py +++ b/plugins/bluecat.py @@ -20,13 +20,20 @@ class ServiceDelegate(ServiceBase) : self.current_auth_header = self.get_bc_auth_header(self.config['host'],self.config['username'],self.config['key']) def perform_lookup(self,host_tuple) : + error = None + if self.return_payload and 'error' in self.return_payload : + error = self.return_payload['error'] self.return_payload = {} + if error : + self.return_payload.update({'error':error}) if self.current_auth_header : object = self.search_bc_object(host_tuple) self.return_payload.update({'object':object}) parent = self.search_bc_parent_object(host_tuple) self.return_payload.update({'parent':parent}) return self.return_payload + else : + return self.return_payload def shutdown(self) : if self.host and self.current_auth_header : @@ -49,7 +56,7 @@ class ServiceDelegate(ServiceBase) : def search_bc_parent_object(self,host_tuple) : """Search for and return information about the objects parent, if any""" - if 'object' in self.return_payload and 'id' in self.return_payload['object'] : + if 'object' in self.return_payload and type(self.return_payload['object']) is dict and 'id' in self.return_payload['object'] : response = requests.get("https://" + self.host + "/Services/REST/v1/getParent?entityId=" + str(self.return_payload['object']['id']), headers=self.current_auth_header) return self.clean_response(response) diff --git a/plugins/f5.py b/plugins/f5.py index e1ac20a..734fe29 100644 --- a/plugins/f5.py +++ b/plugins/f5.py @@ -1,18 +1,108 @@ from servicebase import ServiceBase +import requests +import re +import json class ServiceDelegate(ServiceBase) : + + current_auth_header = None + return_payload = {} + hosts = None + def get_arguments(cls) : - """Returns an array of information used to construct an argumentparser argument.""" - # [ ,,, ] - # Example return: [ '-n', '--net', 'store_true', "Return network information about the subject" ] return ['-cg', '--vip', 'store_true', "Return VIP information about the subject"] - def get_f5_auth_header(host,username,password,authprovider) : - """Given authentication information, return the authenticated header for use in REST requests to an F5 device.""" - pass - def get_f5_for_dc(dc) : - """Given a datacenter, return the next F5 device for that datacenter.""" - pass - def get_vip_cg(ip) : - """Given an IP, return all VIPs that the IP is a member of.""" - pass + def startup(self) : + for requirement in ['hosts','username','key'] : + if requirement not in self.config or (requirement in self.config and self.config[requirement] is ''): + self.return_payload.update({'error':'Missing required config option ' + requirement}) + return + self.hosts = self.config['hosts'] + #self.current_auth_header = self.get_bc_auth_header(self.config['host'],self.config['username'],self.config['key']) + + def perform_lookup(self,host_tuple) : + error = None + if self.return_payload and 'error' in self.return_payload : + error = self.return_payload['error'] + self.return_payload = {} + if error : + self.return_payload.update({'error':error}) + self.return_payload.update({'lookupcandidates':self.choose_host(host_tuple)}) + + + return self.return_payload + if self.current_auth_header : + object = self.search_bc_object(host_tuple) + self.return_payload.update({'object':object}) + parent = self.search_bc_parent_object(host_tuple) + self.return_payload.update({'parent':parent}) + return self.return_payload + + def shutdown(self) : + return + if self.host and self.current_auth_header : + logout = requests.get("https://" + self.host + "/Services/REST/v1/logout",headers=self.current_auth_header) + if not logout.text.endswith("successfully logged out.\"") : + self.return_payload.update({'error':'Unable to log out of BlueCat API session'}) + + + def get_bc_auth_header(self,host,username,password) : + auth_token = requests.get("https://" + host + "/Services/REST/v1/login?username=" + username + "&password=" + password).text[17:71] + if auth_token.startswith('BAMAuthToken') : + return { "Authorization" : auth_token } + else : + return None + + def search_bc_object(self,host_tuple) : + """Searches BC for the subject.""" + response = requests.get("https://" + self.host + "/Services/REST/v1/searchByObjectTypes?keyword=" + host_tuple[2][0] + "&types=IP4Address,IP6Address&start=0&count=10",headers=self.current_auth_header) + return self.clean_response(response) + + def search_bc_parent_object(self,host_tuple) : + """Search for and return information about the objects parent, if any""" + if 'object' in self.return_payload and 'id' in self.return_payload['object'] : + response = requests.get("https://" + self.host + "/Services/REST/v1/getParent?entityId=" + str(self.return_payload['object']['id']), headers=self.current_auth_header) + return self.clean_response(response) + + def choose_host(self,host_tuple) : + """Given a host_tuple, return the host that we suspect is responsible for the VIP resides in.""" + hosts = {} + # Guess based on hostname + dcmatch = re.search('^[a-zA-Z]*-([a-zA-Z0-9]*)(-.*)*$',host_tuple[0]) + if dcmatch : + potentialdc = dcmatch.group(1) + else : + potentialdc = None + if potentialdc : + for host in self.hosts.keys() : + if potentialdc.lower() in host.lower() : + hosts.update({host:self.hosts[host]}) + if hosts != {} : + return hosts + # No hostname match, maybe we have info from bluecat? + if 'bluecat' in self.data and host_tuple in self.data['bluecat'] and self.data['bluecat'][host_tuple]['object'] : + potentialdc = self.data['bluecat'][host_tuple]['object']['locationCode'].split(' ')[-1] + for host in self.hosts.keys() : + if potentialdc.lower() in host.lower() : + hosts.update({host:self.hosts[host]}) + return hosts + return hosts + + def clean_response(self,response) : + parsed = {} + try : + parsed = json.loads(response.text) + except Exception : + return parsed + if type(parsed) is list and len(parsed) > 0 : + parsed = parsed[0] + if type(parsed) is dict : + if 'properties' in parsed : + parsed["properties"] = parsed["properties"][:-1] + bad_delim_props = [x for x in parsed["properties"].split("|") if x.count('=') > 1] + if len(bad_delim_props) > 0 : + parsed.update({"unknown":bad_delim_props}) + attributes = dict(x.split("=") for x in parsed["properties"].split("|") if x.count('=') == 1) + parsed.pop("properties", None) + parsed.update(attributes) + return parsed