xsoar-client 1.0.1__py3-none-any.whl → 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xsoar_client/__about__.py +1 -1
- xsoar_client/artifact_provider.py +6 -58
- xsoar_client/artifact_providers/__init__.py +11 -0
- xsoar_client/artifact_providers/azure.py +58 -0
- xsoar_client/artifact_providers/base.py +31 -0
- xsoar_client/artifact_providers/s3.py +56 -0
- xsoar_client/config.py +33 -0
- xsoar_client/xsoar_client.py +33 -71
- xsoar_client-2.0.0.dist-info/METADATA +85 -0
- xsoar_client-2.0.0.dist-info/RECORD +13 -0
- {xsoar_client-1.0.1.dist-info → xsoar_client-2.0.0.dist-info}/WHEEL +1 -1
- xsoar_client-1.0.1.dist-info/METADATA +0 -49
- xsoar_client-1.0.1.dist-info/RECORD +0 -8
- {xsoar_client-1.0.1.dist-info → xsoar_client-2.0.0.dist-info}/licenses/LICENSE.txt +0 -0
xsoar_client/__about__.py
CHANGED
|
@@ -1,61 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
from packaging import version
|
|
3
|
+
from .artifact_providers import AzureArtifactProvider, BaseArtifactProvider, S3ArtifactProvider
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if location is None:
|
|
12
|
-
return
|
|
13
|
-
if location not in SUPPORTED_STORES:
|
|
14
|
-
msg = f"Artifact store {location} is not yet implemented."
|
|
15
|
-
raise NotImplementedError(msg)
|
|
16
|
-
self.artifacts_repo = location
|
|
17
|
-
self.s3_bucket_name = s3_bucket_name
|
|
18
|
-
self.verify_ssl = verify_ssl
|
|
19
|
-
self.boto3_session = boto3.session.Session() # pyright: ignore # noqa: PGH003
|
|
20
|
-
self.s3 = self.boto3_session.resource("s3")
|
|
21
|
-
|
|
22
|
-
def _is_available_s3(self, *, pack_id: str, pack_version: str) -> bool:
|
|
23
|
-
"""Returns True if pack is available, False otherwise"""
|
|
24
|
-
key_name = f"content/packs/{pack_id}/{pack_version}/{pack_id}.zip"
|
|
25
|
-
try:
|
|
26
|
-
self.s3.Object(self.s3_bucket_name, key_name).load()
|
|
27
|
-
except Exception: # noqa: BLE001
|
|
28
|
-
return False
|
|
29
|
-
return True
|
|
30
|
-
|
|
31
|
-
def is_available(self, *, pack_id: str, pack_version: str) -> bool:
|
|
32
|
-
if self.artifacts_repo == "S3":
|
|
33
|
-
return self._is_available_s3(pack_id=pack_id, pack_version=pack_version)
|
|
34
|
-
raise NotImplementedError
|
|
35
|
-
|
|
36
|
-
def _download_s3(self, *, pack_id: str, pack_version: str) -> bytes:
|
|
37
|
-
"""Downloads a custom content pack from AWS S3."""
|
|
38
|
-
key_name = f"content/packs/{pack_id}/{pack_version}/{pack_id}.zip"
|
|
39
|
-
obj = self.s3.Object(bucket_name=self.s3_bucket_name, key=key_name)
|
|
40
|
-
response = obj.get()
|
|
41
|
-
return response["Body"].read()
|
|
42
|
-
|
|
43
|
-
def download(self, *, pack_id: str, pack_version: str) -> bytes:
|
|
44
|
-
if self.artifacts_repo == "S3":
|
|
45
|
-
return self._download_s3(pack_id=pack_id, pack_version=pack_version)
|
|
46
|
-
raise NotImplementedError
|
|
47
|
-
|
|
48
|
-
def _get_latest_version_s3(self, pack_id: str) -> str:
|
|
49
|
-
b3_client = boto3.client("s3", verify=self.verify_ssl)
|
|
50
|
-
result = b3_client.list_objects_v2(
|
|
51
|
-
Bucket=self.s3_bucket_name,
|
|
52
|
-
Prefix=f"content/packs/{pack_id}/",
|
|
53
|
-
Delimiter="/",
|
|
54
|
-
)
|
|
55
|
-
version_list = [x["Prefix"].split("/")[3] for x in result.get("CommonPrefixes")]
|
|
56
|
-
return str(max(version_list, key=version.parse))
|
|
57
|
-
|
|
58
|
-
def get_latest_version(self, pack_id: str) -> str:
|
|
59
|
-
if self.artifacts_repo == "S3":
|
|
60
|
-
return self._get_latest_version_s3(pack_id=pack_id)
|
|
61
|
-
raise NotImplementedError
|
|
5
|
+
__all__ = [
|
|
6
|
+
"BaseArtifactProvider",
|
|
7
|
+
"S3ArtifactProvider",
|
|
8
|
+
"AzureArtifactProvider",
|
|
9
|
+
]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from azure.core.exceptions import ResourceNotFoundError
|
|
6
|
+
from azure.storage.blob import BlobServiceClient
|
|
7
|
+
from packaging import version
|
|
8
|
+
|
|
9
|
+
from .base import BaseArtifactProvider
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AzureArtifactProvider(BaseArtifactProvider):
|
|
13
|
+
"""Azure Blob Storage artifact provider."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, *, storage_account_url: str, container_name: str) -> None:
|
|
16
|
+
self.storage_account_url = storage_account_url
|
|
17
|
+
self.container_name = container_name
|
|
18
|
+
self._service = None
|
|
19
|
+
self._container_client = None
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def service(self) -> BlobServiceClient:
|
|
23
|
+
if self._service is None:
|
|
24
|
+
credential = os.environ.get("SAS_TOKEN")
|
|
25
|
+
if not credential:
|
|
26
|
+
raise RuntimeError("Required environment variable SAS_TOKEN is not set.")
|
|
27
|
+
self._service = BlobServiceClient(account_url=self.storage_account_url, credential=credential)
|
|
28
|
+
return self._service
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def container_client(self):
|
|
32
|
+
if self._container_client is None:
|
|
33
|
+
self._container_client = self.service.get_container_client(self.container_name)
|
|
34
|
+
return self._container_client
|
|
35
|
+
|
|
36
|
+
def test_connection(self) -> bool:
|
|
37
|
+
self.container_client.get_container_properties()
|
|
38
|
+
return True
|
|
39
|
+
|
|
40
|
+
def is_available(self, *, pack_id: str, pack_version: str) -> bool:
|
|
41
|
+
key_name = self.get_pack_path(pack_id, pack_version)
|
|
42
|
+
blob_client = self.container_client.get_blob_client(blob=key_name)
|
|
43
|
+
try:
|
|
44
|
+
blob_client.get_blob_properties()
|
|
45
|
+
return True
|
|
46
|
+
except ResourceNotFoundError:
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
def download(self, *, pack_id: str, pack_version: str) -> bytes:
|
|
50
|
+
key_name = self.get_pack_path(pack_id, pack_version)
|
|
51
|
+
download_stream = self.container_client.download_blob(blob=key_name)
|
|
52
|
+
return download_stream.readall()
|
|
53
|
+
|
|
54
|
+
def get_latest_version(self, pack_id: str) -> str:
|
|
55
|
+
prefix = f"content/packs/{pack_id}/"
|
|
56
|
+
iter_names = self.container_client.list_blob_names(name_starts_with=prefix)
|
|
57
|
+
version_list = [x.split("/")[3] for x in list(iter_names)]
|
|
58
|
+
return str(max(version_list, key=version.parse))
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BaseArtifactProvider(ABC):
|
|
7
|
+
"""Abstract base class for artifact storage providers."""
|
|
8
|
+
|
|
9
|
+
@abstractmethod
|
|
10
|
+
def test_connection(self) -> bool:
|
|
11
|
+
"""Test connection to the artifact storage."""
|
|
12
|
+
...
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def is_available(self, *, pack_id: str, pack_version: str) -> bool:
|
|
16
|
+
"""Check if a specific pack version is available."""
|
|
17
|
+
...
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def download(self, *, pack_id: str, pack_version: str) -> bytes:
|
|
21
|
+
"""Download a pack from the artifact storage."""
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def get_latest_version(self, pack_id: str) -> str:
|
|
26
|
+
"""Get the latest version of a pack."""
|
|
27
|
+
...
|
|
28
|
+
|
|
29
|
+
def get_pack_path(self, pack_id: str, pack_version: str) -> str:
|
|
30
|
+
"""Generate the standard path for a pack artifact."""
|
|
31
|
+
return f"content/packs/{pack_id}/{pack_version}/{pack_id}.zip"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import boto3
|
|
4
|
+
from botocore.exceptions import EndpointConnectionError, NoCredentialsError, PartialCredentialsError
|
|
5
|
+
from botocore.httpsession import ConnectTimeoutError
|
|
6
|
+
from packaging import version
|
|
7
|
+
|
|
8
|
+
from .base import BaseArtifactProvider
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class S3ArtifactProvider(BaseArtifactProvider):
|
|
12
|
+
"""AWS S3 artifact storage provider."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, *, bucket_name: str, verify_ssl: str | bool = True) -> None:
|
|
15
|
+
self.bucket_name = bucket_name
|
|
16
|
+
self.verify_ssl = verify_ssl
|
|
17
|
+
self.session = boto3.session.Session()
|
|
18
|
+
self.s3 = self.session.resource("s3")
|
|
19
|
+
|
|
20
|
+
def test_connection(self) -> bool:
|
|
21
|
+
try:
|
|
22
|
+
bucket = self.s3.Bucket(self.bucket_name)
|
|
23
|
+
bucket.load()
|
|
24
|
+
except NoCredentialsError as ex:
|
|
25
|
+
raise RuntimeError("AWS credentials not found.") from ex
|
|
26
|
+
except PartialCredentialsError as ex:
|
|
27
|
+
raise RuntimeError("Incomplete AWS credentials.") from ex
|
|
28
|
+
except EndpointConnectionError as ex:
|
|
29
|
+
raise RuntimeError("Could not connect to the S3") from ex
|
|
30
|
+
except ConnectTimeoutError as ex:
|
|
31
|
+
raise RuntimeError("Connection timed out") from ex
|
|
32
|
+
return True
|
|
33
|
+
|
|
34
|
+
def is_available(self, *, pack_id: str, pack_version: str) -> bool:
|
|
35
|
+
key_name = self.get_pack_path(pack_id, pack_version)
|
|
36
|
+
try:
|
|
37
|
+
self.s3.Object(self.bucket_name, key_name).load()
|
|
38
|
+
return True
|
|
39
|
+
except Exception:
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
def download(self, *, pack_id: str, pack_version: str) -> bytes:
|
|
43
|
+
key_name = self.get_pack_path(pack_id, pack_version)
|
|
44
|
+
obj = self.s3.Object(bucket_name=self.bucket_name, key=key_name)
|
|
45
|
+
response = obj.get()
|
|
46
|
+
return response["Body"].read()
|
|
47
|
+
|
|
48
|
+
def get_latest_version(self, pack_id: str) -> str:
|
|
49
|
+
client = boto3.client("s3", verify=self.verify_ssl)
|
|
50
|
+
result = client.list_objects_v2(
|
|
51
|
+
Bucket=self.bucket_name,
|
|
52
|
+
Prefix=f"content/packs/{pack_id}/",
|
|
53
|
+
Delimiter="/",
|
|
54
|
+
)
|
|
55
|
+
version_list = [x["Prefix"].split("/")[3] for x in result.get("CommonPrefixes", [])]
|
|
56
|
+
return str(max(version_list, key=version.parse))
|
xsoar_client/config.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class ClientConfig:
|
|
9
|
+
"""Configuration for XSOAR Client."""
|
|
10
|
+
|
|
11
|
+
server_version: int
|
|
12
|
+
custom_pack_authors: list[str] = field(default_factory=list)
|
|
13
|
+
api_token: str = ""
|
|
14
|
+
server_url: str = ""
|
|
15
|
+
xsiam_auth_id: str = ""
|
|
16
|
+
verify_ssl: bool | str = False
|
|
17
|
+
|
|
18
|
+
def __post_init__(self) -> None:
|
|
19
|
+
"""Load credentials from environment if not provided."""
|
|
20
|
+
if not self.api_token or not self.server_url:
|
|
21
|
+
self._load_from_env()
|
|
22
|
+
self._validate()
|
|
23
|
+
|
|
24
|
+
def _load_from_env(self) -> None:
|
|
25
|
+
"""Load credentials from environment variables."""
|
|
26
|
+
self.api_token = self.api_token or os.getenv("DEMISTO_API_KEY", "")
|
|
27
|
+
self.server_url = self.server_url or os.getenv("DEMISTO_BASE_URL", "")
|
|
28
|
+
self.xsiam_auth_id = self.xsiam_auth_id or os.getenv("XSIAM_AUTH_ID", "")
|
|
29
|
+
|
|
30
|
+
def _validate(self) -> None:
|
|
31
|
+
"""Validate configuration."""
|
|
32
|
+
if not self.api_token or not self.server_url:
|
|
33
|
+
raise ValueError("Both api_token and server_url are required")
|
xsoar_client/xsoar_client.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import os
|
|
4
3
|
import tarfile
|
|
5
4
|
import tempfile
|
|
6
5
|
from io import BytesIO, StringIO
|
|
@@ -12,7 +11,8 @@ import requests
|
|
|
12
11
|
from demisto_client.demisto_api.rest import ApiException
|
|
13
12
|
from packaging import version
|
|
14
13
|
|
|
15
|
-
from .
|
|
14
|
+
from .artifact_providers import BaseArtifactProvider
|
|
15
|
+
from .config import ClientConfig
|
|
16
16
|
|
|
17
17
|
if TYPE_CHECKING:
|
|
18
18
|
from requests.models import Response
|
|
@@ -29,69 +29,20 @@ class Client:
|
|
|
29
29
|
def __init__(
|
|
30
30
|
self,
|
|
31
31
|
*,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
server_url: str = "",
|
|
35
|
-
xsiam_auth_id: str = "",
|
|
36
|
-
custom_pack_authors: list[str],
|
|
37
|
-
server_version: int,
|
|
38
|
-
artifacts_location: str = "S3",
|
|
39
|
-
s3_bucket_name: str = "",
|
|
32
|
+
config: ClientConfig,
|
|
33
|
+
artifact_provider: BaseArtifactProvider | None = None,
|
|
40
34
|
) -> None:
|
|
41
|
-
self.
|
|
42
|
-
self.
|
|
43
|
-
self.xsiam_auth_id = None
|
|
44
|
-
self.server_version = server_version
|
|
35
|
+
self.config = config
|
|
36
|
+
self.artifact_provider = artifact_provider
|
|
45
37
|
self.installed_packs = None
|
|
46
|
-
self.custom_pack_authors = custom_pack_authors
|
|
47
38
|
self.installed_expired = None
|
|
48
|
-
self._set_credentials(api_token, server_url, xsiam_auth_id)
|
|
49
39
|
self.http_timeout = HTTP_CALL_TIMEOUT
|
|
50
|
-
self.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
self.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
auth_id=self.xsiam_auth_id,
|
|
57
|
-
verify_ssl=self.verify_ssl,
|
|
58
|
-
)
|
|
59
|
-
else:
|
|
60
|
-
self.demisto_py_instance = demisto_client.configure(
|
|
61
|
-
base_url=self.server_url,
|
|
62
|
-
api_key=self.api_token,
|
|
63
|
-
verify_ssl=self.verify_ssl,
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
def _set_credentials(
|
|
67
|
-
self,
|
|
68
|
-
api_token: str,
|
|
69
|
-
server_url: str,
|
|
70
|
-
xsiam_auth_id: str,
|
|
71
|
-
) -> None:
|
|
72
|
-
if api_token and server_url:
|
|
73
|
-
self.api_token = api_token
|
|
74
|
-
self.server_url = server_url
|
|
75
|
-
self.xsiam_auth_id = str(xsiam_auth_id)
|
|
76
|
-
|
|
77
|
-
"""
|
|
78
|
-
This should be moved somewhere else
|
|
79
|
-
# We only use xsiam_auth_id for XSOAR 8. Assuming XSOAR 8 server if this variable contains any value
|
|
80
|
-
if int(self.server_version) > XSOAR_OLD_VERSION:
|
|
81
|
-
self.server_url = f"{server_url}/xsoar/public/v1"
|
|
82
|
-
"""
|
|
83
|
-
|
|
84
|
-
return
|
|
85
|
-
if (api_token and not server_url) or (server_url and not api_token):
|
|
86
|
-
msg = "If api_token is specified in constructor, then server_url must also be specified (or vice versa)."
|
|
87
|
-
raise RuntimeError(msg)
|
|
88
|
-
try:
|
|
89
|
-
self.api_token = os.environ["DEMISTO_API_KEY"]
|
|
90
|
-
self.server_url = os.environ["DEMISTO_BASE_URL"]
|
|
91
|
-
self.xsiam_auth_id = os.environ.get("XSIAM_AUTH_ID", None)
|
|
92
|
-
except KeyError as ex:
|
|
93
|
-
msg = "Cannot find all required environment varaibles. Please refer to the docs for required environment variables."
|
|
94
|
-
raise ValueError(msg) from ex
|
|
40
|
+
self.demisto_py_instance = demisto_client.configure(
|
|
41
|
+
base_url=self.config.server_url,
|
|
42
|
+
api_key=self.config.api_token,
|
|
43
|
+
auth_id=self.config.xsiam_auth_id,
|
|
44
|
+
verify_ssl=self.config.verify_ssl,
|
|
45
|
+
)
|
|
95
46
|
|
|
96
47
|
def _make_request(
|
|
97
48
|
self,
|
|
@@ -103,14 +54,14 @@ class Client:
|
|
|
103
54
|
data: dict | None = None,
|
|
104
55
|
) -> Response:
|
|
105
56
|
"""Wrapper for Requests. Sets the appropriate headers and authentication token."""
|
|
106
|
-
url = f"{self.server_url}{endpoint}"
|
|
57
|
+
url = f"{self.config.server_url}{endpoint}"
|
|
107
58
|
headers = {
|
|
108
59
|
"Accept": "application/json",
|
|
109
|
-
"Authorization": self.api_token,
|
|
60
|
+
"Authorization": self.config.api_token,
|
|
110
61
|
"Content-Type": "application/json",
|
|
111
62
|
}
|
|
112
|
-
if self.xsiam_auth_id:
|
|
113
|
-
headers["x-xdr-auth-id"] = self.xsiam_auth_id
|
|
63
|
+
if self.config.xsiam_auth_id:
|
|
64
|
+
headers["x-xdr-auth-id"] = self.config.xsiam_auth_id
|
|
114
65
|
return requests.request(
|
|
115
66
|
method=method,
|
|
116
67
|
url=url,
|
|
@@ -118,7 +69,7 @@ class Client:
|
|
|
118
69
|
json=json,
|
|
119
70
|
files=files,
|
|
120
71
|
data=data,
|
|
121
|
-
verify=self.verify_ssl,
|
|
72
|
+
verify=self.config.verify_ssl,
|
|
122
73
|
timeout=self.http_timeout,
|
|
123
74
|
)
|
|
124
75
|
|
|
@@ -150,6 +101,8 @@ class Client:
|
|
|
150
101
|
|
|
151
102
|
def is_pack_available(self, *, pack_id: str, version: str, custom: bool) -> bool:
|
|
152
103
|
if custom:
|
|
104
|
+
if not self.artifact_provider:
|
|
105
|
+
raise RuntimeError("No artifact provider configured")
|
|
153
106
|
return self.artifact_provider.is_available(pack_id=pack_id, pack_version=version)
|
|
154
107
|
baseurl = "https://marketplace.xsoar.paloaltonetworks.com/content/packs"
|
|
155
108
|
path = f"/{pack_id}/{version}/{pack_id}.zip"
|
|
@@ -178,7 +131,7 @@ class Client:
|
|
|
178
131
|
response.raise_for_status()
|
|
179
132
|
|
|
180
133
|
def test_connectivity(self) -> bool:
|
|
181
|
-
if self.server_version > XSOAR_OLD_VERSION: # noqa: SIM108
|
|
134
|
+
if self.config.server_version > XSOAR_OLD_VERSION: # noqa: SIM108
|
|
182
135
|
endpoint = "/xsoar/health"
|
|
183
136
|
else:
|
|
184
137
|
endpoint = "/health"
|
|
@@ -192,7 +145,7 @@ class Client:
|
|
|
192
145
|
|
|
193
146
|
def get_installed_packs(self) -> list[dict]:
|
|
194
147
|
"""Fetches a JSON blob containing a complete list of installed packages."""
|
|
195
|
-
if self.server_version > XSOAR_OLD_VERSION:
|
|
148
|
+
if self.config.server_version > XSOAR_OLD_VERSION:
|
|
196
149
|
endpoint = "/xsoar/public/v1/contentpacks/metadata/installed"
|
|
197
150
|
else:
|
|
198
151
|
endpoint = "/contentpacks/metadata/installed"
|
|
@@ -208,7 +161,7 @@ class Client:
|
|
|
208
161
|
|
|
209
162
|
def get_installed_expired_packs(self) -> list[dict]:
|
|
210
163
|
"""Fetches a JSON blob containing a complete list of installed expired packages."""
|
|
211
|
-
if self.server_version > XSOAR_OLD_VERSION: # noqa: SIM108
|
|
164
|
+
if self.config.server_version > XSOAR_OLD_VERSION: # noqa: SIM108
|
|
212
165
|
endpoint = "/xsoar/contentpacks/installed-expired"
|
|
213
166
|
else:
|
|
214
167
|
endpoint = "/contentpacks/installed-expired"
|
|
@@ -234,7 +187,7 @@ class Client:
|
|
|
234
187
|
return response.json()
|
|
235
188
|
|
|
236
189
|
def create_case(self, data: dict) -> dict:
|
|
237
|
-
if self.server_version > XSOAR_OLD_VERSION: # noqa: SIM108
|
|
190
|
+
if self.config.server_version > XSOAR_OLD_VERSION: # noqa: SIM108
|
|
238
191
|
endpoint = "/xsoar/public/v1/incident"
|
|
239
192
|
else:
|
|
240
193
|
endpoint = "/incident"
|
|
@@ -250,6 +203,8 @@ class Client:
|
|
|
250
203
|
|
|
251
204
|
def download_pack(self, pack_id: str, pack_version: str, custom: bool) -> bytes: # noqa: FBT001
|
|
252
205
|
if custom:
|
|
206
|
+
if not self.artifact_provider:
|
|
207
|
+
raise RuntimeError("No artifact provider configured")
|
|
253
208
|
return self.artifact_provider.download(pack_id=pack_id, pack_version=pack_version)
|
|
254
209
|
"""Downloads a upstream content pack from the official XSOAR marketplace."""
|
|
255
210
|
baseurl = "https://marketplace.xsoar.paloaltonetworks.com/content/packs"
|
|
@@ -296,7 +251,9 @@ class Client:
|
|
|
296
251
|
expired_packs = self.get_installed_expired_packs()
|
|
297
252
|
update_available = []
|
|
298
253
|
for pack in expired_packs:
|
|
299
|
-
if pack["author"] in self.custom_pack_authors:
|
|
254
|
+
if pack["author"] in self.config.custom_pack_authors:
|
|
255
|
+
if not self.artifact_provider:
|
|
256
|
+
raise RuntimeError("No artifact provider configured")
|
|
300
257
|
latest_version = self.artifact_provider.get_latest_version(pack["id"])
|
|
301
258
|
if latest_version == pack["currentVersion"]:
|
|
302
259
|
continue
|
|
@@ -319,3 +276,8 @@ class Client:
|
|
|
319
276
|
update_available.append(tmpobj)
|
|
320
277
|
|
|
321
278
|
return update_available
|
|
279
|
+
|
|
280
|
+
def get_latest_custom_pack_version(self, pack_id: str) -> str:
|
|
281
|
+
if not self.artifact_provider:
|
|
282
|
+
raise RuntimeError("No artifact provider configured")
|
|
283
|
+
return self.artifact_provider.get_latest_version(pack_id)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xsoar-client
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Project-URL: Documentation, https://github.com/tlium/xsoar-client#readme
|
|
5
|
+
Project-URL: Issues, https://github.com/tlium/xsoar-client/issues
|
|
6
|
+
Project-URL: Source, https://github.com/tlium/xsoar-client
|
|
7
|
+
Author-email: Torbjørn Lium <torben@lium.org>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE.txt
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: azure-identity>=1.25.1
|
|
19
|
+
Requires-Dist: azure-storage-blob>=12.28.0
|
|
20
|
+
Requires-Dist: boto3>=1.36.17
|
|
21
|
+
Requires-Dist: demisto-py>=3.2.18
|
|
22
|
+
Requires-Dist: requests>=2.8.0
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# xsoar-client
|
|
26
|
+
|
|
27
|
+
Python client library for Palo Alto XSOAR (formerly Demisto). Provides programmatic access to XSOAR servers for content pack management, case operations, and artifact handling.
|
|
28
|
+
|
|
29
|
+
This library is the foundation for [xsoar-cli](https://github.com/tlium/xsoar-cli). Use `xsoar-client` directly when building custom integrations or automation scripts. For command-line usage, use `xsoar-cli` instead.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install xsoar-client
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Requirements
|
|
38
|
+
|
|
39
|
+
- Python 3.10 or higher
|
|
40
|
+
- XSOAR/XSIAM server (version 6 or 8)
|
|
41
|
+
- Valid API credentials
|
|
42
|
+
|
|
43
|
+
## Configuration
|
|
44
|
+
|
|
45
|
+
### Environment Variables
|
|
46
|
+
|
|
47
|
+
Authentication credentials are read from environment variables:
|
|
48
|
+
|
|
49
|
+
- `DEMISTO_API_KEY` - XSOAR API key
|
|
50
|
+
- `DEMISTO_BASE_URL` - XSOAR server URL (e.g., `https://xsoar.example.com`)
|
|
51
|
+
- `XSIAM_AUTH_ID` - XSIAM authentication ID (XSOAR 8 only)
|
|
52
|
+
|
|
53
|
+
### ClientConfig Parameters
|
|
54
|
+
|
|
55
|
+
- `server_version` (required) - XSOAR server version (6 or 8)
|
|
56
|
+
- `custom_pack_authors` - List of custom pack authors (default: `[]`)
|
|
57
|
+
- `api_token` - API token (default: from `DEMISTO_API_KEY` environment variable)
|
|
58
|
+
- `server_url` - Server URL (default: from `DEMISTO_BASE_URL` environment variable)
|
|
59
|
+
- `xsiam_auth_id` - XSIAM auth ID (default: from `XSIAM_AUTH_ID` environment variable)
|
|
60
|
+
- `verify_ssl` - SSL verification, boolean or path to CA bundle (default: `False`)
|
|
61
|
+
|
|
62
|
+
## Artifact Providers
|
|
63
|
+
|
|
64
|
+
Artifact providers handle storage and retrieval of custom content packs from cloud storage.
|
|
65
|
+
|
|
66
|
+
### AWS S3
|
|
67
|
+
|
|
68
|
+
Requires AWS credentials configured via AWS CLI or standard AWS environment variables.
|
|
69
|
+
|
|
70
|
+
### Azure Blob Storage
|
|
71
|
+
|
|
72
|
+
Requires `AZURE_STORAGE_SAS_TOKEN` environment variable.
|
|
73
|
+
|
|
74
|
+
## Use Cases
|
|
75
|
+
|
|
76
|
+
- Custom automation scripts for XSOAR operations
|
|
77
|
+
- CI/CD pipeline integration for content deployment
|
|
78
|
+
- Bulk operations across multiple XSOAR instances
|
|
79
|
+
- Custom tooling built on top of XSOAR APIs
|
|
80
|
+
|
|
81
|
+
For CLI-based workflows and usage examples, see [xsoar-cli](https://github.com/tlium/xsoar-cli).
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
|
|
85
|
+
Distributed under the MIT license.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
xsoar_client/__about__.py,sha256=MMU9leoFnuxdjDJo0iyuTliKJ6UnIKiVkTnE-FMzKVc,132
|
|
2
|
+
xsoar_client/__init__.py,sha256=RjMY_4AMoQYO9urL0tOVXgAtc4nFUO-z0po9DDBuR5M,105
|
|
3
|
+
xsoar_client/artifact_provider.py,sha256=TruJaMLR09elO3b3_TnYPwXV6mRgqjwrQ32oww69D4I,230
|
|
4
|
+
xsoar_client/config.py,sha256=LmDIqJhUuKCoizk4C6A6xe6RNeBoxkyomGFY2XKEIFA,1116
|
|
5
|
+
xsoar_client/xsoar_client.py,sha256=hmS6xYUFbCJtKyqETRRhM_SgLTV2CPTDb8ax6IDFdE0,11161
|
|
6
|
+
xsoar_client/artifact_providers/__init__.py,sha256=qhi_L2XNi8Hf1ne1ElIaq55amcOzyKn-XUvMUH8r2do,249
|
|
7
|
+
xsoar_client/artifact_providers/azure.py,sha256=ETJVeUuVMx4nKXMuyVfkcFoaDV-Fw5xCffV_Yx5_7hs,2226
|
|
8
|
+
xsoar_client/artifact_providers/base.py,sha256=H7XuINSRUyN9pGklCBAG4Kb1CZb9ENmXlsyN7Hz2vtw,960
|
|
9
|
+
xsoar_client/artifact_providers/s3.py,sha256=RvOceUautk3Gkxl3LzggWRa7HeXQCcXkZEL_FbQ8ACA,2206
|
|
10
|
+
xsoar_client-2.0.0.dist-info/METADATA,sha256=ZBVC3GEFXwicDCvgLriKZOJPHxYjH88rRHKOey2_OaA,2935
|
|
11
|
+
xsoar_client-2.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
12
|
+
xsoar_client-2.0.0.dist-info/licenses/LICENSE.txt,sha256=l6xnqWKshqwwTXt6ayO6MX8Uvygq0YnkUuFTNnR3ba4,1097
|
|
13
|
+
xsoar_client-2.0.0.dist-info/RECORD,,
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: xsoar-client
|
|
3
|
-
Version: 1.0.1
|
|
4
|
-
Project-URL: Documentation, https://github.com/tlium/xsoar-client#readme
|
|
5
|
-
Project-URL: Issues, https://github.com/tlium/xsoar-client/issues
|
|
6
|
-
Project-URL: Source, https://github.com/tlium/xsoar-client
|
|
7
|
-
Author-email: Torbjørn Lium <torben@lium.org>
|
|
8
|
-
License-Expression: MIT
|
|
9
|
-
License-File: LICENSE.txt
|
|
10
|
-
Classifier: Development Status :: 4 - Beta
|
|
11
|
-
Classifier: Programming Language :: Python
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
-
Requires-Python: >=3.10
|
|
18
|
-
Requires-Dist: boto3>=1.36.17
|
|
19
|
-
Requires-Dist: demisto-py>=3.2.18
|
|
20
|
-
Requires-Dist: requests>=2.8.0
|
|
21
|
-
Description-Content-Type: text/markdown
|
|
22
|
-
|
|
23
|
-
# xsoar-client
|
|
24
|
-
-----
|
|
25
|
-
|
|
26
|
-
## Table of Contents
|
|
27
|
-
|
|
28
|
-
- [Installation](#installation)
|
|
29
|
-
- [Configuration](#configuration)
|
|
30
|
-
- [License](#license)
|
|
31
|
-
|
|
32
|
-
## Installation
|
|
33
|
-
1. Install with pip:
|
|
34
|
-
```
|
|
35
|
-
pip install xsoar-client
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
## Configuration
|
|
39
|
-
For the XSOAR client to work properly you need to have a few environment variables set:
|
|
40
|
-
- DEMISTO_API_KEY - API key for XSOAR
|
|
41
|
-
- DEMISTO_BASE_URL - URL to XSOAR
|
|
42
|
-
- AWS_PROJECT - must contain the name of the AWS profile configured below
|
|
43
|
-
|
|
44
|
-
This application requires that you store your deployment artifacts (XSOAR content packs) in an artifact repository somewhere. Currently only AWS S3 is supported.
|
|
45
|
-
You also need to be logged in to the proper AWS project.
|
|
46
|
-
Install `awscli` for your platform and make sure you are properly authenticated.
|
|
47
|
-
|
|
48
|
-
## License
|
|
49
|
-
`xsoar-client` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
xsoar_client/__about__.py,sha256=yJHUtXXvuYYywTx7dLwV7Vtpi7GB6PAbprYsi3AzKBQ,132
|
|
2
|
-
xsoar_client/__init__.py,sha256=RjMY_4AMoQYO9urL0tOVXgAtc4nFUO-z0po9DDBuR5M,105
|
|
3
|
-
xsoar_client/artifact_provider.py,sha256=nLbEotUaf9t7ezPti_3apNVsT8M_0ckZ_HwC76aP96M,2546
|
|
4
|
-
xsoar_client/xsoar_client.py,sha256=f8HTnxNTg5ULO0CWzF7pfkdQ70B_r6KHEs0O2BPVmNU,12536
|
|
5
|
-
xsoar_client-1.0.1.dist-info/METADATA,sha256=iHpY5avMD4DfUv_QfcbM4ixcd6lXLrRXHFu7OL-QU_E,1722
|
|
6
|
-
xsoar_client-1.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
-
xsoar_client-1.0.1.dist-info/licenses/LICENSE.txt,sha256=l6xnqWKshqwwTXt6ayO6MX8Uvygq0YnkUuFTNnR3ba4,1097
|
|
8
|
-
xsoar_client-1.0.1.dist-info/RECORD,,
|
|
File without changes
|