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.
- {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/METADATA +22 -7
- iam_policy_validator-1.8.0.dist-info/RECORD +87 -0
- iam_validator/__version__.py +4 -2
- iam_validator/checks/__init__.py +5 -3
- iam_validator/checks/action_condition_enforcement.py +81 -36
- iam_validator/checks/action_resource_matching.py +75 -37
- iam_validator/checks/action_validation.py +1 -1
- iam_validator/checks/condition_key_validation.py +7 -7
- iam_validator/checks/condition_type_mismatch.py +10 -8
- iam_validator/checks/full_wildcard.py +2 -8
- iam_validator/checks/mfa_condition_check.py +8 -8
- iam_validator/checks/policy_structure.py +577 -0
- iam_validator/checks/policy_type_validation.py +48 -32
- iam_validator/checks/principal_validation.py +86 -150
- iam_validator/checks/resource_validation.py +8 -8
- iam_validator/checks/sensitive_action.py +9 -11
- iam_validator/checks/service_wildcard.py +4 -10
- iam_validator/checks/set_operator_validation.py +11 -11
- iam_validator/checks/sid_uniqueness.py +8 -4
- iam_validator/checks/trust_policy_validation.py +512 -0
- iam_validator/checks/utils/sensitive_action_matcher.py +26 -26
- iam_validator/checks/utils/wildcard_expansion.py +1 -1
- iam_validator/checks/wildcard_action.py +5 -9
- iam_validator/checks/wildcard_resource.py +5 -9
- iam_validator/commands/validate.py +8 -14
- iam_validator/core/__init__.py +1 -2
- iam_validator/core/access_analyzer.py +1 -1
- iam_validator/core/access_analyzer_report.py +2 -2
- iam_validator/core/aws_fetcher.py +159 -64
- iam_validator/core/check_registry.py +83 -79
- iam_validator/core/config/condition_requirements.py +69 -17
- iam_validator/core/config/config_loader.py +1 -2
- iam_validator/core/config/defaults.py +74 -59
- iam_validator/core/config/service_principals.py +40 -3
- iam_validator/core/constants.py +57 -0
- iam_validator/core/formatters/console.py +10 -1
- iam_validator/core/formatters/csv.py +2 -1
- iam_validator/core/formatters/enhanced.py +42 -8
- iam_validator/core/formatters/markdown.py +2 -1
- iam_validator/core/ignore_patterns.py +297 -0
- iam_validator/core/models.py +35 -10
- iam_validator/core/policy_checks.py +34 -474
- iam_validator/core/policy_loader.py +98 -18
- iam_validator/core/report.py +65 -24
- iam_validator/integrations/github_integration.py +4 -5
- iam_validator/utils/__init__.py +4 -0
- iam_validator/utils/terminal.py +22 -0
- iam_policy_validator-1.7.1.dist-info/RECORD +0 -83
- {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/entry_points.txt +0 -0
- {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/licenses/LICENSE +0 -0
iam_validator/core/models.py
CHANGED
|
@@ -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("
|
|
229
|
-
parts.append("
|
|
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(
|
|
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."""
|