允许 VPC 中的 Lambda 访问同一 VPC 中的 Elasticsearch 域

Posted

技术标签:

【中文标题】允许 VPC 中的 Lambda 访问同一 VPC 中的 Elasticsearch 域【英文标题】:Allowing Lambda in a VPC to access an Elasticsearch domain in the same VPC 【发布时间】:2019-05-23 11:33:57 【问题描述】:

我正在学习如何使用 Amazon 服务,特别是我目前想使用 Cloud Formation 脚本创建一个简单的设置:一个 VPC,它有一个用 JS 编写的 lambda,它可以访问同一个 Elasticsearch 服务VPC。

不知怎的,我无法让它工作。从 lambda 到 Elasticsearch 域的所有请求总是超时。但是,来自在同一 VPC 中运行 Amazon Linux 2 的 EC2 实例从相同的 JS 代码或 curl(即使没有任何额外的授权,只是卷曲 ES 域端点)发出的相同请求工作正常,我可以与 Elasticsearch 通信就好了从那个 EC2 实例(通过 SSH 连接到它)。

同时 lambda 能够访问 VPC 中的 Aurora 集群,因此 lambda 无法访问 VPC 资源不是一般问题。

请告诉我我在 Cloud Formation 的设置描述中做错了什么?下面是我的 Cloud Formation 模板的相关摘录以及能够从 EC2 实例访问 ES 服务但不能对 lambda 执行相同操作的 JS 代码示例:

AWSTemplateFormatVersion: 2010-09-09
Description: The AWS CloudFormation tutorial
Resources:

  SomeDeploymentBucket:
    Type: 'AWS::S3::Bucket'

  AppLogGroup:
    Type: 'AWS::Logs::LogGroup'
    Properties:
      LogGroupName: /aws/lambda/some-lambda

    # ========= The Lambda Execution Role =========

  IamRoleLambdaExecution:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Policies:
        - PolicyName: !Join 
            - '-'
            - - dev
              - some-app
              - lambda
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 's3:*'
                  - 'rds-db:connect'
                  - 'rds:*'
                  - 'es:*'
                Resource: '*'
      Path: /
      RoleName: !Join 
        - '-'
        - - some-app
          - dev
          - eu-west-1
          - lambdaRole
      ManagedPolicyArns:
        - !Join 
          - ''
          - - 'arn:'
            - !Ref 'AWS::Partition'
            - ':iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole'

    # ========= The Lambda =========

  AppLambdaFunction:
    Type: 'AWS::Lambda::Function'
    Properties:
      Code:
        S3Bucket: !Ref SomeDeploymentBucket
        S3Key: >-
          tutorial/some-app/dev/1545610972669-2018-12-24T00:22:52.669Z/some-app.zip
      FunctionName: some-lambda
      Handler: app.server
      MemorySize: 1024
      Role: !GetAtt 
        - IamRoleLambdaExecution
        - Arn
      Runtime: nodejs8.10
      Timeout: 6
      VpcConfig:
        SecurityGroupIds:
          - !Ref xxxVPCSecurityGroup
        SubnetIds:
          - !Ref xxxLambdaSubnet
    DependsOn:
      - AppLogGroup
      - IamRoleLambdaExecution

    # ========= VPC =========

  xxxVPC:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: 172.31.0.0/16
      InstanceTenancy: default
      EnableDnsSupport: 'true'
      EnableDnsHostnames: 'true'

  xxxVPCSecurityGroup:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      GroupName: VPC SG
      GroupDescription: VPC Security Group
      VpcId: !Ref xxxVPC

  xxxLambdaSubnet:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !Ref xxxVPC
      CidrBlock: 172.31.32.0/20

    # ========= Elasticsearch =========

  xxxESSecurityGroup:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      GroupName: ES SG
      GroupDescription: ES Security group
      VpcId: !Ref xxxVPC
      SecurityGroupIngress:
        - IpProtocol: -1
          FromPort: 0
          ToPort: 65535
          SourceSecurityGroupId: !Ref xxxVPCSecurityGroup

  xxxElasticSearch:
    Type: 'AWS::Elasticsearch::Domain'
    Properties:
      AccessPolicies:
        Version: 2012-10-17
        Statement:
          - Action:
              - 'es:*'
              - 'ec2:*'
              - 's3:*'
            Principal:
              AWS:
                - '*'
            Resource: '*'
            Effect: Allow
      DomainName: es-xxx-domain
      AdvancedOptions:
        rest.action.multi.allow_explicit_index: 'true'
      ElasticsearchVersion: 6.3
      ElasticsearchClusterConfig:
        InstanceCount: 2
        InstanceType: m3.medium.elasticsearch
        DedicatedMasterEnabled: 'false'
      VPCOptions:
        SecurityGroupIds:
          - !Ref xxxESSecurityGroup
        SubnetIds:
          - !Ref xxxLambdaSubnet

JS代码(没有用creds签名的版本,但签名时也不起作用):

var es = require('elasticsearch');
var client = new es.Client(
    host: 'vpc-es-domain-AMAZON.eu-west-1.es.amazonaws.com:80',
    log: 'trace'
);

client.ping(
    requestTimeout: 1000
, function(error, res, status)
    if(error) 
        console.trace('es cluster error!');
        console.trace(error);
     else 
        console.log('All is well');
        var response = 
            error: error,
            res: res,
            status: status
        
        console.log(JSON.stringify(response));
    
);

在同一 VPC 中的 EC2 实例中执行此操作会从 ES 域获得响应没有任何问题

curl vpc-es-domain-AMAZON.eu-west-1.es.amazonaws.com:80

非常感谢您的帮助,因为我已经遇到了这个问题。

【问题讨论】:

Lambda 函数和 ES 域是否在 VPC 内的同一子网上?如果它们在不同的子网中,请确保适当的路由表具有彼此的路由。 @ben5556 是的,lambda 和 ES 在同一个子网中,即示例中的xxxLambdaSubnet。我已经改进了代码以更简洁地显示这一点。 您是在运行自管理的elasticsearch 集群还是AWS elasticsearch 服务集群? @RenéGonzálezVenegas 这是一个与同一 VPC 关联的 AWS 弹性搜索服务集群。您可以在我的问题中提供的第一个代码示例中看到它的配置(将示例向下滚动到它的最底部,在那里您可以看到配置的 elasticsearch 部分) 【参考方案1】:

在您的设置中发现了两个问题

    您在堆栈中仅创建一个子网,并且仅将一个子网分配给 Lambda。您需要为 Lambda 分配多个子网 You need to fix access policy for ES。我手动更新为“不需要使用 IAM 凭据签署请求”,以便在控制台中进行测试。

我更新了 Cloudformation 模板以创建基于 python 的 lambda 处理程序,以从同一 vpc 查询弹性搜索。它不完整,但如果您确定上述问题,那么它应该可以工作。

AWSTemplateFormatVersion: 2010-09-09
Description: The AWS CloudFormation tutorial
Resources:

  SomeDeploymentBucket:
    Type: 'AWS::S3::Bucket'

  AppLogGroup:
    Type: 'AWS::Logs::LogGroup'
    Properties:
      LogGroupName: /aws/lambda/some-lambda

    # ========= The Lambda Execution Role =========

  IamRoleLambdaExecution:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Policies:
        - PolicyName: !Join
            - '-'
            - - dev
              - some-app
              - lambda
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 's3:*'
                  - 'rds-db:connect'
                  - 'rds:*'
                  - 'es:*'
                Resource: '*'


      Path: /
      RoleName: !Join
        - '-'
        - - some-app
          - dev
          - eu-west-1
          - lambdaRole
      ManagedPolicyArns:
        - !Join
          - ''
          - - 'arn:'
            - !Ref 'AWS::Partition'
            - ':iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole'

    # ========= The Lambda =========

  AppLambdaFunction:
    Type: AWS::Lambda::Function
    DependsOn:
      - AppLogGroup
      - IamRoleLambdaExecution
    Properties:
      FunctionName: some-lambda
      Handler: index.lambda_handler
      Runtime: python2.7
      Timeout: 60
      MemorySize: 1024
      Role: !GetAtt
        - IamRoleLambdaExecution
        - Arn
      VpcConfig:
        SecurityGroupIds:
          - !Ref xxxVPCSecurityGroup
        SubnetIds:
          - !Ref xxxLambdaSubnet1
          - !Ref xxxLambdaSubnet2

      Code:
        ZipFile: !Sub |
          from __future__ import print_function
          import boto3
          iam = boto3.client('iam')

          def lambda_handler(event, context):
            print('called lambda_handler')

    # ========= VPC =========

  xxxVPC:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: 172.31.0.0/16
      InstanceTenancy: default
      EnableDnsSupport: 'true'
      EnableDnsHostnames: 'true'

  xxxVPCSecurityGroup:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      GroupName: VPC SG
      GroupDescription: VPC Security Group
      VpcId: !Ref xxxVPC

  xxxLambdaSubnet1:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !Ref xxxVPC
      CidrBlock: 172.31.32.0/20
  xxxLambdaSubnet2:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !Ref xxxVPC
      CidrBlock: 172.31.16.0/20


    # ========= Elasticsearch =========

  xxxESSecurityGroup:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      GroupName: ES SG
      GroupDescription: ES Security group
      VpcId: !Ref xxxVPC
      SecurityGroupIngress:
        - IpProtocol: -1
          FromPort: 0
          ToPort: 65535
          SourceSecurityGroupId: !Ref xxxVPCSecurityGroup

  xxxElasticSearch:
    Type: 'AWS::Elasticsearch::Domain'
    Properties:
      AccessPolicies:
        Version: 2012-10-17
        Statement:
          - Action:
              - 'es:*'
              - 'ec2:*'
              - 's3:*'
            Principal:
              AWS:
                - '*'
            Resource: '*'
            Effect: Allow
      DomainName: es-xxx-domain
      EBSOptions:
        EBSEnabled: true
        Iops: 0
        VolumeSize: 20
        VolumeType: "gp2"
      AdvancedOptions:
        rest.action.multi.allow_explicit_index: 'true'

      ElasticsearchClusterConfig:
        InstanceCount: 1
        InstanceType: m4.large.elasticsearch
        DedicatedMasterEnabled: 'false'
      VPCOptions:
        SecurityGroupIds:
          - !Ref xxxESSecurityGroup
        SubnetIds:
          - !Ref xxxLambdaSubnet1

更新的 Lambda 函数处理程序代码

  import urllib2

def lambda_handler(event, context):
    print('called lambda_handler')
    data = ''
    url = 'https://vpc-es-xxx-domain-fixthis.es.amazonaws.com'
    req = urllib2.Request(url, data, 'Content-Type': 'application/json')
    f = urllib2.urlopen(req)
    for x in f:
        print(x)
    f.close()

【讨论】:

以上是关于允许 VPC 中的 Lambda 访问同一 VPC 中的 Elasticsearch 域的主要内容,如果未能解决你的问题,请参考以下文章

启用 VPC 的 Lambda 函数无法在同一 VPC 中启动/访问 EC2

从 VPC 中的 Lambda 访问 AWS S3

如何从账户 A 中的 Lambda(VPC 中的 Lambda)调用账户 B 中的 AWS Lambda 函数(VPC 中的这个 Lambda)

VPC 中的 AWS Lambda 在 NAT 之后没有互联网访问权限

从没有 VPC 的 Lambda 连接到公共 Redshift 数据库

从 VPC 中的 Elastic Beanstalk 实例访问 RDS