diff --git a/Makefile b/Makefile
index 94a528ea..3b75aecc 100644
--- a/Makefile
+++ b/Makefile
@@ -62,7 +62,8 @@ test:
package:
@echo Package sdk
- poetry build
+ poetry build --format wheel
+ poetry run scripts/wheel_editor.sh dist/ark_sdk_python*x86_64.whl
publish-test:
@echo Release to test.pypi.org and create git tag
diff --git a/README.md b/README.md
index ca533232..9174de8f 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,7 @@ CyberArk's Official SDK and CLI for different services operations
- [x] DPA SSO Service
- [x] DPA K8S Service
- [x] DPA DB Service
+ - [x] Session Monitoring Service
- [x] All services contains CRUD and Statistics per respective service
- [x] Ready to use SDK in Python
- [x] CLI and SDK Examples
diff --git a/ark_sdk_python/ark_api.py b/ark_sdk_python/ark_api.py
index 29615db7..de64e579 100644
--- a/ark_sdk_python/ark_api.py
+++ b/ark_sdk_python/ark_api.py
@@ -175,3 +175,15 @@ def dpa_k8s(self) -> "ArkDPAK8SService":
from ark_sdk_python.services.dpa.k8s import ArkDPAK8SService
return cast(ArkDPAK8SService, self.service(ArkDPAK8SService))
+
+ @property
+ def sm(self) -> "ArkSMService":
+ """
+ Returns the SM service if the appropriate authenticators were given
+
+ Returns:
+ ArkSMService: _description_
+ """
+ from ark_sdk_python.services.sm import ArkSMService
+
+ return cast(ArkSMService, self.service(ArkSMService))
diff --git a/ark_sdk_python/models/actions/services/__init__.py b/ark_sdk_python/models/actions/services/__init__.py
index f7e93ae8..11f77a61 100644
--- a/ark_sdk_python/models/actions/services/__init__.py
+++ b/ark_sdk_python/models/actions/services/__init__.py
@@ -1,12 +1,15 @@
from typing import Any, List
from ark_sdk_python.models.actions.services.ark_dpa_exec_action_consts import DPA_ACTIONS
+from ark_sdk_python.models.actions.services.ark_sm_exec_action_consts import SM_ACTIONS
SUPPORTED_SERVICE_ACTIONS: List[Any] = [
DPA_ACTIONS,
+ SM_ACTIONS,
]
__all__ = [
'DPA_ACTIONS',
+ 'SM_ACTIONS',
'SUPPORTED_SERVICE_ACTIONS',
]
diff --git a/ark_sdk_python/models/actions/services/ark_sm_exec_action_consts.py b/ark_sdk_python/models/actions/services/ark_sm_exec_action_consts.py
new file mode 100644
index 00000000..b5e82190
--- /dev/null
+++ b/ark_sdk_python/models/actions/services/ark_sm_exec_action_consts.py
@@ -0,0 +1,27 @@
+from typing import Any, Dict, Final, Optional, Type
+
+from ark_sdk_python.models import ArkModel
+from ark_sdk_python.models.actions.ark_service_action_definition import ArkServiceActionDefinition
+from ark_sdk_python.models.services.sm import ArkSMGetSession, ArkSMGetSessionActivities, ArkSMSessionActivitiesFilter, ArkSMSessionsFilter
+
+# Session Monitoring Definitions
+SM_ACTION_TO_SCHEMA_MAP: Final[Dict[str, Optional[Type[ArkModel]]]] = {
+ 'list-sessions': None,
+ 'count-sessions': None,
+ 'list-sessions-by': ArkSMSessionsFilter,
+ 'count-sessions-by': ArkSMSessionsFilter,
+ 'session': ArkSMGetSession,
+ 'list-session-activities': ArkSMGetSessionActivities,
+ 'count-session-activities': ArkSMGetSessionActivities,
+ 'list-session-activities-by': ArkSMSessionActivitiesFilter,
+ 'count-session-activities-by': ArkSMSessionActivitiesFilter,
+ 'sessions-stats': None,
+}
+SM_ACTION_DEFAULTS_MAP: Final[Dict[str, Dict[str, Any]]] = {}
+
+# Service Actions Definition
+SM_ACTIONS: Final[ArkServiceActionDefinition] = ArkServiceActionDefinition(
+ action_name='sm',
+ schemas=SM_ACTION_TO_SCHEMA_MAP,
+ defaults=SM_ACTION_DEFAULTS_MAP,
+)
diff --git a/ark_sdk_python/models/common/__init__.py b/ark_sdk_python/models/common/__init__.py
index ac4e03f2..e4fdb559 100644
--- a/ark_sdk_python/models/common/__init__.py
+++ b/ark_sdk_python/models/common/__init__.py
@@ -1,3 +1,5 @@
+from ark_sdk_python.models.common.ark_access_method import ArkAccessMethod
+from ark_sdk_python.models.common.ark_application_code import ArkApplicationCode
from ark_sdk_python.models.common.ark_async_request_settings import ArkAsyncRequestSettings
from ark_sdk_python.models.common.ark_async_status import ArkAsyncStatus
from ark_sdk_python.models.common.ark_async_task import ArkAsyncTask
@@ -29,9 +31,11 @@
'ArkWorkspaceType',
'ArkNetworkEntityType',
'ArkConnectorType',
+ 'ArkApplicationCode',
'ArkProtocolType',
'VALID_DATE_REGEX',
'VALID_LOGIN_MAX_LENGTH',
'VALID_LOGIN_NAME_REGEX',
'ArkConnectionMethod',
+ 'ArkAccessMethod',
]
diff --git a/ark_sdk_python/models/common/ark_access_method.py b/ark_sdk_python/models/common/ark_access_method.py
new file mode 100644
index 00000000..19283c81
--- /dev/null
+++ b/ark_sdk_python/models/common/ark_access_method.py
@@ -0,0 +1,6 @@
+from enum import Enum
+
+
+class ArkAccessMethod(str, Enum):
+ VAULTED = 'Vaulted'
+ JIT = 'JIT'
diff --git a/ark_sdk_python/models/common/ark_application_code.py b/ark_sdk_python/models/common/ark_application_code.py
new file mode 100644
index 00000000..dde79780
--- /dev/null
+++ b/ark_sdk_python/models/common/ark_application_code.py
@@ -0,0 +1,20 @@
+from enum import Enum
+
+
+class ArkApplicationCode(str, Enum):
+ DPA = 'DPA'
+ CSM = 'CSM'
+ PAM = 'PAM'
+ DAP = 'DAP'
+ ITI = 'ITI'
+ UBA = 'UBA'
+ ADM = 'ADM'
+ USR = 'USR'
+ AUD = 'AUD'
+ ALR = 'ALR'
+ CEM = 'CEM'
+ EPM = 'EPM'
+ SCA = 'SCA'
+ SHSM = 'SHSM'
+ CLO = 'CLO'
+ CMS = 'CMS'
diff --git a/ark_sdk_python/models/common/ark_protocol_type.py b/ark_sdk_python/models/common/ark_protocol_type.py
index 36196f13..15156dd5 100644
--- a/ark_sdk_python/models/common/ark_protocol_type.py
+++ b/ark_sdk_python/models/common/ark_protocol_type.py
@@ -9,3 +9,5 @@ class ArkProtocolType(str, MultiValueEnum):
CLI = 'cli', 'CLI'
CONSOLE = 'console', 'Console'
HTTPS = 'https', 'HTTPS'
+ K8S = 'K8S', 'k8s'
+ DB = 'Database', 'database', 'DATABASE'
diff --git a/ark_sdk_python/models/services/sm/__init__.py b/ark_sdk_python/models/services/sm/__init__.py
new file mode 100644
index 00000000..1e7c8580
--- /dev/null
+++ b/ark_sdk_python/models/services/sm/__init__.py
@@ -0,0 +1,24 @@
+from ark_sdk_python.models.services.sm.ark_sm_get_session import ArkSMGetSession
+from ark_sdk_python.models.services.sm.ark_sm_get_session_activities import ArkSMGetSessionActivities
+from ark_sdk_python.models.services.sm.ark_sm_protocol_type_serializer import serialize_sm_protocol_type
+from ark_sdk_python.models.services.sm.ark_sm_session import ArkSMSession, ArkSMSessions, ArkSMSessionStatus
+from ark_sdk_python.models.services.sm.ark_sm_session_activity import ArkSMSessionActivities, ArkSMSessionActivity
+from ark_sdk_python.models.services.sm.ark_sm_session_activity_filter import ArkSMSessionActivitiesFilter
+from ark_sdk_python.models.services.sm.ark_sm_sessions_filter import ArkSMSessionsFilter
+from ark_sdk_python.models.services.sm.ark_sm_sessions_stats import ArkSMSessionsStats
+from ark_sdk_python.models.services.sm.ark_sm_workspace_type_serializer import serialize_sm_workspace_type
+
+__all__ = [
+ 'ArkSMSession',
+ 'ArkSMSessions',
+ 'ArkSMSessionStatus',
+ 'ArkSMSessionsFilter',
+ 'ArkSMSessionsStats',
+ 'ArkSMGetSession',
+ 'ArkSMGetSessionActivities',
+ 'ArkSMSessionActivity',
+ 'ArkSMSessionActivities',
+ 'ArkSMSessionActivitiesFilter',
+ 'serialize_sm_workspace_type',
+ 'serialize_sm_protocol_type',
+]
diff --git a/ark_sdk_python/models/services/sm/ark_sm_get_session.py b/ark_sdk_python/models/services/sm/ark_sm_get_session.py
new file mode 100644
index 00000000..a82714ea
--- /dev/null
+++ b/ark_sdk_python/models/services/sm/ark_sm_get_session.py
@@ -0,0 +1,7 @@
+from pydantic import Field
+
+from ark_sdk_python.models import ArkModel
+
+
+class ArkSMGetSession(ArkModel):
+ session_id: str = Field(description='Session id to get')
diff --git a/ark_sdk_python/models/services/sm/ark_sm_get_session_activities.py b/ark_sdk_python/models/services/sm/ark_sm_get_session_activities.py
new file mode 100644
index 00000000..6bd0f697
--- /dev/null
+++ b/ark_sdk_python/models/services/sm/ark_sm_get_session_activities.py
@@ -0,0 +1,7 @@
+from pydantic import Field
+
+from ark_sdk_python.models import ArkModel
+
+
+class ArkSMGetSessionActivities(ArkModel):
+ session_id: str = Field(description='Session id to get the activities for')
diff --git a/ark_sdk_python/models/services/sm/ark_sm_protocol_type_serializer.py b/ark_sdk_python/models/services/sm/ark_sm_protocol_type_serializer.py
new file mode 100644
index 00000000..f84a5e91
--- /dev/null
+++ b/ark_sdk_python/models/services/sm/ark_sm_protocol_type_serializer.py
@@ -0,0 +1,22 @@
+from ark_sdk_python.models import ArkException
+from ark_sdk_python.models.common import ArkProtocolType
+
+
+def serialize_sm_protocol_type(protocol_type: ArkProtocolType) -> str:
+ if isinstance(protocol_type, str):
+ protocol_type = ArkProtocolType(protocol_type)
+ if protocol_type == ArkProtocolType.SSH:
+ return 'SSH'
+ elif protocol_type == ArkProtocolType.RDP:
+ return 'RDP'
+ elif protocol_type == ArkProtocolType.CLI:
+ return 'CLI'
+ elif protocol_type == ArkProtocolType.CONSOLE:
+ return 'Console'
+ elif protocol_type == ArkProtocolType.HTTPS:
+ return 'HTTPS'
+ elif protocol_type == ArkProtocolType.K8S:
+ return 'K8S'
+ elif protocol_type == ArkProtocolType.DB:
+ return 'Database'
+ raise ArkException('Invalid SM Protocol Type')
diff --git a/ark_sdk_python/models/services/sm/ark_sm_session.py b/ark_sdk_python/models/services/sm/ark_sm_session.py
new file mode 100644
index 00000000..82555695
--- /dev/null
+++ b/ark_sdk_python/models/services/sm/ark_sm_session.py
@@ -0,0 +1,40 @@
+from datetime import datetime, timedelta
+from enum import Enum
+from typing import Any, Dict, List, Optional
+
+from pydantic import Field
+
+from ark_sdk_python.models import ArkCamelizedModel
+from ark_sdk_python.models.common import ArkAccessMethod, ArkApplicationCode, ArkProtocolType, ArkWorkspaceType
+
+
+class ArkSMSessionStatus(str, Enum):
+ ACTIVE = 'Active'
+ ENDED = 'Ended'
+ FAILED = 'Failed'
+
+
+class ArkSMSession(ArkCamelizedModel):
+ tenant_id: Optional[str] = Field(description='Tenant id of the session')
+ session_id: str = Field(description='Session id')
+ session_status: Optional[ArkSMSessionStatus] = Field(description='Status of the session')
+ session_duration: Optional[timedelta] = Field(description='Duration of the session in seconds')
+ end_reason: Optional[str] = Field(description='End reason for the session')
+ error_code: Optional[str] = Field(description='Error code for the session')
+ application_code: Optional[ArkApplicationCode] = Field(description='Application code of the session')
+ access_method: Optional[ArkAccessMethod] = Field(description='Access method of the session')
+ start_time: Optional[datetime] = Field(description='Start time of the session')
+ end_time: Optional[datetime] = Field(description='End time of the session')
+ user: Optional[str] = Field(description='Username of the session')
+ source: Optional[str] = Field(description='Source of the session (Usually Ip)')
+ target: Optional[str] = Field(description='Target of the session (Usually Ip/Dns)')
+ target_username: Optional[str] = Field(description='Target username of the session')
+ protocol: Optional[ArkProtocolType] = Field(description='Connection protocol of the session')
+ platform: Optional[ArkWorkspaceType] = Field(description='Connection platform of the session')
+ custom_data: Optional[Dict[str, Any]] = Field(description='Custom data of the session')
+
+
+class ArkSMSessions(ArkCamelizedModel):
+ sessions: List[ArkSMSession] = Field(description='List of the sessions')
+ filtered_count: int = Field(description='How many sessions were filtered')
+ returned_count: int = Field(description='How many sessions were returned')
diff --git a/ark_sdk_python/models/services/sm/ark_sm_session_activity.py b/ark_sdk_python/models/services/sm/ark_sm_session_activity.py
new file mode 100644
index 00000000..6ec24a7b
--- /dev/null
+++ b/ark_sdk_python/models/services/sm/ark_sm_session_activity.py
@@ -0,0 +1,31 @@
+from datetime import datetime
+from typing import List, Optional
+
+from pydantic import Field
+
+from ark_sdk_python.models import ArkCamelizedModel
+from ark_sdk_python.models.common import ArkApplicationCode
+
+
+class ArkSMSessionActivity(ArkCamelizedModel):
+ uuid: str = Field(description='ID of the audit')
+ tenant_id: str = Field(description='Tenant id of the audit')
+ timestamp: datetime = Field(description='Time of the audit')
+ username: str = Field(description='Username of the audit')
+ application_code: ArkApplicationCode = Field(description='Application code of the audit')
+ action: str = Field(description='Action performed for the audit')
+ user_id: str = Field(description='Id of the user who performed the audit')
+ source: str = Field(description='Source of the audit')
+ action_type: str = Field(description='Type of action for the audit')
+ audit_code: Optional[str] = Field(description='Audit code of the audit')
+ command: Optional[str] = Field(description='Command performed as part of the audit')
+ target: Optional[str] = Field(description='Target of the audit')
+ service_name: Optional[str] = Field(description='Service name of the audit')
+ session_id: Optional[str] = Field(description='Session id of the audit if related to a session')
+ message: Optional[str] = Field(description='Message of the audit')
+
+
+class ArkSMSessionActivities(ArkCamelizedModel):
+ activities: List[ArkSMSessionActivity] = Field(description='List of the session activities')
+ filtered_count: int = Field(description='How many session activities were filtered')
+ returned_count: int = Field(description='How many session activities were returned')
diff --git a/ark_sdk_python/models/services/sm/ark_sm_session_activity_filter.py b/ark_sdk_python/models/services/sm/ark_sm_session_activity_filter.py
new file mode 100644
index 00000000..838bc4ea
--- /dev/null
+++ b/ark_sdk_python/models/services/sm/ark_sm_session_activity_filter.py
@@ -0,0 +1,8 @@
+from pydantic import Field
+
+from ark_sdk_python.models import ArkCamelizedModel
+
+
+class ArkSMSessionActivitiesFilter(ArkCamelizedModel):
+ session_id: str = Field(description='Session id to get')
+ command_contains: str = Field(description='String which the command contains')
diff --git a/ark_sdk_python/models/services/sm/ark_sm_sessions_filter.py b/ark_sdk_python/models/services/sm/ark_sm_sessions_filter.py
new file mode 100644
index 00000000..b30941c1
--- /dev/null
+++ b/ark_sdk_python/models/services/sm/ark_sm_sessions_filter.py
@@ -0,0 +1,30 @@
+from pydantic import Field, constr
+
+from ark_sdk_python.models import ArkCamelizedModel
+
+
+class ArkSMSessionsFilter(ArkCamelizedModel):
+ search: constr(max_length=4096) = Field(
+ description='Free text query to search sessions by. For example: "startTime GE 2023-11-18T06:53:30Z AND status IN Failed,Ended AND endReason STARTSWITH Err008"'
+ )
+
+ class Config:
+ schema_extra = {
+ 'examples': [
+ {
+ 'search': 'duration LE 01:00:00',
+ },
+ {
+ 'search': 'startTime GE 2023-11-18T06:53:30Z',
+ },
+ {
+ 'search': 'status IN Failed,Ended AND endReason STARTSWITH Err008',
+ },
+ {
+ 'search': 'command STARTSWITH ls',
+ },
+ {
+ 'search': 'protocol IN SSH,RDP',
+ },
+ ]
+ }
diff --git a/ark_sdk_python/models/services/sm/ark_sm_sessions_stats.py b/ark_sdk_python/models/services/sm/ark_sm_sessions_stats.py
new file mode 100644
index 00000000..2c3b0abf
--- /dev/null
+++ b/ark_sdk_python/models/services/sm/ark_sm_sessions_stats.py
@@ -0,0 +1,46 @@
+from typing import Dict
+
+from pydantic import Field, validator
+
+from ark_sdk_python.models import ArkModel
+from ark_sdk_python.models.common import ArkApplicationCode, ArkProtocolType, ArkWorkspaceType
+from ark_sdk_python.models.services.sm.ark_sm_session import ArkSMSessionStatus
+
+
+class ArkSMSessionsStats(ArkModel):
+ sessions_count: int = Field(description='Sessions count in the last 30 days')
+ sessions_count_per_application_code: Dict[ArkApplicationCode, int] = Field(description='Sessions count per application code')
+ sessions_count_per_platform: Dict[ArkWorkspaceType, int] = Field(description='Sessions count per platform')
+ sessions_count_per_status: Dict[ArkSMSessionStatus, int] = Field(description='Sessions count per status')
+ sessions_count_per_protocol: Dict[ArkProtocolType, int] = Field(description='Sessions count per protocol')
+ sessions_failure_count: int = Field(description='Sessions count with failures')
+
+ # pylint: disable=no-self-use,no-self-argument
+ @validator('sessions_count_per_platform')
+ def validate_sessions_count_per_platform(cls, val):
+ for platform in val.keys():
+ if ArkWorkspaceType(platform) not in [
+ ArkWorkspaceType.AWS,
+ ArkWorkspaceType.AZURE,
+ ArkWorkspaceType.GCP,
+ ArkWorkspaceType.ONPREM,
+ ArkWorkspaceType.UNKNOWN,
+ ]:
+ raise ValueError('Invalid Platform / Workspace Type')
+ return val
+
+ # pylint: disable=no-self-use,no-self-argument
+ @validator('sessions_count_per_protocol')
+ def validate_sessions_count_per_protocol(cls, val):
+ for protocol in val.keys():
+ if ArkProtocolType(protocol) not in [
+ ArkProtocolType.SSH,
+ ArkProtocolType.RDP,
+ ArkProtocolType.CLI,
+ ArkProtocolType.CONSOLE,
+ ArkProtocolType.HTTPS,
+ ArkProtocolType.K8S,
+ ArkProtocolType.DB,
+ ]:
+ raise ValueError('Invalid Protocol Type')
+ return val
diff --git a/ark_sdk_python/models/services/sm/ark_sm_workspace_type_serializer.py b/ark_sdk_python/models/services/sm/ark_sm_workspace_type_serializer.py
new file mode 100644
index 00000000..c5038463
--- /dev/null
+++ b/ark_sdk_python/models/services/sm/ark_sm_workspace_type_serializer.py
@@ -0,0 +1,18 @@
+from ark_sdk_python.models import ArkException
+from ark_sdk_python.models.common import ArkWorkspaceType
+
+
+def serialize_sm_workspace_type(ws_type: ArkWorkspaceType):
+ if isinstance(ws_type, str):
+ ws_type = ArkWorkspaceType(ws_type)
+ if ws_type == ArkWorkspaceType.AWS:
+ return 'AWS'
+ elif ws_type == ArkWorkspaceType.AZURE:
+ return 'Azure'
+ elif ws_type == ArkWorkspaceType.ONPREM:
+ return 'OnPrem'
+ elif ws_type == ArkWorkspaceType.GCP:
+ return 'GCP'
+ elif ws_type == ArkWorkspaceType.UNKNOWN:
+ return 'Unknown'
+ raise ArkException('Invalid SM Workspace Type')
diff --git a/ark_sdk_python/services/sm/__init__.py b/ark_sdk_python/services/sm/__init__.py
new file mode 100644
index 00000000..4529c5c8
--- /dev/null
+++ b/ark_sdk_python/services/sm/__init__.py
@@ -0,0 +1,3 @@
+from ark_sdk_python.services.sm.ark_sm_service import ArkSMService
+
+__all__ = ['ArkSMService']
diff --git a/ark_sdk_python/services/sm/ark_sm_service.py b/ark_sdk_python/services/sm/ark_sm_service.py
new file mode 100644
index 00000000..be42bc51
--- /dev/null
+++ b/ark_sdk_python/services/sm/ark_sm_service.py
@@ -0,0 +1,275 @@
+import itertools
+from datetime import datetime, timedelta
+from http import HTTPStatus
+from typing import Dict, Final, Iterator, Optional, Set
+
+from dateutil.tz import tzutc
+from overrides import overrides
+from pydantic import validate_arguments
+
+from ark_sdk_python.auth.ark_isp_auth import ArkISPAuth
+from ark_sdk_python.common import ArkPage
+from ark_sdk_python.common.isp import ArkISPServiceClient
+from ark_sdk_python.models import ArkServiceException
+from ark_sdk_python.models.common import ArkApplicationCode, ArkProtocolType, ArkWorkspaceType
+from ark_sdk_python.models.services import ArkServiceConfig
+from ark_sdk_python.models.services.sm import (
+ ArkSMGetSession,
+ ArkSMGetSessionActivities,
+ ArkSMSession,
+ ArkSMSessionActivities,
+ ArkSMSessionActivitiesFilter,
+ ArkSMSessionActivity,
+ ArkSMSessions,
+ ArkSMSessionsFilter,
+ ArkSMSessionsStats,
+ ArkSMSessionStatus,
+)
+from ark_sdk_python.services.ark_service import ArkService
+
+UTC = tzutc()
+SERVICE_CONFIG: Final[ArkServiceConfig] = ArkServiceConfig(
+ service_name='sm', required_authenticator_names=['isp'], optional_authenticator_names=[]
+)
+DEFAULT_TIME_DELTA_DAYS: Final[int] = 30
+SESSIONS_API_URL: Final[str] = 'api/sessions'
+SESSION_API_URL: Final[str] = 'api/sessions/{session_id}'
+SESSION_ACTIVITIES_API_URL: Final[str] = 'api/sessions/{session_id}/activities'
+
+ArkSMPage = ArkPage[ArkSMSession]
+ArkSMActivitiesPage = ArkPage[ArkSMSessionActivity]
+
+
+class ArkSMService(ArkService):
+ def __init__(self, isp_auth: ArkISPAuth) -> None:
+ super().__init__(isp_auth)
+ self.__isp_auth = isp_auth
+ self.__client: ArkISPServiceClient = ArkISPServiceClient.from_isp_auth(self.__isp_auth, 'sessionmonitoring')
+
+ @validate_arguments
+ def __search_params_from_filter(self, sessions_filter: ArkSMSessionsFilter):
+ return {'search': sessions_filter.search}
+
+ @validate_arguments
+ def __call_sessions_api(self, params: Optional[dict] = None) -> ArkSMSessions:
+ params_dict = {}
+ if params:
+ params_dict['params'] = params
+ resp = self.__client.get(SESSIONS_API_URL, **params_dict)
+ if resp.status_code != HTTPStatus.OK:
+ raise ArkServiceException(f'Failed to list sessions [{resp.text}] {params=}')
+ return ArkSMSessions.parse_obj(resp.json())
+
+ @validate_arguments
+ def __call_activities_api(self, session_id: str, params: Optional[dict] = None) -> ArkSMSessionActivities:
+ endpoint = SESSION_ACTIVITIES_API_URL.format(session_id=session_id)
+ resp = self.__client.get(endpoint, params=params)
+ if resp.status_code != HTTPStatus.OK:
+ raise ArkServiceException(f'Failed to list activities [{resp.text}]')
+ return ArkSMSessionActivities.parse_obj(resp.json())
+
+ @validate_arguments
+ def __list_sessions(self, params: Optional[Dict] = None) -> Iterator[ArkSMPage]:
+ params = params or {}
+ sessions: ArkSMSessions = self.__call_sessions_api(params)
+ offset = 0
+ while sessions.returned_count > 0:
+ yield ArkSMPage(items=sessions.sessions)
+ offset += sessions.returned_count
+ params['offset'] = offset
+ sessions = self.__call_sessions_api(params)
+
+ @validate_arguments
+ def __list_activities(self, session_id: str, params: Optional[Dict] = None) -> Iterator[ArkSMActivitiesPage]:
+ params = params or {}
+ activities: ArkSMSessionActivities = self.__call_activities_api(session_id=session_id, params=params)
+ offset = 0
+ while activities.returned_count > 0:
+ yield ArkSMActivitiesPage(items=activities.activities)
+ offset += activities.returned_count
+ params['offset'] = offset
+ activities = self.__call_activities_api(session_id=session_id, params=params)
+
+ def list_sessions(self) -> Iterator[ArkSMPage]:
+ """
+ Lists all sessions done on the last 24 hours
+
+ Raises:
+ ArkServiceException: _description_
+
+ Yields:
+ Iterator[ArkSMPage]: _description_
+ """
+ self._logger.info('Listing all session')
+ yield from self.__list_sessions()
+
+ def count_sessions(self) -> int:
+ """
+ Counts all sessions done on the last 24 hours
+
+ Returns:
+ int: _description_
+ """
+ return self.__call_sessions_api().filtered_count
+
+ def list_sessions_by(self, sessions_filter: ArkSMSessionsFilter) -> Iterator[ArkSMPage]:
+ """
+ Lists all sessions with given filter
+
+ Args:
+ sessions_filter (ArkSMSessionsFilter): _description_
+ Examples:
+ ArkSMSessionsFilter(search='startTime GE 2023-12-03T08:55:29Z AND sessionDuration GE 00:00:01')
+ ArkSMSessionsFilter(search='sessionStatus IN Failed,Ended AND endReason STARTSWITH Err008')
+ ArkSMSessionsFilter(search='command STARTSWITH ls')
+ ArkSMSessionsFilter(search='protocol IN SSH,RDP,Database')
+
+ Raises:
+ ArkServiceException: _description_
+
+ Yields:
+ Iterator[ArkSMPage]: _description_
+ """
+ self._logger.info('Listing sessions by filter', search=sessions_filter.search)
+ yield from self.__list_sessions(self.__search_params_from_filter(sessions_filter))
+
+ def count_sessions_by(self, sessions_filter: ArkSMSessionsFilter) -> int:
+ """
+ Counts all sessions with given filter
+
+ Args:
+ sessions_filter (ArkSMSessionsFilter): _description_
+ Examples:
+ ArkSMSessionsFilter(search='startTime GE 2023-12-03T08:55:29Z AND sessionDuration GE 00:00:01')
+ ArkSMSessionsFilter(search='sessionStatus IN Failed,Ended AND endReason STARTSWITH Err008')
+ ArkSMSessionsFilter(search='command STARTSWITH ls')
+ ArkSMSessionsFilter(search='protocol IN SSH,RDP,Database')
+
+ Returns:
+ int: _description_
+ """
+ return self.__call_sessions_api(self.__search_params_from_filter(sessions_filter)).filtered_count
+
+ def session(self, get_session: ArkSMGetSession) -> ArkSMSession:
+ """
+ Retrieves a session by id
+
+ Args:
+ get_session (ArkSMGetSession): _description_
+
+ Raises:
+ ArkServiceException: _description_
+ ArkServiceException: _description_
+
+ Returns:
+ ArkSMSession: _description_
+ """
+ self._logger.info(f'Retrieving session by id [{get_session.session_id}]')
+ resp = self.__client.get(SESSION_API_URL.format(session_id=get_session.session_id))
+ if resp.status_code != HTTPStatus.OK:
+ raise ArkServiceException(f'Failed to list sessions [{resp.text}]')
+ session = resp.json()
+ if len(session) == 0:
+ raise ArkServiceException(f'No session found for requested session id [{get_session.session_id}]')
+ return ArkSMSession.parse_obj(session)
+
+ def list_session_activities(self, get_session_activities: ArkSMGetSessionActivities) -> Iterator[ArkSMActivitiesPage]:
+ """
+ Lists all session activities by session id
+
+ Args:
+ get_session_activities (ArkSMGetSessionActivities): _description_
+
+ Yields:
+ Iterator[ArkSMActivitiesPage]: _description_
+ """
+ self._logger.info(f'Retrieving session activities by id [{get_session_activities.session_id}]')
+ yield from self.__list_activities(session_id=get_session_activities.session_id)
+
+ def count_session_activities(self, get_session_activities: ArkSMGetSessionActivities) -> int:
+ """
+ Count all session activities by session id
+
+ Args:
+ get_session_activities (ArkSMGetSessionActivities): _description_
+
+ Returns:
+ int: _description_
+ """
+ self._logger.info(f'Counting session activities by id [{get_session_activities.session_id}]')
+ return self.__call_activities_api(session_id=get_session_activities.session_id).filtered_count
+
+ def list_session_activities_by(self, session_activities_filter: ArkSMSessionActivitiesFilter) -> Iterator[ArkSMActivitiesPage]:
+ """
+ Lists all session activities for session id by filter
+
+ Args:
+ session_activities_filter (ArkSMSessionActivitiesFilter): _description_
+
+ Yields:
+ Iterator[ArkSMActivitiesPage]: _description_
+ """
+ self._logger.info(f'Retrieving session activities by id [{session_activities_filter.session_id}]')
+ for page in self.__list_activities(session_id=session_activities_filter.session_id):
+ yield ArkSMActivitiesPage(
+ items=[activity for activity in page.items if session_activities_filter.command_contain in activity.command]
+ )
+
+ def count_session_activities_by(self, session_activities_filter: ArkSMSessionActivitiesFilter) -> int:
+ """
+ Count all session activities for session id by filter
+
+ Args:
+ session_activities_filter (ArkSMSessionActivitiesFilter): _description_
+
+ Returns:
+ int: _description_
+ """
+ count = 0
+ self._logger.info(f'Counting session activities by id [{session_activities_filter.session_id}] and filter')
+ for page in self.list_session_activities_by(session_activities_filter):
+ count += len(page.items)
+ return count
+
+ def sessions_stats(self) -> ArkSMSessionsStats:
+ """
+ Returns statistics about the sessions in the last 30 days
+
+ Returns:
+ ArkSMSessionsStats: _description_
+ """
+ self._logger.info('Calculating sessions stats for the last 30 days')
+ start_time_from = (datetime.now() - timedelta(days=30)).isoformat(timespec='seconds') + 'Z'
+ sessions = list(
+ itertools.chain.from_iterable(
+ [p.items for p in self.list_sessions_by(ArkSMSessionsFilter(search=f'startTime ge {start_time_from}'))]
+ )
+ )
+ sessions_stats = ArkSMSessionsStats.construct()
+ sessions_stats.sessions_count = len(sessions)
+ sessions_stats.sessions_failure_count = len([s for s in sessions if s.session_status == ArkSMSessionStatus.FAILED])
+
+ # Get sessions per application code
+ app_codes: Set[ArkApplicationCode] = {s.application_code for s in sessions}
+ sessions_stats.sessions_count_per_application_code = {
+ ac: len([s for s in sessions if s.application_code == ac]) for ac in app_codes
+ }
+
+ # Get sessions per platform
+ platforms: Set[ArkWorkspaceType] = {s.platform for s in sessions}
+ sessions_stats.sessions_count_per_platform = {p: len([s for s in sessions if s.platform == p]) for p in platforms}
+
+ # Get sessions per protocol
+ protocols: Set[ArkProtocolType] = {s.protocol for s in sessions}
+ sessions_stats.sessions_count_per_protocol = {p: len([s for s in sessions if s.protocol == p]) for p in protocols}
+
+ # Get sessions per status
+ statuses: Set[ArkSMSessionStatus] = {s.session_status for s in sessions}
+ sessions_stats.sessions_count_per_status = {st: len([s for s in sessions if s.session_status == st]) for st in statuses}
+
+ return sessions_stats
+
+ @staticmethod
+ @overrides
+ def service_config() -> ArkServiceConfig:
+ return SERVICE_CONFIG
diff --git a/docs/examples/commands_examples.md b/docs/examples/commands_examples.md
index 3b733d51..bc45faa4 100644
--- a/docs/examples/commands_examples.md
+++ b/docs/examples/commands_examples.md
@@ -109,3 +109,58 @@ ark exec dpa k8s generate-kubeconfig
```shell linenums="0"
ark exec dpa k8s generate-kubeconfig --folder=/Users/My.User/.kube
```
+
+### List All Session Monitoring sessions from the last 24 hours
+```shell
+ark exec sm list-sessions
+```
+
+### Count All Session Monitoring sessions from the last 24 hours
+```shell
+ark exec sm count-sessions
+```
+
+### List All Session Monitoring sessions matching Search Query
+```shell
+ark exec sm list-sessions-by --search 'startTime ge 2023-12-03T08:55:29Z AND sessionDuration GE 00:00:01 AND protocol IN SSH,RDP,Database'
+```
+
+### Count All Session Monitoring sessions matching Search Query
+```shell
+ark exec sm count-sessions-by --search 'startTime ge 2023-12-03T08:55:29Z AND sessionDuration GE 00:00:01 AND protocol IN SSH,RDP,Database'
+```
+
+### Count All Session Monitoring sessions from the last 24 hours
+```shell
+ark exec sm count-sessions
+```
+
+### Retrieve a session by id
+```shell
+ark exec sm session --session-id 5e62bdb8-cd81-42b8-ac72-1e06bf9c496d
+```
+
+### List all session activities
+```shell
+ark exec sm list-session-activities --session-id 5e62bdb8-cd81-42b8-ac72-1e06bf9c496d
+```
+
+### Count all session activities
+```shell
+ark exec sm count-session-activities --session-id 5e62bdb8-cd81-42b8-ac72-1e06bf9c496d
+```
+
+### List all session activities with specific command
+```shell
+ark exec sm list-session-activities-by --session-id 5e62bdb8-cd81-42b8-ac72-1e06bf9c496d --command-contains 'ls'
+```
+
+### Count all session activities with specific command
+```shell
+ark exec sm count-session-activities-by --session-id 5e62bdb8-cd81-42b8-ac72-1e06bf9c496d --command-contains 'ls'
+```
+
+### Display general sessions statistics from the last 30 days
+```shell
+ark exec sm sessions-stats
+```
diff --git a/docs/examples/sdk_examples.md b/docs/examples/sdk_examples.md
index 292b07ff..e8a05c77 100644
--- a/docs/examples/sdk_examples.md
+++ b/docs/examples/sdk_examples.md
@@ -141,4 +141,38 @@ if __name__ == '__main__':
],
)
)
-```
\ No newline at end of file
+```
+
+## View Session Monitoring Sessions And Activities Per Session
+
+```python
+ from ark_sdk_python.services.sm import ArkSMService
+ from ark_sdk_python.models.services.sm import ArkSMSessionsFilter, ArkSMGetSession, ArkSMGetSessionActivities
+ from ark_sdk_python.models.ark_profile import ArkProfileLoader
+ from ark_sdk_python.models.common import ArkProtocolType
+ from ark_sdk_python.auth import ArkISPAuth
+ from datetime import datetime, timedelta
+ if __name__ == "__main__":
+ isp_auth = ArkISPAuth()
+ isp_auth.authenticate(
+ profile=ArkProfileLoader().load_default_profile()
+ )
+ sm: ArkSMService = ArkSMService(isp_auth)
+ search_by = 'startTime ge {start_time_from} AND sessionDuration GE {min_duration} AND protocol IN {protocols}'
+ search_by = search_by.format(
+ start_time_from=(datetime.utcnow() - timedelta(days=30)).isoformat(timespec='seconds'),
+ min_duration='00:00:01',
+ protocols=','.join([ArkProtocolType.DB[0], ArkProtocolType.SSH[0], ArkProtocolType.RDP[0]]),
+ )
+ sessions_filter = ArkSMSessionsFilter(
+ search=search_by,
+ )
+ print(f'session_count = {sm.count_sessions_by(sessions_filter)}')
+ for s_page in sm.list_sessions_by(sessions_filter):
+ for session in s_page.items:
+ session = sm.session(ArkSMGetSession(session_id=session.session_id))
+ get_session_activities = ArkSMGetSessionActivities(session_id=session.session_id)
+ print(f'session = {session}, activities_count = {sm.count_session_activities(get_session_activities)}')
+ session_activities = [activity for page in sm.list_session_activities(get_session_activities) for activity in page.items]
+ print(session_activities)
+```
diff --git a/docs/sdk/services.md b/docs/sdk/services.md
index df5ed852..6c277781 100644
--- a/docs/sdk/services.md
+++ b/docs/sdk/services.md
@@ -51,3 +51,9 @@ The Dynamic Privilege Access (DPA) service requires the ArkISPAuth authenticator
- ArkDPADBSecretsService (db) - DPA DB secrets services
- ArkDPAWorkspacesService (workspaces) - DPA workspaces management
- ArkDPADBWorkspaceService (db) - DPA DB workspace management
+
+
+## Session monitoring service
+
+The Session Monitoring (SM) service requires ArkISPAuth authenticator, and exposes these service classes:
+- ArkSMService (sm) - Session Monitoring Service
diff --git a/scripts/wheel_editor.sh b/scripts/wheel_editor.sh
new file mode 100755
index 00000000..4ae12a02
--- /dev/null
+++ b/scripts/wheel_editor.sh
@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+set -e
+
+ORIGINAL_PACKAGE=$1
+START_DIR=$(pwd)
+TEMP_DIR=$(mktemp -d)
+WORKING_WHEEL=$TEMP_DIR/package.whl
+FINAL_PACKAGE=$(echo $ORIGINAL_PACKAGE | rev | cut -d"-" -f4- | rev)-py3-none-any.whl
+
+if [[ ! -f "$ORIGINAL_PACKAGE" ]]; then
+ echo "File not found at path $1"
+ exit 1
+fi
+
+cp $ORIGINAL_PACKAGE $WORKING_WHEEL
+
+cd $TEMP_DIR
+
+mkdir unzipped
+unzip $WORKING_WHEEL -d unzipped
+
+WHEEL_FILE=$(find unzipped -name "WHEEL")
+
+grep -v '^Root-Is-Purelib' $WHEEL_FILE > $WHEEL_FILE.tmp
+grep -v '^Tag' $WHEEL_FILE.tmp > $WHEEL_FILE
+rm $WHEEL_FILE.tmp
+
+echo "Root-Is-Purelib: true" >> $WHEEL_FILE
+echo "Tag: py3-none-any" >> $WHEEL_FILE
+
+cat $WHEEL_FILE
+
+cd unzipped
+zip -r ../universal_package.whl *
+cd ..
+
+cd $START_DIR
+
+cp $TEMP_DIR/universal_package.whl $FINAL_PACKAGE
+rm $ORIGINAL_PACKAGE