iam-policy-validator 1.3.1__py3-none-any.whl → 1.5.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 (41) hide show
  1. {iam_policy_validator-1.3.1.dist-info → iam_policy_validator-1.5.0.dist-info}/METADATA +164 -19
  2. iam_policy_validator-1.5.0.dist-info/RECORD +67 -0
  3. iam_validator/__version__.py +1 -1
  4. iam_validator/checks/__init__.py +15 -3
  5. iam_validator/checks/action_condition_enforcement.py +1 -6
  6. iam_validator/checks/condition_key_validation.py +21 -1
  7. iam_validator/checks/full_wildcard.py +67 -0
  8. iam_validator/checks/policy_size.py +1 -0
  9. iam_validator/checks/policy_type_validation.py +299 -0
  10. iam_validator/checks/principal_validation.py +776 -0
  11. iam_validator/checks/sensitive_action.py +178 -0
  12. iam_validator/checks/service_wildcard.py +105 -0
  13. iam_validator/checks/sid_uniqueness.py +45 -7
  14. iam_validator/checks/utils/sensitive_action_matcher.py +39 -31
  15. iam_validator/checks/wildcard_action.py +62 -0
  16. iam_validator/checks/wildcard_resource.py +131 -0
  17. iam_validator/commands/download_services.py +3 -8
  18. iam_validator/commands/post_to_pr.py +7 -0
  19. iam_validator/commands/validate.py +204 -16
  20. iam_validator/core/aws_fetcher.py +25 -12
  21. iam_validator/core/check_registry.py +25 -21
  22. iam_validator/core/config/__init__.py +83 -0
  23. iam_validator/core/config/aws_api.py +35 -0
  24. iam_validator/core/config/condition_requirements.py +535 -0
  25. iam_validator/core/config/defaults.py +390 -0
  26. iam_validator/core/config/principal_requirements.py +421 -0
  27. iam_validator/core/config/sensitive_actions.py +133 -0
  28. iam_validator/core/config/service_principals.py +95 -0
  29. iam_validator/core/config/wildcards.py +124 -0
  30. iam_validator/core/config_loader.py +29 -9
  31. iam_validator/core/formatters/enhanced.py +11 -5
  32. iam_validator/core/formatters/sarif.py +78 -14
  33. iam_validator/core/models.py +13 -3
  34. iam_validator/core/policy_checks.py +39 -6
  35. iam_validator/core/pr_commenter.py +30 -9
  36. iam_policy_validator-1.3.1.dist-info/RECORD +0 -54
  37. iam_validator/checks/security_best_practices.py +0 -535
  38. iam_validator/core/defaults.py +0 -366
  39. {iam_policy_validator-1.3.1.dist-info → iam_policy_validator-1.5.0.dist-info}/WHEEL +0 -0
  40. {iam_policy_validator-1.3.1.dist-info → iam_policy_validator-1.5.0.dist-info}/entry_points.txt +0 -0
  41. {iam_policy_validator-1.3.1.dist-info → iam_policy_validator-1.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -9,20 +9,15 @@ from pathlib import Path
9
9
 
10
10
  import httpx
11
11
  from rich.console import Console
12
- from rich.progress import (
13
- BarColumn,
14
- Progress,
15
- TaskID,
16
- TextColumn,
17
- TimeRemainingColumn,
18
- )
12
+ from rich.progress import BarColumn, Progress, TaskID, TextColumn, TimeRemainingColumn
19
13
 
20
14
  from iam_validator.commands.base import Command
15
+ from iam_validator.core.config import AWS_SERVICE_REFERENCE_BASE_URL
21
16
 
22
17
  logger = logging.getLogger(__name__)
23
18
  console = Console()
24
19
 
25
- BASE_URL = "https://servicereference.us-east-1.amazonaws.com/"
20
+ BASE_URL = AWS_SERVICE_REFERENCE_BASE_URL
26
21
  DEFAULT_OUTPUT_DIR = Path("aws_services")
27
22
 
28
23
 
@@ -68,12 +68,19 @@ Examples:
68
68
  help="Don't add summary comment",
69
69
  )
70
70
 
71
+ parser.add_argument(
72
+ "--config",
73
+ "-c",
74
+ help="Path to configuration file (for fail_on_severity setting)",
75
+ )
76
+
71
77
  async def execute(self, args: argparse.Namespace) -> int:
72
78
  """Execute the post-to-pr command."""
73
79
  success = await post_report_to_pr(
74
80
  args.report,
75
81
  create_review=args.create_review,
76
82
  add_summary=args.add_summary,
83
+ config_path=getattr(args, "config", None),
77
84
  )
78
85
 
79
86
  return 0 if success else 1
@@ -3,8 +3,10 @@
3
3
  import argparse
4
4
  import logging
5
5
  import os
6
+ from typing import cast
6
7
 
7
8
  from iam_validator.commands.base import Command
9
+ from iam_validator.core.models import PolicyType, ValidationReport
8
10
  from iam_validator.core.policy_checks import validate_policies
9
11
  from iam_validator.core.policy_loader import PolicyLoader
10
12
  from iam_validator.core.report import ReportGenerator
@@ -41,8 +43,20 @@ Examples:
41
43
  # Generate JSON output
42
44
  iam-validator validate --path ./policies/ --format json --output report.json
43
45
 
44
- # Post to GitHub PR with line comments
45
- iam-validator validate --path ./policies/ --github-comment --github-review
46
+ # Validate resource policies (S3 bucket policies, SNS topics, etc.)
47
+ iam-validator validate --path ./bucket-policies/ --policy-type RESOURCE_POLICY
48
+
49
+ # GitHub integration - all options (PR comment + review comments + job summary)
50
+ iam-validator validate --path ./policies/ --github-comment --github-review --github-summary
51
+
52
+ # Only line-specific review comments (clean, minimal)
53
+ iam-validator validate --path ./policies/ --github-review
54
+
55
+ # Only PR summary comment
56
+ iam-validator validate --path ./policies/ --github-comment
57
+
58
+ # Only GitHub Actions job summary
59
+ iam-validator validate --path ./policies/ --github-summary
46
60
  """
47
61
 
48
62
  def add_arguments(self, parser: argparse.ArgumentParser) -> None:
@@ -82,16 +96,37 @@ Examples:
82
96
  help="Fail validation if warnings are found (default: only fail on errors)",
83
97
  )
84
98
 
99
+ parser.add_argument(
100
+ "--policy-type",
101
+ "-t",
102
+ choices=[
103
+ "IDENTITY_POLICY",
104
+ "RESOURCE_POLICY",
105
+ "SERVICE_CONTROL_POLICY",
106
+ "RESOURCE_CONTROL_POLICY",
107
+ ],
108
+ default="IDENTITY_POLICY",
109
+ help="Type of IAM policy being validated (default: IDENTITY_POLICY). "
110
+ "Enables policy-type-specific validation (e.g., requiring Principal for resource policies, "
111
+ "strict RCP requirements for resource control policies)",
112
+ )
113
+
85
114
  parser.add_argument(
86
115
  "--github-comment",
87
116
  action="store_true",
88
- help="Post validation results as GitHub PR comment",
117
+ help="Post summary comment to PR conversation",
89
118
  )
90
119
 
91
120
  parser.add_argument(
92
121
  "--github-review",
93
122
  action="store_true",
94
- help="Create line-specific review comments on PR (requires --github-comment)",
123
+ help="Create line-specific review comments on PR files",
124
+ )
125
+
126
+ parser.add_argument(
127
+ "--github-summary",
128
+ action="store_true",
129
+ help="Write summary to GitHub Actions job summary (visible in Actions tab)",
95
130
  )
96
131
 
97
132
  parser.add_argument(
@@ -131,6 +166,18 @@ Examples:
131
166
  help="Number of policies to process per batch (default: 10, only with --stream)",
132
167
  )
133
168
 
169
+ parser.add_argument(
170
+ "--no-summary",
171
+ action="store_true",
172
+ help="Hide Executive Summary section in enhanced format output",
173
+ )
174
+
175
+ parser.add_argument(
176
+ "--no-severity-breakdown",
177
+ action="store_true",
178
+ help="Hide Issue Severity Breakdown section in enhanced format output",
179
+ )
180
+
134
181
  async def execute(self, args: argparse.Namespace) -> int:
135
182
  """Execute the validate command."""
136
183
  # Check if streaming mode is enabled
@@ -165,11 +212,13 @@ Examples:
165
212
  use_registry = not getattr(args, "no_registry", False)
166
213
  config_path = getattr(args, "config", None)
167
214
  custom_checks_dir = getattr(args, "custom_checks_dir", None)
215
+ policy_type = cast(PolicyType, getattr(args, "policy_type", "IDENTITY_POLICY"))
168
216
  results = await validate_policies(
169
217
  policies,
170
218
  config_path=config_path,
171
219
  use_registry=use_registry,
172
220
  custom_checks_dir=custom_checks_dir,
221
+ policy_type=policy_type,
173
222
  )
174
223
 
175
224
  # Generate report
@@ -192,7 +241,14 @@ Examples:
192
241
  print(generator.generate_github_comment(report))
193
242
  else:
194
243
  # Use formatter registry for other formats (enhanced, html, csv, sarif)
195
- output_content = generator.format_report(report, args.format)
244
+ # Pass options for enhanced format
245
+ format_options = {}
246
+ if args.format == "enhanced":
247
+ format_options["show_summary"] = not getattr(args, "no_summary", False)
248
+ format_options["show_severity_breakdown"] = not getattr(
249
+ args, "no_severity_breakdown", False
250
+ )
251
+ output_content = generator.format_report(report, args.format, **format_options)
196
252
  if args.output:
197
253
  with open(args.output, "w", encoding="utf-8") as f:
198
254
  f.write(output_content)
@@ -201,19 +257,28 @@ Examples:
201
257
  print(output_content)
202
258
 
203
259
  # Post to GitHub if configured
204
- if args.github_comment:
260
+ if args.github_comment or getattr(args, "github_review", False):
261
+ from iam_validator.core.config_loader import ConfigLoader
205
262
  from iam_validator.core.pr_commenter import PRCommenter
206
263
 
264
+ # Load config to get fail_on_severity setting
265
+ config = ConfigLoader.load_config(config_path)
266
+ fail_on_severities = config.get_setting("fail_on_severity", ["error", "critical"])
267
+
207
268
  async with GitHubIntegration() as github:
208
- commenter = PRCommenter(github)
269
+ commenter = PRCommenter(github, fail_on_severities=fail_on_severities)
209
270
  success = await commenter.post_findings_to_pr(
210
271
  report,
211
- create_review=getattr(args, "github_review", True),
212
- add_summary_comment=True,
272
+ create_review=getattr(args, "github_review", False),
273
+ add_summary_comment=args.github_comment,
213
274
  )
214
275
  if not success:
215
276
  logging.error("Failed to post to GitHub PR")
216
277
 
278
+ # Write to GitHub Actions job summary if configured
279
+ if getattr(args, "github_summary", False):
280
+ self._write_github_actions_summary(report)
281
+
217
282
  # Return exit code based on validation results
218
283
  if args.fail_on_warnings:
219
284
  return 0 if report.total_issues == 0 else 1
@@ -234,12 +299,13 @@ Examples:
234
299
  use_registry = not getattr(args, "no_registry", False)
235
300
  config_path = getattr(args, "config", None)
236
301
  custom_checks_dir = getattr(args, "custom_checks_dir", None)
302
+ policy_type = cast(PolicyType, getattr(args, "policy_type", "IDENTITY_POLICY"))
237
303
 
238
304
  all_results = []
239
305
  total_processed = 0
240
306
 
241
307
  # Clean up old review comments at the start (before posting any new ones)
242
- if args.github_comment and getattr(args, "github_review", False):
308
+ if getattr(args, "github_review", False):
243
309
  await self._cleanup_old_comments()
244
310
 
245
311
  logging.info(f"Starting streaming validation from {len(args.paths)} path(s)")
@@ -257,6 +323,7 @@ Examples:
257
323
  config_path=config_path,
258
324
  use_registry=use_registry,
259
325
  custom_checks_dir=custom_checks_dir,
326
+ policy_type=policy_type,
260
327
  )
261
328
 
262
329
  if results:
@@ -272,7 +339,7 @@ Examples:
272
339
  # Note: validation_success tracks overall status
273
340
 
274
341
  # Post to GitHub immediately for this file (progressive PR comments)
275
- if args.github_comment and getattr(args, "github_review", False):
342
+ if getattr(args, "github_review", False):
276
343
  await self._post_file_review(result, args)
277
344
 
278
345
  if total_processed == 0:
@@ -300,7 +367,14 @@ Examples:
300
367
  print(generator.generate_github_comment(report))
301
368
  else:
302
369
  # Use formatter registry for other formats (enhanced, html, csv, sarif)
303
- output_content = generator.format_report(report, args.format)
370
+ # Pass options for enhanced format
371
+ format_options = {}
372
+ if args.format == "enhanced":
373
+ format_options["show_summary"] = not getattr(args, "no_summary", False)
374
+ format_options["show_severity_breakdown"] = not getattr(
375
+ args, "no_severity_breakdown", False
376
+ )
377
+ output_content = generator.format_report(report, args.format, **format_options)
304
378
  if args.output:
305
379
  with open(args.output, "w", encoding="utf-8") as f:
306
380
  f.write(output_content)
@@ -308,20 +382,29 @@ Examples:
308
382
  else:
309
383
  print(output_content)
310
384
 
311
- # Post summary comment to GitHub
385
+ # Post summary comment to GitHub (if requested and not already posted per-file reviews)
312
386
  if args.github_comment:
387
+ from iam_validator.core.config_loader import ConfigLoader
313
388
  from iam_validator.core.pr_commenter import PRCommenter
314
389
 
390
+ # Load config to get fail_on_severity setting
391
+ config = ConfigLoader.load_config(config_path)
392
+ fail_on_severities = config.get_setting("fail_on_severity", ["error", "critical"])
393
+
315
394
  async with GitHubIntegration() as github:
316
- commenter = PRCommenter(github)
395
+ commenter = PRCommenter(github, fail_on_severities=fail_on_severities)
317
396
  success = await commenter.post_findings_to_pr(
318
397
  report,
319
- create_review=False, # Already posted per-file reviews
398
+ create_review=False, # Already posted per-file reviews in streaming mode
320
399
  add_summary_comment=True,
321
400
  )
322
401
  if not success:
323
402
  logging.error("Failed to post summary to GitHub PR")
324
403
 
404
+ # Write to GitHub Actions job summary if configured
405
+ if getattr(args, "github_summary", False):
406
+ self._write_github_actions_summary(report)
407
+
325
408
  # Return exit code based on validation results
326
409
  if args.fail_on_warnings:
327
410
  return 0 if report.total_issues == 0 else 1
@@ -353,15 +436,23 @@ Examples:
353
436
  This provides progressive feedback in PRs as files are processed.
354
437
  """
355
438
  try:
439
+ from iam_validator.core.config_loader import ConfigLoader
356
440
  from iam_validator.core.pr_commenter import PRCommenter
357
441
 
358
442
  async with GitHubIntegration() as github:
359
443
  if not github.is_configured():
360
444
  return
361
445
 
446
+ # Load config to get fail_on_severity setting
447
+ config_path = getattr(args, "config", None)
448
+ config = ConfigLoader.load_config(config_path)
449
+ fail_on_severities = config.get_setting("fail_on_severity", ["error", "critical"])
450
+
362
451
  # In streaming mode, don't cleanup comments (we want to keep earlier files)
363
452
  # Cleanup will happen once at the end
364
- commenter = PRCommenter(github, cleanup_old_comments=False)
453
+ commenter = PRCommenter(
454
+ github, cleanup_old_comments=False, fail_on_severities=fail_on_severities
455
+ )
365
456
 
366
457
  # Create a mini-report for just this file
367
458
  generator = ReportGenerator()
@@ -375,3 +466,100 @@ Examples:
375
466
  )
376
467
  except Exception as e:
377
468
  logging.warning(f"Failed to post review for {result.policy_file}: {e}")
469
+
470
+ def _write_github_actions_summary(self, report: ValidationReport) -> None:
471
+ """Write a high-level summary to GitHub Actions job summary.
472
+
473
+ This appears in the Actions tab and provides a quick overview without all details.
474
+ Uses GITHUB_STEP_SUMMARY environment variable.
475
+
476
+ Args:
477
+ report: Validation report to summarize
478
+ """
479
+ summary_file = os.getenv("GITHUB_STEP_SUMMARY")
480
+ if not summary_file:
481
+ logging.warning(
482
+ "--github-summary specified but GITHUB_STEP_SUMMARY env var not found. "
483
+ "This feature only works in GitHub Actions."
484
+ )
485
+ return
486
+
487
+ try:
488
+ # Generate high-level summary (no detailed issue list)
489
+ summary_parts = []
490
+
491
+ # Header with status
492
+ if report.total_issues == 0:
493
+ summary_parts.append("# ✅ IAM Policy Validation - Passed")
494
+ elif report.invalid_policies > 0:
495
+ summary_parts.append("# ❌ IAM Policy Validation - Failed")
496
+ else:
497
+ summary_parts.append("# ⚠️ IAM Policy Validation - Security Issues Found")
498
+
499
+ summary_parts.append("")
500
+
501
+ # Summary table
502
+ summary_parts.append("## Summary")
503
+ summary_parts.append("")
504
+ summary_parts.append("| Metric | Count |")
505
+ summary_parts.append("|--------|-------|")
506
+ summary_parts.append(f"| Total Policies | {report.total_policies} |")
507
+ summary_parts.append(f"| Valid Policies | {report.valid_policies} |")
508
+ summary_parts.append(f"| Invalid Policies | {report.invalid_policies} |")
509
+ summary_parts.append(
510
+ f"| Policies with Security Issues | {report.policies_with_security_issues} |"
511
+ )
512
+ summary_parts.append(f"| **Total Issues** | **{report.total_issues}** |")
513
+
514
+ # Issue breakdown by severity if there are issues
515
+ if report.total_issues > 0:
516
+ summary_parts.append("")
517
+ summary_parts.append("## Issues by Severity")
518
+ summary_parts.append("")
519
+
520
+ # Count issues by severity
521
+ severity_counts: dict[str, int] = {}
522
+ for result in report.results:
523
+ for issue in result.issues:
524
+ severity_counts[issue.severity] = severity_counts.get(issue.severity, 0) + 1
525
+
526
+ # Sort by severity rank (highest first)
527
+ from iam_validator.core.models import ValidationIssue
528
+
529
+ sorted_severities = sorted(
530
+ severity_counts.items(),
531
+ key=lambda x: ValidationIssue.SEVERITY_RANK.get(x[0], 0),
532
+ reverse=True,
533
+ )
534
+
535
+ summary_parts.append("| Severity | Count |")
536
+ summary_parts.append("|----------|-------|")
537
+ for severity, count in sorted_severities:
538
+ emoji = {
539
+ "error": "❌",
540
+ "critical": "🔴",
541
+ "high": "🟠",
542
+ "warning": "⚠️",
543
+ "medium": "🟡",
544
+ "low": "🔵",
545
+ "info": "ℹ️",
546
+ }.get(severity, "•")
547
+ summary_parts.append(f"| {emoji} {severity.upper()} | {count} |")
548
+
549
+ # Add footer with links
550
+ summary_parts.append("")
551
+ summary_parts.append("---")
552
+ summary_parts.append("")
553
+ summary_parts.append(
554
+ "📝 For detailed findings, check the PR comments or review the workflow logs."
555
+ )
556
+
557
+ # Write to summary file (append mode)
558
+ with open(summary_file, "a", encoding="utf-8") as f:
559
+ f.write("\n".join(summary_parts))
560
+ f.write("\n")
561
+
562
+ logging.info("Wrote summary to GitHub Actions job summary")
563
+
564
+ except Exception as e:
565
+ logging.warning(f"Failed to write GitHub Actions summary: {e}")
@@ -33,6 +33,7 @@ from typing import Any
33
33
 
34
34
  import httpx
35
35
 
36
+ from iam_validator.core.config import AWS_SERVICE_REFERENCE_BASE_URL
36
37
  from iam_validator.core.models import ServiceDetail, ServiceInfo
37
38
 
38
39
  logger = logging.getLogger(__name__)
@@ -121,7 +122,7 @@ class CompiledPatterns:
121
122
  class AWSServiceFetcher:
122
123
  """Fetches AWS service information from the AWS service reference API with enhanced performance features."""
123
124
 
124
- BASE_URL = "https://servicereference.us-east-1.amazonaws.com/"
125
+ BASE_URL = AWS_SERVICE_REFERENCE_BASE_URL
125
126
 
126
127
  # Common AWS services to pre-fetch
127
128
  # All other services will be fetched on-demand (lazy loading if found in policies)
@@ -797,8 +798,15 @@ class AWSServiceFetcher:
797
798
 
798
799
  async def validate_condition_key(
799
800
  self, action: str, condition_key: str
800
- ) -> tuple[bool, str | None]:
801
- """Validate condition key with optimized caching."""
801
+ ) -> tuple[bool, str | None, str | None]:
802
+ """Validate condition key with optimized caching.
803
+
804
+ Returns:
805
+ Tuple of (is_valid, error_message, warning_message)
806
+ - is_valid: True if key is valid (even with warning)
807
+ - error_message: Error message if invalid (is_valid=False)
808
+ - warning_message: Warning message if valid but not recommended
809
+ """
802
810
  try:
803
811
  from iam_validator.core.aws_global_conditions import get_global_conditions
804
812
 
@@ -814,6 +822,7 @@ class AWSServiceFetcher:
814
822
  return (
815
823
  False,
816
824
  f"Invalid AWS global condition key: '{condition_key}'.",
825
+ None,
817
826
  )
818
827
 
819
828
  # Fetch service detail (cached)
@@ -821,7 +830,7 @@ class AWSServiceFetcher:
821
830
 
822
831
  # Check service-specific condition keys
823
832
  if condition_key in service_detail.condition_keys:
824
- return True, None
833
+ return True, None, None
825
834
 
826
835
  # Check action-specific condition keys
827
836
  if action_name in service_detail.actions:
@@ -830,29 +839,33 @@ class AWSServiceFetcher:
830
839
  action_detail.action_condition_keys
831
840
  and condition_key in action_detail.action_condition_keys
832
841
  ):
833
- return True, None
842
+ return True, None, None
834
843
 
835
844
  # If it's a global key but the action has specific condition keys defined,
836
- # check if the global key is explicitly listed in the action's supported keys
845
+ # AWS allows it but the key may not be available in every request context
837
846
  if is_global_key and action_detail.action_condition_keys is not None:
838
- return (
839
- False,
840
- f"Condition key '{condition_key}' is not supported by action '{action}'. "
841
- f"This action has a specific set of supported condition keys.",
847
+ warning_msg = (
848
+ f"Global condition key '{condition_key}' is used with action '{action}'. "
849
+ f"While global condition keys can be used across all AWS services, "
850
+ f"the key may not be available in every request context. "
851
+ f"Verify that '{condition_key}' is available for this specific action's request context. "
852
+ f"Consider using '*IfExists' operators (e.g., StringEqualsIfExists) if the key might be missing."
842
853
  )
854
+ return True, None, warning_msg
843
855
 
844
856
  # If it's a global key and action doesn't define specific keys, allow it
845
857
  if is_global_key:
846
- return True, None
858
+ return True, None, None
847
859
 
848
860
  return (
849
861
  False,
850
862
  f"Condition key '{condition_key}' is not valid for action '{action}'",
863
+ None,
851
864
  )
852
865
 
853
866
  except Exception as e:
854
867
  logger.error(f"Error validating condition key {condition_key} for {action}: {e}")
855
- return False, f"Failed to validate condition key: {str(e)}"
868
+ return False, f"Failed to validate condition key: {str(e)}", None
856
869
 
857
870
  async def clear_caches(self) -> None:
858
871
  """Clear all caches (memory and disk)."""
@@ -98,6 +98,7 @@ class PolicyCheck(ABC):
98
98
  policy_file: str,
99
99
  fetcher: AWSServiceFetcher,
100
100
  config: CheckConfig,
101
+ **kwargs,
101
102
  ) -> list[ValidationIssue]:
102
103
  """
103
104
  Execute the check on the entire policy (optional method).
@@ -113,6 +114,7 @@ class PolicyCheck(ABC):
113
114
  policy_file: Path to the policy file (for context/reporting)
114
115
  fetcher: AWS service fetcher for validation against AWS APIs
115
116
  config: Configuration for this check instance
117
+ **kwargs: Additional context (policy_type, etc.)
116
118
 
117
119
  Returns:
118
120
  List of ValidationIssue objects found by this check
@@ -353,6 +355,7 @@ class CheckRegistry:
353
355
  policy: "IAMPolicy",
354
356
  policy_file: str,
355
357
  fetcher: AWSServiceFetcher,
358
+ policy_type: str = "IDENTITY_POLICY",
356
359
  ) -> list[ValidationIssue]:
357
360
  """
358
361
  Execute all enabled policy-level checks.
@@ -364,6 +367,7 @@ class CheckRegistry:
364
367
  policy: The complete IAM policy to validate
365
368
  policy_file: Path to the policy file (for context/reporting)
366
369
  fetcher: AWS service fetcher for API calls
370
+ policy_type: Type of policy (IDENTITY_POLICY, RESOURCE_POLICY, SERVICE_CONTROL_POLICY)
367
371
 
368
372
  Returns:
369
373
  List of all ValidationIssue objects from all policy-level checks
@@ -383,7 +387,9 @@ class CheckRegistry:
383
387
  config = self.get_config(check.check_id)
384
388
  if config:
385
389
  try:
386
- issues = await check.execute_policy(policy, policy_file, fetcher, config)
390
+ issues = await check.execute_policy(
391
+ policy, policy_file, fetcher, config, policy_type=policy_type
392
+ )
387
393
  all_issues.extend(issues)
388
394
  except Exception as e:
389
395
  print(f"Warning: Check '{check.check_id}' failed: {e}")
@@ -394,7 +400,9 @@ class CheckRegistry:
394
400
  for check in policy_level_checks:
395
401
  config = self.get_config(check.check_id)
396
402
  if config:
397
- task = check.execute_policy(policy, policy_file, fetcher, config)
403
+ task = check.execute_policy(
404
+ policy, policy_file, fetcher, config, policy_type=policy_type
405
+ )
398
406
  tasks.append(task)
399
407
 
400
408
  # Wait for all checks to complete
@@ -432,25 +440,21 @@ def create_default_registry(
432
440
 
433
441
  if include_builtin_checks:
434
442
  # Import and register built-in checks
435
- from iam_validator.checks import (
436
- ActionConditionEnforcementCheck,
437
- ActionResourceConstraintCheck,
438
- ActionValidationCheck,
439
- ConditionKeyValidationCheck,
440
- PolicySizeCheck,
441
- ResourceValidationCheck,
442
- SecurityBestPracticesCheck,
443
- SidUniquenessCheck,
444
- )
445
-
446
- registry.register(ActionValidationCheck())
447
- registry.register(ConditionKeyValidationCheck())
448
- registry.register(ResourceValidationCheck())
449
- registry.register(SecurityBestPracticesCheck())
450
- registry.register(ActionConditionEnforcementCheck())
451
- registry.register(ActionResourceConstraintCheck())
452
- registry.register(SidUniquenessCheck())
453
- registry.register(PolicySizeCheck())
443
+ from iam_validator import checks
444
+
445
+ registry.register(checks.ActionValidationCheck())
446
+ registry.register(checks.ConditionKeyValidationCheck())
447
+ registry.register(checks.ResourceValidationCheck())
448
+ registry.register(checks.WildcardActionCheck())
449
+ registry.register(checks.WildcardResourceCheck())
450
+ registry.register(checks.FullWildcardCheck())
451
+ registry.register(checks.ServiceWildcardCheck())
452
+ registry.register(checks.SensitiveActionCheck())
453
+ registry.register(checks.ActionConditionEnforcementCheck())
454
+ registry.register(checks.ActionResourceConstraintCheck())
455
+ registry.register(checks.SidUniquenessCheck())
456
+ registry.register(checks.PolicySizeCheck())
457
+ registry.register(checks.PrincipalValidationCheck())
454
458
 
455
459
  # Note: SID uniqueness check is registered above but its actual execution
456
460
  # happens at the policy level in _validate_policy_with_registry() since it
@@ -0,0 +1,83 @@
1
+ """
2
+ Core configuration modules for IAM Policy Validator.
3
+
4
+ This package contains default configuration data used by validators, organized into
5
+ logical modules for better maintainability and performance.
6
+
7
+ All configuration is defined as Python code (not YAML/JSON) for:
8
+ - Faster loading (no parsing overhead)
9
+ - Better PyPI packaging (no data files to manage)
10
+ - Type hints and IDE support
11
+ - Compiled to .pyc for even faster imports
12
+
13
+ Performance benefits:
14
+ - 5-10x faster than YAML parsing
15
+ - Zero runtime parsing overhead
16
+ - Lazy loading support
17
+ - O(1) frozenset lookups
18
+ """
19
+
20
+ from iam_validator.core.config.aws_api import (
21
+ AWS_SERVICE_REFERENCE_BASE_URL,
22
+ get_service_reference_url,
23
+ )
24
+ from iam_validator.core.config.condition_requirements import (
25
+ ALL_CONDITION_REQUIREMENTS,
26
+ DEFAULT_CONDITION_REQUIREMENTS,
27
+ get_all_requirement_names,
28
+ get_default_requirements,
29
+ get_requirement,
30
+ get_requirements_by_names,
31
+ get_requirements_by_severity,
32
+ )
33
+ from iam_validator.core.config.defaults import DEFAULT_CONFIG
34
+ from iam_validator.core.config.principal_requirements import (
35
+ ALL_PRINCIPAL_REQUIREMENTS,
36
+ DEFAULT_ENABLED_REQUIREMENTS,
37
+ get_all_principal_requirement_names,
38
+ get_default_principal_requirements,
39
+ get_principal_requirement,
40
+ get_principal_requirements_by_names,
41
+ get_principal_requirements_by_severity,
42
+ )
43
+ from iam_validator.core.config.sensitive_actions import (
44
+ DEFAULT_SENSITIVE_ACTIONS,
45
+ get_sensitive_actions,
46
+ )
47
+ from iam_validator.core.config.service_principals import DEFAULT_SERVICE_PRINCIPALS
48
+ from iam_validator.core.config.wildcards import (
49
+ DEFAULT_ALLOWED_WILDCARDS,
50
+ DEFAULT_SERVICE_WILDCARDS,
51
+ )
52
+
53
+ __all__ = [
54
+ # Default configuration
55
+ "DEFAULT_CONFIG",
56
+ # AWS API endpoints
57
+ "AWS_SERVICE_REFERENCE_BASE_URL",
58
+ "get_service_reference_url",
59
+ # Sensitive actions
60
+ "DEFAULT_SENSITIVE_ACTIONS",
61
+ "get_sensitive_actions",
62
+ # Condition requirements (for actions)
63
+ "DEFAULT_CONDITION_REQUIREMENTS",
64
+ "ALL_CONDITION_REQUIREMENTS",
65
+ "get_default_requirements",
66
+ "get_requirement",
67
+ "get_all_requirement_names",
68
+ "get_requirements_by_names",
69
+ "get_requirements_by_severity",
70
+ # Principal requirements (for principals)
71
+ "ALL_PRINCIPAL_REQUIREMENTS",
72
+ "DEFAULT_ENABLED_REQUIREMENTS",
73
+ "get_default_principal_requirements",
74
+ "get_principal_requirement",
75
+ "get_all_principal_requirement_names",
76
+ "get_principal_requirements_by_names",
77
+ "get_principal_requirements_by_severity",
78
+ # Wildcards
79
+ "DEFAULT_ALLOWED_WILDCARDS",
80
+ "DEFAULT_SERVICE_WILDCARDS",
81
+ # Service principals
82
+ "DEFAULT_SERVICE_PRINCIPALS",
83
+ ]