Covalence Managed Security

Triggers by triaged alerts from endpoint, cloud, and network security monitoring. Contains event details and easy-to-follow mitigation steps.

Endpoint · Covalence Managed Security

Details

IDCovalence Managed Security
ProviderField Effect Software Inc
CategoryEndpoint
From Version5.0.0
Docker Imagedemisto/python3:3.12.12.7090913
Supported ModulesAgentix XSIAM

README

Triggers by triaged alerts from endpoint, cloud, and network security monitoring. Contains event details and easy-to-follow mitigation steps.
This integration was integrated and tested with version 1.1.10 of Covalence Managed Security.

Configure Covalence Managed Security in Cortex

Parameter Description Required
Credentials   True
Password   True
Use system proxy settings   False
First run time range When fetching incidents for the first time, this parameter specifies in days how far the integration looks for incidents. For instance if set to “2”, it will pull all alerts in Covalence for the last 2 days and will create corresponding incidents. False
Incident type   False
Fetch incidents   False
First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)   False
Fetch Limit The maximum number of incidents to fetch False
Broker Server URL Broker Server URL (Optional). Required to use Broker commands. 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.

cov-mgsec-get-aro


Query FES Portal for ARO.

Base Command

cov-mgsec-get-aro

Input

Argument Name Description Required
details if details=true, will return the complete response from Covalence API. Optional
query Portal query, for example: “resolution=Unresolved&type=Recommendation”
Available Keys to filter on:
- id; eg: “id=<ARO_id>
- status; eg: “status=In Triage” or “status=Open” or “status=Closed”
- resolution; eg: “resolution=Unresolved” or “resolution=Resolved” or “resolution=Help Requested” or “resolution=Dismissed”
- type; eg: “type=Action” or “type=Recommendation” or “type=Observation”
- org; eg: “org=<organization_name>“
- since; eg: “since=2021-01-31 14:00:00”
- until; eg: “until=2021-01-31 14:00:00”.
Required

Context Output

Path Type Description
FESPortal.Aro.ID String ID.
FESPortal.Aro.alert_key String Alert_key.
FESPortal.Aro.analyst_notes String Analyst_notes.
FESPortal.Aro.count Number Count.
FESPortal.Aro.creation_time Date Creation_time.
FESPortal.Aro.details String Details.
FESPortal.Aro.details_markdown String Details_markdown.
FESPortal.Aro.display_url String Display_url.
FESPortal.Aro.external_bug_id String External_bug_id.
FESPortal.Aro.last_updated_time Date Last_updated_time.
FESPortal.Aro.notes String Notes.
FESPortal.Aro.organization.ID String ID.
FESPortal.Aro.organization.email String Email.
FESPortal.Aro.organization.name String Name.
FESPortal.Aro.resolution String Resolution.
FESPortal.Aro.serial_id String Serial_id.
FESPortal.Aro.severity String Severity.
FESPortal.Aro.status String Status.
FESPortal.Aro.steps.ID String ID.
FESPortal.Aro.steps.completed Boolean Completed.
FESPortal.Aro.steps.label String Label.
FESPortal.Aro.steps.last_updated_time Date Last_updated_time.
FESPortal.Aro.template_id String Template_id.
FESPortal.Aro.title String Title.
FESPortal.Aro.triage_id String Triage_id.
FESPortal.Aro.type String Type.

Command example

!cov-mgsec-get-aro query="since=2023-11-30 18:00:00"

Context Example

{
    "FESPortal": {
        "ARO": [
            {
                "organization": {
                    "ID": "9d4297ea-089e-42bd-884d-51744e31a471",
                    "email": "foo@bar.com",
                    "name": "Acme"
                },
                "resolution": "Unresolved",
                "severity": "Critical",
                "status": "Open",
                "title": "test2",
                "type": "Action"
            },
            {
                "organization": {
                    "ID": "e0e04c8b-d50c-4379-bfd6-5e0f2b1037cd",
                    "email": "foo@bar.com",
                    "name": "Capsule Corp"
                },
                "resolution": "Unresolved",
                "severity": "High",
                "status": "Open",
                "title": "Vulnerable Software Detected",
                "type": "Recommendation"
            }
        ]
    }
}

Human Readable Output

AROs

Organization Resolution Severity Status Title Type
ID: 9d4297ea-089e-42bd-884d-51744e31a471
email: foo@bar.com
name: Acme
Unresolved Critical Open test2 Action
ID: e0e04c8b-d50c-4379-bfd6-5e0f2b1037cd
email: foo@bar.com
name: Capsule Corp
Unresolved High Open Vulnerable Software Detected Recommendation

cov-mgsec-list-org


List organizations.

Base Command

cov-mgsec-list-org

Input

There are no input arguments for this command.

Context Output

Path Type Description
FESPortal.Org.ID String ID.
FESPortal.Org.email String Email.
FESPortal.Org.email_aro_details Boolean Email_aro_details.
FESPortal.Org.name String Name.

Command example


#### Context Example

```json
{
    "FESPortal": {
        "Org": [
            {
                "ID": "9d4297ea-089e-42bd-884d-51744e31a471",
                "email": "foo@bar.com",
                "email_aro_details": false,
                "name": "Acme"
            },
            {
                "ID": "e0e04c8b-d50c-4379-bfd6-5e0f2b1037cd",
                "email": "foo@bar.com",
                "email_aro_details": false,
                "name": "Capsule Corp"
            }
        ]
    }
}

Human Readable Output

Organizations

Id Email Email Aro Details Name
9d4297ea-089e-42bd-884d-51744e31a471 foo@bar.com false Acme
e0e04c8b-d50c-4379-bfd6-5e0f2b1037cd foo@bar.com false Capsule Corp

cov-mgsec-transition-aro


Transition an ARO.

Base Command

cov-mgsec-transition-aro

Input

Argument Name Description Required
aro_id This ARO ID to transition. Required
resolution Resolution to transition the ARO to. Possible values are: Unresolved, Help Requested, Resolved, Dismissed. Required
comment Optional comment to leave on the ARO. Optional
is_comment_sensitive Optionally mark the comment as sensitive. Optional

Context Output

Path Type Description
FESPortal.Aro.ID String ID.
FESPortal.Aro.alert_key String Alert_key.
FESPortal.Aro.analyst_notes String Analyst_notes.
FESPortal.Aro.count Number Count.
FESPortal.Aro.creation_time Date Creation_time.
FESPortal.Aro.details String Details.
FESPortal.Aro.details_markdown String Details_markdown.
FESPortal.Aro.display_url String Display_url.
FESPortal.Aro.external_bug_id String External_bug_id.
FESPortal.Aro.last_updated_time Date Last_updated_time.
FESPortal.Aro.notes String Notes.
FESPortal.Aro.organization.ID String ID.
FESPortal.Aro.organization.email String Email.
FESPortal.Aro.organization.name String Name.
FESPortal.Aro.resolution String Resolution.
FESPortal.Aro.serial_id String Serial_id.
FESPortal.Aro.severity String Severity.
FESPortal.Aro.status String Status.
FESPortal.Aro.steps.ID String ID.
FESPortal.Aro.steps.completed Boolean Completed.
FESPortal.Aro.steps.label String Label.
FESPortal.Aro.steps.last_updated_time Date Last_updated_time.
FESPortal.Aro.template_id String Template_id.
FESPortal.Aro.title String Title.
FESPortal.Aro.triage_id String Triage_id.
FESPortal.Aro.type String Type.

cov-mgsec-transition-aro


Transition an ARO.

Base Command

cov-mgsec-transition-aro

Input

Argument Name Description Required
aro_id This ARO ID to transition. Required
resolution Resolution to transition the ARO to. Options include: Unresolved, Help Requested, Resolved, or Dismissed. Required
comment Optional comment to leave on the ARO. Optional
is_comment_sensitive Optionally mark the comment as sensitive. Optional

Context Output

Path Type Description
FESPortal.Aro.ID String ID.
FESPortal.Aro.alert_key String Alert_key.
FESPortal.Aro.analyst_notes String Analyst_notes.
FESPortal.Aro.count Number Count.
FESPortal.Aro.creation_time Date Creation_time.
FESPortal.Aro.details String Details.
FESPortal.Aro.details_markdown String Details_markdown.
FESPortal.Aro.display_url String Display_url.
FESPortal.Aro.external_bug_id String External_bug_id.
FESPortal.Aro.last_updated_time Date Last_updated_time.
FESPortal.Aro.notes String Notes.
FESPortal.Aro.organization.ID String ID.
FESPortal.Aro.organization.email String Email.
FESPortal.Aro.organization.name String Name.
FESPortal.Aro.resolution String Resolution.
FESPortal.Aro.serial_id String Serial_id.
FESPortal.Aro.severity String Severity.
FESPortal.Aro.status String Status.
FESPortal.Aro.steps.ID String ID.
FESPortal.Aro.steps.completed Boolean Completed.
FESPortal.Aro.steps.label String Label.
FESPortal.Aro.steps.last_updated_time Date Last_updated_time.
FESPortal.Aro.template_id String Template_id.
FESPortal.Aro.title String Title.
FESPortal.Aro.triage_id String Triage_id.
FESPortal.Aro.type String Type.

Command example

!cov-mgsec-transition-aro aro_id="7ea9b17d-7529-4b17-b0e7-92334d6c674b" resolution="Resolved" comment="Risk mitigated."

Context Example

{
    "FESPortal": {
        "Org": {
            "ID": "7ea9b17d-7529-4b17-b0e7-92334d6c674b",
            "alert_key": "test_alert_key",
            "attachments": [],
            "count": 1,
            "creation_time": "2023-08-16 19:48:02",
            "data": null,
            "details": "ARO Details",
            "details_markdown": null,
            "display_url": "test_url",
            "external_ticket": null,
            "frameworks": [],
            "insights": {},
            "last_updated_time": "2023-11-30 19:01:59",
            "organization": {
                "ID": "test_ID",
                "email": null,
                "name": "test_org_id"
            },
            "references": [],
            "resolution": "Resolved",
            "resolution_duration_seconds": 9155637,
            "resolution_time": "2023-11-30 19:01:59",
            "serial_id": "15",
            "severity": "Low",
            "status": "Open",
            "steps": [
                {
                    "ID": "test_id",
                    "completed": true,
                    "label": "test_resolution_step",
                    "last_updated_time": "2023-10-24 20:53:45"
                }
            ],
            "template_id": null,
            "title": "test_aro_title",
            "triage_id": null,
            "type": "Observation"
        }
    }
}

Human Readable Output

ARO

Id Alert Key Count Creation Time Details Display Url Last Updated Time Organization Resolution Resolution Duration Seconds Resolution Time Serial Id Severity Status Steps Title Type
7ea9b17d-7529-4b17-b0e7-92334d6c674b test_alert_key 1 2023-08-16 19:48:02 ARO Details test_url 2023-11-30 19:01:59 ID: test_ID
email: null
name: test_org_id
Resolved 9155637 2023-11-30 19:01:59 15 Low Open {‘ID’: ‘test_id’, ‘completed’: True, ‘label’: ‘test_resolution_step’, ‘last_updated_time’: ‘2023-10-24 20:53:45’} test_aro_title Observation

cov-mgsec-broker-cloud-action-by-aro


Broker - Cloud Action By ARO.

Base Command

cov-mgsec-broker-cloud-action-by-aro

Input

Argument Name Description Required
action_type Action to perform. Possible values are: DISABLE_USER, ENABLE_USER, REVOKE_SESSIONS. Required
aro_id ARO ID (eg. “00000000-1111-2222-3333-444444444444”). Required

Context Output

Path Type Description
FESBroker.action_id String Action ID
FESBroker.action_type String Action Type
FESBroker.action_params Unknown Action Parameters
FESBroker.created_time String Created Time
FESBroker.status String Status
FESBroker.result String Result

Command example

!cov-mgsec-broker-cloud-action-by-aro action_type=DISABLE_USER aro_id=00000000-1111-2222-3333-444444444444

Context Example

{
    "FESBroker": {
        "Action": {
            "action_id": "00000000-1111-2222-3333-444444444444",
            "action_params": {
                "user": "azure credential configuration endpoint service"
            },
            "action_type": "disable_user",
            "created_time": "2024-02-22T01:27:04.344179Z",
            "result": "SUCCESS",
            "status": "COMPLETE"
        }
    }
}

Human Readable Output

Command Result

action_id action_params action_type created_time result status
00000000-1111-2222-3333-444444444444 user: azure credential configuration endpoint service disable_user 2024-02-22T01:27:04.344179Z SUCCESS COMPLETE

cov-mgsec-broker-endpoint-action-by-aro


Broker - Endpoint Action By ARO.

Base Command

cov-mgsec-broker-endpoint-action-by-aro

Input

Argument Name Description Required
action_type Action to send to Host. Possible values are: ISOLATE, UNISOLATE, SHUTDOWN, RESTART, DEFENDER_QUICK_SCAN, DEFENDER_FULL_SCAN, DEFENDER_SIGNATURE_UPDATE. Required
aro_id ARO ID (eg. “00000000-1111-2222-3333-444444444444”). Required

Context Output

Path Type Description
FESBroker.host_identifier String Host Identifier
FESBroker.agent_uuid String Agent UUID
FESBroker.covalence_appliance String Covalence Appliance ID
FESBroker.task_id Number Endpoint Action Task ID

Command example

!cov-mgsec-broker-endpoint-action-by-aro action_type=DEFENDER_QUICK_SCAN aro_id=00000000-1111-2222-3333-444444444444

Context Example

{
    "FESBroker": {
        "Action": {
            "agent_uuid": "00000000-1111-2222-3333-444444444444",
            "covalence_appliance": "2000-001-XX-0",
            "host_identifier": "00000000-1111-2222-3333-444444444444",
            "task_id": 26876
        }
    }
}

Human Readable Output

Command Result - Success

agent_uuid covalence_appliance host_identifier task_id
00000000-1111-2222-3333-444444444444 2000-001-XX-0 00000000-1111-2222-3333-444444444444 26876

cov-mgsec-broker-ping


Broker - Ping.

Base Command

cov-mgsec-broker-ping

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

Path Type Description
FESBroker.APIStatus String API Status

Command example


#### Context Example

```json
{
    "FESBroker": {
        "APIStatus": "pong"
    }
}

Human Readable Output

Success

cov-mgsec-broker-endpoint-action-by-host


Broker - Endpoint Action By Host.

Base Command

cov-mgsec-broker-endpoint-action-by-host

Input

Argument Name Description Required
action_type Action to send to Host. Possible values are: ISOLATE, UNISOLATE, SHUTDOWN, RESTART, DEFENDER_QUICK_SCAN, DEFENDER_FULL_SCAN, DEFENDER_SIGNATURE_UPDATE. Required
org_id Organization ID (eg. “00000000-1111-2222-3333-444444444444”). Required
host_identifier Hostname. Required

Context Output

Path Type Description
FESBroker.host_identifier String Host Identifier
FESBroker.agent_uuid String Agent UUID
FESBroker.covalence_appliance String Covalence Appliance ID
FESBroker.task_id Number Endpoint Action Task ID

Command example

!cov-mgsec-broker-endpoint-action-by-host action_type=DEFENDER_QUICK_SCAN host_identifier=test-hostname org_id=00000000-1111-2222-3333-444444444444

Context Example

{
    "FESBroker": {
        "Action": {
            "agent_uuid": "00000000-1111-2222-3333-444444444444",
            "covalence_appliance": "2000-001-XX-0",
            "host_identifier": "test-hostname",
            "task_id": 24773
        }
    }
}

Human Readable Output

Command Result - Success

agent_uuid covalence_appliance host_identifier task_id
00000000-1111-2222-3333-444444444444 2000-001-XX-0 test-hostname 24773

cov-mgsec-broker-list-org


Broker - List organizations.

Base Command

cov-mgsec-broker-list-org

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

Path Type Description
FESBroker.ID String Organization ID
FESBroker.name String Organization Name
FESBroker.client_id String Client ID

Command example


#### Context Example

```json
{
    "FESBroker": {
        "Org": [
            {
                "ID": "00000000-1111-2222-3333-444444444444",
                "client_id": "2000-001-XX-0",
                "name": "Test Company"
            }
        ]
    }
}

Human Readable Output

Organizations

ID client_id name
00000000-1111-2222-3333-444444444444 2024-1384-SAN 110 Sand Company

cov-mgsec-comment-aro


Comment on an ARO.

Base Command

cov-mgsec-comment-aro

Input

Argument Name Description Required
aro_id This ARO ID to transition. Required
comment Comment to leave on the ARO. Required
is_comment_sensitive Optionally mark the comment as sensitive. Optional

Context Output

Path Type Description
FESPortal.Aro.ID String ID.
FESPortal.Aro.acknowledged Boolean Acknowledged
FESPortal.Aro.acknowledged_by.ID String Acknowledged By ID
FESPortal.Aro.acknowledged_by.avatar_file_url String Acknowledged By
FESPortal.Aro.acknowledged_by.email String Acknowledged By Email
FESPortal.Aro.acknowledged_by.first_name String Acknowledged By First Name
FESPortal.Aro.acknowledged_by.last_name String Acknowledged By Last Name
FESPortal.Aro.acknowledged_time Date Acknowledged Time
FESPortal.Aro.aro_id String ARO ID
FESPortal.Aro.author.ID String Author ID
FESPortal.Aro.author.avatar_file_url String Author Avatar File URL
FESPortal.Aro.author.email String Author Email
FESPortal.Aro.author.first_name String Author First Name
FESPortal.Aro.author.last_name String Author Last Name
FESPortal.Aro.author_organization.ID String Author Organization ID
FESPortal.Aro.author_organization.email String Author Organization Email
FESPortal.Aro.author_organization.name String Author Organization Name
FESPortal.Aro.author_organization_type String Author Organization Type
FESPortal.Aro.available_only_to_organization_id String ARO Comment Available Only to Organization ID
FESPortal.Aro.available_only_to_provider_id String ARO Comment Available Only to Provider ID
FESPortal.Aro.created_time Date ARO Created Time
FESPortal.Aro.id String ARO Comment ID
FESPortal.Aro.last_updated_time Date ARO Comment Last Updated Time
FESPortal.Aro.sensitive Boolean ARO Comment Sensitive
FESPortal.Aro.source String ARO Comment Source
FESPortal.Aro.text String ARO Comment Text
FESPortal.Aro.type String ARO Comment Type
FESPortal.Aro.visible_to.ID String ARO Comment Visible to ID
FESPortal.Aro.visible_to.email String ARO Comment Visible to Email
FESPortal.Aro.visible_to.name String ARO Comment Visible to Name

Command example

!cov-mgsec-comment-aro aro_id="b25e461e-75e9-415b-a631-6d0f4516f33a" comment="Risk mitigated."

Context Example

{
    "FESPortal": {
        "Org": {
            "acknowledged": true,
            "acknowledged_by": {
                "ID": "abcdefghijklmnopqrstuvwxyzabd1",
                "avatar_file_url": null,
                "email": "foo@bar.com",
                "first_name": "John",
                "last_name": "Smith"
            },
            "acknowledged_time": "2024-04-12 17:01:25",
            "aro_id": "b25e461e-75e9-415b-a631-6d0f4516f33a",
            "author": {
                "ID": "abcdefghijklmnopqrstuvwxyzabd1",
                "avatar_file_url": null,
                "email": "foo@bar.com",
                "first_name": "John",
                "last_name": "Smith"
            },
            "author_organization": {
                "ID": "00000000-1111-2222-3333-444444444444",
                "email": "foo@bar.com",
                "name": "Field Effect"
            },
            "author_organization_type": "Field Effect",
            "available_only_to_organization_id": null,
            "available_only_to_provider_id": null,
            "created_time": "2024-04-12 17:01:25",
            "id": "b14a53a4-23ac-488d-b992-dbc1d5ef5361",
            "last_updated_time": "2024-04-12 17:01:25",
            "sensitive": false,
            "source": "Portal",
            "text": "Risk mitigated.",
            "type": "Comment",
            "visible_to": [
                {
                    "ID": "00000000-1111-2222-3333-444444444444",
                    "email": null,
                    "name": "Tradecraft Test & Development (Do Not Delete)"
                },
                {
                    "ID": "00000000-1111-2222-3333-444444444444",
                    "email": "foo@bar.com",
                    "name": "Field Effect"
                }
            ]
        }
    }
}

Human Readable Output

ARO

Acknowledged Acknowledged By Acknowledged Time Aro Id Author Author Organization Author Organization Type Created Time Id Last Updated Time Sensitive Source Text Type Visible To
true ID: abcdefghijklmnopqrstuvwxyzabd1
avatar_file_url: null
email: foo@bar.com
first_name: John
last_name: Smith
2024-04-12 17:01:25 b25e461e-75e9-415b-a631-6d0f4516f33a ID: abcdefghijklmnopqrstuvwxyzabd1
avatar_file_url: null
email: foo@bar.com
first_name: John
last_name: Smith
ID: 00000000-1111-2222-3333-444444444444
email: foo@bar.com
name: Field Effect
Field Effect 2024-04-12 17:01:25 b14a53a4-23ac-488d-b992-dbc1d5ef5361 2024-04-12 17:01:25 false Portal Risk mitigated. Comment {‘ID’: ‘00000000-1111-2222-3333-444444444444’, ‘email’: None, ‘name’: ‘Tradecraft Test & Development (Do Not Delete)’},
{‘ID’: ‘00000000-1111-2222-3333-444444444444’, ‘email’: ‘foo@bar.com’, ‘name’: ‘Field Effect’}

cov-mgsec-list-escalation-contacts


Get the escalation contact list for a given organization.

Base Command

cov-mgsec-list-escalation-contacts

Input

Argument Name Description Required
org_id Organization ID (eg. “00000000-1111-2222-3333-444444444444”). Required

Context Output

Path Type Description
FESPortal.Org.priority String Priority.
FESPortal.Org.first_name String First Name.
FESPortal.Org.last_name String Last Name.
FESPortal.Org.job_title String Job Title.
FESPortal.Org.phone_number String Phone Number.
FESPortal.Org.secondary_phone String Secondary Phone Number.
FESPortal.Org.email String Email.
FESPortal.Org.notes String Notes.

Command example

!cov-mgsec-list-escalation-contacts org_id=cb7e26b7-9d62-4cc7-acf1-4264327ac552

Context Example

{
    "FESPortal": {
        "Org": [
            {
                "email": "test-email@fieldeffect.com",
                "first_name": "John",
                "job_title": null,
                "last_name": "Doe",
                "notes": "test notes",
                "phone_number": null,
                "priority": 1,
                "secondary_phone": null
            },
            {
                "email": "test-user2@fieldeffect.com",
                "first_name": "Testing1",
                "job_title": null,
                "last_name": "User",
                "notes": "test-note",
                "phone_number": null,
                "priority": 2,
                "secondary_phone": null
            },
            {
                "email": "test-user-4@fieldeffect.com",
                "first_name": "Bob",
                "job_title": "QA",
                "last_name": "Tester",
                "notes": "123\n456\n789",
                "phone_number": "+15550199999",
                "priority": 3,
                "secondary_phone": "+15069999999"
            },
            {
                "email": "test5@example.om",
                "first_name": "Smith",
                "job_title": "UI Dev",
                "last_name": "fes",
                "notes": null,
                "phone_number": "+16135445544",
                "priority": 4,
                "secondary_phone": "+16135554548"
            },
            {
                "email": "test-user-44@fieldeffect.com",
                "first_name": "John",
                "job_title": "Head of UI Development",
                "last_name": "Doe",
                "notes": null,
                "phone_number": "+16135554545",
                "priority": 5,
                "secondary_phone": null
            }
        ]
    }
}

Human Readable Output

Escalation Contacts

Priority First Name Last Name Job Title Phone Number Secondary Phone Email Notes
1 John Doe       test-email@fieldeffect.com some other test note
2 Testing1 User       test-user2@fieldeffect.com test-note
3 Bob Tester QA +15550199999 +15069999999 test-user-4@fieldeffect.com  
4 Smith fes UI Dev +16135445544 +16135554548 test5@example.om  
5 John Doe Head of UI Development +16135554545   test-user-44@fieldeffect.com  

cov-mgsec-list-key-contacts


Get the key contact list for a given organization.

Base Command

cov-mgsec-list-key-contacts

Input

Argument Name Description Required
org_id Organization ID (eg. “00000000-1111-2222-3333-444444444444”). Required

Context Output

Path Type Description
FESPortal.Org.first_name String First Name.
FESPortal.Org.last_name String Last Name.
FESPortal.Org.phone_number String Phone Number.
FESPortal.Org.email String Email.
FESPortal.Org.type String Contact Type.

Command example

!cov-mgsec-list-key-contacts org_id=cb7e26b7-9d62-4cc7-acf1-4264327ac552

Context Example

{
    "FESPortal": {
        "Org": [
            {
                "email": "test-email@fieldeffect.com",
                "first_name": "John",
                "last_name": "Doe",
                "phone_number": null,
                "type": "Administrative"
            },
            {
                "email": "test-email@fieldeffect.com",
                "first_name": "John",
                "last_name": "Doe",
                "phone_number": "+19022223333",
                "type": "Technical - Primary"
            },
            {
                "email": "test5@example.om",
                "first_name": "Smith",
                "last_name": "fes",
                "phone_number": "+16135445544",
                "type": "Technical - Secondary"
            }
        ]
    }
}

Human Readable Output

Key Contacts

First Name Last Name Phone Number Email Type
John Doe   test-email@fieldeffect.com Administrative
John Doe +19022223333 test-email@fieldeffect.com Technical - Primary
Smith fes +16135445544 test5@example.om Technical - Secondary

cov-mgsec-list-language


Get the default language for a given organization.

Base Command

cov-mgsec-list-language

Input

Argument Name Description Required
org_id Organization ID (eg. “00000000-1111-2222-3333-444444444444”). Required

Context Output

Path Type Description
FESPortal.Org.default_language String Default Language.

Command example

!cov-mgsec-list-language org_id=cb7e26b7-9d62-4cc7-acf1-4264327ac552

Context Example

{
    "FESPortal": {
        "Org": {
            "default_language": "en-CA"
        }
    }
}

Human Readable Output

Default Organization Language

Default Language
en-CA

Configuration parameters

  • credentials — Credentials (required)
  • proxy — Use system proxy settings
  • first_run_time_range — First run time range
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • isFetch — Fetch incidents
  • first_fetch — First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)
  • max_fetch — Fetch Limit
  • broker_url — Broker Server URL

Commands (12)

  • cov-mgsec-broker-cloud-action-by-aro

    Broker - Cloud Action By ARO.

  • cov-mgsec-broker-endpoint-action-by-aro

    Broker - Endpoint Action By ARO.

  • cov-mgsec-broker-endpoint-action-by-host

    Broker - Endpoint Action By Host.

  • cov-mgsec-broker-list-org

    Broker - List organizations.

  • cov-mgsec-broker-ping

    Broker - Test connectivity to the Broker service.

  • cov-mgsec-comment-aro

    Comment on an ARO.

  • cov-mgsec-get-aro

    Query FES Portal for ARO.

  • cov-mgsec-list-escalation-contacts

    Get the escalation contact list for a given organization.

  • cov-mgsec-list-key-contacts

    Get the key contact list for a given organization.

  • cov-mgsec-list-language

    Get the default language for a given organization.

  • cov-mgsec-list-org

    List organizations.

  • cov-mgsec-transition-aro

    Transition an ARO.

import json
import os
import traceback
from dataclasses import dataclass
from datetime import datetime, timedelta
from enum import Enum
from typing import Literal

import demistomock as demisto  # noqa: F401
import requests
import urllib3
from CommonServerPython import *  # noqa: F401
from requests import HTTPError, Response

DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
EMAIL = demisto.params().get("credentials")["identifier"]
API_KEY = demisto.params().get("credentials")["password"]
FIRST_RUN_TIME_RANGE = int(demisto.params().get("first_run_time_range").strip())
PROXY = demisto.params().get("proxy")
if not demisto.params().get("proxy", False):
    # Remove proxy environment variables if they exist
    for proxy_var in ["HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy"]:
        os.environ.pop(proxy_var, None)


class EndpointActionType(str, Enum):
    ISOLATE = "isolate"
    UNISOLATE = "unisolate"
    SHUTDOWN = "shutdown"
    RESTART = "restart"
    DEFENDER_QUICK_SCAN = "defender_quick_scan"
    DEFENDER_FULL_SCAN = "defender_full_scan"
    DEFENDER_SIGNATURE_UPDATE = "defender_signature_update"


class CloudActionType(str, Enum):
    DISABLE_USER = "disable_user"
    ENABLE_USER = "enable_user"
    REVOKE_SESSIONS = "revoke_sessions"


@dataclass
class OrganizationResponse:
    # pylint: disable=invalid-name
    ID: str
    name: str
    client_id: str


@dataclass
class ActionByHostResponse:
    host_identifier: str
    agent_uuid: str
    covalence_appliance: str
    task_id: int | str


@dataclass
class ActionByCloudAccountResponse:
    action_id: str
    action_type: Literal["disable_user", "enable_user", "revoke_sessions"]
    action_params: dict
    created_time: str
    status: Literal["QUEUED", "COMPLETE"]
    result: Literal["SUCCESS", "FAILED", "PENDING"]


class Portal:
    def __init__(self, bearer=None, portal_url="https://services.fieldeffect.net/v1", provider=None, verbose=False):
        self.auth = None
        self.portal_url = portal_url
        self.verbose = verbose
        self.provider_name = provider
        if bearer == "gan ceann":
            self.scheme = self.AuthScheme.FES
            # Headless login for those cursed with a GUI
        elif bearer:
            self.scheme = self.AuthScheme.BEARER
            self.auth = {"token": bearer, "expires": datetime.now() + timedelta(days=10 * 365), "refresh": None}
            self.provider_id = self.get_provider_id()
        else:
            raise ValueError("Bearer is missing")

    class AuthScheme:
        FES = "FieldEffectAuth"
        BEARER = "Bearer"
        KEY = "FieldEffectKey"

    def try_saved_token(self, token):
        # Return True if this token works, also save this token as the token
        # Return False if this token doesn't work and user will need to auth
        try:
            self.auth = token
            if self.provider_name:
                self.provider_id = self.find_provider(self.provider_name)
            else:
                self.provider_id = self.get_provider_id()
            return True

        except requests.exceptions.HTTPError:
            self.auth = None
            return False

    def paginated_get(
        self,
        uri: str,
        page_size: int = 500,
        limit: Optional[int] = None,
        query: Optional[Dict[str, Any]] = None,
        **kwargs,
    ):
        results = []
        query = query or {}

        # If a limit is passed into the query dict then use it as long as it's an int and another limit
        # hasn't already been provided as a param
        if "limit" in query and isinstance(query["limit"], int) and limit is None:
            # popping it out to check if it's < 0 in a common check with a param provided limit
            limit = query.pop("limit")

        # if a negative limit is provided then ignore it
        if isinstance(limit, int) and limit < 0:
            limit = None

        # On the initial query if the limit is smaller than the page size, the page size needs to be reduced to only hit the limit
        if isinstance(limit, int) and limit < page_size:
            query["limit"] = limit
        else:
            query["limit"] = page_size

        r = self.get(uri, query=query, **kwargs).json()
        results.extend(r["items"])

        while True:
            results_length = len(results)

            # break if the limit or total number of results has been exceeded
            if (isinstance(limit, int) and results_length >= limit) or results_length >= r["total"]:
                break

            # If there is a limit and the results_length is >= the limit size it would have broken above.
            # So we know here that results_length is < limit if a limit has been provided.
            # If the limit is smaller than the page size then just get up to the limit.
            if isinstance(limit, int) and (limit - results_length) < page_size:
                query["limit"] = limit - results_length

            query["page"] = r["page"] + 1
            r = self.get(uri, query=query, **kwargs).json()
            results.extend(r["items"])

        return results

    def get(self, uri, query=None, headers=None, remove_subdomain=False, **kwargs):
        return self._request(uri, method="GET", query=query, headers=headers, remove_subdomain=remove_subdomain, **kwargs)

    def post(self, uri, query=None, headers=None, remove_subdomain=False, **kwargs):
        return self._request(uri, method="POST", query=query, headers=headers, remove_subdomain=remove_subdomain, **kwargs)

    def _request(
        self, uri, method="GET", query=None, json=None, data=None, files=None, headers=None, remove_subdomain=False, **kwargs
    ):
        all_headers = {"Content-Type": "application/json"} if json is not None else {}

        if headers is not None:
            all_headers.update(headers)
        if self.auth:
            auth = "{} {}".format(self.scheme, self.auth["token"])
            all_headers.update({"Authorization": auth})

        url = f"{self.portal_url}/{uri if len(kwargs) == 0 else uri.format(**kwargs)}"
        if remove_subdomain:
            url = url.replace("services.", "")

        if self.verbose:
            sys.stdout.write(f"{method} {url} ")

        if method == "GET":
            r = requests.get(url, headers=all_headers, params=query)
        elif method == "POST":
            r = requests.post(url, headers=all_headers, json=json, data=data, params=query, files=files)
        elif method == "PUT":
            r = requests.put(url, headers=all_headers, json=json, data=data, params=query, files=files)
        elif method == "DELETE":
            r = requests.delete(url, headers=all_headers, params=query)
        else:
            raise AssertionError(f"Unsupported HTTP method: {method}")

        if self.verbose:
            sys.stdout.write(str(r.status_code) + "\n")
        if r.status_code >= 400:
            raise HTTPError(r.text)
        return r

    def get_provider_id(self):
        r = self.get("my_providers", auth=self.auth)
        if not r.json():
            raise ValueError(f"Account {EMAIL} is not part of any provider")
        return r.json()[0]["ID"]

    def find_provider(self, provider):
        r = self.get("providers", auth=self.auth)
        providers = r.json()
        for prov in providers:
            if provider == prov["name"] or provider == prov["ID"]:
                return prov["ID"]
        return None

    def get_organizations(self):
        r = self.get("my_providers/{id}/organizations", auth=self.auth, id=self.provider_id)
        return r.json()

    def find_organizations(self, org):
        r = self.get("my_providers/{id}/organizations", auth=self.auth, id=self.provider_id)
        orgs = r.json()
        matches = []
        for o in orgs:
            if org.lower() in o["name"].lower():
                matches.append(o)
        return matches

    def get_aros(self, **kwargs):
        aros = []
        if "query" not in kwargs:
            kwargs["query"] = {}
        kwargs["query"]["limit"] = 500
        r = self.get("providers/{id}/aros", auth=self.auth, id=self.provider_id, **kwargs).json()
        aros.extend(r["items"])
        while len(aros) < r["total"]:
            kwargs["query"]["page"] = r["page"] + 1
            r = self.get("providers/{id}/aros", auth=self.auth, id=self.provider_id, **kwargs).json()
            aros.extend(r["items"])
        return aros

    def get_active_response_profile(self, org_id):
        r = self.get("my_organizations/{org_id}", auth=self.auth, org_id=org_id)
        org_details = r.json()
        return org_details.get("active_response_profile", None)

    def transition_aro(self, aro_id, resolution, comment="", is_comment_sensitive=False):
        request = {"status": "Open", "resolution": resolution}
        if comment:
            request["comment"] = {"text": comment, "sensitive": is_comment_sensitive}

        r = self.post("aros/{aro_id}/transition", aro_id=aro_id, json=request)
        return r.json()

    def comment_aro(self, aro_id, comment="", is_comment_sensitive=False):
        request = {"aro_id": aro_id, "sensitive": is_comment_sensitive, "text": comment}

        r = self.post("aro_comments", json=request)
        return r.json()

    def list_escalation_contacts(self, org_id: str | None = None) -> List[Dict[str, Any]]:
        """
        Get the escalation contact list for a given organization.
        :param org_id: Org ID.
        :return: List of escalation contacts.
        """
        query = {}
        if org_id:
            query["organization_id"] = org_id

        return self.paginated_get("escalation_contact_lists", query=query)

    def list_organization_contacts(self, org_id: str) -> List[Dict[str, Any]]:
        """
        Get the organization contact list for a given organization
        :param org_id: Org ID.
        :return: List of organization contacts.
        """
        r = self.get("organizations/{org_id}", auth=self.auth, org_id=org_id)
        org_details = r.json()
        return org_details.get("contacts", None)

    def list_organization_language(self, org_id: str) -> Dict[str, Any]:
        """
        Get the default language for a given organization
        :param org_id: Org ID.
        :return: Default Language
        """
        r = self.get("organizations/{org_id}", auth=self.auth, org_id=org_id)
        org_details = r.json()
        locale = org_details.get("profile", {}).get("default_locale", None)
        return {"default_language": locale}


class BrokerClient:
    def __init__(self, host: str, verify_ssl: bool = False, api_key: str | None = None, timeout: int = 60):
        self.host = host[:-1] if host.endswith("/") else host
        self.session = requests.Session()
        self.verify_ssl = verify_ssl
        self.api_key = api_key
        self.timeout = timeout

        if not verify_ssl:
            urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

    @property
    def api_key(self) -> str | None:
        return self._api_key

    @api_key.setter
    def api_key(self, value: str | None) -> None:
        if isinstance(value, str):
            if not value.startswith("Bearer "):
                value = f"Bearer {value}"
            self.session.headers["Authorization"] = value
        self._api_key = value

    def ping(self) -> str:
        return self._request(method="GET", path="/ping", check_authentication=False).text

    def organizations(self, to_dataclass: bool = False) -> list[dict] | list[OrganizationResponse]:
        response: list[dict] = self._request(method="GET", path="/organizations").json()
        if to_dataclass is True:
            return [OrganizationResponse(**entry) for entry in response]
        return response

    def endpoint_action_by_host(
        self,
        action_type: EndpointActionType,
        org_id: str,
        host_identifier: str,
        to_dataclass: bool = False,
    ) -> dict | ActionByHostResponse:
        response: dict = self._request(
            method="POST",
            path=f"/endpoint/host/{action_type.value}",
            json={"org_id": org_id, "host_identifier": host_identifier},
        ).json()
        if to_dataclass is True:
            return ActionByHostResponse(**response)
        return response

    def endpoint_action_by_aro(
        self, action_type: EndpointActionType, aro_id: str, to_dataclass: bool = False
    ) -> dict | ActionByHostResponse:
        response: dict = self._request(
            method="POST",
            path=f"/endpoint/aro/{action_type.value}",
            json={"aro_id": aro_id},
        ).json()
        if to_dataclass is True:
            return ActionByHostResponse(**response)
        return response

    def cloud_action_by_aro(
        self, action_type: CloudActionType, aro_id: str, to_dataclass: bool = False
    ) -> dict | ActionByCloudAccountResponse:
        response: dict = self._request(
            method="POST",
            path=f"/cloud/aro/{action_type.value}",
            json={"aro_id": aro_id},
        ).json()
        if to_dataclass is True:
            return ActionByCloudAccountResponse(**response)
        return response

    # pylint: disable=too-many-arguments
    def _request(
        self,
        method: Literal["GET", "POST"],
        path: str,
        json: dict | None = None,
        data: dict | None = None,
        check_authentication: bool = True,
    ) -> Response:
        if check_authentication is True and not isinstance(self.session.headers.get("Authorization"), str):
            raise Exception("Must provide API Authorization to use Broker commands.")

        host_and_path = f"{self.host}{path}"

        if method == "GET":
            resp: Response = self.session.get(host_and_path, verify=self.verify_ssl, timeout=self.timeout)
        else:
            resp: Response = self.session.post(  # type: ignore[no-redef]
                host_and_path, json=json, data=data, verify=self.verify_ssl, timeout=self.timeout
            )

        resp.raise_for_status()
        return resp


""" Commands """


def portal_check():
    """
    Poking to the portal to make sure it's up
    """
    try:
        Portal(bearer=API_KEY)
        return True
    except Exception:
        demisto.debug(traceback.format_exc())
        return False


def fetch_incidents(last_run, first_run_time_range):
    last_fetch = last_run.get("last_fetch", None)
    aro_time_max = datetime.utcnow() - timedelta(seconds=1)

    if last_fetch is None:
        aro_time_min = aro_time_max - timedelta(days=first_run_time_range)
    else:
        aro_time_min = dateparser.parse(last_fetch)  # type: ignore
    assert aro_time_min is not None

    p = Portal(bearer=API_KEY)
    query = {"resolution": "Unresolved", "since": aro_time_min.strftime(DATE_FORMAT), "until": aro_time_max.strftime(DATE_FORMAT)}
    aros = p.get_aros(query=query)

    incidents = []

    # AROs are ordered by most recent ARO
    # it's required to traverse aros in chronological order (so last element first)
    # to avoid duplicating incidents
    for a in reversed(aros):
        created_time = dateparser.parse(a["creation_time"])
        assert created_time is not None, f'could not parse {a["creation_time"]}'
        if created_time != last_fetch:
            created_time_str = created_time.strftime(DATE_FORMAT)

            if a.get("organization", None):
                org_name = a["organization"].get("name", "No org name")
                org_id = a["organization"].get("ID", None)
            else:
                org_name = "No org name"
                org_id = None

            aro_type = a.get("type", "No ARO type")

            aro_title = a.get("title", "No title")

            incident: Dict[str, Any] = {
                "name": f"""[{org_name}] [{aro_type}] {aro_title}""",
                "occured": created_time_str,
                "rawJSON": json.dumps(a),
            }
            if a.get("severity", None):
                # XSOAR mapping
                # Unknown: 0
                # Informational: 0.5
                # Low: 1
                # Medium: 2
                # High: 3
                # Critical: 4
                severity_from_portal = a["severity"]
                if severity_from_portal == "Informational":
                    incident["severity"] = 0.5
                elif severity_from_portal == "Warning":
                    incident["severity"] = 1
                elif severity_from_portal == "Low":
                    incident["severity"] = 1
                elif severity_from_portal == "Medium":
                    incident["severity"] = 2
                elif severity_from_portal == "High":
                    incident["severity"] = 3
                elif severity_from_portal == "Critical":
                    incident["severity"] = 4
            else:
                incident["severity"] = 0
            if a.get("details", None):
                incident["details"] = a["details"]
                if a.get("steps", None) and len(a["steps"]) > 0:
                    incident["details"] += "\n\nMitigation Steps\n"
                    for step in a["steps"]:
                        incident["details"] += f"""- {step['label']}\n"""
                if org_id:
                    active_response_profile = p.get_active_response_profile(org_id)
                    if active_response_profile:
                        policy = active_response_profile.get("response_policy")
                        options = active_response_profile.get("options")
                        incident["details"] += "\nActive Response Profile\n"
                        incident["details"] += f"""- Response policy: {policy}\n"""
                        incident["details"] += f"""- Exclusions/ Modifications: {options}\n"""

            incidents.append(incident)

    next_run = {"last_fetch": aro_time_max.strftime(DATE_FORMAT)}

    return next_run, incidents


def get_aros():
    p = Portal(bearer=API_KEY)

    q = demisto.args().get("query", None)

    if q:
        query = {}  # pragma: no cover
        for param in q.split("&"):
            key = param.split("=")[0]
            value = param.split("=")[1]
            query[key] = value

            if "org" in query:
                org = p.find_organizations(query["org"])
                if not org:
                    raise ValueError(f'Unknown organization named {query["org"]}')
                del query["org"]
                query["organization_id"] = org[0]["ID"]
        aros = p.get_aros(query=query)
    else:
        aros = p.get_aros()

    details = argToBoolean(demisto.args().get("details", "false"))
    keys = ["title", "organization", "resolution", "severity", "status", "type"]

    if not details:
        filtered_r = []
        # returning only data in keys
        for aro in aros:
            a = {k: aro[k] for k in keys}
            filtered_r.append(a)
        return filtered_r
    else:
        return aros


def list_organizations():
    p = Portal(bearer=API_KEY)

    return p.get_organizations()


def transition_aro_command():
    p = Portal(bearer=API_KEY)
    args = demisto.args()
    return p.transition_aro(**args)


def comment_aro_command():
    p = Portal(bearer=API_KEY)
    args = demisto.args()
    return p.comment_aro(**args)


def list_escalation_contacts_command(portal_instance, args):
    contacts = []
    headers = ["priority", "first_name", "last_name", "job_title", "phone_number", "secondary_phone", "email", "notes"]
    result = portal_instance.list_escalation_contacts(**args)
    if result and result[0]["organization_id"] == args["org_id"]:
        for contact in result[0]["escalation_contacts"]:
            # Extract the required fields
            contact_info = {
                "priority": contact.get("priority"),
                "first_name": contact.get("first_name"),
                "last_name": contact.get("last_name"),
                "job_title": contact.get("job_title"),
                "phone_number": contact.get("phone_number"),
                "secondary_phone": contact.get("secondary_phone"),
                "email": contact.get("email"),
                "notes": contact.get("notes"),
            }
            contacts.append(contact_info)

        readable_output = tableToMarkdown(
            "Escalation Contacts", contacts, headers=headers, headerTransform=string_to_table_header
        )
    else:
        readable_output = "No escalation contacts found."
    return CommandResults(
        readable_output=readable_output, outputs_prefix="FESPortal.Org", outputs_key_field="ID", outputs=contacts
    )


def list_organization_key_contacts_command(portal_instance, args):
    contacts = []
    headers = ["first_name", "last_name", "phone_number", "email", "type"]
    result = portal_instance.list_organization_contacts(**args)
    if result:
        for contact in result:
            # Extract the required fields
            if contact["type"] in ["Administrative", "Technical - Primary", "Technical - Secondary", "Primary", "Technical"]:
                details_key = contact.get("contact_info") or contact.get("user")  # Data can be in either key.
                contact_info = {
                    "first_name": details_key.get("first_name"),
                    "last_name": details_key.get("last_name"),
                    "phone_number": details_key.get("phone_number"),
                    "email": details_key.get("email"),
                    "type": contact["type"],
                }
                contacts.append(contact_info)
        readable_output = tableToMarkdown("Key Contacts", contacts, headers=headers, headerTransform=string_to_table_header)
    else:
        readable_output = "No key contacts found."
    return CommandResults(
        readable_output=readable_output, outputs_prefix="FESPortal.Org", outputs_key_field="ID", outputs=contacts
    )


def list_organization_language_command(portal_instance, args):
    result = portal_instance.list_organization_language(**args)
    if result:
        readable_output = tableToMarkdown("Default Organization Language", result, headerTransform=string_to_table_header)
    else:
        readable_output = "No default language found."
    return CommandResults(readable_output=readable_output, outputs_prefix="FESPortal.Org", outputs_key_field="ID", outputs=result)


""" Broker Commands """


def ping_broker_command(broker_instance: BrokerClient):
    result = broker_instance.ping()
    if "pong" in result:
        readable_output = "## Success"
    else:
        readable_output = f"Failure - {result}."
    return CommandResults(
        readable_output=readable_output, outputs_prefix="FESBroker.APIStatus", outputs_key_field="", outputs=result
    )


def list_organizations_broker_command(broker_instance: BrokerClient):
    result = broker_instance.organizations()
    if result:
        readable_output = tableToMarkdown("Organizations", result)
    else:
        readable_output = "No broker organizations found."
    return CommandResults(readable_output=readable_output, outputs_prefix="FESBroker.Org", outputs_key_field="ID", outputs=result)


def endpoint_action_by_host_broker_command(broker_instance: BrokerClient, args):
    action_type = EndpointActionType[args["action_type"]]
    result = broker_instance.endpoint_action_by_host(action_type, args["org_id"], args["host_identifier"])
    return CommandResults(
        readable_output=tableToMarkdown("Command Result - Success", result),
        outputs_prefix="FESBroker.Action",
        outputs_key_field="agent_uuid",
        outputs=result,
    )


def endpoint_action_by_aro_broker_command(broker_instance: BrokerClient, args):
    action_type = EndpointActionType[args["action_type"]]
    result = broker_instance.endpoint_action_by_aro(action_type, args["aro_id"])
    return CommandResults(
        readable_output=tableToMarkdown("Command Result - Success", result),
        outputs_prefix="FESBroker.Action",
        outputs_key_field="agent_uuid",
        outputs=result,
    )


def cloud_action_by_aro_broker_command(broker_instance: BrokerClient, args):
    action_type = CloudActionType[args["action_type"]]
    result = broker_instance.cloud_action_by_aro(action_type, args["aro_id"])
    return CommandResults(
        readable_output=tableToMarkdown("Command Result", result),
        outputs_prefix="FESBroker.Action",
        outputs_key_field="action_id",
        outputs=result,
    )


def main():
    params = demisto.params()
    args = demisto.args()
    command = demisto.command()
    broker_url = params.get("broker_url", "")
    return_error_msg = None
    demisto.info(f"{command} is called")

    if broker_url and (command.startswith("cov-mgsec-broker") or command == "test-module"):
        # Initialize Broker client only if required, allowing the Portal commands to still function if the Broker
        # connection is down or unwanted.
        broker_instance = BrokerClient(host=broker_url, api_key=API_KEY)
    else:
        demisto.debug("No condition was met. Initializing BrokerClient")
        broker_instance = BrokerClient(host=broker_url, api_key=API_KEY)

    portal_instance = Portal(bearer=API_KEY)

    try:
        if command == "test-module":
            portal_result = portal_check()
            if broker_url:
                broker_result = bool(broker_instance.ping() == "pong")
                if broker_result is True and portal_result is True:
                    return_results("ok")
            elif portal_result is True:
                return_results("ok")

        elif command == "fetch-incidents":
            next_run, incidents = fetch_incidents(last_run=demisto.getLastRun(), first_run_time_range=FIRST_RUN_TIME_RANGE)

            demisto.setLastRun(next_run)
            demisto.incidents(incidents)

        elif command == "cov-mgsec-get-aro":
            r = get_aros()
            if r:
                readable_output = tableToMarkdown("AROs", r, removeNull=True, headerTransform=string_to_table_header)
            else:
                readable_output = "No AROs found"

            results = CommandResults(
                outputs_prefix="FESPortal.ARO", outputs_key_field="ID", outputs=r, readable_output=readable_output
            )
            return_results(results)
        elif command == "cov-mgsec-list-org":
            r = list_organizations()
            if r:
                readable_output = tableToMarkdown("Organizations", r, removeNull=True, headerTransform=string_to_table_header)
            else:
                readable_output = "No organizations found"

            results = CommandResults(
                outputs_prefix="FESPortal.Org", outputs_key_field="ID", outputs=r, readable_output=readable_output
            )
            return_results(results)
        elif command == "cov-mgsec-transition-aro":
            r = transition_aro_command()
            if r:
                readable_output = tableToMarkdown("ARO", r, removeNull=True, headerTransform=string_to_table_header)
            else:
                readable_output = "Error transitioning ARO."

            results = CommandResults(
                outputs_prefix="FESPortal.Org", outputs_key_field="ID", outputs=r, readable_output=readable_output
            )
            return_results(results)
        elif command == "cov-mgsec-comment-aro":
            r = comment_aro_command()
            if r:
                readable_output = tableToMarkdown("ARO", r, removeNull=True, headerTransform=string_to_table_header)
            else:
                readable_output = "Error commenting on ARO."

            results = CommandResults(
                outputs_prefix="FESPortal.Org", outputs_key_field="ID", outputs=r, readable_output=readable_output
            )
            return_results(results)
        elif command == "cov-mgsec-list-escalation-contacts":
            return_results(list_escalation_contacts_command(portal_instance, args))
        elif command == "cov-mgsec-list-key-contacts":
            return_results(list_organization_key_contacts_command(portal_instance, args))
        elif command == "cov-mgsec-list-language":
            return_results(list_organization_language_command(portal_instance, args))
        elif command == "cov-mgsec-broker-ping":
            return_results(ping_broker_command(broker_instance))
        elif command == "cov-mgsec-broker-list-org":
            return_results(list_organizations_broker_command(broker_instance))
        elif command == "cov-mgsec-broker-endpoint-action-by-host":
            return_results(endpoint_action_by_host_broker_command(broker_instance, args))
        elif command == "cov-mgsec-broker-endpoint-action-by-aro":
            return_results(endpoint_action_by_aro_broker_command(broker_instance, args))
        elif command == "cov-mgsec-broker-cloud-action-by-aro":
            return_results(cloud_action_by_aro_broker_command(broker_instance, args))
    except HTTPError as e:
        demisto.error(traceback.format_exc())
        http_text = None
        try:
            http_text = e.response.text  # Try to extract a text response if it exists.
        except AttributeError:
            http_text = e.response
        return_error_msg = (
            f"Failed to execute {command} command with HTTP response: {http_text!s}.\nStack trace: {traceback.format_exc()}"
        )
    except Exception as e:
        demisto.error(traceback.format_exc())
        return_error_msg = f"Failed to execute {command} command. Error: {e!s}.\nStack trace: {traceback.format_exc()}"

    if return_error_msg:
        return_error(return_error_msg)


if __name__ in ("__main__", "__builtin__", "builtins"):
    main()