Skip to content

Commit 019c947

Browse files
Adding Endpoint command and outputs to Endpoint integrations (demisto#12023)
* changes Co-authored-by: ShirleyDenkberg <[email protected]>
1 parent 43055db commit 019c947

18 files changed

Lines changed: 792 additions & 38 deletions

File tree

Packs/Base/ReleaseNotes/1_10_8.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
#### Scripts
3+
##### CommonServerPython
4+
Added the following optional fields to the **Endpoint** indicator:
5+
- **Status**
6+
- **Vendor**
7+
- **IsIsolated**

Packs/Base/Scripts/CommonServerPython/CommonServerPython.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,18 @@ def __del__(self):
9898
'widget': 17
9999
}
100100

101+
ENDPOINT_STATUS_OPTIONS = [
102+
'Online',
103+
'Offline'
104+
]
105+
106+
ENDPOINT_ISISOLATED_OPTIONS = [
107+
'Yes',
108+
'No',
109+
'Pending isolation',
110+
'Pending unisolation'
111+
]
112+
101113

102114
class EntryType(object):
103115
"""
@@ -3024,7 +3036,8 @@ class Endpoint(Indicator):
30243036

30253037
def __init__(self, id, hostname=None, ip_address=None, domain=None, mac_address=None,
30263038
os=None, os_version=None, dhcp_server=None, bios_version=None, model=None,
3027-
memory=None, processors=None, processor=None, relations=None):
3039+
memory=None, processors=None, processor=None, relations=None, vendor=None, status=None,
3040+
is_isolated=None):
30283041
self.id = id
30293042
self.hostname = hostname
30303043
self.ip_address = ip_address
@@ -3038,6 +3051,9 @@ def __init__(self, id, hostname=None, ip_address=None, domain=None, mac_address=
30383051
self.memory = memory
30393052
self.processors = processors
30403053
self.processor = processor
3054+
self.vendor = vendor
3055+
self.status = status
3056+
self.is_isolated = is_isolated
30413057
self.relations = relations
30423058

30433059
def to_context(self):
@@ -3085,6 +3101,20 @@ def to_context(self):
30853101
relations_context = [relation.to_context() for relation in self.relations if relation.to_context()]
30863102
endpoint_context['Relationships'] = relations_context
30873103

3104+
if self.vendor:
3105+
endpoint_context['Vendor'] = self.vendor
3106+
3107+
if self.status:
3108+
if self.status not in ENDPOINT_STATUS_OPTIONS:
3109+
raise ValueError('Status does not have a valid value such as: Online or Offline')
3110+
endpoint_context['Status'] = self.status
3111+
3112+
if self.is_isolated:
3113+
if self.is_isolated not in ENDPOINT_ISISOLATED_OPTIONS:
3114+
raise ValueError('Is Isolated does not have a valid value such as: Yes, No, Pending'
3115+
' isolation or Pending unisolation')
3116+
endpoint_context['IsIsolated'] = self.is_isolated
3117+
30883118
ret_value = {
30893119
Common.Endpoint.CONTEXT_PATH: endpoint_context
30903120
}

Packs/Base/pack_metadata.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "Base",
33
"description": "The base pack for Cortex XSOAR.",
44
"support": "xsoar",
5-
"currentVersion": "1.10.7",
5+
"currentVersion": "1.10.8",
66
"author": "Cortex XSOAR",
77
"serverMinVersion": "6.0.0",
88
"url": "https://www.paloaltonetworks.com/cortex",

Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.py

Lines changed: 83 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
INTEGRATION_CONTEXT_BRAND = 'PaloAltoNetworksXDR'
2323
XDR_INCIDENT_TYPE_NAME = 'Cortex XDR Incident'
24+
INTEGRATION_NAME = 'Cortex XDR - IR'
2425

2526
XDR_INCIDENT_FIELDS = {
2627
"status": {"description": "Current status of the incident: \"new\",\"under_"
@@ -1681,9 +1682,16 @@ def get_endpoints_command(client, args):
16811682
sort_by_first_seen=sort_by_first_seen,
16821683
sort_by_last_seen=sort_by_last_seen
16831684
)
1685+
1686+
standard_endpoints = generate_endpoint_by_contex_standard(endpoints, False)
1687+
endpoint_context_list = []
1688+
for endpoint in standard_endpoints:
1689+
endpoint_context = endpoint.to_context().get(Common.Endpoint.CONTEXT_PATH)
1690+
endpoint_context_list.append(endpoint_context)
1691+
16841692
context = {
16851693
f'{INTEGRATION_CONTEXT_BRAND}.Endpoint(val.endpoint_id == obj.endpoint_id)': endpoints,
1686-
Common.Endpoint.CONTEXT_PATH: return_endpoint_standard_context(endpoints)
1694+
Common.Endpoint.CONTEXT_PATH: endpoint_context_list
16871695
}
16881696
account_context = create_account_context(endpoints)
16891697
if account_context:
@@ -1695,17 +1703,77 @@ def get_endpoints_command(client, args):
16951703
)
16961704

16971705

1698-
def return_endpoint_standard_context(endpoints):
1699-
endpoints_context_list = []
1700-
for endpoint in endpoints:
1701-
endpoints_context_list.append(assign_params(**{
1702-
"Hostname": (endpoint['host_name'] if endpoint.get('host_name', '') else endpoint.get('endpoint_name')),
1703-
"ID": endpoint.get('endpoint_id'),
1704-
"IPAddress": endpoint.get('ip'),
1705-
"Domain": endpoint.get('domain'),
1706-
"OS": endpoint.get('os_type'),
1707-
}))
1708-
return endpoints_context_list
1706+
def convert_os_to_standard(endpoint_os):
1707+
os_type = ''
1708+
endpoint_os = endpoint_os.lower()
1709+
if 'windows' in endpoint_os:
1710+
os_type = "Windows"
1711+
elif 'linux' in endpoint_os:
1712+
os_type = "Linux"
1713+
elif 'macos' in endpoint_os:
1714+
os_type = "Macos"
1715+
elif 'android' in endpoint_os:
1716+
os_type = "Android"
1717+
return os_type
1718+
1719+
1720+
def generate_endpoint_by_contex_standard(endpoints, ip_as_string):
1721+
standard_endpoints = []
1722+
for single_endpoint in endpoints:
1723+
status = 'Online' if single_endpoint.get('endpoint_status') == 'connected' else 'Offline'
1724+
is_isolated = 'No' if 'unisolated' in single_endpoint.get('is_isolated', '').lower() else 'Yes'
1725+
hostname = single_endpoint['host_name'] if single_endpoint.get('host_name', '') else single_endpoint.get(
1726+
'endpoint_name')
1727+
ip = single_endpoint.get('ip')
1728+
# in the `xdr-get-endpoints` command the ip is returned as list, in order not to break bc we will keep it
1729+
# in the `endpoint` command we use the standard
1730+
if ip_as_string and isinstance(ip, list):
1731+
ip = ip[0]
1732+
os_type = convert_os_to_standard(single_endpoint.get('os_type', ''))
1733+
endpoint = Common.Endpoint(
1734+
id=single_endpoint.get('endpoint_id'),
1735+
hostname=hostname,
1736+
ip_address=ip,
1737+
os=os_type,
1738+
status=status,
1739+
is_isolated=is_isolated,
1740+
mac_address=single_endpoint.get('mac_address'),
1741+
domain=single_endpoint.get('domain'),
1742+
vendor=INTEGRATION_NAME)
1743+
1744+
standard_endpoints.append(endpoint)
1745+
return standard_endpoints
1746+
1747+
1748+
def endpoint_command(client, args):
1749+
endpoint_id_list = argToList(args.get('id'))
1750+
endpoint_ip_list = argToList(args.get('ip'))
1751+
endpoint_hostname_list = argToList(args.get('hostname'))
1752+
1753+
endpoints = client.get_endpoints(
1754+
endpoint_id_list=endpoint_id_list,
1755+
ip_list=endpoint_ip_list,
1756+
hostname=endpoint_hostname_list,
1757+
)
1758+
standard_endpoints = generate_endpoint_by_contex_standard(endpoints, True)
1759+
command_results = []
1760+
if standard_endpoints:
1761+
for endpoint in standard_endpoints:
1762+
endpoint_context = endpoint.to_context().get(Common.Endpoint.CONTEXT_PATH)
1763+
hr = tableToMarkdown('Cortex XDR Endpoint', endpoint_context)
1764+
1765+
command_results.append(CommandResults(
1766+
readable_output=hr,
1767+
raw_response=endpoints,
1768+
indicator=endpoint
1769+
))
1770+
1771+
else:
1772+
command_results.append(CommandResults(
1773+
readable_output="No endpoints were found",
1774+
raw_response=endpoints,
1775+
))
1776+
return command_results
17091777

17101778

17111779
def create_parsed_alert(product, vendor, local_ip, local_port, remote_ip, remote_port, event_timestamp, severity,
@@ -3297,6 +3365,9 @@ def main():
32973365
elif demisto.command() == 'xdr-run-script-kill-process':
32983366
return_results(run_script_kill_process_command(client, args))
32993367

3368+
elif demisto.command() == 'endpoint':
3369+
return_results(endpoint_command(client, args))
3370+
33003371
except Exception as err:
33013372
if demisto.command() == 'fetch-incidents':
33023373
LOG(str(err))

Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.yml

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ configuration:
4040
name: timeout
4141
required: false
4242
type: 0
43-
- additionalinfo: The maximum number of incidents per fetch. cannot exceed 100.
43+
- additionalinfo: The maximum number of incidents per fetch. Cannot exceed 100.
4444
defaultvalue: '10'
4545
display: Maximum number of incidents per fetch
4646
name: max_fetch
@@ -73,7 +73,7 @@ configuration:
7373
required: false
7474
type: 8
7575
- additionalinfo: 'The statuses of the incidents that will be fetched. If no status
76-
is provided then incidents of all the statuses will be fetched. Note: an incident
76+
is provided then incidents of all the statuses will be fetched. Note: An incident
7777
whose status was changed to a filtered status after its creation time will not
7878
be fetched.'
7979
display: Incident Statuses to Fetch
@@ -1033,14 +1033,26 @@ script:
10331033
description: The domain of the endpoint.
10341034
type: String
10351035
- contextPath: Endpoint.OS
1036-
description: Endpoint OS.
1036+
description: The endpoint's operation system.
10371037
type: String
10381038
- contextPath: Account.Username
10391039
description: The username in the relevant system.
10401040
type: String
10411041
- contextPath: Account.Domain
10421042
description: The domain of the account.
10431043
type: String
1044+
- contextPath: Endpoint.Status
1045+
description: The endpoint's status.
1046+
type: String
1047+
- contextPath: Endpoint.IsIsolated
1048+
description: The endpoint's isolation status.
1049+
type: String
1050+
- contextPath: Endpoint.MACAddress
1051+
description: The endpoint's MAC address.
1052+
type: String
1053+
- contextPath: Endpoint.Vendor
1054+
description: The integration name of the endpoint vendor.
1055+
type: String
10441056
- deprecated: false
10451057
description: Gets a list of all the agent versions to use for creating a distribution
10461058
list.
@@ -2741,6 +2753,54 @@ script:
27412753
- contextPath: PaloAltoNetworksXDR.ScriptRun.endpoints_count
27422754
description: Number of endpoints the action was initiated on.
27432755
type: Number
2756+
- arguments:
2757+
- default: false
2758+
description: The endpoint ID.
2759+
isArray: false
2760+
name: id
2761+
required: false
2762+
secret: false
2763+
- default: true
2764+
description: The endpoint IP address.
2765+
isArray: false
2766+
name: ip
2767+
required: false
2768+
secret: false
2769+
- default: false
2770+
description: The endpoint hostname.
2771+
isArray: false
2772+
name: hostname
2773+
required: false
2774+
secret: false
2775+
deprecated: false
2776+
description: Returns information about an endpoint.
2777+
execution: false
2778+
name: endpoint
2779+
outputs:
2780+
- contextPath: Endpoint.Hostname
2781+
description: The endpoint's hostname.
2782+
type: String
2783+
- contextPath: Endpoint.OS
2784+
description: The endpoint's operation system.
2785+
type: String
2786+
- contextPath: Endpoint.IPAddress
2787+
description: The endpoint's IP address.
2788+
type: String
2789+
- contextPath: Endpoint.ID
2790+
description: The endpoint's ID.
2791+
type: String
2792+
- contextPath: Endpoint.Status
2793+
description: The endpoint's status.
2794+
type: String
2795+
- contextPath: Endpoint.IsIsolated
2796+
description: The endpoint's isolation status.
2797+
type: String
2798+
- contextPath: Endpoint.MACAddress
2799+
description: The endpoint's MAC address.
2800+
type: String
2801+
- contextPath: Endpoint.Vendor
2802+
description: The integration name of the endpoint vendor.
2803+
type: String
27442804
dockerimage: demisto/python3:3.9.4.18682
27452805
feed: false
27462806
isfetch: true

Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR_test.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,13 +312,38 @@ def test_get_all_endpoints_using_limit(requests_mock):
312312
'page': 0,
313313
'sort_order': 'asc'
314314
}
315-
316315
_, outputs, _ = get_endpoints_command(client, args)
317316
expected_endpoint = get_endpoints_response.get('reply')[0]
318317

319318
assert [expected_endpoint] == outputs['PaloAltoNetworksXDR.Endpoint(val.endpoint_id == obj.endpoint_id)']
320319

321320

321+
def test_endpoint_command(requests_mock):
322+
from CortexXDRIR import endpoint_command, Client
323+
324+
get_endpoints_response = load_test_data('./test_data/get_endpoints.json')
325+
requests_mock.post(f'{XDR_URL}/public_api/v1/endpoints/get_endpoint/', json=get_endpoints_response)
326+
327+
client = Client(
328+
base_url=f'{XDR_URL}/public_api/v1', headers={}
329+
)
330+
args = {'id': 'identifier'}
331+
332+
outputs = endpoint_command(client, args)
333+
334+
get_endpoints_response = {
335+
Common.Endpoint.CONTEXT_PATH: [{'ID': '1111',
336+
'Hostname': 'ip-3.3.3.3',
337+
'IPAddress': '3.3.3.3',
338+
'OS': 'Linux',
339+
'Vendor': 'Cortex XDR - IR',
340+
'Status': 'Offline',
341+
'IsIsolated': 'No'}]}
342+
343+
results = outputs[0].to_context()
344+
assert results.get("EntryContext") == get_endpoints_response
345+
346+
322347
def test_insert_parsed_alert(requests_mock):
323348
from CortexXDRIR import insert_parsed_alert_command, Client
324349

0 commit comments

Comments
 (0)