Zscaler
Zscaler is a cloud security solution built for performance and flexible scalability. This integration enables you to manage URL and IP address allow lists and block lists, manage and update categories, get Sandbox reports, create, manage, and update IP destination groups and manually log in, log out, and activate changes in a Zscaler session.
Network Security · Zscaler Internet Access
Details
| ID | Zscaler |
|---|---|
| Provider | Zscaler |
| Category | Network Security |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM EDR Cortex Cloud Cloud Runtime Security |
README
Zscaler is a cloud security solution built for performance and flexible scalability. This integration enables you to manage URL and IP address allow lists and block lists, manage and update categories, get Sandbox reports, create, manage, and update IP destination groups and manually log in, log out, and activate changes in a Zscaler session.
For the integration to work properly, the Zscaler user must have admin permissions.
Category ID is the same as the category name, except all letters are capitalized and each word is separated with an underscore instead of spaces. For example, if the category name is Other Education, then the Category ID is OTHER_EDUCATION.
A custom category ID has the format CUSTOM_01, which is not indicative of the category. Use the zscaler-get-categories command to get a custom category and its configured name.
Configure Zscaler Internet Access in Cortex
| Parameter | Description | Required |
|---|---|---|
| Cloud Name (i.e. https://zsapi.zscalertwo.net) | See https://help.zscaler.com/zia/getting-started-zia-api#RetrieveBaseURL on how to retrieve your cloud name. | True |
| Username | True | |
| Password | True | |
| API Key | True | |
| Source Reliability | Reliability of the source providing the intelligence data. | False |
| Auto Logout | If enabled, the integration will log out after executing each command. | False |
| Auto Activate Changes | If enabled, the integration will activate the command changes after each execution. If disabled, use the ‘zscaler-activate-changes’ command to activate Zscaler command changes. | False |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False | |
| Timeout (in seconds) for HTTP requests to Zscaler | False |
Commands
You can execute these commands from the 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.
zscaler-blacklist-url
Adds the specified URLs to the block list.
Base Command
zscaler-blacklist-url
Input
| Argument Name | Description | Required |
|---|---|---|
| url | A comma-separated list of URLs to add to block list. For example, snapchat.com,facebook.com. | Required |
Context Output
There is no context output for this command.
Command Example
!zscaler-blacklist-url url=phishing.com,malware.net
Human Readable Output
Added the following URLs to the block list successfully:
phishing.com
malware.net
url
Looks up the classification for the each of the specified URLs.
Base Command
url
Input
| Argument Name | Description | Required |
|---|---|---|
| url | A comma-separated list of URLs for which to look up the classification. For example, abc.com,xyz.com. The maximum number of URLs per call is 100. A URL cannot exceed 1024 characters. If there are multiple URLs, set the ‘multiple’ argument to ‘true’. | Required |
| multiple | Whether there are multiple URLs in the ‘url’ argument. If a URL contains commas, set this argument to ‘false’ and enter the single URL as the ‘url’ argument. Possible values are: true, false. Default is true. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| URL.Data | string | The URL that was searched. |
| URL.Address | string | The URL that was searched. |
| Zscaler.URL.urlClassifications | string | The classification of the URL. For example, MUSIC or WEB_SEARCH. |
| Zscaler.URL.urlClassificationsWithSecurityAlert | string | The classifications of the URLs that have security alerts. |
| URL.Malicious.Vendor | string | For malicious URLs, the vendor that tagged the URL as malicious. |
| URL.Malicious.Description | string | For malicious URLs, the reason the vendor tagged the URL as malicious. |
| DBotScore.Indicator | string | The indicator that was tested. |
| DBotScore.Type | string | The indicator type. |
| DBotScore.Vendor | string | The vendor used to calculate the score. |
| DBotScore.Score | number | The actual score. |
Command Example
!url url=facebook.com
Context Example
{
"DBotScore": [
{
"Indicator": "facebook.com",
"Score": 1,
"Type": "url",
"Vendor": "Zscaler"
}
],
"URL": {
"Address": "facebook.com",
"Data": "facebook.com",
"urlClassifications": "SOCIAL_NETWORKING"
}
}
Human Readable Output
Zscaler URL Lookup
url urlClassifications facebook.com SOCIAL_NETWORKING
ip
Looks up the classification for each of the specified IP addresses.
Base Command
ip
Input
| Argument Name | Description | Required |
|---|---|---|
| ip | A comma-separated list of IP address for which to look up the classification. For example, 8.8.8.8,1.2.3.4. The maximum number of URLs per call is 100. An IP address cannot exceed 1024 characters. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| IP.Address | string | The IP address that was searched. |
| Zscaler.IP.ipClassifications | string | The classification of the IP address. For example, MUSIC or WEB_SEARCH. |
| Zscaler.IP.iplClassificationsWithSecurityAlert | string | Classifications that have a security alert for the IP address. |
| IP.Malicious.Vendor | string | For malicious IP addresses, the vendor that tagged the IP address as malicious. |
| IP.Malicious.Description | string | For malicious IP addresses, the reason the vendor tagged the IP address as malicious. |
| DBotScore.Indicator | string | The indicator that was tested. |
| DBotScore.Type | string | The indicator type. |
| DBotScore.Vendor | string | The vendor used to calculate the score. |
| DBotScore.Score | number | The actual score. |
Command Example
!ip ip=8.8.8.8
Context Example
{
"DBotScore": [
{
"Indicator": "8.8.8.8",
"Score": 1,
"Type": "ip",
"Vendor": "Zscaler"
}
],
"IP": {
"Address": "8.8.8.8",
"ipClassifications": "WEB_SEARCH"
}
}
Human Readable Output
Zscaler IP Lookup
ip ipClassifications 8.8.8.8 WEB_SEARCH
domain
Looks up the classification for each of the specified domains.
Base Command
domain
Input
| Argument Name | Description | Required |
|---|---|---|
| domain | A comma-separated list of domains for which to look up the classification. For example, example.com,google.com. | Required |
| multiple | Whether there are multiple domains in the ‘domain’ argument. If a domain contains commas, set this argument to ‘false’ and enter the single domain as the ‘domain’ argument. Possible values are: true, false. Default is true. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Domain.Name | string | The domain that was searched. |
| Zscaler.Domain.domainClassifications | string | The classification of the domain. For example, MUSIC or WEB_SEARCH. |
| Zscaler.Domain.domainClassificationsWithSecurityAlert | string | The classifications of the domains that have security alerts. |
| Domain.Malicious.Vendor | string | For malicious domains, the vendor that tagged the domain as malicious. |
| Domain.Malicious.Description | string | For malicious domains, the reason the vendor tagged the domain as malicious. |
| DBotScore.Indicator | string | The indicator that was tested. |
| DBotScore.Type | string | The indicator type. |
| DBotScore.Vendor | string | The vendor used to calculate the score. |
| DBotScore.Score | number | The actual score. |
Command Example
!domain domain=example.com
Context Example
{
"DBotScore": [
{
"Indicator": "example.com",
"Score": 1,
"Type": "domain",
"Vendor": "Zscaler"
}
],
"Domain": {
"Name": "example.com"
},
"Zscaler": {
"Domain": {
"Name": "example.com",
"domainClassifications": "REFERENCE_SITES"
}
}
}
Human Readable Output
Zscaler Domain Lookup
domain domainClassifications example.com REFERENCE_SITES
Command Example with Multiple Domains
!domain domain=example.com,google.com multiple=true
Context Example
{
"DBotScore": [
{
"Indicator": "example.com",
"Score": 1,
"Type": "domain",
"Vendor": "Zscaler"
},
{
"Indicator": "google.com",
"Score": 1,
"Type": "domain",
"Vendor": "Zscaler"
}
],
"Domain": [
{
"Name": "example.com"
},
{
"Name": "google.com"
}
],
"Zscaler": {
"Domain": [
{
"Name": "example.com",
"domainClassifications": "REFERENCE_SITES"
},
{
"Name": "google.com",
"domainClassifications": "WEB_SEARCH"
}
]
}
}
Human Readable Output
Zscaler Domain Lookup
domain domainClassifications example.com REFERENCE_SITES google.com WEB_SEARCH
zscaler-undo-blacklist-url
Removes the specified URLs from the block list.
Base Command
zscaler-undo-blacklist-url
Input
| Argument Name | Description | Required |
|---|---|---|
| url | A comma-separated list of URLs to remove from the block list. For example, snapchat.com,facebook.com. | Required |
Context Output
There is no context output for this command.
zscaler-whitelist-url
Adds the specified URLs to the allow list.
Base Command
zscaler-whitelist-url
Input
| Argument Name | Description | Required |
|---|---|---|
| url | A comma-separated list of URLs to add to the allow list. For example, snapchat.com,facebook.com. | Required |
Context Output
There is no context output for this command.
Command Example
!zscaler-whitelist-url url=phising.com,malware.net
Human Readable Output
Added the following URLs to the allow list successfully:
phishing.com
malware.net
zscaler-undo-whitelist-url
Removes the specified URLs from the allow list.
Base Command
zscaler-undo-whitelist-url
Input
| Argument Name | Description | Required |
|---|---|---|
| url | A comma-separated list of URLs to remove from the allow list. For example, snapchat.com,facebook.com. | Required |
Context Output
There is no context output for this command.
Command Example
!zscaler-undo-whitelist-url url=phising.com,malware.net
Human Readable Output
Removed the following URLs from the allow list successfully:
phishing.com
malware.net
zscaler-undo-whitelist-ip
Removes the specified IP addresses from the allow list.
Base Command
zscaler-undo-whitelist-ip
Input
| Argument Name | Description | Required |
|---|---|---|
| ip | A comma-separated list of IP addresses to remove from the allow list. For example, 8.8.8.8,1.2.3.4. | Required |
Context Output
There is no context output for this command.
Command Example
!zscaler-undo-whitelist-ip ip=2.2.2.2,3.3.3.3
Human Readable Output
Removed the following IP addresses from the allow list successfully:
2.2.2.2
3.3.3.3
zscaler-whitelist-ip
Adds the specified IP address to the allow list.
Base Command
zscaler-whitelist-ip
Input
| Argument Name | Description | Required |
|---|---|---|
| ip | A comma-separated list of IP addresses to add to the allow list. For example, 8.8.8.8,1.2.3.4. | Required |
Context Output
There is no context output for this command.
Command Example
!zscaler-whitelist-ip ip=2.2.2.2,3.3.3.3
Human Readable Output
Added the following IP addresses to the allow list successfully:
2.2.2.2
3.3.3.3
zscaler-undo-blacklist-ip
Removes the specified IP addresses from the block list.
Base Command
zscaler-undo-blacklist-ip
Input
| Argument Name | Description | Required |
|---|---|---|
| ip | A comma-separated list of IP addresses to remove from the allow list. For example, 8.8.8.8,1.2.3.4. | Required |
Context Output
There is no context output for this command.
Command Example
!zscaler-undo-blacklist-ip ip=2.2.2.2,3.3.3.3
Human Readable Output
Removed the following IP addresses from the block list successfully:
2.2.2.2
3.3.3.3
zscaler-blacklist-ip
Adds the specified IP addresses to the block list.
Base Command
zscaler-blacklist-ip
Input
| Argument Name | Description | Required |
|---|---|---|
| ip | A comma-separated list of IP addresses to add to the block list. For example, 8.8.8.8,1.2.3.4. | Required |
Context Output
There is no context output for this command.
Command Example
!zscaler-blacklist-ip ip=2.2.2.2,3.3.3.3
Human Readable Output
Added the following IP addresses to the block list successfully:
2.2.2.2
3.3.3.3
zscaler-category-add-url
Adds URLs to the specified category.
Ensure that the URLs are properly formatted according to Zscaler’s guidelines. For more information on valid URL formats, refer to the Zscaler URL Format Guidelines.
Base Command
zscaler-category-add-url
Input
| Argument Name | Description | Required |
|---|---|---|
| category-id | The ID of the category to add the specified URLs to. For example, RADIO_STATIONS. You can retrieve the category IDs by running the ‘zscaler-get-categories’ command. | Required |
| url | A comma-separated list of URLs to add to the specified category. For example, pandora.com,spotify.com. Important: If any URL contains a comma ( ,), you must pass the url argument as a JSON list wrapped in backticks (`). Example (single URL with comma): url=`[“https://example.com/foo,bar”]` Example (multiple URLs with commas): url=`[“https://example.com/foo,bar”,”https://example2.com/foo,bar”]` |
Optional |
| retaining-parent-category-url | A comma-separated list of URLs to add to the retaining parent category section inside the specified category. For example, pandora.com,spotify.com. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Zscaler.Category.CustomCategory | boolean | True, if the category is a custom category. Otherwise, false. |
| Zscaler.Category.Description | string | The description of the category. |
| Zscaler.Category.ID | string | The ID of the category. |
| Zscaler.Category.URL | string | The URL of the category. |
Command Example
!zscaler-category-add-url category-id=MUSIC url=demisto.com,apple.com
Example with a URL containing a comma:
!zscaler-category-add-url category-id="CUSTOM_123" url=`["https://example.com/foo,bar"]`
Context example
{
"Zscaler": {
"Category": {
"CustomCategory": false,
"Description": "MUSIC_DESC",
"ID": "MUSIC",
"URL": [
"demisto.com",
"apple.com"
]
}
}
}
Human Readable Output
Added the following URL addresses to category MUSIC:
- demisto.com
- apple.com
zscaler-category-add-ip
Adds IP address to the specified category.
Base Command
zscaler-category-add-ip
Input
| Argument Name | Description | Required |
|---|---|---|
| category-id | The ID of the category to add the specified IP addresses to. For example, RADIO_STATIONS. You can retrieve the category IDs by running the ‘zscaler-get-categories’ command. | Required |
| ip | A comma-separated list of IP address to add to the specified category. For example, 1.2.3.4,8.8.8.8. | Optional |
| retaining-parent-category-ip | A comma-separated list of IP address to add to the retaining parent category section inside the specified category. For example, 1.2.3.4,8.8.8.8. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Zscaler.Category.CustomCategory | boolean | True, if the category is a custom category. Otherwise, false. |
| Zscaler.Category.Description | string | The description of the category. |
| Zscaler.Category.ID | string | The ID of the category. |
| Zscaler.Category.URL | string | The URL of the category |
Command Example
!zscaler-category-add-ip category-id=REFERENCE_SITES ip=1.2.3.4,8.8.8.8
Context Example
{
"Zscaler": {
"Category": {
"CustomCategory": false,
"Description": "REFERENCE_SITES_DESC",
"ID": "REFERENCE_SITES",
"URL": [
"1.2.3.4",
"8.8.8.8"
]
}
}
}
Human Readable Output
Added the following IP addresses to category REFERENCE_SITES:
- 1.2.3.4
- 8.8.8.8
zscaler-category-remove-url
Removes URLs from the specified category.
Base Command
zscaler-category-remove-url
Input
| Argument Name | Description | Required |
|---|---|---|
| category-id | The ID of the category to remove the specified URLs from. For example, RADIO_STATIONS. You can retrieve the category IDs by running the ‘zscaler-get-categories’ command. | Required |
| url | A comma-separated list of URLs to remove from the specified category. For example, pandora.com,spotify.com. | Optional |
| retaining-parent-category-url | A comma-separated list of URLs to remove from the retaining parent category section inside the specified category. For example, pandora.com,spotify.com. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Zscaler.Category.CustomCategory | boolean | True, if the category is a custom category. Otherwise, false. |
| Zscaler.Category.Description | string | The description of the category. |
| Zscaler.Category.ID | string | The ID of the category. |
| Zscaler.Category.URL | string | The URL of the category. |
Command Example
!zscaler-category-remove-url category-id=MUSIC url=apple.com
Context Example
{
"Zscaler": {
"Category": {
"CustomCategory": false,
"Description": "MUSIC_DESC",
"ID": "MUSIC",
"URL": [
"demisto.com"
]
}
}
}
Human Readable Output
Removed the following URL addresses to category MUSIC:
- apple.com
zscaler-category-remove-ip
Removes IP address from the specified category.
Base Command
zscaler-category-remove-ip
Input
| Argument Name | Description | Required |
|---|---|---|
| category-id | The ID of the category to remove the specified IP addresses from. For example, RADIO_STATIONS. You can retrieve the category IDs by running the ‘zscaler-get-categories’ command. | Required |
| ip | A comma-separated list of IP addresses to remove from the specified category. For example, 1.2.3.4,8.8.8.8. | Optional |
| retaining-parent-category-ip | A comma-separated list of IP address to remove from the retaining parent category section inside the specified category. For example, 1.2.3.4,8.8.8.8. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Zscaler.Category.CustomCategory | boolean | True, if the category is a custom category. Otherwise, false. |
| Zscaler.Category.Description | string | The description of the category. |
| Zscaler.Category.ID | string | The ID of the category. |
| Zscaler.Category.URL | string | The URL of the category. |
Command Example
!zscaler-category-remove-ip category-id=REFERENCE_SITES ip=1.2.3.4
Context Example
{
"Zscaler": {
"Category": {
"CustomCategory": false,
"Description": "REFERENCE_SITES_DESC",
"ID": "REFERENCE_SITES",
"URL": [
"8.8.8.8"
]
}
}
}
Human Readable Output
Removed the following IP addresses to category REFERENCE_SITES:
- 1.2.3.4
zscaler-get-categories
Retrieves a list of all categories.
Base Command
zscaler-get-categories
Input
| Argument Name | Description | Required |
|---|---|---|
| displayURL | Whether to display the URLs of each category in the War Room. URLs will always be returned to the Context Data. Possible values are: true, false. Default is false. | Optional |
| custom_categories_only | Whether to retrieve only custom categories to the War Room. Possible values are: true, false. Default is false. | Optional |
| get_ids_and_names_only | Whether to retrieve only a list containing URL category IDs and names. Even if displayURL is set to true, URLs will not be returned. Please note - the API does not support the combination of custom_only and get_ids_and_names_only. Possible values are: true, false. Default is false. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Zscaler.Category.ID | string | The ID of the category. |
| Zscaler.Category.CustomCategory | boolean | True, if the category is a custom category. Otherwise, false. |
| Zscaler.Category.URL | string | The URL of the category. |
| Zscaler.Category.RetainingParentCategoryURL | string | The URLs of the retaining parent category. |
| Zscaler.Category.Description | string | The description of the category. |
| Zscaler.Category.Name | string | The name of the category. |
Command Example
#### Context Example
```json
{
"Zscaler":{
"Category":{
"ID":"INTERNET_SERVICES",
"Description":"INTERNET_SERVICES_DESC",
"URL":[
"google.com",
"facebook.com"
],
"CustomCategory":"false"
},
"ID":"CUSTOM_01",
"Name":"CustomCategory",
"URL":[
"demisto.com",
"apple.com"
],
"RetainingParentCategoryURL":[
"pandora.com",
"spotify.com"
],
"CustomCategory":"true"
}
}
Human Readable Output
| CustomCategory | Description | ID | Name | URL | RetainingParentCategoryURL |
|---|---|---|---|---|---|
| false | INTERNET_SERVICES_DESC | INTERNET_SERVICES | google.com,facebook.com | ||
| true | CUSTOM_01 | CustomCategory | demisto.com,apple.com | pandora.com”,”spotify.com |
zscaler-get-blacklist
Retrieves the Zscaler default block list.
Base Command
zscaler-get-blacklist
Input
| Argument Name | Description | Required |
|---|---|---|
| filter | Filter results by URL or IP objects. Possible values are: url, ip. | Optional |
| query | Query (Python regular expression) to match against. For example, 8...8. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Zscaler.Blacklist | string | The Zscaler block list. |
Command Example
#### Context Example
```json
{
"Zscaler": {
"Blacklist": [
"malicious.com,
"bad.net"
]
}
}
Human Readable Output
Zscaler block list
- malicious.com
- bad.net
zscaler-get-whitelist
Retrieves the Zscaler default allow list.
Base Command
zscaler-get-whitelist
Input
There are no input arguments for this command.
Context Output
| Path | Type | Description |
|---|---|---|
| Zscaler.Whitelist | string | The Zscaler allow list. |
Command Example
#### Context Example
```json
{
"Zscaler": {
"Whitelist": [
"demisto.com,
"apple.com"
]
}
}
Human Readable Output
Zscaler whitelist
- demisto.com
- apple.net
zscaler-sandbox-report
Retrieves a full or summary report of the file that was analyzed by Sandbox. The file is represented by the specified MD5 hash.
Base Command
zscaler-sandbox-report
Input
| Argument Name | Description | Required |
|---|---|---|
| md5 | The MD5 hash of a file. | Required |
| details | The type of report. Possible values are ‘full’ or ‘summary’. Default is ‘full’. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| File.MD5 | string | The MD5 hash of the file. |
| File.Malicious.Vendor | string | For malicious files, the vendor that tagged the file as malicious. |
| File.Malicious.Description | string | For malicious files, the reason the vendor tagged the file as malicious. |
| File.DetectedMalware | string | The malware detected in the file. |
| File.FileType | string | The file type. |
| DBotScore.Indicator | string | The MD5 hash file that was tested. |
| DBotScore.Type | string | The MD5 hash file type. |
| DBotScore.Vendor | string | The vendor that calculated the DBot score. |
| DBotScore.Score | number | The actual DBot score. |
Command Example
!zscaler-sandbox-report md5=3FD0EA0AE759D58274310C022FB0CBBA details=summary
Context Example
{
"DBotScore": {
"Vendor": "Zscaler",
"Indicator": "3FD0EA0AE759D58274310C022FB0CBBA",
"Score": 3,
"Type": "file"
},
"File": {
"Zscaler": {
"FileType": null,
"DetectedMalware": ""
},
"Malicious": {
"Vendor": "Zscaler",
"Description": "Classified as Malicious, with threat score: 100"
},
"MD5": "3FD0EA0AE759D58274310C022FB0CBBA"
}
}
Human Readable Output
Full Sandbox Report
| Category | Indicator | Vendor | Score | Zscaler Score | Type |
|---|---|---|---|---|---|
| MALWARE_BOTNET | 3FD0EA0AE759D58274310C022FB0CBBA | Zscaler | 3 | 100 | file |
Additional Information
zscaler-login
Manually create a Zscaler login session. This command will also try to log out of the previous session.
Base Command
zscaler-login
Input
There are no input arguments for this command.
Context Output
There is no context output for this command.
Command Example
#### Human Readable Output
>Zscaler session created successfully.
### zscaler-logout
***
Logs out of the current Zscaler session.
#### Base Command
`zscaler-logout`
#### Input
There are no input arguments for this command.
#### Context Output
There is no context output for this command.
#### Command Example
```!zscaler-logout```
#### Human Readable Output
>API session logged out of Zscaler successfully.
### zscaler-activate-changes
***
Activates the changes executed by other Zscaler commands in this session.
#### Base Command
`zscaler-activate-changes`
#### Input
There are no input arguments for this command.
#### Context Output
There is no context output for this command.
#### Command Example
```!zscaler-activate-changes```
#### Human Readable Output
>Changes have been activated successfully.
### zscaler-url-quota
***
Gets information on the number of unique URLs that are currently provisioned for your organization as well as how many URLs you can add before reaching that number.
#### Base Command
`zscaler-url-quota`
#### Input
There are no input arguments for this command.
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Zscaler.remainingUrlsQuota | Number | The number of URLs you can add before reaching the quota. |
| Zscaler.uniqueUrlsProvisioned | Number | The number of unique URLs that are currently provisioned for your organization. |
### zscaler-get-users
***
Get Zscaler users
#### Base Command
`zscaler-get-users`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| name | Filer by user name. | Optional |
| page | Specifies the page offset. | Optional |
| pageSize | Specifies the page size. Default is 100. | Optional |
#### Context Output
There is no context output for this command.
### zscaler-update-user
***
Updates the user information for the specified ID.
#### Base Command
`zscaler-update-user`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| id | The unique identifier for the user. | Required |
| user | New user information. Docs: <https://help.zscaler.com/zia/api#/User%20Management/updateUser>. | Required |
#### Context Output
There is no context output for this command.
### zscaler-get-departments
***
Get a list of departments. It can be searched by name.
#### Base Command
`zscaler-get-departments`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| name | Filter by department name. | Optional |
| page | Specifies the page offset. | Optional |
| pageSize | Specifies the page size. Default is 100. | Optional |
#### Context Output
There is no context output for this command.
### zscaler-get-usergroups
***
Gets a list of groups
#### Base Command
`zscaler-get-usergroups`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| name | Filter by group name or comment. | Optional |
| page | Specifies the page offset. | Optional |
| pageSize | Specifies the page size. Default is 100. | Optional |
#### Context Output
There is no context output for this command.
### zscaler-create-ip-destination-group
***
Adds a new IP destination group.
#### Base Command
`zscaler-create-ip-destination-group`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| name | Destination IP group name. | Required |
| type | Destination IP group type (i.e., the group can contain destination IP addresses, countries, URL categories or FQDNs). Possible values are: DSTN_IP, DSTN_FQDN, DSTN_DOMAIN, DSTN_OTHER. | Required |
| addresses | Destination IP addresses, FQDNs, or wildcard FQDNs added to the group. | Optional |
| description | Additional information about the destination IP group. | Optional |
| ip_categories | Destination IP address URL categories. You can identify destinations based on the URL category of the domain. To retrieve a list of possible values, you can execute the zscaler-get-categories command. | Optional |
| countries | Destination IP address countries. You can identify destinations based on the location of a server. A list of possible values can be found here <https://help.zscaler.com/zia/firewall-policies#/ipDestinationGroups-post>. | Optional |
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Zscaler.IPDestinationGroup.ID | number | Unique identifier for the destination IP group. |
| Zscaler.IPDestinationGroup.Name | string | Destination IP group name. |
| Zscaler.IPDestinationGroup.Type | string | Destination IP group type \(i.e., the group can contain destination IP addresses, countries, URL categories or FQDNs\). |
| Zscaler.IPDestinationGroup.Description | string | Destination IP group description. |
| Zscaler.IPDestinationGroup.Addresses | string | Destination IP addresses, FQDNs, or wildcard FQDNs added to the group. |
| Zscaler.IPDestinationGroup.IpCategories | string | Destination IP address URL categories. You can identify destinations based on the URL category of the domain. |
| Zscaler.IPDestinationGroup.Countries | string | Destination IP address countries. You can identify destinations based on the location of a server. |
| Zscaler.IPDestinationGroup.IsNonEditable | boolean | If set to true, the destination IP address group is non-editable. This field is applicable only to predefined IP address groups, which cannot be modified. |
#### Command Example
```!zscaler-create-ip-destination-group addresses="127.0.0.2,127.0.0.1" description=Localhost name=Test99 type=DSTN_IP```
#### Context example
```json
{
"Zscaler.IPDestinationGroup": {
"ID": 2000359,
"Name": "Test99",
"Type": "DSTN_IP",
"Addresses": [
"127.0.0.2",
"127.0.0.1"
],
"Description": "Localhost",
"IpCategories": [],
"Countries": [],
"IsNonEditable": false
}
}
Human Readable Output
IP Destination group created
| Addresses | Countries | Description | ID | IpCategories | IsNonEditable | Name | Type |
|---|---|---|---|---|---|---|---|
| 127.0.0.2, 127.0.0.1 |
Localhost | 2000359 | false | Test99 | DSTN_IP |
zscaler-edit-ip-destination-group
Updates the IP destination group information for the specified group ID.
Base Command
zscaler-edit-ip-destination-group
Input
| Argument Name | Description | Required |
|---|---|---|
| ip_group_id | The unique identifier for the IP destination group. | Required |
| name | Destination IP group name. | Optional |
| addresses | Destination IP addresses, FQDNs, or wildcard FQDNs added to the group. | Optional |
| description | Additional information about the destination IP group. | Optional |
| ip_categories | Destination IP address URL categories. You can identify destinations based on the URL category of the domain. To retrieve a list of possible values you can execute the zscaler-get-categories command. | Optional |
| countries | Destination IP address countries. You can identify destinations based on the location of a server. A list of possible values can be found here https://help.zscaler.com/zia/firewall-policies#/ipDestinationGroups/{ipGroupId}-put. | Optional |
| is_non_editable | If set to true, the destination IP address group is non-editable. This field is applicable only to predefined IP address groups, which cannot be modified. Possible values are: True, False. Default is False. | Optional |
| override | When set to true (default), the existing IP destination group configuration is replaced with the new values. When set to false, the existing configuration is preserved and new entries are appended. Possible values are: True, False. Default is True. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Zscaler.IPDestinationGroup.ID | number | Unique identifier for the destination IP group. |
| Zscaler.IPDestinationGroup.Name | string | Destination IP group name. |
| Zscaler.IPDestinationGroup.Type | string | Destination IP group type (i.e., the group can contain destination IP addresses, countries, URL categories or FQDNs). |
| Zscaler.IPDestinationGroup.Description | string | Destination IP group description. |
| Zscaler.IPDestinationGroup.Addresses | string | Destination IP addresses, FQDNs, or wildcard FQDNs added to the group. |
| Zscaler.IPDestinationGroup.IpCategories | string | Destination IP address URL categories. You can identify destinations based on the URL category of the domain. |
| Zscaler.IPDestinationGroup.Countries | string | Destination IP address countries. You can identify destinations based on the location of a server. |
Command Example
!zscaler-edit-ip-destination-group ip_group_id=2000359 addresses="127.0.0.2" description="Localhost v2" name=Test01
Context example
{
"Zscaler.IPDestinationGroup": {
"ID": 2000359,
"Name": "Test01",
"Type": "DSTN_IP",
"Description": "Localhost v2",
"Addresses": [
"127.0.0.2"
],
"IpCategories": [],
"Countries": []
}
}
Human Readable Output
IP Destination group updated
| Addresses | Countries | Description | ID | IpCategories | Name | Type |
|---|---|---|---|---|---|---|
| 127.0.0.2 | Localhost v2 | 2000359 | Test01 | DSTN_IP |
zscaler-list-ip-destination-groups
Gets a list of all IP destination groups or the IP destination group information for the specified group ID.
Base Command
zscaler-list-ip-destination-groups
Input
| Argument Name | Description | Required |
|---|---|---|
| ip_group_id | A comma-separated list of unique identifiers for the IP destination groups. | Optional |
| exclude_type | The IP group type to be excluded from the results. Possible values are: DSTN_IP, DSTN_FQDN, DSTN_DOMAIN, DSTN_OTHER. | Optional |
| category_type | The IP group type to be filtered from results. This argument is only supported when the ‘lite’ argument is set to True. Possible values are: DSTN_IP, DSTN_FQDN, DSTN_DOMAIN, DSTN_OTHER. | Optional |
| include_ipv6 | Retrieve IPv6 destination groups. Possible values are: True, False. Default is False. | Optional |
| limit | Limit of the results to be retrieved. Default is 50. | Optional |
| all_results | Whether to retrieve all results at once. Possible values are: True, False. Default is False. | Optional |
| lite | Whether to retrieve only limited information of IP destination groups. Includes ID, name and type of the IP destination groups. Possible values are: True, False. Default is False. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Zscaler.IPDestinationGroup.ID | string | Unique identifier for the destination IP group. |
| Zscaler.IPDestinationGroup.Name | string | Destination IP group name. |
| Zscaler.IPDestinationGroup.Type | string | Destination IP group type (i.e., the group can contain destination IP addresses, countries, URL categories or FQDNs). |
| Zscaler.IPDestinationGroup.Addresses | string | Destination IP addresses, FQDNs, or wildcard FQDNs added to the group. |
| Zscaler.IPDestinationGroup.Description | string | Additional information about the destination IP group |
| Zscaler.IPDestinationGroup.IpCategories | string | Destination IP address URL categories. You can identify destinations based on the URL category of the domain. |
| Zscaler.IPDestinationGroup.Countries | string | Destination IP address countries. You can identify destinations based on the location of a server. |
Command Example
!zscaler-list-ip-destination-groups exclude_type=DSTN_OTHER
Context example
{
"Zscaler.IPDestinationGroup": [
{
"ID": 1997898,
"Name": "Test99",
"Type": "DSTN_IP",
"Addresses": ["127.0.0.2"],
"Description": "Localhost v2",
"IpCategories": [],
"Countries": []
},
{
"ID": 2001335,
"Name": "Test01",
"Type": "DSTN_IP",
"Addresses": ["127.0.0.1"],
"Description": "Localhost v1",
"IpCategories": [],
"Countries": []
}
]
}
Human Readable Output
IPv4 Destination groups (2)
| Addresses | Countries | Description | ID | IpCategories | Name | Type |
|---|---|---|---|---|---|---|
| 127.0.0.2 | Localhost v2 | 1997898 | Test99 | DSTN_IP | ||
| 127.0.0.1 | Localhost v1 | 2001335 | Test01 | DSTN_IP |
!zscaler-list-ip-destination-groups lite=True
Context Example
[
{
"ID": 1964949,
"Name": "Russia-Region",
"Type": "DSTN_OTHER"
}
]
Human Readable Output
IPv4 Destination groups lite (1)
| ID | Name | Type | ||
|---|---|---|---|---|
| 1964949 | Russia-Region | DSTN_OTHER |
zscaler-delete-ip-destination-groups
Deletes the IP destination group associated with the specified group ID.
Base Command
zscaler-delete-ip-destination-groups
Input
| Argument Name | Description | Required |
|---|---|---|
| ip_group_ids | A comma-separated list of unique identifiers for the IP destination groups. | Optional |
Context Output
There is no context output for this command.
Command Example
!zscaler-delete-ip-destination-groups ip_group_id=2077311,2030563
Human Readable Output
IP Destination Group 2077311,2030563 deleted successfully
Configuration parameters
cloud— Cloud Name (i.e., <https://zsapi.zscalertwo.net>) (required)credentials— Username (required)creds_key—key— API Keyreliability— Source Reliabilityauto_logout— Auto Logoutauto_activate— Auto Activate Changesinsecure— Trust any certificate (not secure)proxy— Use system proxy settingsrequestTimeout— Timeout (in seconds) for HTTP requests to Zscaler
Commands (31)
-
domainLooks up the classification for each of the specified Domains.
-
ipLooks up the classification for each of the specified IP addresses.
-
urlLooks up the classification for the each of the specified URLs.
-
zscaler-activate-changesActivates the changes executed by other Zscaler commands in this session.
-
zscaler-blacklist-ipAdds the specified IP addresses to the block list.
-
zscaler-blacklist-urlAdds the specified URLs to the block list.
-
zscaler-category-add-ipAdds IP address to the specified category.
-
zscaler-category-add-urlAdds URLs to the specified category.
-
zscaler-category-remove-ipRemoves IP address from the specified category.
-
zscaler-category-remove-urlRemoves URLs from the specified category.
-
zscaler-create-ip-destination-groupAdds a new IP destination group.
-
zscaler-delete-ip-destination-groupsDeletes the IP destination group associated with the specified group ID.
-
zscaler-edit-ip-destination-groupUpdates the IP destination group information for the specified group ID.
-
zscaler-get-blacklistRetrieves the Zscaler default block list.
-
zscaler-get-categoriesRetrieves a list of all categories.
-
zscaler-get-departmentsGet a list of departments. It can be searched by name.
-
zscaler-get-usergroupsGets a list of groups.
-
zscaler-get-usersGet Zscaler users.
-
zscaler-get-whitelistRetrieves the Zscaler default allow list.
-
zscaler-list-ip-destination-groupsGets a list of all IP destination groups or the IP destination group information for the specified group ID.
-
zscaler-loginManually create a Zscaler login session. This command will also try to log out of the previous session.
-
zscaler-logoutLogs out of the current Zscaler session.
-
zscaler-sandbox-reportRetrieves a full or summary report of the file that was analyzed by Sandbox. The file is represented by the specified MD5 hash.
-
zscaler-undo-blacklist-ipRemoves the specified IP addresses from the block list.
-
zscaler-undo-blacklist-urlRemoves the specified URLs from the block list.
-
zscaler-undo-whitelist-ipRemoves the specified IP addresses from the allow list.
-
zscaler-undo-whitelist-urlRemoves the specified URLs from the allow list.
-
zscaler-update-userUpdates the user information for the specified ID.
-
zscaler-url-quotaGets information on the number of unique URLs that are currently provisioned for your organization as well as how many URLs you can add before reaching that number.
-
zscaler-whitelist-ipAdds the specified IP address to the allow list.
-
zscaler-whitelist-urlAdds the specified URLs to the allow list.
import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 """ GLOBAL VARS """ ADD = "ADD_TO_LIST" REMOVE = "REMOVE_FROM_LIST" INTEGRATION_NAME = "Zscaler" SUSPICIOUS_CATEGORIES = ["SUSPICIOUS_DESTINATION", "SPYWARE_OR_ADWARE"] CLOUD_NAME = demisto.params()["cloud"] USERNAME = demisto.params()["credentials"]["identifier"] PASSWORD = demisto.params()["credentials"]["password"] API_KEY = str(demisto.params().get("creds_key", {}).get("password", "")) or str(demisto.params().get("key", "")) if not API_KEY: raise Exception("API Key is missing. Please provide an API Key.") BASE_URL = CLOUD_NAME + "/api/v1" USE_SSL = not demisto.params().get("insecure", False) PROXY = demisto.params().get("proxy", True) REQUEST_TIMEOUT = int(demisto.params().get("requestTimeout", 15)) DEFAULT_HEADERS = {"content-type": "application/json"} SESSION_ID_KEY = "session_id" ERROR_CODES_DICT = { 400: "Invalid or bad request", 401: "Session is not authenticated or timed out", 403: "One of the following permission errors occurred:\n-The API key was disabled by your service provider\n" "-User role has no access permissions or functional scope\n-A required SKU subscription is missing\n" "Contact support or your account team for assistance.", 404: "Resource does not exist", 409: "Request could not be processed because of possible edit conflict occurred. Another admin might be saving a " "configuration change at the same time. In this scenario, the client is expected to retry after a short " "time period.", 406: "Not Acceptable", 415: "Unsupported media type.", 429: "Exceeded the rate limit or quota.", 500: "Unexpected error", 503: "Service is temporarily unavailable", } AUTO_ACTIVATE_CHANGES_COMMANDS = ( "zscaler-blacklist-url", "zscaler-undo-blacklist-url", "zscaler-whitelist-url", "zscaler-undo-whitelist-url", "zscaler-blacklist-ip", "zscaler-undo-blacklist-ip", "zscaler-whitelist-ip", "zscaler-undo-whitelist-ip", "zscaler-category-add-url", "zscaler-category-add-ip", "zscaler-category-remove-url", "zscaler-category-remove-ip", "zscaler-list-ip-destination-groups", "zscaler-edit-ip-destination-group", "zscaler-create-ip-destination-group", "zscaler-delete-ip-destination-groups", ) """ HANDLE PROXY """ # Remove proxy if not set to true in params handle_proxy() """ HELPER CLASSES """ class AuthorizationError(DemistoException): """Error to be raised when 401/403 headers are present in http response""" """ HELPER FUNCTIONS """ def error_handler(res): """ Deals with unsuccessful calls """ if res.status_code in (401, 403): raise AuthorizationError(res.content) elif res.status_code == 400 and res.request.method == "PUT" and "/urlCategories/" in res.request.url: raise Exception( f"The request failed with the following error: {res.status_code}.\nMessage: {res.text}\n" f"This error might be due to an invalid URL or exceeding your organization's quota.\n" f"For more information about URL formatting, refer to the Zscaler URL Format Guidelines: " f"https://help.zscaler.com/zia/url-format-guidelines\n" f"To check your quota usage, run the command `zscaler-url-quota`." ) else: if res.status_code in ERROR_CODES_DICT: raise Exception( f"The request failed with the following error: {ERROR_CODES_DICT[res.status_code]}.\nMessage: {res.text}" ) else: raise Exception(f"The request failed with the following error: {res.status_code}.\nMessage: {res.text}") def http_request(method, url_suffix, data=None, headers=None, resp_type="json"): time_sensitive = is_time_sensitive() demisto.debug(f"{time_sensitive=}") retries = 0 if time_sensitive else 3 status_list_to_retry = None if time_sensitive else [429] timeout = 2 if time_sensitive else REQUEST_TIMEOUT try: res = generic_http_request( method=method, server_url=BASE_URL, timeout=timeout, verify=USE_SSL, proxy=PROXY, client_headers=DEFAULT_HEADERS, headers=headers, url_suffix=url_suffix, data=data or {}, ok_codes=(200, 204), error_handler=error_handler, retries=retries, status_list_to_retry=status_list_to_retry, resp_type=resp_type, ) except Exception as e: LOG(f"Zscaler request failed with url suffix={url_suffix}\tdata={data}") LOG(e) raise e return res def validate_urls(urls): for url in urls: if url.startswith(("http://", "https://", "*")): return_error( "Enter a valid URL address without an http:// or https:// or * prefix. URL should have at least host." "domain pattern to qualify." ) """ FUNCTIONS """ def login(): """ Try to use integration context if available and valid, otherwise create new session """ cmd_url = "/authenticatedSession" def obfuscateApiKey(seed): now = str(int(time.time() * 1000)) n = now[-6:] r = str(int(n) >> 1).zfill(6) key = "" for i in range(0, len(n), 1): key += seed[int(n[i])] for j in range(0, len(r), 1): key += seed[int(r[j]) + 2] return now, key ctx = get_integration_context() or {} session_id = ctx.get(SESSION_ID_KEY) if session_id: DEFAULT_HEADERS["cookie"] = session_id try: return test_module() except AuthorizationError as e: demisto.info(f"Zscaler encountered an authentication error.\nError: {str(e)}") ts, key = obfuscateApiKey(API_KEY) add_sensitive_log_strs(key) data = {"username": USERNAME, "timestamp": ts, "password": PASSWORD, "apiKey": key} json_data = json.dumps(data) result = http_request("POST", cmd_url, json_data, DEFAULT_HEADERS, resp_type="response") auth = result.headers["Set-Cookie"] ctx[SESSION_ID_KEY] = DEFAULT_HEADERS["cookie"] = auth[: auth.index(";")] set_integration_context(ctx) return test_module() def activate_changes(): cmd_url = "/status/activate" return http_request("POST", cmd_url, None, DEFAULT_HEADERS) def logout(): cmd_url = "/authenticatedSession" return http_request("DELETE", cmd_url, None, DEFAULT_HEADERS) def blacklist_url(url): urls_to_blacklist = argToList(url) validate_urls(urls_to_blacklist) cmd_url = "/security/advanced/blacklistUrls?action=ADD_TO_LIST" data = {"blacklistUrls": urls_to_blacklist} json_data = json.dumps(data) http_request("POST", cmd_url, json_data, DEFAULT_HEADERS, resp_type="response") list_of_urls = "" for url in urls_to_blacklist: list_of_urls += "- " + url + "\n" return "Added the following URLs to the blacklist successfully:\n" + list_of_urls def unblacklist_url(url): urls_to_unblacklist = argToList(url) cmd_url = "/security/advanced/blacklistUrls?action=REMOVE_FROM_LIST" # Check if given URLs is blacklisted blacklisted_urls = get_blacklist()["blacklistUrls"] if len(urls_to_unblacklist) == 1: # Given only one URL to unblacklist if urls_to_unblacklist[0] not in blacklisted_urls: raise Exception("Given URL is not blacklisted.") elif not any(url in urls_to_unblacklist for url in blacklisted_urls): # Given more than one URL to blacklist raise Exception("Given URLs are not blacklisted.") data = {"blacklistUrls": urls_to_unblacklist} json_data = json.dumps(data) http_request("POST", cmd_url, json_data, DEFAULT_HEADERS, resp_type="response") list_of_urls = "" for url in urls_to_unblacklist: list_of_urls += "- " + url + "\n" return "Removed the following URLs from the blacklist successfully:\n" + list_of_urls def blacklist_ip(ip): ips_to_blacklist = argToList(ip) cmd_url = "/security/advanced/blacklistUrls?action=ADD_TO_LIST" data = {"blacklistUrls": ips_to_blacklist} json_data = json.dumps(data) http_request("POST", cmd_url, json_data, DEFAULT_HEADERS, resp_type="response") list_of_ips = "" for ip in ips_to_blacklist: list_of_ips += "- " + ip + "\n" return "Added the following IP addresses to the blacklist successfully:\n" + list_of_ips def unblacklist_ip(ip): ips_to_unblacklist = argToList(ip) cmd_url = "/security/advanced/blacklistUrls?action=REMOVE_FROM_LIST" # Check if given IPs is blacklisted blacklisted_ips = get_blacklist()["blacklistUrls"] if len(ips_to_unblacklist) == 1: # Given only one IP address to blacklist if ips_to_unblacklist[0] not in blacklisted_ips: raise Exception("Given IP address is not blacklisted.") elif not set(ips_to_unblacklist).issubset(set(blacklisted_ips)): # Given more than one IP address to blacklist raise Exception("Given IP addresses are not blacklisted.") data = {"blacklistUrls": ips_to_unblacklist} json_data = json.dumps(data) http_request("POST", cmd_url, json_data, DEFAULT_HEADERS, resp_type="response") list_of_ips = "" for ip in ips_to_unblacklist: list_of_ips += "- " + ip + "\n" return "Removed the following IP addresses from the blacklist successfully:\n" + list_of_ips def whitelist_url(url): cmd_url = "/security" urls_to_whitelist = argToList(url) # Get the current whitelist whitelist_urls = get_whitelist() if not whitelist_urls: whitelist_urls["whitelistUrls"] = [] whitelist_urls["whitelistUrls"] += urls_to_whitelist json_data = json.dumps(whitelist_urls) http_request("PUT", cmd_url, json_data, DEFAULT_HEADERS) list_of_urls = "" for url in urls_to_whitelist: list_of_urls += "- " + url + "\n" return "Added the following URLs to the whitelist successfully:\n" + list_of_urls def unwhitelist_url(url): cmd_url = "/security" urls_to_unwhitelist = argToList(url) # Get the current whitelist whitelist_urls = get_whitelist() if not whitelist_urls: whitelist_urls["whitelistUrls"] = [] # Check if given URL is whitelisted if len(urls_to_unwhitelist) == 1: # Given only one URL to whitelist if urls_to_unwhitelist[0] not in whitelist_urls["whitelistUrls"]: raise Exception("Given host address is not whitelisted.") elif not set(urls_to_unwhitelist).issubset(set(whitelist_urls["whitelistUrls"])): # Given more than one URL to whitelist raise Exception("Given host addresses are not whitelisted.") # List comprehension to remove requested URLs from the whitelist whitelist_urls["whitelistUrls"] = [x for x in whitelist_urls["whitelistUrls"] if x not in urls_to_unwhitelist] json_data = json.dumps(whitelist_urls) http_request("PUT", cmd_url, json_data, DEFAULT_HEADERS) list_of_urls = "" for url in whitelist_urls: list_of_urls += "- " + url + "\n" return "Removed the following URLs from the whitelist successfully:\n" + list_of_urls def whitelist_ip(ip): cmd_url = "/security" ips_to_whitelist = argToList(ip) # Get the current whitelist whitelist_ips = get_whitelist() if not whitelist_ips: whitelist_ips["whitelistUrls"] = [] whitelist_ips["whitelistUrls"] += ips_to_whitelist json_data = json.dumps(whitelist_ips) http_request("PUT", cmd_url, json_data, DEFAULT_HEADERS) list_of_ips = "" for ip in ips_to_whitelist: list_of_ips += "- " + ip + "\n" return "Added the following URLs to the whitelist successfully:\n" + list_of_ips def unwhitelist_ip(ip): cmd_url = "/security" ips_to_unwhitelist = argToList(ip) # Get the current whitelist whitelist_ips = get_whitelist() if not whitelist_ips: whitelist_ips["whitelistUrls"] = [] # Check if given IP is whitelisted if len(ips_to_unwhitelist) == 1: # Given only one IP to whitelist if ips_to_unwhitelist[0] not in whitelist_ips["whitelistUrls"]: raise Exception("Given IP address is not whitelisted.") elif not set(ips_to_unwhitelist).issubset(set(whitelist_ips["whitelistUrls"])): # Given more than one IP to whitelist raise Exception("Given IP address is not whitelisted.") # List comprehension to remove requested IPs from the whitelist whitelist_ips["whitelistUrls"] = [x for x in whitelist_ips["whitelistUrls"] if x not in ips_to_unwhitelist] json_data = json.dumps(whitelist_ips) http_request("PUT", cmd_url, json_data, DEFAULT_HEADERS) list_of_ips = "" for ip in ips_to_unwhitelist: list_of_ips += "- " + ip + "\n" return "Removed the following IP addresses from the whitelist successfully:\n" + list_of_ips def get_blacklist_command(args): blacklist = get_blacklist().get("blacklistUrls") if blacklist: filter_ = args.get("filter", "") query = args.get("query", "") if filter_ or query: filtered_blacklist = [] for entity in blacklist: # if filter / query were not provided, then there is a match on it vacuously is_filter_match = not filter_ is_query_match = not query if filter_: if re.match(ipv4Regex, entity): if filter_ == "ip": is_filter_match = True elif filter_ == "url": is_filter_match = True if query: if re.search(query, entity): is_query_match = True else: is_query_match = False if is_filter_match and is_query_match: filtered_blacklist.append(entity) blacklist = filtered_blacklist if blacklist: hr = "### Zscaler blacklist\n" for url in blacklist: hr += "- " + url + "\n" ec = {"Zscaler.Blacklist": blacklist} entry = { "Type": entryTypes["note"], "Contents": blacklist, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": hr, "EntryContext": ec, } return entry else: return "No results found" def get_blacklist(): cmd_url = "/security/advanced" result = http_request("GET", cmd_url, None, DEFAULT_HEADERS, resp_type="content") return json.loads(result) def get_whitelist_command(): whitelist = get_whitelist().get("whitelistUrls") if whitelist: hr = "### Zscaler whitelist\n" for url in whitelist: hr += "- " + url + "\n" ec = {"Zscaler.Whitelist": whitelist} entry = { "Type": entryTypes["note"], "Contents": whitelist, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": hr, "EntryContext": ec, } return entry else: return "No results found" def get_whitelist(): cmd_url = "/security" result = http_request("GET", cmd_url, None, DEFAULT_HEADERS, resp_type="content") return json.loads(result) def url_lookup(args): url = args.get("url", "") multiple = args.get("multiple", "true").lower() == "true" response = lookup_request(url, multiple) raw_res = json.loads(response) urls_list = argToList(url) results: List[CommandResults] = [] for data in raw_res: res_url = data.get("url") for url in urls_list: # since zscaler expects to recieve a URL without the protocol, we omit it in `lookup_request` # in the response, the URL is returned as it was sent, so we add back the protocol by replacing # the URL retruned with the one we got as an argument if "http://" + res_url in url or "https://" + res_url in url: data["url"] = url ioc_context = {"Address": data["url"], "Data": data["url"]} score = Common.DBotScore.GOOD if len(data["urlClassifications"]) == 0: data["urlClassifications"] = "" else: data["urlClassifications"] = "".join(data["urlClassifications"]) ioc_context["urlClassifications"] = data["urlClassifications"] if data["urlClassifications"] == "MISCELLANEOUS_OR_UNKNOWN": score = Common.DBotScore.NONE if len(data["urlClassificationsWithSecurityAlert"]) == 0: data["urlClassificationsWithSecurityAlert"] = "" else: data["urlClassificationsWithSecurityAlert"] = "".join(data["urlClassificationsWithSecurityAlert"]) ioc_context["urlClassificationsWithSecurityAlert"] = data["urlClassificationsWithSecurityAlert"] if data["urlClassificationsWithSecurityAlert"] in SUSPICIOUS_CATEGORIES: score = Common.DBotScore.SUSPICIOUS else: score = Common.DBotScore.BAD data["ip"] = data.pop("url") url_indicator = Common.URL( url=ioc_context["Data"], dbot_score=Common.DBotScore( indicator=ioc_context["Data"], indicator_type=DBotScoreType.URL, integration_name=INTEGRATION_NAME, malicious_description=data.get("urlClassificationsWithSecurityAlert", None), score=score, reliability=demisto.params().get("reliability"), ), ) results.append( CommandResults( outputs_prefix=f"{INTEGRATION_NAME}.URL", outputs_key_field="Data", indicator=url_indicator, readable_output=tableToMarkdown( f'Zscaler URL Lookup for {ioc_context["Data"]}', data, removeNull=True, ), outputs=createContext(data=ioc_context, removeNull=True), raw_response=data, ) ) return results or "No results found." def ip_lookup(ip): results: List[CommandResults] = [] response = lookup_request(ip, multiple=True) raw_res = json.loads(response) for data in raw_res: ioc_context = {"Address": data["url"]} score = Common.DBotScore.GOOD if len(data["urlClassifications"]) == 0: data["iplClassifications"] = "" else: data["ipClassifications"] = "".join(data["urlClassifications"]) ioc_context["ipClassifications"] = data["ipClassifications"] del data["urlClassifications"] if len(data["urlClassificationsWithSecurityAlert"]) == 0: data["ipClassificationsWithSecurityAlert"] = "" else: data["ipClassificationsWithSecurityAlert"] = "".join(data["urlClassificationsWithSecurityAlert"]) ioc_context["ipClassificationsWithSecurityAlert"] = data["ipClassificationsWithSecurityAlert"] if data["urlClassificationsWithSecurityAlert"] in SUSPICIOUS_CATEGORIES: score = Common.DBotScore.SUSPICIOUS else: score = Common.DBotScore.BAD del data["urlClassificationsWithSecurityAlert"] data["ip"] = data.pop("url") ip_indicator = Common.IP( ip=data["ip"], dbot_score=Common.DBotScore( indicator=data["ip"], indicator_type=DBotScoreType.IP, integration_name=INTEGRATION_NAME, malicious_description=data.get("ipClassificationsWithSecurityAlert", None), score=score, reliability=demisto.params().get("reliability"), ), ) results.append( CommandResults( outputs_prefix=f"{INTEGRATION_NAME}.IP", indicator=ip_indicator, outputs_key_field="Address", readable_output=tableToMarkdown( f'Zscaler IP Lookup for {ioc_context["Address"]}', data, removeNull=True, ), outputs=createContext(data=ioc_context, removeNull=True), raw_response=data, ) ) return results or "No results found." def domain_lookup(args): domain_arg = args.get("domain", "") multiple = argToBoolean(args.get("multiple", "true")) response = lookup_request(domain_arg, multiple) raw_res = json.loads(response) results: List[CommandResults] = [] for data in raw_res: res_domain = data.get("url") ioc_context = {"Name": res_domain} score = Common.DBotScore.GOOD if not data.get("urlClassifications", []): data["domainClassifications"] = "" ioc_context["domainClassifications"] = "" else: data["domainClassifications"] = "".join(data["urlClassifications"]) ioc_context["domainClassifications"] = data["domainClassifications"] if data["domainClassifications"] == "MISCELLANEOUS_OR_UNKNOWN": score = Common.DBotScore.NONE data.pop("urlClassifications", None) if not data.get("urlClassificationsWithSecurityAlert", []): data["domainClassificationsWithSecurityAlert"] = "" ioc_context["domainClassificationsWithSecurityAlert"] = "" else: data["domainClassificationsWithSecurityAlert"] = "".join(data["urlClassificationsWithSecurityAlert"]) ioc_context["domainClassificationsWithSecurityAlert"] = data["domainClassificationsWithSecurityAlert"] if data["domainClassificationsWithSecurityAlert"] in SUSPICIOUS_CATEGORIES: score = Common.DBotScore.SUSPICIOUS else: score = Common.DBotScore.BAD data.pop("urlClassificationsWithSecurityAlert", None) data["domain"] = data.pop("url", None) domain_indicator = Common.Domain( domain=ioc_context["Name"], dbot_score=Common.DBotScore( indicator=ioc_context["Name"], indicator_type=DBotScoreType.DOMAIN, integration_name=INTEGRATION_NAME, malicious_description=data.get("domainClassificationsWithSecurityAlert", None), score=score, reliability=demisto.params().get("reliability"), ), ) results.append( CommandResults( outputs_prefix=f"{INTEGRATION_NAME}.Domain", outputs_key_field="Name", indicator=domain_indicator, readable_output=tableToMarkdown( f'Zscaler Domain Lookup for {ioc_context["Name"]}', data, removeNull=True, ), outputs=createContext(data=ioc_context, removeNull=True), raw_response=data, ) ) return results or "No results found." def lookup_request(ioc, multiple=True): cmd_url = "/urlLookup" if multiple: ioc_list = argToList(ioc) else: ioc_list = [ioc] ioc_list = [url.replace("https://", "").replace("http://", "") for url in ioc_list] json_data = json.dumps(ioc_list) response = http_request("POST", cmd_url, json_data, DEFAULT_HEADERS, resp_type="content") return response def category_add(category_id, data, retaining_parent_category_data, data_type, category_name=None): if not any((data, retaining_parent_category_data)): return_error(f"Either {data_type} argument or retaining-parent-category-{data_type} argument must be provided.") category_data = get_category_by_id(category_id, category_name) demisto.debug(f"{category_data=}") if category_data: # check if the category exists data_list = argToList(data) all_data = data_list[:] all_data.extend([x.strip() for x in category_data["urls"]]) category_data["urls"] = all_data retaining_parent_category_data_list = argToList(retaining_parent_category_data) if not any((data_list, retaining_parent_category_data_list)): return_error(f"Either {data_type} argument or retaining-parent-category-{data_type} argument must be provided.") add_or_remove_urls_from_category( ADD, data_list, category_data, retaining_parent_category_data_list, category_name ) # add the urls to the category context = { "ID": category_id, "CustomCategory": category_data.get("customCategory"), "URL": category_data.get("urls"), } if category_data.get("description"): # Custom might not have description context["Description"] = category_data["description"] ec = {"Zscaler.Category(val.ID && val.ID === obj.ID)": context} added_data = "\n".join(f"- {item}" for item in data_list) + "\n".join( f"- {item}" for item in retaining_parent_category_data_list ) hr = ( f"Added the following {data_type.upper()}, retaining-parent-category-{data_type} " f"addresses to category {category_id}:\n{added_data}\n" ) entry = { "Type": entryTypes["note"], "Contents": category_data, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": hr, "EntryContext": ec, } return entry else: return return_error("Category could not be found.") def category_remove(category_id, data, retaining_parent_category_data, data_type, category_name=None): if not any((data, retaining_parent_category_data)): return_error(f"Either {data_type} argument or retaining-parent-category-{data_type} argument must be provided.") category_data = get_category_by_id(category_id, category_name) # check if the category exists demisto.debug(f"{category_data=}") if category_data: removed_data = "" data_list = [] retaining_parent_category_data_list = [] if data: data_list = argToList(data) updated_data = [item for item in category_data["urls"] if item not in data_list] if updated_data == category_data["urls"]: return return_error(f"Could not find given {data_type.upper()} in the category.") category_data["urls"] = updated_data for item in data_list: removed_data += f"- {item}\n" if retaining_parent_category_data: retaining_parent_category_data_list = argToList(retaining_parent_category_data) for item in retaining_parent_category_data_list: removed_data += f"- {item}\n" add_or_remove_urls_from_category( REMOVE, data_list, category_data, retaining_parent_category_data_list, category_name ) # remove the urls from list context = { "ID": category_id, "CustomCategory": category_data.get("customCategory"), "URL": category_data.get("urls"), } if category_data.get("description"): # Custom might not have description context["Description"] = category_data["description"] hr = f"Removed the following {data_type.upper()} addresses to category {category_id}:\n{removed_data}" ec = {"Zscaler.Category(val.ID && val.ID === obj.ID)": context} entry = { "Type": entryTypes["note"], "Contents": category_data, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": hr, "EntryContext": ec, } return entry else: return return_error("Category could not be found.") def add_or_remove_urls_from_category(action, urls, category_data, retaining_parent_category_data=None, category_name=None): """ Add or remove urls from a category. Args: str action: The action requested, can be 'ADD_TO_LIST' for adding or 'REMOVE_FROM'_LIST for removing. List[Any] urls: the list of urls to add or remove from the category Dict[str: Any] category_data: the data of the category as returned from the API List[Any] retaining_parent_category_data: optional list of URLs to add/remove from dbCategorizedUrls str category_name: optional category name to use (required for custom categories in newer API versions) Returns: The response as returned from the API """ demisto.debug("##### add_or_remove_urls_from_category function is now running") cmd_url = "/urlCategories/" + category_data.get("id") + "?action=" + action data = { "customCategory": category_data.get("customCategory"), "urls": urls, "id": category_data.get("id"), } if retaining_parent_category_data: data["dbCategorizedUrls"] = retaining_parent_category_data if "description" in category_data: data["description"] = category_data["description"] # Per Zscaler API documentation: If you are modifying a custom URL category, this request must # additionally specify the configuredName and superCategory information in the request Body. # For predefined categories, these fields are optional but included if available. configured_name = category_name or category_data.get("configuredName") super_category = category_data.get("superCategory") is_custom = argToBoolean(category_data.get("customCategory")) if is_custom and not configured_name: raise DemistoException( "The 'configuredName' field is required for custom categories but was not found in the category data. " "Please provide the category name using the 'category-name' argument. " "You can find the category name by running 'zscaler-get-categories' command." ) if configured_name: data["configuredName"] = configured_name if super_category: data["superCategory"] = super_category demisto.debug(f"{data=}") json_data = json.dumps(data) http_request("PUT", cmd_url, json_data) # if the request is successful, it returns an empty response def url_quota_command(): cmd_url = "/urlCategories/urlQuota" response = http_request("GET", cmd_url) human_readable = { "Unique Provisioned URLs": response.get("uniqueUrlsProvisioned"), "Remaining URLs Quota": response.get("remainingUrlsQuota"), } entry = { "Type": entryTypes["note"], "Contents": response, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown("Quota Information", human_readable), "EntryContext": {"Zscaler.Quota": response}, } return entry def get_categories_command(args): display_urls = argToBoolean(args.get("displayURL")) # urls returned to context data even if set to false custom_only = argToBoolean(args.get("custom_categories_only", False)) ids_and_names_only = argToBoolean(args.get("get_ids_and_names_only", False)) # won't get URLs at all categories = [] raw_categories = get_categories(custom_only, ids_and_names_only) for raw_category in raw_categories: category = { "ID": raw_category["id"], "CustomCategory": raw_category["customCategory"], } if raw_category.get("urls"): category["URL"] = raw_category["urls"] if raw_category.get("dbCategorizedUrls"): category["RetainingParentCategoryURL"] = raw_category["dbCategorizedUrls"] if "description" in raw_category: category["Description"] = raw_category["description"] if "configuredName" in raw_category: category["Name"] = raw_category["configuredName"] categories.append(category) ec = {"Zscaler.Category(val.ID && val.ID === obj.ID)": categories} if display_urls and not ids_and_names_only: headers = ["ID", "Description", "URL", "RetainingParentCategoryURL", "CustomCategory", "Name"] else: headers = ["ID", "Description", "CustomCategory", "Name"] title = "Zscaler Categories" entry = { "Type": entryTypes["note"], "Contents": raw_categories, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown(title, categories, headers), "EntryContext": ec, } return entry def get_categories(custom_only=False, ids_and_names_only=False): if ids_and_names_only: # if you only want a list of URL category IDs and names (i.e without urls list). # Note: API does not support the combination of custom_only and 'lite' endpoint cmd_url = "/urlCategories/lite" else: cmd_url = "/urlCategories?customOnly=true" if custom_only else "/urlCategories" response = http_request("GET", cmd_url) return response def sandbox_report_command(): md5 = demisto.getArg("md5") details = demisto.getArg("details") res = sandbox_report(md5, details) report = "Full Details" if details == "full" else "Summary" ctype = demisto.get(res, f"{report}.Classification.Type") dbot_score = 3 if ctype == "MALICIOUS" else 2 if ctype == "SUSPICIOUS" else 1 if ctype == "BENIGN" else 0 ec = { outputPaths["dbotscore"]: { "Indicator": md5, "Type": "file", "Vendor": "Zscaler", "Score": dbot_score, "Reliability": demisto.params().get("reliability"), } } human_readable_report = ec["DBotScore"].copy() human_readable_report["Detected Malware"] = str(demisto.get(res, f"{report}.Classification.DetectedMalware")) human_readable_report["Zscaler Score"] = demisto.get(res, f"{report}.Classification.Score") human_readable_report["Category"] = demisto.get(res, f"{report}.Classification.Category") ec[outputPaths["file"]] = { "MD5": md5, "Zscaler": { "DetectedMalware": demisto.get(res, f"{report}.Classification.DetectedMalware"), "FileType": demisto.get(res, f"{report}.File Properties.File Type"), }, } if dbot_score == 3: ec[outputPaths["file"]]["Malicious"] = { "Vendor": "Zscaler", "Description": "Classified as Malicious, with threat score: " + str(human_readable_report["Zscaler Score"]), } entry = { "Type": entryTypes["note"], "Contents": res, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown("Full Sandbox Report", human_readable_report, removeNull=True), "EntryContext": ec, } return entry def sandbox_report(md5, details): cmd_url = f"/sandbox/report/{md5}?details={details}" response = http_request("GET", cmd_url) return response def login_command(): ctx = get_integration_context() or {} session_id = ctx.get(SESSION_ID_KEY) if session_id: try: DEFAULT_HEADERS["cookie"] = session_id demisto.info("Zscaler logout active session triggered by zscaler-login command.") logout() except Exception as e: demisto.info(f"Zscaler logout failed with: {str(e)}") login() return CommandResults(readable_output="Zscaler session created successfully.") def logout_command(): ctx = get_integration_context() or {} session_id = ctx.get(SESSION_ID_KEY) if not session_id: return CommandResults(readable_output="No API session was found. No action was performed.") try: DEFAULT_HEADERS["cookie"] = session_id raw_res = logout() except AuthorizationError: return CommandResults(readable_output="API session is not authenticated. No action was performed.") return CommandResults( readable_output="API session logged out of Zscaler successfully.", raw_response=raw_res, ) def activate_command(): raw_res = activate_changes() return CommandResults( readable_output="Changes have been activated successfully.", raw_response=raw_res, ) def test_module(): http_request("GET", "/status", None, DEFAULT_HEADERS) return "ok" def get_category_by_id(category_id, category_name=None): """ Get category data by ID. Args: category_id: The category ID to look up category_name: Optional category name to inject into the result for custom categories Returns: The category data dict, or None if not found """ categories = get_categories() for category in categories: if category["id"] == category_id: # If category_name is provided and the category doesn't have configuredName, add it if category_name and "configuredName" not in category: category["configuredName"] = category_name return category return None def get_users_command(args): name = args.get("name", None) pageSize = args.get("pageSize") pageNo = args.get("page", 1) if name is not None: cmd_url = f"/users?page={pageNo}&pageSize={pageSize}&name={name}" else: cmd_url = f"/users?page={pageNo}&pageSize={pageSize}" response = http_request("GET", cmd_url) if len(response) < 10: human_readable = tableToMarkdown(f"Users ({len(response)})", response) else: human_readable = f"Retrieved {len(response)} users" entry = { "Type": entryTypes["note"], "Contents": response, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": human_readable, "EntryContext": {"Zscaler.Users": response}, } return entry def get_departments_command(args): name = args.get("name", None) pageSize = args.get("pageSize") pageNo = args.get("page", 1) if name is not None: cmd_url = f"/departments?page={pageNo}&pageSize={pageSize}&search={name}&limitSearch=true" else: cmd_url = f"/departments?page={pageNo}&pageSize={pageSize}" response = http_request("GET", cmd_url) if len(response) < 10: human_readable = tableToMarkdown(f"Departments ({len(response)})", response) else: human_readable = f"Retrieved {len(response)} departments" entry = { "Type": entryTypes["note"], "Contents": response, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": human_readable, "EntryContext": {"Zscaler.Departments": response}, } return entry def get_usergroups_command(args): name = args.get("name", None) pageSize = args.get("pageSize") pageNo = args.get("page", 1) if name is not None: cmd_url = f"/groups?page={pageNo}&pageSize={pageSize}&search={name}" else: cmd_url = f"/groups?page={pageNo}&pageSize={pageSize}" response = http_request("GET", cmd_url) if len(response) < 10: human_readable = tableToMarkdown(f"User groups ({len(response)})", response) else: human_readable = f"Retrieved {len(response)} user groups" entry = { "Type": entryTypes["note"], "Contents": response, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": human_readable, "EntryContext": {"Zscaler.UserGroups": response}, } return entry def set_user_command(args): userId = args.get("id") params = json.loads(args.get("user")) cmd_url = f"/users/{userId}" response = http_request("PUT", cmd_url, json.dumps(params), DEFAULT_HEADERS, resp_type="response") responseJson = response.json() if response.status_code == 200: entry = { "Type": entryTypes["note"], "Contents": responseJson, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": "Successfully updated the user (id: {} name: {})".format(responseJson["id"], responseJson["name"]), "EntryContext": {"Zscaler.Users": responseJson}, } return entry else: return responseJson def create_ip_destination_group(args: dict): headers = [ "ID", "Name", "Type", "Description", "Addresses", "Countries", "IpCategories", "IsNonEditable", ] payload = { "name": args.get("name", ""), "type": args.get("type", ""), "countries": argToList(args.get("countries", "")), "ipCategories": argToList(args.get("ip_categories", "")), "description": args.get("description", ""), "addresses": argToList(args.get("addresses", "")), "isNonEditable": args.get("is_non_editable", False), } cmd_url = "/ipDestinationGroups" response = http_request("POST", cmd_url, data=json.dumps(payload), headers=DEFAULT_HEADERS) content = { "ID": int(response.get("id", "")), "Name": response.get("name", ""), "Type": response.get("type", ""), "Description": response.get("description", ""), "Addresses": response.get("addresses", []), "IpCategories": response.get("ipCategories", []), "Countries": response.get("countries", []), "IsNonEditable": response.get("isNonEditable", False), } markdown = tableToMarkdown("IPv4 Destination group created", content, headers, removeNull=True) results = CommandResults( readable_output=markdown, outputs_prefix="Zscaler.IPDestinationGroup", outputs_key_field="ID", outputs=content, ) return results def list_ip_destination_groups(args: dict): ip_group_ids = argToList(args.get("ip_group_id", "")) exclude_type = str(args.get("exclude_type", "")).strip() category_type = argToList(args.get("type", "")) include_ipv6 = argToBoolean(args.get("include_ipv6", False)) limit = arg_to_number(args.get("limit", 50)) all_results = argToBoolean(args.get("all_results", False)) lite = argToBoolean(args.get("lite", False)) headers = [ "ID", "Name", "Type", "Description", "Addresses", "Countries", "IpCategories", ] def get_contents(responses: List[dict]): contents = [] for response in responses: content = { "ID": int(response.get("id", "")), "Name": response.get("name", ""), "Type": response.get("type", ""), "Description": response.get("description", ""), "Addresses": response.get("addresses", []), "IpCategories": response.get("ipCategories", []), "Countries": response.get("countries", []), } contents.append(content) return contents def get_contents_lite(responses: List[dict]): contents = [] for response in responses: content = {} for key, value in response.items(): if key == "extensions": for extensions_key, extensions_value in value.items(): content[f"{extensions_key.capitalize()}"] = extensions_value elif key == "id": content[key.upper()] = value else: content[key.capitalize()] = value contents.append(content) return contents if len(ip_group_ids) == 0: lite_endpoint = "/lite" if lite else "" if exclude_type: exclude_type_param = f"?excludeType={exclude_type}&" else: exclude_type_param = "?" type_params = [f"type={t}" for t in category_type] type_params_str = "&".join(type_params) if include_ipv6: ipv4_cmd_url = "/ipDestinationGroups" + lite_endpoint + exclude_type_param + type_params_str ipv6_cmd_url = "/ipDestinationGroups/ipv6DestinationGroups" + lite_endpoint + exclude_type_param + type_params_str ipv4_responses = http_request("GET", ipv4_cmd_url) ipv6_responses = http_request("GET", ipv6_cmd_url) ipv4_contents_filter = get_contents_lite(ipv4_responses) if lite else get_contents(ipv4_responses) ipv4_contents = ipv4_contents_filter if all_results else ipv4_contents_filter[:limit] ipv6_contents_filter = get_contents_lite(ipv6_responses) if lite else get_contents(ipv6_responses) ipv6_contents = ipv6_contents_filter if all_results else ipv6_contents_filter[:limit] markdown = tableToMarkdown( f"IPv4 Destination groups ({len(ipv4_contents)})", ipv4_contents, headers, removeNull=True, ) markdown += tableToMarkdown( f"IPv6 Destination groups ({len(ipv6_contents)})", ipv6_contents, headers, removeNull=True, ) contents = ipv4_contents + ipv6_contents results = CommandResults( readable_output=markdown, outputs_prefix="Zscaler.IPDestinationGroup", outputs_key_field="ID", outputs=contents, ) return results else: cmd_url = "/ipDestinationGroups" + lite_endpoint + exclude_type_param + type_params_str responses = http_request("GET", cmd_url) contents_filter = get_contents_lite(responses) if lite else get_contents(responses) contents = contents_filter if all_results else contents_filter[:limit] markdown = tableToMarkdown( f"IPv4 Destination groups ({len(contents)})", contents, headers, removeNull=True, ) results = CommandResults( readable_output=markdown, outputs_prefix="Zscaler.IPDestinationGroup", outputs_key_field="ID", outputs=contents, ) return results else: responses = [] for ip_group_id in ip_group_ids: cmd_url = f"/ipDestinationGroups/{ip_group_id}" responses.append(http_request("GET", cmd_url)) contents = get_contents(responses) markdown = tableToMarkdown( f"IPv4 Destination groups ({len(contents)})", contents, headers, removeNull=True, ) results = CommandResults( readable_output=markdown, outputs_prefix="Zscaler.IPDestinationGroup", outputs_key_field="ID", outputs=contents, ) return results def edit_ip_destination_group(args: dict): headers = [ "ID", "Name", "Type", "Description", "Addresses", "Countries", "IpCategories", ] payload: dict = {} ip_group_id = str(args["ip_group_id"]).strip() check_url = f"/ipDestinationGroups/{ip_group_id}" demisto.debug(f"Fetching existing IP destination group: {ip_group_id}") response_data = http_request("GET", check_url) demisto.debug(f"Fetched group data: {response_data}") if not response_data or not response_data.get("id"): raise Exception(f"Resource not found with ip_group_id {ip_group_id}") payload["name"] = args.get("name", response_data.get("name", "")) payload["countries"] = argToList(args.get("countries", response_data.get("countries", []))) payload["ipCategories"] = argToList(args.get("ip_categories", response_data.get("ipCategories", []))) payload["addresses"] = argToList(args.get("addresses", response_data.get("addresses", []))) payload["description"] = args.get("description", response_data.get("description", "")) payload["isNonEditable"] = args.get("is_non_editable", False) payload["type"] = response_data.get("type", "") cmd_url = f"/ipDestinationGroups/{ip_group_id}" # The override query parameter controls whether existing entries are replaced (true) or preserved (false). Default is true. override: bool = argToBoolean(args.get("override", True)) if not override: cmd_url = f"{cmd_url}?override=false" json_data = json.dumps(payload) demisto.debug(f"Updating IP destination group {ip_group_id} with payload={payload}, override={override}") response = http_request("PUT", cmd_url, json_data, DEFAULT_HEADERS) content = { "ID": int(response.get("id", "")), "Name": response.get("name", ""), "Type": response.get("type", ""), "Description": response.get("description", ""), "Addresses": response.get("addresses", []), "IpCategories": response.get("ipCategories", []), "Countries": response.get("countries", []), } markdown = tableToMarkdown("IPv4 Destination group updated", content, headers, removeNull=True) results = CommandResults( readable_output=markdown, outputs_prefix="Zscaler.IPDestinationGroup", outputs_key_field="ID", outputs=content ) return results def delete_ip_destination_groups(args: dict): ip_group_ids = argToList(args.get("ip_group_id", "")) for ip_group_id in ip_group_ids: cmd_url = f"/ipDestinationGroups/{ip_group_id}" _ = http_request("DELETE", cmd_url, None, DEFAULT_HEADERS) markdown = "### IP Destination Group {} deleted successfully".format(",".join(ip_group_ids)) results = CommandResults( readable_output=markdown, outputs_prefix="Zscaler.IPDestinationGroup", outputs_key_field=None, outputs=None ) return results """ EXECUTION CODE """ def main(): # pragma: no cover command = demisto.command() params = demisto.params() args = demisto.args() add_sensitive_log_strs(USERNAME) add_sensitive_log_strs(PASSWORD) demisto.debug(f"command is {command}") if command == "zscaler-login": return_results(login_command()) elif command == "zscaler-logout": return_results(logout_command()) else: try: login() if command == "test-module": return_results(test_module()) elif command == "url": return_results(url_lookup(args)) elif command == "ip": return_results(ip_lookup(args.get("ip"))) elif command == "domain": return_results(domain_lookup(args)) elif command == "zscaler-blacklist-url": return_results(blacklist_url(args.get("url"))) elif command == "zscaler-undo-blacklist-url": return_results(unblacklist_url(args.get("url"))) elif command == "zscaler-whitelist-url": return_results(whitelist_url(args.get("url"))) elif command == "zscaler-undo-whitelist-url": return_results(unwhitelist_url(args.get("url"))) elif command == "zscaler-blacklist-ip": return_results(blacklist_ip(args.get("ip"))) elif command == "zscaler-undo-blacklist-ip": return_results(unblacklist_ip(args.get("ip"))) elif command == "zscaler-whitelist-ip": return_results(whitelist_ip(args.get("ip"))) elif command == "zscaler-undo-whitelist-ip": return_results(unwhitelist_ip(args.get("ip"))) elif command == "zscaler-category-add-url": return_results( category_add( args.get("category-id"), args.get("url"), args.get("retaining-parent-category-url"), "url", args.get("category-name"), ) ) elif command == "zscaler-category-add-ip": return_results( category_add( args.get("category-id"), args.get("ip"), args.get("retaining-parent-category-ip"), "ip", args.get("category-name"), ) ) elif command == "zscaler-category-remove-url": return_results( category_remove( args.get("category-id"), args.get("url"), args.get("retaining-parent-category-url"), "url", args.get("category-name"), ) ) elif command == "zscaler-category-remove-ip": return_results( category_remove( args.get("category-id"), args.get("ip"), args.get("retaining-parent-category-ip"), "ip", args.get("category-name"), ) ) elif command == "zscaler-get-categories": return_results(get_categories_command(args)) elif command == "zscaler-get-blacklist": return_results(get_blacklist_command(args)) elif command == "zscaler-get-whitelist": return_results(get_whitelist_command()) elif command == "zscaler-sandbox-report": return_results(sandbox_report_command()) elif command == "zscaler-activate-changes": return_results(activate_command()) elif command == "zscaler-url-quota": return_results(url_quota_command()) elif command == "zscaler-get-users": return_results(get_users_command(args)) elif command == "zscaler-update-user": return_results(set_user_command(args)) elif command == "zscaler-get-departments": return_results(get_departments_command(args)) elif command == "zscaler-get-usergroups": return_results(get_usergroups_command(args)) elif command == "zscaler-list-ip-destination-groups": return_results(list_ip_destination_groups(args)) elif command == "zscaler-create-ip-destination-group": return_results(create_ip_destination_group(args)) elif command == "zscaler-edit-ip-destination-group": return_results(edit_ip_destination_group(args)) elif command == "zscaler-delete-ip-destination-groups": return_results(delete_ip_destination_groups(args)) else: raise NotImplementedError(f"Command {command} is not implemented") except Exception as e: return_error(f"Failed to execute {command} command. Error: {str(e)}") finally: try: # activate changes only when required if params.get("auto_activate") and command in AUTO_ACTIVATE_CHANGES_COMMANDS: demisto.debug(f"Activating changes for command {command}") activate_changes() if params.get("auto_logout"): demisto.debug("Logging out") logout() except Exception as err: return_error("Zscaler error: " + str(err)) # python2 uses __builtin__ python3 uses builtins if __name__ in ("__builtin__", "builtins", "__main__"): # pragma: no cover main()

