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