@cloud-copilot/iam-lens
Version:
Visibility in IAM in and across AWS accounts
206 lines • 9.66 kB
JavaScript
#!/usr/bin/env node
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const cli_1 = require("@cloud-copilot/cli");
const client_js_1 = require("./collect/client.js");
const collect_js_1 = require("./collect/collect.js");
const principalCan_js_1 = require("./principalCan/principalCan.js");
const makePrincipalIndex_js_1 = require("./principalIndex/makePrincipalIndex.js");
const simulate_js_1 = require("./simulate/simulate.js");
const packageVersion_js_1 = require("./utils/packageVersion.js");
const stringOrFileArgument_js_1 = require("./utils/stringOrFileArgument.js");
const whoCan_js_1 = require("./whoCan/whoCan.js");
const main = async () => {
const cli = await (0, cli_1.parseCliArguments)('iam-lens', {
simulate: {
description: 'Simulate an IAM request',
arguments: {
principal: (0, cli_1.stringArgument)({
description: 'The principal to simulate. Can be a user, role, session, or AWS service'
}),
resource: (0, cli_1.stringArgument)({
description: 'The ARN of the resource to simulate access to. Ignore for wildcard actions'
}),
resourceAccount: (0, cli_1.stringArgument)({
description: 'The account ID of the resource, only required if it cannot be determined from the resource ARN.'
}),
action: (0, cli_1.stringArgument)({
description: 'The action to simulate; must be a valid IAM service and action such as `s3:ListBucket`'
}),
context: (0, cli_1.mapArgument)({
description: 'The context keys to use for the simulation. The first value is the key and the rest are the values. Specify multiple keys by using --context multiple times',
defaultValue: {}
}),
verbose: (0, cli_1.booleanArgument)({
description: 'Enable verbose output for the simulation',
character: 'v'
}),
expect: (0, cli_1.enumArgument)({
description: 'The expected result of the simulation, if the result does not match the expected response a non-zero exit code will be returned',
validValues: ['Allowed', 'ImplicitlyDenied', 'ExplicitlyDenied', 'AnyDeny']
}),
ignoreMissingPrincipal: (0, cli_1.booleanArgument)({
description: 'Ignore if the principal does not exist. Useful for simulating actions from principals that may not exist or are outside your data set',
character: 'i'
}),
s3AbacOverride: (0, cli_1.enumArgument)({
description: 'Override the S3 ABAC setting for S3 buckets. Defaults to the bucket setting stored in your iam-collect data',
validValues: ['enabled', 'disabled'],
defaultValue: undefined
}),
sessionPolicy: (0, stringOrFileArgument_js_1.stringOrFileArgument)({
description: 'The session policy to use for the simulation, if the principal type supports it'
})
}
},
'who-can': {
description: 'Find who can perform an action on a resource',
arguments: {
resource: (0, cli_1.stringArgument)({
description: 'The ARN of the resource to check permissions for. Ignore for wildcard actions'
}),
resourceAccount: (0, cli_1.stringArgument)({
description: 'The account ID of the resource, only required if it cannot be determined from the resource ARN. Required for wildcard actions'
}),
actions: (0, cli_1.stringArrayArgument)({
description: 'The actions to check permissions for; must be a valid IAM service and action such as `s3:GetObject`',
defaultValue: []
}),
s3AbacOverride: (0, cli_1.enumArgument)({
description: 'Override the S3 ABAC setting for S3 buckets. Defaults to the bucket setting stored in your iam-collect data',
validValues: ['enabled', 'disabled'],
defaultValue: undefined
}),
sort: (0, cli_1.booleanArgument)({
description: 'Sort the results before outputting',
character: 's'
})
}
},
'principal-can': {
description: 'Create a consolidated view of all permissions for a principal, see readme for limitations',
arguments: {
principal: (0, cli_1.stringArgument)({
description: 'The principal to check permissions for. Can be a user or role'
}),
shrinkActionLists: (0, cli_1.booleanArgument)({
description: 'Shrink action lists to reduce policy size',
character: 's'
})
}
},
'index-principals': {
description: 'Index all principals',
arguments: {}
}
}, {
collectConfigs: (0, cli_1.stringArrayArgument)({
description: 'The iam-collect configuration files to use',
defaultValue: []
}),
partition: (0, cli_1.stringArgument)({
description: 'The AWS partition to use (aws, aws-cn, aws-us-gov). Defaults to aws',
defaultValue: 'aws'
})
}, {
envPrefix: 'IAM_LENS',
showHelpIfNoArgs: true,
requireSubcommand: true,
expectOperands: false,
version: {
currentVersion: packageVersion_js_1.iamLensVersion,
checkForUpdates: '@cloud-copilot/iam-lens'
}
});
if (cli.args.collectConfigs.length === 0) {
cli.args.collectConfigs.push('./iam-collect.jsonc');
}
const collectConfigs = await (0, collect_js_1.loadCollectConfigs)(cli.args.collectConfigs);
const collectClient = await (0, collect_js_1.getCollectClient)(collectConfigs, cli.args.partition);
if (cli.subcommand === 'simulate') {
const { principal, resource, resourceAccount, action, context, ignoreMissingPrincipal, sessionPolicy } = cli.args;
const { request, result } = await (0, simulate_js_1.simulateRequest)({
sessionPolicy,
principal: principal,
resourceArn: resource,
resourceAccount: resourceAccount,
action: action,
customContextKeys: singularizeOneEntryArrays(context),
simulationMode: 'Strict',
ignoreMissingPrincipal,
s3AbacOverride: cli.args.s3AbacOverride
}, collectClient);
if (result.resultType === 'error') {
console.error('Simulation Errors:');
console.log(JSON.stringify(result.errors, null, 2));
process.exit(1);
}
console.log(`Simulation Result: ${result.overallResult}`);
if (cli.args.verbose) {
console.log(JSON.stringify({ request, result }, null, 2));
}
if (!(0, simulate_js_1.resultMatchesExpectation)(cli.args.expect, result.overallResult)) {
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 (0, whoCan_js_1.whoCan)(collectConfigs, cli.args.partition, {
resource: cli.args.resource,
actions: cli.args.actions,
resourceAccount: cli.args.resourceAccount,
s3AbacOverride: cli.args.s3AbacOverride,
sort: cli.args.sort
});
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 (0, principalCan_js_1.principalCan)(collectClient, {
principal: principal,
shrinkActionLists
});
console.log(JSON.stringify(results, null, 2));
}
else if (cli.subcommand === 'index-principals') {
const indexClient = await (0, collect_js_1.getCollectClient)(collectConfigs, cli.args.partition, {
cacheProvider: new client_js_1.NoCacheProvider()
});
await (0, makePrincipalIndex_js_1.makePrincipalIndex)(indexClient);
}
};
/**
* Take a record of string arrays and convert it to a record of strings or string arrays,
* where any array with a single element is converted to a string.
*
* @param input - The input record of string arrays.
* @returns A new record with singularized values.
*/
function singularizeOneEntryArrays(input) {
const output = {};
for (const [key, value] of Object.entries(input)) {
if (value.length === 1) {
output[key] = value[0];
}
else {
output[key] = value;
}
}
return output;
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.then(() => { })
.finally(() => { });
//# sourceMappingURL=cli.js.map