mirror of
https://github.com/Sea-Haven-Industries/payments-dashboard.git
synced 2026-09-30 07:43:12 +00:00
Same-day settlement visibility plus maximal data capture from the
reporting feed. The handler gains an allowlisted endpoint parameter
(EventBridge passes {"endpoint":"current-day"} on a new 16/19/22 UTC
weekday rule; the 9am previous-day sweep is unchanged and remains
authoritative). Every response's exact bytes archive to a new
Retain-protected bucket before classification, so the feed is
replayable and auditable even across parser changes. Summary rows
become per-date boa_balance# snapshots (latest-wins on run_at, no
TTL) instead of being discarded. The staleness sweep is gated to
previous-day runs so intraday runs don't re-alert the backlog three
times a day. History events carry a via:<endpoint> audit tag outside
the replay-idempotence identity. Fixtures are sanitized real API
captures; classification histograms assert against live-verified
counts.
Refs: #66, #69
1185 lines
40 KiB
YAML
1185 lines
40 KiB
YAML
AWSTemplateFormatVersion: '2010-09-09'
|
|
Transform: AWS::Serverless-2016-10-31
|
|
Description: Payments Dashboard - S3 CSV ingestion to Slack App Home
|
|
|
|
Parameters:
|
|
DynamoDbCmkArn:
|
|
Type: AWS::SSM::Parameter::Value<String>
|
|
Default: /seahaven/dynamodb/cmk-arn
|
|
Description: >-
|
|
ARN of the shared customer-managed CMK (alias/seahaven-dynamodb) that
|
|
encrypts the PaymentsDashboard table. Functions that read/write the table
|
|
need kms:Decrypt/GenerateDataKey/DescribeKey on this key (the boundary
|
|
permits exactly these), or DynamoDB calls fail with AccessDeniedException.
|
|
|
|
Globals:
|
|
Function:
|
|
Runtime: nodejs24.x
|
|
Architectures:
|
|
- arm64
|
|
Timeout: 30
|
|
MemorySize: 256
|
|
PermissionsBoundary: arn:aws:iam::328440206208:policy/seahaven-lambda-execution-boundary
|
|
Environment:
|
|
Variables:
|
|
TABLE_NAME: !Ref DashboardTable
|
|
# Access logging + default throttling on the implicit HTTP API (audit M-18).
|
|
HttpApi:
|
|
AccessLogSettings:
|
|
DestinationArn: !GetAtt ApiAccessLogGroup.Arn
|
|
Format: '{"requestId":"$context.requestId","ip":"$context.identity.sourceIp","requestTime":"$context.requestTime","method":"$context.httpMethod","routeKey":"$context.routeKey","status":"$context.status","protocol":"$context.protocol","responseLength":"$context.responseLength","integrationError":"$context.integrationErrorMessage"}'
|
|
DefaultRouteSettings:
|
|
ThrottlingBurstLimit: 50
|
|
ThrottlingRateLimit: 100
|
|
|
|
Resources:
|
|
ApiAccessLogGroup:
|
|
Type: AWS::Logs::LogGroup
|
|
Properties:
|
|
LogGroupName: /aws/apigateway/payments-dashboard
|
|
RetentionInDays: 90
|
|
|
|
# VPC with private subnet + NAT Gateway for static outbound IP
|
|
Vpc:
|
|
Type: AWS::EC2::VPC
|
|
Properties:
|
|
CidrBlock: 10.20.0.0/16
|
|
EnableDnsSupport: true
|
|
EnableDnsHostnames: true
|
|
Tags:
|
|
- Key: Name
|
|
Value: payments-dashboard-vpc
|
|
|
|
PrivateSubnet:
|
|
Type: AWS::EC2::Subnet
|
|
Properties:
|
|
VpcId: !Ref Vpc
|
|
CidrBlock: 10.20.1.0/24
|
|
AvailabilityZone: !Select [0, !GetAZs ""]
|
|
Tags:
|
|
- Key: Name
|
|
Value: payments-dashboard-private
|
|
|
|
PublicSubnet:
|
|
Type: AWS::EC2::Subnet
|
|
Properties:
|
|
VpcId: !Ref Vpc
|
|
CidrBlock: 10.20.2.0/24
|
|
AvailabilityZone: !Select [0, !GetAZs ""]
|
|
Tags:
|
|
- Key: Name
|
|
Value: payments-dashboard-public
|
|
|
|
InternetGateway:
|
|
Type: AWS::EC2::InternetGateway
|
|
|
|
VpcGatewayAttachment:
|
|
Type: AWS::EC2::VPCGatewayAttachment
|
|
Properties:
|
|
VpcId: !Ref Vpc
|
|
InternetGatewayId: !Ref InternetGateway
|
|
|
|
NatEip:
|
|
Type: AWS::EC2::EIP
|
|
Properties:
|
|
Domain: vpc
|
|
|
|
NatGateway:
|
|
Type: AWS::EC2::NatGateway
|
|
Properties:
|
|
AllocationId: !GetAtt NatEip.AllocationId
|
|
SubnetId: !Ref PublicSubnet
|
|
|
|
PublicRouteTable:
|
|
Type: AWS::EC2::RouteTable
|
|
Properties:
|
|
VpcId: !Ref Vpc
|
|
|
|
PublicRoute:
|
|
Type: AWS::EC2::Route
|
|
DependsOn: VpcGatewayAttachment
|
|
Properties:
|
|
RouteTableId: !Ref PublicRouteTable
|
|
DestinationCidrBlock: 0.0.0.0/0
|
|
GatewayId: !Ref InternetGateway
|
|
|
|
PublicSubnetRouteTableAssociation:
|
|
Type: AWS::EC2::SubnetRouteTableAssociation
|
|
Properties:
|
|
SubnetId: !Ref PublicSubnet
|
|
RouteTableId: !Ref PublicRouteTable
|
|
|
|
PrivateRouteTable:
|
|
Type: AWS::EC2::RouteTable
|
|
Properties:
|
|
VpcId: !Ref Vpc
|
|
|
|
PrivateRoute:
|
|
Type: AWS::EC2::Route
|
|
Properties:
|
|
RouteTableId: !Ref PrivateRouteTable
|
|
DestinationCidrBlock: 0.0.0.0/0
|
|
NatGatewayId: !Ref NatGateway
|
|
|
|
# Gateway endpoints (audit M-22): keep S3/DynamoDB traffic off the NAT
|
|
# gateway — free, and removes per-GB NAT data-processing charges.
|
|
S3GatewayEndpoint:
|
|
Type: AWS::EC2::VPCEndpoint
|
|
Properties:
|
|
VpcId: !Ref Vpc
|
|
ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
|
|
VpcEndpointType: Gateway
|
|
RouteTableIds:
|
|
- !Ref PublicRouteTable
|
|
- !Ref PrivateRouteTable
|
|
|
|
DynamoDbGatewayEndpoint:
|
|
Type: AWS::EC2::VPCEndpoint
|
|
Properties:
|
|
VpcId: !Ref Vpc
|
|
ServiceName: !Sub com.amazonaws.${AWS::Region}.dynamodb
|
|
VpcEndpointType: Gateway
|
|
RouteTableIds:
|
|
- !Ref PublicRouteTable
|
|
- !Ref PrivateRouteTable
|
|
|
|
PrivateSubnetRouteTableAssociation:
|
|
Type: AWS::EC2::SubnetRouteTableAssociation
|
|
Properties:
|
|
SubnetId: !Ref PrivateSubnet
|
|
RouteTableId: !Ref PrivateRouteTable
|
|
|
|
LambdaSecurityGroup:
|
|
Type: AWS::EC2::SecurityGroup
|
|
Properties:
|
|
GroupDescription: Payments Dashboard Lambda outbound access
|
|
VpcId: !Ref Vpc
|
|
SecurityGroupEgress:
|
|
- IpProtocol: "-1"
|
|
CidrIp: 0.0.0.0/0
|
|
|
|
PaymentsCsvBucket:
|
|
Type: AWS::S3::Bucket
|
|
Properties:
|
|
BucketName: !Sub seahaven-payments-csv-${AWS::AccountId}
|
|
PublicAccessBlockConfiguration:
|
|
BlockPublicAcls: true
|
|
IgnorePublicAcls: true
|
|
BlockPublicPolicy: true
|
|
RestrictPublicBuckets: true
|
|
|
|
# Raw archive of every BoA reporting API response (exact bytes, keyed
|
|
# raw/<endpoint>/<fromDate>_<toDate>/<runAt>.json). Replayable corpus for
|
|
# parser changes + audit trail. Retain: a template revert must never
|
|
# attempt to delete a bank-data bucket; decommission goes through the CFN
|
|
# decommission runbook (inventory, purge, deliberate deletion).
|
|
# Retain + fixed name = rollback-orphan hazard (same class as the RETAIN
|
|
# secret deadlock): if a failed deploy orphans the bucket, ADOPT it back
|
|
# with a CloudFormation resource import — never delete-and-recreate.
|
|
BoaRawBucket:
|
|
Type: AWS::S3::Bucket
|
|
DeletionPolicy: Retain
|
|
UpdateReplacePolicy: Retain
|
|
Properties:
|
|
BucketName: !Sub seahaven-payments-boa-raw-${AWS::AccountId}
|
|
PublicAccessBlockConfiguration:
|
|
BlockPublicAcls: true
|
|
IgnorePublicAcls: true
|
|
BlockPublicPolicy: true
|
|
RestrictPublicBuckets: true
|
|
BucketEncryption:
|
|
ServerSideEncryptionConfiguration:
|
|
- ServerSideEncryptionByDefault:
|
|
SSEAlgorithm: AES256
|
|
LifecycleConfiguration:
|
|
Rules:
|
|
- Id: expire-raw-responses
|
|
Status: Enabled
|
|
ExpirationInDays: 730
|
|
|
|
# Defense-in-depth for bank data: deny any non-TLS access. No Allow
|
|
# statements — access is IAM-only (the Lambda's PutObject grant); unlike
|
|
# PayrollEmailBucket there is no cross-service principal here. The Deny
|
|
# covers bucket-level actions too, which is safe because nothing is
|
|
# granted List/Get — revisit if read access is ever added.
|
|
BoaRawBucketPolicy:
|
|
Type: AWS::S3::BucketPolicy
|
|
Properties:
|
|
Bucket: !Ref BoaRawBucket
|
|
PolicyDocument:
|
|
Version: "2012-10-17"
|
|
Statement:
|
|
- Sid: DenyInsecureTransport
|
|
Effect: Deny
|
|
Principal: "*"
|
|
Action: s3:*
|
|
Resource:
|
|
- !GetAtt BoaRawBucket.Arn
|
|
- !Sub "${BoaRawBucket.Arn}/*"
|
|
Condition:
|
|
Bool:
|
|
aws:SecureTransport: "false"
|
|
|
|
DashboardTable:
|
|
Type: AWS::DynamoDB::Table
|
|
Properties:
|
|
TableName: PaymentsDashboard
|
|
BillingMode: PAY_PER_REQUEST
|
|
AttributeDefinitions:
|
|
- AttributeName: pk
|
|
AttributeType: S
|
|
KeySchema:
|
|
- AttributeName: pk
|
|
KeyType: HASH
|
|
TimeToLiveSpecification:
|
|
AttributeName: ttl
|
|
Enabled: true
|
|
# SSE-KMS with the shared CMK (alias/seahaven-dynamodb, INFRA-95 / M-3).
|
|
# The table was migrated to this key out-of-band, so declaring it here
|
|
# reconciles the template drift (no-op against the live table). Consumer
|
|
# roles still need explicit kms perms below (SAM policies do not auto-add).
|
|
SSESpecification:
|
|
SSEEnabled: true
|
|
SSEType: KMS
|
|
KMSMasterKeyId: !Ref DynamoDbCmkArn
|
|
|
|
PayrollEmailBucket:
|
|
Type: AWS::S3::Bucket
|
|
Properties:
|
|
BucketName: !Sub seahaven-payroll-emails-${AWS::AccountId}
|
|
PublicAccessBlockConfiguration:
|
|
BlockPublicAcls: true
|
|
IgnorePublicAcls: true
|
|
BlockPublicPolicy: true
|
|
RestrictPublicBuckets: true
|
|
LifecycleConfiguration:
|
|
Rules:
|
|
- Id: ExpireEmails
|
|
Status: Enabled
|
|
ExpirationInDays: 30
|
|
|
|
PayrollEmailBucketPolicy:
|
|
Type: AWS::S3::BucketPolicy
|
|
Properties:
|
|
Bucket: !Ref PayrollEmailBucket
|
|
PolicyDocument:
|
|
Version: "2012-10-17"
|
|
Statement:
|
|
- Sid: AllowSESPut
|
|
Effect: Allow
|
|
Principal:
|
|
Service: ses.amazonaws.com
|
|
Action: s3:PutObject
|
|
Resource: !Sub arn:aws:s3:::seahaven-payroll-emails-${AWS::AccountId}/*
|
|
Condition:
|
|
StringEquals:
|
|
AWS:SourceAccount: !Ref AWS::AccountId
|
|
|
|
PayrollEmailRule:
|
|
Type: AWS::SES::ReceiptRule
|
|
DependsOn: PayrollEmailBucketPolicy
|
|
Properties:
|
|
RuleSetName: INBOUND_MAIL
|
|
After: ExistingRuleSetWorkorderEmailRuleEA29F845-okepxcVarTfu
|
|
Rule:
|
|
Name: store-payroll-emails
|
|
Enabled: true
|
|
ScanEnabled: true
|
|
Recipients:
|
|
- payroll@int.seahaven.com
|
|
Actions:
|
|
- S3Action:
|
|
BucketName: !Ref PayrollEmailBucket
|
|
ObjectKeyPrefix: inbound/
|
|
|
|
PayrollBatchQueue:
|
|
Type: AWS::SQS::Queue
|
|
Properties:
|
|
QueueName: payments-payroll-batch
|
|
DelaySeconds: 600
|
|
MessageRetentionPeriod: 86400
|
|
VisibilityTimeout: 60
|
|
RedrivePolicy:
|
|
deadLetterTargetArn: !GetAtt PayrollBatchDLQ.Arn
|
|
maxReceiveCount: 3
|
|
|
|
# Audit L-15: payroll-batch messages were lost after max receives. 14-day
|
|
# retention so a failure on Friday survives the weekend.
|
|
PayrollBatchDLQ:
|
|
Type: AWS::SQS::Queue
|
|
Properties:
|
|
QueueName: payments-payroll-batch-dlq
|
|
MessageRetentionPeriod: 1209600
|
|
|
|
PayrollBatchDLQAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-payroll-batch-dlq-messages
|
|
AlarmDescription: Failed payroll-batch messages landed in the DLQ
|
|
Namespace: AWS/SQS
|
|
MetricName: ApproximateNumberOfMessagesVisible
|
|
Dimensions:
|
|
- Name: QueueName
|
|
Value: !GetAtt PayrollBatchDLQ.QueueName
|
|
Statistic: Maximum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 0
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
# ALARM-only notification by convention — no OK/recovery action
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
# Async-invoke OnFailure DLQs (INFRA-41 / H-8). Reconciles the interim
|
|
# CLI-created queues into CloudFormation. Names are CFN-generated to avoid
|
|
# colliding with the live interim payments-<fn>-dlq queues (deleted after
|
|
# this deploy). 14-day retention mirrors the payments-payroll-batch DLQ so a
|
|
# Friday failure survives the weekend.
|
|
# Distinct -async-dlq name (not the live interim payments-<fn>-dlq) so this
|
|
# CFN queue does not collide with the queue being deleted post-deploy.
|
|
ProcessPaymentCsvDLQ:
|
|
Type: AWS::SQS::Queue
|
|
Properties:
|
|
QueueName: payments-processPaymentCsv-async-dlq
|
|
MessageRetentionPeriod: 1209600 # 14d, matches payments-payroll-batch-dlq
|
|
|
|
ProcessPayrollEmailDLQ:
|
|
Type: AWS::SQS::Queue
|
|
Properties:
|
|
QueueName: payments-processPayrollEmail-async-dlq
|
|
MessageRetentionPeriod: 1209600 # 14d, matches payments-payroll-batch-dlq
|
|
|
|
# ALARM-only Lambda Errors alarms (INFRA-41 / H-8). Threshold > 0 on the
|
|
# Errors Sum, no OK/recovery action by convention.
|
|
ProcessPaymentCsvErrorsAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-processPaymentCsv-errors
|
|
AlarmDescription: payments-processPaymentCsv invocation errors
|
|
Namespace: AWS/Lambda
|
|
MetricName: Errors
|
|
Dimensions:
|
|
- Name: FunctionName
|
|
Value: !Ref ProcessPaymentCsvFunction
|
|
Statistic: Sum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 0
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
ProcessPayrollEmailErrorsAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-processPayrollEmail-errors
|
|
AlarmDescription: payments-processPayrollEmail invocation errors
|
|
Namespace: AWS/Lambda
|
|
MetricName: Errors
|
|
Dimensions:
|
|
- Name: FunctionName
|
|
Value: !Ref ProcessPayrollEmailFunction
|
|
Statistic: Sum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 0
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
# ── Lambda Errors alarms (Wave 1) ──────────────────────────────────────────
|
|
# Clone of ProcessPaymentCsvErrorsAlarm for the remaining functions. AWS/Lambda
|
|
# Errors, Sum over 5m, threshold > 0, ALARM-only by convention.
|
|
SlackAppHomeErrorsAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-slackAppHome-errors
|
|
AlarmDescription: payments-slackAppHome invocation errors
|
|
Namespace: AWS/Lambda
|
|
MetricName: Errors
|
|
Dimensions:
|
|
- Name: FunctionName
|
|
Value: !Ref SlackAppHomeFunction
|
|
Statistic: Sum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 0
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
FetchBoaTransactionsErrorsAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-fetchBoaTransactions-errors
|
|
AlarmDescription: payments-fetchBoaTransactions invocation errors
|
|
Namespace: AWS/Lambda
|
|
MetricName: Errors
|
|
Dimensions:
|
|
- Name: FunctionName
|
|
Value: !Ref FetchBoaTransactionsFunction
|
|
Statistic: Sum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 0
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
ExpenseReceiverErrorsAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-expenseReceiver-errors
|
|
AlarmDescription: payments-expenseReceiver invocation errors
|
|
Namespace: AWS/Lambda
|
|
MetricName: Errors
|
|
Dimensions:
|
|
- Name: FunctionName
|
|
Value: !Ref ExpenseReceiverFunction
|
|
Statistic: Sum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 0
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
ExpenseProcessorErrorsAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-expenseProcessor-errors
|
|
AlarmDescription: payments-expenseProcessor invocation errors
|
|
Namespace: AWS/Lambda
|
|
MetricName: Errors
|
|
Dimensions:
|
|
- Name: FunctionName
|
|
Value: !Ref ExpenseProcessorFunction
|
|
Statistic: Sum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 0
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
# ── Lambda Throttles alarms (Wave 1) ───────────────────────────────────────
|
|
# AWS/Lambda Throttles, Sum over 5m, threshold > 0, ALARM-only. Throttling
|
|
# signals concurrency exhaustion / reserved-concurrency starvation.
|
|
ProcessPaymentCsvThrottlesAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-processPaymentCsv-throttles
|
|
AlarmDescription: payments-processPaymentCsv invocations throttled
|
|
Namespace: AWS/Lambda
|
|
MetricName: Throttles
|
|
Dimensions:
|
|
- Name: FunctionName
|
|
Value: !Ref ProcessPaymentCsvFunction
|
|
Statistic: Sum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 0
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
ProcessPayrollEmailThrottlesAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-processPayrollEmail-throttles
|
|
AlarmDescription: payments-processPayrollEmail invocations throttled
|
|
Namespace: AWS/Lambda
|
|
MetricName: Throttles
|
|
Dimensions:
|
|
- Name: FunctionName
|
|
Value: !Ref ProcessPayrollEmailFunction
|
|
Statistic: Sum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 0
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
FetchBoaTransactionsThrottlesAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-fetchBoaTransactions-throttles
|
|
AlarmDescription: payments-fetchBoaTransactions invocations throttled
|
|
Namespace: AWS/Lambda
|
|
MetricName: Throttles
|
|
Dimensions:
|
|
- Name: FunctionName
|
|
Value: !Ref FetchBoaTransactionsFunction
|
|
Statistic: Sum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 0
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
SlackAppHomeThrottlesAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-slackAppHome-throttles
|
|
AlarmDescription: payments-slackAppHome invocations throttled
|
|
Namespace: AWS/Lambda
|
|
MetricName: Throttles
|
|
Dimensions:
|
|
- Name: FunctionName
|
|
Value: !Ref SlackAppHomeFunction
|
|
Statistic: Sum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 0
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
ExpenseReceiverThrottlesAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-expenseReceiver-throttles
|
|
AlarmDescription: payments-expenseReceiver invocations throttled
|
|
Namespace: AWS/Lambda
|
|
MetricName: Throttles
|
|
Dimensions:
|
|
- Name: FunctionName
|
|
Value: !Ref ExpenseReceiverFunction
|
|
Statistic: Sum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 0
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
ExpenseProcessorThrottlesAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-expenseProcessor-throttles
|
|
AlarmDescription: payments-expenseProcessor invocations throttled
|
|
Namespace: AWS/Lambda
|
|
MetricName: Throttles
|
|
Dimensions:
|
|
- Name: FunctionName
|
|
Value: !Ref ExpenseProcessorFunction
|
|
Statistic: Sum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 0
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
# ── Lambda Duration alarms (Wave 1) ────────────────────────────────────────
|
|
# AWS/Lambda Duration (ms), Statistic Maximum over 5m. Thresholds are ~80% of
|
|
# each function's configured timeout — early warning before timeout-kills.
|
|
ProcessPaymentCsvDurationAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-processPaymentCsv-duration
|
|
AlarmDescription: payments-processPaymentCsv approaching timeout (~80% of 120s)
|
|
Namespace: AWS/Lambda
|
|
MetricName: Duration
|
|
Dimensions:
|
|
- Name: FunctionName
|
|
Value: !Ref ProcessPaymentCsvFunction
|
|
Statistic: Maximum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 96000
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
ProcessPayrollEmailDurationAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-processPayrollEmail-duration
|
|
AlarmDescription: payments-processPayrollEmail approaching timeout (~80% of 60s)
|
|
Namespace: AWS/Lambda
|
|
MetricName: Duration
|
|
Dimensions:
|
|
- Name: FunctionName
|
|
Value: !Ref ProcessPayrollEmailFunction
|
|
Statistic: Maximum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 48000
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
FetchBoaTransactionsDurationAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-fetchBoaTransactions-duration
|
|
AlarmDescription: payments-fetchBoaTransactions approaching timeout (~80% of 60s)
|
|
Namespace: AWS/Lambda
|
|
MetricName: Duration
|
|
Dimensions:
|
|
- Name: FunctionName
|
|
Value: !Ref FetchBoaTransactionsFunction
|
|
Statistic: Maximum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 48000
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
ExpenseProcessorDurationAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-expenseProcessor-duration
|
|
AlarmDescription: payments-expenseProcessor approaching timeout (~80% of 15s)
|
|
Namespace: AWS/Lambda
|
|
MetricName: Duration
|
|
Dimensions:
|
|
- Name: FunctionName
|
|
Value: !Ref ExpenseProcessorFunction
|
|
Statistic: Maximum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 12000
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
ExpenseReceiverDurationAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-expenseReceiver-duration
|
|
AlarmDescription: payments-expenseReceiver approaching timeout (~80% of 5s)
|
|
Namespace: AWS/Lambda
|
|
MetricName: Duration
|
|
Dimensions:
|
|
- Name: FunctionName
|
|
Value: !Ref ExpenseReceiverFunction
|
|
Statistic: Maximum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 4000
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
SlackAppHomeDurationAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-slackAppHome-duration
|
|
AlarmDescription: payments-slackAppHome approaching timeout (~80% of 30s default)
|
|
Namespace: AWS/Lambda
|
|
MetricName: Duration
|
|
Dimensions:
|
|
- Name: FunctionName
|
|
Value: !Ref SlackAppHomeFunction
|
|
Statistic: Maximum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 24000
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
# ── DynamoDB alarms (Wave 1, SCOPE-CONFIRM) ────────────────────────────────
|
|
# AWS/DynamoDB throttle metrics for the PaymentsDashboard table. These metrics
|
|
# emit at the TableName dimension and only on the occurrence of a throttle
|
|
# event — none are currently present in CloudWatch (the table is
|
|
# PAY_PER_REQUEST, so sustained throttling is unlikely but possible during
|
|
# burst-capacity ramp). SystemErrors is intentionally not alarmed: AWS/DynamoDB
|
|
# SystemErrors does not emit at the TableName-only dimension, so it can never
|
|
# fire. Threshold > 0, Sum over 5m, ALARM-only.
|
|
DashboardTableReadThrottleAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-dashboard-table-read-throttle
|
|
AlarmDescription: PaymentsDashboard table read requests throttled
|
|
Namespace: AWS/DynamoDB
|
|
MetricName: ReadThrottleEvents
|
|
Dimensions:
|
|
- Name: TableName
|
|
Value: !Ref DashboardTable
|
|
Statistic: Sum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 0
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
DashboardTableWriteThrottleAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-dashboard-table-write-throttle
|
|
AlarmDescription: PaymentsDashboard table write requests throttled
|
|
Namespace: AWS/DynamoDB
|
|
MetricName: WriteThrottleEvents
|
|
Dimensions:
|
|
- Name: TableName
|
|
Value: !Ref DashboardTable
|
|
Statistic: Sum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 0
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
# ── API Gateway (HTTP API v2) alarms (Wave 1, SCOPE-CONFIRM) ────────────────
|
|
# AWS/ApiGateway v2 metrics on the implicit ServerlessHttpApi (ApiId dim).
|
|
# v2 metric names are 4xx/5xx/Latency (not 4XXError/5XXError). 5xx and Latency
|
|
# alarm on the API itself; 4xx is mostly client-driven so its threshold is
|
|
# set above zero to avoid noise (Slack URL-verification / bad requests).
|
|
ApiGateway5xxAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-dashboard-api-5xx
|
|
AlarmDescription: payments-dashboard HTTP API returned 5xx responses
|
|
Namespace: AWS/ApiGateway
|
|
MetricName: 5xx
|
|
Dimensions:
|
|
- Name: ApiId
|
|
Value: !Ref ServerlessHttpApi
|
|
Statistic: Sum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 0
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
ApiGateway4xxAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-dashboard-api-4xx
|
|
AlarmDescription: payments-dashboard HTTP API elevated 4xx responses
|
|
Namespace: AWS/ApiGateway
|
|
MetricName: 4xx
|
|
Dimensions:
|
|
- Name: ApiId
|
|
Value: !Ref ServerlessHttpApi
|
|
Statistic: Sum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 10
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
ApiGatewayLatencyAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-dashboard-api-latency-p99
|
|
AlarmDescription: payments-dashboard HTTP API p99 latency elevated (>3s)
|
|
Namespace: AWS/ApiGateway
|
|
MetricName: Latency
|
|
Dimensions:
|
|
- Name: ApiId
|
|
Value: !Ref ServerlessHttpApi
|
|
ExtendedStatistic: p99
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 3000
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
# Messages-present alarms on the async-invoke OnFailure DLQs, mirroring
|
|
# PayrollBatchDLQAlarm. Threshold > 0 on the visible-message count, ALARM-only.
|
|
ProcessPaymentCsvDLQAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-processPaymentCsv-async-dlq-messages
|
|
AlarmDescription: Failed processPaymentCsv async invocations landed in the DLQ
|
|
Namespace: AWS/SQS
|
|
MetricName: ApproximateNumberOfMessagesVisible
|
|
Dimensions:
|
|
- Name: QueueName
|
|
Value: !GetAtt ProcessPaymentCsvDLQ.QueueName
|
|
Statistic: Maximum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 0
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
# ALARM-only notification by convention — no OK/recovery action
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
ProcessPayrollEmailDLQAlarm:
|
|
Type: AWS::CloudWatch::Alarm
|
|
Properties:
|
|
AlarmName: payments-processPayrollEmail-async-dlq-messages
|
|
AlarmDescription: Failed processPayrollEmail async invocations landed in the DLQ
|
|
Namespace: AWS/SQS
|
|
MetricName: ApproximateNumberOfMessagesVisible
|
|
Dimensions:
|
|
- Name: QueueName
|
|
Value: !GetAtt ProcessPayrollEmailDLQ.QueueName
|
|
Statistic: Maximum
|
|
Period: 300
|
|
EvaluationPeriods: 1
|
|
Threshold: 0
|
|
ComparisonOperator: GreaterThanThreshold
|
|
TreatMissingData: notBreaching
|
|
# ALARM-only notification by convention — no OK/recovery action
|
|
AlarmActions:
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
ProcessPayrollEmailLogGroup:
|
|
Type: AWS::Logs::LogGroup
|
|
Properties:
|
|
LogGroupName: /aws/lambda/payments-processPayrollEmail
|
|
RetentionInDays: 60
|
|
|
|
ProcessPaymentCsvLogGroup:
|
|
Type: AWS::Logs::LogGroup
|
|
Properties:
|
|
LogGroupName: /aws/lambda/payments-processPaymentCsv
|
|
RetentionInDays: 60
|
|
|
|
SlackAppHomeLogGroup:
|
|
Type: AWS::Logs::LogGroup
|
|
Properties:
|
|
LogGroupName: /aws/lambda/payments-slackAppHome
|
|
RetentionInDays: 60
|
|
|
|
FetchBoaTransactionsLogGroup:
|
|
Type: AWS::Logs::LogGroup
|
|
Properties:
|
|
LogGroupName: /aws/lambda/payments-fetchBoaTransactions
|
|
RetentionInDays: 60
|
|
|
|
ExpenseReceiverLogGroup:
|
|
Type: AWS::Logs::LogGroup
|
|
Properties:
|
|
LogGroupName: /aws/lambda/payments-expenseReceiver
|
|
RetentionInDays: 60
|
|
|
|
ExpenseProcessorLogGroup:
|
|
Type: AWS::Logs::LogGroup
|
|
Properties:
|
|
LogGroupName: /aws/lambda/payments-expenseProcessor
|
|
RetentionInDays: 60
|
|
|
|
ProcessPayrollEmailFunction:
|
|
Type: AWS::Serverless::Function
|
|
Properties:
|
|
FunctionName: payments-processPayrollEmail
|
|
Handler: src/processPayrollEmail.handler
|
|
Timeout: 60
|
|
EventInvokeConfig:
|
|
MaximumRetryAttempts: 2
|
|
MaximumEventAgeInSeconds: 21600
|
|
DestinationConfig:
|
|
OnFailure:
|
|
Type: SQS
|
|
Destination: !GetAtt ProcessPayrollEmailDLQ.Arn
|
|
Environment:
|
|
Variables:
|
|
SLACK_BOT_TOKEN_SECRET_NAME: payments-dashboard/slack-bot-token
|
|
PAYROLL_CHANNEL_ID: C0AV5RBMYKU
|
|
PAYROLL_BATCH_QUEUE_URL: !Ref PayrollBatchQueue
|
|
Events:
|
|
EmailReceived:
|
|
Type: S3
|
|
Properties:
|
|
Bucket: !Ref PayrollEmailBucket
|
|
Events: s3:ObjectCreated:*
|
|
Filter:
|
|
S3Key:
|
|
Rules:
|
|
- Name: prefix
|
|
Value: inbound/
|
|
PayrollBatch:
|
|
Type: SQS
|
|
Properties:
|
|
Queue: !GetAtt PayrollBatchQueue.Arn
|
|
BatchSize: 1
|
|
Policies:
|
|
- S3ReadPolicy:
|
|
BucketName: !Sub seahaven-payroll-emails-${AWS::AccountId}
|
|
- DynamoDBCrudPolicy:
|
|
TableName: !Ref DashboardTable
|
|
- Version: "2012-10-17"
|
|
Statement:
|
|
- Effect: Allow
|
|
Action:
|
|
- kms:Decrypt
|
|
- kms:GenerateDataKey
|
|
- kms:DescribeKey
|
|
Resource: !Ref DynamoDbCmkArn
|
|
- Version: "2012-10-17"
|
|
Statement:
|
|
- Effect: Allow
|
|
Action: secretsmanager:GetSecretValue
|
|
Resource: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:payments-dashboard/slack-bot-token-*
|
|
- SQSSendMessagePolicy:
|
|
QueueName: !GetAtt PayrollBatchQueue.QueueName
|
|
- SQSPollerPolicy:
|
|
QueueName: !GetAtt PayrollBatchQueue.QueueName
|
|
|
|
ProcessPaymentCsvFunction:
|
|
Type: AWS::Serverless::Function
|
|
Properties:
|
|
FunctionName: payments-processPaymentCsv
|
|
Handler: src/processPaymentCsv.handler
|
|
Timeout: 120
|
|
EventInvokeConfig:
|
|
MaximumRetryAttempts: 2
|
|
MaximumEventAgeInSeconds: 21600
|
|
DestinationConfig:
|
|
OnFailure:
|
|
Type: SQS
|
|
Destination: !GetAtt ProcessPaymentCsvDLQ.Arn
|
|
Environment:
|
|
Variables:
|
|
BOA_BASE_URL: https://api.bofa.com
|
|
BOA_CHECK_MGMT_SECRET_NAME: payments-dashboard/boa-check-mgmt
|
|
VpcConfig:
|
|
SubnetIds:
|
|
- !Ref PrivateSubnet
|
|
SecurityGroupIds:
|
|
- !Ref LambdaSecurityGroup
|
|
Events:
|
|
CsvUpload:
|
|
Type: S3
|
|
Properties:
|
|
Bucket: !Ref PaymentsCsvBucket
|
|
Events: s3:ObjectCreated:*
|
|
Filter:
|
|
S3Key:
|
|
Rules:
|
|
- Name: suffix
|
|
Value: .csv
|
|
Policies:
|
|
- S3ReadPolicy:
|
|
BucketName: !Sub seahaven-payments-csv-${AWS::AccountId}
|
|
- DynamoDBCrudPolicy:
|
|
TableName: !Ref DashboardTable
|
|
- Version: "2012-10-17"
|
|
Statement:
|
|
- Effect: Allow
|
|
Action:
|
|
- kms:Decrypt
|
|
- kms:GenerateDataKey
|
|
- kms:DescribeKey
|
|
Resource: !Ref DynamoDbCmkArn
|
|
- Version: "2012-10-17"
|
|
Statement:
|
|
- Effect: Allow
|
|
Action: secretsmanager:GetSecretValue
|
|
Resource: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:payments-dashboard/boa-check-mgmt-*
|
|
- Version: "2012-10-17"
|
|
Statement:
|
|
- Effect: Allow
|
|
Action:
|
|
- ec2:CreateNetworkInterface
|
|
- ec2:DescribeNetworkInterfaces
|
|
- ec2:DeleteNetworkInterface
|
|
Resource: "*"
|
|
|
|
SlackAppHomeFunction:
|
|
Type: AWS::Serverless::Function
|
|
Properties:
|
|
FunctionName: payments-slackAppHome
|
|
Handler: src/slackAppHome.handler
|
|
VpcConfig:
|
|
SubnetIds:
|
|
- !Ref PrivateSubnet
|
|
SecurityGroupIds:
|
|
- !Ref LambdaSecurityGroup
|
|
Environment:
|
|
Variables:
|
|
SLACK_BOT_TOKEN_SECRET_NAME: payments-dashboard/slack-bot-token
|
|
SLACK_SIGNING_SECRET_NAME: payments-dashboard/slack-signing-secret
|
|
Events:
|
|
SlackEvent:
|
|
Type: HttpApi
|
|
Properties:
|
|
Path: /slack/events
|
|
Method: POST
|
|
Policies:
|
|
- DynamoDBReadPolicy:
|
|
TableName: !Ref DashboardTable
|
|
- Version: "2012-10-17"
|
|
Statement:
|
|
- Effect: Allow
|
|
Action:
|
|
- kms:Decrypt
|
|
- kms:DescribeKey
|
|
Resource: !Ref DynamoDbCmkArn
|
|
- Version: "2012-10-17"
|
|
Statement:
|
|
- Effect: Allow
|
|
Action: secretsmanager:GetSecretValue
|
|
Resource:
|
|
- !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:payments-dashboard/slack-bot-token-*
|
|
- !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:payments-dashboard/slack-signing-secret-*
|
|
- Version: "2012-10-17"
|
|
Statement:
|
|
- Effect: Allow
|
|
Action:
|
|
- ec2:CreateNetworkInterface
|
|
- ec2:DescribeNetworkInterfaces
|
|
- ec2:DeleteNetworkInterface
|
|
Resource: "*"
|
|
|
|
FetchBoaTransactionsFunction:
|
|
Type: AWS::Serverless::Function
|
|
Properties:
|
|
FunctionName: payments-fetchBoaTransactions
|
|
Handler: src/fetchBoaTransactions.handler
|
|
Timeout: 60
|
|
VpcConfig:
|
|
SubnetIds:
|
|
- !Ref PrivateSubnet
|
|
SecurityGroupIds:
|
|
- !Ref LambdaSecurityGroup
|
|
Environment:
|
|
Variables:
|
|
BOA_BASE_URL: https://api.bofa.com
|
|
BOA_REPORTING_SECRET_NAME: payments-dashboard/boa-reporting
|
|
BOA_RAW_BUCKET: !Ref BoaRawBucket
|
|
Events:
|
|
DailySchedule:
|
|
Type: Schedule
|
|
Properties:
|
|
Schedule: cron(0 13 ? * MON-FRI *)
|
|
Description: Fetch BoA previous day transactions at 9am ET (13:00 UTC)
|
|
Enabled: true
|
|
# One rule for all intraday runs so they can be disabled as a unit
|
|
# (aws events disable-rule) without touching the authoritative 9am
|
|
# previous-day sweep. Fixed UTC: ~12/3/6pm ET in DST, 11/2/5pm in
|
|
# winter (accepted drift, documented in README).
|
|
IntradaySchedule:
|
|
Type: Schedule
|
|
Properties:
|
|
Schedule: cron(0 16,19,22 ? * MON-FRI *)
|
|
Description: Intraday BoA current-day sweep (~12pm/3pm/6pm ET)
|
|
Enabled: true
|
|
Input: '{"endpoint":"current-day"}'
|
|
Policies:
|
|
- DynamoDBCrudPolicy:
|
|
TableName: !Ref DashboardTable
|
|
- Version: "2012-10-17"
|
|
Statement:
|
|
# Archive writes only: no read, no list, no other principal.
|
|
# Derived from the bucket resource so a rename can't silently
|
|
# detach the grant.
|
|
- Effect: Allow
|
|
Action: s3:PutObject
|
|
Resource: !Sub "${BoaRawBucket.Arn}/*"
|
|
- Version: "2012-10-17"
|
|
Statement:
|
|
- Effect: Allow
|
|
Action:
|
|
- kms:Decrypt
|
|
- kms:GenerateDataKey
|
|
- kms:DescribeKey
|
|
Resource: !Ref DynamoDbCmkArn
|
|
- Version: "2012-10-17"
|
|
Statement:
|
|
- Effect: Allow
|
|
Action: secretsmanager:GetSecretValue
|
|
Resource: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:payments-dashboard/boa-reporting-*
|
|
- Version: "2012-10-17"
|
|
Statement:
|
|
- Effect: Allow
|
|
Action:
|
|
- ec2:CreateNetworkInterface
|
|
- ec2:DescribeNetworkInterfaces
|
|
- ec2:DeleteNetworkInterface
|
|
Resource: "*"
|
|
|
|
ExpenseProcessorFunction:
|
|
Type: AWS::Serverless::Function
|
|
Properties:
|
|
FunctionName: payments-expenseProcessor
|
|
Handler: src/expenseProcessor.handler
|
|
Timeout: 15
|
|
Environment:
|
|
Variables:
|
|
EXPENSE_BOT_TOKEN_SECRET_NAME: payments-dashboard/expense-slack-token
|
|
Policies:
|
|
- Version: "2012-10-17"
|
|
Statement:
|
|
- Effect: Allow
|
|
Action: secretsmanager:GetSecretValue
|
|
Resource: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:payments-dashboard/expense-slack-token-*
|
|
|
|
ExpenseReceiverFunction:
|
|
Type: AWS::Serverless::Function
|
|
Properties:
|
|
FunctionName: payments-expenseReceiver
|
|
Handler: src/expenseReceiver.handler
|
|
Timeout: 5
|
|
Environment:
|
|
Variables:
|
|
EXPENSE_PROCESSOR_FN: !Ref ExpenseProcessorFunction
|
|
EXPENSE_SIGNING_SECRET_NAME: payments-dashboard/expense-slack-signing-secret
|
|
Events:
|
|
ExpenseSlackEvent:
|
|
Type: HttpApi
|
|
Properties:
|
|
Path: /slack/expense-events
|
|
Method: POST
|
|
Policies:
|
|
- Version: "2012-10-17"
|
|
Statement:
|
|
- Effect: Allow
|
|
Action: lambda:InvokeFunction
|
|
Resource: !GetAtt ExpenseProcessorFunction.Arn
|
|
- Effect: Allow
|
|
Action: secretsmanager:GetSecretValue
|
|
Resource: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:payments-dashboard/expense-slack-signing-secret-*
|
|
|
|
Outputs:
|
|
SlackEventUrl:
|
|
Description: URL to set as the Slack app Request URL
|
|
Value: !Sub https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/slack/events
|
|
CsvBucket:
|
|
Description: S3 bucket for CSV uploads
|
|
Value: !Ref PaymentsCsvBucket
|
|
StaticOutboundIp:
|
|
Description: Static IP for BoA API whitelist
|
|
Value: !Ref NatEip
|
|
PayrollEmailBucket:
|
|
Description: S3 bucket for inbound payroll emails from SES
|
|
Value: !Ref PayrollEmailBucket
|
|
ExpenseSlackEventsUrl:
|
|
Description: URL for Expense Approval Bot Slack Event Subscriptions
|
|
Value: !Sub https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/slack/expense-events
|
|
ExpenseProcessorFunctionArn:
|
|
Description: Expense Processor Lambda ARN
|
|
Value: !GetAtt ExpenseProcessorFunction.Arn
|
|
ExpenseReceiverFunctionArn:
|
|
Description: Expense Receiver Lambda ARN
|
|
Value: !GetAtt ExpenseReceiverFunction.Arn
|