Some good progress on making it a module
This commit is contained in:
parent
cf477c7d5e
commit
5211142f5c
400
ifxlookup
400
ifxlookup
@ -1,4 +1,4 @@
|
|||||||
#!/usr/local/bin/python3
|
#!bin/python3
|
||||||
# ifxlookup, A python command-line client for infrastructure equipment.
|
# ifxlookup, A python command-line client for infrastructure equipment.
|
||||||
# November 2019 by Daniel Dayley
|
# November 2019 by Daniel Dayley
|
||||||
|
|
||||||
@ -7,7 +7,9 @@ import sys
|
|||||||
import requests
|
import requests
|
||||||
import argparse
|
import argparse
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
import jsonpath_ng
|
||||||
import socket
|
import socket
|
||||||
|
import inspect
|
||||||
import yaml
|
import yaml
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
@ -15,142 +17,320 @@ import importlib
|
|||||||
|
|
||||||
yaml.Dumper.ignore_aliases = lambda *args : True
|
yaml.Dumper.ignore_aliases = lambda *args : True
|
||||||
|
|
||||||
|
|
||||||
class IFXLookup():
|
class IFXLookup():
|
||||||
configdir = os.path.expanduser('~/.config')
|
|
||||||
configfilename = 'ifxlookup.yml'
|
DEFAULT_CONFIG = '~/.config/ifxlookup.yml'
|
||||||
servicetemplate = {'hosts':[],'username':'','key':''}
|
|
||||||
configtemplate = {'bluecat':servicetemplate,'f5':servicetemplate,'paloalto':servicetemplate,'aruba':servicetemplate,'openvpn':servicetemplate}
|
_errorinfo = {}
|
||||||
errorinfo = {}
|
_warninfo = {}
|
||||||
warninfo = {}
|
_plugin_map = {}
|
||||||
namespace = ''
|
_delegate_map = {}
|
||||||
servicedelegates = []
|
|
||||||
totalconfig = None
|
|
||||||
_internal_messages = {}
|
_internal_messages = {}
|
||||||
_subjects = []
|
_debug_level = 0
|
||||||
def __init__(self,delegates,arglist) :
|
_config = {}
|
||||||
self.servicedelegates = delegates
|
_caching = False
|
||||||
self.namespace = arglist
|
_initialized_plugins = []
|
||||||
# Load configuration
|
|
||||||
if arglist.config :
|
def __init__(self,config=None) :
|
||||||
|
if config :
|
||||||
|
self.load_config(config=config)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __add_plugin(self,plugin,reload) :
|
||||||
|
"""Adds a given ServiceBase plugin to the instance, reinitializing it if it already exists and such is specified."""
|
||||||
|
plugin_name = plugin.__module__.split('.')[-1]
|
||||||
|
if not reload and plugin_name in self._plugin_map.keys():
|
||||||
|
pass
|
||||||
|
else :
|
||||||
|
# We can't startup the plugin here because it hasn't been configured. We'll handle that at runtime.
|
||||||
try:
|
try:
|
||||||
self.totalconfig = yaml.safe_load(open(os.path.normpath(arglist.config)))
|
# Remove any intialized objects of the same name, forcing a reinitialization
|
||||||
except Exception as exception :
|
self._initialized_plugins.remove(self._delegate_map[plugin_name])
|
||||||
self.errorinfo.update({"config": "There was a problem reading your config file, aborting."})
|
except:
|
||||||
else :
|
pass
|
||||||
try:
|
self._plugin_map.update({plugin_name:plugin})
|
||||||
self.totalconfig = yaml.safe_load(open(self.configdir + '/' + self.configfilename))
|
self._delegate_map.update({plugin_name:plugin()})
|
||||||
except FileNotFoundError as exception :
|
|
||||||
if not os.path.exists(os.path.normpath(self.configdir)):
|
|
||||||
os.makedirs(os.path.normpath(self.configdir))
|
|
||||||
if not os.path.exists(os.path.normpath(self.configdir + '/' + self.configfilename)):
|
|
||||||
with open(self.configdir + '/' + self.configfilename, 'w') as output :
|
|
||||||
yaml.dump(self.configtemplate, output)
|
|
||||||
self.errorinfo.update({'config': 'Config file ' + self.configdir + '/' + self.configfilename + ' is empty, aborting.'})
|
|
||||||
else :
|
|
||||||
self.errorinfo.update({"config": "There was a problem reading your config file, aborting."})
|
|
||||||
except Exception as exception :
|
|
||||||
self.errorinfo.update({"config": "There was a problem reading your config file, aborting."})
|
|
||||||
if self.totalconfig == {} :
|
|
||||||
with open(self.configdir + '/' + self.configfilename, 'w') as output :
|
|
||||||
yaml.dump(self.configtemplate, output)
|
|
||||||
self.errorinfo.update({'config': 'Config file ' + self.configdir + '/' + self.configfilename + ' is empty, populating with a template.'})
|
|
||||||
|
|
||||||
def dispatch(self) :
|
def __init_once(self,plugin_obj) :
|
||||||
|
if plugin_obj not in self._initialized_plugins :
|
||||||
|
plugin_obj.startup()
|
||||||
|
self._initialized_plugins.append(plugin_obj)
|
||||||
|
|
||||||
# Resolve subject names
|
def __shutdown_plugins(self) :
|
||||||
for item in self.namespace.subjects :
|
"""Shutdown all plugins."""
|
||||||
# Pass in arguments raw
|
for plugin in self._delegate_map.values() :
|
||||||
self._subjects.append(item)
|
plugin.shutdown()
|
||||||
|
|
||||||
# Stop on initialization error
|
def __namespace(self) :
|
||||||
if self.errorinfo != {} :
|
"""Generate map of ifxlookup runtime values that should be made available to plugins."""
|
||||||
self._internal_messages['error'] = self.errorinfo
|
return {'debug' : self._debug_level,'caching': self._caching}
|
||||||
exit(1)
|
|
||||||
|
|
||||||
# Dispatch service delegates
|
def __filter_and_return(self,subjects,dataset,filter) :
|
||||||
delegatereports = {}
|
|
||||||
for delegate in self.servicedelegates :
|
|
||||||
delegatename = delegate.__module__.split('plugins.')[1]
|
|
||||||
if delegatename in self.totalconfig.keys() :
|
|
||||||
delegateconfig = self.totalconfig[delegatename]
|
|
||||||
else :
|
|
||||||
delegateconfig = {}
|
|
||||||
# Run Plugin
|
|
||||||
delegateinstance = delegate(delegateconfig,self.namespace,self._subjects,delegatereports)
|
|
||||||
delegateresponse = delegateinstance._lookup_subjects()
|
|
||||||
|
|
||||||
if delegateinstance.error != [] :
|
|
||||||
self.errorinfo.update({delegatename:delegateinstance.error})
|
|
||||||
if delegateresponse:
|
|
||||||
delegatereports.update({delegatename:delegateresponse})
|
|
||||||
|
|
||||||
# Format and display results
|
|
||||||
if self.warninfo != {} :
|
|
||||||
self._internal_messages['warn'] = self.warninfo
|
|
||||||
if self.errorinfo != {} :
|
|
||||||
self._internal_messages['error'] = self.errorinfo
|
|
||||||
delegatereports.update(self._internal_messages)
|
|
||||||
self.print_results(delegatereports)
|
|
||||||
|
|
||||||
|
|
||||||
def get_dc_from_hostname(self,hostname) :
|
|
||||||
"""Given a hostname, return the datacenter the host resides in."""
|
|
||||||
dcmatch = re.search('^[a-zA-Z]*-([a-zA-Z0-9]*)(-.*)*$',hostname)
|
|
||||||
if dcmatch :
|
|
||||||
return dcmatch.group(1)
|
|
||||||
else :
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def print_results(self,dataset) :
|
|
||||||
"""Returns the submitted dataset"""
|
"""Returns the submitted dataset"""
|
||||||
finalresult = {}
|
final_result = {}
|
||||||
if 'error' in dataset or 'warn' in dataset :
|
if 'error' in dataset or 'warn' in dataset :
|
||||||
finalresult['status'] = {}
|
final_result['status'] = {}
|
||||||
if 'error' in dataset :
|
if 'error' in dataset :
|
||||||
finalresult['status']['error'] = dataset['error']
|
final_result['status']['error'] = dataset['error']
|
||||||
if 'warn' in dataset :
|
if 'warn' in dataset :
|
||||||
finalresult['status']['warn'] = dataset['warn']
|
final_result['status']['warn'] = dataset['warn']
|
||||||
for subject in self._subjects :
|
for subject in subjects :
|
||||||
subjectdataset = {}
|
subjectdataset = {}
|
||||||
for plugin in [x for x in dataset if x != 'error' and x != 'warn'] :
|
for plugin in [x for x in dataset if x != 'error' and x != 'warn'] :
|
||||||
subjectdataset.update({plugin:dataset[plugin][subject]})
|
subjectdataset.update({plugin:dataset[plugin][subject]})
|
||||||
finalresult.update({subject:subjectdataset})
|
final_result.update({subject:subjectdataset})
|
||||||
|
|
||||||
|
# Apply Filter
|
||||||
|
if filter :
|
||||||
|
jsonpath_filter = jsonpath_ng.parse(filter)
|
||||||
|
final_result = [x.value for x in jsonpath_filter.find(final_result)]
|
||||||
|
return final_result
|
||||||
|
|
||||||
if self.namespace.json :
|
def use_plugins(self,plugins,reload=False) :
|
||||||
print(json.dumps(finalresult))
|
"""Defines plugins that should be used in a lookup, optionally forcing them to reset."""
|
||||||
else :
|
# Verify data
|
||||||
yaml.dump(finalresult,sys.stdout)
|
if type(plugins) is not list :
|
||||||
|
raise ValueError('argument \'plugins\' should be of type list')
|
||||||
if __name__ == "__main__":
|
for plugin in plugins :
|
||||||
|
# Check if the plugin is a string or a descendent of the ServiceBase class
|
||||||
# Load Plugins
|
if type(plugin) is not str and 'ServiceBase' not in [x.__name__ for x in inspect.getmro(plugin)] :
|
||||||
|
raise ValueError('unkown type for plugin')
|
||||||
|
# Find plugins by name using a default path
|
||||||
|
if type(plugin) is str :
|
||||||
pluginfolder = 'plugins'
|
pluginfolder = 'plugins'
|
||||||
testpath = os.path.realpath(__file__)
|
testpath = os.path.realpath(__file__)
|
||||||
filedir = '/'.join(testpath.split('/')[:-1])
|
filedir = '/'.join(testpath.split('/')[:-1])
|
||||||
pluginpath = filedir + '/' + pluginfolder
|
pluginpath = filedir + '/' + pluginfolder
|
||||||
plugindir = pluginfolder
|
pluginfiles = [x for x in os.listdir(pluginpath) if plugin in x and x.endswith('.py')]
|
||||||
pluginfiles = [x for x in os.listdir(pluginpath) if x != "servicebase.py" and x.endswith('.py')]
|
|
||||||
pluginfiles = sorted(pluginfiles)
|
pluginfiles = sorted(pluginfiles)
|
||||||
|
if len(pluginfiles) == 0 :
|
||||||
|
raise FileNotFoundError(plugin + '.py not found')
|
||||||
importlib.import_module(pluginfolder)
|
importlib.import_module(pluginfolder)
|
||||||
delegates = []
|
|
||||||
for pluginfile in pluginfiles :
|
for pluginfile in pluginfiles :
|
||||||
delegates.append(getattr(importlib.import_module(plugindir+"."+pluginfile.split('.')[0]),"ServiceDelegate"))
|
plugin = getattr(importlib.import_module(pluginfolder+"."+pluginfile.split('.')[0]),'ServiceDelegate')
|
||||||
|
self.__add_plugin(plugin,reload)
|
||||||
|
continue
|
||||||
|
# Load normal ServiceBase classes
|
||||||
|
if 'ServiceBase' in [x.__name__ for x in inspect.getmro(plugin)] :
|
||||||
|
self.__add_plugin(plugin,reload)
|
||||||
|
continue
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_plugins(self) :
|
||||||
|
"""Returns a map of plugins configured to use in the lookup."""
|
||||||
|
return self._delegate_map
|
||||||
|
|
||||||
|
def search_plugins(self,directory='/'.join(os.path.realpath(__file__).split('/')[:-1]) + '/' + 'plugins') :
|
||||||
|
"""Searches a given directory for compatible plugins and returns a map of available plugin names and classes"""
|
||||||
|
directory = os.path.normpath(os.path.expanduser(os.path.expandvars(directory)))
|
||||||
|
name_map = {}
|
||||||
|
candidates = {x.split('.')[0]:x for x in os.listdir(directory) if x.endswith('.py') and x != 'servicebase.py'}
|
||||||
|
for name,filename in candidates.items() :
|
||||||
|
spec = importlib.util.spec_from_file_location(name, directory + '/' + filename)
|
||||||
|
mod = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(mod)
|
||||||
|
instance = getattr(mod,'ServiceDelegate')
|
||||||
|
name_map.update({filename.split('.')[0]:instance})
|
||||||
|
return name_map
|
||||||
|
|
||||||
|
def configure(self,config) :
|
||||||
|
"""Updates the configuration of the lookup with the given config."""
|
||||||
|
if type(config) is not dict :
|
||||||
|
raise TypeError('argument \'config\' should be of type dict')
|
||||||
|
self._config.update(config)
|
||||||
|
|
||||||
|
def load_config_from_file(self,config=DEFAULT_CONFIG) :
|
||||||
|
"""Loads a JSON or YAML file containing a configuration map for plugins. Used to configure several plugins at once."""
|
||||||
|
if config :
|
||||||
|
file_descriptor = open(os.path.normpath(os.path.expanduser(os.path.expandvars(config))))
|
||||||
|
try :
|
||||||
|
file_descriptor = open(os.path.normpath(os.path.expanduser(os.path.expandvars(config))))
|
||||||
|
totalconfig = json.load(file_descriptor)
|
||||||
|
except json.decoder.JSONDecodeError as exception :
|
||||||
|
file_descriptor = open(os.path.normpath(os.path.expanduser(os.path.expandvars(config))))
|
||||||
|
totalconfig = yaml.safe_load(file_descriptor)
|
||||||
|
self._config.update(totalconfig)
|
||||||
|
|
||||||
|
def dump_config(self) :
|
||||||
|
"""Returns the current configuration state. This likely contains sensitive information such as API keys."""
|
||||||
|
return self._config
|
||||||
|
|
||||||
|
def dump_config_to_file(self,path,print_json=False) :
|
||||||
|
"""Exports the current plugin configuration to a file."""
|
||||||
|
with open(os.path.normpath(os.path.expanduser(os.path.expandvars(path))),'a') as file_handle :
|
||||||
|
if print_json :
|
||||||
|
json.dump(self._config, file_handle)
|
||||||
|
else :
|
||||||
|
yaml.dump(self._config, file_handle, default_flow_style=False)
|
||||||
|
|
||||||
|
def set_debug_level(self,level) :
|
||||||
|
"""Sets the debug level"""
|
||||||
|
if not level :
|
||||||
|
level = 0
|
||||||
|
self._debug_level = int(level)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def debug_level(self) :
|
||||||
|
"""Gets the debug level"""
|
||||||
|
return this._debug_level
|
||||||
|
|
||||||
|
def enable_caching(self) :
|
||||||
|
"""Enables results caching and disables auto-shutdown of plugins."""
|
||||||
|
self._caching = True
|
||||||
|
return self
|
||||||
|
|
||||||
|
def disable_caching(self) :
|
||||||
|
"""Disables results caching and enables auto-shutdown of plugins."""
|
||||||
|
self._caching = False
|
||||||
|
return self
|
||||||
|
|
||||||
|
def lookup(self,subjects,filter=None) :
|
||||||
|
"""Performs a search with the configured plugins and filter and returns a dictionary of search data."""
|
||||||
|
hints = {}
|
||||||
|
final_report = {}
|
||||||
|
error_info = {}
|
||||||
|
warn_info = {}
|
||||||
|
# Assert that each plugin has a config
|
||||||
|
for delegatename,delegate in self._delegate_map.items() :
|
||||||
|
if not delegatename in self._config.keys() :
|
||||||
|
raise KeyError('no configuration provided for plugin ' + delegatename)
|
||||||
|
|
||||||
|
# Assert that 'subjects' is a list
|
||||||
|
if type(subjects) is not list :
|
||||||
|
raise TypeError('expected list but got ' + str(type(subjects)))
|
||||||
|
|
||||||
|
# Assert that subjects are strings
|
||||||
|
for subject in subjects :
|
||||||
|
if type(subject) is not str :
|
||||||
|
raise TypeError('subjects must be of type \'str\'')
|
||||||
|
|
||||||
|
for delegatename,delegate in self._delegate_map.items() :
|
||||||
|
# Add runtime info from ifxlookup
|
||||||
|
delegateconfig = self._config[delegatename]
|
||||||
|
delegateconfig.update({'_namespace':self.__namespace()})
|
||||||
|
# Configure and start the plugin
|
||||||
|
delegate._set_config(delegateconfig)
|
||||||
|
delegate._set_hints(hints)
|
||||||
|
# Only runs once
|
||||||
|
self.__init_once(delegate)
|
||||||
|
|
||||||
|
report = {}
|
||||||
|
for subject in subjects :
|
||||||
|
report.update({subject:delegate.lookup(subject)})
|
||||||
|
if delegate.error != [] :
|
||||||
|
error_info.update({delegatename:delegate.error})
|
||||||
|
if delegate.warn != [] :
|
||||||
|
warn_info.update({delegatename:delegate.warn})
|
||||||
|
report.update(error_info)
|
||||||
|
report.update(warn_info)
|
||||||
|
if report and report != {}:
|
||||||
|
final_report.update({delegatename:report})
|
||||||
|
|
||||||
|
# If caching is enabled prevent shutdown. Otherwise shutdown.
|
||||||
|
if not self._caching :
|
||||||
|
self.__shutdown_plugins()
|
||||||
|
return self.__filter_and_return(subjects,final_report,filter)
|
||||||
|
|
||||||
|
def finish(self) :
|
||||||
|
"""In the case that caching has been enabled, this enables the plugins to be manually shut down."""
|
||||||
|
self.__shutdown_plugins()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Command-line client
|
||||||
|
|
||||||
|
search = IFXLookup()
|
||||||
|
available_plugins = search.search_plugins()
|
||||||
|
|
||||||
|
# Define constants
|
||||||
|
configdir = os.path.expanduser('~/.config')
|
||||||
|
configfilename = 'ifxlookup.yml'
|
||||||
|
servicetemplate = {'hosts':[],'username':'','key':''}
|
||||||
|
configtemplate = {'bluecat':servicetemplate,'f5':servicetemplate,'paloalto':servicetemplate,'aruba':servicetemplate,'openvpn':servicetemplate}
|
||||||
|
configerror = {'config': 'There was a problem reading your config file, aborting.'}
|
||||||
|
errorinfo = {}
|
||||||
|
|
||||||
# Gather Argument options
|
# Gather Argument options
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('-c', '--config', action='store', help="Specify a config file (~/.config/ifxlookup.yml by default)")
|
parser.add_argument('-c', '--config', action='store', help='Specify a config file (~/.config/ifxlookup.yml by default)')
|
||||||
parser.add_argument('-j', '--json', action='store_true', help="Return results as a json object")
|
parser.add_argument('-f', '--filter', action='store', default=None, help='Apply a JSONPath filter to the results')
|
||||||
parser.add_argument('-l', '--link', action='store_true', help="Return physical link information about the subject")
|
parser.add_argument('-j', '--json', action='store_true', help='Return results as a json object')
|
||||||
for delegate in delegates :
|
# parser.add_argument('-l', '--link', action='store_true', help="Return physical link information about the subject")
|
||||||
arguments = delegate.get_arguments(delegate)
|
for delegate in available_plugins.values() :
|
||||||
|
arguments = delegate().get_arguments()
|
||||||
if arguments and len(arguments) == 4 :
|
if arguments and len(arguments) == 4 :
|
||||||
parser.add_argument(arguments[0],arguments[1],action=arguments[2],help=arguments[3])
|
parser.add_argument(arguments[0],arguments[1],action=arguments[2],help=arguments[3])
|
||||||
parser.add_argument('-a', '--all', action='store_true', help="Return all searchable information about the subject")
|
parser.add_argument('-v', '--debug', action='count', help='Include debug information in the output. Add \'v\'s for more output.')
|
||||||
parser.add_argument('-v', '--debug', action='count', help="Include debug information in the output. Add 'v's for more output.")
|
parser.add_argument('-a', '--all', action='store_true', help='Return all searchable information about the subject')
|
||||||
parser.add_argument('subjects', metavar='subjects', nargs='+', help='IP\'s, hostnames, MAC\'s, or usernames to look up.')
|
parser.add_argument('subjects', metavar='subjects', nargs='+', help='IP\'s, hostnames, MAC\'s, usernames, or other strings to look up.')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Dispatch master object
|
# Load plugins based on submitted args
|
||||||
main = IFXLookup(delegates,args)
|
selected_plugins = []
|
||||||
main.dispatch()
|
|
||||||
|
if vars(args)['all'] :
|
||||||
|
for delegate in available_plugins.values() :
|
||||||
|
selected_plugins.append(delegate)
|
||||||
|
else :
|
||||||
|
for name,delegate in available_plugins.items() :
|
||||||
|
argument = delegate().get_arguments()[1].split('--')[-1]
|
||||||
|
if argument in vars(args) and vars(args)[argument] :
|
||||||
|
selected_plugins.append(delegate)
|
||||||
|
search.use_plugins(selected_plugins)
|
||||||
|
|
||||||
|
# Configure subjects
|
||||||
|
subjects = []
|
||||||
|
for item in args.subjects :
|
||||||
|
subjects.append(item)
|
||||||
|
|
||||||
|
# Load configuration
|
||||||
|
# The command line configuration accepts YAML docs with required keys and values. The command line tool can generate them if
|
||||||
|
# they do not exist.
|
||||||
|
if args.config :
|
||||||
|
# TODO: this section throws a yaml error if a json doc can't be found, errors should be more specific.
|
||||||
|
try:
|
||||||
|
# totalconfig = yaml.safe_load(open(os.path.normpath(args.config)))
|
||||||
|
config = config=os.path.normpath(args.config)
|
||||||
|
except Exception as exception :
|
||||||
|
errorinfo.update(configerror)
|
||||||
|
else :
|
||||||
|
try:
|
||||||
|
config = config=os.path.normpath(configdir + '/' + configfilename)
|
||||||
|
except FileNotFoundError as exception :
|
||||||
|
if not os.path.exists(os.path.normpath(configdir)):
|
||||||
|
os.makedirs(os.path.normpath(configdir))
|
||||||
|
if not os.path.exists(os.path.normpath(configdir + '/' + configfilename)):
|
||||||
|
with open(configdir + '/' + configfilename, 'w') as output :
|
||||||
|
yaml.dump(configtemplate, output)
|
||||||
|
errorinfo.update({'config': 'Config file ' + configdir + '/' + configfilename + ' is empty, aborting.'})
|
||||||
|
else :
|
||||||
|
errorinfo.update(configerror)
|
||||||
|
except Exception as exception :
|
||||||
|
errorinfo.update(configerror)
|
||||||
|
if not config :
|
||||||
|
with open(configdir + '/' + configfilename, 'w') as output :
|
||||||
|
yaml.dump(configtemplate, output)
|
||||||
|
errorinfo.update({'config': 'Config file ' + configdir + '/' + configfilename + ' is empty, populating with a template.'})
|
||||||
|
# Stop on initialization error
|
||||||
|
if not config :
|
||||||
|
errorinfo.update(configerror)
|
||||||
|
try :
|
||||||
|
file_descriptor = open(os.path.normpath(os.path.expanduser(os.path.expandvars(config))))
|
||||||
|
totalconfig = json.load(file_descriptor)
|
||||||
|
except json.decoder.JSONDecodeError as exception :
|
||||||
|
file_descriptor = open(os.path.normpath(os.path.expanduser(os.path.expandvars(config))))
|
||||||
|
totalconfig = yaml.safe_load(file_descriptor)
|
||||||
|
for plugin_name in search.get_plugins().keys() :
|
||||||
|
if plugin_name not in totalconfig.keys() :
|
||||||
|
errorinfo.update({'config': 'Config file is missing information for plugin ' + plugin_name})
|
||||||
|
if errorinfo != {} :
|
||||||
|
print(subjects,{'error':errorinfo})
|
||||||
|
exit(1)
|
||||||
|
search.configure(totalconfig)
|
||||||
|
|
||||||
|
search.set_debug_level(args.debug)
|
||||||
|
report = search.lookup(args.subjects,filter=args.filter)
|
||||||
|
if args.json :
|
||||||
|
print(json.dumps(report))
|
||||||
|
else :
|
||||||
|
yaml.dump(report,sys.stdout)
|
||||||
|
@ -41,15 +41,15 @@ class ServiceDelegate(ServiceBase) :
|
|||||||
self.aruba_hosts = self.perform_list_query(clientsquery,self.clients_fields,clientssortfield,clientsfilter,clientsdevicetype)
|
self.aruba_hosts = self.perform_list_query(clientsquery,self.clients_fields,clientssortfield,clientsfilter,clientsdevicetype)
|
||||||
self.debug("Searching Aruba information...",1)
|
self.debug("Searching Aruba information...",1)
|
||||||
|
|
||||||
def perform_lookup(self,subject) :
|
def lookup(self,subject) :
|
||||||
# We return the first result that matches the subject out of our lists of Aruba information.
|
# We return the first result that matches the subject out of our lists of Aruba information.
|
||||||
# Because this isn't very effective we need to order our search by order of least-likely to
|
# Because this isn't very effective we need to order our search by order of least-likely to
|
||||||
# most-likely for the match.
|
# most-likely for the match.
|
||||||
|
|
||||||
# Search APs first, since they are less likely to match
|
# Search APs first, since they are less likely to match
|
||||||
# By IP
|
# By IP
|
||||||
if 'dns' in self.data and subject in self.data['dns'] and self.data['dns'][subject]['addresses'] :
|
if 'dns' in self.hints and subject in self.hints['dns'] and self.hints['dns'][subject]['addresses'] :
|
||||||
for ip in self.data['dns'][subject]['addresses'] :
|
for ip in self.hints['dns'][subject]['addresses'] :
|
||||||
for entry in self.aruba_basestations :
|
for entry in self.aruba_basestations :
|
||||||
if ip in entry.values() :
|
if ip in entry.values() :
|
||||||
return entry
|
return entry
|
||||||
@ -62,8 +62,8 @@ class ServiceDelegate(ServiceBase) :
|
|||||||
return entry
|
return entry
|
||||||
# Search connected Aruba hosts
|
# Search connected Aruba hosts
|
||||||
# By IP
|
# By IP
|
||||||
if 'dns' in self.data and subject in self.data['dns'] and self.data['dns'][subject]['addresses'] :
|
if 'dns' in self.hints and subject in self.hints['dns'] and self.hints['dns'][subject]['addresses'] :
|
||||||
for ip in self.data['dns'][subject]['addresses'] :
|
for ip in self.hints['dns'][subject]['addresses'] :
|
||||||
for entry in self.aruba_hosts :
|
for entry in self.aruba_hosts :
|
||||||
if ip in entry.values() :
|
if ip in entry.values() :
|
||||||
return entry
|
return entry
|
||||||
@ -124,9 +124,9 @@ class ServiceDelegate(ServiceBase) :
|
|||||||
|
|
||||||
# Repeat with page size on the query, aggregating results
|
# Repeat with page size on the query, aggregating results
|
||||||
nextstart = 0
|
nextstart = 0
|
||||||
finish = False
|
finished = False
|
||||||
allitems = []
|
allitems = []
|
||||||
while not finish :
|
while not finished :
|
||||||
start_row.text = str(nextstart)
|
start_row.text = str(nextstart)
|
||||||
num_rows.text = str(self.query_page_size)
|
num_rows.text = str(self.query_page_size)
|
||||||
datapayload = "".join(['query=',tostring(aruba_queries).decode(),'&UIDARUBA=',self.session_key])
|
datapayload = "".join(['query=',tostring(aruba_queries).decode(),'&UIDARUBA=',self.session_key])
|
||||||
@ -137,7 +137,7 @@ class ServiceDelegate(ServiceBase) :
|
|||||||
rows = pagecontents.iter('row')
|
rows = pagecontents.iter('row')
|
||||||
for row in rows:
|
for row in rows:
|
||||||
if row == {} :
|
if row == {} :
|
||||||
finish = Tru
|
finished = True
|
||||||
item = {}
|
item = {}
|
||||||
headers = pagecontents.iter('column_name')
|
headers = pagecontents.iter('column_name')
|
||||||
for value, header in zip(row,headers) :
|
for value, header in zip(row,headers) :
|
||||||
@ -148,12 +148,11 @@ class ServiceDelegate(ServiceBase) :
|
|||||||
rows = pagecontents.iter('row')
|
rows = pagecontents.iter('row')
|
||||||
next(rows)
|
next(rows)
|
||||||
except Exception :
|
except Exception :
|
||||||
finish = True
|
finished = True
|
||||||
if page.status_code != 100 and page.status_code !=200 :
|
if page.status_code != 100 and page.status_code !=200 :
|
||||||
finish = True
|
finished = True
|
||||||
nextstart = nextstart + self.query_page_size
|
nextstart = nextstart + self.query_page_size
|
||||||
else :
|
else :
|
||||||
finish = True
|
finished = True
|
||||||
self.debug("Retrieved " + str((len(allitems))) + " results from query",2)
|
self.debug("Retrieved " + str((len(allitems))) + " results from query",2)
|
||||||
return allitems
|
return allitems
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ class ServiceDelegate(ServiceBase) :
|
|||||||
self.current_auth_header = self.get_bc_auth_header(self.config['host'],self.config['username'],self.config['key'])
|
self.current_auth_header = self.get_bc_auth_header(self.config['host'],self.config['username'],self.config['key'])
|
||||||
self.debug("Searching BlueCat for hosts...",1)
|
self.debug("Searching BlueCat for hosts...",1)
|
||||||
|
|
||||||
def perform_lookup(self,subject) :
|
def lookup(self,subject) :
|
||||||
self.return_payload = {}
|
self.return_payload = {}
|
||||||
if self.current_auth_header :
|
if self.current_auth_header :
|
||||||
object = self.search_bc_object(subject)
|
object = self.search_bc_object(subject)
|
||||||
|
@ -15,7 +15,7 @@ class ServiceDelegate(ServiceBase) :
|
|||||||
if 'resolver' in self.config :
|
if 'resolver' in self.config :
|
||||||
self.resolver.nameservers.append(self.config['resolver'])
|
self.resolver.nameservers.append(self.config['resolver'])
|
||||||
|
|
||||||
def perform_lookup(self,subject) :
|
def lookup(self,subject) :
|
||||||
|
|
||||||
# Note : hostname lookups always use system resolver.
|
# Note : hostname lookups always use system resolver.
|
||||||
# TODO: Make host lookups use the config-sepcified resolver.
|
# TODO: Make host lookups use the config-sepcified resolver.
|
||||||
@ -68,4 +68,3 @@ class ServiceDelegate(ServiceBase) :
|
|||||||
return recs
|
return recs
|
||||||
except Exception as exception :
|
except Exception as exception :
|
||||||
return recs
|
return recs
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ class ServiceDelegate(ServiceBase) :
|
|||||||
self.hosts = self.config['hosts']
|
self.hosts = self.config['hosts']
|
||||||
self.debug("Logging into F5's and searching for hosts, this make take some time.",1)
|
self.debug("Logging into F5's and searching for hosts, this make take some time.",1)
|
||||||
|
|
||||||
def perform_lookup(self,subject) :
|
def lookup(self,subject) :
|
||||||
self.return_payload = {}
|
self.return_payload = {}
|
||||||
|
|
||||||
logincandidates = self.choose_host(subject)
|
logincandidates = self.choose_host(subject)
|
||||||
@ -134,15 +134,14 @@ class ServiceDelegate(ServiceBase) :
|
|||||||
return hosts
|
return hosts
|
||||||
self.debug("Unable to determine DC from subject name: " + subject,3)
|
self.debug("Unable to determine DC from subject name: " + subject,3)
|
||||||
# No hostname match, maybe we have info from bluecat?
|
# No hostname match, maybe we have info from bluecat?
|
||||||
if 'bluecat' in self.data and subject in self.data['bluecat'] and self.data['bluecat'][subject]['object'] :
|
if 'bluecat' in self.hints and subject in self.hints['bluecat'] and self.hints['bluecat'][subject]['object'] :
|
||||||
if 'locationCode' in self.data['bluecat'][subject]['object'] :
|
if 'locationCode' in self.hints['bluecat'][subject]['object'] :
|
||||||
potentialdc = self.data['bluecat'][subject]['object']['locationCode'].split(' ')[-1]
|
potentialdc = self.hints['bluecat'][subject]['object']['locationCode'].split(' ')[-1]
|
||||||
elif 'parent' in self.data['bluecat'][subject] and 'name' in self.data['bluecat'][subject]['parent'] :
|
elif 'parent' in self.hints['bluecat'][subject] and 'name' in self.hints['bluecat'][subject]['parent'] :
|
||||||
potentialdc = self.data['bluecat'][subject]['parent']['name'].split(' ')[0]
|
potentialdc = self.hints['bluecat'][subject]['parent']['name'].split(' ')[0]
|
||||||
for host in self.hosts.keys() :
|
for host in self.hosts.keys() :
|
||||||
if potentialdc.lower() in host.lower() :
|
if potentialdc.lower() in host.lower() :
|
||||||
hosts.update({host:self.hosts[host]})
|
hosts.update({host:self.hosts[host]})
|
||||||
self.debug("Got additional host info from the BlueCat plugin!",3)
|
self.debug("Got additional host info from the BlueCat plugin!",3)
|
||||||
return hosts
|
return hosts
|
||||||
return hosts
|
return hosts
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ class ServiceDelegate(ServiceBase) :
|
|||||||
self.debug('This will take a long time. Increase the verbosity level to watch the progress.',1)
|
self.debug('This will take a long time. Increase the verbosity level to watch the progress.',1)
|
||||||
self.progress = 0
|
self.progress = 0
|
||||||
self.goal = len(self.channels_small)
|
self.goal = len(self.channels_small)
|
||||||
def perform_lookup(self,subject) :
|
def lookup(self,subject) :
|
||||||
channels = {}
|
channels = {}
|
||||||
guid = list(re.match('([0-9a-fA-F]*)', subject.replace('-','')).groups())
|
guid = list(re.match('([0-9a-fA-F]*)', subject.replace('-','')).groups())
|
||||||
if len(guid) > 0 and len(guid[0]) == 32 :
|
if len(guid) > 0 and len(guid[0]) == 32 :
|
||||||
@ -31,6 +31,8 @@ class ServiceDelegate(ServiceBase) :
|
|||||||
else :
|
else :
|
||||||
guid = None
|
guid = None
|
||||||
self.debug('Subject does not appear to be a valid GUID',1)
|
self.debug('Subject does not appear to be a valid GUID',1)
|
||||||
|
self.error.append('Subject does not appear to be a valid GUID')
|
||||||
|
return None
|
||||||
for channel in self.channels_small :
|
for channel in self.channels_small :
|
||||||
self.progress += 1
|
self.progress += 1
|
||||||
resp = self.make_drm_request(channel,guid)
|
resp = self.make_drm_request(channel,guid)
|
||||||
@ -51,5 +53,5 @@ class ServiceDelegate(ServiceBase) :
|
|||||||
verbosity = 2
|
verbosity = 2
|
||||||
else :
|
else :
|
||||||
verbosity = 3
|
verbosity = 3
|
||||||
self.debug('Got ' + str(drm_req.status_code) + ' for channel ' + channel_guid + ' for user ' + user_guid + ' (' + str(self.progress) + '/' + str(self.goal) + ')',verbosity)
|
self.debug('Got ' + str(drm_req.status_code) + ' for channel ' + str(channel_guid) + ' for user ' + str(user_guid) + ' (' + str(self.progress) + '/' + str(self.goal) + ')',verbosity)
|
||||||
return drm_req.status_code
|
return drm_req.status_code
|
||||||
|
@ -33,7 +33,7 @@ class ServiceDelegate(ServiceBase) :
|
|||||||
for connection in self.connections :
|
for connection in self.connections :
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
||||||
def perform_lookup(self,subject) :
|
def lookup(self,subject) :
|
||||||
search_command = "sudo -S cat /etc/openvpn/openvpn-*p.log | grep 'primary virtual IP for' | grep '" + subject + "' | tail -n 1"
|
search_command = "sudo -S cat /etc/openvpn/openvpn-*p.log | grep 'primary virtual IP for' | grep '" + subject + "' | tail -n 1"
|
||||||
for connection in self.connections :
|
for connection in self.connections :
|
||||||
try:
|
try:
|
||||||
@ -54,7 +54,8 @@ class ServiceDelegate(ServiceBase) :
|
|||||||
return_dictionary.update({item: matches[1]})
|
return_dictionary.update({item: matches[1]})
|
||||||
if return_dictionary is not {} :
|
if return_dictionary is not {} :
|
||||||
return return_dictionary
|
return return_dictionary
|
||||||
|
else :
|
||||||
|
return None
|
||||||
except Exception as exception :
|
except Exception as exception :
|
||||||
self.error.append("Unable to get results from ssh: " + str(exception))
|
self.error.append("Unable to get results from ssh: " + str(exception))
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ class ServiceDelegate(ServiceBase) :
|
|||||||
self.debug("Searching Shodan for hosts...",1)
|
self.debug("Searching Shodan for hosts...",1)
|
||||||
|
|
||||||
|
|
||||||
def perform_lookup(self,subject) :
|
def lookup(self,subject) :
|
||||||
if self._api_sessions :
|
if self._api_sessions :
|
||||||
session = next(self.get_api())
|
session = next(self.get_api())
|
||||||
try :
|
try :
|
||||||
|
68
plugins/vmware.py
Normal file
68
plugins/vmware.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
from servicebase import ServiceBase
|
||||||
|
import socket
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceDelegate(ServiceBase) :
|
||||||
|
|
||||||
|
def get_arguments(cls) :
|
||||||
|
"""Returns an array of information used to construct an argumentparser argument."""
|
||||||
|
return [ '-vm', '--vm', 'store_true', "Return DNS resolution about the subject (dns)" ]
|
||||||
|
|
||||||
|
def startup(self) :
|
||||||
|
self.resolver = dns.resolver.Resolver()
|
||||||
|
if 'resolver' in self.config :
|
||||||
|
self.resolver.nameservers.append(self.config['resolver'])
|
||||||
|
|
||||||
|
def lookup(self,subject) :
|
||||||
|
|
||||||
|
# Note : hostname lookups always use system resolver.
|
||||||
|
# TODO: Make host lookups use the config-sepcified resolver.
|
||||||
|
|
||||||
|
try :
|
||||||
|
subjecttuple = socket.gethostbyname_ex(subject)
|
||||||
|
# Can't make an IP of the subject
|
||||||
|
except ValueError :
|
||||||
|
try :
|
||||||
|
# Lookup with system
|
||||||
|
subjecttuple = socket.gethostbyname_ex(subject)
|
||||||
|
except socket.gaierror :
|
||||||
|
# Not a valid host or IP
|
||||||
|
self.error.append("Unable to resolve " + subject)
|
||||||
|
subjecttuple = [subject,[],['']]
|
||||||
|
# Can't make IPv4 out of it
|
||||||
|
except socket.gaierror :
|
||||||
|
self.error.append("Unable to resolve " + subject)
|
||||||
|
subjecttuple = [subject,[],['']]
|
||||||
|
# Get additional records
|
||||||
|
return_dict = {}
|
||||||
|
mx = self.get_records(subjecttuple[0],'MX')
|
||||||
|
ns = self.get_records(subjecttuple[0],'NS')
|
||||||
|
txt = self.get_records(subjecttuple[0],'TXT')
|
||||||
|
mx.sort()
|
||||||
|
ns.sort()
|
||||||
|
txt.sort()
|
||||||
|
if mx and len(mx) > 0 :
|
||||||
|
return_dict.update({'mx':mx})
|
||||||
|
if ns and len(ns) > 0 :
|
||||||
|
return_dict.update({'ns':ns})
|
||||||
|
if txt and len(txt) > 0 :
|
||||||
|
return_dict.update({'txt':txt})
|
||||||
|
# Write final dictionary
|
||||||
|
if len(subjecttuple[2]) > 0 and subjecttuple[2][0] != '':
|
||||||
|
return_dict['addresses'] = list(subjecttuple[2])
|
||||||
|
if subjecttuple[0] and subjecttuple[0] != '' :
|
||||||
|
return_dict['name'] = subjecttuple[0]
|
||||||
|
if len(subjecttuple[1]) > 0 :
|
||||||
|
return_dict['aliases'] = list(subjecttuple[1])
|
||||||
|
return return_dict
|
||||||
|
|
||||||
|
|
||||||
|
def get_records(self,subject,recordtype) :
|
||||||
|
recs = []
|
||||||
|
try :
|
||||||
|
results = self.resolver.query(subject,recordtype)
|
||||||
|
for result in results :
|
||||||
|
recs.append(result.to_text())
|
||||||
|
return recs
|
||||||
|
except Exception as exception :
|
||||||
|
return recs
|
@ -7,36 +7,28 @@ class ServiceBase :
|
|||||||
error = []
|
error = []
|
||||||
warn = []
|
warn = []
|
||||||
_status = "Ready"
|
_status = "Ready"
|
||||||
_subjects = []
|
def __init__(self,hints={},config={}):
|
||||||
def __init__(self,config,namespace,subjects,dossiercopy):
|
|
||||||
if not config or config == {} :
|
|
||||||
self._status = "Error"
|
|
||||||
else :
|
|
||||||
self.config = config
|
self.config = config
|
||||||
# The following lines indicate a problem in my understanding of Python's class structure.
|
|
||||||
# Why do new instances of subclasses inherit the written values of their superclass instance?
|
|
||||||
self.error = []
|
self.error = []
|
||||||
self.warn = []
|
self.warn = []
|
||||||
self.data = dossiercopy
|
self.hints = hints
|
||||||
self.namespace = namespace
|
|
||||||
self._subjects = subjects
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _lookup_subjects(self) :
|
def _set_config(self,config) :
|
||||||
args = self.get_arguments()
|
"""Allows for post-initialization configuration"""
|
||||||
if args :
|
self.config = config
|
||||||
args = args[1][2:]
|
|
||||||
if getattr(self.namespace,args) == False and getattr(self.namespace,'all') == False :
|
def _set_hints(self,hints) :
|
||||||
return None
|
"""Allows for post-initialization configuration of hint data"""
|
||||||
finaldictionary = {}
|
self.hints = hints
|
||||||
self.startup()
|
|
||||||
for item in self._subjects :
|
def _cache_invalid(self) :
|
||||||
finaldictionary.update({item:self.perform_lookup(item)})
|
"""Returns a boolean representing whether or not a cached value from the last lookup should be invalidated."""
|
||||||
self.shutdown()
|
return False
|
||||||
return finaldictionary
|
|
||||||
|
|
||||||
def debug(self,message,verbosity_level) :
|
def debug(self,message,verbosity_level) :
|
||||||
if self.namespace.debug and self.namespace.debug >= verbosity_level :
|
"""Prints the submitted message if the environment's verbosity level matches or is surpassed by the submitted level."""
|
||||||
|
if self.config['_namespace']['debug'] and self.config['_namespace']['debug'] >= verbosity_level :
|
||||||
print(message,file=sys.stderr)
|
print(message,file=sys.stderr)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -50,14 +42,8 @@ class ServiceBase :
|
|||||||
"""Perform any setup that is needed to perform lookups, such as logging in or obtaining auth sessions."""
|
"""Perform any setup that is needed to perform lookups, such as logging in or obtaining auth sessions."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def perform_lookup(self,host_tuple) :
|
def lookup(self,subject) :
|
||||||
"""Returns a dictionary with lookup information about the given IP or Hostname."""
|
"""Returns a dictionary with lookup information about the given subject."""
|
||||||
# host_tuple is a socket triple (hostname, aliaslist, ipaddrlist) where hostname is
|
|
||||||
# the primary host name responding to the given ip_address, aliaslist is a (possibly
|
|
||||||
# empty) tuple of alternative host names for the same address, and ipaddrlist is a tuple
|
|
||||||
# of IPv4/v6 addresses for the same interface on the same host (most likely containing
|
|
||||||
# only a single address).
|
|
||||||
|
|
||||||
# Note: You should only return arrays and dictionaries with strings as keys so the information
|
# Note: You should only return arrays and dictionaries with strings as keys so the information
|
||||||
# serializes correctly.
|
# serializes correctly.
|
||||||
|
|
||||||
@ -66,3 +52,8 @@ class ServiceBase :
|
|||||||
def shutdown(self) :
|
def shutdown(self) :
|
||||||
"""Perform any cleanup that is needed, such as logging out of auth sessions."""
|
"""Perform any cleanup that is needed, such as logging out of auth sessions."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def reset(self) :
|
||||||
|
"""Reinitialize the plugin, performing any involved operations."""
|
||||||
|
self.shutdown()
|
||||||
|
self.startup()
|
||||||
|
Loading…
Reference in New Issue
Block a user