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.
@@ -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.response import PangeaResponse
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: Dict[str, str] = {}, tenant_id: Optional[str] = None
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, canonicalize_event(audit_envelope.event), public_key # type: ignore[arg-type]
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: Dict[str, str] = {},
381
- tenant_id: Optional[str] = None,
382
- logger_name="pangea",
383
- config_id: Optional[str] = None,
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[dict] = None,
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 (dict, optional): A list of keys to restrict the search results to. Useful for partitioning data available to the query string.
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, result_id: str, format: Optional[DownloadFormat] = None
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
- input = DownloadRequest(result_id=result_id, format=format)
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):
@@ -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(dict):
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[dict] = None
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
- result_id: str
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
+ """