AWSTemplateFormatVersion: '2010-09-09' Description: > "(SO0054) AI Powered Speach Analytics for Amazon Connect Version %%VERSION%% : - Create the basic foundation for streaming customer audio from Amazon Connect by deploying: - S3 Bucket for audio files, and the sample contact flow - Dynamo DB tables: transcriptSegments and contactDetails - A Lambda triggered on inbound contacts to store the initial contact details - A Lambda to trigger and pass the stream details to the Java Lambda - A Java Lambda to consume KVS and stream it to Amazon Transcribe, store the segments in DDB and upload the raw audio to S3 - A Node.js Lambda triggered by S3 once WAV file is uploaded to store the concatenated transcript segments in the contact details table along with the S3 location of the audio file - A Node.js Lambda triggered by CloudFormation to create a sample Amazon Connect contact flow, pre-populated with the Lambda ARNs and placed in the S3 bucket for you to import in to your Amazon Connect instance. - AWS Fargate task for transcribing call audio to text" Mappings: FunctionMap: Configuration: S3Bucket: "%%BUCKET_NAME%%" S3Key: "ai-powered-speech-analytics-for-amazon-connect/%%VERSION%%" SolutionID: "SO0054" Version: "%%VERSION%%" Send: AnonymousUsage: Data: "Yes" Parameters: S3BucketName: Type: String Default: "new-audio-bucket-name" Description: > Enter the (globally unique) name you would like to use for the Amazon S3 bucket where we will store the audio files, and the sample contact flow. This template will fail to deploy if the bucket name you chose is currently in use. AllowedPattern: '(?=^.{3,63}$)(?!^(\d+\.)+\d+$)(^(([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])$)' audioFilePrefix: Type: String Default: recordings/ Description: The Amazon S3 prefix where the audio files will be saved (must end in "/") rawAudioUploadPrefix: Type: String Default: audio-file-input/ Description: > The Amazon S3 prefix where raw/wav (audio/L16; mono; 8 kHz) audio recordings may be uploaded in the event you would like process an audio file vs making a phone call and streaming from KVS. Mainly for testing, or for realtime transcription of audio files. This will only work with single channel files (mono). S3BucketNameForWebSite: Type: String Default: "new-website-bucket-name" Description: > Enter the (globally unique) name you would like to use for the Amazon S3 bucket where we will store the website assets and the sample contact flow. This template will fail to deploy if the bucket name you chose is currently in use. AllowedPattern: '(?=^.{3,63}$)(?!^(\d+\.)+\d+$)(^(([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])$)' cloudFrontPriceClass: Type: String Default: PriceClass_100 AllowedValues: - PriceClass_100 - PriceClass_200 - PriceClass_All ConstraintDescription: "Allowed Price Classes PriceClass_100 PriceClass_200 and PriceClass_All" Description: Specify the CloudFront price class. See https://aws.amazon.com/cloudfront/pricing/ for a description of each price class. instanceIdParam: Type: String AllowedPattern: '\w{8}-\w{4}-\w{4}-\w{4}-\w{12}' ConstraintDescription: "Invalid Amazon Connect instance Id" Description: Amazon Connect Instance ID (Ensure you it is entered accurately in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx ). instanceNameParam: Type: String ConstraintDescription: "Invalid Amazon Connect instance alias" Description: Amazon Connect Instance Alias (Ensure you it is entered accurately as configured under Amazon Connect Service ). computeType: Type: String Default: Lambda AllowedValues: [Lambda, Fargate] Description: Specify whether to use AWS Fargate or AWS Lambda for transcribing call audio to text. The solution, by default, uses an AWS Lambda function to transcribe call audio consumed from Kinesis Video Streams. Please note that Lambda has a maximum run time of 15 minutes per invocation whereas Fargate has no such limits and can run as long as the call is connected. sessionDuration: Type: Number Default: 3600 MinValue: 900 MaxValue: 3600 ConstraintDescription: "Must specify a value from 900 to 3600." Description: You can give a value from 900 seconds (15 minutes) to 3600 seconds (1 hour). This is the maximum call duration limit until which the customer interaction in Amazon Connect need to be processed by this solution. Conditions: ShouldCreateFargateResources: !Equals [Fargate, !Ref computeType] ShouldCreateLambdaResource: !Equals [Lambda, !Ref computeType] Metadata: 'AWS::CloudFormation::Interface': ParameterGroups: - Label: default: Amazon Connect Configuration Parameters: - instanceIdParam - instanceNameParam - Label: default: Amazon S3 Configuration Parameters: - S3BucketNameForWebSite - S3BucketName - audioFilePrefix - rawAudioUploadPrefix - Label: default: Amazon CloudFront Configuration Parameters: - cloudFrontPriceClass - Label: default: Amazon Compute Configuration Parameters: - computeType - Label: default: IAM Role Configuration Parameters: - sessionDuration ParameterLabels: instanceIdParam: default: Instance ID instanceNameParam: default: Instance Alias S3BucketNameForWebSite: default: Website Bucket Name S3BucketName: default: Call Audio Bucket Name cloudFrontPriceClass: default: Price Class audioFilePrefix: default: Audio File Prefix rawAudioUploadPrefix: default: Test Mono Audio Prefix sessionDuration: default: The maximum duration of the role session (in seconds) computeType: default: Compute Type Outputs: transcriptSegmentsDDBTable: Description: The ARN of the DynamoDB table created to store segments of call transcripts (customer audio) Value: !GetAtt transcriptSegmentsDDBTable.Arn contactsDDBTable: Description: The ARN of the DynamoDB table created to store contact details used in this solution Value: !GetAtt contactDetailsDDBTable.Arn GwIamRole: Description: The IAM Role created for the API GW Lambda Functions in this template Value: !Ref AIPoweredSpeechAnalyticsApiGwLambdaRole initContactDetails: Description: > AWS Lambda Function that will be triggered when the call starts so that we have the initial contact details which can later add to when we have the transcript, and audio file location. Value: !Ref initContactDetails transcriptionTrigger: Description: > AWS Lambda Function to start (asynchronous) streaming transcription; it is expected to be called by the Amazon Connect Contact Flow. Value: !Ref kvsConsumerTrigger Condition: ShouldCreateLambdaResource transcriptionTriggerARN: Description: ARN for the TranscriptionTriggerFunction Value: !GetAtt kvsConsumerTrigger.Arn Condition: ShouldCreateLambdaResource CallTranscription: Description: AWS Lambda Function to get audio from Kinesis Video Streams and use Amazon Transcribe to get text for the caller audio. Should be invoked by TranscriptionTrigger and write results to the transcriptSegments table. Value: !Ref kvsTranscriber Condition: ShouldCreateLambdaResource wsrealtimetranscribeOnConnectOP: Description: AWS Lambda Function gets called when a new connection is coming from a browser. Value: !Ref wsrealtimetranscribeOnConnect wsrealtimetranscribeDefaultOP: Description: AWS Lambda Function that will be triggered when the new call is received by the agent. Value: !Ref wsrealtimetranscribeDefault wsrealtimetranscribeOnDDBInsertOP: Description: AWS Lambda function gets called when a new row is inserted into the segment table Value: !Ref wsrealtimetranscribeOnDDBInsert wsrealtimetranscribeOnDisConnectOP: Description: AWS Lambda function gets called when a websocket connection is dropped from browser Value: !Ref wsrealtimetranscribeOnDisConnect wsrealtimetranscribeOnMessageOP: Description: AWS Lambda function gets called when a message comes from the websocket connection Value: !Ref wsrealtimetranscribeOnMessage webSocketConnectionsTableOP: Description: The Arn of the DynamoDB table created to websocket connection information used in this solution Value: !GetAtt webSocketConnectionsDDBTable.Arn WebSocketURI: Description: The WSS Protocol URI to connect to Value: !Join - '' - - 'wss://' - !Ref AIPoweredSpeechAnalyticsWS - .execute-api. - !Ref 'AWS::Region' - .amazonaws.com/ - !Ref Stage createS3BucketOP: Description: Bucket contains all the call recordings and sample contactflow Value: !GetAtt [createS3Bucket, WebsiteURL] createS3BucketSSLOP: Description: Bucket contains all the call recordings and sample contactflow Value: !Join ['', ['https://', !GetAtt [createS3Bucket, DomainName]]] cloudfrontEndpoint: Value: !Join - '' - - 'https://' - !GetAtt [AIPSAnalyticsCloudFrontDistribution, DomainName] - '/agentAssist.html' Description: Endpoint for Cloudfront distribution transcribingFargateTrigger: Description: > AWS Lambda Function triggered by the Agent whisper contact flow to send an Amazon SQS Message with the stream details; it is expected to be called by the Amazon Connect Contact Flow. Value: !Ref transcribingFargateTrigger Condition: ShouldCreateFargateResources transcribingFargateTriggerARN: Description: ARN for the transcribingFargateTrigger Function Value: !GetAtt transcribingFargateTrigger.Arn Condition: ShouldCreateFargateResources uuid: Description: UUID for this deployment Value: !GetAtt SolutionUuid.UUID Resources: allowConnectToKvsConsumerTriggerLambda: Type: 'AWS::Lambda::Permission' Condition: ShouldCreateLambdaResource Properties: FunctionName: !Ref kvsConsumerTrigger Action: 'lambda:InvokeFunction' Principal: connect.amazonaws.com SourceAccount: !Ref 'AWS::AccountId' allowConnectToInitContactDetailsLambda: Type: 'AWS::Lambda::Permission' Properties: FunctionName: !Ref initContactDetails Action: 'lambda:InvokeFunction' Principal: connect.amazonaws.com SourceAccount: !Ref 'AWS::AccountId' allowS3toProcessContactSummaryLambda: Type: 'AWS::Lambda::Permission' Properties: FunctionName: !Ref processContactSummary Action: 'lambda:InvokeFunction' Principal: s3.amazonaws.com SourceAccount: !Ref 'AWS::AccountId' createS3Bucket: Type: 'AWS::S3::Bucket' Properties: BucketName: !Ref S3BucketName NotificationConfiguration: LambdaConfigurations: - Function: !GetAtt processContactSummary.Arn Event: "s3:ObjectCreated:*" Filter: S3Key: Rules: - Name: suffix Value: wav AccessControl: LogDeliveryWrite LoggingConfiguration: DestinationBucketName: !Ref S3BucketName LogFilePrefix: 'logs/' VersioningConfiguration: Status: Enabled PublicAccessBlockConfiguration: BlockPublicAcls: True BlockPublicPolicy: True IgnorePublicAcls: True RestrictPublicBuckets: True BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 CorsConfiguration: CorsRules: - AllowedOrigins: - 'https://*.cloudfront.net' AllowedHeaders: - '*' AllowedMethods: - PUT - HEAD MaxAge: '3000' s3BucketPolicy: Type: AWS::S3::BucketPolicy DependsOn: - createS3Bucket Properties: Bucket: !Ref createS3Bucket PolicyDocument: Statement: - Action: - "s3:GetObject" Effect: "Deny" Principal: "*" Resource: !Sub ${createS3Bucket.Arn}/CCP/* - Action: 's3:*' Resource: !Sub ${createS3Bucket.Arn}/CCP/* Effect: Deny Condition: Bool: 'aws:SecureTransport': false Principal: '*' - Action: 's3:*' Resource: !Sub ${createS3Bucket.Arn}/CCP/* Effect: Deny Condition: Bool: 'aws:SecureTransport': false Principal: '*' createWebSiteS3Bucket: Type: 'AWS::S3::Bucket' UpdateReplacePolicy: Retain DeletionPolicy: Retain DependsOn: - createS3Bucket Properties: BucketName: !Ref S3BucketNameForWebSite LoggingConfiguration: DestinationBucketName: !Ref S3BucketName LogFilePrefix: 'logs/' PublicAccessBlockConfiguration: BlockPublicAcls: True BlockPublicPolicy: True IgnorePublicAcls: True RestrictPublicBuckets: True WebsiteConfiguration: IndexDocument: agentAssist.html ErrorDocument: error.html BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 s3WebsiteBucketPolicy: Type: AWS::S3::BucketPolicy DependsOn: - createWebSiteS3Bucket - AIPSAnalyticsCloudFrontDistributionAccessIdentity Properties: Bucket: !Ref createWebSiteS3Bucket PolicyDocument: Statement: - Action: - "s3:GetObject" Effect: "Allow" Principal: CanonicalUser: Fn::GetAtt: [ AIPSAnalyticsCloudFrontDistributionAccessIdentity , S3CanonicalUserId ] Resource: !Sub ${createWebSiteS3Bucket.Arn}/CCP/* - Action: 's3:*' Resource: !Sub ${createWebSiteS3Bucket.Arn}/CCP/* Effect: Deny Condition: Bool: 'aws:SecureTransport': false Principal: '*' - Action: 's3:*' Resource: !Sub ${createWebSiteS3Bucket.Arn}/CCP/* Effect: Deny Condition: Bool: 'aws:SecureTransport': false Principal: '*' transcriptSegmentsDDBTable: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: "ContactId" AttributeType: "S" - AttributeName: "StartTime" AttributeType: "N" KeySchema: - AttributeName: "ContactId" KeyType: "HASH" - AttributeName: "StartTime" KeyType: "RANGE" # assuming 5 concurrent calls ProvisionedThroughput: ReadCapacityUnits: 5 WriteCapacityUnits: 5 PointInTimeRecoverySpecification: PointInTimeRecoveryEnabled: True SSESpecification: SSEEnabled: True TimeToLiveSpecification: AttributeName: "ExpiresAfter" Enabled: True StreamSpecification: StreamViewType: NEW_AND_OLD_IMAGES Metadata: cfn_nag: rules_to_suppress: - id: W73 reason: "Billing mode needs to be determined by customer." transcriptSegmentsToCustomerDDBTable: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: "ContactId" AttributeType: "S" - AttributeName: "StartTime" AttributeType: "N" KeySchema: - AttributeName: "ContactId" KeyType: "HASH" - AttributeName: "StartTime" KeyType: "RANGE" # assuming 5 concurrent calls ProvisionedThroughput: ReadCapacityUnits: 5 WriteCapacityUnits: 5 PointInTimeRecoverySpecification: PointInTimeRecoveryEnabled: True SSESpecification: SSEEnabled: True TimeToLiveSpecification: AttributeName: "ExpiresAfter" Enabled: True StreamSpecification: StreamViewType: NEW_AND_OLD_IMAGES Metadata: cfn_nag: rules_to_suppress: - id: W73 reason: "Billing mode needs to be determined by customer." contactDetailsDDBTable: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: "contactId" AttributeType: "S" KeySchema: - AttributeName: "contactId" KeyType: "HASH" # assuming 5 concurrent calls ProvisionedThroughput: ReadCapacityUnits: 5 WriteCapacityUnits: 5 PointInTimeRecoverySpecification: PointInTimeRecoveryEnabled: True SSESpecification: SSEEnabled: True Metadata: cfn_nag: rules_to_suppress: - id: W73 reason: "Billing mode needs to be determined by customer." AIPoweredSpeechAnalyticsKvsTranscribeRole: Type: "AWS::IAM::Role" Condition: ShouldCreateLambdaResource Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "lambda.amazonaws.com" Action: - "sts:AssumeRole" Path: "/" Policies: - PolicyName: kvs-streaming-transcribe-policy PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*" - Effect: "Allow" Action: - "dynamodb:Query" - "dynamodb:Scan" - "dynamodb:PutItem" - "dynamodb:UpdateItem" - "dynamodb:GetRecords" - "dynamodb:GetShardIterator" - "dynamodb:DescribeStream" - "dynamodb:ListStreams" Resource: - !Sub ${transcriptSegmentsDDBTable.Arn} - !Sub ${transcriptSegmentsToCustomerDDBTable.Arn} - Effect: "Allow" Action: - "s3:PutObject" - "s3:GetObject" - "s3:PutObjectAcl" Resource: - !Sub ${createS3Bucket.Arn}/* - Effect: "Allow" Action: - "transcribe:DeleteTranscriptionJob" - "transcribe:GetTranscriptionJob" - "transcribe:GetVocabulary" - "transcribe:ListTranscriptionJobs" - "transcribe:ListVocabularies" - "transcribe:StartStreamTranscription" - "transcribe:StartTranscriptionJob" Resource: "*" - Effect: "Allow" Action: - "kinesisvideo:Describe*" - "kinesisvideo:Get*" - "kinesisvideo:List*" Resource: "*" Metadata: cfn_nag: rules_to_suppress: - id: F3 reason: transcribe do not support resource-level permissions and kinesisvideo streams are dynamically created and therefore cannot be specificed directly - id: W11 reason: transcribe do not support resource-level permissions and kinesisvideo streams are dynamically created and therefore cannot be specificed directly AIPoweredSpeechAnalyticsKvsTriggerRole: Type: "AWS::IAM::Role" Condition: ShouldCreateLambdaResource Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "lambda.amazonaws.com" Action: - "sts:AssumeRole" Path: "/" Policies: - PolicyName: kvs-streaming-trigger-policy PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*" - Effect: "Allow" Action: - "lambda:InvokeFunction" - "lambda:InvokeAsync" Resource: - !GetAtt kvsTranscriber.Arn AIPoweredSpeechAnalyticsProcessContactRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "lambda.amazonaws.com" Action: - "sts:AssumeRole" Path: "/" Policies: - PolicyName: process-contact-policy PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*" - Effect: "Allow" Action: - "dynamodb:Query" Resource: - !Sub ${transcriptSegmentsDDBTable.Arn} - !Sub ${transcriptSegmentsToCustomerDDBTable.Arn} - Effect: "Allow" Action: - "dynamodb:Update" - "dynamodb:UpdateItem" Resource: - !Sub ${contactDetailsDDBTable.Arn} AIPoweredSpeechAnalyticsContactFlowCreatorRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "lambda.amazonaws.com" Action: - "sts:AssumeRole" Path: "/" Policies: - PolicyName: contact-flow-creator-policy PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*" - Effect: "Allow" Action: - "s3:PutObject" Resource: - !Sub ${createS3Bucket.Arn}/* AIPoweredSpeechAnalyticsApiGwLambdaRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "lambda.amazonaws.com" Action: - "sts:AssumeRole" Path: "/" Policies: - PolicyName: apigw-lambda-policy PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*" - Effect: "Allow" Action: - "dynamodb:DeleteItem" - "dynamodb:GetRecords" - "dynamodb:GetShardIterator" - "dynamodb:PutItem" - "dynamodb:Scan" Resource: - !Sub ${webSocketConnectionsDDBTable.Arn} - Effect: "Allow" Action: - "dynamodb:DescribeStream" - "dynamodb:GetRecords" - "dynamodb:GetShardIterator" - "dynamodb:ListStreams" Resource: - !Sub ${transcriptSegmentsDDBTable.Arn}/* - Effect: "Allow" Action: - "execute-api:Invoke" - "execute-api:InvalidateCache" - "execute-api:ManageConnections" Resource: !Sub "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${AIPoweredSpeechAnalyticsWS}*" AIPoweredSpeechAnalyticsConnectUserStsRole: Type: "AWS::IAM::Role" Properties: MaxSessionDuration: 3600 AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: AWS: !Join - '' - - 'arn:' - !Ref 'AWS::Partition' - ':iam::' - !Ref 'AWS::AccountId' - ':' - 'root' Action: - "sts:AssumeRole" Path: "/" AIPoweredSpeechAnalyticsConnectUserStsPolicy: Type: 'AWS::IAM::Policy' Properties: PolicyName: !Sub ${AWS::StackName}-UserStsPolicy Roles: - !Ref AIPoweredSpeechAnalyticsConnectUserStsRole PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "comprehend:ListEntityRecognizers" - "comprehend:DetectSentiment" - "comprehend:DetectEntities" - "comprehend:ListDocumentClassifiers" - "comprehend:DetectSyntax" - "comprehend:DetectKeyPhrases" - "comprehend:DetectDominantLanguage" Resource: "*" - Effect: "Allow" Action: - "translate:TranslateText" Resource: "*" - Effect: "Allow" Action: - "s3:PutObject" Resource: - !Sub ${createS3Bucket.Arn}/* - Effect: "Allow" Action: - "connect:UpdateContactAttributes" Resource: "*" - Effect: "Allow" Action: - "execute-api:Invoke" - "execute-api:InvalidateCache" - "execute-api:ManageConnections" Resource: !Join - '' - - 'arn:' - !Ref 'AWS::Partition' - ':execute-api:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - !Ref AIPoweredSpeechAnalyticsWS - '*' Metadata: cfn_nag: rules_to_suppress: - id: W12 reason: comprehend, translate, and connect do not support resource-level permissions AIPoweredSpeechAnalyticsSTSTokenLambdaIAMRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "lambda.amazonaws.com" Action: - "sts:AssumeRole" Path: "/" Policies: - PolicyName: connect-aipsas-ststoken PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*" - Effect: "Allow" Action: - "dynamodb:UpdateItem" Resource: - !Sub ${contactDetailsDDBTable.Arn} - Effect: "Allow" Action: - 'sts:AssumeRole' Resource: - !GetAtt AIPoweredSpeechAnalyticsConnectUserStsRole.Arn kvsTranscriber: Type: "AWS::Lambda::Function" Condition: ShouldCreateLambdaResource Properties: Description: > Process audio from Kinesis Video Stream and use Amazon Transcribe to get text for the caller audio. Will be invoked by the kvsConsumerTrigger Lambda, writes results to the transcriptSegmentsDDBTable, and uploads the audio file to S3. Handler: "com.amazonaws.kvstranscribestreaming.KVSTranscribeStreamingLambda::handleRequest" Role: !GetAtt AIPoweredSpeechAnalyticsKvsTranscribeRole.Arn Runtime: java8 MemorySize: 512 # maximum timeout is 15 minutes today Timeout: 900 Environment: Variables: # JAVA_TOOL_OPTIONS: "-Djavax.net.ssl.trustStore=lib/InternalAndExternalTrustStore.jks -Djavax.net.ssl.trustStorePassword=amazon" APP_REGION: !Ref "AWS::Region" TRANSCRIBE_REGION: !Ref "AWS::Region" RECORDINGS_BUCKET_NAME: !Ref S3BucketName RECORDINGS_KEY_PREFIX: !Ref audioFilePrefix INPUT_KEY_PREFIX: !Ref rawAudioUploadPrefix TABLE_CALLER_TRANSCRIPT: !Ref transcriptSegmentsDDBTable TABLE_CALLER_TRANSCRIPT_TO_CUSTOMER: !Ref transcriptSegmentsToCustomerDDBTable RECORDINGS_PUBLIC_READ_ACL: "FALSE" START_SELECTOR_TYPE: "NOW" CONSOLE_LOG_TRANSCRIPT_FLAG: "TRUE" LOGGING_LEVEL: "FINE" SAVE_PARTIAL_TRANSCRIPTS: "TRUE" Code: S3Bucket: !Join ["-", [!FindInMap [FunctionMap, Configuration, S3Bucket], Ref: "AWS::Region"]] S3Key: !Join ["/", [!FindInMap [FunctionMap, Configuration, S3Key], 'kvs_transcribe_streaming_lambda.zip']] Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: Customer can use VPC if desired kvsConsumerTrigger: Type: "AWS::Lambda::Function" Condition: ShouldCreateLambdaResource Properties: Description: > AWS Lambda Function to start (asynchronous) streaming transcription; it is expected to be called by the Amazon Connect Contact Flow. Handler: "index.handler" Role: !GetAtt AIPoweredSpeechAnalyticsKvsTriggerRole.Arn Runtime: "nodejs14.x" MemorySize: 128 Timeout: 30 Environment: Variables: transcriptionFunction: !Ref kvsTranscriber Code: S3Bucket: !Join ["-", [!FindInMap [FunctionMap, Configuration, S3Bucket], Ref: "AWS::Region"]] S3Key: !Join ["/", [!FindInMap [FunctionMap, Configuration, S3Key], 'kvs_trigger.zip']] Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: Customer can use VPC if desired initContactDetails: Type: "AWS::Lambda::Function" Properties: Description: > AWS Lambda Function that will be triggered when the call starts so that we have the initial contact details which can later add to when we have the transcript, and audio file location. Handler: "index.handler" Role: !GetAtt AIPoweredSpeechAnalyticsSTSTokenLambdaIAMRole.Arn Runtime: "nodejs14.x" MemorySize: 128 Timeout: 30 Environment: Variables: table_name: !Ref contactDetailsDDBTable assume_role: !GetAtt AIPoweredSpeechAnalyticsConnectUserStsRole.Arn session_duration: !If [ShouldCreateFargateResources, !Ref sessionDuration, 900] Code: S3Bucket: !Join ["-", [!FindInMap [FunctionMap, Configuration, S3Bucket], Ref: "AWS::Region"]] S3Key: !Join ["/", [!FindInMap [FunctionMap, Configuration, S3Key], 'contact_init.zip']] Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: Customer can use VPC if desired processContactSummary: Type: "AWS::Lambda::Function" Properties: Description: > AWS Lambda Function that will be triggered when the wav call recording file is placed in S3. This function will collect all the transcript segments, and the audio file location and update the contact db. Handler: "index.handler" Role: !GetAtt AIPoweredSpeechAnalyticsProcessContactRole.Arn Runtime: "nodejs14.x" MemorySize: 256 Timeout: 120 Environment: Variables: contact_table_name: !Ref contactDetailsDDBTable transcript_seg_table_name: !Ref transcriptSegmentsDDBTable transcript_seg_to_customer_table_name: !Ref transcriptSegmentsToCustomerDDBTable Code: S3Bucket: !Join ["-", [!FindInMap [FunctionMap, Configuration, S3Bucket], Ref: "AWS::Region"]] S3Key: !Join ["/", [!FindInMap [FunctionMap, Configuration, S3Key], 'process_contact.zip']] Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: Customer can use VPC if desired #DynamoDB Table to maintain connections and contactId information webSocketConnectionsDDBTable: Type: 'AWS::DynamoDB::Table' Properties: AttributeDefinitions: - AttributeName: connectionId AttributeType: S KeySchema: - AttributeName: connectionId KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 5 WriteCapacityUnits: 5 PointInTimeRecoverySpecification: PointInTimeRecoveryEnabled: true SSESpecification: SSEEnabled: true TimeToLiveSpecification: AttributeName: ExpiresAfter Enabled: true Metadata: cfn_nag: rules_to_suppress: - id: W73 reason: "Billing mode needs to be determined by customer." #Beggining of Websocket API AIPoweredSpeechAnalyticsWS: Type: 'AWS::ApiGatewayV2::Api' Properties: Name: AIPoweredSpeechAnalyticsWS ProtocolType: WEBSOCKET RouteSelectionExpression: $request.body.action ConnectRoute: Type: 'AWS::ApiGatewayV2::Route' Properties: ApiId: !Ref AIPoweredSpeechAnalyticsWS RouteKey: $connect AuthorizationType: AWS_IAM OperationName: ConnectRoute Target: !Join - / - - integrations - !Ref ConnectInteg ConnectInteg: Type: 'AWS::ApiGatewayV2::Integration' Properties: ApiId: !Ref AIPoweredSpeechAnalyticsWS Description: Connect Integration IntegrationType: AWS_PROXY IntegrationUri: !Sub arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${wsrealtimetranscribeOnConnect.Arn}/invocations DisconnectRoute: Type: 'AWS::ApiGatewayV2::Route' Properties: ApiId: !Ref AIPoweredSpeechAnalyticsWS RouteKey: $disconnect OperationName: DisconnectRoute Target: !Join - / - - integrations - !Ref DisconnectInteg DisconnectInteg: Type: 'AWS::ApiGatewayV2::Integration' Properties: ApiId: !Ref AIPoweredSpeechAnalyticsWS Description: Disconnect Integration IntegrationType: AWS_PROXY IntegrationUri: !Sub arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${wsrealtimetranscribeOnDisConnect.Arn}/invocations SendRoute: Type: 'AWS::ApiGatewayV2::Route' Properties: ApiId: !Ref AIPoweredSpeechAnalyticsWS RouteKey: sendmessage OperationName: SendRoute Target: !Join - / - - integrations - !Ref SendInteg SendInteg: Type: 'AWS::ApiGatewayV2::Integration' Properties: ApiId: !Ref AIPoweredSpeechAnalyticsWS Description: Send Integration IntegrationType: AWS_PROXY IntegrationUri: !Sub arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${wsrealtimetranscribeOnMessage.Arn}/invocations NewCallRoute: Type: 'AWS::ApiGatewayV2::Route' Properties: ApiId: !Ref AIPoweredSpeechAnalyticsWS RouteKey: newcall OperationName: NewCallRoute Target: !Join - / - - integrations - !Ref NewCallInteg NewCallInteg: Type: 'AWS::ApiGatewayV2::Integration' Properties: ApiId: !Ref AIPoweredSpeechAnalyticsWS Description: New Call Integration IntegrationType: AWS_PROXY IntegrationUri: !Sub arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${wsrealtimetranscribeDefault.Arn}/invocations Deployment: Type: 'AWS::ApiGatewayV2::Deployment' DependsOn: - ConnectRoute - SendRoute - DisconnectRoute - NewCallRoute Properties: ApiId: !Ref AIPoweredSpeechAnalyticsWS Stage: Type: 'AWS::ApiGatewayV2::Stage' Properties: StageName: Prod Description: Prod Stage DeploymentId: !Ref Deployment ApiId: !Ref AIPoweredSpeechAnalyticsWS AccessLogSettings: DestinationArn: !GetAtt ApiAccessLogGroup.Arn Format: >- {"requestId": "$context.requestId", "caller": "$context.identity.caller", "requestTime": "$context.requestTime", "httpMethod": "$context.httpMethod", "resourcePath": "$context.resourcePath", "status": "$context.status", "responseLength": "$context.responseLength", "integrationLatency": "$context.integrationLatency"}" Metadata: cfn_nag: rules_to_suppress: - id: W46 reason: Access Log Setting is applied ApiGwIamRole: Type: AWS::IAM::Role Properties: Path: / AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - apigateway.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - "arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs" ApiGwAccountConfig: Type: "AWS::ApiGateway::Account" Properties: CloudWatchRoleArn: !GetAtt "ApiGwIamRole.Arn" ApiAccessLogGroup: Type: AWS::Logs::LogGroup Properties: RetentionInDays: 3653 Metadata: cfn_nag: rules_to_suppress: - id: W84 reason: By default CloudWatchLogs LogGroups data is encrypted using the CloudWatch server-side encryption keys (AWS Managed Keys). wsrealtimetranscribeOnConnect: Type: 'AWS::Lambda::Function' Properties: Description: AWS Lambda Function that will be triggered when the browser initiates the websocket connection. Handler: index.handler Role: !GetAtt AIPoweredSpeechAnalyticsApiGwLambdaRole.Arn Runtime: nodejs14.x MemorySize: 128 Timeout: 30 Environment: Variables: TABLE_NAME: !Ref webSocketConnectionsDDBTable Code: S3Bucket: !Join ["-", [!FindInMap [FunctionMap, Configuration, S3Bucket], Ref: "AWS::Region"]] S3Key: !Join ["/", [!FindInMap [FunctionMap, Configuration, S3Key], 'ws_realtime_transcribe_OnConnect.zip']] Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: Customer can use VPC if desired wsrealtimetranscribeOnConnectPermission: Type: 'AWS::Lambda::Permission' DependsOn: - AIPoweredSpeechAnalyticsWS - wsrealtimetranscribeOnConnect Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref wsrealtimetranscribeOnConnect Principal: apigateway.amazonaws.com wsrealtimetranscribeOnDisConnect: Type: 'AWS::Lambda::Function' Properties: Description: AWS Lambda Function that will be triggered when the browser disconnect the websocket connection. Handler: index.handler Role: !GetAtt AIPoweredSpeechAnalyticsApiGwLambdaRole.Arn Runtime: nodejs14.x MemorySize: 128 Timeout: 30 Environment: Variables: TABLE_NAME: !Ref webSocketConnectionsDDBTable Code: S3Bucket: !Join ["-", [!FindInMap [FunctionMap, Configuration, S3Bucket], Ref: "AWS::Region"]] S3Key: !Join ["/", [!FindInMap [FunctionMap, Configuration, S3Key], 'ws_realtime_transcribe_OnDisconnect.zip']] Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: Customer can use VPC if desired wsrealtimetranscribeOnDisConnectPermission: Type: 'AWS::Lambda::Permission' DependsOn: - AIPoweredSpeechAnalyticsWS - wsrealtimetranscribeOnDisConnect Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref wsrealtimetranscribeOnDisConnect Principal: apigateway.amazonaws.com wsrealtimetranscribeOnMessage: Type: 'AWS::Lambda::Function' Properties: Description: AWS Lambda Function that will be triggered when a message arrives from websocket. Handler: index.handler Role: !GetAtt AIPoweredSpeechAnalyticsApiGwLambdaRole.Arn Runtime: nodejs14.x MemorySize: 128 Timeout: 30 Environment: Variables: TABLE_NAME: !Ref webSocketConnectionsDDBTable Code: S3Bucket: !Join ["-", [!FindInMap [FunctionMap, Configuration, S3Bucket], Ref: "AWS::Region"]] S3Key: !Join ["/", [!FindInMap [FunctionMap, Configuration, S3Key], 'ws_realtime_transcribe_OnMessage.zip']] Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: Customer can use VPC if desired wsrealtimetranscribeOnMessagePermission: Type: 'AWS::Lambda::Permission' DependsOn: - AIPoweredSpeechAnalyticsWS - wsrealtimetranscribeOnMessage Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref wsrealtimetranscribeOnMessage Principal: apigateway.amazonaws.com wsrealtimetranscribeDefault: Type: 'AWS::Lambda::Function' Properties: Description: AWS Lambda Function that will be triggered when no action is mapped to the websocket. Handler: index.handler Role: !GetAtt AIPoweredSpeechAnalyticsApiGwLambdaRole.Arn Runtime: nodejs14.x MemorySize: 128 Timeout: 30 Environment: Variables: TABLE_NAME: !Ref webSocketConnectionsDDBTable Code: S3Bucket: !Join ["-", [!FindInMap [FunctionMap, Configuration, S3Bucket], Ref: "AWS::Region"]] S3Key: !Join ["/", [!FindInMap [FunctionMap, Configuration, S3Key], 'ws_realtime_transcribe_Default.zip']] Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: Customer can use VPC if desired wsrealtimetranscribeDefaultPermission: Type: 'AWS::Lambda::Permission' DependsOn: - AIPoweredSpeechAnalyticsWS - wsrealtimetranscribeDefault Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref wsrealtimetranscribeDefault Principal: apigateway.amazonaws.com wsrealtimetranscribeOnDDBInsert: Type: 'AWS::Lambda::Function' DependsOn: - transcriptSegmentsDDBTable Properties: Description: AWS Lambda Function that will be triggered when transcribe segments are written into the dynamodb. Handler: index.handler Role: !GetAtt AIPoweredSpeechAnalyticsApiGwLambdaRole.Arn Runtime: nodejs14.x MemorySize: 128 Timeout: 30 Environment: Variables: TABLE_NAME: !Ref webSocketConnectionsDDBTable DOMAIN_NAME: !Join - '' - - !Ref AIPoweredSpeechAnalyticsWS - .execute-api. - !Ref 'AWS::Region' - .amazonaws.com STAGE_NAME: !Ref Stage Code: S3Bucket: !Join ["-", [!FindInMap [FunctionMap, Configuration, S3Bucket], Ref: "AWS::Region"]] S3Key: !Join ["/", [!FindInMap [FunctionMap, Configuration, S3Key], 'ws_realtime_transcribe_OnDDBInsert.zip']] Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: Customer can use VPC if desired transcribeSegmentsDDBTrigger: Type: AWS::Lambda::EventSourceMapping DependsOn: - transcriptSegmentsDDBTable - AIPoweredSpeechAnalyticsApiGwLambdaRole - wsrealtimetranscribeOnDDBInsert Properties: BatchSize: 1 Enabled: True EventSourceArn: Fn::GetAtt: [ transcriptSegmentsDDBTable, StreamArn ] FunctionName: !GetAtt wsrealtimetranscribeOnDDBInsert.Arn StartingPosition: LATEST AIPoweredSpeechAnalyticsLambdaCRHIAMRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "lambda.amazonaws.com" Action: - "sts:AssumeRole" Path: "/" Policies: - PolicyName: connect-kvs-s3-for-aipsas PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*" - Effect: "Allow" Action: - "s3:PutObject" - "s3:GetObject" - "s3:PutObjectAcl" Resource: - !Sub ${createWebSiteS3Bucket.Arn}/* - Effect: "Allow" Action: - "s3:GetObject" Resource: - !Join - '' - - 'arn:' - !Ref 'AWS::Partition' - ':s3:::' - !Join ["-", [!FindInMap [FunctionMap, Configuration, S3Bucket], Ref: "AWS::Region"]] - '/*' CustomResourceHelper: Type: AWS::Lambda::Function Properties: Code: S3Bucket: !Join ["-", [!FindInMap [FunctionMap, Configuration, S3Bucket], Ref: "AWS::Region"]] S3Key: !Join ["/", [!FindInMap [FunctionMap, Configuration, S3Key], 'custom-resource-helper.zip']] Description: Solution Accelerator for Web Analytics - Function to deploy web pages. Handler: index.handler MemorySize: 256 Role: !GetAtt AIPoweredSpeechAnalyticsLambdaCRHIAMRole.Arn Runtime: nodejs14.x Timeout: 300 Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: Customer can use VPC if desired SolutionAnonymousMetric: Type: "Custom::LoadLambda" Properties: ServiceToken: Fn::GetAtt: - "CustomResourceHelper" - "Arn" Region: - Ref: "AWS::Region" solutionId: !FindInMap ["FunctionMap", "Configuration", "SolutionID"] UUID: !GetAtt SolutionUuid.UUID version: "1" anonymousData: !FindInMap ["Send", "AnonymousUsage", "Data"] customAction: "sendMetric" SolutionUuid: Type: "Custom::LoadLambda" Properties: ServiceToken: Fn::GetAtt: - "CustomResourceHelper" - "Arn" Region: - Ref: "AWS::Region" customAction: "createUuid" ConfigureWebsite: Type: Custom::LoadLambda DependsOn: - createS3Bucket - AIPoweredSpeechAnalyticsWS Properties: ServiceToken: !GetAtt CustomResourceHelper.Arn Region: !Ref AWS::Region customAction: configureWebsite sourceS3Bucket: !Join ["-", [!FindInMap [FunctionMap, Configuration, S3Bucket], Ref: "AWS::Region"]] sourceS3key: !Join ["/", [!FindInMap [FunctionMap, Configuration, S3Key], 'web_site']] destS3Bucket: !Ref createWebSiteS3Bucket destS3KeyPrefix: CCP transcriptS3KeyBucket: !Ref S3BucketName instanceId: !Ref instanceIdParam instanceName: !Ref instanceNameParam UUID: !GetAtt SolutionUuid.UUID data: !FindInMap [Send, AnonymousUsage, Data] solutionId: !FindInMap [FunctionMap, Configuration, SolutionID] version: !FindInMap [FunctionMap, Configuration, Version] webSocketHost: !Join - '' - - 'wss://' - !Ref AIPoweredSpeechAnalyticsWS - .execute-api. - !Ref 'AWS::Region' - .amazonaws.com/ - !Ref Stage AIPSAnalyticsCloudFrontDistributionAccessIdentity: Type: AWS::CloudFront::CloudFrontOriginAccessIdentity Properties: CloudFrontOriginAccessIdentityConfig: Comment: 'CloudFront OAI for AI Powered Speech Analytics' AIPSAnalyticsCloudFrontDistribution: Type: AWS::CloudFront::Distribution DependsOn: - CustomResourceHelper - createWebSiteS3Bucket - createS3Bucket Properties: DistributionConfig: Origins: - DomainName: !Join - '' - - !Ref S3BucketNameForWebSite - .s3.amazonaws.com Id: !Ref S3BucketNameForWebSite OriginPath: '/CCP' S3OriginConfig: OriginAccessIdentity: !Join - '' - - 'origin-access-identity/cloudfront/' - !Ref AIPSAnalyticsCloudFrontDistributionAccessIdentity Enabled: 'true' Logging: Bucket: !GetAtt createS3Bucket.DomainName Prefix: 'logs/' IncludeCookies: 'true' Comment: CloudFront for AI Powered Speech Analytics DefaultRootObject: agentAssist.html DefaultCacheBehavior: AllowedMethods: - DELETE - GET - HEAD - OPTIONS - PATCH - POST - PUT TargetOriginId: !Ref S3BucketNameForWebSite ForwardedValues: QueryString: true Cookies: Forward: all ViewerProtocolPolicy: redirect-to-https PriceClass: !Ref cloudFrontPriceClass Restrictions: GeoRestriction: RestrictionType: whitelist Locations: - US - JP - AU - FR Metadata: cfn_nag: rules_to_suppress: - id: W70 reason: This is using Cloudfront default TLS, can be changed by customer if needed. # Fargate Resources transcriberECRRepository: Type: AWS::ECR::Repository Condition: ShouldCreateFargateResources Properties: ImageScanningConfiguration: ScanOnPush: "true" RepositoryPolicyText: Version: "2012-10-17" Statement: - Sid: "Allow Amazon ECR read access to the users of this AWS account" Effect: Allow Principal: AWS: - !Sub ${AWS::AccountId} Action: - "ecr:GetDownloadUrlForLayer" - "ecr:BatchGetImage" - "ecr:BatchCheckLayerAvailability" transcriberCodeBuildServiceRole: Type: AWS::IAM::Role Condition: ShouldCreateFargateResources Properties: Path: / AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: codebuild.amazonaws.com Policies: - PolicyName: ecs-service PolicyDocument: Version: 2012-10-17 Statement: - Resource: - !Join - '' - - 'arn:' - !Ref 'AWS::Partition' - ':s3:::' - !Join ["/", [!Join ["-", [!FindInMap [FunctionMap, Configuration, S3Bucket], Ref: "AWS::Region"]], !FindInMap [FunctionMap, Configuration, S3Key]]] - '/*' Effect: Allow Action: - s3:GetObject - s3:GetObjectVersion - s3:GetBucketAcl - s3:GetBucketLocation - s3:PutObject - s3:ListObjects - s3:ListBucket - Resource: - !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/*' - !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/*:*' Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents - Resource: Fn::Join: - '' - - !Sub 'arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/' - !Ref transcriberECRRepository Effect: Allow Action: - ecr:DescribeImages - ecr:ListImages - ecr:PutImage - ecr:BatchCheckLayerAvailability - ecr:BatchGetImage - ecr:CompleteLayerUpload - ecr:GetDownloadUrlForLayer - ecr:GetRepositoryPolicy - ecr:InitiateLayerUpload - ecr:UploadLayerPart - Resource: "*" Effect: Allow Action: - ecr:GetAuthorizationToken Metadata: cfn_nag: rules_to_suppress: - id: W11 reason: ECR do not support resource-level permissions for GetAuthorizationToken and therefore cannot be specificed directly. transcriberCodeBuildProject: Type: AWS::CodeBuild::Project Condition: ShouldCreateFargateResources Properties: Description: "ProjectName" ServiceRole: !Ref transcriberCodeBuildServiceRole EncryptionKey: alias/aws/s3 Artifacts: Type: no_artifacts Source: Location: Fn::Join: - "/" - - !Join ["/", [!Join ["-", [!FindInMap [FunctionMap, Configuration, S3Bucket], Ref: "AWS::Region"]], !FindInMap [FunctionMap, Configuration, S3Key]]] - kvs_transcribe_streaming_fargate.zip Type: "S3" BuildSpec: | version: 0.2 phases: install: runtime-versions: docker: 18 commands: - apt-get -y update - apt-get -y install jq pre_build: commands: - echo "Starting docker daemon..." - echo ${SOURCE_CODE_LOCATION} - echo `ls -altr` - echo `pwd` - nohup /usr/local/bin/dockerd --host=unix:///var/run/docker.sock --host=tcp://127.0.0.1:2375 --storage-driver=overlay2& - timeout 15 sh -c "until docker info; do echo .; sleep 1; done" - echo "Logging into Amazon ECR..." - $(aws ecr get-login --no-include-email --region ${AWS_DEFAULT_REGION}) build: commands: - echo Build started on `date` - docker build -t "${REPOSITORY_URI}:latest" -f kvs_transcribe_streaming_lambda/Dockerfile . - printf '{"RepositoryUri":"%s","ProjectName":"%s","ArtifactBucket":"%s"}' $REPOSITORY_URI $PROJECT_NAME $ARTIFACT_BUCKET > build.json post_build: commands: - echo Build completed on `date` - echo "Pushing Docker image to ECR" - docker push "${REPOSITORY_URI}:latest" artifacts: files: - build.json Environment: ComputeType: "BUILD_GENERAL1_SMALL" Image: "aws/codebuild/standard:4.0" Type: "LINUX_CONTAINER" PrivilegedMode: True EnvironmentVariables: - Name: AWS_DEFAULT_REGION Value: !Ref AWS::Region - Name: AWS_ACCOUNT_ID Value: !Ref 'AWS::AccountId' - Name: REPOSITORY_URI Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${transcriberECRRepository} - Name: SOURCE_CODE_LOCATION Value: !Join ["/", [!Join ["-", [!FindInMap [FunctionMap, Configuration, S3Bucket], Ref: "AWS::Region"]], !FindInMap [FunctionMap, Configuration, S3Key]]] TimeoutInMinutes: 10 LambdaCodeBuildStartBuildExecutionRole: Type: AWS::IAM::Role Condition: ShouldCreateFargateResources DependsOn: - transcriberCodeBuildProject Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: "/" Policies: - PolicyName: root PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - codebuild:StartBuild Resource: !GetAtt transcriberCodeBuildProject.Arn - Effect: Allow Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*" - Effect: Allow Resource: - !Sub 'arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/${transcriberECRRepository}' Action: - ecr:DescribeImages - ecr:ListImages - ecr:BatchGetImage - ecr:BatchDeleteImage CodeBuildRun: Type: Custom::CodeBuildRun Condition: ShouldCreateFargateResources Properties: ServiceToken: !GetAtt LambdaCodeBuildStartBuild.Arn BuildProjectName: !Ref transcriberCodeBuildProject ECRRepository: !Ref transcriberECRRepository LambdaCodeBuildStartBuild: Type: AWS::Lambda::Function Condition: ShouldCreateFargateResources Properties: Role: !GetAtt LambdaCodeBuildStartBuildExecutionRole.Arn Runtime: python3.8 Timeout: 300 MemorySize: 128 Handler: lambda_start_codebuild.lambda_handler Code: S3Bucket: !Join ["-", [!FindInMap [FunctionMap, Configuration, S3Bucket], Ref: "AWS::Region"]] S3Key: !Join ["/", [!FindInMap [FunctionMap, Configuration, S3Key], 'lambda_start_codebuild.zip']] Description: This AWS Lambda Function kicks off a code build job. DependsOn: - transcriberCodeBuildProject - LambdaCodeBuildStartBuildExecutionRole Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: Customer can use VPC if desired transcribingQueue: Type: AWS::SQS::Queue Properties: KmsMasterKeyId: alias/aws/sqs Condition: ShouldCreateFargateResources transcribingFargateTriggerServiceRole: Type: AWS::IAM::Role Condition: ShouldCreateFargateResources Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "lambda.amazonaws.com" Action: - "sts:AssumeRole" Path: "/" Policies: - PolicyName: !Sub ${AWS::StackName}-transcribingFargateTriggerServiceRoleDefaultPolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*" - Effect: Allow Action: - sqs:SendMessage - sqs:GetQueueAttributes - sqs:GetQueueUrl Resource: Fn::GetAtt: - transcribingQueue - Arn transcribingFargateTrigger: Type: AWS::Lambda::Function Condition: ShouldCreateFargateResources Properties: Code: S3Bucket: !Join ["-", [!FindInMap [FunctionMap, Configuration, S3Bucket], Ref: "AWS::Region"]] S3Key: !Join ["/", [!FindInMap [FunctionMap, Configuration, S3Key], 'lambda_transcribing_fargate_trigger.zip']] Description: This Lambda triggered by the Agent whisper contact flow sends an Amazon SQS Message with the stream details for an inbound contact. Handler: lambda_transcribing_fargate_trigger.lambda_handler Role: Fn::GetAtt: - transcribingFargateTriggerServiceRole - Arn Runtime: python3.8 Environment: Variables: QUEUE_URL: Ref: transcribingQueue TracingConfig: Mode: Active DependsOn: - transcribingFargateTriggerServiceRole Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: Customer can use VPC if desired allowConnectToTranscribingFargateTriggerLambda: Type: 'AWS::Lambda::Permission' Condition: ShouldCreateFargateResources Properties: FunctionName: !Ref transcribingFargateTrigger Action: 'lambda:InvokeFunction' Principal: connect.amazonaws.com SourceAccount: !Ref 'AWS::AccountId' transcribingVPC: Type: AWS::EC2::VPC Condition: ShouldCreateFargateResources Properties: CidrBlock: 10.0.0.0/16 EnableDnsHostnames: true EnableDnsSupport: true InstanceTenancy: default VPCFlowLogsLogGroup: Type: AWS::Logs::LogGroup Condition: ShouldCreateFargateResources Properties: RetentionInDays: 3653 Metadata: cfn_nag: rules_to_suppress: - id: W84 reason: By default CloudWatchLogs LogGroups data is encrypted using the CloudWatch server-side encryption keys (AWS Managed Keys). VPCFlowLogsRole: Type: AWS::IAM::Role Condition: ShouldCreateFargateResources Metadata: cfn_nag: rules_to_suppress: - id: W11 reason: "Allow Resource * for CloudWatch Logs API since the resources are customer defined." Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - vpc-flow-logs.amazonaws.com Action: - sts:AssumeRole Path: / Policies: - PolicyName: LogRolePolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:DescribeLogGroups - logs:DescribeLogStreams - logs:PutLogEvents Resource: '*' VPCFlowLog: Type: AWS::EC2::FlowLog Condition: ShouldCreateFargateResources Properties: DeliverLogsPermissionArn: !GetAtt 'VPCFlowLogsRole.Arn' LogGroupName: !Ref 'VPCFlowLogsLogGroup' ResourceId: !Ref 'transcribingVPC' ResourceType: VPC TrafficType: ALL DependsOn: - transcribingVPC - VPCFlowLogsLogGroup transcribingVPCPublicSubnet1Subnet: Type: AWS::EC2::Subnet Condition: ShouldCreateFargateResources Properties: CidrBlock: 10.0.0.0/18 VpcId: Ref: transcribingVPC AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: "" MapPublicIpOnLaunch: true Metadata: cfn_nag: rules_to_suppress: - id: W33 reason: Public IP on launch is needed by the solution DependsOn: - transcribingVPC transcribingVPCPublicSubnet1RouteTable: Type: AWS::EC2::RouteTable Condition: ShouldCreateFargateResources Properties: VpcId: Ref: transcribingVPC DependsOn: - transcribingVPC transcribingVPCPublicSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Condition: ShouldCreateFargateResources Properties: RouteTableId: Ref: transcribingVPCPublicSubnet1RouteTable SubnetId: Ref: transcribingVPCPublicSubnet1Subnet transcribingVPCPublicSubnet1DefaultRoute: Type: AWS::EC2::Route Condition: ShouldCreateFargateResources Properties: RouteTableId: Ref: transcribingVPCPublicSubnet1RouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: Ref: transcribingVPCIGW DependsOn: - transcribingVPCVPCGW transcribingVPCPublicSubnet: Type: AWS::EC2::EIP Condition: ShouldCreateFargateResources Properties: Domain: vpc transcribingVPCPublicSubnet1NATGateway: Type: AWS::EC2::NatGateway Condition: ShouldCreateFargateResources Properties: AllocationId: Fn::GetAtt: - transcribingVPCPublicSubnet - AllocationId SubnetId: Ref: transcribingVPCPublicSubnet1Subnet transcribingVPCPublicSubnet2Subnet: Type: AWS::EC2::Subnet Condition: ShouldCreateFargateResources Properties: CidrBlock: 10.0.64.0/18 VpcId: Ref: transcribingVPC AvailabilityZone: Fn::Select: - 1 - Fn::GetAZs: "" MapPublicIpOnLaunch: true Metadata: cfn_nag: rules_to_suppress: - id: W33 reason: Public IP on launch is needed by the solution transcribingVPCPublicSubnet2RouteTable: Type: AWS::EC2::RouteTable Condition: ShouldCreateFargateResources Properties: VpcId: Ref: transcribingVPC transcribingVPCPublicSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Condition: ShouldCreateFargateResources Properties: RouteTableId: Ref: transcribingVPCPublicSubnet2RouteTable SubnetId: Ref: transcribingVPCPublicSubnet2Subnet transcribingVPCPublicSubnet2DefaultRoute: Type: AWS::EC2::Route Condition: ShouldCreateFargateResources Properties: RouteTableId: Ref: transcribingVPCPublicSubnet2RouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: Ref: transcribingVPCIGW DependsOn: - transcribingVPCVPCGW transcribingVPCPublicSubnet2: Type: AWS::EC2::EIP Condition: ShouldCreateFargateResources Properties: Domain: vpc transcribingVPCPublicSubnet2NATGateway: Type: AWS::EC2::NatGateway Condition: ShouldCreateFargateResources Properties: AllocationId: Fn::GetAtt: - transcribingVPCPublicSubnet2 - AllocationId SubnetId: Ref: transcribingVPCPublicSubnet2Subnet transcribingVPCPrivateSubnet1Subnet: Type: AWS::EC2::Subnet Condition: ShouldCreateFargateResources Properties: CidrBlock: 10.0.128.0/18 VpcId: Ref: transcribingVPC AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: "" MapPublicIpOnLaunch: false transcribingVPCPrivateSubnet1RouteTable: Type: AWS::EC2::RouteTable Condition: ShouldCreateFargateResources Properties: VpcId: Ref: transcribingVPC transcribingVPCPrivateSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Condition: ShouldCreateFargateResources Properties: RouteTableId: Ref: transcribingVPCPrivateSubnet1RouteTable SubnetId: Ref: transcribingVPCPrivateSubnet1Subnet transcribingVPCPrivateSubnet1DefaultRoute: Type: AWS::EC2::Route Condition: ShouldCreateFargateResources Properties: RouteTableId: Ref: transcribingVPCPrivateSubnet1RouteTable DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: Ref: transcribingVPCPublicSubnet1NATGateway transcribingVPCPrivateSubnet2Subnet: Type: AWS::EC2::Subnet Condition: ShouldCreateFargateResources Properties: CidrBlock: 10.0.192.0/18 VpcId: Ref: transcribingVPC AvailabilityZone: Fn::Select: - 1 - Fn::GetAZs: "" MapPublicIpOnLaunch: false transcribingVPCPrivateSubnet2RouteTable: Type: AWS::EC2::RouteTable Condition: ShouldCreateFargateResources Properties: VpcId: Ref: transcribingVPC transcribingVPCPrivateSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Condition: ShouldCreateFargateResources Properties: RouteTableId: Ref: transcribingVPCPrivateSubnet2RouteTable SubnetId: Ref: transcribingVPCPrivateSubnet2Subnet transcribingVPCPrivateSubnet2DefaultRoute: Type: AWS::EC2::Route Condition: ShouldCreateFargateResources Properties: RouteTableId: Ref: transcribingVPCPrivateSubnet2RouteTable DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: Ref: transcribingVPCPublicSubnet2NATGateway transcribingVPCIGW: Type: AWS::EC2::InternetGateway Condition: ShouldCreateFargateResources transcribingVPCVPCGW: Type: AWS::EC2::VPCGatewayAttachment Condition: ShouldCreateFargateResources Properties: VpcId: Ref: transcribingVPC InternetGatewayId: Ref: transcribingVPCIGW transcribingCluster: Type: AWS::ECS::Cluster Condition: ShouldCreateFargateResources Properties: ClusterSettings: - Name: containerInsights Value: enabled transcriberPoolQueueProcessingTaskDefTaskRole: Type: AWS::IAM::Role Condition: ShouldCreateFargateResources Properties: AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: ecs-tasks.amazonaws.com Version: "2012-10-17" transcriberPoolQueueProcessingTaskDefTaskRoleDefaultPolicy: Type: AWS::IAM::Policy Condition: ShouldCreateFargateResources Properties: PolicyDocument: Statement: - Action: - sqs:ReceiveMessage - sqs:ChangeMessageVisibility - sqs:GetQueueUrl - sqs:DeleteMessage - sqs:GetQueueAttributes Effect: Allow Resource: Fn::GetAtt: - transcribingQueue - Arn - Action: - s3:GetObject* - s3:GetBucket* - s3:List* - s3:DeleteObject* - s3:PutObject* - s3:Abort* Effect: Allow Resource: - Fn::GetAtt: - createS3Bucket - Arn - Fn::Join: - "" - - Fn::GetAtt: - createS3Bucket - Arn - /* - Action: - dynamodb:BatchGetItem - dynamodb:GetRecords - dynamodb:GetShardIterator - dynamodb:Query - dynamodb:GetItem - dynamodb:Scan - dynamodb:BatchWriteItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem Effect: Allow Resource: - Fn::GetAtt: - transcriptSegmentsDDBTable - Arn - Fn::GetAtt: - transcriptSegmentsToCustomerDDBTable - Arn - Action: - transcribe:DeleteTranscriptionJob - transcribe:GetTranscriptionJob - transcribe:GetVocabulary - transcribe:ListTranscriptionJobs - transcribe:ListVocabularies - transcribe:StartStreamTranscription - transcribe:StartTranscriptionJob Effect: Allow Resource: "*" - Action: - "kinesisvideo:Describe*" - "kinesisvideo:Get*" - "kinesisvideo:List*" Effect: "Allow" Resource: "*" - Action: - logs:CreateLogStream - logs:PutLogEvents Effect: Allow Resource: Fn::GetAtt: - transcriberPoolQueueProcessingTaskDefxraydaemonLogGroup - Arn Version: "2012-10-17" PolicyName: !Sub ${AWS::StackName}-transcriberPoolQueueProcessingTaskDefTaskRoleDefaultPolicy Roles: - Ref: transcriberPoolQueueProcessingTaskDefTaskRole Metadata: cfn_nag: rules_to_suppress: - id: W12 reason: comprehend, translate, and connect do not support resource-level permissions. transcriberPoolQueueProcessingTaskDef: Type: AWS::ECS::TaskDefinition Condition: ShouldCreateFargateResources Properties: ContainerDefinitions: - Environment: - Name: MAX_THREADS Value: "30" - Name: APP_REGION Value: !Ref AWS::Region - Name: RECORDINGS_BUCKET_NAME Value: Ref: S3BucketName - Name: RECORDINGS_KEY_PREFIX Value: Ref: audioFilePrefix - Name: RECORDINGS_PUBLIC_READ_ACL Value: "FALSE" - Name: START_SELECTOR_TYPE Value: "NOW" - Name: TRANSCRIBE_REGION Value: !Ref AWS::Region - Name: AWS_REGION Value: !Ref AWS::Region - Name: INPUT_KEY_PREFIX Value: !Ref rawAudioUploadPrefix - Name: CONSOLE_LOG_TRANSCRIPT_FLAG Value: "TRUE" - Name: LOGGING_LEVEL Value: FINE - Name: TABLE_CALLER_TRANSCRIPT Value: Ref: transcriptSegmentsDDBTable - Name: TABLE_CALLER_TRANSCRIPT_TO_CUSTOMER Value: Ref: transcriptSegmentsToCustomerDDBTable - Name: SAVE_PARTIAL_TRANSCRIPTS Value: "TRUE" - Name: QUEUE_NAME Value: Fn::GetAtt: - transcribingQueue - QueueName Essential: true Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${transcriberECRRepository}:latest LogConfiguration: LogDriver: awslogs Options: awslogs-group: Ref: transcriberPoolQueueProcessingTaskDefQueueProcessingContainerLogGroup awslogs-stream-prefix: transcriberPool awslogs-region: Ref: AWS::Region Name: QueueProcessingContainer - Cpu: 32 Essential: false Image: amazon/aws-xray-daemon LogConfiguration: LogDriver: awslogs Options: awslogs-group: Ref: transcriberPoolQueueProcessingTaskDefxraydaemonLogGroup awslogs-stream-prefix: xrayLog awslogs-region: Ref: AWS::Region MemoryReservation: 256 Name: xray-daemon PortMappings: - ContainerPort: 2000 Protocol: udp Cpu: "1024" ExecutionRoleArn: Fn::GetAtt: - transcriberPoolQueueProcessingTaskDefExecutionRole - Arn Family: !Sub ${AWS::StackName}transcriberPoolQueueProcessingTaskDef Memory: "4096" NetworkMode: awsvpc RequiresCompatibilities: - FARGATE TaskRoleArn: Fn::GetAtt: - transcriberPoolQueueProcessingTaskDefTaskRole - Arn transcriberPoolQueueProcessingTaskDefQueueProcessingContainerLogGroup: Type: AWS::Logs::LogGroup Condition: ShouldCreateFargateResources UpdateReplacePolicy: Retain DeletionPolicy: Retain Properties: RetentionInDays: 3653 Metadata: cfn_nag: rules_to_suppress: - id: W84 reason: By default CloudWatchLogs LogGroups data is encrypted using the CloudWatch server-side encryption keys (AWS Managed Keys). transcriberPoolQueueProcessingTaskDefExecutionRole: Type: AWS::IAM::Role Condition: ShouldCreateFargateResources Properties: AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: ecs-tasks.amazonaws.com Version: "2012-10-17" transcriberPoolQueueProcessingTaskDefExecutionRoleDefaultPolicy: Type: AWS::IAM::Policy Condition: ShouldCreateFargateResources Properties: PolicyDocument: Statement: - Action: - ecr:BatchCheckLayerAvailability - ecr:GetDownloadUrlForLayer - ecr:BatchGetImage Effect: Allow Resource: Fn::GetAtt: - transcriberECRRepository - Arn - Action: ecr:GetAuthorizationToken Effect: Allow Resource: "*" - Action: - logs:CreateLogStream - logs:PutLogEvents Effect: Allow Resource: Fn::GetAtt: - transcriberPoolQueueProcessingTaskDefQueueProcessingContainerLogGroup - Arn - Action: - logs:CreateLogStream - logs:PutLogEvents Effect: Allow Resource: Fn::GetAtt: - transcriberPoolQueueProcessingTaskDefxraydaemonLogGroup - Arn Version: "2012-10-17" PolicyName: !Sub ${AWS::StackName}-transcriberPoolQueueProcessingTaskDefExecutionRoleDefaultPolicy Roles: - Ref: transcriberPoolQueueProcessingTaskDefExecutionRole Metadata: cfn_nag: rules_to_suppress: - id: W12 reason: comprehend, translate, and connect do not support resource-level permissions. transcriberPoolQueueProcessingTaskDefxraydaemonLogGroup: Type: AWS::Logs::LogGroup Condition: ShouldCreateFargateResources UpdateReplacePolicy: Retain DeletionPolicy: Retain Properties: RetentionInDays: 3653 Metadata: cfn_nag: rules_to_suppress: - id: W84 reason: By default CloudWatchLogs LogGroups data is encrypted using the CloudWatch server-side encryption keys (AWS Managed Keys). transcriberPoolQueueProcessingFargateService: Type: AWS::ECS::Service Condition: ShouldCreateFargateResources Properties: Cluster: Ref: transcribingCluster DeploymentConfiguration: MaximumPercent: 200 MinimumHealthyPercent: 50 DesiredCount: 1 EnableECSManagedTags: false LaunchType: FARGATE NetworkConfiguration: AwsvpcConfiguration: AssignPublicIp: DISABLED SecurityGroups: - Fn::GetAtt: - transcriberPoolQueueProcessingFargateServiceSecurityGroup - GroupId Subnets: - Ref: transcribingVPCPrivateSubnet1Subnet - Ref: transcribingVPCPrivateSubnet2Subnet TaskDefinition: Ref: transcriberPoolQueueProcessingTaskDef transcriberPoolQueueProcessingFargateServiceSecurityGroup: Type: AWS::EC2::SecurityGroup Condition: ShouldCreateFargateResources Properties: GroupDescription: !Sub ${AWS::StackName}/transcriberPool/QueueProcessingFargateService/SecurityGroup SecurityGroupEgress: - CidrIp: 0.0.0.0/0 Description: Allow all outbound traffic by default IpProtocol: "-1" VpcId: Ref: transcribingVPC Metadata: cfn_nag: rules_to_suppress: - id: W40 reason: IpProtocol set to -1 to specify all protocols. - id: W5 reason: Egress open to the world is expected. Customer can use VPC endpoints to privately connect the VPC to supported AWS services. transcriberPoolQueueProcessingFargateServiceTaskCountTarget: Type: AWS::ApplicationAutoScaling::ScalableTarget Condition: ShouldCreateFargateResources Properties: MaxCapacity: 5 MinCapacity: 1 ResourceId: Fn::Join: - "" - - service/ - Ref: transcribingCluster - / - Fn::GetAtt: - transcriberPoolQueueProcessingFargateService - Name RoleARN: Fn::Join: - "" - - "arn:" - Ref: AWS::Partition - ":iam::" - Ref: AWS::AccountId - :role/aws-service-role/ecs.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_ECSService ScalableDimension: ecs:service:DesiredCount ServiceNamespace: ecs transcriberPoolQueueProcessingFargateServiceTaskCountTargetCpuScaling: Type: AWS::ApplicationAutoScaling::ScalingPolicy Condition: ShouldCreateFargateResources Properties: PolicyName: !Sub ${AWS::StackName}transcriberPoolQueueProcessingFargateServiceTaskCountTargetCpuScaling PolicyType: TargetTrackingScaling ScalingTargetId: Ref: transcriberPoolQueueProcessingFargateServiceTaskCountTarget TargetTrackingScalingPolicyConfiguration: PredefinedMetricSpecification: PredefinedMetricType: ECSServiceAverageCPUUtilization TargetValue: 50 transcriberPoolQueueProcessingFargateServiceTaskCountTargetQueueMessagesVisibleScalingLowerPolicy: Type: AWS::ApplicationAutoScaling::ScalingPolicy Condition: ShouldCreateFargateResources Properties: PolicyName: !Sub ${AWS::StackName}transcriberPoolQueueProcessingFargateServiceTaskCountTargetQueueMessagesVisibleScalingLowerPolicy PolicyType: StepScaling ScalingTargetId: Ref: transcriberPoolQueueProcessingFargateServiceTaskCountTarget StepScalingPolicyConfiguration: AdjustmentType: ChangeInCapacity MetricAggregationType: Maximum StepAdjustments: - MetricIntervalUpperBound: 0 ScalingAdjustment: -1 transcriberPoolQueueProcessingFargateServiceTaskCountTargetQueueMessagesVisibleScalingLowerAlarm: Type: AWS::CloudWatch::Alarm Condition: ShouldCreateFargateResources Properties: ComparisonOperator: LessThanOrEqualToThreshold EvaluationPeriods: 1 AlarmActions: - Ref: transcriberPoolQueueProcessingFargateServiceTaskCountTargetQueueMessagesVisibleScalingLowerPolicy AlarmDescription: Lower threshold scaling alarm Dimensions: - Name: QueueName Value: Fn::GetAtt: - transcribingQueue - QueueName MetricName: ApproximateNumberOfMessagesVisible Namespace: AWS/SQS Period: 300 Statistic: Maximum Threshold: 0 transcriberPoolQueueProcessingFargateServiceTaskCountTargetQueueMessagesVisibleScalingUpperPolicy: Type: AWS::ApplicationAutoScaling::ScalingPolicy Condition: ShouldCreateFargateResources Properties: PolicyName: !Sub ${AWS::StackName}transcriberPoolQueueProcessingFargateServiceTaskCountTargetQueueMessagesVisibleScalingUpperPolicy PolicyType: StepScaling ScalingTargetId: Ref: transcriberPoolQueueProcessingFargateServiceTaskCountTarget StepScalingPolicyConfiguration: AdjustmentType: ChangeInCapacity MetricAggregationType: Maximum StepAdjustments: - MetricIntervalLowerBound: 0 MetricIntervalUpperBound: 400 ScalingAdjustment: 1 - MetricIntervalLowerBound: 400 ScalingAdjustment: 5 transcriberPoolQueueProcessingFargateServiceTaskCountTargetQueueMessagesVisibleScalingUpperAlarm: Type: AWS::CloudWatch::Alarm Condition: ShouldCreateFargateResources Properties: ComparisonOperator: GreaterThanOrEqualToThreshold EvaluationPeriods: 1 AlarmActions: - Ref: transcriberPoolQueueProcessingFargateServiceTaskCountTargetQueueMessagesVisibleScalingUpperPolicy AlarmDescription: Upper threshold scaling alarm Dimensions: - Name: QueueName Value: Fn::GetAtt: - transcribingQueue - QueueName MetricName: ApproximateNumberOfMessagesVisible Namespace: AWS/SQS Period: 300 Statistic: Maximum Threshold: 100 contactFlowCreator: Type: "AWS::Lambda::Function" Properties: Description: > AWS Lambda Function that will create the initial sample contact flow and upload it to the S3 bucket Handler: "index.handler" Role: !GetAtt AIPoweredSpeechAnalyticsContactFlowCreatorRole.Arn Runtime: "nodejs14.x" MemorySize: 256 Timeout: 120 Code: S3Bucket: !Join ["-", [!FindInMap [FunctionMap, Configuration, S3Bucket], Ref: "AWS::Region"]] S3Key: !Join ["/", [!FindInMap [FunctionMap, Configuration, S3Key], 'create_contact_flow.zip']] Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: Customer can use VPC if desired invokeContactFlowCreator: Type: Custom::CreateKVSContactFlow Properties: ServiceToken: !GetAtt contactFlowCreator.Arn bucketName: !Ref createS3Bucket contactInitFunction: !GetAtt initContactDetails.Arn kvsTriggerFunction: !If [ ShouldCreateLambdaResource, !GetAtt kvsConsumerTrigger.Arn, !GetAtt transcribingFargateTrigger.Arn ]