iam-policy-validator 1.13.1__py3-none-any.whl → 1.14.1__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.13.1.dist-info → iam_policy_validator-1.14.1.dist-info}/METADATA +1 -1
- {iam_policy_validator-1.13.1.dist-info → iam_policy_validator-1.14.1.dist-info}/RECORD +45 -39
- iam_validator/__version__.py +1 -1
- iam_validator/checks/action_condition_enforcement.py +6 -0
- iam_validator/checks/action_resource_matching.py +12 -12
- iam_validator/checks/action_validation.py +1 -0
- iam_validator/checks/condition_key_validation.py +2 -0
- iam_validator/checks/condition_type_mismatch.py +3 -0
- iam_validator/checks/full_wildcard.py +1 -0
- iam_validator/checks/mfa_condition_check.py +2 -0
- iam_validator/checks/policy_structure.py +9 -0
- iam_validator/checks/policy_type_validation.py +11 -0
- iam_validator/checks/principal_validation.py +5 -0
- iam_validator/checks/resource_validation.py +4 -0
- iam_validator/checks/sensitive_action.py +1 -0
- iam_validator/checks/service_wildcard.py +6 -3
- iam_validator/checks/set_operator_validation.py +3 -0
- iam_validator/checks/sid_uniqueness.py +2 -0
- iam_validator/checks/trust_policy_validation.py +3 -0
- iam_validator/checks/utils/__init__.py +16 -0
- iam_validator/checks/utils/action_parser.py +149 -0
- iam_validator/checks/wildcard_action.py +1 -0
- iam_validator/checks/wildcard_resource.py +231 -4
- iam_validator/commands/analyze.py +19 -1
- iam_validator/commands/completion.py +6 -2
- iam_validator/commands/validate.py +231 -12
- iam_validator/core/aws_service/fetcher.py +21 -9
- iam_validator/core/codeowners.py +245 -0
- iam_validator/core/config/check_documentation.py +390 -0
- iam_validator/core/config/config_loader.py +199 -0
- iam_validator/core/config/defaults.py +25 -0
- iam_validator/core/constants.py +1 -0
- iam_validator/core/diff_parser.py +8 -4
- iam_validator/core/finding_fingerprint.py +131 -0
- iam_validator/core/formatters/sarif.py +370 -128
- iam_validator/core/ignore_processor.py +309 -0
- iam_validator/core/ignored_findings.py +400 -0
- iam_validator/core/models.py +54 -4
- iam_validator/core/policy_loader.py +313 -4
- iam_validator/core/pr_commenter.py +223 -22
- iam_validator/core/report.py +22 -6
- iam_validator/integrations/github_integration.py +881 -123
- {iam_policy_validator-1.13.1.dist-info → iam_policy_validator-1.14.1.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.13.1.dist-info → iam_policy_validator-1.14.1.dist-info}/entry_points.txt +0 -0
- {iam_policy_validator-1.13.1.dist-info → iam_policy_validator-1.14.1.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.
|
|
3
|
+
Version: 1.14.1
|
|
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
|
|
@@ -1,59 +1,64 @@
|
|
|
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=AoaYA0Mf5TpngovvZd8XG-okIqOb8ySOhHKPoa8aUwo,374
|
|
4
4
|
iam_validator/checks/__init__.py,sha256=OTkPnmlelu4YjMO8krjhu2wXiTV72RzopA5u1SfPQA0,1990
|
|
5
|
-
iam_validator/checks/action_condition_enforcement.py,sha256=
|
|
6
|
-
iam_validator/checks/action_resource_matching.py,sha256=
|
|
7
|
-
iam_validator/checks/action_validation.py,sha256=
|
|
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=
|
|
5
|
+
iam_validator/checks/action_condition_enforcement.py,sha256=2-XUMbof9tQ7SHZNmAHMkR1DgbOIzY2eFWlp9S9dwLk,60625
|
|
6
|
+
iam_validator/checks/action_resource_matching.py,sha256=qND0hfDgNoxFEdLWwrxOPVDfdj3k50nzedT2qF7nK7o,19428
|
|
7
|
+
iam_validator/checks/action_validation.py,sha256=glA2F3iQBU-d8rruiyB9IdzVOESC2Kb-91SnwRCdQF0,2562
|
|
8
|
+
iam_validator/checks/condition_key_validation.py,sha256=u9eCxI_OjkWqK_KcgPPxu4ZO16E9pqjK8CDiW7QbvZ4,4031
|
|
9
|
+
iam_validator/checks/condition_type_mismatch.py,sha256=KJp7zQHDd8VeTcfjcD-ur3S4070cXEDTWkFtxfp7CuE,10652
|
|
10
|
+
iam_validator/checks/full_wildcard.py,sha256=0TkkHtV0MZ6nZtJRtGdn3wwOMM96TRyGO7l7mmdHNUo,2325
|
|
11
|
+
iam_validator/checks/mfa_condition_check.py,sha256=y1LbqcvQ_fL2BPTNaKRQoBYM5hM7JET9cDPUOWKFEVs,4814
|
|
12
12
|
iam_validator/checks/policy_size.py,sha256=eJd36Nj4gqWLIkQ5imhHR1hGtQ6T-iJsC22Wd1VSUf0,4681
|
|
13
|
-
iam_validator/checks/policy_structure.py,sha256=
|
|
14
|
-
iam_validator/checks/policy_type_validation.py,sha256
|
|
15
|
-
iam_validator/checks/principal_validation.py,sha256=
|
|
16
|
-
iam_validator/checks/resource_validation.py,sha256=
|
|
17
|
-
iam_validator/checks/sensitive_action.py,sha256=
|
|
18
|
-
iam_validator/checks/service_wildcard.py,sha256=
|
|
19
|
-
iam_validator/checks/set_operator_validation.py,sha256=
|
|
20
|
-
iam_validator/checks/sid_uniqueness.py,sha256=
|
|
21
|
-
iam_validator/checks/trust_policy_validation.py,sha256=
|
|
22
|
-
iam_validator/checks/wildcard_action.py,sha256=
|
|
23
|
-
iam_validator/checks/wildcard_resource.py,sha256=
|
|
24
|
-
iam_validator/checks/utils/__init__.py,sha256=
|
|
13
|
+
iam_validator/checks/policy_structure.py,sha256=LExdm93JeqsyhikWrh9lfJ9sBiZ4818Ts0-N3MwUrtk,22089
|
|
14
|
+
iam_validator/checks/policy_type_validation.py,sha256=-Q2Yn_sa7Cba_Fb9ESxSL5qNbRpncuC7NQy7R_fdJtY,16521
|
|
15
|
+
iam_validator/checks/principal_validation.py,sha256=TthgDW5YXFfv5li1u4nn56k0Feqt5HeOEtQgo5pBKqo,27911
|
|
16
|
+
iam_validator/checks/resource_validation.py,sha256=RIBMiCMwX45wwnDwSzpvF1mIught5QSbSbmTQSVa1wI,6192
|
|
17
|
+
iam_validator/checks/sensitive_action.py,sha256=LHicl6dd5E1JV19cLKvFAMGzLzYr5h_Y7QvS8s57kvA,18952
|
|
18
|
+
iam_validator/checks/service_wildcard.py,sha256=1epcET5oDclAmzxhtmQfKBg3Q5Rl4VXBMoZouxCJLpM,4114
|
|
19
|
+
iam_validator/checks/set_operator_validation.py,sha256=GMZ1OWqySptYWV7565-K4R5ODs1eYgWXDozwtU-3sgY,7422
|
|
20
|
+
iam_validator/checks/sid_uniqueness.py,sha256=MPQwALBjcvbY4Q55mpxDrGMqmVCMb13YSg621YbQYF8,6048
|
|
21
|
+
iam_validator/checks/trust_policy_validation.py,sha256=r3fM2hU7W0yCFS6hbd4ZSlD8y2tm0zk99mUVlIt0LsI,17956
|
|
22
|
+
iam_validator/checks/wildcard_action.py,sha256=VhWezb1JXmFnwV9WKgVu-48ythScGfZasg8fFd6tAG4,2001
|
|
23
|
+
iam_validator/checks/wildcard_resource.py,sha256=5nswLCYvjgiZvv64y3OOywVwmTZMlpYBRSC94FS3X8M,16801
|
|
24
|
+
iam_validator/checks/utils/__init__.py,sha256=iI9jHIDlDuuPK2vxjJA-qc927PaWC9zVJb9z3vBRUsQ,356
|
|
25
|
+
iam_validator/checks/utils/action_parser.py,sha256=zA_NGknc5gJvsmIlf4aC14eHFIkBLyL9if5ad80gHwY,4421
|
|
25
26
|
iam_validator/checks/utils/policy_level_checks.py,sha256=pr-uLo-otB612YLZ-rd8W5Kl9ENaTHuzTNOUhQaULKc,7593
|
|
26
27
|
iam_validator/checks/utils/sensitive_action_matcher.py,sha256=qDXcJa_2sCJu9pBbjDlI7x5lPtLRc6jQCpKPMheCOJQ,11215
|
|
27
28
|
iam_validator/checks/utils/wildcard_expansion.py,sha256=3W13hlyWcP2wJ6w-BwM887VOnRzglK6Bk3eHMjUtOco,3131
|
|
28
29
|
iam_validator/commands/__init__.py,sha256=RBEz-Kgt3aRVn_9B1HRy_XgQMIKzlSSQs4Gtg2jQEv8,729
|
|
29
|
-
iam_validator/commands/analyze.py,sha256=
|
|
30
|
+
iam_validator/commands/analyze.py,sha256=rtXZmevC7GCXrADoGrxihRkrLbma59wMAMP2yBhqWPU,21752
|
|
30
31
|
iam_validator/commands/base.py,sha256=5baCCMwxz7pdQ6XMpWfXFNz7i1l5dB8Qv9dKKR04Gzs,1074
|
|
31
32
|
iam_validator/commands/cache.py,sha256=llfyQzPE5Azd5YcW0ohYcYjF_OCyiQ1GoJQ982t71lQ,14294
|
|
32
|
-
iam_validator/commands/completion.py,sha256=
|
|
33
|
+
iam_validator/commands/completion.py,sha256=cnQf3qcV5WSNpwJlPyl_hMNB1z3TkeeXCxj8K2FmEBw,19320
|
|
33
34
|
iam_validator/commands/download_services.py,sha256=KKz3ybMLT8DQUf9aFZ0tilJ-o1b6PE8Pf1pC4K6cT8I,9175
|
|
34
35
|
iam_validator/commands/post_to_pr.py,sha256=CvUXs2xvO-UhluxdfNM6F0TCWD8hDBEOiYw60fm1Dms,2363
|
|
35
36
|
iam_validator/commands/query.py,sha256=ft8ptWfsNUK4Wprq_A11txdV_chBgqkoAo7SQfzEwK0,17079
|
|
36
|
-
iam_validator/commands/validate.py,sha256=
|
|
37
|
+
iam_validator/commands/validate.py,sha256=4wk-eSwhgqnQvyUre426S831JadVUctb0FDeCZD4RTk,34176
|
|
37
38
|
iam_validator/core/__init__.py,sha256=hYXkSbxplKzhM6dqrVzV4M3k7GKLsZbgExypxKq74gs,376
|
|
38
39
|
iam_validator/core/access_analyzer.py,sha256=mtMaY-FnKjKEVITky_9ywZe1FaCAm61ElRv5Z_ZeC7E,24562
|
|
39
40
|
iam_validator/core/access_analyzer_report.py,sha256=UMm2RNGj2rAKav1zsCw_htQZZRwRC0jjayd2zvKma1A,24896
|
|
40
41
|
iam_validator/core/aws_fetcher.py,sha256=op93QvtGmeLF9dHobl2IuoPDeunn33pBLb8h7XjtmoQ,920
|
|
41
42
|
iam_validator/core/check_registry.py,sha256=oRCdWoCGQ8VZERVYd821u9r5NdKQ9FMC54e6dRWJfqw,25475
|
|
42
43
|
iam_validator/core/cli.py,sha256=PkXiZjlgrQ21QustBbspefYsdbxst4gxoClyG2_HQR8,3843
|
|
44
|
+
iam_validator/core/codeowners.py,sha256=dfRjYTpcTVmc-h95i4EoPXCXlcblD8yryeJBaTKQfjM,7530
|
|
43
45
|
iam_validator/core/condition_validators.py,sha256=7zBjlcf2xGFKGbcFrXSLvWT5tFhWxoqwzhsJqS2E8uY,21524
|
|
44
|
-
iam_validator/core/constants.py,sha256=
|
|
45
|
-
iam_validator/core/diff_parser.py,sha256=
|
|
46
|
+
iam_validator/core/constants.py,sha256=tHAZwINyB1wj_l5ZjnhFZ_LIA8S2C5lF-9CZtKijFac,6192
|
|
47
|
+
iam_validator/core/diff_parser.py,sha256=5Jxa6WvQZtG5grblZeUH2OQ2R46tFLK-h8tvkHOSfLk,12110
|
|
48
|
+
iam_validator/core/finding_fingerprint.py,sha256=NJIlu8NhdenWbLS7ww8LyWFasJgpKWN63-DprrNW7Zs,4353
|
|
46
49
|
iam_validator/core/ignore_patterns.py,sha256=pZqDJBtkbck-85QK5eFPM5ZOPEKs3McRh3avqiCT5z0,10398
|
|
50
|
+
iam_validator/core/ignore_processor.py,sha256=zgWfS-4BU4c_W6VxUxHIHorMtB5XzB410wZ3bbzVgH8,10686
|
|
51
|
+
iam_validator/core/ignored_findings.py,sha256=b4PySz46so1rGKNt4prg2dkysHPfTJP4wsHYorVn1FA,12756
|
|
47
52
|
iam_validator/core/label_manager.py,sha256=48CRASWg98wyjfVF_1pUzj6dm9itzmG7SeIWf0TSUfc,7502
|
|
48
|
-
iam_validator/core/models.py,sha256=
|
|
53
|
+
iam_validator/core/models.py,sha256=lXUadIsTpp_j0Vt89Ez7aJkTKs2GD2ty3Ukl2NeY9Zo,15680
|
|
49
54
|
iam_validator/core/policy_checks.py,sha256=FNVuS2GTffwCjjrlupVIazC172gSxKYAAT_ObV6Apbo,8803
|
|
50
|
-
iam_validator/core/policy_loader.py,sha256=
|
|
51
|
-
iam_validator/core/pr_commenter.py,sha256=
|
|
52
|
-
iam_validator/core/report.py,sha256=
|
|
55
|
+
iam_validator/core/policy_loader.py,sha256=iid3mGfDzSXASzKDqbLnrqJHBdVQvvebofVqNImsGKM,29201
|
|
56
|
+
iam_validator/core/pr_commenter.py,sha256=BMTovWROjaxmhaNg-9emUGNFF_FGtrwYmCKvioh7x5M,32448
|
|
57
|
+
iam_validator/core/report.py,sha256=uMhUYv-8mNoTMZzD0F2buSQTxr4YIRh8UMZjvFq9tmc,37312
|
|
53
58
|
iam_validator/core/aws_service/__init__.py,sha256=UqMh4HUdGlx2QF5OoueJJ2UlCnhX4QW_x3KeE_bxRQc,735
|
|
54
59
|
iam_validator/core/aws_service/cache.py,sha256=DPuOOPPJC867KAYgV1e0RyQs_k3mtefMdYli3jPaN64,3589
|
|
55
60
|
iam_validator/core/aws_service/client.py,sha256=Zv7rIpEFdUCDXKGp3migPDkj8L5eZltgrGe64M2t2Ko,7336
|
|
56
|
-
iam_validator/core/aws_service/fetcher.py,sha256=
|
|
61
|
+
iam_validator/core/aws_service/fetcher.py,sha256=TD9qQ4tQF4xdEGhVVADGgF8QlXe15R3nAc2hWZnVoUw,23996
|
|
57
62
|
iam_validator/core/aws_service/parsers.py,sha256=gJzR7HCD8ItCWCCbguTQIZpPEdj2rdMwC7LPhu7ve14,5174
|
|
58
63
|
iam_validator/core/aws_service/patterns.py,sha256=gGc55Tn-EJ3cmcWtmYAZROUajKYz7DaMchYWGEhHpC0,1726
|
|
59
64
|
iam_validator/core/aws_service/storage.py,sha256=PrfKdvF60IL7E_8xYs_XwFoAJPRcVYw57FVLHCoqwVk,10429
|
|
@@ -62,9 +67,10 @@ iam_validator/core/config/__init__.py,sha256=CWSyIA7kEyzrskEenjYbs9Iih10BXRpiY9H
|
|
|
62
67
|
iam_validator/core/config/aws_api.py,sha256=HLIzOItQ0A37wxHcgWck6ZFO0wmNY8JNTiWMMK6JKYU,1248
|
|
63
68
|
iam_validator/core/config/aws_global_conditions.py,sha256=gdmMxXGBy95B3uYUG-J7rnM6Ixgc6L7Y9Pcd2XAMb60,7170
|
|
64
69
|
iam_validator/core/config/category_suggestions.py,sha256=fopaZ9kXDrsLgi_r0pERrLwgdPPJl5VIiKvXtQK9tj0,8583
|
|
70
|
+
iam_validator/core/config/check_documentation.py,sha256=Adyt3Q4JSRlVNPWulXVlRIEorxXG_nt0mkPz_ySa8y4,15931
|
|
65
71
|
iam_validator/core/config/condition_requirements.py,sha256=1CeQJfWV-Y2ImW0Mq9YdrgvH-hj9IXe0gVOm3B36Rc8,10655
|
|
66
|
-
iam_validator/core/config/config_loader.py,sha256=
|
|
67
|
-
iam_validator/core/config/defaults.py,sha256=
|
|
72
|
+
iam_validator/core/config/config_loader.py,sha256=M51apOv_JwRXC8VM0Spghu8ns-_qnbSoYQZWmgBP3Ww,24059
|
|
73
|
+
iam_validator/core/config/defaults.py,sha256=poREMGKiDLWy9hSWfuWLPy6xXNLf9bkROUQ2A_cJIn4,38502
|
|
68
74
|
iam_validator/core/config/principal_requirements.py,sha256=VCX7fBDgeDTJQyoz7_x7GI7Kf9O1Eu-sbihoHOrKv6o,15105
|
|
69
75
|
iam_validator/core/config/sensitive_actions.py,sha256=uATDIp_TD3OQQlsYTZp79qd1mSK2Bf9hJ0JwcqLBr84,25344
|
|
70
76
|
iam_validator/core/config/service_principals.py,sha256=8pys5H_yycVJ9KTyimAKFYBg83Aol2Iri53wiHjtnEM,3959
|
|
@@ -77,9 +83,9 @@ iam_validator/core/formatters/enhanced.py,sha256=GD7RIAL1hLDAsypCKECwDMGslAx2AaM
|
|
|
77
83
|
iam_validator/core/formatters/html.py,sha256=j4sQi-wXiD9kCHldW5JCzbJe0frhiP5uQI9KlH3Sj_g,22994
|
|
78
84
|
iam_validator/core/formatters/json.py,sha256=A7gZ8P32GEdbDvrSn6v56yQ4fOP_kyMaoFVXG2bgnew,939
|
|
79
85
|
iam_validator/core/formatters/markdown.py,sha256=dk4STeY-tOEZsVrlmolIEqZvWYP9JhRtygxxNA49DEE,2293
|
|
80
|
-
iam_validator/core/formatters/sarif.py,sha256=
|
|
86
|
+
iam_validator/core/formatters/sarif.py,sha256=03MHSyuZm9FlzaPeWg7wH-UTzzCDhSy6vMPrFpFNkS8,18884
|
|
81
87
|
iam_validator/integrations/__init__.py,sha256=7Hlor_X9j0NZaEjFuSvoXAAuSKQ-zgY19Rk-Dz3JpKo,616
|
|
82
|
-
iam_validator/integrations/github_integration.py,sha256=
|
|
88
|
+
iam_validator/integrations/github_integration.py,sha256=2pOjTVsLMymx-wU31Ly7JqODgNXf5U7lvteVqBpaRgE,67913
|
|
83
89
|
iam_validator/integrations/ms_teams.py,sha256=t2PlWuTDb6GGH-eDU1jnOKd8D1w4FCB68bahGA7MJcE,14475
|
|
84
90
|
iam_validator/sdk/__init__.py,sha256=AZLnfdn3A9AWb0pMhsbu3GAOAzt6rV7Fi3E3d9_3ZdI,6388
|
|
85
91
|
iam_validator/sdk/arn_matching.py,sha256=HSDpLltOYISq-SoPebAlM89mKOaUaghq_04urchEFDA,12778
|
|
@@ -93,8 +99,8 @@ iam_validator/utils/__init__.py,sha256=NveA2F3G1E6-ANZzFr7J6Q6u5mogvMp862iFokmYu
|
|
|
93
99
|
iam_validator/utils/cache.py,sha256=wOQKOBeoG6QqC5f0oXcHz63Cjtu_-SsSS-0pTSwyAiM,3254
|
|
94
100
|
iam_validator/utils/regex.py,sha256=xHoMECttb7qaMhts-c9b0GIxdhHNZTt-UBr7wNhWfzg,6219
|
|
95
101
|
iam_validator/utils/terminal.py,sha256=FsRaRMH_JAyDgXWBCOgOEhbS89cs17HCmKYoughq5io,724
|
|
96
|
-
iam_policy_validator-1.
|
|
97
|
-
iam_policy_validator-1.
|
|
98
|
-
iam_policy_validator-1.
|
|
99
|
-
iam_policy_validator-1.
|
|
100
|
-
iam_policy_validator-1.
|
|
102
|
+
iam_policy_validator-1.14.1.dist-info/METADATA,sha256=lvh7lKnQzk57j2Q-42nEOZttkrTcjFLGQczq_zK8p5k,34456
|
|
103
|
+
iam_policy_validator-1.14.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
104
|
+
iam_policy_validator-1.14.1.dist-info/entry_points.txt,sha256=8HtWd8O7mvPiPdZR5YbzY8or_qcqLM4-pKaFdhtFT8M,62
|
|
105
|
+
iam_policy_validator-1.14.1.dist-info/licenses/LICENSE,sha256=AMnbFTBDcK4_MITe2wiQBkj0vg-jjBBhsc43ydC7tt4,1098
|
|
106
|
+
iam_policy_validator-1.14.1.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.
|
|
6
|
+
__version__ = "1.14.1"
|
|
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("."))
|
|
@@ -645,6 +645,7 @@ class ActionConditionEnforcementCheck(PolicyCheck):
|
|
|
645
645
|
action=", ".join(actions),
|
|
646
646
|
suggestion=f"Remove these forbidden actions. Found in: {', '.join(statement_refs)}. {description}",
|
|
647
647
|
line_number=stmt.line_number,
|
|
648
|
+
field_name="action",
|
|
648
649
|
)
|
|
649
650
|
)
|
|
650
651
|
|
|
@@ -683,6 +684,7 @@ class ActionConditionEnforcementCheck(PolicyCheck):
|
|
|
683
684
|
action=", ".join(sorted(set(found_actions))),
|
|
684
685
|
suggestion=f"Review these statements: {', '.join(statement_refs)}. {description}",
|
|
685
686
|
line_number=first_stmt.line_number,
|
|
687
|
+
field_name="action",
|
|
686
688
|
)
|
|
687
689
|
)
|
|
688
690
|
return issues
|
|
@@ -772,6 +774,7 @@ class ActionConditionEnforcementCheck(PolicyCheck):
|
|
|
772
774
|
action=", ".join(sorted(all_actions)),
|
|
773
775
|
suggestion=f"Review these statements: {', '.join(statement_refs)}. {description}",
|
|
774
776
|
line_number=first_stmt.line_number,
|
|
777
|
+
field_name="action",
|
|
775
778
|
)
|
|
776
779
|
)
|
|
777
780
|
return issues
|
|
@@ -1129,6 +1132,7 @@ class ActionConditionEnforcementCheck(PolicyCheck):
|
|
|
1129
1132
|
suggestion=suggestion,
|
|
1130
1133
|
example=example if example else None,
|
|
1131
1134
|
line_number=statement.line_number,
|
|
1135
|
+
field_name="condition",
|
|
1132
1136
|
)
|
|
1133
1137
|
)
|
|
1134
1138
|
|
|
@@ -1273,6 +1277,7 @@ class ActionConditionEnforcementCheck(PolicyCheck):
|
|
|
1273
1277
|
suggestion=suggestion_text,
|
|
1274
1278
|
example=example_code,
|
|
1275
1279
|
line_number=statement.line_number,
|
|
1280
|
+
field_name="condition",
|
|
1276
1281
|
)
|
|
1277
1282
|
|
|
1278
1283
|
def _build_suggestion(
|
|
@@ -1433,4 +1438,5 @@ class ActionConditionEnforcementCheck(PolicyCheck):
|
|
|
1433
1438
|
condition_key=condition_key,
|
|
1434
1439
|
suggestion=suggestion,
|
|
1435
1440
|
line_number=statement.line_number,
|
|
1441
|
+
field_name="condition",
|
|
1436
1442
|
)
|
|
@@ -24,6 +24,7 @@ Example:
|
|
|
24
24
|
import re
|
|
25
25
|
from typing import ClassVar
|
|
26
26
|
|
|
27
|
+
from iam_validator.checks.utils.action_parser import get_action_case_insensitive, parse_action
|
|
27
28
|
from iam_validator.core.aws_service import AWSServiceFetcher
|
|
28
29
|
from iam_validator.core.check_registry import CheckConfig, PolicyCheck
|
|
29
30
|
from iam_validator.core.models import Statement, ValidationIssue
|
|
@@ -91,27 +92,25 @@ class ActionResourceMatchingCheck(PolicyCheck):
|
|
|
91
92
|
|
|
92
93
|
# Check each action
|
|
93
94
|
for action in actions:
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
# Parse service and action name
|
|
99
|
-
try:
|
|
100
|
-
service, action_name = action.split(":", 1)
|
|
101
|
-
except ValueError:
|
|
102
|
-
continue # Invalid action format, handled by action_validation
|
|
95
|
+
# Parse and validate action
|
|
96
|
+
parsed = parse_action(action)
|
|
97
|
+
if not parsed:
|
|
98
|
+
continue # Invalid action format (or "*"), handled by action_validation
|
|
103
99
|
|
|
104
100
|
# Skip wildcard actions
|
|
105
|
-
if
|
|
101
|
+
if parsed.has_wildcard:
|
|
106
102
|
continue
|
|
107
103
|
|
|
104
|
+
service = parsed.service
|
|
105
|
+
action_name = parsed.action_name
|
|
106
|
+
|
|
108
107
|
# Get service definition
|
|
109
108
|
service_detail = await fetcher.fetch_service_by_name(service)
|
|
110
109
|
if not service_detail:
|
|
111
110
|
continue # Unknown service, handled by action_validation
|
|
112
111
|
|
|
113
|
-
# Get action definition
|
|
114
|
-
action_detail = service_detail.actions
|
|
112
|
+
# Get action definition (case-insensitive since AWS actions are case-insensitive)
|
|
113
|
+
action_detail = get_action_case_insensitive(service_detail.actions, action_name)
|
|
115
114
|
if not action_detail:
|
|
116
115
|
continue # Unknown action, handled by action_validation
|
|
117
116
|
|
|
@@ -262,6 +261,7 @@ class ActionResourceMatchingCheck(PolicyCheck):
|
|
|
262
261
|
),
|
|
263
262
|
suggestion=suggestion,
|
|
264
263
|
line_number=line_number,
|
|
264
|
+
field_name="resource",
|
|
265
265
|
)
|
|
266
266
|
|
|
267
267
|
def _get_suggestion(
|
|
@@ -61,6 +61,7 @@ class ConditionKeyValidationCheck(PolicyCheck):
|
|
|
61
61
|
condition_key=condition_key,
|
|
62
62
|
line_number=line_number,
|
|
63
63
|
suggestion=result.suggestion,
|
|
64
|
+
field_name="condition",
|
|
64
65
|
)
|
|
65
66
|
)
|
|
66
67
|
# Only report once per condition key (not per action)
|
|
@@ -78,6 +79,7 @@ class ConditionKeyValidationCheck(PolicyCheck):
|
|
|
78
79
|
action=action,
|
|
79
80
|
condition_key=condition_key,
|
|
80
81
|
line_number=line_number,
|
|
82
|
+
field_name="condition",
|
|
81
83
|
)
|
|
82
84
|
)
|
|
83
85
|
# Only report once per condition key (not per action)
|
|
@@ -108,6 +108,7 @@ class ConditionTypeMismatchCheck(PolicyCheck):
|
|
|
108
108
|
statement_index=statement_idx,
|
|
109
109
|
issue_type="type_mismatch_usable",
|
|
110
110
|
line_number=line_number,
|
|
111
|
+
field_name="condition",
|
|
111
112
|
)
|
|
112
113
|
)
|
|
113
114
|
# Check if operator type matches key type
|
|
@@ -124,6 +125,7 @@ class ConditionTypeMismatchCheck(PolicyCheck):
|
|
|
124
125
|
issue_type="type_mismatch",
|
|
125
126
|
condition_key=condition_key,
|
|
126
127
|
line_number=line_number,
|
|
128
|
+
field_name="condition",
|
|
127
129
|
)
|
|
128
130
|
)
|
|
129
131
|
|
|
@@ -141,6 +143,7 @@ class ConditionTypeMismatchCheck(PolicyCheck):
|
|
|
141
143
|
issue_type="invalid_value_format",
|
|
142
144
|
condition_key=condition_key,
|
|
143
145
|
line_number=line_number,
|
|
146
|
+
field_name="condition",
|
|
144
147
|
)
|
|
145
148
|
)
|
|
146
149
|
|
|
@@ -71,6 +71,7 @@ class MFAConditionCheck(PolicyCheck):
|
|
|
71
71
|
statement_index=statement_idx,
|
|
72
72
|
issue_type="mfa_antipattern_bool_false",
|
|
73
73
|
line_number=line_number,
|
|
74
|
+
field_name="condition",
|
|
74
75
|
)
|
|
75
76
|
)
|
|
76
77
|
|
|
@@ -97,6 +98,7 @@ class MFAConditionCheck(PolicyCheck):
|
|
|
97
98
|
statement_index=statement_idx,
|
|
98
99
|
issue_type="mfa_antipattern_null_false",
|
|
99
100
|
line_number=line_number,
|
|
101
|
+
field_name="condition",
|
|
100
102
|
)
|
|
101
103
|
)
|
|
102
104
|
|
|
@@ -352,6 +352,7 @@ def validate_statement_structure(
|
|
|
352
352
|
message="`Statement` is missing the required `Effect` field",
|
|
353
353
|
suggestion="Add an `Effect` field with value `Allow` or `Deny`",
|
|
354
354
|
example='"Effect": "Allow"',
|
|
355
|
+
field_name="effect",
|
|
355
356
|
)
|
|
356
357
|
)
|
|
357
358
|
elif statement_dict["Effect"] not in VALID_EFFECTS:
|
|
@@ -364,6 +365,7 @@ def validate_statement_structure(
|
|
|
364
365
|
message=f"Invalid `Effect` value: `{statement_dict['Effect']}`. Must be `Allow` or `Deny`",
|
|
365
366
|
suggestion="Change `Effect` to either `Allow` or `Deny`",
|
|
366
367
|
example='"Effect": "Allow"',
|
|
368
|
+
field_name="effect",
|
|
367
369
|
)
|
|
368
370
|
)
|
|
369
371
|
|
|
@@ -379,6 +381,7 @@ def validate_statement_structure(
|
|
|
379
381
|
message=f"`Sid` must be a `string`, not `{type(sid).__name__}`",
|
|
380
382
|
suggestion='Wrap the `Sid` value in quotes to make it a string: `"Sid": "AllowS3Access"`',
|
|
381
383
|
example='"Sid": "AllowS3Access"',
|
|
384
|
+
field_name="sid",
|
|
382
385
|
)
|
|
383
386
|
)
|
|
384
387
|
elif not SID_PATTERN.match(sid):
|
|
@@ -393,6 +396,7 @@ def validate_statement_structure(
|
|
|
393
396
|
issue_type="invalid_sid_format",
|
|
394
397
|
message=f"`Sid` `{sid}` contains non-alphanumeric characters: `{invalid_chars}`",
|
|
395
398
|
suggestion="According to AWS IAM policy grammar, `Sid` should contain only alphanumeric characters `(A-Z, a-z, 0-9)`.",
|
|
399
|
+
field_name="sid",
|
|
396
400
|
)
|
|
397
401
|
)
|
|
398
402
|
|
|
@@ -406,6 +410,7 @@ def validate_statement_structure(
|
|
|
406
410
|
issue_type="principal_conflict",
|
|
407
411
|
message="`Statement` contains both `Principal` and `NotPrincipal` fields",
|
|
408
412
|
suggestion="Use either `Principal` or `NotPrincipal`, not both",
|
|
413
|
+
field_name="principal",
|
|
409
414
|
)
|
|
410
415
|
)
|
|
411
416
|
|
|
@@ -422,6 +427,7 @@ def validate_statement_structure(
|
|
|
422
427
|
issue_type="action_conflict",
|
|
423
428
|
message="`Statement` contains both `Action` and `NotAction` fields",
|
|
424
429
|
suggestion="Use either `Action` or `NotAction`, not both",
|
|
430
|
+
field_name="action",
|
|
425
431
|
)
|
|
426
432
|
)
|
|
427
433
|
elif not has_action and not has_not_action:
|
|
@@ -434,6 +440,7 @@ def validate_statement_structure(
|
|
|
434
440
|
message="`Statement` is missing both `Action` and `NotAction` fields",
|
|
435
441
|
suggestion="Add either an `Action` or `NotAction` field to specify which AWS actions this statement applies to",
|
|
436
442
|
example=('"Action": [\n "s3:GetObject",\n "s3:PutObject"\n]'),
|
|
443
|
+
field_name="action",
|
|
437
444
|
)
|
|
438
445
|
)
|
|
439
446
|
|
|
@@ -450,6 +457,7 @@ def validate_statement_structure(
|
|
|
450
457
|
issue_type="resource_conflict",
|
|
451
458
|
message="`Statement` contains both `Resource` and `NotResource` fields",
|
|
452
459
|
suggestion="Use either `Resource` or `NotResource`, not both",
|
|
460
|
+
field_name="resource",
|
|
453
461
|
)
|
|
454
462
|
)
|
|
455
463
|
elif not has_resource and not has_not_resource:
|
|
@@ -469,6 +477,7 @@ def validate_statement_structure(
|
|
|
469
477
|
message="`Statement` is missing both `Resource` and `NotResource` fields",
|
|
470
478
|
suggestion="Most policies require a `Resource` field. Add a `Resource` or `NotResource` field to specify which AWS resources this statement applies to.",
|
|
471
479
|
example=('"Resource": "*" OR "Resource": "arn:aws:s3:::my-bucket/*"'),
|
|
480
|
+
field_name="resource",
|
|
472
481
|
)
|
|
473
482
|
)
|
|
474
483
|
|
|
@@ -98,6 +98,7 @@ async def execute_policy(
|
|
|
98
98
|
' "Resource": "arn:aws:s3:::bucket/*"\n'
|
|
99
99
|
"}\n"
|
|
100
100
|
"```",
|
|
101
|
+
field_name="principal",
|
|
101
102
|
)
|
|
102
103
|
)
|
|
103
104
|
|
|
@@ -127,6 +128,7 @@ async def execute_policy(
|
|
|
127
128
|
' "Resource": "arn:aws:s3:::bucket/*"\n'
|
|
128
129
|
"}\n"
|
|
129
130
|
"```",
|
|
131
|
+
field_name="principal",
|
|
130
132
|
)
|
|
131
133
|
)
|
|
132
134
|
|
|
@@ -160,6 +162,7 @@ async def execute_policy(
|
|
|
160
162
|
" }\n"
|
|
161
163
|
"}\n"
|
|
162
164
|
"```",
|
|
165
|
+
field_name="principal",
|
|
163
166
|
)
|
|
164
167
|
)
|
|
165
168
|
|
|
@@ -182,6 +185,7 @@ async def execute_policy(
|
|
|
182
185
|
statement_sid=statement.sid,
|
|
183
186
|
line_number=statement.line_number,
|
|
184
187
|
suggestion="Change the `Effect` to `Deny` for this RCP statement.",
|
|
188
|
+
field_name="effect",
|
|
185
189
|
)
|
|
186
190
|
)
|
|
187
191
|
|
|
@@ -201,6 +205,7 @@ async def execute_policy(
|
|
|
201
205
|
statement_sid=statement.sid,
|
|
202
206
|
line_number=statement.line_number,
|
|
203
207
|
suggestion='Remove `NotPrincipal` and use `Principal: "*"` with `Condition` elements to restrict access.',
|
|
208
|
+
field_name="principal",
|
|
204
209
|
)
|
|
205
210
|
)
|
|
206
211
|
elif not has_principal:
|
|
@@ -215,6 +220,7 @@ async def execute_policy(
|
|
|
215
220
|
statement_sid=statement.sid,
|
|
216
221
|
line_number=statement.line_number,
|
|
217
222
|
suggestion='Add `Principal: "*"` to this RCP statement.',
|
|
223
|
+
field_name="principal",
|
|
218
224
|
)
|
|
219
225
|
)
|
|
220
226
|
elif statement.principal != "*":
|
|
@@ -232,6 +238,7 @@ async def execute_policy(
|
|
|
232
238
|
statement_sid=statement.sid,
|
|
233
239
|
line_number=statement.line_number,
|
|
234
240
|
suggestion='Change `Principal` to `"*"` and use `Condition` elements to restrict access.',
|
|
241
|
+
field_name="principal",
|
|
235
242
|
)
|
|
236
243
|
)
|
|
237
244
|
|
|
@@ -258,6 +265,7 @@ async def execute_policy(
|
|
|
258
265
|
line_number=statement.line_number,
|
|
259
266
|
suggestion="Replace `*` with service-specific actions from supported "
|
|
260
267
|
f"services: {', '.join(f'`{a}`' for a in sorted(rcp_supported_services))}",
|
|
268
|
+
field_name="action",
|
|
261
269
|
)
|
|
262
270
|
)
|
|
263
271
|
else:
|
|
@@ -282,6 +290,7 @@ async def execute_policy(
|
|
|
282
290
|
line_number=statement.line_number,
|
|
283
291
|
suggestion=f"Use only actions from supported RCP services: "
|
|
284
292
|
f"{', '.join(f'`{a}`' for a in sorted(rcp_supported_services))}",
|
|
293
|
+
field_name="action",
|
|
285
294
|
)
|
|
286
295
|
)
|
|
287
296
|
|
|
@@ -297,6 +306,7 @@ async def execute_policy(
|
|
|
297
306
|
statement_sid=statement.sid,
|
|
298
307
|
line_number=statement.line_number,
|
|
299
308
|
suggestion="Replace `NotAction` with `Action` element listing the specific actions to deny.",
|
|
309
|
+
field_name="action",
|
|
300
310
|
)
|
|
301
311
|
)
|
|
302
312
|
|
|
@@ -314,6 +324,7 @@ async def execute_policy(
|
|
|
314
324
|
statement_sid=statement.sid,
|
|
315
325
|
line_number=statement.line_number,
|
|
316
326
|
suggestion='Add `Resource: "*"` or specify specific resource ARNs.',
|
|
327
|
+
field_name="resource",
|
|
317
328
|
)
|
|
318
329
|
)
|
|
319
330
|
|
|
@@ -103,6 +103,7 @@ class PrincipalValidationCheck(PolicyCheck):
|
|
|
103
103
|
line_number=statement.line_number,
|
|
104
104
|
suggestion=f"Remove the `Principal` `{principal}` or add appropriate `Condition`s to restrict access. "
|
|
105
105
|
"Consider using more specific `Principal`s instead of `*` (wildcard).",
|
|
106
|
+
field_name="principal",
|
|
106
107
|
)
|
|
107
108
|
)
|
|
108
109
|
continue
|
|
@@ -122,6 +123,7 @@ class PrincipalValidationCheck(PolicyCheck):
|
|
|
122
123
|
line_number=statement.line_number,
|
|
123
124
|
suggestion=f"Add `{principal}` to the `allowed_principals` list in your config, "
|
|
124
125
|
"or use a `Principal` that matches an allowed pattern.",
|
|
126
|
+
field_name="principal",
|
|
125
127
|
)
|
|
126
128
|
)
|
|
127
129
|
continue
|
|
@@ -407,6 +409,7 @@ class PrincipalValidationCheck(PolicyCheck):
|
|
|
407
409
|
),
|
|
408
410
|
suggestion=self._build_any_of_suggestion(any_of),
|
|
409
411
|
line_number=statement.line_number,
|
|
412
|
+
field_name="principal",
|
|
410
413
|
)
|
|
411
414
|
)
|
|
412
415
|
|
|
@@ -568,6 +571,7 @@ class PrincipalValidationCheck(PolicyCheck):
|
|
|
568
571
|
suggestion=suggestion_text,
|
|
569
572
|
example=example_code,
|
|
570
573
|
line_number=statement.line_number,
|
|
574
|
+
field_name="principal",
|
|
571
575
|
)
|
|
572
576
|
|
|
573
577
|
def _build_condition_suggestion(
|
|
@@ -700,4 +704,5 @@ class PrincipalValidationCheck(PolicyCheck):
|
|
|
700
704
|
message=message,
|
|
701
705
|
suggestion=suggestion,
|
|
702
706
|
line_number=statement.line_number,
|
|
707
|
+
field_name="principal",
|
|
703
708
|
)
|
|
@@ -70,6 +70,7 @@ class ResourceValidationCheck(PolicyCheck):
|
|
|
70
70
|
resource=resource[:100] + "...",
|
|
71
71
|
suggestion="`ARN` is too long and may be invalid",
|
|
72
72
|
line_number=line_number,
|
|
73
|
+
field_name="resource",
|
|
73
74
|
)
|
|
74
75
|
)
|
|
75
76
|
continue
|
|
@@ -98,6 +99,7 @@ class ResourceValidationCheck(PolicyCheck):
|
|
|
98
99
|
resource=resource,
|
|
99
100
|
suggestion="`ARN` should follow format: `arn:partition:service:region:account-id:resource` (template variables like `${aws_account_id}` are supported)",
|
|
100
101
|
line_number=line_number,
|
|
102
|
+
field_name="resource",
|
|
101
103
|
)
|
|
102
104
|
)
|
|
103
105
|
else:
|
|
@@ -111,6 +113,7 @@ class ResourceValidationCheck(PolicyCheck):
|
|
|
111
113
|
resource=resource,
|
|
112
114
|
suggestion="`ARN` should follow format: `arn:partition:service:region:account-id:resource`",
|
|
113
115
|
line_number=line_number,
|
|
116
|
+
field_name="resource",
|
|
114
117
|
)
|
|
115
118
|
)
|
|
116
119
|
except Exception: # pylint: disable=broad-exception-caught
|
|
@@ -125,6 +128,7 @@ class ResourceValidationCheck(PolicyCheck):
|
|
|
125
128
|
resource=resource,
|
|
126
129
|
suggestion="`ARN` validation failed - may contain unexpected characters",
|
|
127
130
|
line_number=line_number,
|
|
131
|
+
field_name="resource",
|
|
128
132
|
)
|
|
129
133
|
)
|
|
130
134
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import ClassVar
|
|
4
4
|
|
|
5
|
+
from iam_validator.checks.utils.action_parser import parse_action
|
|
5
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.models import Statement, ValidationIssue
|
|
@@ -36,9 +37,10 @@ class ServiceWildcardCheck(PolicyCheck):
|
|
|
36
37
|
if action == "*":
|
|
37
38
|
continue
|
|
38
39
|
|
|
39
|
-
#
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
# Parse action and check if it's a service-level wildcard (e.g., "iam:*", "s3:*")
|
|
41
|
+
parsed = parse_action(action)
|
|
42
|
+
if parsed and parsed.action_name == "*":
|
|
43
|
+
service = parsed.service
|
|
42
44
|
|
|
43
45
|
# Check if this service is in the allowed list
|
|
44
46
|
if service not in allowed_services:
|
|
@@ -72,6 +74,7 @@ class ServiceWildcardCheck(PolicyCheck):
|
|
|
72
74
|
suggestion=suggestion,
|
|
73
75
|
example=example if example else None,
|
|
74
76
|
line_number=statement.line_number,
|
|
77
|
+
field_name="action",
|
|
75
78
|
)
|
|
76
79
|
)
|
|
77
80
|
|
|
@@ -103,6 +103,7 @@ class SetOperatorValidationCheck(PolicyCheck):
|
|
|
103
103
|
issue_type="set_operator_on_single_valued_key",
|
|
104
104
|
condition_key=condition_key,
|
|
105
105
|
line_number=line_number,
|
|
106
|
+
field_name="condition",
|
|
106
107
|
)
|
|
107
108
|
)
|
|
108
109
|
|
|
@@ -123,6 +124,7 @@ class SetOperatorValidationCheck(PolicyCheck):
|
|
|
123
124
|
issue_type="forallvalues_allow_without_null_check",
|
|
124
125
|
condition_key=condition_key,
|
|
125
126
|
line_number=line_number,
|
|
127
|
+
field_name="condition",
|
|
126
128
|
)
|
|
127
129
|
)
|
|
128
130
|
|
|
@@ -142,6 +144,7 @@ class SetOperatorValidationCheck(PolicyCheck):
|
|
|
142
144
|
statement_sid=statement_sid,
|
|
143
145
|
statement_index=statement_idx,
|
|
144
146
|
issue_type="foranyvalue_deny_without_null_check",
|
|
147
|
+
field_name="condition",
|
|
145
148
|
condition_key=condition_key,
|
|
146
149
|
line_number=line_number,
|
|
147
150
|
)
|
|
@@ -70,6 +70,7 @@ def _check_sid_uniqueness_impl(policy: IAMPolicy, severity: str) -> list[Validat
|
|
|
70
70
|
message=issue_msg,
|
|
71
71
|
suggestion=suggestion,
|
|
72
72
|
line_number=statement.line_number,
|
|
73
|
+
field_name="sid",
|
|
73
74
|
)
|
|
74
75
|
)
|
|
75
76
|
|
|
@@ -99,6 +100,7 @@ def _check_sid_uniqueness_impl(policy: IAMPolicy, severity: str) -> list[Validat
|
|
|
99
100
|
message=f"Statement ID `{duplicate_sid}` is used **{count} times** in this policy (found in statements `{statement_numbers}`)",
|
|
100
101
|
suggestion="Change this SID to a unique value. Statement IDs help identify and reference specific statements, so duplicates can cause confusion.",
|
|
101
102
|
line_number=statement.line_number,
|
|
103
|
+
field_name="sid",
|
|
102
104
|
)
|
|
103
105
|
)
|
|
104
106
|
|