iam-policy-validator 1.7.1__py3-none-any.whl → 1.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/METADATA +22 -7
  2. iam_policy_validator-1.8.0.dist-info/RECORD +87 -0
  3. iam_validator/__version__.py +4 -2
  4. iam_validator/checks/__init__.py +5 -3
  5. iam_validator/checks/action_condition_enforcement.py +81 -36
  6. iam_validator/checks/action_resource_matching.py +75 -37
  7. iam_validator/checks/action_validation.py +1 -1
  8. iam_validator/checks/condition_key_validation.py +7 -7
  9. iam_validator/checks/condition_type_mismatch.py +10 -8
  10. iam_validator/checks/full_wildcard.py +2 -8
  11. iam_validator/checks/mfa_condition_check.py +8 -8
  12. iam_validator/checks/policy_structure.py +577 -0
  13. iam_validator/checks/policy_type_validation.py +48 -32
  14. iam_validator/checks/principal_validation.py +86 -150
  15. iam_validator/checks/resource_validation.py +8 -8
  16. iam_validator/checks/sensitive_action.py +9 -11
  17. iam_validator/checks/service_wildcard.py +4 -10
  18. iam_validator/checks/set_operator_validation.py +11 -11
  19. iam_validator/checks/sid_uniqueness.py +8 -4
  20. iam_validator/checks/trust_policy_validation.py +512 -0
  21. iam_validator/checks/utils/sensitive_action_matcher.py +26 -26
  22. iam_validator/checks/utils/wildcard_expansion.py +1 -1
  23. iam_validator/checks/wildcard_action.py +5 -9
  24. iam_validator/checks/wildcard_resource.py +5 -9
  25. iam_validator/commands/validate.py +8 -14
  26. iam_validator/core/__init__.py +1 -2
  27. iam_validator/core/access_analyzer.py +1 -1
  28. iam_validator/core/access_analyzer_report.py +2 -2
  29. iam_validator/core/aws_fetcher.py +159 -64
  30. iam_validator/core/check_registry.py +83 -79
  31. iam_validator/core/config/condition_requirements.py +69 -17
  32. iam_validator/core/config/config_loader.py +1 -2
  33. iam_validator/core/config/defaults.py +74 -59
  34. iam_validator/core/config/service_principals.py +40 -3
  35. iam_validator/core/constants.py +57 -0
  36. iam_validator/core/formatters/console.py +10 -1
  37. iam_validator/core/formatters/csv.py +2 -1
  38. iam_validator/core/formatters/enhanced.py +42 -8
  39. iam_validator/core/formatters/markdown.py +2 -1
  40. iam_validator/core/ignore_patterns.py +297 -0
  41. iam_validator/core/models.py +35 -10
  42. iam_validator/core/policy_checks.py +34 -474
  43. iam_validator/core/policy_loader.py +98 -18
  44. iam_validator/core/report.py +65 -24
  45. iam_validator/integrations/github_integration.py +4 -5
  46. iam_validator/utils/__init__.py +4 -0
  47. iam_validator/utils/terminal.py +22 -0
  48. iam_policy_validator-1.7.1.dist-info/RECORD +0 -83
  49. {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/WHEEL +0 -0
  50. {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/entry_points.txt +0 -0
  51. {iam_policy_validator-1.7.1.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.1
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
 
@@ -426,4 +442,3 @@ MIT License - see [LICENSE](LICENSE) file for details.
426
442
  ## 🆘 Support
427
443
 
428
444
  - **Issues**: [GitHub Issues](https://github.com/boogy/iam-policy-validator/issues)
429
- - **Discussions**: [GitHub Discussions](https://github.com/boogy/iam-policy-validator/discussions)
@@ -0,0 +1,87 @@
1
+ iam_validator/__init__.py,sha256=APnMR3Fu4fHhxfsHBvUM2dJIwazgvLKQbfOsSgFPidg,693
2
+ iam_validator/__main__.py,sha256=to_nz3n_IerJpVVZZ6WSFlFR5s_06J0csfPOTfQZG8g,197
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
+ iam_validator/checks/full_wildcard.py,sha256=ywD762BOV8WxFuTTARkaGMJn27f3ZZVuZUjKo8URnTc,2281
11
+ iam_validator/checks/mfa_condition_check.py,sha256=YCBX3tFTQRmVTAed_W-Tu1b6WqD2LBYyom53P7lBjh4,4935
12
+ iam_validator/checks/policy_size.py,sha256=ibgmrErpkz6OfUAN6bFuHe1KHzpzzra9gHwNtVAkPWc,5729
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
24
+ iam_validator/checks/utils/__init__.py,sha256=j0X4ibUB6RGx2a-kNoJnlVZwHfoEvzZsIeTmJIAoFzA,45
25
+ iam_validator/checks/utils/policy_level_checks.py,sha256=2V60C0zhKfsFPjQ-NMlD3EemtwA9S6-4no8nETgXdQE,5274
26
+ iam_validator/checks/utils/sensitive_action_matcher.py,sha256=qDXcJa_2sCJu9pBbjDlI7x5lPtLRc6jQCpKPMheCOJQ,11215
27
+ iam_validator/checks/utils/wildcard_expansion.py,sha256=JK8iYcYpOzi5RN4IvIMBCbzkCEhVRJxZDKLZIKgD8nY,3131
28
+ iam_validator/commands/__init__.py,sha256=M-5bo8w0TCWydK0cXgJyPD2fmk8bpQs-3b26YbgLzlc,565
29
+ iam_validator/commands/analyze.py,sha256=rvLBJ5_A3HB530xtixhaIsC19QON68olEQnn8TievgI,20784
30
+ iam_validator/commands/base.py,sha256=5baCCMwxz7pdQ6XMpWfXFNz7i1l5dB8Qv9dKKR04Gzs,1074
31
+ iam_validator/commands/cache.py,sha256=p4ucRVuh42sbK3Lk0b610L3ofAR5TnUreF00fpO6VFg,14219
32
+ iam_validator/commands/download_services.py,sha256=KKz3ybMLT8DQUf9aFZ0tilJ-o1b6PE8Pf1pC4K6cT8I,9175
33
+ iam_validator/commands/post_to_pr.py,sha256=CvUXs2xvO-UhluxdfNM6F0TCWD8hDBEOiYw60fm1Dms,2363
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
40
+ iam_validator/core/cli.py,sha256=PkXiZjlgrQ21QustBbspefYsdbxst4gxoClyG2_HQR8,3843
41
+ iam_validator/core/condition_validators.py,sha256=7zBjlcf2xGFKGbcFrXSLvWT5tFhWxoqwzhsJqS2E8uY,21524
42
+ iam_validator/core/constants.py,sha256=H3eH0yddn5Dk-xZxJWtuvluRIpuXKYGiiteBSHPpJoI,5560
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
47
+ iam_validator/core/pr_commenter.py,sha256=MU-t7SfdHUpSc6BDbh8_dNAbxDiG-bZBCry-jUXivAc,15066
48
+ iam_validator/core/report.py,sha256=kzSeWnT1LqWZVA5pqKKz-maVowXVj0djdoShfRhhpz4,35899
49
+ iam_validator/core/config/__init__.py,sha256=CWSyIA7kEyzrskEenjYbs9Iih10BXRpiY9H2dHg61rU,2671
50
+ iam_validator/core/config/aws_api.py,sha256=HLIzOItQ0A37wxHcgWck6ZFO0wmNY8JNTiWMMK6JKYU,1248
51
+ iam_validator/core/config/aws_global_conditions.py,sha256=gdmMxXGBy95B3uYUG-J7rnM6Ixgc6L7Y9Pcd2XAMb60,7170
52
+ iam_validator/core/config/category_suggestions.py,sha256=QlrYi4BTkxDSTlL7NZGE9BWN-atWetZ6XjkI9F_7YzI,4370
53
+ iam_validator/core/config/condition_requirements.py,sha256=qauIP73HFnOw1dchUeFpg1x7Y7QWkILo3GfxV_dxdQo,7696
54
+ iam_validator/core/config/config_loader.py,sha256=qKD8aR8YAswaFf68pnYJLFNwKznvcc6lNxSQWU3i6SY,17713
55
+ iam_validator/core/config/defaults.py,sha256=rWzDrlw0AAudtm_If6zjNFvruLg71jpLJEdRgKYSKMQ,27917
56
+ iam_validator/core/config/principal_requirements.py,sha256=VCX7fBDgeDTJQyoz7_x7GI7Kf9O1Eu-sbihoHOrKv6o,15105
57
+ iam_validator/core/config/sensitive_actions.py,sha256=uATDIp_TD3OQQlsYTZp79qd1mSK2Bf9hJ0JwcqLBr84,25344
58
+ iam_validator/core/config/service_principals.py,sha256=8pys5H_yycVJ9KTyimAKFYBg83Aol2Iri53wiHjtnEM,3959
59
+ iam_validator/core/config/wildcards.py,sha256=H_v6hb-rZ0UUz4cul9lxkVI39e6knaK4Y-MbWz2Ebpw,3228
60
+ iam_validator/core/formatters/__init__.py,sha256=fnCKAEBXItnOf2m4rhVs7zwMaTxbG6ESh3CF8V5j5ec,868
61
+ iam_validator/core/formatters/base.py,sha256=SShDeDiy5mYQnS6BpA8xYg91N-KX1EObkOtlrVHqx1Q,4451
62
+ iam_validator/core/formatters/console.py,sha256=FdTp7AzeILCWrUynSvSew8QJKGOMJaAA9_YiQJd-uco,2196
63
+ iam_validator/core/formatters/csv.py,sha256=pPqgvGh4KtD5Qm36xnMaDAavXYR6MlQhs4zbcrxT550,5941
64
+ iam_validator/core/formatters/enhanced.py,sha256=TVtkcTIow8NGoLhG45-5ms-_PTxyxMcAHxf_uPMyKAc,18155
65
+ iam_validator/core/formatters/html.py,sha256=j4sQi-wXiD9kCHldW5JCzbJe0frhiP5uQI9KlH3Sj_g,22994
66
+ iam_validator/core/formatters/json.py,sha256=A7gZ8P32GEdbDvrSn6v56yQ4fOP_kyMaoFVXG2bgnew,939
67
+ iam_validator/core/formatters/markdown.py,sha256=dk4STeY-tOEZsVrlmolIEqZvWYP9JhRtygxxNA49DEE,2293
68
+ iam_validator/core/formatters/sarif.py,sha256=O3pn7whqFq5xxk-tuoqSb2k4Fk5ai_A2SKX_ph8GLV4,10469
69
+ iam_validator/integrations/__init__.py,sha256=7Hlor_X9j0NZaEjFuSvoXAAuSKQ-zgY19Rk-Dz3JpKo,616
70
+ iam_validator/integrations/github_integration.py,sha256=EnrolMq3uZbKWPxUMhYnqcKAfic6Fb8qJzieDruKqsc,26485
71
+ iam_validator/integrations/ms_teams.py,sha256=t2PlWuTDb6GGH-eDU1jnOKd8D1w4FCB68bahGA7MJcE,14475
72
+ iam_validator/sdk/__init__.py,sha256=fRDSXAclGmCU3KDft4StL8JUcpAsdzwIRf8mVj461q0,5306
73
+ iam_validator/sdk/arn_matching.py,sha256=HSDpLltOYISq-SoPebAlM89mKOaUaghq_04urchEFDA,12778
74
+ iam_validator/sdk/context.py,sha256=SBFeedu8rhCzFA-zC2cH4wLZxEJT6XOW30hIZAyXPVU,6826
75
+ iam_validator/sdk/exceptions.py,sha256=tm91TxIwU157U_UHN7w5qICf_OhU11agj6pV5W_YP-4,1023
76
+ iam_validator/sdk/helpers.py,sha256=OVBg4xrW95LT74wXCg1LQkba9kw5RfFqeCLuTqhgL-A,5697
77
+ iam_validator/sdk/policy_utils.py,sha256=CZS1OGSdiWsd2lsCwg0BDcUNWa61tUwgvn-P5rKqeN8,12987
78
+ iam_validator/sdk/shortcuts.py,sha256=EVNSYV7rv4TFH03ulsZ3mS1UVmTSp2jKpc2AXs4j1q4,8531
79
+ iam_validator/utils/__init__.py,sha256=NveA2F3G1E6-ANZzFr7J6Q6u5mogvMp862iFokmYuCs,1021
80
+ iam_validator/utils/cache.py,sha256=wOQKOBeoG6QqC5f0oXcHz63Cjtu_-SsSS-0pTSwyAiM,3254
81
+ iam_validator/utils/regex.py,sha256=xHoMECttb7qaMhts-c9b0GIxdhHNZTt-UBr7wNhWfzg,6219
82
+ iam_validator/utils/terminal.py,sha256=FsRaRMH_JAyDgXWBCOgOEhbS89cs17HCmKYoughq5io,724
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,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.1"
7
- __version_info__ = tuple(int(part) for part in __version__.split("."))
6
+ __version__ = "1.8.0"
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("."))
@@ -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,
@@ -349,7 +351,10 @@ class ActionConditionEnforcementCheck(PolicyCheck):
349
351
  return issues
350
352
 
351
353
  async def _check_action_match(
352
- self, statement_actions: list[str], requirement: dict[str, Any], fetcher: AWSServiceFetcher
354
+ self,
355
+ statement_actions: list[str],
356
+ requirement: dict[str, Any],
357
+ fetcher: AWSServiceFetcher,
353
358
  ) -> tuple[bool, list[str]]:
354
359
  """
355
360
  Check if statement actions match the requirement.
@@ -527,7 +532,7 @@ class ActionConditionEnforcementCheck(PolicyCheck):
527
532
  available_actions = list(service_detail.actions.keys())
528
533
 
529
534
  # Find which actual AWS actions the wildcard would grant
530
- _, granted_actions = fetcher._match_wildcard_action(
535
+ _, granted_actions = fetcher.match_wildcard_action(
531
536
  statement_action.split(":", 1)[1], # Just the action part (e.g., "C*")
532
537
  available_actions,
533
538
  )
@@ -542,7 +547,7 @@ class ActionConditionEnforcementCheck(PolicyCheck):
542
547
  except re.error:
543
548
  continue
544
549
 
545
- except (ValueError, Exception):
550
+ except (ValueError, Exception): # pylint: disable=broad-exception-caught
546
551
  # If we can't fetch the service or parse the action, fall back to prefix matching
547
552
  stmt_prefix = statement_action.rstrip("*")
548
553
  for pattern in patterns:
@@ -624,7 +629,20 @@ class ActionConditionEnforcementCheck(PolicyCheck):
624
629
 
625
630
  if not any_present:
626
631
  # Create a combined error for any_of
627
- 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)
628
646
  issues.append(
629
647
  ValidationIssue(
630
648
  severity=self.get_severity(config),
@@ -632,8 +650,8 @@ class ActionConditionEnforcementCheck(PolicyCheck):
632
650
  statement_index=statement_idx,
633
651
  issue_type="missing_required_condition_any_of",
634
652
  message=(
635
- f"Actions {matching_actions} require at least ONE of these conditions: "
636
- f"{', '.join(condition_keys)}"
653
+ f"Actions `{matching_actions_formatted}` require at least ONE of these conditions: "
654
+ f"{condition_keys_formatted}"
637
655
  ),
638
656
  action=", ".join(matching_actions),
639
657
  suggestion=self._build_any_of_suggestion(any_of),
@@ -766,17 +784,21 @@ class ActionConditionEnforcementCheck(PolicyCheck):
766
784
  or self.get_severity(config) # Global check severity
767
785
  )
768
786
 
787
+ suggestion_text, example_code = self._build_suggestion(
788
+ condition_key, description, example, expected_value, operator
789
+ )
790
+
791
+ matching_actions_str = ", ".join(f"`{a}`" for a in matching_actions)
769
792
  return ValidationIssue(
770
793
  severity=severity,
771
794
  statement_sid=statement.sid,
772
795
  statement_index=statement_idx,
773
796
  issue_type="missing_required_condition",
774
- 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}`",
775
798
  action=", ".join(matching_actions),
776
799
  condition_key=condition_key,
777
- suggestion=self._build_suggestion(
778
- condition_key, description, example, expected_value, operator
779
- ),
800
+ suggestion=suggestion_text,
801
+ example=example_code,
780
802
  line_number=statement.line_number,
781
803
  )
782
804
 
@@ -787,19 +809,20 @@ class ActionConditionEnforcementCheck(PolicyCheck):
787
809
  example: str,
788
810
  expected_value: Any = None,
789
811
  operator: str = "StringEquals",
790
- ) -> str:
791
- """Build a helpful suggestion for adding the missing condition."""
792
- parts = []
812
+ ) -> tuple[str, str]:
813
+ """Build suggestion and example for adding the missing condition.
793
814
 
794
- if description:
795
- parts.append(description)
815
+ Returns:
816
+ Tuple of (suggestion_text, example_code)
817
+ """
818
+ suggestion = description if description else f"Add condition: {condition_key}"
796
819
 
797
820
  # Build example based on condition key type
798
821
  if example:
799
- parts.append(f"Example:\n```json\n{example}\n```")
822
+ example_code = example
800
823
  else:
801
824
  # Auto-generate example
802
- example_lines = ['Add to "Condition" block:', f' "{operator}": {{']
825
+ example_lines = [f' "{operator}": {{']
803
826
 
804
827
  if isinstance(expected_value, list):
805
828
  value_str = (
@@ -826,9 +849,9 @@ class ActionConditionEnforcementCheck(PolicyCheck):
826
849
  example_lines.append(f' "{condition_key}": {value_str}')
827
850
  example_lines.append(" }")
828
851
 
829
- parts.append("\n".join(example_lines))
852
+ example_code = "\n".join(example_lines)
830
853
 
831
- return ". ".join(parts) if parts else f"Add condition: {condition_key}"
854
+ return suggestion, example_code
832
855
 
833
856
  def _build_any_of_suggestion(self, any_of_conditions: list[dict[str, Any]]) -> str:
834
857
  """Build suggestion for any_of conditions."""
@@ -836,15 +859,38 @@ class ActionConditionEnforcementCheck(PolicyCheck):
836
859
  suggestions.append("Add at least ONE of these conditions:")
837
860
 
838
861
  for i, cond in enumerate(any_of_conditions, 1):
839
- condition_key = cond.get("condition_key", "unknown")
840
- description = cond.get("description", "")
841
- 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")
842
888
 
843
- option = f"\nOption {i}: {condition_key}"
844
- if description:
845
- option += f" - {description}"
846
- if expected_value is not None:
847
- 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}`)"
848
894
 
849
895
  suggestions.append(option)
850
896
 
@@ -863,13 +909,12 @@ class ActionConditionEnforcementCheck(PolicyCheck):
863
909
  description = condition_requirement.get("description", "")
864
910
  expected_value = condition_requirement.get("expected_value")
865
911
 
866
- message = (
867
- f"FORBIDDEN: Action(s) {matching_actions} must NOT have condition '{condition_key}'"
868
- )
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}`"
869
914
  if expected_value is not None:
870
- message += f" with value '{expected_value}'"
915
+ message += f" with value `{expected_value}`"
871
916
 
872
- suggestion = f"Remove the '{condition_key}' condition from the statement"
917
+ suggestion = f"Remove the `{condition_key}` condition from the statement"
873
918
  if description:
874
919
  suggestion += f". {description}"
875
920