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
@@ -85,9 +85,9 @@ class SensitiveActionCheck(PolicyCheck):
85
85
  # Generic ABAC fallback for uncategorized actions
86
86
  return (
87
87
  "Add IAM conditions to limit when this action can be used. Use ABAC for scalability:\n"
88
- "• Match principal tags to resource tags (aws:PrincipalTag/X = aws:ResourceTag/X)\n"
89
- "• Require MFA (aws:MultiFactorAuthPresent = true)\n"
90
- "• Restrict by IP (aws:SourceIp) or VPC (aws:SourceVpc)",
88
+ "• Match principal tags to resource tags (`aws:PrincipalTag/<tag-name>` = `aws:ResourceTag/<tag-name>`)\n"
89
+ "• Require MFA (`aws:MultiFactorAuthPresent` = `true`)\n"
90
+ "• Restrict by IP (`aws:SourceIp`) or VPC (`aws:SourceVpc`)",
91
91
  '"Condition": {\n'
92
92
  ' "StringEquals": {\n'
93
93
  ' "aws:PrincipalTag/owner": "${aws:ResourceTag/owner}"\n'
@@ -142,13 +142,6 @@ class SensitiveActionCheck(PolicyCheck):
142
142
  matched_actions[0], config
143
143
  )
144
144
 
145
- # Combine suggestion + example
146
- suggestion = (
147
- f"{suggestion_text}\n\nExample:\n```json\n{example}\n```"
148
- if example
149
- else suggestion_text
150
- )
151
-
152
145
  # Determine severity based on the highest severity action in the list
153
146
  # If single action, use its category severity
154
147
  # If multiple actions, use the highest severity among them
@@ -165,7 +158,8 @@ class SensitiveActionCheck(PolicyCheck):
165
158
  issue_type="missing_condition",
166
159
  message=message,
167
160
  action=(matched_actions[0] if len(matched_actions) == 1 else None),
168
- suggestion=suggestion,
161
+ suggestion=suggestion_text,
162
+ example=example if example else None,
169
163
  line_number=statement.line_number,
170
164
  )
171
165
  )
@@ -198,6 +192,10 @@ class SensitiveActionCheck(PolicyCheck):
198
192
  del policy_file, fetcher # Not used in current implementation
199
193
  issues = []
200
194
 
195
+ # Handle policies with no statements
196
+ if not policy.statement:
197
+ return []
198
+
201
199
  # Collect all actions from all Allow statements across the entire policy
202
200
  all_actions: set[str] = set()
203
201
  statement_map: dict[
@@ -51,29 +51,22 @@ class ServiceWildcardCheck(PolicyCheck):
51
51
  # Get message template and replace placeholders
52
52
  message_template = config.config.get(
53
53
  "message",
54
- "Service-level wildcard '{action}' grants all permissions for {service} service",
54
+ "Service-level wildcard `{action}` grants all permissions for `{service}` service",
55
55
  )
56
56
  suggestion_template = config.config.get(
57
57
  "suggestion",
58
- "Consider specifying explicit actions instead of '{action}'. If you need multiple actions, list them individually or use more specific wildcards like '{service}:Get*' or '{service}:List*'.",
58
+ "Consider specifying explicit actions instead of `{action}`. If you need multiple actions, list them individually or use more specific wildcards like `{service}:Get*` or `{service}:List*`.",
59
59
  )
60
60
  example_template = config.config.get("example", "")
61
61
 
62
62
  message = message_template.format(action=action, service=service)
63
- suggestion_text = suggestion_template.format(action=action, service=service)
63
+ suggestion = suggestion_template.format(action=action, service=service)
64
64
  example = (
65
65
  example_template.format(action=action, service=service)
66
66
  if example_template
67
67
  else ""
68
68
  )
69
69
 
70
- # Combine suggestion + example
71
- suggestion = (
72
- f"{suggestion_text}\nExample:\n```json\n{example}\n```"
73
- if example
74
- else suggestion_text
75
- )
76
-
77
70
  issues.append(
78
71
  ValidationIssue(
79
72
  severity=self.get_severity(config),
@@ -83,6 +76,7 @@ class ServiceWildcardCheck(PolicyCheck):
83
76
  message=message,
84
77
  action=action,
85
78
  suggestion=suggestion,
79
+ example=example if example else None,
86
80
  line_number=statement.line_number,
87
81
  )
88
82
  )
@@ -73,7 +73,7 @@ class SetOperatorValidationCheck(PolicyCheck):
73
73
 
74
74
  # First pass: Identify set operators and Null checks
75
75
  for operator, conditions in statement.condition.items():
76
- base_operator, operator_type, set_prefix = normalize_operator(operator)
76
+ base_operator, _operator_type, set_prefix = normalize_operator(operator)
77
77
 
78
78
  # Track Null checks
79
79
  if base_operator == "Null":
@@ -87,22 +87,22 @@ class SetOperatorValidationCheck(PolicyCheck):
87
87
 
88
88
  # Second pass: Validate set operator usage
89
89
  for operator, conditions in statement.condition.items():
90
- base_operator, operator_type, set_prefix = normalize_operator(operator)
90
+ base_operator, _operator_type, set_prefix = normalize_operator(operator)
91
91
 
92
92
  if not set_prefix:
93
93
  continue
94
94
 
95
95
  # Check each condition key used with a set operator
96
- for condition_key, condition_values in conditions.items():
96
+ for condition_key, _condition_values in conditions.items():
97
97
  # Issue 1: Set operator used with single-valued context key (anti-pattern)
98
98
  if not is_multivalued_context_key(condition_key):
99
99
  issues.append(
100
100
  ValidationIssue(
101
101
  severity=self.get_severity(config),
102
102
  message=(
103
- f"Set operator '{set_prefix}' should not be used with single-valued "
104
- f"condition key '{condition_key}'. This can lead to overly permissive policies. "
105
- f"Set operators are designed for multivalued context keys like 'aws:TagKeys'. "
103
+ f"Set operator `{set_prefix}` should not be used with single-valued "
104
+ f"condition key `{condition_key}`. This can lead to overly permissive policies. "
105
+ f"Set operators are designed for multivalued context keys like `aws:TagKeys`. "
106
106
  f"See: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-single-vs-multi-valued-context-keys.html"
107
107
  ),
108
108
  statement_sid=statement_sid,
@@ -120,9 +120,9 @@ class SetOperatorValidationCheck(PolicyCheck):
120
120
  ValidationIssue(
121
121
  severity="warning",
122
122
  message=(
123
- f"Security risk: ForAllValues with Allow effect on '{condition_key}' "
123
+ f"Security risk: ForAllValues with Allow effect on `{condition_key}` "
124
124
  f"should include a Null condition check. Without it, requests with missing "
125
- f'\'{condition_key}\' will be granted access. Add: \'"Null": {{"{condition_key}": "false"}}\'. '
125
+ f'`{condition_key}` will be granted access. Add: `"Null": {{"{condition_key}": "false"}}`. '
126
126
  f"See: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-single-vs-multi-valued-context-keys.html"
127
127
  ),
128
128
  statement_sid=statement_sid,
@@ -140,10 +140,10 @@ class SetOperatorValidationCheck(PolicyCheck):
140
140
  ValidationIssue(
141
141
  severity="warning",
142
142
  message=(
143
- f"Unpredictable behavior: ForAnyValue with Deny effect on '{condition_key}' "
143
+ f"Unpredictable behavior: `ForAnyValue` with `Deny` effect on `{condition_key}` "
144
144
  f"should include a Null condition check. Without it, requests with missing "
145
- f"'{condition_key}' will evaluate to 'No match' instead of denying access. "
146
- f'Add: \'"Null": {{"{condition_key}": "false"}}\'. '
145
+ f"`{condition_key}` will evaluate to `No match` instead of denying access. "
146
+ f'Add: `"Null": {{"{condition_key}": "false"}}`. '
147
147
  f"See: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-single-vs-multi-valued-context-keys.html"
148
148
  ),
149
149
  statement_sid=statement_sid,
@@ -35,6 +35,10 @@ def _check_sid_uniqueness_impl(policy: IAMPolicy, severity: str) -> list[Validat
35
35
  # No spaces allowed
36
36
  sid_pattern = re.compile(r"^[a-zA-Z0-9_-]+$")
37
37
 
38
+ # Handle policies with no statements
39
+ if not policy.statement:
40
+ return []
41
+
38
42
  # Collect all SIDs (ignoring None/empty values) and check format
39
43
  sids_with_indices: list[tuple[str, int]] = []
40
44
  for idx, statement in enumerate(policy.statement):
@@ -43,15 +47,15 @@ def _check_sid_uniqueness_impl(policy: IAMPolicy, severity: str) -> list[Validat
43
47
  if not sid_pattern.match(statement.sid):
44
48
  # Identify the issue
45
49
  if " " in statement.sid:
46
- issue_msg = f"Statement ID '{statement.sid}' contains spaces, which are not allowed by AWS"
50
+ issue_msg = f"Statement ID `{statement.sid}` contains spaces, which are not allowed by AWS"
47
51
  suggestion = (
48
- f"Remove spaces from the SID. Example: '{statement.sid.replace(' ', '')}'"
52
+ f"Remove spaces from the SID. Example: `{statement.sid.replace(' ', '')}`"
49
53
  )
50
54
  else:
51
55
  invalid_chars = "".join(
52
56
  set(c for c in statement.sid if not c.isalnum() and c not in "_-")
53
57
  )
54
- issue_msg = f"Statement ID '{statement.sid}' contains invalid characters: {invalid_chars}"
58
+ issue_msg = f"Statement ID `{statement.sid}` contains invalid characters: `{invalid_chars}`"
55
59
  suggestion = (
56
60
  "SIDs must contain only alphanumeric characters, hyphens, and underscores"
57
61
  )
@@ -91,7 +95,7 @@ def _check_sid_uniqueness_impl(policy: IAMPolicy, severity: str) -> list[Validat
91
95
  statement_sid=duplicate_sid,
92
96
  statement_index=idx,
93
97
  issue_type="duplicate_sid",
94
- message=f"Statement ID '{duplicate_sid}' is used {count} times in this policy (found in statements {statement_numbers})",
98
+ message=f"Statement ID `{duplicate_sid}` is used **{count} times** in this policy (found in statements `{statement_numbers}`)",
95
99
  suggestion="Change this SID to a unique value. Statement IDs help identify and reference specific statements, so duplicates can cause confusion.",
96
100
  line_number=statement.line_number,
97
101
  )