/* eslint-disable @typescript-eslint/ban-ts-comment*/ import * as cdk from "@aws-cdk/core"; import * as iam from "@aws-cdk/aws-iam"; import { getChildLogger } from "@serverless-stack/core"; import { Api, Table, Topic, Queue, Bucket, Function, AppSyncApi, WebSocketApi, KinesisStream, ApiGatewayV1Api, Stack, } from "../index"; import { isConstructOf } from "./construct"; const logger = getChildLogger("resources"); export type Permissions = PermissionType | Permission[]; type Permission = | string | cdk.Construct | [cdk.Construct, string] | iam.PolicyStatement; export enum PermissionType { ALL = "*", } export function attachPermissionsToRole( role: iam.Role, permissions: Permissions ): void { // Four patterns // // attachPermissions(PermissionType.ALL); // attachPermissions([ 'sns', 'sqs' ]); // attachPermissions([ event, queue ]); // attachPermissions([ // [ event.snsTopic, 'grantPublish' ], // [ queue.sqsQueue, 'grantSendMessages' ], // ]); // attachPermissions([ // new iam.PolicyStatement({ // actions: ["s3:*"], // effect: iam.Effect.ALLOW, // resources: [ // bucket.bucketArn + "/private/${cognito-identity.amazonaws.com:sub}/*", // ], // }) // ]); //////////////////////////////////// // Case: 'admin' permissions => '*' //////////////////////////////////// if (typeof permissions === "string") { if (permissions === PermissionType.ALL) { role.addToPolicy(buildPolicy(permissions, ["*"])); } else { throw new Error(`The specified permissions are not supported.`); } return; } if (!Array.isArray(permissions)) { throw new Error( `The specified permissions are not supported. They are expected to be PermissionType.ALL or an array.` ); } // Handle array of permissions permissions.forEach((permission: Permission) => { //////////////////////////////////// // Case: string ie. 's3' or 's3:*' //////////////////////////////////// if (typeof permission === "string") { const perm = permission.indexOf(":") === -1 ? `${permission}:*` : permission; role.addToPolicy(buildPolicy(perm, ["*"])); } //////////////////////////////////// // Case: iam.PolicyStatement //////////////////////////////////// else if ( isConstructOf(permission as cdk.Construct, "aws-iam.PolicyStatement") ) { role.addToPolicy(permission as iam.PolicyStatement); } //////////////////////////////////// // Case: CDK constructs //////////////////////////////////// else if (isConstructOf(permission as cdk.Construct, "aws-dynamodb.Table")) { // @ts-expect-error We do not want to import the cdk modules, just cast to any const tableArn = permission.tableArn; role.addToPolicy(buildPolicy("dynamodb:*", [tableArn, `${tableArn}/*`])); } else if (isConstructOf(permission as cdk.Construct, "aws-sns.Topic")) { // @ts-expect-error We do not want to import the cdk modules, just cast to any role.addToPolicy(buildPolicy("sns:*", [permission.topicArn])); } else if (isConstructOf(permission as cdk.Construct, "aws-sqs.Queue")) { // @ts-expect-error We do not want to import the cdk modules, just cast to any role.addToPolicy(buildPolicy("sqs:*", [permission.queueArn])); } else if ( isConstructOf(permission as cdk.Construct, "aws-kinesis.Stream") ) { // @ts-expect-error We do not want to import the cdk modules, just cast to any role.addToPolicy(buildPolicy("kinesis:*", [permission.streamArn])); } else if (isConstructOf(permission as cdk.Construct, "aws-s3.Bucket")) { // @ts-expect-error We do not want to import the cdk modules, just cast to any const bucketArn = permission.bucketArn; role.addToPolicy(buildPolicy("s3:*", [bucketArn, `${bucketArn}/*`])); } else if (isConstructOf(permission as cdk.Construct, "aws-rds.ServerlessCluster")) { // For ServerlessCluster, we need to grant: // - permisssions to access the Data API; // - permisssions to access the Secret Manager (required by Data API). // No need to grant the permissions for IAM database authentication // @ts-expect-error We do not want to import the cdk modules, just cast to any role.addToPolicy(buildPolicy("rds-data:*", [permission.clusterArn])); // @ts-expect-error We do not want to import the cdk modules, just cast to any const secret = permission.secret; if (secret) { role.addToPolicy(buildPolicy("secretsmanager:GetSecretValue", [secret.secretArn])) } } //////////////////////////////////// // Case: SST construct //////////////////////////////////// else if (permission instanceof Api) { const httpApi = permission.httpApi; const { account, region } = Stack.of(httpApi); role.addToPolicy( buildPolicy("execute-api:Invoke", [ `arn:aws:execute-api:${region}:${account}:${httpApi.httpApiId}/*`, ]) ); } else if (permission instanceof ApiGatewayV1Api) { const restApi = permission.restApi; const { account, region } = Stack.of(restApi); role.addToPolicy( buildPolicy("execute-api:Invoke", [ `arn:aws:execute-api:${region}:${account}:${restApi.restApiId}/*`, ]) ); } else if (permission instanceof WebSocketApi) { const webSocketApi = permission.webSocketApi; const { account, region } = Stack.of(webSocketApi); role.addToPolicy( buildPolicy("execute-api:Invoke", [ `arn:aws:execute-api:${region}:${account}:${webSocketApi.apiId}/*`, ]) ); } else if (permission instanceof AppSyncApi) { const graphqlApi = permission.graphqlApi; const { account, region } = Stack.of(graphqlApi); role.addToPolicy( buildPolicy("appsync:GraphQL", [ `arn:aws:appsync:${region}:${account}:apis/${graphqlApi.apiId}/*`, ]) ); } else if (permission instanceof Table) { const tableArn = permission.dynamodbTable.tableArn; role.addToPolicy(buildPolicy("dynamodb:*", [tableArn, `${tableArn}/*`])); } else if (permission instanceof Topic) { role.addToPolicy(buildPolicy("sns:*", [permission.snsTopic.topicArn])); } else if (permission instanceof Queue) { role.addToPolicy(buildPolicy("sqs:*", [permission.sqsQueue.queueArn])); } else if (permission instanceof KinesisStream) { role.addToPolicy( buildPolicy("kinesis:*", [permission.kinesisStream.streamArn]) ); } else if (permission instanceof Bucket) { const bucketArn = permission.s3Bucket.bucketArn; role.addToPolicy(buildPolicy("s3:*", [bucketArn, `${bucketArn}/*`])); } else if (permission instanceof Function) { role.addToPolicy(buildPolicy("lambda:*", [permission.functionArn])); } //////////////////////////////////// // Case: grant method //////////////////////////////////// else if ( Array.isArray(permission) && permission.length === 2 && cdk.Construct.isConstruct(permission[0]) && typeof permission[1] === "string" ) { const construct = permission[0] as cdk.Construct; const methodName = permission[1] as keyof cdk.Construct; (construct[methodName] as { (construct: cdk.Construct): void })(role); } else { logger.debug("permission object", permission); throw new Error(`The specified permissions are not supported.`); } }); } function buildPolicy(action: string, resources: string[]): iam.PolicyStatement { return new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: [action], resources, }); }