iam-policy-validator 1.8.0__py3-none-any.whl → 1.10.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 (49) hide show
  1. {iam_policy_validator-1.8.0.dist-info → iam_policy_validator-1.10.0.dist-info}/METADATA +106 -1
  2. iam_policy_validator-1.10.0.dist-info/RECORD +96 -0
  3. iam_validator/__init__.py +1 -1
  4. iam_validator/__version__.py +1 -1
  5. iam_validator/checks/action_condition_enforcement.py +504 -190
  6. iam_validator/checks/action_resource_matching.py +8 -15
  7. iam_validator/checks/action_validation.py +6 -12
  8. iam_validator/checks/condition_key_validation.py +6 -12
  9. iam_validator/checks/condition_type_mismatch.py +9 -16
  10. iam_validator/checks/full_wildcard.py +9 -13
  11. iam_validator/checks/mfa_condition_check.py +8 -17
  12. iam_validator/checks/policy_size.py +6 -39
  13. iam_validator/checks/policy_structure.py +10 -40
  14. iam_validator/checks/policy_type_validation.py +18 -19
  15. iam_validator/checks/principal_validation.py +11 -20
  16. iam_validator/checks/resource_validation.py +5 -12
  17. iam_validator/checks/sensitive_action.py +8 -15
  18. iam_validator/checks/service_wildcard.py +6 -12
  19. iam_validator/checks/set_operator_validation.py +11 -18
  20. iam_validator/checks/sid_uniqueness.py +8 -38
  21. iam_validator/checks/trust_policy_validation.py +8 -14
  22. iam_validator/checks/utils/wildcard_expansion.py +1 -1
  23. iam_validator/checks/wildcard_action.py +6 -12
  24. iam_validator/checks/wildcard_resource.py +6 -12
  25. iam_validator/commands/cache.py +4 -3
  26. iam_validator/commands/validate.py +26 -4
  27. iam_validator/core/__init__.py +1 -1
  28. iam_validator/core/aws_fetcher.py +24 -1030
  29. iam_validator/core/aws_service/__init__.py +21 -0
  30. iam_validator/core/aws_service/cache.py +108 -0
  31. iam_validator/core/aws_service/client.py +205 -0
  32. iam_validator/core/aws_service/fetcher.py +612 -0
  33. iam_validator/core/aws_service/parsers.py +149 -0
  34. iam_validator/core/aws_service/patterns.py +51 -0
  35. iam_validator/core/aws_service/storage.py +291 -0
  36. iam_validator/core/aws_service/validators.py +379 -0
  37. iam_validator/core/check_registry.py +82 -14
  38. iam_validator/core/config/defaults.py +10 -0
  39. iam_validator/core/constants.py +17 -0
  40. iam_validator/core/label_manager.py +197 -0
  41. iam_validator/core/policy_checks.py +7 -3
  42. iam_validator/core/pr_commenter.py +34 -7
  43. iam_validator/sdk/__init__.py +1 -1
  44. iam_validator/sdk/context.py +1 -1
  45. iam_validator/sdk/helpers.py +1 -1
  46. iam_policy_validator-1.8.0.dist-info/RECORD +0 -87
  47. {iam_policy_validator-1.8.0.dist-info → iam_policy_validator-1.10.0.dist-info}/WHEEL +0 -0
  48. {iam_policy_validator-1.8.0.dist-info → iam_policy_validator-1.10.0.dist-info}/entry_points.txt +0 -0
  49. {iam_policy_validator-1.8.0.dist-info → iam_policy_validator-1.10.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,379 @@
1
+ """Validation logic for AWS actions, condition keys, and resources.
2
+
3
+ This module provides comprehensive validation for IAM policy elements
4
+ including actions, condition keys, and ARN formats.
5
+ """
6
+
7
+ import logging
8
+ from dataclasses import dataclass
9
+ from typing import Any
10
+
11
+ from iam_validator.core.aws_service.parsers import ServiceParser
12
+ from iam_validator.core.models import ServiceDetail
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ @dataclass
18
+ class ConditionKeyValidationResult:
19
+ """Result of condition key validation.
20
+
21
+ Attributes:
22
+ is_valid: True if the condition key is valid for the action
23
+ error_message: Short error message if invalid (shown prominently)
24
+ warning_message: Warning message if valid but not recommended
25
+ suggestion: Detailed suggestion with valid keys (shown in collapsible section)
26
+ """
27
+
28
+ is_valid: bool
29
+ error_message: str | None = None
30
+ warning_message: str | None = None
31
+ suggestion: str | None = None
32
+
33
+
34
+ class ServiceValidator:
35
+ """Validates AWS actions, condition keys, and resources.
36
+
37
+ This class provides validation logic for IAM policy elements,
38
+ working with AWS service definitions to ensure correctness.
39
+ """
40
+
41
+ def __init__(self, parser: ServiceParser | None = None) -> None:
42
+ """Initialize validator with parser.
43
+
44
+ Args:
45
+ parser: Optional ServiceParser instance (creates new one if not provided)
46
+ """
47
+ self._parser = parser or ServiceParser()
48
+
49
+ async def validate_action(
50
+ self,
51
+ action: str,
52
+ service_detail: ServiceDetail,
53
+ allow_wildcards: bool = True,
54
+ ) -> tuple[bool, str | None, bool]:
55
+ """Validate IAM action against service definition.
56
+
57
+ Supports:
58
+ - Exact actions: s3:GetObject
59
+ - Full wildcards: s3:*
60
+ - Partial wildcards: s3:Get*, s3:*Object, s3:*Get*
61
+
62
+ Args:
63
+ action: Full action string (e.g., "s3:GetObject")
64
+ service_detail: Service definition to validate against
65
+ allow_wildcards: Whether to allow wildcard actions
66
+
67
+ Returns:
68
+ Tuple of (is_valid, error_message, is_wildcard)
69
+
70
+ Example:
71
+ >>> validator = ServiceValidator()
72
+ >>> service = await fetcher.fetch_service_by_name("s3")
73
+ >>> is_valid, error, is_wildcard = await validator.validate_action(
74
+ ... "s3:GetObject", service
75
+ ... )
76
+ """
77
+ try:
78
+ service_prefix, action_name = self._parser.parse_action(action)
79
+
80
+ # Quick wildcard check
81
+ is_wildcard = self._parser.is_wildcard_action(action_name)
82
+
83
+ # Handle full wildcard
84
+ if action_name == "*":
85
+ if allow_wildcards:
86
+ return True, None, True
87
+ return False, "Wildcard actions are not allowed", True
88
+
89
+ # Get available actions from service
90
+ available_actions = list(service_detail.actions.keys())
91
+
92
+ # Handle partial wildcards (e.g., Get*, *Object, Describe*)
93
+ if is_wildcard:
94
+ if not allow_wildcards:
95
+ return False, "Wildcard actions are not allowed", True
96
+
97
+ has_matches, matched_actions = self._parser.match_wildcard_action(
98
+ action_name, available_actions
99
+ )
100
+
101
+ if has_matches:
102
+ # Wildcard is valid and matches at least one action
103
+ return True, None, True
104
+
105
+ # Wildcard doesn't match any actions
106
+ return (
107
+ False,
108
+ f"Action pattern `{action_name}` does not match any actions in service `{service_prefix}`",
109
+ True,
110
+ )
111
+
112
+ # Check if exact action exists (case-insensitive)
113
+ action_exists = any(a.lower() == action_name.lower() for a in available_actions)
114
+
115
+ if action_exists:
116
+ return True, None, False
117
+
118
+ # Suggest similar actions
119
+ similar = [f"`{a}`" for a in available_actions if action_name.lower() in a.lower()][:3]
120
+
121
+ suggestion = f" Did you mean: {', '.join(similar)}?" if similar else ""
122
+ return (
123
+ False,
124
+ f"Action `{action_name}` not found in service `{service_prefix}`.{suggestion}",
125
+ False,
126
+ )
127
+
128
+ except ValueError as e:
129
+ return False, str(e), False
130
+ except Exception as e: # pylint: disable=broad-exception-caught
131
+ logger.error(f"Error validating action {action}: {e}")
132
+ return False, f"Failed to validate action: {e!s}", False
133
+
134
+ async def validate_condition_key(
135
+ self,
136
+ action: str,
137
+ condition_key: str,
138
+ service_detail: ServiceDetail,
139
+ resources: list[str] | None = None,
140
+ ) -> ConditionKeyValidationResult:
141
+ """Validate condition key against action and optionally resource types.
142
+
143
+ Args:
144
+ action: IAM action (e.g., "s3:GetObject")
145
+ condition_key: Condition key to validate (e.g., "s3:prefix")
146
+ service_detail: Service definition containing actions and resources
147
+ resources: Optional list of resource ARNs to validate against
148
+
149
+ Returns:
150
+ ConditionKeyValidationResult with validation details
151
+
152
+ Example:
153
+ >>> validator = ServiceValidator()
154
+ >>> service = await fetcher.fetch_service_by_name("s3")
155
+ >>> result = await validator.validate_condition_key(
156
+ ... "s3:GetObject", "s3:prefix", service
157
+ ... )
158
+ """
159
+ try:
160
+ from iam_validator.core.config.aws_global_conditions import ( # pylint: disable=import-outside-toplevel
161
+ get_global_conditions,
162
+ )
163
+
164
+ service_prefix, action_name = self._parser.parse_action(action)
165
+
166
+ # Check if it's a global condition key
167
+ is_global_key = False
168
+ if condition_key.startswith("aws:"):
169
+ global_conditions = get_global_conditions()
170
+ if global_conditions.is_valid_global_key(condition_key):
171
+ is_global_key = True
172
+ else:
173
+ return ConditionKeyValidationResult(
174
+ is_valid=False,
175
+ error_message=f"Invalid AWS global condition key: `{condition_key}`.",
176
+ )
177
+
178
+ # Check service-specific condition keys
179
+ if condition_key in service_detail.condition_keys:
180
+ return ConditionKeyValidationResult(is_valid=True)
181
+
182
+ # Check action-specific condition keys
183
+ if action_name in service_detail.actions:
184
+ action_detail = service_detail.actions[action_name]
185
+ if (
186
+ action_detail.action_condition_keys
187
+ and condition_key in action_detail.action_condition_keys
188
+ ):
189
+ return ConditionKeyValidationResult(is_valid=True)
190
+
191
+ # Check resource-specific condition keys
192
+ # Get resource types required by this action
193
+ if resources and action_detail.resources:
194
+ for res_req in action_detail.resources:
195
+ resource_name = res_req.get("Name", "")
196
+ if not resource_name:
197
+ continue
198
+
199
+ # Look up resource type definition
200
+ resource_type = service_detail.resources.get(resource_name)
201
+ if resource_type and resource_type.condition_keys:
202
+ if condition_key in resource_type.condition_keys:
203
+ return ConditionKeyValidationResult(is_valid=True)
204
+
205
+ # If it's a global key but the action has specific condition keys defined,
206
+ # AWS allows it but the key may not be available in every request context
207
+ if is_global_key and action_detail.action_condition_keys is not None:
208
+ warning_msg = (
209
+ f"Global condition key `{condition_key}` is used with action `{action}`. "
210
+ f"While global condition keys can be used across all AWS services, "
211
+ f"the key may not be available in every request context. "
212
+ f"Verify that `{condition_key}` is available for this specific action's request context. "
213
+ f"Consider using `*IfExists` operators (e.g., `StringEqualsIfExists`) if the key might be missing."
214
+ )
215
+ return ConditionKeyValidationResult(is_valid=True, warning_message=warning_msg)
216
+
217
+ # If it's a global key and action doesn't define specific keys, allow it
218
+ if is_global_key:
219
+ return ConditionKeyValidationResult(is_valid=True)
220
+
221
+ # Short error message
222
+ error_msg = f"Condition key `{condition_key}` is not valid for action `{action}`"
223
+
224
+ # Collect valid condition keys for this action
225
+ valid_keys: set[str] = set()
226
+
227
+ # Add service-level condition keys
228
+ if service_detail.condition_keys:
229
+ if isinstance(service_detail.condition_keys, dict):
230
+ valid_keys.update(service_detail.condition_keys.keys())
231
+ elif isinstance(service_detail.condition_keys, list):
232
+ valid_keys.update(service_detail.condition_keys)
233
+
234
+ # Add action-specific condition keys
235
+ if action_name in service_detail.actions:
236
+ action_detail = service_detail.actions[action_name]
237
+ if action_detail.action_condition_keys:
238
+ if isinstance(action_detail.action_condition_keys, dict):
239
+ valid_keys.update(action_detail.action_condition_keys.keys())
240
+ elif isinstance(action_detail.action_condition_keys, list):
241
+ valid_keys.update(action_detail.action_condition_keys)
242
+
243
+ # Add resource-specific condition keys
244
+ if action_detail.resources:
245
+ for res_req in action_detail.resources:
246
+ resource_name = res_req.get("Name", "")
247
+ if resource_name:
248
+ resource_type = service_detail.resources.get(resource_name)
249
+ if resource_type and resource_type.condition_keys:
250
+ if isinstance(resource_type.condition_keys, dict):
251
+ valid_keys.update(resource_type.condition_keys.keys())
252
+ elif isinstance(resource_type.condition_keys, list):
253
+ valid_keys.update(resource_type.condition_keys)
254
+
255
+ # Build detailed suggestion with valid keys (goes in collapsible section)
256
+ suggestion_parts = []
257
+
258
+ if valid_keys:
259
+ # Sort and limit to first 10 keys for readability
260
+ sorted_keys = sorted(valid_keys)
261
+ suggestion_parts.append("**Valid condition keys for this action:**")
262
+ if len(sorted_keys) <= 10:
263
+ for key in sorted_keys:
264
+ suggestion_parts.append(f"- `{key}`")
265
+ else:
266
+ for key in sorted_keys[:10]:
267
+ suggestion_parts.append(f"- `{key}`")
268
+ suggestion_parts.append(f"- ... and {len(sorted_keys) - 10} more")
269
+
270
+ suggestion_parts.append("")
271
+ suggestion_parts.append(
272
+ "**Global condition keys** (e.g., `aws:ResourceOrgID`, `aws:RequestedRegion`, `aws:SourceIp`, `aws:SourceVpce`) "
273
+ "can also be used with any AWS action"
274
+ )
275
+ else:
276
+ # No action-specific keys - mention global keys
277
+ suggestion_parts.append(
278
+ "This action does not have specific condition keys defined.\n\n"
279
+ "However, you can use **global condition keys** such as:\n"
280
+ "- `aws:RequestedRegion`\n"
281
+ "- `aws:SourceIp`\n"
282
+ "- `aws:SourceVpce`\n"
283
+ "- `aws:UserAgent`\n"
284
+ "- `aws:CurrentTime`\n"
285
+ "- `aws:SecureTransport`\n"
286
+ "- `aws:PrincipalArn`\n"
287
+ "- And many others"
288
+ )
289
+
290
+ suggestion = "\n".join(suggestion_parts)
291
+
292
+ return ConditionKeyValidationResult(
293
+ is_valid=False,
294
+ error_message=error_msg,
295
+ suggestion=suggestion,
296
+ )
297
+
298
+ except Exception as e: # pylint: disable=broad-exception-caught
299
+ logger.error(f"Error validating condition key {condition_key} for {action}: {e}")
300
+ return ConditionKeyValidationResult(
301
+ is_valid=False,
302
+ error_message=f"Failed to validate condition key: {e!s}",
303
+ )
304
+
305
+ def get_resources_for_action(
306
+ self, action: str, service_detail: ServiceDetail
307
+ ) -> list[dict[str, Any]]:
308
+ """Get resource types required for a specific action.
309
+
310
+ Args:
311
+ action: Full action name (e.g., "s3:GetObject", "iam:CreateUser")
312
+ service_detail: Service definition containing action details
313
+
314
+ Returns:
315
+ List of resource dictionaries from AWS API, or empty list if action not found
316
+
317
+ Example:
318
+ >>> validator = ServiceValidator()
319
+ >>> service = await fetcher.fetch_service_by_name("s3")
320
+ >>> resources = validator.get_resources_for_action("s3:GetObject", service)
321
+ """
322
+ try:
323
+ _, action_name = self._parser.parse_action(action)
324
+
325
+ # Find the action (case-insensitive)
326
+ action_detail = service_detail.actions.get(action_name)
327
+ if action_detail and action_detail.resources:
328
+ return action_detail.resources
329
+ return []
330
+ except Exception as e: # pylint: disable=broad-exception-caught
331
+ logger.error(f"Error getting resources for action {action}: {e}")
332
+ return []
333
+
334
+ def get_arn_formats_for_action(self, action: str, service_detail: ServiceDetail) -> list[str]:
335
+ """Get ARN formats/patterns for resources used by a specific action.
336
+
337
+ This method extracts the ARN format patterns from the resource types
338
+ that an action can operate on. Useful for validating Resource elements
339
+ in IAM policies.
340
+
341
+ Args:
342
+ action: Full action name (e.g., "s3:GetObject", "iam:CreateUser")
343
+ service_detail: Service definition containing action and resource details
344
+
345
+ Returns:
346
+ List of ARN format strings, or empty list if action not found or has no resources
347
+
348
+ Example:
349
+ >>> validator = ServiceValidator()
350
+ >>> service = await fetcher.fetch_service_by_name("s3")
351
+ >>> arns = validator.get_arn_formats_for_action("s3:GetObject", service)
352
+ >>> # Returns: ["arn:${Partition}:s3:::${BucketName}/${ObjectName}"]
353
+ """
354
+ try:
355
+ _, action_name = self._parser.parse_action(action)
356
+
357
+ # Find the action
358
+ action_detail = service_detail.actions.get(action_name)
359
+ if not action_detail or not action_detail.resources:
360
+ return []
361
+
362
+ # Extract ARN formats from resource types
363
+ arn_formats = []
364
+ for resource_ref in action_detail.resources:
365
+ # resource_ref is a dict with "Name" key pointing to resource type name
366
+ resource_name = resource_ref.get("Name", "")
367
+ if not resource_name:
368
+ continue
369
+
370
+ # Look up the resource type in service definition
371
+ resource_type = service_detail.resources.get(resource_name)
372
+ if resource_type and resource_type.arn_formats:
373
+ arn_formats.extend(resource_type.arn_formats)
374
+
375
+ return arn_formats
376
+
377
+ except Exception as e: # pylint: disable=broad-exception-caught
378
+ logger.error(f"Error getting ARN formats for action {action}: {e}")
379
+ return []
@@ -10,11 +10,11 @@ This module provides a pluggable check system that allows:
10
10
  """
11
11
 
12
12
  import asyncio
13
- from abc import ABC, abstractmethod
13
+ from abc import ABC
14
14
  from dataclasses import dataclass, field
15
15
  from typing import TYPE_CHECKING, Any
16
16
 
17
- from iam_validator.core.aws_fetcher import AWSServiceFetcher
17
+ from iam_validator.core.aws_service import AWSServiceFetcher
18
18
  from iam_validator.core.ignore_patterns import IgnorePatternMatcher
19
19
  from iam_validator.core.models import Statement, ValidationIssue
20
20
 
@@ -103,38 +103,101 @@ class PolicyCheck(ABC):
103
103
 
104
104
  To create a custom check:
105
105
  1. Inherit from this class
106
- 2. Implement check_id, description, and execute()
107
- 3. Register with CheckRegistry
106
+ 2. Implement check_id and description (required)
107
+ 3. Implement either execute() OR execute_policy() (or both)
108
+ 4. Register with CheckRegistry
108
109
 
109
- Example:
110
- class MyCustomCheck(PolicyCheck):
111
- check_id = "my_custom_check"
112
- description = "Validates custom compliance rules"
110
+ Two ways to define check_id and description:
111
+
112
+ Option 1 - Class attributes (simpler, recommended for static values):
113
+ from typing import ClassVar
114
+
115
+ class MyCheck(PolicyCheck):
116
+ check_id: ClassVar[str] = "my_check"
117
+ description: ClassVar[str] = "My check description"
118
+
119
+ async def execute(self, statement, statement_idx, fetcher, config):
120
+ return []
121
+
122
+ Note: ClassVar annotation is required for Pylance type checker compatibility.
123
+
124
+ Option 2 - Property decorators (more flexible, supports dynamic values):
125
+ class MyCheck(PolicyCheck):
126
+ @property
127
+ def check_id(self) -> str:
128
+ return "my_check"
129
+
130
+ @property
131
+ def description(self) -> str:
132
+ return "My check description"
113
133
 
114
134
  async def execute(self, statement, statement_idx, fetcher, config):
135
+ return []
136
+
137
+ Statement-level check example:
138
+ from typing import ClassVar
139
+
140
+ class MyStatementCheck(PolicyCheck):
141
+ check_id: ClassVar[str] = "my_statement_check"
142
+ description: ClassVar[str] = "Validates individual statements"
143
+
144
+ async def execute(self, statement, statement_idx, fetcher, config):
145
+ issues = []
146
+ # Your validation logic here
147
+ return issues
148
+
149
+ Policy-level check example:
150
+ from typing import ClassVar
151
+
152
+ class MyPolicyCheck(PolicyCheck):
153
+ check_id: ClassVar[str] = "my_policy_check"
154
+ description: ClassVar[str] = "Validates entire policy"
155
+
156
+ async def execute_policy(self, policy, policy_file, fetcher, config, **kwargs):
115
157
  issues = []
116
158
  # Your validation logic here
117
159
  return issues
118
160
  """
119
161
 
120
162
  @property
121
- @abstractmethod
122
163
  def check_id(self) -> str:
123
164
  """Unique identifier for this check (e.g., 'action_validation')."""
124
- pass
165
+ raise NotImplementedError("Subclasses must define check_id")
125
166
 
126
167
  @property
127
- @abstractmethod
128
168
  def description(self) -> str:
129
169
  """Human-readable description of what this check does."""
130
- pass
170
+ raise NotImplementedError("Subclasses must define description")
131
171
 
132
172
  @property
133
173
  def default_severity(self) -> str:
134
174
  """Default severity level for issues found by this check."""
135
175
  return "warning"
136
176
 
137
- @abstractmethod
177
+ def __init_subclass__(cls, **kwargs):
178
+ """
179
+ Validate that subclasses override at least one execution method.
180
+
181
+ This ensures checks implement either execute() OR execute_policy() (or both).
182
+ If neither is overridden, the check would never produce any results.
183
+ """
184
+ super().__init_subclass__(**kwargs)
185
+
186
+ # Skip validation for abstract classes
187
+ if ABC in cls.__bases__:
188
+ return
189
+
190
+ # Check if at least one method is overridden
191
+ has_execute = cls.execute is not PolicyCheck.execute
192
+ has_execute_policy = cls.execute_policy is not PolicyCheck.execute_policy
193
+
194
+ if not has_execute and not has_execute_policy:
195
+ raise TypeError(
196
+ f"Check '{cls.__name__}' must override at least one of: "
197
+ "execute() for statement-level checks, or "
198
+ "execute_policy() for policy-level checks"
199
+ )
200
+
138
201
  async def execute(
139
202
  self,
140
203
  statement: Statement,
@@ -145,6 +208,10 @@ class PolicyCheck(ABC):
145
208
  """
146
209
  Execute the check on a policy statement.
147
210
 
211
+ This method is called for statement-level checks. If your check only needs
212
+ to examine the entire policy (not individual statements), you can leave this
213
+ as the default implementation and override execute_policy() instead.
214
+
148
215
  Args:
149
216
  statement: The IAM policy statement to check
150
217
  statement_idx: Index of the statement in the policy
@@ -154,7 +221,8 @@ class PolicyCheck(ABC):
154
221
  Returns:
155
222
  List of ValidationIssue objects found by this check
156
223
  """
157
- pass
224
+ del statement, statement_idx, fetcher, config # Unused in default implementation
225
+ return []
158
226
 
159
227
  async def execute_policy(
160
228
  self,
@@ -75,6 +75,16 @@ DEFAULT_CONFIG = {
75
75
  # IAM Validity: error, warning, info
76
76
  # Security: critical, high, medium, low
77
77
  "fail_on_severity": list(constants.HIGH_SEVERITY_LEVELS),
78
+ # GitHub PR label mapping based on severity findings
79
+ # When issues with these severities are found, apply the corresponding labels
80
+ # If no issues with these severities exist, remove the labels if present
81
+ # Supports both single labels and lists of labels per severity
82
+ # Examples:
83
+ # Single label per severity: {"error": "iam-validity-error", "critical": "security-critical"}
84
+ # Multiple labels per severity: {"error": ["iam-error", "needs-fix"], "critical": ["security-critical", "needs-review"]}
85
+ # Mixed: {"error": "iam-validity-error", "critical": ["security-critical", "needs-review"]}
86
+ # Default: {} (disabled)
87
+ "severity_labels": {},
78
88
  },
79
89
  # ========================================================================
80
90
  # AWS IAM Validation Checks (17 checks total)
@@ -123,6 +123,23 @@ DEFAULT_HTTP_TIMEOUT_SECONDS = 30.0
123
123
  # Time conversion constants
124
124
  SECONDS_PER_HOUR = 3600
125
125
 
126
+ # ============================================================================
127
+ # Policy Type Restrictions
128
+ # ============================================================================
129
+
130
+ # AWS services that support Resource Control Policies (RCP)
131
+ # These services can have wildcard actions in RCP policy statements
132
+ # Reference: https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_rcps.html
133
+ RCP_SUPPORTED_SERVICES = frozenset(
134
+ {
135
+ "s3",
136
+ "sts",
137
+ "sqs",
138
+ "secretsmanager",
139
+ "kms",
140
+ }
141
+ )
142
+
126
143
  # ============================================================================
127
144
  # AWS Documentation URLs
128
145
  # ============================================================================