UNPKG

@cloud-copilot/iam-lens

Version:

Visibility in IAM in and across AWS accounts

195 lines 11.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.principalCan = principalCan; const iam_policy_1 = require("@cloud-copilot/iam-policy"); const iam_shrink_1 = require("@cloud-copilot/iam-shrink"); const iam_utils_1 = require("@cloud-copilot/iam-utils"); const principals_js_1 = require("../principals.js"); const permissionSet_js_1 = require("./permissionSet.js"); const iamRoles_js_1 = require("./resources/resourceTypes/iamRoles.js"); const kmsKeys_js_1 = require("./resources/resourceTypes/kmsKeys.js"); const s3Buckets_js_1 = require("./resources/resourceTypes/s3Buckets.js"); const statements_js_1 = require("./resources/statements.js"); /** * Get what actions a principal can perform based on their policies. * * @param collectClient the IAM collect client to use for retrieving policies. * @param input the input containing the principal and options. * @returns A promise that resolves to the permissions the principal can perform, or void if the implementation is incomplete. */ async function principalCan(collectClient, input) { const { principal } = input; if (!principal) { throw new Error('Principal must be provided for principal-can command'); } const principalAccountId = (0, iam_utils_1.splitArnParts)(principal).accountId; const principalPolicies = await (0, principals_js_1.getAllPoliciesForPrincipal)(collectClient, principal); const identityPolicies = [ ...principalPolicies.managedPolicies, ...principalPolicies.inlinePolicies, ...(principalPolicies.groupPolicies?.map((group) => group.managedPolicies).flat() || []), ...(principalPolicies.groupPolicies?.map((group) => group.inlinePolicies).flat() || []) ].map((policy) => (0, iam_policy_1.loadPolicy)(policy.policy)); const allowedPermissions = await (0, permissionSet_js_1.buildPermissionSetFromPolicies)('Allow', identityPolicies); const identityDenyPermissions = await (0, permissionSet_js_1.buildPermissionSetFromPolicies)('Deny', identityPolicies); let finalPermissions = allowedPermissions; const resourceDenyPermissions = new permissionSet_js_1.PermissionSet('Deny'); /*********** Start KMS Keys *************/ //📋 Get all current permissions const { keyAllows: allKeysAllow, keyDenies: allKeysDeny } = await (0, kmsKeys_js_1.allKmsKeysAllActionsPermissionSets)(); //📋 Capture what is currently allowed by identity policies const identityKeyPermissions = allKeysAllow.intersection(allowedPermissions); //📋 Do any subtractions first // Remove all the KMS permissions from the identityAllows, add them back later finalPermissions = finalPermissions.subtract(allKeysDeny).allow; //📋 Get the permissions for the account // Get all the KMS permission for the same account const { accountAllows: keyAccountAllows, principalAllows: keyPrincipalAllows, denies: keyDenies } = await (0, kmsKeys_js_1.kmsKeysSameAccount)(collectClient, principal); //📋 Add direct permissions for the principal // Add in the principal allows finalPermissions.addAll(keyPrincipalAllows); //📋 Intersect account allows with identity allows // Add the account allows intersected with the identity allows for (const keyAcctAllow of keyAccountAllows) { finalPermissions.addAll(keyAcctAllow.intersection(identityKeyPermissions)); } //📋 Add the denies. // Add the denies for later resourceDenyPermissions.addAll(keyDenies); /*********** End KMS Keys *************/ /*********** Start Role Trust Policies *************/ const { roleAllows: allRolesAllow, roleDenies: allRolesDeny } = await (0, iamRoles_js_1.allIamRolesAssumeRolePermissionSets)(); const identityAssumeRolePermissions = allRolesAllow.intersection(allowedPermissions); // Remove all the IAM role permissions from the identityAllows, add them back later finalPermissions = finalPermissions.subtract(allRolesDeny).allow; // Get all the KMS permission for the same account const { accountAllows: roleAccountAllows, principalAllows: rolePrincipalAllows, denies: roleDenies } = await (0, iamRoles_js_1.iamRolesSameAccount)(collectClient, principal); // Add in the principal allows finalPermissions.addAll(rolePrincipalAllows); // Add the account allows intersected with the identity allows for (const roleAcctAllow of roleAccountAllows) { finalPermissions.addAll(roleAcctAllow.intersection(identityAssumeRolePermissions)); } // Add the denies for later resourceDenyPermissions.addAll(roleDenies); /*********** End Role Trust Policies *************/ /*********** Start Buckets *************/ const { allows: bucketAllows, denies: bucketDenies } = await (0, s3Buckets_js_1.s3BucketsSameAccount)(collectClient, principal); finalPermissions.addAll(bucketAllows); resourceDenyPermissions.addAll(bucketDenies); /*********** End Buckets *************/ // TODO: There is a slight wrinkle where same account resource policies can override implicit denies from Permission Boundaries. if (principalPolicies.permissionBoundary) { const boundaryPolicy = (0, iam_policy_1.loadPolicy)(principalPolicies.permissionBoundary.policy); const boundaryPermissions = await (0, permissionSet_js_1.buildPermissionSetFromPolicies)('Allow', [boundaryPolicy]); const boundaryDenies = await (0, permissionSet_js_1.buildPermissionSetFromPolicies)('Deny', [boundaryPolicy]); identityDenyPermissions.addAll(boundaryDenies); finalPermissions = finalPermissions.intersection(boundaryPermissions); } /*********** Start Cross Account Checks *************/ // 📋 List the accounts // 📋 Get the RCPs, if any // 📋 Get the bucket permission sets for the account // 📋 Intersect with identity permissions and Permission boundary by using `finalPermissions` // 📋 Subtract denies from identity permissions, SCPs, and Permission Boundary const allAccounts = await collectClient.allAccounts(); const otherAccountAllows = new permissionSet_js_1.PermissionSet('Allow'); const otherAccountDenies = new permissionSet_js_1.PermissionSet('Deny'); for (const otherAccountId of allAccounts) { if (otherAccountId === principalAccountId) { continue; } const rcpPolicies = await collectClient.getRcpHierarchyForAccount(otherAccountId); const otherAccountRcpDenies = new permissionSet_js_1.PermissionSet('Deny'); for (const level of rcpPolicies) { const rcpPolicies = level.policies.map((rcp) => (0, iam_policy_1.loadPolicy)(rcp.policy)); for (const policy of rcpPolicies) { for (const statement of policy.statements()) { if (statement.isDeny()) { // Only Add RCP denies that apply to the principal const applies = await (0, statements_js_1.statementAppliesToPrincipal)(statement, principal, collectClient); if (applies === 'PrincipalMatch' || applies === 'AccountMatch') { await (0, permissionSet_js_1.addStatementToPermissionSet)(statement, otherAccountRcpDenies); } } } } } const { allows: otherAccountBucketAllows, denies: otherAccountBucketDenies } = await (0, s3Buckets_js_1.s3BucketsCrossAccount)(collectClient, otherAccountId, otherAccountRcpDenies, principal); otherAccountAllows.addAll(otherAccountBucketAllows); otherAccountDenies.addAll(otherAccountBucketDenies); } let effectiveOtherAccountAllows = otherAccountAllows.intersection(finalPermissions); /*********** End Cross Account Checks *************/ const scpAllowsByLevel = []; const rcpAllowsByLevel = []; for (const level of principalPolicies.scps) { const scpPolicies = level.policies.map((scp) => (0, iam_policy_1.loadPolicy)(scp.policy)); scpAllowsByLevel.push(await (0, permissionSet_js_1.buildPermissionSetFromPolicies)('Allow', scpPolicies)); for (const policy of scpPolicies) { for (const statement of policy.statements()) { if (statement.isDeny()) { // Only Add SCP denies that apply to the principal const applies = await (0, statements_js_1.statementAppliesToPrincipal)(statement, principal, collectClient); if (applies === 'PrincipalMatch' || applies === 'AccountMatch') { await (0, permissionSet_js_1.addStatementToPermissionSet)(statement, identityDenyPermissions); } } } } } const principalAccountDenyPermissions = identityDenyPermissions.clone(); for (const level of principalPolicies.rcps) { const rcpPolicies = level.policies.map((rcp) => (0, iam_policy_1.loadPolicy)(rcp.policy)); rcpAllowsByLevel.push(await (0, permissionSet_js_1.buildPermissionSetFromPolicies)('Allow', rcpPolicies)); for (const policy of rcpPolicies) { for (const statement of policy.statements()) { if (statement.isDeny()) { // Only Add RCPs denies that apply to the principal const applies = await (0, statements_js_1.statementAppliesToPrincipal)(statement, principal, collectClient); if (applies === 'PrincipalMatch' || applies === 'AccountMatch') { await (0, permissionSet_js_1.addStatementToPermissionSet)(statement, principalAccountDenyPermissions); } } } } } for (const scpAllow of scpAllowsByLevel) { finalPermissions = finalPermissions.intersection(scpAllow); effectiveOtherAccountAllows = effectiveOtherAccountAllows.intersection(scpAllow); } for (const rcpAllow of rcpAllowsByLevel) { finalPermissions = finalPermissions.intersection(rcpAllow); } //Put together all the denies principalAccountDenyPermissions.addAll(resourceDenyPermissions); /* Same account final permissions after denies */ const sameAccountPermissionsAfterDeny = finalPermissions.subtract(principalAccountDenyPermissions); finalPermissions = sameAccountPermissionsAfterDeny.allow; const deniedPermissions = sameAccountPermissionsAfterDeny.deny; const allowStatements = (0, permissionSet_js_1.toPolicyStatements)(finalPermissions); const denyStatements = (0, permissionSet_js_1.toPolicyStatements)(deniedPermissions); /* Cross account final permissions */ // Combine all the denies that apply to cross account const allCrossAccountDenies = principalAccountDenyPermissions.clone(); allCrossAccountDenies.addAll(otherAccountDenies); // Subtract the denies from the cross account allows const crossAccountPermissionsAfterDeny = effectiveOtherAccountAllows.subtract(allCrossAccountDenies); const crossAccountAllowStatements = (0, permissionSet_js_1.toPolicyStatements)(crossAccountPermissionsAfterDeny.allow); const crossAccountDenyStatements = (0, permissionSet_js_1.toPolicyStatements)(crossAccountPermissionsAfterDeny.deny); /* Create a policy document for everything */ const policyDocument = { Version: '2012-10-17', Statement: [ ...allowStatements, ...denyStatements, ...crossAccountAllowStatements, ...crossAccountDenyStatements ] }; if (input.shrinkActionLists) { await (0, iam_shrink_1.shrinkJsonDocument)({ iterations: 0 }, policyDocument); } return policyDocument; } //# sourceMappingURL=principalCan.js.map