iam-policy-validator 1.4.0__py3-none-any.whl → 1.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {iam_policy_validator-1.4.0.dist-info → iam_policy_validator-1.5.0.dist-info}/METADATA +18 -19
  2. {iam_policy_validator-1.4.0.dist-info → iam_policy_validator-1.5.0.dist-info}/RECORD +31 -20
  3. iam_validator/__version__.py +1 -1
  4. iam_validator/checks/__init__.py +13 -3
  5. iam_validator/checks/action_condition_enforcement.py +1 -6
  6. iam_validator/checks/condition_key_validation.py +21 -1
  7. iam_validator/checks/full_wildcard.py +67 -0
  8. iam_validator/checks/principal_validation.py +497 -3
  9. iam_validator/checks/sensitive_action.py +178 -0
  10. iam_validator/checks/service_wildcard.py +105 -0
  11. iam_validator/checks/utils/sensitive_action_matcher.py +39 -31
  12. iam_validator/checks/wildcard_action.py +62 -0
  13. iam_validator/checks/wildcard_resource.py +131 -0
  14. iam_validator/commands/download_services.py +3 -8
  15. iam_validator/commands/validate.py +28 -2
  16. iam_validator/core/aws_fetcher.py +25 -12
  17. iam_validator/core/check_registry.py +15 -21
  18. iam_validator/core/config/__init__.py +83 -0
  19. iam_validator/core/config/aws_api.py +35 -0
  20. iam_validator/core/config/condition_requirements.py +535 -0
  21. iam_validator/core/config/defaults.py +390 -0
  22. iam_validator/core/config/principal_requirements.py +421 -0
  23. iam_validator/core/config/sensitive_actions.py +133 -0
  24. iam_validator/core/config/service_principals.py +95 -0
  25. iam_validator/core/config/wildcards.py +124 -0
  26. iam_validator/core/config_loader.py +29 -9
  27. iam_validator/core/formatters/enhanced.py +11 -5
  28. iam_validator/core/formatters/sarif.py +78 -14
  29. iam_validator/checks/security_best_practices.py +0 -536
  30. iam_validator/core/defaults.py +0 -393
  31. {iam_policy_validator-1.4.0.dist-info → iam_policy_validator-1.5.0.dist-info}/WHEEL +0 -0
  32. {iam_policy_validator-1.4.0.dist-info → iam_policy_validator-1.5.0.dist-info}/entry_points.txt +0 -0
  33. {iam_policy_validator-1.4.0.dist-info → iam_policy_validator-1.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,178 @@
1
+ """Sensitive action check - detects sensitive actions without IAM conditions."""
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from iam_validator.checks.utils.policy_level_checks import check_policy_level_actions
6
+ from iam_validator.checks.utils.sensitive_action_matcher import (
7
+ DEFAULT_SENSITIVE_ACTIONS,
8
+ check_sensitive_actions,
9
+ )
10
+ from iam_validator.checks.utils.wildcard_expansion import expand_wildcard_actions
11
+ from iam_validator.core.aws_fetcher import AWSServiceFetcher
12
+ from iam_validator.core.check_registry import CheckConfig, PolicyCheck
13
+ from iam_validator.core.models import Statement, ValidationIssue
14
+
15
+ if TYPE_CHECKING:
16
+ from iam_validator.core.models import IAMPolicy
17
+
18
+
19
+ class SensitiveActionCheck(PolicyCheck):
20
+ """Checks for sensitive actions without IAM conditions to limit their use."""
21
+
22
+ @property
23
+ def check_id(self) -> str:
24
+ return "sensitive_action"
25
+
26
+ @property
27
+ def description(self) -> str:
28
+ return "Checks for sensitive actions without conditions"
29
+
30
+ @property
31
+ def default_severity(self) -> str:
32
+ return "medium"
33
+
34
+ async def execute(
35
+ self,
36
+ statement: Statement,
37
+ statement_idx: int,
38
+ fetcher: AWSServiceFetcher,
39
+ config: CheckConfig,
40
+ ) -> list[ValidationIssue]:
41
+ """Execute sensitive action check on a statement."""
42
+ issues = []
43
+
44
+ # Only check Allow statements
45
+ if statement.effect != "Allow":
46
+ return issues
47
+
48
+ actions = statement.get_actions()
49
+ has_conditions = statement.condition is not None and len(statement.condition) > 0
50
+
51
+ # Expand wildcards to actual actions using AWS API
52
+ expanded_actions = await expand_wildcard_actions(actions, fetcher)
53
+
54
+ # Check if sensitive actions match using any_of/all_of logic
55
+ is_sensitive, matched_actions = check_sensitive_actions(
56
+ expanded_actions, config, DEFAULT_SENSITIVE_ACTIONS
57
+ )
58
+
59
+ if is_sensitive and not has_conditions:
60
+ # Create appropriate message based on matched actions using configurable templates
61
+ if len(matched_actions) == 1:
62
+ message_template = config.config.get(
63
+ "message_single",
64
+ "Sensitive action '{action}' should have conditions to limit when it can be used",
65
+ )
66
+ message = message_template.format(action=matched_actions[0])
67
+ else:
68
+ action_list = "', '".join(matched_actions)
69
+ message_template = config.config.get(
70
+ "message_multiple",
71
+ "Sensitive actions '{actions}' should have conditions to limit when they can be used",
72
+ )
73
+ message = message_template.format(actions=action_list)
74
+
75
+ suggestion_text = config.config.get(
76
+ "suggestion",
77
+ "Add IAM conditions to limit when this action can be used. Consider: ABAC (ResourceTag OR RequestTag matching ${aws:PrincipalTag}), IP restrictions (aws:SourceIp), MFA requirements (aws:MultiFactorAuthPresent), or time-based restrictions (aws:CurrentTime)",
78
+ )
79
+ example = config.config.get("example", "")
80
+
81
+ # Combine suggestion + example
82
+ suggestion = f"{suggestion_text}\nExample:\n{example}" if example else suggestion_text
83
+
84
+ issues.append(
85
+ ValidationIssue(
86
+ severity=self.get_severity(config),
87
+ statement_sid=statement.sid,
88
+ statement_index=statement_idx,
89
+ issue_type="missing_condition",
90
+ message=message,
91
+ action=(matched_actions[0] if len(matched_actions) == 1 else None),
92
+ suggestion=suggestion,
93
+ line_number=statement.line_number,
94
+ )
95
+ )
96
+
97
+ return issues
98
+
99
+ async def execute_policy(
100
+ self,
101
+ policy: "IAMPolicy",
102
+ policy_file: str,
103
+ fetcher: AWSServiceFetcher,
104
+ config: CheckConfig,
105
+ **kwargs,
106
+ ) -> list[ValidationIssue]:
107
+ """
108
+ Execute policy-level sensitive action checks.
109
+
110
+ This method examines the entire policy to detect privilege escalation patterns
111
+ and other security issues that span multiple statements.
112
+
113
+ Args:
114
+ policy: The complete IAM policy to check
115
+ policy_file: Path to the policy file (for context/reporting)
116
+ fetcher: AWS service fetcher for validation against AWS APIs
117
+ config: Configuration for this check instance
118
+
119
+ Returns:
120
+ List of ValidationIssue objects found by this check
121
+ """
122
+ del policy_file, fetcher # Not used in current implementation
123
+ issues = []
124
+
125
+ # Collect all actions from all Allow statements across the entire policy
126
+ all_actions: set[str] = set()
127
+ statement_map: dict[
128
+ str, list[tuple[int, str | None]]
129
+ ] = {} # action -> [(stmt_idx, sid), ...]
130
+
131
+ for idx, statement in enumerate(policy.statement):
132
+ if statement.effect == "Allow":
133
+ actions = statement.get_actions()
134
+ # Filter out wildcards for privilege escalation detection
135
+ filtered_actions = [a for a in actions if a != "*"]
136
+
137
+ for action in filtered_actions:
138
+ all_actions.add(action)
139
+ if action not in statement_map:
140
+ statement_map[action] = []
141
+ statement_map[action].append((idx, statement.sid))
142
+
143
+ # Get configuration for sensitive actions
144
+ sensitive_actions_config = config.config.get("sensitive_actions")
145
+ sensitive_patterns_config = config.config.get("sensitive_action_patterns")
146
+
147
+ # Check for privilege escalation patterns using all_of logic
148
+ # We need to check both exact actions and patterns
149
+ policy_issues = []
150
+
151
+ # Check sensitive_actions configuration
152
+ if sensitive_actions_config:
153
+ policy_issues.extend(
154
+ check_policy_level_actions(
155
+ list(all_actions),
156
+ statement_map,
157
+ sensitive_actions_config,
158
+ config,
159
+ "actions",
160
+ self.get_severity,
161
+ )
162
+ )
163
+
164
+ # Check sensitive_action_patterns configuration
165
+ if sensitive_patterns_config:
166
+ policy_issues.extend(
167
+ check_policy_level_actions(
168
+ list(all_actions),
169
+ statement_map,
170
+ sensitive_patterns_config,
171
+ config,
172
+ "patterns",
173
+ self.get_severity,
174
+ )
175
+ )
176
+
177
+ issues.extend(policy_issues)
178
+ return issues
@@ -0,0 +1,105 @@
1
+ """Service wildcard check - detects service-level wildcards like 'iam:*', 's3:*'."""
2
+
3
+ from iam_validator.core.aws_fetcher import AWSServiceFetcher
4
+ from iam_validator.core.check_registry import CheckConfig, PolicyCheck
5
+ from iam_validator.core.models import Statement, ValidationIssue
6
+
7
+
8
+ class ServiceWildcardCheck(PolicyCheck):
9
+ """Checks for service-level wildcards (e.g., 'iam:*', 's3:*') which grant all permissions for a service."""
10
+
11
+ @property
12
+ def check_id(self) -> str:
13
+ return "service_wildcard"
14
+
15
+ @property
16
+ def description(self) -> str:
17
+ return "Checks for service-level wildcards (e.g., 'iam:*', 's3:*')"
18
+
19
+ @property
20
+ def default_severity(self) -> str:
21
+ return "high"
22
+
23
+ async def execute(
24
+ self,
25
+ statement: Statement,
26
+ statement_idx: int,
27
+ fetcher: AWSServiceFetcher,
28
+ config: CheckConfig,
29
+ ) -> list[ValidationIssue]:
30
+ """Execute service wildcard check on a statement."""
31
+ issues = []
32
+
33
+ # Only check Allow statements
34
+ if statement.effect != "Allow":
35
+ return issues
36
+
37
+ actions = statement.get_actions()
38
+ allowed_services = self._get_allowed_service_wildcards(config)
39
+
40
+ for action in actions:
41
+ # Skip full wildcard (covered by wildcard_action check)
42
+ if action == "*":
43
+ continue
44
+
45
+ # Check if it's a service-level wildcard (e.g., "iam:*", "s3:*")
46
+ if ":" in action and action.endswith(":*"):
47
+ service = action.split(":")[0]
48
+
49
+ # Check if this service is in the allowed list
50
+ if service not in allowed_services:
51
+ # Get message template and replace placeholders
52
+ message_template = config.config.get(
53
+ "message",
54
+ "Service-level wildcard '{action}' grants all permissions for {service} service",
55
+ )
56
+ suggestion_template = config.config.get(
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*'.",
59
+ )
60
+ example_template = config.config.get("example", "")
61
+
62
+ message = message_template.format(action=action, service=service)
63
+ suggestion_text = suggestion_template.format(action=action, service=service)
64
+ example = (
65
+ example_template.format(action=action, service=service)
66
+ if example_template
67
+ else ""
68
+ )
69
+
70
+ # Combine suggestion + example
71
+ suggestion = (
72
+ f"{suggestion_text}\nExample:\n{example}" if example else suggestion_text
73
+ )
74
+
75
+ issues.append(
76
+ ValidationIssue(
77
+ severity=self.get_severity(config),
78
+ statement_sid=statement.sid,
79
+ statement_index=statement_idx,
80
+ issue_type="overly_permissive",
81
+ message=message,
82
+ action=action,
83
+ suggestion=suggestion,
84
+ line_number=statement.line_number,
85
+ )
86
+ )
87
+
88
+ return issues
89
+
90
+ def _get_allowed_service_wildcards(self, config: CheckConfig) -> set[str]:
91
+ """
92
+ Get list of services that are allowed to use service-level wildcards.
93
+
94
+ This allows configuration like:
95
+ service_wildcard:
96
+ allowed_services:
97
+ - "logs" # Allow "logs:*"
98
+ - "cloudwatch" # Allow "cloudwatch:*"
99
+
100
+ Returns empty set if no exceptions are configured.
101
+ """
102
+ allowed = config.config.get("allowed_services", [])
103
+ if allowed and isinstance(allowed, list):
104
+ return set(allowed)
105
+ return set()
@@ -2,6 +2,11 @@
2
2
 
3
3
  This module provides functionality to match actions against sensitive action
4
4
  configurations, supporting exact matches, regex patterns, and any_of/all_of logic.
5
+
6
+ Performance optimizations:
7
+ - Uses frozenset for O(1) lookups
8
+ - LRU cache for compiled regex patterns
9
+ - Lazy loading of default actions from modular data structure
5
10
  """
6
11
 
7
12
  import re
@@ -9,30 +14,32 @@ from functools import lru_cache
9
14
  from re import Pattern
10
15
 
11
16
  from iam_validator.core.check_registry import CheckConfig
17
+ from iam_validator.core.config.sensitive_actions import get_sensitive_actions
18
+
19
+ # Lazy-loaded default set of sensitive actions
20
+ # This will be loaded only when first accessed
21
+ _DEFAULT_SENSITIVE_ACTIONS_CACHE: frozenset[str] | None = None
22
+
23
+
24
+ def _get_default_sensitive_actions() -> frozenset[str]:
25
+ """
26
+ Get default sensitive actions with lazy loading and caching.
12
27
 
13
- # Default set of sensitive actions for backward compatibility
14
- # Using frozenset for O(1) lookups and immutability
15
- DEFAULT_SENSITIVE_ACTIONS = frozenset(
16
- {
17
- "ec2:DeleteVolume",
18
- "ec2:TerminateInstances",
19
- "eks:DeleteCluster",
20
- "iam:AttachRolePolicy",
21
- "iam:AttachUserPolicy",
22
- "iam:CreateAccessKey",
23
- "iam:CreateRole",
24
- "iam:CreateUser",
25
- "iam:DeleteRole",
26
- "iam:DeleteUser",
27
- "iam:PutRolePolicy",
28
- "iam:PutUserPolicy",
29
- "lambda:DeleteFunction",
30
- "rds:DeleteDBInstance",
31
- "s3:DeleteBucket",
32
- "s3:DeleteBucketPolicy",
33
- "s3:PutBucketPolicy",
34
- }
35
- )
28
+ Returns:
29
+ Frozenset of all default sensitive actions
30
+
31
+ Performance:
32
+ - First call: Loads from sensitive actions list
33
+ - Subsequent calls: O(1) cached lookup
34
+ """
35
+ global _DEFAULT_SENSITIVE_ACTIONS_CACHE
36
+ if _DEFAULT_SENSITIVE_ACTIONS_CACHE is None:
37
+ _DEFAULT_SENSITIVE_ACTIONS_CACHE = get_sensitive_actions()
38
+ return _DEFAULT_SENSITIVE_ACTIONS_CACHE
39
+
40
+
41
+ # Export for backward compatibility
42
+ DEFAULT_SENSITIVE_ACTIONS = _get_default_sensitive_actions()
36
43
 
37
44
 
38
45
  # Global regex pattern cache for performance
@@ -61,15 +68,19 @@ def check_sensitive_actions(
61
68
  Args:
62
69
  actions: List of actions to check
63
70
  config: Check configuration
64
- default_actions: Default sensitive actions to use if no config (defaults to DEFAULT_SENSITIVE_ACTIONS)
71
+ default_actions: Default sensitive actions to use if no config (lazy-loaded)
65
72
 
66
73
  Returns:
67
74
  tuple[bool, list[str]]: (is_sensitive, matched_actions)
68
75
  - is_sensitive: True if the actions match the sensitive criteria
69
76
  - matched_actions: List of actions that matched the criteria
77
+
78
+ Performance:
79
+ - Uses lazy-loaded defaults (only loaded on first use)
80
+ - O(1) frozenset lookups for action matching
70
81
  """
71
82
  if default_actions is None:
72
- default_actions = DEFAULT_SENSITIVE_ACTIONS
83
+ default_actions = _get_default_sensitive_actions()
73
84
 
74
85
  # Filter out wildcards
75
86
  filtered_actions = [a for a in actions if a != "*"]
@@ -77,12 +88,9 @@ def check_sensitive_actions(
77
88
  return False, []
78
89
 
79
90
  # Get configuration for both sensitive_actions and sensitive_action_patterns
80
- sub_check_config = config.config.get("sensitive_action_check", {})
81
- if not isinstance(sub_check_config, dict):
82
- return False, []
83
-
84
- sensitive_actions_config = sub_check_config.get("sensitive_actions")
85
- sensitive_patterns_config = sub_check_config.get("sensitive_action_patterns")
91
+ # Config is now flat (no longer nested under sensitive_action_check)
92
+ sensitive_actions_config = config.config.get("sensitive_actions")
93
+ sensitive_patterns_config = config.config.get("sensitive_action_patterns")
86
94
 
87
95
  # Check sensitive_actions (exact matches)
88
96
  actions_match, actions_matched = check_actions_config(
@@ -0,0 +1,62 @@
1
+ """Wildcard action check - detects Action: '*' in IAM policies."""
2
+
3
+ from iam_validator.core.aws_fetcher import AWSServiceFetcher
4
+ from iam_validator.core.check_registry import CheckConfig, PolicyCheck
5
+ from iam_validator.core.models import Statement, ValidationIssue
6
+
7
+
8
+ class WildcardActionCheck(PolicyCheck):
9
+ """Checks for wildcard actions (Action: '*') which grant all permissions."""
10
+
11
+ @property
12
+ def check_id(self) -> str:
13
+ return "wildcard_action"
14
+
15
+ @property
16
+ def description(self) -> str:
17
+ return "Checks for wildcard actions (*)"
18
+
19
+ @property
20
+ def default_severity(self) -> str:
21
+ return "medium"
22
+
23
+ async def execute(
24
+ self,
25
+ statement: Statement,
26
+ statement_idx: int,
27
+ fetcher: AWSServiceFetcher,
28
+ config: CheckConfig,
29
+ ) -> list[ValidationIssue]:
30
+ """Execute wildcard action check on a statement."""
31
+ issues = []
32
+
33
+ # Only check Allow statements
34
+ if statement.effect != "Allow":
35
+ return issues
36
+
37
+ actions = statement.get_actions()
38
+
39
+ # Check for wildcard action (Action: "*")
40
+ if "*" in actions:
41
+ message = config.config.get("message", "Statement allows all actions (*)")
42
+ suggestion_text = config.config.get(
43
+ "suggestion", "Replace wildcard with specific actions needed for your use case"
44
+ )
45
+ example = config.config.get("example", "")
46
+
47
+ # Combine suggestion + example
48
+ suggestion = f"{suggestion_text}\nExample:\n{example}" if example else suggestion_text
49
+
50
+ issues.append(
51
+ ValidationIssue(
52
+ severity=self.get_severity(config),
53
+ statement_sid=statement.sid,
54
+ statement_index=statement_idx,
55
+ issue_type="overly_permissive",
56
+ message=message,
57
+ suggestion=suggestion,
58
+ line_number=statement.line_number,
59
+ )
60
+ )
61
+
62
+ return issues
@@ -0,0 +1,131 @@
1
+ """Wildcard resource check - detects Resource: '*' in IAM policies."""
2
+
3
+ from iam_validator.checks.utils.wildcard_expansion import expand_wildcard_actions
4
+ from iam_validator.core.aws_fetcher import AWSServiceFetcher
5
+ from iam_validator.core.check_registry import CheckConfig, PolicyCheck
6
+ from iam_validator.core.models import Statement, ValidationIssue
7
+
8
+
9
+ class WildcardResourceCheck(PolicyCheck):
10
+ """Checks for wildcard resources (Resource: '*') which grant access to all resources."""
11
+
12
+ @property
13
+ def check_id(self) -> str:
14
+ return "wildcard_resource"
15
+
16
+ @property
17
+ def description(self) -> str:
18
+ return "Checks for wildcard resources (*)"
19
+
20
+ @property
21
+ def default_severity(self) -> str:
22
+ return "medium"
23
+
24
+ async def execute(
25
+ self,
26
+ statement: Statement,
27
+ statement_idx: int,
28
+ fetcher: AWSServiceFetcher,
29
+ config: CheckConfig,
30
+ ) -> list[ValidationIssue]:
31
+ """Execute wildcard resource check on a statement."""
32
+ issues = []
33
+
34
+ # Only check Allow statements
35
+ if statement.effect != "Allow":
36
+ return issues
37
+
38
+ actions = statement.get_actions()
39
+ resources = statement.get_resources()
40
+
41
+ # Check for wildcard resource (Resource: "*")
42
+ if "*" in resources:
43
+ # Check if all actions are in the allowed_wildcards list
44
+ # allowed_wildcards works by expanding wildcard patterns (like "ec2:Describe*")
45
+ # to all matching AWS actions using the AWS API, then checking if the policy's
46
+ # actions are in that expanded list. This ensures only validated AWS actions
47
+ # are allowed with Resource: "*".
48
+ allowed_wildcards_expanded = await self._get_expanded_allowed_wildcards(config, fetcher)
49
+
50
+ # Check if ALL actions (excluding full wildcard "*") are in the expanded list
51
+ non_wildcard_actions = [a for a in actions if a != "*"]
52
+
53
+ if allowed_wildcards_expanded and non_wildcard_actions:
54
+ # Check if all actions are in the expanded allowed list (exact match)
55
+ all_actions_allowed = all(
56
+ action in allowed_wildcards_expanded for action in non_wildcard_actions
57
+ )
58
+
59
+ # If all actions are in the expanded list, skip the wildcard resource warning
60
+ if all_actions_allowed:
61
+ # All actions are safe, Resource: "*" is acceptable
62
+ return issues
63
+
64
+ # Flag the issue if actions are not all allowed or no allowed_wildcards configured
65
+ message = config.config.get("message", "Statement applies to all resources (*)")
66
+ suggestion_text = config.config.get(
67
+ "suggestion", "Replace wildcard with specific resource ARNs"
68
+ )
69
+ example = config.config.get("example", "")
70
+
71
+ # Combine suggestion + example
72
+ suggestion = f"{suggestion_text}\nExample:\n{example}" if example else suggestion_text
73
+
74
+ issues.append(
75
+ ValidationIssue(
76
+ severity=self.get_severity(config),
77
+ statement_sid=statement.sid,
78
+ statement_index=statement_idx,
79
+ issue_type="overly_permissive",
80
+ message=message,
81
+ suggestion=suggestion,
82
+ line_number=statement.line_number,
83
+ )
84
+ )
85
+
86
+ return issues
87
+
88
+ async def _get_expanded_allowed_wildcards(
89
+ self, config: CheckConfig, fetcher: AWSServiceFetcher
90
+ ) -> frozenset[str]:
91
+ """Get and expand allowed_wildcards configuration.
92
+
93
+ This method retrieves wildcard patterns from the allowed_wildcards config
94
+ and expands them using the AWS API to get all matching actual AWS actions.
95
+
96
+ How it works:
97
+ 1. Retrieves patterns from config (e.g., ["ec2:Describe*", "s3:List*"])
98
+ 2. Expands each pattern using AWS API:
99
+ - "ec2:Describe*" → ["ec2:DescribeInstances", "ec2:DescribeImages", ...]
100
+ - "s3:List*" → ["s3:ListBucket", "s3:ListObjects", ...]
101
+ 3. Returns a set of all expanded actions
102
+
103
+ This allows you to:
104
+ - Specify patterns like "ec2:Describe*" in config
105
+ - Have the validator allow specific actions like "ec2:DescribeInstances" with Resource: "*"
106
+ - Ensure only real AWS actions (validated via API) are allowed
107
+
108
+ Example:
109
+ Config: allowed_wildcards: ["ec2:Describe*"]
110
+ Expands to: ["ec2:DescribeInstances", "ec2:DescribeImages", ...]
111
+ Policy: "Action": ["ec2:DescribeInstances"], "Resource": "*"
112
+ Result: ✅ Allowed (ec2:DescribeInstances is in expanded list)
113
+
114
+ Args:
115
+ config: The check configuration
116
+ fetcher: AWS service fetcher for expanding wildcards via AWS API
117
+
118
+ Returns:
119
+ A frozenset of all expanded action names from the configured patterns
120
+ """
121
+ patterns_to_expand = config.config.get("allowed_wildcards", [])
122
+
123
+ # If no patterns configured, return empty set
124
+ if not patterns_to_expand or not isinstance(patterns_to_expand, list):
125
+ return frozenset()
126
+
127
+ # Expand the wildcard patterns using the AWS API
128
+ # This converts patterns like "ec2:Describe*" to actual AWS actions
129
+ expanded_actions = await expand_wildcard_actions(patterns_to_expand, fetcher)
130
+
131
+ return frozenset(expanded_actions)
@@ -9,20 +9,15 @@ from pathlib import Path
9
9
 
10
10
  import httpx
11
11
  from rich.console import Console
12
- from rich.progress import (
13
- BarColumn,
14
- Progress,
15
- TaskID,
16
- TextColumn,
17
- TimeRemainingColumn,
18
- )
12
+ from rich.progress import BarColumn, Progress, TaskID, TextColumn, TimeRemainingColumn
19
13
 
20
14
  from iam_validator.commands.base import Command
15
+ from iam_validator.core.config import AWS_SERVICE_REFERENCE_BASE_URL
21
16
 
22
17
  logger = logging.getLogger(__name__)
23
18
  console = Console()
24
19
 
25
- BASE_URL = "https://servicereference.us-east-1.amazonaws.com/"
20
+ BASE_URL = AWS_SERVICE_REFERENCE_BASE_URL
26
21
  DEFAULT_OUTPUT_DIR = Path("aws_services")
27
22
 
28
23
 
@@ -166,6 +166,18 @@ Examples:
166
166
  help="Number of policies to process per batch (default: 10, only with --stream)",
167
167
  )
168
168
 
169
+ parser.add_argument(
170
+ "--no-summary",
171
+ action="store_true",
172
+ help="Hide Executive Summary section in enhanced format output",
173
+ )
174
+
175
+ parser.add_argument(
176
+ "--no-severity-breakdown",
177
+ action="store_true",
178
+ help="Hide Issue Severity Breakdown section in enhanced format output",
179
+ )
180
+
169
181
  async def execute(self, args: argparse.Namespace) -> int:
170
182
  """Execute the validate command."""
171
183
  # Check if streaming mode is enabled
@@ -229,7 +241,14 @@ Examples:
229
241
  print(generator.generate_github_comment(report))
230
242
  else:
231
243
  # Use formatter registry for other formats (enhanced, html, csv, sarif)
232
- output_content = generator.format_report(report, args.format)
244
+ # Pass options for enhanced format
245
+ format_options = {}
246
+ if args.format == "enhanced":
247
+ format_options["show_summary"] = not getattr(args, "no_summary", False)
248
+ format_options["show_severity_breakdown"] = not getattr(
249
+ args, "no_severity_breakdown", False
250
+ )
251
+ output_content = generator.format_report(report, args.format, **format_options)
233
252
  if args.output:
234
253
  with open(args.output, "w", encoding="utf-8") as f:
235
254
  f.write(output_content)
@@ -348,7 +367,14 @@ Examples:
348
367
  print(generator.generate_github_comment(report))
349
368
  else:
350
369
  # Use formatter registry for other formats (enhanced, html, csv, sarif)
351
- output_content = generator.format_report(report, args.format)
370
+ # Pass options for enhanced format
371
+ format_options = {}
372
+ if args.format == "enhanced":
373
+ format_options["show_summary"] = not getattr(args, "no_summary", False)
374
+ format_options["show_severity_breakdown"] = not getattr(
375
+ args, "no_severity_breakdown", False
376
+ )
377
+ output_content = generator.format_report(report, args.format, **format_options)
352
378
  if args.output:
353
379
  with open(args.output, "w", encoding="utf-8") as f:
354
380
  f.write(output_content)