@cloud-copilot/iam-lens
Version:
Visibility in IAM in and across AWS accounts
400 lines • 16.9 kB
TypeScript
import { type TopLevelConfig } from '@cloud-copilot/iam-collect';
import { type ClientFactoryPlugin } from '../collect/collect.js';
import { type S3AbacOverride } from '../utils/s3Abac.js';
import { type LightRequestAnalysis } from './requestAnalysis.js';
import { type WhoCanPrincipalScope, type WhoCanResponse } from './whoCan.js';
import { type WorkerBootstrapPlugin } from './workerBootstrapPlugin.js';
export type { WorkerBootstrapPlugin } from './workerBootstrapPlugin.js';
/**
* Configuration for creating a WhoCanProcessor. These settings are fixed
* for the lifetime of the processor and baked into worker threads at creation time.
*/
export interface WhoCanProcessorConfig {
/** The collect configurations for loading IAM data. */
collectConfigs: TopLevelConfig[];
/** The AWS partition to use (e.g. 'aws', 'aws-cn'). */
partition: string;
tuning?: {
/**
* The number of worker threads to use beyond the main thread. Defaults to number of CPUs - 1.
*/
workerThreads?: number;
/**
* The concurrency level for processing simulations on the main thread. Defaults to 50.
*/
mainThreadConcurrency?: number;
/**
* The concurrency level for processing simulations on worker threads.
* This is the value for EACH worker.
* Defaults to 50.
*/
perWorkerConcurrency?: number;
/**
* The concurrency level for the shared preparation queue (account/principal fetches
* across all active requests). Defaults to min(50, max(1, number of CPUs * 2)).
*/
preparationConcurrency?: number;
/**
* The maximum number of requests that may be actively expanded into scenarios
* at once. Later requests remain as lightweight entries in pendingRequests.
* Defaults to 30.
*/
maxRequestsInProgress?: number;
};
/** Optional plugin to wrap the collect client with a custom implementation. */
clientFactoryPlugin?: ClientFactoryPlugin;
/**
* Optional plugin that runs once per worker thread at startup before any work
* is processed. Use this for loading instrumentation, initializing logging
* context, or other worker-lifetime setup. If the bootstrap function throws,
* the worker fails and the processor surfaces the error.
*/
workerBootstrapPlugin?: WorkerBootstrapPlugin;
/** An override for S3 ABAC being enabled when checking access to S3 Bucket resources. */
s3AbacOverride?: S3AbacOverride;
/** Whether workers should collect grant details for allowed simulations. */
collectGrantDetails?: boolean;
/**
* Async callback invoked when a request settles (succeeds or fails). The processor
* awaits this callback before removing the request from active state and admitting
* the next pending request. This allows consumers to perform async work with backpressure.
*
* @param event - The settlement event containing the request ID, original request,
* status, and either the result or the error.
*/
onRequestSettled: (event: WhoCanSettledEvent) => Promise<void>;
/**
* Optional async callback invoked when an `onRequestSettled` callback throws or rejects.
* If this callback itself throws, the error is silently ignored. If not provided, a
* warning is logged indicating that a settlement callback failed with no handler defined.
*
* @param event - The settlement event that was being delivered when the error occurred.
* @param error - The error thrown by the `onRequestSettled` callback.
*/
onSettlementFailure?: (event: WhoCanSettledEvent, error: Error) => Promise<void>;
/**
* Whether the processor should ignore an existing principal index. Use this with testing.
*/
ignorePrincipalIndex?: boolean;
}
/**
* Request parameters that vary per whoCan call on a processor.
*/
export interface WhoCanProcessorRequest {
/** The ARN of the resource to check access for. */
resource?: string;
/** The account ID the resource belongs to. */
resourceAccount?: string;
/** The actions to check access for. */
actions: string[];
/** Whether to sort the results for consistent output. */
sort?: boolean;
/**
* Optional callback to filter which denied simulations should include detailed
* denial analysis. If provided, deny details are collected for this request.
* If the callback returns true for a given denial, the full deny details are
* included in the response. If omitted, no deny details are collected for this request.
*
* @param details - A lightweight summary of the denied simulation.
* @returns true to include full deny details for this denial.
*/
denyDetailsCallback?: (details: LightRequestAnalysis) => boolean;
/** Optional scope to limit the set of principals tested. */
principalScope?: WhoCanPrincipalScope;
/** Optional context keys to consider strict when running simulations. */
strictContextKeys?: string[];
}
/**
* Event delivered to the onRequestSettled callback when a request completes
* (either successfully or with an error).
*/
export type WhoCanSettledEvent = WhoCanSettledSuccess | WhoCanSettledError;
/**
* Settlement event for a successfully completed request.
*/
export interface WhoCanSettledSuccess {
/** Discriminator for the settlement outcome. */
status: 'fulfilled';
/** The unique ID assigned when the request was enqueued. */
requestId: string;
/** The original request that was enqueued. */
request: WhoCanProcessorRequest;
/** The whoCan result for this request. */
result: WhoCanResponse;
}
/**
* Settlement event for a request that failed during preparation or simulation.
*/
export interface WhoCanSettledError {
/** Discriminator for the settlement outcome. */
status: 'rejected';
/** The unique ID assigned when the request was enqueued. */
requestId: string;
/** The original request that was enqueued. */
request: WhoCanProcessorRequest;
/** The error that caused the request to fail. */
error: Error;
}
/**
* A queue-first bulk processor that accepts many whoCan requests, expands
* scenarios on the main thread, and feeds a shared simulation scheduler used
* by worker threads and an optional main-thread runner.
*
* Results are delivered through the {@link WhoCanProcessorConfig.onRequestSettled}
* callback as each request completes — they are not stored inside the processor.
*
* Use {@link enqueueWhoCan} to submit requests, then {@link waitForIdle} to
* wait for all work to complete. Call {@link shutdown} when done to terminate
* worker threads.
*/
export declare class WhoCanProcessor {
private workers;
private collectClient;
private config;
private isShutdown;
private workersDead;
private pendingRequests;
private activeRequestOrder;
private requestStates;
private admissionPumpRunning;
private draining;
private preparationQueue;
private idleWaiters;
private mainThreadWorker;
private fatalError?;
private shutdownPromise?;
private constructor();
/**
* Waits for every worker to post a `{ type: 'ready' }` message. If any
* worker fails (error, non-zero exit, or explicit `startupError` message)
* the remaining workers are terminated and the returned promise rejects.
*
* @param workers - The worker instances to wait on.
*/
private static awaitWorkersReady;
/**
* Creates a new WhoCanProcessor with worker threads, a shared cache, and
* lifetime-scoped message routing. The processor is ready to accept requests
* immediately after creation.
*
* @param config - The configuration for the processor, including collect configs,
* partition, simulation options, tuning, and the onRequestSettled callback.
* @returns a new WhoCanProcessor instance
*/
static create(config: WhoCanProcessorConfig): Promise<WhoCanProcessor>;
/**
* Enqueues a whoCan request for processing. Returns a unique request ID
* that will appear in the corresponding {@link WhoCanSettledEvent}.
*
* This method never activates a request directly — it appends to
* pendingRequests and signals the admission pump.
*
* @param request - The whoCan request parameters.
* @returns the unique request ID assigned to this request.
* @throws if the processor is shut down or draining via waitForIdle.
*/
enqueueWhoCan(request: WhoCanProcessorRequest): string;
/**
* Returns a promise that resolves when all pending and active work has
* completed and all onRequestSettled callbacks have finished.
*
* While draining, new calls to {@link enqueueWhoCan} will throw. Once
* the drain completes, the processor re-opens for new enqueues.
*
* @returns a promise that resolves when idle, or rejects if a worker crashes.
*/
waitForIdle(): Promise<void>;
/**
* Shuts down the processor by rejecting all pending requests, waiting for
* active requests to settle, and terminating all worker threads.
*
* This method is idempotent — calling it multiple times is safe.
*/
shutdown(): Promise<void>;
/**
* Internal shutdown implementation. Rejects pending requests, waits for
* active requests to drain, then terminates workers.
*/
private executeShutdown;
/**
* Installs lifetime-scoped message, error, and exit listeners on all workers.
* Message listeners route simulation results and deny-detail checks to the
* correct request state using requestId. Error/exit listeners detect crashes
* and mark the processor as fatally failed.
*/
private installLifetimeWorkerListeners;
/**
* Routes a message from a worker thread to the appropriate handler based
* on message type and requestId.
*
* @param msg - The message received from the worker.
* @param worker - The worker that sent the message.
*/
private handleWorkerMessage;
/**
* Creates the main-thread simulation runner if mainThreadConcurrency > 0.
* The runner pulls from the FIFO scheduler and routes results by requestId.
*/
private createMainThreadRunner;
/**
* Dequeues the next simulation scenario using FIFO request priority.
* Prefers the oldest active request that has ready scenarios. If the oldest
* is temporarily empty (still preparing), falls back to the next request
* with ready scenarios so cores do not idle.
*
* @returns the next work item, or undefined if no scenarios are ready.
*/
private dequeueNextScenario;
/**
* Notifies all simulation consumers (workers and main thread) that new
* work may be available in the scheduler.
*/
private notifySimulationConsumers;
/**
* Wakes the admission pump to process pending requests. If the pump is
* already running, this is a no-op — the running pump will pick up new
* pending requests on its next iteration.
*/
private wakeAdmissionPump;
/**
* The admission pump loop. Drains pendingRequests into active processing
* up to maxRequestsInProgress. Only one instance of this loop runs at a time,
* guarded by admissionPumpRunning.
*/
private runAdmissionPump;
/**
* Creates a fresh RequestState for an admitted request.
*
* @param submitted - The submitted request to create state for.
* @returns the new RequestState.
*/
private createRequestState;
/**
* Enqueues the root preparation job for a request. This job performs resource
* account resolution, resource policy lookup, action expansion, principal scope
* handling, and then enqueues follow-up preparation jobs to enumerate principals.
*
* @param state - The request state to prepare.
*/
private enqueueRootPreparation;
/**
* Executes the root preparation for a request: resolves the resource account,
* fetches the resource policy, expands actions, determines which accounts and
* principals to check, and enqueues follow-up preparation jobs.
*
* @param state - The request state to prepare.
*/
private executeRootPreparation;
/**
* Handles a simulation result from a worker or the main thread runner.
* Routes the result to the correct request state and checks for completion.
*
* @param requestId - The ID of the request this result belongs to.
* @param result - The simulation job result.
*/
private handleSimulationResult;
/**
* Handles a checkDenyDetails request from a worker thread. Looks up the
* request's denyDetailsCallback and responds.
*
* @param requestId - The ID of the request.
* @param checkId - The unique check ID for this deny-details round trip.
* @param lightAnalysis - The light analysis to pass to the callback.
* @param worker - The worker to respond to.
*/
private handleCheckDenyDetails;
/**
* Handles a deny details result from a worker thread. Decrements the
* pending deny-details counter and checks for request completion.
*
* @param requestId - The ID of the request.
* @param denyDetail - The deny detail to store.
*/
private handleDenyDetailsResult;
/**
* Checks whether a request has completed all preparation and simulation work.
* If so, settles the request as successful.
*
* @param state - The request state to check.
*/
private checkRequestCompletion;
/**
* Settles a request as successful: builds the WhoCanResponse, awaits
* onRequestSettled, removes the request from active state, and wakes
* the admission pump.
*
* @param state - The request state to settle.
*/
private settleRequestAsSuccess;
/**
* Settles a request as failed: invokes onRequestSettled with the error
* immediately, but keeps the request in active state until all in-flight
* work drains (created === completed). Late results for settled requests
* are discarded but still counted so the drain completes.
*
* @param state - The request state to settle.
* @param error - The error that caused the failure.
*/
private settleRequestAsError;
/**
* Invokes the onRequestSettled callback and routes any errors through
* {@link handleSettlementFailure}.
*
* @param event - The settlement event to deliver.
*/
private invokeSettledCallback;
/**
* Awaits the onRequestSettled callback, then removes the request from
* active state and wakes the admission pump. Used for successful settlements
* where all work is already complete.
*
* @param state - The request state being settled.
* @param event - The settlement event to deliver.
*/
private invokeSettledCallbackAndCleanup;
/**
* Checks whether a settled request has fully drained: the onRequestSettled
* callback has been awaited, all preparation jobs have finished, all
* simulation results have been received, and all deny-detail round trips
* have completed. Only then is the request removed from active state.
*
* @param state - The request state to check.
*/
private checkRequestDrain;
/**
* Removes a request from active state, wakes the admission pump to fill
* the freed slot, and checks if the processor is now idle.
*
* @param state - The request state to remove.
*/
private removeFromActiveState;
/**
* Returns true if the processor has no pending, active, or in-flight work.
*
* @returns true if fully idle.
*/
private isIdle;
/**
* Checks whether the processor has become idle and resolves or rejects the
* waitForIdle promise if so.
*/
private checkIdle;
/**
* Handles an error thrown by the `onRequestSettled` callback. If the
* `onSettlementFailure` callback is defined, it is invoked with the event
* and error; any error it throws is silently ignored. If not defined, a
* warning is logged.
*
* @param event - The settlement event that was being delivered.
* @param err - The raw error thrown by the `onRequestSettled` callback.
*/
private handleSettlementFailure;
/**
* Handles an unexpected worker failure by marking the processor as dead,
* terminating remaining workers, and rejecting all active and pending requests.
*
* @param error - The error that caused the worker failure.
*/
private handleWorkerFailure;
}
//# sourceMappingURL=WhoCanProcessor.d.ts.map