iam-policy-validator 1.7.1__py3-none-any.whl → 1.8.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 (51) hide show
  1. {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/METADATA +22 -7
  2. iam_policy_validator-1.8.0.dist-info/RECORD +87 -0
  3. iam_validator/__version__.py +4 -2
  4. iam_validator/checks/__init__.py +5 -3
  5. iam_validator/checks/action_condition_enforcement.py +81 -36
  6. iam_validator/checks/action_resource_matching.py +75 -37
  7. iam_validator/checks/action_validation.py +1 -1
  8. iam_validator/checks/condition_key_validation.py +7 -7
  9. iam_validator/checks/condition_type_mismatch.py +10 -8
  10. iam_validator/checks/full_wildcard.py +2 -8
  11. iam_validator/checks/mfa_condition_check.py +8 -8
  12. iam_validator/checks/policy_structure.py +577 -0
  13. iam_validator/checks/policy_type_validation.py +48 -32
  14. iam_validator/checks/principal_validation.py +86 -150
  15. iam_validator/checks/resource_validation.py +8 -8
  16. iam_validator/checks/sensitive_action.py +9 -11
  17. iam_validator/checks/service_wildcard.py +4 -10
  18. iam_validator/checks/set_operator_validation.py +11 -11
  19. iam_validator/checks/sid_uniqueness.py +8 -4
  20. iam_validator/checks/trust_policy_validation.py +512 -0
  21. iam_validator/checks/utils/sensitive_action_matcher.py +26 -26
  22. iam_validator/checks/utils/wildcard_expansion.py +1 -1
  23. iam_validator/checks/wildcard_action.py +5 -9
  24. iam_validator/checks/wildcard_resource.py +5 -9
  25. iam_validator/commands/validate.py +8 -14
  26. iam_validator/core/__init__.py +1 -2
  27. iam_validator/core/access_analyzer.py +1 -1
  28. iam_validator/core/access_analyzer_report.py +2 -2
  29. iam_validator/core/aws_fetcher.py +159 -64
  30. iam_validator/core/check_registry.py +83 -79
  31. iam_validator/core/config/condition_requirements.py +69 -17
  32. iam_validator/core/config/config_loader.py +1 -2
  33. iam_validator/core/config/defaults.py +74 -59
  34. iam_validator/core/config/service_principals.py +40 -3
  35. iam_validator/core/constants.py +57 -0
  36. iam_validator/core/formatters/console.py +10 -1
  37. iam_validator/core/formatters/csv.py +2 -1
  38. iam_validator/core/formatters/enhanced.py +42 -8
  39. iam_validator/core/formatters/markdown.py +2 -1
  40. iam_validator/core/ignore_patterns.py +297 -0
  41. iam_validator/core/models.py +35 -10
  42. iam_validator/core/policy_checks.py +34 -474
  43. iam_validator/core/policy_loader.py +98 -18
  44. iam_validator/core/report.py +65 -24
  45. iam_validator/integrations/github_integration.py +4 -5
  46. iam_validator/utils/__init__.py +4 -0
  47. iam_validator/utils/terminal.py +22 -0
  48. iam_policy_validator-1.7.1.dist-info/RECORD +0 -83
  49. {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/WHEEL +0 -0
  50. {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/entry_points.txt +0 -0
  51. {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -15,6 +15,7 @@ from dataclasses import dataclass, field
15
15
  from typing import TYPE_CHECKING, Any
16
16
 
17
17
  from iam_validator.core.aws_fetcher import AWSServiceFetcher
18
+ from iam_validator.core.ignore_patterns import IgnorePatternMatcher
18
19
  from iam_validator.core.models import Statement, ValidationIssue
19
20
 
20
21
  if TYPE_CHECKING:
@@ -36,107 +37,64 @@ class CheckConfig:
36
37
  List of patterns to ignore findings.
37
38
 
38
39
  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
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
44
45
 
45
46
  Multiple fields in one pattern = AND logic
46
47
  Multiple patterns = OR logic (any pattern matches → ignore)
47
48
 
48
49
  Example:
49
50
  ignore_patterns:
50
- - filepath_regex: "test/.*|examples/.*"
51
- - filepath_regex: "policies/readonly-.*"
52
- action_matches: ".*:(Get|List|Describe).*"
53
- - statement_sid: "AllowReadOnlyAccess"
51
+ - filepath: "test/.*|examples/.*"
52
+ - filepath: "policies/readonly-.*"
53
+ action: ".*:(Get|List|Describe).*"
54
+ - sid: "AllowReadOnlyAccess"
54
55
  """
55
56
 
56
57
  def should_ignore(self, issue: ValidationIssue, filepath: str = "") -> bool:
57
58
  """
58
59
  Check if issue should be ignored based on ignore patterns.
59
60
 
61
+ Uses centralized IgnorePatternMatcher for high-performance filtering
62
+ with cached compiled regex patterns.
63
+
60
64
  Args:
61
65
  issue: The validation issue to check
62
66
  filepath: Path to the policy file
63
67
 
64
68
  Returns:
65
69
  True if the issue should be ignored
66
- """
67
- if not self.ignore_patterns:
68
- return False
69
-
70
- import re
71
70
 
72
- for pattern in self.ignore_patterns:
73
- if self._matches_pattern(pattern, issue, filepath, re):
74
- return True
75
-
76
- return False
71
+ Performance:
72
+ - Cached regex compilation (LRU cache)
73
+ - Early exit optimization
74
+ """
75
+ return IgnorePatternMatcher.should_ignore_issue(issue, filepath, self.ignore_patterns)
77
76
 
78
- def _matches_pattern(
79
- self,
80
- pattern: dict[str, Any],
81
- issue: ValidationIssue,
82
- filepath: str,
83
- re_module: Any,
84
- ) -> bool:
77
+ def filter_actions(self, actions: frozenset[str]) -> frozenset[str]:
85
78
  """
86
- Check if issue matches a single ignore pattern.
79
+ Filter actions based on action ignore patterns.
87
80
 
88
- All fields in pattern must match (AND logic).
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).
89
86
 
90
87
  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
88
+ actions: Set of actions to filter
95
89
 
96
90
  Returns:
97
- True if all fields in pattern match the issue
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
98
96
  """
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
97
+ return IgnorePatternMatcher.filter_actions(actions, self.ignore_patterns)
140
98
 
141
99
 
142
100
  class PolicyCheck(ABC):
@@ -399,6 +357,10 @@ class CheckRegistry:
399
357
  config = self.get_config(check.check_id)
400
358
  if config:
401
359
  issues = await check.execute(statement, statement_idx, fetcher, config)
360
+ # Inject check_id into each issue
361
+ for issue in issues:
362
+ if issue.check_id is None:
363
+ issue.check_id = check.check_id
402
364
  # Filter issues based on ignore_patterns
403
365
  filtered_issues = [
404
366
  issue for issue in issues if not config.should_ignore(issue, filepath)
@@ -427,7 +389,12 @@ class CheckRegistry:
427
389
  check = enabled_checks[idx]
428
390
  print(f"Warning: Check '{check.check_id}' failed: {result}")
429
391
  elif isinstance(result, list):
392
+ check = enabled_checks[idx]
430
393
  config = configs[idx]
394
+ # Inject check_id into each issue
395
+ for issue in result:
396
+ if issue.check_id is None:
397
+ issue.check_id = check.check_id
431
398
  # Filter issues based on ignore_patterns
432
399
  filtered_issues = [
433
400
  issue for issue in result if not config.should_ignore(issue, filepath)
@@ -475,6 +442,7 @@ class CheckRegistry:
475
442
  policy_file: str,
476
443
  fetcher: AWSServiceFetcher,
477
444
  policy_type: str = "IDENTITY_POLICY",
445
+ **kwargs,
478
446
  ) -> list[ValidationIssue]:
479
447
  """
480
448
  Execute all enabled policy-level checks.
@@ -487,6 +455,7 @@ class CheckRegistry:
487
455
  policy_file: Path to the policy file (for context/reporting)
488
456
  fetcher: AWS service fetcher for API calls
489
457
  policy_type: Type of policy (IDENTITY_POLICY, RESOURCE_POLICY, SERVICE_CONTROL_POLICY)
458
+ **kwargs: Additional arguments to pass to checks (e.g., raw_policy_dict)
490
459
 
491
460
  Returns:
492
461
  List of all ValidationIssue objects from all policy-level checks
@@ -507,34 +476,61 @@ class CheckRegistry:
507
476
  if config:
508
477
  try:
509
478
  issues = await check.execute_policy(
510
- policy, policy_file, fetcher, config, policy_type=policy_type
479
+ policy,
480
+ policy_file,
481
+ fetcher,
482
+ config,
483
+ policy_type=policy_type,
484
+ **kwargs,
511
485
  )
512
- all_issues.extend(issues)
486
+ # Inject check_id into each issue
487
+ for issue in issues:
488
+ if issue.check_id is None:
489
+ issue.check_id = check.check_id
490
+ # Filter issues based on ignore_patterns
491
+ filtered_issues = [
492
+ issue
493
+ for issue in issues
494
+ if not config.should_ignore(issue, policy_file)
495
+ ]
496
+ all_issues.extend(filtered_issues)
513
497
  except Exception as e:
514
498
  print(f"Warning: Check '{check.check_id}' failed: {e}")
515
499
  return all_issues
516
500
 
517
501
  # Execute all policy-level checks in parallel
518
502
  tasks = []
503
+ configs = []
519
504
  for check in policy_level_checks:
520
505
  config = self.get_config(check.check_id)
521
506
  if config:
522
507
  task = check.execute_policy(
523
- policy, policy_file, fetcher, config, policy_type=policy_type
508
+ policy, policy_file, fetcher, config, policy_type=policy_type, **kwargs
524
509
  )
525
510
  tasks.append(task)
511
+ configs.append(config)
526
512
 
527
513
  # Wait for all checks to complete
528
514
  results = await asyncio.gather(*tasks, return_exceptions=True)
529
515
 
530
- # Collect all issues, handling any exceptions
516
+ # Collect all issues, handling any exceptions and applying ignore_patterns
531
517
  for idx, result in enumerate(results):
532
518
  if isinstance(result, Exception):
533
519
  # Log error but continue with other checks
534
520
  check = policy_level_checks[idx]
535
521
  print(f"Warning: Check '{check.check_id}' failed: {result}")
536
522
  elif isinstance(result, list):
537
- all_issues.extend(result)
523
+ check = policy_level_checks[idx]
524
+ config = configs[idx]
525
+ # Inject check_id into each issue
526
+ for issue in result:
527
+ if issue.check_id is None:
528
+ issue.check_id = check.check_id
529
+ # Filter issues based on ignore_patterns
530
+ filtered_issues = [
531
+ issue for issue in result if not config.should_ignore(issue, policy_file)
532
+ ]
533
+ all_issues.extend(filtered_issues)
538
534
 
539
535
  return all_issues
540
536
 
@@ -561,6 +557,11 @@ def create_default_registry(
561
557
  # Import and register built-in checks
562
558
  from iam_validator import checks
563
559
 
560
+ # 0. FUNDAMENTAL STRUCTURE (Must run FIRST - validates basic policy structure)
561
+ registry.register(
562
+ checks.PolicyStructureCheck()
563
+ ) # Policy-level: Validates required fields, conflicts, valid values
564
+
564
565
  # 1. POLICY STRUCTURE (Checks that examine the entire policy, not individual statements)
565
566
  registry.register(
566
567
  checks.SidUniquenessCheck()
@@ -600,6 +601,9 @@ def create_default_registry(
600
601
  registry.register(
601
602
  checks.PrincipalValidationCheck()
602
603
  ) # Principal validation (resource policies)
604
+ registry.register(
605
+ checks.TrustPolicyValidationCheck()
606
+ ) # Trust policy validation (role assumption policies)
603
607
 
604
608
  # Note: policy_type_validation is a standalone function (not a class-based check)
605
609
  # and is called separately in the validation flow
@@ -54,23 +54,75 @@ IAM_PASS_ROLE_REQUIREMENT: Final[dict[str, Any]] = {
54
54
  S3_WRITE_ORG_ID: Final[dict[str, Any]] = {
55
55
  "actions": ["s3:PutObject"],
56
56
  "severity": "medium",
57
- "required_conditions": [
58
- {
59
- "condition_key": "aws:ResourceOrgId",
60
- "description": (
61
- "Require aws:ResourceAccount, aws:ResourceOrgID or aws:ResourceOrgPaths condition(s) for S3 write actions to enforce organization-level access control"
62
- ),
63
- "example": (
64
- "{\n"
65
- ' "Condition": {\n'
66
- ' "StringEquals": {\n'
67
- ' "aws:ResourceOrgId": "${aws:PrincipalOrgID}"\n'
68
- " }\n"
69
- " }\n"
70
- "}"
71
- ),
72
- },
73
- ],
57
+ "required_conditions": {
58
+ "any_of": [
59
+ # Option 1: Use organization-level control with ResourceOrgID
60
+ {
61
+ "all_of": [
62
+ {
63
+ "condition_key": "aws:ResourceOrgID",
64
+ "description": "Restrict S3 write actions to resources within your AWS Organization",
65
+ "expected_value": "${aws:PrincipalOrgID}",
66
+ "example": (
67
+ "{\n"
68
+ ' "Condition": {\n'
69
+ ' "StringEquals": {\n'
70
+ ' "aws:ResourceOrgID": "${aws:PrincipalOrgID}",\n'
71
+ ' "aws:ResourceAccount": "${aws:PrincipalAccount}"\n'
72
+ " }\n"
73
+ " }\n"
74
+ "}"
75
+ ),
76
+ },
77
+ {
78
+ "condition_key": "aws:ResourceAccount",
79
+ "description": "Ensure the S3 resource belongs to the same AWS account as the principal",
80
+ "expected_value": "${aws:PrincipalAccount}",
81
+ },
82
+ ]
83
+ },
84
+ # Option 2: Use organization path-based control
85
+ {
86
+ "all_of": [
87
+ {
88
+ "condition_key": "aws:ResourceOrgPaths",
89
+ "description": "Restrict S3 write actions to resources within your AWS Organization path",
90
+ "expected_value": "${aws:PrincipalOrgPaths}",
91
+ "example": (
92
+ "{\n"
93
+ ' "Condition": {\n'
94
+ ' "StringEquals": {\n'
95
+ ' "aws:ResourceOrgPaths": "${aws:PrincipalOrgPaths}",\n'
96
+ ' "aws:ResourceAccount": "${aws:PrincipalAccount}"\n'
97
+ " }\n"
98
+ " }\n"
99
+ "}"
100
+ ),
101
+ },
102
+ {
103
+ "condition_key": "aws:ResourceAccount",
104
+ "description": "Ensure the S3 resource belongs to the same AWS account as the principal",
105
+ "expected_value": "${aws:PrincipalAccount}",
106
+ },
107
+ ]
108
+ },
109
+ # Option 3: Account-only control (less restrictive, but still secure)
110
+ {
111
+ "condition_key": "aws:ResourceAccount",
112
+ "description": "Restrict S3 write actions to resources within the same AWS account",
113
+ "expected_value": "${aws:PrincipalAccount}",
114
+ "example": (
115
+ "{\n"
116
+ ' "Condition": {\n'
117
+ ' "StringEquals": {\n'
118
+ ' "aws:ResourceAccount": "${aws:PrincipalAccount}"\n'
119
+ " }\n"
120
+ " }\n"
121
+ "}"
122
+ ),
123
+ },
124
+ ],
125
+ },
74
126
  }
75
127
 
76
128
  # IP Restrictions - Source IP requirements
@@ -5,6 +5,7 @@ Loads and parses configuration from YAML files, environment variables,
5
5
  and command-line arguments.
6
6
  """
7
7
 
8
+ import importlib
8
9
  import importlib.util
9
10
  import inspect
10
11
  import logging
@@ -314,8 +315,6 @@ class ConfigLoader:
314
315
  module_name, class_name = parts
315
316
 
316
317
  # Import the module
317
- import importlib
318
-
319
318
  module = importlib.import_module(module_name)
320
319
  check_class = getattr(module, class_name)
321
320
 
@@ -16,12 +16,12 @@ Benefits of code-first approach:
16
16
  - 5-10x faster than YAML parsing
17
17
  """
18
18
 
19
+ from iam_validator.core import constants
19
20
  from iam_validator.core.config.category_suggestions import get_category_suggestions
20
21
  from iam_validator.core.config.condition_requirements import CONDITION_REQUIREMENTS
21
22
  from iam_validator.core.config.principal_requirements import (
22
23
  get_default_principal_requirements,
23
24
  )
24
- from iam_validator.core.config.service_principals import DEFAULT_SERVICE_PRINCIPALS
25
25
  from iam_validator.core.config.wildcards import (
26
26
  DEFAULT_ALLOWED_WILDCARDS,
27
27
  DEFAULT_SERVICE_WILDCARDS,
@@ -70,11 +70,11 @@ DEFAULT_CONFIG = {
70
70
  # Cache AWS service definitions locally (persists between runs)
71
71
  "cache_enabled": True,
72
72
  # Cache TTL in hours (default: 168 = 7 days)
73
- "cache_ttl_hours": 168,
73
+ "cache_ttl_hours": constants.DEFAULT_CACHE_TTL_HOURS,
74
74
  # Severity levels that cause validation to fail
75
75
  # IAM Validity: error, warning, info
76
76
  # Security: critical, high, medium, low
77
- "fail_on_severity": ["error", "critical", "high"],
77
+ "fail_on_severity": list(constants.HIGH_SEVERITY_LEVELS),
78
78
  },
79
79
  # ========================================================================
80
80
  # AWS IAM Validation Checks (17 checks total)
@@ -188,67 +188,74 @@ DEFAULT_CONFIG = {
188
188
  "enabled": True,
189
189
  "severity": "error", # IAM validity error
190
190
  "description": "Validates ARN format for resources",
191
- "arn_pattern": "^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*]*:.+$",
191
+ "arn_pattern": constants.DEFAULT_ARN_VALIDATION_PATTERN,
192
192
  },
193
193
  # ========================================================================
194
194
  # 9. PRINCIPAL VALIDATION
195
195
  # ========================================================================
196
196
  # Validates Principal elements in resource-based policies
197
- # (S3 buckets, SNS topics, SQS queues, etc.)
198
- # Only runs when --policy-type RESOURCE_POLICY is specified
197
+ # Applies to: S3 buckets, SNS topics, SQS queues, Lambda functions, etc.
198
+ # Only runs when: --policy-type RESOURCE_POLICY
199
199
  #
200
- # See: iam_validator/core/config/service_principals.py for defaults
200
+ # Three control mechanisms:
201
+ # 1. blocked_principals - Block specific principals (deny list)
202
+ # 2. allowed_principals - Allow only specific principals (whitelist mode)
203
+ # 3. principal_condition_requirements - Require conditions for principals
204
+ # 4. allowed_service_principals - Always allow AWS service principals
201
205
  "principal_validation": {
202
206
  "enabled": True,
203
207
  "severity": "high", # Security issue, not IAM validity error
204
208
  "description": "Validates Principal elements in resource policies for security best practices",
205
- # blocked_principals: Principals that should NEVER be allowed (deny list)
206
- # Default: ["*"] blocks public access to everyone
207
- # Examples:
208
- # ["*"] - Block public access
209
- # ["*", "arn:aws:iam::*:root"] - Block public + all AWS accounts
209
+ # blocked_principals: Deny list - these principals are never allowed
210
+ # Default: ["*"] blocks public access
210
211
  "blocked_principals": ["*"],
211
- # allowed_principals: When set, ONLY these principals are allowed (whitelist mode)
212
- # Leave empty to allow all except blocked principals
213
- # Examples:
214
- # [] - Allow all (except blocked)
215
- # ["arn:aws:iam::123456789012:root"] - Only allow specific account
216
- # ["arn:aws:iam::*:role/OrgAccessRole"] - Allow specific role in any account
212
+ # allowed_principals: Whitelist mode - when set, ONLY these are allowed
213
+ # Default: [] allows all (except blocked)
217
214
  "allowed_principals": [],
218
- # require_conditions_for: Principals that MUST have specific IAM conditions
219
- # Format: {principal_pattern: [required_condition_keys]}
220
- # Default: Public access (*) must specify source to limit scope
221
- # Examples:
222
- # "*": ["aws:SourceArn"] - Public access must specify source ARN
223
- # "arn:aws:iam::*:root": ["aws:PrincipalOrgID"] - Cross-account must be from org
224
- "require_conditions_for": {
225
- "*": [
226
- "aws:SourceArn",
227
- "aws:SourceAccount",
228
- "aws:SourceVpce",
229
- "aws:SourceIp",
230
- "aws:SourceOrgID",
231
- "aws:SourceOrgPaths",
232
- ],
233
- },
234
- # principal_condition_requirements: Advanced condition requirements for principals
235
- # Similar to action_condition_enforcement but for principals
236
- # Supports all_of/any_of/none_of logic with rich metadata
237
- # Default: 2 critical requirements enabled (public_access, prevent_insecure_transport)
215
+ # principal_condition_requirements: Require conditions for specific principals
216
+ # Supports all_of/any_of/none_of logic like action_condition_enforcement
217
+ # Default: 2 enabled (public_access, prevent_insecure_transport)
238
218
  # See: iam_validator/core/config/principal_requirements.py
239
- # To customize requirements, use Python API:
240
- # from iam_validator.core.config import get_principal_requirements_by_names
241
- # requirements = get_principal_requirements_by_names(['public_access', 'cross_account_org'])
242
- # To disable: set to empty list []
243
219
  "principal_condition_requirements": get_default_principal_requirements(),
244
- # allowed_service_principals: AWS service principals that are always allowed
245
- # Default: 16 common AWS services (cloudfront, s3, lambda, logs, etc.)
246
- # These are typically safe as AWS services need access to resources
247
- # See: iam_validator/core/config/service_principals.py
248
- "allowed_service_principals": list(DEFAULT_SERVICE_PRINCIPALS),
220
+ # allowed_service_principals: AWS service principals (*.amazonaws.com)
221
+ # Default: ["aws:*"] allows ALL AWS service principals
222
+ # Note: "aws:*" is different from "*" (public access)
223
+ "allowed_service_principals": ["aws:*"],
224
+ },
225
+ # ========================================================================
226
+ # 10. TRUST POLICY VALIDATION
227
+ # ========================================================================
228
+ # Validate trust policies (role assumption policies) for security best practices
229
+ # Ensures assume role actions have appropriate principals and conditions
230
+ #
231
+ # Key validations:
232
+ # - Action-Principal type matching (e.g., AssumeRoleWithSAML needs Federated)
233
+ # - Provider ARN format validation (SAML vs OIDC provider patterns)
234
+ # - Required conditions per assume method
235
+ #
236
+ # Complements principal_validation check (which validates principal allowlists/blocklists)
237
+ # This check focuses on action-principal coupling specific to trust policies
238
+ #
239
+ # Auto-detection: Only runs on statements with assume role actions
240
+ "trust_policy_validation": {
241
+ "enabled": True, # Enabled by default (auto-detects trust policies)
242
+ "severity": "high", # Security issue
243
+ "description": "Validates trust policies for role assumption security and action-principal coupling",
244
+ # validation_rules: Custom rules override defaults
245
+ # Default rules validate:
246
+ # - sts:AssumeRole → AWS or Service principals
247
+ # - sts:AssumeRoleWithSAML → Federated (SAML provider) with SAML:aud
248
+ # - sts:AssumeRoleWithWebIdentity → Federated (OIDC provider)
249
+ # Example custom rules:
250
+ # "validation_rules": {
251
+ # "sts:AssumeRole": {
252
+ # "allowed_principal_types": ["AWS"], # Only AWS, not Service
253
+ # "required_conditions": ["sts:ExternalId"], # Always require ExternalId
254
+ # }
255
+ # }
249
256
  },
250
257
  # ========================================================================
251
- # 10. POLICY TYPE VALIDATION
258
+ # 11. POLICY TYPE VALIDATION
252
259
  # ========================================================================
253
260
  # Validate policy type requirements (new in v1.3.0)
254
261
  # Ensures policies conform to the declared type (IDENTITY vs RESOURCE_POLICY)
@@ -263,7 +270,7 @@ DEFAULT_CONFIG = {
263
270
  "description": "Validates policies match declared type and enforces RCP requirements",
264
271
  },
265
272
  # ========================================================================
266
- # 11. ACTION-RESOURCE MATCHING
273
+ # 12. ACTION-RESOURCE MATCHING
267
274
  # ========================================================================
268
275
  # Validate action-resource matching
269
276
  # Ensures resources match the required resource types for actions
@@ -303,7 +310,7 @@ DEFAULT_CONFIG = {
303
310
  # See: iam_validator/core/config/sensitive_actions.py for sensitive actions
304
311
  # ========================================================================
305
312
  # ========================================================================
306
- # 12. WILDCARD ACTION
313
+ # 13. WILDCARD ACTION
307
314
  # ========================================================================
308
315
  # Check for wildcard actions (Action: "*")
309
316
  # Flags statements that allow all actions
@@ -322,7 +329,7 @@ DEFAULT_CONFIG = {
322
329
  ),
323
330
  },
324
331
  # ========================================================================
325
- # 13. WILDCARD RESOURCE
332
+ # 14. WILDCARD RESOURCE
326
333
  # ========================================================================
327
334
  # Check for wildcard resources (Resource: "*")
328
335
  # Flags statements that apply to all resources
@@ -349,7 +356,7 @@ DEFAULT_CONFIG = {
349
356
  ),
350
357
  },
351
358
  # ========================================================================
352
- # 14. FULL WILDCARD (CRITICAL)
359
+ # 15. FULL WILDCARD (CRITICAL)
353
360
  # ========================================================================
354
361
  # Check for BOTH Action: "*" AND Resource: "*" (CRITICAL)
355
362
  # This grants full administrative access (AdministratorAccess equivalent)
@@ -373,7 +380,7 @@ DEFAULT_CONFIG = {
373
380
  ),
374
381
  },
375
382
  # ========================================================================
376
- # 15. SERVICE WILDCARD
383
+ # 16. SERVICE WILDCARD
377
384
  # ========================================================================
378
385
  # Check for service-level wildcards (e.g., "iam:*", "s3:*", "ec2:*")
379
386
  # These grant ALL permissions for a service (often too permissive)
@@ -389,9 +396,21 @@ DEFAULT_CONFIG = {
389
396
  # Services that are allowed to use wildcards (default: logs, cloudwatch, xray)
390
397
  # See: iam_validator/core/config/wildcards.py
391
398
  "allowed_services": list(DEFAULT_SERVICE_WILDCARDS),
399
+ "message": "Service wildcard '{action}' grants all permissions for the {service} service",
400
+ "suggestion": (
401
+ "Replace '{action}' with specific actions needed for your use case to follow least-privilege principle.\n"
402
+ "Find valid {service} actions: https://docs.aws.amazon.com/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html"
403
+ ),
404
+ "example": (
405
+ "Replace:\n"
406
+ ' "Action": ["{action}"]\n'
407
+ "\n"
408
+ "With specific actions:\n"
409
+ ' "Action": ["{service}:Describe*", "{service}:List*"]\n'
410
+ ),
392
411
  },
393
412
  # ========================================================================
394
- # 16. SENSITIVE ACTION
413
+ # 17. SENSITIVE ACTION
395
414
  # ========================================================================
396
415
  # Check for sensitive actions without IAM conditions
397
416
  # Sensitive actions: IAM changes, secrets access, destructive operations
@@ -466,7 +485,7 @@ DEFAULT_CONFIG = {
466
485
  ],
467
486
  },
468
487
  # ========================================================================
469
- # 17. ACTION CONDITION ENFORCEMENT
488
+ # 18. ACTION CONDITION ENFORCEMENT
470
489
  # ========================================================================
471
490
  # Enforce specific IAM condition requirements for actions
472
491
  # Examples: iam:PassRole must specify iam:PassedToService,
@@ -482,10 +501,6 @@ DEFAULT_CONFIG = {
482
501
  # - prevent_public_ip: Prevents 0.0.0.0/0 IP ranges
483
502
  #
484
503
  # See: iam_validator/core/config/condition_requirements.py
485
- # Python API:
486
- # from iam_validator.core.config import CONDITION_REQUIREMENTS
487
- # import copy
488
- # requirements = copy.deepcopy(CONDITION_REQUIREMENTS)
489
504
  "action_condition_enforcement": {
490
505
  "enabled": True,
491
506
  "severity": "high", # Default severity (can be overridden per-requirement)