Details
| ID | Gamma |
|---|---|
| Provider | Gamma |
| Category | Network Security |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Gamma Enterprise DLP provides 1-click automatic discovery and remediation of data loss instances
across enterprise sanctioned SaaS applications (cloud and on-prem) such as: Slack, Github, GSuite (Gmail, GDrive), Atlassian Suite (Jira, Confluence), Microsoft Office 365 (Outlook, Teams, OneDrive), ServiceNow, ZenDesk and many more.
Configure Gamma.AI Enterprise DLP on Cortex XSOAR
- Navigate to Settings > Integrations > Servers & Services.
- Search for Gamma.
- Click Add instance to create and configure a new integration instance.
| Parameter | Description | Required |
|---|---|---|
| api_key | Gamma Discovery API Key | True |
| url | URL of the Gamma API | True |
| first_fetch | The violation ID (offset) to begin fetching from. The value must be a number equal to or greater than 1. If empty, the fetch will default to the first violation that exists. You can retrieve a list of violation IDs by running the gamma-get-violation-list command. | False |
| max_fetch | Max results to return | False |
| insecure | Trust any certificate (not secure) | False |
| proxy | Use system proxy settings | False |
Click Test to validate the URLs, token, and connection.
Commands
You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
gamma-get-violation-list
Fetch DLP violations found across SaaS applications monitored by Gamma
Base Command
gamma-get-violation-list
Input
| Argument Name | Description | Required |
|---|---|---|
| minimum_violation | Violation ID to begin pulling from. Defaults to the earliest existing violation for your account. | Required |
| limit | Default is “10”. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| GammaViolation.violation_id | Integer | Violation ID |
| GammaViolation.file_labels_map | Array | File in reference to the DLP violation |
| GammaViolation.violation_status | String | one of ‘OPEN’, ‘RESOLVED’, ‘IGNORED’ |
| GammaViolation.violation_category | String | Category of the violation e.g. PII, Secrets, GDPR/CCPA, etc. |
| GammaViolation.violation_event_timestamp | Integer | Timestamp of violation in epoch milliseconds |
| GammaViolation.text_labels | Array | Data classification labels |
| GammaViolation.user | JSON Object | a JSON field containing optional information (based on what the app allows us to access) like email address, name, atlassian account id, AD id, github login, etc. All these fields are nullable. |
| GammaViolation.dashboard_url | String | Gamma dashboard URL |
| GammaViolation.app_name | String | Name of the application |
Command Example
!gamma-get-violation-list minimum_violation=998 limit=1
Context Example
{
"response": [
{
"violation_id": 999,
"file_labels_map": {
"svc-prod-account.json": [
"cloud_db_credential"
]
},
"violation_status": "OPEN",
"violation_category": "secrets",
"violation_event_timestamp": 1569550580,
"text_labels": [],
"user": {
"name": null,
"atlassian_account_id": null,
"email_address": "foo@example.com",
"active_directory_user_id": null,
"atlassian_server_user_key": null,
"slack_user_id": "USER9Aa2",
"github_handle": "markzuck"
},
"dashboard_url": "https://prod-iab12.gamma.ai/dashboard/slack/monitor/violationId/999",
"app_name": "slack"
}]
}
gamma-get-violation
Fetches a single DLP violation. This command is the same as gamma-get-violation-list except that this
command only returns the DLP violation details of the given violation id.
Base Command
gamma-get-violation
Input
| Argument Name | Description | Required |
|---|---|---|
| violation | Violation id | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| GammaViolation.violation_id | Integer | Violation ID |
| GammaViolation.file_labels_map | Array | File in reference to the DLP violation |
| GammaViolation.violation_status | String | one of ‘OPEN’, ‘RESOLVED’, ‘IGNORED’ |
| GammaViolation.violation_category | String | Category of the violation e.g. PII, Secrets, GDPR/CCPA, etc. |
| GammaViolation.violation_event_timestamp | Integer | Timestamp of violation in epoch milliseconds |
| GammaViolation.text_labels | Array | Data classification labels |
| GammaViolation.user | JSON Object | a JSON field containing optional information (based on what the app allows us to access) like email address, name, atlassian account id, AD id, github login, etc. All these fields are nullable. |
| GammaViolation.dashboard_url | String | Gamma dashboard URL |
| GammaViolation.app_name | String | Name of the application |
Command Example
!gamma-get-violation violation=998
Context Example
{
"response": [
{
"violation_id": 999,
"file_labels_map": {
"svc-prod-account.json": [
"cloud_db_credential"
]
},
"violation_status": "OPEN",
"violation_category": "secrets",
"violation_event_timestamp": 1569550580,
"text_labels": [],
"user": {
"name": null,
"atlassian_account_id": null,
"email_address": "foo@example.com",
"active_directory_user_id": null,
"atlassian_server_user_key": null,
"slack_user_id": "USER9Aa2",
"github_handle": "markzuck"
},
"dashboard_url": "https://prod-iab12.gamma.ai/dashboard/slack/monitor/violationId/999",
"app_name": "slack"
}]
}
gamma-update-violation
Updates a DLP violation status in Gamma
Base Command
gamma-update-violation
Input
| Argument Name | Description | Required |
|---|---|---|
| violation | Violation id | Required |
| status | Status of violation | Required |
| notes | Notes for violation | Optional |
Context Output
There is no context output for this command
Configuration parameters
url— Server URL (e.g. https://example.net) (required)api_key— Gamma API Keycredentials_api_key—first_fetch— Starting Violationmax_fetch— Results per fetchinsecure— Trust any certificate (not secure)proxy— Use system proxy settingsincidentType— Incident typeincidentFetchInterval— Incidents Fetch IntervalisFetch— Fetch incidents
Commands (3)
-
gamma-get-violationGet a specific violation.
-
gamma-get-violation-listGet a list of violations.
-
gamma-update-violationUpdate a specific violation.
import json import pytest import demistomock as demisto from Gamma import Client, fetch_incidents, Command, main MOCK_URL = "mock://fake-api.net" MOCK_VIOLATION = { "response": [ { "app_name": "jira", "dashboard_url": f"{MOCK_URL}/violationId/2036", "file_labels_map": {}, "text_labels": [], "user": { "active_directory_user_id": None, "atlassian_account_id": None, "email_address": None, "github_handle": None, "name": "Amane Suzuha", "slack_user_id": None, }, "violation_category": "mock_category", "violation_event_timestamp": 1605805555, "violation_id": 2036, "violation_status": "OPEN", } ] } MOCK_VIOLATION_2 = { "response": [ { "app_name": "jira", "dashboard_url": f"{MOCK_URL}/violationId/5100", "file_labels_map": {}, "text_labels": [], "user": { "active_directory_user_id": None, "atlassian_account_id": None, "email_address": None, "github_handle": None, "name": "Rintaro Okabe", "slack_user_id": None, }, "violation_category": "mock_category", "violation_event_timestamp": 1605804455, "violation_id": 5100, "violation_status": "OPEN", } ] } MOCK_VIOLATION_2_UPDATED = { "response": [ { "app_name": "jira", "dashboard_url": f"{MOCK_URL}/violationId/5100", "file_labels_map": {}, "text_labels": [], "user": { "active_directory_user_id": None, "atlassian_account_id": None, "email_address": None, "github_handle": None, "name": "Rintaro Okabe", "slack_user_id": None, }, "violation_category": "mock_category", "violation_event_timestamp": 1605804455, "violation_id": 5100, "violation_status": "RESOLVED", } ] } MOCK_ALL_VIOLATIONS = {"response": [MOCK_VIOLATION["response"][0], MOCK_VIOLATION_2["response"][0]]} def mock_demisto(mocker, args_value=None, command_value=None): mocker.patch.object(demisto, "results") mocker.patch.object(demisto, "params", return_value={"api_key": "thisisatestkey", "url": MOCK_URL}) if not args_value: args_value = {"entryID": "entry_id", "parseAll": "yes", "codec": "utf-8"} if command_value: mocker.patch.object(demisto, "command", return_value=command_value) mocker.patch.object(demisto, "args", return_value=args_value) def mock_client(mocker, demisto): mocker.patch.object(demisto, "params", return_value={"api_key": "thisisatestkey", "url": MOCK_URL}) client = Client(demisto) return client @pytest.mark.parametrize( "last_run_violation,first_fetch_violation,max_results,output_1,output_2", [ ({}, "1", "10", "Gamma Violation 2036", 5100), ({}, "1", "0", "Gamma Violation 2036", 5100), ({}, "1", "-1", "Gamma Violation 2036", 5100), ({}, "1", "200", "Gamma Violation 2036", 5100), ], ) def test_fetch_incidents(last_run_violation, first_fetch_violation, max_results, output_1, output_2, requests_mock, mocker): requests_mock.get(MOCK_URL + "/api/discovery/v1/violation/list", json=MOCK_ALL_VIOLATIONS) # Test fetch next_run, incidents = fetch_incidents(mock_client(mocker, demisto), last_run_violation, first_fetch_violation, max_results) mocker.patch.object(demisto, "incidents", incidents) assert output_1 == demisto.incidents[0]["name"] assert output_2 == next_run["starting_violation"] @pytest.mark.parametrize( "next_run,first_fetch_violation,max_results,output_1,output_2", [({"starting_violation": 2036}, "1", "10", 1, 5100), ({"starting_violation": 5100}, "1", "10", 0, 5100)], ) def test_fetch_incidents_next_fetch(next_run, first_fetch_violation, max_results, output_1, output_2, requests_mock, mocker): requests_mock.get(MOCK_URL + "/api/discovery/v1/violation/list", json=MOCK_ALL_VIOLATIONS) next_run, incidents = fetch_incidents(mock_client(mocker, demisto), next_run, first_fetch_violation, max_results) assert output_1 == len(incidents) assert output_2 == next_run["starting_violation"] @pytest.mark.parametrize( "last_run_violation,first_fetch_violation,max_results,output", [ ({}, "0", "10", "first_fetch_violation must be equal to 1 or higher"), ({}, "-1", "10", "first_fetch_violation must be equal to 1 or higher"), ({}, "test", "10", "first_fetch_violation and max_limit must be integers"), ({}, "1", "test", "first_fetch_violation and max_limit must be integers"), ], ) def test_fetch_incidents_bad_input(last_run_violation, first_fetch_violation, max_results, output, mocker): with pytest.raises(ValueError) as err: fetch_incidents(mock_client(mocker, demisto), last_run_violation, first_fetch_violation, max_results) assert output == str(err.value) def test_get_violation_command(requests_mock, mocker, capfd): requests_mock.get(MOCK_URL + "/api/discovery/v1/violation/list", json=MOCK_VIOLATION) args = {"violation": "2036"} mock_demisto(mocker, args, "gamma-get-violation") with capfd.disabled(): main() content = demisto.results.call_args[0][0]["Contents"][0]["violation_id"] assert content == 2036 @pytest.mark.parametrize( "demisto_args,output", [ ({"violation": "0"}, "Violation must be greater than 0"), ({"violation": "-1"}, "Violation must be greater than 0"), ({"violation": "test"}, "invalid literal for int() with base 10: 'test'"), ], ) def test_get_violation_command_bad_input(demisto_args, output, mocker): client = mock_client(mocker, demisto) with pytest.raises(ValueError) as err: Command.get_violation(client, demisto_args) assert output == str(err.value) @pytest.mark.parametrize( "demisto_args,output", [ ({"minimum_violation": "2036", "limit": "2"}, 2036), ({"minimum_violation": "2035", "limit": "2"}, 2036), ], ) def test_get_violation_list_command(demisto_args, output, requests_mock, mocker, capfd): requests_mock.get(MOCK_URL + "/api/discovery/v1/violation/list", json=MOCK_ALL_VIOLATIONS) mock_demisto(mocker, demisto_args, "gamma-get-violation-list") with capfd.disabled(): main() content = demisto.results.call_args[0][0]["Contents"][0]["violation_id"] assert output == content @pytest.mark.parametrize( "demisto_args,output", [ ({"minimum_violation": "0", "limit": "2"}, "minimum_violation must be greater than 0"), ({"minimum_violation": "test", "limit": "2"}, "invalid literal for int() with base 10: 'test'"), ({"minimum_violation": "-1", "limit": "2"}, "minimum_violation must be greater than 0"), ({"minimum_violation": "2035", "limit": "0"}, "limit must be between 1 and 100"), ({"minimum_violation": "2035", "limit": "-1"}, "limit must be between 1 and 100"), ({"minimum_violation": "2035", "limit": "test"}, "invalid literal for int() with base 10: 'test'"), ], ) def test_get_violation_list_command_bad_input(demisto_args, output, mocker): client = mock_client(mocker, demisto) with pytest.raises(ValueError) as err: Command.get_violation_list(client, demisto_args) assert output == str(err.value) @pytest.mark.parametrize( "demisto_args,demisto_command,output", [ ( {"violation": "5100", "status": "resolved", "notes": "This has been updated!"}, "gamma-update-violation", "RESOLVED", ), ], ) def test_update_violation_command(demisto_args, demisto_command, output, requests_mock, mocker, capfd): test_violation = 5100 requests_mock.put(MOCK_URL + f"/api/discovery/v1/violation/{test_violation}", json=MOCK_VIOLATION_2) requests_mock.get(MOCK_URL + "/api/discovery/v1/violation/list", json=MOCK_VIOLATION_2_UPDATED) mock_demisto(mocker, demisto_args, demisto_command) with capfd.disabled(): main() contents = demisto.results.call_args[0][0]["Contents"][0]["violation_status"] assert output == contents @pytest.mark.parametrize( "demisto_args,output", [ ({"violation": "0", "status": "resolved", "notes": "This has been updated!"}, "Violation must be greater than 0"), ({"violation": "-1", "status": "resolved", "notes": "This has been updated!"}, "Violation must be greater than 0"), ( {"violation": "test", "status": "resolved", "notes": "This has been updated!"}, "invalid literal for int() with base 10: 'test'", ), ( {"violation": "5100", "status": "closed", "notes": "This has been updated!"}, "Status must be one of the following: OPEN, RESOLVED, IGNORED", ), ], ) def test_update_violation_command_bad_input(demisto_args, output, mocker): client = mock_client(mocker, demisto) with pytest.raises(ValueError) as err: Command.update_violation(client, demisto_args) assert output == str(err.value) @pytest.mark.parametrize( "demisto_args,demisto_command,output_1,output_2", [ ({}, "fetch-incidents", "Gamma Violation 2036", "Gamma Violation 5100"), ( {"first_fetch_violation": "2036", "max_results": "5"}, "fetch-incidents", "Gamma Violation 2036", "Gamma Violation 5100", ), ], ) def test_main_fetch_incidents(demisto_args, demisto_command, output_1, output_2, requests_mock, mocker, capfd): # Test fetch requests_mock.get(MOCK_URL + "/api/discovery/v1/violation/list", json=MOCK_ALL_VIOLATIONS) mock_demisto(mocker, demisto_args, demisto_command) with capfd.disabled(): main() contents = json.loads(demisto.results.call_args[0][0]["Contents"]) assert output_1 == contents[0]["name"] assert output_2 == contents[1]["name"] def test_main_get_violation_list(requests_mock, mocker, capfd): # Test fetch requests_mock.get(MOCK_URL + "/api/discovery/v1/violation/list", json=MOCK_ALL_VIOLATIONS) args = {"minimum_id": "2036", "limit": "5"} command = "gamma-get-violation-list" mock_demisto(mocker, args, command) with capfd.disabled(): main() response = demisto.results.call_args[0][0]["Contents"] assert {2036, 5100} == {i["violation_id"] for i in response} def test_main_get_bad_violation(mocker, requests_mock, capfd): requests_mock.get(MOCK_URL + "/api/discovery/v1/violation/list", json=MOCK_VIOLATION) # Test wrong ID command = "gamma-get-violation" args = {"violation": "5100"} mock_demisto(mocker, args, command) with pytest.raises(SystemExit), capfd.disabled(): main() assert ( demisto.results.call_args[0][0]["Contents"] == "Failed to execute gamma-get-violation " "command.\nError:\nViolation with this " "ID does not exist." ) def test_main_get_violation(requests_mock, mocker, capfd): # Test get violation requests_mock.get(MOCK_URL + "/api/discovery/v1/violation/list", json=MOCK_VIOLATION) args = {"violation": "2036"} command = "gamma-get-violation" mock_demisto(mocker, args, command) with capfd.disabled(): main() assert demisto.results.call_args[0][0]["Contents"][0]["violation_id"] == 2036 def test_main_update(requests_mock, mocker, capfd): # Test get violation test_violation = 2036 requests_mock.put(MOCK_URL + f"/api/discovery/v1/violation/{test_violation}", json=MOCK_VIOLATION) requests_mock.get(MOCK_URL + "/api/discovery/v1/violation/list", json=MOCK_VIOLATION) args = {"violation": f"{test_violation}", "status": "RESOLVED", "notes": ""} command = "gamma-update-violation" mock_demisto(mocker, args, command) with capfd.disabled(): main() assert test_violation == demisto.results.call_args[0][0]["Contents"][0]["violation_id"] def test_bad_command(mocker, capfd): test_violation = 2036 args = {"violation": f"{test_violation}", "status": "resolved", "notes": ""} command = "gamma-violation-update" mock_demisto(mocker, args, command) with pytest.raises(SystemExit), capfd.disabled(): main() assert ( demisto.results.call_args[0][0]["Contents"] == "Failed to execute " "gamma-violation-update " "command.\nError:\nCommand " '"gamma-violation-update" is not ' "implemented." )