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.
- {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.8.0.dist-info}/METADATA +22 -6
- {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.8.0.dist-info}/RECORD +38 -35
- iam_validator/__version__.py +1 -1
- iam_validator/checks/__init__.py +5 -3
- iam_validator/checks/action_condition_enforcement.py +61 -23
- iam_validator/checks/action_resource_matching.py +6 -2
- iam_validator/checks/action_validation.py +1 -1
- iam_validator/checks/condition_key_validation.py +1 -1
- iam_validator/checks/condition_type_mismatch.py +6 -6
- iam_validator/checks/policy_structure.py +577 -0
- iam_validator/checks/policy_type_validation.py +48 -32
- iam_validator/checks/principal_validation.py +65 -133
- iam_validator/checks/resource_validation.py +8 -8
- iam_validator/checks/sensitive_action.py +7 -3
- iam_validator/checks/service_wildcard.py +2 -2
- 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 +3 -1
- iam_validator/checks/wildcard_resource.py +3 -1
- iam_validator/commands/validate.py +6 -12
- 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 +45 -43
- iam_validator/core/check_registry.py +83 -79
- iam_validator/core/config/condition_requirements.py +69 -17
- iam_validator/core/config/defaults.py +58 -52
- iam_validator/core/config/service_principals.py +40 -3
- iam_validator/core/ignore_patterns.py +297 -0
- iam_validator/core/models.py +15 -5
- iam_validator/core/policy_checks.py +31 -472
- iam_validator/core/policy_loader.py +27 -4
- {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.8.0.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.8.0.dist-info}/entry_points.txt +0 -0
- {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.
|
|
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
|
|
|
@@ -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=
|
|
4
|
-
iam_validator/checks/__init__.py,sha256=
|
|
5
|
-
iam_validator/checks/action_condition_enforcement.py,sha256=
|
|
6
|
-
iam_validator/checks/action_resource_matching.py,sha256=
|
|
7
|
-
iam_validator/checks/action_validation.py,sha256=
|
|
8
|
-
iam_validator/checks/condition_key_validation.py,sha256=
|
|
9
|
-
iam_validator/checks/condition_type_mismatch.py,sha256=
|
|
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/
|
|
14
|
-
iam_validator/checks/
|
|
15
|
-
iam_validator/checks/
|
|
16
|
-
iam_validator/checks/
|
|
17
|
-
iam_validator/checks/
|
|
18
|
-
iam_validator/checks/
|
|
19
|
-
iam_validator/checks/
|
|
20
|
-
iam_validator/checks/
|
|
21
|
-
iam_validator/checks/
|
|
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=
|
|
25
|
-
iam_validator/checks/utils/wildcard_expansion.py,sha256=
|
|
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=
|
|
33
|
-
iam_validator/core/__init__.py,sha256=
|
|
34
|
-
iam_validator/core/access_analyzer.py,sha256=
|
|
35
|
-
iam_validator/core/access_analyzer_report.py,sha256=
|
|
36
|
-
iam_validator/core/aws_fetcher.py,sha256=
|
|
37
|
-
iam_validator/core/check_registry.py,sha256=
|
|
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/
|
|
42
|
-
iam_validator/core/
|
|
43
|
-
iam_validator/core/
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
81
|
-
iam_policy_validator-1.
|
|
82
|
-
iam_policy_validator-1.
|
|
83
|
-
iam_policy_validator-1.
|
|
84
|
-
iam_policy_validator-1.
|
|
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,7 +3,7 @@
|
|
|
3
3
|
This file is the single source of truth for the package version.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
__version__ = "1.
|
|
6
|
+
__version__ = "1.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("."))
|
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,
|
|
@@ -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.
|
|
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
|
-
|
|
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 {
|
|
639
|
-
f"{
|
|
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) {
|
|
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
|
-
|
|
847
|
-
|
|
848
|
-
|
|
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
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
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
|
-
|
|
874
|
-
|
|
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
|
|
915
|
+
message += f" with value `{expected_value}`"
|
|
878
916
|
|
|
879
|
-
suggestion = f"Remove the
|
|
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,
|
|
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
|
|