Cisco Umbrella Cloud Security v2
Cisco Umbrella is a cloud security platform providing the first line of defense against internet threats. It uses DNS-layer security to block malicious requests before a connection is established, offering protection against malware, ransomware, phishing, and more. It offers real-time reporting, integrates with other Cisco solutions for layered security, and uses machine learning to uncover and predict threats.
Network Security · Cisco Umbrella cloud security
Details
| ID | Cisco Umbrella Cloud Security v2 |
|---|---|
| Provider | Cisco Systems |
| Category | Network Security |
| From Version | 6.9.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Cisco Umbrella is a cloud security platform providing the first line of defense against internet threats. It uses DNS-layer security to block malicious requests before a connection is established, offering protection against malware, ransomware, phishing, and more. It offers real-time reporting, integrates with other Cisco solutions for layered security, and uses machine learning to uncover and predict threats.
This integration was tested with version 2 of Cisco Umbrella Cloud Security
Configure Cisco Umbrella Cloud Security v2 in Cortex
| Parameter | Required |
|---|---|
| API Key | True |
| API Secret | True |
| Use system proxy settings | False |
| Trust any certificate (not secure) | False |
| Base URL | True |
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.
umbrella-destinations-list
Get destinations within a destination list. A destination is a URL, IP or fully qualified domain name.
Base Command
umbrella-destinations-list
Input
| Argument Name | Description | Required |
|---|---|---|
| destination_list_id | The ID of the destination list. Destination lists can be fetched with the umbrella-destination-lists-list command. |
Required |
| destination_ids | Comma-separated list of destination IDs to be retrieved from a list of destinations. | Optional |
| destinations | Comma-separated list of destinations to retrieve, a destination may be a domain, URL, or IP address. | Optional |
| page | Page number of paginated results. Minimum 1; Default 1. | Optional |
| page_size | The number of items per page. Minimum 1; Maximum 100; Default 50. | Optional |
| limit | The number of items per page. Minimum 1. Default is 50. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Umbrella.Destinations.id | String | The unique ID of the destination. |
| Umbrella.Destinations.destination | String | A destination may be a domain, URL, or IP address. |
| Umbrella.Destinations.type | String | The type of destination within the destination list. |
| Umbrella.Destinations.comment | String | A comment about the destination. |
| Umbrella.Destinations.createdAt | Date | The date and time when the destination list was created. |
Command example
!umbrella-destinations-list destination_list_id=17425859 limit=3
Context Example
{
"Umbrella": {
"Destinations": [
{
"comment": "Added from XSOAR",
"createdAt": "2023-07-19 18:21:11",
"destination": "www.facebook.com",
"id": "154",
"type": "domain"
},
{
"comment": "Lior",
"createdAt": "2023-07-06 04:42:55",
"destination": "cisco.com",
"id": "30058",
"type": "domain"
},
{
"comment": "Sabri",
"createdAt": "2023-07-06 04:42:55",
"destination": "www.pokemon.com",
"id": "138036",
"type": "domain"
}
]
}
}
Human Readable Output
Destination(s)
Id Destination Type Comment Created At 154 www.facebook.com domain Added from XSOAR 2023-07-19 18:21:11 30058 cisco.com domain Pikachu 2023-07-06 04:42:55 138036 www.pokemon.com domain Choose 2023-07-06 04:42:55
umbrella-destination-add
Add a destination to a destination list. A destination is a URL, IPv4, CIDR or fully qualified domain name. Accepted types for destination list with the access “allow” are: DOMAIN, IPv4 and CIDR. Accepted types for destination list with the access “block” are: URL and DOMAIN.
Base Command
umbrella-destination-add
Input
| Argument Name | Description | Required |
|---|---|---|
| destination_list_id | The ID of the destination list. Destination lists can be fetched with the umbrella-destination-lists-list command. |
Required |
| destinations | Comma-separated list of destinations. A destination may be a URL, IPv4, CIDR or fully qualified domain name. | Required |
| comment | A comment about all the inserted destinations. Default is Added from XSOAR. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Umbrella.DestinationLists.id | Number | The unique ID of the destination list. |
| Umbrella.DestinationLists.organizationId | Number | The organization ID. |
| Umbrella.DestinationLists.access | String | The type of access for the destination list. Valid values are: allow or block. |
| Umbrella.DestinationLists.isGlobal | Boolean | Specifies whether the destination list is a global destination list. There is only one default destination list of type ‘allow’ or ‘block’ for an organization. |
| Umbrella.DestinationLists.name | String | The name of the destination list. |
| Umbrella.DestinationLists.thirdpartyCategoryId | Number | The third-party category ID of the destination list. |
| Umbrella.DestinationLists.createdAt | Number | The date and time when the destination list was created. |
| Umbrella.DestinationLists.modifiedAt | Number | The date and time when the destination list was modified. |
| Umbrella.DestinationLists.isMspDefault | Boolean | Specifies whether MSP is the default. |
| Umbrella.DestinationLists.markedForDeletion | Boolean | Specifies whether the destination list is marked for deletion. |
| Umbrella.DestinationLists.bundleTypeId | Number | The number that represents the type of the Umbrella policy associated with the destination list. Umbrella returns ‘1’ for the DNS policy or ‘2’ for the Web policy. |
| Umbrella.DestinationLists.meta.destinationCount | Number | The total number of destinations in a destination list. |
Base Command
umbrella-destination-add
Input
| Argument Name | Description | Required |
|---|---|---|
| destination_list_id | The ID of the destination list. Destination Lists can be fetched with umbrella-destination-lists-list. |
Required |
| destinations | Comma separated list of destinations. A destination may be a URL, IPv4, CIDR or fully qualified domain name. | Required |
| comment | A comment about all the inserted destinations. Default value: “Added from XSOAR”. Default is Added from XSOAR. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Umbrella.DestinationLists.id | Number | The unique ID of the destination list. |
| Umbrella.DestinationLists.organizationId | Number | The organization ID. |
| Umbrella.DestinationLists.access | String | The type of access for the destination list. Valid values are: allow or block. |
| Umbrella.DestinationLists.isGlobal | Boolean | Specifies whether the destination list is a global destination list. There is only one default destination list of type ‘allow’ or ‘block’ for an organization. |
| Umbrella.DestinationLists.name | String | The name of the destination list. |
| Umbrella.DestinationLists.thirdpartyCategoryId | Number | The third-party category ID of the destination list. |
| Umbrella.DestinationLists.createdAt | Number | The date and time when the destination list was created. |
| Umbrella.DestinationLists.modifiedAt | Number | The date and time when the destination list was modified. |
| Umbrella.DestinationLists.isMspDefault | Boolean | Specifies whether MSP is the default. |
| Umbrella.DestinationLists.markedForDeletion | Boolean | Specifies whether the destination list is marked for deletion. |
| Umbrella.DestinationLists.bundleTypeId | Number | The number that represents the type of the Umbrella policy associated with the destination list. Umbrella returns ‘1’ for the DNS policy or ‘2’ for the Web policy. |
| Umbrella.DestinationLists.meta.destinationCount | Number | The total number of destinations in a destination list. |
Command example
!umbrella-destination-add destination_list_id=17463731 destinations="www.LiorSabri.com,1.1.1.1"
Human Readable Output
The destination(s) “[‘www.LiorSabri.com’, ‘1.1.1.1’]” were successfully added to the destination list “17463731”
umbrella-destination-delete
Remove a destination from a destination list. A destination is a URL, IP or fully qualified domain name.
Base Command
umbrella-destination-delete
Input
| Argument Name | Description | Required |
|---|---|---|
| destination_list_id | The ID of the destination list. Destination lists can be fetched with the umbrella-destination-lists-list command. |
Required |
| destination_ids | Comma-separated list of destination IDs. Destinations can be fetched with the umbrella-destination-list command. |
Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Umbrella.DestinationLists.id | Number | The unique ID of the destination list. |
| Umbrella.DestinationLists.organizationId | Number | The organization ID. |
| Umbrella.DestinationLists.access | String | The type of access for the destination list. Valid values are: allow or block. |
| Umbrella.DestinationLists.isGlobal | Boolean | Specifies whether the destination list is a global destination list. There is only one default destination list of type ‘allow’ or ‘block’ for an organization. |
| Umbrella.DestinationLists.name | String | The name of the destination list. |
| Umbrella.DestinationLists.thirdpartyCategoryId | Number | The third-party category ID of the destination list. |
| Umbrella.DestinationLists.createdAt | Number | The date and time when the destination list was created. |
| Umbrella.DestinationLists.modifiedAt | Number | The date and time when the destination list was modified. |
| Umbrella.DestinationLists.isMspDefault | Boolean | Specifies whether MSP is the default. |
| Umbrella.DestinationLists.markedForDeletion | Boolean | Specifies whether the destination list is marked for deletion. |
| Umbrella.DestinationLists.bundleTypeId | Number | The number that represents the type of the Umbrella policy associated with the destination list. Umbrella returns ‘1’ for the DNS policy or ‘2’ for the Web policy. |
| Umbrella.DestinationLists.meta.destinationCount | Number | The total number of destinations in a destination list. |
Command example
!umbrella-destination-delete destination_list_id=17463733 destination_ids=25826
Human Readable Output
The destination(s) “[25826]” were successfully removed from the destination list “17463733”
umbrella-destination-lists-list
Get destination lists. A list of destinations (for example, domain name or URL) to which you can block or allow access.
Base Command
umbrella-destination-lists-list
Input
| Argument Name | Description | Required |
|---|---|---|
| destination_list_id | The ID of the destination list to retrieve. | Optional |
| page | Page number of paginated results. Minimum 1; Default 1. | Optional |
| page_size | The number of items per page. Minimum 1; Maximum 100; Default 50. | Optional |
| limit | The maximum number of records to retrieve. Minimum 1. Default is 50. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Umbrella.DestinationLists.id | Number | The unique ID of the destination list. |
| Umbrella.DestinationLists.organizationId | Number | The organization ID. |
| Umbrella.DestinationLists.access | String | The type of access for the destination list. Valid values are: allow or block. |
| Umbrella.DestinationLists.isGlobal | Boolean | Specifies whether the destination list is a global destination list. There is only one default destination list of type ‘allow’ or ‘block’ for an organization. |
| Umbrella.DestinationLists.name | String | The name of the destination list. |
| Umbrella.DestinationLists.thirdpartyCategoryId | Number | The third-party category ID of the destination list. |
| Umbrella.DestinationLists.createdAt | Number | The date and time when the destination list was created. |
| Umbrella.DestinationLists.modifiedAt | Number | The date and time when the destination list was modified. |
| Umbrella.DestinationLists.isMspDefault | Boolean | Specifies whether MSP is the default. |
| Umbrella.DestinationLists.markedForDeletion | Boolean | Specifies whether the destination list is marked for deletion. |
| Umbrella.DestinationLists.bundleTypeId | Number | The number that represents the type of the Umbrella policy associated with the destination list. Umbrella returns ‘1’ for the DNS policy or ‘2’ for the Web policy. |
| Umbrella.DestinationLists.meta.destinationCount | Number | The total number of destinations in a destination list. |
| Umbrella.DestinationLists.meta.domainCount | Number | The total number of domains in a destination list. Domains are part of the total number of destinations in a destination list. |
| Umbrella.DestinationLists.meta.ipv4Count | Number | The total number of IP addresses in a destination list. IP addresses are part of the total number of destinations in a destination list. |
| Umbrella.DestinationLists.meta.urlCount | Number | The total number of URLs in a destination list. URLs are part of the total number of destinations in a destination list. |
| Umbrella.DestinationLists.meta.applicationCount | Number | The total number or applications in a destination list. Applications are part of the total number of destinations in a destination list. |
Command example
!umbrella-destination-lists-list limit=3
Context Example
{
"Umbrella": {
"DestinationLists": [
{
"access": "allow",
"bundleTypeId": 1,
"createdAt": 1690184121,
"id": 17463749,
"isGlobal": false,
"isMspDefault": false,
"markedForDeletion": false,
"meta": {
"applicationCount": 0,
"destinationCount": 1,
"domainCount": 0,
"ipv4Count": 1,
"urlCount": 0
},
"modifiedAt": 1690184121,
"name": "Lior",
"organizationId": 123456,
"thirdpartyCategoryId": null
},
{
"access": "allow",
"bundleTypeId": 1,
"createdAt": 1690184234,
"id": 17463756,
"isGlobal": false,
"isMspDefault": false,
"markedForDeletion": false,
"meta": {
"applicationCount": 0,
"destinationCount": 8,
"domainCount": 4,
"ipv4Count": 4,
"urlCount": 0
},
"modifiedAt": 1690184435,
"name": "LiorSB",
"organizationId": 123456,
"thirdpartyCategoryId": null
},
{
"access": "allow",
"bundleTypeId": 1,
"createdAt": 1638798710,
"id": 15609454,
"isGlobal": true,
"isMspDefault": false,
"markedForDeletion": false,
"meta": {
"applicationCount": 0,
"destinationCount": 14,
"domainCount": 10,
"ipv4Count": 4,
"urlCount": 0
},
"modifiedAt": 1690183660,
"name": "Global Allow List",
"organizationId": 123456,
"thirdpartyCategoryId": null
}
]
}
}
Human Readable Output
Destination List
Id Name Access Is Global Destination Count 17463749 Lior allow false 1 17463756 LiorSB allow false 8 15609454 Global Allow List allow true 14
umbrella-destination-list-create
Create a destination list. A list of destinations (for example, domain name or URL) to which you can block or allow access. Accepted types for destination list with the access “allow” are: DOMAIN, IPv4 and CIDR. Accepted types for destination list with the access “block” are: URL and DOMAIN.
Base Command
umbrella-destination-list-create
Input
| Argument Name | Description | Required |
|---|---|---|
| bundle_type | The type of the Umbrella policy associated with the destination list. If the field is not specified, the default value is ‘DNS’. Possible values are: DNS, WEB. | Optional |
| access | The type of access for the destination list. Valid values are “allow” or “block”. Accepted types for destination list with the access “allow” are: DOMAIN, IPv4 and CIDR. Accepted types for destination list with the access “block” are: URL and DOMAIN. Possible values are: allow, block. | Required |
| is_global | Specifies whether the destination list is a global destination list. There is only one default destination list of type ‘allow’ or ‘block’ for an organization. Possible values are: True, False. | Required |
| name | The name of the destination list. | Required |
| destinations | Comma-separated list of destinations. A destination may be a URL, IPv4, CIDR or fully qualified domain name. | Optional |
| destinations_comment | A comment about all the inserted destinations. Default is Added from XSOAR. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Umbrella.DestinationLists.id | Number | The unique ID of the destination list. |
| Umbrella.DestinationLists.organizationId | Number | The organization ID. |
| Umbrella.DestinationLists.access | String | The type of access for the destination list. Valid values are: allow or block. |
| Umbrella.DestinationLists.isGlobal | Boolean | Specifies whether the destination list is a global destination list. There is only one default destination list of type ‘allow’ or ‘block’ for an organization. |
| Umbrella.DestinationLists.name | String | The name of the destination list. |
| Umbrella.DestinationLists.thirdpartyCategoryId | Number | The third-party category ID of the destination list. |
| Umbrella.DestinationLists.createdAt | Number | The date and time when the destination list was created. |
| Umbrella.DestinationLists.modifiedAt | Number | The date and time when the destination list was modified. |
| Umbrella.DestinationLists.isMspDefault | Boolean | Specifies whether MSP is the default. |
| Umbrella.DestinationLists.markedForDeletion | Boolean | Specifies whether the destination list is marked for deletion. |
| Umbrella.DestinationLists.bundleTypeId | Number | The number that represents the type of the Umbrella policy associated with the destination list. Umbrella returns ‘1’ for the DNS policy or ‘2’ for the Web policy. |
| Umbrella.DestinationLists.meta.destinationCount | Number | The total number of destinations in a destination list. |
Command example
!umbrella-destination-list-create access=allow is_global=False name=LiorSBList bundle_type=WEB destinations="https://pokemon.com"
Context Example
{
"Umbrella": {
"DestinationLists": {
"access": "allow",
"bundleTypeId": 1,
"createdAt": 1690208665,
"id": 17464621,
"isGlobal": false,
"isMspDefault": false,
"markedForDeletion": false,
"meta": {
"destinationCount": 1
},
"modifiedAt": 1690208665,
"name": "LiorSBList",
"organizationId": 123456,
"thirdpartyCategoryId": null
}
}
}
Human Readable Output
Destination List
Id Name Access Is Global Destination Count 17464621 LiorSBList allow false 1
umbrella-destination-list-update
Edit a destination list. A list of destinations (for example, domain name or URL) to which you can block or allow access.
Base Command
umbrella-destination-list-update
Input
| Argument Name | Description | Required |
|---|---|---|
| destination_list_id | The ID of the destination list. Destination lists can be fetched with the umbrella-destination-lists-list command. |
Required |
| name | The name of the destination list. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Umbrella.DestinationLists.id | Number | The unique ID of the destination list. |
| Umbrella.DestinationLists.organizationId | Number | The organization ID. |
| Umbrella.DestinationLists.access | String | The type of access for the destination list. Valid values are: allow or block. |
| Umbrella.DestinationLists.isGlobal | Boolean | Specifies whether the destination list is a global destination list. There is only one default destination list of type ‘allow’ or ‘block’ for an organization. |
| Umbrella.DestinationLists.name | String | The name of the destination list. |
| Umbrella.DestinationLists.thirdpartyCategoryId | Number | The third-party category ID of the destination list. |
| Umbrella.DestinationLists.createdAt | Number | The date and time when the destination list was created. |
| Umbrella.DestinationLists.modifiedAt | Number | The date and time when the destination list was modified. |
| Umbrella.DestinationLists.isMspDefault | Boolean | Specifies whether MSP is the default. |
| Umbrella.DestinationLists.markedForDeletion | Boolean | Specifies whether the destination list is marked for deletion. |
| Umbrella.DestinationLists.bundleTypeId | Number | The number that represents the type of the Umbrella policy associated with the destination list. Umbrella returns ‘1’ for the DNS policy or ‘2’ for the Web policy. |
| Umbrella.DestinationLists.meta.destinationCount | Number | The total number of destinations in a destination list. |
Command example
!umbrella-destination-list-update destination_list_id=17463733 name=LiorUpdated
Context Example
{
"Umbrella": {
"DestinationLists": {
"access": "allow",
"bundleTypeId": 1,
"createdAt": 1690183414,
"id": 17463733,
"isGlobal": false,
"isMspDefault": false,
"markedForDeletion": false,
"meta": {
"destinationCount": 0
},
"modifiedAt": 1690208670,
"name": "LiorUpdated",
"organizationId": 123456,
"thirdpartyCategoryId": null
}
}
}
Human Readable Output
Destination List
Id Name Access Is Global Destination Count 17463733 LiorUpdated allow false 0
umbrella-destination-list-delete
Delete a destination list. A list of destinations (for example, domain name or URL) to which you can block or allow access.
Base Command
umbrella-destination-list-delete
Input
| Argument Name | Description | Required |
|---|---|---|
| destination_list_id | The ID of the destination list. Destination lists can be fetched with the umbrella-destination-lists-list command. |
Required |
Context Output
There is no context output for this command.
Command example
!umbrella-destination-list-delete destination_list_id=17463733
Human Readable Output
The destination list “17463733” was successfully deleted
Breaking changes from the previous version of this integration - Cisco Umbrella Cloud Security v2
The following sections list the changes in this version.
Commands
The following commands were removed in this version
- umbrella-get-destination-lists - this command was replaced by umbrella-destination-lists-list.
- umbrella-add-domain - this command was replaced by umbrella-destination-add.
- umbrella-remove-domain - this command was replaced by umbrella-destination-delete.
- umbrella-get-destination-domain - this command was replaced by umbrella-destinations-list.
- umbrella-get-destination-domains - this command was replaced by umbrella-destinations-list.
- umbrella-search-destination-domains - this command was replaced by umbrella-destinations-list.
Arguments
The following arguments were removed in this version
In the umbrella-get-destination-lists command:
- orgId - this argument was removed.
In the umbrella-add-domain command: - orgId - this argument was removed.
In the umbrella-remove-domain command: - orgId - this argument was removed.
In the umbrella-get-destination-domain command: - orgId - this argument was removed.
In the umbrella-get-destination-domains command: - orgId - this argument was removed.
In the umbrella-search-destination-domains command: - orgId - this argument was removed.
The behavior of the following arguments was changed
In the umbrella-add-domain command:
- destId - this argument was replaced by destination_list_id.
- domains - this argument was replaced by destinations.
In the umbrella-remove-domain command: - destId - this argument was replaced by destination_list_id.
- domainIds - this argument was replaced by destination_ids.
In the umbrella-get-destination-domain command: - destId - this argument was replaced by destination_list_id.
In the umbrella-get-destination-domains command: - destId - this argument was replaced by destination_list_id.
In the umbrella-search-destination-domains command: - destId - this argument was replaced by destination_list_id.
- domains - this argument was replaced by destinations.
Configuration parameters
credentials— API Key (required)proxy— Use system proxy settingsinsecure— Trust any certificate (not secure)baseURL— Base URL (required)
Commands (13)
-
umbrella-add-domainDeprecatedDeprecated. Use the `umbrella-destination-add` instead.
-
umbrella-destination-addAdd a destination to a destination list. A destination is a URL, IPv4, CIDR or fully qualified domain name. Accepted types for destination list with the access "allow" are: DOMAIN, IPv4 and CIDR. Accepted types for destination list with the access "block" are: URL and DOMAIN.
-
umbrella-destination-deleteRemove a destination from a destination list. A destination is a URL, IP or fully qualified domain name.
-
umbrella-destination-list-createCreate a destination list. A list of destinations (for example, domain name or URL) to which you can block or allow access. Accepted types for destination list with the access "allow" are: DOMAIN, IPv4 and CIDR. Accepted types for destination list with the access "block" are: URL and DOMAIN.
-
umbrella-destination-list-deleteDelete a destination list. A list of destinations (for example, domain name or URL) to which you can block or allow access.
-
umbrella-destination-list-updateEdit a destination list. A list of destinations (for example, domain name or URL) to which you can block or allow access.
-
umbrella-destination-lists-listGet destination lists. A list of destinations (for example, domain name or URL) to which you can block or allow access.
-
umbrella-destinations-listGet destinations within a destination list. A destination is a URL, IP or fully qualified domain name.
-
umbrella-get-destination-domainDeprecatedDeprecated. Use the `umbrella-destinations-list` instead.
-
umbrella-get-destination-domainsDeprecatedDeprecated. Use the `umbrella-destinations-list` instead.
-
umbrella-get-destination-listsDeprecatedDeprecated. Use the `umbrella-destination-lists-list` instead.
-
umbrella-remove-domainDeprecatedDeprecated. Use the `umbrella-destination-delete` instead.
-
umbrella-search-destination-domainsDeprecatedDeprecated. Use the `umbrella-destinations-list` instead.
import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 import enum import http from collections.abc import Callable from typing import Any, TypeVar INTEGRATION_COMMAND_PREFIX = "umbrella" DESTINATION = "destination" DESTINATION_LIST = "destination-list" DOMAIN = "domain" INTEGRATION_OUTPUT_PREFIX = "Umbrella" DESTINATION_OUTPUT_PREFIX = "Destinations" DESTINATION_LIST_OUTPUT_PREFIX = "DestinationLists" ID_OUTPUTS_KEY_FIELD = "id" MAX_LIMIT = 100 DEFAULT_LIMIT = 50 DEFAULT_URL = "https://api.umbrella.com" DESTINATION_LIST_HEADERS = ["id", "name", "access", "isGlobal", "destinationCount"] DESTINATION_LIST_JSON_TRANSFORMER = JsonTransformer(keys=DESTINATION_LIST_HEADERS, is_nested=True) OptionalDictOrList = TypeVar("OptionalDictOrList", None, dict[str, Any], list[dict[str, Any]]) class BundleType(int, enum.Enum): DNS = 1 WEB = 2 class Access(str, enum.Enum): ALLOW = "allow" BLOCK = "block" """ Client """ class Client(BaseClient): """Client class to interact with the Umbrella API.""" AUTH_SUFFIX = "auth/v2/token" POLICIES_SUFFIX = "policies/v2" DESTINATION_LIST_ENDPOINT = urljoin(POLICIES_SUFFIX, "destinationlists") def __init__( self, base_url: str, api_key: str, api_secret: str, verify: bool = False, proxy: bool = False, ) -> None: """Initialize the Client class. Args: base_url (str): The base URL of the API. api_key (str): The API key to use. api_secret (str): The API secret to use. verify (bool, optional): Whether to verify the SSL certificate. Defaults to False. proxy (bool, optional): Whether to use a proxy. Defaults to False. """ self.api_key = api_key self.api_secret = api_secret super().__init__( base_url=base_url, verify=verify, proxy=proxy, headers={}, ) def login(self) -> None: """ Log in to the API using the API key and API secret. The access token is stored in the headers of the request. """ access_token = self.get_access_token() self._headers["Authorization"] = f"Bearer {access_token}" def get_access_token(self): """ Get an access token that was previously created if it is still valid, else, generate a new access token from the API key and secret. """ # Check if there is an existing valid access token integration_context = get_integration_context() if integration_context.get("access_token") and integration_context.get("expiry_time") > date_to_timestamp(datetime.now()): return integration_context.get("access_token") else: try: res = self._http_request( method="POST", url_suffix=Client.AUTH_SUFFIX, headers={"Content-Type": "application/x-www-form-urlencoded"}, auth=(self.api_key, self.api_secret), data={"grant_type": "client_credentials"}, ) if res.get("access_token"): expiry_time = date_to_timestamp(datetime.now(), date_format="%Y-%m-%dT%H:%M:%S") expiry_time += res.get("expires_in", 0) * 1000 - 10 context = {"access_token": res.get("access_token"), "expiry_time": expiry_time} set_integration_context(context) return res.get("access_token") except Exception as e: return_error( f"Error occurred while creating an access token. Please check the instance configuration.\n\n{e.args[0]}" ) def _get_destination_payload( self, destinations: list[str] | None = None, destinations_comment: str | None = None, ) -> list[dict[str, Any]] | None: """Get the destination payload. Args: destinations (list[str] | None, optional): The list of destinations. Defaults to None. destinations_comment (str | None, optional): The comment of the destinations. Defaults to None. Returns: list[dict[str, Any]] | None: The destination payload or None incase destinations weren't given. """ if not destinations: return None return [ { "destination": destination, "comment": destinations_comment, } for destination in destinations ] def list_destinations( self, destination_list_id: str, page: int | None = None, limit: int = DEFAULT_LIMIT, ) -> dict[str, Any]: """Retrieve the list of destinations for a given destination list ID. Args: destination_list_id (str): The ID of the destination list. page (int, optional): The page number. Defaults to None. limit (int, optional): The number of items per page. Defaults to DEFAULT_LIMIT. Returns: dict[str, Any]: The list of destinations. """ params = assign_params( page=page, limit=limit, ) url_suffix = urljoin(Client.DESTINATION_LIST_ENDPOINT, f"{destination_list_id}/destinations") return self._http_request( method="GET", url_suffix=url_suffix, params=params, ) def add_destination( self, destination_list_id: str, destinations: list[str], destinations_comment: str | None = None, ) -> dict[str, Any]: """Add a list of destinations to a given destination list ID. Args: destination_list_id (str): The ID of the destination list. destinations (list[str]): The list of destinations. destinations_comment (str, optional): The comment of the destination. Defaults to None. Returns: dict[str, Any]: The destination list that holds the added destinations. """ payload = self._get_destination_payload( destinations=destinations, destinations_comment=destinations_comment, ) url_suffix = urljoin(Client.DESTINATION_LIST_ENDPOINT, f"{destination_list_id}/destinations") return self._http_request( method="POST", url_suffix=url_suffix, json_data=payload, ) def delete_destination(self, destination_list_id: str, destination_ids: list[int]) -> dict[str, Any]: """Delete a list of destinations from a given destination list ID. Args: destination_list_id (str): The ID of the destination list. destination_ids (list[int]): The list of destination IDs. Returns: dict[str, Any]: The destination list that holds the deleted destinations. """ url_suffix = urljoin(Client.DESTINATION_LIST_ENDPOINT, f"{destination_list_id}/destinations/remove") return self._http_request( method="DELETE", url_suffix=url_suffix, json_data=destination_ids, ) def list_destination_lists( self, page: int | None = None, limit: int = DEFAULT_LIMIT, ) -> dict[str, Any]: """Retrieve the list of destination lists. Args: destination_list_id (str): The ID of the destination list. Returns: dict[str, Any]: The destination list. """ params = assign_params( page=page, limit=limit, ) return self._http_request( method="GET", url_suffix=Client.DESTINATION_LIST_ENDPOINT, params=params, ) def get_destination_list(self, destination_list_id: str) -> dict[str, Any]: """Retrieve a destination list. Args: destination_list_id (str): The ID of the destination list. Returns: dict[str, Any]: The destination list. """ url_suffix = urljoin(Client.DESTINATION_LIST_ENDPOINT, destination_list_id) return self._http_request(method="GET", url_suffix=url_suffix) def create_destination_list( self, name: str, access: Access, is_global: bool, bundle_type: str | None = None, destinations: list[str] | None = None, destinations_comment: str | None = None, ) -> dict[str, Any]: """Create a destination list. Args: name (str): The name of the destination list. access (Access): The access of the destination list. is_global (bool): Whether the destination list is global. bundle_type (str | None, optional): The bundle type of the destination list. Defaults to None. destinations (list[str] | None, optional): The list of destinations. Defaults to None. destinations_comment (str | None, optional): The comment of the destinations. Defaults to None. Returns: dict[str, Any]: The destination list. """ payload = remove_empty_elements( { "name": name, "access": access, "isGlobal": is_global, "bundleType": bundle_type and BundleType[bundle_type].value, "destinations": self._get_destination_payload( destinations=destinations, destinations_comment=destinations_comment, ), } ) return self._http_request( method="POST", url_suffix=Client.DESTINATION_LIST_ENDPOINT, json_data=payload, ) def update_destination_list(self, destination_list_id: str, name: str) -> dict[str, Any]: """Update a destination list. Args: destination_list_id (str): The ID of the destination list. name (str): The name of the destination list. Returns: dict[str, Any]: The destination list. """ url_suffix = urljoin(Client.DESTINATION_LIST_ENDPOINT, destination_list_id) return self._http_request( method="PATCH", url_suffix=url_suffix, json_data={"name": name}, ) def delete_destination_list(self, destination_list_id: str) -> dict[str, Any]: """Delete a destination list. Args: destination_list_id (str): The ID of the destination list. Returns: dict[str, Any]: The destination list. """ url_suffix = urljoin(Client.DESTINATION_LIST_ENDPOINT, destination_list_id) return self._http_request( method="DELETE", url_suffix=url_suffix, ) """ HELPER COMMANDS """ @logger def bridge_v1_to_v2( v2_command: Callable[[Client, dict[str, Any]], CommandResults], client: Client, args: dict[str, Any], ) -> CommandResults: """ Bridge commands from the v1 pack (not API) to v2 commands. Args: v2_command (Callable[[Client, dict[str, Any]], CommandResults]): The v2 command. client (Client): Session to Cisco Umbrella to run API requests. args (dict[str, Any]): Arguments passed down by the CLI to configure the request. Returns: CommandResults: The result of v2 command. """ args["destination_list_id"] = args.pop("destId", None) args["destinations"] = args.pop("domain", None) or args.pop("domains", None) args["destination_ids"] = args.pop("domainIds", None) args["limit"] = DEFAULT_LIMIT return v2_command(client, args) @logger def is_get_request_type(get_args: list, list_args: list) -> bool: """ Determine whether the request arguments are for a GET or LIST request. Args: get_args (list): GET request arguments. list_args (list): LIST request arguments. Raises: ValueError: In case the user has entered both GET and LIST arguments, raise an error. Returns: bool: True if the arguments are for a GET request, False otherwise. """ is_get_request = any(get_args) is_list_request = any(list_args) if is_get_request and is_list_request: raise ValueError("GET and LIST arguments can not be supported simultaneously.") return is_get_request @logger def get_json_table(obj: OptionalDictOrList, json_transformer: JsonTransformer) -> OptionalDictOrList: """Convert a dict or list to a table. Args: obj (OptionalDictOrList): The dict or list to convert. json_transformer (JsonTransformer): The json transformer to use. Returns: OptionalDictOrList: The converted dict or list, otherwise returns obj if nothing was inserted. """ if not obj: return obj def transform_json_to_dict(data: dict[str, Any]) -> dict[str, Any]: """Use a JsonTransformer to extract the given keys from a dict and reconstruct it. Args: data (dict[str, Any]): The dict to transform. Returns: dict[str, Any]: The transformed dict. """ return {transformed_data[1]: transformed_data[2] for transformed_data in json_transformer.json_to_path_generator(data)} if isinstance(obj, list): return [transform_json_to_dict(o) for o in obj] return transform_json_to_dict(obj) def get_single_or_full_list(items: list) -> list | dict: """Get a single item or a full list of items. If the list contains only one item, return the item. If the list contains more than one item, return the full list. Args: items (list): The list of items. Returns: list | dict: The single item or the full list of items. """ return items[0] if len(items) == 1 else items @logger def handle_pagination( list_command: Callable[..., dict[str, Any]], limit: int, *args, page_size: int | None = None, page: int | None = None, **kwargs, ) -> tuple[list[dict[str, Any]] | dict[str, Any], dict[str, Any]]: """Handles pagination for the given list_command. Args: list_command (Callable[..., dict[str, Any]]): Callable that returns data containing destinations. limit (int): The maximum number of items to return. page (int | None, optional): The page number to start from. Defaults to None. page_size (int | None, optional): The number of items to return per page. Defaults to None. *args: Arguments passed down by the CLI to configure the request. **kwargs: Keyword arguments passed down by the CLI to configure the request. Returns: tuple[list[dict[str, Any]] | dict[str, Any], dict[str, Any]]: A tuple containing the list of items and raw responses, or a single item and raw response if page is None. """ if page: page_size = min(page_size or DEFAULT_LIMIT, MAX_LIMIT) demisto.debug(f"Calling list command with {args=}, {page=}, limit={page_size}") raw_response = list_command(*args, page=page, limit=page_size, **kwargs) return raw_response.get("data", []), raw_response page = 1 outputs: list[dict[str, Any]] = [] # Keep calling the API until the required amount of items have been met. while limit > 0: demisto.debug(f"Calling list command with {args=}, {page=}, limit={MAX_LIMIT}") raw_response = list_command(*args, page=page, limit=MAX_LIMIT, **kwargs) # If the API returned no items, we're done. if not (output := raw_response.get("data")): demisto.debug(f"The API returned no items for {page=}, stopping") break received_items = len(output) # If the API returned more than the required amount of items, we need to trim the output. if limit < received_items: output = output[:limit] outputs += output # If the API returned less than the required amount of items, we're done. if received_items < MAX_LIMIT: demisto.debug(f"These are the last items in the API {page=}, stopping") break limit -= received_items page += 1 raw_response["meta"]["total"] = len(outputs) outputs_result = get_single_or_full_list(outputs) raw_response["data"] = outputs_result return outputs_result, raw_response @logger def find_destinations( list_command: Callable[..., dict[str, Any]], destination_list_id: str, destinations: set[str] | None = None, destination_ids: set[str] | None = None, ) -> tuple[list[dict[str, Any]] | dict[str, Any], dict[str, Any]]: """Fetches and returns destinations and their associated data from a given list_command callable. Args: list_command (Callable[..., dict[str, Any]]): Callable that returns data containing destinations. destination_list_id (str): The destination list to search through. destinations (list[str] | None, optional): A set of destination names to look for. Defaults to None. destination_ids (list[str] | None, optional): A set of destination ids to look for. Defaults to None. Raises: ValueError: If both destinations and destination_ids weren't provided. Returns: tuple[list[dict[str, Any]] | dict[str, Any], dict[str, Any]]: A tuple containing two lists: one with the fetched data, and one with the raw responses from the list command. """ if not any((destinations, destination_ids)): raise ValueError('At least one of the arguments "destinations" or "destination_ids" must be provided.') def filter_out_non_target_items( item: str, target_items: set[str] | None, other_item: str, other_target_items: set[str] | None, ) -> bool: """ Checks if an item is in target_items. If so, removes the item and associated other_item from their respective sets. Args: item (str): Item to check for in target_items. target_items (set[str]): Set of items to search in. other_item (str): Associated item to remove from other_target_items if item is found in target_items. other_target_items (set[str]): Set of associated items. Returns: bool: True if the item was found and removed, False otherwise. """ if not target_items: return False if item in (target_items or ()): target_items.discard(item) if other_target_items: other_target_items.discard(other_item) return True return False destinations = set(destinations) if destinations else None destination_ids = set(destination_ids) if destination_ids else None page = 0 outputs: list[dict[str, Any]] = [] while destinations or destination_ids: page += 1 demisto.debug(f"Calling list command with {destination_list_id=}, {page=}, limit={MAX_LIMIT}") raw_response = list_command(destination_list_id=destination_list_id, page=page, limit=MAX_LIMIT) items = raw_response.get("data", []) if not items: demisto.debug(f"The API returned no items for {page=}, stopping") break for item in items: # Called twice to avoid duplicates if an item was given in ID and destination. if filter_out_non_target_items( item["id"], destination_ids, item["destination"], destinations, ) or filter_out_non_target_items( item["destination"], destinations, item["id"], destination_ids, ): outputs.append(item) if len(items) < MAX_LIMIT: demisto.debug(f"These are the last items in the API {page=}, stopping") break raw_response["meta"]["total"] = len(outputs) outputs_result = get_single_or_full_list(outputs) raw_response["data"] = outputs_result return outputs_result, raw_response @logger def get_destination_list_command_results(raw_response: list | dict, outputs: list | dict) -> CommandResults: """Get the destination list command results. Args: raw_response (list | dict): The raw response from the API. outputs (list | dict): The destination list data. Returns: CommandResults: The destination list command results. """ readable_output = tableToMarkdown( name="Destination List:", t=get_json_table(outputs, DESTINATION_LIST_JSON_TRANSFORMER), headers=DESTINATION_LIST_HEADERS, headerTransform=pascalToSpace, ) return CommandResults( outputs_prefix=".".join([INTEGRATION_OUTPUT_PREFIX, DESTINATION_LIST_OUTPUT_PREFIX]), outputs_key_field=ID_OUTPUTS_KEY_FIELD, outputs=outputs, readable_output=readable_output, raw_response=raw_response, ) """ COMMANDS """ @logger def list_destinations_command(client: Client, args: dict[str, Any]) -> CommandResults: destination_list_id = args["destination_list_id"] # GET arguments destinations = argToList(args.get("destinations")) destination_ids = argToList(args.get("destination_ids")) # LIST arguments limit = arg_to_number(args.get("limit")) or DEFAULT_LIMIT page = arg_to_number(args.get("page")) page_size = arg_to_number(args.get("page_size")) if is_get_request_type( get_args=[destinations, destination_ids], list_args=[page, page_size], ): outputs, raw_response = find_destinations( list_command=client.list_destinations, destination_list_id=destination_list_id, destinations=destinations, destination_ids=destination_ids, ) else: # is_list_request outputs, raw_response = handle_pagination( list_command=client.list_destinations, limit=limit, page=page, page_size=page_size, destination_list_id=destination_list_id, ) readable_output = tableToMarkdown( name="Destination(s):", t=outputs, headers=["id", "destination", "type", "comment", "createdAt"], headerTransform=pascalToSpace, ) return CommandResults( outputs_prefix=".".join([INTEGRATION_OUTPUT_PREFIX, DESTINATION_OUTPUT_PREFIX]), outputs_key_field=ID_OUTPUTS_KEY_FIELD, outputs=outputs, readable_output=readable_output, raw_response=raw_response, ) @logger def add_destination_command(client: Client, args: dict[str, Any]) -> CommandResults: """Add destinations to a destination list. Args: client (Client): Session to Cisco Umbrella to run API requests. args (dict[str, Any]): Arguments passed down by the CLI to configure the request. destination_list_id (str): The ID of the destination list. destinations (list[str]): The list of destinations to add. comment (str, optional): The comment for the destinations. Returns: CommandResults: The result of the destination addition. """ destination_list_id = args["destination_list_id"] destinations = argToList(args["destinations"]) destinations_comment = args.get("comment") raw_response = client.add_destination( destination_list_id=destination_list_id, destinations=destinations, destinations_comment=destinations_comment, ) return CommandResults( readable_output=( f'The destination(s) "{destinations}" were successfully added to the destination list "{destination_list_id}"' ), raw_response=raw_response, ) @logger def delete_destination_command(client: Client, args: dict[str, Any]) -> CommandResults: """Delete destinations from a destination list. Args: client (Client): Session to Cisco Umbrella to run API requests. args (dict[str, Any]): Arguments passed down by the CLI to configure the request. destination_list_id (str): The ID of the destination list. destination_ids (list[int]): The IDs of the destinations to delete. Returns: CommandResults: The result of the destination deletion. """ destination_list_id = args["destination_list_id"] destination_ids = [number for arg in argToList(args["destination_ids"]) if (number := arg_to_number(arg)) is not None] raw_response = client.delete_destination( destination_list_id=destination_list_id, destination_ids=destination_ids, ) return CommandResults( readable_output=( f'The destination(s) "{destination_ids}" ' f'were successfully removed from the destination list "{destination_list_id}"' ), raw_response=raw_response, ) @logger def list_destination_lists_command(client: Client, args: dict[str, Any]) -> CommandResults: """List destination lists. Args: client (Client): Session to Cisco Umbrella to run API requests. args (dict[str, Any]): Arguments passed down by the CLI to configure the request. destination_list_id (str, optional): The ID of a specific destination list. limit (int, optional): The maximum number of records to retrieve. page (int, optional): Page number of paginated results. page_size (int, optional): The number of items per page. Returns: CommandResults: The requested destination lists. """ # GET arguments destination_list_id = args.get("destination_list_id", "") # LIST arguments limit = arg_to_number(args.get("limit")) or DEFAULT_LIMIT page = arg_to_number(args.get("page")) page_size = arg_to_number(args.get("page_size")) if is_get_request_type( get_args=[destination_list_id], list_args=[page, page_size], ): outputs, raw_response = None, client.get_destination_list(destination_list_id) else: # is_list_request outputs, raw_response = handle_pagination( list_command=client.list_destination_lists, limit=limit, page=page, page_size=page_size, ) return get_destination_list_command_results( outputs=outputs or raw_response.get("data"), raw_response=raw_response, ) @logger def create_destination_list_command(client: Client, args: dict[str, Any]) -> CommandResults: """Create a destination list. Args: client (Client): Session to Cisco Umbrella to run API requests. args (dict[str, Any]): Arguments passed down by the CLI to configure the request. name (str): The name of the destination list. access (str): The access level of the destination list. is_global (bool): Whether the destination list is global. bundle_type (str, optional): The type of bundle. destinations (list[str], optional): The list of destinations. destinations_comment (str, optional): The comment for the destinations. Returns: CommandResults: The created destination list. """ raw_response = client.create_destination_list( name=args["name"], access=args["access"], is_global=argToBoolean(args["is_global"]), bundle_type=args.get("bundle_type"), destinations=argToList(args.get("destinations")), destinations_comment=args.get("destinations_comment"), ) return get_destination_list_command_results( outputs=raw_response, raw_response=raw_response, ) @logger def update_destination_list_command(client: Client, args: dict[str, Any]) -> CommandResults: """Update a destination list. Args: client (Client): Session to Cisco Umbrella to run API requests. args (dict[str, Any]): Arguments passed down by the CLI to configure the request. destination_list_id (str): The ID of the destination list. name (str): The name of the destination list. Returns: CommandResults: The updated destination list. """ raw_response = client.update_destination_list( destination_list_id=args["destination_list_id"], name=args["name"], ) return get_destination_list_command_results( outputs=raw_response["data"], raw_response=raw_response, ) @logger def delete_destination_list_command(client: Client, args: dict[str, Any]) -> CommandResults: """Delete a destination list. Args: client (Client): Session to Cisco Umbrella to run API requests. args (dict[str, Any]): Arguments passed down by the CLI to configure the request. destination_list_id (str): The ID of the destination list. Returns: CommandResults: The result of the destination list deletion. """ destination_list_id = args["destination_list_id"] raw_response = client.delete_destination_list(destination_list_id) return CommandResults( readable_output=f'The destination list "{destination_list_id}" was successfully deleted', raw_response=raw_response, ) @logger def test_module(client: Client) -> str: """Tests API connectivity and authentication. Returning 'ok' indicates that the integration works like it is supposed to. Raises exceptions if something goes wrong. Args: client (Client): Session to Cisco Umbrella to run API requests. Raises: DemistoException: Incase there is an unknown error. Returns: str: : 'ok' if test passed, or an error message if the credentials are incorrect. """ try: client.login() except DemistoException as exc: if exc.res is not None and exc.res.status_code == http.HTTPStatus.UNAUTHORIZED: return "Authorization Error: invalid API key or secret" raise exc return "ok" def main() -> None: params: dict[str, Any] = demisto.params() args: dict[str, Any] = demisto.args() command: str = demisto.command() base_url = params.get("baseURL") or DEFAULT_URL api_key: str = params["credentials"]["identifier"] api_secret: str = params["credentials"]["password"] verify_certificate: bool = not params.get("insecure", False) proxy: bool = params.get("proxy", False) demisto.debug(f"Command being called is {command}") commands_v2 = { f"{INTEGRATION_COMMAND_PREFIX}-{DESTINATION}s-list": list_destinations_command, f"{INTEGRATION_COMMAND_PREFIX}-{DESTINATION}-add": add_destination_command, f"{INTEGRATION_COMMAND_PREFIX}-{DESTINATION}-delete": delete_destination_command, f"{INTEGRATION_COMMAND_PREFIX}-{DESTINATION_LIST}s-list": list_destination_lists_command, f"{INTEGRATION_COMMAND_PREFIX}-{DESTINATION_LIST}-create": create_destination_list_command, f"{INTEGRATION_COMMAND_PREFIX}-{DESTINATION_LIST}-update": update_destination_list_command, f"{INTEGRATION_COMMAND_PREFIX}-{DESTINATION_LIST}-delete": delete_destination_list_command, } commands_v1 = { f"{INTEGRATION_COMMAND_PREFIX}-get-{DESTINATION}-{DOMAIN}": list_destinations_command, f"{INTEGRATION_COMMAND_PREFIX}-get-{DESTINATION}-{DOMAIN}s": list_destinations_command, f"{INTEGRATION_COMMAND_PREFIX}-search-{DESTINATION}-{DOMAIN}s": list_destinations_command, f"{INTEGRATION_COMMAND_PREFIX}-add-{DOMAIN}": add_destination_command, f"{INTEGRATION_COMMAND_PREFIX}-remove-{DOMAIN}": delete_destination_command, f"{INTEGRATION_COMMAND_PREFIX}-get-{DESTINATION}-lists": list_destination_lists_command, } try: client = Client( base_url=base_url, api_key=api_key, api_secret=api_secret, verify=verify_certificate, proxy=proxy, ) if command == "test-module": results = test_module(client) elif command in commands_v2: client.login() results = commands_v2[command](client, args) elif command in commands_v1: client.login() results = bridge_v1_to_v2(v2_command=commands_v1[command], client=client, args=args) else: raise NotImplementedError(f"{command} command is not implemented.") return_results(results) except Exception as e: return_error(str(e)) if __name__ in ["__main__", "builtin", "builtins"]: main()