diff --git a/README.md b/README.md index 6cd5a01..d875896 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,10 @@ The most useful thing will be the log file from the Add-on. This will be called `tail -f /opt/splunk/var/log/splunk/ta_intersight_addon_intersight.log` +It should also be available via the following search... + +`index="_internal" sourcetype="taintersightaddon:log"` + Note that if you have multiple inputs (i.e. different Intersight accounts/appliances) configured in the Add-on, the log messages for all of the configured inputs will be interspersed. The Name from the 'Add Input' dialog above is used in the log to differentiate. In these logs, the name EXAMPLE was used. A normal run without inventory might look like this... diff --git a/input_module_intersight.py b/input_module_intersight.py index fccf92f..e79ea55 100644 --- a/input_module_intersight.py +++ b/input_module_intersight.py @@ -8,9 +8,11 @@ from types import SimpleNamespace import requests + from intersight_auth import IntersightAuth, repair_pem + def validate_input(helper, definition): intersight_hostname = definition.parameters.get( 'intersight_hostname', None) @@ -19,7 +21,6 @@ def validate_input(helper, definition): validate_ssl = definition.parameters.get('validate_ssl', None) pass - def collect_events(helper, ew): # User helper functions to retrieve the configuration stanza_name = next(iter(helper.get_input_stanza())) @@ -38,7 +39,7 @@ def collect_events(helper, ew): # get proxy setting configuration proxy = SimpleNamespace(**helper.get_proxy()) - + if len(proxy.__dict__) == 0: # if `Enable proxy` is not checked, the dict will be empty r_proxy = {'https': None} @@ -47,14 +48,11 @@ def collect_events(helper, ew): if proxy.proxy_type != 'http': raise Exception("Only HTTP proxy type is implemented") if proxy.proxy_username and proxy.proxy_password: - r_proxy = {"https": "http://"+proxy.proxy_username+":" + - proxy.proxy_password+"@"+proxy.proxy_url+":"+proxy.proxy_port} - helper.log_debug(stanza_name + ' | ' + "Proxy is " + - proxy.proxy_url+":"+proxy.proxy_port + " with authentication") + r_proxy = {"https": "http://"+proxy.proxy_username+":"+proxy.proxy_password+"@"+proxy.proxy_url+":"+proxy.proxy_port} + helper.log_debug(stanza_name + ' | ' + "Proxy is " + proxy.proxy_url+":"+proxy.proxy_port + " with authentication") else: r_proxy = {"https": "http://"+proxy.proxy_url+":"+proxy.proxy_port} - helper.log_debug(stanza_name + ' | ' + "Proxy is " + - proxy.proxy_url+":"+proxy.proxy_port) + helper.log_debug(stanza_name + ' | ' + "Proxy is " + proxy.proxy_url+":"+proxy.proxy_port) # get the configured index index = helper.get_output_index() @@ -120,9 +118,28 @@ def collect_events(helper, ew): ## # Audit records ## - + + doAuditRecords = False if opt_enable_aaa_audit_records: helper.log_info(stanza_name + ' | ' + "Retrieving Audit Records...") + RESPONSE = requests.request( + method='GET', + url='https://'+opt_intersight_hostname + + '/api/v1/aaa/AuditRecords?$top=1', + auth=AUTH, + verify=opt_validate_ssl, + proxies=r_proxy + ) + if RESPONSE.status_code != 200: + try: + code = RESPONSE.json()['code'] + helper.log_warning(stanza_name + ' | ' + 'Audit Records error code is ' + code) + except: + RESPONSE.raise_for_status + else: + doAuditRecords = True + + if opt_enable_aaa_audit_records and doAuditRecords: # Let's go retrieve our state try: @@ -150,8 +167,11 @@ def collect_events(helper, ew): # Process the audit records for data in RESPONSE.json()['Results']: # remove things that just aren't helpful in splunk - for thepop in ['Account', 'Ancestors', 'PermissionResources', 'Owners', 'User']: - data.pop(thepop) + try: + for thepop in ['Account', 'Ancestors', 'PermissionResources', 'Owners', 'User']: + data.pop(thepop) + except: + pass # Splunk default doesn't allow events over 10k characters by default if len(json.dumps(data)) > 9999: # we're truncating the Request if it's larger than that @@ -178,10 +198,28 @@ def collect_events(helper, ew): ## # Alarms ## - + + doAlarms = False if opt_enable_alarms: helper.log_info(stanza_name + ' | ' + "Retrieving Alarms...") + RESPONSE = requests.request( + method='GET', + url='https://'+opt_intersight_hostname + + '/api/v1/cond/Alarms?$top=1', + auth=AUTH, + verify=opt_validate_ssl, + proxies=r_proxy + ) + if RESPONSE.status_code != 200: + try: + code = RESPONSE.json()['code'] + helper.log_warning(stanza_name + ' | ' + 'Alarms error code is ' + code) + except: + RESPONSE.raise_for_status + else: + doAlarms = True + if opt_enable_alarms and doAlarms: # Let's go retrieve our state try: state = helper.get_check_point(account_name+'_last_alarm_record') @@ -207,9 +245,12 @@ def collect_events(helper, ew): # Process the alarm records for data in RESPONSE.json()['Results']: - # remove things that just aren't helpful in splunk - for thepop in ['AffectedMo', 'Ancestors', 'Owners', 'PermissionResources', 'RegisteredDevice']: - data.pop(thepop) + try: + # remove things that just aren't helpful in splunk + for thepop in ['AffectedMo', 'Ancestors', 'Owners', 'PermissionResources', 'RegisteredDevice']: + data.pop(thepop) + except: + pass event = helper.new_event(source=account_name, index=index, sourcetype='cisco:intersight:condAlarms', data=json.dumps(data)) ew.write_event(event) @@ -266,9 +307,28 @@ def collect_events(helper, ew): ## # Advisories ## - + doAdvisories = False if 'advisories' in opt_inventory and doInventory: - helper.log_info(stanza_name + ' | ' + "Retrieving Advisories...") + helper.log_info(stanza_name + ' | ' + + "Retrieving Advisories...") + RESPONSE = requests.request( + method='GET', + url='https://'+opt_intersight_hostname + + '/api/v1/tam/AdvisoryInstances?$top=1', + auth=AUTH, + verify=opt_validate_ssl, + proxies=r_proxy + ) + if RESPONSE.status_code != 200: + try: + code = RESPONSE.json()['code'] + helper.log_warning(stanza_name + ' | ' + 'Advisories error code is ' + code) + except: + RESPONSE.raise_for_status + else: + doAdvisories = True + + if 'advisories' in opt_inventory and doInventory and doAdvisories: RESPONSE = requests.request( method='GET', url='https://'+opt_intersight_hostname + @@ -293,11 +353,14 @@ def collect_events(helper, ew): ) for data in RESPONSE.json()['Results']: - # remove things that just aren't helpful in splunk - for thepop in ['Ancestors', 'AffectedObject', 'PermissionResources', 'Owners', 'DeviceRegistration']: - data.pop(thepop) - for thepop in ['Ancestors', 'Actions', 'ApiDataSources', 'Organization', 'Owners', 'PermissionResources', 'Recommendation']: - data['Advisory'].pop(thepop) + try: + # remove things that just aren't helpful in splunk + for thepop in ['Ancestors', 'AffectedObject', 'PermissionResources', 'Owners', 'DeviceRegistration']: + data.pop(thepop) + for thepop in ['Ancestors', 'Actions', 'ApiDataSources', 'Organization', 'Owners', 'PermissionResources', 'Recommendation']: + data['Advisory'].pop(thepop) + except: + pass event = helper.new_event(source=account_name, index=index, sourcetype='cisco:intersight:tamAdvisoryInstances', data=json.dumps(data)) ew.write_event(event) @@ -311,9 +374,28 @@ def collect_events(helper, ew): # Compute Inventory ### + doCompute = False if 'compute' in opt_inventory and doInventory: helper.log_info(stanza_name + ' | ' + "Retrieving compute inventory...") + RESPONSE = requests.request( + method='GET', + url='https://'+opt_intersight_hostname + + '/api/v1/compute/PhysicalSummaries?$top=1', + auth=AUTH, + verify=opt_validate_ssl, + proxies=r_proxy + ) + if RESPONSE.status_code != 200: + try: + code = RESPONSE.json()['code'] + helper.log_warning(stanza_name + ' | ' + 'Compute inventory error code is ' + code) + except: + RESPONSE.raise_for_status + else: + doCompute = True + + if 'compute' in opt_inventory and doInventory and doCompute: RESPONSE = requests.request( method='GET', url='https://'+opt_intersight_hostname + @@ -338,9 +420,12 @@ def collect_events(helper, ew): ) for data in RESPONSE.json()['Results']: - # remove things that just aren't helpful in splunk - for thepop in ['Ancestors', 'PermissionResources', 'Owners', 'RegisteredDevice']: - data.pop(thepop) + try: + # remove things that just aren't helpful in splunk + for thepop in ['Ancestors', 'PermissionResources', 'Owners', 'RegisteredDevice']: + data.pop(thepop) + except: + pass event = helper.new_event( source=account_name, index=index, sourcetype='cisco:intersight:computePhysicalSummaries', data=json.dumps(data)) ew.write_event(event) @@ -353,10 +438,28 @@ def collect_events(helper, ew): ### # Compute HCL Status ### - + doHCL = False if 'compute' in opt_inventory and doInventory: helper.log_info(stanza_name + ' | ' + "Retrieving compute HCL status...") + RESPONSE = requests.request( + method='GET', + url='https://'+opt_intersight_hostname + + '/api/v1/cond/HclStatuses?$top=1', + auth=AUTH, + verify=opt_validate_ssl, + proxies=r_proxy + ) + if RESPONSE.status_code != 200: + try: + code = RESPONSE.json()['code'] + helper.log_warning(stanza_name + ' | ' + 'HCL error code is ' + code) + except: + RESPONSE.raise_for_status + else: + doHCL = True + + if 'compute' in opt_inventory and doInventory and doHCL: RESPONSE = requests.request( method='GET', url='https://'+opt_intersight_hostname + @@ -381,11 +484,14 @@ def collect_events(helper, ew): ) for data in RESPONSE.json()['Results']: - # remove things that just aren't helpful in splunk - for thepop in ['Ancestors', 'Details', 'Owners', 'PermissionResources', 'RegisteredDevice']: - data.pop(thepop) - for thepop in ['ClassId', 'link']: - data['ManagedObject'].pop(thepop) + try: + # remove things that just aren't helpful in splunk + for thepop in ['Ancestors', 'Details', 'Owners', 'PermissionResources', 'RegisteredDevice']: + data.pop(thepop) + for thepop in ['ClassId', 'link']: + data['ManagedObject'].pop(thepop) + except: + pass event = helper.new_event( source=account_name, index=index, sourcetype='cisco:intersight:condHclStatuses', data=json.dumps(data)) ew.write_event(event) @@ -399,9 +505,28 @@ def collect_events(helper, ew): # Contract Status ### + doContracts = False if 'contract' in opt_inventory and doInventory: helper.log_info(stanza_name + ' | ' + "Retrieving contract status items...") + RESPONSE = requests.request( + method='GET', + url='https://'+opt_intersight_hostname + + '/api/v1/asset/DeviceContractInformations?$top=1', + auth=AUTH, + verify=opt_validate_ssl, + proxies=r_proxy + ) + if RESPONSE.status_code != 200: + try: + code = RESPONSE.json()['code'] + helper.log_warning(stanza_name + ' | ' + 'Contract inventory error code is ' + code) + except: + RESPONSE.raise_for_status + else: + doContracts = True + + if 'contract' in opt_inventory and doInventory and doContracts: RESPONSE = requests.request( method='GET', url='https://'+opt_intersight_hostname + @@ -426,11 +551,14 @@ def collect_events(helper, ew): ) for data in RESPONSE.json()['Results']: - # remove things that just aren't helpful in splunk - for thepop in ['Ancestors', 'Contract', 'EndCustomer', 'EndUserGlobalUltimate', 'Owners', 'PermissionResources', 'Product', 'RegisteredDevice', 'ResellerGlobalUltimate']: - data.pop(thepop) - for thepop in ['ClassId', 'link']: - data['Source'].pop(thepop) + try: + # remove things that just aren't helpful in splunk + for thepop in ['Ancestors', 'Contract', 'EndCustomer', 'EndUserGlobalUltimate', 'Owners', 'PermissionResources', 'Product', 'RegisteredDevice', 'ResellerGlobalUltimate']: + data.pop(thepop) + for thepop in ['ClassId', 'link']: + data['Source'].pop(thepop) + except: + pass event = helper.new_event( source=account_name, index=index, sourcetype='cisco:intersight:assetDeviceContractInformations', data=json.dumps(data)) ew.write_event(event) @@ -438,17 +566,36 @@ def collect_events(helper, ew): "Creating inventory for Moid "+data['Moid']) else: - helper.log_debug(stanza_name + ' | ' + - "Skipping compute contract status.") + helper.log_debug(stanza_name + ' | ' + "Skipping compute contract status.") + ### # Network Inventory ### + doNetwork = False if 'network' in opt_inventory and doInventory: helper.log_info(stanza_name + ' | ' + "Retrieving network inventory...") + RESPONSE = requests.request( + method='GET', + url='https://'+opt_intersight_hostname + + '/api/v1/network/ElementSummaries?$top=1', + auth=AUTH, + verify=opt_validate_ssl, + proxies=r_proxy + ) + if RESPONSE.status_code != 200: + try: + code = RESPONSE.json()['code'] + helper.log_warning(stanza_name + ' | ' + 'Network inventory error code is ' + code) + except: + RESPONSE.raise_for_status + else: + doNetwork = True + + if 'network' in opt_inventory and doInventory and doNetwork: RESPONSE = requests.request( method='GET', url='https://'+opt_intersight_hostname + @@ -473,8 +620,11 @@ def collect_events(helper, ew): ) for data in RESPONSE.json()['Results']: - for thepop in ['Ancestors', 'PermissionResources', 'Owners', 'RegisteredDevice']: - data.pop(thepop) + try: + for thepop in ['Ancestors', 'PermissionResources', 'Owners', 'RegisteredDevice']: + data.pop(thepop) + except: + pass event = helper.new_event( source=account_name, index=index, sourcetype='cisco:intersight:networkElementSummaries', data=json.dumps(data)) ew.write_event(event) @@ -488,8 +638,27 @@ def collect_events(helper, ew): # Target Inventory ### + doTarget = False if 'target' in opt_inventory and doInventory: helper.log_info(stanza_name + ' | ' + "Retrieving target inventory...") + RESPONSE = requests.request( + method='GET', + url='https://'+opt_intersight_hostname + + '/api/v1/asset/Targets?$top=1', + auth=AUTH, + verify=opt_validate_ssl, + proxies=r_proxy + ) + if RESPONSE.status_code != 200: + try: + code = RESPONSE.json()['code'] + helper.log_warning(stanza_name + ' | ' + 'Target inventory error code is ' + code) + except: + RESPONSE.raise_for_status + else: + doTarget = True + + if 'target' in opt_inventory and doInventory and doTarget: RESPONSE = requests.request( method='GET', @@ -513,8 +682,11 @@ def collect_events(helper, ew): ) for data in RESPONSE.json()['Results']: - for thepop in ['Account', 'Ancestors', 'Connections', 'PermissionResources', 'Owners', 'RegisteredDevice']: - data.pop(thepop) + try: + for thepop in ['Account', 'Ancestors', 'Connections', 'PermissionResources', 'Owners', 'RegisteredDevice']: + data.pop(thepop) + except: + pass event = helper.new_event( source=account_name, index=index, sourcetype='cisco:intersight:assetTargets', data=json.dumps(data)) ew.write_event(event) @@ -526,11 +698,29 @@ def collect_events(helper, ew): ### # Hyperflex Cluster Inventory ### - + + doHxClusters = False if 'hyperflex' in opt_inventory and doInventory: helper.log_info(stanza_name + ' | ' + "Retrieving Hyperflex cluster inventory...") + RESPONSE = requests.request( + method='GET', + url='https://'+opt_intersight_hostname + + '/api/v1/hyperflex/Clusters?$top=1', + auth=AUTH, + verify=opt_validate_ssl, + proxies=r_proxy + ) + if RESPONSE.status_code != 200: + try: + code = RESPONSE.json()['code'] + helper.log_warning(stanza_name + ' | ' + 'Hx Cluster inventory error code is ' + code) + except: + RESPONSE.raise_for_status + else: + doHxClusters = True + if 'hyperflex' in opt_inventory and doInventory and doHxClusters: RESPONSE = requests.request( method='GET', url='https://'+opt_intersight_hostname+'/api/v1/hyperflex/Clusters?$count=True', @@ -554,10 +744,13 @@ def collect_events(helper, ew): ) for data in RESPONSE.json()['Results']: - for thepop in ['Alarm', 'Ancestors', 'ChildClusters', 'Owners', 'PermissionResources', 'RegisteredDevice', 'StorageContainers', 'Nodes', 'Health', 'ParentCluster']: - data.pop(thepop) - for thepop in ['Ancestors', 'Cluster', 'Owners', 'PermissionResources', 'RegisteredDevice']: - data['License'].pop(thepop) + try: + for thepop in ['Alarm', 'Ancestors', 'ChildClusters', 'Owners', 'PermissionResources', 'RegisteredDevice', 'StorageContainers', 'Nodes', 'Health', 'ParentCluster']: + data.pop(thepop) + for thepop in ['Ancestors', 'Cluster', 'Owners', 'PermissionResources', 'RegisteredDevice']: + data['License'].pop(thepop) + except: + pass event = helper.new_event( source=account_name, index=index, sourcetype='cisco:intersight:hyperflexClusters', data=json.dumps(data)) ew.write_event(event) @@ -571,10 +764,28 @@ def collect_events(helper, ew): # Hyperflex Node Inventory ### + doHxNodes = False if 'hyperflex' in opt_inventory and doInventory: helper.log_info(stanza_name + ' | ' + "Retrieving Hyperflex node inventory...") + RESPONSE = requests.request( + method='GET', + url='https://'+opt_intersight_hostname + + '/api/v1/hyperflex/Nodes?$top=1', + auth=AUTH, + verify=opt_validate_ssl, + proxies=r_proxy + ) + if RESPONSE.status_code != 200: + try: + code = RESPONSE.json()['code'] + helper.log_warning(stanza_name + ' | ' + 'Hx Node inventory error code is ' + code) + except: + RESPONSE.raise_for_status + else: + doHxNodes = True + if 'hyperflex' in opt_inventory and doInventory and doHxNodes: RESPONSE = requests.request( method='GET', url='https://'+opt_intersight_hostname+'/api/v1/hyperflex/Nodes?$count=True', @@ -598,15 +809,18 @@ def collect_events(helper, ew): ) for data in RESPONSE.json()['Results']: - for thepop in ['Ancestors', 'ClusterMember', 'Identity', 'Owners', 'Parent', 'PermissionResources']: - data.pop(thepop) - for thepop in ['ClassId', 'link']: - data['Cluster'].pop(thepop) - for thepop in ['ClassId', 'link']: - data['PhysicalServer'].pop(thepop) - for i in range(0, len(data['Drives'])): - for thepop in ['Ancestors', 'LocatorLed', 'Node', 'Owners', 'Parent', 'PermissionResources']: - data['Drives'][i].pop(thepop) + try: + for thepop in ['Ancestors', 'ClusterMember', 'Identity', 'Owners', 'Parent', 'PermissionResources']: + data.pop(thepop) + for thepop in ['ClassId', 'link']: + data['Cluster'].pop(thepop) + for thepop in ['ClassId', 'link']: + data['PhysicalServer'].pop(thepop) + for i in range(0,len(data['Drives'])): + for thepop in ['Ancestors', 'LocatorLed', 'Node', 'Owners', 'Parent', 'PermissionResources']: + data['Drives'][i].pop(thepop) + except: + pass event = helper.new_event( source=account_name, index=index, sourcetype='cisco:intersight:hyperflexNodes', data=json.dumps(data)) ew.write_event(event) diff --git a/splunkbase/releasenotes.md b/splunkbase/releasenotes.md index df8cd99..fd88e4b 100644 --- a/splunkbase/releasenotes.md +++ b/splunkbase/releasenotes.md @@ -18,3 +18,9 @@ Unfortunately, the change from checkboxes to a mutliple dropdown means that the Proxy support was tested, but perhaps not as thoroughly as possible. Please provide feedback if you have a problem. Only HTTP proxy (and not SOCKS) is supported at this time. Proxy support is configured at the Add-on level and not per-Input. Click the `Configuration` tab at the top of the Add-on for this setting. This means that on a given Splunk server, all Intersight inputs will share the same proxy configuration. + +## 1.2.6 + +Fixed an issue that could cause the add-on to fail if the Intersight SaaS account did not have any servers licensed at Essentials or higher when either the Compute inventory (because of the HCL feature) or Advisories Inventory items were selected. + +Fixed an issue that would cause the add-on to fail if Hyperflex inventory was selected and no Hyperflex clusters were present.