允许AWS Lambda访问RDS数据库。

56

我正在尝试从AWS Lambda(Java)连接到RDS数据库。

我应该在RDS安全组规则中启用哪个IP地址?


1
截至2020年,如果所涉及的RDS数据库是Aurora Serverless MySQL或PostgreSQL,则我认为您应该能够通过VPC内的Lambda使用data API从VPC端点连接到RDS,因此无需NAT(或持久性数据库连接)。 - jarmod
1
作为问题提出后4年的跟进,您是否能够使用更好的架构来允许Lambda与RDS和互联网一起使用?正如@ChrisO在下面指出的那样,不得不创建一个不必要复杂(且昂贵)的架构只是为了遵循安全最佳实践是愚蠢的。 - m01010011
6个回答

90

您无法通过IP启用此功能。首先,您需要为Lambda函数启用VPC访问权限,在此过程中,您将为其分配安全组。然后,在分配给RDS实例的安全组中,您将启用对Lambda函数指定的安全组的访问权限。


36
由于我需要从AWS Lambda访问互联网,所以我将不得不创建一个VPC和一个子网,再连接到NAT网关... 这会增加很多工作量,只是为了连接到RDS。 - giò
3
您提到您的RDS实例不对公众开放(尽管您为某些原因将其删除),因此唯一访问它的方式是通过VPC,这意味着您还需要在VPC中创建NAT网关,以允许Lambda函数访问互联网。 - Mark B
2
好的,所以唯一的解决方案是为这个AWS Lambda创建一个VPC子网,分配一个NAT网关(需要费用),并拥有一个从RDS启用的安全组。 - giò
11
这太荒谬了。基本上,您需要支付一个NAT网关才能遵循安全最佳实践。我负担不起NAT网关,所以必须将我的RDS开放给整个互联网,这样我的Lambda函数才能使用它。 - ChrisO
2
@m01010011 没有“更好”的架构 - 现状仍然如此。 - Ermiya Eskandary
显示剩余6条评论

19

您可以配置Lambda访问您的RDS实例。

您可以通过Lambda管理控制台启用此功能。 选择需要访问RDS实例的Lambda函数,然后转到“配置” - >“高级设置”,并选择您需要访问它的VPC(即您的RDS实例所在的VPC)。

了解更多信息,请访问http://docs.aws.amazon.com/lambda/latest/dg/vpc.html


如果我的VPC中的RDS在另一个账户中,我想在Lambda中访问它怎么办? - Tayyab
1
这很不错,但如果您需要进行API调用,您将会遇到一些问题。 - Manza

13

如果其他人正在寻找更详细的解决方案,或者通过AWS SAM / Cloudformation进行配置的lambda,对我有用的是:

i. 创建一个安全组(SG),允许在您希望连接的特定端口上出站流量(例如:5432或3306。注意,我认为入站规则对lambda没有影响,目前)将该SG应用于您的lambda。

ii. 创建一个允许在相同端口(如5432或3306)上入站流量并引用lambda SG的SG,以便流量仅限于lambda,并在同一端口(5432或3306)上出站。将该SG应用于您的RDS实例。

进一步详细信息:

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模板.yaml用于配置主要资源,包括:一个RDS集群(此示例中显示了用Aurora Postgres无服务器来最小化运行成本),存储在Secrets Manager中的Postgres主用户密码,一个Lambda函数,将应用于Lambda的SG允许在5432端口上进行出站流量,将应用于RDS集群的SG引用Lambda SG(限制流量到Lambda),我还展示了您可能希望如何使用桌面数据库客户端(例如DBeaver)通过SSH隧道连接到RDS,并通过堡垒机(例如附有EIP的nano EC2实例,以便可以停止并保持所有配置不变)从本地计算机管理RDS。

(请注意,对于生产系统,您可能希望将RDS配置在私有子网中以提高安全性。为简洁起见,在此未介绍子网的配置。另请注意,对于生产系统,作为环境变量传递安全秘密并不是最佳做法,Lambda应该每次解析秘密 - 但由于简洁起见,显示已作为env var传递)

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

请问您能分享一下 RDS 的模板吗? - kuvic
嗨@kuvic,是的我可以帮忙。对于一个完整的生产级解决方案,您真的需要了解公共子网与私有子网之间的区别(并将您的RDS配置为私有子网以提高安全性)。您可能还要设置一个RDS主用户名和密码,并通过template.yaml来解析这些信息。您可能还需要一个EC2实例,它可以用作堡垒并通过ssh隧道连接到RDS,以便您可以使用本地桌面客户端(如DBeaver)对RDS进行临时更改(个人使用的是附加EIP的nano,因此您只需在DBeaver中设置一次ssh隧道配置即可)。 - Leigh Mathieson
没有人应该将这个模板作为参考,因为你将数据库的机密信息存储在环境变量中,任何拥有Lambda只读访问权限的人都能够看到它们并获得对你的数据库的访问权限。 - Daniil
非常感谢,它帮助了我(我卡了5-6个小时:D) - Ganesh Patil

3

这是我所做的:

我将相同的子网和虚拟私有云(VPC)分配给Lambda服务和RDS服务。现在,我创建了一个NAT网关,并选择了其中一个子网,以便Lambda可以使用该NAT网关与外部世界进行交互。

最后一件事是在附加到RDS及Lambda函数的安全组中添加入站条目。在我的情况下,将DB端口5432列入白名单,同时在源中添加安全组名称。

通过在入站规则中添加条目,安全组本身也被列入白名单。

这对我来说很有效。


你是如何处理NAT网关的费用的?它是否在整个月内保持预配状态?你是否有一个脚本,在Lambda被调用之前立即预配NAT网关,然后立即删除它? - m01010011

1

0

您不需要使用IP。

我假设您的RDS位于VPC的私有子网中。这意味着您的Lambda函数也应该在VPC中,以便与数据库通信。

假设您的RDS凭据存储在Secret Manager中。您可以授予必要的权限,以便Lambda函数可以访问解密Secret。

向数据库添加适当的入站规则。确保您的安全组已正确配置。您还可以使用RDS代理来重用数据库连接,以提高性能。

本文介绍了如何从Lambda函数与RDS进行通信 https://www.freecodecamp.org/news/aws-lambda-rds/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接