handlers/sf-emails-to-s3-exporter/cfn.yaml (463 lines of code) (raw):
Transform: AWS::Serverless-2016-10-31
Parameters:
Stage:
Description: Stage name
Type: String
AllowedValues:
- PROD
- CODE
Default: CODE
StackName:
Type: String
Default: emails-transporter-between-sf-and-s3
Mappings:
StageMap:
PROD:
Schedule: 'cron(00,30 03 * * ? *)'
SalesforceUsername: EmailsToS3APIUser
AppName: SFEmailsToS3
ToLowerCase: prod
CODE:
Schedule: 'cron(00,30 03 * * ? *)'
SalesforceUsername: EmailsToS3APIUser
AppName: AwsConnectorSandbox
ToLowerCase: code
Conditions:
IsProd: !Equals [ !Ref Stage, PROD ]
Resources:
IAMRoleS3ReadWrite:
Type: "AWS::IAM::Role"
Properties:
Path: "/"
RoleName: !Sub emails-to-sf-api-${Stage}-api-gateway-role
AssumeRolePolicyDocument: "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"apigateway.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
MaxSessionDuration: 3600
ManagedPolicyArns:
- !Ref IAMManagedPolicy
- "arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"
Description: "Allows API Gateway to Read/Write/Delete from S3."
IAMManagedPolicy:
Type: "AWS::IAM::ManagedPolicy"
Properties:
ManagedPolicyName: !Sub "s3-emails-from-sf-${Stage}-get-delete"
Path: "/"
PolicyDocument: !Sub
- "{\"Version\": \"2012-10-17\",\"Statement\": [{\"Effect\": \"Allow\",\"Action\": [\"s3:GetObject\",\"s3:PutObject\",\"s3:DeleteObject\"],\"Resource\": \"arn:aws:s3:::emails-from-sf-${lowerCaseStage}/*\"}]}"
- { lowerCaseStage: !FindInMap [ StageMap, !Ref Stage, ToLowerCase ] }
ApiGatewayRestApi:
Type: "AWS::ApiGateway::RestApi"
Properties:
Name: !Sub import-emails-from-s3-to-sf-${Stage}-api-gateway
ApiKeySourceType: "HEADER"
EndpointConfiguration:
Types:
- "EDGE"
ApiGatewayResourceS3Bucket:
Type: "AWS::ApiGateway::Resource"
Properties:
RestApiId: !Ref ApiGatewayRestApi
PathPart: "{bucketName}"
ParentId: !GetAtt ApiGatewayRestApi.RootResourceId
ApiGatewayResourceS3File:
Type: "AWS::ApiGateway::Resource"
Properties:
RestApiId: !Ref ApiGatewayRestApi
PathPart: "{caseNumber}"
ParentId: !Ref ApiGatewayResourceS3Bucket
ApiGatewayApiKey:
Type: "AWS::ApiGateway::ApiKey"
Properties:
Enabled: true
Name: !Sub import-emails-from-s3-to-sf-${Stage}-api-key
ApiGatewayStage:
Type: "AWS::ApiGateway::Stage"
Properties:
StageName: !Sub ${Stage}
DeploymentId: !Ref ApiGatewayDeployment
RestApiId: !Ref ApiGatewayRestApi
CacheClusterEnabled: false
MethodSettings:
- CacheDataEncrypted: false
CacheTtlInSeconds: 300
CachingEnabled: false
DataTraceEnabled: true
HttpMethod: "*"
LoggingLevel: "INFO"
MetricsEnabled: false
ResourcePath: "/*"
ThrottlingBurstLimit: 5000
ThrottlingRateLimit: 10000
TracingEnabled: false
ApiGatewayMethodGET:
Type: "AWS::ApiGateway::Method"
DependsOn:
- IAMRoleS3ReadWrite
Properties:
RestApiId: !Ref ApiGatewayRestApi
ResourceId: !Ref ApiGatewayResourceS3File
HttpMethod: "GET"
AuthorizationType: "NONE"
ApiKeyRequired: true
RequestParameters:
"method.request.path.bucketName": true
"method.request.path.caseNumber": true
MethodResponses:
- ResponseParameters:
"method.response.header.Content-Length": false
"method.response.header.Content-Type": false
"method.response.header.Timestamp": false
StatusCode: "200"
- ResponseParameters:
"method.response.header.Content-Length": false
"method.response.header.Content-Type": false
"method.response.header.Timestamp": false
StatusCode: "400"
- ResponseParameters:
"method.response.header.Content-Length": false
"method.response.header.Content-Type": false
"method.response.header.Timestamp": false
StatusCode: "500"
Integration:
CacheNamespace: !Ref ApiGatewayResourceS3File
Credentials: !Sub "arn:aws:iam::${AWS::AccountId}:role/emails-to-sf-api-${Stage}-api-gateway-role"
IntegrationHttpMethod: "GET"
IntegrationResponses:
- ResponseParameters:
"method.response.header.Content-Length": "integration.response.header.Content-Length"
"method.response.header.Content-Type": "integration.response.header.Content-Type"
"method.response.header.Timestamp": "integration.response.header.Date"
ResponseTemplates: { }
SelectionPattern: "2\\d{2}"
StatusCode: "200"
- SelectionPattern: "4\\d{2}"
StatusCode: "400"
- SelectionPattern: "5\\d{2}"
StatusCode: "500"
PassthroughBehavior: "WHEN_NO_MATCH"
RequestParameters:
"integration.request.path.folder": "method.request.path.bucketName"
"integration.request.path.item": "method.request.path.caseNumber"
TimeoutInMillis: 29000
Type: "AWS"
Uri: !Sub "arn:aws:apigateway:${AWS::Region}:s3:path/{folder}/{item}"
ApiGatewayMethodPOST:
Type: "AWS::ApiGateway::Method"
DependsOn:
- IAMRoleS3ReadWrite
Properties:
RestApiId: !Ref ApiGatewayRestApi
ResourceId: !Ref ApiGatewayResourceS3Bucket
HttpMethod: "POST"
AuthorizationType: "NONE"
ApiKeyRequired: true
RequestParameters:
"method.request.header.x-amz-checksum-sha1": false
"method.request.header.x-amz-sdk-checksum-algorithm": false
"method.request.path.bucketName": true
MethodResponses:
- ResponseParameters:
"method.response.header.Content-Length": false
"method.response.header.Content-Type": false
"method.response.header.Timestamp": false
StatusCode: "200"
- ResponseParameters:
"method.response.header.Content-Length": false
"method.response.header.Content-Type": false
"method.response.header.Timestamp": false
StatusCode: "400"
- ResponseParameters:
"method.response.header.Content-Length": false
"method.response.header.Content-Type": false
"method.response.header.Timestamp": false
StatusCode: "500"
Integration:
CacheNamespace: !Ref ApiGatewayResourceS3File
Credentials: !Sub "arn:aws:iam::${AWS::AccountId}:role/emails-to-sf-api-${Stage}-api-gateway-role"
IntegrationHttpMethod: "POST"
IntegrationResponses:
- ResponseParameters:
"method.response.header.Content-Length": "integration.response.header.Content-Length"
"method.response.header.Content-Type": "integration.response.header.Content-Type"
"method.response.header.Timestamp": "integration.response.header.Date"
ResponseTemplates: { }
SelectionPattern: "2\\d{2}"
StatusCode: "200"
- SelectionPattern: "4\\d{2}"
StatusCode: "400"
- SelectionPattern: "5\\d{2}"
StatusCode: "500"
PassthroughBehavior: "WHEN_NO_MATCH"
RequestParameters:
"integration.request.header.x-amz-checksum-sha1": "method.request.header.x-amz-checksum-sha1"
"integration.request.header.x-amz-sdk-checksum-algorithm": "method.request.header.x-amz-sdk-checksum-algorithm"
TimeoutInMillis: 29000
Type: "AWS"
Uri:
!Sub
- "arn:aws:apigateway:${AWS::Region}:emails-from-sf-${lowerCaseStage}.s3:path/?delete"
- lowerCaseStage: !FindInMap [ StageMap, !Ref Stage, ToLowerCase ]
ApiGatewayDeployment:
Type: "AWS::ApiGateway::Deployment"
DependsOn:
- ApiGatewayMethodGET
- ApiGatewayMethodPOST
Properties:
RestApiId: !Ref ApiGatewayRestApi
ApiGatewayUsagePlan:
Type: "AWS::ApiGateway::UsagePlan"
DependsOn:
- ApiGatewayStage
Properties:
UsagePlanName: !Sub import-emails-from-s3-to-sf-${Stage}-usage-plan
ApiStages:
- ApiId: !Ref ApiGatewayRestApi
Stage: !Sub ${Stage}
ApiGatewayUsagePlanKey:
Type: "AWS::ApiGateway::UsagePlanKey"
DependsOn:
- ApiGatewayUsagePlan
- ApiGatewayStage
Properties:
KeyId: !GetAtt ApiGatewayApiKey.APIKeyId
KeyType: "API_KEY"
UsagePlanId: !Ref ApiGatewayUsagePlan
LambdaFunction:
Type: AWS::Serverless::Function
Properties:
Description: Retrieves emails from Salesforce and saves as Json to S3
FunctionName: !Sub export-emails-from-sf-to-s3-${Stage}
Handler: com.gu.sf_emails_to_s3_exporter.Handler::handleRequest
CodeUri:
Bucket: support-service-lambdas-dist
Key: !Sub membership/${Stage}/sf-emails-to-s3-exporter/sf-emails-to-s3-exporter.jar
MemorySize: 512
Runtime: java21
Timeout: 900
Environment:
Variables:
Stage: !Ref Stage
bucketName: !Sub emails-from-sf-${Stage}
sfApiVersion: v50.0
Policies:
- Statement:
- Effect: Allow
Action:
- secretsmanager:DescribeSecret
- secretsmanager:GetSecretValue
Resource:
- "arn:aws:secretsmanager:eu-west-1:865473395570:secret:CODE/Salesforce/ConnectedApp/AwsConnectorSandbox-jaCgRl"
- "arn:aws:secretsmanager:eu-west-1:865473395570:secret:PROD/Salesforce/ConnectedApp/SFEmailsToS3-6QJGTX"
- "arn:aws:secretsmanager:eu-west-1:865473395570:secret:CODE/Salesforce/User/EmailsToS3APIUser-EbXFEb"
- "arn:aws:secretsmanager:eu-west-1:865473395570:secret:PROD/Salesforce/User/EmailsToS3APIUser-kGtUDC"
- Statement:
- Effect: Allow
Action: cloudwatch:PutMetricData
Resource: "*"
- Statement:
- Effect: Allow
Action: s3:GetObject
Resource:
- !Sub
- "arn:aws:s3:::emails-from-sf-${lowerCaseStage}/*"
- { lowerCaseStage: !FindInMap [ StageMap, !Ref Stage, ToLowerCase ] }
- Statement:
- Effect: Allow
Action: s3:PutObject
Resource:
- !Sub
- "arn:aws:s3:::emails-from-sf-${lowerCaseStage}/*"
- { lowerCaseStage: !FindInMap [ StageMap, !Ref Stage, ToLowerCase ] }
- Statement:
- Effect: Allow
Action: s3:ListBucket
Resource:
- !Sub
- "arn:aws:s3:::emails-from-sf-${lowerCaseStage}"
- { lowerCaseStage: !FindInMap [ StageMap, !Ref Stage, ToLowerCase ] }
- !Sub
- "arn:aws:s3:::emails-from-sf-${lowerCaseStage}/*"
- { lowerCaseStage: !FindInMap [ StageMap, !Ref Stage, ToLowerCase ] }
- Statement:
- Effect: Allow
Action: s3:ListObjectV2
Resource:
- !Sub
- "arn:aws:s3:::emails-from-sf-${lowerCaseStage}"
- { lowerCaseStage: !FindInMap [ StageMap, !Ref Stage, ToLowerCase ] }
- !Sub
- "arn:aws:s3:::emails-from-sf-${lowerCaseStage}/*"
- { lowerCaseStage: !FindInMap [ StageMap, !Ref Stage, ToLowerCase ] }
- Statement:
- Sid: readDeployedArtefact
Effect: Allow
Action: s3:GetObject
Resource:
- arn:aws:s3::*:membership-dist/*
Events:
ScheduledRun:
Type: Schedule
Properties:
Schedule: !FindInMap [ StageMap, !Ref Stage, Schedule ]
Description: Runs SF Emails to S3 Exporter
Enabled: True
failedS3WriteFileAlarm:
Type: AWS::CloudWatch::Alarm
Condition: IsProd
DependsOn:
- LambdaFunction
Properties:
AlarmActions:
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:alarms-handler-topic-PROD
AlarmName: emails-from-sf failed when writing file to s3
AlarmDescription: >
!Sub Something went wrong when writing a file to S3 bucket emails-from-sf-${Stage}
ComparisonOperator: GreaterThanOrEqualToThreshold
Dimensions:
- Name: "Stage"
Value: !Ref Stage
DatapointsToAlarm: 1
EvaluationPeriods: 1
MetricName: failed_s3_write_file
Namespace: s3-emails-from-sf
Period: 3600
Statistic: Sum
Threshold: 1
TreatMissingData: notBreaching
failedS3CheckFileExistsAlarm:
Type: AWS::CloudWatch::Alarm
Condition: IsProd
DependsOn:
- LambdaFunction
Properties:
AlarmActions:
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:alarms-handler-topic-PROD
AlarmName: emails-from-sf failed when checking if a file exists in s3
AlarmDescription: >
!Sub Something went wrong when checking if a file exists in S3 bucket emails-from-sf-${Stage}
ComparisonOperator: GreaterThanOrEqualToThreshold
Dimensions:
- Name: "Stage"
Value: !Ref Stage
DatapointsToAlarm: 1
EvaluationPeriods: 1
MetricName: failed_s3_check_file_exists
Namespace: s3-emails-from-sf
Period: 3600
Statistic: Sum
Threshold: 1
TreatMissingData: notBreaching
failedS3GetFileAlarm:
Type: AWS::CloudWatch::Alarm
Condition: IsProd
DependsOn:
- LambdaFunction
Properties:
AlarmActions:
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:alarms-handler-topic-PROD
AlarmName: emails-from-sf failed when getting file from s3
AlarmDescription: >
!Sub Something went wrong when getting file from S3 bucket emails-from-sf-${Stage}
ComparisonOperator: GreaterThanOrEqualToThreshold
Dimensions:
- Name: "Stage"
Value: !Ref Stage
DatapointsToAlarm: 1
EvaluationPeriods: 1
MetricName: failed_s3_get_file
Namespace: s3-emails-from-sf
Period: 3600
Statistic: Sum
Threshold: 1
TreatMissingData: notBreaching
failedWritebackToSFRequestAlarm:
Type: AWS::CloudWatch::Alarm
Condition: IsProd
DependsOn:
- LambdaFunction
Properties:
AlarmActions:
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:alarms-handler-topic-PROD
AlarmName: emails-from-sf failed request to Salesforce
AlarmDescription: >
!Sub Something went wrong writing successes back to Salesforce (Bad Request)
ComparisonOperator: GreaterThanOrEqualToThreshold
Dimensions:
- Name: "Stage"
Value: !Ref Stage
DatapointsToAlarm: 1
EvaluationPeriods: 1
MetricName: failed_writeback_request_to_sf
Namespace: s3-emails-from-sf
Period: 3600
Statistic: Sum
Threshold: 1
TreatMissingData: notBreaching
failedWritebackToSFRecordAlarm:
Type: AWS::CloudWatch::Alarm
Condition: IsProd
DependsOn:
- LambdaFunction
Properties:
AlarmActions:
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:alarms-handler-topic-PROD
AlarmName: emails-from-sf failed writeback to Salesforce record
AlarmDescription: >
Something went wrong writing success back to Salesforce record
ComparisonOperator: GreaterThanOrEqualToThreshold
Dimensions:
- Name: "Stage"
Value: !Ref Stage
DatapointsToAlarm: 1
EvaluationPeriods: 1
MetricName: failed_writeback_to_sf_record
Namespace: s3-emails-from-sf
Period: 3600
Statistic: Sum
Threshold: 1
TreatMissingData: notBreaching
failedToRetrieveEmailsFromSalesforceAlarm:
Type: AWS::CloudWatch::Alarm
Condition: IsProd
DependsOn:
- LambdaFunction
Properties:
AlarmActions:
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:alarms-handler-topic-PROD
AlarmName: emails-from-sf failed to retrieve emails from Salesforce
AlarmDescription: >
Something went wrong retrieving emails from Salesforce
ComparisonOperator: GreaterThanOrEqualToThreshold
Dimensions:
- Name: "Stage"
Value: !Ref Stage
DatapointsToAlarm: 1
EvaluationPeriods: 1
MetricName: failed_to_get_records_from_sf
Namespace: s3-emails-from-sf
Period: 3600
Statistic: Sum
Threshold: 1
TreatMissingData: notBreaching
failedToAuthenticateWithSalesforceAlarm:
Type: AWS::CloudWatch::Alarm
Condition: IsProd
DependsOn:
- LambdaFunction
Properties:
AlarmActions:
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:alarms-handler-topic-PROD
AlarmName: emails-from-sf failed to authenticate with Salesforce
AlarmDescription: >
Something went wrong authenticating with Salesforce
ComparisonOperator: GreaterThanOrEqualToThreshold
Dimensions:
- Name: "Stage"
Value: !Ref Stage
DatapointsToAlarm: 1
EvaluationPeriods: 1
MetricName: failed_to_authenticate_with_sf
Namespace: s3-emails-from-sf
Period: 3600
Statistic: Sum
Threshold: 1
TreatMissingData: notBreaching