@cloud-copilot/iam-simulate
Version:
Simulate evaluation of AWS IAM policies
278 lines • 10.7 kB
JavaScript
"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