diff --git a/sam-app/lambda_functions/sfExecuteAWSService.py b/sam-app/lambda_functions/sfExecuteAWSService.py index 55ea11f..c230058 100644 --- a/sam-app/lambda_functions/sfExecuteAWSService.py +++ b/sam-app/lambda_functions/sfExecuteAWSService.py @@ -56,12 +56,8 @@ def lambda_handler(event, context): result = connect_associate_approved_origin(**params) elif method == "retrieve_lambda_parameters": result = retrieve_lambda_parameters(**params) - elif method == "setup_audio_recording": - result = setup_audio_recording(**params) elif method == "get_aws_region": result = get_aws_region() - elif method == "generate_audio_recording_url": - result = generate_audio_recording_url(params) else: raise Exception("Invalid method: " + method) return { @@ -229,120 +225,9 @@ def retrieve_lambda_parameters(ConnectInstanceAlias): logger.info("result: %s" % json.dumps(result)) return result -def setup_audio_recording(CloudfrontPublicKey): - s3_client = boto3.client("s3") - bucket_name = os.environ["RECORDING_BUCKET_NAME"] - bucket_cors_rules = [] - try: - bucket_cors_rules = s3_client.get_bucket_cors(Bucket=bucket_name)["CORSRules"] - except botocore.exceptions.ClientError as e: - if e.response['Error']['Code'] != 'NoSuchCORSConfiguration': - raise e - - sf_host = os.environ["SALESFORCE_HOST"] - lightning_url = sf_host[:sf_host.index(".my.salesforce.com")] + ".lightning.force.com" - visualforce_url = sf_host[:sf_host.index(".my.salesforce.com")] - if os.environ["NAMESPACE"]: - visualforce_url = visualforce_url + "--" + os.environ["NAMESPACE"] + ".visualforce.com" - else: - visualforce_url = visualforce_url + "--c.visualforce.com" - - for rule in bucket_cors_rules: - if lightning_url in rule["AllowedOrigins"]: - bucket_cors_rules.remove(rule) - - bucket_cors_rules.append({ - "AllowedHeaders": ["Access-Control-Allow-Origin"], - "AllowedMethods": ["GET"], - "AllowedOrigins": [lightning_url, visualforce_url] - }) - s3_client.put_bucket_cors( - Bucket=bucket_name, - CORSConfiguration={ - "CORSRules": bucket_cors_rules - } - ) - - cloudfront_client = boto3.client("cloudfront") - create_public_key_response = cloudfront_client.create_public_key( - PublicKeyConfig={ - 'CallerReference': str(uuid.uuid4()), - 'Name': 'AmazonConnectSalesforceCTIAdapterContactLens', - 'EncodedKey': CloudfrontPublicKey - } - ) - create_key_group_response = cloudfront_client.create_key_group( - KeyGroupConfig={ - 'Name': 'AmazonConnectSalesforceCTIAdapterContactLens', - 'Items': [ - create_public_key_response["PublicKey"]["Id"] - ] - } - ) - - # edge lambdas must be created in us-east-1 - lambda_client = boto3.client("lambda", region_name='us-east-1') - cloudformation_stack_name = os.environ["CLOUDFORMATION_STACK_NAME"] - MAX_LAMBDA_NAME_LENGTH = 64 - function_name_end = '-sfSig4RequestToS3' - function_name_start = cloudformation_stack_name[:MAX_LAMBDA_NAME_LENGTH - len(function_name_end)] - function_name = function_name_start + function_name_end - create_function_response = lambda_client.create_function( - FunctionName=function_name, - Runtime='nodejs12.x', - Role=os.environ["SIG4_LAMBDA_ROLE_ARN"], - Handler='sfSig4RequestToS3.handler', - Code={ - 'ZipFile': open('./sfSig4RequestToS3.zip', 'rb').read() - } - ) - publish_version_response = lambda_client.publish_version( - FunctionName=function_name - ) - - get_distribution_config_response = cloudfront_client.get_distribution_config( - Id=os.environ["CLOUDFRONT_DISTRIBUTION_ID"] - ) - - distribution_config = get_distribution_config_response["DistributionConfig"] - distribution_config["DefaultCacheBehavior"]["LambdaFunctionAssociations"] = { - 'Quantity': 1, - 'Items': [ - { - 'LambdaFunctionARN': publish_version_response["FunctionArn"], - 'EventType': 'origin-request', - 'IncludeBody': False - }, - ] - } - distribution_config["DefaultCacheBehavior"]["TrustedSigners"] = { - 'Enabled': False, - 'Quantity': 0 - } - distribution_config["DefaultCacheBehavior"]["TrustedKeyGroups"] = { - 'Enabled': True, - 'Quantity': 1, - 'Items': [ create_key_group_response["KeyGroup"]["Id"] ] - } - - return format_datetime_values(cloudfront_client.update_distribution( - DistributionConfig=distribution_config, - Id=os.environ["CLOUDFRONT_DISTRIBUTION_ID"], - IfMatch=get_distribution_config_response["ETag"] - )) - def get_aws_region(): return os.environ["AWS_REGION"] -def generate_audio_recording_url(params): - lambda_client = boto3.client('lambda') - resp = lambda_client.invoke(FunctionName=os.environ["GENERATE_AUDIO_RECORDING_LAMBDA"], InvocationType='RequestResponse', Payload=json.dumps(params)) - logger.info(resp) - lambda_result = resp["Payload"].read().decode("utf-8") - if resp["StatusCode"] < 200 or resp["StatusCode"] >= 300: - raise Exception("ERROR: GENERATE_AUDIO_RECORDING_LAMBDA failed with " + lambda_result) - return lambda_result - def getConnectInstanceIdFromInstanceAlias(ConnectInstanceAlias, connect_client): list_instances_result = connect_client.list_instances(MaxResults=20) instance_list = list_instances_result["InstanceSummaryList"] diff --git a/sam-app/lambda_functions/sfGenerateAudioRecordingStreamingURL.py b/sam-app/lambda_functions/sfGenerateAudioRecordingStreamingURL.py deleted file mode 100644 index eb3e387..0000000 --- a/sam-app/lambda_functions/sfGenerateAudioRecordingStreamingURL.py +++ /dev/null @@ -1,93 +0,0 @@ -""" -You must have an AWS account to use the Amazon Connect CTI Adapter. -Downloading and/or using the Amazon Connect CTI Adapter is subject to the terms of the AWS Customer Agreement, -AWS Service Terms, and AWS Privacy Notice. - -© 2017, Amazon Web Services, Inc. or its affiliates. All rights reserved. - -NOTE: Other license terms may apply to certain, identified software components -contained within or distributed with the Amazon Connect CTI Adapter if such terms are -included in the LibPhoneNumber-js and Salesforce Open CTI. For such identified components, -such other license terms will then apply in lieu of the terms above. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import datetime -import boto3 -import base64 -import json, logging, os - -from sf_util import get_arg -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import padding -from botocore.signers import CloudFrontSigner -import logging -logger = logging.getLogger() -logger.setLevel(logging.getLevelName(os.environ["LOGGING_LEVEL"])) - -PRIVATE_KEY_HEADER = '-----BEGIN RSA PRIVATE KEY-----' -PRIVATE_KEY_FOOTER = '-----END RSA PRIVATE KEY-----\r\n' - -def lambda_handler(event, context): - if 'recordingPath' not in event or not event['recordingPath'] or event['recordingPath'] == 'null': - logger.info("No recordingPath in event; returning.") - return None - # retrieve secrets - logger.info("Retrieving cloudfront credentials") - session = boto3.session.Session() - client = session.client(service_name='secretsmanager') - sf_credentials_secrets_manager_arn = get_arg(os.environ, - 'SF_CREDENTIALS_SECRETS_MANAGER_ARN') - secrets = json.loads(client.get_secret_value(SecretId=sf_credentials_secrets_manager_arn)['SecretString']) - private_key = secrets['CloudFrontPrivateKey'] - access_key_id = secrets['CloudFrontAccessKeyID'] - logger.info("Cloudfront credentials retrieved") - - # construct url to audio recording - recordingPath = event['recordingPath'] # need to remove bucket name, connect dir from path - if("/connect/" in recordingPath): - recordingPath = "connect/" + recordingPath.split("/connect/", 1)[1] - elif("/Analysis/" in recordingPath): - recordingPath = "Analysis/" + recordingPath.split("/Analysis/", 1)[1] - cloudfront_domain = get_arg(os.environ, 'CLOUDFRONT_DISTRIBUTION_DOMAIN_NAME') - url = 'https://' + cloudfront_domain + '/' + recordingPath - logger.info('Unsigned audio recording url: %s' % url) - - # sign url - expire_date = datetime.datetime.utcnow() + datetime.timedelta(minutes=60) - cloudfront_signer = CloudFrontSigner(access_key_id, rsa_signer(private_key)) - signed_url = cloudfront_signer.generate_presigned_url( - url, date_less_than=expire_date) - logger.info('Signed audio recording url: %s' % signed_url) - return signed_url - -def rsa_signer(key): - def rsa_signer_with_key(message): - private_key = serialization.load_pem_private_key( - format_private_key(key), - password=None, - backend=default_backend() - ) - return private_key.sign(message, padding.PKCS1v15(), hashes.SHA1()) - return rsa_signer_with_key - -def format_private_key(private_key): - header_len = len(PRIVATE_KEY_HEADER) - footer_len = len(PRIVATE_KEY_FOOTER) - rsa_key = PRIVATE_KEY_HEADER \ - + private_key[header_len:-(footer_len-2)].replace(' ', '\r\n') \ - + PRIVATE_KEY_FOOTER - return rsa_key.encode('utf8') \ No newline at end of file diff --git a/sam-app/lambda_functions/sfSig4RequestToS3.zip b/sam-app/lambda_functions/sfSig4RequestToS3.zip deleted file mode 100644 index 776c2d4..0000000 Binary files a/sam-app/lambda_functions/sfSig4RequestToS3.zip and /dev/null differ diff --git a/sam-app/lambda_functions/template.yaml b/sam-app/lambda_functions/template.yaml index 2e5167b..d50f89d 100644 --- a/sam-app/lambda_functions/template.yaml +++ b/sam-app/lambda_functions/template.yaml @@ -202,10 +202,6 @@ Conditions: !And - Condition: ContactLensImportEnabledCondition - Condition: ConnectRecordingS3BucketNameHasValue - sfAudioRecordingStreamingCloudFrontDistributionCondition: - !And - - Condition: PostcallRecordingImportEnabledCondition - - Condition: ConnectRecordingS3BucketNameHasValue sfExecuteTranscriptionStateMachineLockS3PolicyCondition: !And - !Or @@ -427,83 +423,6 @@ Resources: Fn::Sub: arn:aws:iam::${AWS::AccountId}:role/aws-service-role/connect.amazonaws.com* Version: '2012-10-17' PolicyName: sfExecuteAWSServiceServiceLinkedRolePolicy - - Fn::If: - - sfAudioRecordingStreamingCloudFrontDistributionCondition - - PolicyDocument: - Statement: - - Action: - - cloudfront:GetDistributionConfig - - cloudfront:UpdateDistribution - Effect: Allow - Resource: - Fn::Sub: arn:aws:cloudfront::${AWS::AccountId}:distribution/${sfAudioRecordingStreamingCloudFrontDistribution} - Version: '2012-10-17' - PolicyName: sfExecuteAWSServiceConnectAudioRecordingPolicyCloudfront - - !Ref AWS::NoValue - - Fn::If: - - sfAudioRecordingStreamingCloudFrontDistributionCondition - - PolicyDocument: - Statement: - - Action: - - iam:PassRole - Effect: Allow - Resource: - Fn::GetAtt: sfSig4RequestToS3Role.Arn - Version: '2012-10-17' - PolicyName: sfExecuteAWSServiceConnectAudioRecordingPolicyIAM - - !Ref AWS::NoValue - - Fn::If: - - sfAudioRecordingStreamingCloudFrontDistributionCondition - - PolicyDocument: - Statement: - - Action: - - cloudfront:CreatePublicKey - - cloudfront:CreateKeyGroup - Effect: Allow - Resource: '*' - Version: '2012-10-17' - PolicyName: sfExecuteAWSServiceConnectAudioRecordingPolicyCloudfrontKey - - !Ref AWS::NoValue - - Fn::If: - - sfAudioRecordingStreamingCloudFrontDistributionCondition - - PolicyDocument: - Statement: - - Action: - - lambda:EnableReplication - - lambda:PublishVersion - - lambda:CreateFunction - - lambda:GetFunction - Effect: Allow - Resource: - Fn::Sub: arn:aws:lambda:us-east-1:${AWS::AccountId}:function:*-sfSig4RequestToS3* - Version: '2012-10-17' - PolicyName: sfExecuteAWSServiceConnectAudioRecordingPolicyLambdaReplication - - !Ref AWS::NoValue - - Fn::If: - - sfAudioRecordingStreamingCloudFrontDistributionCondition - - PolicyDocument: - Statement: - - Action: - - lambda:InvokeFunction - Effect: Allow - Resource: - Fn::GetAtt: sfGenerateAudioRecordingStreamingURL.Arn - Version: '2012-10-17' - PolicyName: sfExecuteAWSServiceConnectAudioRecordingPolicyLambdaInvoke - - !Ref AWS::NoValue - - Fn::If: - - sfAudioRecordingStreamingCloudFrontDistributionCondition - - PolicyDocument: - Statement: - - Action: - - s3:GetBucketCors - - s3:PutBucketCors - Effect: Allow - Resource: - Fn::Sub: "arn:aws:s3:::${ConnectRecordingS3BucketName}" - Version: '2012-10-17' - PolicyName: sfExecuteAWSServiceConnectAudioRecordingPolicyS3 - - !Ref AWS::NoValue sfLambdaBasicExecWithS3Read: Type: AWS::IAM::Role @@ -736,35 +655,6 @@ Resources: Version: '2012-10-17' PolicyName: sfExecuteTranscriptionStateMachineSfInvokeAPIPolicy - sfSig4RequestToS3Role: - Condition: PostcallRecordingImportEnabledCondition - Type: AWS::IAM::Role - Properties: - AssumeRolePolicyDocument: - Statement: - - Action: sts:AssumeRole - Effect: Allow - Principal: - Service: - - lambda.amazonaws.com - - edgelambda.amazonaws.com - Version: '2012-10-17' - Policies: - - Fn::If: - - ConnectRecordingS3BucketNameHasValue - - PolicyDocument: - Statement: - - Action: - - s3:GetObject - Effect: Allow - Resource: - - Fn::Sub: arn:aws:s3:::${ConnectRecordingS3BucketName}/* - Version: '2012-10-17' - PolicyName: sfSig4RequestToS3RolePolicy - - !Ref AWS::NoValue - ManagedPolicyArns: - - !Ref CloudWatchManagedPolicy - sfCTRTriggerRole: Type: AWS::IAM::Role Properties: @@ -1026,33 +916,18 @@ Resources: Timeout: 10 Environment: Variables: - GENERATE_AUDIO_RECORDING_LAMBDA: - Fn::If: - - sfAudioRecordingStreamingCloudFrontDistributionCondition - - !Ref sfGenerateAudioRecordingStreamingURL - - !Ref AWS::NoValue CLOUDFORMATION_STACK_ID: Ref: AWS::StackId CLOUDFORMATION_STACK_NAME: Ref: AWS::StackName LOGGING_LEVEL: Ref: LambdaLoggingLevel - CLOUDFRONT_DISTRIBUTION_ID: - Fn::If: - - sfAudioRecordingStreamingCloudFrontDistributionCondition - - !Ref sfAudioRecordingStreamingCloudFrontDistribution - - !Ref AWS::NoValue RECORDING_BUCKET_NAME: Ref: ConnectRecordingS3BucketName NAMESPACE: Ref: SalesforceAdapterNamespace SALESFORCE_HOST: Ref: SalesforceHost - SIG4_LAMBDA_ROLE_ARN: - Fn::If: - - PostcallRecordingImportEnabledCondition - - Fn::GetAtt: sfSig4RequestToS3Role.Arn - - !Ref AWS::NoValue sfContactTraceRecord: Type: AWS::Serverless::Function @@ -1210,59 +1085,6 @@ Resources: LOGGING_LEVEL: Ref: LambdaLoggingLevel - sfGenerateAudioRecordingStreamingURL: - Condition: sfAudioRecordingStreamingCloudFrontDistributionCondition - Type: AWS::Serverless::Function - Properties: - Handler: sfGenerateAudioRecordingStreamingURL.lambda_handler - VpcConfig: - !If - - PrivateVpcEnabledCondition - - SubnetIds: !Ref VpcSubnetList - SecurityGroupIds: !Ref VpcSecurityGroupList - - Ref: AWS::NoValue - Role: - Fn::GetAtt: sfLambdaBasicExec.Arn - Layers: - - Ref: sfLambdaLayer - Timeout: 10 - Environment: - Variables: - SF_HOST: - Ref: SalesforceHost - SF_CREDENTIALS_SECRETS_MANAGER_ARN: - Ref: SalesforceCredentialsSecretsManagerARN - CLOUDFRONT_DISTRIBUTION_DOMAIN_NAME: - Fn::GetAtt: sfAudioRecordingStreamingCloudFrontDistribution.DomainName - LOGGING_LEVEL: - Ref: LambdaLoggingLevel - - sfAudioRecordingStreamingCloudFrontDistribution: - Condition: sfAudioRecordingStreamingCloudFrontDistributionCondition - Type: AWS::CloudFront::Distribution - Properties: - DistributionConfig: - DefaultCacheBehavior: - AllowedMethods: [GET, HEAD, OPTIONS] - Compress: true - CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 # Managed-CachingOptimized - OriginRequestPolicyId: 88a5eaf4-2fd4-4709-b370-b4c650ea3fcf # Managed-CORS-S3Origin - TargetOriginId: AudioRecordingStreamingCloudFrontOrigin - TrustedSigners: [self] - ViewerProtocolPolicy: redirect-to-https - Enabled: true - IPV6Enabled: true - Origins: - - DomainName: !Join ['.', [!Ref ConnectRecordingS3BucketName, 's3', !Ref 'AWS::Region', 'amazonaws.com']] - Id: AudioRecordingStreamingCloudFrontOrigin - CustomOriginConfig: - OriginProtocolPolicy: https-only - OriginSSLProtocols: - - TLSv1.2 - OriginCustomHeaders: - - HeaderName: Access-Control-Allow-Origin - HeaderValue: !Ref SalesforceHost - sfGetTranscribeJobStatus: Type: AWS::Serverless::Function Properties: