service: esdp-infra app: esdp-api-app frameworkVersion: '4' stages: shared: observability: false params: # Single shared database name for all app stages (dev/alpha/beta/prod) db_name: esdp # Your public IP in CIDR form for direct DB access (lock this down). admin_cidr: 70.36.57.88/32 provider: name: aws runtime: nodejs22.x region: ca-central-1 resources: Resources: EsdpVpc: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/16 EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: ${self:service}-${sls:stage}-vpc InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: ${self:service}-${sls:stage}-igw VpcGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: Ref: EsdpVpc InternetGatewayId: Ref: InternetGateway PublicSubnetA: Type: AWS::EC2::Subnet Properties: VpcId: Ref: EsdpVpc AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: '' CidrBlock: 10.0.0.0/24 MapPublicIpOnLaunch: true Tags: - Key: Name Value: ${self:service}-${sls:stage}-public-a PublicSubnetB: Type: AWS::EC2::Subnet Properties: VpcId: Ref: EsdpVpc AvailabilityZone: Fn::Select: - 1 - Fn::GetAZs: '' CidrBlock: 10.0.1.0/24 MapPublicIpOnLaunch: true Tags: - Key: Name Value: ${self:service}-${sls:stage}-public-b PrivateSubnetA: Type: AWS::EC2::Subnet Properties: VpcId: Ref: EsdpVpc AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: '' CidrBlock: 10.0.10.0/24 MapPublicIpOnLaunch: false Tags: - Key: Name Value: ${self:service}-${sls:stage}-private-a PrivateSubnetB: Type: AWS::EC2::Subnet Properties: VpcId: Ref: EsdpVpc AvailabilityZone: Fn::Select: - 1 - Fn::GetAZs: '' CidrBlock: 10.0.11.0/24 MapPublicIpOnLaunch: false Tags: - Key: Name Value: ${self:service}-${sls:stage}-private-b PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: Ref: EsdpVpc Tags: - Key: Name Value: ${self:service}-${sls:stage}-public-rt PublicRoute: Type: AWS::EC2::Route DependsOn: VpcGatewayAttachment Properties: RouteTableId: Ref: PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: Ref: InternetGateway PublicSubnetARouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: Ref: PublicSubnetA RouteTableId: Ref: PublicRouteTable PublicSubnetBRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: Ref: PublicSubnetB RouteTableId: Ref: PublicRouteTable NatEip: Type: AWS::EC2::EIP Properties: Domain: vpc NatGateway: Type: AWS::EC2::NatGateway Properties: AllocationId: Fn::GetAtt: - NatEip - AllocationId SubnetId: Ref: PublicSubnetA Tags: - Key: Name Value: ${self:service}-${sls:stage}-nat PrivateRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: Ref: EsdpVpc Tags: - Key: Name Value: ${self:service}-${sls:stage}-private-rt PrivateRoute: Type: AWS::EC2::Route Properties: RouteTableId: Ref: PrivateRouteTable DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: Ref: NatGateway PrivateSubnetARouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: Ref: PrivateSubnetA RouteTableId: Ref: PrivateRouteTable PrivateSubnetBRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: Ref: PrivateSubnetB RouteTableId: Ref: PrivateRouteTable LambdaSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: ${self:service}-${sls:stage} Lambda security group VpcId: Ref: EsdpVpc SecurityGroupEgress: - IpProtocol: -1 CidrIp: 0.0.0.0/0 Tags: - Key: Name Value: ${self:service}-${sls:stage}-lambda-sg ProxySecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: ${self:service}-${sls:stage} RDS proxy security group VpcId: Ref: EsdpVpc SecurityGroupIngress: - IpProtocol: tcp FromPort: 5432 ToPort: 5432 SourceSecurityGroupId: Ref: LambdaSecurityGroup - IpProtocol: tcp FromPort: 5432 ToPort: 5432 CidrIp: ${param:admin_cidr} SecurityGroupEgress: - IpProtocol: -1 CidrIp: 0.0.0.0/0 Tags: - Key: Name Value: ${self:service}-${sls:stage}-proxy-sg RdsSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: ${self:service}-${sls:stage} RDS security group VpcId: Ref: EsdpVpc SecurityGroupIngress: - IpProtocol: tcp FromPort: 5432 ToPort: 5432 SourceSecurityGroupId: Ref: ProxySecurityGroup - IpProtocol: tcp FromPort: 5432 ToPort: 5432 CidrIp: ${param:admin_cidr} Tags: - Key: Name Value: ${self:service}-${sls:stage}-rds-sg DbSubnetGroup: Type: AWS::RDS::DBSubnetGroup Properties: DBSubnetGroupDescription: ${self:service}-${sls:stage} DB subnet group SubnetIds: - Ref: PublicSubnetA - Ref: PublicSubnetB DbSecret: Type: AWS::SecretsManager::Secret Properties: Description: ${self:service}-${sls:stage} RDS master credentials GenerateSecretString: SecretStringTemplate: '{"username":"esdp_admin"}' GenerateStringKey: password PasswordLength: 32 ExcludeCharacters: '"@/\\' DbProxyRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - rds.amazonaws.com Action: - sts:AssumeRole Policies: - PolicyName: ${self:service}-${sls:stage}-db-proxy-secrets PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - secretsmanager:GetSecretValue Resource: - Ref: DbSecret DbProxy: Type: AWS::RDS::DBProxy Properties: DBProxyName: ${self:service}-${sls:stage}-proxy EngineFamily: POSTGRESQL IdleClientTimeout: 1800 RequireTLS: true RoleArn: Fn::GetAtt: - DbProxyRole - Arn VpcSubnetIds: - Ref: PrivateSubnetA - Ref: PrivateSubnetB VpcSecurityGroupIds: - Ref: ProxySecurityGroup Auth: - AuthScheme: SECRETS SecretArn: Ref: DbSecret IAMAuth: DISABLED DbProxyTargetGroup: Type: AWS::RDS::DBProxyTargetGroup Properties: DBProxyName: Ref: DbProxy TargetGroupName: default DBInstanceIdentifiers: - Ref: PostgresDb ConnectionPoolConfigurationInfo: MaxConnectionsPercent: 75 MaxIdleConnectionsPercent: 50 ConnectionBorrowTimeout: 120 PostgresDb: Type: AWS::RDS::DBInstance DeletionPolicy: Snapshot UpdateReplacePolicy: Snapshot Properties: DBInstanceIdentifier: ${self:service}-${sls:stage}-postgres Engine: postgres # EngineVersion intentionally omitted so AWS uses the current default/latest for RDS Postgres. AutoMinorVersionUpgrade: true DBInstanceClass: db.t4g.micro AllocatedStorage: 20 StorageType: gp3 StorageEncrypted: true PubliclyAccessible: true MultiAZ: false DBName: ${param:db_name} BackupRetentionPeriod: 7 CopyTagsToSnapshot: true DeletionProtection: true VPCSecurityGroups: - Ref: RdsSecurityGroup DBSubnetGroupName: Ref: DbSubnetGroup MasterUsername: Fn::Sub: '{{resolve:secretsmanager:${DbSecret}::username}}' MasterUserPassword: Fn::Sub: '{{resolve:secretsmanager:${DbSecret}::password}}' Outputs: VpcId: Value: Ref: EsdpVpc PrivateSubnetAId: Value: Ref: PrivateSubnetA PrivateSubnetBId: Value: Ref: PrivateSubnetB LambdaSecurityGroupId: Value: Ref: LambdaSecurityGroup ProxySecurityGroupId: Value: Ref: ProxySecurityGroup RdsSecurityGroupId: Value: Ref: RdsSecurityGroup DbName: Value: ${param:db_name} DbPort: Value: 5432 DbEndpointAddress: Value: Fn::GetAtt: - PostgresDb - Endpoint.Address DbEndpointPort: Value: Fn::GetAtt: - PostgresDb - Endpoint.Port DbSecretArn: Value: Ref: DbSecret DbProxyEndpoint: Value: Fn::GetAtt: - DbProxy - Endpoint