pangea-sdk 3.9.0__py3-none-any.whl → 4.1.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 -1
- pangea/asyncio/request.py +4 -4
- pangea/asyncio/services/audit.py +30 -11
- pangea/asyncio/services/authn.py +49 -29
- pangea/asyncio/services/authz.py +13 -7
- pangea/asyncio/services/embargo.py +2 -2
- pangea/asyncio/services/file_scan.py +3 -3
- pangea/asyncio/services/intel.py +40 -22
- pangea/asyncio/services/redact.py +5 -3
- pangea/asyncio/services/vault.py +93 -28
- pangea/crypto/rsa.py +47 -0
- pangea/dump_audit.py +1 -1
- pangea/request.py +8 -5
- pangea/response.py +9 -16
- pangea/services/audit/audit.py +43 -20
- pangea/services/audit/models.py +3 -3
- pangea/services/audit/util.py +3 -3
- pangea/services/authn/authn.py +39 -29
- pangea/services/authn/models.py +9 -4
- pangea/services/authz.py +10 -8
- pangea/services/embargo.py +2 -2
- pangea/services/file_scan.py +2 -2
- pangea/services/intel.py +23 -21
- pangea/services/redact.py +3 -3
- pangea/services/vault/models/asymmetric.py +2 -0
- pangea/services/vault/models/common.py +67 -6
- pangea/services/vault/models/symmetric.py +6 -2
- pangea/services/vault/vault.py +89 -28
- pangea/utils.py +4 -16
- pangea/verify_audit.py +270 -83
- {pangea_sdk-3.9.0.dist-info → pangea_sdk-4.1.0.dist-info}/METADATA +10 -9
- pangea_sdk-4.1.0.dist-info/RECORD +47 -0
- {pangea_sdk-3.9.0.dist-info → pangea_sdk-4.1.0.dist-info}/WHEEL +1 -1
- pangea_sdk-3.9.0.dist-info/RECORD +0 -46
pangea/asyncio/services/intel.py
CHANGED
@@ -67,7 +67,9 @@ class FileIntelAsync(ServiceBaseAsync):
|
|
67
67
|
|
68
68
|
"""
|
69
69
|
input = m.FileReputationRequest(hash=hash, hash_type=hash_type, verbose=verbose, raw=raw, provider=provider)
|
70
|
-
return await self.request.post(
|
70
|
+
return await self.request.post(
|
71
|
+
"v1/reputation", m.FileReputationResult, data=input.model_dump(exclude_none=True)
|
72
|
+
)
|
71
73
|
|
72
74
|
async def hash_reputation_bulk(
|
73
75
|
self,
|
@@ -103,7 +105,9 @@ class FileIntelAsync(ServiceBaseAsync):
|
|
103
105
|
input = m.FileReputationBulkRequest( # type: ignore[call-arg]
|
104
106
|
hashes=hashes, hash_type=hash_type, verbose=verbose, raw=raw, provider=provider
|
105
107
|
)
|
106
|
-
return await self.request.post(
|
108
|
+
return await self.request.post(
|
109
|
+
"v2/reputation", m.FileReputationBulkResult, data=input.model_dump(exclude_none=True)
|
110
|
+
)
|
107
111
|
|
108
112
|
async def filepath_reputation(
|
109
113
|
self,
|
@@ -144,7 +148,9 @@ class FileIntelAsync(ServiceBaseAsync):
|
|
144
148
|
hash = hashlib.sha256(data.read()).hexdigest()
|
145
149
|
|
146
150
|
input = m.FileReputationRequest(hash=hash, hash_type="sha256", verbose=verbose, raw=raw, provider=provider)
|
147
|
-
return await self.request.post(
|
151
|
+
return await self.request.post(
|
152
|
+
"v1/reputation", m.FileReputationResult, data=input.model_dump(exclude_none=True)
|
153
|
+
)
|
148
154
|
|
149
155
|
async def filepath_reputation_bulk(
|
150
156
|
self,
|
@@ -243,7 +249,9 @@ class DomainIntelAsync(ServiceBaseAsync):
|
|
243
249
|
)
|
244
250
|
"""
|
245
251
|
input = m.DomainReputationRequest(domain=domain, verbose=verbose, provider=provider, raw=raw)
|
246
|
-
return await self.request.post(
|
252
|
+
return await self.request.post(
|
253
|
+
"v1/reputation", m.DomainReputationResult, data=input.model_dump(exclude_none=True)
|
254
|
+
)
|
247
255
|
|
248
256
|
async def reputation_bulk(
|
249
257
|
self,
|
@@ -277,7 +285,7 @@ class DomainIntelAsync(ServiceBaseAsync):
|
|
277
285
|
"""
|
278
286
|
input = m.DomainReputationBulkRequest(domains=domains, verbose=verbose, provider=provider, raw=raw)
|
279
287
|
return await self.request.post(
|
280
|
-
"v2/reputation", m.DomainReputationBulkResult, data=input.
|
288
|
+
"v2/reputation", m.DomainReputationBulkResult, data=input.model_dump(exclude_none=True)
|
281
289
|
)
|
282
290
|
|
283
291
|
async def who_is(
|
@@ -310,7 +318,7 @@ class DomainIntelAsync(ServiceBaseAsync):
|
|
310
318
|
)
|
311
319
|
"""
|
312
320
|
input = m.DomainWhoIsRequest(domain=domain, verbose=verbose, provider=provider, raw=raw) # type: ignore[call-arg]
|
313
|
-
return await self.request.post("v1/whois", m.DomainWhoIsResult, data=input.
|
321
|
+
return await self.request.post("v1/whois", m.DomainWhoIsResult, data=input.model_dump(exclude_none=True))
|
314
322
|
|
315
323
|
|
316
324
|
class IpIntelAsync(ServiceBaseAsync):
|
@@ -369,7 +377,7 @@ class IpIntelAsync(ServiceBaseAsync):
|
|
369
377
|
)
|
370
378
|
"""
|
371
379
|
input = m.IPReputationRequest(ip=ip, verbose=verbose, raw=raw, provider=provider)
|
372
|
-
return await self.request.post("v1/reputation", m.IPReputationResult, data=input.
|
380
|
+
return await self.request.post("v1/reputation", m.IPReputationResult, data=input.model_dump(exclude_none=True))
|
373
381
|
|
374
382
|
async def reputation_bulk(
|
375
383
|
self, ips: List[str], verbose: Optional[bool] = None, raw: Optional[bool] = None, provider: Optional[str] = None
|
@@ -398,7 +406,9 @@ class IpIntelAsync(ServiceBaseAsync):
|
|
398
406
|
FIXME:
|
399
407
|
"""
|
400
408
|
input = m.IPReputationBulkRequest(ips=ips, verbose=verbose, raw=raw, provider=provider)
|
401
|
-
return await self.request.post(
|
409
|
+
return await self.request.post(
|
410
|
+
"v2/reputation", m.IPReputationBulkResult, data=input.model_dump(exclude_none=True)
|
411
|
+
)
|
402
412
|
|
403
413
|
async def geolocate(
|
404
414
|
self, ip: str, verbose: Optional[bool] = None, raw: Optional[bool] = None, provider: Optional[str] = None
|
@@ -430,7 +440,7 @@ class IpIntelAsync(ServiceBaseAsync):
|
|
430
440
|
)
|
431
441
|
"""
|
432
442
|
input = m.IPGeolocateRequest(ip=ip, verbose=verbose, raw=raw, provider=provider)
|
433
|
-
return await self.request.post("v1/geolocate", m.IPGeolocateResult, data=input.
|
443
|
+
return await self.request.post("v1/geolocate", m.IPGeolocateResult, data=input.model_dump(exclude_none=True))
|
434
444
|
|
435
445
|
async def geolocate_bulk(
|
436
446
|
self, ips: List[str], verbose: Optional[bool] = None, raw: Optional[bool] = None, provider: Optional[str] = None
|
@@ -459,7 +469,9 @@ class IpIntelAsync(ServiceBaseAsync):
|
|
459
469
|
FIXME:
|
460
470
|
"""
|
461
471
|
input = m.IPGeolocateBulkRequest(ips=ips, verbose=verbose, raw=raw, provider=provider)
|
462
|
-
return await self.request.post(
|
472
|
+
return await self.request.post(
|
473
|
+
"v2/geolocate", m.IPGeolocateBulkResult, data=input.model_dump(exclude_none=True)
|
474
|
+
)
|
463
475
|
|
464
476
|
async def get_domain(
|
465
477
|
self, ip: str, verbose: Optional[bool] = None, raw: Optional[bool] = None, provider: Optional[str] = None
|
@@ -491,7 +503,7 @@ class IpIntelAsync(ServiceBaseAsync):
|
|
491
503
|
)
|
492
504
|
"""
|
493
505
|
input = m.IPDomainRequest(ip=ip, verbose=verbose, raw=raw, provider=provider)
|
494
|
-
return await self.request.post("v1/domain", m.IPDomainResult, data=input.
|
506
|
+
return await self.request.post("v1/domain", m.IPDomainResult, data=input.model_dump(exclude_none=True))
|
495
507
|
|
496
508
|
async def get_domain_bulk(
|
497
509
|
self, ips: List[str], verbose: Optional[bool] = None, raw: Optional[bool] = None, provider: Optional[str] = None
|
@@ -520,7 +532,7 @@ class IpIntelAsync(ServiceBaseAsync):
|
|
520
532
|
FIXME:
|
521
533
|
"""
|
522
534
|
input = m.IPDomainBulkRequest(ips=ips, verbose=verbose, raw=raw, provider=provider)
|
523
|
-
return await self.request.post("v2/domain", m.IPDomainBulkResult, data=input.
|
535
|
+
return await self.request.post("v2/domain", m.IPDomainBulkResult, data=input.model_dump(exclude_none=True))
|
524
536
|
|
525
537
|
async def is_vpn(
|
526
538
|
self, ip: str, verbose: Optional[bool] = None, raw: Optional[bool] = None, provider: Optional[str] = None
|
@@ -552,7 +564,7 @@ class IpIntelAsync(ServiceBaseAsync):
|
|
552
564
|
)
|
553
565
|
"""
|
554
566
|
input = m.IPVPNRequest(ip=ip, verbose=verbose, raw=raw, provider=provider)
|
555
|
-
return await self.request.post("v1/vpn", m.IPVPNResult, data=input.
|
567
|
+
return await self.request.post("v1/vpn", m.IPVPNResult, data=input.model_dump(exclude_none=True))
|
556
568
|
|
557
569
|
async def is_vpn_bulk(
|
558
570
|
self, ips: List[str], verbose: Optional[bool] = None, raw: Optional[bool] = None, provider: Optional[str] = None
|
@@ -581,7 +593,7 @@ class IpIntelAsync(ServiceBaseAsync):
|
|
581
593
|
FIXME:
|
582
594
|
"""
|
583
595
|
input = m.IPVPNBulkRequest(ips=ips, verbose=verbose, raw=raw, provider=provider)
|
584
|
-
return await self.request.post("v2/vpn", m.IPVPNBulkResult, data=input.
|
596
|
+
return await self.request.post("v2/vpn", m.IPVPNBulkResult, data=input.model_dump(exclude_none=True))
|
585
597
|
|
586
598
|
async def is_proxy(
|
587
599
|
self, ip: str, verbose: Optional[bool] = None, raw: Optional[bool] = None, provider: Optional[str] = None
|
@@ -613,7 +625,7 @@ class IpIntelAsync(ServiceBaseAsync):
|
|
613
625
|
)
|
614
626
|
"""
|
615
627
|
input = m.IPProxyRequest(ip=ip, verbose=verbose, raw=raw, provider=provider)
|
616
|
-
return await self.request.post("v1/proxy", m.IPProxyResult, data=input.
|
628
|
+
return await self.request.post("v1/proxy", m.IPProxyResult, data=input.model_dump(exclude_none=True))
|
617
629
|
|
618
630
|
async def is_proxy_bulk(
|
619
631
|
self, ips: List[str], verbose: Optional[bool] = None, raw: Optional[bool] = None, provider: Optional[str] = None
|
@@ -642,7 +654,7 @@ class IpIntelAsync(ServiceBaseAsync):
|
|
642
654
|
FIXME:
|
643
655
|
"""
|
644
656
|
input = m.IPProxyBulkRequest(ips=ips, verbose=verbose, raw=raw, provider=provider)
|
645
|
-
return await self.request.post("v2/proxy", m.IPProxyBulkResult, data=input.
|
657
|
+
return await self.request.post("v2/proxy", m.IPProxyBulkResult, data=input.model_dump(exclude_none=True))
|
646
658
|
|
647
659
|
|
648
660
|
class UrlIntelAsync(ServiceBaseAsync):
|
@@ -702,7 +714,7 @@ class UrlIntelAsync(ServiceBaseAsync):
|
|
702
714
|
"""
|
703
715
|
|
704
716
|
input = m.URLReputationRequest(url=url, provider=provider, verbose=verbose, raw=raw)
|
705
|
-
return await self.request.post("v1/reputation", m.URLReputationResult, data=input.
|
717
|
+
return await self.request.post("v1/reputation", m.URLReputationResult, data=input.model_dump(exclude_none=True))
|
706
718
|
|
707
719
|
async def reputation_bulk(
|
708
720
|
self,
|
@@ -736,7 +748,9 @@ class UrlIntelAsync(ServiceBaseAsync):
|
|
736
748
|
"""
|
737
749
|
|
738
750
|
input = m.URLReputationBulkRequest(urls=urls, provider=provider, verbose=verbose, raw=raw)
|
739
|
-
return await self.request.post(
|
751
|
+
return await self.request.post(
|
752
|
+
"v2/reputation", m.URLReputationBulkResult, data=input.model_dump(exclude_none=True)
|
753
|
+
)
|
740
754
|
|
741
755
|
|
742
756
|
class UserIntelAsync(ServiceBaseAsync):
|
@@ -822,7 +836,9 @@ class UserIntelAsync(ServiceBaseAsync):
|
|
822
836
|
verbose=verbose,
|
823
837
|
raw=raw,
|
824
838
|
)
|
825
|
-
return await self.request.post(
|
839
|
+
return await self.request.post(
|
840
|
+
"v1/user/breached", m.UserBreachedResult, data=input.model_dump(exclude_none=True)
|
841
|
+
)
|
826
842
|
|
827
843
|
async def user_breached_bulk(
|
828
844
|
self,
|
@@ -876,7 +892,9 @@ class UserIntelAsync(ServiceBaseAsync):
|
|
876
892
|
verbose=verbose,
|
877
893
|
raw=raw,
|
878
894
|
)
|
879
|
-
return await self.request.post(
|
895
|
+
return await self.request.post(
|
896
|
+
"v2/user/breached", m.UserBreachedBulkResult, data=input.model_dump(exclude_none=True)
|
897
|
+
)
|
880
898
|
|
881
899
|
async def password_breached(
|
882
900
|
self,
|
@@ -919,7 +937,7 @@ class UserIntelAsync(ServiceBaseAsync):
|
|
919
937
|
hash_type=hash_type, hash_prefix=hash_prefix, provider=provider, verbose=verbose, raw=raw
|
920
938
|
)
|
921
939
|
return await self.request.post(
|
922
|
-
"v1/password/breached", m.UserPasswordBreachedResult, data=input.
|
940
|
+
"v1/password/breached", m.UserPasswordBreachedResult, data=input.model_dump(exclude_none=True)
|
923
941
|
)
|
924
942
|
|
925
943
|
async def password_breached_bulk(
|
@@ -959,5 +977,5 @@ class UserIntelAsync(ServiceBaseAsync):
|
|
959
977
|
hash_type=hash_type, hash_prefixes=hash_prefixes, provider=provider, verbose=verbose, raw=raw
|
960
978
|
)
|
961
979
|
return await self.request.post(
|
962
|
-
"v2/password/breached", m.UserPasswordBreachedBulkResult, data=input.
|
980
|
+
"v2/password/breached", m.UserPasswordBreachedBulkResult, data=input.model_dump(exclude_none=True)
|
963
981
|
)
|
@@ -102,7 +102,7 @@ class RedactAsync(ServiceBaseAsync):
|
|
102
102
|
return_result=return_result,
|
103
103
|
redaction_method_overrides=redaction_method_overrides,
|
104
104
|
)
|
105
|
-
return await self.request.post("v1/redact", m.RedactResult, data=input.
|
105
|
+
return await self.request.post("v1/redact", m.RedactResult, data=input.model_dump(exclude_none=True))
|
106
106
|
|
107
107
|
async def redact_structured(
|
108
108
|
self,
|
@@ -162,7 +162,9 @@ class RedactAsync(ServiceBaseAsync):
|
|
162
162
|
return_result=return_result,
|
163
163
|
redaction_method_overrides=redaction_method_overrides,
|
164
164
|
)
|
165
|
-
return await self.request.post(
|
165
|
+
return await self.request.post(
|
166
|
+
"v1/redact_structured", m.StructuredResult, data=input.model_dump(exclude_none=True)
|
167
|
+
)
|
166
168
|
|
167
169
|
async def unredact(self, redacted_data: m.RedactedData, fpe_context: str) -> PangeaResponse[m.UnredactResult]:
|
168
170
|
"""
|
@@ -185,4 +187,4 @@ class RedactAsync(ServiceBaseAsync):
|
|
185
187
|
[API Documentation](https://pangea.cloud/docs/api/redact#unredact)
|
186
188
|
"""
|
187
189
|
input = m.UnredactRequest(redacted_data=redacted_data, fpe_context=fpe_context)
|
188
|
-
return await self.request.post("v1/unredact", m.UnredactResult, data=input.
|
190
|
+
return await self.request.post("v1/unredact", m.UnredactResult, data=input.model_dump(exclude_none=True))
|
pangea/asyncio/services/vault.py
CHANGED
@@ -31,6 +31,9 @@ from pangea.services.vault.models.common import (
|
|
31
31
|
EncryptStructuredResult,
|
32
32
|
EncryptTransformRequest,
|
33
33
|
EncryptTransformResult,
|
34
|
+
ExportEncryptionAlgorithm,
|
35
|
+
ExportRequest,
|
36
|
+
ExportResult,
|
34
37
|
FolderCreateRequest,
|
35
38
|
FolderCreateResult,
|
36
39
|
GetRequest,
|
@@ -151,7 +154,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
151
154
|
input = DeleteRequest(
|
152
155
|
id=id,
|
153
156
|
)
|
154
|
-
return await self.request.post("v1/delete", DeleteResult, data=input.
|
157
|
+
return await self.request.post("v1/delete", DeleteResult, data=input.model_dump(exclude_none=True))
|
155
158
|
|
156
159
|
# Get endpoint
|
157
160
|
async def get(
|
@@ -198,7 +201,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
198
201
|
verbose=verbose,
|
199
202
|
version_state=version_state,
|
200
203
|
)
|
201
|
-
return await self.request.post("v1/get", GetResult, data=input.
|
204
|
+
return await self.request.post("v1/get", GetResult, data=input.model_dump(exclude_none=True))
|
202
205
|
|
203
206
|
# List endpoint
|
204
207
|
async def list(
|
@@ -254,7 +257,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
254
257
|
)
|
255
258
|
"""
|
256
259
|
input = ListRequest(filter=filter, last=last, order=order, order_by=order_by, size=size)
|
257
|
-
return await self.request.post("v1/list", ListResult, data=input.
|
260
|
+
return await self.request.post("v1/list", ListResult, data=input.model_dump(exclude_none=True))
|
258
261
|
|
259
262
|
# Update endpoint
|
260
263
|
async def update(
|
@@ -335,7 +338,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
335
338
|
expiration=expiration,
|
336
339
|
item_state=item_state,
|
337
340
|
)
|
338
|
-
return await self.request.post("v1/update", UpdateResult, data=input.
|
341
|
+
return await self.request.post("v1/update", UpdateResult, data=input.model_dump(exclude_none=True))
|
339
342
|
|
340
343
|
async def secret_store(
|
341
344
|
self,
|
@@ -405,7 +408,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
405
408
|
rotation_state=rotation_state,
|
406
409
|
expiration=expiration,
|
407
410
|
)
|
408
|
-
return await self.request.post("v1/secret/store", SecretStoreResult, data=input.
|
411
|
+
return await self.request.post("v1/secret/store", SecretStoreResult, data=input.model_dump(exclude_none=True))
|
409
412
|
|
410
413
|
async def pangea_token_store(
|
411
414
|
self,
|
@@ -475,7 +478,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
475
478
|
rotation_state=rotation_state,
|
476
479
|
expiration=expiration,
|
477
480
|
)
|
478
|
-
return await self.request.post("v1/secret/store", SecretStoreResult, data=input.
|
481
|
+
return await self.request.post("v1/secret/store", SecretStoreResult, data=input.model_dump(exclude_none=True))
|
479
482
|
|
480
483
|
# Rotate endpoint
|
481
484
|
async def secret_rotate(
|
@@ -515,7 +518,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
515
518
|
)
|
516
519
|
"""
|
517
520
|
input = SecretRotateRequest(id=id, secret=secret, rotation_state=rotation_state)
|
518
|
-
return await self.request.post("v1/secret/rotate", SecretRotateResult, data=input.
|
521
|
+
return await self.request.post("v1/secret/rotate", SecretRotateResult, data=input.model_dump(exclude_none=True))
|
519
522
|
|
520
523
|
# Rotate endpoint
|
521
524
|
async def pangea_token_rotate(self, id: str) -> PangeaResponse[SecretRotateResult]:
|
@@ -543,7 +546,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
543
546
|
)
|
544
547
|
"""
|
545
548
|
input = SecretRotateRequest(id=id) # type: ignore[call-arg]
|
546
|
-
return await self.request.post("v1/secret/rotate", SecretRotateResult, data=input.
|
549
|
+
return await self.request.post("v1/secret/rotate", SecretRotateResult, data=input.model_dump(exclude_none=True))
|
547
550
|
|
548
551
|
async def symmetric_generate(
|
549
552
|
self,
|
@@ -556,6 +559,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
556
559
|
rotation_frequency: Optional[str] = None,
|
557
560
|
rotation_state: Optional[ItemVersionState] = None,
|
558
561
|
expiration: Optional[datetime.datetime] = None,
|
562
|
+
exportable: Optional[bool] = None,
|
559
563
|
) -> PangeaResponse[SymmetricGenerateResult]:
|
560
564
|
"""
|
561
565
|
Symmetric generate
|
@@ -577,6 +581,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
577
581
|
- `deactivated`
|
578
582
|
- `destroyed`
|
579
583
|
expiration (str, optional): Expiration timestamp
|
584
|
+
exportable (bool, optional): Whether the key is exportable or not
|
580
585
|
|
581
586
|
Raises:
|
582
587
|
PangeaAPIException: If an API Error happens
|
@@ -616,8 +621,11 @@ class VaultAsync(ServiceBaseAsync):
|
|
616
621
|
rotation_frequency=rotation_frequency,
|
617
622
|
rotation_state=rotation_state,
|
618
623
|
expiration=expiration,
|
624
|
+
exportable=exportable,
|
625
|
+
)
|
626
|
+
return await self.request.post(
|
627
|
+
"v1/key/generate", SymmetricGenerateResult, data=input.model_dump(exclude_none=True)
|
619
628
|
)
|
620
|
-
return await self.request.post("v1/key/generate", SymmetricGenerateResult, data=input.dict(exclude_none=True))
|
621
629
|
|
622
630
|
async def asymmetric_generate(
|
623
631
|
self,
|
@@ -630,6 +638,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
630
638
|
rotation_frequency: Optional[str] = None,
|
631
639
|
rotation_state: Optional[ItemVersionState] = None,
|
632
640
|
expiration: Optional[datetime.datetime] = None,
|
641
|
+
exportable: Optional[bool] = None,
|
633
642
|
) -> PangeaResponse[AsymmetricGenerateResult]:
|
634
643
|
"""
|
635
644
|
Asymmetric generate
|
@@ -651,6 +660,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
651
660
|
- `deactivated`
|
652
661
|
- `destroyed`
|
653
662
|
expiration (str, optional): Expiration timestamp
|
663
|
+
exportable (bool, optional): Whether the key is exportable or not
|
654
664
|
|
655
665
|
Raises:
|
656
666
|
PangeaAPIException: If an API Error happens
|
@@ -690,8 +700,11 @@ class VaultAsync(ServiceBaseAsync):
|
|
690
700
|
rotation_frequency=rotation_frequency,
|
691
701
|
rotation_state=rotation_state,
|
692
702
|
expiration=expiration,
|
703
|
+
exportable=exportable,
|
704
|
+
)
|
705
|
+
return await self.request.post(
|
706
|
+
"v1/key/generate", AsymmetricGenerateResult, data=input.model_dump(exclude_none=True)
|
693
707
|
)
|
694
|
-
return await self.request.post("v1/key/generate", AsymmetricGenerateResult, data=input.dict(exclude_none=True))
|
695
708
|
|
696
709
|
# Store endpoints
|
697
710
|
async def asymmetric_store(
|
@@ -707,6 +720,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
707
720
|
rotation_frequency: Optional[str] = None,
|
708
721
|
rotation_state: Optional[ItemVersionState] = None,
|
709
722
|
expiration: Optional[datetime.datetime] = None,
|
723
|
+
exportable: Optional[bool] = None,
|
710
724
|
) -> PangeaResponse[AsymmetricStoreResult]:
|
711
725
|
"""
|
712
726
|
Asymmetric store
|
@@ -730,6 +744,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
730
744
|
- `deactivated`
|
731
745
|
- `destroyed`
|
732
746
|
expiration (str, optional): Expiration timestamp
|
747
|
+
exportable (bool, optional): Whether the key is exportable or not
|
733
748
|
|
734
749
|
Raises:
|
735
750
|
PangeaAPIException: If an API Error happens
|
@@ -773,8 +788,9 @@ class VaultAsync(ServiceBaseAsync):
|
|
773
788
|
rotation_frequency=rotation_frequency,
|
774
789
|
rotation_state=rotation_state,
|
775
790
|
expiration=expiration,
|
791
|
+
exportable=exportable,
|
776
792
|
)
|
777
|
-
return await self.request.post("v1/key/store", AsymmetricStoreResult, data=input.
|
793
|
+
return await self.request.post("v1/key/store", AsymmetricStoreResult, data=input.model_dump(exclude_none=True))
|
778
794
|
|
779
795
|
async def symmetric_store(
|
780
796
|
self,
|
@@ -788,6 +804,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
788
804
|
rotation_frequency: Optional[str] = None,
|
789
805
|
rotation_state: Optional[ItemVersionState] = None,
|
790
806
|
expiration: Optional[datetime.datetime] = None,
|
807
|
+
exportable: Optional[bool] = None,
|
791
808
|
) -> PangeaResponse[SymmetricStoreResult]:
|
792
809
|
"""
|
793
810
|
Symmetric store
|
@@ -810,6 +827,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
810
827
|
- `deactivated`
|
811
828
|
- `destroyed`
|
812
829
|
expiration (str, optional): Expiration timestamp
|
830
|
+
exportable (bool, optional): Whether the key is exportable or not
|
813
831
|
|
814
832
|
Raises:
|
815
833
|
PangeaAPIException: If an API Error happens
|
@@ -851,8 +869,9 @@ class VaultAsync(ServiceBaseAsync):
|
|
851
869
|
rotation_frequency=rotation_frequency,
|
852
870
|
rotation_state=rotation_state,
|
853
871
|
expiration=expiration,
|
872
|
+
exportable=exportable,
|
854
873
|
)
|
855
|
-
return await self.request.post("v1/key/store", SymmetricStoreResult, data=input.
|
874
|
+
return await self.request.post("v1/key/store", SymmetricStoreResult, data=input.model_dump(exclude_none=True))
|
856
875
|
|
857
876
|
# Rotate endpoint
|
858
877
|
async def key_rotate(
|
@@ -901,7 +920,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
901
920
|
input = KeyRotateRequest(
|
902
921
|
id=id, public_key=public_key, private_key=private_key, key=key, rotation_state=rotation_state
|
903
922
|
)
|
904
|
-
return await self.request.post("v1/key/rotate", KeyRotateResult, data=input.
|
923
|
+
return await self.request.post("v1/key/rotate", KeyRotateResult, data=input.model_dump(exclude_none=True))
|
905
924
|
|
906
925
|
# Encrypt
|
907
926
|
async def encrypt(self, id: str, plain_text: str, version: Optional[int] = None) -> PangeaResponse[EncryptResult]:
|
@@ -932,8 +951,8 @@ class VaultAsync(ServiceBaseAsync):
|
|
932
951
|
version=1,
|
933
952
|
)
|
934
953
|
"""
|
935
|
-
input = EncryptRequest(id=id, plain_text=plain_text, version=version)
|
936
|
-
return await self.request.post("v1/key/encrypt", EncryptResult, data=input.
|
954
|
+
input = EncryptRequest(id=id, plain_text=plain_text, version=version)
|
955
|
+
return await self.request.post("v1/key/encrypt", EncryptResult, data=input.model_dump(exclude_none=True))
|
937
956
|
|
938
957
|
# Decrypt
|
939
958
|
async def decrypt(self, id: str, cipher_text: str, version: Optional[int] = None) -> PangeaResponse[DecryptResult]:
|
@@ -964,8 +983,8 @@ class VaultAsync(ServiceBaseAsync):
|
|
964
983
|
version=1,
|
965
984
|
)
|
966
985
|
"""
|
967
|
-
input = DecryptRequest(id=id, cipher_text=cipher_text, version=version)
|
968
|
-
return await self.request.post("v1/key/decrypt", DecryptResult, data=input.
|
986
|
+
input = DecryptRequest(id=id, cipher_text=cipher_text, version=version)
|
987
|
+
return await self.request.post("v1/key/decrypt", DecryptResult, data=input.model_dump(exclude_none=True))
|
969
988
|
|
970
989
|
# Sign
|
971
990
|
async def sign(self, id: str, message: str, version: Optional[int] = None) -> PangeaResponse[SignResult]:
|
@@ -997,7 +1016,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
997
1016
|
)
|
998
1017
|
"""
|
999
1018
|
input = SignRequest(id=id, message=message, version=version)
|
1000
|
-
return await self.request.post("v1/key/sign", SignResult, data=input.
|
1019
|
+
return await self.request.post("v1/key/sign", SignResult, data=input.model_dump(exclude_none=True))
|
1001
1020
|
|
1002
1021
|
# Verify
|
1003
1022
|
async def verify(
|
@@ -1038,7 +1057,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
1038
1057
|
signature=signature,
|
1039
1058
|
version=version,
|
1040
1059
|
)
|
1041
|
-
return await self.request.post("v1/key/verify", VerifyResult, data=input.
|
1060
|
+
return await self.request.post("v1/key/verify", VerifyResult, data=input.model_dump(exclude_none=True))
|
1042
1061
|
|
1043
1062
|
async def jwt_verify(self, jws: str) -> PangeaResponse[JWTVerifyResult]:
|
1044
1063
|
"""
|
@@ -1065,7 +1084,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
1065
1084
|
)
|
1066
1085
|
"""
|
1067
1086
|
input = JWTVerifyRequest(jws=jws)
|
1068
|
-
return await self.request.post("v1/key/verify/jwt", JWTVerifyResult, data=input.
|
1087
|
+
return await self.request.post("v1/key/verify/jwt", JWTVerifyResult, data=input.model_dump(exclude_none=True))
|
1069
1088
|
|
1070
1089
|
async def jwt_sign(self, id: str, payload: str) -> PangeaResponse[JWTSignResult]:
|
1071
1090
|
"""
|
@@ -1094,7 +1113,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
1094
1113
|
)
|
1095
1114
|
"""
|
1096
1115
|
input = JWTSignRequest(id=id, payload=payload)
|
1097
|
-
return await self.request.post("v1/key/sign/jwt", JWTSignResult, data=input.
|
1116
|
+
return await self.request.post("v1/key/sign/jwt", JWTSignResult, data=input.model_dump(exclude_none=True))
|
1098
1117
|
|
1099
1118
|
# Get endpoint
|
1100
1119
|
async def jwk_get(self, id: str, version: Optional[str] = None) -> PangeaResponse[JWKGetResult]:
|
@@ -1125,7 +1144,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
1125
1144
|
)
|
1126
1145
|
"""
|
1127
1146
|
input = JWKGetRequest(id=id, version=version)
|
1128
|
-
return await self.request.post("v1/get/jwk", JWKGetResult, data=input.
|
1147
|
+
return await self.request.post("v1/get/jwk", JWKGetResult, data=input.model_dump(exclude_none=True))
|
1129
1148
|
|
1130
1149
|
# State change
|
1131
1150
|
async def state_change(
|
@@ -1164,7 +1183,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
1164
1183
|
)
|
1165
1184
|
"""
|
1166
1185
|
input = StateChangeRequest(id=id, state=state, version=version, destroy_period=destroy_period)
|
1167
|
-
return await self.request.post("v1/state/change", StateChangeResult, data=input.
|
1186
|
+
return await self.request.post("v1/state/change", StateChangeResult, data=input.model_dump(exclude_none=True))
|
1168
1187
|
|
1169
1188
|
# Folder create
|
1170
1189
|
async def folder_create(
|
@@ -1201,7 +1220,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
1201
1220
|
)
|
1202
1221
|
"""
|
1203
1222
|
input = FolderCreateRequest(name=name, folder=folder, metadata=metadata, tags=tags)
|
1204
|
-
return await self.request.post("v1/folder/create", FolderCreateResult, data=input.
|
1223
|
+
return await self.request.post("v1/folder/create", FolderCreateResult, data=input.model_dump(exclude_none=True))
|
1205
1224
|
|
1206
1225
|
# Encrypt structured
|
1207
1226
|
async def encrypt_structured(
|
@@ -1249,7 +1268,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
1249
1268
|
return await self.request.post(
|
1250
1269
|
"v1/key/encrypt/structured",
|
1251
1270
|
EncryptStructuredResult,
|
1252
|
-
data=input.
|
1271
|
+
data=input.model_dump(exclude_none=True),
|
1253
1272
|
)
|
1254
1273
|
|
1255
1274
|
# Decrypt structured
|
@@ -1298,7 +1317,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
1298
1317
|
return await self.request.post(
|
1299
1318
|
"v1/key/decrypt/structured",
|
1300
1319
|
EncryptStructuredResult,
|
1301
|
-
data=input.
|
1320
|
+
data=input.model_dump(exclude_none=True),
|
1302
1321
|
)
|
1303
1322
|
|
1304
1323
|
async def encrypt_transform(
|
@@ -1348,7 +1367,7 @@ class VaultAsync(ServiceBaseAsync):
|
|
1348
1367
|
return await self.request.post(
|
1349
1368
|
"v1/key/encrypt/transform",
|
1350
1369
|
EncryptTransformResult,
|
1351
|
-
data=input.
|
1370
|
+
data=input.model_dump(exclude_none=True),
|
1352
1371
|
)
|
1353
1372
|
|
1354
1373
|
async def decrypt_transform(
|
@@ -1387,5 +1406,51 @@ class VaultAsync(ServiceBaseAsync):
|
|
1387
1406
|
return await self.request.post(
|
1388
1407
|
"v1/key/decrypt/transform",
|
1389
1408
|
DecryptTransformResult,
|
1390
|
-
data=input.
|
1409
|
+
data=input.model_dump(exclude_none=True),
|
1410
|
+
)
|
1411
|
+
|
1412
|
+
async def export(
|
1413
|
+
self,
|
1414
|
+
id: str,
|
1415
|
+
version: int | None = None,
|
1416
|
+
encryption_key: str | None = None,
|
1417
|
+
encryption_algorithm: ExportEncryptionAlgorithm | None = None,
|
1418
|
+
) -> PangeaResponse[ExportResult]:
|
1419
|
+
"""
|
1420
|
+
Export
|
1421
|
+
|
1422
|
+
Export a symmetric or asymmetric key.
|
1423
|
+
|
1424
|
+
OperationId: vault_post_v1_export
|
1425
|
+
|
1426
|
+
Args:
|
1427
|
+
id: The ID of the item.
|
1428
|
+
version: The item version.
|
1429
|
+
encryption_key: Public key in pem format used to encrypt exported key(s).
|
1430
|
+
encryption_algorithm: The algorithm of the public key.
|
1431
|
+
|
1432
|
+
Raises:
|
1433
|
+
PangeaAPIException: If an API error happens.
|
1434
|
+
|
1435
|
+
Returns:
|
1436
|
+
A `PangeaResponse` where the exported key is returned in the
|
1437
|
+
`response.result` field. Available response fields can be found in
|
1438
|
+
our [API documentation](https://pangea.cloud/docs/api/vault#export).
|
1439
|
+
|
1440
|
+
Examples:
|
1441
|
+
exp_encrypted_resp = await self.vault.export(
|
1442
|
+
id=id,
|
1443
|
+
version=1,
|
1444
|
+
encryption_key=rsa_pub_key_pem,
|
1445
|
+
encryption_algorithm=ExportEncryptionAlgorithm.RSA4096_OAEP_SHA512,
|
1446
|
+
)
|
1447
|
+
"""
|
1448
|
+
|
1449
|
+
input: ExportRequest = ExportRequest(
|
1450
|
+
id=id, version=version, encryption_algorithm=encryption_algorithm, encryption_key=encryption_key
|
1451
|
+
)
|
1452
|
+
return await self.request.post(
|
1453
|
+
"v1/export",
|
1454
|
+
ExportResult,
|
1455
|
+
data=input.model_dump(exclude_none=True),
|
1391
1456
|
)
|
pangea/crypto/rsa.py
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
4
|
+
from cryptography.hazmat.primitives.asymmetric import padding, rsa
|
5
|
+
|
6
|
+
|
7
|
+
def generate_key_pair() -> tuple[rsa.RSAPrivateKey, rsa.RSAPublicKey]:
|
8
|
+
# Generate a 4096-bit RSA key pair
|
9
|
+
private_key = rsa.generate_private_key(
|
10
|
+
public_exponent=65537,
|
11
|
+
key_size=4096,
|
12
|
+
)
|
13
|
+
|
14
|
+
# Extract the public key from the private key
|
15
|
+
public_key = private_key.public_key()
|
16
|
+
return private_key, public_key
|
17
|
+
|
18
|
+
|
19
|
+
def decrypt_sha512(private_key: rsa.RSAPrivateKey, encrypted_message: bytes) -> bytes:
|
20
|
+
# Decrypt the message using the private key and OAEP padding
|
21
|
+
return private_key.decrypt(
|
22
|
+
encrypted_message,
|
23
|
+
padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA512()), algorithm=hashes.SHA512(), label=None),
|
24
|
+
)
|
25
|
+
|
26
|
+
|
27
|
+
def encrypt_sha512(public_key: rsa.RSAPublicKey, message: bytes) -> bytes:
|
28
|
+
# Encrypt the message using the public key and OAEP padding
|
29
|
+
return public_key.encrypt(
|
30
|
+
message, padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA512()), algorithm=hashes.SHA512(), label=None)
|
31
|
+
)
|
32
|
+
|
33
|
+
|
34
|
+
def private_key_to_pem(private_key: rsa.RSAPrivateKey) -> bytes:
|
35
|
+
# Serialize private key to PEM format
|
36
|
+
return private_key.private_bytes(
|
37
|
+
encoding=serialization.Encoding.PEM,
|
38
|
+
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
39
|
+
encryption_algorithm=serialization.NoEncryption(),
|
40
|
+
)
|
41
|
+
|
42
|
+
|
43
|
+
def public_key_to_pem(public_key: rsa.RSAPublicKey) -> bytes:
|
44
|
+
# Serialize public key to PEM format
|
45
|
+
return public_key.public_bytes(
|
46
|
+
encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo
|
47
|
+
)
|
pangea/dump_audit.py
CHANGED
@@ -19,7 +19,7 @@ from pangea.utils import default_encoder
|
|
19
19
|
|
20
20
|
|
21
21
|
def dump_event(output: io.TextIOWrapper, row: SearchEvent, resp: PangeaResponse[SearchOutput]):
|
22
|
-
row_data = filter_deep_none(row.
|
22
|
+
row_data = filter_deep_none(row.model_dump())
|
23
23
|
if resp.result and resp.result.root:
|
24
24
|
row_data["tree_size"] = resp.result.root.size
|
25
25
|
output.write(json.dumps(row_data, default=default_encoder) + "\n")
|