iam-policy-validator 1.7.1__py3-none-any.whl → 1.7.2__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.7.2.dist-info}/METADATA +1 -2
- {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.7.2.dist-info}/RECORD +34 -33
- iam_validator/__version__.py +4 -2
- iam_validator/checks/action_condition_enforcement.py +20 -13
- iam_validator/checks/action_resource_matching.py +70 -36
- iam_validator/checks/condition_key_validation.py +7 -7
- iam_validator/checks/condition_type_mismatch.py +8 -6
- iam_validator/checks/full_wildcard.py +2 -8
- iam_validator/checks/mfa_condition_check.py +8 -8
- iam_validator/checks/principal_validation.py +24 -20
- iam_validator/checks/sensitive_action.py +3 -9
- iam_validator/checks/service_wildcard.py +2 -8
- iam_validator/checks/sid_uniqueness.py +1 -1
- iam_validator/checks/wildcard_action.py +2 -8
- iam_validator/checks/wildcard_resource.py +2 -8
- iam_validator/commands/validate.py +2 -2
- iam_validator/core/aws_fetcher.py +115 -22
- iam_validator/core/config/config_loader.py +1 -2
- iam_validator/core/config/defaults.py +16 -7
- 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/models.py +22 -7
- iam_validator/core/policy_checks.py +5 -4
- iam_validator/core/policy_loader.py +71 -14
- 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 → iam_policy_validator-1.7.2.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.7.2.dist-info}/entry_points.txt +0 -0
- {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.7.2.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.7.
|
|
3
|
+
Version: 1.7.2
|
|
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://github.com/boogy/iam-policy-validator/tree/main/docs
|
|
@@ -426,4 +426,3 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
|
|
426
426
|
## 🆘 Support
|
|
427
427
|
|
|
428
428
|
- **Issues**: [GitHub Issues](https://github.com/boogy/iam-policy-validator/issues)
|
|
429
|
-
- **Discussions**: [GitHub Discussions](https://github.com/boogy/iam-policy-validator/discussions)
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
iam_validator/__init__.py,sha256=APnMR3Fu4fHhxfsHBvUM2dJIwazgvLKQbfOsSgFPidg,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=0niAY6KgsXeeyFV5nTmvfvem16X3OTrO_DItEqsW74A,361
|
|
4
4
|
iam_validator/checks/__init__.py,sha256=eDiDlVon0CwWGSBnZgM-arn1i5R5ZSG89pgR-ifETxE,1782
|
|
5
|
-
iam_validator/checks/action_condition_enforcement.py,sha256=
|
|
6
|
-
iam_validator/checks/action_resource_matching.py,sha256=
|
|
5
|
+
iam_validator/checks/action_condition_enforcement.py,sha256=VhFEGbkcgkRwNRRuslvat5uib2tlH2Nr6sltbAQTs6I,36834
|
|
6
|
+
iam_validator/checks/action_resource_matching.py,sha256=sk67jcDF1WzW4tPgcRSdTj4UBe2stALdwHx5ViVA9dU,19207
|
|
7
7
|
iam_validator/checks/action_validation.py,sha256=IpxtTsk58f2zEZ-xzAoyHw4QK8BCRV43OffP-8ydf9E,2578
|
|
8
|
-
iam_validator/checks/condition_key_validation.py,sha256=
|
|
9
|
-
iam_validator/checks/condition_type_mismatch.py,sha256=
|
|
10
|
-
iam_validator/checks/full_wildcard.py,sha256=
|
|
11
|
-
iam_validator/checks/mfa_condition_check.py,sha256=
|
|
8
|
+
iam_validator/checks/condition_key_validation.py,sha256=10XxTwIcr887CbgmN90jfRZabj5RHo08dGa8csM50Fo,3980
|
|
9
|
+
iam_validator/checks/condition_type_mismatch.py,sha256=JyiAOyUZShzXZI8dgycL4oqwRkpJYUPwoGX4zigsi5I,10613
|
|
10
|
+
iam_validator/checks/full_wildcard.py,sha256=ywD762BOV8WxFuTTARkaGMJn27f3ZZVuZUjKo8URnTc,2281
|
|
11
|
+
iam_validator/checks/mfa_condition_check.py,sha256=YCBX3tFTQRmVTAed_W-Tu1b6WqD2LBYyom53P7lBjh4,4935
|
|
12
12
|
iam_validator/checks/policy_size.py,sha256=ibgmrErpkz6OfUAN6bFuHe1KHzpzzra9gHwNtVAkPWc,5729
|
|
13
13
|
iam_validator/checks/policy_type_validation.py,sha256=9qmrA8CXwsVpCU4rT0RrqDXgVOzNamMEpdg3cXWAtBI,15213
|
|
14
|
-
iam_validator/checks/principal_validation.py,sha256=
|
|
14
|
+
iam_validator/checks/principal_validation.py,sha256=gTv_TqJDspGEX3iJkHXrw3DyKMJeyE33uQakZ0PjNoo,29969
|
|
15
15
|
iam_validator/checks/resource_validation.py,sha256=fGi9QuX-lIHDtLm8xB3VReFFhbZpQ2Yub-FKRafQCkw,5984
|
|
16
|
-
iam_validator/checks/sensitive_action.py,sha256=
|
|
17
|
-
iam_validator/checks/service_wildcard.py,sha256=
|
|
16
|
+
iam_validator/checks/sensitive_action.py,sha256=0vuhF1UkAH_vxhfHsC8xk68aJXHvI7c9KTLcJFNlnHM,9652
|
|
17
|
+
iam_validator/checks/service_wildcard.py,sha256=1ynXLG6_82SIH8aHP88qQojJf38ZH0agnSmHp0VkZ98,4010
|
|
18
18
|
iam_validator/checks/set_operator_validation.py,sha256=1XjOdf-xk-m6m1bODuHsELZccriGqOJTDI-HCcuId80,7464
|
|
19
|
-
iam_validator/checks/sid_uniqueness.py,sha256=
|
|
20
|
-
iam_validator/checks/wildcard_action.py,sha256=
|
|
21
|
-
iam_validator/checks/wildcard_resource.py,sha256=
|
|
19
|
+
iam_validator/checks/sid_uniqueness.py,sha256=yWNHyy002aIHxJKtHeYpYds7bKgreL0BvQmRkI2UwvQ,6891
|
|
20
|
+
iam_validator/checks/wildcard_action.py,sha256=f1QZ68eHzQwCTeYY_9UiYaMxUaq7XYia6DaBjIspZ2A,1972
|
|
21
|
+
iam_validator/checks/wildcard_resource.py,sha256=GNpbk7WDExHG6Yqu4_gxeRCK6NUEL8TFjgbvaHgg7V0,5414
|
|
22
22
|
iam_validator/checks/utils/__init__.py,sha256=j0X4ibUB6RGx2a-kNoJnlVZwHfoEvzZsIeTmJIAoFzA,45
|
|
23
23
|
iam_validator/checks/utils/policy_level_checks.py,sha256=2V60C0zhKfsFPjQ-NMlD3EemtwA9S6-4no8nETgXdQE,5274
|
|
24
24
|
iam_validator/checks/utils/sensitive_action_matcher.py,sha256=tcWK4nImpSVNia0FUsN2uLK9LM5EnzjRFtaPQLHZaLw,10667
|
|
@@ -29,42 +29,42 @@ iam_validator/commands/base.py,sha256=5baCCMwxz7pdQ6XMpWfXFNz7i1l5dB8Qv9dKKR04Gz
|
|
|
29
29
|
iam_validator/commands/cache.py,sha256=p4ucRVuh42sbK3Lk0b610L3ofAR5TnUreF00fpO6VFg,14219
|
|
30
30
|
iam_validator/commands/download_services.py,sha256=KKz3ybMLT8DQUf9aFZ0tilJ-o1b6PE8Pf1pC4K6cT8I,9175
|
|
31
31
|
iam_validator/commands/post_to_pr.py,sha256=CvUXs2xvO-UhluxdfNM6F0TCWD8hDBEOiYw60fm1Dms,2363
|
|
32
|
-
iam_validator/commands/validate.py,sha256=
|
|
32
|
+
iam_validator/commands/validate.py,sha256=2v91ogbEzKfjk2u6Y4NO0yvsCOwxi9jXoqD7acBbVTE,23624
|
|
33
33
|
iam_validator/core/__init__.py,sha256=1FvJPMrbzJfS9YbRUJCshJLd5gzWwR9Fd_slS0Aq9c8,416
|
|
34
34
|
iam_validator/core/access_analyzer.py,sha256=8GgkR-vCkCtSxtXGywvQNBPYq-rvDLexUuLSyflq0V4,24520
|
|
35
35
|
iam_validator/core/access_analyzer_report.py,sha256=O17gagknvkNMTTlq7BrLM68FjlCEm4LjIKD9oqxEbPg,24860
|
|
36
|
-
iam_validator/core/aws_fetcher.py,sha256=
|
|
36
|
+
iam_validator/core/aws_fetcher.py,sha256=obTzxHD9pMsWo-SojSOeWyw2s2_St-LNgbmh5BGEM9c,41215
|
|
37
37
|
iam_validator/core/check_registry.py,sha256=cMjtJROkZOLzXxl-mTdLYHdxyajNnOsaHGs-EeaSZ7k,21741
|
|
38
38
|
iam_validator/core/cli.py,sha256=PkXiZjlgrQ21QustBbspefYsdbxst4gxoClyG2_HQR8,3843
|
|
39
39
|
iam_validator/core/condition_validators.py,sha256=7zBjlcf2xGFKGbcFrXSLvWT5tFhWxoqwzhsJqS2E8uY,21524
|
|
40
|
-
iam_validator/core/constants.py,sha256=
|
|
41
|
-
iam_validator/core/models.py,sha256=
|
|
42
|
-
iam_validator/core/policy_checks.py,sha256=
|
|
43
|
-
iam_validator/core/policy_loader.py,sha256=
|
|
40
|
+
iam_validator/core/constants.py,sha256=H3eH0yddn5Dk-xZxJWtuvluRIpuXKYGiiteBSHPpJoI,5560
|
|
41
|
+
iam_validator/core/models.py,sha256=55BCSqqJiAN96SFwK3tiTy6fhu6YBL6avKo8VpCpy2A,12766
|
|
42
|
+
iam_validator/core/policy_checks.py,sha256=3UMLl8SQ4oJLTU1kwscvh7c7gpT5QtjITk_bJCJ_rzs,26616
|
|
43
|
+
iam_validator/core/policy_loader.py,sha256=HVEnaXhQwrb9WbXpu0tn8SJBvHNW9UgDO6w4zLjLsu0,16776
|
|
44
44
|
iam_validator/core/pr_commenter.py,sha256=MU-t7SfdHUpSc6BDbh8_dNAbxDiG-bZBCry-jUXivAc,15066
|
|
45
|
-
iam_validator/core/report.py,sha256=
|
|
45
|
+
iam_validator/core/report.py,sha256=kzSeWnT1LqWZVA5pqKKz-maVowXVj0djdoShfRhhpz4,35899
|
|
46
46
|
iam_validator/core/config/__init__.py,sha256=CWSyIA7kEyzrskEenjYbs9Iih10BXRpiY9H2dHg61rU,2671
|
|
47
47
|
iam_validator/core/config/aws_api.py,sha256=HLIzOItQ0A37wxHcgWck6ZFO0wmNY8JNTiWMMK6JKYU,1248
|
|
48
48
|
iam_validator/core/config/aws_global_conditions.py,sha256=gdmMxXGBy95B3uYUG-J7rnM6Ixgc6L7Y9Pcd2XAMb60,7170
|
|
49
49
|
iam_validator/core/config/category_suggestions.py,sha256=QlrYi4BTkxDSTlL7NZGE9BWN-atWetZ6XjkI9F_7YzI,4370
|
|
50
50
|
iam_validator/core/config/condition_requirements.py,sha256=1PuADTB9pLqh-kNUGC7kSU6LMLtXMSc003tvI7qKeAY,5170
|
|
51
|
-
iam_validator/core/config/config_loader.py,sha256=
|
|
52
|
-
iam_validator/core/config/defaults.py,sha256=
|
|
51
|
+
iam_validator/core/config/config_loader.py,sha256=qKD8aR8YAswaFf68pnYJLFNwKznvcc6lNxSQWU3i6SY,17713
|
|
52
|
+
iam_validator/core/config/defaults.py,sha256=mCOr_YgiRQp6fThtxrcjMtm-LPdZQbd6AS16gLzV17c,27589
|
|
53
53
|
iam_validator/core/config/principal_requirements.py,sha256=VCX7fBDgeDTJQyoz7_x7GI7Kf9O1Eu-sbihoHOrKv6o,15105
|
|
54
54
|
iam_validator/core/config/sensitive_actions.py,sha256=uATDIp_TD3OQQlsYTZp79qd1mSK2Bf9hJ0JwcqLBr84,25344
|
|
55
55
|
iam_validator/core/config/service_principals.py,sha256=gQSROsxUWBD6P2F9qP320UZV4lHGlsyvHSkMyy0njrU,2685
|
|
56
56
|
iam_validator/core/config/wildcards.py,sha256=H_v6hb-rZ0UUz4cul9lxkVI39e6knaK4Y-MbWz2Ebpw,3228
|
|
57
57
|
iam_validator/core/formatters/__init__.py,sha256=fnCKAEBXItnOf2m4rhVs7zwMaTxbG6ESh3CF8V5j5ec,868
|
|
58
58
|
iam_validator/core/formatters/base.py,sha256=SShDeDiy5mYQnS6BpA8xYg91N-KX1EObkOtlrVHqx1Q,4451
|
|
59
|
-
iam_validator/core/formatters/console.py,sha256=
|
|
60
|
-
iam_validator/core/formatters/csv.py,sha256=
|
|
61
|
-
iam_validator/core/formatters/enhanced.py,sha256=
|
|
59
|
+
iam_validator/core/formatters/console.py,sha256=FdTp7AzeILCWrUynSvSew8QJKGOMJaAA9_YiQJd-uco,2196
|
|
60
|
+
iam_validator/core/formatters/csv.py,sha256=pPqgvGh4KtD5Qm36xnMaDAavXYR6MlQhs4zbcrxT550,5941
|
|
61
|
+
iam_validator/core/formatters/enhanced.py,sha256=TVtkcTIow8NGoLhG45-5ms-_PTxyxMcAHxf_uPMyKAc,18155
|
|
62
62
|
iam_validator/core/formatters/html.py,sha256=j4sQi-wXiD9kCHldW5JCzbJe0frhiP5uQI9KlH3Sj_g,22994
|
|
63
63
|
iam_validator/core/formatters/json.py,sha256=A7gZ8P32GEdbDvrSn6v56yQ4fOP_kyMaoFVXG2bgnew,939
|
|
64
|
-
iam_validator/core/formatters/markdown.py,sha256=
|
|
64
|
+
iam_validator/core/formatters/markdown.py,sha256=dk4STeY-tOEZsVrlmolIEqZvWYP9JhRtygxxNA49DEE,2293
|
|
65
65
|
iam_validator/core/formatters/sarif.py,sha256=O3pn7whqFq5xxk-tuoqSb2k4Fk5ai_A2SKX_ph8GLV4,10469
|
|
66
66
|
iam_validator/integrations/__init__.py,sha256=7Hlor_X9j0NZaEjFuSvoXAAuSKQ-zgY19Rk-Dz3JpKo,616
|
|
67
|
-
iam_validator/integrations/github_integration.py,sha256=
|
|
67
|
+
iam_validator/integrations/github_integration.py,sha256=EnrolMq3uZbKWPxUMhYnqcKAfic6Fb8qJzieDruKqsc,26485
|
|
68
68
|
iam_validator/integrations/ms_teams.py,sha256=t2PlWuTDb6GGH-eDU1jnOKd8D1w4FCB68bahGA7MJcE,14475
|
|
69
69
|
iam_validator/sdk/__init__.py,sha256=fRDSXAclGmCU3KDft4StL8JUcpAsdzwIRf8mVj461q0,5306
|
|
70
70
|
iam_validator/sdk/arn_matching.py,sha256=HSDpLltOYISq-SoPebAlM89mKOaUaghq_04urchEFDA,12778
|
|
@@ -73,11 +73,12 @@ iam_validator/sdk/exceptions.py,sha256=tm91TxIwU157U_UHN7w5qICf_OhU11agj6pV5W_YP
|
|
|
73
73
|
iam_validator/sdk/helpers.py,sha256=OVBg4xrW95LT74wXCg1LQkba9kw5RfFqeCLuTqhgL-A,5697
|
|
74
74
|
iam_validator/sdk/policy_utils.py,sha256=CZS1OGSdiWsd2lsCwg0BDcUNWa61tUwgvn-P5rKqeN8,12987
|
|
75
75
|
iam_validator/sdk/shortcuts.py,sha256=EVNSYV7rv4TFH03ulsZ3mS1UVmTSp2jKpc2AXs4j1q4,8531
|
|
76
|
-
iam_validator/utils/__init__.py,sha256=
|
|
76
|
+
iam_validator/utils/__init__.py,sha256=NveA2F3G1E6-ANZzFr7J6Q6u5mogvMp862iFokmYuCs,1021
|
|
77
77
|
iam_validator/utils/cache.py,sha256=wOQKOBeoG6QqC5f0oXcHz63Cjtu_-SsSS-0pTSwyAiM,3254
|
|
78
78
|
iam_validator/utils/regex.py,sha256=xHoMECttb7qaMhts-c9b0GIxdhHNZTt-UBr7wNhWfzg,6219
|
|
79
|
-
|
|
80
|
-
iam_policy_validator-1.7.
|
|
81
|
-
iam_policy_validator-1.7.
|
|
82
|
-
iam_policy_validator-1.7.
|
|
83
|
-
iam_policy_validator-1.7.
|
|
79
|
+
iam_validator/utils/terminal.py,sha256=FsRaRMH_JAyDgXWBCOgOEhbS89cs17HCmKYoughq5io,724
|
|
80
|
+
iam_policy_validator-1.7.2.dist-info/METADATA,sha256=fwySi0xxZPeiRTXyYfmp8YZPNyphy8HylBzhSXkNNG0,15244
|
|
81
|
+
iam_policy_validator-1.7.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
82
|
+
iam_policy_validator-1.7.2.dist-info/entry_points.txt,sha256=8HtWd8O7mvPiPdZR5YbzY8or_qcqLM4-pKaFdhtFT8M,62
|
|
83
|
+
iam_policy_validator-1.7.2.dist-info/licenses/LICENSE,sha256=AMnbFTBDcK4_MITe2wiQBkj0vg-jjBBhsc43ydC7tt4,1098
|
|
84
|
+
iam_policy_validator-1.7.2.dist-info/RECORD,,
|
iam_validator/__version__.py
CHANGED
|
@@ -3,5 +3,7 @@
|
|
|
3
3
|
This file is the single source of truth for the package version.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
__version__ = "1.7.
|
|
7
|
-
|
|
6
|
+
__version__ = "1.7.2"
|
|
7
|
+
# Parse version, handling pre-release suffixes like -rc, -alpha, -beta
|
|
8
|
+
_version_base = __version__.split("-")[0] # Remove pre-release suffix if present
|
|
9
|
+
__version_info__ = tuple(int(part) for part in _version_base.split("."))
|
|
@@ -349,7 +349,10 @@ class ActionConditionEnforcementCheck(PolicyCheck):
|
|
|
349
349
|
return issues
|
|
350
350
|
|
|
351
351
|
async def _check_action_match(
|
|
352
|
-
self,
|
|
352
|
+
self,
|
|
353
|
+
statement_actions: list[str],
|
|
354
|
+
requirement: dict[str, Any],
|
|
355
|
+
fetcher: AWSServiceFetcher,
|
|
353
356
|
) -> tuple[bool, list[str]]:
|
|
354
357
|
"""
|
|
355
358
|
Check if statement actions match the requirement.
|
|
@@ -766,6 +769,10 @@ class ActionConditionEnforcementCheck(PolicyCheck):
|
|
|
766
769
|
or self.get_severity(config) # Global check severity
|
|
767
770
|
)
|
|
768
771
|
|
|
772
|
+
suggestion_text, example_code = self._build_suggestion(
|
|
773
|
+
condition_key, description, example, expected_value, operator
|
|
774
|
+
)
|
|
775
|
+
|
|
769
776
|
return ValidationIssue(
|
|
770
777
|
severity=severity,
|
|
771
778
|
statement_sid=statement.sid,
|
|
@@ -774,9 +781,8 @@ class ActionConditionEnforcementCheck(PolicyCheck):
|
|
|
774
781
|
message=f"{message_prefix} Action(s) {matching_actions} require condition '{condition_key}'",
|
|
775
782
|
action=", ".join(matching_actions),
|
|
776
783
|
condition_key=condition_key,
|
|
777
|
-
suggestion=
|
|
778
|
-
|
|
779
|
-
),
|
|
784
|
+
suggestion=suggestion_text,
|
|
785
|
+
example=example_code,
|
|
780
786
|
line_number=statement.line_number,
|
|
781
787
|
)
|
|
782
788
|
|
|
@@ -787,19 +793,20 @@ class ActionConditionEnforcementCheck(PolicyCheck):
|
|
|
787
793
|
example: str,
|
|
788
794
|
expected_value: Any = None,
|
|
789
795
|
operator: str = "StringEquals",
|
|
790
|
-
) -> str:
|
|
791
|
-
"""Build
|
|
792
|
-
parts = []
|
|
796
|
+
) -> tuple[str, str]:
|
|
797
|
+
"""Build suggestion and example for adding the missing condition.
|
|
793
798
|
|
|
794
|
-
|
|
795
|
-
|
|
799
|
+
Returns:
|
|
800
|
+
Tuple of (suggestion_text, example_code)
|
|
801
|
+
"""
|
|
802
|
+
suggestion = description if description else f"Add condition: {condition_key}"
|
|
796
803
|
|
|
797
804
|
# Build example based on condition key type
|
|
798
805
|
if example:
|
|
799
|
-
|
|
806
|
+
example_code = example
|
|
800
807
|
else:
|
|
801
808
|
# Auto-generate example
|
|
802
|
-
example_lines = [
|
|
809
|
+
example_lines = [f' "{operator}": {{']
|
|
803
810
|
|
|
804
811
|
if isinstance(expected_value, list):
|
|
805
812
|
value_str = (
|
|
@@ -826,9 +833,9 @@ class ActionConditionEnforcementCheck(PolicyCheck):
|
|
|
826
833
|
example_lines.append(f' "{condition_key}": {value_str}')
|
|
827
834
|
example_lines.append(" }")
|
|
828
835
|
|
|
829
|
-
|
|
836
|
+
example_code = "\n".join(example_lines)
|
|
830
837
|
|
|
831
|
-
return
|
|
838
|
+
return suggestion, example_code
|
|
832
839
|
|
|
833
840
|
def _build_any_of_suggestion(self, any_of_conditions: list[dict[str, Any]]) -> str:
|
|
834
841
|
"""Build suggestion for any_of conditions."""
|
|
@@ -21,6 +21,8 @@ Example:
|
|
|
21
21
|
This check will report: s3:GetObject requires arn:aws:s3:::mybucket/*
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
|
+
import re
|
|
25
|
+
|
|
24
26
|
from iam_validator.core.aws_fetcher import AWSServiceFetcher
|
|
25
27
|
from iam_validator.core.check_registry import CheckConfig, PolicyCheck
|
|
26
28
|
from iam_validator.core.models import Statement, ValidationIssue
|
|
@@ -231,18 +233,23 @@ class ActionResourceMatchingCheck(PolicyCheck):
|
|
|
231
233
|
if reason:
|
|
232
234
|
message = reason
|
|
233
235
|
elif all_required_formats and len(all_required_formats) > 1:
|
|
234
|
-
types = ", ".join(f[
|
|
236
|
+
types = ", ".join(f"`{f['type']}`" for f in all_required_formats)
|
|
235
237
|
message = (
|
|
236
|
-
f"No resources match for action
|
|
238
|
+
f"No resources match for action `{action}`. This action requires one of: {types}"
|
|
237
239
|
)
|
|
238
240
|
else:
|
|
239
241
|
message = (
|
|
240
|
-
f"No resources match for action
|
|
241
|
-
f"This action requires resource type: {required_type}"
|
|
242
|
+
f"No resources match for action `{action}`. "
|
|
243
|
+
f"This action requires resource type: `{required_type}`"
|
|
242
244
|
)
|
|
243
245
|
|
|
244
246
|
# Build suggestion with examples
|
|
245
|
-
suggestion = self._get_suggestion(
|
|
247
|
+
suggestion = self._get_suggestion(
|
|
248
|
+
action=action,
|
|
249
|
+
required_format=required_format,
|
|
250
|
+
provided_resources=provided_resources,
|
|
251
|
+
all_required_formats=all_required_formats,
|
|
252
|
+
)
|
|
246
253
|
|
|
247
254
|
return ValidationIssue(
|
|
248
255
|
severity=self.get_severity(config),
|
|
@@ -265,6 +272,7 @@ class ActionResourceMatchingCheck(PolicyCheck):
|
|
|
265
272
|
action: str,
|
|
266
273
|
required_format: str,
|
|
267
274
|
provided_resources: list[str],
|
|
275
|
+
all_required_formats: list[dict] | None = None,
|
|
268
276
|
) -> str:
|
|
269
277
|
"""
|
|
270
278
|
Generate helpful suggestion for fixing the mismatch.
|
|
@@ -281,44 +289,75 @@ class ActionResourceMatchingCheck(PolicyCheck):
|
|
|
281
289
|
# Special case: Wildcard resource
|
|
282
290
|
if required_format == "*":
|
|
283
291
|
return (
|
|
284
|
-
f'Action {action} can only use Resource: "*" (wildcard).\n'
|
|
292
|
+
f'Action `{action}` can only use Resource: "*" (wildcard).\n'
|
|
285
293
|
f" This action does not support resource-level permissions.\n"
|
|
286
294
|
f' Example: "Resource": "*"'
|
|
287
295
|
)
|
|
288
296
|
|
|
289
|
-
#
|
|
290
|
-
# Pattern format: arn:${Partition}:service:${Region}:${Account}:resourceType/...
|
|
291
|
-
# Examples:
|
|
292
|
-
# arn:${Partition}:s3:::${BucketName}/${ObjectName} -> object
|
|
293
|
-
# arn:${Partition}:iam::${Account}:user/${UserName} -> user
|
|
294
|
-
resource_type = self._extract_resource_type_from_pattern(required_format)
|
|
295
|
-
|
|
296
|
-
# Build service-specific suggestion
|
|
297
|
+
# Build service-specific suggestion with proper markdown formatting
|
|
297
298
|
suggestion_parts = []
|
|
298
299
|
|
|
299
|
-
#
|
|
300
|
-
|
|
300
|
+
# If multiple resource types are valid, show all of them
|
|
301
|
+
if all_required_formats and len(all_required_formats) > 1:
|
|
302
|
+
resource_types = [fmt["type"] for fmt in all_required_formats]
|
|
303
|
+
suggestion_parts.append(
|
|
304
|
+
f"Action `{action}` requires one of these resource types: {', '.join(f'`{t}`' for t in resource_types)}"
|
|
305
|
+
)
|
|
306
|
+
suggestion_parts.append("")
|
|
301
307
|
|
|
302
|
-
|
|
303
|
-
|
|
308
|
+
# Show format and example for each resource type
|
|
309
|
+
for fmt in all_required_formats:
|
|
310
|
+
resource_type = fmt["type"]
|
|
311
|
+
arn_format = fmt["format"]
|
|
304
312
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
313
|
+
suggestion_parts.append(
|
|
314
|
+
f"**Option {all_required_formats.index(fmt) + 1}: `{resource_type}` resource**"
|
|
315
|
+
)
|
|
316
|
+
suggestion_parts.append("```")
|
|
317
|
+
suggestion_parts.append(arn_format)
|
|
318
|
+
suggestion_parts.append("```")
|
|
309
319
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
320
|
+
# Add practical example
|
|
321
|
+
example = self._generate_example_arn(arn_format)
|
|
322
|
+
if example:
|
|
323
|
+
suggestion_parts.append(f"Example: `{example}`")
|
|
314
324
|
|
|
315
|
-
|
|
325
|
+
suggestion_parts.append("")
|
|
326
|
+
else:
|
|
327
|
+
# Single resource type - show detailed info
|
|
328
|
+
# Extract resource type from ARN pattern
|
|
329
|
+
# Pattern format: arn:${Partition}:service:${Region}:${Account}:resourceType/...
|
|
330
|
+
# Examples:
|
|
331
|
+
# arn:${Partition}:s3:::${BucketName}/${ObjectName} -> object
|
|
332
|
+
# arn:${Partition}:iam::${Account}:user/${UserName} -> user
|
|
333
|
+
resource_type = self._extract_resource_type_from_pattern(required_format)
|
|
334
|
+
|
|
335
|
+
# Add action description
|
|
336
|
+
suggestion_parts.append(f"Action `{action}` requires `{resource_type}` resource type.")
|
|
337
|
+
suggestion_parts.append("")
|
|
338
|
+
|
|
339
|
+
# Add expected format in code block
|
|
340
|
+
suggestion_parts.append("**Expected format:**")
|
|
341
|
+
suggestion_parts.append(f"```\n{required_format}\n```")
|
|
342
|
+
|
|
343
|
+
# Add practical example based on the pattern
|
|
344
|
+
example = self._generate_example_arn(required_format)
|
|
345
|
+
if example:
|
|
346
|
+
suggestion_parts.append("**Example:**")
|
|
347
|
+
suggestion_parts.append(f"```\n{example}\n```")
|
|
348
|
+
|
|
349
|
+
# Add helpful context for common patterns
|
|
350
|
+
context = self._get_resource_context(action_name, resource_type, required_format)
|
|
351
|
+
if context:
|
|
352
|
+
suggestion_parts.append(f"**Note:** {context}")
|
|
316
353
|
|
|
317
354
|
# Add current resources to help user understand the mismatch
|
|
318
355
|
if provided_resources and len(provided_resources) <= 3:
|
|
319
|
-
|
|
320
|
-
|
|
356
|
+
suggestion_parts.append("**Current resources:**")
|
|
357
|
+
for resource in provided_resources:
|
|
358
|
+
suggestion_parts.append(f"- `{resource}`")
|
|
321
359
|
|
|
360
|
+
suggestion = "\n".join(suggestion_parts)
|
|
322
361
|
return suggestion
|
|
323
362
|
|
|
324
363
|
def _extract_resource_type_from_pattern(self, pattern: str) -> str:
|
|
@@ -340,17 +379,14 @@ class ActionResourceMatchingCheck(PolicyCheck):
|
|
|
340
379
|
|
|
341
380
|
# Extract resource type (part before / or entire string)
|
|
342
381
|
if "/" in resource_part:
|
|
343
|
-
resource_type = resource_part.split("/")[0]
|
|
382
|
+
resource_type = resource_part.split("/", maxsplit=1)[0]
|
|
344
383
|
elif ":" in resource_part:
|
|
345
|
-
resource_type = resource_part.split(":")[0]
|
|
384
|
+
resource_type = resource_part.split(":", maxsplit=1)[0]
|
|
346
385
|
else:
|
|
347
386
|
resource_type = resource_part
|
|
348
387
|
|
|
349
388
|
# Remove template variables like ${...}
|
|
350
|
-
import re
|
|
351
|
-
|
|
352
389
|
resource_type = re.sub(r"\$\{[^}]+\}", "", resource_type)
|
|
353
|
-
|
|
354
390
|
return resource_type.strip() or "resource"
|
|
355
391
|
|
|
356
392
|
def _generate_example_arn(self, pattern: str) -> str:
|
|
@@ -359,8 +395,6 @@ class ActionResourceMatchingCheck(PolicyCheck):
|
|
|
359
395
|
|
|
360
396
|
Converts AWS template variables to realistic examples.
|
|
361
397
|
"""
|
|
362
|
-
import re
|
|
363
|
-
|
|
364
398
|
example = pattern
|
|
365
399
|
|
|
366
400
|
# Common substitutions
|
|
@@ -52,26 +52,26 @@ class ConditionKeyValidationCheck(PolicyCheck):
|
|
|
52
52
|
continue
|
|
53
53
|
|
|
54
54
|
# Validate against action and resource types
|
|
55
|
-
|
|
56
|
-
action, condition_key, resources
|
|
57
|
-
)
|
|
55
|
+
result = await fetcher.validate_condition_key(action, condition_key, resources)
|
|
58
56
|
|
|
59
|
-
if not is_valid:
|
|
57
|
+
if not result.is_valid:
|
|
60
58
|
issues.append(
|
|
61
59
|
ValidationIssue(
|
|
62
60
|
severity=self.get_severity(config),
|
|
63
61
|
statement_sid=statement_sid,
|
|
64
62
|
statement_index=statement_idx,
|
|
65
63
|
issue_type="invalid_condition_key",
|
|
66
|
-
message=
|
|
64
|
+
message=result.error_message
|
|
65
|
+
or f"Invalid condition key: {condition_key}",
|
|
67
66
|
action=action,
|
|
68
67
|
condition_key=condition_key,
|
|
69
68
|
line_number=line_number,
|
|
69
|
+
suggestion=result.suggestion,
|
|
70
70
|
)
|
|
71
71
|
)
|
|
72
72
|
# Only report once per condition key (not per action)
|
|
73
73
|
break
|
|
74
|
-
elif
|
|
74
|
+
elif result.warning_message and warn_on_global_keys:
|
|
75
75
|
# Add warning for global condition keys with action-specific keys
|
|
76
76
|
# Only if warn_on_global_condition_keys is enabled
|
|
77
77
|
issues.append(
|
|
@@ -80,7 +80,7 @@ class ConditionKeyValidationCheck(PolicyCheck):
|
|
|
80
80
|
statement_sid=statement_sid,
|
|
81
81
|
statement_index=statement_idx,
|
|
82
82
|
issue_type="global_condition_key_with_action_specific",
|
|
83
|
-
message=
|
|
83
|
+
message=result.warning_message,
|
|
84
84
|
action=action,
|
|
85
85
|
condition_key=condition_key,
|
|
86
86
|
line_number=line_number,
|
|
@@ -107,8 +107,8 @@ class ConditionTypeMismatchCheck(PolicyCheck):
|
|
|
107
107
|
ValidationIssue(
|
|
108
108
|
severity="warning",
|
|
109
109
|
message=(
|
|
110
|
-
f"Type mismatch (usable but not recommended): Operator
|
|
111
|
-
f"{operator_type} values, but condition key
|
|
110
|
+
f"Type mismatch (usable but not recommended): Operator `{operator}` expects "
|
|
111
|
+
f"{operator_type} values, but condition key `{condition_key}` is type {key_type}. "
|
|
112
112
|
f"Consider using an ARN-specific operator like ArnEquals or ArnLike instead."
|
|
113
113
|
),
|
|
114
114
|
statement_sid=statement_sid,
|
|
@@ -123,8 +123,8 @@ class ConditionTypeMismatchCheck(PolicyCheck):
|
|
|
123
123
|
ValidationIssue(
|
|
124
124
|
severity=self.get_severity(config),
|
|
125
125
|
message=(
|
|
126
|
-
f"Type mismatch: Operator
|
|
127
|
-
f"but condition key
|
|
126
|
+
f"Type mismatch: Operator `{operator}` expects {operator_type} values, "
|
|
127
|
+
f"but condition key `{condition_key}` is type {key_type}."
|
|
128
128
|
),
|
|
129
129
|
statement_sid=statement_sid,
|
|
130
130
|
statement_index=statement_idx,
|
|
@@ -141,7 +141,7 @@ class ConditionTypeMismatchCheck(PolicyCheck):
|
|
|
141
141
|
ValidationIssue(
|
|
142
142
|
severity=self.get_severity(config),
|
|
143
143
|
message=(
|
|
144
|
-
f"Invalid value format for condition key
|
|
144
|
+
f"Invalid value format for condition key `{condition_key}`: {error_msg}"
|
|
145
145
|
),
|
|
146
146
|
statement_sid=statement_sid,
|
|
147
147
|
statement_index=statement_idx,
|
|
@@ -172,7 +172,9 @@ class ConditionTypeMismatchCheck(PolicyCheck):
|
|
|
172
172
|
Returns:
|
|
173
173
|
Type string or None if not found
|
|
174
174
|
"""
|
|
175
|
-
from iam_validator.core.config.aws_global_conditions import
|
|
175
|
+
from iam_validator.core.config.aws_global_conditions import (
|
|
176
|
+
get_global_conditions,
|
|
177
|
+
)
|
|
176
178
|
|
|
177
179
|
# Check if it's a global condition key
|
|
178
180
|
global_conditions = get_global_conditions()
|
|
@@ -43,19 +43,12 @@ class FullWildcardCheck(PolicyCheck):
|
|
|
43
43
|
"message",
|
|
44
44
|
"Statement allows all actions on all resources - CRITICAL SECURITY RISK",
|
|
45
45
|
)
|
|
46
|
-
|
|
46
|
+
suggestion = config.config.get(
|
|
47
47
|
"suggestion",
|
|
48
48
|
"This grants full administrative access. Replace both wildcards with specific actions and resources to follow least-privilege principle",
|
|
49
49
|
)
|
|
50
50
|
example = config.config.get("example", "")
|
|
51
51
|
|
|
52
|
-
# Combine suggestion + example
|
|
53
|
-
suggestion = (
|
|
54
|
-
f"{suggestion_text}\nExample:\n```json\n{example}\n```"
|
|
55
|
-
if example
|
|
56
|
-
else suggestion_text
|
|
57
|
-
)
|
|
58
|
-
|
|
59
52
|
issues.append(
|
|
60
53
|
ValidationIssue(
|
|
61
54
|
severity=self.get_severity(config),
|
|
@@ -64,6 +57,7 @@ class FullWildcardCheck(PolicyCheck):
|
|
|
64
57
|
issue_type="security_risk",
|
|
65
58
|
message=message,
|
|
66
59
|
suggestion=suggestion,
|
|
60
|
+
example=example if example else None,
|
|
67
61
|
line_number=statement.line_number,
|
|
68
62
|
)
|
|
69
63
|
)
|
|
@@ -70,11 +70,11 @@ class MFAConditionCheck(PolicyCheck):
|
|
|
70
70
|
ValidationIssue(
|
|
71
71
|
severity=self.get_severity(config),
|
|
72
72
|
message=(
|
|
73
|
-
"Dangerous MFA condition pattern detected
|
|
74
|
-
'Using {"Bool": {"aws:MultiFactorAuthPresent": "false"}} does not enforce MFA '
|
|
75
|
-
"because aws:MultiFactorAuthPresent may not exist in the request context. "
|
|
76
|
-
'Consider using {"Bool": {"aws:MultiFactorAuthPresent": "true"}} in an Allow statement, '
|
|
77
|
-
"or use BoolIfExists in a Deny statement."
|
|
73
|
+
"**Dangerous MFA condition pattern detected.** "
|
|
74
|
+
'Using `{"Bool": {"aws:MultiFactorAuthPresent": "false"}}` does not enforce MFA '
|
|
75
|
+
"because `aws:MultiFactorAuthPresent` may not exist in the request context. "
|
|
76
|
+
'Consider using `{"Bool": {"aws:MultiFactorAuthPresent": "true"}}` in an Allow statement, '
|
|
77
|
+
"or use `BoolIfExists` in a Deny statement."
|
|
78
78
|
),
|
|
79
79
|
statement_sid=statement_sid,
|
|
80
80
|
statement_index=statement_idx,
|
|
@@ -97,10 +97,10 @@ class MFAConditionCheck(PolicyCheck):
|
|
|
97
97
|
ValidationIssue(
|
|
98
98
|
severity=self.get_severity(config),
|
|
99
99
|
message=(
|
|
100
|
-
"Dangerous MFA condition pattern detected
|
|
101
|
-
'Using {"Null": {"aws:MultiFactorAuthPresent": "false"}} only checks if the key exists, '
|
|
100
|
+
"**Dangerous MFA condition pattern detected.** "
|
|
101
|
+
'Using `{"Null": {"aws:MultiFactorAuthPresent": "false"}}` only checks if the key exists, '
|
|
102
102
|
"not whether MFA was actually used. This does not enforce MFA. "
|
|
103
|
-
'Consider using {"Bool": {"aws:MultiFactorAuthPresent": "true"}} in an Allow statement instead.'
|
|
103
|
+
'Consider using `{"Bool": {"aws:MultiFactorAuthPresent": "true"}}` in an Allow statement instead.'
|
|
104
104
|
),
|
|
105
105
|
statement_sid=statement_sid,
|
|
106
106
|
statement_index=statement_idx,
|