iam-policy-validator 1.7.2__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.
Files changed (38) hide show
  1. {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.8.0.dist-info}/METADATA +22 -6
  2. {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.8.0.dist-info}/RECORD +38 -35
  3. iam_validator/__version__.py +1 -1
  4. iam_validator/checks/__init__.py +5 -3
  5. iam_validator/checks/action_condition_enforcement.py +61 -23
  6. iam_validator/checks/action_resource_matching.py +6 -2
  7. iam_validator/checks/action_validation.py +1 -1
  8. iam_validator/checks/condition_key_validation.py +1 -1
  9. iam_validator/checks/condition_type_mismatch.py +6 -6
  10. iam_validator/checks/policy_structure.py +577 -0
  11. iam_validator/checks/policy_type_validation.py +48 -32
  12. iam_validator/checks/principal_validation.py +65 -133
  13. iam_validator/checks/resource_validation.py +8 -8
  14. iam_validator/checks/sensitive_action.py +7 -3
  15. iam_validator/checks/service_wildcard.py +2 -2
  16. iam_validator/checks/set_operator_validation.py +11 -11
  17. iam_validator/checks/sid_uniqueness.py +8 -4
  18. iam_validator/checks/trust_policy_validation.py +512 -0
  19. iam_validator/checks/utils/sensitive_action_matcher.py +26 -26
  20. iam_validator/checks/utils/wildcard_expansion.py +1 -1
  21. iam_validator/checks/wildcard_action.py +3 -1
  22. iam_validator/checks/wildcard_resource.py +3 -1
  23. iam_validator/commands/validate.py +6 -12
  24. iam_validator/core/__init__.py +1 -2
  25. iam_validator/core/access_analyzer.py +1 -1
  26. iam_validator/core/access_analyzer_report.py +2 -2
  27. iam_validator/core/aws_fetcher.py +45 -43
  28. iam_validator/core/check_registry.py +83 -79
  29. iam_validator/core/config/condition_requirements.py +69 -17
  30. iam_validator/core/config/defaults.py +58 -52
  31. iam_validator/core/config/service_principals.py +40 -3
  32. iam_validator/core/ignore_patterns.py +297 -0
  33. iam_validator/core/models.py +15 -5
  34. iam_validator/core/policy_checks.py +31 -472
  35. iam_validator/core/policy_loader.py +27 -4
  36. {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.8.0.dist-info}/WHEEL +0 -0
  37. {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.8.0.dist-info}/entry_points.txt +0 -0
  38. {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.8.0.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.2
3
+ Version: 1.8.0
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
@@ -242,8 +242,13 @@ results = await validate_policies(policies)
242
242
 
243
243
  **All checks are fully configurable** - Enable/disable checks, adjust severity levels, add custom requirements, and define ignore patterns through the configuration file.
244
244
 
245
+ ### Core Checks (18 always-on + 1 opt-in)
246
+
247
+ The validator includes **19 built-in checks** organized into three categories:
248
+
245
249
  ### AWS Correctness Checks (12)
246
250
  Validates policies against AWS IAM requirements:
251
+ - **Policy structure** - Validates fundamental IAM policy grammar (Version, Effect, required fields, conflicts)
247
252
  - **Action validation** - Verify actions exist in AWS services
248
253
  - **Condition key validation** - Check condition keys are valid for actions
249
254
  - **Condition type matching** - Ensure condition values match expected types
@@ -255,7 +260,6 @@ Validates policies against AWS IAM requirements:
255
260
  - **MFA condition patterns** - Detect common MFA anti-patterns
256
261
  - **Policy type validation** - Enforce policy type requirements (RCP, SCP, etc.)
257
262
  - **Action-resource matching** - Detect impossible action-resource combinations
258
- - **Action-resource constraints** - Validate service-specific constraints
259
263
 
260
264
  ### Security Best Practices (6)
261
265
  Identifies security risks and overly permissive permissions:
@@ -266,6 +270,15 @@ Identifies security risks and overly permissive permissions:
266
270
  - **Sensitive actions** - ~490 actions across 4 risk categories requiring conditions
267
271
  - **Action condition enforcement** - Enforce required conditions (MFA, IP, SourceArn, etc.)
268
272
 
273
+ ### Trust Policy Validation (1 - Opt-in, Disabled by Default)
274
+ Specialized validation for role assumption policies:
275
+ - **Trust policy validation** - Validates action-principal coupling for assume role actions
276
+ - Ensures correct principal types (`AssumeRoleWithSAML` → Federated, etc.)
277
+ - Validates SAML/OIDC provider ARN formats
278
+ - Enforces required conditions (`SAML:aud`, OIDC audience, etc.)
279
+ - Use with `--policy-type TRUST_POLICY` flag
280
+ - See [Trust Policy Examples](examples/trust-policies/README.md)
281
+
269
282
  ### Configuration & Customization
270
283
 
271
284
  All checks can be customized via a yaml configuration file ex: `.iam-validator.yaml`:
@@ -325,10 +338,11 @@ ignore_patterns:
325
338
  ```
326
339
 
327
340
  **📖 Complete documentation:**
328
- - [Check Reference Guide](docs/check-reference.md) - All 18 checks with examples
341
+ - [Check Reference Guide](docs/check-reference.md) - All 19 checks with examples
329
342
  - [Configuration Guide](docs/configuration.md) - Full configuration options
330
343
  - [Condition Requirements](docs/condition-requirements.md) - Action-specific requirements
331
344
  - [Privilege Escalation Detection](docs/privilege-escalation.md) - How privilege escalation works
345
+ - [Trust Policy Validation](examples/trust-policies/README.md) - Trust policy examples and validation
332
346
 
333
347
  ## Output Formats & GitHub Integration
334
348
 
@@ -357,7 +371,7 @@ ignore_patterns:
357
371
 
358
372
  ## AWS Access Analyzer (Optional)
359
373
 
360
- In addition to the 18 built-in checks, optionally enable AWS Access Analyzer for additional validation capabilities that require AWS credentials:
374
+ In addition to the 19 built-in checks, optionally enable AWS Access Analyzer for additional validation capabilities that require AWS credentials:
361
375
 
362
376
  ### Access Analyzer Capabilities
363
377
 
@@ -394,16 +408,18 @@ iam-validator analyze --path bucket-policy.json \
394
408
  ## 📚 Documentation
395
409
 
396
410
  **Guides:**
397
- - [Check Reference](docs/check-reference.md) - All 18 checks with examples
411
+ - [Check Reference](docs/check-reference.md) - All 19 checks with examples
398
412
  - [Configuration Guide](docs/configuration.md) - Customize checks and behavior
399
413
  - [GitHub Actions Guide](docs/github-actions-workflows.md) - CI/CD integration
400
414
  - [Python Library Guide](docs/python-library-usage.md) - Use as Python package
415
+ - [Trust Policy Guide](examples/trust-policies/README.md) - Trust policy validation
401
416
  - [Contributing Guide](CONTRIBUTING.md) - How to contribute
402
417
 
403
418
  **Examples:**
404
- - [Configuration Examples](examples/configs/) - 9 config file templates
419
+ - [Configuration Examples](examples/configs/) - 9+ config file templates
405
420
  - [Workflow Examples](examples/github-actions/) - GitHub Actions workflows
406
421
  - [Custom Checks](examples/custom_checks/) - Add your own validation rules
422
+ - [Trust Policies](examples/trust-policies/) - Trust policy examples
407
423
 
408
424
  ## 🤝 Contributing
409
425
 
@@ -1,58 +1,61 @@
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=0niAY6KgsXeeyFV5nTmvfvem16X3OTrO_DItEqsW74A,361
4
- iam_validator/checks/__init__.py,sha256=eDiDlVon0CwWGSBnZgM-arn1i5R5ZSG89pgR-ifETxE,1782
5
- iam_validator/checks/action_condition_enforcement.py,sha256=VhFEGbkcgkRwNRRuslvat5uib2tlH2Nr6sltbAQTs6I,36834
6
- iam_validator/checks/action_resource_matching.py,sha256=sk67jcDF1WzW4tPgcRSdTj4UBe2stALdwHx5ViVA9dU,19207
7
- iam_validator/checks/action_validation.py,sha256=IpxtTsk58f2zEZ-xzAoyHw4QK8BCRV43OffP-8ydf9E,2578
8
- iam_validator/checks/condition_key_validation.py,sha256=10XxTwIcr887CbgmN90jfRZabj5RHo08dGa8csM50Fo,3980
9
- iam_validator/checks/condition_type_mismatch.py,sha256=JyiAOyUZShzXZI8dgycL4oqwRkpJYUPwoGX4zigsi5I,10613
3
+ iam_validator/__version__.py,sha256=3gyLuIf6oV1A2rFZSmXco3rdq2hmgGAYEB2we_Su_TU,361
4
+ iam_validator/checks/__init__.py,sha256=OTkPnmlelu4YjMO8krjhu2wXiTV72RzopA5u1SfPQA0,1990
5
+ iam_validator/checks/action_condition_enforcement.py,sha256=XztLhckc3Xc5RwxlMM2U2M81TNyt9M5xgBwn5fqHK1M,39141
6
+ iam_validator/checks/action_resource_matching.py,sha256=BTrweeD1SNy9hcsfIDEN4L1E7oLNiVLyg-yerlZ5eC0,19356
7
+ iam_validator/checks/action_validation.py,sha256=t8JPbvLiQb0Lx7SF_gruxehVgy0WU5uWppDJhhsSnI0,2580
8
+ iam_validator/checks/condition_key_validation.py,sha256=VaOo1CLrMVRi23oUE1nMlKAgzNY9_jrnnEfGbdfF53g,3982
9
+ iam_validator/checks/condition_type_mismatch.py,sha256=VK7KxkdeX2FmKWJUaZwJXSFQUxqtnqGj5sigOuhGCWo,10707
10
10
  iam_validator/checks/full_wildcard.py,sha256=ywD762BOV8WxFuTTARkaGMJn27f3ZZVuZUjKo8URnTc,2281
11
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
- iam_validator/checks/policy_type_validation.py,sha256=9qmrA8CXwsVpCU4rT0RrqDXgVOzNamMEpdg3cXWAtBI,15213
14
- iam_validator/checks/principal_validation.py,sha256=gTv_TqJDspGEX3iJkHXrw3DyKMJeyE33uQakZ0PjNoo,29969
15
- iam_validator/checks/resource_validation.py,sha256=fGi9QuX-lIHDtLm8xB3VReFFhbZpQ2Yub-FKRafQCkw,5984
16
- iam_validator/checks/sensitive_action.py,sha256=0vuhF1UkAH_vxhfHsC8xk68aJXHvI7c9KTLcJFNlnHM,9652
17
- iam_validator/checks/service_wildcard.py,sha256=1ynXLG6_82SIH8aHP88qQojJf38ZH0agnSmHp0VkZ98,4010
18
- iam_validator/checks/set_operator_validation.py,sha256=1XjOdf-xk-m6m1bODuHsELZccriGqOJTDI-HCcuId80,7464
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
13
+ iam_validator/checks/policy_structure.py,sha256=CQh1FsqVOFENAQe7mbyRzlIntjMs0gAwv6wUUqsdXqc,22636
14
+ iam_validator/checks/policy_type_validation.py,sha256=8wnaCmvFb_q4ToqPR8B1exwFdwwqXYEf-Dw8YUgnj0s,15873
15
+ iam_validator/checks/principal_validation.py,sha256=HYxcUtTWDvWh9AZbqtUSWrZOJrNGALRu1jrDydNF3xk,27895
16
+ iam_validator/checks/resource_validation.py,sha256=8FAVPxAsralAPlao5GeJNSutqKC7DLWYZaI7CHhLs_E,6052
17
+ iam_validator/checks/sensitive_action.py,sha256=1-Bumsk_fPHTCW0iRmIHaqMaQY-1uYI5vH2uO3YGvf0,9765
18
+ iam_validator/checks/service_wildcard.py,sha256=brdm4cm7bOgpHB8jyAzGEGPQzoytaFIy5CgfUVgamv8,4012
19
+ iam_validator/checks/set_operator_validation.py,sha256=AnOC7Ywd3tk7zdqNkQXAAdtsiCxKwVQ6pGsbpsopZfY,7465
20
+ iam_validator/checks/sid_uniqueness.py,sha256=ZScVKa1RROhrY2uSOUSeeIK4xXNVzfnp9hn5Jr-TVHk,6984
21
+ iam_validator/checks/trust_policy_validation.py,sha256=q3YFqMk3MYa4pEr1CPCCPvMcbv8-6rzT74EfsgZ6k0k,17880
22
+ iam_validator/checks/wildcard_action.py,sha256=-yS4o2SgDRMEp_SA_kcwmmf6do0gWppkP1rpg7IP9Zg,2023
23
+ iam_validator/checks/wildcard_resource.py,sha256=zsz7nfvvqnOYLMUfM1w7E46267Y4SrBF4x1Nvgg-B7U,5467
22
24
  iam_validator/checks/utils/__init__.py,sha256=j0X4ibUB6RGx2a-kNoJnlVZwHfoEvzZsIeTmJIAoFzA,45
23
25
  iam_validator/checks/utils/policy_level_checks.py,sha256=2V60C0zhKfsFPjQ-NMlD3EemtwA9S6-4no8nETgXdQE,5274
24
- iam_validator/checks/utils/sensitive_action_matcher.py,sha256=tcWK4nImpSVNia0FUsN2uLK9LM5EnzjRFtaPQLHZaLw,10667
25
- iam_validator/checks/utils/wildcard_expansion.py,sha256=fSSoquVdVZaVWS_qBxAx7LMOzxgHed4ffQ6OAZnuqos,3132
26
+ iam_validator/checks/utils/sensitive_action_matcher.py,sha256=qDXcJa_2sCJu9pBbjDlI7x5lPtLRc6jQCpKPMheCOJQ,11215
27
+ iam_validator/checks/utils/wildcard_expansion.py,sha256=JK8iYcYpOzi5RN4IvIMBCbzkCEhVRJxZDKLZIKgD8nY,3131
26
28
  iam_validator/commands/__init__.py,sha256=M-5bo8w0TCWydK0cXgJyPD2fmk8bpQs-3b26YbgLzlc,565
27
29
  iam_validator/commands/analyze.py,sha256=rvLBJ5_A3HB530xtixhaIsC19QON68olEQnn8TievgI,20784
28
30
  iam_validator/commands/base.py,sha256=5baCCMwxz7pdQ6XMpWfXFNz7i1l5dB8Qv9dKKR04Gzs,1074
29
31
  iam_validator/commands/cache.py,sha256=p4ucRVuh42sbK3Lk0b610L3ofAR5TnUreF00fpO6VFg,14219
30
32
  iam_validator/commands/download_services.py,sha256=KKz3ybMLT8DQUf9aFZ0tilJ-o1b6PE8Pf1pC4K6cT8I,9175
31
33
  iam_validator/commands/post_to_pr.py,sha256=CvUXs2xvO-UhluxdfNM6F0TCWD8hDBEOiYw60fm1Dms,2363
32
- iam_validator/commands/validate.py,sha256=2v91ogbEzKfjk2u6Y4NO0yvsCOwxi9jXoqD7acBbVTE,23624
33
- iam_validator/core/__init__.py,sha256=1FvJPMrbzJfS9YbRUJCshJLd5gzWwR9Fd_slS0Aq9c8,416
34
- iam_validator/core/access_analyzer.py,sha256=8GgkR-vCkCtSxtXGywvQNBPYq-rvDLexUuLSyflq0V4,24520
35
- iam_validator/core/access_analyzer_report.py,sha256=O17gagknvkNMTTlq7BrLM68FjlCEm4LjIKD9oqxEbPg,24860
36
- iam_validator/core/aws_fetcher.py,sha256=obTzxHD9pMsWo-SojSOeWyw2s2_St-LNgbmh5BGEM9c,41215
37
- iam_validator/core/check_registry.py,sha256=cMjtJROkZOLzXxl-mTdLYHdxyajNnOsaHGs-EeaSZ7k,21741
34
+ iam_validator/commands/validate.py,sha256=hau8oPCNGya8gAUrvbhQxAlR4fhaGKzl7MQSryBgQfQ,23381
35
+ iam_validator/core/__init__.py,sha256=JyeUUjuVsXFPkxLjsZ9omLPAFudWR-lQ_8oBX9jTY70,376
36
+ iam_validator/core/access_analyzer.py,sha256=mtMaY-FnKjKEVITky_9ywZe1FaCAm61ElRv5Z_ZeC7E,24562
37
+ iam_validator/core/access_analyzer_report.py,sha256=UMm2RNGj2rAKav1zsCw_htQZZRwRC0jjayd2zvKma1A,24896
38
+ iam_validator/core/aws_fetcher.py,sha256=U9aE1kJ6HqJYYFpT8bq90dAAKGvmy8wKV6v_1aQCno4,41741
39
+ iam_validator/core/check_registry.py,sha256=5hmhKiF45cenLYviGMAEhb08_Fqvu9PFxXzBmLEQuqk,22639
38
40
  iam_validator/core/cli.py,sha256=PkXiZjlgrQ21QustBbspefYsdbxst4gxoClyG2_HQR8,3843
39
41
  iam_validator/core/condition_validators.py,sha256=7zBjlcf2xGFKGbcFrXSLvWT5tFhWxoqwzhsJqS2E8uY,21524
40
42
  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
43
+ iam_validator/core/ignore_patterns.py,sha256=pZqDJBtkbck-85QK5eFPM5ZOPEKs3McRh3avqiCT5z0,10398
44
+ iam_validator/core/models.py,sha256=f5d9ovtO1xMSwhyBrKIgc2psEq0eugnd3S3ioqurqEE,13242
45
+ iam_validator/core/policy_checks.py,sha256=le1ovFm3qcgq3JG-L336h4nCSfol0PzL65RC3Beslwk,8520
46
+ iam_validator/core/policy_loader.py,sha256=2KJnXzGg3g9pDXWZHk3DO0xpZnZZ-wXWFEOdQ_naJ8s,17862
44
47
  iam_validator/core/pr_commenter.py,sha256=MU-t7SfdHUpSc6BDbh8_dNAbxDiG-bZBCry-jUXivAc,15066
45
48
  iam_validator/core/report.py,sha256=kzSeWnT1LqWZVA5pqKKz-maVowXVj0djdoShfRhhpz4,35899
46
49
  iam_validator/core/config/__init__.py,sha256=CWSyIA7kEyzrskEenjYbs9Iih10BXRpiY9H2dHg61rU,2671
47
50
  iam_validator/core/config/aws_api.py,sha256=HLIzOItQ0A37wxHcgWck6ZFO0wmNY8JNTiWMMK6JKYU,1248
48
51
  iam_validator/core/config/aws_global_conditions.py,sha256=gdmMxXGBy95B3uYUG-J7rnM6Ixgc6L7Y9Pcd2XAMb60,7170
49
52
  iam_validator/core/config/category_suggestions.py,sha256=QlrYi4BTkxDSTlL7NZGE9BWN-atWetZ6XjkI9F_7YzI,4370
50
- iam_validator/core/config/condition_requirements.py,sha256=1PuADTB9pLqh-kNUGC7kSU6LMLtXMSc003tvI7qKeAY,5170
53
+ iam_validator/core/config/condition_requirements.py,sha256=qauIP73HFnOw1dchUeFpg1x7Y7QWkILo3GfxV_dxdQo,7696
51
54
  iam_validator/core/config/config_loader.py,sha256=qKD8aR8YAswaFf68pnYJLFNwKznvcc6lNxSQWU3i6SY,17713
52
- iam_validator/core/config/defaults.py,sha256=mCOr_YgiRQp6fThtxrcjMtm-LPdZQbd6AS16gLzV17c,27589
55
+ iam_validator/core/config/defaults.py,sha256=rWzDrlw0AAudtm_If6zjNFvruLg71jpLJEdRgKYSKMQ,27917
53
56
  iam_validator/core/config/principal_requirements.py,sha256=VCX7fBDgeDTJQyoz7_x7GI7Kf9O1Eu-sbihoHOrKv6o,15105
54
57
  iam_validator/core/config/sensitive_actions.py,sha256=uATDIp_TD3OQQlsYTZp79qd1mSK2Bf9hJ0JwcqLBr84,25344
55
- iam_validator/core/config/service_principals.py,sha256=gQSROsxUWBD6P2F9qP320UZV4lHGlsyvHSkMyy0njrU,2685
58
+ iam_validator/core/config/service_principals.py,sha256=8pys5H_yycVJ9KTyimAKFYBg83Aol2Iri53wiHjtnEM,3959
56
59
  iam_validator/core/config/wildcards.py,sha256=H_v6hb-rZ0UUz4cul9lxkVI39e6knaK4Y-MbWz2Ebpw,3228
57
60
  iam_validator/core/formatters/__init__.py,sha256=fnCKAEBXItnOf2m4rhVs7zwMaTxbG6ESh3CF8V5j5ec,868
58
61
  iam_validator/core/formatters/base.py,sha256=SShDeDiy5mYQnS6BpA8xYg91N-KX1EObkOtlrVHqx1Q,4451
@@ -77,8 +80,8 @@ iam_validator/utils/__init__.py,sha256=NveA2F3G1E6-ANZzFr7J6Q6u5mogvMp862iFokmYu
77
80
  iam_validator/utils/cache.py,sha256=wOQKOBeoG6QqC5f0oXcHz63Cjtu_-SsSS-0pTSwyAiM,3254
78
81
  iam_validator/utils/regex.py,sha256=xHoMECttb7qaMhts-c9b0GIxdhHNZTt-UBr7wNhWfzg,6219
79
82
  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,,
83
+ iam_policy_validator-1.8.0.dist-info/METADATA,sha256=vccDFxkghziff8eO-K7hIVreQjE5gkLsWrIS5b61EwY,16174
84
+ iam_policy_validator-1.8.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
85
+ iam_policy_validator-1.8.0.dist-info/entry_points.txt,sha256=8HtWd8O7mvPiPdZR5YbzY8or_qcqLM4-pKaFdhtFT8M,62
86
+ iam_policy_validator-1.8.0.dist-info/licenses/LICENSE,sha256=AMnbFTBDcK4_MITe2wiQBkj0vg-jjBBhsc43ydC7tt4,1098
87
+ iam_policy_validator-1.8.0.dist-info/RECORD,,
@@ -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.7.2"
6
+ __version__ = "1.8.0"
7
7
  # Parse version, handling pre-release suffixes like -rc, -alpha, -beta
8
8
  _version_base = __version__.split("-")[0] # Remove pre-release suffix if present
9
9
  __version_info__ = tuple(int(part) for part in _version_base.split("."))
@@ -5,21 +5,21 @@ Built-in policy checks for IAM Policy Validator.
5
5
  from iam_validator.checks.action_condition_enforcement import (
6
6
  ActionConditionEnforcementCheck,
7
7
  )
8
- from iam_validator.checks.action_resource_matching import (
9
- ActionResourceMatchingCheck,
10
- )
8
+ from iam_validator.checks.action_resource_matching import ActionResourceMatchingCheck
11
9
  from iam_validator.checks.action_validation import ActionValidationCheck
12
10
  from iam_validator.checks.condition_key_validation import ConditionKeyValidationCheck
13
11
  from iam_validator.checks.condition_type_mismatch import ConditionTypeMismatchCheck
14
12
  from iam_validator.checks.full_wildcard import FullWildcardCheck
15
13
  from iam_validator.checks.mfa_condition_check import MFAConditionCheck
16
14
  from iam_validator.checks.policy_size import PolicySizeCheck
15
+ from iam_validator.checks.policy_structure import PolicyStructureCheck
17
16
  from iam_validator.checks.principal_validation import PrincipalValidationCheck
18
17
  from iam_validator.checks.resource_validation import ResourceValidationCheck
19
18
  from iam_validator.checks.sensitive_action import SensitiveActionCheck
20
19
  from iam_validator.checks.service_wildcard import ServiceWildcardCheck
21
20
  from iam_validator.checks.set_operator_validation import SetOperatorValidationCheck
22
21
  from iam_validator.checks.sid_uniqueness import SidUniquenessCheck
22
+ from iam_validator.checks.trust_policy_validation import TrustPolicyValidationCheck
23
23
  from iam_validator.checks.wildcard_action import WildcardActionCheck
24
24
  from iam_validator.checks.wildcard_resource import WildcardResourceCheck
25
25
 
@@ -32,12 +32,14 @@ __all__ = [
32
32
  "FullWildcardCheck",
33
33
  "MFAConditionCheck",
34
34
  "PolicySizeCheck",
35
+ "PolicyStructureCheck",
35
36
  "PrincipalValidationCheck",
36
37
  "ResourceValidationCheck",
37
38
  "SensitiveActionCheck",
38
39
  "ServiceWildcardCheck",
39
40
  "SetOperatorValidationCheck",
40
41
  "SidUniquenessCheck",
42
+ "TrustPolicyValidationCheck",
41
43
  "WildcardActionCheck",
42
44
  "WildcardResourceCheck",
43
45
  ]
@@ -197,15 +197,16 @@ class ActionConditionEnforcementCheck(PolicyCheck):
197
197
  description = requirement.get("description", "These actions should not be used")
198
198
  # Use per-requirement severity if specified, else use global
199
199
  severity = requirement.get("severity", self.get_severity(config))
200
+ matching_actions_formatted = ", ".join(f"`{a}`" for a in matching_actions)
200
201
  issues.append(
201
202
  ValidationIssue(
202
203
  severity=severity,
203
204
  statement_sid=statement.sid,
204
205
  statement_index=statement_idx,
205
206
  issue_type="forbidden_action_present",
206
- message=f"FORBIDDEN: Actions {matching_actions} should not be used. {description}",
207
+ message=f"FORBIDDEN: Actions {matching_actions_formatted} should not be used. {description}",
207
208
  action=", ".join(matching_actions),
208
- suggestion=f"Remove these forbidden actions from the statement: {', '.join(matching_actions)}. {description}",
209
+ suggestion=f"Remove these forbidden actions from the statement: {matching_actions_formatted}. {description}",
209
210
  line_number=statement.line_number,
210
211
  )
211
212
  )
@@ -272,7 +273,7 @@ class ActionConditionEnforcementCheck(PolicyCheck):
272
273
  # Collect all statements that match the action criteria
273
274
  matching_statements: list[tuple[int, Statement, list[str]]] = []
274
275
 
275
- for idx, statement in enumerate(policy.statement):
276
+ for idx, statement in enumerate(policy.statement or []):
276
277
  # Only check Allow statements
277
278
  if statement.effect != "Allow":
278
279
  continue
@@ -308,6 +309,7 @@ class ActionConditionEnforcementCheck(PolicyCheck):
308
309
 
309
310
  # Use the first matching statement's index for the issue
310
311
  first_idx, first_stmt, _ = matching_statements[0]
312
+ all_actions_formatted = ", ".join(f"`{a}`" for a in sorted(all_actions))
311
313
 
312
314
  issues.append(
313
315
  ValidationIssue(
@@ -315,7 +317,7 @@ class ActionConditionEnforcementCheck(PolicyCheck):
315
317
  statement_sid=first_stmt.sid,
316
318
  statement_index=first_idx,
317
319
  issue_type="policy_level_action_detected",
318
- message=f"POLICY-LEVEL: Actions {sorted(all_actions)} found in {len(matching_statements)} statement(s). {description}",
320
+ message=f"POLICY-LEVEL: Actions {all_actions_formatted} found in {len(matching_statements)} statement(s). {description}",
319
321
  action=", ".join(sorted(all_actions)),
320
322
  suggestion=f"Review these statements: {', '.join(statement_refs)}. {description}",
321
323
  line_number=first_stmt.line_number,
@@ -530,7 +532,7 @@ class ActionConditionEnforcementCheck(PolicyCheck):
530
532
  available_actions = list(service_detail.actions.keys())
531
533
 
532
534
  # Find which actual AWS actions the wildcard would grant
533
- _, granted_actions = fetcher._match_wildcard_action(
535
+ _, granted_actions = fetcher.match_wildcard_action(
534
536
  statement_action.split(":", 1)[1], # Just the action part (e.g., "C*")
535
537
  available_actions,
536
538
  )
@@ -545,7 +547,7 @@ class ActionConditionEnforcementCheck(PolicyCheck):
545
547
  except re.error:
546
548
  continue
547
549
 
548
- except (ValueError, Exception):
550
+ except (ValueError, Exception): # pylint: disable=broad-exception-caught
549
551
  # If we can't fetch the service or parse the action, fall back to prefix matching
550
552
  stmt_prefix = statement_action.rstrip("*")
551
553
  for pattern in patterns:
@@ -627,7 +629,20 @@ class ActionConditionEnforcementCheck(PolicyCheck):
627
629
 
628
630
  if not any_present:
629
631
  # Create a combined error for any_of
630
- condition_keys = [cond.get("condition_key", "unknown") for cond in any_of]
632
+ # Handle both simple conditions and nested all_of
633
+ condition_keys = []
634
+ for cond in any_of:
635
+ if "all_of" in cond:
636
+ # Nested all_of - collect all condition keys
637
+ nested_keys = [
638
+ c.get("condition_key", "unknown") for c in cond["all_of"]
639
+ ]
640
+ condition_keys.append(f"({' + '.join(f'`{k}`' for k in nested_keys)})")
641
+ else:
642
+ # Simple condition
643
+ condition_keys.append(f"`{cond.get('condition_key', 'unknown')}`")
644
+ condition_keys_formatted = " OR ".join(condition_keys)
645
+ matching_actions_formatted = ", ".join(f"`{a}`" for a in matching_actions)
631
646
  issues.append(
632
647
  ValidationIssue(
633
648
  severity=self.get_severity(config),
@@ -635,8 +650,8 @@ class ActionConditionEnforcementCheck(PolicyCheck):
635
650
  statement_index=statement_idx,
636
651
  issue_type="missing_required_condition_any_of",
637
652
  message=(
638
- f"Actions {matching_actions} require at least ONE of these conditions: "
639
- f"{', '.join(condition_keys)}"
653
+ f"Actions `{matching_actions_formatted}` require at least ONE of these conditions: "
654
+ f"{condition_keys_formatted}"
640
655
  ),
641
656
  action=", ".join(matching_actions),
642
657
  suggestion=self._build_any_of_suggestion(any_of),
@@ -773,12 +788,13 @@ class ActionConditionEnforcementCheck(PolicyCheck):
773
788
  condition_key, description, example, expected_value, operator
774
789
  )
775
790
 
791
+ matching_actions_str = ", ".join(f"`{a}`" for a in matching_actions)
776
792
  return ValidationIssue(
777
793
  severity=severity,
778
794
  statement_sid=statement.sid,
779
795
  statement_index=statement_idx,
780
796
  issue_type="missing_required_condition",
781
- message=f"{message_prefix} Action(s) {matching_actions} require condition '{condition_key}'",
797
+ message=f"{message_prefix} Action(s) `{matching_actions_str}` require condition `{condition_key}`",
782
798
  action=", ".join(matching_actions),
783
799
  condition_key=condition_key,
784
800
  suggestion=suggestion_text,
@@ -843,15 +859,38 @@ class ActionConditionEnforcementCheck(PolicyCheck):
843
859
  suggestions.append("Add at least ONE of these conditions:")
844
860
 
845
861
  for i, cond in enumerate(any_of_conditions, 1):
846
- condition_key = cond.get("condition_key", "unknown")
847
- description = cond.get("description", "")
848
- expected_value = cond.get("expected_value")
862
+ # Handle nested all_of blocks
863
+ if "all_of" in cond:
864
+ # Nested all_of - show all required conditions together
865
+ all_of_list = cond["all_of"]
866
+ condition_keys = [c.get("condition_key", "unknown") for c in all_of_list]
867
+ condition_keys_formatted = " + ".join(f"`{k}`" for k in condition_keys)
868
+
869
+ option = f"\n- **Option {i}**: {condition_keys_formatted} (both required)"
870
+
871
+ # Use description from first condition or combine them
872
+ descriptions = [
873
+ c.get("description", "") for c in all_of_list if c.get("description")
874
+ ]
875
+ if descriptions:
876
+ option += f" - {descriptions[0]}"
877
+
878
+ # Show example from first condition that has one
879
+ for c in all_of_list:
880
+ if c.get("example"):
881
+ # Example will be shown separately, just note it's available
882
+ break
883
+ else:
884
+ # Simple condition (original behavior)
885
+ condition_key = cond.get("condition_key", "unknown")
886
+ description = cond.get("description", "")
887
+ expected_value = cond.get("expected_value")
849
888
 
850
- option = f"\nOption {i}: {condition_key}"
851
- if description:
852
- option += f" - {description}"
853
- if expected_value is not None:
854
- option += f" (value: {expected_value})"
889
+ option = f"\n- **Option {i}**: `{condition_key}`"
890
+ if description:
891
+ option += f" - {description}"
892
+ if expected_value is not None:
893
+ option += f" (value: `{expected_value}`)"
855
894
 
856
895
  suggestions.append(option)
857
896
 
@@ -870,13 +909,12 @@ class ActionConditionEnforcementCheck(PolicyCheck):
870
909
  description = condition_requirement.get("description", "")
871
910
  expected_value = condition_requirement.get("expected_value")
872
911
 
873
- message = (
874
- f"FORBIDDEN: Action(s) {matching_actions} must NOT have condition '{condition_key}'"
875
- )
912
+ matching_actions_str = ", ".join(f"`{a}`" for a in matching_actions)
913
+ message = f"FORBIDDEN: Action(s) `{matching_actions_str}` must NOT have condition `{condition_key}`"
876
914
  if expected_value is not None:
877
- message += f" with value '{expected_value}'"
915
+ message += f" with value `{expected_value}`"
878
916
 
879
- suggestion = f"Remove the '{condition_key}' condition from the statement"
917
+ suggestion = f"Remove the `{condition_key}` condition from the statement"
880
918
  if description:
881
919
  suggestion += f". {description}"
882
920
 
@@ -88,6 +88,10 @@ class ActionResourceMatchingCheck(PolicyCheck):
88
88
  statement_sid = statement.sid
89
89
  line_number = statement.line_number
90
90
 
91
+ # Skip if no resources to validate (e.g., trust policies don't have Resource field)
92
+ if not resources:
93
+ return issues
94
+
91
95
  # Skip if we have a wildcard resource (handled by other checks)
92
96
  if "*" in resources:
93
97
  return issues
@@ -289,9 +293,9 @@ class ActionResourceMatchingCheck(PolicyCheck):
289
293
  # Special case: Wildcard resource
290
294
  if required_format == "*":
291
295
  return (
292
- f'Action `{action}` can only use Resource: "*" (wildcard).\n'
296
+ f'Action `{action}` can only use Resource: `"*"` (wildcard).\n'
293
297
  f" This action does not support resource-level permissions.\n"
294
- f' Example: "Resource": "*"'
298
+ f' Example: "Resource": `"*"`'
295
299
  )
296
300
 
297
301
  # Build service-specific suggestion with proper markdown formatting
@@ -63,7 +63,7 @@ class ActionValidationCheck(PolicyCheck):
63
63
  statement_sid=statement_sid,
64
64
  statement_index=statement_idx,
65
65
  issue_type="invalid_action",
66
- message=error_msg or f"Invalid action: {action}",
66
+ message=error_msg or f"Invalid action: `{action}`",
67
67
  action=action,
68
68
  line_number=line_number,
69
69
  )
@@ -62,7 +62,7 @@ class ConditionKeyValidationCheck(PolicyCheck):
62
62
  statement_index=statement_idx,
63
63
  issue_type="invalid_condition_key",
64
64
  message=result.error_message
65
- or f"Invalid condition key: {condition_key}",
65
+ or f"Invalid condition key: `{condition_key}`",
66
66
  action=action,
67
67
  condition_key=condition_key,
68
68
  line_number=line_number,
@@ -72,7 +72,7 @@ class ConditionTypeMismatchCheck(PolicyCheck):
72
72
  # Check each condition operator and its keys/values
73
73
  for operator, conditions in statement.condition.items():
74
74
  # Normalize the operator and get its expected type
75
- base_operator, operator_type, set_prefix = normalize_operator(operator)
75
+ base_operator, operator_type, _set_prefix = normalize_operator(operator)
76
76
 
77
77
  if operator_type is None:
78
78
  # Unknown operator - this will be caught by another check
@@ -108,7 +108,7 @@ class ConditionTypeMismatchCheck(PolicyCheck):
108
108
  severity="warning",
109
109
  message=(
110
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}. "
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 `{operator}` expects {operator_type} values, "
127
- f"but condition key `{condition_key}` is type {key_type}."
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,
@@ -172,7 +172,7 @@ 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 ( # pylint: disable=import-outside-toplevel
176
176
  get_global_conditions,
177
177
  )
178
178
 
@@ -229,7 +229,7 @@ class ConditionTypeMismatchCheck(PolicyCheck):
229
229
  if condition_key_obj.types:
230
230
  return condition_key_obj.types[0]
231
231
 
232
- except Exception:
232
+ except Exception: # pylint: disable=broad-exception-caught
233
233
  # If we can't look up the action, skip it
234
234
  continue
235
235