pangea-sdk 3.8.0b4__py3-none-any.whl → 3.9.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.
- pangea/__init__.py +1 -2
- pangea/asyncio/request.py +14 -19
- pangea/asyncio/services/__init__.py +0 -2
- pangea/asyncio/services/audit.py +160 -14
- pangea/asyncio/services/authn.py +118 -79
- pangea/asyncio/services/authz.py +35 -50
- pangea/asyncio/services/intel.py +4 -4
- pangea/asyncio/services/redact.py +56 -2
- pangea/asyncio/services/vault.py +115 -4
- pangea/request.py +24 -21
- pangea/response.py +27 -28
- pangea/services/__init__.py +0 -2
- pangea/services/audit/audit.py +161 -16
- pangea/services/audit/models.py +53 -5
- pangea/services/authn/authn.py +77 -36
- pangea/services/authn/models.py +79 -0
- pangea/services/authz.py +47 -54
- pangea/services/base.py +23 -6
- pangea/services/file_scan.py +1 -0
- pangea/services/intel.py +2 -2
- pangea/services/redact.py +122 -2
- pangea/services/vault/models/common.py +115 -0
- pangea/services/vault/vault.py +117 -6
- pangea/utils.py +19 -91
- {pangea_sdk-3.8.0b4.dist-info → pangea_sdk-3.9.0.dist-info}/METADATA +6 -15
- pangea_sdk-3.9.0.dist-info/RECORD +46 -0
- pangea/asyncio/__init__.py +0 -1
- pangea/asyncio/file_uploader.py +0 -39
- pangea/asyncio/services/sanitize.py +0 -185
- pangea/asyncio/services/share.py +0 -573
- pangea/file_uploader.py +0 -35
- pangea/services/sanitize.py +0 -275
- pangea/services/share/file_format.py +0 -170
- pangea/services/share/share.py +0 -877
- pangea_sdk-3.8.0b4.dist-info/RECORD +0 -54
- {pangea_sdk-3.8.0b4.dist-info → pangea_sdk-3.9.0.dist-info}/WHEEL +0 -0
pangea/services/audit/audit.py
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
# Copyright 2022 Pangea Cyber Corporation
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
|
+
from __future__ import annotations
|
4
|
+
|
3
5
|
import datetime
|
4
6
|
import json
|
5
|
-
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
7
|
+
from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Union
|
6
8
|
|
7
9
|
import pangea.exceptions as pexc
|
8
|
-
from pangea.
|
10
|
+
from pangea.config import PangeaConfig
|
11
|
+
from pangea.response import PangeaResponse, PangeaResponseResult
|
9
12
|
from pangea.services.audit.exceptions import AuditException, EventCorruption
|
10
13
|
from pangea.services.audit.models import (
|
11
14
|
DownloadFormat,
|
@@ -14,6 +17,7 @@ from pangea.services.audit.models import (
|
|
14
17
|
Event,
|
15
18
|
EventEnvelope,
|
16
19
|
EventVerification,
|
20
|
+
ExportRequest,
|
17
21
|
LogBulkRequest,
|
18
22
|
LogBulkResult,
|
19
23
|
LogEvent,
|
@@ -51,8 +55,8 @@ from pangea.utils import canonicalize_nested_json
|
|
51
55
|
|
52
56
|
class AuditBase:
|
53
57
|
def __init__(
|
54
|
-
self, private_key_file: str = "", public_key_info:
|
55
|
-
):
|
58
|
+
self, private_key_file: str = "", public_key_info: dict[str, str] = {}, tenant_id: str | None = None
|
59
|
+
) -> None:
|
56
60
|
self.pub_roots: Dict[int, PublishedRoot] = {}
|
57
61
|
self.buffer_data: Optional[str] = None
|
58
62
|
self.signer: Optional[Signer] = Signer(private_key_file) if private_key_file else None
|
@@ -332,7 +336,9 @@ class AuditBase:
|
|
332
336
|
if audit_envelope and audit_envelope.signature and public_key:
|
333
337
|
v = Verifier()
|
334
338
|
verification = v.verify_signature(
|
335
|
-
audit_envelope.signature,
|
339
|
+
audit_envelope.signature,
|
340
|
+
canonicalize_event(Event(**audit_envelope.event)),
|
341
|
+
public_key,
|
336
342
|
)
|
337
343
|
if verification is not None:
|
338
344
|
return EventVerification.PASS if verification else EventVerification.FAIL
|
@@ -374,14 +380,32 @@ class Audit(ServiceBase, AuditBase):
|
|
374
380
|
|
375
381
|
def __init__(
|
376
382
|
self,
|
377
|
-
token,
|
378
|
-
config=None,
|
383
|
+
token: str,
|
384
|
+
config: PangeaConfig | None = None,
|
379
385
|
private_key_file: str = "",
|
380
|
-
public_key_info:
|
381
|
-
tenant_id:
|
382
|
-
logger_name="pangea",
|
383
|
-
config_id:
|
384
|
-
):
|
386
|
+
public_key_info: dict[str, str] = {},
|
387
|
+
tenant_id: str | None = None,
|
388
|
+
logger_name: str = "pangea",
|
389
|
+
config_id: str | None = None,
|
390
|
+
) -> None:
|
391
|
+
"""
|
392
|
+
Audit client
|
393
|
+
|
394
|
+
Initializes a new Audit client.
|
395
|
+
|
396
|
+
Args:
|
397
|
+
token: Pangea API token.
|
398
|
+
config: Configuration.
|
399
|
+
private_key_file: Private key filepath.
|
400
|
+
public_key_info: Public key information.
|
401
|
+
tenant_id: Tenant ID.
|
402
|
+
logger_name: Logger name.
|
403
|
+
config_id: Configuration ID.
|
404
|
+
|
405
|
+
Examples:
|
406
|
+
config = PangeaConfig(domain="pangea_domain")
|
407
|
+
audit = Audit(token="pangea_token", config=config)
|
408
|
+
"""
|
385
409
|
# FIXME: Temporary check to deprecate config_id from PangeaConfig.
|
386
410
|
# Delete it when deprecate PangeaConfig.config_id
|
387
411
|
if config_id and config is not None and config.config_id is not None:
|
@@ -598,10 +622,11 @@ class Audit(ServiceBase, AuditBase):
|
|
598
622
|
end: Optional[Union[datetime.datetime, str]] = None,
|
599
623
|
limit: Optional[int] = None,
|
600
624
|
max_results: Optional[int] = None,
|
601
|
-
search_restriction: Optional[
|
625
|
+
search_restriction: Optional[Dict[str, Sequence[str]]] = None,
|
602
626
|
verbose: Optional[bool] = None,
|
603
627
|
verify_consistency: bool = False,
|
604
628
|
verify_events: bool = True,
|
629
|
+
return_context: Optional[bool] = None,
|
605
630
|
) -> PangeaResponse[SearchOutput]:
|
606
631
|
"""
|
607
632
|
Search the log
|
@@ -627,10 +652,11 @@ class Audit(ServiceBase, AuditBase):
|
|
627
652
|
end (datetime, optional): An RFC-3339 formatted timestamp, or relative time adjustment from the current time.
|
628
653
|
limit (int, optional): Optional[int] = None,
|
629
654
|
max_results (int, optional): Maximum number of results to return.
|
630
|
-
search_restriction (
|
655
|
+
search_restriction (Dict[str, Sequence[str]], optional): A list of keys to restrict the search results to. Useful for partitioning data available to the query string.
|
631
656
|
verbose (bool, optional): If true, response include root and membership and consistency proofs.
|
632
657
|
verify_consistency (bool): True to verify logs consistency
|
633
658
|
verify_events (bool): True to verify hash events and signatures
|
659
|
+
return_context (bool): Return the context data needed to decrypt secure audit events that have been redacted with format preserving encryption.
|
634
660
|
|
635
661
|
Raises:
|
636
662
|
AuditException: If an audit based api exception happens
|
@@ -664,6 +690,7 @@ class Audit(ServiceBase, AuditBase):
|
|
664
690
|
max_results=max_results,
|
665
691
|
search_restriction=search_restriction,
|
666
692
|
verbose=verbose,
|
693
|
+
return_context=return_context,
|
667
694
|
)
|
668
695
|
|
669
696
|
response: PangeaResponse[SearchOutput] = self.request.post(
|
@@ -678,8 +705,10 @@ class Audit(ServiceBase, AuditBase):
|
|
678
705
|
id: str,
|
679
706
|
limit: Optional[int] = 20,
|
680
707
|
offset: Optional[int] = 0,
|
708
|
+
assert_search_restriction: Optional[Dict[str, Sequence[str]]] = None,
|
681
709
|
verify_consistency: bool = False,
|
682
710
|
verify_events: bool = True,
|
711
|
+
return_context: Optional[bool] = None,
|
683
712
|
) -> PangeaResponse[SearchResultOutput]:
|
684
713
|
"""
|
685
714
|
Results of a search
|
@@ -692,8 +721,10 @@ class Audit(ServiceBase, AuditBase):
|
|
692
721
|
id (string): the id of a search action, found in `response.result.id`
|
693
722
|
limit (integer, optional): the maximum number of results to return, default is 20
|
694
723
|
offset (integer, optional): the position of the first result to return, default is 0
|
724
|
+
assert_search_restriction (Dict[str, Sequence[str]], optional): Assert the requested search results were queried with the exact same search restrictions, to ensure the results comply to the expected restrictions.
|
695
725
|
verify_consistency (bool): True to verify logs consistency
|
696
726
|
verify_events (bool): True to verify hash events and signatures
|
727
|
+
return_context (bool): Return the context data needed to decrypt secure audit events that have been redacted with format preserving encryption.
|
697
728
|
Raises:
|
698
729
|
AuditException: If an audit based api exception happens
|
699
730
|
PangeaAPIException: If an API Error happens
|
@@ -716,6 +747,8 @@ class Audit(ServiceBase, AuditBase):
|
|
716
747
|
id=id,
|
717
748
|
limit=limit,
|
718
749
|
offset=offset,
|
750
|
+
assert_search_restriction=assert_search_restriction,
|
751
|
+
return_context=return_context,
|
719
752
|
)
|
720
753
|
response: PangeaResponse[SearchResultOutput] = self.request.post(
|
721
754
|
"v1/results", SearchResultOutput, data=input.dict(exclude_none=True)
|
@@ -724,6 +757,107 @@ class Audit(ServiceBase, AuditBase):
|
|
724
757
|
self.update_published_roots(response.result)
|
725
758
|
return self.handle_results_response(response, verify_consistency, verify_events)
|
726
759
|
|
760
|
+
def export(
|
761
|
+
self,
|
762
|
+
*,
|
763
|
+
format: DownloadFormat = DownloadFormat.CSV,
|
764
|
+
start: Optional[datetime.datetime] = None,
|
765
|
+
end: Optional[datetime.datetime] = None,
|
766
|
+
order: Optional[SearchOrder] = None,
|
767
|
+
order_by: Optional[str] = None,
|
768
|
+
verbose: bool = True,
|
769
|
+
) -> PangeaResponse[PangeaResponseResult]:
|
770
|
+
"""
|
771
|
+
Export from the audit log
|
772
|
+
|
773
|
+
Bulk export of data from the Secure Audit Log, with optional filtering.
|
774
|
+
|
775
|
+
OperationId: audit_post_v1_export
|
776
|
+
|
777
|
+
Args:
|
778
|
+
format: Format for the records.
|
779
|
+
start: The start of the time range to perform the search on.
|
780
|
+
end: The end of the time range to perform the search on. If omitted,
|
781
|
+
then all records up to the latest will be searched.
|
782
|
+
order: Specify the sort order of the response.
|
783
|
+
order_by: Name of column to sort the results by.
|
784
|
+
verbose: Whether or not to include the root hash of the tree and the
|
785
|
+
membership proof for each record.
|
786
|
+
|
787
|
+
Raises:
|
788
|
+
AuditException: If an audit based api exception happens
|
789
|
+
PangeaAPIException: If an API Error happens
|
790
|
+
|
791
|
+
Examples:
|
792
|
+
export_res = audit.export(verbose=False)
|
793
|
+
|
794
|
+
# Export may take several dozens of minutes, so polling for the result
|
795
|
+
# should be done in a loop. That is omitted here for brevity's sake.
|
796
|
+
try:
|
797
|
+
audit.poll_result(request_id=export_res.request_id)
|
798
|
+
except AcceptedRequestException:
|
799
|
+
# Retry later.
|
800
|
+
|
801
|
+
# Download the result when it's ready.
|
802
|
+
download_res = audit.download_results(request_id=export_res.request_id)
|
803
|
+
download_res.result.dest_url
|
804
|
+
# => https://pangea-runtime.s3.amazonaws.com/audit/xxxxx/search_results_[...]
|
805
|
+
"""
|
806
|
+
input = ExportRequest(
|
807
|
+
format=format,
|
808
|
+
start=start,
|
809
|
+
end=end,
|
810
|
+
order=order,
|
811
|
+
order_by=order_by,
|
812
|
+
verbose=verbose,
|
813
|
+
)
|
814
|
+
try:
|
815
|
+
return self.request.post(
|
816
|
+
"v1/export", PangeaResponseResult, data=input.dict(exclude_none=True), poll_result=False
|
817
|
+
)
|
818
|
+
except pexc.AcceptedRequestException as e:
|
819
|
+
return e.response
|
820
|
+
|
821
|
+
def log_stream(self, data: dict) -> PangeaResponse[PangeaResponseResult]:
|
822
|
+
"""
|
823
|
+
Log streaming endpoint
|
824
|
+
|
825
|
+
This API allows 3rd party vendors (like Auth0) to stream events to this
|
826
|
+
endpoint where the structure of the payload varies across different
|
827
|
+
vendors.
|
828
|
+
|
829
|
+
OperationId: audit_post_v1_log_stream
|
830
|
+
|
831
|
+
Args:
|
832
|
+
data: Event data. The exact schema of this will vary by vendor.
|
833
|
+
|
834
|
+
Raises:
|
835
|
+
AuditException: If an audit based api exception happens
|
836
|
+
PangeaAPIException: If an API Error happens
|
837
|
+
|
838
|
+
Examples:
|
839
|
+
data = {
|
840
|
+
"logs": [
|
841
|
+
{
|
842
|
+
"log_id": "some log ID",
|
843
|
+
"data": {
|
844
|
+
"date": "2024-03-29T17:26:50.193Z",
|
845
|
+
"type": "sapi",
|
846
|
+
"description": "Create a log stream",
|
847
|
+
"client_id": "some client ID",
|
848
|
+
"ip": "127.0.0.1",
|
849
|
+
"user_agent": "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0",
|
850
|
+
"user_id": "some user ID",
|
851
|
+
},
|
852
|
+
}
|
853
|
+
# ...
|
854
|
+
]
|
855
|
+
}
|
856
|
+
|
857
|
+
response = audit.log_stream(data)
|
858
|
+
"""
|
859
|
+
return self.request.post("v1/log_stream", PangeaResponseResult, data=data)
|
860
|
+
|
727
861
|
def root(self, tree_size: Optional[int] = None) -> PangeaResponse[RootResult]:
|
728
862
|
"""
|
729
863
|
Tamperproof verification
|
@@ -749,7 +883,11 @@ class Audit(ServiceBase, AuditBase):
|
|
749
883
|
return self.request.post("v1/root", RootResult, data=input.dict(exclude_none=True))
|
750
884
|
|
751
885
|
def download_results(
|
752
|
-
self,
|
886
|
+
self,
|
887
|
+
result_id: Optional[str] = None,
|
888
|
+
format: DownloadFormat = DownloadFormat.CSV,
|
889
|
+
request_id: Optional[str] = None,
|
890
|
+
return_context: Optional[bool] = None,
|
753
891
|
) -> PangeaResponse[DownloadResult]:
|
754
892
|
"""
|
755
893
|
Download search results
|
@@ -761,6 +899,8 @@ class Audit(ServiceBase, AuditBase):
|
|
761
899
|
Args:
|
762
900
|
result_id: ID returned by the search API.
|
763
901
|
format: Format for the records.
|
902
|
+
request_id: ID returned by the export API.
|
903
|
+
return_context (bool): Return the context data needed to decrypt secure audit events that have been redacted with format preserving encryption.
|
764
904
|
|
765
905
|
Returns:
|
766
906
|
URL where search results can be downloaded.
|
@@ -776,7 +916,12 @@ class Audit(ServiceBase, AuditBase):
|
|
776
916
|
)
|
777
917
|
"""
|
778
918
|
|
779
|
-
|
919
|
+
if request_id is None and result_id is None:
|
920
|
+
raise ValueError("must pass one of `request_id` or `result_id`")
|
921
|
+
|
922
|
+
input = DownloadRequest(
|
923
|
+
request_id=request_id, result_id=result_id, format=format, return_context=return_context
|
924
|
+
)
|
780
925
|
return self.request.post("v1/download_results", DownloadResult, data=input.dict(exclude_none=True))
|
781
926
|
|
782
927
|
def update_published_roots(self, result: SearchResultOutput):
|
pangea/services/audit/models.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# Copyright 2022 Pangea Cyber Corporation
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
|
+
from __future__ import annotations
|
4
|
+
|
3
5
|
import datetime
|
4
6
|
import enum
|
5
|
-
from typing import Any, Dict, List, Optional, Union
|
7
|
+
from typing import Any, Dict, List, Optional, Sequence, Union
|
6
8
|
|
7
9
|
from pangea.response import APIRequestModel, APIResponseModel, PangeaResponseResult
|
8
10
|
|
@@ -19,14 +21,14 @@ class EventVerification(str, enum.Enum):
|
|
19
21
|
return str(self.value)
|
20
22
|
|
21
23
|
|
22
|
-
class Event(
|
24
|
+
class Event(Dict[str, Any]):
|
23
25
|
"""
|
24
26
|
Event to perform an auditable activity
|
25
27
|
|
26
28
|
Auxiliary class to be compatible with older SDKs
|
27
29
|
"""
|
28
30
|
|
29
|
-
def __init__(self, **data):
|
31
|
+
def __init__(self, **data) -> None:
|
30
32
|
super().__init__(**data)
|
31
33
|
|
32
34
|
@property
|
@@ -269,6 +271,7 @@ class SearchRequest(APIRequestModel):
|
|
269
271
|
max_results -- Maximum number of results to return.
|
270
272
|
search_restriction -- A list of keys to restrict the search results to. Useful for partitioning data available to the query string.
|
271
273
|
verbose -- If true, include root, membership and consistency proofs in response.
|
274
|
+
return_context -- Return the context data needed to decrypt secure audit events that have been redacted with format preserving encryption.
|
272
275
|
"""
|
273
276
|
|
274
277
|
query: str
|
@@ -279,8 +282,9 @@ class SearchRequest(APIRequestModel):
|
|
279
282
|
end: Optional[str] = None
|
280
283
|
limit: Optional[int] = None
|
281
284
|
max_results: Optional[int] = None
|
282
|
-
search_restriction: Optional[
|
285
|
+
search_restriction: Optional[Dict[str, Sequence[str]]] = None
|
283
286
|
verbose: Optional[bool] = None
|
287
|
+
return_context: Optional[bool] = None
|
284
288
|
|
285
289
|
|
286
290
|
class RootRequest(APIRequestModel):
|
@@ -361,6 +365,7 @@ class SearchEvent(APIResponseModel):
|
|
361
365
|
consistency_verification -- Consistency verification calculated if required.
|
362
366
|
membership_verification -- Membership verification calculated if required.
|
363
367
|
signature_verification -- Signature verification calculated if required.
|
368
|
+
fpe_context -- The context data needed to decrypt secure audit events that have been redacted with format preserving encryption.
|
364
369
|
"""
|
365
370
|
|
366
371
|
envelope: EventEnvelope
|
@@ -371,6 +376,7 @@ class SearchEvent(APIResponseModel):
|
|
371
376
|
consistency_verification: EventVerification = EventVerification.NONE
|
372
377
|
membership_verification: EventVerification = EventVerification.NONE
|
373
378
|
signature_verification: EventVerification = EventVerification.NONE
|
379
|
+
fpe_context: Optional[str] = None
|
374
380
|
|
375
381
|
|
376
382
|
class SearchResultOutput(PangeaResponseResult):
|
@@ -415,11 +421,15 @@ class SearchResultRequest(APIRequestModel):
|
|
415
421
|
id -- A search results identifier returned by the search call.
|
416
422
|
limit -- Number of audit records to include from the first page of the results.
|
417
423
|
offset -- Offset from the start of the result set to start returning results from.
|
424
|
+
assert_search_restriction -- Assert the requested search results were queried with the exact same search restrictions, to ensure the results comply to the expected restrictions.
|
425
|
+
return_context -- Return the context data needed to decrypt secure audit events that have been redacted with format preserving encryption.
|
418
426
|
"""
|
419
427
|
|
420
428
|
id: str
|
421
429
|
limit: Optional[int] = 20
|
422
430
|
offset: Optional[int] = 0
|
431
|
+
assert_search_restriction: Optional[Dict[str, Sequence[str]]] = None
|
432
|
+
return_context: Optional[bool] = None
|
423
433
|
|
424
434
|
|
425
435
|
class DownloadFormat(str, enum.Enum):
|
@@ -437,13 +447,51 @@ class DownloadFormat(str, enum.Enum):
|
|
437
447
|
|
438
448
|
|
439
449
|
class DownloadRequest(APIRequestModel):
|
440
|
-
|
450
|
+
request_id: Optional[str] = None
|
451
|
+
"""ID returned by the export API."""
|
452
|
+
|
453
|
+
result_id: Optional[str] = None
|
441
454
|
"""ID returned by the search API."""
|
442
455
|
|
443
456
|
format: Optional[str] = None
|
444
457
|
"""Format for the records."""
|
445
458
|
|
459
|
+
return_context: Optional[bool] = None
|
460
|
+
"""Return the context data needed to decrypt secure audit events that have been redacted with format preserving encryption."""
|
461
|
+
|
446
462
|
|
447
463
|
class DownloadResult(PangeaResponseResult):
|
448
464
|
dest_url: str
|
449
465
|
"""URL where search results can be downloaded."""
|
466
|
+
|
467
|
+
expires_at: str
|
468
|
+
"""
|
469
|
+
The time when the results will no longer be available to page through via
|
470
|
+
the results API.
|
471
|
+
"""
|
472
|
+
|
473
|
+
|
474
|
+
class ExportRequest(APIRequestModel):
|
475
|
+
format: DownloadFormat = DownloadFormat.CSV
|
476
|
+
"""Format for the records."""
|
477
|
+
|
478
|
+
start: Optional[datetime.datetime] = None
|
479
|
+
"""The start of the time range to perform the search on."""
|
480
|
+
|
481
|
+
end: Optional[datetime.datetime] = None
|
482
|
+
"""
|
483
|
+
The end of the time range to perform the search on. If omitted, then all
|
484
|
+
records up to the latest will be searched.
|
485
|
+
"""
|
486
|
+
|
487
|
+
order_by: Optional[str] = None
|
488
|
+
"""Name of column to sort the results by."""
|
489
|
+
|
490
|
+
order: Optional[SearchOrder] = None
|
491
|
+
"""Specify the sort order of the response."""
|
492
|
+
|
493
|
+
verbose: bool = True
|
494
|
+
"""
|
495
|
+
Whether or not to include the root hash of the tree and the membership proof
|
496
|
+
for each record.
|
497
|
+
"""
|