Skip to content

Instantly share code, notes, and snippets.

@revmischa
Last active April 11, 2019 10:21
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save revmischa/38c77e225e7334ceffbca74ba779b3c3 to your computer and use it in GitHub Desktop.
Save revmischa/38c77e225e7334ceffbca74ba779b3c3 to your computer and use it in GitHub Desktop.
Serverless CloudFormation to provide private RDS and lambda networking with internet access in VPC.
plugins:
- serverless-pseudo-parameters
provider:
name: aws
runtime: python3.7
stage: ${opt:stage, 'dev'}
environment:
SQLALCHEMY_DATABASE_URI: # DB DSN
"Fn::Join": ['', ['postgresql://', {"Fn::Join": ['', ['{{resolve:secretsmanager:', !Ref RDSInstanceSecret, ':SecretString:username}}']]}, ':', {"Fn::Join": ['', ['{{resolve:secretsmanager:', !Ref RDSInstanceSecret, ':SecretString:password}}']]}, '@',{ "Fn::Join": [":", ["Fn::GetAtt": [ServerlessRDSCluster, Endpoint.Address], "Fn::GetAtt": [ServerlessRDSCluster, Endpoint.Port]]] }, "/${self:custom.db.dbname}"]]
resources:
Resources:
ServerlessVPC:
Type: AWS::EC2::VPC
Properties:
Tags:
- Key: "Name"
Value: "#{AWS::StackName}"
EnableDnsSupport: true # disable for production
EnableDnsHostnames: true # disable for production
CidrBlock: "10.0.0.0/16"
ServerlessSubnetA:
DependsOn: ServerlessVPC
Type: AWS::EC2::Subnet
Properties:
Tags:
- Key: Name
Value: "#{AWS::StackName} Lambda"
VpcId:
Ref: ServerlessVPC
AvailabilityZone: ${self:provider.region}a
CidrBlock: "10.0.3.0/24"
ServerlessSubnetB:
DependsOn: ServerlessVPC
Type: AWS::EC2::Subnet
Properties:
Tags:
- Key: Name
Value: "#{AWS::StackName} Lambda"
VpcId:
Ref: ServerlessVPC
AvailabilityZone: ${self:provider.region}b
CidrBlock: "10.0.1.0/24"
ServerlessSubnetC:
DependsOn: ServerlessVPC
Type: AWS::EC2::Subnet
Properties:
Tags:
- Key: Name
Value: "#{AWS::StackName} Lambda"
VpcId:
Ref: ServerlessVPC
AvailabilityZone: ${self:provider.region}c
CidrBlock: "10.0.2.0/24"
DBSubnetA:
DependsOn: ServerlessVPC
Type: AWS::EC2::Subnet
Properties:
Tags:
- Key: Name
Value: "#{AWS::StackName} DB"
VpcId:
Ref: ServerlessVPC
AvailabilityZone: ${self:provider.region}a
CidrBlock: "10.0.6.0/24"
DBSubnetB:
DependsOn: ServerlessVPC
Type: AWS::EC2::Subnet
Properties:
Tags:
- Key: Name
Value: "#{AWS::StackName} DB"
VpcId:
Ref: ServerlessVPC
AvailabilityZone: ${self:provider.region}b
CidrBlock: "10.0.7.0/24"
DBSubnetC:
DependsOn: ServerlessVPC
Type: AWS::EC2::Subnet
Properties:
Tags:
- Key: Name
Value: "#{AWS::StackName} DB"
VpcId:
Ref: ServerlessVPC
AvailabilityZone: ${self:provider.region}c
CidrBlock: "10.0.8.0/24"
ServerlessSubnetPublic:
DependsOn: ServerlessVPC
Type: AWS::EC2::Subnet
Properties:
VpcId:
Ref: ServerlessVPC
AvailabilityZone: ${self:provider.region}a
CidrBlock: "10.0.10.0/24"
ServerlessSecurityGroup:
DependsOn: ServerlessVPC
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: SecurityGroup for Serverless Functions
VpcId:
Ref: ServerlessVPC
ServerlessStorageSecurityGroup:
DependsOn: ServerlessVPC
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Ingress for RDS Instance
VpcId:
Ref: ServerlessVPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '5432'
ToPort: '5432'
SourceSecurityGroupId:
Ref: ServerlessSecurityGroup
ServerlessPrivateSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: "NAT-ed"
SubnetIds:
- Ref: ServerlessSubnetA
- Ref: ServerlessSubnetB
- Ref: ServerlessSubnetC
DBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: "Database"
SubnetIds:
- Ref: DBSubnetA
- Ref: DBSubnetB
- Ref: DBSubnetC
NATIP: # IP for public NAT
Type: AWS::EC2::EIP
Properties:
Domain: vpc
NatGateway: # NAT GW in public subnet
Type: AWS::EC2::NatGateway
DependsOn:
- NATIP
- ServerlessSubnetPublic
Properties:
AllocationId: !GetAtt NATIP.AllocationId
SubnetId: !Ref ServerlessSubnetPublic
PrivateRouteTable: # Route table for internal routing
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref ServerlessVPC
Tags:
- Key: Name
Value: "#{AWS::StackName} Private"
DefaultPrivateRoute: # Route to provide internet access for private subnet (TODO: provide only for lambda, not RDS etc)
Type: AWS::EC2::Route
DependsOn:
- NatGateway
- PrivateRouteTable
Properties:
RouteTableId: !Ref PrivateRouteTable
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway
SubARouteTableAssociation: # link private subnets to PrivateRouteTable
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable
SubnetId: !Ref ServerlessSubnetA
SubBRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable
SubnetId: !Ref ServerlessSubnetB
SubCRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable
SubnetId: !Ref ServerlessSubnetC
DBARouteTableAssociation: # link DB subnets to routes
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable # change to private to wall off from internet
SubnetId: !Ref DBSubnetA
DBBRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable # see above
SubnetId: !Ref DBSubnetB
DBCRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref DBSubnetC
IGW: # Internet GateWay
Type: AWS::EC2::InternetGateway
ServerlessIGWAttachment: # Connect IGW to Serverless VPC
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref IGW
VpcId: !Ref ServerlessVPC
PublicRouteTable: # Route table for routing to internet
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref ServerlessVPC
Tags:
- Key: Name
Value: "#{AWS::StackName} Public"
DefaultPublicRoute: # Route to provide internet access from NAT to IGW
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref IGW
PublicRouteTableAssociation: # connect public subnet (with NAT GW) to IGW
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref ServerlessSubnetPublic
# DB
ServerlessRDSCluster:
DependsOn: ServerlessStorageSecurityGroup
Type: AWS::RDS::DBInstance
Properties:
DeletionProtection: false # enable this for production!
PubliclyAccessible: true # disable this for production?
Engine: Postgres
DBName: ${self:custom.db.dbname}
MasterUsername: !Join ['', ['{{resolve:secretsmanager:', !Ref RDSInstanceSecret, ':SecretString:username}}' ]]
MasterUserPassword: !Join ['', ['{{resolve:secretsmanager:', !Ref RDSInstanceSecret, ':SecretString:password}}' ]]
DBInstanceClass: ${self:custom.db.instance_class}
AllocatedStorage: 10
VPCSecurityGroups:
- "Fn::GetAtt": ServerlessStorageSecurityGroup.GroupId
DBSubnetGroupName: !Ref DBSubnetGroup
# DB login secret
RDSInstanceSecret:
Type: AWS::SecretsManager::Secret
Properties:
Description: 'RDS master admin'
GenerateSecretString:
SecretStringTemplate: '{"username": "dbadmin"}'
GenerateStringKey: 'password'
PasswordLength: 16
ExcludeCharacters: '"@/\%'
SecretRDSInstanceAttachment:
Type: AWS::SecretsManager::SecretTargetAttachment
Properties:
SecretId:
Ref: RDSInstanceSecret
TargetId:
Ref: ServerlessRDSCluster
TargetType: AWS::RDS::DBInstance
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment