@cloud-copilot/iam-lens
Version:
Visibility in IAM in and across AWS accounts
158 lines • 6.6 kB
JavaScript
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