SendAllPANWIoTDevicesToCiscoISE
Gets all available devices from the IoT cloud and updates or creates them on Cisco ISE using the custom attributes.
- Type
- python
- Pack
- PaloAltoNetworks_IoT3rdParty
Source
import demistomock as demisto # noqa: F401
from CommonServerPython import * # noqa: F401
PANW_IOT_INSTANCE = demisto.args().get("panw_iot_3rd_party_instance")
CISCO_ISE_ACTIVE_INSTANCE = demisto.args().get("active_ise_instance")
GET_EP_ID_CMD = "cisco-ise-get-endpoint-id-by-name"
CISCO_ISE_FIELD_MAP = {
"ip": ["ZingboxIpAddress", "PanwIoTIpAddress"],
"ip address": ["ZingboxIP", "PanwIoTIP"],
"ip_address": ["ZingboxIP", "PanwIoTIP"],
"profile": ["ZingboxProfile", "PanwIoTProfile"],
"category": ["ZingboxCategory", "PanwIoTCategory"],
"risk_score": ["ZingboxRiskScore", "PanwIoTRiskScore"],
"risk score": ["ZingboxRiskScore", "PanwIoTRiskScore"],
"confidence": ["ZingboxConfidence", "PanwIoTConfidence"],
"confidence score": ["ZingboxConfidence", "PanwIoTConfidence"],
"confidence_score": ["ZingboxConfidence", "PanwIoTConfidence"],
"tag": ["ZingboxTag", "PanwIoTTag"],
"asset_tag": ["ZingboxTag", "PanwIoTTag"],
"Tags": ["ZingboxTag", "PanwIoTTag"],
"hostname": ["ZingboxHostname", "PanwIoTHostname"],
"osCombined": ["ZingboxOS", "PanwIoTOS"],
"model": ["ZingboxModel", "PanwIoTModel"],
"vendor": ["ZingboxVendor", "PanwIoTVendor"],
"Serial Number": ["ZingboxSerial", "PanwIoTSerial"],
"Serial_Number": ["ZingboxSerial", "PanwIoTSerial"],
"endpoint protection": ["ZingboxEPP", "PanwIoTEPP"],
"endpoint_protection": ["ZingboxEPP", "PanwIoTEPP"],
"AET": ["ZingboxAET", "PanwIoTAET"],
"External Network": ["ZingboxInternetAccess", "PanwIoTInternetAccess"],
}
INT_FIELDS = ["risk_score", "risk score", "confidence", "confidence score", "confidence_score"]
def send_status_to_panw_iot_cloud(status, msg):
"""
Reports status details back to PANW IoT Cloud.
param status: Status (error, disabled, success) to be send to PANW IoT cloud.
param msg: Debug message to be send to PANW IoT cloud.
"""
resp = demisto.executeCommand(
"panw-iot-3rd-party-report-status-to-panw",
{
"status": status,
"message": msg,
"integration_name": "ise",
"playbook_name": "PANW IoT 3rd Party Cisco ISE Integration - Bulk Export to Cisco ISE",
"asset_type": "device",
"timestamp": int(round(time.time() * 1000)),
"using": PANW_IOT_INSTANCE,
},
)
if isError(resp[0]):
err_msg = f'Error, failed to send status to PANW IoT Cloud - {resp[0].get("Contents")}'
raise Exception(err_msg)
def get_active_ise_instance_or_error_msg():
"""
Get the active configured Cisco ISE instance, if not found then return the error message.
"""
response = demisto.executeCommand("GetCiscoISEActiveInstance", {})
err_msg = None
active_instance = None
data = response[0].get("EntryContext", {})
if "PaloAltoIoTIntegrationBase.ActiveNodeInstance" in data:
active_instance = data.get("PaloAltoIoTIntegrationBase.ActiveNodeInstance")
elif "PaloAltoIoTIntegrationBase.NodeErrorStatus" in data:
err_msg = data.get("PaloAltoIoTIntegrationBase.NodeErrorStatus")
return active_instance, err_msg
def extract_ise_api_error(err_msg):
"""
Extract any connection error or error code if possible,
Otherwise just return the original error
"""
err_msg = err_msg.split("-")[0]
if err_msg.startswith("Error in API call to Cisco"):
start = err_msg.find("[") + 1
end = err_msg.find("]")
return err_msg[start:end]
elif err_msg.startswith("Connection Error. Verify"):
return "Connection Error"
else:
return err_msg
def get_devices_from_panw_iot_cloud(offset, page_size):
"""
Gets assets from PANW IoT cloud.
param offset: Offset number for the asset list.
param page_size: Page size of the response being requested.
"""
resp = demisto.executeCommand(
"panw-iot-3rd-party-get-asset-list",
{"asset_type": "device", "increment_type": None, "offset": offset, "pageLength": page_size, "using": PANW_IOT_INSTANCE},
)
if isError(resp[0]):
err_msg = f'Error, could not get assets from PANW IoT Cloud - {resp[0].get("Contents")}'
raise Exception(err_msg)
return resp[0]["Contents"]
def convert_device_map_to_cisco_ise_attributes(device_map):
"""
Converts a PANW IoT device_map to Cisco ISE custom attributes map.
param device_map: Single PANW IoT device_map with device attributes .
"""
attribute_list = {}
if "deviceid" in device_map:
if device_map["deviceid"] is None or device_map["deviceid"] == "":
return None
attribute_list["mac"] = device_map["deviceid"]
if not is_mac_address(attribute_list["mac"]):
return None
zb_attributes = {}
for field in device_map:
if device_map[field] is None or device_map[field] == "":
continue
if field in CISCO_ISE_FIELD_MAP:
if field in INT_FIELDS:
try:
int_val = int(device_map[field])
except Exception:
continue
zb_attributes[CISCO_ISE_FIELD_MAP[field][0]] = int_val
zb_attributes[CISCO_ISE_FIELD_MAP[field][1]] = int_val
else:
zb_attributes[CISCO_ISE_FIELD_MAP[field][0]] = device_map[field]
zb_attributes[CISCO_ISE_FIELD_MAP[field][1]] = device_map[field]
attribute_list["zb_attributes"] = zb_attributes
return attribute_list
def update_existing_endpoint(mac, attr_map, ep_id, active_instance):
"""
Update an existing endpoint with the given custom attributes.
Param mac: mac address of the endpoint that needs to be updated.
Param attr_map: a map containing various ise custom attributes.
Param ep_id: ID for endpoint that needs to be updated.
Param active_instance: The primary/active ISE instance.
"""
attribute_names = ""
attribute_values = ""
for key in attr_map:
attribute_names += key + ","
attribute_values += str(attr_map[key]) + ","
attribute_names = attribute_names[:-1]
attribute_values = attribute_values[:-1]
resp = demisto.executeCommand(
"cisco-ise-update-endpoint-custom-attribute",
{
"id": ep_id,
"macAddress": mac,
"attributeName": attribute_names,
"attributeValue": attribute_values,
"using": active_instance,
},
)
if isError(resp[0]):
err_msg = f'Error, failed to update custom attributes for endpoint {id} - {resp[0].get("Contents")}'
raise Exception(err_msg)
def create_new_ep(mac, attr_map, active_instance):
"""
Create a new endpoint with the given params
Param mac: mac address of the endpoint that needs to be created.
Param attr_map: a map containing various ise custom attributes.
Param active_instance: The primary/active ISE instance.
"""
resp = demisto.executeCommand(
"cisco-ise-create-endpoint", {"mac_address": mac, "attributes_map": attr_map, "using": active_instance}
)
if isError(resp[0]):
err_msg = f'Failed to create new Endpoint {mac} - {resp[0].get("Contents")}'
raise Exception(err_msg)
def create_or_update_ep(mac, attr_map):
"""
Check if an enpoint exists in ISE, if not create one with the custom attributes
otherwise update it. If at any point the connection goes down or we get a 401 -
unautherized access we will attempt to get the new active instance.
Params mac: Mac adress of the endpoint.
attr_map: Custom attributes for the endpoint.
"""
global CISCO_ISE_ACTIVE_INSTANCE
global GET_EP_ID_CMD
cmd_mac_syntax_map = {"cisco-ise-get-endpoint-id-by-name": "mac_address", "cisco-ise-get-endpoint-id": "macAddress"}
# Check if this mac address (endpoint) is present in ISE by attempting to get its ID
resp = demisto.executeCommand(GET_EP_ID_CMD, {cmd_mac_syntax_map[GET_EP_ID_CMD]: mac, "using": CISCO_ISE_ACTIVE_INSTANCE})
if isError(resp[0]):
err_msg = extract_ise_api_error(resp[0].get("Contents"))
# 404 Not Found or empty results, we need to create a new EP
if err_msg == "404" or err_msg == "list index out of range":
create_new_ep(mac, attr_map, CISCO_ISE_ACTIVE_INSTANCE)
# 405 - Method not allowed means we need to switch to an old filter based API
elif err_msg == "405":
GET_EP_ID_CMD = "cisco-ise-get-endpoint-id"
# The primary went down (connection Error) or 401 if a fail over occurred (this primary/active
# is not a secondary/standby device).We should attempt to get the new Primary/Active
# instance is possible.
elif err_msg == "Connection Error" or err_msg == "401":
# Failover can take up to 10 minutes, its ok to just wait even if its a standalone ISE noe.
msg = f"ISE instance is down. Trying again in 10 minutes. Error = {err_msg}"
demisto.info(f"PANW_IOT_3RD_PARTY_BASE {msg}")
send_status_to_panw_iot_cloud("error", msg)
time.sleep(10 * 60)
# Try again to get a new active instance
new_active_instance, err_msg = get_active_ise_instance_or_error_msg()
if new_active_instance is None:
raise Exception(err_msg)
else:
CISCO_ISE_ACTIVE_INSTANCE = new_active_instance
msg = f"Found new active ISE instance {CISCO_ISE_ACTIVE_INSTANCE}"
send_status_to_panw_iot_cloud("success", msg)
else:
raise Exception(resp[0].get("Contents"))
else:
ep_id = resp[0]["EntryContext"]["Endpoint(val.ID === obj.ID)"]["ID"]
update_existing_endpoint(mac, attr_map, ep_id, CISCO_ISE_ACTIVE_INSTANCE)
def get_all_panw_iot_devices_and_send_to_cisco_ise():
"""
Retrieves all devices from PANW IoT Cloud, 1000 devices at a time and sends it
to the primary/active cisco ise.
"""
count = 0
offset = 0
page_size = 1000
unique_macs = set()
while True:
device_list = get_devices_from_panw_iot_cloud(offset, page_size)
size = len(device_list)
count += size
for device in device_list:
attrs = convert_device_map_to_cisco_ise_attributes(device)
if attrs is not None:
mac = attrs["mac"]
attr_map = attrs["zb_attributes"]
if mac not in unique_macs:
create_or_update_ep(mac, attr_map)
unique_macs.add(mac)
time.sleep(0.5)
if size == page_size:
offset += page_size
msg = f"Successfully exported {count} devices to Cisco ISE"
send_status_to_panw_iot_cloud(
"success",
msg,
)
else:
break
return (
f"Total {count} devices pulled from PANW IoT Cloud.\n"
f"Exported {len(unique_macs)} devices (with available mac addresses) to Cisco ISE"
)
def main():
try:
status_msg = get_all_panw_iot_devices_and_send_to_cisco_ise()
except Exception as ex:
send_status_to_panw_iot_cloud("error", str(ex))
return_error(str(ex))
send_status_to_panw_iot_cloud("success", status_msg)
return_results(status_msg)
if __name__ in ("__main__", "__builtin__", "builtins"):
main()
README
Retrieves all devices from PANW IoT Cloud and exports them as endpoints with custom attributes to Cisco ISE.
Script Data
| Name | Description |
|---|---|
| Script Type | python3 |
| Tags | PANW IoT 3rd Party Integration, Cisco ISE |
| Cortex XSOAR Version | 6.0.0 |
Used In
This script is used in the following playbooks and scripts.
- Bulk Export to Cisco ISE - PANW IoT 3rd Party Integration
Dependencies
This script uses the following commands and scripts.
- panw-iot-3rd-party-get-asset-list
- panw-iot-3rd-party-report-status-to-panw
- cisco-ise-get-endpoint-id
- cisco-ise-get-endpoint-id-by-name
- cisco-ise-create-endpoint
- cisco-ise-update-endpoint-custom-attribute
- GetCiscoISEActiveInstance
Inputs
| Argument Name | Description |
|---|---|
| active_ise_instance | Name of the Active Cisco ISE instance. |
| panw_iot_3rd_party_instance | Name of the configured PANW Iot 3rd Party instance. |
Outputs
There are no outputs for this script.