iam-policy-validator 1.5.0__py3-none-any.whl → 1.6.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.5.0.dist-info → iam_policy_validator-1.6.0.dist-info}/METADATA +89 -60
- {iam_policy_validator-1.5.0.dist-info → iam_policy_validator-1.6.0.dist-info}/RECORD +40 -25
- iam_validator/__version__.py +1 -1
- iam_validator/checks/__init__.py +9 -3
- iam_validator/checks/action_condition_enforcement.py +164 -2
- iam_validator/checks/action_resource_matching.py +424 -0
- iam_validator/checks/condition_key_validation.py +3 -1
- iam_validator/checks/condition_type_mismatch.py +259 -0
- iam_validator/checks/mfa_condition_check.py +112 -0
- iam_validator/checks/sensitive_action.py +78 -6
- iam_validator/checks/set_operator_validation.py +157 -0
- iam_validator/checks/utils/sensitive_action_matcher.py +35 -1
- iam_validator/commands/cache.py +1 -1
- iam_validator/commands/validate.py +44 -11
- iam_validator/core/aws_fetcher.py +89 -52
- iam_validator/core/check_registry.py +165 -21
- iam_validator/core/condition_validators.py +626 -0
- iam_validator/core/config/__init__.py +13 -15
- iam_validator/core/config/aws_global_conditions.py +160 -0
- iam_validator/core/config/category_suggestions.py +104 -0
- iam_validator/core/config/condition_requirements.py +5 -385
- iam_validator/core/{config_loader.py → config/config_loader.py} +3 -0
- iam_validator/core/config/defaults.py +187 -54
- iam_validator/core/config/sensitive_actions.py +620 -81
- iam_validator/core/models.py +14 -1
- iam_validator/core/policy_checks.py +4 -4
- iam_validator/core/pr_commenter.py +1 -1
- iam_validator/sdk/__init__.py +187 -0
- iam_validator/sdk/arn_matching.py +274 -0
- iam_validator/sdk/context.py +222 -0
- iam_validator/sdk/exceptions.py +48 -0
- iam_validator/sdk/helpers.py +177 -0
- iam_validator/sdk/policy_utils.py +425 -0
- iam_validator/sdk/shortcuts.py +283 -0
- iam_validator/utils/__init__.py +31 -0
- iam_validator/utils/cache.py +105 -0
- iam_validator/utils/regex.py +206 -0
- iam_validator/checks/action_resource_constraint.py +0 -151
- iam_validator/core/aws_global_conditions.py +0 -137
- {iam_policy_validator-1.5.0.dist-info → iam_policy_validator-1.6.0.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.5.0.dist-info → iam_policy_validator-1.6.0.dist-info}/entry_points.txt +0 -0
- {iam_policy_validator-1.5.0.dist-info → iam_policy_validator-1.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context managers for common validation workflows.
|
|
3
|
+
|
|
4
|
+
This module provides context managers that handle resource lifecycle
|
|
5
|
+
and make the validation API more convenient to use.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from contextlib import asynccontextmanager
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from iam_validator.core.aws_fetcher import AWSServiceFetcher
|
|
12
|
+
from iam_validator.core.models import PolicyValidationResult
|
|
13
|
+
from iam_validator.core.policy_checks import validate_policies
|
|
14
|
+
from iam_validator.core.policy_loader import PolicyLoader
|
|
15
|
+
from iam_validator.core.report import ReportGenerator
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ValidationContext:
|
|
19
|
+
"""
|
|
20
|
+
Validation context that provides convenience methods with shared resources.
|
|
21
|
+
|
|
22
|
+
This class maintains a shared AWSServiceFetcher and configuration
|
|
23
|
+
across multiple validation operations, improving performance.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
fetcher: AWSServiceFetcher,
|
|
29
|
+
config_path: str | None = None,
|
|
30
|
+
):
|
|
31
|
+
"""
|
|
32
|
+
Initialize validation context.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
fetcher: AWS service fetcher instance
|
|
36
|
+
config_path: Optional path to configuration file
|
|
37
|
+
"""
|
|
38
|
+
self.fetcher = fetcher
|
|
39
|
+
self.config_path = config_path
|
|
40
|
+
self.loader = PolicyLoader()
|
|
41
|
+
|
|
42
|
+
async def validate_file(self, file_path: str | Path) -> PolicyValidationResult:
|
|
43
|
+
"""
|
|
44
|
+
Validate a single IAM policy file.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
file_path: Path to the policy file
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
PolicyValidationResult for the policy
|
|
51
|
+
"""
|
|
52
|
+
policies = self.loader.load_from_path(str(file_path))
|
|
53
|
+
|
|
54
|
+
if not policies:
|
|
55
|
+
raise ValueError(f"No IAM policies found in {file_path}")
|
|
56
|
+
|
|
57
|
+
results = await validate_policies(
|
|
58
|
+
policies,
|
|
59
|
+
config_path=self.config_path,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
results[0]
|
|
64
|
+
if results
|
|
65
|
+
else PolicyValidationResult(
|
|
66
|
+
policy_file=str(file_path),
|
|
67
|
+
is_valid=False,
|
|
68
|
+
issues=[],
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
async def validate_directory(self, dir_path: str | Path) -> list[PolicyValidationResult]:
|
|
73
|
+
"""
|
|
74
|
+
Validate all IAM policies in a directory.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
dir_path: Path to directory containing policy files
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
List of PolicyValidationResults for all policies found
|
|
81
|
+
"""
|
|
82
|
+
policies = self.loader.load_from_path(str(dir_path))
|
|
83
|
+
|
|
84
|
+
if not policies:
|
|
85
|
+
raise ValueError(f"No IAM policies found in {dir_path}")
|
|
86
|
+
|
|
87
|
+
return await validate_policies(
|
|
88
|
+
policies,
|
|
89
|
+
config_path=self.config_path,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
async def validate_json(
|
|
93
|
+
self, policy_json: dict, policy_name: str = "inline-policy"
|
|
94
|
+
) -> PolicyValidationResult:
|
|
95
|
+
"""
|
|
96
|
+
Validate an IAM policy from a Python dictionary.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
policy_json: IAM policy as a Python dict
|
|
100
|
+
policy_name: Name to identify this policy in results
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
PolicyValidationResult for the policy
|
|
104
|
+
"""
|
|
105
|
+
from iam_validator.core.models import IAMPolicy
|
|
106
|
+
|
|
107
|
+
# Parse the dict into an IAMPolicy
|
|
108
|
+
policy = IAMPolicy(**policy_json)
|
|
109
|
+
|
|
110
|
+
results = await validate_policies(
|
|
111
|
+
[(policy_name, policy)],
|
|
112
|
+
config_path=self.config_path,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
results[0]
|
|
117
|
+
if results
|
|
118
|
+
else PolicyValidationResult(
|
|
119
|
+
policy_file=policy_name,
|
|
120
|
+
is_valid=False,
|
|
121
|
+
issues=[],
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def generate_report(
|
|
126
|
+
self, results: list[PolicyValidationResult], format: str = "console"
|
|
127
|
+
) -> str:
|
|
128
|
+
"""
|
|
129
|
+
Generate a report from validation results.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
results: List of validation results
|
|
133
|
+
format: Output format (console, json, html, csv, markdown, sarif)
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Formatted report as string
|
|
137
|
+
"""
|
|
138
|
+
generator = ReportGenerator()
|
|
139
|
+
report = generator.generate_report(results)
|
|
140
|
+
|
|
141
|
+
if format == "console":
|
|
142
|
+
# Return empty string for console (it prints directly)
|
|
143
|
+
generator.print_console_report(report)
|
|
144
|
+
return ""
|
|
145
|
+
elif format == "json":
|
|
146
|
+
from iam_validator.core.formatters.json import JSONFormatter
|
|
147
|
+
|
|
148
|
+
return JSONFormatter().format(report)
|
|
149
|
+
elif format == "html":
|
|
150
|
+
from iam_validator.core.formatters.html import HTMLFormatter
|
|
151
|
+
|
|
152
|
+
return HTMLFormatter().format(report)
|
|
153
|
+
elif format == "csv":
|
|
154
|
+
from iam_validator.core.formatters.csv import CSVFormatter
|
|
155
|
+
|
|
156
|
+
return CSVFormatter().format(report)
|
|
157
|
+
elif format == "markdown":
|
|
158
|
+
from iam_validator.core.formatters.markdown import MarkdownFormatter
|
|
159
|
+
|
|
160
|
+
return MarkdownFormatter().format(report)
|
|
161
|
+
elif format == "sarif":
|
|
162
|
+
from iam_validator.core.formatters.sarif import SARIFFormatter
|
|
163
|
+
|
|
164
|
+
return SARIFFormatter().format(report)
|
|
165
|
+
else:
|
|
166
|
+
raise ValueError(f"Unknown format: {format}")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@asynccontextmanager
|
|
170
|
+
async def validator(
|
|
171
|
+
config_path: str | None = None,
|
|
172
|
+
):
|
|
173
|
+
"""
|
|
174
|
+
Context manager that handles AWS fetcher lifecycle.
|
|
175
|
+
|
|
176
|
+
This context manager creates an AWS service fetcher, provides a validation
|
|
177
|
+
context for performing multiple validations efficiently, and ensures proper
|
|
178
|
+
cleanup when done.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
config_path: Optional path to configuration file
|
|
182
|
+
|
|
183
|
+
Yields:
|
|
184
|
+
ValidationContext for performing validations
|
|
185
|
+
|
|
186
|
+
Example:
|
|
187
|
+
>>> async with validator() as v:
|
|
188
|
+
... result = await v.validate_file("policy.json")
|
|
189
|
+
... report = v.generate_report([result], format="json")
|
|
190
|
+
...
|
|
191
|
+
... # Can do multiple validations with same context
|
|
192
|
+
... result2 = await v.validate_directory("./policies")
|
|
193
|
+
|
|
194
|
+
Example with configuration:
|
|
195
|
+
>>> async with validator(config_path="./iam-validator.yaml") as v:
|
|
196
|
+
... results = await v.validate_directory("./policies")
|
|
197
|
+
... v.generate_report(results, format="console")
|
|
198
|
+
"""
|
|
199
|
+
fetcher = AWSServiceFetcher()
|
|
200
|
+
yield ValidationContext(fetcher, config_path)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@asynccontextmanager
|
|
204
|
+
async def validator_from_config(config_path: str):
|
|
205
|
+
"""
|
|
206
|
+
Context manager that loads configuration and creates a validator.
|
|
207
|
+
|
|
208
|
+
Convenience wrapper around validator() that loads config from a file.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
config_path: Path to configuration file
|
|
212
|
+
|
|
213
|
+
Yields:
|
|
214
|
+
ValidationContext configured from the config file
|
|
215
|
+
|
|
216
|
+
Example:
|
|
217
|
+
>>> async with validator_from_config("./iam-validator.yaml") as v:
|
|
218
|
+
... results = await v.validate_directory("./policies")
|
|
219
|
+
... v.generate_report(results)
|
|
220
|
+
"""
|
|
221
|
+
fetcher = AWSServiceFetcher()
|
|
222
|
+
yield ValidationContext(fetcher, config_path=config_path)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Public exception types for the IAM Policy Validator SDK.
|
|
3
|
+
|
|
4
|
+
This module defines user-facing exceptions that library users might want to catch
|
|
5
|
+
and handle in their code.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class IAMValidatorError(Exception):
|
|
10
|
+
"""Base exception for all IAM Validator errors."""
|
|
11
|
+
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PolicyLoadError(IAMValidatorError):
|
|
16
|
+
"""Raised when a policy file cannot be loaded or parsed."""
|
|
17
|
+
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PolicyValidationError(IAMValidatorError):
|
|
22
|
+
"""Raised when policy validation fails critically."""
|
|
23
|
+
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ConfigurationError(IAMValidatorError):
|
|
28
|
+
"""Raised when configuration is invalid or cannot be loaded."""
|
|
29
|
+
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AWSServiceError(IAMValidatorError):
|
|
34
|
+
"""Raised when AWS service data cannot be fetched."""
|
|
35
|
+
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class InvalidPolicyFormatError(PolicyLoadError):
|
|
40
|
+
"""Raised when policy format is invalid (not valid JSON/YAML or missing required fields)."""
|
|
41
|
+
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class UnsupportedPolicyTypeError(PolicyLoadError):
|
|
46
|
+
"""Raised when policy type is not supported."""
|
|
47
|
+
|
|
48
|
+
pass
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Helper utilities for custom check development.
|
|
3
|
+
|
|
4
|
+
This module provides high-level helper classes and functions that make it
|
|
5
|
+
easy to develop custom IAM policy checks.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from iam_validator.checks.utils.wildcard_expansion import expand_wildcard_actions
|
|
9
|
+
from iam_validator.core.aws_fetcher import AWSServiceFetcher
|
|
10
|
+
from iam_validator.core.models import ValidationIssue
|
|
11
|
+
from iam_validator.sdk.arn_matching import arn_matches, arn_strictly_valid
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CheckHelper:
|
|
15
|
+
"""
|
|
16
|
+
All-in-one helper class for custom check development.
|
|
17
|
+
|
|
18
|
+
This class provides convenient methods for common check operations like
|
|
19
|
+
ARN matching, action expansion, and issue creation.
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
>>> helper = CheckHelper(fetcher)
|
|
23
|
+
>>> actions = await helper.expand_actions(["s3:Get*"])
|
|
24
|
+
>>> if helper.arn_matches("arn:*:s3:::secret-*", resource):
|
|
25
|
+
... issue = helper.create_issue(
|
|
26
|
+
... severity="high",
|
|
27
|
+
... statement_idx=0,
|
|
28
|
+
... message="Sensitive bucket access"
|
|
29
|
+
... )
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, fetcher: AWSServiceFetcher):
|
|
33
|
+
"""
|
|
34
|
+
Initialize helper with AWS service fetcher.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
fetcher: AWS service fetcher for retrieving service definitions
|
|
38
|
+
"""
|
|
39
|
+
self.fetcher = fetcher
|
|
40
|
+
|
|
41
|
+
async def expand_actions(
|
|
42
|
+
self,
|
|
43
|
+
actions: list[str],
|
|
44
|
+
) -> list[str]:
|
|
45
|
+
"""
|
|
46
|
+
Expand action wildcards to concrete actions.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
actions: List of actions that may contain wildcards (e.g., ["s3:Get*"])
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
List of expanded action strings (e.g., ["s3:GetObject", "s3:GetObjectVersion"])
|
|
53
|
+
|
|
54
|
+
Example:
|
|
55
|
+
>>> actions = await helper.expand_actions(["s3:Get*"])
|
|
56
|
+
>>> # Returns: ["s3:GetObject", "s3:GetObjectVersion", ...]
|
|
57
|
+
"""
|
|
58
|
+
return await expand_wildcard_actions(actions, self.fetcher)
|
|
59
|
+
|
|
60
|
+
def arn_matches(
|
|
61
|
+
self,
|
|
62
|
+
pattern: str,
|
|
63
|
+
arn: str,
|
|
64
|
+
resource_type: str | None = None,
|
|
65
|
+
) -> bool:
|
|
66
|
+
"""
|
|
67
|
+
Check if ARN matches pattern with glob support.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
pattern: ARN pattern (can have wildcards)
|
|
71
|
+
arn: ARN to check (can have wildcards)
|
|
72
|
+
resource_type: Optional resource type for special handling
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
True if ARN matches pattern
|
|
76
|
+
|
|
77
|
+
Example:
|
|
78
|
+
>>> helper.arn_matches("arn:*:s3:::secret-*", "arn:aws:s3:::secret-bucket/key")
|
|
79
|
+
True
|
|
80
|
+
"""
|
|
81
|
+
return arn_matches(pattern, arn, resource_type)
|
|
82
|
+
|
|
83
|
+
def arn_strictly_valid(
|
|
84
|
+
self,
|
|
85
|
+
pattern: str,
|
|
86
|
+
arn: str,
|
|
87
|
+
resource_type: str | None = None,
|
|
88
|
+
) -> bool:
|
|
89
|
+
"""
|
|
90
|
+
Strictly validate ARN against pattern.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
pattern: ARN pattern from AWS service definition
|
|
94
|
+
arn: ARN to validate
|
|
95
|
+
resource_type: Optional resource type
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
True if ARN strictly matches pattern
|
|
99
|
+
"""
|
|
100
|
+
return arn_strictly_valid(pattern, arn, resource_type)
|
|
101
|
+
|
|
102
|
+
def create_issue(
|
|
103
|
+
self,
|
|
104
|
+
severity: str,
|
|
105
|
+
statement_idx: int,
|
|
106
|
+
message: str,
|
|
107
|
+
statement_sid: str | None = None,
|
|
108
|
+
issue_type: str = "custom",
|
|
109
|
+
action: str | None = None,
|
|
110
|
+
resource: str | None = None,
|
|
111
|
+
condition_key: str | None = None,
|
|
112
|
+
suggestion: str | None = None,
|
|
113
|
+
line_number: int | None = None,
|
|
114
|
+
) -> ValidationIssue:
|
|
115
|
+
"""
|
|
116
|
+
Create a validation issue with all necessary fields.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
severity: Severity level (critical, high, medium, low, error, warning, info)
|
|
120
|
+
statement_idx: Index of the statement in the policy
|
|
121
|
+
message: Human-readable error message
|
|
122
|
+
statement_sid: Optional statement ID
|
|
123
|
+
issue_type: Type of issue (default: "custom")
|
|
124
|
+
action: Optional action that caused the issue
|
|
125
|
+
resource: Optional resource that caused the issue
|
|
126
|
+
condition_key: Optional condition key that caused the issue
|
|
127
|
+
suggestion: Optional suggestion for fixing the issue
|
|
128
|
+
line_number: Optional line number in source file
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
ValidationIssue object
|
|
132
|
+
"""
|
|
133
|
+
return ValidationIssue(
|
|
134
|
+
severity=severity,
|
|
135
|
+
statement_sid=statement_sid,
|
|
136
|
+
statement_index=statement_idx,
|
|
137
|
+
issue_type=issue_type,
|
|
138
|
+
message=message,
|
|
139
|
+
action=action,
|
|
140
|
+
resource=resource,
|
|
141
|
+
condition_key=condition_key,
|
|
142
|
+
suggestion=suggestion,
|
|
143
|
+
line_number=line_number,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
async def expand_actions(
|
|
148
|
+
actions: list[str],
|
|
149
|
+
fetcher: AWSServiceFetcher | None = None,
|
|
150
|
+
) -> list[str]:
|
|
151
|
+
"""
|
|
152
|
+
Expand action wildcards to concrete actions.
|
|
153
|
+
|
|
154
|
+
This is a standalone function that can be used without CheckHelper.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
actions: List of actions that may contain wildcards
|
|
158
|
+
fetcher: Optional AWS service fetcher (created if not provided)
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
List of expanded action strings (e.g., ["s3:GetObject", "s3:GetObjectVersion"])
|
|
162
|
+
|
|
163
|
+
Example:
|
|
164
|
+
>>> from iam_validator.sdk import expand_actions
|
|
165
|
+
>>> actions = await expand_actions(["s3:Get*"])
|
|
166
|
+
>>> # Returns: ["s3:GetObject", "s3:GetObjectVersion", ...]
|
|
167
|
+
|
|
168
|
+
Note:
|
|
169
|
+
If no fetcher is provided, a temporary one will be created.
|
|
170
|
+
For better performance when making multiple calls, create a
|
|
171
|
+
fetcher once and pass it to this function or use CheckHelper.
|
|
172
|
+
"""
|
|
173
|
+
if fetcher is None:
|
|
174
|
+
# Create temporary fetcher
|
|
175
|
+
fetcher = AWSServiceFetcher()
|
|
176
|
+
|
|
177
|
+
return await expand_wildcard_actions(actions, fetcher)
|