iam-policy-validator 1.6.0__py3-none-any.whl → 1.7.1__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 (33) hide show
  1. iam_policy_validator-1.7.1.dist-info/METADATA +429 -0
  2. {iam_policy_validator-1.6.0.dist-info → iam_policy_validator-1.7.1.dist-info}/RECORD +32 -31
  3. iam_validator/__version__.py +1 -1
  4. iam_validator/checks/action_condition_enforcement.py +3 -1
  5. iam_validator/checks/action_resource_matching.py +23 -6
  6. iam_validator/checks/full_wildcard.py +5 -1
  7. iam_validator/checks/policy_size.py +3 -7
  8. iam_validator/checks/policy_type_validation.py +9 -3
  9. iam_validator/checks/principal_validation.py +1 -1
  10. iam_validator/checks/resource_validation.py +54 -24
  11. iam_validator/checks/sensitive_action.py +5 -1
  12. iam_validator/checks/service_wildcard.py +3 -1
  13. iam_validator/checks/utils/sensitive_action_matcher.py +1 -2
  14. iam_validator/checks/utils/wildcard_expansion.py +1 -2
  15. iam_validator/checks/wildcard_action.py +7 -2
  16. iam_validator/checks/wildcard_resource.py +5 -1
  17. iam_validator/commands/analyze.py +98 -1
  18. iam_validator/commands/validate.py +4 -2
  19. iam_validator/core/access_analyzer.py +5 -0
  20. iam_validator/core/access_analyzer_report.py +2 -5
  21. iam_validator/core/aws_fetcher.py +14 -4
  22. iam_validator/core/config/config_loader.py +3 -6
  23. iam_validator/core/constants.py +74 -0
  24. iam_validator/core/models.py +29 -13
  25. iam_validator/core/pr_commenter.py +104 -18
  26. iam_validator/core/report.py +49 -36
  27. iam_validator/integrations/github_integration.py +21 -1
  28. iam_validator/sdk/arn_matching.py +108 -0
  29. iam_validator/utils/regex.py +7 -8
  30. iam_policy_validator-1.6.0.dist-info/METADATA +0 -1050
  31. {iam_policy_validator-1.6.0.dist-info → iam_policy_validator-1.7.1.dist-info}/WHEEL +0 -0
  32. {iam_policy_validator-1.6.0.dist-info → iam_policy_validator-1.7.1.dist-info}/entry_points.txt +0 -0
  33. {iam_policy_validator-1.6.0.dist-info → iam_policy_validator-1.7.1.dist-info}/licenses/LICENSE +0 -0
@@ -16,6 +16,7 @@ from typing import TYPE_CHECKING
16
16
 
17
17
  from iam_validator.core.aws_fetcher import AWSServiceFetcher
18
18
  from iam_validator.core.check_registry import CheckConfig, PolicyCheck
19
+ from iam_validator.core.constants import AWS_POLICY_SIZE_LIMITS
19
20
  from iam_validator.core.models import Statement, ValidationIssue
20
21
 
21
22
  if TYPE_CHECKING:
@@ -25,13 +26,8 @@ if TYPE_CHECKING:
25
26
  class PolicySizeCheck(PolicyCheck):
26
27
  """Validates that IAM policies don't exceed AWS size limits."""
27
28
 
28
- # AWS IAM policy size limits (in characters, excluding whitespace)
29
- DEFAULT_LIMITS = {
30
- "managed": 6144,
31
- "inline_user": 2048,
32
- "inline_group": 5120,
33
- "inline_role": 10240,
34
- }
29
+ # AWS IAM policy size limits (loaded from constants module)
30
+ DEFAULT_LIMITS = AWS_POLICY_SIZE_LIMITS
35
31
 
36
32
  @property
37
33
  def check_id(self) -> str:
@@ -71,6 +71,7 @@ async def execute_policy(
71
71
  line_number=statement.line_number,
72
72
  suggestion="Add a Principal element to specify who can access this resource.\n"
73
73
  "Example:\n"
74
+ "```json\n"
74
75
  "{\n"
75
76
  ' "Effect": "Allow",\n'
76
77
  ' "Principal": {\n'
@@ -78,7 +79,8 @@ async def execute_policy(
78
79
  " },\n"
79
80
  ' "Action": "s3:GetObject",\n'
80
81
  ' "Resource": "arn:aws:s3:::bucket/*"\n'
81
- "}",
82
+ "}\n"
83
+ "```",
82
84
  )
83
85
  )
84
86
 
@@ -101,11 +103,13 @@ async def execute_policy(
101
103
  line_number=statement.line_number,
102
104
  suggestion="Remove the Principal element from this identity policy statement.\n"
103
105
  "Example:\n"
106
+ "```json\n"
104
107
  "{\n"
105
108
  ' "Effect": "Allow",\n'
106
109
  ' "Action": "s3:GetObject",\n'
107
110
  ' "Resource": "arn:aws:s3:::bucket/*"\n'
108
- "}",
111
+ "}\n"
112
+ "```",
109
113
  )
110
114
  )
111
115
 
@@ -127,6 +131,7 @@ async def execute_policy(
127
131
  line_number=statement.line_number,
128
132
  suggestion="Remove the Principal element from this SCP statement.\n"
129
133
  "Example:\n"
134
+ "```json\n"
130
135
  "{\n"
131
136
  ' "Effect": "Deny",\n'
132
137
  ' "Action": "ec2:*",\n'
@@ -136,7 +141,8 @@ async def execute_policy(
136
141
  ' "ec2:Region": ["us-east-1", "us-west-2"]\n'
137
142
  " }\n"
138
143
  " }\n"
139
- "}",
144
+ "}\n"
145
+ "```",
140
146
  )
141
147
  )
142
148
 
@@ -668,7 +668,7 @@ class PrincipalValidationCheck(PolicyCheck):
668
668
 
669
669
  # Build example based on condition key type
670
670
  if example:
671
- parts.append(f"Example:\n{example}")
671
+ parts.append(f"Example:\n```json\n{example}\n```")
672
672
  else:
673
673
  # Auto-generate example
674
674
  example_lines = ['Add to "Condition" block:', f' "{operator}": {{']
@@ -4,15 +4,17 @@ import re
4
4
 
5
5
  from iam_validator.core.aws_fetcher import AWSServiceFetcher
6
6
  from iam_validator.core.check_registry import CheckConfig, PolicyCheck
7
+ from iam_validator.core.constants import DEFAULT_ARN_VALIDATION_PATTERN, MAX_ARN_LENGTH
7
8
  from iam_validator.core.models import Statement, ValidationIssue
9
+ from iam_validator.sdk.arn_matching import (
10
+ has_template_variables,
11
+ normalize_template_variables,
12
+ )
8
13
 
9
14
 
10
15
  class ResourceValidationCheck(PolicyCheck):
11
16
  """Validates ARN format for resources."""
12
17
 
13
- # Maximum allowed length for ARN to prevent ReDoS attacks
14
- MAX_ARN_LENGTH = 2048 # AWS max ARN length is ~2048 characters
15
-
16
18
  @property
17
19
  def check_id(self) -> str:
18
20
  return "resource_validation"
@@ -42,17 +44,21 @@ class ResourceValidationCheck(PolicyCheck):
42
44
 
43
45
  # Get ARN pattern from config, or use default
44
46
  # Pattern allows wildcards (*) in region and account fields
45
- arn_pattern_str = config.config.get(
46
- "arn_pattern",
47
- r"^arn:(aws|aws-cn|aws-us-gov|aws-eusc|aws-iso|aws-iso-b|aws-iso-e|aws-iso-f):[a-z0-9\-]+:[a-z0-9\-*]*:[0-9*]*:.+$",
48
- )
47
+ arn_pattern_str = config.config.get("arn_pattern", DEFAULT_ARN_VALIDATION_PATTERN)
49
48
 
50
- # Compile pattern with timeout protection (available in Python 3.11+)
49
+ # Compile pattern
51
50
  try:
52
51
  arn_pattern = re.compile(arn_pattern_str)
53
52
  except re.error:
54
- # Fallback to using fetcher's pre-compiled pattern
55
- arn_pattern = fetcher._patterns.arn_pattern
53
+ # Fallback to default pattern if custom pattern is invalid
54
+ arn_pattern = re.compile(DEFAULT_ARN_VALIDATION_PATTERN)
55
+
56
+ # Check if template variable support is enabled (default: true)
57
+ # Try global settings first, then check-specific config
58
+ allow_template_variables = config.root_config.get("settings", {}).get(
59
+ "allow_template_variables",
60
+ config.config.get("allow_template_variables", True),
61
+ )
56
62
 
57
63
  for resource in resources:
58
64
  # Skip wildcard resources (handled by security checks)
@@ -60,14 +66,14 @@ class ResourceValidationCheck(PolicyCheck):
60
66
  continue
61
67
 
62
68
  # Validate ARN length to prevent ReDoS attacks
63
- if len(resource) > self.MAX_ARN_LENGTH:
69
+ if len(resource) > MAX_ARN_LENGTH:
64
70
  issues.append(
65
71
  ValidationIssue(
66
72
  severity=self.get_severity(config),
67
73
  statement_sid=statement_sid,
68
74
  statement_index=statement_idx,
69
75
  issue_type="invalid_resource",
70
- message=f"Resource ARN exceeds maximum length ({len(resource)} > {self.MAX_ARN_LENGTH}): {resource[:100]}...",
76
+ message=f"Resource ARN exceeds maximum length ({len(resource)} > {MAX_ARN_LENGTH}): {resource[:100]}...",
71
77
  resource=resource[:100] + "...",
72
78
  suggestion="ARN is too long and may be invalid",
73
79
  line_number=line_number,
@@ -75,21 +81,45 @@ class ResourceValidationCheck(PolicyCheck):
75
81
  )
76
82
  continue
77
83
 
84
+ # Check if resource contains template variables
85
+ has_templates = has_template_variables(resource)
86
+
87
+ # If template variables are found and allowed, normalize them for validation
88
+ validation_resource = resource
89
+ if has_templates and allow_template_variables:
90
+ validation_resource = normalize_template_variables(resource)
91
+
78
92
  # Validate ARN format
79
93
  try:
80
- if not arn_pattern.match(resource):
81
- issues.append(
82
- ValidationIssue(
83
- severity=self.get_severity(config),
84
- statement_sid=statement_sid,
85
- statement_index=statement_idx,
86
- issue_type="invalid_resource",
87
- message=f"Invalid ARN format: {resource}",
88
- resource=resource,
89
- suggestion="ARN should follow format: arn:partition:service:region:account-id:resource",
90
- line_number=line_number,
94
+ if not arn_pattern.match(validation_resource):
95
+ # If original resource had templates and normalization didn't help,
96
+ # provide a more informative message
97
+ if has_templates and allow_template_variables:
98
+ issues.append(
99
+ ValidationIssue(
100
+ severity=self.get_severity(config),
101
+ statement_sid=statement_sid,
102
+ statement_index=statement_idx,
103
+ issue_type="invalid_resource",
104
+ message=f"Invalid ARN format even after normalizing template variables: {resource}",
105
+ resource=resource,
106
+ suggestion="ARN should follow format: arn:partition:service:region:account-id:resource (template variables like ${aws_account_id} are supported)",
107
+ line_number=line_number,
108
+ )
109
+ )
110
+ else:
111
+ issues.append(
112
+ ValidationIssue(
113
+ severity=self.get_severity(config),
114
+ statement_sid=statement_sid,
115
+ statement_index=statement_idx,
116
+ issue_type="invalid_resource",
117
+ message=f"Invalid ARN format: {resource}",
118
+ resource=resource,
119
+ suggestion="ARN should follow format: arn:partition:service:region:account-id:resource",
120
+ line_number=line_number,
121
+ )
91
122
  )
92
- )
93
123
  except Exception:
94
124
  # If regex matching fails (shouldn't happen with length check), treat as invalid
95
125
  issues.append(
@@ -143,7 +143,11 @@ class SensitiveActionCheck(PolicyCheck):
143
143
  )
144
144
 
145
145
  # Combine suggestion + example
146
- suggestion = f"{suggestion_text}\n\nExample:\n{example}" if example else suggestion_text
146
+ suggestion = (
147
+ f"{suggestion_text}\n\nExample:\n```json\n{example}\n```"
148
+ if example
149
+ else suggestion_text
150
+ )
147
151
 
148
152
  # Determine severity based on the highest severity action in the list
149
153
  # If single action, use its category severity
@@ -69,7 +69,9 @@ class ServiceWildcardCheck(PolicyCheck):
69
69
 
70
70
  # Combine suggestion + example
71
71
  suggestion = (
72
- f"{suggestion_text}\nExample:\n{example}" if example else suggestion_text
72
+ f"{suggestion_text}\nExample:\n```json\n{example}\n```"
73
+ if example
74
+ else suggestion_text
73
75
  )
74
76
 
75
77
  issues.append(
@@ -11,7 +11,6 @@ Performance optimizations:
11
11
 
12
12
  import re
13
13
  from functools import lru_cache
14
- from re import Pattern
15
14
 
16
15
  from iam_validator.core.check_registry import CheckConfig
17
16
  from iam_validator.core.config.sensitive_actions import get_sensitive_actions
@@ -69,7 +68,7 @@ DEFAULT_SENSITIVE_ACTIONS = _get_default_sensitive_actions()
69
68
 
70
69
  # Global regex pattern cache for performance
71
70
  @lru_cache(maxsize=256)
72
- def compile_pattern(pattern: str) -> Pattern[str] | None:
71
+ def compile_pattern(pattern: str) -> re.Pattern[str] | None:
73
72
  """Compile and cache regex patterns.
74
73
 
75
74
  Args:
@@ -6,7 +6,6 @@ to their actual action names using the AWS Service Reference API.
6
6
 
7
7
  import re
8
8
  from functools import lru_cache
9
- from re import Pattern
10
9
 
11
10
  from iam_validator.core.aws_fetcher import AWSServiceFetcher
12
11
 
@@ -14,7 +13,7 @@ from iam_validator.core.aws_fetcher import AWSServiceFetcher
14
13
  # Global cache for compiled wildcard patterns (shared across checks)
15
14
  # Using lru_cache for O(1) pattern reuse and 20-30x performance improvement
16
15
  @lru_cache(maxsize=512)
17
- def compile_wildcard_pattern(pattern: str) -> Pattern[str]:
16
+ def compile_wildcard_pattern(pattern: str) -> re.Pattern[str]:
18
17
  """Compile and cache wildcard patterns for O(1) reuse.
19
18
 
20
19
  Args:
@@ -40,12 +40,17 @@ class WildcardActionCheck(PolicyCheck):
40
40
  if "*" in actions:
41
41
  message = config.config.get("message", "Statement allows all actions (*)")
42
42
  suggestion_text = config.config.get(
43
- "suggestion", "Replace wildcard with specific actions needed for your use case"
43
+ "suggestion",
44
+ "Replace wildcard with specific actions needed for your use case",
44
45
  )
45
46
  example = config.config.get("example", "")
46
47
 
47
48
  # Combine suggestion + example
48
- suggestion = f"{suggestion_text}\nExample:\n{example}" if example else suggestion_text
49
+ suggestion = (
50
+ f"{suggestion_text}\nExample:\n```json\n{example}\n```"
51
+ if example
52
+ else suggestion_text
53
+ )
49
54
 
50
55
  issues.append(
51
56
  ValidationIssue(
@@ -69,7 +69,11 @@ class WildcardResourceCheck(PolicyCheck):
69
69
  example = config.config.get("example", "")
70
70
 
71
71
  # Combine suggestion + example
72
- suggestion = f"{suggestion_text}\nExample:\n{example}" if example else suggestion_text
72
+ suggestion = (
73
+ f"{suggestion_text}\nExample:\n```json\n{example}\n```"
74
+ if example
75
+ else suggestion_text
76
+ )
73
77
 
74
78
  issues.append(
75
79
  ValidationIssue(
@@ -136,6 +136,12 @@ Examples:
136
136
  help="Create line-specific review comments on PR (requires --github-comment)",
137
137
  )
138
138
 
139
+ parser.add_argument(
140
+ "--github-summary",
141
+ action="store_true",
142
+ help="Write validation summary to GitHub Actions job summary (works for all workflow runs)",
143
+ )
144
+
139
145
  parser.add_argument(
140
146
  "--run-all-checks",
141
147
  action="store_true",
@@ -265,6 +271,10 @@ Examples:
265
271
  if not success:
266
272
  logging.error("Failed to post Access Analyzer results to GitHub PR")
267
273
 
274
+ # Write to GitHub Actions job summary if configured
275
+ if getattr(args, "github_summary", False):
276
+ self._write_github_actions_summary(report)
277
+
268
278
  # Determine exit code based on validation results
269
279
  if args.fail_on_warnings:
270
280
  exit_code = 0 if report.total_findings == 0 else 1
@@ -405,7 +415,8 @@ Examples:
405
415
  if not github.is_configured():
406
416
  logging.error(
407
417
  "GitHub integration not configured. "
408
- "Set GITHUB_TOKEN, GITHUB_REPOSITORY, and GITHUB_PR_NUMBER"
418
+ "Required: GITHUB_TOKEN, GITHUB_REPOSITORY, and GITHUB_PR_NUMBER environment variables. "
419
+ "Ensure your workflow is triggered by a pull_request event."
409
420
  )
410
421
  return False
411
422
 
@@ -432,3 +443,89 @@ Examples:
432
443
  logging.error("Failed to post Access Analyzer results to PR")
433
444
 
434
445
  return success
446
+
447
+ def _write_github_actions_summary(self, report: AccessAnalyzerReport) -> None:
448
+ """Write a high-level summary to GitHub Actions job summary.
449
+
450
+ This appears in the Actions tab and provides a quick overview of Access Analyzer results.
451
+ Uses GITHUB_STEP_SUMMARY environment variable.
452
+
453
+ Args:
454
+ report: Access Analyzer report to summarize
455
+ """
456
+ import os
457
+
458
+ summary_file = os.getenv("GITHUB_STEP_SUMMARY")
459
+ if not summary_file:
460
+ logging.warning(
461
+ "--github-summary specified but GITHUB_STEP_SUMMARY env var not found. "
462
+ "This feature only works in GitHub Actions."
463
+ )
464
+ return
465
+
466
+ try:
467
+ # Generate high-level summary
468
+ summary_parts = []
469
+
470
+ # Header with status
471
+ if report.total_findings == 0:
472
+ summary_parts.append("# ✅ IAM Policy Validation (Access Analyzer) - Passed")
473
+ elif report.total_errors > 0:
474
+ summary_parts.append("# ❌ IAM Policy Validation (Access Analyzer) - Failed")
475
+ else:
476
+ summary_parts.append("# ⚠️ IAM Policy Validation (Access Analyzer) - Issues Found")
477
+
478
+ summary_parts.append("")
479
+
480
+ # Summary table
481
+ summary_parts.append("## Summary")
482
+ summary_parts.append("")
483
+ summary_parts.append("| Metric | Count |")
484
+ summary_parts.append("|--------|-------|")
485
+ summary_parts.append(f"| Total Policies Analyzed | {report.total_policies} |")
486
+ summary_parts.append(f"| Policies with Findings | {report.policies_with_findings} |")
487
+ summary_parts.append(f"| Total Findings | {report.total_findings} |")
488
+ summary_parts.append(f"| Errors | {report.total_errors} |")
489
+ summary_parts.append(f"| Warnings | {report.total_warnings} |")
490
+ summary_parts.append(f"| Suggestions | {report.total_suggestions} |")
491
+
492
+ # Finding breakdown by type if there are findings
493
+ if report.total_findings > 0:
494
+ summary_parts.append("")
495
+ summary_parts.append("## 📊 Findings by Type")
496
+ summary_parts.append("")
497
+
498
+ # Count findings by type
499
+ finding_types: dict[str, int] = {}
500
+ for result in report.results:
501
+ for finding in result.findings:
502
+ finding_type = finding.finding_type
503
+ finding_types[finding_type] = finding_types.get(finding_type, 0) + 1
504
+
505
+ # Sort by count (highest first)
506
+ sorted_types = sorted(finding_types.items(), key=lambda x: x[1], reverse=True)
507
+
508
+ summary_parts.append("| Finding Type | Count |")
509
+ summary_parts.append("|--------------|-------|")
510
+ for finding_type, count in sorted_types:
511
+ summary_parts.append(f"| {finding_type} | {count} |")
512
+
513
+ # Add footer with links
514
+ summary_parts.append("")
515
+ summary_parts.append("---")
516
+ summary_parts.append("")
517
+ summary_parts.append(
518
+ "📝 For detailed findings, check the PR comments or review the workflow logs."
519
+ )
520
+ summary_parts.append("")
521
+ summary_parts.append("*Powered by AWS IAM Access Analyzer*")
522
+
523
+ # Write to summary file (append mode)
524
+ with open(summary_file, "a", encoding="utf-8") as f:
525
+ f.write("\n".join(summary_parts))
526
+ f.write("\n")
527
+
528
+ logging.info("Wrote Access Analyzer summary to GitHub Actions job summary")
529
+
530
+ except Exception as e:
531
+ logging.warning(f"Failed to write GitHub Actions summary: {e}")
@@ -484,7 +484,9 @@ Examples:
484
484
  # In streaming mode, don't cleanup comments (we want to keep earlier files)
485
485
  # Cleanup will happen once at the end
486
486
  commenter = PRCommenter(
487
- github, cleanup_old_comments=False, fail_on_severities=fail_on_severities
487
+ github,
488
+ cleanup_old_comments=False,
489
+ fail_on_severities=fail_on_severities,
488
490
  )
489
491
 
490
492
  # Create a mini-report for just this file
@@ -547,7 +549,7 @@ Examples:
547
549
  # Issue breakdown by severity if there are issues
548
550
  if report.total_issues > 0:
549
551
  summary_parts.append("")
550
- summary_parts.append("## Issues by Severity")
552
+ summary_parts.append("## 📊 Issues by Severity")
551
553
  summary_parts.append("")
552
554
 
553
555
  # Count issues by severity
@@ -197,6 +197,11 @@ class AccessAnalyzerReport:
197
197
  """Total number of suggestions across all policies."""
198
198
  return sum(r.suggestion_count for r in self.results)
199
199
 
200
+ @property
201
+ def policies_with_findings(self) -> int:
202
+ """Number of policies that have at least one finding."""
203
+ return sum(1 for r in self.results if r.findings)
204
+
200
205
 
201
206
  class AccessAnalyzerValidator:
202
207
  """Validates IAM policies using AWS IAM Access Analyzer."""
@@ -338,11 +338,8 @@ class AccessAnalyzerReportFormatter:
338
338
  "---",
339
339
  "",
340
340
  "<div align='center'>",
341
- "",
342
- "**🤖 Generated by IAM Policy Validator**",
343
- "",
344
- "_Powered by AWS IAM Access Analyzer_",
345
- "",
341
+ "🤖 <em>Generated by <strong>IAM Policy Validator</strong></em><br>",
342
+ "<sub>Powered by AWS IAM Access Analyzer</sub>",
346
343
  "</div>",
347
344
  ]
348
345
  footer_content = "\n".join(footer_lines)
@@ -40,18 +40,28 @@ logger = logging.getLogger(__name__)
40
40
 
41
41
 
42
42
  class CompiledPatterns:
43
- """Pre-compiled regex patterns for validation."""
43
+ """Pre-compiled regex patterns for validation.
44
+
45
+ This class implements the Singleton pattern to ensure patterns are compiled only once
46
+ and reused across all instances for better performance.
47
+ """
44
48
 
45
49
  _instance = None
50
+ _initialized = False
46
51
 
47
52
  def __new__(cls) -> "CompiledPatterns":
48
53
  if cls._instance is None:
49
54
  cls._instance = super().__new__(cls)
50
- cls._instance._initialize()
51
55
  return cls._instance
52
56
 
53
- def _initialize(self) -> None:
54
- """Initialize compiled patterns."""
57
+ def __init__(self) -> None:
58
+ """Initialize compiled patterns (only once due to Singleton pattern)."""
59
+ # Only initialize once, even if __init__ is called multiple times
60
+ if CompiledPatterns._initialized:
61
+ return
62
+
63
+ CompiledPatterns._initialized = True
64
+
55
65
  # ARN validation pattern
56
66
  self.arn_pattern = re.compile(
57
67
  r"^arn:(?P<partition>(aws|aws-cn|aws-us-gov|aws-eusc|aws-iso|aws-iso-b|aws-iso-e|aws-iso-f)):"
@@ -16,6 +16,7 @@ import yaml
16
16
 
17
17
  from iam_validator.core.check_registry import CheckConfig, CheckRegistry, PolicyCheck
18
18
  from iam_validator.core.config.defaults import get_default_config
19
+ from iam_validator.core.constants import DEFAULT_CONFIG_FILENAMES
19
20
 
20
21
  logger = logging.getLogger(__name__)
21
22
 
@@ -127,12 +128,8 @@ class ValidatorConfig:
127
128
  class ConfigLoader:
128
129
  """Loads configuration from various sources."""
129
130
 
130
- DEFAULT_CONFIG_NAMES = [
131
- "iam-validator.yaml",
132
- "iam-validator.yml",
133
- ".iam-validator.yaml", # Support hidden files as fallback
134
- ".iam-validator.yml",
135
- ]
131
+ # Load default config names from constants module
132
+ DEFAULT_CONFIG_NAMES = DEFAULT_CONFIG_FILENAMES
136
133
 
137
134
  @staticmethod
138
135
  def find_config_file(
@@ -0,0 +1,74 @@
1
+ """
2
+ Core constants for IAM Policy Validator.
3
+
4
+ This module defines constants used across the validator to ensure consistency
5
+ and provide a single source of truth for shared values. These constants are
6
+ based on AWS service limits and documentation.
7
+
8
+ References:
9
+ - AWS IAM Policy Size Limits: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_iam-quotas.html
10
+ - AWS ARN Format: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html
11
+ """
12
+
13
+ # ============================================================================
14
+ # ARN Validation
15
+ # ============================================================================
16
+
17
+ # ARN Validation Pattern
18
+ # This pattern is specifically designed for validation and allows wildcards (*) in region and account fields
19
+ # Unlike the parsing pattern in CompiledPatterns, this is more lenient for validation purposes
20
+ # Supports all AWS partitions: aws, aws-cn, aws-us-gov, aws-eusc, aws-iso*
21
+ DEFAULT_ARN_VALIDATION_PATTERN = r"^arn:(aws|aws-cn|aws-us-gov|aws-eusc|aws-iso|aws-iso-b|aws-iso-e|aws-iso-f):[a-z0-9\-]+:[a-z0-9\-*]*:[0-9*]*:.+$"
22
+
23
+ # Maximum allowed ARN length to prevent ReDoS attacks
24
+ # AWS maximum ARN length is approximately 2048 characters
25
+ MAX_ARN_LENGTH = 2048
26
+
27
+ # ============================================================================
28
+ # AWS IAM Policy Size Limits
29
+ # ============================================================================
30
+ # These limits are enforced by AWS and policies exceeding them will be rejected
31
+ # Note: AWS does not count whitespace when calculating policy size
32
+
33
+ # Managed policy maximum size (characters, excluding whitespace)
34
+ MAX_MANAGED_POLICY_SIZE = 6144
35
+
36
+ # Inline policy maximum size for IAM users (characters, excluding whitespace)
37
+ MAX_INLINE_USER_POLICY_SIZE = 2048
38
+
39
+ # Inline policy maximum size for IAM groups (characters, excluding whitespace)
40
+ MAX_INLINE_GROUP_POLICY_SIZE = 5120
41
+
42
+ # Inline policy maximum size for IAM roles (characters, excluding whitespace)
43
+ MAX_INLINE_ROLE_POLICY_SIZE = 10240
44
+
45
+ # Policy size limits dictionary (for backward compatibility and easy lookup)
46
+ AWS_POLICY_SIZE_LIMITS = {
47
+ "managed": MAX_MANAGED_POLICY_SIZE,
48
+ "inline_user": MAX_INLINE_USER_POLICY_SIZE,
49
+ "inline_group": MAX_INLINE_GROUP_POLICY_SIZE,
50
+ "inline_role": MAX_INLINE_ROLE_POLICY_SIZE,
51
+ }
52
+
53
+ # ============================================================================
54
+ # Configuration Defaults
55
+ # ============================================================================
56
+
57
+ # Default configuration file names (searched in order)
58
+ DEFAULT_CONFIG_FILENAMES = [
59
+ "iam-validator.yaml",
60
+ "iam-validator.yml",
61
+ ".iam-validator.yaml",
62
+ ".iam-validator.yml",
63
+ ]
64
+
65
+ # ============================================================================
66
+ # GitHub Integration
67
+ # ============================================================================
68
+
69
+ # Bot identifier for GitHub comments and reviews
70
+ BOT_IDENTIFIER = "🤖 IAM Policy Validator"
71
+
72
+ # HTML comment markers for identifying bot-generated content (for cleanup/updates)
73
+ SUMMARY_IDENTIFIER = "<!-- iam-policy-validator-summary -->"
74
+ REVIEW_IDENTIFIER = "<!-- iam-policy-validator-review -->"