UNPKG

@cloud-copilot/iam-simulate

Version:
197 lines (195 loc) 8.13 kB
import { isAssumedRoleArn, isFederatedUserArn, isIamRoleArn, isIamUserArn, isServicePrincipal } from '@cloud-copilot/iam-utils'; /** * The default authorizer for services. */ export class DefaultServiceAuthorizer { /** * Authorize a service request after all policy analysis has been completed. * * @param request the service authorization request containing all analyses * @returns the result of the authorization */ authorize(request) { const scpResult = request.scpAnalysis.result; const rcpResult = request.rcpAnalysis.result; const sessionResult = request.sessionAnalysis?.result; const identityStatementResult = request.identityAnalysis.result; const resourcePolicyResult = request.resourceAnalysis?.result; const permissionBoundaryResult = request.permissionBoundaryAnalysis?.result; const endpointPolicyResult = request.endpointAnalysis?.result; const principalAccount = request.request.principal.accountId(); const resourceAccount = request.request.resource?.accountId(); const sameAccount = principalAccount === resourceAccount; const baseResult = { sameAccount, sessionAnalysis: request.sessionAnalysis, identityAnalysis: request.identityAnalysis, scpAnalysis: request.scpAnalysis, rcpAnalysis: request.rcpAnalysis, resourceAnalysis: request.resourceAnalysis, permissionBoundaryAnalysis: request.permissionBoundaryAnalysis, endpointAnalysis: request.endpointAnalysis }; if (scpResult !== 'Allowed') { return { result: scpResult, ...baseResult }; } if (rcpResult !== 'Allowed') { return { result: rcpResult, ...baseResult }; } if (sessionResult && sessionResult !== 'Allowed') { return { result: sessionResult, ...baseResult }; } if (endpointPolicyResult === 'ExplicitlyDenied' || endpointPolicyResult === 'ImplicitlyDenied') { return { result: endpointPolicyResult, ...baseResult }; } if (resourcePolicyResult === 'ExplicitlyDenied' || resourcePolicyResult === 'DeniedForAccount') { return { result: 'ExplicitlyDenied', ...baseResult }; } if (identityStatementResult === 'ExplicitlyDenied') { return { result: 'ExplicitlyDenied', ...baseResult }; } if (permissionBoundaryResult === 'ExplicitlyDenied') { return { result: 'ExplicitlyDenied', ...baseResult }; } // Service Principals if (isServicePrincipal(request.request.principal.value())) { // Service principals are allowed if the resource policy allows them if (resourcePolicyResult === 'Allowed') { return { result: 'Allowed', ...baseResult }; } return { result: 'ImplicitlyDenied', ...baseResult }; } //Same Account if (principalAccount === resourceAccount) { if (permissionBoundaryResult === 'ImplicitlyDenied') { /** * If the permission boundary is an implicit deny * * If the request is from an assumed role ARN AND the resource policy allows the assumed role (session) ARN = ALLOW * If the request is from an IAM user ARN AND the resource policy allows the IAM user ARN = ALLOW * If the request is from a federated user ARN AND the resource policy allows the federated user ARN = ALLOW * The request is allowed: https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html */ if (resourcePolicyResult === 'Allowed') { const principal = request.request.principal.value(); if (isIamRoleArn(principal) && request.simulationParameters.simulationMode === 'Discovery') { if (request.resourceAnalysis.allowStatements.some((statement) => statement.principalMatch === 'Match' && statement.ignoredRoleSessionName)) { return { result: 'Allowed', ...baseResult }; } } if (isAssumedRoleArn(principal) || isIamUserArn(principal) || isFederatedUserArn(principal)) { if (request.resourceAnalysis.allowStatements.some((statement) => statement.principalMatch === 'Match')) { return { result: 'Allowed', ...baseResult }; } } } return { result: 'ImplicitlyDenied', ...baseResult }; } /* TODO: Implicit denies in identity policies I think if the identity policy has an implicit deny for assumed roles or federated users, then the resource policy must have the federated or assumed role ARN exactly. That doesn't seem right though. I know many cases where the resource policy has the role ARN and it works Need to add some tests for this. */ const trustedAccount = this.serviceTrustsPrincipalAccount(sameAccount, request.resourceAnalysis, request.request.resource); if (resourcePolicyResult === 'Allowed' || (trustedAccount && identityStatementResult === 'Allowed')) { return { result: 'Allowed', ...baseResult }; } return { result: 'ImplicitlyDenied', ...baseResult }; } //Cross Account if (permissionBoundaryResult === 'ImplicitlyDenied') { return { result: 'ImplicitlyDenied', ...baseResult }; } if (resourcePolicyResult === 'Allowed' || resourcePolicyResult === 'AllowedForAccount') { if (identityStatementResult === 'Allowed') { return { result: 'Allowed', ...baseResult }; } return { result: 'ImplicitlyDenied', ...baseResult }; } return { result: 'ImplicitlyDenied', ...baseResult }; /** * Add checks for: * * root user - can override resource policies for most resource types * * service linked roles - ignore SCPs and RCPs * * session policies * * vpc endpoint policies * * organization APIs and delegated admin policy */ } /** * Determines if the service trusts the principal's Account's IAM policies * * @param sameAccount - If the principal and resource are in the same account * @param resourceAnalysis - The resource policy analysis * @returns true if the service trusts the principal's account IAM policies */ serviceTrustsPrincipalAccount(sameAccount, resourceAnalysis, resource) { if (sameAccount) { return true; } return resourceAnalysis.allowStatements.some((statement) => statement.principalMatch === 'AccountLevelMatch'); } } //# sourceMappingURL=DefaultServiceAuthorizer.js.map