Skip to main content
Glama
localstack
by localstack
iam-policy.logic.ts4.56 kB
import { type LogEntry } from "../logs/log-retriever"; interface UniquePermission { principal: string; action: string; resource: string; } export async function enrichWithResourceData( denials: LogEntry[], allLogs: LogEntry[] ): Promise<LogEntry[]> { const enriched: LogEntry[] = []; for (const denial of denials) { const enrichedDenial = { ...denial }; if (denial.timestamp && denial.iamAction) { const denialTime = new Date(denial.timestamp); const nearbyResourceLogs = allLogs.filter((log) => { if (!log.timestamp || !log.iamResource) return false; const logTime = new Date(log.timestamp); const timeDiff = Math.abs(logTime.getTime() - denialTime.getTime()); return log.iamAction === denial.iamAction && timeDiff <= 5000; }); if (nearbyResourceLogs.length > 0) { enrichedDenial.iamResource = nearbyResourceLogs[0].iamResource; } } enriched.push(enrichedDenial); } return enriched; } export function deduplicatePermissions(denials: LogEntry[]): Map<string, UniquePermission> { const permissionMap = new Map<string, UniquePermission>(); for (const denial of denials) { if (!denial.iamPrincipal || !denial.iamAction) continue; const resource = denial.iamResource || "*"; const key = `${denial.iamPrincipal}|${denial.iamAction}|${resource}`; if (!permissionMap.has(key)) { permissionMap.set(key, { principal: denial.iamPrincipal, action: denial.iamAction, resource: resource, }); } } return permissionMap; } export function generateIamPolicy(permissions: Map<string, UniquePermission>) { const resourcesToActionMap = new Map<string, Set<string>>(); for (const permission of permissions.values()) { const resource = permission.resource || "*"; if (!resourcesToActionMap.has(resource)) { resourcesToActionMap.set(resource, new Set()); } resourcesToActionMap.get(resource)!.add(permission.action); } const statements = Array.from(resourcesToActionMap.entries()).map(([resource, actions], i) => ({ Sid: `GeneratedStatement${i + 1}`, Effect: "Allow", Action: Array.from(actions).sort(), Resource: resource, })); return { Version: "2012-10-17", Statement: statements, }; } export function formatPolicyReport( denials: LogEntry[], permissions: Map<string, UniquePermission>, policy: any ) { const uniquePrincipals = new Set(Array.from(permissions.values()).map((p) => p.principal)); let result = `# 🔍 IAM Policy Analysis Report\n\n`; result += `**Analysis Summary:**\n`; result += `- Found **${denials.length}** IAM permission errors.\n`; result += `- Identified **${permissions.size}** unique missing permissions.\n`; result += `- This affects **${uniquePrincipals.size}** principal(s): \`${Array.from(uniquePrincipals).join("`, `")}\`\n\n`; result += `## 📋 Missing Permissions\n\n`; for (const permission of permissions.values()) { const resourceDisplay = permission.resource === "*" ? "any resource" : `resource \`${permission.resource}\``; result += `- **Principal** \`${permission.principal}\` is missing action \`${permission.action}\` on ${resourceDisplay}\n`; } result += `\n## 📝 Generated IAM Identity Policy\n\n`; result += `This policy should be attached to the IAM user, role, or group that is making the calls.\n\n`; result += `\`\`\`json\n${JSON.stringify(policy, null, 2)}\n\`\`\`\n\n`; result += `## 🚀 How to Apply This Policy\n\n`; result += `**For CDK/CloudFormation:**\n`; result += `1. Add these statements to the IAM Role or User resource's inline policies or a managed policy.\n`; result += `2. **Do not add a 'Principal' block** to this policy statement.\n`; result += `3. Deploy your stack with the updated permissions.\n\n`; result += `**For Terraform:**\n`; result += `1. Create an \`aws_iam_policy\` resource using this policy document.\n`; result += `2. Attach it to your IAM role, user, or group using an attachment resource (e.g., \`aws_iam_role_policy_attachment\`).\n`; result += `3. Apply your Terraform configuration.\n\n`; result += `## ⚠️ Important Notes\n\n`; result += `- **Review Carefully:** This policy is generated from observed failures. Always follow the principle of least privilege.\n`; result += `- **Refine Resources:** Consider refining resource ARNs to be more specific than wildcards (\`*\`) where possible.\n`; return { content: [{ type: "text", text: result }], }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/localstack/localstack-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server