UNPKG

@cloud-copilot/iam-lens

Version:

Visibility in IAM in and across AWS accounts

189 lines 7.72 kB
#!/usr/bin/env node import { parseCliArguments } from '@cloud-copilot/cli'; import { canWhat } from './canWhat/canWhat.js'; import { getCollectClient, loadCollectConfigs } from './collect/collect.js'; import { resultMatchesExpectation, simulateRequest } from './simulate/simulate.js'; import { iamLensVersion } from './utils/packageVersion.js'; import { whoCan } from './whoCan/whoCan.js'; const main = async () => { const version = await iamLensVersion(); const cli = parseCliArguments('iam-lens', { simulate: { description: 'Simulate an IAM request', options: { principal: { type: 'string', values: 'single', description: 'The principal to simulate. Can be a user, role, session, or AWS service' }, resource: { type: 'string', values: 'single', description: 'The ARN of the resource to simulate access to. Ignore for wildcard actions' }, resourceAccount: { type: 'string', values: 'single', description: 'The account ID of the resource, only required if it cannot be determined from the resource ARN.' }, action: { type: 'string', values: 'single', description: 'The action to simulate; must be a valid IAM service and action such as `s3:ListBucket`' }, context: { type: 'string', values: 'multiple', description: 'The context keys to use for the simulation. Keys are formatted as key=value. Multiple values can be separated by commas (key=value1,value2,value3)' }, verbose: { type: 'boolean', description: 'Enable verbose output for the simulation', character: 'v' }, expect: { type: 'enum', values: 'single', validValues: ['Allowed', 'ImplicitlyDenied', 'ExplicitlyDenied', 'AnyDeny'], description: 'The expected result of the simulation, if the result does not match the expected response a non-zero exit code will be returned' } } }, 'who-can': { description: 'Find who can perform an action on a resource', options: { resource: { type: 'string', values: 'single', description: 'The ARN of the resource to check permissions for. Ignore for wildcard actions' }, resourceAccount: { type: 'string', values: 'single', description: 'The account ID of the resource, only required if it cannot be determined from the resource ARN. Required for wildcard actions' }, actions: { type: 'string', values: 'multiple', description: 'The action to check permissions for; must be a valid IAM service and action such as `s3:GetObject`' } } }, 'principal-can': { description: 'ALPHA: Create a consolidated view of all permissions for a principal', options: { principal: { type: 'string', values: 'single', description: 'The principal to check permissions for. Can be a user or role' }, shrinkActionLists: { type: 'boolean', character: 's', description: 'Shrink action lists to reduce policy size' } } } }, { collectConfigs: { type: 'string', description: 'The iam-collect configuration files to use', values: 'multiple' }, partition: { type: 'string', description: 'The AWS partition to use (aws, aws-cn, aws-us-gov). Defaults to aws.', values: 'single' } }, { envPrefix: 'IAM_LENS', showHelpIfNoArgs: true, requireSubcommand: true, version }); if (cli.args.collectConfigs.length === 0) { cli.args.collectConfigs.push('./iam-collect.jsonc'); } const thePartition = cli.args.partition || 'aws'; const collectConfigs = await loadCollectConfigs(cli.args.collectConfigs); const collectClient = getCollectClient(collectConfigs, thePartition); if (cli.subcommand === 'simulate') { const { principal, resource, resourceAccount, action, context } = cli.args; const contextKeys = convertContextKeysToMap(context); const { request, result } = await simulateRequest({ principal: principal, resourceArn: resource, resourceAccount: resourceAccount, action: action, customContextKeys: contextKeys, simulationMode: 'Strict' }, collectClient); if (result.errors) { console.error('Simulation Errors:'); console.log(JSON.stringify(result.errors, null, 2)); process.exit(1); } console.log(`Simulation Result: ${result.analysis?.result}`); if (cli.args.verbose) { console.log(JSON.stringify({ request, result }, null, 2)); } if (!resultMatchesExpectation(cli.args.expect, result.analysis?.result)) { process.exit(1); } } else if (cli.subcommand === 'who-can') { const { resource, resourceAccount, actions } = cli.args; if (!resourceAccount && !resource && actions.length === 0) { console.error('Error: At least 1) resource or 2) resource-account and actions must be provided for who-can command'); process.exit(1); } const results = await whoCan(collectClient, { resource: cli.args.resource, actions: cli.args.actions, resourceAccount: cli.args.resourceAccount }); console.log(JSON.stringify(results, null, 2)); } else if (cli.subcommand === 'principal-can') { const { principal, shrinkActionLists } = cli.args; if (!principal) { console.error('Error: Principal must be provided for principal-can command'); process.exit(1); } const results = await canWhat(collectClient, { principal: principal, shrinkActionLists }); console.log(JSON.stringify(results, null, 2)); } }; main() .catch((e) => { console.error(e); process.exit(1); }) .then(() => { }) .finally(() => { }); /** * Convert the context keys from the CLI arguments into a map. * * @param contextKeys the context keys from the CLI arguments, formatted as key=value1,value2,... * @returns a map of context keys where each key is associated with a single value or an array of values */ function convertContextKeysToMap(contextKeys) { const contextMap = {}; for (const key of contextKeys) { const [keyName, value] = key.split('='); if (value) { const values = value.split(','); if (values.length > 1) { contextMap[keyName] = values; } else { contextMap[keyName] = values[0]; } } } return contextMap; } //# sourceMappingURL=cli.js.map