iam-policy-validator 1.7.1__py3-none-any.whl → 1.8.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.
Files changed (51) hide show
  1. {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/METADATA +22 -7
  2. iam_policy_validator-1.8.0.dist-info/RECORD +87 -0
  3. iam_validator/__version__.py +4 -2
  4. iam_validator/checks/__init__.py +5 -3
  5. iam_validator/checks/action_condition_enforcement.py +81 -36
  6. iam_validator/checks/action_resource_matching.py +75 -37
  7. iam_validator/checks/action_validation.py +1 -1
  8. iam_validator/checks/condition_key_validation.py +7 -7
  9. iam_validator/checks/condition_type_mismatch.py +10 -8
  10. iam_validator/checks/full_wildcard.py +2 -8
  11. iam_validator/checks/mfa_condition_check.py +8 -8
  12. iam_validator/checks/policy_structure.py +577 -0
  13. iam_validator/checks/policy_type_validation.py +48 -32
  14. iam_validator/checks/principal_validation.py +86 -150
  15. iam_validator/checks/resource_validation.py +8 -8
  16. iam_validator/checks/sensitive_action.py +9 -11
  17. iam_validator/checks/service_wildcard.py +4 -10
  18. iam_validator/checks/set_operator_validation.py +11 -11
  19. iam_validator/checks/sid_uniqueness.py +8 -4
  20. iam_validator/checks/trust_policy_validation.py +512 -0
  21. iam_validator/checks/utils/sensitive_action_matcher.py +26 -26
  22. iam_validator/checks/utils/wildcard_expansion.py +1 -1
  23. iam_validator/checks/wildcard_action.py +5 -9
  24. iam_validator/checks/wildcard_resource.py +5 -9
  25. iam_validator/commands/validate.py +8 -14
  26. iam_validator/core/__init__.py +1 -2
  27. iam_validator/core/access_analyzer.py +1 -1
  28. iam_validator/core/access_analyzer_report.py +2 -2
  29. iam_validator/core/aws_fetcher.py +159 -64
  30. iam_validator/core/check_registry.py +83 -79
  31. iam_validator/core/config/condition_requirements.py +69 -17
  32. iam_validator/core/config/config_loader.py +1 -2
  33. iam_validator/core/config/defaults.py +74 -59
  34. iam_validator/core/config/service_principals.py +40 -3
  35. iam_validator/core/constants.py +57 -0
  36. iam_validator/core/formatters/console.py +10 -1
  37. iam_validator/core/formatters/csv.py +2 -1
  38. iam_validator/core/formatters/enhanced.py +42 -8
  39. iam_validator/core/formatters/markdown.py +2 -1
  40. iam_validator/core/ignore_patterns.py +297 -0
  41. iam_validator/core/models.py +35 -10
  42. iam_validator/core/policy_checks.py +34 -474
  43. iam_validator/core/policy_loader.py +98 -18
  44. iam_validator/core/report.py +65 -24
  45. iam_validator/integrations/github_integration.py +4 -5
  46. iam_validator/utils/__init__.py +4 -0
  47. iam_validator/utils/terminal.py +22 -0
  48. iam_policy_validator-1.7.1.dist-info/RECORD +0 -83
  49. {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/WHEEL +0 -0
  50. {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/entry_points.txt +0 -0
  51. {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -8,10 +8,13 @@ from typing import Any, ClassVar, Literal
8
8
 
9
9
  from pydantic import BaseModel, ConfigDict, Field
10
10
 
11
+ from iam_validator.core import constants
12
+
11
13
  # Policy Type Constants
12
14
  PolicyType = Literal[
13
15
  "IDENTITY_POLICY",
14
16
  "RESOURCE_POLICY",
17
+ "TRUST_POLICY", # Trust policies (role assumption policies - subtype of resource policies)
15
18
  "SERVICE_CONTROL_POLICY",
16
19
  "RESOURCE_CONTROL_POLICY",
17
20
  ]
@@ -89,9 +92,8 @@ class ServiceDetail(BaseModel):
89
92
  resources_list: list[ResourceType] = Field(default_factory=list, alias="Resources")
90
93
  condition_keys_list: list[ConditionKey] = Field(default_factory=list, alias="ConditionKeys")
91
94
 
92
- def model_post_init(self, __context: Any) -> None:
95
+ def model_post_init(self, __context: Any, /) -> None:
93
96
  """Convert lists to dictionaries for easier lookup."""
94
- del __context # Unused
95
97
  # Convert actions list to dict
96
98
  self.actions = {action.name: action for action in self.actions_list}
97
99
  # Convert resources list to dict
@@ -104,10 +106,10 @@ class ServiceDetail(BaseModel):
104
106
  class Statement(BaseModel):
105
107
  """IAM policy statement."""
106
108
 
107
- model_config = ConfigDict(populate_by_name=True)
109
+ model_config = ConfigDict(populate_by_name=True, extra="allow")
108
110
 
109
111
  sid: str | None = Field(default=None, alias="Sid")
110
- effect: str = Field(alias="Effect")
112
+ effect: str | None = Field(default=None, alias="Effect")
111
113
  action: list[str] | str | None = Field(default=None, alias="Action")
112
114
  not_action: list[str] | str | None = Field(default=None, alias="NotAction")
113
115
  resource: list[str] | str | None = Field(default=None, alias="Resource")
@@ -134,10 +136,10 @@ class Statement(BaseModel):
134
136
  class IAMPolicy(BaseModel):
135
137
  """IAM policy document."""
136
138
 
137
- model_config = ConfigDict(populate_by_name=True)
139
+ model_config = ConfigDict(populate_by_name=True, extra="allow")
138
140
 
139
- version: str = Field(alias="Version")
140
- statement: list[Statement] = Field(alias="Statement")
141
+ version: str | None = Field(default=None, alias="Version")
142
+ statement: list[Statement] | None = Field(default=None, alias="Statement")
141
143
  id: str | None = Field(default=None, alias="Id")
142
144
 
143
145
 
@@ -161,7 +163,11 @@ class ValidationIssue(BaseModel):
161
163
  resource: str | None = None
162
164
  condition_key: str | None = None
163
165
  suggestion: str | None = None
166
+ example: str | None = None # Code example (JSON/YAML) - formatted separately for GitHub
164
167
  line_number: int | None = None # Line number in the policy file (if available)
168
+ check_id: str | None = (
169
+ None # Check that triggered this issue (e.g., "policy_size", "sensitive_action")
170
+ )
165
171
 
166
172
  # Severity level constants (ClassVar to avoid Pydantic treating them as fields)
167
173
  VALID_SEVERITIES: ClassVar[frozenset[str]] = frozenset(
@@ -225,8 +231,8 @@ class ValidationIssue(BaseModel):
225
231
 
226
232
  # Add identifier for bot comment cleanup (HTML comment - not visible to users)
227
233
  if include_identifier:
228
- parts.append("<!-- iam-policy-validator-review -->\n")
229
- parts.append("🤖 **IAM Policy Validator**\n")
234
+ parts.append(f"{constants.REVIEW_IDENTIFIER}\n")
235
+ parts.append(f"{constants.BOT_IDENTIFIER}\n")
230
236
 
231
237
  # Build statement context for better navigation
232
238
  statement_context = f"Statement[{self.statement_index}]"
@@ -241,7 +247,9 @@ class ValidationIssue(BaseModel):
241
247
  parts.append(self.message)
242
248
 
243
249
  # Put additional details in collapsible section if there are any
244
- has_details = bool(self.action or self.resource or self.condition_key or self.suggestion)
250
+ has_details = bool(
251
+ self.action or self.resource or self.condition_key or self.suggestion or self.example
252
+ )
245
253
 
246
254
  if has_details:
247
255
  parts.append("")
@@ -266,10 +274,24 @@ class ValidationIssue(BaseModel):
266
274
  parts.append("**💡 Suggested Fix:**")
267
275
  parts.append("")
268
276
  parts.append(self.suggestion)
277
+ parts.append("")
278
+
279
+ # Add example if present (formatted as JSON code block for GitHub)
280
+ if self.example:
281
+ parts.append("**Example:**")
282
+ parts.append("```json")
283
+ parts.append(self.example)
284
+ parts.append("```")
269
285
 
270
286
  parts.append("")
271
287
  parts.append("</details>")
272
288
 
289
+ # Add check ID at the bottom if available
290
+ if self.check_id:
291
+ parts.append("")
292
+ parts.append("---")
293
+ parts.append(f"*Check: `{self.check_id}`*")
294
+
273
295
  return "\n".join(parts)
274
296
 
275
297
 
@@ -298,6 +320,9 @@ class ValidationReport(BaseModel):
298
320
  validity_issues: int = 0 # Count of IAM validity issues (error/warning/info)
299
321
  security_issues: int = 0 # Count of security issues (critical/high/medium/low)
300
322
  results: list[PolicyValidationResult] = Field(default_factory=list)
323
+ parsing_errors: list[tuple[str, str]] = Field(
324
+ default_factory=list
325
+ ) # (file_path, error_message)
301
326
 
302
327
  def get_summary(self) -> str:
303
328
  """Generate a human-readable summary."""