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.
- {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/METADATA +22 -7
- iam_policy_validator-1.8.0.dist-info/RECORD +87 -0
- iam_validator/__version__.py +4 -2
- iam_validator/checks/__init__.py +5 -3
- iam_validator/checks/action_condition_enforcement.py +81 -36
- iam_validator/checks/action_resource_matching.py +75 -37
- iam_validator/checks/action_validation.py +1 -1
- iam_validator/checks/condition_key_validation.py +7 -7
- iam_validator/checks/condition_type_mismatch.py +10 -8
- iam_validator/checks/full_wildcard.py +2 -8
- iam_validator/checks/mfa_condition_check.py +8 -8
- iam_validator/checks/policy_structure.py +577 -0
- iam_validator/checks/policy_type_validation.py +48 -32
- iam_validator/checks/principal_validation.py +86 -150
- iam_validator/checks/resource_validation.py +8 -8
- iam_validator/checks/sensitive_action.py +9 -11
- iam_validator/checks/service_wildcard.py +4 -10
- iam_validator/checks/set_operator_validation.py +11 -11
- iam_validator/checks/sid_uniqueness.py +8 -4
- iam_validator/checks/trust_policy_validation.py +512 -0
- iam_validator/checks/utils/sensitive_action_matcher.py +26 -26
- iam_validator/checks/utils/wildcard_expansion.py +1 -1
- iam_validator/checks/wildcard_action.py +5 -9
- iam_validator/checks/wildcard_resource.py +5 -9
- iam_validator/commands/validate.py +8 -14
- iam_validator/core/__init__.py +1 -2
- iam_validator/core/access_analyzer.py +1 -1
- iam_validator/core/access_analyzer_report.py +2 -2
- iam_validator/core/aws_fetcher.py +159 -64
- iam_validator/core/check_registry.py +83 -79
- iam_validator/core/config/condition_requirements.py +69 -17
- iam_validator/core/config/config_loader.py +1 -2
- iam_validator/core/config/defaults.py +74 -59
- iam_validator/core/config/service_principals.py +40 -3
- iam_validator/core/constants.py +57 -0
- iam_validator/core/formatters/console.py +10 -1
- iam_validator/core/formatters/csv.py +2 -1
- iam_validator/core/formatters/enhanced.py +42 -8
- iam_validator/core/formatters/markdown.py +2 -1
- iam_validator/core/ignore_patterns.py +297 -0
- iam_validator/core/models.py +35 -10
- iam_validator/core/policy_checks.py +34 -474
- iam_validator/core/policy_loader.py +98 -18
- iam_validator/core/report.py +65 -24
- iam_validator/integrations/github_integration.py +4 -5
- iam_validator/utils/__init__.py +4 -0
- iam_validator/utils/terminal.py +22 -0
- iam_policy_validator-1.7.1.dist-info/RECORD +0 -83
- {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/entry_points.txt +0 -0
- {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
|
|
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=
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
104
|
-
f"condition key
|
|
105
|
-
f"Set operators are designed for multivalued context keys like
|
|
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
|
|
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'
|
|
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
|
|
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"
|
|
146
|
-
f'Add:
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
)
|