pangea-sdk 1.3.0__tar.gz → 1.5.0__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/PKG-INFO +4 -3
  2. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/README.md +2 -2
  3. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/pangea/__init__.py +1 -1
  4. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/pangea/deep_verify.py +2 -2
  5. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/pangea/dump_audit.py +3 -9
  6. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/pangea/exceptions.py +40 -2
  7. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/pangea/request.py +40 -7
  8. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/pangea/response.py +8 -0
  9. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/pangea/services/__init__.py +1 -0
  10. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/pangea/services/audit/audit.py +28 -7
  11. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/pangea/services/audit/models.py +12 -0
  12. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/pangea/services/audit/util.py +6 -1
  13. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/pangea/services/intel.py +270 -82
  14. pangea_sdk-1.5.0/pangea/services/vault/models/asymmetric.py +67 -0
  15. pangea_sdk-1.5.0/pangea/services/vault/models/common.py +337 -0
  16. pangea_sdk-1.5.0/pangea/services/vault/models/secret.py +24 -0
  17. pangea_sdk-1.5.0/pangea/services/vault/models/symmetric.py +61 -0
  18. pangea_sdk-1.5.0/pangea/services/vault/vault.py +458 -0
  19. pangea_sdk-1.3.0/pangea/tools_util.py → pangea_sdk-1.5.0/pangea/tools.py +7 -9
  20. pangea_sdk-1.5.0/pangea/utils.py +22 -0
  21. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/pyproject.toml +2 -1
  22. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/pangea/audit_logger.py +0 -0
  23. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/pangea/config.py +0 -0
  24. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/pangea/deprecated.py +0 -0
  25. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/pangea/services/audit/exceptions.py +0 -0
  26. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/pangea/services/audit/signing.py +0 -0
  27. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/pangea/services/base.py +0 -0
  28. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/pangea/services/embargo.py +0 -0
  29. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/pangea/services/redact.py +0 -0
  30. {pangea_sdk-1.3.0 → pangea_sdk-1.5.0}/pangea/verify_audit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pangea-sdk
3
- Version: 1.3.0
3
+ Version: 1.5.0
4
4
  Summary: Pangea API SDK
5
5
  License: MIT
6
6
  Keywords: Pangea,SDK,Audit
@@ -19,6 +19,7 @@ Requires-Dist: alive-progress (>=2.4.1,<3.0.0)
19
19
  Requires-Dist: cryptography (==39.0.1)
20
20
  Requires-Dist: deprecated (>=1.2.13,<2.0.0)
21
21
  Requires-Dist: pydantic (>=1.10.2,<2.0.0)
22
+ Requires-Dist: pytest (>=7.2.0,<8.0.0)
22
23
  Requires-Dist: python-dateutil (>=2.8.2,<3.0.0)
23
24
  Requires-Dist: requests (>=2.27.1,<3.0.0)
24
25
  Requires-Dist: schema (>=0.7.5,<0.8.0)
@@ -27,7 +28,7 @@ Description-Content-Type: text/markdown
27
28
  <p>
28
29
  <br />
29
30
  <a href="https://pangea.cloud?utm_source=github&utm_medium=node-sdk" target="_blank" rel="noopener noreferrer">
30
- <img src="https://pangea-marketing.s3.us-west-2.amazonaws.com/pangea-color.svg" alt="Pangea Logo" height="40">
31
+ <img src="https://pangea-marketing.s3.us-west-2.amazonaws.com/pangea-color.svg" alt="Pangea Logo" height="40" />
31
32
  </a>
32
33
  <br />
33
34
  </p>
@@ -36,7 +37,7 @@ Description-Content-Type: text/markdown
36
37
  <br />
37
38
 
38
39
  [![documentation](https://img.shields.io/badge/documentation-pangea-blue?style=for-the-badge&labelColor=551B76)](https://pangea.cloud/docs/sdk/python/)
39
- [![Discord](https://img.shields.io/discord/1017567751818182786?color=%23551b76&label=Discord&logo=discord&logoColor=%23FFFFFF&style=for-the-badge)](https://discord.gg/z7yXhC7cQr)
40
+ [![Slack](https://img.shields.io/badge/Slack-4A154B?style=for-the-badge&logo=slack&logoColor=white)](https://pangea.cloud/join-slack/)
40
41
 
41
42
  <br />
42
43
  </p>
@@ -1,7 +1,7 @@
1
1
  <p>
2
2
  <br />
3
3
  <a href="https://pangea.cloud?utm_source=github&utm_medium=node-sdk" target="_blank" rel="noopener noreferrer">
4
- <img src="https://pangea-marketing.s3.us-west-2.amazonaws.com/pangea-color.svg" alt="Pangea Logo" height="40">
4
+ <img src="https://pangea-marketing.s3.us-west-2.amazonaws.com/pangea-color.svg" alt="Pangea Logo" height="40" />
5
5
  </a>
6
6
  <br />
7
7
  </p>
@@ -10,7 +10,7 @@
10
10
  <br />
11
11
 
12
12
  [![documentation](https://img.shields.io/badge/documentation-pangea-blue?style=for-the-badge&labelColor=551B76)](https://pangea.cloud/docs/sdk/python/)
13
- [![Discord](https://img.shields.io/discord/1017567751818182786?color=%23551b76&label=Discord&logo=discord&logoColor=%23FFFFFF&style=for-the-badge)](https://discord.gg/z7yXhC7cQr)
13
+ [![Slack](https://img.shields.io/badge/Slack-4A154B?style=for-the-badge&logo=slack&logoColor=white)](https://pangea.cloud/join-slack/)
14
14
 
15
15
  <br />
16
16
  </p>
@@ -1,4 +1,4 @@
1
- __version__ = "1.3.0"
1
+ __version__ = "1.5.0"
2
2
 
3
3
  from pangea.config import PangeaConfig
4
4
  from pangea.request import PangeaRequest
@@ -12,7 +12,7 @@ from itertools import groupby
12
12
  import pangea.services.audit.util as audit_util
13
13
  from pangea.services import Audit
14
14
  from pangea.services.audit.models import EventEnvelope
15
- from pangea.tools_util import Event, SequenceFollower, exit_with_error, file_events, init_audit, print_progress_bar
15
+ from pangea.tools import Event, SequenceFollower, exit_with_error, file_events, init_audit, print_progress_bar
16
16
 
17
17
 
18
18
  class Errors(t.TypedDict):
@@ -161,7 +161,7 @@ def deep_verify(audit: Audit, file: io.TextIOWrapper) -> Errors:
161
161
  }
162
162
 
163
163
  events = file_events(root_hashes, file)
164
- events_by_idx: list[Event] | t.Iterator[Event]
164
+ events_by_idx: t.Union[list[Event], t.Iterator[Event]]
165
165
  cold_indexes = SequenceFollower()
166
166
  for leaf_index, events_by_idx in groupby(events, lambda event: event.get("leaf_index")):
167
167
  events_by_idx = list(events_by_idx)
@@ -11,21 +11,15 @@ from datetime import datetime
11
11
  import dateutil.parser
12
12
  from pangea.response import PangeaResponse
13
13
  from pangea.services import Audit
14
- from pangea.tools_util import (
15
- filter_deep_none,
16
- get_script_name,
17
- init_audit,
18
- json_defaults,
19
- make_aware_datetime,
20
- print_progress_bar,
21
- )
14
+ from pangea.tools import filter_deep_none, get_script_name, init_audit, make_aware_datetime, print_progress_bar
15
+ from pangea.utils import default_encoder
22
16
 
23
17
 
24
18
  def dump_event(output: io.TextIOWrapper, row: dict, resp: PangeaResponse):
25
19
  row_data = filter_deep_none(row.dict())
26
20
  if resp.result.root:
27
21
  row_data["tree_size"] = resp.result.root.size
28
- output.write(json.dumps(row_data, default=json_defaults) + "\n")
22
+ output.write(json.dumps(row_data, default=default_encoder) + "\n")
29
23
 
30
24
 
31
25
  def dump_audit(audit: Audit, output: io.TextIOWrapper, start: datetime, end: datetime) -> int:
@@ -27,6 +27,22 @@ class PangeaAPIException(PangeaException):
27
27
  def errors(self) -> List[ErrorField]:
28
28
  return self.response.errors
29
29
 
30
+ def __repr__(self) -> str:
31
+ ret = f"Summary: {self.response.summary}\n"
32
+ if self.response.errors:
33
+ ret += "Errors: \n"
34
+ for ef in self.response.errors:
35
+ ret += f"\t {ef.detail}\n"
36
+ return ret
37
+
38
+ def __str__(self) -> str:
39
+ ret = f"Summary: {self.response.summary}\n"
40
+ if self.response.errors:
41
+ ret += "Errors: \n"
42
+ for ef in self.response.errors:
43
+ ret += f"\t {ef.detail}\n"
44
+ return ret
45
+
30
46
 
31
47
  class ValidationException(PangeaAPIException):
32
48
  """Pangea Validation Errors denoting issues with an API request"""
@@ -76,8 +92,12 @@ class ProviderErrorException(PangeaAPIException):
76
92
  """Downstream provider error"""
77
93
 
78
94
 
79
- class InternalServiceErrorException(PangeaAPIException):
80
- """A pangea service error"""
95
+ class InternalServerError(PangeaAPIException):
96
+ """A pangea server error"""
97
+
98
+ def __init__(self, response: PangeaResponse):
99
+ message = f"summary: {response.summary}. request_id: {response.request_id}. request_time: {response.request_time}. response_time: ${response.response_time}"
100
+ super().__init__(message, response)
81
101
 
82
102
 
83
103
  class ServiceNotAvailableException(PangeaAPIException):
@@ -103,3 +123,21 @@ class TreeNotFoundException(AuditAPIException):
103
123
 
104
124
  class BadOffsetException(AuditAPIException):
105
125
  """Bad offset in results search"""
126
+
127
+
128
+ # Vault SDK specific exceptions
129
+ class VaultException(PangeaException):
130
+ """Vault SDK specific exceptions"""
131
+
132
+
133
+ # Vault API specific exceptions
134
+ class VaultAPIException(PangeaAPIException):
135
+ """Vault service specific exceptions"""
136
+
137
+
138
+ class ForbiddenVaultOperation(VaultAPIException):
139
+ """Forbiden Vault operation"""
140
+
141
+
142
+ class VaultItemNotFound(VaultAPIException):
143
+ """Vault item not found"""
@@ -4,13 +4,14 @@
4
4
  import json
5
5
  import logging
6
6
  import time
7
- from typing import Optional
7
+ from typing import Dict, Union
8
8
 
9
9
  import pangea
10
10
  import requests
11
11
  from pangea import exceptions
12
12
  from pangea.config import PangeaConfig
13
13
  from pangea.response import PangeaResponse, ResponseStatus
14
+ from pangea.utils import default_encoder
14
15
  from requests.adapters import HTTPAdapter, Retry
15
16
 
16
17
 
@@ -76,7 +77,7 @@ class PangeaRequest(object):
76
77
 
77
78
  return self._queued_retry_enabled
78
79
 
79
- def post(self, endpoint: str = "", data: dict = {}) -> PangeaResponse:
80
+ def post(self, endpoint: str = "", data: Union[str, Dict] = {}) -> PangeaResponse:
80
81
  """Makes the POST call to a Pangea Service endpoint.
81
82
 
82
83
  If queued_support mode is enabled, progress checks will be made for
@@ -92,14 +93,21 @@ class PangeaRequest(object):
92
93
  various properties to retrieve individual fields
93
94
  """
94
95
  url = self._url(endpoint)
95
- data_send = json.dumps(data)
96
-
97
- self.logger.debug(json.dumps({"service": self.service, "action": "post", "url": url, "data": data}))
96
+ data_send = json.dumps(data, default=default_encoder) if isinstance(data, dict) else data
97
+ self.logger.debug(
98
+ json.dumps({"service": self.service, "action": "post", "url": url, "data": data}, default=default_encoder)
99
+ )
98
100
 
99
101
  requests_response = self.session.post(url, headers=self._headers(), data=data_send)
100
102
 
101
103
  if self._queued_retry_enabled and requests_response.status_code == 202:
102
104
  response_json = requests_response.json()
105
+ self.logger.debug(
106
+ json.dumps(
107
+ {"service": self.service, "action": "post", "url": url, "response": response_json},
108
+ default=default_encoder,
109
+ )
110
+ )
103
111
  request_id = response_json.get("request_id", None)
104
112
 
105
113
  if not request_id:
@@ -109,6 +117,12 @@ class PangeaRequest(object):
109
117
  else:
110
118
  pangea_response = PangeaResponse(requests_response)
111
119
 
120
+ self.logger.debug(
121
+ json.dumps(
122
+ {"service": self.service, "action": "post", "url": url, "result": pangea_response.raw_result},
123
+ default=default_encoder,
124
+ )
125
+ )
112
126
  self._check_response(pangea_response)
113
127
  return pangea_response
114
128
 
@@ -126,9 +140,16 @@ class PangeaRequest(object):
126
140
  url = self._url(f"{endpoint}/{path}")
127
141
 
128
142
  self.logger.debug(json.dupms({"service": self.service, "action": "get", "url": url}))
129
-
130
143
  requests_response = self.session.get(url, headers=self._headers())
144
+
131
145
  pangea_response = PangeaResponse(requests_response)
146
+
147
+ self.logger.debug(
148
+ json.dumps(
149
+ {"service": self.service, "action": "post", "url": url, "result": pangea_response.raw_result},
150
+ default=default_encoder,
151
+ )
152
+ )
132
153
  self._check_response(pangea_response)
133
154
  return pangea_response
134
155
 
@@ -189,7 +210,13 @@ class PangeaRequest(object):
189
210
 
190
211
  self.logger.error(
191
212
  json.dumps(
192
- {"service": self.service, "action": "api_error", "summary": summary, "result": response.raw_result}
213
+ {
214
+ "service": self.service,
215
+ "action": "api_error",
216
+ "url": response.raw_response.url,
217
+ "summary": summary,
218
+ "result": response.raw_result,
219
+ }
193
220
  )
194
221
  )
195
222
 
@@ -215,6 +242,12 @@ class PangeaRequest(object):
215
242
  raise exceptions.IPNotFoundException(summary)
216
243
  elif status == ResponseStatus.BAD_OFFSET.value:
217
244
  raise exceptions.BadOffsetException(summary, response)
245
+ elif status == ResponseStatus.FORBIDDEN_VAULT_OPERATION.value:
246
+ raise exceptions.ForbiddenVaultOperation(summary, response)
247
+ elif status == ResponseStatus.VAULT_ITEM_NOT_FOUND.value:
248
+ raise exceptions.VaultItemNotFound(summary, response)
218
249
  elif status == ResponseStatus.NOT_FOUND.value:
219
250
  raise exceptions.NotFound(response.raw_response.url if response.raw_response is not None else "", response)
251
+ elif status == ResponseStatus.INTERNAL_SERVER_ERROR.value:
252
+ raise exceptions.InternalServerError(response)
220
253
  raise exceptions.PangeaAPIException(f"{summary} ", response)
@@ -1,9 +1,11 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
+ import datetime
3
4
  import enum
4
5
  from typing import Any, Dict, Generic, List, Optional, TypeVar
5
6
 
6
7
  import requests
8
+ from pangea.utils import format_datetime
7
9
  from pydantic import BaseModel
8
10
 
9
11
  T = TypeVar("T")
@@ -21,6 +23,9 @@ class APIResponseModel(BaseModel):
21
23
  class APIRequestModel(BaseModel):
22
24
  class Config:
23
25
  arbitrary_types_allowed = True
26
+ json_encoders = {
27
+ datetime.datetime: format_datetime,
28
+ }
24
29
 
25
30
 
26
31
  class PangeaResponseResult(APIResponseModel):
@@ -63,7 +68,10 @@ class ResponseStatus(str, enum.Enum):
63
68
  TREE_NOT_FOUND = "TreeNotFound"
64
69
  IP_NOT_FOUND = "IPNotFound"
65
70
  BAD_OFFSET = "BadOffset"
71
+ FORBIDDEN_VAULT_OPERATION = "ForbiddenVaultOperation"
72
+ VAULT_ITEM_NOT_FOUND = "VaultItemNotFound"
66
73
  NOT_FOUND = "NotFound"
74
+ INTERNAL_SERVER_ERROR = "InternalError"
67
75
 
68
76
 
69
77
  class ResponseHeader(APIResponseModel):
@@ -2,3 +2,4 @@ from .audit.audit import Audit
2
2
  from .embargo import Embargo
3
3
  from .intel import DomainIntel, FileIntel, IpIntel, UrlIntel
4
4
  from .redact import Redact
5
+ from .vault.vault import Vault
@@ -1,10 +1,30 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
- from typing import Dict, Optional
3
+ import datetime
4
+ from typing import Dict, Optional, Union
4
5
 
5
6
  from pangea.response import PangeaResponse
6
7
  from pangea.services.audit.exceptions import AuditException, EventCorruption
7
- from pangea.services.audit.models import *
8
+ from pangea.services.audit.models import (
9
+ Event,
10
+ EventEnvelope,
11
+ EventSigning,
12
+ EventVerification,
13
+ LogRequest,
14
+ LogResult,
15
+ PublishedRoot,
16
+ Root,
17
+ RootRequest,
18
+ RootResult,
19
+ RootSource,
20
+ SearchEvent,
21
+ SearchOrder,
22
+ SearchOrderBy,
23
+ SearchOutput,
24
+ SearchRequest,
25
+ SearchResultOutput,
26
+ SearchResultRequest,
27
+ )
8
28
  from pangea.services.audit.signing import Signer, Verifier
9
29
  from pangea.services.audit.util import (
10
30
  b64encode_ascii,
@@ -99,6 +119,7 @@ class Audit(ServiceBase):
99
119
  verify (bool, optional): True to verify logs consistency after response.
100
120
  signing (bool, optional): True to sign event.
101
121
  verbose (bool, optional): True to get a more verbose response.
122
+ tenant_id (string, optional): Used to record the tenant associated with this activity.
102
123
  Raises:
103
124
  AuditException: If an audit based api exception happens
104
125
  PangeaAPIException: If an API Error happens
@@ -169,7 +190,7 @@ class Audit(ServiceBase):
169
190
  # verify event hash
170
191
  if response.result.hash and not verify_envelope_hash(response.result.envelope, response.result.hash):
171
192
  # it's a extreme case, it's OK to raise an exception
172
- raise EventCorruption(f"Error: Event hash failed.", response.result.envelope)
193
+ raise EventCorruption("Error: Event hash failed.", response.result.envelope)
173
194
 
174
195
  response.result.signature_verification = self.verify_signature(response.result.envelope)
175
196
 
@@ -208,8 +229,8 @@ class Audit(ServiceBase):
208
229
  query: str,
209
230
  order: Optional[SearchOrder] = None,
210
231
  order_by: Optional[SearchOrderBy] = None,
211
- start: Optional[datetime.datetime] = None,
212
- end: Optional[datetime.datetime] = None,
232
+ start: Optional[Union[datetime.datetime, str]] = None,
233
+ end: Optional[Union[datetime.datetime, str]] = None,
213
234
  limit: Optional[int] = None,
214
235
  max_results: Optional[int] = None,
215
236
  search_restriction: Optional[dict] = None,
@@ -266,8 +287,8 @@ class Audit(ServiceBase):
266
287
  query=query,
267
288
  order=order,
268
289
  order_by=order_by,
269
- start=None if start is None else format_datetime(start),
270
- end=None if end is None else format_datetime(end),
290
+ start=format_datetime(start) if isinstance(start, datetime.datetime) else start,
291
+ end=format_datetime(end) if isinstance(end, datetime.datetime) else end,
271
292
  limit=limit,
272
293
  max_results=max_results,
273
294
  search_restriction=search_restriction,
@@ -173,6 +173,12 @@ class SearchOrder(str, enum.Enum):
173
173
  ASC = "desc"
174
174
  DESC = "asc"
175
175
 
176
+ def __str__(self):
177
+ return str(self.value)
178
+
179
+ def __repr__(self):
180
+ return str(self.value)
181
+
176
182
 
177
183
  class SearchOrderBy(str, enum.Enum):
178
184
  ACTOR = "actor"
@@ -184,6 +190,12 @@ class SearchOrderBy(str, enum.Enum):
184
190
  TARGET = "target"
185
191
  TIMESTAMP = "timestamp"
186
192
 
193
+ def __str__(self):
194
+ return str(self.value)
195
+
196
+ def __repr__(self):
197
+ return str(self.value)
198
+
187
199
 
188
200
  class SearchRequest(APIRequestModel):
189
201
  """
@@ -13,6 +13,7 @@ from typing import Dict, List, Optional
13
13
 
14
14
  import requests
15
15
  from pangea.services.audit.models import Event, EventEnvelope, PublishedRoot
16
+ from pangea.utils import format_datetime
16
17
 
17
18
  Hash = bytes
18
19
 
@@ -140,7 +141,11 @@ def format_datetime(dt: datetime):
140
141
  """
141
142
  Format a datetime in ISO format, using Z instead of +00:00
142
143
  """
143
- return dt.isoformat().replace("+00:00", "Z")
144
+ ret = dt.isoformat()
145
+ if dt.tzinfo is not None:
146
+ return ret.replace("+00:00", "Z")
147
+ else:
148
+ return ret + "Z"
144
149
 
145
150
 
146
151
  def normalize_log(audit: dict) -> dict: