iam-policy-validator 1.4.0__py3-none-any.whl → 1.6.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 (57) hide show
  1. {iam_policy_validator-1.4.0.dist-info → iam_policy_validator-1.6.0.dist-info}/METADATA +106 -78
  2. iam_policy_validator-1.6.0.dist-info/RECORD +82 -0
  3. iam_validator/__version__.py +1 -1
  4. iam_validator/checks/__init__.py +20 -4
  5. iam_validator/checks/action_condition_enforcement.py +165 -8
  6. iam_validator/checks/action_resource_matching.py +424 -0
  7. iam_validator/checks/condition_key_validation.py +24 -2
  8. iam_validator/checks/condition_type_mismatch.py +259 -0
  9. iam_validator/checks/full_wildcard.py +67 -0
  10. iam_validator/checks/mfa_condition_check.py +112 -0
  11. iam_validator/checks/principal_validation.py +497 -3
  12. iam_validator/checks/sensitive_action.py +250 -0
  13. iam_validator/checks/service_wildcard.py +105 -0
  14. iam_validator/checks/set_operator_validation.py +157 -0
  15. iam_validator/checks/utils/sensitive_action_matcher.py +74 -32
  16. iam_validator/checks/wildcard_action.py +62 -0
  17. iam_validator/checks/wildcard_resource.py +131 -0
  18. iam_validator/commands/cache.py +1 -1
  19. iam_validator/commands/download_services.py +3 -8
  20. iam_validator/commands/validate.py +72 -13
  21. iam_validator/core/aws_fetcher.py +114 -64
  22. iam_validator/core/check_registry.py +167 -29
  23. iam_validator/core/condition_validators.py +626 -0
  24. iam_validator/core/config/__init__.py +81 -0
  25. iam_validator/core/config/aws_api.py +35 -0
  26. iam_validator/core/config/aws_global_conditions.py +160 -0
  27. iam_validator/core/config/category_suggestions.py +104 -0
  28. iam_validator/core/config/condition_requirements.py +155 -0
  29. iam_validator/core/{config_loader.py → config/config_loader.py} +32 -9
  30. iam_validator/core/config/defaults.py +523 -0
  31. iam_validator/core/config/principal_requirements.py +421 -0
  32. iam_validator/core/config/sensitive_actions.py +672 -0
  33. iam_validator/core/config/service_principals.py +95 -0
  34. iam_validator/core/config/wildcards.py +124 -0
  35. iam_validator/core/formatters/enhanced.py +11 -5
  36. iam_validator/core/formatters/sarif.py +78 -14
  37. iam_validator/core/models.py +14 -1
  38. iam_validator/core/policy_checks.py +4 -4
  39. iam_validator/core/pr_commenter.py +1 -1
  40. iam_validator/sdk/__init__.py +187 -0
  41. iam_validator/sdk/arn_matching.py +274 -0
  42. iam_validator/sdk/context.py +222 -0
  43. iam_validator/sdk/exceptions.py +48 -0
  44. iam_validator/sdk/helpers.py +177 -0
  45. iam_validator/sdk/policy_utils.py +425 -0
  46. iam_validator/sdk/shortcuts.py +283 -0
  47. iam_validator/utils/__init__.py +31 -0
  48. iam_validator/utils/cache.py +105 -0
  49. iam_validator/utils/regex.py +206 -0
  50. iam_policy_validator-1.4.0.dist-info/RECORD +0 -56
  51. iam_validator/checks/action_resource_constraint.py +0 -151
  52. iam_validator/checks/security_best_practices.py +0 -536
  53. iam_validator/core/aws_global_conditions.py +0 -137
  54. iam_validator/core/defaults.py +0 -393
  55. {iam_policy_validator-1.4.0.dist-info → iam_policy_validator-1.6.0.dist-info}/WHEEL +0 -0
  56. {iam_policy_validator-1.4.0.dist-info → iam_policy_validator-1.6.0.dist-info}/entry_points.txt +0 -0
  57. {iam_policy_validator-1.4.0.dist-info → iam_policy_validator-1.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -31,6 +31,112 @@ class CheckConfig:
31
31
  config: dict[str, Any] = field(default_factory=dict) # Check-specific config
32
32
  description: str = ""
33
33
  root_config: dict[str, Any] = field(default_factory=dict) # Full config for cross-check access
34
+ ignore_patterns: list[dict[str, Any]] = field(default_factory=list) # NEW: Ignore patterns
35
+ """
36
+ List of patterns to ignore findings.
37
+
38
+ Each pattern is a dict with optional fields:
39
+ - filepath_regex: Regex to match file path
40
+ - action_matches: Regex to match action name
41
+ - resource_matches: Regex to match resource
42
+ - statement_sid: Exact SID to match (or regex if ends with .*)
43
+ - condition_key_matches: Regex to match condition key
44
+
45
+ Multiple fields in one pattern = AND logic
46
+ Multiple patterns = OR logic (any pattern matches → ignore)
47
+
48
+ Example:
49
+ ignore_patterns:
50
+ - filepath_regex: "test/.*|examples/.*"
51
+ - filepath_regex: "policies/readonly-.*"
52
+ action_matches: ".*:(Get|List|Describe).*"
53
+ - statement_sid: "AllowReadOnlyAccess"
54
+ """
55
+
56
+ def should_ignore(self, issue: ValidationIssue, filepath: str = "") -> bool:
57
+ """
58
+ Check if issue should be ignored based on ignore patterns.
59
+
60
+ Args:
61
+ issue: The validation issue to check
62
+ filepath: Path to the policy file
63
+
64
+ Returns:
65
+ True if the issue should be ignored
66
+ """
67
+ if not self.ignore_patterns:
68
+ return False
69
+
70
+ import re
71
+
72
+ for pattern in self.ignore_patterns:
73
+ if self._matches_pattern(pattern, issue, filepath, re):
74
+ return True
75
+
76
+ return False
77
+
78
+ def _matches_pattern(
79
+ self,
80
+ pattern: dict[str, Any],
81
+ issue: ValidationIssue,
82
+ filepath: str,
83
+ re_module: Any,
84
+ ) -> bool:
85
+ """
86
+ Check if issue matches a single ignore pattern.
87
+
88
+ All fields in pattern must match (AND logic).
89
+
90
+ Args:
91
+ pattern: Pattern dict with optional fields
92
+ issue: ValidationIssue to check
93
+ filepath: Path to policy file
94
+ re_module: re module for regex matching
95
+
96
+ Returns:
97
+ True if all fields in pattern match the issue
98
+ """
99
+ for field_name, regex_pattern in pattern.items():
100
+ actual_value = None
101
+
102
+ if field_name == "filepath_regex":
103
+ actual_value = filepath
104
+ elif field_name == "action_matches":
105
+ actual_value = issue.action or ""
106
+ elif field_name == "resource_matches":
107
+ actual_value = issue.resource or ""
108
+ elif field_name == "statement_sid":
109
+ # For SID, support both exact match and regex
110
+ if isinstance(regex_pattern, str) and "*" in regex_pattern:
111
+ # Treat as regex if contains wildcard
112
+ actual_value = issue.statement_sid or ""
113
+ else:
114
+ # Exact match
115
+ if issue.statement_sid != regex_pattern:
116
+ return False
117
+ continue
118
+ elif field_name == "condition_key_matches":
119
+ actual_value = issue.condition_key or ""
120
+ else:
121
+ # Unknown field, skip
122
+ continue
123
+
124
+ # Check regex match (case-insensitive)
125
+ if actual_value is None:
126
+ return False
127
+
128
+ try:
129
+ if not re_module.search(
130
+ str(regex_pattern),
131
+ str(actual_value),
132
+ re_module.IGNORECASE,
133
+ ):
134
+ return False
135
+ except re_module.error:
136
+ # Invalid regex, don't match
137
+ return False
138
+
139
+ return True # All fields matched
34
140
 
35
141
 
36
142
  class PolicyCheck(ABC):
@@ -264,6 +370,7 @@ class CheckRegistry:
264
370
  statement: Statement,
265
371
  statement_idx: int,
266
372
  fetcher: AWSServiceFetcher,
373
+ filepath: str = "",
267
374
  ) -> list[ValidationIssue]:
268
375
  """
269
376
  Execute all enabled checks in parallel for maximum performance.
@@ -275,9 +382,10 @@ class CheckRegistry:
275
382
  statement: The IAM policy statement to validate
276
383
  statement_idx: Index of the statement in the policy
277
384
  fetcher: AWS service fetcher for API calls
385
+ filepath: Path to the policy file (for ignore_patterns filtering)
278
386
 
279
387
  Returns:
280
- List of all ValidationIssue objects from all checks
388
+ List of all ValidationIssue objects from all checks (filtered by ignore_patterns)
281
389
  """
282
390
  enabled_checks = self.get_enabled_checks()
283
391
 
@@ -291,21 +399,27 @@ class CheckRegistry:
291
399
  config = self.get_config(check.check_id)
292
400
  if config:
293
401
  issues = await check.execute(statement, statement_idx, fetcher, config)
294
- all_issues.extend(issues)
402
+ # Filter issues based on ignore_patterns
403
+ filtered_issues = [
404
+ issue for issue in issues if not config.should_ignore(issue, filepath)
405
+ ]
406
+ all_issues.extend(filtered_issues)
295
407
  return all_issues
296
408
 
297
409
  # Execute all checks in parallel
298
410
  tasks = []
411
+ configs = []
299
412
  for check in enabled_checks:
300
413
  config = self.get_config(check.check_id)
301
414
  if config:
302
415
  task = check.execute(statement, statement_idx, fetcher, config)
303
416
  tasks.append(task)
417
+ configs.append(config)
304
418
 
305
419
  # Wait for all checks to complete
306
420
  results = await asyncio.gather(*tasks, return_exceptions=True)
307
421
 
308
- # Collect all issues, handling any exceptions
422
+ # Collect all issues, handling any exceptions and applying ignore_patterns
309
423
  all_issues = []
310
424
  for idx, result in enumerate(results):
311
425
  if isinstance(result, Exception):
@@ -313,7 +427,12 @@ class CheckRegistry:
313
427
  check = enabled_checks[idx]
314
428
  print(f"Warning: Check '{check.check_id}' failed: {result}")
315
429
  elif isinstance(result, list):
316
- all_issues.extend(result)
430
+ config = configs[idx]
431
+ # Filter issues based on ignore_patterns
432
+ filtered_issues = [
433
+ issue for issue in result if not config.should_ignore(issue, filepath)
434
+ ]
435
+ all_issues.extend(filtered_issues)
317
436
 
318
437
  return all_issues
319
438
 
@@ -440,30 +559,49 @@ def create_default_registry(
440
559
 
441
560
  if include_builtin_checks:
442
561
  # Import and register built-in checks
443
- from iam_validator.checks import (
444
- ActionConditionEnforcementCheck,
445
- ActionResourceConstraintCheck,
446
- ActionValidationCheck,
447
- ConditionKeyValidationCheck,
448
- PolicySizeCheck,
449
- PrincipalValidationCheck,
450
- ResourceValidationCheck,
451
- SecurityBestPracticesCheck,
452
- SidUniquenessCheck,
453
- )
454
-
455
- registry.register(ActionValidationCheck())
456
- registry.register(ConditionKeyValidationCheck())
457
- registry.register(ResourceValidationCheck())
458
- registry.register(SecurityBestPracticesCheck())
459
- registry.register(ActionConditionEnforcementCheck())
460
- registry.register(ActionResourceConstraintCheck())
461
- registry.register(SidUniquenessCheck())
462
- registry.register(PolicySizeCheck())
463
- registry.register(PrincipalValidationCheck())
464
-
465
- # Note: SID uniqueness check is registered above but its actual execution
466
- # happens at the policy level in _validate_policy_with_registry() since it
467
- # needs to see all statements together to find duplicates
562
+ from iam_validator import checks
563
+
564
+ # 1. POLICY STRUCTURE (Checks that examine the entire policy, not individual statements)
565
+ registry.register(
566
+ checks.SidUniquenessCheck()
567
+ ) # Policy-level: Duplicate SID detection across statements
568
+ registry.register(checks.PolicySizeCheck()) # Policy-level: Size limit validation
569
+
570
+ # 2. IAM VALIDITY (AWS syntax validation - must pass before deeper checks)
571
+ registry.register(checks.ActionValidationCheck()) # Validate actions against AWS API
572
+ registry.register(checks.ResourceValidationCheck()) # Validate resource ARNs
573
+ registry.register(checks.ConditionKeyValidationCheck()) # Validate condition keys
574
+
575
+ # 3. TYPE VALIDATION (Condition operator type checking)
576
+ registry.register(checks.ConditionTypeMismatchCheck()) # Operator-value type compatibility
577
+ registry.register(checks.SetOperatorValidationCheck()) # ForAllValues/ForAnyValue usage
578
+
579
+ # 4. RESOURCE MATCHING (Action-resource relationship validation)
580
+ registry.register(
581
+ checks.ActionResourceMatchingCheck()
582
+ ) # ARN type matching and resource constraints
583
+
584
+ # 5. SECURITY - WILDCARDS (Security best practices for wildcards)
585
+ registry.register(checks.WildcardActionCheck()) # Wildcard action detection
586
+ registry.register(checks.WildcardResourceCheck()) # Wildcard resource detection
587
+ registry.register(checks.FullWildcardCheck()) # Full wildcard (*) detection
588
+ registry.register(checks.ServiceWildcardCheck()) # Service-level wildcard detection
589
+
590
+ # 6. SECURITY - ADVANCED (Sensitive actions and condition enforcement)
591
+ registry.register(
592
+ checks.SensitiveActionCheck()
593
+ ) # Policy-level: Privilege escalation detection (all_of across statements)
594
+ registry.register(
595
+ checks.ActionConditionEnforcementCheck()
596
+ ) # Statement + Policy-level: Condition enforcement (any_of/all_of/none_of)
597
+ registry.register(checks.MFAConditionCheck()) # MFA anti-pattern detection
598
+
599
+ # 7. PRINCIPAL VALIDATION (Resource policy specific)
600
+ registry.register(
601
+ checks.PrincipalValidationCheck()
602
+ ) # Principal validation (resource policies)
603
+
604
+ # Note: policy_type_validation is a standalone function (not a class-based check)
605
+ # and is called separately in the validation flow
468
606
 
469
607
  return registry