cloudformation.yaml (1,654 lines of code) (raw):

AWSTemplateFormatVersion: 2010-09-09 Transform: AWS::Serverless-2016-10-31 Description: Validates mobile purchases Parameters: MobileAccountId: Type: AWS::SSM::Parameter::Value<String> Default: 'mobileAccountId' MembershipAccountId: Type: AWS::SSM::Parameter::Value<String> Default: 'membershipAccountId' Stack: Description: Stack name Type: String App: Description: Application name Type: String Stage: Description: Stage name Type: String AllowedValues: - CODE - PROD DeployBucket: Description: Bucket where RiffRaff uploads artifacts on deploy Type: String HostedZoneId: Description: HostedZoneId Type: String HostedZoneName: Description: HostedZoneName Type: String ApiCertArn: Description: ACM Certificate for api use Type: String AppCertArn: Description: ACM Certificate for app use Type: String AppDNS: Description: DNS used by app Type: String GooglePubSubSecret: Type: String Description: The secret used by google's pubsub NoEcho: true ApplePubSubSecret: Type: String Description: The secret used by apple's pubsub NoEcho: true FeastApplePubSubSecret: Type: String Description: The secret used by apple's pubsub for the feast app NoEcho: true FeastGooglePubSubSecret: Type: String Description: The secret used by google's pubsub for the feast app NoEcho: true AlarmTopic: Type: String Description: The ARN of the SNS topic to send all the cloudwatch alarms to Mappings: StageVariables: CODE: Schedule: 'rate(365 days)' AlarmActionsEnabled: FALSE SoftOptInConsentSetterStage: CODE PROD: Schedule: 'rate(30 minutes)' AlarmActionsEnabled: TRUE SoftOptInConsentSetterStage: PROD Conditions: IsCode: !Equals [!Ref "Stage", "CODE"] IsProd: !Equals [!Ref "Stage", "PROD"] Resources: MobilePurchasesLambdasRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: sts:AssumeRole Path: / Policies: - PolicyName: logs PolicyDocument: Statement: Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents - cloudwatch:putMetricData Resource: "*" - PolicyName: config PolicyDocument: Statement: Action: - ssm:GetParametersByPath Effect: Allow Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${App}/${Stage}/${Stack}/* - PolicyName: iosuserpurchases-config PolicyDocument: Statement: Action: - ssm:GetParametersByPath Effect: Allow Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${App}-iosuserpurchases/${Stage}/${Stack} - PolicyName: iosvalidatereceipts-config PolicyDocument: Statement: Action: - ssm:GetParametersByPath Effect: Allow Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${App}-iosvalidatereceipts/${Stage}/${Stack} - PolicyName: googleoauth-config PolicyDocument: Statement: Action: - ssm:GetParametersByPath Effect: Allow Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${App}/${Stage}/google-oauth-lambda - PolicyName: google-access-tokens PolicyDocument: Statement: Action: - s3:GetObject - s3:PutObject Effect: Allow Resource: !Sub arn:aws:s3:::gu-mobile-access-tokens/${Stage}/google-play-developer-api/* - PolicyName: dynamo PolicyDocument: Statement: Effect: Allow Action: - "dynamodb:GetItem" - "dynamodb:BatchGetItem" - "dynamodb:BatchWriteItem" - "dynamodb:PutItem" - "dynamodb:UpdateItem" - "dynamodb:Query" Resource: - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-${Stack}-user-purchases - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-subscription-events - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-subscription-events-v2 - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-subscriptions - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-subscriptions-parallel-test - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-user-subscriptions - PolicyName: Sqs PolicyDocument: Statement: Effect: Allow Action: sqs:* Resource: - !GetAtt GoogleSubscriptionsQueue.Arn - !GetAtt AppleSubscriptionsQueue.Arn - !GetAtt FeastAppleSubscriptionsQueue.Arn - !GetAtt FeastGoogleSubscriptionsQueue.Arn - !Sub arn:aws:sqs:${AWS::Region}:${AWS::AccountId}:${App}-${Stage}-apple-historical-subscriptions - !Sub arn:aws:sqs:${AWS::Region}:${AWS::AccountId}:${App}-${Stage}-google-historical-subscriptions - PolicyName: Kms PolicyDocument: Statement: Effect: Allow Action: ["kms:GenerateDataKey", "kms:Decrypt"] Resource: - !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/0215d06c-81c4-4896-a5da-c818770ea8db LogGroupValidateReceipts: Type: "AWS::Logs::LogGroup" Properties: LogGroupName: !Sub /aws/lambda/${App}-iosvalidatereceipts-${Stage} RetentionInDays: 7 LogGroupUserPurchases: Type: "AWS::Logs::LogGroup" Properties: LogGroupName: !Sub /aws/lambda/${App}-iosuserpurchases-${Stage} RetentionInDays: 7 MobilePuchasesApi: Type: AWS::Serverless::Api Properties: StageName: !Ref Stage MethodSettings: [ { "MetricsEnabled": True, "LoggingLevel": "OFF", "ResourcePath": "/*", "HttpMethod": "*", }, ] DefinitionBody: swagger: "2.0" info: version: "1.0.0" title: !Sub ${App}-${Stage} paths: "/google/pubsub": post: x-amazon-apigateway-integration: httpMethod: POST type: aws_proxy uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GooglePubSubLambda.Arn}/invocations consumes: [application/json] produces: [application/json] responses: "200": "description": "Successfully processed a Live App purchase receipt from Google Play Store" "/google/subscription/{subscriptionId}/status": get: x-amazon-apigateway-integration: httpMethod: POST type: aws_proxy uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GooglePlaySubStatusLambda.Arn}/invocations consumes: [application/json] produces: [application/json] responses: "200": "description": "200 response" "/apple/subscription/status": post: x-amazon-apigateway-integration: httpMethod: POST type: aws_proxy uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${AppleSubStatusLambda.Arn}/invocations consumes: [application/json] produces: [application/json] responses: "200": "description": "200 response" "/apple/pubsub": post: x-amazon-apigateway-integration: httpMethod: POST type: aws_proxy uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ApplePubSubLambda.Arn}/invocations consumes: [application/json] produces: [application/json] responses: "200": "description": "Successfully processed a Live App purchase receipt from the Apple App Store" "/apple/linkToSubscriptions": post: x-amazon-apigateway-integration: httpMethod: POST type: aws_proxy uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${AppleLinkUserToSubLambda.Arn}/invocations consumes: [application/json] produces: [application/json] responses: "200": "description": "Successfully handled link request from iOS Live App" "/google/linkToSubscriptions": post: x-amazon-apigateway-integration: httpMethod: POST type: aws_proxy uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GoogleLinkUserToSubLambda.Arn}/invocations consumes: [application/json] produces: [application/json] responses: "200": "description": "Successfully handled link request from Android Live App" "/user/subscriptions/{userId}": get: x-amazon-apigateway-integration: httpMethod: POST type: aws_proxy uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${UserSubscriptionsLambda.Arn}/invocations consumes: [application/json] produces: [application/json] responses: "200": "description": "200 response" "/apple/fetchOfferDetails": post: x-amazon-apigateway-integration: httpMethod: POST type: aws_proxy uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${AppleFetchOfferDetailsLambda.Arn}/invocations consumes: [application/json] produces: [application/json] responses: "200": "description": "200 response (+)" "feast/apple/pubsub": post: x-amazon-apigateway-integration: httpMethod: POST type: aws_proxy uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${FeastApplePubSubLambda.Arn}/invocations consumes: [application/json] produces: [application/json] responses: "200": "description": "Successfully processed a Feast App purchase receipt from the Apple App Store" "/feast/google/pubsub": post: x-amazon-apigateway-integration: httpMethod: POST type: aws_proxy uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${FeastGooglePubSubLambda.Arn}/invocations consumes: [application/json] produces: [application/json] responses: "200": "description": "Successfully processed a Feast App purchase receipt from the Google Play Store" "/healthcheck": get: responses: "200": description: "200 response" x-amazon-apigateway-integration: type: mock requestTemplates: application/json: | { "statusCode" : 200 } httpMethod: GET responses: default: statusCode: "200" ApiDomainName: Type: AWS::ApiGateway::DomainName Properties: CertificateArn: !Ref ApiCertArn DomainName: !Sub ${App}.${HostedZoneName} AppDomainName: Type: AWS::ApiGateway::DomainName Properties: CertificateArn: !Ref AppCertArn DomainName: !Ref AppDNS ApiRoute53: Type: AWS::Route53::RecordSetGroup Properties: HostedZoneId: !Ref HostedZoneId RecordSets: - Name: !Ref ApiDomainName Type: A AliasTarget: HostedZoneId: Z2FDTNDATAQYW2 DNSName: !GetAtt - ApiDomainName - DistributionDomainName ApiMapping: Type: AWS::ApiGateway::BasePathMapping Properties: DomainName: !Ref ApiDomainName RestApiId: !Ref MobilePuchasesApi Stage: !Ref Stage AppMapping: Type: AWS::ApiGateway::BasePathMapping Properties: DomainName: !Ref AppDomainName RestApiId: !Ref MobilePuchasesApi Stage: !Ref Stage GoogleOAuthLambda: Type: AWS::Serverless::Function Properties: Handler: com.gu.mobilepurchases.googleoauth.lambda.GoogleOAuth::handler Runtime: java8 CodeUri: Bucket: !Ref DeployBucket Key: !Sub ${Stack}/${Stage}/${App}-google-oauth/${App}-google-oauth.jar FunctionName: !Sub ${App}-googleoauth-${Stage} Role: !GetAtt MobilePurchasesLambdasRole.Arn Environment: Variables: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App Description: Fetches access tokens for the Google Play Developer API MemorySize: 512 Timeout: 45 Events: Schedule: Type: Schedule Properties: Schedule: rate(15 minutes) Tags: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App GooglePubSubLambda: Type: AWS::Serverless::Function Properties: Handler: google-pubsub.handler Runtime: nodejs20.x CodeUri: Bucket: !Ref DeployBucket Key: !Sub ${Stack}/${Stage}/${App}-google-pubsub/google-pubsub.zip FunctionName: !Sub ${App}-googlepubsub-${Stage} Role: !GetAtt MobilePurchasesLambdasRole.Arn Environment: Variables: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App Secret: !Ref GooglePubSubSecret QueueUrl: !Ref GoogleSubscriptionsQueue Description: Records play store events MemorySize: 128 Timeout: 29 Events: PostApi: Type: Api Properties: Path: "/google/pubsub" Method: POST RestApiId: !Ref MobilePuchasesApi Tags: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App ApplePubSubLambda: Type: AWS::Serverless::Function Properties: Handler: apple-pubsub.handler Runtime: nodejs20.x CodeUri: Bucket: !Ref DeployBucket Key: !Sub ${Stack}/${Stage}/${App}-apple-pubsub/apple-pubsub.zip FunctionName: !Sub ${App}-applepubsub-${Stage} Role: !GetAtt MobilePurchasesLambdasRole.Arn Environment: Variables: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App Secret: !Ref ApplePubSubSecret QueueUrl: !Ref AppleSubscriptionsQueue Description: Records play store events MemorySize: 128 Timeout: 29 Events: PostApi: Type: Api Properties: Path: "/apple/pubsub" Method: POST RestApiId: !Ref MobilePuchasesApi Tags: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App GoogleLinkUserToSubLambda: Type: AWS::Serverless::Function Properties: Handler: google-link-user-subscription.handler Runtime: nodejs20.x CodeUri: Bucket: !Ref DeployBucket Key: !Sub ${Stack}/${Stage}/${App}-google-link-user-subscription/google-link-user-subscription.zip FunctionName: !Sub ${App}-google-link-user-subscription-${Stage} Role: !GetAtt MobilePurchasesLambdasRole.Arn Environment: Variables: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App QueueUrl: !Ref GoogleSubscriptionsQueue Description: Links users to subscriptions MemorySize: 128 Timeout: 29 Events: PostApi: Type: Api Properties: Path: "/google/linkToSubscriptions" Method: POST RestApiId: !Ref MobilePuchasesApi Tags: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App AppleLinkUserToSubLambda: Type: AWS::Serverless::Function Properties: Handler: apple-link-user-subscription.handler Runtime: nodejs20.x CodeUri: Bucket: !Ref DeployBucket Key: !Sub ${Stack}/${Stage}/${App}-apple-link-user-subscription/apple-link-user-subscription.zip FunctionName: !Sub ${App}-apple-link-user-subscription-${Stage} Role: !GetAtt MobilePurchasesLambdasRole.Arn Environment: Variables: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App QueueUrl: !Ref AppleSubscriptionsQueue Description: Links users to subscriptions MemorySize: 128 Timeout: 29 Events: PostApi: Type: Api Properties: Path: "/apple/linkToSubscriptions" Method: POST RestApiId: !Ref MobilePuchasesApi Tags: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App GooglePlaySubStatusLambda: Type: AWS::Serverless::Function Properties: Handler: google-subscription-status.handler Runtime: nodejs20.x CodeUri: Bucket: !Ref DeployBucket Key: !Sub ${Stack}/${Stage}/${App}-google-subscription-status/google-subscription-status.zip FunctionName: !Sub ${App}-google-subscription-status-${Stage} Role: !GetAtt MobilePurchasesLambdasRole.Arn Environment: Variables: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App Description: Checks the status of a Play Store subscription using the Google Play Developer API MemorySize: 128 Timeout: 29 Events: PostApi: Type: Api Properties: Path: "/google/subscription/{subscriptionId}/status" Method: GET RestApiId: !Ref MobilePuchasesApi Tags: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App AppleSubStatusLambda: Type: AWS::Serverless::Function Properties: Handler: apple-subscription-status.handler Runtime: nodejs20.x CodeUri: Bucket: !Ref DeployBucket Key: !Sub ${Stack}/${Stage}/${App}-apple-subscription-status/apple-subscription-status.zip FunctionName: !Sub ${App}-apple-subscription-status-${Stage} Role: !GetAtt MobilePurchasesLambdasRole.Arn Environment: Variables: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App Description: Checks the status of an Apple App store subscription using the apple API MemorySize: 128 Timeout: 29 Events: PostApi: Type: Api Properties: Path: "/apple/subscription/status" Method: POST RestApiId: !Ref MobilePuchasesApi Tags: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App AppleFetchOfferDetailsLambda: Type: AWS::Serverless::Function Properties: Handler: apple-fetch-offer-details.handler Runtime: nodejs20.x CodeUri: Bucket: !Ref DeployBucket Key: !Sub ${Stack}/${Stage}/${App}-apple-fetch-offer-details/apple-fetch-offer-details.zip FunctionName: !Sub ${App}-apple-fetch-offer-details-${Stage} Role: !GetAtt MobilePurchasesLambdasRole.Arn Environment: Variables: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App Description: apple fetch offer details lambda (+) MemorySize: 128 Timeout: 29 Events: PostApi: Type: Api Properties: Path: "/apple/fetchOfferDetails" Method: POST RestApiId: !Ref MobilePuchasesApi Tags: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App FeastGooglePubSubLambda: Type: AWS::Serverless::Function Properties: Handler: feast-google-pubsub.handler Runtime: nodejs20.x CodeUri: Bucket: !Ref DeployBucket Key: !Sub ${Stack}/${Stage}/${App}-feast-google-pubsub/feast-google-pubsub.zip FunctionName: !Sub ${App}-feastgooglepubsub-${Stage} Role: !GetAtt MobilePurchasesLambdasRole.Arn Environment: Variables: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App Secret: !Ref FeastGooglePubSubSecret QueueUrl: !Ref FeastGoogleSubscriptionsQueue Description: Records Google play store events for Feast app MemorySize: 128 Timeout: 29 Events: PostApi: Type: Api Properties: Path: "/feast/google/pubsub" Method: POST RestApiId: !Ref MobilePuchasesApi Tags: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App FeastApplePubSubLambda: Type: AWS::Serverless::Function Properties: Handler: feast-apple-pubsub.handler Runtime: nodejs20.x CodeUri: Bucket: !Ref DeployBucket Key: !Sub ${Stack}/${Stage}/${App}-feast-apple-pubsub/feast-apple-pubsub.zip FunctionName: !Sub ${App}-feastapplepubsub-${Stage} Role: !GetAtt MobilePurchasesLambdasRole.Arn Environment: Variables: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App QueueUrl: !Ref FeastAppleSubscriptionsQueue Secret: !Ref FeastApplePubSubSecret Description: Records app store events for Feast app MemorySize: 128 Timeout: 29 Events: PostApi: Type: Api Properties: Path: "/feast/apple/pubsub" Method: POST RestApiId: !Ref MobilePuchasesApi Tags: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App GoogleSubscriptionsQueue: Type: AWS::SQS::Queue Properties: QueueName: !Sub ${App}-${Stage}-google-subscriptions-to-fetch RedrivePolicy: deadLetterTargetArn: !GetAtt GoogleSubscriptionsQueueDlq.Arn maxReceiveCount: 8 KmsMasterKeyId: alias/aws/sqs Tags: - Key: Stage Value: !Ref Stage - Key: Stack Value: !Ref Stack - Key: App Value: !Ref App GoogleSubscriptionsQueueDlq: Type: AWS::SQS::Queue Properties: QueueName: !Sub ${App}-${Stage}-google-subscriptions-to-fetch-dlq KmsMasterKeyId: alias/aws/sqs Tags: - Key: Stage Value: !Ref Stage - Key: Stack Value: !Ref Stack - Key: App Value: !Ref App AppleSubscriptionsQueue: Type: AWS::SQS::Queue Properties: QueueName: !Sub ${App}-${Stage}-apple-subscriptions-to-fetch RedrivePolicy: deadLetterTargetArn: !GetAtt AppleSubscriptionsQueueDlq.Arn maxReceiveCount: 8 KmsMasterKeyId: alias/aws/sqs Tags: - Key: Stage Value: !Ref Stage - Key: Stack Value: !Ref Stack - Key: App Value: !Ref App AppleSubscriptionsQueueDlq: Type: AWS::SQS::Queue Properties: QueueName: !Sub ${App}-${Stage}-apple-subscriptions-to-fetch-dlq KmsMasterKeyId: alias/aws/sqs Tags: - Key: Stage Value: !Ref Stage - Key: Stack Value: !Ref Stack - Key: App Value: !Ref App FeastAppleSubscriptionsQueue: Type: AWS::SQS::Queue Properties: QueueName: !Sub ${App}-${Stage}-feast-apple-subscriptions-to-fetch RedrivePolicy: deadLetterTargetArn: !GetAtt FeastAppleSubscriptionsQueueDlq.Arn maxReceiveCount: 8 KmsMasterKeyId: alias/aws/sqs Tags: - Key: Stage Value: !Ref Stage - Key: Stack Value: !Ref Stack - Key: App Value: !Ref App FeastAppleSubscriptionsQueueDlq: Type: AWS::SQS::Queue Properties: QueueName: !Sub ${App}-${Stage}-feast-apple-subscriptions-to-fetch-dlq KmsMasterKeyId: alias/aws/sqs Tags: - Key: Stage Value: !Ref Stage - Key: Stack Value: !Ref Stack - Key: App Value: !Ref App FeastGoogleSubscriptionsQueue: Type: AWS::SQS::Queue Properties: QueueName: !Sub ${App}-${Stage}-feast-google-subscriptions-to-fetch RedrivePolicy: deadLetterTargetArn: !GetAtt FeastGoogleSubscriptionsQueueDlq.Arn maxReceiveCount: 8 KmsMasterKeyId: alias/aws/sqs Tags: - Key: Stage Value: !Ref Stage - Key: Stack Value: !Ref Stack - Key: App Value: !Ref App FeastGoogleSubscriptionsQueueDlq: Type: AWS::SQS::Queue Properties: QueueName: !Sub ${App}-${Stage}-feast-google-subscriptions-to-fetch-dlq KmsMasterKeyId: alias/aws/sqs Tags: - Key: Stage Value: !Ref Stage - Key: Stack Value: !Ref Stack - Key: App Value: !Ref App GoogleTokenRefreshFailureAlarm: Type: AWS::CloudWatch::Alarm Properties: ActionsEnabled: !FindInMap [StageVariables, !Ref Stage, AlarmActionsEnabled] AlarmActions: - Ref: AlarmTopic OKActions: - Ref: AlarmTopic AlarmName: !Sub mobile-purchases-${Stage}-google-oauth-token-refresh-failure AlarmDescription: !Sub Trigger the GoogleOAuth lambda manually to refresh the token ComparisonOperator: GreaterThanOrEqualToThreshold Dimensions: - Name: FunctionName Value: !Ref GoogleOAuthLambda EvaluationPeriods: 1 MetricName: Errors Namespace: AWS/Lambda Period: 60 Statistic: Sum Threshold: 1 TreatMissingData: notBreaching Tags: - Key: App Value: mobile-purchases-google-oauth GooglePlaySubsStatus5xxErrors: Type: AWS::CloudWatch::Alarm Properties: ActionsEnabled: !FindInMap [StageVariables, !Ref Stage, AlarmActionsEnabled] AlarmActions: - Ref: AlarmTopic OKActions: - Ref: AlarmTopic AlarmName: !Sub mobile-purchases-${Stage}-play-subscription-status-check-errors AlarmDescription: | More than 20% of attempts to check Play subscription status resulted in a 5XX error. Runbook: https://docs.google.com/document/d/1OwNDf_xSK3hhq1K0DP_4Uq7vIBFgfvlkd4zfiRZDczw/edit#heading=h.cnuchxbdu0tl Metrics: - Id: e1 Label: Percentage of requests which result in a 5XX error Expression: "100*(FILL(m1,0)/FILL(m2,1))" - Id: m1 Label: Number of 5XX responses MetricStat: Metric: MetricName: 5XXError Namespace: AWS/ApiGateway Dimensions: - Name: ApiName Value: !Sub ${App}-${Stage} - Name: Method Value: GET - Name: Resource Value: /google/subscription/{subscriptionId}/status - Name: Stage Value: !Ref Stage Period: 600 Stat: Sum ReturnData: false - Id: m2 Label: Total number of requests MetricStat: Metric: MetricName: Count Namespace: AWS/ApiGateway Dimensions: - Name: ApiName Value: !Sub ${App}-${Stage} - Name: Method Value: GET - Name: Resource Value: /google/subscription/{subscriptionId}/status - Name: Stage Value: !Ref Stage Period: 600 Stat: Sum ReturnData: false ComparisonOperator: GreaterThanOrEqualToThreshold EvaluationPeriods: 1 Threshold: 20 Tags: - Key: App Value: mobile-purchases-google-subscription-status UpdateGoogleSubscriptionsLambda: Type: AWS::Lambda::Function Properties: FunctionName: !Sub ${App}-google-update-subscriptions-${Stage} Code: S3Bucket: !Ref DeployBucket S3Key: !Sub ${Stack}/${Stage}/${App}-google-update-subscriptions/google-update-subscriptions.zip Environment: Variables: App: !Sub ${App} Stack: !Sub ${Stack} Stage: !Sub ${Stage} HistoricalQueueUrl: !Sub https://sqs.${AWS::Region}.amazonaws.com/${AWS::AccountId}/${App}-${Stage}-google-historical-subscriptions Description: Consomes subscription data updates from google playstore from sqs and stores them in dynamo Handler: google-update-subscriptions.handler MemorySize: 512 Role: !GetAtt MobilePurchasesLambdasRole.Arn Timeout: 25 Runtime: nodejs20.x UpdateGoogleSubscriptionsEventSource: Type: AWS::Lambda::EventSourceMapping Properties: FunctionName: !Ref UpdateGoogleSubscriptionsLambda Enabled: true EventSourceArn: !GetAtt GoogleSubscriptionsQueue.Arn BatchSize: 1 AppleSubsStatus5xxErrors: Type: AWS::CloudWatch::Alarm Properties: ActionsEnabled: !FindInMap [StageVariables, !Ref Stage, AlarmActionsEnabled] AlarmActions: - Ref: AlarmTopic AlarmName: !Sub mobile-purchases-${Stage}-apple-subscription-status-check-errors AlarmDescription: | More than 10% of attempts to check Apple subscription status resulted in a 5XX error over a 20min period. Metrics: - Id: e1 Label: Percentage of requests which result in a 5XX error Expression: "100*(FILL(m1,0)/FILL(m2,1))" - Id: m1 Label: Number of 5XX responses MetricStat: Metric: MetricName: 5XXError Namespace: AWS/ApiGateway Dimensions: - Name: ApiName Value: !Sub ${App}-${Stage} - Name: Method Value: POST - Name: Resource Value: /apple/subscription/status - Name: Stage Value: !Ref Stage Period: 1200 Stat: Sum ReturnData: false - Id: m2 Label: Total number of requests MetricStat: Metric: MetricName: Count Namespace: AWS/ApiGateway Dimensions: - Name: ApiName Value: !Sub ${App}-${Stage} - Name: Method Value: POST - Name: Resource Value: /apple/subscription/status - Name: Stage Value: !Ref Stage Period: 1200 Stat: Sum ReturnData: false ComparisonOperator: GreaterThanOrEqualToThreshold EvaluationPeriods: 1 Threshold: 10 Tags: - Key: App Value: mobile-purchases-apple-subscription-status ApplePubsub5xxErrors: Type: AWS::CloudWatch::Alarm Properties: ActionsEnabled: !FindInMap [StageVariables, !Ref Stage, AlarmActionsEnabled] AlarmActions: - Ref: AlarmTopic OKActions: - Ref: AlarmTopic AlarmName: !Sub mobile-purchases-${Stage}-apple-pubsub-check-errors AlarmDescription: Two HTTP requests to the iOS pubsub endpoint resulted in 5XX errors. Dimensions: - Name: ApiName Value: !Sub ${App}-${Stage} - Name: Method Value: POST - Name: Resource Value: /apple/pubsub - Name: Stage Value: !Ref Stage ComparisonOperator: GreaterThanOrEqualToThreshold EvaluationPeriods: 1 MetricName: 5XXError Namespace: AWS/ApiGateway Period: 300 Statistic: Sum Threshold: 2 TreatMissingData: notBreaching Tags: - Key: App Value: mobile-purchases-apple-pubsub FeastApplePubsub5xxErrors: Type: AWS::CloudWatch::Alarm Properties: ActionsEnabled: !FindInMap [StageVariables, !Ref Stage, AlarmActionsEnabled] AlarmActions: - Ref: AlarmTopic OKActions: - Ref: AlarmTopic AlarmName: !Sub mobile-purchases-${Stage}-feast-apple-pubsub-check-errors AlarmDescription: Two HTTP requests to the iOS Feast pubsub endpoint resulted in 5XX errors. Dimensions: - Name: ApiName Value: !Sub ${App}-${Stage} - Name: Method Value: POST - Name: Resource Value: /feast/apple/pubsub - Name: Stage Value: !Ref Stage ComparisonOperator: GreaterThanOrEqualToThreshold EvaluationPeriods: 1 MetricName: 5XXError Namespace: AWS/ApiGateway Period: 300 Statistic: Sum Threshold: 2 TreatMissingData: notBreaching Tags: - Key: App Value: mobile-purchases-feast-apple-pubsub GooglePubsub5xxErrors: Type: AWS::CloudWatch::Alarm Properties: ActionsEnabled: !FindInMap [StageVariables, !Ref Stage, AlarmActionsEnabled] AlarmActions: - Ref: AlarmTopic OKActions: - Ref: AlarmTopic AlarmName: !Sub mobile-purchases-${Stage}-google-pubsub-check-errors AlarmDescription: Two HTTP requests to the Google pubsub endpoint resulted in 5XX errors. Dimensions: - Name: ApiName Value: !Sub ${App}-${Stage} - Name: Method Value: POST - Name: Resource Value: /google/pubsub - Name: Stage Value: !Ref Stage ComparisonOperator: GreaterThanOrEqualToThreshold EvaluationPeriods: 1 MetricName: 5XXError Namespace: AWS/ApiGateway Period: 300 Statistic: Sum Threshold: 2 TreatMissingData: notBreaching Tags: - Key: App Value: mobile-purchases-google-pubsub FeastGooglePubsub5xxErrors: Type: AWS::CloudWatch::Alarm Properties: ActionsEnabled: !FindInMap [StageVariables, !Ref Stage, AlarmActionsEnabled] AlarmActions: - Ref: AlarmTopic OKActions: - Ref: AlarmTopic AlarmName: !Sub mobile-purchases-${Stage}-feast-google-pubsub-check-errors AlarmDescription: Two HTTP requests to the Feast Google pubsub endpoint resulted in 5XX errors. Dimensions: - Name: ApiName Value: !Sub ${App}-${Stage} - Name: Method Value: POST - Name: Resource Value: /feast/google/pubsub - Name: Stage Value: !Ref Stage ComparisonOperator: GreaterThanOrEqualToThreshold EvaluationPeriods: 1 MetricName: 5XXError Namespace: AWS/ApiGateway Period: 300 Statistic: Sum Threshold: 2 TreatMissingData: notBreaching Tags: - Key: App Value: mobile-purchases-feast-google-pubsub UserSubscriptionsLambda: Type: AWS::Serverless::Function Properties: Handler: user-subscriptions.handler Runtime: nodejs20.x CodeUri: Bucket: !Ref DeployBucket Key: !Sub ${Stack}/${Stage}/${App}-user-subscriptions/user-subscriptions.zip FunctionName: !Sub ${App}-user-subscriptions-${Stage} Role: !GetAtt MobilePurchasesLambdasRole.Arn Environment: Variables: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App Description: Retrieves subscription details for a given user MemorySize: 128 Timeout: 29 Events: InternalAccess: Type: Api Properties: Path: "/user/subscriptions/{userId}" Method: GET RestApiId: !Ref MobilePuchasesApi Tags: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App UpdateAppleSubscriptionsLambda: Type: AWS::Lambda::Function Properties: FunctionName: !Sub ${App}-apple-update-subscriptions-${Stage} Code: S3Bucket: !Ref DeployBucket S3Key: !Sub ${Stack}/${Stage}/${App}-apple-update-subscriptions/apple-update-subscriptions.zip Environment: Variables: App: !Sub ${App} Stack: !Sub ${Stack} Stage: !Sub ${Stage} HistoricalQueueUrl: !Sub https://sqs.${AWS::Region}.amazonaws.com/${AWS::AccountId}/${App}-${Stage}-apple-historical-subscriptions Description: Consumes subscription data updates from app store from sqs and stores them in dynamo Handler: apple-update-subscriptions.handler MemorySize: 512 Role: !GetAtt MobilePurchasesLambdasRole.Arn Timeout: 25 Runtime: nodejs20.x AppleSubscriptionsEventSource: Type: AWS::Lambda::EventSourceMapping Properties: FunctionName: !Ref UpdateAppleSubscriptionsLambda Enabled: true EventSourceArn: !GetAtt AppleSubscriptionsQueue.Arn BatchSize: 1 AppleSubscriptionDlqDepthAlarm: Type: AWS::CloudWatch::Alarm Properties: ActionsEnabled: !FindInMap [StageVariables, !Ref Stage, AlarmActionsEnabled] AlarmDescription: "Ensure that the apple subscription dead letter queue is empty" Namespace: "AWS/SQS" MetricName: ApproximateNumberOfMessagesVisible Dimensions: - Name: QueueName Value: !GetAtt "AppleSubscriptionsQueueDlq.QueueName" Period: 60 Statistic: Sum EvaluationPeriods: 1 ComparisonOperator: GreaterThanThreshold Threshold: 0 AlarmActions: - Ref: AlarmTopic OKActions: - Ref: AlarmTopic TreatMissingData: notBreaching Tags: - Key: App Value: mobile-purchases-apple-update-subscriptions GoogleSubscriptionDlqDepthAlarm: Type: AWS::CloudWatch::Alarm Properties: ActionsEnabled: !FindInMap [StageVariables, !Ref Stage, AlarmActionsEnabled] AlarmDescription: "Ensure that the google subscription dead letter queue is empty" Namespace: "AWS/SQS" MetricName: ApproximateNumberOfMessagesVisible Dimensions: - Name: QueueName Value: !GetAtt "GoogleSubscriptionsQueueDlq.QueueName" Period: 60 Statistic: Sum EvaluationPeriods: 1 ComparisonOperator: GreaterThanThreshold Threshold: 0 AlarmActions: - Ref: AlarmTopic OKActions: - Ref: AlarmTopic TreatMissingData: notBreaching Tags: - Key: App Value: mobile-purchases-google-update-subscriptions UpdateFeastAppleSubscriptionsLambda: Type: AWS::Lambda::Function Properties: FunctionName: !Sub ${App}-feast-apple-update-subscriptions-${Stage} Code: S3Bucket: !Ref DeployBucket S3Key: !Sub ${Stack}/${Stage}/${App}-feast-apple-update-subscriptions/feast-apple-update-subscriptions.zip Environment: Variables: App: !Sub ${App} Stack: !Sub ${Stack} Stage: !Sub ${Stage} HistoricalQueueUrl: !Sub https://sqs.${AWS::Region}.amazonaws.com/${AWS::AccountId}/${App}-${Stage}-apple-historical-subscriptions Description: Consumes Feast subscription data updates from app store from sqs and stores them in dynamo Handler: feast-apple-update-subscriptions.handler MemorySize: 512 Role: !GetAtt MobilePurchasesLambdasRole.Arn Timeout: 25 Runtime: nodejs20.x UpdateFeastGoogleSubscriptionsLambda: Type: AWS::Lambda::Function Properties: FunctionName: !Sub ${App}-feast-google-update-subscriptions-${Stage} Code: S3Bucket: !Ref DeployBucket S3Key: !Sub ${Stack}/${Stage}/${App}-feast-google-update-subscriptions/feast-google-update-subscriptions.zip Environment: Variables: App: !Sub ${App} Stack: !Sub ${Stack} Stage: !Sub ${Stage} HistoricalQueueUrl: !Sub https://sqs.${AWS::Region}.amazonaws.com/${AWS::AccountId}/${App}-${Stage}-google-historical-subscriptions Description: Consumes Feast subscription data updates from Google Play store from sqs and stores them in dynamo Handler: feast-google-update-subscriptions.handler MemorySize: 512 Role: !GetAtt MobilePurchasesLambdasRole.Arn Timeout: 25 Runtime: nodejs20.x FeastAppleSubscriptionsEventSource: Type: AWS::Lambda::EventSourceMapping Properties: FunctionName: !Ref UpdateFeastAppleSubscriptionsLambda Enabled: true EventSourceArn: !GetAtt FeastAppleSubscriptionsQueue.Arn BatchSize: 1 FeastGoogleSubscriptionsEventSource: Type: AWS::Lambda::EventSourceMapping Properties: FunctionName: !Ref UpdateFeastGoogleSubscriptionsLambda Enabled: true EventSourceArn: !GetAtt FeastGoogleSubscriptionsQueue.Arn BatchSize: 1 FeastAppleSubscriptionDlqDepthAlarm: Type: AWS::CloudWatch::Alarm Properties: ActionsEnabled: !FindInMap [StageVariables, !Ref Stage, AlarmActionsEnabled] AlarmDescription: "The Feast Apple subscription dead letter queue is over the threshold" Namespace: "AWS/SQS" MetricName: ApproximateNumberOfMessagesVisible Dimensions: - Name: QueueName Value: !GetAtt "FeastAppleSubscriptionsQueueDlq.QueueName" Period: 60 Statistic: Sum EvaluationPeriods: 1 ComparisonOperator: GreaterThanThreshold Threshold: 10 AlarmActions: - Ref: AlarmTopic OKActions: - Ref: AlarmTopic TreatMissingData: notBreaching Tags: - Key: App Value: mobile-purchases-feast-apple-update-subscriptions FeastGoogleSubscriptionDlqDepthAlarm: Type: AWS::CloudWatch::Alarm Properties: ActionsEnabled: !FindInMap [ StageVariables, !Ref Stage, AlarmActionsEnabled ] AlarmDescription: "Ensure that the Feast Android subscription dead letter queue is empty" Namespace: "AWS/SQS" MetricName: ApproximateNumberOfMessagesVisible Dimensions: - Name: QueueName Value: !GetAtt "FeastGoogleSubscriptionsQueueDlq.QueueName" Period: 60 Statistic: Sum EvaluationPeriods: 1 ComparisonOperator: GreaterThanThreshold Threshold: 0 AlarmActions: - Ref: AlarmTopic OKActions: - Ref: AlarmTopic TreatMissingData: notBreaching Tags: - Key: App Value: mobile-purchases-feast-google-update-subscriptions AppleRevalidateSubscriptionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: sts:AssumeRole Path: / Policies: - PolicyName: logs PolicyDocument: Statement: Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents - cloudwatch:putMetricData Resource: "*" - PolicyName: dynamo PolicyDocument: Statement: Effect: Allow Action: - "dynamodb:Scan" Resource: - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-subscriptions/index/ios-endTimestamp-revalidation-index-with-platform - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-subscriptions - PolicyName: sqs PolicyDocument: Statement: Effect: Allow Action: - "sqs:SendMessage" Resource: - !Sub arn:aws:sqs:${AWS::Region}:${AWS::AccountId}:${App}-${Stage}-apple-subscriptions-to-fetch - !Sub arn:aws:sqs:${AWS::Region}:${AWS::AccountId}:${App}-${Stage}-feast-apple-subscriptions-to-fetch DeleteUserSubscriptionLambda: Type: AWS::Serverless::Function Properties: Handler: delete-user-subscription.handler Runtime: nodejs20.x CodeUri: Bucket: !Ref DeployBucket Key: !Sub ${Stack}/${Stage}/${App}-delete-user-subscription/delete-user-subscription.zip FunctionName: !Sub ${App}-delete-user-subscription-${Stage} Environment: Variables: App: !Sub ${App} Stack: !Sub ${Stack} Stage: !Sub ${Stage} Description: Delete the link to user IDs when the subscription has reached its end of life MemorySize: 512 Timeout: 60 Events: Schedule: Type: DynamoDB Properties: Stream: Fn::ImportValue: !Sub ${App}-${Stage}-subscriptions-stream-arn StartingPosition: LATEST Tags: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App Policies: - Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents - cloudwatch:putMetricData Resource: "*" - Statement: - Effect: Allow Action: - "dynamodb:GetRecords" - "dynamodb:GetShardIterator" - "dynamodb:DescribeStream" - "dynamodb:ListStreams" Resource: - Fn::ImportValue: !Sub ${App}-${Stage}-subscriptions-stream-arn - Statement: - Effect: Allow Action: - "dynamodb:Query" - "dynamodb:DeleteItem" Resource: - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-user-subscriptions - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-user-subscriptions/* - Statement: - Effect: Allow Action: - sts:AssumeRole Resource: !Sub - "arn:aws:iam::${MembershipAccountId}:role/membership-${SoftOptInConsentSetterStage}-soft-opt-in-consent-setter-QueueCrossAccountRole" - SoftOptInConsentSetterStage: !FindInMap [ StageVariables, !Ref Stage, SoftOptInConsentSetterStage ] - Statement: - Effect: Allow Action: - sts:AssumeRole Resource: !Sub "arn:aws:iam::${MembershipAccountId}:role/comms-${Stage}-EmailQueueCrossAccountRole" - Statement: - Effect: Allow Action: - ssm:GetParametersByPath Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${App}/${Stage}/${Stack}/* AppleRevalidateReceiptsLambda: Type: AWS::Serverless::Function Properties: Handler: apple-revalidate-receipts.handler Runtime: nodejs20.x CodeUri: Bucket: !Ref DeployBucket Key: !Sub ${Stack}/${Stage}/${App}-apple-revalidate-receipts/apple-revalidate-receipts.zip FunctionName: !Sub ${App}-apple-revalidate-receipts-${Stage} Role: !GetAtt AppleRevalidateSubscriptionRole.Arn Environment: Variables: App: !Sub ${App} Stack: !Sub ${Stack} Stage: !Sub ${Stage} LiveAppSqsUrl: !Ref AppleSubscriptionsQueue FeastAppSqsUrl: !Ref FeastAppleSubscriptionsQueue Description: Finds recently expired subscriptions MemorySize: 2048 Timeout: 180 Events: Schedule: Type: Schedule Properties: Schedule: rate(6 hours) Tags: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App AcquisitionsDeadLetterQueue: Type: AWS::SQS::Queue Properties: QueueName: !Sub ${App}-soft-opt-in-acquisitions-DLQ-${Stage} SoftOptInAcquisitionsLambda: Type: AWS::Serverless::Function Properties: Handler: soft-opt-in-acquisitions.handler Runtime: nodejs20.x CodeUri: Bucket: !Ref DeployBucket Key: !Sub ${Stack}/${Stage}/${App}-soft-opt-in-acquisitions/soft-opt-in-acquisitions.zip FunctionName: !Sub ${App}-soft-opt-in-acquisitions-${Stage} Environment: Variables: App: !Sub ${App} Stack: !Sub ${Stack} Stage: !Sub ${Stage} DLQUrl: !Ref AcquisitionsDeadLetterQueue Description: Trigger setting soft-opt-in consents and sending emails based on Dynamo events MemorySize: 512 Timeout: 60 Events: Schedule: Type: DynamoDB Properties: Stream: Fn::ImportValue: !Sub ${App}-${Stage}-user-subscriptions-stream-arn StartingPosition: LATEST MaximumRecordAgeInSeconds: 28800 Tags: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App Policies: - Statement: - Effect: Allow Action: - sts:AssumeRole Resource: !Sub - "arn:aws:iam::${MembershipAccountId}:role/membership-${SoftOptInConsentSetterStage}-soft-opt-in-consent-setter-QueueCrossAccountRole" - SoftOptInConsentSetterStage: !FindInMap [ StageVariables, !Ref Stage, SoftOptInConsentSetterStage ] - Statement: - Effect: Allow Action: - sts:AssumeRole Resource: !Sub "arn:aws:iam::${MembershipAccountId}:role/comms-${Stage}-EmailQueueCrossAccountRole" - Statement: - Effect: Allow Action: - "dynamodb:Query" - "dynamodb:GetItem" Resource: - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-subscriptions - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-subscriptions/* - Statement: - Effect: Allow Action: - ssm:GetParametersByPath Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${App}/${Stage}/${Stack}/* - Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents - cloudwatch:putMetricData Resource: "*" - Statement: - Effect: Allow Action: - "dynamodb:GetRecords" - "dynamodb:GetShardIterator" - "dynamodb:DescribeStream" - "dynamodb:ListStreams" Resource: - Fn::ImportValue: !Sub ${App}-${Stage}-user-subscriptions-stream-arn - Statement: - Effect: Allow Action: - sqs:DeleteMessage - sqs:GetQueueAttributes - sqs:ReceiveMessage Resource: "*" - Statement: - Effect: Allow Action: - sqs:SendMessage Resource: !GetAtt AcquisitionsDeadLetterQueue.Arn AcquisitionsDLQProcessorLambda: Type: AWS::Serverless::Function Properties: Handler: soft-opt-in-acquisitions-dlq-processor.handler Runtime: nodejs20.x CodeUri: Bucket: !Ref DeployBucket Key: !Sub ${Stack}/${Stage}/${App}-soft-opt-in-acquisitions-dlq-processor/soft-opt-in-acquisitions-dlq-processor.zip FunctionName: !Sub ${App}-soft-opt-in-acquisitions-dlq-processor-${Stage} Environment: Variables: App: !Sub ${App} Stack: !Sub ${Stack} Stage: !Sub ${Stage} DLQUrl: !Ref AcquisitionsDeadLetterQueue Description: Process DLQ messages MemorySize: 512 Timeout: 60 Tags: Stage: !Ref Stage Stack: !Ref Stack App: !Ref App Events: ScheduledRun: Type: Schedule Properties: Schedule: !FindInMap [ StageVariables, !Ref Stage, Schedule] Description: Runs AcquisitionsDLQProcessorLambda Enabled: True Policies: - Statement: - Effect: Allow Action: - sts:AssumeRole Resource: !Sub - "arn:aws:iam::${MembershipAccountId}:role/membership-${SoftOptInConsentSetterStage}-soft-opt-in-consent-setter-QueueCrossAccountRole" - SoftOptInConsentSetterStage: !FindInMap [ StageVariables, !Ref Stage, SoftOptInConsentSetterStage ] - Statement: - Effect: Allow Action: - sts:AssumeRole Resource: !Sub "arn:aws:iam::${MembershipAccountId}:role/comms-${Stage}-EmailQueueCrossAccountRole" - Statement: - Effect: Allow Action: - "dynamodb:Query" - "dynamodb:GetItem" Resource: - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-subscriptions - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-subscriptions/* - Statement: - Effect: Allow Action: - ssm:GetParametersByPath Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${App}/${Stage}/${Stack}/* - Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents - cloudwatch:putMetricData Resource: "*" - Statement: - Effect: Allow Action: - sqs:DeleteMessage - sqs:GetQueueAttributes - sqs:ReceiveMessage Resource: "*" failedSettingCancellationSoftOptIns: Condition: IsProd Type: AWS::CloudWatch::Alarm DependsOn: - DeleteUserSubscriptionLambda Properties: AlarmActions: - Ref: AlarmTopic AlarmName: !Sub ${App}-delete-user-subscription-${Stage} failed to set soft opt-ins for a user AlarmDescription: > An error occurred in the DeleteUserSubscriptionLambda and failed to set soft opt-ins for a user ComparisonOperator: GreaterThanOrEqualToThreshold Dimensions: - Name: Stage Value: !Sub ${Stage} EvaluationPeriods: 1 MetricName: failed_to_send_cancellation_message Namespace: AWS/Lambda Period: 3600 Statistic: Sum Threshold: 1 TreatMissingData: notBreaching Tags: - Key: App Value: mobile-purchases-delete-user-subscription acquisitionsLambdaExceptionsAlarm: Type: AWS::CloudWatch::Alarm Condition: IsProd DependsOn: - SoftOptInAcquisitionsLambda Properties: AlarmActions: - Ref: AlarmTopic AlarmName: !Sub ${App}-soft-opt-in-acquisitions-${Stage} threw an unhandled exception and failed to set soft opt-ins for a user AlarmDescription: > An error occurred in the SoftOptInAcquisitionsLambda and failed to set soft opt-ins for a user ComparisonOperator: GreaterThanOrEqualToThreshold Dimensions: - Name: FunctionName Value: !Ref SoftOptInAcquisitionsLambda EvaluationPeriods: 1 MetricName: Errors Namespace: AWS/Lambda Period: 3600 Statistic: Sum Threshold: 1 TreatMissingData: notBreaching Tags: - Key: App Value: mobile-purchases-soft-opt-in-acquisitions acquisitionsDlqProcessorExceptionsAlarm: Type: AWS::CloudWatch::Alarm Condition: IsProd DependsOn: - AcquisitionsDLQProcessorLambda Properties: AlarmActions: - Ref: AlarmTopic AlarmName: !Sub ${App}-soft-opt-ins-acquisitions-dlq-processor-${Stage} threw an unhandled exception AlarmDescription: > An error occurred in the AcquisitionsDLQProcessorLambda ComparisonOperator: GreaterThanOrEqualToThreshold Dimensions: - Name: FunctionName Value: !Ref AcquisitionsDLQProcessorLambda EvaluationPeriods: 1 MetricName: Errors Namespace: AWS/Lambda Period: 3600 Statistic: Sum Threshold: 1 TreatMissingData: notBreaching Tags: - Key: App Value: mobile-purchases-soft-opt-in-acquisitions-dlq-processor MembershipCloudwatchRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: AWS: - !Sub arn:aws:iam::${MembershipAccountId}:root Action: sts:AssumeRole Path: / Policies: - PolicyName: cloudwatch-list-tags PolicyDocument: Statement: Effect: Allow Action: - cloudwatch:ListTagsForResource Resource: "*"