iam-policy-validator 1.14.7__py3-none-any.whl → 1.15.1__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.14.7.dist-info → iam_policy_validator-1.15.1.dist-info}/METADATA +16 -11
- {iam_policy_validator-1.14.7.dist-info → iam_policy_validator-1.15.1.dist-info}/RECORD +41 -28
- iam_policy_validator-1.15.1.dist-info/entry_points.txt +4 -0
- iam_validator/__version__.py +1 -1
- iam_validator/checks/__init__.py +2 -0
- iam_validator/checks/action_validation.py +91 -27
- iam_validator/checks/not_action_not_resource.py +163 -0
- iam_validator/checks/resource_validation.py +132 -81
- iam_validator/checks/wildcard_resource.py +136 -6
- iam_validator/commands/__init__.py +3 -0
- iam_validator/commands/cache.py +66 -24
- iam_validator/commands/completion.py +94 -15
- iam_validator/commands/mcp.py +210 -0
- iam_validator/commands/query.py +489 -65
- iam_validator/core/aws_service/__init__.py +5 -1
- iam_validator/core/aws_service/cache.py +20 -0
- iam_validator/core/aws_service/fetcher.py +180 -11
- iam_validator/core/aws_service/storage.py +14 -6
- iam_validator/core/aws_service/validators.py +68 -51
- iam_validator/core/check_registry.py +100 -35
- iam_validator/core/config/aws_global_conditions.py +18 -9
- iam_validator/core/config/check_documentation.py +104 -51
- iam_validator/core/config/config_loader.py +39 -3
- iam_validator/core/config/defaults.py +6 -0
- iam_validator/core/constants.py +11 -4
- iam_validator/core/models.py +39 -14
- iam_validator/mcp/__init__.py +162 -0
- iam_validator/mcp/models.py +118 -0
- iam_validator/mcp/server.py +2928 -0
- iam_validator/mcp/session_config.py +319 -0
- iam_validator/mcp/templates/__init__.py +79 -0
- iam_validator/mcp/templates/builtin.py +856 -0
- iam_validator/mcp/tools/__init__.py +72 -0
- iam_validator/mcp/tools/generation.py +888 -0
- iam_validator/mcp/tools/org_config_tools.py +263 -0
- iam_validator/mcp/tools/query.py +395 -0
- iam_validator/mcp/tools/validation.py +376 -0
- iam_validator/sdk/__init__.py +2 -0
- iam_validator/sdk/policy_utils.py +31 -5
- iam_policy_validator-1.14.7.dist-info/entry_points.txt +0 -2
- {iam_policy_validator-1.14.7.dist-info → iam_policy_validator-1.15.1.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.14.7.dist-info → iam_policy_validator-1.15.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -47,6 +47,7 @@ KNOWN_CHECK_IDS = frozenset(
|
|
|
47
47
|
"service_wildcard",
|
|
48
48
|
"sensitive_action",
|
|
49
49
|
"action_condition_enforcement",
|
|
50
|
+
"not_action_not_resource",
|
|
50
51
|
]
|
|
51
52
|
)
|
|
52
53
|
|
|
@@ -82,6 +83,7 @@ class CheckConfigSchema(BaseModel):
|
|
|
82
83
|
severity: str | None = None
|
|
83
84
|
description: str | None = None
|
|
84
85
|
ignore_patterns: list[dict[str, Any]] = []
|
|
86
|
+
hide_severities: list[str] | None = None # Per-check severity filtering
|
|
85
87
|
|
|
86
88
|
@field_validator("severity")
|
|
87
89
|
@classmethod
|
|
@@ -90,6 +92,18 @@ class CheckConfigSchema(BaseModel):
|
|
|
90
92
|
raise ValueError(f"Invalid severity: {v}. Must be one of: {sorted(SEVERITY_LEVELS)}")
|
|
91
93
|
return v
|
|
92
94
|
|
|
95
|
+
@field_validator("hide_severities")
|
|
96
|
+
@classmethod
|
|
97
|
+
def validate_hide_severities(cls, v: list[str] | None) -> list[str] | None:
|
|
98
|
+
if v is not None:
|
|
99
|
+
for severity in v:
|
|
100
|
+
if severity not in SEVERITY_LEVELS:
|
|
101
|
+
raise ValueError(
|
|
102
|
+
f"Invalid severity in hide_severities: {severity}. "
|
|
103
|
+
f"Must be one of: {sorted(SEVERITY_LEVELS)}"
|
|
104
|
+
)
|
|
105
|
+
return v
|
|
106
|
+
|
|
93
107
|
|
|
94
108
|
class IgnoreSettingsSchema(BaseModel):
|
|
95
109
|
"""Schema for ignore settings."""
|
|
@@ -122,6 +136,7 @@ class SettingsSchema(BaseModel):
|
|
|
122
136
|
severity_labels: dict[str, str | list[str]] = {}
|
|
123
137
|
ignore_settings: IgnoreSettingsSchema = IgnoreSettingsSchema()
|
|
124
138
|
documentation: DocumentationSettingsSchema = DocumentationSettingsSchema()
|
|
139
|
+
hide_severities: list[str] | None = None # Global severity filtering
|
|
125
140
|
|
|
126
141
|
@field_validator("fail_on_severity")
|
|
127
142
|
@classmethod
|
|
@@ -134,6 +149,18 @@ class SettingsSchema(BaseModel):
|
|
|
134
149
|
)
|
|
135
150
|
return v
|
|
136
151
|
|
|
152
|
+
@field_validator("hide_severities")
|
|
153
|
+
@classmethod
|
|
154
|
+
def validate_hide_severities(cls, v: list[str] | None) -> list[str] | None:
|
|
155
|
+
if v is not None:
|
|
156
|
+
for severity in v:
|
|
157
|
+
if severity not in SEVERITY_LEVELS:
|
|
158
|
+
raise ValueError(
|
|
159
|
+
f"Invalid severity in hide_severities: {severity}. "
|
|
160
|
+
f"Must be one of: {sorted(SEVERITY_LEVELS)}"
|
|
161
|
+
)
|
|
162
|
+
return v
|
|
163
|
+
|
|
137
164
|
|
|
138
165
|
class CustomCheckSchema(BaseModel):
|
|
139
166
|
"""Schema for custom check definitions."""
|
|
@@ -446,6 +473,9 @@ class ConfigLoader:
|
|
|
446
473
|
config: Loaded configuration
|
|
447
474
|
registry: Check registry to configure
|
|
448
475
|
"""
|
|
476
|
+
# Get global hide_severities from settings (for fallback)
|
|
477
|
+
global_hide_severities = config.settings.get("hide_severities")
|
|
478
|
+
|
|
449
479
|
# Configure built-in checks
|
|
450
480
|
for check in registry.get_all_checks():
|
|
451
481
|
check_id = check.check_id
|
|
@@ -455,6 +485,13 @@ class ConfigLoader:
|
|
|
455
485
|
existing_config = registry.get_config(check_id)
|
|
456
486
|
existing_enabled = existing_config.enabled if existing_config else True
|
|
457
487
|
|
|
488
|
+
# Parse hide_severities: per-check overrides global
|
|
489
|
+
hide_severities = check_config_dict.get("hide_severities")
|
|
490
|
+
if hide_severities is None:
|
|
491
|
+
hide_severities = global_hide_severities
|
|
492
|
+
if hide_severities is not None:
|
|
493
|
+
hide_severities = frozenset(hide_severities)
|
|
494
|
+
|
|
458
495
|
# Create CheckConfig object
|
|
459
496
|
# If there's explicit config, use it; otherwise preserve existing enabled state
|
|
460
497
|
check_config = CheckConfig(
|
|
@@ -464,9 +501,8 @@ class ConfigLoader:
|
|
|
464
501
|
config=check_config_dict,
|
|
465
502
|
description=check_config_dict.get("description", check.description),
|
|
466
503
|
root_config=config.config_dict, # Pass full config for cross-check access
|
|
467
|
-
ignore_patterns=check_config_dict.get(
|
|
468
|
-
|
|
469
|
-
), # NEW: Ignore patterns
|
|
504
|
+
ignore_patterns=check_config_dict.get("ignore_patterns", []),
|
|
505
|
+
hide_severities=hide_severities,
|
|
470
506
|
)
|
|
471
507
|
|
|
472
508
|
registry.configure_check(check_id, check_config)
|
|
@@ -110,6 +110,12 @@ DEFAULT_CONFIG = {
|
|
|
110
110
|
# Include AWS documentation links alongside org docs
|
|
111
111
|
"include_aws_docs": True,
|
|
112
112
|
},
|
|
113
|
+
# Severity filtering - hide specific severity levels from output
|
|
114
|
+
# When set, issues with these severities will be filtered out globally
|
|
115
|
+
# Can be overridden per-check using check-level hide_severities
|
|
116
|
+
# Valid values: "error", "warning", "info", "critical", "high", "medium", "low"
|
|
117
|
+
# Example: ["low", "info"] - hide low and info severity findings
|
|
118
|
+
"hide_severities": None,
|
|
113
119
|
},
|
|
114
120
|
# ========================================================================
|
|
115
121
|
# AWS IAM Validation Checks (17 checks total)
|
iam_validator/core/constants.py
CHANGED
|
@@ -77,6 +77,17 @@ MEDIUM_SEVERITY_LEVELS = ("warning", "medium")
|
|
|
77
77
|
# Low severity issues (informational)
|
|
78
78
|
LOW_SEVERITY_LEVELS = ("info", "low")
|
|
79
79
|
|
|
80
|
+
# Severity configuration with emoji and action guidance for PR comments
|
|
81
|
+
SEVERITY_CONFIG = {
|
|
82
|
+
"critical": {"emoji": "🔴", "action": "Block deployment"},
|
|
83
|
+
"high": {"emoji": "🟠", "action": "Fix before merge"},
|
|
84
|
+
"medium": {"emoji": "🟡", "action": "Address soon"},
|
|
85
|
+
"low": {"emoji": "🔵", "action": "Consider fixing"},
|
|
86
|
+
"error": {"emoji": "❌", "action": "Must fix - AWS will reject"},
|
|
87
|
+
"warning": {"emoji": "⚠️", "action": "Review"},
|
|
88
|
+
"info": {"emoji": "ℹ️", "action": "Optional"},
|
|
89
|
+
}
|
|
90
|
+
|
|
80
91
|
# ============================================================================
|
|
81
92
|
# GitHub Integration
|
|
82
93
|
# ============================================================================
|
|
@@ -161,10 +172,6 @@ AWS_TAG_KEY_ALLOWED_CHARS = r"a-zA-Z0-9 +\-=._:/@"
|
|
|
161
172
|
# Maximum length for AWS tag keys (per AWS documentation)
|
|
162
173
|
AWS_TAG_KEY_MAX_LENGTH = 128
|
|
163
174
|
|
|
164
|
-
# Tag-key placeholder patterns used in AWS service definitions
|
|
165
|
-
# These patterns indicate where a tag key should be substituted
|
|
166
|
-
AWS_TAG_KEY_PLACEHOLDERS = ("/tag-key", "/${TagKey}", "/${tag-key}")
|
|
167
|
-
|
|
168
175
|
# --- Tag Value Constraints ---
|
|
169
176
|
# Allowed characters in AWS tag values: letters, numbers, spaces, and + - = . _ : / @
|
|
170
177
|
# Same character set as tag keys
|
iam_validator/core/models.py
CHANGED
|
@@ -126,12 +126,24 @@ class Statement(BaseModel):
|
|
|
126
126
|
return []
|
|
127
127
|
return [self.action] if isinstance(self.action, str) else self.action
|
|
128
128
|
|
|
129
|
+
def get_not_actions(self) -> list[str]:
|
|
130
|
+
"""Get list of NotAction values, handling both string and list formats."""
|
|
131
|
+
if self.not_action is None:
|
|
132
|
+
return []
|
|
133
|
+
return [self.not_action] if isinstance(self.not_action, str) else self.not_action
|
|
134
|
+
|
|
129
135
|
def get_resources(self) -> list[str]:
|
|
130
136
|
"""Get list of resources, handling both string and list formats."""
|
|
131
137
|
if self.resource is None:
|
|
132
138
|
return []
|
|
133
139
|
return [self.resource] if isinstance(self.resource, str) else self.resource
|
|
134
140
|
|
|
141
|
+
def get_not_resources(self) -> list[str]:
|
|
142
|
+
"""Get list of NotResource values, handling both string and list formats."""
|
|
143
|
+
if self.not_resource is None:
|
|
144
|
+
return []
|
|
145
|
+
return [self.not_resource] if isinstance(self.not_resource, str) else self.not_resource
|
|
146
|
+
|
|
135
147
|
|
|
136
148
|
class IAMPolicy(BaseModel):
|
|
137
149
|
"""IAM policy document."""
|
|
@@ -179,6 +191,8 @@ class ValidationIssue(BaseModel):
|
|
|
179
191
|
documentation_url: str | None = None
|
|
180
192
|
# Step-by-step remediation guidance
|
|
181
193
|
remediation_steps: list[str] | None = None
|
|
194
|
+
# Risk category for classification (e.g., "privilege_escalation", "data_exfiltration")
|
|
195
|
+
risk_category: str | None = None
|
|
182
196
|
|
|
183
197
|
# Severity level constants (ClassVar to avoid Pydantic treating them as fields)
|
|
184
198
|
VALID_SEVERITIES: ClassVar[frozenset[str]] = frozenset(
|
|
@@ -226,18 +240,23 @@ class ValidationIssue(BaseModel):
|
|
|
226
240
|
Returns:
|
|
227
241
|
Formatted comment string
|
|
228
242
|
"""
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
"
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
243
|
+
# Get severity config with emoji and action guidance
|
|
244
|
+
severity_config = constants.SEVERITY_CONFIG.get(
|
|
245
|
+
self.severity, {"emoji": "•", "action": "Review"}
|
|
246
|
+
)
|
|
247
|
+
emoji = severity_config["emoji"]
|
|
248
|
+
action = severity_config["action"]
|
|
249
|
+
|
|
250
|
+
# Get risk category icon if available
|
|
251
|
+
from iam_validator.core.config.check_documentation import RISK_CATEGORY_ICONS
|
|
252
|
+
|
|
253
|
+
risk_icon = ""
|
|
254
|
+
if self.risk_category:
|
|
255
|
+
icon = RISK_CATEGORY_ICONS.get(self.risk_category, "")
|
|
256
|
+
if icon:
|
|
257
|
+
# Format risk category for display (e.g., "privilege_escalation" -> "Privilege Escalation")
|
|
258
|
+
category_display = self.risk_category.replace("_", " ").title()
|
|
259
|
+
risk_icon = f" | {icon} {category_display}"
|
|
241
260
|
|
|
242
261
|
parts = []
|
|
243
262
|
|
|
@@ -263,13 +282,19 @@ class ValidationIssue(BaseModel):
|
|
|
263
282
|
)
|
|
264
283
|
parts.append(f"<!-- finding-id: {finding_hash} -->\n")
|
|
265
284
|
|
|
285
|
+
# Main issue header with severity, action guidance, and risk category
|
|
286
|
+
parts.append(f"{emoji} **{self.severity.upper()}** - {action}{risk_icon}")
|
|
287
|
+
parts.append("")
|
|
288
|
+
|
|
266
289
|
# Build statement context for better navigation
|
|
267
290
|
statement_context = f"Statement[{self.statement_index}]"
|
|
268
291
|
if self.statement_sid:
|
|
269
292
|
statement_context = f"`{self.statement_sid}` ({statement_context})"
|
|
293
|
+
if self.line_number:
|
|
294
|
+
statement_context = f"{statement_context} (line {self.line_number})"
|
|
270
295
|
|
|
271
|
-
#
|
|
272
|
-
parts.append(f"
|
|
296
|
+
# Statement context on its own line
|
|
297
|
+
parts.append(f"**Statement:** {statement_context}")
|
|
273
298
|
parts.append("")
|
|
274
299
|
|
|
275
300
|
# Show message immediately (not collapsed)
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""IAM Policy Validator MCP Server.
|
|
2
|
+
|
|
3
|
+
This module provides an MCP (Model Context Protocol) server for AI assistants
|
|
4
|
+
to interact with the IAM Policy Validator. It exposes tools for:
|
|
5
|
+
- Validating IAM policies
|
|
6
|
+
- Generating policies from templates or descriptions
|
|
7
|
+
- Querying AWS service definitions
|
|
8
|
+
- Managing session-wide policy configurations
|
|
9
|
+
|
|
10
|
+
The server uses FastMCP and provides a security-first approach to policy generation.
|
|
11
|
+
|
|
12
|
+
Configuration:
|
|
13
|
+
The MCP server uses the same configuration format as the CLI validator.
|
|
14
|
+
You can load configuration from a YAML file using --config or set it
|
|
15
|
+
programmatically using SessionConfigManager.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
|
+
|
|
20
|
+
from iam_validator.mcp.models import (
|
|
21
|
+
ActionDetails,
|
|
22
|
+
GenerationResult,
|
|
23
|
+
PolicySummary,
|
|
24
|
+
ValidationResult,
|
|
25
|
+
)
|
|
26
|
+
from iam_validator.mcp.session_config import (
|
|
27
|
+
CustomInstructionsManager,
|
|
28
|
+
SessionConfigManager,
|
|
29
|
+
merge_conditions,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from fastmcp import FastMCP
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def create_server() -> "FastMCP":
|
|
37
|
+
"""Create and configure the MCP server.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
FastMCP: Configured MCP server instance
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
ImportError: If fastmcp is not installed
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
from iam_validator.mcp.server import create_server as _create_server
|
|
47
|
+
|
|
48
|
+
return _create_server()
|
|
49
|
+
except ImportError as e:
|
|
50
|
+
raise ImportError(
|
|
51
|
+
"fastmcp is required for MCP server. Install with: uv sync --extra mcp"
|
|
52
|
+
) from e
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def run_server() -> None:
|
|
56
|
+
"""Run the MCP server.
|
|
57
|
+
|
|
58
|
+
This is the entry point for the iam-validator-mcp command.
|
|
59
|
+
Supports configuration and custom instructions at startup.
|
|
60
|
+
|
|
61
|
+
Usage:
|
|
62
|
+
iam-validator-mcp
|
|
63
|
+
iam-validator-mcp --config /path/to/config.yaml
|
|
64
|
+
iam-validator-mcp --instructions "Always require MFA for sensitive actions"
|
|
65
|
+
iam-validator-mcp --instructions-file /path/to/instructions.md
|
|
66
|
+
|
|
67
|
+
Custom instructions can also be set via:
|
|
68
|
+
- Environment variable: IAM_VALIDATOR_MCP_INSTRUCTIONS
|
|
69
|
+
- Config file: custom_instructions key in YAML config
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ImportError: If fastmcp is not installed
|
|
73
|
+
"""
|
|
74
|
+
import argparse
|
|
75
|
+
import sys
|
|
76
|
+
from pathlib import Path
|
|
77
|
+
|
|
78
|
+
parser = argparse.ArgumentParser(
|
|
79
|
+
prog="iam-validator-mcp",
|
|
80
|
+
description="IAM Policy Validator MCP Server for AI assistants",
|
|
81
|
+
)
|
|
82
|
+
parser.add_argument(
|
|
83
|
+
"--config",
|
|
84
|
+
type=str,
|
|
85
|
+
metavar="FILE",
|
|
86
|
+
help="Path to configuration YAML file to load at startup",
|
|
87
|
+
)
|
|
88
|
+
parser.add_argument(
|
|
89
|
+
"--instructions",
|
|
90
|
+
type=str,
|
|
91
|
+
metavar="TEXT",
|
|
92
|
+
help="Custom instructions to append to default LLM instructions",
|
|
93
|
+
)
|
|
94
|
+
parser.add_argument(
|
|
95
|
+
"--instructions-file",
|
|
96
|
+
type=str,
|
|
97
|
+
metavar="FILE",
|
|
98
|
+
help="Path to file containing custom instructions (markdown, txt)",
|
|
99
|
+
)
|
|
100
|
+
args = parser.parse_args()
|
|
101
|
+
|
|
102
|
+
# Load config if provided (may include custom_instructions)
|
|
103
|
+
if args.config:
|
|
104
|
+
config_path = Path(args.config)
|
|
105
|
+
if not config_path.exists():
|
|
106
|
+
print(f"Error: Config file not found: {args.config}", file=sys.stderr)
|
|
107
|
+
sys.exit(1)
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
config, warnings = SessionConfigManager.load_from_file(str(config_path))
|
|
111
|
+
|
|
112
|
+
for warning in warnings:
|
|
113
|
+
print(f"Warning: {warning}", file=sys.stderr)
|
|
114
|
+
|
|
115
|
+
print(f"Loaded config from: {args.config}", file=sys.stderr)
|
|
116
|
+
|
|
117
|
+
except Exception as e:
|
|
118
|
+
print(f"Error loading config: {e}", file=sys.stderr)
|
|
119
|
+
sys.exit(1)
|
|
120
|
+
|
|
121
|
+
# Load custom instructions from CLI arguments (overrides config/env)
|
|
122
|
+
if args.instructions_file:
|
|
123
|
+
instructions_path = Path(args.instructions_file)
|
|
124
|
+
if not instructions_path.exists():
|
|
125
|
+
print(
|
|
126
|
+
f"Error: Instructions file not found: {args.instructions_file}",
|
|
127
|
+
file=sys.stderr,
|
|
128
|
+
)
|
|
129
|
+
sys.exit(1)
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
CustomInstructionsManager.load_from_file(str(instructions_path))
|
|
133
|
+
print(f"Loaded instructions from: {args.instructions_file}", file=sys.stderr)
|
|
134
|
+
except Exception as e:
|
|
135
|
+
print(f"Error loading instructions: {e}", file=sys.stderr)
|
|
136
|
+
sys.exit(1)
|
|
137
|
+
|
|
138
|
+
elif args.instructions:
|
|
139
|
+
CustomInstructionsManager.set_instructions(args.instructions, source="cli")
|
|
140
|
+
print("Custom instructions set from CLI argument", file=sys.stderr)
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
from iam_validator.mcp.server import run_server as _run_server
|
|
144
|
+
|
|
145
|
+
_run_server()
|
|
146
|
+
except ImportError as e:
|
|
147
|
+
raise ImportError(
|
|
148
|
+
"fastmcp is required for MCP server. Install with: uv sync --extra mcp"
|
|
149
|
+
) from e
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
__all__ = [
|
|
153
|
+
"create_server",
|
|
154
|
+
"run_server",
|
|
155
|
+
"ValidationResult",
|
|
156
|
+
"GenerationResult",
|
|
157
|
+
"PolicySummary",
|
|
158
|
+
"ActionDetails",
|
|
159
|
+
"SessionConfigManager",
|
|
160
|
+
"CustomInstructionsManager",
|
|
161
|
+
"merge_conditions",
|
|
162
|
+
]
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Pydantic models for MCP tool request/response types.
|
|
2
|
+
|
|
3
|
+
This module defines MCP-specific models that extend the core validation models
|
|
4
|
+
for use with the FastMCP server implementation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
from iam_validator.core.models import ValidationIssue
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ValidationResult(BaseModel):
|
|
15
|
+
"""Result of policy validation.
|
|
16
|
+
|
|
17
|
+
Used by validation tools to return validation status and issues found.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
is_valid: bool = Field(
|
|
21
|
+
description="Whether the policy passed validation (no errors or warnings)"
|
|
22
|
+
)
|
|
23
|
+
issues: list[ValidationIssue] = Field(
|
|
24
|
+
default_factory=list, description="List of validation issues found"
|
|
25
|
+
)
|
|
26
|
+
policy_file: str | None = Field(
|
|
27
|
+
default=None, description="Path to the policy file that was validated"
|
|
28
|
+
)
|
|
29
|
+
policy_type_detected: str | None = Field(
|
|
30
|
+
default=None,
|
|
31
|
+
description="The policy type used for validation: 'identity', 'resource', or 'trust'. "
|
|
32
|
+
"Shows auto-detected type when policy_type was not explicitly provided.",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class GenerationResult(BaseModel):
|
|
37
|
+
"""Result of policy generation.
|
|
38
|
+
|
|
39
|
+
Returned by all policy generation tools (from description, template, or actions).
|
|
40
|
+
Always includes validation results and security notes.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
policy: dict[str, Any] = Field(description="The generated IAM policy document")
|
|
44
|
+
validation: ValidationResult = Field(description="Validation results for the generated policy")
|
|
45
|
+
security_notes: list[str] = Field(
|
|
46
|
+
default_factory=list,
|
|
47
|
+
description="Security warnings and auto-applied conditions (e.g., 'Auto-added MFA condition')",
|
|
48
|
+
)
|
|
49
|
+
template_used: str | None = Field(
|
|
50
|
+
default=None, description="Name of the template used for generation (if applicable)"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class PolicySummary(BaseModel):
|
|
55
|
+
"""Summary of a policy's structure and contents.
|
|
56
|
+
|
|
57
|
+
Provides high-level statistics about a policy for quick analysis.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
total_statements: int = Field(description="Total number of statements in the policy")
|
|
61
|
+
allow_statements: int = Field(description="Number of statements with Effect: Allow")
|
|
62
|
+
deny_statements: int = Field(description="Number of statements with Effect: Deny")
|
|
63
|
+
services_used: list[str] = Field(
|
|
64
|
+
default_factory=list, description="List of AWS services referenced (e.g., ['s3', 'ec2'])"
|
|
65
|
+
)
|
|
66
|
+
actions_count: int = Field(description="Total number of unique actions across all statements")
|
|
67
|
+
has_wildcards: bool = Field(
|
|
68
|
+
description="Whether the policy contains wildcard actions or resources"
|
|
69
|
+
)
|
|
70
|
+
has_conditions: bool = Field(description="Whether the policy contains any conditions")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class ActionDetails(BaseModel):
|
|
74
|
+
"""Details about an AWS action.
|
|
75
|
+
|
|
76
|
+
Returned by query tools to provide comprehensive information about an IAM action.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
action: str = Field(description="Full action name (e.g., 's3:GetObject')")
|
|
80
|
+
service: str = Field(description="AWS service prefix (e.g., 's3', 'ec2')")
|
|
81
|
+
access_level: str = Field(
|
|
82
|
+
description="Access level category: Read, Write, List, Tagging, or Permissions management"
|
|
83
|
+
)
|
|
84
|
+
resource_types: list[str] = Field(
|
|
85
|
+
default_factory=list,
|
|
86
|
+
description="Resource types this action can be applied to (e.g., ['bucket', 'object'])",
|
|
87
|
+
)
|
|
88
|
+
condition_keys: list[str] = Field(
|
|
89
|
+
default_factory=list,
|
|
90
|
+
description="Condition keys that can be used with this action",
|
|
91
|
+
)
|
|
92
|
+
description: str | None = Field(
|
|
93
|
+
default=None, description="Human-readable description of what the action does"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class EnforcementResult(BaseModel):
|
|
98
|
+
"""Result of security enforcement on a policy.
|
|
99
|
+
|
|
100
|
+
Returned by the security enforcement layer after applying required conditions
|
|
101
|
+
and validating security constraints.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
policy: dict[str, Any] = Field(
|
|
105
|
+
description="The policy after security enforcement (with auto-added conditions)"
|
|
106
|
+
)
|
|
107
|
+
warnings: list[str] = Field(
|
|
108
|
+
default_factory=list,
|
|
109
|
+
description="Security warnings for issues that were auto-fixed (e.g., 'Added MFA condition')",
|
|
110
|
+
)
|
|
111
|
+
errors: list[str] = Field(
|
|
112
|
+
default_factory=list,
|
|
113
|
+
description="Security errors that could not be auto-fixed (generation should fail)",
|
|
114
|
+
)
|
|
115
|
+
conditions_added: list[str] = Field(
|
|
116
|
+
default_factory=list,
|
|
117
|
+
description="List of conditions that were automatically added (e.g., 'aws:MultiFactorAuthPresent')",
|
|
118
|
+
)
|