iam-policy-validator 1.10.0__py3-none-any.whl → 1.10.1__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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iam-policy-validator
3
- Version: 1.10.0
3
+ Version: 1.10.1
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=FvUNUrZJC1gRVPHFBok-yxk6rVZGHH_JjnMPCje10kg,362
3
+ iam_validator/__version__.py,sha256=SRRdMee5oIovcL25-xNSjSxoHM9bzGQhwmx_U4YhnXQ,362
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=f5d9ovtO1xMSwhyBrKIgc2psEq0eugnd3S3ioqurqEE,13242
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
@@ -54,7 +54,7 @@ iam_validator/core/aws_service/fetcher.py,sha256=X4iI6fiLj4l9f3W6_J0E58lSP26UsBh
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=rgCScqEjXNH8xNg2R91eJbb4eIV3jZN7a6VW0n0hgA4,16347
57
+ iam_validator/core/aws_service/validators.py,sha256=AY0BjydskXoesEzUShH4gZKp6gtSX7s1rCLP_iOZQMc,16493
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=CZS1OGSdiWsd2lsCwg0BDcUNWa61tUwgvn-P5rKqeN8,12987
86
+ iam_validator/sdk/policy_utils.py,sha256=Fh-QElhmPypzSJuF9rcrY7y46Gz3hQu3-yN5b1_mSHY,13579
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.0.dist-info/METADATA,sha256=ksZBI5NOZxypD5leqm2BSUOnak43V9EeLBOw1XoLXd0,19070
93
- iam_policy_validator-1.10.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
94
- iam_policy_validator-1.10.0.dist-info/entry_points.txt,sha256=8HtWd8O7mvPiPdZR5YbzY8or_qcqLM4-pKaFdhtFT8M,62
95
- iam_policy_validator-1.10.0.dist-info/licenses/LICENSE,sha256=AMnbFTBDcK4_MITe2wiQBkj0vg-jjBBhsc43ydC7tt4,1098
96
- iam_policy_validator-1.10.0.dist-info/RECORD,,
92
+ iam_policy_validator-1.10.1.dist-info/METADATA,sha256=lIMmE1Y6TX34sh3stfe9J2_q5ATb9fYZqVqOupYNcL8,19070
93
+ iam_policy_validator-1.10.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
94
+ iam_policy_validator-1.10.1.dist-info/entry_points.txt,sha256=8HtWd8O7mvPiPdZR5YbzY8or_qcqLM4-pKaFdhtFT8M,62
95
+ iam_policy_validator-1.10.1.dist-info/licenses/LICENSE,sha256=AMnbFTBDcK4_MITe2wiQBkj0vg-jjBBhsc43ydC7tt4,1098
96
+ iam_policy_validator-1.10.1.dist-info/RECORD,,
@@ -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.0"
6
+ __version__ = "1.10.1"
7
7
  # Parse version, handling pre-release suffixes like -rc, -alpha, -beta
8
8
  _version_base = __version__.split("-")[0] # Remove pre-release suffix if present
9
9
  __version_info__ = tuple(int(part) for part in _version_base.split("."))
@@ -280,9 +280,12 @@ class ServiceValidator:
280
280
  "- `aws:RequestedRegion`\n"
281
281
  "- `aws:SourceIp`\n"
282
282
  "- `aws:SourceVpce`\n"
283
- "- `aws:UserAgent`\n"
283
+ "- `aws:ResourceOrgID`\n"
284
+ "- `aws:PrincipalOrgID`\n"
285
+ "- `aws:SourceAccount`\n"
286
+ "- `aws:PrincipalAccount`\n"
284
287
  "- `aws:CurrentTime`\n"
285
- "- `aws:SecureTransport`\n"
288
+ "- `aws:ResourceAccount`\n"
286
289
  "- `aws:PrincipalArn`\n"
287
290
  "- And many others"
288
291
  )
@@ -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(populate_by_name=True)
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(populate_by_name=True)
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(populate_by_name=True)
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(populate_by_name=True)
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(populate_by_name=True, extra="allow")
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(populate_by_name=True, extra="allow")
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
- statements: list[Statement] = (
67
- policy.statement if isinstance(policy.statement, list) else [policy.statement]
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,7 +191,10 @@ 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:
@@ -216,6 +229,9 @@ def find_statements_with_action(policy: IAMPolicy, action: str) -> list[Statemen
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
 
@@ -250,6 +266,9 @@ def find_statements_with_resource(policy: IAMPolicy, resource: str) -> list[Stat
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
- all_statements.extend(policy.statement)
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
- allow_count = sum(1 for s in policy.statement if s.effect.lower() == "allow")
322
- deny_count = sum(1 for s in policy.statement if s.effect.lower() == "deny")
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(policy.statement),
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