Compare full native bucket policies independent of array order
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a17f16-d99c-7ea3-95f0-8212897fba6b
This commit is contained in:
parent
61a82ac74c
commit
ccc0f8b578
2 changed files with 25 additions and 2 deletions
|
|
@ -97,6 +97,17 @@ def denied(call):
|
|||
raise Refused("native_boundary_unexpectedly_allowed")
|
||||
|
||||
|
||||
def canonical_policy(value):
|
||||
# Policy array ordering is not significant. Scaleway sorts actions and
|
||||
# principal arrays on write. Preserve every value (including duplicates)
|
||||
# and compare all fields; never discard unknown permission/condition data.
|
||||
if isinstance(value, dict):
|
||||
return {k: canonical_policy(v) for k, v in value.items()}
|
||||
if isinstance(value, list):
|
||||
return sorted((canonical_policy(v) for v in value), key=lambda v: json.dumps(v, sort_keys=True))
|
||||
return value
|
||||
|
||||
|
||||
def policy(bucket, prefix, apps, owner):
|
||||
def statement(sid, principal, actions, resources, condition=None):
|
||||
value = {"Sid": sid, "Effect": "Allow", "Principal": {"SCW": principal},
|
||||
|
|
@ -200,13 +211,13 @@ class Resources:
|
|||
if status(error) != 404:
|
||||
raise
|
||||
live = None
|
||||
if live is not None and live != intended:
|
||||
if live is not None and canonical_policy(live) != canonical_policy(intended):
|
||||
raise Refused("existing_policy_drift_requires_review")
|
||||
if live is None:
|
||||
self.mutate("put_policy:" + bucket, lambda: client.put_bucket_policy(
|
||||
Bucket=bucket, Policy=json.dumps(intended)))
|
||||
self.save()
|
||||
if json.loads(client.get_bucket_policy(Bucket=bucket)["Policy"]) != intended:
|
||||
if canonical_policy(json.loads(client.get_bucket_policy(Bucket=bucket)["Policy"])) != canonical_policy(intended):
|
||||
raise Refused("policy_readback_differs")
|
||||
if client.get_bucket_versioning(Bucket=bucket).get("Status") != "Enabled":
|
||||
self.mutate("versioning:" + bucket, lambda: client.put_bucket_versioning(
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import copy
|
||||
import importlib.util
|
||||
import sys
|
||||
import unittest
|
||||
|
|
@ -69,6 +70,17 @@ class ResourcesTests(unittest.TestCase):
|
|||
{"application_id:" + self.apps[r] for r in ("reader", "custodian", "publisher")})
|
||||
self.assertTrue(all(a.startswith("s3:Get") and "Object" not in a for a in shared["Action"]))
|
||||
|
||||
def test_provider_policy_reordering_allowed_but_extra_permission_refused(self):
|
||||
original = m.policy("b", "archives", self.apps, "o")
|
||||
returned = copy.deepcopy(original)
|
||||
returned["Statement"].reverse()
|
||||
for row in returned["Statement"]:
|
||||
if isinstance(row["Action"], list):
|
||||
row["Action"].reverse()
|
||||
self.assertEqual(m.canonical_policy(original), m.canonical_policy(returned))
|
||||
returned["Statement"][0]["Action"].append("s3:DeleteObject")
|
||||
self.assertNotEqual(m.canonical_policy(original), m.canonical_policy(returned))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue