AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: Payments Dashboard - S3 CSV ingestion to Slack App Home Globals: Function: Runtime: nodejs20.x Timeout: 30 MemorySize: 256 Environment: Variables: TABLE_NAME: !Ref DashboardTable Resources: # 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 PublicSubnetB: Type: AWS::EC2::Subnet Properties: VpcId: !Ref Vpc CidrBlock: 10.20.3.0/24 AvailabilityZone: !Select [1, !GetAZs ""] MapPublicIpOnLaunch: true Tags: - Key: Name Value: payments-dashboard-public-b PublicSubnetBRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnetB 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 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 # Aurora Serverless v2 PostgreSQL (Dataddo → payroll data) AuroraSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Aurora PostgreSQL access VpcId: !Ref Vpc SecurityGroupIngress: - IpProtocol: tcp FromPort: 5432 ToPort: 5432 SourceSecurityGroupId: !Ref LambdaSecurityGroup - IpProtocol: tcp FromPort: 5432 ToPort: 5432 CidrIp: 0.0.0.0/0 AuroraSubnetGroup: Type: AWS::RDS::DBSubnetGroup Properties: DBSubnetGroupDescription: Public subnets for Aurora PostgreSQL SubnetIds: - !Ref PublicSubnet - !Ref PublicSubnetB AuroraCluster: Type: AWS::RDS::DBCluster Properties: Engine: aurora-postgresql EngineVersion: "16.4" DatabaseName: payroll MasterUsername: payroll_admin ManageMasterUserPassword: true ServerlessV2ScalingConfiguration: MinCapacity: 0.5 MaxCapacity: 2 VpcSecurityGroupIds: - !Ref AuroraSecurityGroup DBSubnetGroupName: !Ref AuroraSubnetGroup EnableHttpEndpoint: true StorageEncrypted: true AuroraInstance: Type: AWS::RDS::DBInstance Properties: DBClusterIdentifier: !Ref AuroraCluster DBInstanceClass: db.serverless Engine: aurora-postgresql PubliclyAccessible: true PaymentsCsvBucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub seahaven-payments-csv-${AWS::AccountId} 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 ProcessPaymentCsvFunction: Type: AWS::Serverless::Function Properties: FunctionName: payments-processPaymentCsv Handler: src/processPaymentCsv.handler Timeout: 120 Environment: Variables: BOA_BASE_URL: https://api.bofa.com BOA_CHECK_MGMT_APP_ID_PARAM: /payments-dashboard/boa-check-mgmt-app-id BOA_CHECK_MGMT_CLIENT_ID_PARAM: /payments-dashboard/boa-check-mgmt-client-id BOA_CHECK_MGMT_SECRET_PARAM: /payments-dashboard/boa-check-mgmt-token BOA_ACCOUNT_NUMBER_PARAM: /payments-dashboard/boa-account-number BOA_COMPANY_ID_PARAM: /payments-dashboard/boa-company-id 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 - SSMParameterReadPolicy: ParameterName: payments-dashboard/boa-check-mgmt-app-id - SSMParameterReadPolicy: ParameterName: payments-dashboard/boa-check-mgmt-client-id - SSMParameterReadPolicy: ParameterName: payments-dashboard/boa-check-mgmt-token - SSMParameterReadPolicy: ParameterName: payments-dashboard/boa-account-number - SSMParameterReadPolicy: ParameterName: payments-dashboard/boa-company-id - 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_PARAM: /payments-dashboard/slack-bot-token Events: SlackEvent: Type: HttpApi Properties: Path: /slack/events Method: POST Policies: - DynamoDBReadPolicy: TableName: !Ref DashboardTable - SSMParameterReadPolicy: ParameterName: payments-dashboard/slack-bot-token - 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_APP_ID_PARAM: /payments-dashboard/boa-reporting-app-id BOA_REPORTING_CLIENT_ID_PARAM: /payments-dashboard/boa-account-info-client-id BOA_REPORTING_SECRET_PARAM: /payments-dashboard/boa-account-info-token BOA_ACCOUNT_NUMBER_PARAM: /payments-dashboard/boa-account-number BOA_BANK_ID_PARAM: /payments-dashboard/boa-bank-id 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 Policies: - DynamoDBCrudPolicy: TableName: !Ref DashboardTable - SSMParameterReadPolicy: ParameterName: payments-dashboard/boa-reporting-app-id - SSMParameterReadPolicy: ParameterName: payments-dashboard/boa-account-info-client-id - SSMParameterReadPolicy: ParameterName: payments-dashboard/boa-account-info-token - SSMParameterReadPolicy: ParameterName: payments-dashboard/boa-account-number - SSMParameterReadPolicy: ParameterName: payments-dashboard/boa-bank-id - Version: "2012-10-17" Statement: - Effect: Allow Action: - ec2:CreateNetworkInterface - ec2:DescribeNetworkInterfaces - ec2:DeleteNetworkInterface Resource: "*" NotifyPayrollFunction: Type: AWS::Serverless::Function Properties: FunctionName: payments-notifyPayroll Handler: src/notifyPayroll.handler Timeout: 60 VpcConfig: SubnetIds: - !Ref PrivateSubnet SecurityGroupIds: - !Ref LambdaSecurityGroup Environment: Variables: AURORA_CLUSTER_ARN: !GetAtt AuroraCluster.DBClusterArn AURORA_SECRET_ARN: !GetAtt AuroraCluster.MasterUserSecret.SecretArn SLACK_BOT_TOKEN_PARAM: /payments-dashboard/slack-bot-token Events: PayrollCheck: Type: Schedule Properties: Schedule: cron(0 18 ? * MON-FRI *) Description: Check for new payroll data at 2pm ET (18:00 UTC) Enabled: true Policies: - DynamoDBCrudPolicy: TableName: !Ref DashboardTable - SSMParameterReadPolicy: ParameterName: payments-dashboard/slack-bot-token - Version: "2012-10-17" Statement: - Effect: Allow Action: - ec2:CreateNetworkInterface - ec2:DescribeNetworkInterfaces - ec2:DeleteNetworkInterface Resource: "*" - Effect: Allow Action: - rds-data:ExecuteStatement Resource: !GetAtt AuroraCluster.DBClusterArn - Effect: Allow Action: - secretsmanager:GetSecretValue Resource: !GetAtt AuroraCluster.MasterUserSecret.SecretArn 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 AuroraEndpoint: Description: Aurora PostgreSQL cluster endpoint Value: !GetAtt AuroraCluster.Endpoint.Address AuroraPort: Description: Aurora PostgreSQL port Value: !GetAtt AuroraCluster.Endpoint.Port AuroraSecretArn: Description: Secrets Manager ARN for Aurora master credentials Value: !GetAtt AuroraCluster.MasterUserSecret.SecretArn