iam-policy-validator 1.15.5__py3-none-any.whl → 1.16.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.15.5.dist-info → iam_policy_validator-1.16.0.dist-info}/METADATA +1 -1
- {iam_policy_validator-1.15.5.dist-info → iam_policy_validator-1.16.0.dist-info}/RECORD +14 -13
- iam_validator/__version__.py +1 -1
- iam_validator/checks/__init__.py +2 -0
- iam_validator/checks/condition_key_validation.py +62 -25
- iam_validator/checks/condition_type_mismatch.py +20 -0
- iam_validator/checks/ifexists_condition_check.py +210 -0
- iam_validator/checks/set_operator_validation.py +20 -6
- iam_validator/core/check_registry.py +1 -0
- iam_validator/core/condition_validators.py +77 -3
- iam_validator/mcp/server.py +389 -843
- {iam_policy_validator-1.15.5.dist-info → iam_policy_validator-1.16.0.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.15.5.dist-info → iam_policy_validator-1.16.0.dist-info}/entry_points.txt +0 -0
- {iam_policy_validator-1.15.5.dist-info → iam_policy_validator-1.16.0.dist-info}/licenses/LICENSE +0 -0
iam_validator/mcp/server.py
CHANGED
|
@@ -72,8 +72,7 @@ def get_shared_fetcher(ctx: Any) -> AWSServiceFetcher | None:
|
|
|
72
72
|
return lifespan_ctx["fetcher"]
|
|
73
73
|
|
|
74
74
|
logger.warning(
|
|
75
|
-
"Shared fetcher unavailable from context. "
|
|
76
|
-
"A new fetcher instance will be created, which may impact performance."
|
|
75
|
+
"Shared fetcher unavailable from context. A new fetcher instance will be created, which may impact performance."
|
|
77
76
|
)
|
|
78
77
|
return None
|
|
79
78
|
|
|
@@ -109,340 +108,85 @@ def _get_cached_checks() -> tuple[dict[str, Any], ...]:
|
|
|
109
108
|
# =============================================================================
|
|
110
109
|
|
|
111
110
|
BASE_INSTRUCTIONS = """
|
|
112
|
-
You are an AWS IAM security expert
|
|
113
|
-
|
|
114
|
-
## CORE
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
│ → What operations (read/write/admin)? │
|
|
182
|
-
│ → What specific resources (ARNs)? │
|
|
183
|
-
│ → What's the principal (Lambda, EC2, role)? |
|
|
184
|
-
│ → Are there existing org restrictions? (get_organization_config) │
|
|
185
|
-
└─────────────────────────────────────────────────────────────────────┘
|
|
186
|
-
↓
|
|
187
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
188
|
-
│ 2. CHOOSE GENERATION APPROACH │
|
|
189
|
-
│ │
|
|
190
|
-
│ Template exists? → generate_policy_from_template │
|
|
191
|
-
│ Custom needs? → build_minimal_policy │
|
|
192
|
-
│ Unknown actions? → suggest_actions → build_minimal_policy │
|
|
193
|
-
│ │
|
|
194
|
-
│ Use list_templates first to check for pre-built secure │
|
|
195
|
-
│ templates (s3-read-only, lambda-basic-execution, etc.) │
|
|
196
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
197
|
-
↓
|
|
198
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
199
|
-
│ 3. VALIDATE ONCE │
|
|
200
|
-
│ │
|
|
201
|
-
│ validate_policy → check for issues │
|
|
202
|
-
│ ⚠️ VALIDATE ONLY ONCE - DO NOT LOOP │
|
|
203
|
-
│ │
|
|
204
|
-
│ BLOCKING (must fix before presenting): │
|
|
205
|
-
│ → severity="error" - Policy won't work in AWS │
|
|
206
|
-
│ → severity="critical" - Severe security risk │
|
|
207
|
-
│ │
|
|
208
|
-
│ NON-BLOCKING (present with warnings): │
|
|
209
|
-
│ → severity="high/medium/low/warning" - Security advice │
|
|
210
|
-
│ → Present policy WITH these warnings, let user decide │
|
|
211
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
212
|
-
↓
|
|
213
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
214
|
-
│ 4. PRESENT TO USER (even with warnings) │
|
|
215
|
-
│ │
|
|
216
|
-
│ → Show the policy immediately after ONE validation │
|
|
217
|
-
│ → List any warnings/suggestions for user awareness │
|
|
218
|
-
│ → DO NOT keep fixing and re-validating in a loop │
|
|
219
|
-
│ → Let the user decide if they want changes │
|
|
220
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
## TOOL SELECTION GUIDE
|
|
224
|
-
|
|
225
|
-
| Task | Primary Tool | Fallback |
|
|
226
|
-
| --------------------- | ---------------------------------------------- | ---------------------------- |
|
|
227
|
-
| Create policy | list_templates → generate_policy_from_template | build_minimal_policy |
|
|
228
|
-
| Validate policy | validate_policy | quick_validate (summary) |
|
|
229
|
-
| Fix structural issues | fix_policy_issues | - |
|
|
230
|
-
| Get fix guidance | get_issue_guidance | read issue.example field |
|
|
231
|
-
| Find actions | query_service_actions | suggest_actions |
|
|
232
|
-
| Check action risks | check_sensitive_actions | get_required_conditions |
|
|
233
|
-
| Get ARN formats | query_arn_formats | query_action_details |
|
|
234
|
-
| Expand wildcards | expand_wildcard_action | - |
|
|
235
|
-
| Batch operations | validate_policies_batch, query_actions_batch | - |
|
|
236
|
-
| Session config | set_organization_config, check_org_compliance | - |
|
|
237
|
-
|
|
238
|
-
### Tool Hierarchy (prefer tools higher in list)
|
|
239
|
-
1. **validate_policy** - Full validation with detailed issue information
|
|
240
|
-
2. **fix_policy_issues** - Structural fixes only (Version, SIDs, action case)
|
|
241
|
-
3. **get_issue_guidance** - Detailed fix instructions for specific check_ids
|
|
242
|
-
|
|
243
|
-
## ANTI-PATTERNS TO PREVENT
|
|
244
|
-
|
|
245
|
-
1. **Overly Broad Resources**
|
|
246
|
-
BAD: `"Resource": "arn:aws:s3:::*"`
|
|
247
|
-
GOOD: `"Resource": "arn:aws:s3:::my-specific-bucket/*"`
|
|
248
|
-
|
|
249
|
-
2. **Service Wildcards Without Conditions**
|
|
250
|
-
BAD: `"Action": "s3:*"`
|
|
251
|
-
GOOD: `"Action": ["s3:GetObject", "s3:ListBucket"]` with specific resources
|
|
252
|
-
|
|
253
|
-
3. **PassRole Without Service Restriction**
|
|
254
|
-
BAD: `"Action": "iam:PassRole", "Resource": "*"`
|
|
255
|
-
GOOD: Add `"Condition": {"StringEquals": {"iam:PassedToService": "lambda.amazonaws.com"}}`
|
|
256
|
-
|
|
257
|
-
4. **Missing Secure Transport**
|
|
258
|
-
For S3, always add: `"Condition": {"StringLike": {"aws:ResourceAccount": "<aws-account-id> OR ${aws:PrincipalAccount}"}}`
|
|
259
|
-
|
|
260
|
-
5. **Cross-Account Without Controls**
|
|
261
|
-
If Principal includes external accounts, require: source IP, or org restrictions
|
|
262
|
-
|
|
263
|
-
## TRUST POLICY SPECIFICS
|
|
264
|
-
|
|
265
|
-
Trust policies control WHO can assume a role. Key differences:
|
|
266
|
-
- Principal is REQUIRED (AWS account, service, or federated user)
|
|
267
|
-
- Resource is NOT used (the role itself is the resource)
|
|
268
|
-
- Action is typically `sts:AssumeRole` only
|
|
269
|
-
|
|
270
|
-
Use validate_policy with auto-detection - it recognizes trust policies automatically.
|
|
271
|
-
For cross-account: generate_policy_from_template("cross-account-assume-role")
|
|
272
|
-
|
|
273
|
-
## HANDLING USER REQUESTS
|
|
274
|
-
|
|
275
|
-
**"Give me full access to..."**
|
|
276
|
-
→ Explain the security risks
|
|
277
|
-
→ Ask: "What specific operations do you need?"
|
|
278
|
-
→ Use suggest_actions to find minimal permissions
|
|
279
|
-
→ Never generate `"Action": "*"`
|
|
280
|
-
|
|
281
|
-
**"Just make it work"**
|
|
282
|
-
→ Still apply least privilege
|
|
283
|
-
→ Validate thoroughly
|
|
284
|
-
→ Present with security_notes explaining any risks
|
|
285
|
-
|
|
286
|
-
**After 3 failed fix attempts**
|
|
287
|
-
→ Stop and ask user for clarification
|
|
288
|
-
→ Present the specific blockers clearly
|
|
289
|
-
→ Suggest alternatives
|
|
290
|
-
|
|
291
|
-
## EXAMPLE INTERACTIONS
|
|
292
|
-
|
|
293
|
-
### Example 1: Lambda needs S3 access
|
|
294
|
-
User: "Create a policy for my Lambda to read from S3 bucket my-data-bucket"
|
|
295
|
-
|
|
296
|
-
Your workflow:
|
|
297
|
-
1. list_templates → find "s3-read-only" template
|
|
298
|
-
2. generate_policy_from_template("s3-read-only", {"bucket_name": "my-data-bucket"})
|
|
299
|
-
3. validate_policy ONCE → check result
|
|
300
|
-
4. Present policy to user with any warnings (DO NOT re-validate)
|
|
301
|
-
|
|
302
|
-
### Example 2: User requests overly broad access
|
|
303
|
-
User: "Give me full S3 access"
|
|
304
|
-
|
|
305
|
-
Your workflow:
|
|
306
|
-
1. Ask: "What specific S3 operations do you need? (read, write, delete, list)"
|
|
307
|
-
2. After clarification → query_service_actions("s3", access_level="read")
|
|
308
|
-
3. build_minimal_policy with specific actions and resources
|
|
309
|
-
4. validate_policy ONCE and present immediately (warnings are informational)
|
|
310
|
-
|
|
311
|
-
### Example 3: Validation returns warnings (DO NOT LOOP)
|
|
312
|
-
After validate_policy returns warnings like "wildcard_resource" or "sensitive_action":
|
|
313
|
-
|
|
314
|
-
WRONG approach (causes infinite loop):
|
|
315
|
-
❌ validate → fix → validate → fix → validate...
|
|
316
|
-
|
|
317
|
-
CORRECT approach:
|
|
318
|
-
✅ validate ONCE → present policy WITH warnings → let user decide
|
|
319
|
-
✅ Say: "Here's your policy. Note: it has these security considerations: [list warnings]"
|
|
320
|
-
✅ Only fix if user explicitly asks for changes
|
|
321
|
-
|
|
322
|
-
## VALIDATION ISSUE FIELDS
|
|
323
|
-
|
|
324
|
-
Each issue contains actionable guidance:
|
|
325
|
-
- `severity`: error/warning/critical/high/medium/low
|
|
326
|
-
- `message`: What's wrong
|
|
327
|
-
- `suggestion`: How to fix it
|
|
328
|
-
- `example`: **USE THIS** - shows the exact correct format
|
|
329
|
-
- `check_id`: For get_issue_guidance lookup
|
|
330
|
-
- `risk_explanation`: Why this matters
|
|
331
|
-
- `remediation_steps`: Step-by-step fix
|
|
332
|
-
|
|
333
|
-
## RESOURCES AND PROMPTS AVAILABLE
|
|
334
|
-
|
|
335
|
-
### Prompts (use for guided workflows)
|
|
336
|
-
- `generate_secure_policy` - Step-by-step policy creation with validation
|
|
337
|
-
- `fix_policy_issues_workflow` - Systematic issue fixing (max 2 iterations)
|
|
338
|
-
- `review_policy_security` - Security analysis without modification
|
|
339
|
-
|
|
340
|
-
### Resources (reference data)
|
|
341
|
-
- `iam://templates` - Pre-built secure templates
|
|
342
|
-
- `iam://checks` - All 19 validation checks
|
|
343
|
-
- `iam://sensitive-categories` - Sensitive action categories
|
|
344
|
-
- `iam://config-schema` - Configuration settings schema
|
|
345
|
-
- `iam://config-examples` - Example configurations
|
|
346
|
-
- `iam://workflow-examples` - Detailed step-by-step examples
|
|
347
|
-
|
|
348
|
-
Read iam://workflow-examples for comprehensive usage patterns.
|
|
349
|
-
|
|
350
|
-
## IAM ACTION AND POLICY FORMATTING RULES
|
|
351
|
-
|
|
352
|
-
### Action Formatting (CRITICAL)
|
|
353
|
-
- **Service prefix MUST be lowercase**: `s3:GetObject` ✓, `S3:GetObject` ✗
|
|
354
|
-
- **Action name uses PascalCase**: `s3:GetObject` ✓, `s3:getobject` ✗
|
|
355
|
-
- **Full format**: `<service>:<ActionName>` (e.g., `lambda:InvokeFunction`)
|
|
356
|
-
- **Wildcards**: Use `*` for patterns (`s3:Get*`, `s3:*Object`, `s3:*`)
|
|
357
|
-
- **Common mistakes to avoid**:
|
|
358
|
-
- `S3:GetObject` → should be `s3:GetObject` (lowercase service)
|
|
359
|
-
- `s3:getObject` → should be `s3:GetObject` (PascalCase action)
|
|
360
|
-
- `s3.GetObject` → should be `s3:GetObject` (colon separator)
|
|
361
|
-
- `arn:aws:s3:::bucket` in Action → Actions are not ARNs
|
|
362
|
-
|
|
363
|
-
### Policy Structure (REQUIRED FORMAT)
|
|
364
|
-
```json
|
|
365
|
-
{
|
|
366
|
-
"Version": "2012-10-17",
|
|
367
|
-
"Statement": [
|
|
368
|
-
{
|
|
369
|
-
"Sid": "UniqueStatementId",
|
|
370
|
-
"Effect": "Allow",
|
|
371
|
-
"Action": ["service:ActionName"],
|
|
372
|
-
"Resource": ["arn:aws:service:region:account:resource"]
|
|
373
|
-
}
|
|
374
|
-
]
|
|
375
|
-
}
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
### Version Field
|
|
379
|
-
- ALWAYS use `"Version": "2012-10-17"` (current version)
|
|
380
|
-
- `"2008-10-17"` is deprecated and lacks features like policy variables
|
|
381
|
-
|
|
382
|
-
### Statement Fields
|
|
383
|
-
| Field | Required | Type | Valid Values |
|
|
384
|
-
| ----------- | ----------- | ------------- | ------------------------------------------- |
|
|
385
|
-
| Effect | Yes | string | `"Allow"` or `"Deny"` |
|
|
386
|
-
| Action | Yes* | string/array | Service actions like `"s3:GetObject"` |
|
|
387
|
-
| NotAction | No* | string/array | Actions to exclude |
|
|
388
|
-
| Resource | Yes* | string/array | ARNs like `"arn:aws:s3:::bucket/*"` |
|
|
389
|
-
| NotResource | No* | string/array | Resources to exclude |
|
|
390
|
-
| Principal | Conditional | string/object | For resource policies only |
|
|
391
|
-
| Condition | No | object | Condition operators and keys |
|
|
392
|
-
| Sid | No | string | Statement identifier (unique within policy) |
|
|
393
|
-
|
|
394
|
-
*Either Action or NotAction required; Either Resource or NotResource required
|
|
395
|
-
|
|
396
|
-
### Resource ARN Formatting
|
|
397
|
-
- **Format**: `arn:aws:<service>:<region>:<account>:<resource>`
|
|
398
|
-
- **S3 buckets**: `arn:aws:s3:::<bucket-name>` (no region/account)
|
|
399
|
-
- **S3 objects**: `arn:aws:s3:::<bucket-name>/<key-path>`
|
|
400
|
-
- **DynamoDB tables**: `arn:aws:dynamodb:<region>:<account>:table/<table-name>`
|
|
401
|
-
- **Lambda functions**: `arn:aws:lambda:<region>:<account>:function:<function-name>`
|
|
402
|
-
- **Wildcards**: Use `*` for patterns (`arn:aws:s3:::my-bucket/*`)
|
|
403
|
-
- Use query_arn_formats to get correct ARN patterns for any service
|
|
404
|
-
|
|
405
|
-
### Condition Block Formatting
|
|
111
|
+
You are an AWS IAM security expert generating secure, least-privilege policies.
|
|
112
|
+
|
|
113
|
+
## CORE PRINCIPLES
|
|
114
|
+
- LEAST PRIVILEGE: Only permissions needed for the task
|
|
115
|
+
- RESOURCE SCOPING: Specific ARNs, never wildcards for write operations
|
|
116
|
+
- CONDITION GUARDS: Add conditions for sensitive actions (MFA, IP, time)
|
|
117
|
+
|
|
118
|
+
## ABSOLUTE RULES (GUARDRAIL: DO NOT REMOVE)
|
|
119
|
+
- NEVER generate `"Action": "*"` or `"Resource": "*"` with write actions
|
|
120
|
+
- NEVER allow `iam:*`, `sts:AssumeRole`, `kms:*` without conditions
|
|
121
|
+
- NEVER guess ARN formats - use query_arn_formats
|
|
122
|
+
- ALWAYS validate actions exist - typos create security gaps
|
|
123
|
+
- ALWAYS present security_notes from generation tools
|
|
124
|
+
|
|
125
|
+
## VALIDATION LOOP PREVENTION (GUARDRAIL: DO NOT REMOVE)
|
|
126
|
+
|
|
127
|
+
⛔ **HARD LIMIT: Maximum 2 validate_policy calls per request**
|
|
128
|
+
|
|
129
|
+
| Severity | Action |
|
|
130
|
+
|----------|--------|
|
|
131
|
+
| error/critical | Fix using `example` field |
|
|
132
|
+
| high/medium/low/warning | **PRESENT AS-IS** - informational only |
|
|
133
|
+
|
|
134
|
+
**Workflow:**
|
|
135
|
+
1. Generate policy (template or build_minimal_policy)
|
|
136
|
+
2. validate_policy (call #1) → fix error/critical only
|
|
137
|
+
3. validate_policy (call #2) → **FINAL - present policy with any warnings**
|
|
138
|
+
|
|
139
|
+
**Stop signs:** Called validate >2 times, same warning repeating, trying to "fix" warnings.
|
|
140
|
+
**When in doubt: PRESENT THE POLICY.**
|
|
141
|
+
|
|
142
|
+
## SENSITIVE ACTIONS (490+ tracked)
|
|
143
|
+
- credential_exposure (CRITICAL): sts:AssumeRole, iam:CreateAccessKey → MFA, IP limits
|
|
144
|
+
- privilege_escalation (CRITICAL): iam:AttachUserPolicy, iam:PassRole → resource scope
|
|
145
|
+
- data_access/resource_exposure (HIGH): s3:GetObject, s3:PutBucketPolicy → scope, encryption
|
|
146
|
+
|
|
147
|
+
## TOOL QUICK REFERENCE
|
|
148
|
+
| Task | Tool |
|
|
149
|
+
|------|------|
|
|
150
|
+
| Create policy | list_templates → generate_policy_from_template, or build_minimal_policy |
|
|
151
|
+
| Validate | validate_policy (full) or quick_validate (summary) |
|
|
152
|
+
| Fix structure | fix_policy_issues (Version, SIDs, case) |
|
|
153
|
+
| Fix guidance | get_issue_guidance or issue.example field |
|
|
154
|
+
| Find actions | query_service_actions or suggest_actions |
|
|
155
|
+
| ARN formats | query_arn_formats |
|
|
156
|
+
| Batch ops | validate_policies_batch, query_actions_batch |
|
|
157
|
+
|
|
158
|
+
## CONDITION KEYS (IMPORTANT)
|
|
159
|
+
When adding conditions, check BOTH:
|
|
160
|
+
1. **Action conditions**: Use get_required_conditions or query_action_details for action-specific keys
|
|
161
|
+
2. **Resource conditions**: Use query_condition_keys(service) - resources have their own condition keys
|
|
162
|
+
Example: s3:GetObject supports action conditions AND s3 bucket/object resource conditions (s3:prefix, etc.)
|
|
163
|
+
|
|
164
|
+
## ANTI-PATTERNS
|
|
165
|
+
- `"Resource": "arn:aws:s3:::*"` → use specific bucket ARN
|
|
166
|
+
- `"Action": "s3:*"` → use specific actions with resources
|
|
167
|
+
- `iam:PassRole` without `iam:PassedToService` condition
|
|
168
|
+
|
|
169
|
+
## TRUST POLICIES
|
|
170
|
+
Principal required, Resource not used. Auto-detected by validate_policy.
|
|
171
|
+
Use generate_policy_from_template("cross-account-assume-role") for cross-account.
|
|
172
|
+
|
|
173
|
+
## IAM ACTION FORMAT (CRITICAL)
|
|
174
|
+
Format: `<service>:<ActionName>` (e.g., `s3:GetObject`, `lambda:InvokeFunction`)
|
|
175
|
+
- Service: lowercase (`s3`, not `S3`)
|
|
176
|
+
- Action: PascalCase (`GetObject`, not `getobject`)
|
|
177
|
+
- Wildcards: `s3:Get*`, `s3:*`
|
|
178
|
+
|
|
179
|
+
## POLICY STRUCTURE
|
|
406
180
|
```json
|
|
407
|
-
"
|
|
408
|
-
"<ConditionOperator>": {
|
|
409
|
-
"<ConditionKey>": "<value>"
|
|
410
|
-
}
|
|
411
|
-
}
|
|
181
|
+
{"Version": "2012-10-17", "Statement": [{"Effect": "Allow", "Action": ["s3:GetObject"], "Resource": ["arn:aws:s3:::bucket/*"]}]}
|
|
412
182
|
```
|
|
183
|
+
- Version: Always "2012-10-17"
|
|
184
|
+
- Effect: "Allow" or "Deny" (exact case)
|
|
185
|
+
- Use query_arn_formats for correct ARN patterns
|
|
413
186
|
|
|
414
|
-
|
|
415
|
-
-
|
|
416
|
-
-
|
|
417
|
-
- `NumericEquals`, `NumericLessThan`, `NumericGreaterThan`
|
|
418
|
-
- `DateEquals`, `DateLessThan`, `DateGreaterThan`
|
|
419
|
-
- `Bool` (for boolean conditions like `aws:SecureTransport`)
|
|
420
|
-
- `IpAddress`, `NotIpAddress` (for source IP restrictions)
|
|
421
|
-
|
|
422
|
-
**Set operators** (for multi-value keys):
|
|
423
|
-
- `ForAllValues:StringEquals` - All values must match
|
|
424
|
-
- `ForAnyValue:StringEquals` - At least one value must match
|
|
425
|
-
|
|
426
|
-
### Principal Formatting (Resource Policies Only)
|
|
427
|
-
```json
|
|
428
|
-
"Principal": {
|
|
429
|
-
"AWS": "arn:aws:iam::123456789012:role/RoleName"
|
|
430
|
-
}
|
|
431
|
-
```
|
|
432
|
-
Or for service principals:
|
|
433
|
-
```json
|
|
434
|
-
"Principal": {
|
|
435
|
-
"Service": "lambda.amazonaws.com"
|
|
436
|
-
}
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
### Common Formatting Errors to Catch
|
|
440
|
-
1. **Missing Version**: Always include `"Version": "2012-10-17"`
|
|
441
|
-
2. **Effect typos**: `"allow"` → `"Allow"`, `"DENY"` → `"Deny"`
|
|
442
|
-
3. **Invalid ARN format**: Missing colons or wrong segment count
|
|
443
|
-
4. **Single string vs array**: `"Action": "s3:GetObject"` works but `["s3:GetObject"]` preferred
|
|
444
|
-
|
|
445
|
-
ALWAYS validate actions exist using query_action_details or validate_policy before presenting to users.
|
|
187
|
+
## RESOURCES
|
|
188
|
+
- iam://templates, iam://checks, iam://workflow-examples
|
|
189
|
+
- Prompts: generate_secure_policy, fix_policy_issues_workflow, review_policy_security
|
|
446
190
|
"""
|
|
447
191
|
|
|
448
192
|
|
|
@@ -480,37 +224,18 @@ async def validate_policy(
|
|
|
480
224
|
verbose: bool = True,
|
|
481
225
|
use_org_config: bool = True,
|
|
482
226
|
) -> dict[str, Any]:
|
|
483
|
-
"""Validate an IAM policy.
|
|
484
|
-
|
|
485
|
-
Validates a policy against AWS IAM rules and security best practices.
|
|
486
|
-
Runs all enabled checks and returns validation results.
|
|
487
|
-
|
|
488
|
-
Policy Type Auto-Detection:
|
|
489
|
-
If policy_type is None (default), the policy type is automatically detected:
|
|
490
|
-
- "trust" if contains sts:AssumeRole action (trust/assume role policy)
|
|
491
|
-
- "resource" if contains Principal/NotPrincipal (resource-based policy)
|
|
492
|
-
- "identity" otherwise (identity-based policy attached to users/roles/groups)
|
|
227
|
+
"""Validate an IAM policy against AWS rules and security best practices.
|
|
493
228
|
|
|
494
|
-
|
|
495
|
-
will use organization-specific check overrides, ignore patterns, and
|
|
496
|
-
severity settings.
|
|
229
|
+
Auto-detects policy type (identity/resource/trust) from structure if not specified.
|
|
497
230
|
|
|
498
231
|
Args:
|
|
499
|
-
policy: IAM policy
|
|
500
|
-
policy_type:
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
- "resource": Resource-based policy (attached to resources like S3 buckets)
|
|
504
|
-
- "trust": Trust policy (role assumption policy)
|
|
505
|
-
verbose: If True (default), return all issue fields. If False, return only
|
|
506
|
-
essential fields (severity, message, suggestion, check_id) to reduce tokens.
|
|
507
|
-
use_org_config: Whether to apply session organization config (default: True)
|
|
232
|
+
policy: IAM policy dictionary
|
|
233
|
+
policy_type: "identity", "resource", or "trust" (auto-detected if None)
|
|
234
|
+
verbose: Return all fields (True) or essential only (False)
|
|
235
|
+
use_org_config: Apply session org config (default: True)
|
|
508
236
|
|
|
509
237
|
Returns:
|
|
510
|
-
|
|
511
|
-
- is_valid: True if no errors/warnings found
|
|
512
|
-
- issues: List of validation issues
|
|
513
|
-
- policy_file: Source identifier
|
|
238
|
+
{is_valid, issues, policy_file}
|
|
514
239
|
"""
|
|
515
240
|
from iam_validator.mcp.tools.validation import validate_policy as _validate
|
|
516
241
|
|
|
@@ -556,19 +281,13 @@ async def validate_policy(
|
|
|
556
281
|
|
|
557
282
|
@mcp.tool()
|
|
558
283
|
async def quick_validate(policy: dict[str, Any]) -> dict[str, Any]:
|
|
559
|
-
"""Quick pass/fail validation
|
|
560
|
-
|
|
561
|
-
Lightweight validation that returns essential information:
|
|
562
|
-
whether the policy is valid, number of issues, and critical issues.
|
|
284
|
+
"""Quick pass/fail validation returning only essential info.
|
|
563
285
|
|
|
564
286
|
Args:
|
|
565
|
-
policy: IAM policy
|
|
287
|
+
policy: IAM policy dictionary
|
|
566
288
|
|
|
567
289
|
Returns:
|
|
568
|
-
|
|
569
|
-
- is_valid: Whether the policy passed validation
|
|
570
|
-
- issue_count: Total number of issues found
|
|
571
|
-
- critical_issues: List of critical/high severity issue messages
|
|
290
|
+
{is_valid, issue_count, critical_issues}
|
|
572
291
|
"""
|
|
573
292
|
from iam_validator.mcp.tools.validation import quick_validate as _quick_validate
|
|
574
293
|
|
|
@@ -584,71 +303,54 @@ async def quick_validate(policy: dict[str, Any]) -> dict[str, Any]:
|
|
|
584
303
|
async def generate_policy_from_template(
|
|
585
304
|
template_name: str,
|
|
586
305
|
variables: dict[str, str],
|
|
306
|
+
verbose: bool = False,
|
|
587
307
|
) -> dict[str, Any]:
|
|
588
308
|
"""Generate an IAM policy from a built-in template.
|
|
589
309
|
|
|
590
|
-
|
|
591
|
-
required variables with descriptions.
|
|
310
|
+
Call list_templates first to see available templates and required variables.
|
|
592
311
|
|
|
593
312
|
Args:
|
|
594
|
-
template_name: Template name
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
- lambda-basic-execution: Basic Lambda with CloudWatch logs
|
|
598
|
-
- lambda-s3-trigger: Lambda triggered by S3 events
|
|
599
|
-
- dynamodb-crud: DynamoDB table operations
|
|
600
|
-
- cloudwatch-logs: Write to CloudWatch Logs
|
|
601
|
-
variables: Dictionary of variable values. Get required variables from
|
|
602
|
-
list_templates. Common variables:
|
|
603
|
-
- bucket_name: S3 bucket name (without arn: prefix)
|
|
604
|
-
- function_name: Lambda function name
|
|
605
|
-
- table_name: DynamoDB table name
|
|
606
|
-
- account_id: 12-digit AWS account ID
|
|
607
|
-
- region: AWS region (e.g., us-east-1)
|
|
313
|
+
template_name: Template name (e.g., "s3-read-only", "lambda-basic-execution")
|
|
314
|
+
variables: Template variables (e.g., {"bucket_name": "my-bucket", "account_id": "123456789012"})
|
|
315
|
+
verbose: Return all fields (True) or essential only (False)
|
|
608
316
|
|
|
609
317
|
Returns:
|
|
610
|
-
|
|
611
|
-
- policy: The generated IAM policy (ready to use)
|
|
612
|
-
- validation: Validation results with any issues found
|
|
613
|
-
- security_notes: Security warnings to review
|
|
614
|
-
- template_used: Template name for reference
|
|
615
|
-
|
|
616
|
-
Example:
|
|
617
|
-
# First check what variables lambda-s3-trigger needs:
|
|
618
|
-
templates = await list_templates()
|
|
619
|
-
|
|
620
|
-
# Then generate with all required variables:
|
|
621
|
-
result = await generate_policy_from_template(
|
|
622
|
-
template_name="lambda-s3-trigger",
|
|
623
|
-
variables={
|
|
624
|
-
"bucket_name": "my-bucket",
|
|
625
|
-
"function_name": "my-function",
|
|
626
|
-
"account_id": "123456789012",
|
|
627
|
-
"region": "us-east-1"
|
|
628
|
-
}
|
|
629
|
-
)
|
|
318
|
+
{policy, validation, security_notes, template_used}
|
|
630
319
|
"""
|
|
631
320
|
from iam_validator.mcp.tools.generation import (
|
|
632
321
|
generate_policy_from_template as _generate,
|
|
633
322
|
)
|
|
634
323
|
|
|
635
324
|
result = await _generate(template_name=template_name, variables=variables)
|
|
325
|
+
|
|
326
|
+
if verbose:
|
|
327
|
+
issues = [
|
|
328
|
+
{
|
|
329
|
+
"severity": issue.severity,
|
|
330
|
+
"message": issue.message,
|
|
331
|
+
"suggestion": issue.suggestion,
|
|
332
|
+
"example": issue.example,
|
|
333
|
+
"check_id": issue.check_id,
|
|
334
|
+
"risk_explanation": issue.risk_explanation,
|
|
335
|
+
"remediation_steps": issue.remediation_steps,
|
|
336
|
+
}
|
|
337
|
+
for issue in result.validation.issues
|
|
338
|
+
]
|
|
339
|
+
else:
|
|
340
|
+
issues = [
|
|
341
|
+
{
|
|
342
|
+
"severity": issue.severity,
|
|
343
|
+
"message": issue.message,
|
|
344
|
+
"check_id": issue.check_id,
|
|
345
|
+
}
|
|
346
|
+
for issue in result.validation.issues
|
|
347
|
+
]
|
|
348
|
+
|
|
636
349
|
return {
|
|
637
350
|
"policy": result.policy,
|
|
638
351
|
"validation": {
|
|
639
352
|
"is_valid": result.validation.is_valid,
|
|
640
|
-
"issues":
|
|
641
|
-
{
|
|
642
|
-
"severity": issue.severity,
|
|
643
|
-
"message": issue.message,
|
|
644
|
-
"suggestion": issue.suggestion,
|
|
645
|
-
"example": issue.example,
|
|
646
|
-
"check_id": issue.check_id,
|
|
647
|
-
"risk_explanation": issue.risk_explanation,
|
|
648
|
-
"remediation_steps": issue.remediation_steps,
|
|
649
|
-
}
|
|
650
|
-
for issue in result.validation.issues
|
|
651
|
-
],
|
|
353
|
+
"issues": issues,
|
|
652
354
|
},
|
|
653
355
|
"security_notes": result.security_notes,
|
|
654
356
|
"template_used": result.template_used,
|
|
@@ -660,43 +362,51 @@ async def build_minimal_policy(
|
|
|
660
362
|
actions: list[str],
|
|
661
363
|
resources: list[str],
|
|
662
364
|
conditions: dict[str, Any] | None = None,
|
|
365
|
+
verbose: bool = False,
|
|
663
366
|
) -> dict[str, Any]:
|
|
664
367
|
"""Build a minimal IAM policy from explicit actions and resources.
|
|
665
368
|
|
|
666
|
-
Constructs a policy statement from provided actions and resources.
|
|
667
|
-
Validates that actions exist in AWS using built-in checks, warns about
|
|
668
|
-
sensitive actions, and returns security notes from validation.
|
|
669
|
-
|
|
670
369
|
Args:
|
|
671
|
-
actions:
|
|
672
|
-
resources:
|
|
673
|
-
conditions: Optional conditions to add
|
|
370
|
+
actions: AWS actions (e.g., ["s3:GetObject", "s3:ListBucket"])
|
|
371
|
+
resources: Resource ARNs (e.g., ["arn:aws:s3:::my-bucket/*"])
|
|
372
|
+
conditions: Optional conditions to add
|
|
373
|
+
verbose: Return all fields (True) or essential only (False)
|
|
674
374
|
|
|
675
375
|
Returns:
|
|
676
|
-
|
|
677
|
-
- policy: The generated IAM policy
|
|
678
|
-
- validation: Validation results from built-in checks
|
|
679
|
-
- security_notes: Security warnings from validation
|
|
376
|
+
{policy, validation, security_notes}
|
|
680
377
|
"""
|
|
681
378
|
from iam_validator.mcp.tools.generation import build_minimal_policy as _build
|
|
682
379
|
|
|
683
380
|
result = await _build(actions=actions, resources=resources, conditions=conditions)
|
|
381
|
+
|
|
382
|
+
if verbose:
|
|
383
|
+
issues = [
|
|
384
|
+
{
|
|
385
|
+
"severity": issue.severity,
|
|
386
|
+
"message": issue.message,
|
|
387
|
+
"suggestion": issue.suggestion,
|
|
388
|
+
"example": issue.example,
|
|
389
|
+
"check_id": issue.check_id,
|
|
390
|
+
"risk_explanation": issue.risk_explanation,
|
|
391
|
+
"remediation_steps": issue.remediation_steps,
|
|
392
|
+
}
|
|
393
|
+
for issue in result.validation.issues
|
|
394
|
+
]
|
|
395
|
+
else:
|
|
396
|
+
issues = [
|
|
397
|
+
{
|
|
398
|
+
"severity": issue.severity,
|
|
399
|
+
"message": issue.message,
|
|
400
|
+
"check_id": issue.check_id,
|
|
401
|
+
}
|
|
402
|
+
for issue in result.validation.issues
|
|
403
|
+
]
|
|
404
|
+
|
|
684
405
|
return {
|
|
685
406
|
"policy": result.policy,
|
|
686
407
|
"validation": {
|
|
687
408
|
"is_valid": result.validation.is_valid,
|
|
688
|
-
"issues":
|
|
689
|
-
{
|
|
690
|
-
"severity": issue.severity,
|
|
691
|
-
"message": issue.message,
|
|
692
|
-
"suggestion": issue.suggestion,
|
|
693
|
-
"example": issue.example,
|
|
694
|
-
"check_id": issue.check_id,
|
|
695
|
-
"risk_explanation": issue.risk_explanation,
|
|
696
|
-
"remediation_steps": issue.remediation_steps,
|
|
697
|
-
}
|
|
698
|
-
for issue in result.validation.issues
|
|
699
|
-
],
|
|
409
|
+
"issues": issues,
|
|
700
410
|
},
|
|
701
411
|
"security_notes": result.security_notes,
|
|
702
412
|
}
|
|
@@ -704,19 +414,12 @@ async def build_minimal_policy(
|
|
|
704
414
|
|
|
705
415
|
@mcp.tool()
|
|
706
416
|
async def list_templates() -> list[dict[str, Any]]:
|
|
707
|
-
"""List
|
|
417
|
+
"""List available policy templates and their required variables.
|
|
708
418
|
|
|
709
|
-
|
|
710
|
-
what variables each template requires.
|
|
419
|
+
Call this before generate_policy_from_template.
|
|
711
420
|
|
|
712
421
|
Returns:
|
|
713
|
-
List of
|
|
714
|
-
- name: Template identifier (pass to generate_policy_from_template)
|
|
715
|
-
- description: What the template does
|
|
716
|
-
- variables: List of required variables with:
|
|
717
|
-
- name: Variable name (key for the variables dict)
|
|
718
|
-
- description: What value to provide (e.g., "AWS account ID (12-digit number)")
|
|
719
|
-
- required: Whether the variable must be provided
|
|
422
|
+
List of {name, description, variables}
|
|
720
423
|
"""
|
|
721
424
|
from iam_validator.mcp.tools.generation import list_templates as _list_templates
|
|
722
425
|
|
|
@@ -728,14 +431,11 @@ async def suggest_actions(
|
|
|
728
431
|
description: str,
|
|
729
432
|
service: str | None = None,
|
|
730
433
|
) -> list[str]:
|
|
731
|
-
"""Suggest AWS actions based on
|
|
732
|
-
|
|
733
|
-
Uses keyword pattern matching to suggest appropriate AWS actions.
|
|
734
|
-
Useful for discovering actions when building policies.
|
|
434
|
+
"""Suggest AWS actions based on natural language description.
|
|
735
435
|
|
|
736
436
|
Args:
|
|
737
|
-
description:
|
|
738
|
-
service: Optional
|
|
437
|
+
description: What you need (e.g., "read files from S3")
|
|
438
|
+
service: Optional service filter (e.g., "s3", "lambda")
|
|
739
439
|
|
|
740
440
|
Returns:
|
|
741
441
|
List of suggested action names
|
|
@@ -747,16 +447,15 @@ async def suggest_actions(
|
|
|
747
447
|
|
|
748
448
|
@mcp.tool()
|
|
749
449
|
async def get_required_conditions(actions: list[str]) -> dict[str, Any]:
|
|
750
|
-
"""Get
|
|
450
|
+
"""Get recommended IAM conditions for actions based on security best practices.
|
|
751
451
|
|
|
752
|
-
|
|
753
|
-
best practices (e.g., MFA for sensitive actions, IP restrictions).
|
|
452
|
+
NOTE: Also check query_condition_keys(service) for resource-level conditions.
|
|
754
453
|
|
|
755
454
|
Args:
|
|
756
|
-
actions:
|
|
455
|
+
actions: AWS actions to analyze (e.g., ["iam:PassRole"])
|
|
757
456
|
|
|
758
457
|
Returns:
|
|
759
|
-
|
|
458
|
+
Condition requirements grouped by type
|
|
760
459
|
"""
|
|
761
460
|
from iam_validator.mcp.tools.generation import (
|
|
762
461
|
get_required_conditions as _get_conditions,
|
|
@@ -766,53 +465,40 @@ async def get_required_conditions(actions: list[str]) -> dict[str, Any]:
|
|
|
766
465
|
|
|
767
466
|
|
|
768
467
|
@mcp.tool()
|
|
769
|
-
async def check_sensitive_actions(
|
|
770
|
-
|
|
468
|
+
async def check_sensitive_actions(
|
|
469
|
+
actions: list[str],
|
|
470
|
+
verbose: bool = False,
|
|
471
|
+
) -> dict[str, Any]:
|
|
472
|
+
"""Check if actions are sensitive and get remediation guidance.
|
|
771
473
|
|
|
772
|
-
Analyzes actions against
|
|
773
|
-
|
|
774
|
-
recommended IAM conditions and mitigation steps.
|
|
474
|
+
Analyzes actions against 490+ sensitive actions catalog. Also verify
|
|
475
|
+
resource-level conditions with query_condition_keys(service).
|
|
775
476
|
|
|
776
477
|
Args:
|
|
777
|
-
actions:
|
|
478
|
+
actions: Actions to check (e.g., ["iam:PassRole", "s3:GetObject"])
|
|
479
|
+
verbose: Return all fields (True) or essential only (False)
|
|
778
480
|
|
|
779
481
|
Returns:
|
|
780
|
-
|
|
781
|
-
- sensitive_actions: List of dictionaries for each sensitive action found
|
|
782
|
-
- action: The action name
|
|
783
|
-
- category: Risk category (credential_exposure, data_access, priv_esc, resource_exposure)
|
|
784
|
-
- severity: Severity level (critical or high)
|
|
785
|
-
- description: Category description
|
|
786
|
-
- remediation: Mitigation guidance including:
|
|
787
|
-
- risk_level: CRITICAL or HIGH
|
|
788
|
-
- why_dangerous: Explanation of the risk
|
|
789
|
-
- recommended_conditions: List of IAM conditions to add
|
|
790
|
-
- mitigation_steps: Steps to reduce risk
|
|
791
|
-
- condition_example: JSON example of the condition block to add
|
|
792
|
-
- specific_guidance: Action-specific advice (for iam:PassRole, sts:AssumeRole, etc.)
|
|
793
|
-
- total_checked: Number of actions checked
|
|
794
|
-
- sensitive_count: Number of sensitive actions found
|
|
795
|
-
- categories_found: List of unique risk categories found
|
|
796
|
-
- has_critical: Whether any critical severity actions were found
|
|
797
|
-
- summary: Quick summary with top recommendations
|
|
798
|
-
|
|
799
|
-
Example response for iam:PassRole:
|
|
800
|
-
{
|
|
801
|
-
"action": "iam:PassRole",
|
|
802
|
-
"category": "priv_esc",
|
|
803
|
-
"severity": "critical",
|
|
804
|
-
"remediation": {
|
|
805
|
-
"recommended_conditions": [{"condition": "iam:PassedToService", ...}],
|
|
806
|
-
"condition_example": {"Condition": {"StringEquals": {"iam:PassedToService": "lambda.amazonaws.com"}}},
|
|
807
|
-
"specific_guidance": "Always restrict iam:PassRole to specific services..."
|
|
808
|
-
}
|
|
809
|
-
}
|
|
482
|
+
{sensitive_actions, total_checked, sensitive_count, categories_found, has_critical, summary}
|
|
810
483
|
"""
|
|
811
484
|
from iam_validator.mcp.tools.generation import (
|
|
812
485
|
check_sensitive_actions as _check_sensitive,
|
|
813
486
|
)
|
|
814
487
|
|
|
815
|
-
|
|
488
|
+
result = await _check_sensitive(actions=actions)
|
|
489
|
+
|
|
490
|
+
if not verbose and "sensitive_actions" in result:
|
|
491
|
+
# Lean response: only essential fields per action
|
|
492
|
+
result["sensitive_actions"] = [
|
|
493
|
+
{
|
|
494
|
+
"action": sa.get("action"),
|
|
495
|
+
"category": sa.get("category"),
|
|
496
|
+
"severity": sa.get("severity"),
|
|
497
|
+
}
|
|
498
|
+
for sa in result.get("sensitive_actions", [])
|
|
499
|
+
]
|
|
500
|
+
|
|
501
|
+
return result
|
|
816
502
|
|
|
817
503
|
|
|
818
504
|
# =============================================================================
|
|
@@ -826,20 +512,19 @@ async def query_service_actions(
|
|
|
826
512
|
access_level: str | None = None,
|
|
827
513
|
limit: int | None = None,
|
|
828
514
|
offset: int = 0,
|
|
515
|
+
verbose: bool = False,
|
|
829
516
|
) -> dict[str, Any]:
|
|
830
517
|
"""Get all actions for a service, optionally filtered by access level.
|
|
831
518
|
|
|
832
519
|
Args:
|
|
833
|
-
service:
|
|
834
|
-
access_level:
|
|
835
|
-
limit:
|
|
836
|
-
offset:
|
|
520
|
+
service: Service prefix (e.g., "s3", "iam", "ec2")
|
|
521
|
+
access_level: Filter: read|write|list|tagging|permissions-management
|
|
522
|
+
limit: Max actions to return
|
|
523
|
+
offset: Skip N actions for pagination
|
|
524
|
+
verbose: Return full action details (True) or names only (False)
|
|
837
525
|
|
|
838
526
|
Returns:
|
|
839
|
-
|
|
840
|
-
- actions: List of action names
|
|
841
|
-
- total: Total number of actions available
|
|
842
|
-
- has_more: Whether more actions are available
|
|
527
|
+
{actions, total, has_more}
|
|
843
528
|
"""
|
|
844
529
|
from iam_validator.mcp.tools.query import query_service_actions as _query
|
|
845
530
|
|
|
@@ -852,6 +537,10 @@ async def query_service_actions(
|
|
|
852
537
|
if limit:
|
|
853
538
|
all_actions = all_actions[:limit]
|
|
854
539
|
|
|
540
|
+
# Lean response: just action names as strings if not verbose
|
|
541
|
+
if not verbose and all_actions and isinstance(all_actions[0], dict):
|
|
542
|
+
all_actions = [a.get("name", a) if isinstance(a, dict) else a for a in all_actions]
|
|
543
|
+
|
|
855
544
|
return {
|
|
856
545
|
"actions": all_actions,
|
|
857
546
|
"total": total,
|
|
@@ -861,14 +550,13 @@ async def query_service_actions(
|
|
|
861
550
|
|
|
862
551
|
@mcp.tool()
|
|
863
552
|
async def query_action_details(action: str) -> dict[str, Any] | None:
|
|
864
|
-
"""Get
|
|
553
|
+
"""Get metadata for a specific action.
|
|
865
554
|
|
|
866
555
|
Args:
|
|
867
556
|
action: Full action name (e.g., "s3:GetObject", "iam:CreateUser")
|
|
868
557
|
|
|
869
558
|
Returns:
|
|
870
|
-
|
|
871
|
-
or None if not found
|
|
559
|
+
{action, service, access_level, resource_types, condition_keys, description} or None
|
|
872
560
|
"""
|
|
873
561
|
from iam_validator.mcp.tools.query import query_action_details as _query
|
|
874
562
|
|
|
@@ -887,10 +575,10 @@ async def query_action_details(action: str) -> dict[str, Any] | None:
|
|
|
887
575
|
|
|
888
576
|
@mcp.tool()
|
|
889
577
|
async def expand_wildcard_action(pattern: str) -> list[str]:
|
|
890
|
-
"""Expand
|
|
578
|
+
"""Expand wildcard action pattern to specific actions.
|
|
891
579
|
|
|
892
580
|
Args:
|
|
893
|
-
pattern:
|
|
581
|
+
pattern: Pattern with wildcards (e.g., "s3:Get*", "iam:*User*")
|
|
894
582
|
|
|
895
583
|
Returns:
|
|
896
584
|
List of matching action names
|
|
@@ -902,13 +590,15 @@ async def expand_wildcard_action(pattern: str) -> list[str]:
|
|
|
902
590
|
|
|
903
591
|
@mcp.tool()
|
|
904
592
|
async def query_condition_keys(service: str) -> list[str]:
|
|
905
|
-
"""Get
|
|
593
|
+
"""Get resource-level condition keys for a service.
|
|
594
|
+
|
|
595
|
+
Use with get_required_conditions for complete condition coverage (action + resource).
|
|
906
596
|
|
|
907
597
|
Args:
|
|
908
|
-
service:
|
|
598
|
+
service: Service prefix (e.g., "s3", "iam")
|
|
909
599
|
|
|
910
600
|
Returns:
|
|
911
|
-
List of condition
|
|
601
|
+
List of condition keys (e.g., ["s3:prefix", "s3:x-amz-acl"])
|
|
912
602
|
"""
|
|
913
603
|
from iam_validator.mcp.tools.query import query_condition_keys as _query
|
|
914
604
|
|
|
@@ -917,13 +607,13 @@ async def query_condition_keys(service: str) -> list[str]:
|
|
|
917
607
|
|
|
918
608
|
@mcp.tool()
|
|
919
609
|
async def query_arn_formats(service: str) -> list[dict[str, Any]]:
|
|
920
|
-
"""Get ARN
|
|
610
|
+
"""Get ARN format patterns for a service's resources.
|
|
921
611
|
|
|
922
612
|
Args:
|
|
923
|
-
service:
|
|
613
|
+
service: Service prefix (e.g., "s3", "iam")
|
|
924
614
|
|
|
925
615
|
Returns:
|
|
926
|
-
List of
|
|
616
|
+
List of {resource_type, arn_formats}
|
|
927
617
|
"""
|
|
928
618
|
from iam_validator.mcp.tools.query import query_arn_formats as _query
|
|
929
619
|
|
|
@@ -935,7 +625,7 @@ async def list_checks() -> list[dict[str, Any]]:
|
|
|
935
625
|
"""List all available validation checks.
|
|
936
626
|
|
|
937
627
|
Returns:
|
|
938
|
-
List of
|
|
628
|
+
List of {check_id, description, default_severity}
|
|
939
629
|
"""
|
|
940
630
|
# Use cached registry instead of creating new one each call
|
|
941
631
|
# Convert tuple back to list for API compatibility
|
|
@@ -944,20 +634,13 @@ async def list_checks() -> list[dict[str, Any]]:
|
|
|
944
634
|
|
|
945
635
|
@mcp.tool()
|
|
946
636
|
async def get_policy_summary(policy: dict[str, Any]) -> dict[str, Any]:
|
|
947
|
-
"""
|
|
637
|
+
"""Get summary statistics for a policy.
|
|
948
638
|
|
|
949
639
|
Args:
|
|
950
|
-
policy: IAM policy
|
|
640
|
+
policy: IAM policy dictionary
|
|
951
641
|
|
|
952
642
|
Returns:
|
|
953
|
-
|
|
954
|
-
- total_statements: Number of statements
|
|
955
|
-
- allow_statements: Number of Allow statements
|
|
956
|
-
- deny_statements: Number of Deny statements
|
|
957
|
-
- services_used: List of AWS services referenced
|
|
958
|
-
- actions_count: Total number of actions
|
|
959
|
-
- has_wildcards: Whether policy contains wildcards
|
|
960
|
-
- has_conditions: Whether policy has conditions
|
|
643
|
+
{total_statements, allow_statements, deny_statements, services_used, actions_count, has_wildcards, has_conditions}
|
|
961
644
|
"""
|
|
962
645
|
from iam_validator.mcp.tools.query import get_policy_summary as _get_summary
|
|
963
646
|
|
|
@@ -978,25 +661,18 @@ async def list_sensitive_actions(
|
|
|
978
661
|
category: str | None = None,
|
|
979
662
|
limit: int | None = None,
|
|
980
663
|
offset: int = 0,
|
|
664
|
+
verbose: bool = False,
|
|
981
665
|
) -> dict[str, Any]:
|
|
982
|
-
"""List sensitive actions
|
|
983
|
-
|
|
984
|
-
The sensitive actions catalog contains 490+ actions across 4 categories.
|
|
666
|
+
"""List sensitive actions from the 490+ action catalog.
|
|
985
667
|
|
|
986
668
|
Args:
|
|
987
|
-
category:
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
- resource_exposure: Actions that can expose resources (321 actions)
|
|
992
|
-
limit: Maximum number of actions to return (default: all)
|
|
993
|
-
offset: Number of actions to skip for pagination (default: 0)
|
|
669
|
+
category: Filter by "credential_exposure", "data_access", "privilege_escalation", or "resource_exposure"
|
|
670
|
+
limit: Max actions to return
|
|
671
|
+
offset: Skip N actions for pagination
|
|
672
|
+
verbose: Return full action details (True) or names only (False)
|
|
994
673
|
|
|
995
674
|
Returns:
|
|
996
|
-
|
|
997
|
-
- actions: List of sensitive action names
|
|
998
|
-
- total: Total number of actions available
|
|
999
|
-
- has_more: Whether more actions are available
|
|
675
|
+
{actions, total, has_more}
|
|
1000
676
|
"""
|
|
1001
677
|
from iam_validator.mcp.tools.query import list_sensitive_actions as _list_sensitive
|
|
1002
678
|
|
|
@@ -1009,6 +685,10 @@ async def list_sensitive_actions(
|
|
|
1009
685
|
if limit:
|
|
1010
686
|
all_actions = all_actions[:limit]
|
|
1011
687
|
|
|
688
|
+
# Lean response: just action names if not verbose
|
|
689
|
+
if not verbose and all_actions and isinstance(all_actions[0], dict):
|
|
690
|
+
all_actions = [a.get("action", a) if isinstance(a, dict) else a for a in all_actions]
|
|
691
|
+
|
|
1012
692
|
return {
|
|
1013
693
|
"actions": all_actions,
|
|
1014
694
|
"total": total,
|
|
@@ -1018,16 +698,13 @@ async def list_sensitive_actions(
|
|
|
1018
698
|
|
|
1019
699
|
@mcp.tool()
|
|
1020
700
|
async def get_condition_requirements_for_action(action: str) -> dict[str, Any] | None:
|
|
1021
|
-
"""Get
|
|
1022
|
-
|
|
1023
|
-
Checks if the action has condition requirements based on the sensitive
|
|
1024
|
-
actions catalog and condition requirements configuration.
|
|
701
|
+
"""Get condition requirements for a specific action.
|
|
1025
702
|
|
|
1026
703
|
Args:
|
|
1027
704
|
action: Full action name (e.g., "iam:PassRole", "s3:GetObject")
|
|
1028
705
|
|
|
1029
706
|
Returns:
|
|
1030
|
-
|
|
707
|
+
Condition requirements dict, or None if no requirements
|
|
1031
708
|
"""
|
|
1032
709
|
from iam_validator.mcp.tools.query import get_condition_requirements as _get_reqs
|
|
1033
710
|
|
|
@@ -1044,37 +721,20 @@ async def fix_policy_issues(
|
|
|
1044
721
|
policy: dict[str, Any],
|
|
1045
722
|
issues_to_fix: list[str] | None = None,
|
|
1046
723
|
policy_type: str | None = None,
|
|
724
|
+
verbose: bool = False,
|
|
1047
725
|
) -> dict[str, Any]:
|
|
1048
|
-
"""
|
|
1049
|
-
|
|
1050
|
-
This tool applies simple structural fixes. For security-related fixes
|
|
1051
|
-
(conditions, sensitive actions), use the suggestion and example fields
|
|
1052
|
-
from validate_policy to apply fixes manually.
|
|
1053
|
-
|
|
1054
|
-
Auto-fixable issues (structural only):
|
|
1055
|
-
- Missing Version field → adds "2012-10-17"
|
|
1056
|
-
- Duplicate SIDs → makes them unique
|
|
1057
|
-
- Action case normalization → converts "S3:GetObject" to "s3:GetObject"
|
|
726
|
+
"""Auto-fix structural policy issues (Version, duplicate SIDs, action case).
|
|
1058
727
|
|
|
1059
|
-
NOT
|
|
1060
|
-
- Action: "*" → requires user to specify which actions
|
|
1061
|
-
- Resource: "*" → requires user to specify which resources
|
|
1062
|
-
- Missing conditions → use validate_policy example field to see correct fix
|
|
1063
|
-
- Invalid actions → use query_service_actions to find valid actions
|
|
728
|
+
Does NOT fix wildcards or missing conditions - those need user input.
|
|
1064
729
|
|
|
1065
730
|
Args:
|
|
1066
|
-
policy:
|
|
1067
|
-
issues_to_fix:
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
Options: "identity", "resource", "trust"
|
|
731
|
+
policy: IAM policy to fix
|
|
732
|
+
issues_to_fix: Check IDs to fix (None = all structural fixes)
|
|
733
|
+
policy_type: "identity", "resource", or "trust" (auto-detected if None)
|
|
734
|
+
verbose: Return all fields (True) or essential only (False)
|
|
1071
735
|
|
|
1072
736
|
Returns:
|
|
1073
|
-
|
|
1074
|
-
- fixed_policy: The policy with structural fixes applied
|
|
1075
|
-
- fixes_applied: List of fixes that were applied
|
|
1076
|
-
- unfixed_issues: Issues that require manual intervention (with guidance)
|
|
1077
|
-
- validation: New validation result after fixes
|
|
737
|
+
{fixed_policy, fixes_applied, unfixed_issues, validation}
|
|
1078
738
|
"""
|
|
1079
739
|
import copy
|
|
1080
740
|
|
|
@@ -1172,47 +832,48 @@ async def fix_policy_issues(
|
|
|
1172
832
|
# Re-validate the fixed policy
|
|
1173
833
|
final_result = await _validate(policy=fixed_policy, policy_type=effective_policy_type)
|
|
1174
834
|
|
|
835
|
+
if verbose:
|
|
836
|
+
validation_issues = [
|
|
837
|
+
{
|
|
838
|
+
"severity": issue.severity,
|
|
839
|
+
"message": issue.message,
|
|
840
|
+
"suggestion": issue.suggestion,
|
|
841
|
+
"example": issue.example,
|
|
842
|
+
"check_id": issue.check_id,
|
|
843
|
+
}
|
|
844
|
+
for issue in final_result.issues
|
|
845
|
+
]
|
|
846
|
+
else:
|
|
847
|
+
validation_issues = [
|
|
848
|
+
{
|
|
849
|
+
"severity": issue.severity,
|
|
850
|
+
"message": issue.message,
|
|
851
|
+
"check_id": issue.check_id,
|
|
852
|
+
}
|
|
853
|
+
for issue in final_result.issues
|
|
854
|
+
]
|
|
855
|
+
|
|
1175
856
|
return {
|
|
1176
857
|
"fixed_policy": fixed_policy,
|
|
1177
858
|
"fixes_applied": fixes_applied,
|
|
1178
|
-
"unfixed_issues": unfixed_issues,
|
|
859
|
+
"unfixed_issues": unfixed_issues if verbose else len(unfixed_issues),
|
|
1179
860
|
"validation": {
|
|
1180
861
|
"is_valid": final_result.is_valid,
|
|
1181
862
|
"issue_count": len(final_result.issues),
|
|
1182
|
-
"issues":
|
|
1183
|
-
{
|
|
1184
|
-
"severity": issue.severity,
|
|
1185
|
-
"message": issue.message,
|
|
1186
|
-
"suggestion": issue.suggestion,
|
|
1187
|
-
"example": issue.example,
|
|
1188
|
-
"check_id": issue.check_id,
|
|
1189
|
-
}
|
|
1190
|
-
for issue in final_result.issues
|
|
1191
|
-
],
|
|
863
|
+
"issues": validation_issues,
|
|
1192
864
|
},
|
|
1193
865
|
}
|
|
1194
866
|
|
|
1195
867
|
|
|
1196
868
|
@mcp.tool()
|
|
1197
869
|
async def get_issue_guidance(check_id: str) -> dict[str, Any]:
|
|
1198
|
-
"""Get
|
|
1199
|
-
|
|
1200
|
-
Use this when you encounter a validation issue and need detailed
|
|
1201
|
-
instructions on how to resolve it. Provides step-by-step fixes.
|
|
870
|
+
"""Get step-by-step fix guidance for a validation issue.
|
|
1202
871
|
|
|
1203
872
|
Args:
|
|
1204
|
-
check_id:
|
|
1205
|
-
"action_validation", "sensitive_action")
|
|
873
|
+
check_id: Check ID (e.g., "wildcard_action", "sensitive_action")
|
|
1206
874
|
|
|
1207
875
|
Returns:
|
|
1208
|
-
|
|
1209
|
-
- check_id: The check identifier
|
|
1210
|
-
- description: What this check validates
|
|
1211
|
-
- common_causes: Why this issue typically occurs
|
|
1212
|
-
- fix_steps: Step-by-step instructions to fix
|
|
1213
|
-
- example_before: Example of problematic policy
|
|
1214
|
-
- example_after: Example of fixed policy
|
|
1215
|
-
- related_tools: MCP tools that can help fix this issue
|
|
876
|
+
{check_id, description, common_causes, fix_steps, example_before, example_after, related_tools}
|
|
1216
877
|
"""
|
|
1217
878
|
guidance_db: dict[str, dict[str, Any]] = {
|
|
1218
879
|
"wildcard_action": {
|
|
@@ -1361,24 +1022,13 @@ async def get_issue_guidance(check_id: str) -> dict[str, Any]:
|
|
|
1361
1022
|
|
|
1362
1023
|
@mcp.tool()
|
|
1363
1024
|
async def get_check_details(check_id: str) -> dict[str, Any]:
|
|
1364
|
-
"""Get full documentation for a
|
|
1365
|
-
|
|
1366
|
-
Returns comprehensive information about a check including its description,
|
|
1367
|
-
severity, configuration options, example violations, and fixes.
|
|
1025
|
+
"""Get full documentation for a validation check.
|
|
1368
1026
|
|
|
1369
1027
|
Args:
|
|
1370
|
-
check_id:
|
|
1028
|
+
check_id: Check ID (e.g., "wildcard_action", "sensitive_action")
|
|
1371
1029
|
|
|
1372
1030
|
Returns:
|
|
1373
|
-
|
|
1374
|
-
- check_id: The check identifier
|
|
1375
|
-
- description: Full description of what the check validates
|
|
1376
|
-
- default_severity: Default severity level
|
|
1377
|
-
- category: Check category (security, aws, structure)
|
|
1378
|
-
- example_violation: Example policy that would trigger this check
|
|
1379
|
-
- example_fix: How to fix the violation
|
|
1380
|
-
- configuration: Available configuration options
|
|
1381
|
-
- related_checks: Related check IDs
|
|
1031
|
+
{check_id, description, default_severity, category, example_violation, example_fix, configuration, related_checks}
|
|
1382
1032
|
"""
|
|
1383
1033
|
from iam_validator.core.check_registry import create_default_registry
|
|
1384
1034
|
|
|
@@ -1476,22 +1126,18 @@ async def get_check_details(check_id: str) -> dict[str, Any]:
|
|
|
1476
1126
|
|
|
1477
1127
|
|
|
1478
1128
|
@mcp.tool()
|
|
1479
|
-
async def explain_policy(
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
of
|
|
1129
|
+
async def explain_policy(
|
|
1130
|
+
policy: dict[str, Any],
|
|
1131
|
+
verbose: bool = False,
|
|
1132
|
+
) -> dict[str, Any]:
|
|
1133
|
+
"""Generate human-readable explanation of policy permissions and security concerns.
|
|
1484
1134
|
|
|
1485
1135
|
Args:
|
|
1486
|
-
policy: IAM policy
|
|
1136
|
+
policy: IAM policy dictionary
|
|
1137
|
+
verbose: Return all fields (True) or essential only (False)
|
|
1487
1138
|
|
|
1488
1139
|
Returns:
|
|
1489
|
-
|
|
1490
|
-
- summary: Brief one-line summary
|
|
1491
|
-
- statements: Detailed explanation of each statement
|
|
1492
|
-
- services_accessed: List of AWS services with access types
|
|
1493
|
-
- security_concerns: Identified security issues
|
|
1494
|
-
- recommendations: Suggested improvements
|
|
1140
|
+
{summary, statements, services_accessed, security_concerns, recommendations}
|
|
1495
1141
|
"""
|
|
1496
1142
|
from iam_validator.mcp.tools.query import get_policy_summary as _get_summary
|
|
1497
1143
|
|
|
@@ -1584,15 +1230,25 @@ async def explain_policy(policy: dict[str, Any]) -> dict[str, Any]:
|
|
|
1584
1230
|
total_deny = len(statements) - total_allow
|
|
1585
1231
|
brief_summary = f"Policy with {len(statements)} statement(s): {total_allow} Allow, {total_deny} Deny across {len(services_with_access)} service(s)"
|
|
1586
1232
|
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1233
|
+
if verbose:
|
|
1234
|
+
return {
|
|
1235
|
+
"summary": brief_summary,
|
|
1236
|
+
"statements": statement_explanations,
|
|
1237
|
+
"services_accessed": services_summary,
|
|
1238
|
+
"security_concerns": security_concerns,
|
|
1239
|
+
"recommendations": recommendations,
|
|
1240
|
+
"has_wildcards": summary.has_wildcards,
|
|
1241
|
+
"has_conditions": summary.has_conditions,
|
|
1242
|
+
}
|
|
1243
|
+
else:
|
|
1244
|
+
return {
|
|
1245
|
+
"summary": brief_summary,
|
|
1246
|
+
"security_concerns": security_concerns,
|
|
1247
|
+
"recommendations": recommendations,
|
|
1248
|
+
"has_wildcards": summary.has_wildcards,
|
|
1249
|
+
"statement_count": len(statement_explanations),
|
|
1250
|
+
"services_count": len(services_summary),
|
|
1251
|
+
}
|
|
1596
1252
|
|
|
1597
1253
|
|
|
1598
1254
|
@mcp.tool()
|
|
@@ -1604,23 +1260,18 @@ async def build_arn(
|
|
|
1604
1260
|
account_id: str = "",
|
|
1605
1261
|
partition: str = "aws",
|
|
1606
1262
|
) -> dict[str, Any]:
|
|
1607
|
-
"""Build a valid ARN from components.
|
|
1608
|
-
|
|
1609
|
-
Helps construct ARNs with proper format validation for the specified service.
|
|
1263
|
+
"""Build a valid ARN from components with format validation.
|
|
1610
1264
|
|
|
1611
1265
|
Args:
|
|
1612
1266
|
service: AWS service (e.g., "s3", "lambda", "dynamodb")
|
|
1613
|
-
resource_type:
|
|
1267
|
+
resource_type: Resource type (e.g., "bucket", "function", "table")
|
|
1614
1268
|
resource_name: Name of the resource
|
|
1615
|
-
region: AWS region (
|
|
1616
|
-
account_id: AWS account ID (
|
|
1617
|
-
partition:
|
|
1269
|
+
region: AWS region (empty for global resources)
|
|
1270
|
+
account_id: 12-digit AWS account ID (empty for S3)
|
|
1271
|
+
partition: "aws", "aws-cn", or "aws-us-gov"
|
|
1618
1272
|
|
|
1619
1273
|
Returns:
|
|
1620
|
-
|
|
1621
|
-
- arn: The constructed ARN
|
|
1622
|
-
- valid: Whether the ARN format is valid
|
|
1623
|
-
- notes: Any notes about the ARN format
|
|
1274
|
+
{arn, valid, notes}
|
|
1624
1275
|
"""
|
|
1625
1276
|
# ARN format patterns by service
|
|
1626
1277
|
arn_patterns: dict[str, dict[str, Any]] = {
|
|
@@ -1753,24 +1404,17 @@ async def build_arn(
|
|
|
1753
1404
|
async def compare_policies(
|
|
1754
1405
|
policy_a: dict[str, Any],
|
|
1755
1406
|
policy_b: dict[str, Any],
|
|
1407
|
+
verbose: bool = False,
|
|
1756
1408
|
) -> dict[str, Any]:
|
|
1757
1409
|
"""Compare two IAM policies and highlight differences.
|
|
1758
1410
|
|
|
1759
|
-
Analyzes both policies and shows what permissions differ between them.
|
|
1760
|
-
|
|
1761
1411
|
Args:
|
|
1762
|
-
policy_a: First
|
|
1763
|
-
policy_b: Second
|
|
1412
|
+
policy_a: First policy (baseline)
|
|
1413
|
+
policy_b: Second policy (comparison)
|
|
1414
|
+
verbose: Return all fields (True) or essential only (False)
|
|
1764
1415
|
|
|
1765
1416
|
Returns:
|
|
1766
|
-
|
|
1767
|
-
- summary: Brief comparison summary
|
|
1768
|
-
- added_actions: Actions in policy_b but not in policy_a
|
|
1769
|
-
- removed_actions: Actions in policy_a but not in policy_b
|
|
1770
|
-
- added_resources: Resources in policy_b but not in policy_a
|
|
1771
|
-
- removed_resources: Resources in policy_a but not in policy_b
|
|
1772
|
-
- condition_changes: Differences in conditions
|
|
1773
|
-
- effect_changes: Statements with different effects
|
|
1417
|
+
{summary, added_actions, removed_actions, added_resources, removed_resources, condition_changes, effect_changes}
|
|
1774
1418
|
"""
|
|
1775
1419
|
|
|
1776
1420
|
def extract_policy_elements(policy: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -1844,18 +1488,28 @@ async def compare_policies(
|
|
|
1844
1488
|
|
|
1845
1489
|
summary = ", ".join(changes) if changes else "No significant differences found"
|
|
1846
1490
|
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
"
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1491
|
+
if verbose:
|
|
1492
|
+
return {
|
|
1493
|
+
"summary": summary,
|
|
1494
|
+
"added_actions": added_actions,
|
|
1495
|
+
"removed_actions": removed_actions,
|
|
1496
|
+
"added_resources": added_resources,
|
|
1497
|
+
"removed_resources": removed_resources,
|
|
1498
|
+
"condition_changes": {
|
|
1499
|
+
"policy_a_conditions": len(elements_a["conditions"]),
|
|
1500
|
+
"policy_b_conditions": len(elements_b["conditions"]),
|
|
1501
|
+
},
|
|
1502
|
+
"effect_changes": effect_changes,
|
|
1503
|
+
}
|
|
1504
|
+
else:
|
|
1505
|
+
return {
|
|
1506
|
+
"summary": summary,
|
|
1507
|
+
"added_actions_count": len(added_actions),
|
|
1508
|
+
"removed_actions_count": len(removed_actions),
|
|
1509
|
+
"added_resources_count": len(added_resources),
|
|
1510
|
+
"removed_resources_count": len(removed_resources),
|
|
1511
|
+
"effect_changes_count": len(effect_changes),
|
|
1512
|
+
}
|
|
1859
1513
|
|
|
1860
1514
|
|
|
1861
1515
|
# =============================================================================
|
|
@@ -1870,28 +1524,15 @@ async def validate_policies_batch(
|
|
|
1870
1524
|
policy_type: str | None = None,
|
|
1871
1525
|
verbose: bool = False,
|
|
1872
1526
|
) -> list[dict[str, Any]]:
|
|
1873
|
-
"""Validate multiple IAM policies in
|
|
1874
|
-
|
|
1875
|
-
More efficient than calling validate_policy multiple times when you need
|
|
1876
|
-
to validate several policies at once. Validations run in parallel.
|
|
1877
|
-
|
|
1878
|
-
Policy Type Auto-Detection:
|
|
1879
|
-
If policy_type is None (default), each policy's type is automatically detected
|
|
1880
|
-
from its structure (see validate_policy for detection rules).
|
|
1527
|
+
"""Validate multiple IAM policies in parallel (more efficient than multiple validate_policy calls).
|
|
1881
1528
|
|
|
1882
1529
|
Args:
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
Options: "identity", "resource", "trust"
|
|
1887
|
-
verbose: If True, return all issue fields. If False (default), return
|
|
1888
|
-
only essential fields to reduce tokens.
|
|
1530
|
+
policies: List of IAM policy dictionaries
|
|
1531
|
+
policy_type: "identity", "resource", or "trust" (auto-detected if None)
|
|
1532
|
+
verbose: Return all fields (True) or essential only (False)
|
|
1889
1533
|
|
|
1890
1534
|
Returns:
|
|
1891
|
-
List of
|
|
1892
|
-
- policy_index: Index of the policy in the input list
|
|
1893
|
-
- is_valid: Whether the policy passed validation
|
|
1894
|
-
- issues: List of validation issues
|
|
1535
|
+
List of {policy_index, is_valid, issues}
|
|
1895
1536
|
"""
|
|
1896
1537
|
import asyncio
|
|
1897
1538
|
|
|
@@ -1941,18 +1582,13 @@ async def validate_policies_batch(
|
|
|
1941
1582
|
|
|
1942
1583
|
@mcp.tool()
|
|
1943
1584
|
async def query_actions_batch(actions: list[str], ctx: Context) -> dict[str, dict[str, Any] | None]:
|
|
1944
|
-
"""Get details for multiple actions in
|
|
1945
|
-
|
|
1946
|
-
More efficient than calling query_action_details multiple times when you
|
|
1947
|
-
need information about several actions at once.
|
|
1585
|
+
"""Get details for multiple actions in parallel (more efficient than multiple query_action_details calls).
|
|
1948
1586
|
|
|
1949
1587
|
Args:
|
|
1950
|
-
|
|
1951
|
-
actions: List of full action names (e.g., ["s3:GetObject", "iam:CreateUser"])
|
|
1588
|
+
actions: Action names (e.g., ["s3:GetObject", "iam:CreateUser"])
|
|
1952
1589
|
|
|
1953
1590
|
Returns:
|
|
1954
|
-
|
|
1955
|
-
Each action detail contains: service, access_level, resource_types, condition_keys
|
|
1591
|
+
Dict mapping action names to {service, access_level, resource_types, condition_keys} or None
|
|
1956
1592
|
"""
|
|
1957
1593
|
import asyncio
|
|
1958
1594
|
|
|
@@ -1986,21 +1622,19 @@ async def query_actions_batch(actions: list[str], ctx: Context) -> dict[str, dic
|
|
|
1986
1622
|
|
|
1987
1623
|
|
|
1988
1624
|
@mcp.tool()
|
|
1989
|
-
async def check_actions_batch(
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1625
|
+
async def check_actions_batch(
|
|
1626
|
+
actions: list[str],
|
|
1627
|
+
ctx: Context,
|
|
1628
|
+
verbose: bool = False,
|
|
1629
|
+
) -> dict[str, Any]:
|
|
1630
|
+
"""Validate existence and check sensitivity for multiple actions in parallel.
|
|
1994
1631
|
|
|
1995
1632
|
Args:
|
|
1996
|
-
|
|
1997
|
-
|
|
1633
|
+
actions: AWS actions to check (e.g., ["s3:GetObject", "iam:PassRole"])
|
|
1634
|
+
verbose: Return all fields (True) or essential only (False)
|
|
1998
1635
|
|
|
1999
1636
|
Returns:
|
|
2000
|
-
|
|
2001
|
-
- valid_actions: List of actions that exist in AWS
|
|
2002
|
-
- invalid_actions: List of actions that don't exist (with error messages)
|
|
2003
|
-
- sensitive_actions: List of sensitive actions with their categories
|
|
1637
|
+
{valid_actions, invalid_actions, sensitive_actions}
|
|
2004
1638
|
"""
|
|
2005
1639
|
import asyncio
|
|
2006
1640
|
|
|
@@ -2078,11 +1712,20 @@ async def check_actions_batch(actions: list[str], ctx: Context) -> dict[str, Any
|
|
|
2078
1712
|
if result["sensitive"]:
|
|
2079
1713
|
sensitive_actions.append({"action": action, **result["sensitive"]})
|
|
2080
1714
|
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
1715
|
+
if verbose:
|
|
1716
|
+
return {
|
|
1717
|
+
"valid_actions": valid_actions,
|
|
1718
|
+
"invalid_actions": invalid_actions,
|
|
1719
|
+
"sensitive_actions": sensitive_actions,
|
|
1720
|
+
}
|
|
1721
|
+
else:
|
|
1722
|
+
return {
|
|
1723
|
+
"valid_actions": valid_actions,
|
|
1724
|
+
"invalid_count": len(invalid_actions),
|
|
1725
|
+
"sensitive_count": len(sensitive_actions),
|
|
1726
|
+
"invalid_actions": [ia["action"] for ia in invalid_actions],
|
|
1727
|
+
"sensitive_actions": [sa["action"] for sa in sensitive_actions],
|
|
1728
|
+
}
|
|
2086
1729
|
|
|
2087
1730
|
|
|
2088
1731
|
# =============================================================================
|
|
@@ -2096,34 +1739,12 @@ async def set_organization_config(
|
|
|
2096
1739
|
) -> dict[str, Any]:
|
|
2097
1740
|
"""Set validator configuration for this MCP session.
|
|
2098
1741
|
|
|
2099
|
-
The configuration uses the same format as the iam-validator YAML config files.
|
|
2100
|
-
It applies to all subsequent validation operations until cleared or updated.
|
|
2101
|
-
|
|
2102
1742
|
Args:
|
|
2103
|
-
config:
|
|
2104
|
-
|
|
2105
|
-
- fail_on_severity: List of severities that cause failure
|
|
2106
|
-
(e.g., ["error", "critical", "high"])
|
|
2107
|
-
- parallel_execution: Enable parallel check execution (default: true)
|
|
2108
|
-
- fail_fast: Stop on first error (default: false)
|
|
2109
|
-
- Check IDs as keys with configuration:
|
|
2110
|
-
- enabled: Enable/disable the check (default: true)
|
|
2111
|
-
- severity: Override check severity (error|critical|high|medium|low|warning)
|
|
2112
|
-
- ignore_patterns: Patterns to skip (see docs for pattern syntax)
|
|
1743
|
+
config: Config with "settings" (fail_on_severity, parallel_execution) and
|
|
1744
|
+
check IDs as keys (enabled, severity, ignore_patterns)
|
|
2113
1745
|
|
|
2114
1746
|
Returns:
|
|
2115
|
-
|
|
2116
|
-
- success: Whether config was set successfully
|
|
2117
|
-
- applied_config: The effective configuration (settings + checks)
|
|
2118
|
-
- warnings: Any configuration warnings
|
|
2119
|
-
|
|
2120
|
-
Example:
|
|
2121
|
-
>>> result = await set_organization_config({
|
|
2122
|
-
... "settings": {"fail_on_severity": ["error", "critical"]},
|
|
2123
|
-
... "wildcard_action": {"enabled": True, "severity": "critical"},
|
|
2124
|
-
... "sensitive_action": {"enabled": True, "severity": "high"},
|
|
2125
|
-
... "policy_size": {"enabled": False} # Disable a check
|
|
2126
|
-
... })
|
|
1747
|
+
{success, applied_config, warnings}
|
|
2127
1748
|
"""
|
|
2128
1749
|
from iam_validator.mcp.tools.org_config_tools import set_organization_config_impl
|
|
2129
1750
|
|
|
@@ -2132,13 +1753,10 @@ async def set_organization_config(
|
|
|
2132
1753
|
|
|
2133
1754
|
@mcp.tool()
|
|
2134
1755
|
async def get_organization_config() -> dict[str, Any]:
|
|
2135
|
-
"""Get the current organization configuration
|
|
1756
|
+
"""Get the current session organization configuration.
|
|
2136
1757
|
|
|
2137
1758
|
Returns:
|
|
2138
|
-
|
|
2139
|
-
- has_config: Whether an organization config is currently set
|
|
2140
|
-
- config: The current configuration (or null if not set)
|
|
2141
|
-
- source: Where the config came from ("session", "yaml", or "none")
|
|
1759
|
+
{has_config, config, source}
|
|
2142
1760
|
"""
|
|
2143
1761
|
from iam_validator.mcp.tools.org_config_tools import get_organization_config_impl
|
|
2144
1762
|
|
|
@@ -2147,14 +1765,10 @@ async def get_organization_config() -> dict[str, Any]:
|
|
|
2147
1765
|
|
|
2148
1766
|
@mcp.tool()
|
|
2149
1767
|
async def clear_organization_config() -> dict[str, str]:
|
|
2150
|
-
"""Clear
|
|
2151
|
-
|
|
2152
|
-
After clearing, validation and generation will use default settings
|
|
2153
|
-
without any organization-specific restrictions.
|
|
1768
|
+
"""Clear session organization config, reverting to defaults.
|
|
2154
1769
|
|
|
2155
1770
|
Returns:
|
|
2156
|
-
|
|
2157
|
-
- status: "cleared" if config was removed, "no_config_set" if none existed
|
|
1771
|
+
{status: "cleared" or "no_config_set"}
|
|
2158
1772
|
"""
|
|
2159
1773
|
from iam_validator.mcp.tools.org_config_tools import clear_organization_config_impl
|
|
2160
1774
|
|
|
@@ -2165,39 +1779,13 @@ async def clear_organization_config() -> dict[str, str]:
|
|
|
2165
1779
|
async def load_organization_config_from_yaml(
|
|
2166
1780
|
yaml_content: str,
|
|
2167
1781
|
) -> dict[str, Any]:
|
|
2168
|
-
"""Load validator configuration from YAML content.
|
|
2169
|
-
|
|
2170
|
-
Parses YAML configuration and sets it as the session config.
|
|
2171
|
-
Uses the same format as iam-validator.yaml configuration files.
|
|
1782
|
+
"""Load validator configuration from YAML content and set as session config.
|
|
2172
1783
|
|
|
2173
1784
|
Args:
|
|
2174
|
-
yaml_content: YAML
|
|
2175
|
-
settings:
|
|
2176
|
-
fail_on_severity:
|
|
2177
|
-
- error
|
|
2178
|
-
- critical
|
|
2179
|
-
- high
|
|
2180
|
-
|
|
2181
|
-
# Enable/disable/configure specific checks
|
|
2182
|
-
wildcard_action:
|
|
2183
|
-
enabled: true
|
|
2184
|
-
severity: critical
|
|
2185
|
-
|
|
2186
|
-
sensitive_action:
|
|
2187
|
-
enabled: true
|
|
2188
|
-
severity: high
|
|
2189
|
-
ignore_patterns:
|
|
2190
|
-
- action: "^s3:Get.*" # Ignore S3 read actions
|
|
2191
|
-
|
|
2192
|
-
policy_size:
|
|
2193
|
-
enabled: false # Disable this check
|
|
1785
|
+
yaml_content: YAML string with settings and check configurations
|
|
2194
1786
|
|
|
2195
1787
|
Returns:
|
|
2196
|
-
|
|
2197
|
-
- success: Whether config was loaded successfully
|
|
2198
|
-
- applied_config: The effective configuration (settings + checks)
|
|
2199
|
-
- warnings: Any warnings (unknown keys, etc.)
|
|
2200
|
-
- error: Error message if loading failed
|
|
1788
|
+
{success, applied_config, warnings, error}
|
|
2201
1789
|
"""
|
|
2202
1790
|
from iam_validator.mcp.tools.org_config_tools import (
|
|
2203
1791
|
load_organization_config_from_yaml_impl,
|
|
@@ -2209,29 +1797,30 @@ async def load_organization_config_from_yaml(
|
|
|
2209
1797
|
@mcp.tool()
|
|
2210
1798
|
async def check_org_compliance(
|
|
2211
1799
|
policy: dict[str, Any],
|
|
1800
|
+
verbose: bool = False,
|
|
2212
1801
|
) -> dict[str, Any]:
|
|
2213
|
-
"""Validate a policy using
|
|
2214
|
-
|
|
2215
|
-
Runs the full IAM validator with the session configuration applied.
|
|
2216
|
-
This includes all enabled checks with their configured severity levels
|
|
2217
|
-
and ignore patterns.
|
|
2218
|
-
|
|
2219
|
-
If no session config is set, uses default validator settings.
|
|
1802
|
+
"""Validate a policy using session org config (or defaults if none set).
|
|
2220
1803
|
|
|
2221
1804
|
Args:
|
|
2222
|
-
policy: IAM policy
|
|
1805
|
+
policy: IAM policy dictionary
|
|
1806
|
+
verbose: Return all fields (True) or essential only (False)
|
|
2223
1807
|
|
|
2224
1808
|
Returns:
|
|
2225
|
-
|
|
2226
|
-
- compliant: True if no issues exceed fail_on_severity threshold
|
|
2227
|
-
- has_org_config: Whether a session config is set
|
|
2228
|
-
- violations: List of validation issues found (type, message, severity)
|
|
2229
|
-
- warnings: List of warnings
|
|
2230
|
-
- suggestions: How to fix issues
|
|
1809
|
+
{compliant, has_org_config, violations, warnings, suggestions}
|
|
2231
1810
|
"""
|
|
2232
1811
|
from iam_validator.mcp.tools.org_config_tools import check_org_compliance_impl
|
|
2233
1812
|
|
|
2234
|
-
|
|
1813
|
+
result = await check_org_compliance_impl(policy)
|
|
1814
|
+
|
|
1815
|
+
if not verbose:
|
|
1816
|
+
# Lean response: counts instead of full lists
|
|
1817
|
+
result["violation_count"] = len(result.get("violations", []))
|
|
1818
|
+
result["warning_count"] = len(result.get("warnings", []))
|
|
1819
|
+
if "suggestions" in result and isinstance(result["suggestions"], list):
|
|
1820
|
+
result["suggestion_count"] = len(result["suggestions"])
|
|
1821
|
+
del result["suggestions"]
|
|
1822
|
+
|
|
1823
|
+
return result
|
|
2235
1824
|
|
|
2236
1825
|
|
|
2237
1826
|
@mcp.tool()
|
|
@@ -2240,36 +1829,15 @@ async def validate_with_config(
|
|
|
2240
1829
|
config: dict[str, Any],
|
|
2241
1830
|
policy_type: str | None = None,
|
|
2242
1831
|
) -> dict[str, Any]:
|
|
2243
|
-
"""Validate a policy with
|
|
2244
|
-
|
|
2245
|
-
Useful for one-off validation with specific settings without modifying
|
|
2246
|
-
the session config. The provided config is used only for this call.
|
|
2247
|
-
|
|
2248
|
-
Policy Type Auto-Detection:
|
|
2249
|
-
If policy_type is None (default), the policy type is automatically detected
|
|
2250
|
-
from the policy structure (see validate_policy for detection rules).
|
|
1832
|
+
"""Validate a policy with inline configuration (one-off, doesn't modify session).
|
|
2251
1833
|
|
|
2252
1834
|
Args:
|
|
2253
1835
|
policy: IAM policy to validate
|
|
2254
|
-
config:
|
|
2255
|
-
|
|
2256
|
-
- Check IDs as keys: {enabled, severity, ignore_patterns}
|
|
2257
|
-
policy_type: Type of policy ("identity", "resource", "trust")
|
|
1836
|
+
config: Same format as set_organization_config
|
|
1837
|
+
policy_type: "identity", "resource", or "trust" (auto-detected if None)
|
|
2258
1838
|
|
|
2259
1839
|
Returns:
|
|
2260
|
-
|
|
2261
|
-
- is_valid: Whether the policy passed all checks
|
|
2262
|
-
- issues: List of validation issues (severity, message, suggestion, check_id)
|
|
2263
|
-
- config_applied: The configuration that was used
|
|
2264
|
-
|
|
2265
|
-
Example:
|
|
2266
|
-
>>> result = await validate_with_config(
|
|
2267
|
-
... policy=my_policy,
|
|
2268
|
-
... config={
|
|
2269
|
-
... "settings": {"fail_on_severity": ["error"]},
|
|
2270
|
-
... "wildcard_action": {"severity": "warning"} # Downgrade to warning
|
|
2271
|
-
... }
|
|
2272
|
-
... )
|
|
1840
|
+
{is_valid, issues, config_applied}
|
|
2273
1841
|
"""
|
|
2274
1842
|
from iam_validator.mcp.tools.org_config_tools import validate_with_config_impl
|
|
2275
1843
|
|
|
@@ -2285,33 +1853,15 @@ async def validate_with_config(
|
|
|
2285
1853
|
async def set_custom_instructions(
|
|
2286
1854
|
instructions: str,
|
|
2287
1855
|
) -> dict[str, Any]:
|
|
2288
|
-
"""Set custom
|
|
2289
|
-
|
|
2290
|
-
Custom instructions are appended to the default MCP server instructions,
|
|
2291
|
-
allowing organizations to add their own policy generation guidelines.
|
|
1856
|
+
"""Set custom policy generation guidelines for this session.
|
|
2292
1857
|
|
|
2293
|
-
|
|
2294
|
-
validates IAM policies during this session.
|
|
1858
|
+
Instructions are appended to default server instructions.
|
|
2295
1859
|
|
|
2296
1860
|
Args:
|
|
2297
|
-
instructions: Custom instructions text (markdown supported)
|
|
2298
|
-
- "All policies must include aws:PrincipalOrgID condition"
|
|
2299
|
-
- "Use resource tags for access control where possible"
|
|
2300
|
-
- "S3 buckets must have encryption conditions"
|
|
1861
|
+
instructions: Custom instructions text (markdown supported)
|
|
2301
1862
|
|
|
2302
1863
|
Returns:
|
|
2303
|
-
|
|
2304
|
-
- success: True if instructions were set
|
|
2305
|
-
- instructions_preview: First 200 chars of the instructions
|
|
2306
|
-
- previous_source: Source of any replaced instructions
|
|
2307
|
-
|
|
2308
|
-
Example:
|
|
2309
|
-
>>> await set_custom_instructions('''
|
|
2310
|
-
... ## Organization Security Requirements
|
|
2311
|
-
... - All policies must restrict to our AWS Organization
|
|
2312
|
-
... - MFA is required for any IAM modifications
|
|
2313
|
-
... - S3 access must include secure transport conditions
|
|
2314
|
-
... ''')
|
|
1864
|
+
{success, instructions_preview, previous_source}
|
|
2315
1865
|
"""
|
|
2316
1866
|
from iam_validator.mcp.session_config import CustomInstructionsManager
|
|
2317
1867
|
|
|
@@ -2333,13 +1883,10 @@ async def set_custom_instructions(
|
|
|
2333
1883
|
|
|
2334
1884
|
@mcp.tool()
|
|
2335
1885
|
async def get_custom_instructions() -> dict[str, Any]:
|
|
2336
|
-
"""Get
|
|
1886
|
+
"""Get current custom instructions.
|
|
2337
1887
|
|
|
2338
1888
|
Returns:
|
|
2339
|
-
|
|
2340
|
-
- has_instructions: Whether custom instructions are set
|
|
2341
|
-
- instructions: The custom instructions (or None)
|
|
2342
|
-
- source: Where the instructions came from (api, env, file, config, none)
|
|
1889
|
+
{has_instructions, instructions, source}
|
|
2343
1890
|
"""
|
|
2344
1891
|
from iam_validator.mcp.session_config import CustomInstructionsManager
|
|
2345
1892
|
|
|
@@ -2354,11 +1901,10 @@ async def get_custom_instructions() -> dict[str, Any]:
|
|
|
2354
1901
|
|
|
2355
1902
|
@mcp.tool()
|
|
2356
1903
|
async def clear_custom_instructions() -> dict[str, str]:
|
|
2357
|
-
"""Clear custom instructions, reverting to
|
|
1904
|
+
"""Clear custom instructions, reverting to defaults.
|
|
2358
1905
|
|
|
2359
1906
|
Returns:
|
|
2360
|
-
|
|
2361
|
-
- status: "cleared" if instructions were removed, "no_instructions_set" if none existed
|
|
1907
|
+
{status: "cleared" or "no_instructions_set"}
|
|
2362
1908
|
"""
|
|
2363
1909
|
from iam_validator.mcp.session_config import CustomInstructionsManager
|
|
2364
1910
|
|