UNPKG

@cloud-copilot/iam-lens

Version:

Visibility in IAM in and across AWS accounts

117 lines 4.34 kB
import { loadPolicy } from '@cloud-copilot/iam-policy'; import { runSimulation } from '@cloud-copilot/iam-simulate'; import { splitArnParts } from '@cloud-copilot/iam-utils'; import { IamCollectClient } from '../../collect/client.js'; import { createContextKeys } from '../../simulate/contextKeys.js'; import {} from '../../simulate/simulate.js'; /** * Checks to see if a statement applies to a principal by running a simulation. * * If the principal is a match return 'PrincipalMatch' * If the account is a match return 'AccountMatch' * Otherwise return 'NoMatch' * * @param statement the statement to check * @param principalArn the arn of the principal to check * @param client the IAM collect client to use for retrieving principal information * @returns Whether the statement applies to the principal */ export async function statementAppliesToPrincipal(statement, principalArn, client) { const principalAccount = splitArnParts(principalArn).accountId; const resourcePolicy = makePrincipalOnlyPolicyFromStatement(statement); const simulationRequest = { principal: principalArn, action: 'kms:DescribeKey', resourceAccount: principalAccount, resourceArn: undefined, customContextKeys: {}, simulationMode: 'Strict' }; // We use KMS, so we get kms:CallerAccount context key support const { contextKeys } = await createContextKeys(client, simulationRequest, 'kms', {}); const request = { action: 'kms:DescribeKey', resource: { resource: 'arn:aws:kms:us-east-1:123456789012:key/abcd1234-ab12-cd34-ef23-456789abcdef', accountId: principalAccount }, principal: principalArn, contextVariables: contextKeys }; const simulation = { request, identityPolicies: [], resourcePolicy: resourcePolicy.toJSON(), serviceControlPolicies: [], resourceControlPolicies: [] }; const result = await runSimulation(simulation, { simulationMode: simulationRequest.simulationMode }); if (result.resultType === 'error') { return 'NoMatch'; } const analysis = result.resultType === 'single' ? result.result.analysis : undefined; if (analysis?.result === 'Allowed') { return 'PrincipalMatch'; } if (analysis?.resourceAnalysis?.result === 'AllowedForAccount') { return 'AccountMatch'; } return 'NoMatch'; } const principalKeys = new Set([ 'aws:PrincipalArn', 'aws:PrincipalAccount', 'aws:PrincipalOrgId', 'aws:PrincipalOrgPaths', 'aws:PrincipalType', 'aws:userid', 'aws:username', 'aws:PrincipalIsAWSService', 'kms:CallerAccount' ].map((k) => k.toLowerCase())); /** * Makes a policy that captures the principal and principal conditions from a statement * and allows all actions on all resources. * * The conditions returned are only those that relate to the principal. * * @param statement the statement to extract the principal from * @returns */ export function makePrincipalOnlyPolicyFromStatement(statement) { const rawStatement = structuredClone(statement.toJSON()); const rawStatementValues = {}; if (statement.isPrincipalStatement()) { rawStatementValues.Principal = rawStatement.Principal; } else if (statement.isNotPrincipalStatement()) { rawStatementValues.NotPrincipal = rawStatement.NotPrincipal; } if (rawStatement.Condition) { for (const operator of Object.keys(rawStatement.Condition)) { for (const key of Object.keys(rawStatement.Condition[operator])) { if (!principalKeys.has(key.toLowerCase())) { delete rawStatement.Condition[operator][key]; } } if (Object.keys(rawStatement.Condition[operator]).length === 0) { delete rawStatement.Condition[operator]; } } if (Object.keys(rawStatement.Condition).length > 0) { rawStatementValues.Condition = rawStatement.Condition; } } return loadPolicy({ Version: '2012-10-17', Statement: { Effect: 'Allow', Resource: '*', Action: '*', ...rawStatementValues } }); } //# sourceMappingURL=statements.js.map