Forescout
Unified device visibility and control platform for IT and OT Security.
Network Security · Forescout eyeSight
Details
| ID | Forescout |
|---|---|
| Provider | Advent International |
| Category | Network Security |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Forescout CounterACT is Unified device visibility and control platform for IT and OT security.
Detailed Information
Read this section and perform all necessary steps before you configure an integration instance.
Forescout CounterACT Module Requirements
Before you can use this integration in Cortex XSOAR, you need to enable certain modules in your Forescout CounterACT environment.
- In the Forescout CounterACT console, from the navigation bar select Tools > Options.
- In the dialog that appears, from the categories section on the left, click Modules.
- In the main area of the dialog, from the drop-down menu, select Open Integration Module. Make sure that the integration module and the following submodules are installed and enabled: Data Exchange (DEX) and Web API are all installed and enabled. If they aren't, install and enable them.
Configuration Parameters
url
This is the network address of the Forescout Enterprise Manager or standalone Appliance. (The host on which the the Forescout Appliance is hosted.) For example, if the Forescout Appliance is hosted at the IP address 192.168.10.23, then you enter https://192.168.10.23.
Web API Username and Password
The credentials entered here should be those created in the Forescout console for the Web API.
- In the Forescout CounterACT console, from the top navigation bar, click Tools > Options.
- From the dialog that appears, in the categories section on the left, click Web API, and select User Settings.
- Create a username and password by clicking the Add button, and completing the fields. These are the credentials that you will enter when configuring the Cortex XSOAR-Forescout integration: Web API Username and Password.
- Select Client IPs towards the top of the main area of the dialog, next to User Settings.
- Add the IP address where your Cortex XSOAR instance is hosted or allow requests from all IP addresses to make sure that requests made by the Cortex XSOAR-Forescout integration will be permitted.
- Click the Apply button to save the changes you made.
Data Exchange (DEX) Username and Password
The credentials entered here should be those created in the Forescout console for Data Exchange (DEX).
- In the Forescout CounterACT console, from the top navigation bar, click Tools > Options.
- From the dialog that appears, in the categories section on the left, click Data Exchange (DEX).
- Select CounterACT Web Service > Accounts.
- Create a username and password by clicking the Add button, and completing the fields. Note: The value you entered for the Name field in the account-creation pop-up window is the value that you should enter for the Data Exchange (DEX) Account configuration parameter.
- Click the Apply button to save the changes you made.
The username and password entered in the account-creation dialog are the credentials that you will enter when configuring the Cortex XSOAR-Forescout integration: Data Exchange (DEX) Username and Password.
Data Exchange (DEX) Account
The Data Exchange (DEX) credentials Name field. This can be found by navigating to Tools > Options > Data Exchange (DEX) > CounterACT Web Service > Accounts.
Important Usage Notes
This integration allows the user to update host properties and Forescout Lists. To create Forescout properties, which can then be updated using the Cortex XSOAR-Forescout integration, from the Forescout console, navigate to Tools > Options > Data Exchange (DEX) > CounterACT Web Console > Properties. This is where you create new properties. Make sure to associate the properties with the account you created, and which you used in the configuration parameters of the Forescout integration in Cortex XSOAR. Lists must also be defined and created in the Forescout console before you can update them using the Cortex XSOAR-Forescout integration. For more information, reference the Defining and Managing Lists section in the Forescout Administration Guide.
Configure Forescout on Cortex XSOAR
- Navigate to Settings > Integrations > Servers & Services.
- Search for Forescout.
- Click Add instance to create and configure a new integration instance.
- Name: a textual name for the integration instance.
- The network address of the Forescout Enterprise Manager or
standalone Appliance, e.g. ‘https://10.0.0.8’. #disable-secrets-detection - Web API Username (see Detailed Instructions (?))
- Data Exchange (DEX) Username (see Detailed Instructions (?))
- Data Exchange (DEX) Account (see Detailed Instructions (?))
- HTTP Timeout (default is 60 seconds)
- Trust any certificate (not secure)
- Use system proxy settings
- Click Test to validate the URLs, token, and connection.
Commands
You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook. After you successfully execute a command, a DBot message appears in the War Room with the command details.
1. Get a list of active endpoints
Retrieves a list of active endpoints.
Base Command
forescout-get-hosts
Input
| Argument Name | Description | Required |
|---|---|---|
| rule_ids | Filter hosts by those selected by policies or policy sub-rules. Policies and/or rules should be specified by their IDs. To find policy and rule IDs by which you can filter, run the forescout-get-policies command. If multiple policy and/or rule IDs are entered, only hosts that are selected by all of the policies and/or rules specified will be returned. Multiple policy or rule IDs should be separated by a comma. | Optional |
| fields | Filter hosts based on host field values. Enter fields with their associated values in the following format, ‘{field_1}={val_1}&{field_2}={val_2} … &{field_n}={val_n}’ where ‘{field_1}’ through ‘{field_n}’ are replaced by actual field names and ‘{val_1}’ through ‘{val_n}’ are replaced by the desired matching values. Note that a list field may be specified with the values separated by commas. Only hosts whose properties match all the specified values will be returned. For a list of potential host fields that may be specified, try executing the ‘forescout-get-hostfields’ command. A composite property may also be specified. If entered in the format where all the field-value pairs are in a single set of square brackets, for example, ‘{composite_prop}=[{field_1},{val_1},…,{field_n},{val_n}]’ then only hosts for which the specified composite property’s fields all match the values entered will be returned. If entered in the format, ‘{composite_prop}=[{field_1},{val}_1],…,[{field_n},{val_n}]’ where each field-value pair is enclosed in its own set of brackets, then hosts for which the composite property contains any of the field-values specified will be returned. Note that for composite properties, sub-fields should be entered as their internal representation in Forescout. To find internal representation for a composite property’s sub-fields try executing ‘forescout-get-host’ command with the host specified in the ‘identifier’ argument and the name of the composite property entered in the ‘fields’ argument of the command. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Forescout.Host.ID | Number | Forescout ID for the host. |
| Forescout.Host.IPAddress | String | IP Address of the host. |
| Forescout.Host.MACAddress | String | MAC Address of the host. |
| Endpoint.IPAddress | String | IP Address of the host. |
| Endpoint.MACAddress | String | MAC Address of the host. |
Command Example
forescout-get-hosts fields=online=true
Context Example
{
"Forescout.Host": [
{
"MACAddress": "000c29e9e452",
"IPAddress": "192.168.1.44",
"ID": "3232235820"
},
{
"MACAddress": "000c297cc5ae",
"IPAddress": "192.168.1.125",
"ID": "3232235901"
},
{
"MACAddress": "005056a1ad60",
"IPAddress": "192.168.1.52",
"ID": "3232235828"
},
{
"MACAddress": "000c29497e4e",
"IPAddress": "192.168.1.119",
"ID": "3232235895"
},
{
"MACAddress": "000000000000",
"IPAddress": "192.168.1.8",
"ID": "3232235784"
},
{
"MACAddress": null,
"IPAddress": "192.168.1.1",
"ID": "3232235777"
},
{
"MACAddress": "005056b1488d",
"IPAddress": "192.168.1.31",
"ID": "3232235807"
},
{
"MACAddress": "005056b1a93f",
"IPAddress": "192.168.1.17",
"ID": "3232235793"
},
{
"MACAddress": null,
"IPAddress": "192.168.1.212",
"ID": "3232235988"
}
],
"Endpoint": [
{
"MACAddress": "000c29e9e452",
"IPAddress": "192.168.1.44"
},
{
"MACAddress": "000c297cc5ae",
"IPAddress": "192.168.1.125"
},
{
"MACAddress": "005056a1ad60",
"IPAddress": "192.168.1.52"
},
{
"MACAddress": "000c29497e4e",
"IPAddress": "192.168.1.119"
},
{
"MACAddress": "000000000000",
"IPAddress": "192.168.1.8"
},
{
"MACAddress": null,
"IPAddress": "192.168.1.1"
},
{
"MACAddress": "005056b1488d",
"IPAddress": "192.168.1.31"
},
{
"MACAddress": "005056b1a93f",
"IPAddress": "192.168.1.17"
},
{
"MACAddress": null,
"IPAddress": "192.168.1.212"
}
]
}
Human Readable Output
Active Endpoints
| ID | IPAddress | MACAddress |
|---|---|---|
| 3232235820 | 192.168.1.44 | 000c29e9e452 |
| 3232235901 | 192.168.1.125 | 000c297cc5ae |
| 3232235828 | 192.168.1.52 | 005056a1ad60 |
| 3232235895 | 192.168.1.119 | 000c29497e4e |
| 3232235784 | 192.168.1.8 | 000000000000 |
| 3232235777 | 192.168.1.1 | |
| 3232235807 | 192.168.1.31 | 005056b1488d |
| 3232235793 | 192.168.1.17 | 005056b1a93f |
| 3232235988 | 192.168.1.212 |
2. Get an index of host fields
Retrieves an index of Forescout host fields that match the specified criteria.
Base Command
forescout-get-host-fields
Input
| Argument Name | Description | Required |
|---|---|---|
| search_in | Each host field has three searchable parts, the ‘name’, ‘label’, and ‘description’. By default only the ‘name’ will be searched. If you want to expand the search to include the description, you would enter ‘name,description’ for this argument. | Optional |
| case_sensitive | Determines whether to match the case of the entered search term. | Optional |
| match_exactly | Determines whether the search term is matched against the entirety of the potential host field instead of just seeing whether the host field contains the search term. | Optional |
| search_term | The term to filter host fields by. By default, the search will be case insensitive and checked to see if a host field contains the search term unless otherwise specified in the ‘case_sensitive’ and ‘match_exactly’ arguments respectively. | Optional |
| host_field_type | Limit the search to host fields whose values are of a certain type. For example, to limit the search to host properties whose values are either boolean, ip or a date enter ‘boolean,ip,date’. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Forescout.HostField | Unknown | List index of host properties. |
Command Example
forescout-get-host-fields search_term=hostname case_sensitive=false host_field_type=tree_path,string match_exactly=False search_in=name,label,description
Context Example
{
"Forescout.HostField": [
{
"Name": "nbthost",
"Type": "string",
"Description": "Indicates the NetBIOS hostname of the host.",
"Label": "NetBIOS Hostname"
},
{
"Name": "hostname",
"Type": "string",
"Description": "Indicates the DNS name of the host.",
"Label": "DNS Name"
},
{
"Name": "aws_instance_public_dns",
"Type": "string",
"Description": "The public hostname of the EC2 instance, which resolves to the public IP address or Elastic IP address of the instance.",
"Label": "EC2 Public DNS"
},
{
"Name": "dhcp_hostname",
"Type": "string",
"Description": "The device Host Name as advertised by DHCP",
"Label": "DHCP Hostname"
},
{
"Name": "linux_hostname",
"Type": "string",
"Description": "Indicates a hostname. Use of this property requires that the host is managed by CounterACT via SecureConnector or remotely.",
"Label": "Linux Hostname"
},
{
"Name": "mac_hostname",
"Type": "string",
"Description": "Indicates a hostname. Use of this property requires that the host is managed by CounterACT via SecureConnector or remotely.",
"Label": "Macintosh Hostname"
},
{
"Name": "sw_hostname",
"Type": "string",
"Description": "The switch name as defined in the switch",
"Label": "Switch Hostname"
},
{
"Name": "wifi_end_point_host_name",
"Type": "string",
"Description": "",
"Label": "WiFi End Point Hostname"
},
{
"Name": "vmware_guest_host",
"Type": "string",
"Description": "Indicates the hostname of the guest operating system. VMware Tools must be running on the endpoint to resolve this property.",
"Label": "Virtual Machine Guest Hostname"
},
{
"Name": "vmware_esxi_hostname",
"Type": "string",
"Description": "Indicates the hostname of the ESXi server.",
"Label": "VMware ESXi Server Name"
},
{
"Name": "wifi_client_hostname",
"Type": "string",
"Description": "Indicates the user name of the client.",
"Label": "WLAN Client Username"
}
]
}
Human Readable Output
Index of Host Fields
| Label | Name | Description | Type |
|---|---|---|---|
| NetBIOS Hostname | nbthost | Indicates the NetBIOS hostname of the host. | string |
| DNS Name | hostname | Indicates the DNS name of the host. | string |
| EC2 Public DNS | aws_instance_public_dns | The public hostname of the EC2 instance, which resolves to the public IP address or Elastic IP address of the instance. | string |
| DHCP Hostname | dhcp_hostname | The device Host Name as advertised by DHCP | string |
| Linux Hostname | linux_hostname | Indicates a hostname. Use of this property requires that the host is managed by CounterACT via SecureConnector or remotely. | string |
| Macintosh Hostname | mac_hostname | Indicates a hostname. Use of this property requires that the host is managed by CounterACT via SecureConnector or remotely. | string |
| Switch Hostname | sw_hostname | The switch name as defined in the switch | string |
| WiFi End Point Hostname | wifi_end_point_host_name | string | |
| Virtual Machine Guest Hostname | vmware_guest_host | Indicates the hostname of the guest operating system. VMware Tools must be running on the endpoint to resolve this property. | string |
| VMware ESXi Server Name | vmware_esxi_hostname | Indicates the hostname of the ESXi server. | string |
| WLAN Client Username | wifi_client_hostname | Indicates the user name of the client. | string |
3. Get details for a host
Retrieves details of specified host.
Base Command
forescout-get-host
Input
| Argument Name | Description | Required |
|---|---|---|
| fields | List of host properties to include in the output for the targeted endpoint. If a specified host property is not found, the property is omitted from the outputs. For a list of potential host properties that may be specified, try executing the ‘forescout-get-host-fields’ command. Requested fields should be comma separated. | Optional |
| ip | IP (ipv4) of the desired endpoint. Endpoint identifiers - IPs, MAC addresses and object IDs - can be found in the returned outputs when forescout-get-hosts is executed. |
Optional |
| mac | MAC address of the desired endpoint. Endpoint identifiers - IPs, MAC addresses and object IDs - can be found in the returned outputs when forescout-get-hosts is executed. |
Optional |
| id | Forescout ID of the desired endpoint. Endpoint identifiers - IPs, MAC addresses and object IDs - can be found in the returned outputs when forescout-get-hosts is executed. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Forescout.Host.MatchedFingerprint | Unknown | An endpoint may match multiple profiles. This property indicates all the classification profiles that this endpoint matches. |
| Forescout.Host.EngineSeenPacket | String | Indicates the host was seen by CounterACT. |
| Forescout.Host.Online | String | Host is online. |
| Forescout.Host.PrimClassification | String | Indicates the most specific endpoint function detected. If CounterACT detects multiple endpoint functions, the property is resolved as the most specific value that is common to all the detected functions. If there is no common value, the property is resolved as ‘Multiple Suggestions’. |
| Forescout.Host.MacVendorString | String | Indicates a value associated with the NIC Vendor |
| Forescout.Host.SambaOpenPort | String | NetBIOS ports are open |
| Forescout.Host.UserDefFp | String | Indicates the operating system of the endpoint, as determined by classification tools. |
| Forescout.Host.Vendor | String | Network Device Vendor, Type and Model |
| Forescout.Host.AgentVersion | String | Indicates the SecureConnector version installed on a Windows host. |
| Forescout.Host.Fingerprint | String | Passive OS detection based on Syn packets |
| Forescout.Host.AccessIP | String | Indicates the last IP that was investigated for this host |
| Forescout.Host.VendorClassification | String | Indicates the most specific vendor and model detected. |
| Forescout.Host.ManageAgent | String | Indicates if the host is running SecureConnector. |
| Forescout.Host.Onsite | String | Indicates that a host is connected to the organizational network |
| Forescout.Host.MacPrefix32 | String | MAC prefix |
| Forescout.Host.VaNetfunc | String | Reported CDP VoIP device description for VA netfunc |
| Forescout.Host.NmapDefFp7 | String | Nmap-OS Fingerprint(Ver. 7.01) |
| Forescout.Host.NmapDefFp5 | String | Nmap-OS Fingerprint(Ver. 5.3) |
| Forescout.Host.AgentInstallMode | String | Indicates the SecureConnector deployment mode installed on the host. |
| Forescout.Host.NmapFp7 | String | Nmap-OS Class(Ver. 7.01) (Obsolete) |
| Forescout.Host.ClType | String | Indicates how CounterACT determines the Network Function property of the endpoint. |
| Forescout.Host.ClRule | String | Indicates the rule responsible for classifying the host |
| Forescout.Host.AgentVisibleMode | String | Indicates the SecureConnector visible mode installed on the host. |
| Forescout.Host.OSClassification | String | Operating System |
| Forescout.Host.ClassificationSourceOS | String | Indicates how the Operating System classification property was determined for this endpoint. |
| Forescout.Host.LastNbtReportTime | String | Last time when NBT name was reported |
| Forescout.Host.Misc | String | Miscellaneous |
| Forescout.Host.ClassificationSourceFunc | String | Indicates how the Function classification property was determined for this endpoint. |
| Forescout.Host.NmapNetfunc7 | String | Nmap-Network Function(Ver. 7.01) |
| Forescout.Host.MAC | Unknown | ARP Spoofing (Obsolete) |
| Forescout.Host.OpenPort | Unknown | Open Ports |
| Forescout.Host.GstSignedInStat | String | Logged In Status |
| Forescout.Host.DhcpClass | String | The device class according to the DHCP fingerprint |
| Forescout.Host.ADM | String | Admission Events. |
| Forescout.Host.DhcpReqFingerprint | String | The host DHCP request fingerprint |
| Forescout.Host.DhcpOptFingerprint | String | The host DHCP options fingerprint |
| Forescout.Host.Ipv4ReportTime | String | Indicates the last time that IPv4 reported to the infrastructure |
| Forescout.Host.DhcpOS | String | The device OS according to the DHCP fingerprint |
| Forescout.Host.DhcpHostname | String | The device Host Name as advertised by DHCP |
| Forescout.Host.IPAddress | String | Host IP address |
| Forescout.Host.MACAddress | String | Host MAC address |
| Forescout.Host.ID | Number | Forescout ID number for the host |
| Endpoint.IPAddress | String | IP Address of the host. |
| Endpoint.MACAddress | String | MAC Address of the host. |
| Endpoint.DHCPServer | String | Endpoint DHCP Server. |
| Endpoint.Hostname | String | Hostname of the endpoint. |
| Endpoint.OS | String | Endpoint Operating System. |
| Endpoint.Model | String | Vendor and Model of the endpoint. |
| Endpoint.Domain | String | Domain of the endpoint. |
Command Example
forescout-get-host ip=192.168.1.212 fields=fsapi_DemistoTest,fsapi_demisto_composite,fsapi_demisto_list,fsapi_composite_1
Context Example
{
"Forescout.Host": {
"MACAddress": null,
"IPAddress": "192.168.1.212",
"ID": "3232235988"
},
"Endpoint": {
"MACAddress": null,
"IPAddress": "192.168.1.212"
}
}
Human Readable Output
Endpoint Details for IP=192.168.1.212
| ID | IPAddress |
|---|---|
| 3232235988 | 192.168.1.212 |
4. Get a list of policies
Retrieves a list of all policies defined in the Forescout platform and
their sub-rules.
Base Command
forescout-get-policies
Input
There are no input arguments for this command.
Context Output
| Path | Type | Description |
|---|---|---|
| Forescout.Policy.ID | String | Forescout ID for the policy. |
| Forescout.Policy.Name | String | Forescout name of the policy. |
| Forescout.Policy.Description | String | Description of the policy. |
| Forescout.Policy.Rule | Unknown | List of rules that make up the policy. |
Command Example
forescout-get-policies
Context Example
{
"Forescout.Policy": [
{
"Name": "Primary Classification",
"Description": "",
"Rule": [
{
"Name": "CounterACT Devices",
"Description": "",
"ID": "-1203369125012565008"
},
{
"Name": "NAT Devices",
"Description": "When a device is NAT, its other classifications may be inaccurate. Therefore, we put the NAT detection first.",
"ID": "-5021668745466479821"
},
{
"Name": "Printers",
"Description": "",
"ID": "-275357014618763061"
},
{
"Name": "VoIP Devices",
"Description": "",
"ID": "4202614624411873493"
},
{
"Name": "Networking Equipment",
"Description": "",
"ID": "195929949297431248"
},
{
"Name": "Storage",
"Description": "",
"ID": "-6750955562195414496"
},
{
"Name": "Windows",
"Description": "",
"ID": "-6030907744367556977"
},
{
"Name": "Macintosh",
"Description": "",
"ID": "2278199708439440583"
},
{
"Name": "Linux\\Unix",
"Description": "",
"ID": "-7562731206926229799"
},
{
"Name": "Mobile Devices",
"Description": "",
"ID": "4030118542035508409"
},
{
"Name": "Approved Misc Devices",
"Description": "",
"ID": "168049340370707647"
},
{
"Name": "Multiple Profile Matches",
"Description": "Endpoints matching this sub-rule could not have either their Function or Operating System determined due to conflicting profile matches.\n\nInvestigate the devices in this sub-rule and either manually classify them or build additional sub-rules to classify them based on patterns you observe. View the values Suggested Function and Suggested Operating System properties to discover the conflicting profile matches.",
"ID": "8701509617393717735"
},
{
"Name": "Other Known Function",
"Description": "",
"ID": "-642863379250182254"
},
{
"Name": "Other Known Operating System",
"Description": "",
"ID": "-4200038946418694277"
},
{
"Name": "Other Known Vendor",
"Description": "",
"ID": "150826048313755731"
},
{
"Name": "Unclassified",
"Description": "",
"ID": "-8959326502596556700"
}
],
"ID": "2101168655015691125"
},
{
"Name": "Corporate/Guest Control",
"Description": "",
"Rule": [
{
"Name": "Corporate Hosts",
"Description": "",
"ID": "2240420499151482925"
},
{
"Name": "Signed-in Guests",
"Description": "",
"ID": "1248354759835029874"
},
{
"Name": "Guest Hosts",
"Description": "",
"ID": "9151906460028315616"
}
],
"ID": "-7733328397206852516"
},
{
"Name": "Antivirus Compliance",
"Description": "",
"Rule": [
{
"Name": "Not Manageable",
"Description": "Optional step: Make Windows machines managable by installing the Secure Connector",
"ID": "7661917523791823306"
},
{
"Name": "AV Not Installed",
"Description": "Antivirus is not installed.",
"ID": "-2012169476997908764"
},
{
"Name": "AV Not Running",
"Description": "Antivirus is not running.",
"ID": "8013197435392890209"
},
{
"Name": "AV Not Updated",
"Description": "Antivirus is not updated.",
"ID": "6048295467368903309"
},
{
"Name": "Compliant",
"Description": "",
"ID": "-7389372863827790785"
}
],
"ID": "-4928940807449738209"
},
{
"Name": "sadfsafg",
"Description": "asdf",
"Rule": [],
"ID": "267720461254861999"
}
]
}
Human Readable Output
Forescout Policies
| ID | Name | Description | Rule |
|---|---|---|---|
| 2101168655015691125 | Primary Classification | ID: -1203369125012565008, Name: CounterACT Devices, Description: , ID: -5021668745466479821, Name: NAT Devices, Description: When a device is NAT, its other classifications may be inaccurate. Therefore, we put the NAT detection first., ID: -275357014618763061, Name: Printers, Description: , ID: 4202614624411873493, Name: VoIP Devices, Description: , ID: 195929949297431248, Name: Networking Equipment, Description: , ID: -6750955562195414496, Name: Storage, Description: , ID: -6030907744367556977, Name: Windows, Description: , ID: 2278199708439440583, Name: Macintosh, Description: , ID: -7562731206926229799, Name: Linux\Unix, Description: , ID: 4030118542035508409, Name: Mobile Devices, Description: , ID: 168049340370707647, Name: Approved Misc Devices, Description: , ID: 8701509617393717735, Name: Multiple Profile Matches, Description: Endpoints matching this sub-rule could not have either their Function or Operating System determined due to conflicting profile matches.\n\nInvestigate the devices in this sub-rule and either manually classify them or build additional sub-rules to classify them based on patterns you observe. View the values Suggested Function and Suggested Operating System properties to discover the conflicting profile matches., ID: -642863379250182254, Name: Other Known Function, Description: , ID: -4200038946418694277, Name: Other Known Operating System, Description: , ID: 150826048313755731, Name: Other Known Vendor, Description: , ID: -8959326502596556700, Name: Unclassified, Description: |
|
| -7733328397206852516 | Corporate/Guest Control | ID: 2240420499151482925, Name: Corporate Hosts, Description: , ID: 1248354759835029874, Name: Signed-in Guests, Description: , ID: 9151906460028315616, Name: Guest Hosts, Description: |
|
| -4928940807449738209 | Antivirus Compliance | ID: 7661917523791823306, Name: Not Manageable, Description: Optional step: Make Windows machines managable by installing the Secure Connector, ID: -2012169476997908764, Name: AV Not Installed, Description: Antivirus is not installed., ID: 8013197435392890209, Name: AV Not Running, Description: Antivirus is not running., ID: 6048295467368903309, Name: AV Not Updated, Description: Antivirus is not updated., ID: -7389372863827790785, Name: Compliant, Description: |
|
| 267720461254861999 | sadfsafg | asdf |
5. Update host fields
Update a host’s field. Note that if a List field or Composite field has not been defined in Forescout to ‘Aggregate new values from each update’ that performing an update operation on a field will overwrite previous data written to that field.
Base Command
forescout-update-host-fields
Input
| Argument Name | Description | Required |
|---|---|---|
| update_type | The type of update to perform on a host field. | Optional |
| host_ip | The IP address of the target host. Required if ‘updated_type’ is ‘update’ or ‘delete’. | Required |
| field | Enter the the name of the field to update. Composite fields should be updated using the ‘fields_json’ command argument. | Optional |
| value | Value to be assigned to the field specified in the ‘field’ argument. If the value is a list of items, then items should be separated using a comma. | Optional |
| fields_json | One may perform multiple field-value assignments using this command argument. The argument should be entered in valid JSON format. This argument is useful for setting composite fields although other fields may be entered as well. For example, ‘{“Example_Composite”: [{“Shape”: “Triangle”, “Color”: “Beige”}, {“Shape”: “Square”, “Color”: “Violet”}], “String_Field”: “Example”}’ where ‘Example_Composite’ is the name of the Composite field in Forescout and ‘Shape’ and ‘Color’ are sub fields. In the example, ‘String_Field’ is a regular host field of type string whose value will be assigned ‘Example’. If the composite field was defined in Forescout as an aggregate property then additional records will be appended, otherwise they will be overwritten. | Optional |
Context Output
There is no context output for this command.
Command Example
forescout-update-host-fields host_ip=192.168.1.212 field=fsapi_DemistoTest value="Testing new Arguments" fields_json={“fsapi_demisto_composite”:
{“age”: “900”, “name”: “Ignatio Permutti”, “alive”: “false”}, “fsapi_demisto_list”:
[“Hey1”, “Hey2”, “Hey3”], “fsapi_composite_1”: [{“animal”: “mongoose”, “sound”:
“squeak”, “lifespan”: “10”, “region”: “North America”}, {“animal”: “squirrel”,
“sound”: “pip”, “lifespan”: “5”, “region”: “Everywher”}]}update_type=update
Human Readable Output
Successfully updated 4 properties for host ip=192.168.1.212
6. Update lists
Updates Forescout lists.
Base Command
forescout-update-lists
Input
| Argument Name | Description | Required |
|---|---|---|
| update_type | The type of update to perform on a Forescout list. | Optional |
| list_names | Names of lists defined in the Forescout platform that you wish to update. If the ‘update_type’ is set to ‘delete_all_list_values’ then it is unnecessary to fill in the ‘values’ command argument. Multiple list names should be separated by a comma. To find names of lists that may be updated, navigate to Tools > Options > Lists in the Forescout platform. | Required |
| values | The values to add or delete from the lists entered in the ‘list_names’ command argument. Multiple values should separated by a comma. Note that the values entered here will be updated for all of the lists entered in the ‘list_names’ command argument. | Optional |
Context Output
There is no context output for this command.
Command Example
forescout-update-lists list_names=disallowed_names,creatures update_type=add_list_values values="ignatius,devon"
Human Readable Output
Successfully added values to the 2 lists.
Configuration parameters
url— The network address of the Forescout Enterprise Manager or standalone Appliance, e.g., 'https://10.0.0.8'. #disable-secrets-detection (required)web_api_credentials— Web API Username (see Detailed Instructions (?))dex_credentials— Data Exchange (DEX) Username (see Detailed Instructions (?))dex_account— Data Exchange (DEX) Account (see Detailed Instructions (?))timeout— HTTP Timeoutinsecure— Trust any certificate (not secure)proxy— Use system proxy settings
Commands (6)
-
forescout-get-hostRetrieves details of specified host.
-
forescout-get-host-fieldsRetrieves an index of Forescout host fields that match the specified criteria.
-
forescout-get-hostsRetrieves a list of active endpoints.
-
forescout-get-policiesRetrieves a list of all policies defined in the Forescout platform and their sub-rules.
-
forescout-update-host-fieldsUpdate a host's field. If a List field or Composite field has not been defined in Forescout to 'Aggregate new values from each update' then performing an update operation on a field will overwrite previous data written to that field.
-
forescout-update-listsUpdate Forescout lists.
import demistomock as demisto from CommonServerPython import * from CommonServerUserPython import * """ IMPORTS """ import json import xml.etree.ElementTree as ET_PHONE_HOME from copy import deepcopy from datetime import UTC, datetime, timedelta from typing import Any, cast import requests import urllib3 from dateutil.parser import parse as parsedate # Disable insecure warnings urllib3.disable_warnings() """ GLOBALS/PARAMS """ PARAMS = demisto.params() WEB_API_CREDENTIALS = PARAMS.get("web_api_credentials") WEB_API_CREDENTIALS = WEB_API_CREDENTIALS if WEB_API_CREDENTIALS else {} WEB_API_USERNAME = WEB_API_CREDENTIALS.get("identifier", "") WEB_API_PASSWORD = WEB_API_CREDENTIALS.get("password", "") DEX_CREDENTIALS = PARAMS.get("dex_credentials") DEX_CREDENTIALS = DEX_CREDENTIALS if DEX_CREDENTIALS else {} DEX_USERNAME = DEX_CREDENTIALS.get("identifier", "") DEX_PASSWORD = DEX_CREDENTIALS.get("password", "") DEX_ACCOUNT = PARAMS.get("dex_account", "") DEX_ACCOUNT = DEX_ACCOUNT if DEX_ACCOUNT else "" # Remove trailing slash to prevent wrong URL path to service BASE_URL = PARAMS.get("url", "").strip().rstrip("/") # Should we use SSL USE_SSL = not PARAMS.get("insecure", False) try: HTTP_TIMEOUT = int(demisto.params().get("timeout", 60)) except ValueError as e: demisto.debug(f"Failed casting timeout parameter to int, falling back to 60 - {e}") HTTP_TIMEOUT = 60 WEB_AUTH = "" LAST_JWT_FETCH = None # Default JWT validity time set in Forescout Web API JWT_VALIDITY_TIME = timedelta(minutes=5) DEX_AUTH = (DEX_USERNAME + "@" + DEX_ACCOUNT, DEX_PASSWORD) DEX_HEADERS = {"Content-Type": "application/xml", "Accept": "application/xml"} # Host fields to be included in output of get_host_command HOSTFIELDS_TO_INCLUDE = { "os_classification": "OSClassification", "classification_source_os": "ClassificationSourceOS", "onsite": "Onsite", "access_ip": "AccessIP", "macs": "MAC", "openports": "OpenPort", "mac_vendor_string": "MacVendorString", "cl_type": "ClType", "cl_rule": "ClRule", "vendor": "Vendor", "fingerprint": "Fingerprint", "gst_signed_in_stat": "GstSignedInStat", "misc": "Misc", "prim_classification": "PrimClassification", "agent_install_mode": "AgentInstallMode", "vendor_classification": "VendorClassification", "user_def_fp": "UserDefFp", "agent_visible_mode": "AgentVisibleMode", "classification_source_func": "ClassificationSourceFunc", "dhcp_class": "DhcpClass", "samba_open_ports": "SambaOpenPort", "mac_prefix32": "MacPrefix32", "adm": "ADM", "last_nbt_report_time": "LastNbtReportTime", "agent_version": "AgentVersion", "matched_fingerprints": "MatchedFingerprint", "manage_agent": "ManageAgent", "dhcp_req_fingerprint": "DhcpReqFingerprint", "dhcp_opt_fingerprint": "DhcpOptFingerprint", "online": "Online", "nmap_def_fp7": "NmapDefFp7", "ipv4_report_time": "Ipv4ReportTime", "nmap_def_fp5": "NmapDefFp5", "va_netfunc": "VaNetfunc", "dhcp_os": "DhcpOS", "engine_seen_packet": "EngineSeenPacket", "nmap_netfunc7": "NmapNetfunc7", "nmap_fp7": "NmapFp7", "dhcp_hostname": "DhcpHostname", } """ HELPER FUNCTIONS """ def check_web_api_credentials(): """ Verify that credentials were entered for Data Exchange (DEX) """ if not (WEB_API_USERNAME and WEB_API_PASSWORD): err_msg = ( "Error in Forescout Integration - Web API credentials must" " be entered in the Forescout integration configuration in order to execute this command." ) return_error(err_msg) def check_dex_credentials(): """ Verify that credentials were entered for Data Exchange (DEX) """ if not (DEX_USERNAME and DEX_PASSWORD and DEX_ACCOUNT): err_msg = ( "Error in Forescout Integration - Data Exchange (DEX) credentials must" " be entered in the Forescout integration configuration in order to execute this command." ) return_error(err_msg) def create_update_lists_request_body(update_type: str, lists: str) -> ET_PHONE_HOME.Element: """ Create XML request body formatted to DEX expectations Parameters ---------- update_type : str The type of update to execute. lists : str The list names and associated values to update the list with. Returns ------- XML Request Body Element """ root = ET_PHONE_HOME.Element("FSAPI", attrib={"TYPE": "request", "API_VERSION": "2.0"}) transaction = ET_PHONE_HOME.SubElement(root, "TRANSACTION", attrib={"TYPE": update_type}) lists_xml = ET_PHONE_HOME.SubElement(transaction, "LISTS") if lists: list_val_pairs = lists.split("&") for list_val_pair in list_val_pairs: list_name, *values = list_val_pair.split("=") list_xml = ET_PHONE_HOME.SubElement(lists_xml, "LIST", attrib={"NAME": list_name}) if update_type != "delete_all_list_values" and values: list_of_vals = "=".join(values).split(":") for val in list_of_vals: val_xml = ET_PHONE_HOME.SubElement(list_xml, "VALUE") val_xml.text = val return root def create_update_hostfields_request_body( host_ip: str, update_type: str, field: str, value: str, fields_json: str ) -> ET_PHONE_HOME.Element: """ Create XML request body formatted to DEX expectations Parameters ---------- host_ip : str IP address of the target host. update_type : str The type of update to execute. field : str The host field to update. value : str The value to assign to the specified host field. fields_json: str Field-value pairs in valid JSON format. Useful for Forescout composite fields. Returns ------- XML Request Body Element """ root = ET_PHONE_HOME.Element("FSAPI", attrib={"TYPE": "request", "API_VERSION": "2.0"}) transaction = ET_PHONE_HOME.SubElement(root, "TRANSACTION", attrib={"TYPE": update_type}) if update_type == "update": ET_PHONE_HOME.SubElement(transaction, "OPTIONS", attrib={"CREATE_NEW_HOST": "false"}) ET_PHONE_HOME.SubElement(transaction, "HOST_KEY", attrib={"NAME": "ip", "VALUE": host_ip}) props_xml = ET_PHONE_HOME.SubElement(transaction, "PROPERTIES") # parse fields_json non_composite_fields = {} composite_fields: dict[Any, Any] = {} if fields_json: fields_json_dict = json.loads(fields_json) for key, val in fields_json_dict.items(): if isinstance(val, dict): composite_fields[key] = val elif isinstance(val, list): if len(val) >= 1 and isinstance(val[0], dict): composite_fields[key] = val else: non_composite_fields[key] = val else: non_composite_fields[key] = val # put non-composite fields all together if field: non_composite_fields[field] = argToList(value) for key, val in non_composite_fields.items(): prop_xml = ET_PHONE_HOME.SubElement(props_xml, "PROPERTY", attrib={"NAME": key}) if update_type != "delete": if isinstance(val, list): for sub_val in val: val_xml = ET_PHONE_HOME.SubElement(prop_xml, "VALUE") val_xml.text = sub_val else: val_xml = ET_PHONE_HOME.SubElement(prop_xml, "VALUE") val_xml.text = val if composite_fields: for table_prop_name, values in composite_fields.items(): table_property_xml = ET_PHONE_HOME.SubElement(props_xml, "TABLE_PROPERTY", attrib={"NAME": table_prop_name}) if update_type == "update": if isinstance(values, list): for row in values: row_xml = ET_PHONE_HOME.SubElement(table_property_xml, "ROW") for key, val in row.items(): key_xml = ET_PHONE_HOME.SubElement(row_xml, "CPROPERTY", attrib={"NAME": key}) if isinstance(val, list): for sub_val in val: value_xml = ET_PHONE_HOME.SubElement(key_xml, "CVALUE") value_xml.text = sub_val else: value_xml = ET_PHONE_HOME.SubElement(key_xml, "CVALUE") value_xml.text = val else: row_xml = ET_PHONE_HOME.SubElement(table_property_xml, "ROW") for key, val in values.items(): key_xml = ET_PHONE_HOME.SubElement(row_xml, "CPROPERTY", attrib={"NAME": key}) if isinstance(val, list): for sub_val in val: value_xml = ET_PHONE_HOME.SubElement(key_xml, "CVALUE") value_xml.text = sub_val else: value_xml = ET_PHONE_HOME.SubElement(key_xml, "CVALUE") value_xml.text = val return root def filter_hostfields_data(args: dict, data: dict) -> list: """ Filter host fields data by get_host_fields_command arguments. Parameters ---------- args : dict The get_host_fields_command arguments. data : dict The data to filter. Returns ------- list Filtered list of hostfields """ search_term = args.get("search_term") host_fields = data.get("hostFields", []) host_field_type = args.get("host_field_type", "all_types") if not search_term: # Still check to see if should filter host fields by their type if host_field_type == "all_types": return host_fields else: host_field_types = argToList(host_field_type) filtered_hostfields = [] for host_field in host_fields: if host_field.get("type") in host_field_types: filtered_hostfields.append(host_field) return filtered_hostfields case_sensitive = args.get("case_sensitive", "false") case_sensitive = case_sensitive.casefold() != "false" if not case_sensitive: search_term = search_term.casefold() match_exactly = args.get("match_exactly", "False") match_exactly = match_exactly.casefold() != "false" if host_field_type != "all_types": host_field_type = argToList(host_field_type) search_in = args.get("search_in", "name") search_in = argToList(search_in) filtered_hostfields = [] for host_field in host_fields: if isinstance(host_field_type, list) and host_field.get("type") not in host_field_type: continue vals_to_search = [host_field.get(part) for part in search_in] vals_to_search = ["" if val is None else val for val in vals_to_search] for val in vals_to_search: val_to_search = val if not case_sensitive: val_to_search = val.casefold() if match_exactly: if search_term == val_to_search: filtered_hostfields.append(host_field) break continue else: if search_term in val_to_search: filtered_hostfields.append(host_field) break return filtered_hostfields def dict_to_formatted_string(dictionary: dict | list) -> str: """ Return dictionary as clean string for war room output. Parameters ---------- dictionary : dict | list The dictionary or list to format as a string. Returns ------- str Clean string version of a dictionary Examples -------- >>> example_dict = {'again': 'FsoD', ... 'church': {'go': 'pArcB', 'month': '2009-08-11 16:42:51'}, ... 'production': 5507, ... 'so': [9350, 'awzn', 7105, 'mMRxc']} >>> dict_to_formatted_string(example_dict) 'again: FsoD, church: {go: pArcB, month: 2009-08-11 16:42:51}, production: 5507, so: [9350, awzn, 7105, mMRxc]' """ return json.dumps(dictionary).lstrip("{").rstrip("}").replace("'", "").replace('"', "") def format_policies_data(data: dict) -> list: """ Return policies formatted to Demisto standards. Parameters ---------- data : dict The data returned from making API call to Forescout Web API policies endpoint. Returns ------- list Formatted Policies """ formatted_policies = [] policies = data.get("policies", []) for policy in policies: formatted_policy = { "ID": str(policy.get("policyId")), "Name": policy.get("name"), "Description": policy.get("description"), } formatted_rules = [] rules = policy.get("rules", []) for rule in rules: formatted_rule = {"ID": str(rule.get("ruleId")), "Name": rule.get("name"), "Description": rule.get("description")} formatted_rules.append(formatted_rule) formatted_policy["Rule"] = formatted_rules formatted_policies.append(formatted_policy) return formatted_policies def create_web_api_headers() -> dict: """ Update JWT if it has expired and return headers object that formats to Forescout Web API expectations Returns ------- dict Headers object for the Forescout Web API calls """ web_api_login() headers = {"Authorization": WEB_AUTH, "Accept": "application/hal+json"} return headers def web_api_login(): """ Get a JWT (Javascript Web Token) for authorization in calls to Web API """ global LAST_JWT_FETCH global WEB_AUTH if not LAST_JWT_FETCH or datetime.now(UTC) >= LAST_JWT_FETCH + JWT_VALIDITY_TIME: url_suffix = "/api/login" headers = {"Content-Type": "application/x-www-form-urlencoded"} params = {"username": WEB_API_USERNAME, "password": WEB_API_PASSWORD} response = http_request("POST", url_suffix, headers=headers, params=params, resp_type="response") fetch_time = parsedate(response.headers.get("Date", "")) WEB_AUTH = response.text LAST_JWT_FETCH = fetch_time def http_request( method: str, url_suffix: str, full_url: str = None, headers: dict = None, auth: tuple = None, params: dict = None, data: dict = None, files: dict = None, timeout: float = HTTP_TIMEOUT, resp_type: str = "json", ) -> Any: """ A wrapper for requests lib to send our requests and handle requests and responses better Parameters ---------- method : str HTTP method, e.g. 'GET', 'POST' ... etc. url_suffix : str API endpoint. full_url : str Bypasses the use of BASE_URL + url_suffix. Useful if there is a need to make a request to an address outside of the scope of the integration API. headers : dict Headers to send in the request. auth : tuple Auth tuple to enable Basic/Digest/Custom HTTP Auth. params : dict URL parameters. data : dict Data to be sent in a 'POST' request. files : dict File data to be sent in a 'POST' request. timeout : int The amount of time in seconds a Request will wait for a client to establish a connection to a remote machine. resp_type : str Determines what to return from having made the HTTP request. The default is 'json'. Other options are 'text', 'content' or 'response' if the user would like the full response object returned. Returns ------- dict | str | bytes | obj Response JSON from having made the request. """ try: address = full_url if full_url else BASE_URL + url_suffix res = requests.request( method, address, verify=USE_SSL, params=params, data=data, files=files, headers=headers, auth=auth, # type: ignore[arg-type] timeout=timeout, ) # Handle error responses gracefully if res.status_code not in {200, 304}: err_msg = f"Error in Forescout Integration API call [{res.status_code}] - {res.reason}" try: # Try to parse json error response res_json = res.json() message = res_json.get("message") if message.endswith(" See log for more details."): message = message.replace(" See log for more details.", "") err_msg += f"\n{message}" return_error(err_msg) except json.decoder.JSONDecodeError: if res.status_code in {400, 401, 501}: # Try to parse xml error response resp_xml = ET_PHONE_HOME.fromstring(res.content) codes = [child.text for child in resp_xml.iter() if child.tag == "CODE"] messages = [child.text for child in resp_xml.iter() if child.tag == "MESSAGE"] err_msg += "".join([f"\n{code}: {msg}" for code, msg in zip(codes, messages)]) return_error(err_msg) resp_type = resp_type.casefold() try: if resp_type == "json": return res.json() elif resp_type == "text": return res.text elif resp_type == "content": return res.content else: return res except json.decoder.JSONDecodeError: return_error(f"Failed to parse json object from response: {res.content!r}") except requests.exceptions.ConnectTimeout: err_msg = ( "Connection Timeout Error - potential reasons may be that the Server URL parameter" " is incorrect or that the Server is not accessible from your host." ) return_error(err_msg) except requests.exceptions.SSLError: err_msg = "SSL Certificate Verification Failed - try selecting 'Trust any certificate' in the integration configuration." return_error(err_msg) except requests.exceptions.ProxyError: err_msg = "Proxy Error - if 'Use system proxy' in the integration configuration has been selected, try deselecting it." return_error(err_msg) except requests.exceptions.ConnectionError as e: # Get originating Exception in Exception chain while "__context__" in dir(e) and e.__context__: e = cast(Any, e.__context__) error_class = str(e.__class__) err_type = "<" + error_class[error_class.find("'") + 1 : error_class.rfind("'")] + ">" err_msg = ( f"\nERRTYPE: {err_type}\nERRNO: [{e.errno}]\nMESSAGE: {e.strerror}\n" f"ADVICE: Check that the Server URL parameter is correct and that you" f" have access to the Server from your host." ) return_error(err_msg) """ COMMANDS + REQUESTS FUNCTIONS """ def test_module(): """ Performs API calls to Forescout Web API and DEX that require proper authentication """ if WEB_API_USERNAME and WEB_API_PASSWORD: get_hosts({}) if DEX_USERNAME and DEX_PASSWORD and DEX_ACCOUNT: update_lists({"update_type": "add_list_values"}) demisto.results("ok") def get_host(args): fields = args.get("fields", "") ip = args.get("ip", "") mac = args.get("mac", "") id = args.get("id", "") url_suffix = "/api/hosts/" if not (ip or mac or id): err_msg = ( "One of the command arguments, 'ip', 'mac' or 'id' must be entered in order to identify the endpoint to retrieve. " ) return_error(err_msg) if ip: # API endpoint format - https://{EM.IP}/api/hosts/ip/{ipv4}?fields={prop},..,{prop_n} url_suffix += "ip/" + ip elif mac: # API endpoint format - https://{EM.IP}/api/hosts/mac/{mac}?fields={prop},..,{prop_n} url_suffix += "mac/" + mac elif id: # API endpoint format - https://{EM.IP}/api/hosts/{obj_ID}?fields={prop},..,{prop_n} url_suffix += id params = {"fields": fields} if fields != "" else None headers = create_web_api_headers() response_data = http_request("GET", url_suffix, headers=headers, params=params, resp_type="json") return response_data def get_host_command(): check_web_api_credentials() args = demisto.args() ip = args.get("ip", "") mac = args.get("mac", "") id = args.get("id", "") identifier = "IP=" + ip if ip else ("MAC=" + mac if mac else "ID=" + id) requested_fields = argToList(args.get("fields", "")) data = get_host(args) host = data.get("host", {}) fields = host.get("fields", {}) included_fields = {HOSTFIELDS_TO_INCLUDE.get(key, key): val for key, val in fields.items()} for key, val in included_fields.items(): if isinstance(val, list): new_val = [item.get("value") for item in val] included_fields[key] = new_val else: included_fields[key] = val.get("value") if not requested_fields: for key in list(included_fields.keys()): if key not in HOSTFIELDS_TO_INCLUDE.values(): del included_fields[key] included_fields_readable = {} for key, val in included_fields.items(): included_fields_readable[key] = dict_to_formatted_string(val) if isinstance(val, dict | list) else val content = {"ID": str(host.get("id")), "IPAddress": host.get("ip", ""), "MACAddress": host.get("mac", ""), **included_fields} # Construct endpoint object from API data according to Demisto conventions endpoint = {"IPAddress": host.get("ip", ""), "MACAddress": host.get("mac", "")} dhcp_server = fields.get("dhcp_server", {}).get("value") if dhcp_server: endpoint["DHCPServer"] = dhcp_server hostname = fields.get("hostname", {}).get("value") nbt_host = fields.get("nbthost", {}).get("value") hostname = hostname if hostname else nbt_host if hostname: endpoint["Hostname"] = hostname os = fields.get("os_classification", {}).get("value") if os: endpoint["OS"] = os vendor_and_model = fields.get("vendor_classification", {}).get("value") if vendor_and_model: endpoint["Model"] = vendor_and_model domain = fields.get("nbtdomain", {}).get("value") if domain: endpoint["Domain"] = domain human_readable_content = deepcopy(content) human_readable_content.update(included_fields_readable) context = {"Forescout.Host(val.ID && val.ID === obj.ID)": content, "Endpoint(val.ID && val.ID === obj.ID)": endpoint} title = f"Endpoint Details for {identifier}" if identifier else "Endpoint Details" human_readable = tableToMarkdown(title, human_readable_content, removeNull=True) return_outputs(readable_output=human_readable, outputs=context, raw_response=data) def get_hosts(args={}): url_suffix = "/api/hosts" headers = create_web_api_headers() rule_ids = args.get("rule_ids") fields = args.get("fields") if rule_ids and fields: url_suffix += "?matchRuleId=" + rule_ids + "&" + fields elif rule_ids: url_suffix += "?matchRuleId=" + rule_ids elif fields: url_suffix += "?" + fields response_data = http_request("GET", url_suffix, headers=headers, resp_type="json") return response_data def get_hosts_command(): check_web_api_credentials() args = demisto.args() response_data = get_hosts(args) content = [ {"ID": str(x.get("hostId")), "IPAddress": x.get("ip", ""), "MACAddress": x.get("mac", "")} for x in response_data.get("hosts", []) ] endpoints = [{"IPAddress": x.get("ip", ""), "MACAddress": x.get("mac", "")} for x in response_data.get("hosts", [])] context = {"Forescout.Host(val.ID && val.ID === obj.ID)": content, "Endpoint(val.ID && val.ID === obj.ID)": endpoints} title = "Active Endpoints" human_readable = tableToMarkdown(title, content, removeNull=True) if not content: demisto.results("No hosts found for the specified filters.") else: return_outputs(readable_output=human_readable, outputs=context, raw_response=response_data) def get_host_fields(): url_suffix = "/api/hostfields" headers = create_web_api_headers() response_data = http_request("GET", url_suffix, headers=headers, resp_type="json") return response_data def get_host_fields_command(): check_web_api_credentials() args = demisto.args() data = get_host_fields() filtered_data = filter_hostfields_data(args, data) if not filtered_data: demisto.results("No host fields matched the specified filters.") else: content = [{key.title(): val for key, val in x.items()} for x in filtered_data] context = {"Forescout.HostField": content} title = "Index of Host Fields" table_headers = ["Label", "Name", "Description", "Type"] human_readable = tableToMarkdown(title, content, headers=table_headers, removeNull=True) return_outputs(readable_output=human_readable, outputs=context, raw_response=data) def get_policies(): url_suffix = "/api/policies" headers = create_web_api_headers() response_data = http_request("GET", url_suffix, headers=headers, resp_type="json") return response_data def get_policies_command(): check_web_api_credentials() data = get_policies() content = format_policies_data(data) readable_content = deepcopy(content) for policy in readable_content: readable_rules = [] for rule in policy.get("Rule", []): readable_rules.append(dict_to_formatted_string(rule)) policy["Rule"] = readable_rules context = {"Forescout.Policy(val.ID && val.ID === obj.ID)": content} title = "Forescout Policies" human_readable = tableToMarkdown(title, readable_content, removeNull=True) return_outputs(readable_output=human_readable, outputs=context, raw_response=data) def update_lists(args={}): update_type = args.get("update_type", "") list_names = argToList(args.get("list_names", "")) values = ":".join(argToList(args.get("values", ""))) if values: lists = "&".join([list_name + "=" + values for list_name in list_names]) else: lists = "&".join(list_names) req_body = create_update_lists_request_body(update_type, lists) data = ET_PHONE_HOME.tostring(req_body, encoding="UTF-8", method="xml") url_suffix = "/fsapi/niCore/Lists" resp_content = http_request("POST", url_suffix, headers=DEX_HEADERS, auth=DEX_AUTH, data=data, resp_type="content") return resp_content def update_lists_command(): check_dex_credentials() args = demisto.args() response_content = update_lists(args) resp_xml = ET_PHONE_HOME.fromstring(response_content) msg_list = [child.text for child in resp_xml.iter() if child.tag == "MESSAGE"] if len(msg_list) >= 1 and msg_list[0] is not None: msg = msg_list[0] msg = msg.replace("[", "").replace("]", "") else: err_msg = ( "The response from Forescout could not be parsed correctly. It is uncertain if the list updates " "were successfully executed." ) return_error(err_msg) demisto.results(msg) def update_host_fields(args={}): host_ip = args.get("host_ip", "") update_type = args.get("update_type", "") field = args.get("field", "") value = args.get("value", "") fields_json = args.get("fields_json", "") req_body = create_update_hostfields_request_body(host_ip, update_type, field, value, fields_json) data = ET_PHONE_HOME.tostring(req_body, encoding="UTF-8", method="xml") url_suffix = "/fsapi/niCore/Hosts" resp_content = http_request("POST", url_suffix, headers=DEX_HEADERS, auth=DEX_AUTH, data=data, resp_type="content") return resp_content def update_host_fields_command(): check_dex_credentials() args = demisto.args() update_type = args.get("update_type", "") field = args.get("field", "") host_ip = args.get("host_ip", "") fields_json = args.get("fields_json", "{}") try: fields_json_dict = json.loads(fields_json) except json.decoder.JSONDecodeError: return_error("Failed to parse 'fields_json' command argument - invalid JSON format.") # Because the API has an error and says it deletes multiple things when it only deletes one # have to take care of it behind the curtains if update_type == "delete": temp_args = {"update_type": update_type, "host_ip": host_ip} for key, val in fields_json_dict.items(): temp_args["fields_json"] = json.dumps({key: val}) update_host_fields(temp_args) if field: temp_args["fields_json"] = json.dumps({field: ""}) update_host_fields(temp_args) temp_args["field"] = "" update_host_fields(args) # Takes care of composite_field response_content = update_host_fields(args) resp_xml = ET_PHONE_HOME.fromstring(response_content) msg_list = [child.text for child in resp_xml.iter() if child.tag == "MESSAGE"] if len(msg_list) >= 1 and msg_list[0] is not None: msg = msg_list[0] msg = msg.replace("[", "").replace("]", "") else: err_msg = ( "The response from Forescout could not be parsed correctly. It is uncertain if the host fields " "were successfully updated." ) return_error(err_msg) demisto.results(msg) """ COMMANDS MANAGER / SWITCH PANEL """ COMMANDS = { "test-module": test_module, "forescout-get-host": get_host_command, "forescout-get-hosts": get_hosts_command, "forescout-get-host-fields": get_host_fields_command, "forescout-get-policies": get_policies_command, "forescout-update-lists": update_lists_command, "forescout-update-host-fields": update_host_fields_command, } """ EXECUTION """ def main(): """Main execution block""" try: """ SETUP """ if not ((WEB_API_USERNAME and WEB_API_PASSWORD) or (DEX_USERNAME and DEX_PASSWORD)): err_msg = ( "The username and password for at least one of the 'Data Exchange (DEX)' or the 'Web API' " "credentials are required though it is advisable to enter both in order for the integration to" " be fully functional." ) return_error(err_msg) if (DEX_USERNAME and DEX_PASSWORD) and not DEX_ACCOUNT: err_msg = ( "When entering your 'Data Exchange (DEX)' credentials, the 'Data Exchange (DEX) Account' " "configuration parameter is also required. For information on the correct value to enter here" " - see Detailed Instructions (?)." ) return_error(err_msg) # Remove proxy if not set to true in params handle_proxy() cmd_name = demisto.command() LOG(f"Command being called is {cmd_name}") if cmd_name in COMMANDS: COMMANDS[cmd_name]() except Exception as e: return_error(str(e)) # python2 uses __builtin__ python3 uses builtins if __name__ == "__builtin__" or __name__ == "builtins": main()