iam-policy-validator 1.14.6__py3-none-any.whl → 1.15.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.14.6.dist-info → iam_policy_validator-1.15.0.dist-info}/METADATA +34 -23
- {iam_policy_validator-1.14.6.dist-info → iam_policy_validator-1.15.0.dist-info}/RECORD +42 -29
- iam_policy_validator-1.15.0.dist-info/entry_points.txt +4 -0
- iam_validator/__version__.py +1 -1
- iam_validator/checks/__init__.py +2 -0
- iam_validator/checks/action_validation.py +91 -27
- iam_validator/checks/not_action_not_resource.py +163 -0
- iam_validator/checks/resource_validation.py +132 -81
- iam_validator/checks/wildcard_resource.py +136 -6
- iam_validator/commands/__init__.py +3 -0
- iam_validator/commands/cache.py +66 -24
- iam_validator/commands/completion.py +94 -15
- iam_validator/commands/mcp.py +210 -0
- iam_validator/commands/query.py +489 -65
- iam_validator/core/aws_service/__init__.py +5 -1
- iam_validator/core/aws_service/cache.py +20 -0
- iam_validator/core/aws_service/fetcher.py +180 -11
- iam_validator/core/aws_service/storage.py +14 -6
- iam_validator/core/aws_service/validators.py +32 -41
- iam_validator/core/check_registry.py +100 -35
- iam_validator/core/config/aws_global_conditions.py +13 -0
- iam_validator/core/config/check_documentation.py +104 -51
- iam_validator/core/config/config_loader.py +39 -3
- iam_validator/core/config/defaults.py +6 -0
- iam_validator/core/constants.py +11 -4
- iam_validator/core/models.py +39 -14
- iam_validator/mcp/__init__.py +162 -0
- iam_validator/mcp/models.py +118 -0
- iam_validator/mcp/server.py +2928 -0
- iam_validator/mcp/session_config.py +319 -0
- iam_validator/mcp/templates/__init__.py +79 -0
- iam_validator/mcp/templates/builtin.py +856 -0
- iam_validator/mcp/tools/__init__.py +72 -0
- iam_validator/mcp/tools/generation.py +888 -0
- iam_validator/mcp/tools/org_config_tools.py +263 -0
- iam_validator/mcp/tools/query.py +395 -0
- iam_validator/mcp/tools/validation.py +376 -0
- iam_validator/sdk/__init__.py +64 -63
- iam_validator/sdk/context.py +3 -2
- iam_validator/sdk/policy_utils.py +31 -5
- iam_policy_validator-1.14.6.dist-info/entry_points.txt +0 -2
- {iam_policy_validator-1.14.6.dist-info → iam_policy_validator-1.15.0.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.14.6.dist-info → iam_policy_validator-1.15.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
"""Validation tools for MCP server.
|
|
2
|
+
|
|
3
|
+
This module provides MCP tools for validating IAM policies using the existing
|
|
4
|
+
SDK validation functionality. All functions wrap the core validation logic
|
|
5
|
+
from iam_validator.sdk without reimplementing it.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import atexit
|
|
9
|
+
import json
|
|
10
|
+
import tempfile
|
|
11
|
+
from collections.abc import Generator
|
|
12
|
+
from contextlib import contextmanager
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
import yaml
|
|
17
|
+
|
|
18
|
+
from iam_validator.core.models import IAMPolicy
|
|
19
|
+
from iam_validator.core.policy_checks import validate_policies
|
|
20
|
+
from iam_validator.mcp.models import ValidationResult
|
|
21
|
+
|
|
22
|
+
# Track temp files for cleanup on exit (safety net for abnormal termination)
|
|
23
|
+
_temp_files_to_cleanup: set[Path] = set()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _cleanup_temp_files() -> None:
|
|
27
|
+
"""Clean up any remaining temp files on process exit."""
|
|
28
|
+
for temp_path in list(_temp_files_to_cleanup):
|
|
29
|
+
try:
|
|
30
|
+
if temp_path.exists():
|
|
31
|
+
temp_path.unlink()
|
|
32
|
+
except OSError:
|
|
33
|
+
pass
|
|
34
|
+
_temp_files_to_cleanup.clear()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
atexit.register(_cleanup_temp_files)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@contextmanager
|
|
41
|
+
def _temp_config_file(
|
|
42
|
+
session_config: Any,
|
|
43
|
+
) -> Generator[str | None, None, None]:
|
|
44
|
+
"""Context manager for temporary config file with guaranteed cleanup.
|
|
45
|
+
|
|
46
|
+
Creates a temporary YAML config file from ValidatorConfig and ensures
|
|
47
|
+
cleanup even if exceptions occur or process is killed.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
session_config: ValidatorConfig instance from SessionConfigManager
|
|
51
|
+
|
|
52
|
+
Yields:
|
|
53
|
+
Path to temporary config file, or None if no config provided
|
|
54
|
+
"""
|
|
55
|
+
if session_config is None:
|
|
56
|
+
yield None
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
# ValidatorConfig already has the right structure - just dump its config_dict
|
|
60
|
+
config_dict = session_config.config_dict
|
|
61
|
+
|
|
62
|
+
# Create temp file and register for cleanup
|
|
63
|
+
temp_path: Path | None = None
|
|
64
|
+
try:
|
|
65
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
|
|
66
|
+
yaml.dump(config_dict, f)
|
|
67
|
+
temp_path = Path(f.name)
|
|
68
|
+
_temp_files_to_cleanup.add(temp_path)
|
|
69
|
+
|
|
70
|
+
yield str(temp_path)
|
|
71
|
+
finally:
|
|
72
|
+
# Clean up temp file
|
|
73
|
+
if temp_path:
|
|
74
|
+
try:
|
|
75
|
+
if temp_path.exists():
|
|
76
|
+
temp_path.unlink()
|
|
77
|
+
except OSError:
|
|
78
|
+
pass
|
|
79
|
+
_temp_files_to_cleanup.discard(temp_path)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# Trust policy actions (case-insensitive prefixes for matching)
|
|
83
|
+
_TRUST_POLICY_ACTIONS = frozenset(
|
|
84
|
+
[
|
|
85
|
+
"sts:assumerole",
|
|
86
|
+
"sts:assumerolewithwebidentity",
|
|
87
|
+
"sts:assumerolewithsaml",
|
|
88
|
+
]
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _is_trust_action(action: str) -> bool:
|
|
93
|
+
"""Check if an action indicates a trust policy (case-insensitive)."""
|
|
94
|
+
action_lower = action.lower()
|
|
95
|
+
# Check exact match or if it's a wildcard that would include assume role
|
|
96
|
+
return action_lower in _TRUST_POLICY_ACTIONS or action_lower in ("sts:*", "*")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _detect_policy_type(policy: dict[str, Any]) -> str:
|
|
100
|
+
"""Auto-detect policy type from structure.
|
|
101
|
+
|
|
102
|
+
Analyzes ALL statements in the policy to determine the appropriate policy type
|
|
103
|
+
based on AWS IAM policy patterns. Uses case-insensitive matching for actions.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
policy: IAM policy dictionary to analyze
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
- "trust" if ANY statement contains sts:AssumeRole* actions with Principal
|
|
110
|
+
- "resource" if ANY statement contains Principal/NotPrincipal without trust actions
|
|
111
|
+
- "identity" otherwise (default, identity-based policy)
|
|
112
|
+
"""
|
|
113
|
+
statements = policy.get("Statement", [])
|
|
114
|
+
if isinstance(statements, dict):
|
|
115
|
+
statements = [statements]
|
|
116
|
+
|
|
117
|
+
has_principal = False
|
|
118
|
+
has_trust_action = False
|
|
119
|
+
|
|
120
|
+
for stmt in statements:
|
|
121
|
+
# Check for Principal in any statement
|
|
122
|
+
if "Principal" in stmt or "NotPrincipal" in stmt:
|
|
123
|
+
has_principal = True
|
|
124
|
+
|
|
125
|
+
# Check for trust policy actions (case-insensitive)
|
|
126
|
+
actions = stmt.get("Action", [])
|
|
127
|
+
if isinstance(actions, str):
|
|
128
|
+
actions = [actions]
|
|
129
|
+
|
|
130
|
+
for action in actions:
|
|
131
|
+
if _is_trust_action(action):
|
|
132
|
+
has_trust_action = True
|
|
133
|
+
break
|
|
134
|
+
|
|
135
|
+
# Determine type based on all statements
|
|
136
|
+
if has_principal:
|
|
137
|
+
if has_trust_action:
|
|
138
|
+
return "trust"
|
|
139
|
+
return "resource"
|
|
140
|
+
|
|
141
|
+
return "identity"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
async def validate_policy(
|
|
145
|
+
policy: dict[str, Any],
|
|
146
|
+
policy_type: str | None = None,
|
|
147
|
+
config_path: str | None = None,
|
|
148
|
+
use_org_config: bool = True,
|
|
149
|
+
) -> ValidationResult:
|
|
150
|
+
"""Validate an IAM policy dictionary.
|
|
151
|
+
|
|
152
|
+
This tool validates a policy object against AWS IAM rules and security best
|
|
153
|
+
practices. It runs all enabled checks and returns detailed validation results.
|
|
154
|
+
|
|
155
|
+
Policy Type Auto-Detection:
|
|
156
|
+
If policy_type is None (default), the policy type is automatically detected:
|
|
157
|
+
- "trust" if contains sts:AssumeRole action (trust/assume role policy)
|
|
158
|
+
- "resource" if contains Principal/NotPrincipal (resource-based policy)
|
|
159
|
+
- "identity" otherwise (identity-based policy attached to users/roles/groups)
|
|
160
|
+
|
|
161
|
+
Configuration priority:
|
|
162
|
+
1. config_path (if provided) - explicit YAML config file path
|
|
163
|
+
2. Session org config (if use_org_config=True and config set)
|
|
164
|
+
3. Default validator configuration
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
policy: IAM policy as a Python dictionary (must contain Version and Statement)
|
|
168
|
+
policy_type: Type of policy to validate. If None (default), auto-detects from structure.
|
|
169
|
+
Explicit options:
|
|
170
|
+
- "identity": Identity-based policy (attached to users/roles/groups)
|
|
171
|
+
- "resource": Resource-based policy (attached to resources like S3 buckets)
|
|
172
|
+
- "trust": Trust policy (role assumption policy)
|
|
173
|
+
config_path: Optional path to YAML configuration file
|
|
174
|
+
use_org_config: Whether to use session organization config (default: True)
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
ValidationResult with:
|
|
178
|
+
- is_valid: True if no errors/warnings found
|
|
179
|
+
- issues: List of ValidationIssue objects with details
|
|
180
|
+
- policy_file: Set to "inline-policy" for dict validation
|
|
181
|
+
- policy_type_detected: The policy type used (auto-detected or provided)
|
|
182
|
+
|
|
183
|
+
Example:
|
|
184
|
+
>>> policy = {
|
|
185
|
+
... "Version": "2012-10-17",
|
|
186
|
+
... "Statement": [{
|
|
187
|
+
... "Effect": "Allow",
|
|
188
|
+
... "Action": "s3:GetObject",
|
|
189
|
+
... "Resource": "arn:aws:s3:::my-bucket/*"
|
|
190
|
+
... }]
|
|
191
|
+
... }
|
|
192
|
+
>>> result = await validate_policy(policy)
|
|
193
|
+
>>> print(f"Valid: {result.is_valid}, Issues: {len(result.issues)}")
|
|
194
|
+
"""
|
|
195
|
+
# Auto-detect policy type if not provided
|
|
196
|
+
effective_policy_type = policy_type
|
|
197
|
+
if effective_policy_type is None:
|
|
198
|
+
effective_policy_type = _detect_policy_type(policy)
|
|
199
|
+
|
|
200
|
+
# Map user-friendly policy type names to internal constants
|
|
201
|
+
policy_type_mapping = {
|
|
202
|
+
"identity": "IDENTITY_POLICY",
|
|
203
|
+
"resource": "RESOURCE_POLICY",
|
|
204
|
+
"trust": "TRUST_POLICY",
|
|
205
|
+
"scp": "SERVICE_CONTROL_POLICY",
|
|
206
|
+
"rcp": "RESOURCE_CONTROL_POLICY",
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
# Normalize the policy type
|
|
210
|
+
normalized_type = policy_type_mapping.get(effective_policy_type.lower(), "IDENTITY_POLICY")
|
|
211
|
+
|
|
212
|
+
# Parse the dict into an IAMPolicy model
|
|
213
|
+
iam_policy = IAMPolicy(**policy)
|
|
214
|
+
|
|
215
|
+
# Determine config path and session_config to use
|
|
216
|
+
session_config = None
|
|
217
|
+
if not config_path and use_org_config:
|
|
218
|
+
# Try to use session config
|
|
219
|
+
from iam_validator.mcp.session_config import SessionConfigManager
|
|
220
|
+
|
|
221
|
+
session_config = SessionConfigManager.get_config()
|
|
222
|
+
|
|
223
|
+
# Use context manager for temp file to ensure cleanup
|
|
224
|
+
with _temp_config_file(session_config) as temp_path:
|
|
225
|
+
effective_config_path = config_path or temp_path
|
|
226
|
+
|
|
227
|
+
# Use validate_policies to perform validation with policy_type support
|
|
228
|
+
# This handles all the validation logic including check execution
|
|
229
|
+
results = await validate_policies(
|
|
230
|
+
policies=[("inline-policy", iam_policy)],
|
|
231
|
+
config_path=effective_config_path,
|
|
232
|
+
policy_type=normalized_type, # type: ignore
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# Get the first (and only) result
|
|
236
|
+
sdk_result = results[0] if results else None
|
|
237
|
+
if not sdk_result:
|
|
238
|
+
# Fallback if no results returned (shouldn't happen)
|
|
239
|
+
from iam_validator.core.models import PolicyValidationResult
|
|
240
|
+
|
|
241
|
+
sdk_result = PolicyValidationResult(
|
|
242
|
+
policy_file="inline-policy",
|
|
243
|
+
is_valid=False,
|
|
244
|
+
issues=[],
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# Convert SDK result to MCP ValidationResult
|
|
248
|
+
return ValidationResult(
|
|
249
|
+
is_valid=sdk_result.is_valid,
|
|
250
|
+
issues=sdk_result.issues,
|
|
251
|
+
policy_file=sdk_result.policy_file,
|
|
252
|
+
policy_type_detected=effective_policy_type,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
async def validate_policy_json(
|
|
257
|
+
policy_json: str, policy_type: str | None = None
|
|
258
|
+
) -> ValidationResult:
|
|
259
|
+
"""Validate an IAM policy from a JSON string.
|
|
260
|
+
|
|
261
|
+
This tool parses a JSON string into a policy object and validates it.
|
|
262
|
+
Useful when working with policy text from files, API responses, or user input.
|
|
263
|
+
|
|
264
|
+
Policy Type Auto-Detection:
|
|
265
|
+
If policy_type is None (default), the policy type is automatically detected
|
|
266
|
+
from the policy structure (see validate_policy for details).
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
policy_json: IAM policy as a JSON string
|
|
270
|
+
policy_type: Type of policy to validate. If None (default), auto-detects.
|
|
271
|
+
Options: "identity", "resource", "trust"
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
ValidationResult with validation status and issues
|
|
275
|
+
|
|
276
|
+
Raises:
|
|
277
|
+
Returns ValidationResult with parsing error if JSON is invalid
|
|
278
|
+
|
|
279
|
+
Example:
|
|
280
|
+
>>> policy_json = '''
|
|
281
|
+
... {
|
|
282
|
+
... "Version": "2012-10-17",
|
|
283
|
+
... "Statement": [{
|
|
284
|
+
... "Effect": "Allow",
|
|
285
|
+
... "Action": "*",
|
|
286
|
+
... "Resource": "*"
|
|
287
|
+
... }]
|
|
288
|
+
... }
|
|
289
|
+
... '''
|
|
290
|
+
>>> result = await validate_policy_json(policy_json)
|
|
291
|
+
>>> for issue in result.issues:
|
|
292
|
+
... print(f"{issue.severity}: {issue.message}")
|
|
293
|
+
"""
|
|
294
|
+
try:
|
|
295
|
+
# Parse JSON string to dict
|
|
296
|
+
policy_dict = json.loads(policy_json)
|
|
297
|
+
except json.JSONDecodeError as e:
|
|
298
|
+
# Return validation result with parsing error
|
|
299
|
+
from iam_validator.core.models import ValidationIssue
|
|
300
|
+
|
|
301
|
+
return ValidationResult(
|
|
302
|
+
is_valid=False,
|
|
303
|
+
issues=[
|
|
304
|
+
ValidationIssue(
|
|
305
|
+
severity="error",
|
|
306
|
+
statement_index=-1,
|
|
307
|
+
issue_type="json_parse_error",
|
|
308
|
+
message=f"Failed to parse policy JSON: {e}",
|
|
309
|
+
suggestion="Ensure the policy is valid JSON format",
|
|
310
|
+
check_id="policy_structure",
|
|
311
|
+
)
|
|
312
|
+
],
|
|
313
|
+
policy_file="inline-policy",
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# Validate the parsed policy dict
|
|
317
|
+
return await validate_policy(policy=policy_dict, policy_type=policy_type)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
async def quick_validate(policy: dict[str, Any]) -> dict[str, Any]:
|
|
321
|
+
"""Quick pass/fail validation check for a policy.
|
|
322
|
+
|
|
323
|
+
This is a lightweight validation that returns just the essential information:
|
|
324
|
+
whether the policy is valid, the number of issues found, and critical issues.
|
|
325
|
+
Useful for rapid validation without detailed issue analysis.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
policy: IAM policy as a Python dictionary
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
Dictionary containing:
|
|
332
|
+
- is_valid (bool): Whether the policy passed validation
|
|
333
|
+
- issue_count (int): Total number of issues found
|
|
334
|
+
- critical_issues (list[str]): List of critical/high severity issue messages
|
|
335
|
+
- sensitive_actions_found (int): Count of sensitive actions detected
|
|
336
|
+
- wildcards_detected (bool): Whether wildcards were found in actions/resources
|
|
337
|
+
|
|
338
|
+
Example:
|
|
339
|
+
>>> policy = {"Version": "2012-10-17", "Statement": [...]}
|
|
340
|
+
>>> result = await quick_validate(policy)
|
|
341
|
+
>>> if result["is_valid"]:
|
|
342
|
+
... print("Policy is valid!")
|
|
343
|
+
>>> else:
|
|
344
|
+
... print(f"Found {result['issue_count']} issues")
|
|
345
|
+
... for msg in result["critical_issues"]:
|
|
346
|
+
... print(f" - {msg}")
|
|
347
|
+
"""
|
|
348
|
+
# Use validate_policy to get full results
|
|
349
|
+
validation_result = await validate_policy(policy=policy)
|
|
350
|
+
|
|
351
|
+
# Filter critical and high severity issues
|
|
352
|
+
critical_issues = []
|
|
353
|
+
sensitive_actions_count = 0
|
|
354
|
+
wildcards_detected = False
|
|
355
|
+
|
|
356
|
+
for issue in validation_result.issues:
|
|
357
|
+
severity = issue.severity.lower()
|
|
358
|
+
if severity in {"critical", "high", "error"}:
|
|
359
|
+
critical_issues.append(issue.message)
|
|
360
|
+
|
|
361
|
+
# Count sensitive action issues
|
|
362
|
+
if issue.check_id == "sensitive_action":
|
|
363
|
+
sensitive_actions_count += 1
|
|
364
|
+
|
|
365
|
+
# Detect wildcard issues
|
|
366
|
+
if issue.check_id in {"wildcard_action", "wildcard_resource", "service_wildcard"}:
|
|
367
|
+
wildcards_detected = True
|
|
368
|
+
|
|
369
|
+
# Return simplified result with enhanced fields
|
|
370
|
+
return {
|
|
371
|
+
"is_valid": validation_result.is_valid,
|
|
372
|
+
"issue_count": len(validation_result.issues),
|
|
373
|
+
"critical_issues": critical_issues,
|
|
374
|
+
"sensitive_actions_found": sensitive_actions_count,
|
|
375
|
+
"wildcards_detected": wildcards_detected,
|
|
376
|
+
}
|
iam_validator/sdk/__init__.py
CHANGED
|
@@ -1,66 +1,71 @@
|
|
|
1
|
-
"""
|
|
2
|
-
IAM Policy Validator SDK - Public API for library usage.
|
|
1
|
+
"""IAM Policy Validator SDK - Public API for library usage.
|
|
3
2
|
|
|
4
3
|
This module provides the complete public API for using IAM Policy Validator
|
|
5
4
|
as a Python library. It exposes both high-level convenience functions and
|
|
6
5
|
low-level components for custom integrations.
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
Basic validation
|
|
10
|
-
>>> from iam_validator.sdk import validate_file
|
|
11
|
-
>>> result = await validate_file("policy.json")
|
|
12
|
-
>>> print(f"Valid: {result.is_valid}")
|
|
13
|
-
|
|
14
|
-
With context manager:
|
|
15
|
-
>>> from iam_validator.sdk import validator
|
|
16
|
-
>>> async with validator() as v:
|
|
17
|
-
... result = await v.validate_file("policy.json")
|
|
18
|
-
... v.generate_report([result])
|
|
19
|
-
|
|
20
|
-
Policy manipulation:
|
|
21
|
-
>>> from iam_validator.sdk import parse_policy, get_policy_summary
|
|
22
|
-
>>> policy = parse_policy(policy_json)
|
|
23
|
-
>>> summary = get_policy_summary(policy)
|
|
24
|
-
>>> print(f"Actions: {summary['action_count']}")
|
|
25
|
-
|
|
26
|
-
Query AWS service definitions:
|
|
27
|
-
>>> from iam_validator.sdk import AWSServiceFetcher, query_actions, query_arn_formats
|
|
28
|
-
>>> async with AWSServiceFetcher() as fetcher:
|
|
29
|
-
... # Query all S3 write actions
|
|
30
|
-
... write_actions = await query_actions(fetcher, "s3", access_level="write")
|
|
31
|
-
... # Get ARN formats for S3
|
|
32
|
-
... arns = await query_arn_formats(fetcher, "s3")
|
|
33
|
-
|
|
34
|
-
Custom check development:
|
|
35
|
-
>>> from iam_validator.sdk import PolicyCheck, CheckHelper
|
|
36
|
-
>>> class MyCheck(PolicyCheck):
|
|
37
|
-
... @property
|
|
38
|
-
... def check_id(self) -> str:
|
|
39
|
-
... return "my_check"
|
|
40
|
-
... async def execute(self, statement, idx, fetcher, config):
|
|
41
|
-
... helper = CheckHelper(fetcher)
|
|
42
|
-
... # Use helper.arn_matches(), helper.create_issue(), etc.
|
|
43
|
-
... return []
|
|
44
|
-
"""
|
|
7
|
+
Example:
|
|
8
|
+
Basic validation::
|
|
45
9
|
|
|
46
|
-
|
|
47
|
-
# === AWS utilities ===
|
|
48
|
-
from iam_validator.core.aws_service import AWSServiceFetcher
|
|
10
|
+
from iam_validator.sdk import validate_file
|
|
49
11
|
|
|
50
|
-
|
|
51
|
-
|
|
12
|
+
result = await validate_file("policy.json")
|
|
13
|
+
print(f"Valid: {result.is_valid}")
|
|
14
|
+
|
|
15
|
+
With context manager::
|
|
16
|
+
|
|
17
|
+
from iam_validator.sdk import validator
|
|
18
|
+
|
|
19
|
+
async with validator() as v:
|
|
20
|
+
result = await v.validate_file("policy.json")
|
|
21
|
+
v.generate_report([result])
|
|
22
|
+
|
|
23
|
+
Policy manipulation::
|
|
24
|
+
|
|
25
|
+
from iam_validator.sdk import parse_policy, get_policy_summary
|
|
26
|
+
|
|
27
|
+
policy = parse_policy(policy_json)
|
|
28
|
+
summary = get_policy_summary(policy)
|
|
29
|
+
print(f"Actions: {summary['action_count']}")
|
|
30
|
+
|
|
31
|
+
Query AWS service definitions::
|
|
32
|
+
|
|
33
|
+
from iam_validator.sdk import AWSServiceFetcher, query_actions
|
|
34
|
+
|
|
35
|
+
async with AWSServiceFetcher() as fetcher:
|
|
36
|
+
# Query all S3 write actions
|
|
37
|
+
write_actions = await query_actions(fetcher, "s3", access_level="write")
|
|
38
|
+
|
|
39
|
+
Custom check development::
|
|
40
|
+
|
|
41
|
+
from iam_validator.sdk import PolicyCheck, CheckHelper
|
|
52
42
|
|
|
53
|
-
|
|
54
|
-
|
|
43
|
+
class MyCheck(PolicyCheck):
|
|
44
|
+
check_id = "my_check"
|
|
45
|
+
description = "My custom check"
|
|
46
|
+
default_severity = "medium"
|
|
55
47
|
|
|
56
|
-
|
|
48
|
+
async def execute(self, statement, idx, fetcher, config):
|
|
49
|
+
helper = CheckHelper(fetcher)
|
|
50
|
+
# Use helper.arn_matches(), helper.create_issue(), etc.
|
|
51
|
+
return []
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
# ruff: noqa: E402
|
|
55
|
+
# Imports are organized by category with comments, which triggers E402.
|
|
56
|
+
# This is intentional for readability in this public API module.
|
|
57
|
+
|
|
58
|
+
from iam_validator.core.aws_service import AWSServiceFetcher
|
|
59
|
+
from iam_validator.core.check_registry import CheckRegistry, PolicyCheck
|
|
60
|
+
from iam_validator.core.config.config_loader import (
|
|
61
|
+
ValidatorConfig,
|
|
62
|
+
load_validator_config,
|
|
63
|
+
)
|
|
57
64
|
from iam_validator.core.formatters.csv import CSVFormatter
|
|
58
65
|
from iam_validator.core.formatters.html import HTMLFormatter
|
|
59
66
|
from iam_validator.core.formatters.json import JSONFormatter
|
|
60
67
|
from iam_validator.core.formatters.markdown import MarkdownFormatter
|
|
61
68
|
from iam_validator.core.formatters.sarif import SARIFFormatter
|
|
62
|
-
|
|
63
|
-
# === Models (for type hints and inspection) ===
|
|
64
69
|
from iam_validator.core.models import (
|
|
65
70
|
IAMPolicy,
|
|
66
71
|
PolicyValidationResult,
|
|
@@ -70,23 +75,17 @@ from iam_validator.core.models import (
|
|
|
70
75
|
from iam_validator.core.policy_checks import validate_policies
|
|
71
76
|
from iam_validator.core.policy_loader import PolicyLoader
|
|
72
77
|
from iam_validator.core.report import ReportGenerator
|
|
73
|
-
|
|
74
|
-
# === ARN matching utilities ===
|
|
75
78
|
from iam_validator.sdk.arn_matching import (
|
|
76
79
|
arn_matches,
|
|
77
80
|
arn_strictly_valid,
|
|
78
81
|
convert_aws_pattern_to_wildcard,
|
|
79
82
|
is_glob_match,
|
|
80
83
|
)
|
|
81
|
-
|
|
82
|
-
# === Context managers ===
|
|
83
84
|
from iam_validator.sdk.context import (
|
|
84
85
|
ValidationContext,
|
|
85
86
|
validator,
|
|
86
87
|
validator_from_config,
|
|
87
88
|
)
|
|
88
|
-
|
|
89
|
-
# === Public exceptions ===
|
|
90
89
|
from iam_validator.sdk.exceptions import (
|
|
91
90
|
AWSServiceError,
|
|
92
91
|
ConfigurationError,
|
|
@@ -96,14 +95,11 @@ from iam_validator.sdk.exceptions import (
|
|
|
96
95
|
PolicyValidationError,
|
|
97
96
|
UnsupportedPolicyTypeError,
|
|
98
97
|
)
|
|
99
|
-
|
|
100
|
-
# === Custom check development ===
|
|
101
98
|
from iam_validator.sdk.helpers import CheckHelper, expand_actions
|
|
102
|
-
|
|
103
|
-
# === Policy manipulation utilities ===
|
|
104
99
|
from iam_validator.sdk.policy_utils import (
|
|
105
100
|
extract_actions,
|
|
106
101
|
extract_condition_keys,
|
|
102
|
+
extract_condition_keys_from_statement,
|
|
107
103
|
extract_resources,
|
|
108
104
|
find_statements_with_action,
|
|
109
105
|
find_statements_with_resource,
|
|
@@ -116,8 +112,6 @@ from iam_validator.sdk.policy_utils import (
|
|
|
116
112
|
policy_to_dict,
|
|
117
113
|
policy_to_json,
|
|
118
114
|
)
|
|
119
|
-
|
|
120
|
-
# === Query utilities (AWS service definition queries) ===
|
|
121
115
|
from iam_validator.sdk.query_utils import (
|
|
122
116
|
get_actions_by_access_level,
|
|
123
117
|
get_actions_supporting_condition,
|
|
@@ -139,6 +133,9 @@ from iam_validator.sdk.shortcuts import (
|
|
|
139
133
|
validate_json,
|
|
140
134
|
)
|
|
141
135
|
|
|
136
|
+
# Alias for convenience (matches documentation)
|
|
137
|
+
Config = ValidatorConfig
|
|
138
|
+
|
|
142
139
|
__all__ = [
|
|
143
140
|
# === High-level shortcuts ===
|
|
144
141
|
"validate_file",
|
|
@@ -157,6 +154,7 @@ __all__ = [
|
|
|
157
154
|
"extract_actions",
|
|
158
155
|
"extract_resources",
|
|
159
156
|
"extract_condition_keys",
|
|
157
|
+
"extract_condition_keys_from_statement",
|
|
160
158
|
"find_statements_with_action",
|
|
161
159
|
"find_statements_with_resource",
|
|
162
160
|
"merge_policies",
|
|
@@ -203,6 +201,7 @@ __all__ = [
|
|
|
203
201
|
"Statement",
|
|
204
202
|
# === ValidatorConfiguration ===
|
|
205
203
|
"ValidatorConfig",
|
|
204
|
+
"Config", # Alias for ValidatorConfig
|
|
206
205
|
"load_validator_config",
|
|
207
206
|
# === AWS utilities ===
|
|
208
207
|
"AWSServiceFetcher",
|
|
@@ -214,7 +213,9 @@ __all__ = [
|
|
|
214
213
|
"AWSServiceError",
|
|
215
214
|
"InvalidPolicyFormatError",
|
|
216
215
|
"UnsupportedPolicyTypeError",
|
|
216
|
+
# Version
|
|
217
|
+
"__version__",
|
|
217
218
|
]
|
|
218
219
|
|
|
219
|
-
# SDK version
|
|
220
|
-
__version__
|
|
220
|
+
# SDK version (same as main package)
|
|
221
|
+
from iam_validator.__version__ import __version__
|
iam_validator/sdk/context.py
CHANGED
|
@@ -5,6 +5,7 @@ This module provides context managers that handle resource lifecycle
|
|
|
5
5
|
and make the validation API more convenient to use.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
from collections.abc import AsyncIterator
|
|
8
9
|
from contextlib import asynccontextmanager
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
|
|
@@ -169,7 +170,7 @@ class ValidationContext:
|
|
|
169
170
|
@asynccontextmanager
|
|
170
171
|
async def validator(
|
|
171
172
|
config_path: str | None = None,
|
|
172
|
-
):
|
|
173
|
+
) -> AsyncIterator[ValidationContext]:
|
|
173
174
|
"""
|
|
174
175
|
Context manager that handles AWS fetcher lifecycle.
|
|
175
176
|
|
|
@@ -201,7 +202,7 @@ async def validator(
|
|
|
201
202
|
|
|
202
203
|
|
|
203
204
|
@asynccontextmanager
|
|
204
|
-
async def validator_from_config(config_path: str):
|
|
205
|
+
async def validator_from_config(config_path: str) -> AsyncIterator[ValidationContext]:
|
|
205
206
|
"""
|
|
206
207
|
Context manager that loads configuration and creates a validator.
|
|
207
208
|
|
|
@@ -176,6 +176,36 @@ def extract_resources(policy: IAMPolicy) -> list[str]:
|
|
|
176
176
|
return sorted(resources)
|
|
177
177
|
|
|
178
178
|
|
|
179
|
+
def extract_condition_keys_from_statement(statement: Statement) -> set[str]:
|
|
180
|
+
"""
|
|
181
|
+
Extract all condition keys from a single statement.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
statement: Statement to extract condition keys from
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Set of condition key names (e.g., {"aws:ResourceAccount", "aws:SourceIp"})
|
|
188
|
+
|
|
189
|
+
Example:
|
|
190
|
+
>>> stmt = Statement(
|
|
191
|
+
... Effect="Allow",
|
|
192
|
+
... Action=["s3:GetObject"],
|
|
193
|
+
... Resource=["*"],
|
|
194
|
+
... Condition={"StringEquals": {"aws:ResourceAccount": "123456789012"}}
|
|
195
|
+
... )
|
|
196
|
+
>>> keys = extract_condition_keys_from_statement(stmt)
|
|
197
|
+
>>> print(keys) # {"aws:ResourceAccount"}
|
|
198
|
+
"""
|
|
199
|
+
if not statement.condition:
|
|
200
|
+
return set()
|
|
201
|
+
|
|
202
|
+
keys: set[str] = set()
|
|
203
|
+
for operator_block in statement.condition.values():
|
|
204
|
+
if isinstance(operator_block, dict):
|
|
205
|
+
keys.update(operator_block.keys())
|
|
206
|
+
return keys
|
|
207
|
+
|
|
208
|
+
|
|
179
209
|
def extract_condition_keys(policy: IAMPolicy) -> list[str]:
|
|
180
210
|
"""
|
|
181
211
|
Extract all condition keys used in a policy.
|
|
@@ -197,11 +227,7 @@ def extract_condition_keys(policy: IAMPolicy) -> list[str]:
|
|
|
197
227
|
return []
|
|
198
228
|
|
|
199
229
|
for stmt in policy.statement:
|
|
200
|
-
|
|
201
|
-
# Condition format: {"StringEquals": {"aws:username": "johndoe"}}
|
|
202
|
-
for _, key_values in stmt.condition.items():
|
|
203
|
-
if isinstance(key_values, dict):
|
|
204
|
-
condition_keys.update(key_values.keys())
|
|
230
|
+
condition_keys.update(extract_condition_keys_from_statement(stmt))
|
|
205
231
|
|
|
206
232
|
return sorted(condition_keys)
|
|
207
233
|
|
|
File without changes
|
{iam_policy_validator-1.14.6.dist-info → iam_policy_validator-1.15.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|