UNPKG

@cloud-copilot/iam-lens

Version:

Visibility in IAM in and across AWS accounts

193 lines 8.51 kB
// Worker thread entry point for whoCan simulations. // Lifetime-scoped: a single PullBasedJobRunner is created at init and // processes work items until 'finishWork' is received from the main thread. import {} from '@cloud-copilot/iam-collect'; import { parentPort, threadId, workerData } from 'worker_threads'; import { getCollectClient } from '../collect/collect.js'; import { dynamicImport } from '../utils/dynamicImport.js'; import {} from '../utils/s3Abac.js'; import { PullBasedJobRunner } from '../workers/JobRunner.js'; import { SharedArrayBufferWorkerCache } from '../workers/SharedArrayBufferWorkerCache.js'; import { convertToDenialDetails, toLightRequestAnalysis } from './requestAnalysis.js'; import { executeWhoCan } from './WhoCanWorker.js'; import {} from './workerBootstrapPlugin.js'; if (!parentPort) { throw new Error('Must be run as a worker thread'); } // Get config from the main thread const { concurrency, collectConfigs, partition, s3AbacOverride, collectGrantDetails, clientFactoryPlugin, workerBootstrapPlugin } = workerData; // Pending task requests from workers, keyed by workerId const taskPromises = {}; // Pending deny details checks, keyed by a unique id for each check let denyDetailsCheckId = 0; const pendingDenyDetailsChecks = {}; /** * Runs the consumer-provided bootstrap plugin if one was specified. * Called before any other worker initialization. */ async function runBootstrapPlugin() { if (!workerBootstrapPlugin) return; const mod = await dynamicImport(workerBootstrapPlugin.module); const factory = mod[workerBootstrapPlugin.factoryExport]; if (!factory) { throw new Error(`Bootstrap export '${workerBootstrapPlugin.factoryExport}' not found in module '${workerBootstrapPlugin.module}'`); } else if (typeof factory !== 'function') { throw new Error(`Bootstrap export '${workerBootstrapPlugin.factoryExport}' in module '${workerBootstrapPlugin.module}' is not a function`); } await factory({ data: workerBootstrapPlugin.data, threadId, isMainThread: false }); } /** * Creates the lifetime-scoped PullBasedJobRunner. The runner starts * immediately and processes work items until finishAllWork() is called. * * @param collectClient - Promise for the collect client, resolved during bootstrap. * @returns the PullBasedJobRunner instance. */ function createJobRunner(collectClient) { return new PullBasedJobRunner(concurrency, async (workerId) => { return new Promise((resolve) => { parentPort.postMessage({ type: 'requestTask', workerId }); taskPromises[workerId] = resolve; }); }, (taggedTask) => { const { requestId, ...workItem } = taggedTask; return { properties: { requestId, collectDenyDetails: workItem.collectDenyDetails }, execute: async () => { return executeWhoCan(workItem, collectClient, { s3AbacOverride, collectDenyDetails: workItem.collectDenyDetails, collectGrantDetails, strictContextKeys: workItem.strictContextKeys }); } }; }, async (result) => { const { requestId, collectDenyDetails } = result.properties; if (result.status === 'fulfilled') { const executionResult = result.value; if (executionResult.type === 'allowed') { parentPort.postMessage({ type: 'result', requestId, result: { status: 'fulfilled', value: executionResult.allowed, properties: {} } }); } else { // Check if deny details check will follow const hasDetails = executionResult.type === 'denied_single' || executionResult.type === 'denied_wildcard'; const denyDetailsCheckWillFollow = collectDenyDetails && hasDetails; // Post count-only result for denied simulations parentPort.postMessage({ type: 'result', requestId, denyDetailsCheckWillFollow, result: { status: 'fulfilled', value: undefined, properties: {} } }); if (denyDetailsCheckWillFollow) { const lightAnalysis = toLightRequestAnalysis(executionResult); const checkId = denyDetailsCheckId++; parentPort.postMessage({ type: 'checkDenyDetails', requestId, checkId, workItem: executionResult.workItem, lightAnalysis }); const shouldInclude = await new Promise((resolve) => { pendingDenyDetailsChecks[checkId] = resolve; }); if (shouldInclude) { parentPort.postMessage({ type: 'denyDetailsResult', requestId, denyDetail: convertToDenialDetails(executionResult) }); } } } } else { // Error case — pass through with requestId parentPort.postMessage({ type: 'result', requestId, result: result }); } }); } /** * Async entry point for the worker. Initializes bootstrap, collect client, * message handler, and job runner in the correct order. */ async function main() { // 1. Run bootstrap plugin if present await runBootstrapPlugin(); // 2. Start creating the collect client (resolves asynchronously — the // shared-cache bridge is already installed on the main thread so cache // messages will be served during startup) const collectClientPromise = await getCollectClient(collectConfigs, partition, { cacheProvider: new SharedArrayBufferWorkerCache(parentPort), clientFactoryPlugin }); // 3. Signal ready (tells main thread bootstrap + client init succeeded) parentPort.postMessage({ type: 'ready' }); // 4. Wait for 'start' from the main thread. The main thread sends this // after installing its lifetime message listeners, so that requestTask // messages emitted by the job runner are not dropped. await new Promise((resolve) => { const onStart = (msg) => { if (msg.type === 'start') { parentPort.off('message', onStart); resolve(); } }; parentPort.on('message', onStart); }); // 5. Install message handler (must be ready before runner pulls tasks). // jobRunner is assigned after the handler is installed but before the // runner constructor fires its first requestTask, so the closure is safe. let jobRunner; parentPort.on('message', (msg) => { if (msg.type === 'task' && msg.workerId in taskPromises) { // Task delivered from main thread (may be undefined if nothing is ready) taskPromises[msg.workerId](msg.task); delete taskPromises[msg.workerId]; } else if (msg.type === 'workAvailable') { jobRunner.notifyWorkAvailable(); } else if (msg.type === 'finishWork') { jobRunner.finishAllWork().then(() => { parentPort.postMessage({ type: 'finished' }); }); } else if (msg.type === 'denyDetailsCheckResult') { const checkId = msg.checkId; const resolveFn = pendingDenyDetailsChecks[checkId]; if (resolveFn) { resolveFn(msg.shouldInclude); delete pendingDenyDetailsChecks[checkId]; } } }); // 6. Create job runner (constructor eagerly starts pulling tasks) jobRunner = createJobRunner(collectClientPromise); } void main().catch((err) => { // Post explicit error so main thread doesn't depend on unhandled-rejection behavior. parentPort.postMessage({ type: 'startupError', error: String(err) }); process.exit(1); }); //# sourceMappingURL=WhoCanWorkerThreadWorker.js.map