diff --git a/deployment/document-understanding-solution.template b/deployment/document-understanding-solution.template index 0d10ba4b4..2fa37b5eb 100644 --- a/deployment/document-understanding-solution.template +++ b/deployment/document-understanding-solution.template @@ -686,7 +686,10 @@ Resources: "iam:Get*", "iam:AttachRolePolicy", "iam:PutRolePolicy", - "iam:CreateServiceLinkedRole" + "iam:DeleteRolePolicy", + "iam:DetachRolePolicy", + "iam:CreateServiceLinkedRole", + "iam:DeleteServiceLinkedRole" ] }, { diff --git a/source/lib/cdk-textract-stack.ts b/source/lib/cdk-textract-stack.ts index 718a10b94..0c8af965d 100644 --- a/source/lib/cdk-textract-stack.ts +++ b/source/lib/cdk-textract-stack.ts @@ -46,7 +46,7 @@ import { QueueEncryption } from "@aws-cdk/aws-sqs"; import { LogGroup } from "@aws-cdk/aws-logs"; import { LogGroupLogDestination } from "@aws-cdk/aws-apigateway"; -const API_CONCURRENT_REQUESTS = 20; //approximate number of 1-2 page documents to be processed parallelly +const API_CONCURRENT_REQUESTS = 20; //approximate number of 1-2 page documents to be processed in parallell export interface TextractStackProps { email: string; @@ -171,12 +171,14 @@ export class CdkTextractStack extends cdk.Stack { behaviors: [{ isDefaultBehavior: true }], }, ], - errorConfigurations: [{ - errorCode: 404, - responseCode: 200, - errorCachingMinTtl: 5, - responsePagePath: '/index.html' - }], + errorConfigurations: [ + { + errorCode: 404, + responseCode: 200, + errorCachingMinTtl: 5, + responsePagePath: "/index.html", + }, + ], priceClass: PriceClass.PRICE_CLASS_100, httpVersion: HttpVersion.HTTP2, enableIpV6: true, @@ -324,12 +326,39 @@ export class CdkTextractStack extends cdk.Stack { elasticSearch.node.addDependency(serviceLinkedRole); } + const jobResultsKey = new kms.Key( + this, + this.resourceName("JobResultsKey"), + { + enableKeyRotation: true, + enabled: true, + trustAccountIdentities: true, + policy: new iam.PolicyDocument({ + assignSids: true, + statements: [ + new iam.PolicyStatement({ + actions: ["kms:GenerateDataKey*", "kms:Decrypt"], + resources: ["*"], // Resource level permissions are not necessary in this policy statement, as it is automatically restricted to this key + effect: iam.Effect.ALLOW, + principals: [ + new iam.ServicePrincipal("sns.amazonaws.com"), + new iam.ServicePrincipal("lambda.amazonaws.com"), + new iam.ServicePrincipal("textract.amazonaws.com"), + new iam.ServicePrincipal("sqs.amazonaws.com"), + ], + }), + ], + }), + } + ); + // SNS Topic const jobCompletionTopic = new sns.Topic( this, - this.resourceName("JobCompletion"), + this.resourceName("JobCompletionTopic"), { displayName: "Job completion topic", + masterKey: jobResultsKey, } ); @@ -349,6 +378,13 @@ export class CdkTextractStack extends cdk.Stack { resources: [jobCompletionTopic.topicArn], }) ); + textractServiceRole.addToPolicy( + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ["kms:Decrypt", "kms:GenerateDataKey*"], + resources: [jobResultsKey.keyArn], + }) + ); // DynamoDB tables const outputTable = new ddb.Table(this, this.resourceName("OutputTable"), { @@ -440,6 +476,7 @@ export class CdkTextractStack extends cdk.Stack { { visibilityTimeout: cdk.Duration.seconds(900), retentionPeriod: cdk.Duration.seconds(1209600), + encryption: QueueEncryption.KMS_MANAGED, } ); @@ -449,12 +486,16 @@ export class CdkTextractStack extends cdk.Stack { { visibilityTimeout: cdk.Duration.seconds(900), retentionPeriod: cdk.Duration.seconds(1209600), + encryption: QueueEncryption.KMS, + encryptionMasterKey: jobResultsKey, + dataKeyReuse: cdk.Duration.seconds(86400), deadLetterQueue: { maxReceiveCount: 3, queue: jobResultsDLQueue, }, } ); + // trigger jobCompletionTopic.addSubscription( new snsSubscriptions.SqsSubscription(jobResultsQueue) @@ -874,6 +915,7 @@ export class CdkTextractStack extends cdk.Stack { jobResultProcessor.addLayers(textractorLayer); jobResultProcessor.addLayers(boto3Layer); jobResultProcessor.addLayers(elasticSearchLayer); + jobResultsKey.grantEncryptDecrypt(jobResultProcessor); // Triggers jobResultProcessor.addEventSource(