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.
- {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/METADATA +22 -7
- iam_policy_validator-1.8.0.dist-info/RECORD +87 -0
- iam_validator/__version__.py +4 -2
- iam_validator/checks/__init__.py +5 -3
- iam_validator/checks/action_condition_enforcement.py +81 -36
- iam_validator/checks/action_resource_matching.py +75 -37
- iam_validator/checks/action_validation.py +1 -1
- iam_validator/checks/condition_key_validation.py +7 -7
- iam_validator/checks/condition_type_mismatch.py +10 -8
- iam_validator/checks/full_wildcard.py +2 -8
- iam_validator/checks/mfa_condition_check.py +8 -8
- iam_validator/checks/policy_structure.py +577 -0
- iam_validator/checks/policy_type_validation.py +48 -32
- iam_validator/checks/principal_validation.py +86 -150
- iam_validator/checks/resource_validation.py +8 -8
- iam_validator/checks/sensitive_action.py +9 -11
- iam_validator/checks/service_wildcard.py +4 -10
- iam_validator/checks/set_operator_validation.py +11 -11
- iam_validator/checks/sid_uniqueness.py +8 -4
- iam_validator/checks/trust_policy_validation.py +512 -0
- iam_validator/checks/utils/sensitive_action_matcher.py +26 -26
- iam_validator/checks/utils/wildcard_expansion.py +1 -1
- iam_validator/checks/wildcard_action.py +5 -9
- iam_validator/checks/wildcard_resource.py +5 -9
- iam_validator/commands/validate.py +8 -14
- iam_validator/core/__init__.py +1 -2
- iam_validator/core/access_analyzer.py +1 -1
- iam_validator/core/access_analyzer_report.py +2 -2
- iam_validator/core/aws_fetcher.py +159 -64
- iam_validator/core/check_registry.py +83 -79
- iam_validator/core/config/condition_requirements.py +69 -17
- iam_validator/core/config/config_loader.py +1 -2
- iam_validator/core/config/defaults.py +74 -59
- iam_validator/core/config/service_principals.py +40 -3
- iam_validator/core/constants.py +57 -0
- iam_validator/core/formatters/console.py +10 -1
- iam_validator/core/formatters/csv.py +2 -1
- iam_validator/core/formatters/enhanced.py +42 -8
- iam_validator/core/formatters/markdown.py +2 -1
- iam_validator/core/ignore_patterns.py +297 -0
- iam_validator/core/models.py +35 -10
- iam_validator/core/policy_checks.py +34 -474
- iam_validator/core/policy_loader.py +98 -18
- iam_validator/core/report.py +65 -24
- iam_validator/integrations/github_integration.py +4 -5
- iam_validator/utils/__init__.py +4 -0
- iam_validator/utils/terminal.py +22 -0
- iam_policy_validator-1.7.1.dist-info/RECORD +0 -83
- {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/entry_points.txt +0 -0
- {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.
|
|
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
|
|
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
|
|
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
|
|
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,,
|
iam_validator/__version__.py
CHANGED
|
@@ -3,5 +3,7 @@
|
|
|
3
3
|
This file is the single source of truth for the package version.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
__version__ = "1.
|
|
7
|
-
|
|
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("."))
|
iam_validator/checks/__init__.py
CHANGED
|
@@ -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 {
|
|
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: {
|
|
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 {
|
|
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,
|
|
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.
|
|
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
|
-
|
|
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 {
|
|
636
|
-
f"{
|
|
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) {
|
|
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=
|
|
778
|
-
|
|
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
|
|
792
|
-
parts = []
|
|
812
|
+
) -> tuple[str, str]:
|
|
813
|
+
"""Build suggestion and example for adding the missing condition.
|
|
793
814
|
|
|
794
|
-
|
|
795
|
-
|
|
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
|
-
|
|
822
|
+
example_code = example
|
|
800
823
|
else:
|
|
801
824
|
# Auto-generate example
|
|
802
|
-
example_lines = [
|
|
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
|
-
|
|
852
|
+
example_code = "\n".join(example_lines)
|
|
830
853
|
|
|
831
|
-
return
|
|
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
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
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
|
-
|
|
867
|
-
|
|
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
|
|
915
|
+
message += f" with value `{expected_value}`"
|
|
871
916
|
|
|
872
|
-
suggestion = f"Remove the
|
|
917
|
+
suggestion = f"Remove the `{condition_key}` condition from the statement"
|
|
873
918
|
if description:
|
|
874
919
|
suggestion += f". {description}"
|
|
875
920
|
|