@cloud-copilot/iam-lens
Version:
Visibility in IAM in and across AWS accounts
117 lines • 4.34 kB
JavaScript
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