56

I am trying to connect to RDS Database from an AWS Lambda (Java).

Which IP should I enable from the RDS Security group rules?

Mark B
  • 183,023
  • 24
  • 297
  • 295
giò
  • 3,402
  • 7
  • 27
  • 54
  • 1
    As of 2020, if the RDS database in question is Aurora Serverless MySQL or PostgreSQL then I think you should be able to use the [data API](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html) from Lambda within VPC to RDS via VPC endpoint, so no need for NAT (or persistent database connections). – jarmod Sep 23 '21 at 17:11
  • 1
    As a follow-up after 4 years since the question was asked, were you able to use a better architecture that allowed you to use Lambda with RDS and internet? Like @ChrisO notes below, having to create an unnecessarily complex (and expensive) architecture just to follow security best practices is silly. – m01010011 Apr 11 '22 at 13:04

6 Answers6

89

You can't enable this via IP. First you will need to enable VPC access for the Lambda function, during which you will assign it a Security Group. Then, within the Security Group assigned to the RDS instance you will enable access for the Security Group assigned to the Lambda function.

Mark B
  • 183,023
  • 24
  • 297
  • 295
  • 35
    Since I need internet access from AWS Lambda too I will have to create a VPC with a subnet to a NAT Gateway... That's a lot of overhead just to connect to RDS – giò May 04 '16 at 14:34
  • 3
    You noted that your RDS instance isn't publicly available (although you removed that for some reason), so the only way to access it is via the VPC, which means you will also need to create a NAT gateway in your VPC to allow internet access for the Lambda function. – Mark B May 04 '16 at 14:36
  • 1
    I am sorry, RDS IS publicy accessible but it is behind security group rules. I can access RDS from my home pc since I have added my IP to the security group. But I can't access from AWS Lambda since If I don't put it inside a VPC I can't have a security group – giò May 04 '16 at 14:37
  • You still won't know what the IP of the Lambda instance is. You could enable all the IPs AWS uses http://docs.aws.amazon.com/general/latest/gr/aws-ip-ranges.html, and constantly keep that list updated as those IPs change. That's a difficult solution and would allow other AWS users to try to login to your database server. The other option is to open the RDS instance to all IPs, but that isn't a very good solution either. – Mark B May 04 '16 at 14:40
  • 2
    All right, so only solution is to create a VPC subnet just for this AWS Lambda, assign it a NAT Gateway (that costs) and have a security group that is enabled from RDS – giò May 04 '16 at 14:42
  • 1
    even with Nat Gateway I had no luck to connect to internet http://stackoverflow.com/questions/37135725/aws-lambda-connecting-to-internet I think that's a Bug – giò May 10 '16 at 10:24
  • 10
    This is so silly. You essentially need to pay for a NAT gateway so that you can follow security best practices. I can't afford a NAT gateway so I need to open my RDS to the entire internet so my lambda function can use it. – ChrisO Nov 25 '19 at 00:04
  • what if my rds in vpc is in another account and i want to access it in another account in lambda? – Tayyab Jun 23 '21 at 06:21
  • @ChrisO, agreed. This is unnecessarily complex and expensive just to follow best practices. However, since it's been 2+ years since you posted that comment, were you able to use a better architecture that allowed you to use Lambda with RDS and internet? – m01010011 Apr 11 '22 at 13:09
  • 2
    @m01010011 There is no "better" architecture - the status quo remains the same. – Ermiya Eskandary May 02 '22 at 04:14
  • Personally I use a NAT instance just a nano EC2 - $3.30/m instead of a NAT gateway it doubles as my bastion to ssh tunnel onto RDS from my desktop client app (eg DBeaver) – Leigh Mathieson May 11 '22 at 22:05
19

You can configure Lambda to access your RDS instance.

You can enable this using Lambda management console. Select Lambda function which need access to RDS instance and then go to Configuration -> Advanced settings and select the VPC (which is your RDS instance is in) you need it to access.

find out more here http://docs.aws.amazon.com/lambda/latest/dg/vpc.html

Naween Niroshan
  • 558
  • 4
  • 8
12

For anyone else searching for a more detailed solution, or lambda config provisioned via AWS SAM / Cloudformation, what worked for me was:

i. create a Security Group (SG) allowing outbound traffic on the desired port you'd like to connect over (eg: 5432 or 3306. Note, inbound rules have no affect on lambda I believe, currently) Apply that SG to your lambda.

ii. create an SG allowing inbound traffic on the same port (say 5432 or 3306) which references the lambda SG, so traffic is locked down to only the lambda. And outbound on the same port (5432 or 3306). Apply that SG to your RDS instance.

Further detail:

Lambda SG:

Direction    Protocol    Port     Source
Outbound     TCP         5432     ALL

RDS SG:

Direction    Protocol    Port     Source
Inbound      TCP         5432     Lambda SG
Outbound     TCP         5432     ALL

SAM template.yaml to provision the main resources you'll probably require including: an RDS cluster (Aurora Postgres serverless to minimise running costs is shown in this example), a Postgres master user password stored in secrets manager, a lambda, an SG that is applied to the lambda allowing outbound traffic on port 5432, an SG that is applied to the RDS cluster referencing the lambda SG (locking down traffic to the lambda) and I have also shown optionally how you may wish to connect to the RDS from your local desktop machine using a desktop DB client (eg DBeaver) over SSH tunnel via a bastion (eg a nano EC2 instance with an EIP attached so it can be stopped and all config remain the same) to admin the RDS from your local machine.

(Please note for a production system you may wish to provision your RDS into a private subnet for security. Provisioning of subnets not covered here for brevity. Also please note for a production system passing a secure secret as an env variable is not best practice the lambda should really resolve the secret each time - however shown passed as an env var for brevity)

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Provisions stack with Aurora Serverless

Parameters:
  AppName:
    Description: "Application Name"
    Type: String
    Default: RDS-example-stack
  DBClusterName:
    Description: "Aurora RDS cluster name"
    Type: String
    Default: rdsexamplecluster
  DatabaseName:
    Description: "Aurora RDS database name"
    Type: String
    Default: examplerdsdbname
  DBMasterUserName:
    AllowedPattern: "[a-zA-Z0-9_]+"
    ConstraintDescription: must be between 1 to 16 alphanumeric characters.
    Description: The database admin account user name, between 1 to 16 alphanumeric characters.
    MaxLength: '16'
    MinLength: '1'
    Type: String
    Default: aurora_admin_0

Resources:
  # lambdas
  someLambda:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub '${AWS::StackName}-someLambda'
      # Role: !GetAtt ExecutionRole.Arn # if you require a custom execution role and permissions
      VpcConfig:
        SubnetIds: [subnet-90f79cd8, subnet-9743e6cd, subnet-8bf962ed]
        SecurityGroupIds: [!Ref lambdaOutboundSGToRDS]
      Handler: index.handler
      CodeUri: ./dist/someLambda
      Runtime: nodejs14.x
      Timeout: 5 # ensure matches your PG/ mySQL connection pool timeout
      ReservedConcurrentExecutions: 5
      MemorySize: 128
      Environment: # optional env vars useful for your DB connection
        Variables:
          pgDb: !Ref DatabaseName
          # dbUser: '{{resolve:secretsmanager:some-stackName-AuroraDBCreds:SecretString:username}}'
          # dbPw: '{{resolve:secretsmanager:some-stackName-AuroraDBCreds:SecretString:password}}'

  # SGs
  lambdaOutboundSGToRDS: # Outbound access for lambda to access Aurora Postgres DB
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: !Sub ${AWS::StackName} access to Aurora PG DB
      GroupName: !Sub ${AWS::StackName} lambda to Aurora access
      SecurityGroupEgress: 
        -
          CidrIp: '0.0.0.0/0'
          Description: lambda to Aurora access over 5432
          FromPort: 5432
          IpProtocol: TCP
          ToPort: 5432
      VpcId: vpc-f6c4ea91

  RDSSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: !Sub ${AWS::StackName} RDS ingress and egress
      SecurityGroupEgress: 
        -
          CidrIp: '0.0.0.0/0'
          Description: lambda RDS access over 5432
          FromPort: 5432
          IpProtocol: TCP
          ToPort: 5432
      SecurityGroupIngress: 
        -
          SourceSecurityGroupId: !Ref lambdaOutboundSGToRDS # ingress SG for lambda to access RDS
          Description: lambda to Aurora access over 5432
          FromPort: 5432
          IpProtocol: TCP
          ToPort: 5432
        - # optional
          CidrIp: '172.12.34.217/32' # private IP of your EIP/ bastion instance the EIP is assigned to. /32 ie a single IP address
          Description: EC2 bastion host providing access to Aurora RDS via SSH tunnel for DBeaver desktop access over 5432
          FromPort: 5432
          IpProtocol: TCP
          ToPort: 5432
      VpcId: vpc-f6c4ea91

  DBSubnetGroup: # just a logical grouping of subnets that you can apply as a group to your RDS
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: CloudFormation managed DB subnet group.
      SubnetIds:
        - subnet-80f79cd8
        - subnet-8743e6cd
        - subnet-9bf962ed

  AuroraDBCreds: # provisions a password for the DB master username, which we set in Parameters
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: !Sub ${AWS::StackName}-AuroraDBCreds
      Description: RDS database auto-generated user password
      GenerateSecretString:
        SecretStringTemplate: !Sub '{"username": "${DBMasterUserName}"}'
        GenerateStringKey: "password"
        PasswordLength: 30
        ExcludeCharacters: '"@/\'
      Tags:
        -
          Key: AppName
          Value: !Ref AppName

  RDSCluster:
    Type: AWS::RDS::DBCluster
    Properties:
      DBClusterIdentifier: !Ref DBClusterName
      MasterUsername: !Join ['', ['{{resolve:secretsmanager:', !Ref AuroraDBCreds, ':SecretString:username}}' ]]
      MasterUserPassword: !Join ['', ['{{resolve:secretsmanager:', !Ref AuroraDBCreds, ':SecretString:password}}' ]]
      DatabaseName: !Ref DatabaseName
      Engine: aurora-postgresql
      EngineMode: serverless
      EngineVersion: '10' # currently provisions '10.serverless_14' 10.14
      EnableHttpEndpoint: true
      ScalingConfiguration:
        AutoPause: true
        MaxCapacity: 2
        MinCapacity: 2
        SecondsUntilAutoPause: 300 # 5 min
      DBSubnetGroupName:
        Ref: DBSubnetGroup
      VpcSecurityGroupIds:
        - !Ref RDSSG

# optional outputs useful for importing into another stack or viewing in the terminal on deploy
Outputs:
  StackName:
    Description: Aurora Stack Name
    Value: !Ref AWS::StackName
    Export:
      Name: !Sub ${AWS::StackName}-StackName

  DatabaseName:
    Description: Aurora Database Name
    Value: !Ref DatabaseName
    Export:
      Name: !Sub ${AWS::StackName}-DatabaseName

  DatabaseClusterArn:
    Description: Aurora Cluster ARN
    Value: !Sub arn:aws:rds:${AWS::Region}:${AWS::AccountId}:cluster:${DBClusterName}
    Export:
      Name: !Sub ${AWS::StackName}-DatabaseClusterArn

  DatabaseSecretArn:
    Description: Aurora Secret ARN
    Value: !Ref AuroraDBCreds
    Export:
      Name: !Sub ${AWS::StackName}-DatabaseSecretArn

  DatabaseClusterID:
    Description: Aurora Cluster ID
    Value: !Ref RDSCluster
    Export:
      Name: !Sub ${AWS::StackName}-DatabaseClusterID

  AuroraDbURL:
    Description: Aurora Database URL
    Value: !GetAtt RDSCluster.Endpoint.Address
    Export:
      Name: !Sub ${AWS::StackName}-DatabaseURL

  DatabaseMasterUserName:
    Description: Aurora Database User
    Value: !Ref DBMasterUserName
    Export:
      Name: !Sub ${AWS::StackName}-DatabaseMasterUserName
Leigh Mathieson
  • 1,658
  • 2
  • 17
  • 25
  • Could you share the template for the RDS please ? – kuvic Mar 03 '22 at 14:45
  • Hi @kuvic yes I can - for a full production grade solution you really need an understanding of public vs private subnets (and provision your RDS into a private subnet for security) you'll perhaps like to also provision an RDS master username & password and resolve those on deployment via your template.yaml, you'd possibly also want an EC2 instance that you can use as a bastion and ssh tunnel onto your RDS so you can make adhoc changes to your RDS by a local desktop client (like DBeaver) (personally I use a nano with an EIP attached, so you only need to set ssh tunnel config once in DBeaver) – Leigh Mathieson Mar 05 '22 at 15:51
  • No one should use this template as a reference because you store your DB secrets openly as environment variables and anyone who has even read-only access to Lambda will be able to see them and get access to your DB. – Daniil Aug 11 '22 at 12:32
  • Thanks a lot.. it helped me (I was stuck for 5-6 hours :D ) – Ganesh Patil Mar 29 '23 at 12:05
3

Here is what I did

I assigned same Subnets and VPCs to both services Lambda and RDS. Now I created a NAT Gateway choosing one of the subnet so that Lambda can use that NAT Gateway to interact with the outside world.

Last thing is to add inbound entry in the security group that is attached to RDS as well as Lambda functions. Whitelist DB port 5432 in my case for postgresql and add security group name in the source.

Security group is somehow whitelisting itself by adding an entry in inbound rules.

This worked for me pretty well.

  • How are you dealing with NAT Gateway's cost? Does it stay provisioned for the entire month? Do you have a script that provisions NAT Gateway just before a Lambda is invoked and then deleted immediately afterwards? – m01010011 Apr 11 '22 at 13:07
1

The recommended way is still (1) VPC and data-api, however you can also go with (2) which is RDS proxy (https://aws.amazon.com/blogs/compute/using-amazon-rds-proxy-with-aws-lambda/) that supports both MySQL and PostgreSQL since June 30 2020.

PitBoss
  • 56
  • 5
0

You don't need to use IP.

I assume that your RDS is in private subnet of the VPC. This means that your lambda should also be in VPC to communicate with database.

Let's assume that the credentials for your RDS is in secret manager. You can give necessary permission to the secret so that your lambda can access the decrypt secret within lambda function.

Add proper ingress rule to the database. Make sure your security groups are configured properly. You can also use RDS proxy to re-use the db connections to give better performance.

This post talks about how to communicate to RDS from lambda https://www.freecodecamp.org/news/aws-lambda-rds/