UNPKG

@cloud-copilot/iam-lens

Version:

Visibility in IAM in and across AWS accounts

158 lines 6.6 kB
import { iamActionDetails } from '@cloud-copilot/iam-data'; import { getGrantReasons } from '@cloud-copilot/iam-simulate'; import { IamCollectClient } from '../collect/client.js'; import { simulateRequest } from '../simulate/simulate.js'; import { log } from '@cloud-copilot/log'; export function createJobForWhoCanWorkItem(workItem, collectClient, whoCanOptions) { return { properties: {}, execute: async (context) => { return executeWhoCan(workItem, collectClient, whoCanOptions); } }; } export async function executeWhoCan(workItem, collectClient, whoCanOptions) { const { principal, resource, resourceAccount, action } = workItem; const [service, serviceAction] = action.split(':'); const discoveryResult = await simulateRequest({ principal, resourceArn: resource, resourceAccount: resourceAccount, action, customContextKeys: {}, simulationMode: 'Discovery', s3AbacOverride: whoCanOptions.s3AbacOverride, additionalStrictContextKeys: whoCanOptions.strictContextKeys }, collectClient); if (discoveryResult.result.resultType === 'error') { log.error({ mode: 'discovery', simulationErrors: true, errors: discoveryResult.result.errors, resource }); throw new Error('Discovery simulation failed: ' + JSON.stringify(discoveryResult.result.errors)); } const actionType = await getActionLevel(service, serviceAction); if (discoveryResult?.result.overallResult === 'Allowed') { const strictResult = await simulateRequest({ principal, resourceArn: resource, resourceAccount, action, customContextKeys: {}, simulationMode: 'Strict', s3AbacOverride: whoCanOptions.s3AbacOverride, additionalStrictContextKeys: whoCanOptions.strictContextKeys }, collectClient); if (strictResult.result.resultType === 'error') { log.error({ mode: 'strict', simulationErrors: true, errors: strictResult.result.errors, resource }); throw new Error('Strict simulation failed: ' + JSON.stringify(strictResult.result.errors)); } if (strictResult?.result.overallResult === 'Allowed') { return mapSimulationResultToWhoCanExecutionResult(workItem, service, serviceAction, actionType, strictResult.result, !!whoCanOptions.collectDenyDetails, !!whoCanOptions.collectGrantDetails); } } else { return mapSimulationResultToWhoCanExecutionResult(workItem, service, serviceAction, actionType, discoveryResult.result, !!whoCanOptions.collectDenyDetails, !!whoCanOptions.collectGrantDetails); } return mapSimulationResultToWhoCanExecutionResult(workItem, service, serviceAction, actionType, discoveryResult.result, !!whoCanOptions.collectDenyDetails, !!whoCanOptions.collectGrantDetails); } /** * Get the action level for a specific service action, will fail if the service or action does not exist. * * @param service the service the action belongs to * @param action the action to get the level for * @returns the access level of the action, e.g. 'Read', 'Write', 'List', 'Tagging', 'Permissions management', 'Other' */ async function getActionLevel(service, action) { const details = await iamActionDetails(service, action); return details.accessLevel; } function mapSimulationResultToWhoCanExecutionResult(workItem, service, action, actionType, simulationResponse, collectDenyDetails, collectGrantDetails) { const { principal } = workItem; if (simulationResponse.overallResult === 'Allowed') { // Build allowed result const allowed = { principal, service, action, level: actionType.toLowerCase() }; if (simulationResponse.resultType === 'single') { const analysis = simulationResponse.result.analysis; if (analysis.ignoredConditions && Object.keys(analysis.ignoredConditions).length > 0) { allowed.conditions = analysis.ignoredConditions; } if (analysis.ignoredRoleSessionName) { allowed.dependsOnSessionName = true; } if (simulationResponse.result.resourceType) { allowed.resourceType = simulationResponse.result.resourceType; } if (collectGrantDetails) { allowed.details = getGrantReasons(analysis); } } else { // Wildcard result - collect allowed patterns with per-pattern grant details const allowedPatterns = []; for (const r of simulationResponse.results) { if (r.analysis.result === 'Allowed') { allowedPatterns.push({ pattern: r.resourcePattern, resourceType: r.resourceType, conditions: r.analysis.ignoredConditions, dependsOnSessionName: r.analysis.ignoredRoleSessionName ? true : undefined, ...(collectGrantDetails && { details: getGrantReasons(r.analysis) }) }); } } if (allowedPatterns.length > 0) { allowed.allowedPatterns = allowedPatterns; } } return { type: 'allowed', workItem, allowed }; } // Denied result if (!collectDenyDetails) { // If we don't need to collect deny details, we can return a simple denied result without analysis return { type: 'denied', workItem }; } if (simulationResponse.resultType === 'single') { return { type: 'denied_single', workItem, analysis: simulationResponse.result.analysis }; } else { // Wildcard denial - collect denied patterns const deniedPatterns = simulationResponse.results .filter((r) => r.analysis.result !== 'Allowed') .map((r) => ({ pattern: r.resourcePattern, resourceType: r.resourceType, analysis: r.analysis })); return { type: 'denied_wildcard', overallResult: simulationResponse.overallResult, workItem, deniedPatterns }; } } //# sourceMappingURL=WhoCanWorker.js.map