pangea-sdk 3.9.0__py3-none-any.whl → 4.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.
- 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 +32 -28
- 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/common.py +6 -6
- pangea/services/vault/models/symmetric.py +2 -2
- pangea/services/vault/vault.py +28 -28
- pangea/utils.py +1 -18
- pangea/verify_audit.py +267 -83
- {pangea_sdk-3.9.0.dist-info → pangea_sdk-4.0.0.dist-info}/METADATA +10 -9
- pangea_sdk-4.0.0.dist-info/RECORD +46 -0
- {pangea_sdk-3.9.0.dist-info → pangea_sdk-4.0.0.dist-info}/WHEEL +1 -1
- pangea_sdk-3.9.0.dist-info/RECORD +0 -46
pangea/verify_audit.py
CHANGED
@@ -12,9 +12,16 @@ In the latter case, all the events are verified.
|
|
12
12
|
import argparse
|
13
13
|
import json
|
14
14
|
import logging
|
15
|
+
import os
|
15
16
|
import sys
|
16
|
-
from
|
17
|
-
|
17
|
+
from collections.abc import Set
|
18
|
+
from enum import Enum
|
19
|
+
from typing import Dict, Iterable, List, Optional, Union
|
20
|
+
|
21
|
+
from pangea.config import PangeaConfig
|
22
|
+
from pangea.exceptions import TreeNotFoundException
|
23
|
+
from pangea.services import Audit
|
24
|
+
from pangea.services.audit.models import PublishedRoot, Root
|
18
25
|
from pangea.services.audit.signing import Verifier
|
19
26
|
from pangea.services.audit.util import (
|
20
27
|
canonicalize_json,
|
@@ -29,7 +36,16 @@ from pangea.services.audit.util import (
|
|
29
36
|
)
|
30
37
|
|
31
38
|
logger = logging.getLogger("audit")
|
32
|
-
|
39
|
+
arweave_roots: Dict[int, PublishedRoot] = {} # roots fetched from Arweave
|
40
|
+
pangea_roots: Dict[int, Root] = {} # roots fetched from Pangea
|
41
|
+
audit: Optional[Audit] = None
|
42
|
+
|
43
|
+
|
44
|
+
class Status(Enum):
|
45
|
+
SUCCEEDED = "succeeded"
|
46
|
+
SUCCEEDED_PANGEA = "succeeded_pangea" # succeeded with data fetched from Pangea instead of Arweave
|
47
|
+
FAILED = "failed"
|
48
|
+
SKIPPED = "skipped"
|
33
49
|
|
34
50
|
|
35
51
|
class VerifierLogFormatter(logging.Formatter):
|
@@ -40,9 +56,11 @@ class VerifierLogFormatter(logging.Formatter):
|
|
40
56
|
|
41
57
|
def format(self, record):
|
42
58
|
if hasattr(record, "is_result"):
|
43
|
-
if record.
|
59
|
+
if record.status == Status.SUCCEEDED:
|
44
60
|
point = "🟢"
|
45
|
-
elif record.
|
61
|
+
elif record.status == Status.SUCCEEDED_PANGEA:
|
62
|
+
point = "🟡"
|
63
|
+
elif record.status == Status.SKIPPED:
|
46
64
|
point = "⚪️"
|
47
65
|
else:
|
48
66
|
point = "🔴"
|
@@ -61,14 +79,16 @@ class VerifierLogFormatter(logging.Formatter):
|
|
61
79
|
return f"{pre}{record.msg}"
|
62
80
|
|
63
81
|
|
64
|
-
def log_result(msg: str,
|
65
|
-
if
|
82
|
+
def log_result(msg: str, status: Status):
|
83
|
+
if status == Status.SUCCEEDED:
|
66
84
|
msg += " succeeded"
|
67
|
-
elif
|
85
|
+
elif status == Status.SUCCEEDED_PANGEA:
|
86
|
+
msg += " succeeded (with data fetched from Pangea)"
|
87
|
+
elif status == Status.FAILED:
|
68
88
|
msg += " failed"
|
69
89
|
else:
|
70
|
-
msg += "
|
71
|
-
logger.log(logging.INFO, msg, extra={"is_result": True, "
|
90
|
+
msg += " skipped"
|
91
|
+
logger.log(logging.INFO, msg, extra={"is_result": True, "status": status})
|
72
92
|
|
73
93
|
|
74
94
|
def log_section(msg: str):
|
@@ -77,9 +97,37 @@ def log_section(msg: str):
|
|
77
97
|
|
78
98
|
formatter = VerifierLogFormatter()
|
79
99
|
|
100
|
+
InvalidTokenError = ValueError("Invalid Pangea Token provided")
|
101
|
+
|
102
|
+
|
103
|
+
def get_pangea_roots(tree_name: str, tree_sizes: Iterable[int]) -> Dict[int, Root]:
|
104
|
+
ans: Dict[int, Root] = {}
|
105
|
+
if audit is None:
|
106
|
+
return ans
|
107
|
+
|
108
|
+
for size in tree_sizes:
|
109
|
+
try:
|
110
|
+
resp = audit.root(size)
|
111
|
+
|
112
|
+
if resp.status != "Success":
|
113
|
+
raise ValueError(resp.status)
|
114
|
+
elif resp.result is None or resp.result.data is None:
|
115
|
+
raise ValueError("No result")
|
116
|
+
elif resp.result.data.tree_name != tree_name:
|
117
|
+
raise InvalidTokenError
|
118
|
+
|
119
|
+
ans[int(size)] = resp.result.data
|
120
|
+
except TreeNotFoundException as e:
|
121
|
+
ex = InvalidTokenError
|
122
|
+
logger.error(f"Error fetching root from Pangea for size {size}: {str(ex)}")
|
123
|
+
except Exception as e:
|
124
|
+
logger.error(f"Error fetching root from Pangea for size {size}: {str(e)}")
|
125
|
+
return ans
|
126
|
+
|
80
127
|
|
81
|
-
def _verify_hash(data: Dict, data_hash: str) ->
|
128
|
+
def _verify_hash(data: Dict, data_hash: str) -> Status:
|
82
129
|
log_section("Checking data hash")
|
130
|
+
status = Status.SKIPPED
|
83
131
|
try:
|
84
132
|
logger.debug("Canonicalizing data")
|
85
133
|
data_canon = canonicalize_json(data)
|
@@ -89,22 +137,20 @@ def _verify_hash(data: Dict, data_hash: str) -> Optional[bool]:
|
|
89
137
|
logger.debug("Comparing calculated hash with server hash")
|
90
138
|
if computed_hash_dec != data_hash_dec:
|
91
139
|
raise ValueError("Hash does not match")
|
92
|
-
|
140
|
+
status = Status.SUCCEEDED
|
93
141
|
except Exception:
|
94
|
-
|
142
|
+
status = Status.FAILED
|
95
143
|
|
96
|
-
log_result("Data hash verification",
|
144
|
+
log_result("Data hash verification", status)
|
97
145
|
logger.info("")
|
98
|
-
return
|
146
|
+
return status
|
99
147
|
|
100
148
|
|
101
|
-
def _verify_unpublished_membership_proof(root_hash, node_hash: str, proof: Optional[str]) ->
|
102
|
-
global pub_roots
|
103
|
-
|
149
|
+
def _verify_unpublished_membership_proof(root_hash, node_hash: str, proof: Optional[str]) -> Status:
|
104
150
|
log_section("Checking unpublished membership proof")
|
105
151
|
|
106
152
|
if proof is None:
|
107
|
-
|
153
|
+
status = Status.SKIPPED
|
108
154
|
logger.debug("Proof not found")
|
109
155
|
else:
|
110
156
|
try:
|
@@ -116,94 +162,209 @@ def _verify_unpublished_membership_proof(root_hash, node_hash: str, proof: Optio
|
|
116
162
|
proof_dec = decode_membership_proof(proof)
|
117
163
|
|
118
164
|
logger.debug("Comparing the unpublished root hash with the proof hash")
|
119
|
-
|
165
|
+
if verify_membership_proof(node_hash_dec, root_hash_dec, proof_dec):
|
166
|
+
status = Status.SUCCEEDED
|
167
|
+
else:
|
168
|
+
status = Status.FAILED
|
120
169
|
|
121
170
|
except Exception as e:
|
122
|
-
|
171
|
+
status = Status.FAILED
|
123
172
|
logger.debug(str(e))
|
124
173
|
|
125
|
-
log_result("Unpublished membership proof verification",
|
174
|
+
log_result("Unpublished membership proof verification", status)
|
175
|
+
logger.info("")
|
176
|
+
return status
|
177
|
+
|
178
|
+
|
179
|
+
def _fetch_roots(tree_name: str, tree_size: int, leaf_index: Optional[int]) -> Status:
|
180
|
+
global arweave_roots, pangea_roots
|
181
|
+
|
182
|
+
log_section("Fetching published roots")
|
183
|
+
|
184
|
+
needed_roots = {tree_size}
|
185
|
+
if leaf_index:
|
186
|
+
needed_roots |= {leaf_index, leaf_index + 1}
|
187
|
+
|
188
|
+
pending_roots = set()
|
189
|
+
|
190
|
+
def update_pending_roots():
|
191
|
+
nonlocal pending_roots
|
192
|
+
pending_roots = needed_roots - set(arweave_roots.keys()) - set(pangea_roots.keys())
|
193
|
+
|
194
|
+
def comma_sep(s: Set):
|
195
|
+
return ",".join(map(str, s))
|
196
|
+
|
197
|
+
status = Status.SUCCEEDED
|
198
|
+
update_pending_roots()
|
199
|
+
|
200
|
+
# print message for roots already fetched
|
201
|
+
arweave_fetched_roots = needed_roots & set(arweave_roots.keys())
|
202
|
+
if arweave_fetched_roots:
|
203
|
+
logger.debug(f"Roots {comma_sep(arweave_fetched_roots)} already fetched from Arweave")
|
204
|
+
|
205
|
+
pangea_fetched_roots = needed_roots & set(pangea_roots.keys())
|
206
|
+
if pangea_fetched_roots:
|
207
|
+
logger.debug(f"Roots {comma_sep(pangea_fetched_roots)} already fetched from Pangea")
|
208
|
+
status = Status.SUCCEEDED_PANGEA
|
209
|
+
|
210
|
+
if pending_roots:
|
211
|
+
# try Arweave first
|
212
|
+
try:
|
213
|
+
logger.debug(f"Fetching root(s) {comma_sep(pending_roots)} from Arweave")
|
214
|
+
arweave_roots |= {int(k): v for k, v in get_arweave_published_roots(tree_name, pending_roots).items()} # type: ignore[operator]
|
215
|
+
update_pending_roots()
|
216
|
+
except:
|
217
|
+
pass
|
218
|
+
|
219
|
+
if pending_roots:
|
220
|
+
logger.debug(f"Published root(s) {comma_sep(pending_roots)} could not be fetched from Arweave")
|
221
|
+
|
222
|
+
if pending_roots:
|
223
|
+
if audit:
|
224
|
+
# and then Pangea (if we've set an audit client)
|
225
|
+
try:
|
226
|
+
logger.debug(f"Fetching root(s) {comma_sep(pending_roots)} from Pangea")
|
227
|
+
pangea_roots |= {int(k): v for k, v in get_pangea_roots(tree_name, pending_roots).items()} # type: ignore[operator]
|
228
|
+
update_pending_roots()
|
229
|
+
status = Status.SUCCEEDED_PANGEA
|
230
|
+
except:
|
231
|
+
pass
|
232
|
+
|
233
|
+
if pending_roots:
|
234
|
+
logger.debug(f"Roots {comma_sep(pending_roots)} could not be fetched")
|
235
|
+
else:
|
236
|
+
logger.debug("Set Pangea token and domain (from envvars or script parameters) to fetch roots from Pangea")
|
237
|
+
|
238
|
+
if pending_roots:
|
239
|
+
status = Status.FAILED
|
240
|
+
|
241
|
+
log_result("Fetching published roots", status)
|
126
242
|
logger.info("")
|
127
|
-
return
|
243
|
+
return status
|
128
244
|
|
129
245
|
|
130
|
-
def _verify_membership_proof(
|
131
|
-
|
246
|
+
def _verify_membership_proof(tree_size: int, node_hash: str, proof: Optional[str]) -> Status:
|
247
|
+
pub_roots: Dict[int, Union[Root, PublishedRoot]] = arweave_roots | pangea_roots # type: ignore[operator]
|
132
248
|
|
133
249
|
log_section("Checking membership proof")
|
134
250
|
|
135
|
-
if
|
136
|
-
|
251
|
+
if tree_size not in pub_roots:
|
252
|
+
status = Status.SKIPPED
|
253
|
+
logger.debug("Published root not found")
|
254
|
+
elif proof is None:
|
255
|
+
status = Status.SKIPPED
|
137
256
|
logger.debug("Proof not found (event not published yet)")
|
138
257
|
else:
|
139
258
|
try:
|
140
|
-
|
141
|
-
if tree_size not in pub_roots:
|
142
|
-
pub_roots |= {int(k): v for k, v in get_arweave_published_roots(tree_name, [tree_size]).items()} # type: ignore[operator]
|
143
|
-
if tree_size not in pub_roots:
|
144
|
-
raise ValueError("Published root could was not found")
|
145
|
-
|
146
|
-
root_hash_dec = decode_hash(pub_roots[tree_size].root_hash) # type: ignore[attr-defined]
|
259
|
+
root_hash_dec = decode_hash(pub_roots[tree_size].root_hash)
|
147
260
|
node_hash_dec = decode_hash(node_hash)
|
148
261
|
logger.debug("Calculating the proof")
|
262
|
+
if proof is None:
|
263
|
+
logger.debug("Consistency proof is missing")
|
264
|
+
return False
|
149
265
|
proof_dec = decode_membership_proof(proof)
|
150
266
|
logger.debug("Comparing the root hash with the proof hash")
|
151
|
-
|
267
|
+
if verify_membership_proof(node_hash_dec, root_hash_dec, proof_dec):
|
268
|
+
status = Status.SUCCEEDED
|
269
|
+
else:
|
270
|
+
status = Status.FAILED
|
152
271
|
except Exception as e:
|
153
|
-
|
272
|
+
status = Status.FAILED
|
154
273
|
logger.debug(str(e))
|
155
274
|
|
156
|
-
log_result("Membership proof verification",
|
275
|
+
log_result("Membership proof verification", status)
|
157
276
|
logger.info("")
|
158
|
-
return
|
277
|
+
return status
|
278
|
+
|
279
|
+
|
280
|
+
def _consistency_proof_ok(pub_roots: Dict[int, Union[Root, PublishedRoot]], leaf_index: int) -> bool:
|
281
|
+
"""returns true if a consistency proof is correct"""
|
282
|
+
|
283
|
+
curr_root = pub_roots[leaf_index + 1]
|
284
|
+
prev_root = pub_roots[leaf_index]
|
285
|
+
curr_root_hash = decode_hash(curr_root.root_hash)
|
286
|
+
prev_root_hash = decode_hash(prev_root.root_hash)
|
287
|
+
if curr_root.consistency_proof is None:
|
288
|
+
logger.debug("Consistency proof is missing")
|
289
|
+
return False
|
159
290
|
|
291
|
+
logger.debug("Calculating the proof")
|
292
|
+
proof = decode_consistency_proof(curr_root.consistency_proof)
|
293
|
+
return verify_consistency_proof(curr_root_hash, prev_root_hash, proof)
|
294
|
+
|
295
|
+
|
296
|
+
# Due to an (already fixed) bug, some proofs from Arweave may be wrong.
|
297
|
+
# Try the proof from Pangea instead. If the root hash in both Arweave and Pangea is the same,
|
298
|
+
# it doesn't matter where the proof came from.
|
299
|
+
def _fix_consistency_proof(pub_roots: Dict[int, Union[Root, PublishedRoot]], tree_name: str, leaf_index: int):
|
300
|
+
logger.debug("Consistency proof from Arweave failed to verify")
|
301
|
+
size = leaf_index + 1
|
302
|
+
logger.debug(f"Fetching root from Pangea for size {size}")
|
303
|
+
new_roots = get_pangea_roots(tree_name, [size])
|
304
|
+
if size not in new_roots:
|
305
|
+
raise ValueError("Error fetching root from Pangea")
|
306
|
+
pangea_roots[size] = new_roots[size]
|
307
|
+
pub_roots[size] = pangea_roots[size]
|
308
|
+
logger.debug(f"Comparing Arweave root hash with Pangea root hash")
|
309
|
+
if pangea_roots[size].root_hash != arweave_roots[size].root_hash:
|
310
|
+
raise ValueError("Hash does not match")
|
311
|
+
|
312
|
+
|
313
|
+
def _verify_consistency_proof(tree_name: str, leaf_index: Optional[int]) -> Status:
|
314
|
+
pub_roots: Dict[int, Union[Root, PublishedRoot]] = arweave_roots | pangea_roots # type: ignore[operator]
|
160
315
|
|
161
|
-
def _verify_consistency_proof(tree_name: str, leaf_index: Optional[int]) -> Optional[bool]:
|
162
|
-
global pub_roots
|
163
316
|
log_section("Checking consistency proof")
|
164
317
|
|
165
318
|
if leaf_index is None:
|
166
|
-
|
319
|
+
status = Status.SKIPPED
|
167
320
|
logger.debug("Proof not found (event was not published yet)")
|
168
321
|
|
169
322
|
elif leaf_index == 0:
|
170
|
-
|
323
|
+
status = Status.SKIPPED
|
171
324
|
logger.debug("Proof not found (event was published in the first leaf)")
|
325
|
+
|
326
|
+
elif leaf_index not in pub_roots:
|
327
|
+
status = Status.SKIPPED
|
328
|
+
logger.debug("Published root not found")
|
329
|
+
|
172
330
|
else:
|
173
331
|
try:
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
332
|
+
if _consistency_proof_ok(pub_roots, leaf_index):
|
333
|
+
status = Status.SUCCEEDED
|
334
|
+
|
335
|
+
elif audit:
|
336
|
+
_fix_consistency_proof(pub_roots, tree_name, leaf_index)
|
337
|
+
|
338
|
+
# check again
|
339
|
+
if _consistency_proof_ok(pub_roots, leaf_index):
|
340
|
+
status = Status.SUCCEEDED
|
341
|
+
else:
|
342
|
+
status = Status.FAILED
|
343
|
+
|
344
|
+
else:
|
345
|
+
logger.debug(
|
346
|
+
"Set Pangea token and domain (from envvars or script parameters) to fetch roots from Pangea"
|
347
|
+
)
|
348
|
+
status = Status.FAILED
|
188
349
|
|
189
350
|
except Exception as e:
|
190
|
-
|
351
|
+
status = Status.FAILED
|
191
352
|
logger.debug(str(e))
|
192
353
|
|
193
|
-
log_result("Consistency proof verification",
|
354
|
+
log_result("Consistency proof verification", status)
|
194
355
|
logger.info("")
|
195
|
-
return
|
356
|
+
return status
|
196
357
|
|
197
358
|
|
198
359
|
def create_signed_event(event: Dict) -> Dict:
|
199
360
|
return {k: v for k, v in event.items() if v is not None}
|
200
361
|
|
201
362
|
|
202
|
-
def _verify_signature(data: Dict) ->
|
363
|
+
def _verify_signature(data: Dict) -> Status:
|
203
364
|
log_section("Checking signature")
|
204
365
|
if "signature" not in data:
|
205
366
|
logger.debug("Signature is not present")
|
206
|
-
|
367
|
+
status = Status.SKIPPED
|
207
368
|
else:
|
208
369
|
try:
|
209
370
|
logger.debug("Obtaining signature and public key from the event")
|
@@ -213,30 +374,36 @@ def _verify_signature(data: Dict) -> Optional[bool]:
|
|
213
374
|
logger.debug("Checking the signature")
|
214
375
|
if not sign_verifier.verify_signature(data["signature"], canonicalize_json(sign_event), public_key):
|
215
376
|
raise ValueError("Signature is invalid")
|
216
|
-
|
377
|
+
status = Status.SUCCEEDED
|
217
378
|
except Exception:
|
218
|
-
|
379
|
+
status = Status.FAILED
|
219
380
|
|
220
|
-
log_result("Data signature verification",
|
381
|
+
log_result("Data signature verification", status)
|
221
382
|
logger.info("")
|
222
|
-
return
|
383
|
+
return status
|
223
384
|
|
224
385
|
|
225
|
-
def verify_multiple(root: Dict, unpublished_root: Dict, events: List[Dict]) ->
|
386
|
+
def verify_multiple(root: Dict, unpublished_root: Dict, events: List[Dict]) -> Status:
|
226
387
|
"""
|
227
388
|
Verify a list of events.
|
228
389
|
Returns a status.
|
229
390
|
"""
|
230
391
|
|
231
|
-
|
392
|
+
statuses: List[Status] = []
|
232
393
|
for counter, event in enumerate(events):
|
233
394
|
event.update({"root": root, "unpublished_root": unpublished_root})
|
234
|
-
|
235
|
-
|
236
|
-
return not any(event_succeeded is False for event_succeeded in succeeded)
|
395
|
+
event_status = verify_single(event, counter + 1)
|
396
|
+
statuses.append(event_status)
|
237
397
|
|
398
|
+
for event_status in statuses:
|
399
|
+
if event_status == Status.FAILED:
|
400
|
+
return Status.FAILED
|
401
|
+
elif event_status == Status.SKIPPED:
|
402
|
+
return Status.SKIPPED
|
403
|
+
return Status.SUCCEEDED
|
238
404
|
|
239
|
-
|
405
|
+
|
406
|
+
def verify_single(data: Dict, counter: Optional[int] = None) -> Status:
|
240
407
|
"""
|
241
408
|
Verify a single event.
|
242
409
|
Returns a status.
|
@@ -248,11 +415,12 @@ def verify_single(data: Dict, counter: Optional[int] = None) -> Optional[bool]:
|
|
248
415
|
ok_hash = _verify_hash(data["envelope"], data["hash"])
|
249
416
|
ok_signature = _verify_signature(data["envelope"])
|
250
417
|
|
418
|
+
ok_roots = _fetch_roots(data["root"]["tree_name"], data["root"]["size"], data.get("leaf_index"))
|
419
|
+
|
251
420
|
if data["published"]:
|
252
421
|
if not data.get("root"):
|
253
422
|
raise ValueError("Missing 'root' element")
|
254
423
|
ok_membership = _verify_membership_proof(
|
255
|
-
data["root"]["tree_name"],
|
256
424
|
data["root"]["size"],
|
257
425
|
data["hash"],
|
258
426
|
data.get("membership_proof"),
|
@@ -267,25 +435,31 @@ def verify_single(data: Dict, counter: Optional[int] = None) -> Optional[bool]:
|
|
267
435
|
if data["published"]:
|
268
436
|
ok_consistency = _verify_consistency_proof(data["root"]["tree_name"], data.get("leaf_index"))
|
269
437
|
else:
|
270
|
-
ok_consistency =
|
438
|
+
ok_consistency = Status.SUCCEEDED
|
271
439
|
|
272
440
|
all_ok = (
|
273
|
-
ok_hash
|
274
|
-
and (ok_signature
|
275
|
-
and
|
276
|
-
and
|
441
|
+
ok_hash == Status.SUCCEEDED
|
442
|
+
and (ok_signature in (Status.SUCCEEDED, Status.SKIPPED))
|
443
|
+
and (ok_roots in (Status.SUCCEEDED, Status.SUCCEEDED_PANGEA))
|
444
|
+
and ok_membership == Status.SUCCEEDED
|
445
|
+
and (ok_consistency in (Status.SUCCEEDED, Status.SKIPPED))
|
446
|
+
)
|
447
|
+
any_failed = (
|
448
|
+
ok_hash == Status.FAILED
|
449
|
+
or (ok_signature == Status.FAILED)
|
450
|
+
or (ok_membership == Status.FAILED)
|
451
|
+
or (ok_consistency == Status.FAILED)
|
277
452
|
)
|
278
|
-
any_failed = ok_hash is False or ok_signature is False or ok_membership is False or ok_consistency is False
|
279
453
|
|
280
454
|
if counter:
|
281
455
|
formatter.indent = 0
|
282
456
|
|
283
457
|
if all_ok:
|
284
|
-
return
|
458
|
+
return Status.SUCCEEDED
|
285
459
|
elif any_failed:
|
286
|
-
return
|
460
|
+
return Status.FAILED
|
287
461
|
else:
|
288
|
-
return
|
462
|
+
return Status.SKIPPED
|
289
463
|
|
290
464
|
|
291
465
|
def main():
|
@@ -303,6 +477,12 @@ def main():
|
|
303
477
|
metavar="PATH",
|
304
478
|
help="Input file (default: standard input).",
|
305
479
|
)
|
480
|
+
parser.add_argument(
|
481
|
+
"--token", "-t", default=os.getenv("PANGEA_TOKEN"), help="Pangea token (default: env PANGEA_TOKEN)"
|
482
|
+
)
|
483
|
+
parser.add_argument(
|
484
|
+
"--domain", "-d", default=os.getenv("PANGEA_DOMAIN"), help="Pangea domain (default: env PANGEA_DOMAIN)"
|
485
|
+
)
|
306
486
|
args = parser.parse_args()
|
307
487
|
|
308
488
|
data = json.load(args.file)
|
@@ -311,15 +491,19 @@ def main():
|
|
311
491
|
logger.info("Pangea Audit - Verification Tool")
|
312
492
|
logger.info("")
|
313
493
|
|
494
|
+
if args.token and args.domain:
|
495
|
+
global audit
|
496
|
+
audit = Audit(token=args.token, config=PangeaConfig(domain=args.domain))
|
497
|
+
|
314
498
|
if events:
|
315
499
|
status = verify_multiple(data["result"].get("root"), data["result"].get("unpublished_root"), events)
|
316
500
|
else:
|
317
501
|
status = verify_single(data)
|
318
502
|
|
319
503
|
logger.info("")
|
320
|
-
if status
|
504
|
+
if status == Status.SUCCEEDED:
|
321
505
|
logger.info("🟢 Verification succeeded 🟢")
|
322
|
-
elif status
|
506
|
+
elif status == Status.FAILED:
|
323
507
|
logger.info("🔴 Verification failed 🔴")
|
324
508
|
else:
|
325
509
|
logger.info("⚪️ Verification could not be finished ⚪️")
|
@@ -1,31 +1,32 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: pangea-sdk
|
3
|
-
Version:
|
3
|
+
Version: 4.0.0
|
4
4
|
Summary: Pangea API SDK
|
5
5
|
Home-page: https://pangea.cloud/docs/sdk/python/
|
6
6
|
License: MIT
|
7
7
|
Keywords: Pangea,SDK,Audit
|
8
8
|
Author: Glenn Gallien
|
9
9
|
Author-email: glenn.gallien@pangea.cloud
|
10
|
-
Requires-Python: >=3.
|
10
|
+
Requires-Python: >=3.8.0,<4.0.0
|
11
11
|
Classifier: License :: OSI Approved :: MIT License
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
13
13
|
Classifier: Programming Language :: Python :: 3.8
|
14
14
|
Classifier: Programming Language :: Python :: 3.9
|
15
15
|
Classifier: Programming Language :: Python :: 3.10
|
16
16
|
Classifier: Programming Language :: Python :: 3.11
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
17
18
|
Classifier: Topic :: Software Development
|
18
19
|
Classifier: Topic :: Software Development :: Libraries
|
19
|
-
Requires-Dist: aiohttp (>=3.
|
20
|
+
Requires-Dist: aiohttp (>=3.9.3,<4.0.0)
|
20
21
|
Requires-Dist: asyncio (>=3.4.3,<4.0.0)
|
21
|
-
Requires-Dist: cryptography (>=42.0.
|
22
|
+
Requires-Dist: cryptography (>=42.0.8,<43.0.0)
|
22
23
|
Requires-Dist: deprecated (>=1.2.14,<2.0.0)
|
23
24
|
Requires-Dist: google-crc32c (>=1.5.0,<2.0.0)
|
24
|
-
Requires-Dist: pydantic (>=
|
25
|
+
Requires-Dist: pydantic (>=2.7.4,<3.0.0)
|
25
26
|
Requires-Dist: python-dateutil (>=2.9.0,<3.0.0)
|
26
27
|
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
27
28
|
Requires-Dist: requests-toolbelt (>=1.0.0,<2.0.0)
|
28
|
-
Requires-Dist: typing-extensions (>=4.
|
29
|
+
Requires-Dist: typing-extensions (>=4.12.2,<5.0.0)
|
29
30
|
Project-URL: Repository, https://github.com/pangeacyber/pangea-python/tree/main/packages/pangea-sdk
|
30
31
|
Description-Content-Type: text/markdown
|
31
32
|
|
@@ -36,11 +37,11 @@ Description-Content-Type: text/markdown
|
|
36
37
|
<br />
|
37
38
|
|
38
39
|
[][Documentation]
|
39
|
-
[][Discourse]
|
40
41
|
|
41
42
|
# Pangea Python SDK
|
42
43
|
|
43
|
-
A Python SDK for integrating with Pangea services. Supports Python v3.
|
44
|
+
A Python SDK for integrating with Pangea services. Supports Python v3.8 and
|
44
45
|
above.
|
45
46
|
|
46
47
|
## Installation
|
@@ -231,6 +232,6 @@ It accepts multiple file formats:
|
|
231
232
|
[GA Examples]: https://github.com/pangeacyber/pangea-python/tree/main/examples
|
232
233
|
[Beta Examples]: https://github.com/pangeacyber/pangea-python/tree/beta/examples
|
233
234
|
[Pangea Console]: https://console.pangea.cloud/
|
234
|
-
[
|
235
|
+
[Discourse]: https://l.pangea.cloud/Jd4wlGs
|
235
236
|
[Secure Audit Log]: https://pangea.cloud/docs/audit
|
236
237
|
|
@@ -0,0 +1,46 @@
|
|
1
|
+
pangea/__init__.py,sha256=xX-TnwsJ5recSAa_KWNsLdPa7CEaOzFbap_GzsZ2Gv4,200
|
2
|
+
pangea/asyncio/request.py,sha256=ysCT-SB1nkAr64O6JkI64xZt-Za1TQGt8Jp9gfqeLxE,17077
|
3
|
+
pangea/asyncio/services/__init__.py,sha256=hmySN2LoXYpHzSKLVSdVjMbpGXi5Z5iNx-fJ2Fc8W6I,320
|
4
|
+
pangea/asyncio/services/audit.py,sha256=bZ7gdkVWkzqLqUVc1Wnf3oDAaCLg97-zTWhY8UdX0_Y,26549
|
5
|
+
pangea/asyncio/services/authn.py,sha256=3NUiw1I47fWzjc99pr_boQiHY-sMdkJ-wdOoNWgH4rY,45909
|
6
|
+
pangea/asyncio/services/authz.py,sha256=DcPn5FpdWMunim6Qtsk2vFLXyUbFb3lgl1XpX3w0hew,9176
|
7
|
+
pangea/asyncio/services/base.py,sha256=4FtKtlq74NmE9myrgIt9HMA6JDnP4mPZ6krafWr286o,2663
|
8
|
+
pangea/asyncio/services/embargo.py,sha256=ctzj3kip6xos-Eu3JuOskrCGYC8T3JlsgAopZHiPSXM,3068
|
9
|
+
pangea/asyncio/services/file_scan.py,sha256=MSNyRdyEfNiYglfxvEohOYO0kyo-FuSJRVxvgeYQ4l4,6287
|
10
|
+
pangea/asyncio/services/intel.py,sha256=ph-Kor2CyKWtVaYVx3OD7pRjb4MGCHrZnrgqMkfMu4I,37977
|
11
|
+
pangea/asyncio/services/redact.py,sha256=jRNtXr_DZ_cY7guhut-eZmOEhy2uN_VCXrjGH6bkh74,7265
|
12
|
+
pangea/asyncio/services/vault.py,sha256=6gmWgFKjZklTlqTxMqepyeUkvHxxwE4qolSwF2p9Vt8,51045
|
13
|
+
pangea/audit_logger.py,sha256=gRkCfUUT5LDNaycwxkhZUySgY47jDfn1ZeKOul4XCQI,3842
|
14
|
+
pangea/config.py,sha256=mQUu8GX_6weIuv3vjNdG5plppXskXYASmxMWtFQh-hc,1662
|
15
|
+
pangea/deep_verify.py,sha256=mocaGbC6XLbMTVWxTpMv4oJtXGPWpT-SbFqT3obpiZs,8443
|
16
|
+
pangea/deprecated.py,sha256=IjFYEVvY1E0ld0SMkEYC1o62MAleX3nnT1If2dFVbHo,608
|
17
|
+
pangea/dump_audit.py,sha256=1Je8D2fXwU4PWcZ-ZD4icfO3DNFvWqJkwsac4qFEhOo,7025
|
18
|
+
pangea/exceptions.py,sha256=OBtzUECpNa6vNp8ySkHC-tm4QjFRCOAHBkMHqzAlOu8,5656
|
19
|
+
pangea/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
|
+
pangea/request.py,sha256=ADMbnSlU-vrQQTlDp6w0b352ZDA_t4srZAihjaciQ5E,24285
|
21
|
+
pangea/response.py,sha256=w-jTsCiNxDVt_qP27DQXbtqxPucSOAAYVlI7CnA4aiU,6597
|
22
|
+
pangea/services/__init__.py,sha256=iAIa1kk_C0EHBsSn2XP3QT-bOZNGwMBPUcO2JoI-9cE,278
|
23
|
+
pangea/services/audit/audit.py,sha256=IFv7jANA8S2SypQVS47x94_Cr5Z9zSsL9Dp9eXw9RHk,39593
|
24
|
+
pangea/services/audit/exceptions.py,sha256=bhVuYe4ammacOVxwg98CChxvwZf5FKgR2DcgqILOcwc,471
|
25
|
+
pangea/services/audit/models.py,sha256=1h1B9eSYQMYG3f8WNi1UcDX2-impRrET_ErjJYUnj7M,14678
|
26
|
+
pangea/services/audit/signing.py,sha256=pOjw60BIYDcg3_5YKDCMWZUaapsEZpCHaFhyFu7TEgc,5567
|
27
|
+
pangea/services/audit/util.py,sha256=Zq1qvfeplYfhCP_ud5YMvntSB0UvnCdsuYbOzZkHbjg,7620
|
28
|
+
pangea/services/authn/authn.py,sha256=H0mzbAuZlcQUINktLGrLTD2aygRfrkQqzTN7oFo2o8o,44740
|
29
|
+
pangea/services/authn/models.py,sha256=y4N-rbJzIUCga_xvqCdIUkfIheRy9gVRd6BtMYn60Lg,19995
|
30
|
+
pangea/services/authz.py,sha256=kgJ4iWytqm2PqLB2n2AQy1lHR8TI8tdYBuOYAfY0fY0,11818
|
31
|
+
pangea/services/base.py,sha256=lwhHoe5Juy28Ir3Mfj2lHdM58gxZRaxa2SRFi4_DBRw,3453
|
32
|
+
pangea/services/embargo.py,sha256=9Wfku4td5ORaIENKmnGmS5jxJJIRfWp6Q51L36Jsy0I,3897
|
33
|
+
pangea/services/file_scan.py,sha256=lWXiD4lHpagQ7O-nJeqwRQJKyP4BtauUdU19HZ3zcQM,6954
|
34
|
+
pangea/services/intel.py,sha256=H7mo5r5pUDuYFphG4eWfI86twsOMAM3Qlf6dsAZ8UKM,51793
|
35
|
+
pangea/services/redact.py,sha256=ZYXkzEoriLJyCqaj5dqmgsC56mIz4T3pPToZ7TcNfhg,11465
|
36
|
+
pangea/services/vault/models/asymmetric.py,sha256=ac2Exc66elXxO-HxBqtvLPQWNI7y_00kb6SVqBPKecA,1450
|
37
|
+
pangea/services/vault/models/common.py,sha256=CL3DCNi9pM_ROFDxrXFkTe94RFZB0M1EEqi_Rs_Ptj0,13987
|
38
|
+
pangea/services/vault/models/secret.py,sha256=cLgEj-_BeGkB4-pmSeTkWVyasFbaJwcEltIEcOyf1U8,481
|
39
|
+
pangea/services/vault/models/symmetric.py,sha256=HEdBIihLvlCepdWRD5HPsyXSYcItCsqPSywR6YHLA0s,1344
|
40
|
+
pangea/services/vault/vault.py,sha256=r7eUrnNQCtan1oU-H9kldFDOMldMFYEDhaPTJRb7Tnw,50748
|
41
|
+
pangea/tools.py,sha256=sa2pSz-L8tB6GcZg6lghsmm8w0qMQAIkzqcv7dilU6Q,6429
|
42
|
+
pangea/utils.py,sha256=KaKHWM9Os6IDwyRW1LUij2JLtZ0AQPn6-M5J_Psz6JI,3042
|
43
|
+
pangea/verify_audit.py,sha256=Pu4jW-hgGUciMNs_42hAaPUpEr79xM54Rf0JLjELQWw,17369
|
44
|
+
pangea_sdk-4.0.0.dist-info/METADATA,sha256=xRnXCNSbbnF0sWqfVdxV-lFIrO637f4scM10tNtGNpg,7532
|
45
|
+
pangea_sdk-4.0.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
46
|
+
pangea_sdk-4.0.0.dist-info/RECORD,,
|