CitrixCloud
Citrix cloud services simplify the delivery and management of Citrix technologies.
Analytics & SIEM · Citrix
Details
| ID | CitrixCloud |
|---|---|
| Provider | Cloud Software Group |
| Category | Analytics & SIEM |
| From Version | 6.8.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
README
This is the default integration for this content pack when configured by the Data Onboarder in Cortex XSIAM.
Configure Citrix Cloud Event Collector in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL | True | |
| Client Id | True | |
| Client Secret | True | |
| Customer ID | True | |
| Max events per fetch | The maximum amount of events to retrieve. | False |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False |
Step by step configuration
Prerequisites
Get Access to Citrix Cloud
Sign up for a free Citrix Cloud account, or log in to Citrix Cloud.
Citrix Cloud API Access with Service Principals
A service principal acts as an API client to Citrix Cloud APIs and has the following characteristics:
1. Create a Service Principal
In the Citrix Cloud console, click the menu in the upper left corner.
2. Select Identity and Access Management > API Access > Service principals > Create service principal and follow the steps to completion.
If these options do not appear, you might not have sufficient permissions to manage service principals. Contact your administrator to get the required full access permission.

3. Add the credentials to your secret management tool as the secret will only appear once
4. Customer ID is a mandatory parameter that must be passed in the Citrix-CustomerId header. To get the customer ID:
- Log in to the Citrix Cloud.
- Select the Identity and Access Management option from the menu.
- Click the API Access tab. You can see the customer ID in the description above the Create Client button.
Steps to identify your tenant’s Citrix Cloud ID
- Log in to https://citrix.cloud.com
- If you have access to more than one tenant, their names and Citrix Cloud IDs will be presented to you in a list format for you to select which one you want to sign in to. Example below:

- Once you sign in, the Tenant’s Citrix Cloud ID is also presented in the top right corner of the screen as shown in an example here (ctxtsnaxa)
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.
citrix-cloud-get-events
Returns system log events extracted from Citrix.
Base Command
citrix-cloud-get-events
Input
| Argument Name | Description | Required |
|---|---|---|
| should_push_events | Set this argument to True in order to create events, otherwise the command will only display the events. Possible values are: true, false. Default is false. | Required |
| limit | The maximum number of logs to return. Default is 2000. | Optional |
| start_date_time | Start DateTime for the records to be retrieved. | Optional |
| end_date_time | End DateTime for the records to be retrieved. | Optional |
Context Output
There is no context output for this command.
Configuration parameters
url— Server URL (required)client_id— Client Id (required)credentials— (required)customer_id— Customer ID (required)max_fetch— Max events per fetchisFetchEvents— Fetch EventseventFetchInterval— Events Fetch Intervalinsecure— Trust any certificate (not secure)proxy— Use system proxy settings
Commands (1)
-
citrix-cloud-get-eventsReturns system log events extracted from Citrix.This command is used for developing/debugging and is to be used with caution, as it can create events, leading to events duplication and API request limitation exceeding.
import pytest from CommonServerPython import * from unittest.mock import MagicMock from freezegun import freeze_time import CitrixCloudEventCollector from CitrixCloudEventCollector import Client, get_events_command, fetch_events_command, module_test_command, main @pytest.fixture def client() -> Client: """ Fixture to create and return a Client instance for testing. Uses mock credentials defined at the top of the file. """ return Client( base_url="https://api.citrixcloud.com", customer_id="cust", client_id="id", client_secret="secret", verify=True, proxy=False, ) @pytest.fixture(autouse=True) def mock_demisto(mocker): mocker.patch.object(demisto, "getIntegrationContext", return_value={}) mocker.patch.object(demisto, "setIntegrationContext") # ---------------------------------------------------------------------- # CLIENT TESTS # ---------------------------------------------------------------------- def test_request_access_token(mocker): """ Given: - A Citrix Cloud Client with mocked HTTP response. When: - Calling `request_access_token` to obtain an OAuth2 token. Then: - The token should be returned and stored in integration context. """ client = Client("https://api.citrixcloud.com", "cust", "id", "secret", False, True) mock_http = mocker.patch.object(client, "_http_request", return_value={"access_token": "abc123"}) token = client.request_access_token() assert token == "abc123" mock_http.assert_called_once() demisto.setIntegrationContext.assert_called_once_with({"access_token": "abc123"}) def test_get_records_with_valid_token(mocker): """ Given: - A valid access token in integration context. When: - Calling `get_records` with valid parameters. Then: - The function should return a JSON response with event records. """ client = Client("https://api.citrixcloud.com", "cust", "id", "secret", False, True) demisto.getIntegrationContext.return_value = {"access_token": "token123"} mock_resp = MagicMock() mock_resp.status_code = 200 mock_resp.json.return_value = {"items": [{"id": 1}], "continuationToken": None} mocker.patch.object(client, "_http_request", return_value=mock_resp) res = client.get_records("2024-01-01", "2024-01-02") assert res["items"][0]["id"] == 1 def test_get_records_refreshes_token_on_401(mocker): """ Given: - A client with an expired access token. When: - The first HTTP request returns 401 Unauthorized. Then: - The client should request a new token and retry successfully. """ client = Client("https://api.citrixcloud.com", "cust", "id", "secret", False, True) def side_effect(*args, **kwargs): if not hasattr(side_effect, "called"): side_effect.called = True mock_resp = MagicMock(status_code=401) return mock_resp return {"items": [{"id": 2}], "continuationToken": None} mocker.patch.object(client, "_http_request", side_effect=side_effect) mocker.patch.object(client, "request_access_token", return_value="newtoken") res = client.get_records("2024-01-01", "2024-01-02") assert res["items"][0]["id"] == 2 assert client.request_access_token.call_count == 2 def test_get_records_with_pagination(mocker): """ Given: - A client that returns multiple pages of records. When: - Calling `get_records_with_pagination` with limit=10. Then: - The function should merge pages and return all records with `_time` set. """ client = Client("https://api.citrixcloud.com", "cust", "id", "secret", False, True) responses = [ {"items": [{"utcTimestamp": "2024-01-01T00:00:00Z"}], "continuationToken": "abc"}, {"items": [{"utcTimestamp": "2024-01-01T01:00:00Z"}], "continuationToken": None}, ] mocker.patch.object(client, "get_records", side_effect=responses) records, raw_res = client.get_records_with_pagination(limit=10, start_date_time=None) assert len(records) == 2 assert records[0]["_time"] == "2024-01-01T00:00:00Z" assert raw_res["continuationToken"] is None # ---------------------------------------------------------------------- # COMMAND TESTS # ---------------------------------------------------------------------- def test_get_events_command_returns_results(mocker): """ Given: - A client returning mocked event records. When: - Running the `citrix-cloud-get-events` command. Then: - A CommandResults object is returned containing the event data. """ client = Client("url", "cust", "id", "secret", False, True) mocker.patch.object( client, "get_records_with_pagination", return_value=([{"recordId": "r1", "utcTimestamp": "2024-01-01T00:00:00Z"}], {"meta": "ok"}), ) results = get_events_command(client, {"limit": "1", "should_push_events": "false"}) assert isinstance(results, CommandResults) assert results.outputs[0]["recordId"] == "r1" @freeze_time("2025-01-14T00:00:00Z") def test_fetch_events_command_first_run(mocker): """ Given: - A client returning 2 event records in ascending order (oldest first, as produced in production). When: - Running `fetch_events_command` for the first time (empty last_run) to retrieve events. Then: - LastRun is set to the newest (last) event, and start_date_time uses the look-back window (5 minutes ago), not the exact current second. """ client = Client("url", "cust", "id", "secret", False, True) get_records_mocker = mocker.patch.object( client, "get_records_with_pagination", return_value=( [{"_time": "2024-01-01T00:00:00Z", "recordId": "id1"}, {"_time": "2025-01-01T00:00:00Z", "recordId": "id2"}], {}, ), ) events, last_run = fetch_events_command(client, 5, {}) assert len(events) == 2 assert last_run["LastRun"] == "2025-01-01T00:00:00Z" assert last_run["RecordId"] == "id2" assert get_records_mocker.call_args.kwargs["start_date_time"] == "2025-01-13T23:55:00.000Z" @freeze_time("2025-01-14T00:00:00Z") def test_fetch_events_command_first_run_no_events(mocker): """ Given: - A client returning no event records on the first fetch (empty last_run). When: - Running `fetch_events_command`. Then: - No LastRun is set, so the next cycle looks back FIRST_FETCH_LOOKBACK from "now" again (rolling window) instead of getting stuck querying "now". """ client = Client("url", "cust", "id", "secret", False, True) get_records_mocker = mocker.patch.object(client, "get_records_with_pagination", return_value=([], {})) events, last_run = fetch_events_command(client, 5, {}) assert events == [] assert get_records_mocker.call_args.kwargs["start_date_time"] == "2025-01-13T23:55:00.000Z" assert "LastRun" not in last_run def test_fetch_events_command_no_events_keeps_existing_last_run(mocker): """ Given: - An existing LastRun (past events) and no new events this cycle. When: - Running `fetch_events_command`. Then: - The window continues from the existing LastRun and last_run is returned unchanged. """ client = Client("url", "cust", "id", "secret", False, True) get_records_mocker = mocker.patch.object(client, "get_records_with_pagination", return_value=([], {})) prev_last_run = {"LastRun": "2024-06-01T00:00:00Z", "RecordId": "prev_id"} events, last_run = fetch_events_command(client, 5, prev_last_run) assert events == [] assert get_records_mocker.call_args.kwargs["start_date_time"] == "2024-06-01T00:00:00Z" assert last_run == {"LastRun": "2024-06-01T00:00:00Z", "RecordId": "prev_id"} def test_fetch_events_command_sets_last_run(mocker): """ Given: - A client returning one event record with a timestamp. When: - Running `fetch_events_command` to retrieve events. Then: - The function should return events and set a new LastRun value. """ client = Client("url", "cust", "id", "secret", False, True) mocker.patch.object( client, "get_records_with_pagination", return_value=([{"_time": "2024-01-01T00:00:00Z", "recordId": "id1"}], {}) ) events, last_run = fetch_events_command(client, 5, {"LastRun": "2024-01-01T00:00:00Z"}) assert len(events) == 1 assert "LastRun" in last_run def test_module_test_command_returns_ok(mocker): """ Given: - A Client with mocked event data. When: - Running the `module_test_command` function. Then: - The result should be 'ok', indicating a successful connection and fetch logic. """ client = Client("url", "cust", "id", "secret", False, True) mocker.patch("CitrixCloudEventCollector.get_events_command", return_value="ok") result = module_test_command(client, {}) assert result == "ok" @freeze_time("2025-01-14T00:00:00Z") def test_main_fetch_events_first_run_no_events(mocker): """ Given: - A fresh instance (empty getLastRun) where the API returns no events. When: - Running main() with the `fetch-events` command. Then: - The look-back window is queried (not "now") and no LastRun is saved, so the next cycle rolls back again instead of getting stuck (XSUP-74934 regression). """ mocker.patch.object( demisto, "params", return_value={ "url": "https://api.cloud.com", "customer_id": "cust", "client_id": "id", "credentials": {"password": "secret"}, "insecure": True, "proxy": False, "max_fetch": "2000", }, ) mocker.patch.object(demisto, "command", return_value="fetch-events") mocker.patch.object(demisto, "args", return_value={}) mocker.patch.object(demisto, "getLastRun", return_value={}) set_last_run = mocker.patch.object(demisto, "setLastRun") send_events = mocker.patch.object(CitrixCloudEventCollector, "send_events_to_xsiam") get_records = mocker.patch.object(Client, "get_records_with_pagination", return_value=([], {})) main() assert get_records.call_args.kwargs["start_date_time"] == "2025-01-13T23:55:00.000Z" set_last_run.assert_called_once() assert "LastRun" not in set_last_run.call_args.args[0] send_events.assert_called_once() assert send_events.call_args.args[0] == [] @freeze_time("2025-01-14T00:00:00Z") def test_main_fetch_events_with_events(mocker): """ Given: - A fresh instance where the API returns events. When: - Running main() with the `fetch-events` command. Then: - Events are sent to XSIAM and last run advances to the last event's timestamp/recordId. """ mocker.patch.object( demisto, "params", return_value={ "url": "https://api.cloud.com", "customer_id": "cust", "client_id": "id", "credentials": {"password": "secret"}, "insecure": True, "proxy": False, }, ) mocker.patch.object(demisto, "command", return_value="fetch-events") mocker.patch.object(demisto, "args", return_value={}) mocker.patch.object(demisto, "getLastRun", return_value={}) set_last_run = mocker.patch.object(demisto, "setLastRun") send_events = mocker.patch.object(CitrixCloudEventCollector, "send_events_to_xsiam") records = [ {"_time": "2025-01-13T23:57:00Z", "recordId": "r1"}, {"_time": "2025-01-13T23:58:00Z", "recordId": "r2"}, ] mocker.patch.object(Client, "get_records_with_pagination", return_value=(records, {})) main() send_events.assert_called_once() assert send_events.call_args.args[0] == records set_last_run.assert_called_once_with({"LastRun": "2025-01-13T23:58:00Z", "RecordId": "r2"}) def test_main_test_module(mocker): """ Given: - A configured instance. When: - Running main() with the `test-module` command. Then: - return_results is called with 'ok'. """ mocker.patch.object( demisto, "params", return_value={ "url": "https://api.cloud.com", "customer_id": "cust", "client_id": "id", "credentials": {"password": "secret"}, "insecure": True, "proxy": False, }, ) mocker.patch.object(demisto, "command", return_value="test-module") mocker.patch.object(demisto, "args", return_value={}) mocker.patch.object(CitrixCloudEventCollector, "get_events_command", return_value=None) return_results = mocker.patch.object(CitrixCloudEventCollector, "return_results") main() return_results.assert_called_once_with("ok") def test_get_records_default_limit_capping(mocker): """ Given: - A client with a valid token and a requested limit larger than the API max. When: - Calling get_records with limit greater than RECORDS_REQUEST_LIMIT. Then: - The Limit param sent to the API is capped at RECORDS_REQUEST_LIMIT. """ client = Client("https://api.citrixcloud.com", "cust", "id", "secret", False, True) demisto.getIntegrationContext.return_value = {"access_token": "token123"} mock_resp = MagicMock() mock_resp.status_code = 200 mock_resp.json.return_value = {"items": [], "continuationToken": None} http = mocker.patch.object(client, "_http_request", return_value=mock_resp) client.get_records("2024-01-01", None, limit=99999) sent_params = http.call_args.kwargs["params"] assert sent_params["Limit"] == CitrixCloudEventCollector.RECORDS_REQUEST_LIMIT