iam-policy-validator 1.10.0__py3-none-any.whl → 1.10.2__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.
- {iam_policy_validator-1.10.0.dist-info → iam_policy_validator-1.10.2.dist-info}/METADATA +1 -1
- {iam_policy_validator-1.10.0.dist-info → iam_policy_validator-1.10.2.dist-info}/RECORD +10 -10
- iam_validator/__version__.py +2 -2
- iam_validator/core/aws_service/fetcher.py +24 -7
- iam_validator/core/aws_service/validators.py +8 -7
- iam_validator/core/models.py +6 -6
- iam_validator/sdk/policy_utils.py +37 -11
- {iam_policy_validator-1.10.0.dist-info → iam_policy_validator-1.10.2.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.10.0.dist-info → iam_policy_validator-1.10.2.dist-info}/entry_points.txt +0 -0
- {iam_policy_validator-1.10.0.dist-info → iam_policy_validator-1.10.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iam-policy-validator
|
|
3
|
-
Version: 1.10.
|
|
3
|
+
Version: 1.10.2
|
|
4
4
|
Summary: Validate AWS IAM policies for correctness and security using AWS Service Reference API
|
|
5
5
|
Project-URL: Homepage, https://github.com/boogy/iam-policy-validator
|
|
6
6
|
Project-URL: Documentation, https://github.com/boogy/iam-policy-validator/tree/main/docs
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
iam_validator/__init__.py,sha256=xHdUASOxFHwEXfT_GSr_KrkLlnxZ-pAAr1wW1PwAGko,693
|
|
2
2
|
iam_validator/__main__.py,sha256=to_nz3n_IerJpVVZZ6WSFlFR5s_06J0csfPOTfQZG8g,197
|
|
3
|
-
iam_validator/__version__.py,sha256=
|
|
3
|
+
iam_validator/__version__.py,sha256=Fz08JonmD1dXoibF83ZYkSo1e0IWJO9aJ4iIAfq64mI,374
|
|
4
4
|
iam_validator/checks/__init__.py,sha256=OTkPnmlelu4YjMO8krjhu2wXiTV72RzopA5u1SfPQA0,1990
|
|
5
5
|
iam_validator/checks/action_condition_enforcement.py,sha256=0dCH_xX-Xc0uLxtNeRjrpNjWYbdWQRzO1XNcLTSn6sI,51698
|
|
6
6
|
iam_validator/checks/action_resource_matching.py,sha256=WiGJmCIJfx5yituMjZxpKmk-99N6nK20ueN02ddy9oM,19296
|
|
@@ -42,7 +42,7 @@ iam_validator/core/condition_validators.py,sha256=7zBjlcf2xGFKGbcFrXSLvWT5tFhWxo
|
|
|
42
42
|
iam_validator/core/constants.py,sha256=cVBPgbXr4ALltH_NTSKsgBi6wmndLnOyUWhyBx0ZwrM,6113
|
|
43
43
|
iam_validator/core/ignore_patterns.py,sha256=pZqDJBtkbck-85QK5eFPM5ZOPEKs3McRh3avqiCT5z0,10398
|
|
44
44
|
iam_validator/core/label_manager.py,sha256=48CRASWg98wyjfVF_1pUzj6dm9itzmG7SeIWf0TSUfc,7502
|
|
45
|
-
iam_validator/core/models.py,sha256=
|
|
45
|
+
iam_validator/core/models.py,sha256=yQ5iBTffdAzx88h8RyVCCmBg6kkD2zg5_lb-qLdjy3w,13386
|
|
46
46
|
iam_validator/core/policy_checks.py,sha256=FNVuS2GTffwCjjrlupVIazC172gSxKYAAT_ObV6Apbo,8803
|
|
47
47
|
iam_validator/core/policy_loader.py,sha256=2KJnXzGg3g9pDXWZHk3DO0xpZnZZ-wXWFEOdQ_naJ8s,17862
|
|
48
48
|
iam_validator/core/pr_commenter.py,sha256=NTKoSmjvspYX2rbl3Xn8d611XkTNSfYlGUY0zBHBP4g,16801
|
|
@@ -50,11 +50,11 @@ iam_validator/core/report.py,sha256=kzSeWnT1LqWZVA5pqKKz-maVowXVj0djdoShfRhhpz4,
|
|
|
50
50
|
iam_validator/core/aws_service/__init__.py,sha256=UqMh4HUdGlx2QF5OoueJJ2UlCnhX4QW_x3KeE_bxRQc,735
|
|
51
51
|
iam_validator/core/aws_service/cache.py,sha256=DPuOOPPJC867KAYgV1e0RyQs_k3mtefMdYli3jPaN64,3589
|
|
52
52
|
iam_validator/core/aws_service/client.py,sha256=Zv7rIpEFdUCDXKGp3migPDkj8L5eZltgrGe64M2t2Ko,7336
|
|
53
|
-
iam_validator/core/aws_service/fetcher.py,sha256=
|
|
53
|
+
iam_validator/core/aws_service/fetcher.py,sha256=s_o8h6ua9gPdiV9-ElNs7YY0HlyoP0Ewtl71hTrhsZA,23340
|
|
54
54
|
iam_validator/core/aws_service/parsers.py,sha256=gJzR7HCD8ItCWCCbguTQIZpPEdj2rdMwC7LPhu7ve14,5174
|
|
55
55
|
iam_validator/core/aws_service/patterns.py,sha256=gGc55Tn-EJ3cmcWtmYAZROUajKYz7DaMchYWGEhHpC0,1726
|
|
56
56
|
iam_validator/core/aws_service/storage.py,sha256=PrfKdvF60IL7E_8xYs_XwFoAJPRcVYw57FVLHCoqwVk,10429
|
|
57
|
-
iam_validator/core/aws_service/validators.py,sha256=
|
|
57
|
+
iam_validator/core/aws_service/validators.py,sha256=L9XRJdGmR-vZ1r0bj5SCznULyKEY_G1OAjij7-kOZPM,16463
|
|
58
58
|
iam_validator/core/config/__init__.py,sha256=CWSyIA7kEyzrskEenjYbs9Iih10BXRpiY9H2dHg61rU,2671
|
|
59
59
|
iam_validator/core/config/aws_api.py,sha256=HLIzOItQ0A37wxHcgWck6ZFO0wmNY8JNTiWMMK6JKYU,1248
|
|
60
60
|
iam_validator/core/config/aws_global_conditions.py,sha256=gdmMxXGBy95B3uYUG-J7rnM6Ixgc6L7Y9Pcd2XAMb60,7170
|
|
@@ -83,14 +83,14 @@ iam_validator/sdk/arn_matching.py,sha256=HSDpLltOYISq-SoPebAlM89mKOaUaghq_04urch
|
|
|
83
83
|
iam_validator/sdk/context.py,sha256=FvAEyUa_s7tHWoSdgjSkzHf1CLlYpAEmLZANxs2IJ4A,6826
|
|
84
84
|
iam_validator/sdk/exceptions.py,sha256=tm91TxIwU157U_UHN7w5qICf_OhU11agj6pV5W_YP-4,1023
|
|
85
85
|
iam_validator/sdk/helpers.py,sha256=sjfK0na_Fo7O8GhEVhl44rVHqOdw6nAKkBL4FVL-QdU,5697
|
|
86
|
-
iam_validator/sdk/policy_utils.py,sha256=
|
|
86
|
+
iam_validator/sdk/policy_utils.py,sha256=bGdJ1X1aC72dVXXpAnAwyBpAiiX-qXvblpetY5BsjKU,13658
|
|
87
87
|
iam_validator/sdk/shortcuts.py,sha256=EVNSYV7rv4TFH03ulsZ3mS1UVmTSp2jKpc2AXs4j1q4,8531
|
|
88
88
|
iam_validator/utils/__init__.py,sha256=NveA2F3G1E6-ANZzFr7J6Q6u5mogvMp862iFokmYuCs,1021
|
|
89
89
|
iam_validator/utils/cache.py,sha256=wOQKOBeoG6QqC5f0oXcHz63Cjtu_-SsSS-0pTSwyAiM,3254
|
|
90
90
|
iam_validator/utils/regex.py,sha256=xHoMECttb7qaMhts-c9b0GIxdhHNZTt-UBr7wNhWfzg,6219
|
|
91
91
|
iam_validator/utils/terminal.py,sha256=FsRaRMH_JAyDgXWBCOgOEhbS89cs17HCmKYoughq5io,724
|
|
92
|
-
iam_policy_validator-1.10.
|
|
93
|
-
iam_policy_validator-1.10.
|
|
94
|
-
iam_policy_validator-1.10.
|
|
95
|
-
iam_policy_validator-1.10.
|
|
96
|
-
iam_policy_validator-1.10.
|
|
92
|
+
iam_policy_validator-1.10.2.dist-info/METADATA,sha256=t5vzMawKVACC8uFWNJznjMBvw4xX9T7z1EduP8HbAQA,19070
|
|
93
|
+
iam_policy_validator-1.10.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
94
|
+
iam_policy_validator-1.10.2.dist-info/entry_points.txt,sha256=8HtWd8O7mvPiPdZR5YbzY8or_qcqLM4-pKaFdhtFT8M,62
|
|
95
|
+
iam_policy_validator-1.10.2.dist-info/licenses/LICENSE,sha256=AMnbFTBDcK4_MITe2wiQBkj0vg-jjBBhsc43ydC7tt4,1098
|
|
96
|
+
iam_policy_validator-1.10.2.dist-info/RECORD,,
|
iam_validator/__version__.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
This file is the single source of truth for the package version.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
__version__ = "1.10.
|
|
6
|
+
__version__ = "1.10.2"
|
|
7
7
|
# Parse version, handling pre-release suffixes like -rc, -alpha, -beta
|
|
8
|
-
_version_base = __version__.split("-")[0] # Remove pre-release suffix if present
|
|
8
|
+
_version_base = __version__.split("-", maxsplit=1)[0] # Remove pre-release suffix if present
|
|
9
9
|
__version_info__ = tuple(int(part) for part in _version_base.split("."))
|
|
@@ -233,8 +233,16 @@ class AWSServiceFetcher:
|
|
|
233
233
|
await self._cache.set(services_cache_key, loaded_services)
|
|
234
234
|
return loaded_services
|
|
235
235
|
|
|
236
|
-
# Not in parsed cache,
|
|
237
|
-
data = await self.
|
|
236
|
+
# Not in parsed cache, check disk cache then fetch from API
|
|
237
|
+
data = await self._cache.get(
|
|
238
|
+
f"raw:{self.BASE_URL}", url=self.BASE_URL, base_url=self.BASE_URL
|
|
239
|
+
)
|
|
240
|
+
if data is None:
|
|
241
|
+
data = await self._client.fetch(self.BASE_URL)
|
|
242
|
+
# Cache the raw data
|
|
243
|
+
await self._cache.set(
|
|
244
|
+
f"raw:{self.BASE_URL}", data, url=self.BASE_URL, base_url=self.BASE_URL
|
|
245
|
+
)
|
|
238
246
|
|
|
239
247
|
if not isinstance(data, list):
|
|
240
248
|
raise ValueError("Expected list of services from root endpoint")
|
|
@@ -247,7 +255,7 @@ class AWSServiceFetcher:
|
|
|
247
255
|
if service and url:
|
|
248
256
|
services.append(ServiceInfo(service=str(service), url=str(url)))
|
|
249
257
|
|
|
250
|
-
# Cache the parsed services list (memory only
|
|
258
|
+
# Cache the parsed services list (memory only)
|
|
251
259
|
await self._cache.set(services_cache_key, services)
|
|
252
260
|
|
|
253
261
|
# Log only on first fetch (when parsed cache was empty)
|
|
@@ -312,13 +320,22 @@ class AWSServiceFetcher:
|
|
|
312
320
|
|
|
313
321
|
for service in services:
|
|
314
322
|
if service.service.lower() == service_name_lower:
|
|
315
|
-
#
|
|
316
|
-
data = await self.
|
|
323
|
+
# Check disk cache first, then fetch from API
|
|
324
|
+
data = await self._cache.get(
|
|
325
|
+
f"raw:{service.url}", url=service.url, base_url=self.BASE_URL
|
|
326
|
+
)
|
|
327
|
+
if data is None:
|
|
328
|
+
# Fetch service detail from API
|
|
329
|
+
data = await self._client.fetch(service.url)
|
|
330
|
+
# Cache the raw data
|
|
331
|
+
await self._cache.set(
|
|
332
|
+
f"raw:{service.url}", data, url=service.url, base_url=self.BASE_URL
|
|
333
|
+
)
|
|
317
334
|
|
|
318
335
|
# Validate and parse
|
|
319
336
|
service_detail = ServiceDetail.model_validate(data)
|
|
320
337
|
|
|
321
|
-
# Cache with service name as key (memory only
|
|
338
|
+
# Cache with service name as key (memory only)
|
|
322
339
|
await self._cache.set(cache_key, service_detail)
|
|
323
340
|
|
|
324
341
|
return service_detail
|
|
@@ -550,7 +567,7 @@ class AWSServiceFetcher:
|
|
|
550
567
|
if action_pattern in ("*", "*:*"):
|
|
551
568
|
return ["*"]
|
|
552
569
|
|
|
553
|
-
service_prefix,
|
|
570
|
+
service_prefix, _ = self._parser.parse_action(action_pattern)
|
|
554
571
|
service_detail = await self.fetch_service_by_name(service_prefix)
|
|
555
572
|
available = list(service_detail.actions.keys())
|
|
556
573
|
return self._parser.expand_wildcard_to_actions(action_pattern, available, service_prefix)
|
|
@@ -94,9 +94,7 @@ class ServiceValidator:
|
|
|
94
94
|
if not allow_wildcards:
|
|
95
95
|
return False, "Wildcard actions are not allowed", True
|
|
96
96
|
|
|
97
|
-
has_matches,
|
|
98
|
-
action_name, available_actions
|
|
99
|
-
)
|
|
97
|
+
has_matches, _ = self._parser.match_wildcard_action(action_name, available_actions)
|
|
100
98
|
|
|
101
99
|
if has_matches:
|
|
102
100
|
# Wildcard is valid and matches at least one action
|
|
@@ -161,7 +159,7 @@ class ServiceValidator:
|
|
|
161
159
|
get_global_conditions,
|
|
162
160
|
)
|
|
163
161
|
|
|
164
|
-
|
|
162
|
+
_, action_name = self._parser.parse_action(action)
|
|
165
163
|
|
|
166
164
|
# Check if it's a global condition key
|
|
167
165
|
is_global_key = False
|
|
@@ -280,9 +278,12 @@ class ServiceValidator:
|
|
|
280
278
|
"- `aws:RequestedRegion`\n"
|
|
281
279
|
"- `aws:SourceIp`\n"
|
|
282
280
|
"- `aws:SourceVpce`\n"
|
|
283
|
-
"- `aws:
|
|
281
|
+
"- `aws:ResourceOrgID`\n"
|
|
282
|
+
"- `aws:PrincipalOrgID`\n"
|
|
283
|
+
"- `aws:SourceAccount`\n"
|
|
284
|
+
"- `aws:PrincipalAccount`\n"
|
|
284
285
|
"- `aws:CurrentTime`\n"
|
|
285
|
-
"- `aws:
|
|
286
|
+
"- `aws:ResourceAccount`\n"
|
|
286
287
|
"- `aws:PrincipalArn`\n"
|
|
287
288
|
"- And many others"
|
|
288
289
|
)
|
|
@@ -320,7 +321,7 @@ class ServiceValidator:
|
|
|
320
321
|
>>> resources = validator.get_resources_for_action("s3:GetObject", service)
|
|
321
322
|
"""
|
|
322
323
|
try:
|
|
323
|
-
_, action_name = self._parser.parse_action(action)
|
|
324
|
+
_, action_name = self._parser.parse_action(action) # pylint: disable=unused-variable
|
|
324
325
|
|
|
325
326
|
# Find the action (case-insensitive)
|
|
326
327
|
action_detail = service_detail.actions.get(action_name)
|
iam_validator/core/models.py
CHANGED
|
@@ -31,7 +31,7 @@ class ServiceInfo(BaseModel):
|
|
|
31
31
|
class ActionDetail(BaseModel):
|
|
32
32
|
"""Details about an AWS IAM action."""
|
|
33
33
|
|
|
34
|
-
model_config = ConfigDict(
|
|
34
|
+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
|
|
35
35
|
|
|
36
36
|
name: str = Field(alias="Name")
|
|
37
37
|
action_condition_keys: list[str] | None = Field(
|
|
@@ -45,7 +45,7 @@ class ActionDetail(BaseModel):
|
|
|
45
45
|
class ResourceType(BaseModel):
|
|
46
46
|
"""Details about an AWS resource type."""
|
|
47
47
|
|
|
48
|
-
model_config = ConfigDict(
|
|
48
|
+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
|
|
49
49
|
|
|
50
50
|
name: str = Field(alias="Name")
|
|
51
51
|
arn_formats: list[str] | None = Field(default=None, alias="ARNFormats")
|
|
@@ -68,7 +68,7 @@ class ResourceType(BaseModel):
|
|
|
68
68
|
class ConditionKey(BaseModel):
|
|
69
69
|
"""Details about an AWS condition key."""
|
|
70
70
|
|
|
71
|
-
model_config = ConfigDict(
|
|
71
|
+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
|
|
72
72
|
|
|
73
73
|
name: str = Field(alias="Name")
|
|
74
74
|
description: str | None = Field(default=None, alias="Description")
|
|
@@ -78,7 +78,7 @@ class ConditionKey(BaseModel):
|
|
|
78
78
|
class ServiceDetail(BaseModel):
|
|
79
79
|
"""Detailed information about an AWS service."""
|
|
80
80
|
|
|
81
|
-
model_config = ConfigDict(
|
|
81
|
+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
|
|
82
82
|
|
|
83
83
|
name: str = Field(alias="Name")
|
|
84
84
|
prefix: str | None = None # Not always present in API response
|
|
@@ -106,7 +106,7 @@ class ServiceDetail(BaseModel):
|
|
|
106
106
|
class Statement(BaseModel):
|
|
107
107
|
"""IAM policy statement."""
|
|
108
108
|
|
|
109
|
-
model_config = ConfigDict(
|
|
109
|
+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True, extra="allow")
|
|
110
110
|
|
|
111
111
|
sid: str | None = Field(default=None, alias="Sid")
|
|
112
112
|
effect: str | None = Field(default=None, alias="Effect")
|
|
@@ -136,7 +136,7 @@ class Statement(BaseModel):
|
|
|
136
136
|
class IAMPolicy(BaseModel):
|
|
137
137
|
"""IAM policy document."""
|
|
138
138
|
|
|
139
|
-
model_config = ConfigDict(
|
|
139
|
+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True, extra="allow")
|
|
140
140
|
|
|
141
141
|
version: str | None = Field(default=None, alias="Version")
|
|
142
142
|
statement: list[Statement] | None = Field(default=None, alias="Statement")
|
|
@@ -63,9 +63,13 @@ def normalize_policy(policy: IAMPolicy) -> IAMPolicy:
|
|
|
63
63
|
"""
|
|
64
64
|
# Pydantic model already handles this via Field(alias="Statement")
|
|
65
65
|
# which expects a list, but we can ensure it's always a list
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
)
|
|
66
|
+
if policy.statement is None:
|
|
67
|
+
statements: list[Statement] = []
|
|
68
|
+
elif isinstance(policy.statement, list):
|
|
69
|
+
statements = policy.statement
|
|
70
|
+
else:
|
|
71
|
+
# Single statement - wrap in list
|
|
72
|
+
statements = [policy.statement]
|
|
69
73
|
|
|
70
74
|
# Normalize actions and resources in each statement
|
|
71
75
|
normalized_statements: list[Statement] = []
|
|
@@ -118,6 +122,9 @@ def extract_actions(policy: IAMPolicy) -> list[str]:
|
|
|
118
122
|
"""
|
|
119
123
|
actions = set()
|
|
120
124
|
|
|
125
|
+
if policy.statement is None:
|
|
126
|
+
return []
|
|
127
|
+
|
|
121
128
|
for stmt in policy.statement:
|
|
122
129
|
# Handle Action field
|
|
123
130
|
if stmt.action:
|
|
@@ -150,6 +157,9 @@ def extract_resources(policy: IAMPolicy) -> list[str]:
|
|
|
150
157
|
"""
|
|
151
158
|
resources = set()
|
|
152
159
|
|
|
160
|
+
if policy.statement is None:
|
|
161
|
+
return []
|
|
162
|
+
|
|
153
163
|
for stmt in policy.statement:
|
|
154
164
|
# Handle Resource field
|
|
155
165
|
if stmt.resource:
|
|
@@ -181,12 +191,15 @@ def extract_condition_keys(policy: IAMPolicy) -> list[str]:
|
|
|
181
191
|
>>> keys = extract_condition_keys(policy)
|
|
182
192
|
>>> print(f"Policy uses condition keys: {', '.join(keys)}")
|
|
183
193
|
"""
|
|
184
|
-
condition_keys = set()
|
|
194
|
+
condition_keys: set[str] = set()
|
|
195
|
+
|
|
196
|
+
if policy.statement is None:
|
|
197
|
+
return []
|
|
185
198
|
|
|
186
199
|
for stmt in policy.statement:
|
|
187
200
|
if stmt.condition:
|
|
188
201
|
# Condition format: {"StringEquals": {"aws:username": "johndoe"}}
|
|
189
|
-
for
|
|
202
|
+
for _, key_values in stmt.condition.items():
|
|
190
203
|
if isinstance(key_values, dict):
|
|
191
204
|
condition_keys.update(key_values.keys())
|
|
192
205
|
|
|
@@ -212,10 +225,13 @@ def find_statements_with_action(policy: IAMPolicy, action: str) -> list[Statemen
|
|
|
212
225
|
>>> for stmt in stmts:
|
|
213
226
|
... print(f"Statement {stmt.sid} allows s3:GetObject")
|
|
214
227
|
"""
|
|
215
|
-
import fnmatch
|
|
228
|
+
import fnmatch # pylint: disable=import-outside-toplevel
|
|
216
229
|
|
|
217
230
|
matching_statements = []
|
|
218
231
|
|
|
232
|
+
if policy.statement is None:
|
|
233
|
+
return []
|
|
234
|
+
|
|
219
235
|
for stmt in policy.statement:
|
|
220
236
|
stmt_actions = stmt.get_actions()
|
|
221
237
|
|
|
@@ -246,10 +262,13 @@ def find_statements_with_resource(policy: IAMPolicy, resource: str) -> list[Stat
|
|
|
246
262
|
>>> stmts = find_statements_with_resource(policy, "arn:aws:s3:::my-bucket/*")
|
|
247
263
|
>>> print(f"Found {len(stmts)} statements with this resource")
|
|
248
264
|
"""
|
|
249
|
-
import fnmatch
|
|
265
|
+
import fnmatch # pylint: disable=import-outside-toplevel
|
|
250
266
|
|
|
251
267
|
matching_statements = []
|
|
252
268
|
|
|
269
|
+
if policy.statement is None:
|
|
270
|
+
return []
|
|
271
|
+
|
|
253
272
|
for stmt in policy.statement:
|
|
254
273
|
stmt_resources = stmt.get_resources()
|
|
255
274
|
|
|
@@ -286,7 +305,8 @@ def merge_policies(*policies: IAMPolicy) -> IAMPolicy:
|
|
|
286
305
|
|
|
287
306
|
all_statements: list[Statement] = []
|
|
288
307
|
for policy in policies:
|
|
289
|
-
|
|
308
|
+
if policy.statement is not None:
|
|
309
|
+
all_statements.extend(policy.statement)
|
|
290
310
|
|
|
291
311
|
# Use capitalized field names (aliases) for Pydantic model construction
|
|
292
312
|
return IAMPolicy(
|
|
@@ -318,8 +338,9 @@ def get_policy_summary(policy: IAMPolicy) -> dict[str, Any]:
|
|
|
318
338
|
condition_keys = extract_condition_keys(policy)
|
|
319
339
|
|
|
320
340
|
# Count allow vs deny statements
|
|
321
|
-
|
|
322
|
-
|
|
341
|
+
statements = policy.statement or []
|
|
342
|
+
allow_count = sum(1 for s in statements if s.effect and s.effect.lower() == "allow")
|
|
343
|
+
deny_count = sum(1 for s in statements if s.effect and s.effect.lower() == "deny")
|
|
323
344
|
|
|
324
345
|
# Check for wildcards
|
|
325
346
|
has_wildcard_actions = any("*" in action for action in actions)
|
|
@@ -327,7 +348,7 @@ def get_policy_summary(policy: IAMPolicy) -> dict[str, Any]:
|
|
|
327
348
|
|
|
328
349
|
return {
|
|
329
350
|
"version": policy.version,
|
|
330
|
-
"statement_count": len(
|
|
351
|
+
"statement_count": len(statements),
|
|
331
352
|
"allow_statements": allow_count,
|
|
332
353
|
"deny_statements": deny_count,
|
|
333
354
|
"action_count": len(actions),
|
|
@@ -396,6 +417,8 @@ def is_resource_policy(policy: IAMPolicy) -> bool:
|
|
|
396
417
|
>>> if is_resource_policy(policy):
|
|
397
418
|
... print("This is an S3 bucket policy or similar")
|
|
398
419
|
"""
|
|
420
|
+
if policy.statement is None:
|
|
421
|
+
return False
|
|
399
422
|
return any(stmt.principal is not None for stmt in policy.statement)
|
|
400
423
|
|
|
401
424
|
|
|
@@ -414,6 +437,9 @@ def has_public_access(policy: IAMPolicy) -> bool:
|
|
|
414
437
|
>>> if has_public_access(policy):
|
|
415
438
|
... print("WARNING: This policy allows public access!")
|
|
416
439
|
"""
|
|
440
|
+
if policy.statement is None:
|
|
441
|
+
return False
|
|
442
|
+
|
|
417
443
|
for stmt in policy.statement:
|
|
418
444
|
if stmt.principal == "*":
|
|
419
445
|
return True
|
|
File without changes
|
{iam_policy_validator-1.10.0.dist-info → iam_policy_validator-1.10.2.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{iam_policy_validator-1.10.0.dist-info → iam_policy_validator-1.10.2.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|