iam-policy-validator 1.15.2__py3-none-any.whl → 1.15.3__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.15.2.dist-info → iam_policy_validator-1.15.3.dist-info}/METADATA +1 -1
- {iam_policy_validator-1.15.2.dist-info → iam_policy_validator-1.15.3.dist-info}/RECORD +8 -8
- iam_validator/__version__.py +1 -1
- iam_validator/checks/mfa_condition_check.py +50 -1
- iam_validator/checks/not_action_not_resource.py +54 -24
- {iam_policy_validator-1.15.2.dist-info → iam_policy_validator-1.15.3.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.15.2.dist-info → iam_policy_validator-1.15.3.dist-info}/entry_points.txt +0 -0
- {iam_policy_validator-1.15.2.dist-info → iam_policy_validator-1.15.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iam-policy-validator
|
|
3
|
-
Version: 1.15.
|
|
3
|
+
Version: 1.15.3
|
|
4
4
|
Summary: Validate AWS IAM policies for correctness and security using AWS Service Reference API
|
|
5
5
|
Project-URL: Homepage, https://github.com/boogy/iam-policy-validator
|
|
6
6
|
Project-URL: Documentation, https://boogy.github.io/iam-policy-validator
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
iam_validator/__init__.py,sha256=xHdUASOxFHwEXfT_GSr_KrkLlnxZ-pAAr1wW1PwAGko,693
|
|
2
2
|
iam_validator/__main__.py,sha256=to_nz3n_IerJpVVZZ6WSFlFR5s_06J0csfPOTfQZG8g,197
|
|
3
|
-
iam_validator/__version__.py,sha256=
|
|
3
|
+
iam_validator/__version__.py,sha256=2oTvvCnwbVqgXWNdEDQWe8-xj2ecUXJtl1jF_CIIGxk,374
|
|
4
4
|
iam_validator/checks/__init__.py,sha256=wFU5Lz-ZIQBcn2y1u0Kl88B--vEO3btOOaTGPPSjJ74,2106
|
|
5
5
|
iam_validator/checks/action_condition_enforcement.py,sha256=2-XUMbof9tQ7SHZNmAHMkR1DgbOIzY2eFWlp9S9dwLk,60625
|
|
6
6
|
iam_validator/checks/action_resource_matching.py,sha256=qND0hfDgNoxFEdLWwrxOPVDfdj3k50nzedT2qF7nK7o,19428
|
|
@@ -8,8 +8,8 @@ iam_validator/checks/action_validation.py,sha256=gy9_mujYbojBboLk0WwuJYJjggrY2sR
|
|
|
8
8
|
iam_validator/checks/condition_key_validation.py,sha256=5i8LqqV78SjWK6pLrbttWmeMAD4pDC12_FjTjx5dFSU,4024
|
|
9
9
|
iam_validator/checks/condition_type_mismatch.py,sha256=KJp7zQHDd8VeTcfjcD-ur3S4070cXEDTWkFtxfp7CuE,10652
|
|
10
10
|
iam_validator/checks/full_wildcard.py,sha256=0TkkHtV0MZ6nZtJRtGdn3wwOMM96TRyGO7l7mmdHNUo,2325
|
|
11
|
-
iam_validator/checks/mfa_condition_check.py,sha256=
|
|
12
|
-
iam_validator/checks/not_action_not_resource.py,sha256=
|
|
11
|
+
iam_validator/checks/mfa_condition_check.py,sha256=EYOzESzsZVlxW9Pp6hSqPZkH0UT1h21SoZibBX7k_OU,7705
|
|
12
|
+
iam_validator/checks/not_action_not_resource.py,sha256=18wB1NKaCj4X7o8xcTLmtFjsXFJLXTukzp1FhZzvMa8,9779
|
|
13
13
|
iam_validator/checks/policy_size.py,sha256=eJd36Nj4gqWLIkQ5imhHR1hGtQ6T-iJsC22Wd1VSUf0,4681
|
|
14
14
|
iam_validator/checks/policy_structure.py,sha256=LExdm93JeqsyhikWrh9lfJ9sBiZ4818Ts0-N3MwUrtk,22089
|
|
15
15
|
iam_validator/checks/policy_type_validation.py,sha256=-Q2Yn_sa7Cba_Fb9ESxSL5qNbRpncuC7NQy7R_fdJtY,16521
|
|
@@ -112,8 +112,8 @@ iam_validator/utils/__init__.py,sha256=NveA2F3G1E6-ANZzFr7J6Q6u5mogvMp862iFokmYu
|
|
|
112
112
|
iam_validator/utils/cache.py,sha256=wOQKOBeoG6QqC5f0oXcHz63Cjtu_-SsSS-0pTSwyAiM,3254
|
|
113
113
|
iam_validator/utils/regex.py,sha256=xHoMECttb7qaMhts-c9b0GIxdhHNZTt-UBr7wNhWfzg,6219
|
|
114
114
|
iam_validator/utils/terminal.py,sha256=FsRaRMH_JAyDgXWBCOgOEhbS89cs17HCmKYoughq5io,724
|
|
115
|
-
iam_policy_validator-1.15.
|
|
116
|
-
iam_policy_validator-1.15.
|
|
117
|
-
iam_policy_validator-1.15.
|
|
118
|
-
iam_policy_validator-1.15.
|
|
119
|
-
iam_policy_validator-1.15.
|
|
115
|
+
iam_policy_validator-1.15.3.dist-info/METADATA,sha256=r1dXILWhINVsEMyiTTC3IJpAEAoS0usdkBkt7lVe1og,34939
|
|
116
|
+
iam_policy_validator-1.15.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
117
|
+
iam_policy_validator-1.15.3.dist-info/entry_points.txt,sha256=VXAcx1evo9fuxX0Gtj3J2HnzWcBHSXugiZwBtQ1BXE0,162
|
|
118
|
+
iam_policy_validator-1.15.3.dist-info/licenses/LICENSE,sha256=AMnbFTBDcK4_MITe2wiQBkj0vg-jjBBhsc43ydC7tt4,1098
|
|
119
|
+
iam_policy_validator-1.15.3.dist-info/RECORD,,
|
iam_validator/__version__.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
This file is the single source of truth for the package version.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
__version__ = "1.15.
|
|
6
|
+
__version__ = "1.15.3"
|
|
7
7
|
# Parse version, handling pre-release suffixes like -rc, -alpha, -beta
|
|
8
8
|
_version_base = __version__.split("-", maxsplit=1)[0] # Remove pre-release suffix if present
|
|
9
9
|
__version_info__ = tuple(int(part) for part in _version_base.split("."))
|
|
@@ -75,7 +75,36 @@ class MFAConditionCheck(PolicyCheck):
|
|
|
75
75
|
)
|
|
76
76
|
)
|
|
77
77
|
|
|
78
|
-
# Check for anti-pattern #2:
|
|
78
|
+
# Check for anti-pattern #2: BoolIfExists with aws:MultiFactorAuthPresent = false
|
|
79
|
+
# This is MORE dangerous than Bool because it also matches when the key is missing
|
|
80
|
+
bool_if_exists_conditions = statement.condition.get("BoolIfExists", {})
|
|
81
|
+
for key, value in bool_if_exists_conditions.items():
|
|
82
|
+
if key.lower() == "aws:multifactorauthpresent":
|
|
83
|
+
# Normalize value to list
|
|
84
|
+
values = value if isinstance(value, list) else [value]
|
|
85
|
+
# Convert to lowercase strings for comparison
|
|
86
|
+
values_lower = [str(v).lower() for v in values]
|
|
87
|
+
|
|
88
|
+
if "false" in values_lower or False in values:
|
|
89
|
+
issues.append(
|
|
90
|
+
ValidationIssue(
|
|
91
|
+
severity="high", # Higher than default - this is worse than Bool
|
|
92
|
+
message=(
|
|
93
|
+
"**DANGEROUS MFA condition pattern detected.** "
|
|
94
|
+
'Using `{"BoolIfExists": {"aws:MultiFactorAuthPresent": "false"}}` '
|
|
95
|
+
"is MORE dangerous than using `Bool` because it also matches when "
|
|
96
|
+
"the key is missing entirely (no MFA context in the request). "
|
|
97
|
+
"This effectively allows access without any MFA verification."
|
|
98
|
+
),
|
|
99
|
+
statement_sid=statement_sid,
|
|
100
|
+
statement_index=statement_idx,
|
|
101
|
+
issue_type="mfa_antipattern_boolif_exists_false",
|
|
102
|
+
line_number=line_number,
|
|
103
|
+
field_name="condition",
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Check for anti-pattern #3: Null with aws:MultiFactorAuthPresent = false
|
|
79
108
|
null_conditions = statement.condition.get("Null", {})
|
|
80
109
|
for key, value in null_conditions.items():
|
|
81
110
|
if key.lower() == "aws:multifactorauthpresent":
|
|
@@ -102,4 +131,24 @@ class MFAConditionCheck(PolicyCheck):
|
|
|
102
131
|
)
|
|
103
132
|
)
|
|
104
133
|
|
|
134
|
+
# Check for anti-pattern #4: Null with aws:MultiFactorAuthPresent = true
|
|
135
|
+
# This means "key does NOT exist" = no MFA was used
|
|
136
|
+
if "true" in values_lower or True in values:
|
|
137
|
+
issues.append(
|
|
138
|
+
ValidationIssue(
|
|
139
|
+
severity=self.get_severity(config),
|
|
140
|
+
message=(
|
|
141
|
+
"**Dangerous MFA condition pattern detected.** "
|
|
142
|
+
'Using `{"Null": {"aws:MultiFactorAuthPresent": "true"}}` checks if the key '
|
|
143
|
+
"does NOT exist, which means no MFA was provided in the request context. "
|
|
144
|
+
"This condition allows access when MFA is absent."
|
|
145
|
+
),
|
|
146
|
+
statement_sid=statement_sid,
|
|
147
|
+
statement_index=statement_idx,
|
|
148
|
+
issue_type="mfa_antipattern_null_true",
|
|
149
|
+
line_number=line_number,
|
|
150
|
+
field_name="condition",
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
|
|
105
154
|
return issues
|
|
@@ -12,6 +12,13 @@ from iam_validator.core.check_registry import CheckConfig, PolicyCheck
|
|
|
12
12
|
from iam_validator.core.models import Statement, ValidationIssue
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
def _format_list_with_backticks(items: list[str], max_items: int = 3) -> str:
|
|
16
|
+
"""Format a list of items with backticks for markdown rendering."""
|
|
17
|
+
formatted = [f"`{item}`" for item in items[:max_items]]
|
|
18
|
+
suffix = "..." if len(items) > max_items else ""
|
|
19
|
+
return ", ".join(formatted) + suffix
|
|
20
|
+
|
|
21
|
+
|
|
15
22
|
class NotActionNotResourceCheck(PolicyCheck):
|
|
16
23
|
"""Checks for dangerous NotAction/NotResource patterns.
|
|
17
24
|
|
|
@@ -59,18 +66,18 @@ class NotActionNotResourceCheck(PolicyCheck):
|
|
|
59
66
|
statement_index=statement_idx,
|
|
60
67
|
issue_type="not_action_allow",
|
|
61
68
|
message=(
|
|
62
|
-
"Statement uses NotAction with Allow effect. "
|
|
69
|
+
"Statement uses `NotAction` with `Allow` effect. "
|
|
63
70
|
"This grants ALL actions except the listed ones. "
|
|
64
71
|
"While conditions are present, this pattern is still risky."
|
|
65
72
|
),
|
|
66
73
|
suggestion=(
|
|
67
|
-
"Consider using explicit Action lists instead of NotAction
|
|
68
|
-
"If NotAction is required, ensure conditions are comprehensive."
|
|
74
|
+
"Consider using explicit `Action` lists instead of `NotAction`. "
|
|
75
|
+
"If `NotAction` is required, ensure conditions are comprehensive."
|
|
69
76
|
),
|
|
70
77
|
example='{\n "Effect": "Allow",\n "Action": ["s3:GetObject", "s3:ListBucket"],\n "Resource": "*"\n}',
|
|
71
78
|
line_number=statement.line_number,
|
|
72
79
|
field_name="action",
|
|
73
|
-
action=
|
|
80
|
+
action=_format_list_with_backticks(not_actions, 3),
|
|
74
81
|
)
|
|
75
82
|
)
|
|
76
83
|
else:
|
|
@@ -82,20 +89,19 @@ class NotActionNotResourceCheck(PolicyCheck):
|
|
|
82
89
|
statement_index=statement_idx,
|
|
83
90
|
issue_type="not_action_allow_no_condition",
|
|
84
91
|
message=(
|
|
85
|
-
"Statement uses NotAction with Allow effect and NO conditions. "
|
|
86
|
-
f"This grants ALL AWS actions except: {
|
|
87
|
-
f"{'...' if len(not_actions) > 5 else ''}. "
|
|
92
|
+
"Statement uses `NotAction` with `Allow` effect and NO conditions. "
|
|
93
|
+
f"This grants ALL AWS actions except: {_format_list_with_backticks(not_actions, 5)}. "
|
|
88
94
|
"This is equivalent to granting near-administrator access."
|
|
89
95
|
),
|
|
90
96
|
suggestion=(
|
|
91
|
-
"Replace NotAction with explicit Action list. "
|
|
92
|
-
"If NotAction is required, add strict conditions like "
|
|
93
|
-
"aws:SourceIp
|
|
97
|
+
"Replace `NotAction` with explicit `Action` list. "
|
|
98
|
+
"If `NotAction` is required, add strict conditions like "
|
|
99
|
+
"`aws:SourceIp`, `aws:PrincipalArn`, or `aws:MultiFactorAuthPresent`."
|
|
94
100
|
),
|
|
95
101
|
example='{\n "Effect": "Allow",\n "Action": ["specific:Action"],\n "Resource": "*",\n "Condition": {\n "Bool": {"aws:MultiFactorAuthPresent": "true"}\n }\n}',
|
|
96
102
|
line_number=statement.line_number,
|
|
97
103
|
field_name="action",
|
|
98
|
-
action=
|
|
104
|
+
action=_format_list_with_backticks(not_actions, 3),
|
|
99
105
|
)
|
|
100
106
|
)
|
|
101
107
|
|
|
@@ -113,24 +119,49 @@ class NotActionNotResourceCheck(PolicyCheck):
|
|
|
113
119
|
statement_index=statement_idx,
|
|
114
120
|
issue_type="not_resource_broad",
|
|
115
121
|
message=(
|
|
116
|
-
"Statement uses NotResource with Allow effect and broad Resource
|
|
117
|
-
f"This grants access to ALL resources except: {
|
|
118
|
-
f"{'...' if len(not_resources) > 3 else ''}."
|
|
122
|
+
"Statement uses `NotResource` with `Allow` effect and broad `Resource`. "
|
|
123
|
+
f"This grants access to ALL resources except: {_format_list_with_backticks(not_resources, 3)}."
|
|
119
124
|
),
|
|
120
125
|
suggestion=(
|
|
121
|
-
"Replace NotResource with explicit Resource ARNs. "
|
|
122
|
-
"Using NotResource grants access to all current and future resources "
|
|
126
|
+
"Replace `NotResource` with explicit `Resource` ARNs. "
|
|
127
|
+
"Using `NotResource` grants access to all current and future resources "
|
|
123
128
|
"except those explicitly excluded."
|
|
124
129
|
),
|
|
125
130
|
example='{\n "Effect": "Allow",\n "Action": ["s3:GetObject"],\n "Resource": "arn:aws:s3:::my-bucket/*"\n}',
|
|
126
131
|
line_number=statement.line_number,
|
|
127
132
|
field_name="resource",
|
|
128
|
-
resource=
|
|
129
|
-
+ ("..." if len(not_resources) > 3 else ""),
|
|
133
|
+
resource=_format_list_with_backticks(not_resources, 3),
|
|
130
134
|
)
|
|
131
135
|
)
|
|
132
136
|
|
|
133
|
-
# Check 3: NotAction
|
|
137
|
+
# Check 3: Combined NotAction AND NotResource with Allow
|
|
138
|
+
# This is the most dangerous pattern - grants nearly all actions on nearly all resources
|
|
139
|
+
if not_actions and not_resources and effect == "Allow":
|
|
140
|
+
issues.append(
|
|
141
|
+
ValidationIssue(
|
|
142
|
+
severity="critical",
|
|
143
|
+
statement_sid=statement.sid,
|
|
144
|
+
statement_index=statement_idx,
|
|
145
|
+
issue_type="combined_not_action_not_resource",
|
|
146
|
+
message=(
|
|
147
|
+
"**CRITICAL:** Policy uses both `NotAction` AND `NotResource` with `Allow` effect. "
|
|
148
|
+
f"This grants ALL actions except [{_format_list_with_backticks(not_actions, 3)}] "
|
|
149
|
+
f"on ALL resources except [{_format_list_with_backticks(not_resources, 3)}]. "
|
|
150
|
+
"This is equivalent to near-administrator access."
|
|
151
|
+
),
|
|
152
|
+
suggestion=(
|
|
153
|
+
"Rewrite using explicit `Action` and `Resource` lists instead of negations. "
|
|
154
|
+
"The combination of `NotAction` + `NotResource` is almost always a mistake."
|
|
155
|
+
),
|
|
156
|
+
example='{\n "Effect": "Allow",\n "Action": ["specific:Action"],\n "Resource": "arn:aws:service:::specific-resource"\n}',
|
|
157
|
+
line_number=statement.line_number,
|
|
158
|
+
field_name="action",
|
|
159
|
+
action=_format_list_with_backticks(not_actions, 3),
|
|
160
|
+
resource=_format_list_with_backticks(not_resources, 3),
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Check 4: NotAction with Deny - less dangerous but worth noting
|
|
134
165
|
# NotAction with Deny means "deny everything except these actions"
|
|
135
166
|
# which is actually a valid deny pattern but should be reviewed
|
|
136
167
|
if not_actions and effect == "Deny":
|
|
@@ -145,18 +176,17 @@ class NotActionNotResourceCheck(PolicyCheck):
|
|
|
145
176
|
statement_index=statement_idx,
|
|
146
177
|
issue_type="not_action_deny_review",
|
|
147
178
|
message=(
|
|
148
|
-
"Statement uses NotAction with Deny effect on all resources. "
|
|
149
|
-
f"This denies everything except: {
|
|
150
|
-
f"{'...' if len(not_actions) > 5 else ''}. "
|
|
179
|
+
"Statement uses `NotAction` with `Deny` effect on all resources. "
|
|
180
|
+
f"This denies everything except: {_format_list_with_backticks(not_actions, 5)}. "
|
|
151
181
|
"Review to ensure this is the intended behavior."
|
|
152
182
|
),
|
|
153
183
|
suggestion=(
|
|
154
184
|
"Verify that allowing only these specific actions is intended. "
|
|
155
|
-
"Consider if an explicit Allow list would be clearer."
|
|
185
|
+
"Consider if an explicit `Allow` list would be clearer."
|
|
156
186
|
),
|
|
157
187
|
line_number=statement.line_number,
|
|
158
188
|
field_name="action",
|
|
159
|
-
action=
|
|
189
|
+
action=_format_list_with_backticks(not_actions, 3),
|
|
160
190
|
)
|
|
161
191
|
)
|
|
162
192
|
|
|
File without changes
|
{iam_policy_validator-1.15.2.dist-info → iam_policy_validator-1.15.3.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{iam_policy_validator-1.15.2.dist-info → iam_policy_validator-1.15.3.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|