iam-policy-validator 1.14.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 (106) hide show
  1. iam_policy_validator-1.14.0.dist-info/METADATA +782 -0
  2. iam_policy_validator-1.14.0.dist-info/RECORD +106 -0
  3. iam_policy_validator-1.14.0.dist-info/WHEEL +4 -0
  4. iam_policy_validator-1.14.0.dist-info/entry_points.txt +2 -0
  5. iam_policy_validator-1.14.0.dist-info/licenses/LICENSE +21 -0
  6. iam_validator/__init__.py +27 -0
  7. iam_validator/__main__.py +11 -0
  8. iam_validator/__version__.py +9 -0
  9. iam_validator/checks/__init__.py +45 -0
  10. iam_validator/checks/action_condition_enforcement.py +1442 -0
  11. iam_validator/checks/action_resource_matching.py +472 -0
  12. iam_validator/checks/action_validation.py +67 -0
  13. iam_validator/checks/condition_key_validation.py +88 -0
  14. iam_validator/checks/condition_type_mismatch.py +257 -0
  15. iam_validator/checks/full_wildcard.py +62 -0
  16. iam_validator/checks/mfa_condition_check.py +105 -0
  17. iam_validator/checks/policy_size.py +114 -0
  18. iam_validator/checks/policy_structure.py +556 -0
  19. iam_validator/checks/policy_type_validation.py +331 -0
  20. iam_validator/checks/principal_validation.py +708 -0
  21. iam_validator/checks/resource_validation.py +135 -0
  22. iam_validator/checks/sensitive_action.py +438 -0
  23. iam_validator/checks/service_wildcard.py +98 -0
  24. iam_validator/checks/set_operator_validation.py +153 -0
  25. iam_validator/checks/sid_uniqueness.py +146 -0
  26. iam_validator/checks/trust_policy_validation.py +509 -0
  27. iam_validator/checks/utils/__init__.py +17 -0
  28. iam_validator/checks/utils/action_parser.py +149 -0
  29. iam_validator/checks/utils/policy_level_checks.py +190 -0
  30. iam_validator/checks/utils/sensitive_action_matcher.py +293 -0
  31. iam_validator/checks/utils/wildcard_expansion.py +86 -0
  32. iam_validator/checks/wildcard_action.py +58 -0
  33. iam_validator/checks/wildcard_resource.py +374 -0
  34. iam_validator/commands/__init__.py +31 -0
  35. iam_validator/commands/analyze.py +549 -0
  36. iam_validator/commands/base.py +48 -0
  37. iam_validator/commands/cache.py +393 -0
  38. iam_validator/commands/completion.py +471 -0
  39. iam_validator/commands/download_services.py +255 -0
  40. iam_validator/commands/post_to_pr.py +86 -0
  41. iam_validator/commands/query.py +485 -0
  42. iam_validator/commands/validate.py +830 -0
  43. iam_validator/core/__init__.py +13 -0
  44. iam_validator/core/access_analyzer.py +671 -0
  45. iam_validator/core/access_analyzer_report.py +640 -0
  46. iam_validator/core/aws_fetcher.py +29 -0
  47. iam_validator/core/aws_service/__init__.py +21 -0
  48. iam_validator/core/aws_service/cache.py +108 -0
  49. iam_validator/core/aws_service/client.py +205 -0
  50. iam_validator/core/aws_service/fetcher.py +641 -0
  51. iam_validator/core/aws_service/parsers.py +149 -0
  52. iam_validator/core/aws_service/patterns.py +51 -0
  53. iam_validator/core/aws_service/storage.py +291 -0
  54. iam_validator/core/aws_service/validators.py +380 -0
  55. iam_validator/core/check_registry.py +679 -0
  56. iam_validator/core/cli.py +134 -0
  57. iam_validator/core/codeowners.py +245 -0
  58. iam_validator/core/condition_validators.py +626 -0
  59. iam_validator/core/config/__init__.py +81 -0
  60. iam_validator/core/config/aws_api.py +35 -0
  61. iam_validator/core/config/aws_global_conditions.py +160 -0
  62. iam_validator/core/config/category_suggestions.py +181 -0
  63. iam_validator/core/config/check_documentation.py +390 -0
  64. iam_validator/core/config/condition_requirements.py +258 -0
  65. iam_validator/core/config/config_loader.py +670 -0
  66. iam_validator/core/config/defaults.py +739 -0
  67. iam_validator/core/config/principal_requirements.py +421 -0
  68. iam_validator/core/config/sensitive_actions.py +672 -0
  69. iam_validator/core/config/service_principals.py +132 -0
  70. iam_validator/core/config/wildcards.py +127 -0
  71. iam_validator/core/constants.py +149 -0
  72. iam_validator/core/diff_parser.py +325 -0
  73. iam_validator/core/finding_fingerprint.py +131 -0
  74. iam_validator/core/formatters/__init__.py +27 -0
  75. iam_validator/core/formatters/base.py +147 -0
  76. iam_validator/core/formatters/console.py +68 -0
  77. iam_validator/core/formatters/csv.py +171 -0
  78. iam_validator/core/formatters/enhanced.py +481 -0
  79. iam_validator/core/formatters/html.py +672 -0
  80. iam_validator/core/formatters/json.py +33 -0
  81. iam_validator/core/formatters/markdown.py +64 -0
  82. iam_validator/core/formatters/sarif.py +251 -0
  83. iam_validator/core/ignore_patterns.py +297 -0
  84. iam_validator/core/ignore_processor.py +309 -0
  85. iam_validator/core/ignored_findings.py +400 -0
  86. iam_validator/core/label_manager.py +197 -0
  87. iam_validator/core/models.py +404 -0
  88. iam_validator/core/policy_checks.py +220 -0
  89. iam_validator/core/policy_loader.py +785 -0
  90. iam_validator/core/pr_commenter.py +780 -0
  91. iam_validator/core/report.py +942 -0
  92. iam_validator/integrations/__init__.py +28 -0
  93. iam_validator/integrations/github_integration.py +1821 -0
  94. iam_validator/integrations/ms_teams.py +442 -0
  95. iam_validator/sdk/__init__.py +220 -0
  96. iam_validator/sdk/arn_matching.py +382 -0
  97. iam_validator/sdk/context.py +222 -0
  98. iam_validator/sdk/exceptions.py +48 -0
  99. iam_validator/sdk/helpers.py +177 -0
  100. iam_validator/sdk/policy_utils.py +451 -0
  101. iam_validator/sdk/query_utils.py +454 -0
  102. iam_validator/sdk/shortcuts.py +283 -0
  103. iam_validator/utils/__init__.py +35 -0
  104. iam_validator/utils/cache.py +105 -0
  105. iam_validator/utils/regex.py +205 -0
  106. iam_validator/utils/terminal.py +22 -0
@@ -0,0 +1,679 @@
1
+ """
2
+ Check Registry for IAM Policy Validator.
3
+
4
+ This module provides a pluggable check system that allows:
5
+ 1. Registering built-in and custom checks
6
+ 2. Enabling/disabling checks via configuration
7
+ 3. Configuring check behavior
8
+ 4. Easy extension without modifying core code
9
+ 5. Parallel execution of checks for performance
10
+ """
11
+
12
+ import asyncio
13
+ from abc import ABC
14
+ from dataclasses import dataclass, field
15
+ from typing import TYPE_CHECKING, Any
16
+
17
+ from iam_validator.core.aws_service import AWSServiceFetcher
18
+ from iam_validator.core.ignore_patterns import IgnorePatternMatcher
19
+ from iam_validator.core.models import Statement, ValidationIssue
20
+
21
+ if TYPE_CHECKING:
22
+ from iam_validator.core.models import IAMPolicy
23
+
24
+
25
+ @dataclass
26
+ class CheckConfig:
27
+ """Configuration for a single check."""
28
+
29
+ check_id: str
30
+ enabled: bool = True
31
+ severity: str | None = None # Override default severity
32
+ config: dict[str, Any] = field(default_factory=dict) # Check-specific config
33
+ description: str = ""
34
+ root_config: dict[str, Any] = field(default_factory=dict) # Full config for cross-check access
35
+ ignore_patterns: list[dict[str, Any]] = field(default_factory=list) # NEW: Ignore patterns
36
+ """
37
+ List of patterns to ignore findings.
38
+
39
+ Each pattern is a dict with optional fields:
40
+ - filepath: Regex to match file path
41
+ - action: Regex to match action name
42
+ - resource: Regex to match resource
43
+ - sid: Exact SID to match (or regex if ends with .*)
44
+ - condition_key: Regex to match condition key
45
+
46
+ Multiple fields in one pattern = AND logic
47
+ Multiple patterns = OR logic (any pattern matches → ignore)
48
+
49
+ Example:
50
+ ignore_patterns:
51
+ - filepath: "test/.*|examples/.*"
52
+ - filepath: "policies/readonly-.*"
53
+ action: ".*:(Get|List|Describe).*"
54
+ - sid: "AllowReadOnlyAccess"
55
+ """
56
+
57
+ def should_ignore(self, issue: ValidationIssue, filepath: str = "") -> bool:
58
+ """
59
+ Check if issue should be ignored based on ignore patterns.
60
+
61
+ Uses centralized IgnorePatternMatcher for high-performance filtering
62
+ with cached compiled regex patterns.
63
+
64
+ Args:
65
+ issue: The validation issue to check
66
+ filepath: Path to the policy file
67
+
68
+ Returns:
69
+ True if the issue should be ignored
70
+
71
+ Performance:
72
+ - Cached regex compilation (LRU cache)
73
+ - Early exit optimization
74
+ """
75
+ return IgnorePatternMatcher.should_ignore_issue(issue, filepath, self.ignore_patterns)
76
+
77
+ def filter_actions(self, actions: frozenset[str]) -> frozenset[str]:
78
+ """
79
+ Filter actions based on action ignore patterns.
80
+
81
+ Uses centralized IgnorePatternMatcher for high-performance filtering
82
+ with cached compiled regex patterns.
83
+
84
+ This is useful for checks that need to filter a set of actions before
85
+ creating ValidationIssues (e.g., sensitive_action check).
86
+
87
+ Args:
88
+ actions: Set of actions to filter
89
+
90
+ Returns:
91
+ Filtered set of actions (actions matching ignore patterns removed)
92
+
93
+ Performance:
94
+ - Cached regex compilation (LRU cache)
95
+ - Early exit per action on first match
96
+ """
97
+ return IgnorePatternMatcher.filter_actions(actions, self.ignore_patterns)
98
+
99
+
100
+ class PolicyCheck(ABC):
101
+ """
102
+ Base class for all policy checks.
103
+
104
+ To create a custom check:
105
+ 1. Inherit from this class
106
+ 2. Implement check_id and description (required)
107
+ 3. Implement either execute() OR execute_policy() (or both)
108
+ 4. Register with CheckRegistry
109
+
110
+ Two ways to define check_id and description:
111
+
112
+ Option 1 - Class attributes (simpler, recommended for static values):
113
+ from typing import ClassVar
114
+
115
+ class MyCheck(PolicyCheck):
116
+ check_id: ClassVar[str] = "my_check"
117
+ description: ClassVar[str] = "My check description"
118
+
119
+ async def execute(self, statement, statement_idx, fetcher, config):
120
+ return []
121
+
122
+ Note: ClassVar annotation is required for Pylance type checker compatibility.
123
+
124
+ Option 2 - Property decorators (more flexible, supports dynamic values):
125
+ class MyCheck(PolicyCheck):
126
+ @property
127
+ def check_id(self) -> str:
128
+ return "my_check"
129
+
130
+ @property
131
+ def description(self) -> str:
132
+ return "My check description"
133
+
134
+ async def execute(self, statement, statement_idx, fetcher, config):
135
+ return []
136
+
137
+ Statement-level check example:
138
+ from typing import ClassVar
139
+
140
+ class MyStatementCheck(PolicyCheck):
141
+ check_id: ClassVar[str] = "my_statement_check"
142
+ description: ClassVar[str] = "Validates individual statements"
143
+
144
+ async def execute(self, statement, statement_idx, fetcher, config):
145
+ issues = []
146
+ # Your validation logic here
147
+ return issues
148
+
149
+ Policy-level check example:
150
+ from typing import ClassVar
151
+
152
+ class MyPolicyCheck(PolicyCheck):
153
+ check_id: ClassVar[str] = "my_policy_check"
154
+ description: ClassVar[str] = "Validates entire policy"
155
+
156
+ async def execute_policy(self, policy, policy_file, fetcher, config, **kwargs):
157
+ issues = []
158
+ # Your validation logic here
159
+ return issues
160
+ """
161
+
162
+ @property
163
+ def check_id(self) -> str:
164
+ """Unique identifier for this check (e.g., 'action_validation')."""
165
+ raise NotImplementedError("Subclasses must define check_id")
166
+
167
+ @property
168
+ def description(self) -> str:
169
+ """Human-readable description of what this check does."""
170
+ raise NotImplementedError("Subclasses must define description")
171
+
172
+ @property
173
+ def default_severity(self) -> str:
174
+ """Default severity level for issues found by this check."""
175
+ return "warning"
176
+
177
+ def __init_subclass__(cls, **kwargs):
178
+ """
179
+ Validate that subclasses override at least one execution method.
180
+
181
+ This ensures checks implement either execute() OR execute_policy() (or both).
182
+ If neither is overridden, the check would never produce any results.
183
+ """
184
+ super().__init_subclass__(**kwargs)
185
+
186
+ # Skip validation for abstract classes
187
+ if ABC in cls.__bases__:
188
+ return
189
+
190
+ # Check if at least one method is overridden
191
+ has_execute = cls.execute is not PolicyCheck.execute
192
+ has_execute_policy = cls.execute_policy is not PolicyCheck.execute_policy
193
+
194
+ if not has_execute and not has_execute_policy:
195
+ raise TypeError(
196
+ f"Check '{cls.__name__}' must override at least one of: "
197
+ "execute() for statement-level checks, or "
198
+ "execute_policy() for policy-level checks"
199
+ )
200
+
201
+ async def execute(
202
+ self,
203
+ statement: Statement,
204
+ statement_idx: int,
205
+ fetcher: AWSServiceFetcher,
206
+ config: CheckConfig,
207
+ ) -> list[ValidationIssue]:
208
+ """
209
+ Execute the check on a policy statement.
210
+
211
+ This method is called for statement-level checks. If your check only needs
212
+ to examine the entire policy (not individual statements), you can leave this
213
+ as the default implementation and override execute_policy() instead.
214
+
215
+ Args:
216
+ statement: The IAM policy statement to check
217
+ statement_idx: Index of the statement in the policy
218
+ fetcher: AWS service fetcher for validation against AWS APIs
219
+ config: Configuration for this check instance
220
+
221
+ Returns:
222
+ List of ValidationIssue objects found by this check
223
+ """
224
+ del statement, statement_idx, fetcher, config # Unused in default implementation
225
+ return []
226
+
227
+ async def execute_policy(
228
+ self,
229
+ policy: "IAMPolicy",
230
+ policy_file: str,
231
+ fetcher: AWSServiceFetcher,
232
+ config: CheckConfig,
233
+ **kwargs,
234
+ ) -> list[ValidationIssue]:
235
+ """
236
+ Execute the check on the entire policy (optional method).
237
+
238
+ This method is for checks that need to examine all statements together,
239
+ such as checking for duplicate SIDs or cross-statement relationships.
240
+
241
+ By default, this returns an empty list. Override this method if your
242
+ check needs access to the full policy.
243
+
244
+ Args:
245
+ policy: The complete IAM policy to check
246
+ policy_file: Path to the policy file (for context/reporting)
247
+ fetcher: AWS service fetcher for validation against AWS APIs
248
+ config: Configuration for this check instance
249
+ **kwargs: Additional context (policy_type, etc.)
250
+
251
+ Returns:
252
+ List of ValidationIssue objects found by this check
253
+ """
254
+ del policy, policy_file, fetcher, config # Unused in default implementation
255
+ return []
256
+
257
+ def get_severity(self, config: CheckConfig) -> str:
258
+ """Get the severity level, respecting config overrides."""
259
+ return config.severity or self.default_severity
260
+
261
+ def is_policy_level_check(self) -> bool:
262
+ """
263
+ Check if this is a policy-level check.
264
+
265
+ Returns True if the check overrides execute_policy() method.
266
+ This helps the registry know whether to call execute_policy() or execute().
267
+ """
268
+ # Check if execute_policy has been overridden from the base class
269
+ return type(self).execute_policy is not PolicyCheck.execute_policy
270
+
271
+
272
+ class CheckRegistry:
273
+ """
274
+ Registry for managing validation checks.
275
+
276
+ Supports parallel execution of checks for improved performance.
277
+
278
+ Usage:
279
+ registry = CheckRegistry()
280
+ registry.register(ActionValidationCheck())
281
+ registry.register(MyCustomCheck())
282
+
283
+ # Get all enabled checks
284
+ checks = registry.get_enabled_checks()
285
+
286
+ # Configure checks
287
+ registry.configure_check('action_validation', CheckConfig(
288
+ check_id='action_validation',
289
+ enabled=True,
290
+ severity='error'
291
+ ))
292
+
293
+ # Execute checks in parallel
294
+ issues = await registry.execute_checks_parallel(statement, idx, fetcher)
295
+ """
296
+
297
+ def __init__(self, enable_parallel: bool = True):
298
+ """
299
+ Initialize the registry.
300
+
301
+ Args:
302
+ enable_parallel: If True, execute checks in parallel (default: True)
303
+ """
304
+ self._checks: dict[str, PolicyCheck] = {}
305
+ self._configs: dict[str, CheckConfig] = {}
306
+ self.enable_parallel = enable_parallel
307
+
308
+ def register(self, check: PolicyCheck) -> None:
309
+ """
310
+ Register a new check.
311
+
312
+ Args:
313
+ check: PolicyCheck instance to register
314
+ """
315
+ self._checks[check.check_id] = check
316
+
317
+ # Create default config if not exists
318
+ if check.check_id not in self._configs:
319
+ self._configs[check.check_id] = CheckConfig(
320
+ check_id=check.check_id,
321
+ enabled=True,
322
+ description=check.description,
323
+ )
324
+
325
+ def unregister(self, check_id: str) -> None:
326
+ """
327
+ Unregister a check by ID.
328
+
329
+ Args:
330
+ check_id: ID of the check to unregister
331
+ """
332
+ if check_id in self._checks:
333
+ del self._checks[check_id]
334
+ if check_id in self._configs:
335
+ del self._configs[check_id]
336
+
337
+ def configure_check(self, check_id: str, config: CheckConfig) -> None:
338
+ """
339
+ Configure a registered check.
340
+
341
+ Args:
342
+ check_id: ID of the check to configure
343
+ config: Configuration to apply
344
+ """
345
+ if check_id not in self._checks:
346
+ raise ValueError(f"Check '{check_id}' is not registered")
347
+ self._configs[check_id] = config
348
+
349
+ def get_all_checks(self) -> list[PolicyCheck]:
350
+ """Get all registered checks (enabled and disabled)."""
351
+ return list(self._checks.values())
352
+
353
+ def get_enabled_checks(self) -> list[PolicyCheck]:
354
+ """Get only enabled checks."""
355
+ return [
356
+ check
357
+ for check_id, check in self._checks.items()
358
+ if self._configs.get(check_id, CheckConfig(check_id=check_id)).enabled
359
+ ]
360
+
361
+ def get_check(self, check_id: str) -> PolicyCheck | None:
362
+ """Get a specific check by ID."""
363
+ return self._checks.get(check_id)
364
+
365
+ def get_config(self, check_id: str) -> CheckConfig | None:
366
+ """Get configuration for a specific check."""
367
+ return self._configs.get(check_id)
368
+
369
+ def is_enabled(self, check_id: str) -> bool:
370
+ """Check if a specific check is enabled."""
371
+ config = self._configs.get(check_id)
372
+ return config.enabled if config else False
373
+
374
+ def list_checks(self) -> list[dict[str, Any]]:
375
+ """
376
+ List all checks with their status and description.
377
+
378
+ Returns:
379
+ List of dicts with check information
380
+ """
381
+ result = []
382
+ for check_id, check in self._checks.items():
383
+ config = self._configs.get(check_id, CheckConfig(check_id=check_id))
384
+ result.append(
385
+ {
386
+ "check_id": check_id,
387
+ "description": check.description,
388
+ "enabled": config.enabled,
389
+ "severity": config.severity or check.default_severity,
390
+ }
391
+ )
392
+ return result
393
+
394
+ async def execute_checks_parallel(
395
+ self,
396
+ statement: Statement,
397
+ statement_idx: int,
398
+ fetcher: AWSServiceFetcher,
399
+ filepath: str = "",
400
+ ) -> list[ValidationIssue]:
401
+ """
402
+ Execute all enabled checks in parallel for maximum performance.
403
+
404
+ This method runs all enabled checks concurrently using asyncio.gather(),
405
+ which can significantly speed up validation when multiple checks are enabled.
406
+
407
+ Args:
408
+ statement: The IAM policy statement to validate
409
+ statement_idx: Index of the statement in the policy
410
+ fetcher: AWS service fetcher for API calls
411
+ filepath: Path to the policy file (for ignore_patterns filtering)
412
+
413
+ Returns:
414
+ List of all ValidationIssue objects from all checks (filtered by ignore_patterns)
415
+ """
416
+ enabled_checks = self.get_enabled_checks()
417
+
418
+ if not enabled_checks:
419
+ return []
420
+
421
+ if not self.enable_parallel or len(enabled_checks) == 1:
422
+ # Run sequentially if parallel disabled or only one check
423
+ all_issues = []
424
+ for check in enabled_checks:
425
+ config = self.get_config(check.check_id)
426
+ if config:
427
+ issues = await check.execute(statement, statement_idx, fetcher, config)
428
+ # Inject check_id into each issue
429
+ for issue in issues:
430
+ if issue.check_id is None:
431
+ issue.check_id = check.check_id
432
+ # Filter issues based on ignore_patterns
433
+ filtered_issues = [
434
+ issue for issue in issues if not config.should_ignore(issue, filepath)
435
+ ]
436
+ all_issues.extend(filtered_issues)
437
+ return all_issues
438
+
439
+ # Execute all checks in parallel
440
+ tasks = []
441
+ configs = []
442
+ for check in enabled_checks:
443
+ config = self.get_config(check.check_id)
444
+ if config:
445
+ task = check.execute(statement, statement_idx, fetcher, config)
446
+ tasks.append(task)
447
+ configs.append(config)
448
+
449
+ # Wait for all checks to complete
450
+ results = await asyncio.gather(*tasks, return_exceptions=True)
451
+
452
+ # Collect all issues, handling any exceptions and applying ignore_patterns
453
+ all_issues = []
454
+ for idx, result in enumerate(results):
455
+ if isinstance(result, Exception):
456
+ # Log error but continue with other checks
457
+ check = enabled_checks[idx]
458
+ print(f"Warning: Check '{check.check_id}' failed: {result}")
459
+ elif isinstance(result, list):
460
+ check = enabled_checks[idx]
461
+ config = configs[idx]
462
+ # Inject check_id into each issue
463
+ for issue in result:
464
+ if issue.check_id is None:
465
+ issue.check_id = check.check_id
466
+ # Filter issues based on ignore_patterns
467
+ filtered_issues = [
468
+ issue for issue in result if not config.should_ignore(issue, filepath)
469
+ ]
470
+ all_issues.extend(filtered_issues)
471
+
472
+ return all_issues
473
+
474
+ async def execute_checks_sequential(
475
+ self,
476
+ statement: Statement,
477
+ statement_idx: int,
478
+ fetcher: AWSServiceFetcher,
479
+ ) -> list[ValidationIssue]:
480
+ """
481
+ Execute all enabled checks sequentially.
482
+
483
+ Useful for debugging or when parallel execution causes issues.
484
+
485
+ Args:
486
+ statement: The IAM policy statement to validate
487
+ statement_idx: Index of the statement in the policy
488
+ fetcher: AWS service fetcher for API calls
489
+
490
+ Returns:
491
+ List of all ValidationIssue objects from all checks
492
+ """
493
+ all_issues = []
494
+ enabled_checks = self.get_enabled_checks()
495
+
496
+ for check in enabled_checks:
497
+ config = self.get_config(check.check_id)
498
+ if config:
499
+ try:
500
+ issues = await check.execute(statement, statement_idx, fetcher, config)
501
+ all_issues.extend(issues)
502
+ except Exception as e:
503
+ print(f"Warning: Check '{check.check_id}' failed: {e}")
504
+
505
+ return all_issues
506
+
507
+ async def execute_policy_checks(
508
+ self,
509
+ policy: "IAMPolicy",
510
+ policy_file: str,
511
+ fetcher: AWSServiceFetcher,
512
+ policy_type: str = "IDENTITY_POLICY",
513
+ **kwargs,
514
+ ) -> list[ValidationIssue]:
515
+ """
516
+ Execute all enabled policy-level checks.
517
+
518
+ Policy-level checks examine the entire policy at once, which is useful for
519
+ checks that need to see relationships between statements (e.g., duplicate SIDs).
520
+
521
+ Args:
522
+ policy: The complete IAM policy to validate
523
+ policy_file: Path to the policy file (for context/reporting)
524
+ fetcher: AWS service fetcher for API calls
525
+ policy_type: Type of policy (IDENTITY_POLICY, RESOURCE_POLICY, SERVICE_CONTROL_POLICY)
526
+ **kwargs: Additional arguments to pass to checks (e.g., raw_policy_dict)
527
+
528
+ Returns:
529
+ List of all ValidationIssue objects from all policy-level checks
530
+ """
531
+ all_issues = []
532
+ enabled_checks = self.get_enabled_checks()
533
+
534
+ # Filter to only policy-level checks
535
+ policy_level_checks = [c for c in enabled_checks if c.is_policy_level_check()]
536
+
537
+ if not policy_level_checks:
538
+ return []
539
+
540
+ if not self.enable_parallel or len(policy_level_checks) == 1:
541
+ # Run sequentially if parallel disabled or only one check
542
+ for check in policy_level_checks:
543
+ config = self.get_config(check.check_id)
544
+ if config:
545
+ try:
546
+ issues = await check.execute_policy(
547
+ policy,
548
+ policy_file,
549
+ fetcher,
550
+ config,
551
+ policy_type=policy_type,
552
+ **kwargs,
553
+ )
554
+ # Inject check_id into each issue
555
+ for issue in issues:
556
+ if issue.check_id is None:
557
+ issue.check_id = check.check_id
558
+ # Filter issues based on ignore_patterns
559
+ filtered_issues = [
560
+ issue
561
+ for issue in issues
562
+ if not config.should_ignore(issue, policy_file)
563
+ ]
564
+ all_issues.extend(filtered_issues)
565
+ except Exception as e:
566
+ print(f"Warning: Check '{check.check_id}' failed: {e}")
567
+ return all_issues
568
+
569
+ # Execute all policy-level checks in parallel
570
+ tasks = []
571
+ configs = []
572
+ for check in policy_level_checks:
573
+ config = self.get_config(check.check_id)
574
+ if config:
575
+ task = check.execute_policy(
576
+ policy, policy_file, fetcher, config, policy_type=policy_type, **kwargs
577
+ )
578
+ tasks.append(task)
579
+ configs.append(config)
580
+
581
+ # Wait for all checks to complete
582
+ results = await asyncio.gather(*tasks, return_exceptions=True)
583
+
584
+ # Collect all issues, handling any exceptions and applying ignore_patterns
585
+ for idx, result in enumerate(results):
586
+ if isinstance(result, Exception):
587
+ # Log error but continue with other checks
588
+ check = policy_level_checks[idx]
589
+ print(f"Warning: Check '{check.check_id}' failed: {result}")
590
+ elif isinstance(result, list):
591
+ check = policy_level_checks[idx]
592
+ config = configs[idx]
593
+ # Inject check_id into each issue
594
+ for issue in result:
595
+ if issue.check_id is None:
596
+ issue.check_id = check.check_id
597
+ # Filter issues based on ignore_patterns
598
+ filtered_issues = [
599
+ issue for issue in result if not config.should_ignore(issue, policy_file)
600
+ ]
601
+ all_issues.extend(filtered_issues)
602
+
603
+ return all_issues
604
+
605
+
606
+ def create_default_registry(
607
+ enable_parallel: bool = True, include_builtin_checks: bool = True
608
+ ) -> CheckRegistry:
609
+ """
610
+ Create a registry with all built-in checks registered.
611
+
612
+ This is a factory function that will be called when no custom
613
+ registry is provided.
614
+
615
+ Args:
616
+ enable_parallel: If True, checks will execute in parallel (default: True)
617
+ include_builtin_checks: If True, register built-in checks (default: True)
618
+
619
+ Returns:
620
+ CheckRegistry with all built-in checks registered (if include_builtin_checks=True)
621
+ """
622
+ registry = CheckRegistry(enable_parallel=enable_parallel)
623
+
624
+ if include_builtin_checks:
625
+ # Import and register built-in checks
626
+ from iam_validator import checks
627
+
628
+ # 0. FUNDAMENTAL STRUCTURE (Must run FIRST - validates basic policy structure)
629
+ registry.register(
630
+ checks.PolicyStructureCheck()
631
+ ) # Policy-level: Validates required fields, conflicts, valid values
632
+
633
+ # 1. POLICY STRUCTURE (Checks that examine the entire policy, not individual statements)
634
+ registry.register(
635
+ checks.SidUniquenessCheck()
636
+ ) # Policy-level: Duplicate SID detection across statements
637
+ registry.register(checks.PolicySizeCheck()) # Policy-level: Size limit validation
638
+
639
+ # 2. IAM VALIDITY (AWS syntax validation - must pass before deeper checks)
640
+ registry.register(checks.ActionValidationCheck()) # Validate actions against AWS API
641
+ registry.register(checks.ResourceValidationCheck()) # Validate resource ARNs
642
+ registry.register(checks.ConditionKeyValidationCheck()) # Validate condition keys
643
+
644
+ # 3. TYPE VALIDATION (Condition operator type checking)
645
+ registry.register(checks.ConditionTypeMismatchCheck()) # Operator-value type compatibility
646
+ registry.register(checks.SetOperatorValidationCheck()) # ForAllValues/ForAnyValue usage
647
+
648
+ # 4. RESOURCE MATCHING (Action-resource relationship validation)
649
+ registry.register(
650
+ checks.ActionResourceMatchingCheck()
651
+ ) # ARN type matching and resource constraints
652
+
653
+ # 5. SECURITY - WILDCARDS (Security best practices for wildcards)
654
+ registry.register(checks.WildcardActionCheck()) # Wildcard action detection
655
+ registry.register(checks.WildcardResourceCheck()) # Wildcard resource detection
656
+ registry.register(checks.FullWildcardCheck()) # Full wildcard (*) detection
657
+ registry.register(checks.ServiceWildcardCheck()) # Service-level wildcard detection
658
+
659
+ # 6. SECURITY - ADVANCED (Sensitive actions and condition enforcement)
660
+ registry.register(
661
+ checks.SensitiveActionCheck()
662
+ ) # Policy-level: Privilege escalation detection (all_of across statements)
663
+ registry.register(
664
+ checks.ActionConditionEnforcementCheck()
665
+ ) # Statement + Policy-level: Condition enforcement (any_of/all_of/none_of)
666
+ registry.register(checks.MFAConditionCheck()) # MFA anti-pattern detection
667
+
668
+ # 7. PRINCIPAL VALIDATION (Resource policy specific)
669
+ registry.register(
670
+ checks.PrincipalValidationCheck()
671
+ ) # Principal validation (resource policies)
672
+ registry.register(
673
+ checks.TrustPolicyValidationCheck()
674
+ ) # Trust policy validation (role assumption policies)
675
+
676
+ # Note: policy_type_validation is a standalone function (not a class-based check)
677
+ # and is called separately in the validation flow
678
+
679
+ return registry