iam-policy-validator 1.7.2__py3-none-any.whl → 1.9.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.
- {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.9.0.dist-info}/METADATA +127 -6
- iam_policy_validator-1.9.0.dist-info/RECORD +95 -0
- iam_validator/__init__.py +1 -1
- iam_validator/__version__.py +1 -1
- iam_validator/checks/__init__.py +5 -3
- iam_validator/checks/action_condition_enforcement.py +559 -207
- iam_validator/checks/action_resource_matching.py +12 -15
- iam_validator/checks/action_validation.py +7 -13
- iam_validator/checks/condition_key_validation.py +7 -13
- iam_validator/checks/condition_type_mismatch.py +15 -22
- iam_validator/checks/full_wildcard.py +9 -13
- iam_validator/checks/mfa_condition_check.py +8 -17
- iam_validator/checks/policy_size.py +6 -39
- iam_validator/checks/policy_structure.py +547 -0
- iam_validator/checks/policy_type_validation.py +61 -46
- iam_validator/checks/principal_validation.py +71 -148
- iam_validator/checks/resource_validation.py +13 -20
- iam_validator/checks/sensitive_action.py +15 -18
- iam_validator/checks/service_wildcard.py +8 -14
- iam_validator/checks/set_operator_validation.py +21 -28
- iam_validator/checks/sid_uniqueness.py +16 -42
- iam_validator/checks/trust_policy_validation.py +506 -0
- iam_validator/checks/utils/sensitive_action_matcher.py +26 -26
- iam_validator/checks/utils/wildcard_expansion.py +2 -2
- iam_validator/checks/wildcard_action.py +9 -13
- iam_validator/checks/wildcard_resource.py +9 -13
- iam_validator/commands/cache.py +4 -3
- iam_validator/commands/validate.py +15 -9
- iam_validator/core/__init__.py +2 -3
- iam_validator/core/access_analyzer.py +1 -1
- iam_validator/core/access_analyzer_report.py +2 -2
- iam_validator/core/aws_fetcher.py +24 -1028
- iam_validator/core/aws_service/__init__.py +21 -0
- iam_validator/core/aws_service/cache.py +108 -0
- iam_validator/core/aws_service/client.py +205 -0
- iam_validator/core/aws_service/fetcher.py +612 -0
- iam_validator/core/aws_service/parsers.py +149 -0
- iam_validator/core/aws_service/patterns.py +51 -0
- iam_validator/core/aws_service/storage.py +291 -0
- iam_validator/core/aws_service/validators.py +379 -0
- iam_validator/core/check_registry.py +165 -93
- iam_validator/core/config/condition_requirements.py +69 -17
- iam_validator/core/config/defaults.py +58 -52
- iam_validator/core/config/service_principals.py +40 -3
- iam_validator/core/constants.py +17 -0
- iam_validator/core/ignore_patterns.py +297 -0
- iam_validator/core/models.py +15 -5
- iam_validator/core/policy_checks.py +38 -475
- iam_validator/core/policy_loader.py +27 -4
- iam_validator/sdk/__init__.py +1 -1
- iam_validator/sdk/context.py +1 -1
- iam_validator/sdk/helpers.py +1 -1
- iam_policy_validator-1.7.2.dist-info/RECORD +0 -84
- {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.9.0.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.9.0.dist-info}/entry_points.txt +0 -0
- {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.9.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""Resource validation check - validates ARN formats."""
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
+
from typing import ClassVar
|
|
4
5
|
|
|
5
|
-
from iam_validator.core.
|
|
6
|
+
from iam_validator.core.aws_service import AWSServiceFetcher
|
|
6
7
|
from iam_validator.core.check_registry import CheckConfig, PolicyCheck
|
|
7
8
|
from iam_validator.core.constants import DEFAULT_ARN_VALIDATION_PATTERN, MAX_ARN_LENGTH
|
|
8
9
|
from iam_validator.core.models import Statement, ValidationIssue
|
|
@@ -15,17 +16,9 @@ from iam_validator.sdk.arn_matching import (
|
|
|
15
16
|
class ResourceValidationCheck(PolicyCheck):
|
|
16
17
|
"""Validates ARN format for resources."""
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@property
|
|
23
|
-
def description(self) -> str:
|
|
24
|
-
return "Validates ARN format for resources"
|
|
25
|
-
|
|
26
|
-
@property
|
|
27
|
-
def default_severity(self) -> str:
|
|
28
|
-
return "error"
|
|
19
|
+
check_id: ClassVar[str] = "resource_validation"
|
|
20
|
+
description: ClassVar[str] = "Validates ARN format for resources"
|
|
21
|
+
default_severity: ClassVar[str] = "error"
|
|
29
22
|
|
|
30
23
|
async def execute(
|
|
31
24
|
self,
|
|
@@ -75,7 +68,7 @@ class ResourceValidationCheck(PolicyCheck):
|
|
|
75
68
|
issue_type="invalid_resource",
|
|
76
69
|
message=f"Resource ARN exceeds maximum length ({len(resource)} > {MAX_ARN_LENGTH}): {resource[:100]}...",
|
|
77
70
|
resource=resource[:100] + "...",
|
|
78
|
-
suggestion="ARN is too long and may be invalid",
|
|
71
|
+
suggestion="`ARN` is too long and may be invalid",
|
|
79
72
|
line_number=line_number,
|
|
80
73
|
)
|
|
81
74
|
)
|
|
@@ -101,9 +94,9 @@ class ResourceValidationCheck(PolicyCheck):
|
|
|
101
94
|
statement_sid=statement_sid,
|
|
102
95
|
statement_index=statement_idx,
|
|
103
96
|
issue_type="invalid_resource",
|
|
104
|
-
message=f"Invalid ARN format even after normalizing template variables: {resource}",
|
|
97
|
+
message=f"Invalid `ARN` format even after normalizing template variables: `{resource}`",
|
|
105
98
|
resource=resource,
|
|
106
|
-
suggestion="ARN should follow format: arn:partition:service:region:account-id:resource (template variables like
|
|
99
|
+
suggestion="`ARN` should follow format: `arn:partition:service:region:account-id:resource` (template variables like `${aws_account_id}` are supported)",
|
|
107
100
|
line_number=line_number,
|
|
108
101
|
)
|
|
109
102
|
)
|
|
@@ -114,13 +107,13 @@ class ResourceValidationCheck(PolicyCheck):
|
|
|
114
107
|
statement_sid=statement_sid,
|
|
115
108
|
statement_index=statement_idx,
|
|
116
109
|
issue_type="invalid_resource",
|
|
117
|
-
message=f"Invalid ARN format: {resource}",
|
|
110
|
+
message=f"Invalid `ARN` format: `{resource}`",
|
|
118
111
|
resource=resource,
|
|
119
|
-
suggestion="ARN should follow format: arn:partition:service:region:account-id:resource",
|
|
112
|
+
suggestion="`ARN` should follow format: `arn:partition:service:region:account-id:resource`",
|
|
120
113
|
line_number=line_number,
|
|
121
114
|
)
|
|
122
115
|
)
|
|
123
|
-
except Exception:
|
|
116
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
124
117
|
# If regex matching fails (shouldn't happen with length check), treat as invalid
|
|
125
118
|
issues.append(
|
|
126
119
|
ValidationIssue(
|
|
@@ -128,9 +121,9 @@ class ResourceValidationCheck(PolicyCheck):
|
|
|
128
121
|
statement_sid=statement_sid,
|
|
129
122
|
statement_index=statement_idx,
|
|
130
123
|
issue_type="invalid_resource",
|
|
131
|
-
message=f"Could not validate ARN format: {resource}",
|
|
124
|
+
message=f"Could not validate `ARN` format: `{resource}`",
|
|
132
125
|
resource=resource,
|
|
133
|
-
suggestion="ARN validation failed - may contain unexpected characters",
|
|
126
|
+
suggestion="`ARN` validation failed - may contain unexpected characters",
|
|
134
127
|
line_number=line_number,
|
|
135
128
|
)
|
|
136
129
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Sensitive action check - detects sensitive actions without IAM conditions."""
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
3
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
4
4
|
|
|
5
5
|
from iam_validator.checks.utils.policy_level_checks import check_policy_level_actions
|
|
6
6
|
from iam_validator.checks.utils.sensitive_action_matcher import (
|
|
@@ -8,7 +8,7 @@ from iam_validator.checks.utils.sensitive_action_matcher import (
|
|
|
8
8
|
check_sensitive_actions,
|
|
9
9
|
)
|
|
10
10
|
from iam_validator.checks.utils.wildcard_expansion import expand_wildcard_actions
|
|
11
|
-
from iam_validator.core.
|
|
11
|
+
from iam_validator.core.aws_service import AWSServiceFetcher
|
|
12
12
|
from iam_validator.core.check_registry import CheckConfig, PolicyCheck
|
|
13
13
|
from iam_validator.core.config.sensitive_actions import get_category_for_action
|
|
14
14
|
from iam_validator.core.models import Statement, ValidationIssue
|
|
@@ -20,17 +20,9 @@ if TYPE_CHECKING:
|
|
|
20
20
|
class SensitiveActionCheck(PolicyCheck):
|
|
21
21
|
"""Checks for sensitive actions without IAM conditions to limit their use."""
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@property
|
|
28
|
-
def description(self) -> str:
|
|
29
|
-
return "Checks for sensitive actions without conditions"
|
|
30
|
-
|
|
31
|
-
@property
|
|
32
|
-
def default_severity(self) -> str:
|
|
33
|
-
return "medium"
|
|
23
|
+
check_id: ClassVar[str] = "sensitive_action"
|
|
24
|
+
description: ClassVar[str] = "Checks for sensitive actions without conditions"
|
|
25
|
+
default_severity: ClassVar[str] = "medium"
|
|
34
26
|
|
|
35
27
|
def _get_severity_for_action(self, action: str, config: CheckConfig) -> str:
|
|
36
28
|
"""
|
|
@@ -85,9 +77,10 @@ class SensitiveActionCheck(PolicyCheck):
|
|
|
85
77
|
# Generic ABAC fallback for uncategorized actions
|
|
86
78
|
return (
|
|
87
79
|
"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/<tag-name
|
|
89
|
-
"•
|
|
90
|
-
"•
|
|
80
|
+
"• Match principal tags to resource tags (`aws:PrincipalTag/<tag-name>` = `aws:ResourceTag/<tag-name>`)\n"
|
|
81
|
+
"• Match organization principal tags to resource tags (`aws:PrincipalOrgID` = `aws:ResourceOrgID`)\n"
|
|
82
|
+
"• Require MFA (`aws:MultiFactorAuthPresent` = `true`)\n"
|
|
83
|
+
"• Restrict by IP (`aws:SourceIp`) or VPC (`aws:SourceVpc`)",
|
|
91
84
|
'"Condition": {\n'
|
|
92
85
|
' "StringEquals": {\n'
|
|
93
86
|
' "aws:PrincipalTag/owner": "${aws:ResourceTag/owner}"\n'
|
|
@@ -125,14 +118,14 @@ class SensitiveActionCheck(PolicyCheck):
|
|
|
125
118
|
if len(matched_actions) == 1:
|
|
126
119
|
message_template = config.config.get(
|
|
127
120
|
"message_single",
|
|
128
|
-
"Sensitive action
|
|
121
|
+
"Sensitive action `{action}` should have conditions to limit when it can be used",
|
|
129
122
|
)
|
|
130
123
|
message = message_template.format(action=matched_actions[0])
|
|
131
124
|
else:
|
|
132
125
|
action_list = "', '".join(matched_actions)
|
|
133
126
|
message_template = config.config.get(
|
|
134
127
|
"message_multiple",
|
|
135
|
-
"Sensitive actions
|
|
128
|
+
"Sensitive actions `{actions}` should have conditions to limit when they can be used",
|
|
136
129
|
)
|
|
137
130
|
message = message_template.format(actions=action_list)
|
|
138
131
|
|
|
@@ -192,6 +185,10 @@ class SensitiveActionCheck(PolicyCheck):
|
|
|
192
185
|
del policy_file, fetcher # Not used in current implementation
|
|
193
186
|
issues = []
|
|
194
187
|
|
|
188
|
+
# Handle policies with no statements
|
|
189
|
+
if not policy.statement:
|
|
190
|
+
return []
|
|
191
|
+
|
|
195
192
|
# Collect all actions from all Allow statements across the entire policy
|
|
196
193
|
all_actions: set[str] = set()
|
|
197
194
|
statement_map: dict[
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""Service wildcard check - detects service-level wildcards like 'iam:*', 's3:*'."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from typing import ClassVar
|
|
4
|
+
|
|
5
|
+
from iam_validator.core.aws_service import AWSServiceFetcher
|
|
4
6
|
from iam_validator.core.check_registry import CheckConfig, PolicyCheck
|
|
5
7
|
from iam_validator.core.models import Statement, ValidationIssue
|
|
6
8
|
|
|
@@ -8,17 +10,9 @@ from iam_validator.core.models import Statement, ValidationIssue
|
|
|
8
10
|
class ServiceWildcardCheck(PolicyCheck):
|
|
9
11
|
"""Checks for service-level wildcards (e.g., 'iam:*', 's3:*') which grant all permissions for a service."""
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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"
|
|
13
|
+
check_id: ClassVar[str] = "service_wildcard"
|
|
14
|
+
description: ClassVar[str] = "Checks for service-level wildcards (e.g., 'iam:*', 's3:*')"
|
|
15
|
+
default_severity: ClassVar[str] = "high"
|
|
22
16
|
|
|
23
17
|
async def execute(
|
|
24
18
|
self,
|
|
@@ -51,11 +45,11 @@ class ServiceWildcardCheck(PolicyCheck):
|
|
|
51
45
|
# Get message template and replace placeholders
|
|
52
46
|
message_template = config.config.get(
|
|
53
47
|
"message",
|
|
54
|
-
"Service-level wildcard
|
|
48
|
+
"Service-level wildcard `{action}` grants all permissions for `{service}` service",
|
|
55
49
|
)
|
|
56
50
|
suggestion_template = config.config.get(
|
|
57
51
|
"suggestion",
|
|
58
|
-
"Consider specifying explicit actions instead of
|
|
52
|
+
"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
53
|
)
|
|
60
54
|
example_template = config.config.get("example", "")
|
|
61
55
|
|
|
@@ -6,7 +6,9 @@ Based on AWS IAM best practices:
|
|
|
6
6
|
https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-single-vs-multi-valued-context-keys.html
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from typing import ClassVar
|
|
10
|
+
|
|
11
|
+
from iam_validator.core.aws_service import AWSServiceFetcher
|
|
10
12
|
from iam_validator.core.check_registry import CheckConfig, PolicyCheck
|
|
11
13
|
from iam_validator.core.condition_validators import (
|
|
12
14
|
is_multivalued_context_key,
|
|
@@ -18,20 +20,11 @@ from iam_validator.core.models import Statement, ValidationIssue
|
|
|
18
20
|
class SetOperatorValidationCheck(PolicyCheck):
|
|
19
21
|
"""Check for proper usage of ForAllValues and ForAnyValue set operators."""
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
@property
|
|
27
|
-
def description(self) -> str:
|
|
28
|
-
"""Description of what this check does."""
|
|
29
|
-
return "Validates proper usage of ForAllValues and ForAnyValue set operators"
|
|
30
|
-
|
|
31
|
-
@property
|
|
32
|
-
def default_severity(self) -> str:
|
|
33
|
-
"""Default severity level for issues found by this check."""
|
|
34
|
-
return "error"
|
|
23
|
+
check_id: ClassVar[str] = "set_operator_validation"
|
|
24
|
+
description: ClassVar[str] = (
|
|
25
|
+
"Validates proper usage of ForAllValues and ForAnyValue set operators"
|
|
26
|
+
)
|
|
27
|
+
default_severity: ClassVar[str] = "error"
|
|
35
28
|
|
|
36
29
|
async def execute(
|
|
37
30
|
self,
|
|
@@ -73,7 +66,7 @@ class SetOperatorValidationCheck(PolicyCheck):
|
|
|
73
66
|
|
|
74
67
|
# First pass: Identify set operators and Null checks
|
|
75
68
|
for operator, conditions in statement.condition.items():
|
|
76
|
-
base_operator,
|
|
69
|
+
base_operator, _operator_type, set_prefix = normalize_operator(operator)
|
|
77
70
|
|
|
78
71
|
# Track Null checks
|
|
79
72
|
if base_operator == "Null":
|
|
@@ -87,22 +80,22 @@ class SetOperatorValidationCheck(PolicyCheck):
|
|
|
87
80
|
|
|
88
81
|
# Second pass: Validate set operator usage
|
|
89
82
|
for operator, conditions in statement.condition.items():
|
|
90
|
-
base_operator,
|
|
83
|
+
base_operator, _operator_type, set_prefix = normalize_operator(operator)
|
|
91
84
|
|
|
92
85
|
if not set_prefix:
|
|
93
86
|
continue
|
|
94
87
|
|
|
95
88
|
# Check each condition key used with a set operator
|
|
96
|
-
for condition_key,
|
|
89
|
+
for condition_key, _condition_values in conditions.items():
|
|
97
90
|
# Issue 1: Set operator used with single-valued context key (anti-pattern)
|
|
98
91
|
if not is_multivalued_context_key(condition_key):
|
|
99
92
|
issues.append(
|
|
100
93
|
ValidationIssue(
|
|
101
94
|
severity=self.get_severity(config),
|
|
102
95
|
message=(
|
|
103
|
-
f"Set operator
|
|
104
|
-
f"condition key
|
|
105
|
-
f"Set operators are designed for multivalued context keys like
|
|
96
|
+
f"Set operator `{set_prefix}` should not be used with single-valued "
|
|
97
|
+
f"condition key `{condition_key}`. This can lead to overly permissive policies. "
|
|
98
|
+
f"Set operators are designed for multivalued context keys like `aws:TagKeys`. "
|
|
106
99
|
f"See: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-single-vs-multi-valued-context-keys.html"
|
|
107
100
|
),
|
|
108
101
|
statement_sid=statement_sid,
|
|
@@ -120,9 +113,9 @@ class SetOperatorValidationCheck(PolicyCheck):
|
|
|
120
113
|
ValidationIssue(
|
|
121
114
|
severity="warning",
|
|
122
115
|
message=(
|
|
123
|
-
f"Security risk: ForAllValues with Allow effect on
|
|
124
|
-
f"should include a Null condition check. Without it, requests with missing "
|
|
125
|
-
f'
|
|
116
|
+
f"Security risk: `ForAllValues` with `Allow` effect on `{condition_key}` "
|
|
117
|
+
f"should include a `Null` condition check. Without it, requests with missing "
|
|
118
|
+
f'`{condition_key}` will be granted access. Add: `"Null": {{"{condition_key}": "false"}}`. '
|
|
126
119
|
f"See: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-single-vs-multi-valued-context-keys.html"
|
|
127
120
|
),
|
|
128
121
|
statement_sid=statement_sid,
|
|
@@ -140,10 +133,10 @@ class SetOperatorValidationCheck(PolicyCheck):
|
|
|
140
133
|
ValidationIssue(
|
|
141
134
|
severity="warning",
|
|
142
135
|
message=(
|
|
143
|
-
f"Unpredictable behavior: ForAnyValue with Deny effect on
|
|
144
|
-
f"should include a Null condition check. Without it, requests with missing "
|
|
145
|
-
f"
|
|
146
|
-
f'Add:
|
|
136
|
+
f"Unpredictable behavior: `ForAnyValue` with `Deny` effect on `{condition_key}` "
|
|
137
|
+
f"should include a `Null` condition check. Without it, requests with missing "
|
|
138
|
+
f"`{condition_key}` will evaluate to `No match` instead of denying access. "
|
|
139
|
+
f'Add: `"Null": {{"{condition_key}": "false"}}`. '
|
|
147
140
|
f"See: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-single-vs-multi-valued-context-keys.html"
|
|
148
141
|
),
|
|
149
142
|
statement_sid=statement_sid,
|
|
@@ -13,10 +13,11 @@ statement, examining all statements in the policy to find duplicates and format
|
|
|
13
13
|
|
|
14
14
|
import re
|
|
15
15
|
from collections import Counter
|
|
16
|
+
from typing import ClassVar
|
|
16
17
|
|
|
17
|
-
from iam_validator.core.
|
|
18
|
+
from iam_validator.core.aws_service import AWSServiceFetcher
|
|
18
19
|
from iam_validator.core.check_registry import CheckConfig, PolicyCheck
|
|
19
|
-
from iam_validator.core.models import IAMPolicy,
|
|
20
|
+
from iam_validator.core.models import IAMPolicy, ValidationIssue
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def _check_sid_uniqueness_impl(policy: IAMPolicy, severity: str) -> list[ValidationIssue]:
|
|
@@ -35,6 +36,10 @@ def _check_sid_uniqueness_impl(policy: IAMPolicy, severity: str) -> list[Validat
|
|
|
35
36
|
# No spaces allowed
|
|
36
37
|
sid_pattern = re.compile(r"^[a-zA-Z0-9_-]+$")
|
|
37
38
|
|
|
39
|
+
# Handle policies with no statements
|
|
40
|
+
if not policy.statement:
|
|
41
|
+
return []
|
|
42
|
+
|
|
38
43
|
# Collect all SIDs (ignoring None/empty values) and check format
|
|
39
44
|
sids_with_indices: list[tuple[str, int]] = []
|
|
40
45
|
for idx, statement in enumerate(policy.statement):
|
|
@@ -43,15 +48,15 @@ def _check_sid_uniqueness_impl(policy: IAMPolicy, severity: str) -> list[Validat
|
|
|
43
48
|
if not sid_pattern.match(statement.sid):
|
|
44
49
|
# Identify the issue
|
|
45
50
|
if " " in statement.sid:
|
|
46
|
-
issue_msg = f"Statement ID
|
|
51
|
+
issue_msg = f"Statement ID `{statement.sid}` contains spaces, which are not allowed by AWS"
|
|
47
52
|
suggestion = (
|
|
48
|
-
f"Remove spaces from the SID. Example:
|
|
53
|
+
f"Remove spaces from the SID. Example: `{statement.sid.replace(' ', '')}`"
|
|
49
54
|
)
|
|
50
55
|
else:
|
|
51
56
|
invalid_chars = "".join(
|
|
52
57
|
set(c for c in statement.sid if not c.isalnum() and c not in "_-")
|
|
53
58
|
)
|
|
54
|
-
issue_msg = f"Statement ID
|
|
59
|
+
issue_msg = f"Statement ID `{statement.sid}` contains invalid characters: `{invalid_chars}`"
|
|
55
60
|
suggestion = (
|
|
56
61
|
"SIDs must contain only alphanumeric characters, hyphens, and underscores"
|
|
57
62
|
)
|
|
@@ -91,7 +96,7 @@ def _check_sid_uniqueness_impl(policy: IAMPolicy, severity: str) -> list[Validat
|
|
|
91
96
|
statement_sid=duplicate_sid,
|
|
92
97
|
statement_index=idx,
|
|
93
98
|
issue_type="duplicate_sid",
|
|
94
|
-
message=f"Statement ID `{duplicate_sid}` is used **{count} times** in this policy (found in statements {statement_numbers})",
|
|
99
|
+
message=f"Statement ID `{duplicate_sid}` is used **{count} times** in this policy (found in statements `{statement_numbers}`)",
|
|
95
100
|
suggestion="Change this SID to a unique value. Statement IDs help identify and reference specific statements, so duplicates can cause confusion.",
|
|
96
101
|
line_number=statement.line_number,
|
|
97
102
|
)
|
|
@@ -107,42 +112,11 @@ class SidUniquenessCheck(PolicyCheck):
|
|
|
107
112
|
It only runs once when processing the first statement to avoid duplicate work.
|
|
108
113
|
"""
|
|
109
114
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def description(self) -> str:
|
|
116
|
-
return "Validates that Statement IDs (Sids) are unique and follow AWS naming requirements (no spaces)"
|
|
117
|
-
|
|
118
|
-
@property
|
|
119
|
-
def default_severity(self) -> str:
|
|
120
|
-
return "warning"
|
|
121
|
-
|
|
122
|
-
async def execute(
|
|
123
|
-
self,
|
|
124
|
-
statement: Statement,
|
|
125
|
-
statement_idx: int,
|
|
126
|
-
fetcher: AWSServiceFetcher,
|
|
127
|
-
config: CheckConfig,
|
|
128
|
-
) -> list[ValidationIssue]:
|
|
129
|
-
"""Execute the SID uniqueness check at statement level.
|
|
130
|
-
|
|
131
|
-
This is a policy-level check, so statement-level execution returns empty.
|
|
132
|
-
The actual check runs in execute_policy() which has access to all statements.
|
|
133
|
-
|
|
134
|
-
Args:
|
|
135
|
-
statement: The IAM policy statement (unused)
|
|
136
|
-
statement_idx: Index of the statement in the policy (unused)
|
|
137
|
-
fetcher: AWS service fetcher (unused for this check)
|
|
138
|
-
config: Configuration for this check instance (unused)
|
|
139
|
-
|
|
140
|
-
Returns:
|
|
141
|
-
Empty list (actual check runs in execute_policy())
|
|
142
|
-
"""
|
|
143
|
-
del statement, statement_idx, fetcher, config # Unused
|
|
144
|
-
# This is a policy-level check - execution happens in execute_policy()
|
|
145
|
-
return []
|
|
115
|
+
check_id: ClassVar[str] = "sid_uniqueness"
|
|
116
|
+
description: ClassVar[str] = (
|
|
117
|
+
"Validates that Statement IDs (Sids) are unique and follow AWS naming requirements (no spaces)"
|
|
118
|
+
)
|
|
119
|
+
default_severity: ClassVar[str] = "warning"
|
|
146
120
|
|
|
147
121
|
async def execute_policy(
|
|
148
122
|
self,
|