iam-policy-validator 1.8.0__py3-none-any.whl → 1.9.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.8.0.dist-info → iam_policy_validator-1.9.0.dist-info}/METADATA +106 -1
- {iam_policy_validator-1.8.0.dist-info → iam_policy_validator-1.9.0.dist-info}/RECORD +45 -37
- iam_validator/__init__.py +1 -1
- iam_validator/__version__.py +1 -1
- iam_validator/checks/action_condition_enforcement.py +504 -190
- iam_validator/checks/action_resource_matching.py +8 -15
- iam_validator/checks/action_validation.py +6 -12
- iam_validator/checks/condition_key_validation.py +6 -12
- iam_validator/checks/condition_type_mismatch.py +9 -16
- iam_validator/checks/full_wildcard.py +9 -13
- iam_validator/checks/mfa_condition_check.py +8 -17
- iam_validator/checks/policy_size.py +6 -39
- iam_validator/checks/policy_structure.py +10 -40
- iam_validator/checks/policy_type_validation.py +18 -19
- iam_validator/checks/principal_validation.py +11 -20
- iam_validator/checks/resource_validation.py +5 -12
- iam_validator/checks/sensitive_action.py +8 -15
- iam_validator/checks/service_wildcard.py +6 -12
- iam_validator/checks/set_operator_validation.py +11 -18
- iam_validator/checks/sid_uniqueness.py +8 -38
- iam_validator/checks/trust_policy_validation.py +8 -14
- iam_validator/checks/utils/wildcard_expansion.py +1 -1
- iam_validator/checks/wildcard_action.py +6 -12
- iam_validator/checks/wildcard_resource.py +6 -12
- iam_validator/commands/cache.py +4 -3
- iam_validator/commands/validate.py +12 -0
- iam_validator/core/__init__.py +1 -1
- iam_validator/core/aws_fetcher.py +24 -1030
- iam_validator/core/aws_service/__init__.py +21 -0
- iam_validator/core/aws_service/cache.py +108 -0
- iam_validator/core/aws_service/client.py +205 -0
- iam_validator/core/aws_service/fetcher.py +612 -0
- iam_validator/core/aws_service/parsers.py +149 -0
- iam_validator/core/aws_service/patterns.py +51 -0
- iam_validator/core/aws_service/storage.py +291 -0
- iam_validator/core/aws_service/validators.py +379 -0
- iam_validator/core/check_registry.py +82 -14
- iam_validator/core/constants.py +17 -0
- iam_validator/core/policy_checks.py +7 -3
- iam_validator/sdk/__init__.py +1 -1
- iam_validator/sdk/context.py +1 -1
- iam_validator/sdk/helpers.py +1 -1
- {iam_policy_validator-1.8.0.dist-info → iam_policy_validator-1.9.0.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.8.0.dist-info → iam_policy_validator-1.9.0.dist-info}/entry_points.txt +0 -0
- {iam_policy_validator-1.8.0.dist-info → iam_policy_validator-1.9.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -22,8 +22,9 @@ Example:
|
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
24
|
import re
|
|
25
|
+
from typing import ClassVar
|
|
25
26
|
|
|
26
|
-
from iam_validator.core.
|
|
27
|
+
from iam_validator.core.aws_service import AWSServiceFetcher
|
|
27
28
|
from iam_validator.core.check_registry import CheckConfig, PolicyCheck
|
|
28
29
|
from iam_validator.core.models import Statement, ValidationIssue
|
|
29
30
|
from iam_validator.sdk.arn_matching import (
|
|
@@ -42,17 +43,9 @@ class ActionResourceMatchingCheck(PolicyCheck):
|
|
|
42
43
|
work as intended because the resource ARNs don't match what the action requires.
|
|
43
44
|
"""
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@property
|
|
50
|
-
def description(self) -> str:
|
|
51
|
-
return "Validates that resources match required types for actions"
|
|
52
|
-
|
|
53
|
-
@property
|
|
54
|
-
def default_severity(self) -> str:
|
|
55
|
-
return "medium" # Security issue, not IAM validity error
|
|
46
|
+
check_id: ClassVar[str] = "action_resource_matching"
|
|
47
|
+
description: ClassVar[str] = "Validates that resources match required types for actions"
|
|
48
|
+
default_severity: ClassVar[str] = "medium" # Security issue, not IAM validity error
|
|
56
49
|
|
|
57
50
|
async def execute(
|
|
58
51
|
self,
|
|
@@ -139,7 +132,7 @@ class ActionResourceMatchingCheck(PolicyCheck):
|
|
|
139
132
|
statement_sid=statement_sid,
|
|
140
133
|
line_number=line_number,
|
|
141
134
|
config=config,
|
|
142
|
-
reason=f'Action {action} can only use Resource: "*"',
|
|
135
|
+
reason=f'Action `{action}` can only use `Resource: "*"`',
|
|
143
136
|
)
|
|
144
137
|
)
|
|
145
138
|
continue
|
|
@@ -293,9 +286,9 @@ class ActionResourceMatchingCheck(PolicyCheck):
|
|
|
293
286
|
# Special case: Wildcard resource
|
|
294
287
|
if required_format == "*":
|
|
295
288
|
return (
|
|
296
|
-
f'Action `{action}` can only use Resource:
|
|
289
|
+
f'Action `{action}` can only use `Resource: "*"` (wildcard).\n'
|
|
297
290
|
f" This action does not support resource-level permissions.\n"
|
|
298
|
-
f' Example: "Resource":
|
|
291
|
+
f' Example: `"Resource": "*"`'
|
|
299
292
|
)
|
|
300
293
|
|
|
301
294
|
# Build service-specific suggestion with proper markdown formatting
|
|
@@ -9,7 +9,9 @@ validations. However, it can be useful in environments where Access Analyzer is
|
|
|
9
9
|
not available or for pre-deployment policy validation to catch errors early.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
from
|
|
12
|
+
from typing import ClassVar
|
|
13
|
+
|
|
14
|
+
from iam_validator.core.aws_service import AWSServiceFetcher
|
|
13
15
|
from iam_validator.core.check_registry import CheckConfig, PolicyCheck
|
|
14
16
|
from iam_validator.core.models import Statement, ValidationIssue
|
|
15
17
|
|
|
@@ -17,17 +19,9 @@ from iam_validator.core.models import Statement, ValidationIssue
|
|
|
17
19
|
class ActionValidationCheck(PolicyCheck):
|
|
18
20
|
"""Validates that IAM actions exist in AWS services."""
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@property
|
|
25
|
-
def description(self) -> str:
|
|
26
|
-
return "Validates that actions exist in AWS service definitions"
|
|
27
|
-
|
|
28
|
-
@property
|
|
29
|
-
def default_severity(self) -> str:
|
|
30
|
-
return "error"
|
|
22
|
+
check_id: ClassVar[str] = "action_validation"
|
|
23
|
+
description: ClassVar[str] = "Validates that actions exist in AWS service definitions"
|
|
24
|
+
default_severity: ClassVar[str] = "error"
|
|
31
25
|
|
|
32
26
|
async def execute(
|
|
33
27
|
self,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""Condition key validation check - validates condition keys against AWS definitions."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from typing import ClassVar
|
|
4
|
+
|
|
5
|
+
from iam_validator.core.aws_service import AWSServiceFetcher
|
|
4
6
|
from iam_validator.core.check_registry import CheckConfig, PolicyCheck
|
|
5
7
|
from iam_validator.core.models import Statement, ValidationIssue
|
|
6
8
|
|
|
@@ -8,17 +10,9 @@ from iam_validator.core.models import Statement, ValidationIssue
|
|
|
8
10
|
class ConditionKeyValidationCheck(PolicyCheck):
|
|
9
11
|
"""Validates condition keys against AWS service definitions and global keys."""
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@property
|
|
16
|
-
def description(self) -> str:
|
|
17
|
-
return "Validates condition keys against AWS service definitions"
|
|
18
|
-
|
|
19
|
-
@property
|
|
20
|
-
def default_severity(self) -> str:
|
|
21
|
-
return "error" # Invalid condition keys are IAM policy errors
|
|
13
|
+
check_id: ClassVar[str] = "condition_key_validation"
|
|
14
|
+
description: ClassVar[str] = "Validates condition keys against AWS service definitions"
|
|
15
|
+
default_severity: ClassVar[str] = "error" # Invalid condition keys are IAM policy errors
|
|
22
16
|
|
|
23
17
|
async def execute(
|
|
24
18
|
self,
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
Validates that condition operators match the expected types for condition keys and values.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from
|
|
6
|
+
from typing import ClassVar
|
|
7
|
+
|
|
8
|
+
from iam_validator.core.aws_service import AWSServiceFetcher
|
|
7
9
|
from iam_validator.core.check_registry import CheckConfig, PolicyCheck
|
|
8
10
|
from iam_validator.core.condition_validators import (
|
|
9
11
|
normalize_operator,
|
|
@@ -16,20 +18,11 @@ from iam_validator.core.models import Statement, ValidationIssue
|
|
|
16
18
|
class ConditionTypeMismatchCheck(PolicyCheck):
|
|
17
19
|
"""Check for type mismatches between operators, keys, and values."""
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@property
|
|
25
|
-
def description(self) -> str:
|
|
26
|
-
"""Description of what this check does."""
|
|
27
|
-
return "Validates condition operator types match key types and value formats"
|
|
28
|
-
|
|
29
|
-
@property
|
|
30
|
-
def default_severity(self) -> str:
|
|
31
|
-
"""Default severity level for issues found by this check."""
|
|
32
|
-
return "error"
|
|
21
|
+
check_id: ClassVar[str] = "condition_type_mismatch"
|
|
22
|
+
description: ClassVar[str] = (
|
|
23
|
+
"Validates condition operator types match key types and value formats"
|
|
24
|
+
)
|
|
25
|
+
default_severity: ClassVar[str] = "error"
|
|
33
26
|
|
|
34
27
|
async def execute(
|
|
35
28
|
self,
|
|
@@ -109,7 +102,7 @@ class ConditionTypeMismatchCheck(PolicyCheck):
|
|
|
109
102
|
message=(
|
|
110
103
|
f"Type mismatch (usable but not recommended): Operator `{operator}` expects "
|
|
111
104
|
f"`{operator_type}` values, but condition key `{condition_key}` is type `{key_type}`. "
|
|
112
|
-
f"Consider using an ARN-specific operator like ArnEquals or ArnLike instead."
|
|
105
|
+
f"Consider using an ARN-specific operator like `ArnEquals` or `ArnLike` instead."
|
|
113
106
|
),
|
|
114
107
|
statement_sid=statement_sid,
|
|
115
108
|
statement_index=statement_idx,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""Full wildcard check - detects Action: '*' AND Resource: '*' together (critical security risk)."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from typing import ClassVar
|
|
4
|
+
|
|
5
|
+
from iam_validator.core.aws_service import AWSServiceFetcher
|
|
4
6
|
from iam_validator.core.check_registry import CheckConfig, PolicyCheck
|
|
5
7
|
from iam_validator.core.models import Statement, ValidationIssue
|
|
6
8
|
|
|
@@ -8,17 +10,11 @@ from iam_validator.core.models import Statement, ValidationIssue
|
|
|
8
10
|
class FullWildcardCheck(PolicyCheck):
|
|
9
11
|
"""Checks for both Action: '*' AND Resource: '*' which grants full administrative access."""
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def description(self) -> str:
|
|
17
|
-
return "Checks for both action and resource wildcards together (critical risk)"
|
|
18
|
-
|
|
19
|
-
@property
|
|
20
|
-
def default_severity(self) -> str:
|
|
21
|
-
return "critical"
|
|
13
|
+
check_id: ClassVar[str] = "full_wildcard"
|
|
14
|
+
description: ClassVar[str] = (
|
|
15
|
+
"Checks for both action and resource wildcards together (critical risk)"
|
|
16
|
+
)
|
|
17
|
+
default_severity: ClassVar[str] = "critical"
|
|
22
18
|
|
|
23
19
|
async def execute(
|
|
24
20
|
self,
|
|
@@ -41,7 +37,7 @@ class FullWildcardCheck(PolicyCheck):
|
|
|
41
37
|
if "*" in actions and "*" in resources:
|
|
42
38
|
message = config.config.get(
|
|
43
39
|
"message",
|
|
44
|
-
"Statement allows all actions on all resources - CRITICAL SECURITY RISK",
|
|
40
|
+
"Statement allows all actions on all resources - **CRITICAL SECURITY RISK**",
|
|
45
41
|
)
|
|
46
42
|
suggestion = config.config.get(
|
|
47
43
|
"suggestion",
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
Detects dangerous MFA-related condition patterns that may not enforce MFA as intended.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
from typing import ClassVar
|
|
7
|
+
|
|
6
8
|
from iam_validator.core.check_registry import CheckConfig, PolicyCheck
|
|
7
9
|
from iam_validator.core.models import Statement, ValidationIssue
|
|
8
10
|
|
|
@@ -10,20 +12,9 @@ from iam_validator.core.models import Statement, ValidationIssue
|
|
|
10
12
|
class MFAConditionCheck(PolicyCheck):
|
|
11
13
|
"""Check for MFA condition anti-patterns."""
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return "mfa_condition_antipattern"
|
|
17
|
-
|
|
18
|
-
@property
|
|
19
|
-
def description(self) -> str:
|
|
20
|
-
"""Description of what this check does."""
|
|
21
|
-
return "Detects dangerous MFA-related condition patterns"
|
|
22
|
-
|
|
23
|
-
@property
|
|
24
|
-
def default_severity(self) -> str:
|
|
25
|
-
"""Default severity level for issues found by this check."""
|
|
26
|
-
return "warning"
|
|
15
|
+
check_id: ClassVar[str] = "mfa_condition_antipattern"
|
|
16
|
+
description: ClassVar[str] = "Detects dangerous MFA-related condition patterns"
|
|
17
|
+
default_severity: ClassVar[str] = "warning"
|
|
27
18
|
|
|
28
19
|
async def execute(
|
|
29
20
|
self, statement: Statement, statement_idx: int, fetcher, config: CheckConfig
|
|
@@ -73,8 +64,8 @@ class MFAConditionCheck(PolicyCheck):
|
|
|
73
64
|
"**Dangerous MFA condition pattern detected.** "
|
|
74
65
|
'Using `{"Bool": {"aws:MultiFactorAuthPresent": "false"}}` does not enforce MFA '
|
|
75
66
|
"because `aws:MultiFactorAuthPresent` may not exist in the request context. "
|
|
76
|
-
'Consider using `{"Bool": {"aws:MultiFactorAuthPresent": "true"}}` in an Allow statement, '
|
|
77
|
-
"or use `BoolIfExists` in a Deny statement."
|
|
67
|
+
'Consider using `{"Bool": {"aws:MultiFactorAuthPresent": "true"}}` in an `Allow` statement, '
|
|
68
|
+
"or use `BoolIfExists` in a `Deny` statement."
|
|
78
69
|
),
|
|
79
70
|
statement_sid=statement_sid,
|
|
80
71
|
statement_index=statement_idx,
|
|
@@ -100,7 +91,7 @@ class MFAConditionCheck(PolicyCheck):
|
|
|
100
91
|
"**Dangerous MFA condition pattern detected.** "
|
|
101
92
|
'Using `{"Null": {"aws:MultiFactorAuthPresent": "false"}}` only checks if the key exists, '
|
|
102
93
|
"not whether MFA was actually used. This does not enforce MFA. "
|
|
103
|
-
'Consider using `{"Bool": {"aws:MultiFactorAuthPresent": "true"}}` in an Allow statement instead.'
|
|
94
|
+
'Consider using `{"Bool": {"aws:MultiFactorAuthPresent": "true"}}` in an `Allow` statement instead.'
|
|
104
95
|
),
|
|
105
96
|
statement_sid=statement_sid,
|
|
106
97
|
statement_index=statement_idx,
|
|
@@ -12,12 +12,12 @@ Note: AWS does not count whitespace when calculating policy size.
|
|
|
12
12
|
|
|
13
13
|
import json
|
|
14
14
|
import re
|
|
15
|
-
from typing import TYPE_CHECKING
|
|
15
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
16
16
|
|
|
17
|
-
from iam_validator.core.
|
|
17
|
+
from iam_validator.core.aws_service import AWSServiceFetcher
|
|
18
18
|
from iam_validator.core.check_registry import CheckConfig, PolicyCheck
|
|
19
19
|
from iam_validator.core.constants import AWS_POLICY_SIZE_LIMITS
|
|
20
|
-
from iam_validator.core.models import
|
|
20
|
+
from iam_validator.core.models import ValidationIssue
|
|
21
21
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
23
23
|
from iam_validator.core.models import IAMPolicy
|
|
@@ -29,42 +29,9 @@ class PolicySizeCheck(PolicyCheck):
|
|
|
29
29
|
# AWS IAM policy size limits (loaded from constants module)
|
|
30
30
|
DEFAULT_LIMITS = AWS_POLICY_SIZE_LIMITS
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@property
|
|
37
|
-
def description(self) -> str:
|
|
38
|
-
return "Validates that IAM policies don't exceed AWS size limits"
|
|
39
|
-
|
|
40
|
-
@property
|
|
41
|
-
def default_severity(self) -> str:
|
|
42
|
-
return "error"
|
|
43
|
-
|
|
44
|
-
async def execute(
|
|
45
|
-
self,
|
|
46
|
-
statement: Statement,
|
|
47
|
-
statement_idx: int,
|
|
48
|
-
fetcher: AWSServiceFetcher,
|
|
49
|
-
config: CheckConfig,
|
|
50
|
-
) -> list[ValidationIssue]:
|
|
51
|
-
"""Execute the policy size check at statement level.
|
|
52
|
-
|
|
53
|
-
This is a policy-level check, so statement-level execution returns empty.
|
|
54
|
-
The actual check runs in execute_policy() which has access to the full policy.
|
|
55
|
-
|
|
56
|
-
Args:
|
|
57
|
-
statement: The IAM policy statement (unused)
|
|
58
|
-
statement_idx: Index of the statement in the policy (unused)
|
|
59
|
-
fetcher: AWS service fetcher (unused for this check)
|
|
60
|
-
config: Configuration for this check instance (unused)
|
|
61
|
-
|
|
62
|
-
Returns:
|
|
63
|
-
Empty list (actual check runs in execute_policy())
|
|
64
|
-
"""
|
|
65
|
-
del statement, statement_idx, fetcher, config # Unused
|
|
66
|
-
# This is a policy-level check - execution happens in execute_policy()
|
|
67
|
-
return []
|
|
32
|
+
check_id: ClassVar[str] = "policy_size"
|
|
33
|
+
description: ClassVar[str] = "Validates that IAM policies don't exceed AWS size limits"
|
|
34
|
+
default_severity: ClassVar[str] = "error"
|
|
68
35
|
|
|
69
36
|
async def execute_policy(
|
|
70
37
|
self,
|
|
@@ -20,11 +20,11 @@ By detecting these issues early, we can:
|
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
22
|
import re
|
|
23
|
-
from typing import Any
|
|
23
|
+
from typing import Any, ClassVar
|
|
24
24
|
|
|
25
|
-
from iam_validator.core.
|
|
25
|
+
from iam_validator.core.aws_service import AWSServiceFetcher
|
|
26
26
|
from iam_validator.core.check_registry import CheckConfig, PolicyCheck
|
|
27
|
-
from iam_validator.core.models import IAMPolicy, PolicyType,
|
|
27
|
+
from iam_validator.core.models import IAMPolicy, PolicyType, ValidationIssue
|
|
28
28
|
|
|
29
29
|
# Valid statement fields according to AWS IAM policy grammar
|
|
30
30
|
# https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_grammar.html
|
|
@@ -350,7 +350,7 @@ def validate_statement_structure(
|
|
|
350
350
|
statement_index=statement_idx,
|
|
351
351
|
issue_type="missing_effect",
|
|
352
352
|
message="`Statement` is missing the required `Effect` field",
|
|
353
|
-
suggestion="Add an Effect field with value `Allow` or `Deny`",
|
|
353
|
+
suggestion="Add an `Effect` field with value `Allow` or `Deny`",
|
|
354
354
|
example='"Effect": "Allow"',
|
|
355
355
|
)
|
|
356
356
|
)
|
|
@@ -362,7 +362,7 @@ def validate_statement_structure(
|
|
|
362
362
|
statement_index=statement_idx,
|
|
363
363
|
issue_type="invalid_effect",
|
|
364
364
|
message=f"Invalid `Effect` value: `{statement_dict['Effect']}`. Must be `Allow` or `Deny`",
|
|
365
|
-
suggestion="Change Effect to either `Allow` or `Deny`",
|
|
365
|
+
suggestion="Change `Effect` to either `Allow` or `Deny`",
|
|
366
366
|
example='"Effect": "Allow"',
|
|
367
367
|
)
|
|
368
368
|
)
|
|
@@ -488,41 +488,11 @@ class PolicyStructureCheck(PolicyCheck):
|
|
|
488
488
|
issues early.
|
|
489
489
|
"""
|
|
490
490
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
def description(self) -> str:
|
|
497
|
-
return "Validates fundamental IAM policy structure (required fields, field conflicts, valid values)"
|
|
498
|
-
|
|
499
|
-
@property
|
|
500
|
-
def default_severity(self) -> str:
|
|
501
|
-
return "error"
|
|
502
|
-
|
|
503
|
-
async def execute(
|
|
504
|
-
self,
|
|
505
|
-
statement: Statement,
|
|
506
|
-
statement_idx: int,
|
|
507
|
-
fetcher: AWSServiceFetcher,
|
|
508
|
-
config: CheckConfig,
|
|
509
|
-
) -> list[ValidationIssue]:
|
|
510
|
-
"""Execute at statement level.
|
|
511
|
-
|
|
512
|
-
This is a policy-level check, so statement-level execution returns empty.
|
|
513
|
-
The actual check runs in execute_policy() which has access to all statements.
|
|
514
|
-
|
|
515
|
-
Args:
|
|
516
|
-
statement: The IAM policy statement (unused)
|
|
517
|
-
statement_idx: Index of the statement in the policy (unused)
|
|
518
|
-
fetcher: AWS service fetcher (unused)
|
|
519
|
-
config: Check configuration (unused)
|
|
520
|
-
|
|
521
|
-
Returns:
|
|
522
|
-
Empty list (actual check runs in execute_policy())
|
|
523
|
-
"""
|
|
524
|
-
del statement, statement_idx, fetcher, config # Unused
|
|
525
|
-
return []
|
|
491
|
+
check_id: ClassVar[str] = "policy_structure"
|
|
492
|
+
description: ClassVar[str] = (
|
|
493
|
+
"Validates fundamental IAM policy structure (required fields, field conflicts, valid values)"
|
|
494
|
+
)
|
|
495
|
+
default_severity: ClassVar[str] = "error"
|
|
526
496
|
|
|
527
497
|
async def execute_policy(
|
|
528
498
|
self,
|
|
@@ -11,6 +11,7 @@ This check runs automatically based on:
|
|
|
11
11
|
2. Auto-detection: If any statement has a Principal, provides helpful guidance
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
+
from iam_validator.core.constants import RCP_SUPPORTED_SERVICES
|
|
14
15
|
from iam_validator.core.models import IAMPolicy, ValidationIssue
|
|
15
16
|
|
|
16
17
|
|
|
@@ -47,12 +48,12 @@ async def execute_policy(
|
|
|
47
48
|
if is_trust_policy(policy):
|
|
48
49
|
hint_msg = (
|
|
49
50
|
"Policy contains assume role actions - this is a TRUST POLICY. "
|
|
50
|
-
"Use
|
|
51
|
+
"Use `--policy-type TRUST_POLICY` for proper validation (suppresses missing Resource warnings, "
|
|
51
52
|
"enables trust-specific validation)"
|
|
52
53
|
)
|
|
53
54
|
suggestion_msg = "iam-validator validate --path <file> --policy-type TRUST_POLICY"
|
|
54
55
|
else:
|
|
55
|
-
hint_msg = "Policy contains Principal element - this suggests it's a RESOURCE POLICY. Use
|
|
56
|
+
hint_msg = "Policy contains Principal element - this suggests it's a RESOURCE POLICY. Use `--policy-type RESOURCE_POLICY`"
|
|
56
57
|
suggestion_msg = "iam-validator validate --path <file> --policy-type RESOURCE_POLICY"
|
|
57
58
|
|
|
58
59
|
issues.append(
|
|
@@ -164,8 +165,8 @@ async def execute_policy(
|
|
|
164
165
|
|
|
165
166
|
# Resource Control Policies (RCPs) have very strict requirements
|
|
166
167
|
elif policy_type == "RESOURCE_CONTROL_POLICY":
|
|
167
|
-
# RCP supported services
|
|
168
|
-
rcp_supported_services =
|
|
168
|
+
# Use the centralized list of RCP supported services from constants
|
|
169
|
+
rcp_supported_services = RCP_SUPPORTED_SERVICES
|
|
169
170
|
|
|
170
171
|
for idx, statement in enumerate(policy.statement):
|
|
171
172
|
# 1. Effect MUST be Deny (only RCPFullAWSAccess can use Allow)
|
|
@@ -174,13 +175,13 @@ async def execute_policy(
|
|
|
174
175
|
ValidationIssue(
|
|
175
176
|
severity="error",
|
|
176
177
|
issue_type="invalid_rcp_effect",
|
|
177
|
-
message="Resource Control Policy statement must have Effect: Deny
|
|
178
|
-
"For RCPs that you create, the Effect value must be
|
|
179
|
-
"Only the AWS-managed RCPFullAWSAccess policy can use
|
|
178
|
+
message="Resource Control Policy statement must have `Effect: Deny`. "
|
|
179
|
+
"For RCPs that you create, the `Effect` value must be `Deny`. "
|
|
180
|
+
"Only the AWS-managed `RCPFullAWSAccess` policy can use `Allow`.",
|
|
180
181
|
statement_index=idx,
|
|
181
182
|
statement_sid=statement.sid,
|
|
182
183
|
line_number=statement.line_number,
|
|
183
|
-
suggestion=
|
|
184
|
+
suggestion="Change the `Effect` to `Deny` for this RCP statement.",
|
|
184
185
|
)
|
|
185
186
|
)
|
|
186
187
|
|
|
@@ -194,13 +195,12 @@ async def execute_policy(
|
|
|
194
195
|
severity="error",
|
|
195
196
|
issue_type="invalid_rcp_not_principal",
|
|
196
197
|
message="Resource Control Policy must not contain `NotPrincipal` element. "
|
|
197
|
-
"RCPs only support Principal with value
|
|
198
|
+
"RCPs only support `Principal` with value `*`. Use `Condition` elements "
|
|
198
199
|
"to restrict specific principals.",
|
|
199
200
|
statement_index=idx,
|
|
200
201
|
statement_sid=statement.sid,
|
|
201
202
|
line_number=statement.line_number,
|
|
202
|
-
suggestion=
|
|
203
|
-
"elements to restrict access.",
|
|
203
|
+
suggestion='Remove `NotPrincipal` and use `Principal: "*"` with `Condition` elements to restrict access.',
|
|
204
204
|
)
|
|
205
205
|
)
|
|
206
206
|
elif not has_principal:
|
|
@@ -208,8 +208,8 @@ async def execute_policy(
|
|
|
208
208
|
ValidationIssue(
|
|
209
209
|
severity="error",
|
|
210
210
|
issue_type="missing_rcp_principal",
|
|
211
|
-
message=
|
|
212
|
-
|
|
211
|
+
message='Resource Control Policy statement must have `Principal: "*"`. '
|
|
212
|
+
'RCPs require the `Principal` element with value `"*"`. Use `Condition` '
|
|
213
213
|
"elements to restrict specific principals.",
|
|
214
214
|
statement_index=idx,
|
|
215
215
|
statement_sid=statement.sid,
|
|
@@ -257,7 +257,7 @@ async def execute_policy(
|
|
|
257
257
|
statement_sid=statement.sid,
|
|
258
258
|
line_number=statement.line_number,
|
|
259
259
|
suggestion="Replace `*` with service-specific actions from supported "
|
|
260
|
-
f"services: {', '.join(sorted(rcp_supported_services))}",
|
|
260
|
+
f"services: {', '.join(f'`{a}`' for a in sorted(rcp_supported_services))}",
|
|
261
261
|
)
|
|
262
262
|
)
|
|
263
263
|
else:
|
|
@@ -275,13 +275,13 @@ async def execute_policy(
|
|
|
275
275
|
severity="error",
|
|
276
276
|
issue_type="unsupported_rcp_service",
|
|
277
277
|
message=f"Resource Control Policy contains actions from unsupported services: "
|
|
278
|
-
f"{', '.join(unsupported_actions)}. RCPs only support these services: "
|
|
279
|
-
f"{', '.join(sorted(rcp_supported_services))}",
|
|
278
|
+
f"{', '.join(f'`{a}`' for a in unsupported_actions)}. RCPs only support these services: "
|
|
279
|
+
f"{', '.join(f'`{a}`' for a in sorted(rcp_supported_services))}",
|
|
280
280
|
statement_index=idx,
|
|
281
281
|
statement_sid=statement.sid,
|
|
282
282
|
line_number=statement.line_number,
|
|
283
283
|
suggestion=f"Use only actions from supported RCP services: "
|
|
284
|
-
f"{', '.join(sorted(rcp_supported_services))}",
|
|
284
|
+
f"{', '.join(f'`{a}`' for a in sorted(rcp_supported_services))}",
|
|
285
285
|
)
|
|
286
286
|
)
|
|
287
287
|
|
|
@@ -296,8 +296,7 @@ async def execute_policy(
|
|
|
296
296
|
statement_index=idx,
|
|
297
297
|
statement_sid=statement.sid,
|
|
298
298
|
line_number=statement.line_number,
|
|
299
|
-
suggestion="Replace `NotAction` with `Action` element listing the specific "
|
|
300
|
-
"actions to deny.",
|
|
299
|
+
suggestion="Replace `NotAction` with `Action` element listing the specific actions to deny.",
|
|
301
300
|
)
|
|
302
301
|
)
|
|
303
302
|
|
|
@@ -35,9 +35,9 @@ Supports: any_of, all_of, none_of, and expected_value (single value or list)
|
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
37
|
import fnmatch
|
|
38
|
-
from typing import Any
|
|
38
|
+
from typing import Any, ClassVar
|
|
39
39
|
|
|
40
|
-
from iam_validator.core.
|
|
40
|
+
from iam_validator.core.aws_service import AWSServiceFetcher
|
|
41
41
|
from iam_validator.core.check_registry import CheckConfig, PolicyCheck
|
|
42
42
|
from iam_validator.core.config.service_principals import is_aws_service_principal
|
|
43
43
|
from iam_validator.core.models import Statement, ValidationIssue
|
|
@@ -46,17 +46,11 @@ from iam_validator.core.models import Statement, ValidationIssue
|
|
|
46
46
|
class PrincipalValidationCheck(PolicyCheck):
|
|
47
47
|
"""Validates Principal elements in resource policies."""
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def description(self) -> str:
|
|
55
|
-
return "Validates Principal elements in resource policies for security best practices"
|
|
56
|
-
|
|
57
|
-
@property
|
|
58
|
-
def default_severity(self) -> str:
|
|
59
|
-
return "high"
|
|
49
|
+
check_id: ClassVar[str] = "principal_validation"
|
|
50
|
+
description: ClassVar[str] = (
|
|
51
|
+
"Validates Principal elements in resource policies for security best practices"
|
|
52
|
+
)
|
|
53
|
+
default_severity: ClassVar[str] = "high"
|
|
60
54
|
|
|
61
55
|
async def execute(
|
|
62
56
|
self,
|
|
@@ -122,7 +116,7 @@ class PrincipalValidationCheck(PolicyCheck):
|
|
|
122
116
|
severity=self.get_severity(config),
|
|
123
117
|
issue_type="unauthorized_principal",
|
|
124
118
|
message=f"`Principal` not in allowed list: `{principal}`. "
|
|
125
|
-
f"Only principals in the `allowed_principals`
|
|
119
|
+
f"Only principals in the `allowed_principals` allow-list are permitted.",
|
|
126
120
|
statement_index=statement_idx,
|
|
127
121
|
statement_sid=statement.sid,
|
|
128
122
|
line_number=statement.line_number,
|
|
@@ -401,7 +395,6 @@ class PrincipalValidationCheck(PolicyCheck):
|
|
|
401
395
|
# Create a combined error for any_of
|
|
402
396
|
condition_keys = [cond.get("condition_key", "unknown") for cond in any_of]
|
|
403
397
|
severity = requirement.get("severity", self.get_severity(config))
|
|
404
|
-
matching_principals_str = ", ".join(f"`{p}`" for p in matching_principals)
|
|
405
398
|
issues.append(
|
|
406
399
|
ValidationIssue(
|
|
407
400
|
severity=severity,
|
|
@@ -409,7 +402,7 @@ class PrincipalValidationCheck(PolicyCheck):
|
|
|
409
402
|
statement_index=statement_idx,
|
|
410
403
|
issue_type="missing_principal_condition_any_of",
|
|
411
404
|
message=(
|
|
412
|
-
f"`Principal`s `{
|
|
405
|
+
f"`Principal`s `{', '.join(f'`{p}`' for p in matching_principals)}` require at least ONE of these conditions: "
|
|
413
406
|
f"{', '.join(f'`{c}`' for c in condition_keys)}"
|
|
414
407
|
),
|
|
415
408
|
suggestion=self._build_any_of_suggestion(any_of),
|
|
@@ -566,14 +559,12 @@ class PrincipalValidationCheck(PolicyCheck):
|
|
|
566
559
|
condition_key, description, example, expected_value, operator
|
|
567
560
|
)
|
|
568
561
|
|
|
569
|
-
matching_principals_formatted = ", ".join(f"`{p}`" for p in matching_principals)
|
|
570
|
-
|
|
571
562
|
return ValidationIssue(
|
|
572
563
|
severity=severity,
|
|
573
564
|
statement_sid=statement.sid,
|
|
574
565
|
statement_index=statement_idx,
|
|
575
566
|
issue_type="missing_principal_condition",
|
|
576
|
-
message=f"{message_prefix} Principal(s) {
|
|
567
|
+
message=f"{message_prefix} Principal(s) {', '.join(f'`{p}`' for p in matching_principals)} require condition `{condition_key}`",
|
|
577
568
|
suggestion=suggestion_text,
|
|
578
569
|
example=example_code,
|
|
579
570
|
line_number=statement.line_number,
|
|
@@ -599,7 +590,7 @@ class PrincipalValidationCheck(PolicyCheck):
|
|
|
599
590
|
Returns:
|
|
600
591
|
Tuple of (suggestion_text, example_code)
|
|
601
592
|
"""
|
|
602
|
-
suggestion = description if description else f"Add condition: {condition_key}"
|
|
593
|
+
suggestion = description if description else f"Add condition: `{condition_key}`"
|
|
603
594
|
|
|
604
595
|
# Build example based on condition key type
|
|
605
596
|
if example:
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""Resource validation check - validates ARN formats."""
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
+
from typing import ClassVar
|
|
4
5
|
|
|
5
|
-
from iam_validator.core.
|
|
6
|
+
from iam_validator.core.aws_service import AWSServiceFetcher
|
|
6
7
|
from iam_validator.core.check_registry import CheckConfig, PolicyCheck
|
|
7
8
|
from iam_validator.core.constants import DEFAULT_ARN_VALIDATION_PATTERN, MAX_ARN_LENGTH
|
|
8
9
|
from iam_validator.core.models import Statement, ValidationIssue
|
|
@@ -15,17 +16,9 @@ from iam_validator.sdk.arn_matching import (
|
|
|
15
16
|
class ResourceValidationCheck(PolicyCheck):
|
|
16
17
|
"""Validates ARN format for resources."""
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@property
|
|
23
|
-
def description(self) -> str:
|
|
24
|
-
return "Validates ARN format for resources"
|
|
25
|
-
|
|
26
|
-
@property
|
|
27
|
-
def default_severity(self) -> str:
|
|
28
|
-
return "error"
|
|
19
|
+
check_id: ClassVar[str] = "resource_validation"
|
|
20
|
+
description: ClassVar[str] = "Validates ARN format for resources"
|
|
21
|
+
default_severity: ClassVar[str] = "error"
|
|
29
22
|
|
|
30
23
|
async def execute(
|
|
31
24
|
self,
|