# This script is used to run an AQL Query and then load the results into a reference set or reference table import sys, os import json, time from urllib2 import Request from urllib2 import urlopen from urllib2 import HTTPError from optparse import OptionParser from optparse import BadOptionError from optparse import AmbiguousOptionError # A simple HTTP client that can be used to access the REST API class RestApiClient: # Constructor for the RestApiClient Class def __init__(self,args): # Gets configuration information from config.ini. See ReadConfig # for more details. # Set up the default HTTP request headers self.headers = {b'Accept': 'application/json' } self.headers['Version'] = '3.0' self.headers['Content-Type'] = 'application/json' # Set up the security credentials. We can use either an encoded # username and password or a security token self.auth = {'SEC': args[0].token} self.headers.update(self.auth) # Set up the server's ip address and the base URI that will be used for # all requests self.server_ip = args[0].ip self.base_uri = '/api/' self.quiet = not args[0].verbose; # This method is used to set up an HTTP request and send it to the server def call_api(self, endpoint, method, headers=None, params=[], data=None, quiet=False): path = self.parse_path(endpoint, params) # If custom headers are not specified we can merge the default headers if not headers: headers = self.headers else: for key, value in self.headers.items(): if headers.get( key,'') == '': headers[ key ] = value # Send the request and receive the response if not self.quiet: print('\nSending ' + method + ' request to: ' + 'https://' +self.server_ip+self.base_uri+path+'\n') request = Request( 'https://'+self.server_ip+self.base_uri+path, headers=headers) request.get_method = lambda: method try: #returns response object for opening url. return urlopen(request, data) except HTTPError as e: #an object which contains information similar to a request object return e # This method constructs the query string def parse_path(self, endpoint, params): path = endpoint + '?' if isinstance(params, list): for kv in params: if kv[1]: path += kv[0]+'='+(kv[1].replace(' ','%20')).replace(',','%2C')+'&' else: for k, v in params.items(): if params[k]: path += k+'='+v.replace(' ','%20').replace(',','%2C')+'&' # removes last '&' or hanging '?' if no params. return path[:len(path)-1] class PassThroughOptionParser(OptionParser): def _process_args(self, largs, rargs, values): while rargs: try: OptionParser._process_args(self,largs,rargs,values) except (BadOptionError,AmbiguousOptionError) as e: largs.append(e.opt_str) def get_parser(): parser = PassThroughOptionParser(add_help_option=False) parser.add_option('-h', '--help', help='Show help message', action='store_true') parser.add_option('-i', '--ip', default="127.0.0.1", help='IP or Host of the QRadar console, or localhost if not present', action='store') parser.add_option('-t', '--token', help='QRadar authorized service token', action='store') parser.add_option('-f', '--file', help='File with assets to load.', action='store') parser.add_option('-d', '--fields', help='Display asset model fields',action='store_true') parser.add_option('-v', '--verbose', help='Verbose output',action='store_true') return parser def main(): parser = get_parser() args = parser.parse_args() if args[0].help or not (args[0].file or args[0].fields) or not args[0].ip or not args[0].token : print >> sys.stderr, "A simple utility to load a CSV file with asset information into the QRadar asset model based on IP address (which must exist in QRadar)" print >> sys.stderr, "The first column of the first line of the file must be 'ipaddress'" print >> sys.stderr, "The remaining columns of the file must contain field name headers that match the asset properties being loaded" print >> sys.stderr, "The asset with the most recent occurrence of the ip address is updated with the remaining fields on the line" print >> sys.stderr, ""; print >> sys.stderr, "example:" print >> sys.stderr, ""; print >> sys.stderr, "ipaddress,Technical Owner,Location,Description" print >> sys.stderr, "172.16.129.128,Chris Meenan,UK,Email Server" print >> sys.stderr, "172.16.129.129,Joe Blogs,Altanta,Customer Database Server" print >> sys.stderr, "172.16.129.130,Jason Corbin,Boston,Application Server" print >> sys.stderr, ""; print >> sys.stderr, parser.format_help().strip() exit(0) # Creates instance of APIClient. It contains all of the API methods. api_client = RestApiClient(args) # retrieve all the asset fields print("Retrieving asset fields"); response = api_client.call_api('asset_model/properties', 'GET',None, {},None) # Each response contains an HTTP response code. response_json = json.loads(response.read().decode('utf-8')) if response.code != 200: print("When retrieving assets : " + str(response.code)) print(json.dumps(response_json, indent=2, separators=(',', ':'))) exit(1) asset_field_lookup = {} if ( args[0].fields ): print("Asset fields:") for asset_field in response_json: asset_field_lookup[ asset_field['name' ] ] = asset_field['id'] if ( args[0].fields ): print(asset_field['name' ]) if( not args[0].file ): exit(1) # open file and get query file = open(args[0].file, 'r') if file == None: print("File not found " + args[0].file) exit(1) # This is the asset data to load, need to check all the names exist columnnames = file.readline().strip(); fields = columnnames.split(','); asset_file_fields = {} field_index = 0; is_error = 0; for fname in fields: if (fname <> 'ipaddress') and (asset_field_lookup.get(fname,'')==''): print("Field = " + fname + " does not exist") is_error = 1 elif( fname == 'ipaddress' ): asset_file_fields[ field_index ] = 0 else: asset_file_fields[ field_index ] = asset_field_lookup[ fname ] field_index = field_index + 1; # if there was an error print out the field if is_error == 1: print("Assets field: ") for k, v in asset_field_lookup.items(): print(k) exit(1) # retrieve all the assets print("Retrieving assets from QRadar"); response = api_client.call_api('asset_model/assets', 'GET',None, {},None) # Each response contains an HTTP response code. response_json = json.loads(response.read().decode('utf-8')) if response.code != 200: print("When retrieving assets : " + str(response.code)) print(json.dumps(response_json, indent=2, separators=(',', ':'))) exit(1) print( str(len(response_json)) + " assets retrieved"); # loop over assets and add to a lookup table ip_assetid_lookup = {} ip_lastseen_lookup = {} for asset in response_json: interfaces = asset['interfaces']; for interface in interfaces: for ipaddresses in interface['ip_addresses']: # get the largest last seen we have from this asset max_last_seen = ipaddresses['last_seen_scanner'] if ( ipaddresses['last_seen_profiler'] > max_last_seen ): max_last_seen = ipaddresses['last_seen_profiler'] # look to see if we have seen this IP address before last_seen = ip_lastseen_lookup.get( ipaddresses['value'] ,-1); if (last_seen == -1) or (last_seen < max_last_seen): ip_lastseen_lookup[ ipaddresses['value'] ] = max_last_seen ip_assetid_lookup[ ipaddresses['value'] ] = asset['id'] # now we have loaded the assets and mapped ip address to asset id # we can loop over the file data = file.readline().strip() update_success = 0; current_line = 2; while data <> '': # split values data_fields = data.split(',') json_string = "{ \"properties\": [ " index = 0; ip_address = ''; if( len(data_fields) != len(asset_file_fields)): print("Error : Missing or extra fields at line " + str(current_line) ) else: ip_address_found=0 for data_field in data_fields: data_field = data_field.strip() # this is the IP address if index ==0: ip_address = data_field if( ip_assetid_lookup.get(ip_address,'') == '' ): print("Error : IP address " + ip_address + " at line " + str(current_line) + " does not exist in QRadar Asset DB") else: ip_address_found = 1 else: json_string = json_string + "{ \"type_id\":" + str(asset_file_fields[index]) + ",\"value\":\"" + data_field + "\"}" index = index + 1; if (index < len(data_fields)) and (index <> 1): json_string = json_string + "," if ip_address_found == 1: json_string = json_string + "]}" #print(" JSON = " + json_string) # create JSON object response = api_client.call_api('asset_model/assets/'+str(ip_assetid_lookup[ip_address]), 'POST',{b'Accept': 'text/plain' },{},json_string) # Each response contains an HTTP response code. if response.code != 200: response_json = json.loads(response.read().decode('utf-8')) print("When updating asset : " + str(ip_assetid_lookup[ip_address]) + " " + ip_address) print(" JSON = " + json_string) print(json.dumps(response_json, indent=2, separators=(',', ':'))) exit(1) update_success = update_success + 1 data = file.readline().strip() current_line = current_line + 1 print( str(update_success) + " assets sucessfully updated") if __name__ == "__main__": main()