-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathlocalstack-iam-policy-analyzer.ts
More file actions
257 lines (218 loc) Β· 8.38 KB
/
localstack-iam-policy-analyzer.ts
File metadata and controls
257 lines (218 loc) Β· 8.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
import { z } from "zod";
import { type ToolMetadata, type InferSchema } from "xmcp";
import { LocalStackLogRetriever, type LogEntry } from "../lib/logs/log-retriever";
import { ProFeature } from "../lib/localstack/license-checker";
import { httpClient, HttpError } from "../core/http-client";
import { IAM_CONFIG_ENDPOINT } from "../core/config";
import {
enrichWithResourceData,
deduplicatePermissions,
generateIamPolicy,
formatPolicyReport,
} from "../lib/iam/iam-policy.logic";
import {
runPreflights,
requireAuthToken,
requireLocalStackCli,
requireLocalStackRunning,
requireProFeature,
} from "../core/preflight";
import { ResponseBuilder } from "../core/response-builder";
import { withToolAnalytics } from "../core/analytics";
export const schema = {
action: z
.enum(["set-mode", "analyze-policies", "get-status"])
.describe(
"The action to perform: 'set-mode' to configure enforcement, 'analyze-policies' to generate a policy from logs, or 'get-status' to check the current mode."
),
mode: z
.enum(["ENFORCED", "SOFT_MODE", "DISABLED"])
.optional()
.describe("The enforcement mode to set. This is required only when the action is 'set-mode'."),
};
export const metadata: ToolMetadata = {
name: "localstack-iam-policy-analyzer",
description:
"Configures LocalStack's IAM enforcement and analyzes logs to automatically generate missing IAM policies.",
annotations: {
title: "LocalStack IAM Policy Analyzer",
readOnlyHint: false,
destructiveHint: false,
idempotentHint: false,
},
};
// Using centralized IAM config endpoint from core/config
interface IamConfigResponse {
state: string;
[key: string]: any;
}
export default async function localstackIamPolicyAnalyzer({
action,
mode,
}: InferSchema<typeof schema>) {
return withToolAnalytics("localstack-iam-policy-analyzer", { action, mode }, async () => {
const preflightError = await runPreflights([
requireAuthToken(),
requireLocalStackCli(),
requireLocalStackRunning(),
requireProFeature(ProFeature.IAM_ENFORCEMENT),
]);
if (preflightError) return preflightError;
switch (action) {
case "get-status":
return await handleGetStatus();
case "set-mode":
if (!mode) {
return ResponseBuilder.error(
"Missing Required Parameter",
`The 'mode' parameter is required when using 'set-mode' action.
Valid modes:
- **ENFORCED**: Strict IAM enforcement (blocks unauthorized actions)
- **SOFT_MODE**: Log IAM violations without blocking
- **DISABLED**: Turn off IAM enforcement completely`
);
}
return await handleSetMode(mode);
case "analyze-policies":
return await handleAnalyzePolicies();
default:
return ResponseBuilder.error(
"Unknown action",
`Unknown action: ${action}. Supported actions: get-status, set-mode, analyze-policies`
);
}
});
}
async function handleGetStatus() {
try {
const response = await httpClient.request<IamConfigResponse>(IAM_CONFIG_ENDPOINT, {
method: "GET",
});
const currentState = response.state || "UNKNOWN";
let statusEmoji = "β οΈ";
let statusDescription = "";
switch (currentState) {
case "ENFORCED":
statusEmoji = "π";
statusDescription =
"Strict IAM enforcement is active. Unauthorized actions will be blocked.";
break;
case "SOFT_MODE":
statusEmoji = "π";
statusDescription =
"IAM violations are logged but not blocked. Good for testing and policy development.";
break;
case "DISABLED":
statusEmoji = "π";
statusDescription = "IAM enforcement is disabled. All actions are permitted.";
break;
default:
statusDescription = `Unknown state: ${currentState}`;
}
return ResponseBuilder.markdown(`${statusEmoji} **LocalStack IAM Enforcement Status**
**Current Mode:** \`${currentState}\`
${statusDescription}
**Available Actions:**
- Use \`set-mode\` to change enforcement mode
- Use \`analyze-policies\` to generate policies from recent IAM denials`);
} catch (error) {
if (error instanceof HttpError && error.status === 404) {
return ResponseBuilder.markdown(`β οΈ **LocalStack IAM Configuration Not Available**
This could mean:
- LocalStack is not running
- LocalStack version doesn't support IAM configuration
- IAM enforcement is not available in your LocalStack version
Please ensure LocalStack is running and supports IAM enforcement.`);
}
const errorMessage = error instanceof Error ? error.message : String(error);
return ResponseBuilder.markdown(`β **Failed to Get IAM Status**
Error: ${errorMessage}
**Troubleshooting:**
- Ensure LocalStack is running on port 4566
- Check if your LocalStack version supports IAM enforcement
- Verify network connectivity to LocalStack`);
}
}
async function handleSetMode(mode: "ENFORCED" | "SOFT_MODE" | "DISABLED") {
try {
const payload = { state: mode };
await httpClient.request(IAM_CONFIG_ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
let nextStepGuidance = "";
let modeEmoji = "βοΈ";
switch (mode) {
case "ENFORCED":
modeEmoji = "π";
nextStepGuidance = `
**π― Next Step:** Now, run your application, deployment, or tests that are failing due to permissions.
Once you have triggered the errors, ask me to "**analyze the IAM policies**" to automatically generate the required permissions.
**Example workflow:**
1. Deploy your CDK/Terraform stack
2. Run your application tests
3. Use \`analyze-policies\` action to generate missing IAM policies`;
break;
case "SOFT_MODE":
modeEmoji = "π";
nextStepGuidance = `
**π― Next Step:** Run your application to log IAM violations without blocking them.
This mode is perfect for:
- Understanding what permissions your app needs
- Testing policy changes safely
- Gradual migration to stricter IAM enforcement`;
break;
case "DISABLED":
modeEmoji = "π";
nextStepGuidance = `
**Note:** IAM enforcement is now disabled. All AWS actions will be permitted regardless of policies.`;
break;
}
return ResponseBuilder.markdown(`${modeEmoji} **IAM Enforcement Mode Updated**
β
IAM enforcement mode has been set to \`${mode}\`.
${nextStepGuidance}`);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return ResponseBuilder.markdown(`β **Failed to Set IAM Mode**
Error: ${errorMessage}
**Troubleshooting:**
- Ensure LocalStack is running on port 4566
- Check if your LocalStack version supports IAM configuration
- Verify you have permission to modify LocalStack settings`);
}
}
async function handleAnalyzePolicies() {
try {
const logRetriever = new LocalStackLogRetriever();
const logResult = await logRetriever.retrieveLogs(5000);
if (!logResult.success) {
return ResponseBuilder.markdown(`β **Failed to Retrieve Logs**
${logResult.errorMessage}
Please ensure LocalStack is running and generating logs.`);
}
const iamDenials = logResult.logs.filter((log) => log.isIamDenial === true);
if (iamDenials.length === 0) {
return ResponseBuilder.markdown(`β
**Analysis Complete - No IAM Denials Found**
No IAM permission errors were found in the recent logs.
**This means either:**
- Your application has all necessary permissions
- IAM enforcement is not active (check with \`get-status\`)
- No recent activity has triggered permission checks
- IAM denials occurred outside the analyzed log window
**Next steps:**
- If you expected to see denials, ensure IAM enforcement is in \`ENFORCED\` or \`SOFT_MODE\`
- Try running your application again to generate fresh logs
- Increase the log analysis window if needed`);
}
const enrichedDenials = await enrichWithResourceData(iamDenials, logResult.logs);
const uniquePermissions = deduplicatePermissions(enrichedDenials);
const iamPolicy = generateIamPolicy(uniquePermissions);
return formatPolicyReport(enrichedDenials, uniquePermissions, iamPolicy);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return ResponseBuilder.markdown(`β **Policy Analysis Failed**
Error: ${errorMessage}
Please ensure LocalStack is running and check the logs for more details.`);
}
}