UNPKG

@cloud-copilot/iam-simulate

Version:
278 lines 10.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.runSimulation = runSimulation; exports.normalizeSimulationParameters = normalizeSimulationParameters; const iam_data_1 = require("@cloud-copilot/iam-data"); const iam_policy_1 = require("@cloud-copilot/iam-policy"); const contextKeyTypes_js_1 = require("../context_keys/contextKeyTypes.js"); const contextKeys_js_1 = require("../context_keys/contextKeys.js"); const CoreSimulatorEngine_js_1 = require("../core_engine/CoreSimulatorEngine.js"); const request_js_1 = require("../request/request.js"); const requestContext_js_1 = require("../requestContext.js"); const util_js_1 = require("../util.js"); const contextKeys_js_2 = require("./contextKeys.js"); const DEFAULT_RCP = { name: 'RCPFullAWSAccess', policy: { Version: '2012-10-17', Statement: [ { Effect: 'Allow', Principal: '*', Action: '*', Resource: '*' } ] } }; /** * Run a simulation with validation * * @param simulation The simulation to run * @param simulationOptions Options for the simulation * @returns */ async function runSimulation(simulation, simulationOptions) { const identityPolicyErrors = {}; const identityPolicies = []; simulation.identityPolicies.forEach((value) => { const { name, policy } = value; const validationErrors = (0, iam_policy_1.validateIdentityPolicy)(policy); if (validationErrors.length == 0) { identityPolicies.push((0, iam_policy_1.loadPolicy)(policy, { name })); } else { identityPolicyErrors[name] = validationErrors; } }); const serviceControlPolicyErrors = {}; const serviceControlPolicies = simulation.serviceControlPolicies.map((scp) => { const ouId = scp.orgIdentifier; const validPolicies = []; scp.policies.forEach((value) => { const { name, policy } = value; const validationErrors = (0, iam_policy_1.validateServiceControlPolicy)(policy); if (validationErrors.length > 0) { serviceControlPolicyErrors[name] = validationErrors; } else { validPolicies.push((0, iam_policy_1.loadPolicy)(policy, { name })); } }); return { orgIdentifier: ouId, policies: validPolicies }; }); const resourceControlPolicyErrors = {}; const resourceControlPolicies = simulation.resourceControlPolicies.map((rcp) => { const ouId = rcp.orgIdentifier; const validPolicies = []; validPolicies.push((0, iam_policy_1.loadPolicy)(DEFAULT_RCP.policy, { name: DEFAULT_RCP.name })); rcp.policies.forEach((value) => { const { name, policy } = value; const validationErrors = (0, iam_policy_1.validateResourceControlPolicy)(policy); if (validationErrors.length > 0) { resourceControlPolicyErrors[name] = validationErrors; } else { validPolicies.push((0, iam_policy_1.loadPolicy)(policy, { name })); } }); return { orgIdentifier: ouId, policies: validPolicies }; }); const resourcePolicyErrors = simulation.resourcePolicy ? (0, iam_policy_1.validateResourcePolicy)(simulation.resourcePolicy) : []; const permissionBoundaries = simulation.permissionBoundaryPolicies ? [] : undefined; const permissionBoundaryErrors = {}; simulation.permissionBoundaryPolicies?.map((pb) => { const { name, policy } = pb; const validationErrors = (0, iam_policy_1.validateIdentityPolicy)(policy); if (validationErrors.length == 0) { permissionBoundaries.push((0, iam_policy_1.loadPolicy)(policy, { name })); } else { permissionBoundaryErrors[name] = validationErrors; } }); const vpcEndpointPolicies = simulation.vpcEndpointPolicies ? [] : undefined; const vpcEndpointErrors = {}; simulation.vpcEndpointPolicies?.map((endpointPolicy) => { const { name, policy } = endpointPolicy; const validationErrors = (0, iam_policy_1.validateEndpointPolicy)(policy); if (validationErrors.length == 0) { vpcEndpointPolicies.push((0, iam_policy_1.loadPolicy)(policy, { name })); } else { vpcEndpointErrors[name] = validationErrors; } }); if (Object.keys(identityPolicyErrors).length > 0 || Object.keys(serviceControlPolicyErrors).length > 0 || Object.keys(resourceControlPolicyErrors).length > 0 || Object.keys(permissionBoundaryErrors).length > 0 || Object.keys(vpcEndpointErrors).length > 0 || resourcePolicyErrors.length > 0) { return { errors: { identityPolicyErrors, serviceControlPolicyErrors: serviceControlPolicyErrors, resourceControlPolicyErrors, resourcePolicyErrors, permissionBoundaryErrors, vpcEndpointErrors, message: 'policy.errors' } }; } const resourcePolicy = simulation.resourcePolicy ? (0, iam_policy_1.loadPolicy)(simulation.resourcePolicy, { name: simulation.resourcePolicy.name }) : undefined; if (simulation.request.action.split(':').length != 2) { return { errors: { message: 'invalid.action' } }; } const [service, action] = simulation.request.action.split(':'); const validService = await (0, iam_data_1.iamServiceExists)(service); if (!validService) { return { errors: { message: 'invalid.service' } }; } const validAction = await (0, iam_data_1.iamActionExists)(service, action); if (!validAction) { return { errors: { message: 'invalid.action' } }; } const resourceArn = simulation.request.resource.resource; const isWildCardOnlyAction = await (0, util_js_1.isWildcardOnlyAction)(service, action); let resourceType = undefined; if (isWildCardOnlyAction) { if (resourceArn !== '*') { return { errors: { message: 'must.use.wildcard' } }; } } else { const resourceTypes = await (0, util_js_1.getResourceTypesForAction)(service, action, resourceArn); if (resourceTypes.length === 0) { return { errors: { message: 'no.resource.types' } }; } else if (resourceTypes.length > 1) { return { errors: { message: 'multiple.resource.types' } }; } else { resourceType = resourceTypes[0].key; } } const { validContextValues, ignoredContextKeys } = await normalizeSimulationParameters(simulation); const simulationMode = CoreSimulatorEngine_js_1.validSimulationModes.includes(simulationOptions.simulationMode) ? simulationOptions.simulationMode : 'Strict'; const strictConditionKeys = simulationMode === 'Discovery' ? new Set(simulationOptions.strictConditionKeys?.map((k) => k.toLowerCase()) || []) : new Set(); const simulationResult = (0, CoreSimulatorEngine_js_1.authorize)({ request: new request_js_1.AwsRequestImpl(simulation.request.principal, { resource: simulation.request.resource.resource, accountId: simulation.request.resource.accountId }, simulation.request.action, new requestContext_js_1.RequestContextImpl(validContextValues)), identityPolicies, serviceControlPolicies, resourceControlPolicies, resourcePolicy, permissionBoundaries, vpcEndpointPolicies, simulationParameters: { simulationMode: simulationMode, strictConditionKeys: strictConditionKeys } }); return { analysis: simulationResult, ignoredContextKeys, resourceType }; } async function normalizeSimulationParameters(simulation) { const [service, action] = simulation.request.action.split(':'); const resourceArn = simulation.request.resource.resource; const contextVariablesForAction = new Set(await (0, contextKeys_js_2.allowedContextKeysForRequest)(service, action, resourceArn)); //Get the types of the context variables and set a string or array of strings based on that. const validContextValues = {}; const ignoredContextKeys = []; for (const key of Object.keys(simulation.request.contextVariables)) { const value = simulation.request.contextVariables[key]; const lowerCaseKey = key.toLowerCase(); if (contextVariablesForAction.has(lowerCaseKey) || listHasVariableKeyMatch(lowerCaseKey, contextVariablesForAction)) { const conditionType = await (0, contextKeys_js_1.typeForContextKey)(lowerCaseKey); const normalizedKey = await (0, contextKeys_js_1.normalizeContextKeyCase)(key); if ((0, contextKeyTypes_js_1.isConditionKeyArray)(conditionType)) { validContextValues[normalizedKey] = [value].flat(); } else if (Array.isArray(value)) { validContextValues[normalizedKey] = value[0]; } else { validContextValues[normalizedKey] = value; } } else { ignoredContextKeys.push(key); } } return { validContextValues, ignoredContextKeys }; } /** * Evaluates a context key with a variable such as `aws:PrincipalTag/Foo` to see * if it matches any of the context variables in the allowed variables set. * * @param lowerCaseKey The lower case key to check for a match. * @param contextVariables The set of context variables to check against. * @returns True if the key has a variable match, false otherwise. */ function listHasVariableKeyMatch(lowerCaseKey, contextVariables) { const firstSlashIndex = lowerCaseKey.indexOf('/'); if (firstSlashIndex === -1) { return false; } const prefix = lowerCaseKey.slice(0, firstSlashIndex + 1); for (const variable of contextVariables) { if (variable.startsWith(prefix)) { return true; } } return false; } //# sourceMappingURL=simulationEngine.js.map