IAM ロールを使用して Lambda からのみ Internet-facing Elasticsearch へのアクセスを許可

IAM ロールを使用して Lambda からのみ Internet-facing Elasticsearch へのアクセスを許可

Takahiro Iwasa
Takahiro Iwasa
4 min read
Elasticsearch IAM Lambda

Amazon Elasticsearch ユーザーは、 Elasticsearch クラスターを VPC 内に配置するべきです。 ただし、これにはNAT Gateway または NAT インスタンスが必要で、追加の費用が発生します。 クラスターをパブリックに配置する場合、 Lambda 関数を Elasticsearch ドメインへのプロキシとして使用できます。

前提条件

以下をインストールしてください。

SAM アプリケーション作成

ディレクトリ構成

/
|-- es-proxy-lambda/
|   |-- __init__.py
|   |-- lambda_function.py
|   `-- requirements.txt
|-- samconfig.toml
`-- template.yaml

AWS SAM テンプレート

以下がポイントです。

  • Elasticsearch ドメインは、 AccessPolicies を使用してアクセスを制限しています(8-16行目)。
  • Lambda 関数は Elasticsearch ドメインへのアクセスに必要なポリシーを持っている必要があります(64-70行目)。
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31

Resources:
  Elasticsearch:
    Type: AWS::Elasticsearch::Domain
    Properties:
      AccessPolicies:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              AWS:
                - !GetAtt IamRole.Arn
            Action: es:*
            Resource: !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/es-for-lambda/*
      DomainName: es-for-lambda
      EBSOptions:
        EBSEnabled: true
        VolumeSize: 10
        VolumeType: standard
      ElasticsearchClusterConfig:
        DedicatedMasterEnabled: false
        InstanceCount: 1
        InstanceType: t2.small.elasticsearch
      ElasticsearchVersion: 7.4

  Lambda:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: es-proxy-lambda/
      Environment:
        Variables:
          ES_DOMAIN: !GetAtt Elasticsearch.DomainEndpoint
      FunctionName: es_proxy_lambda
      Handler: lambda_function.lambda_handler
      Role: !GetAtt IamRole.Arn
      Runtime: python3.8

  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub
        - /aws/lambda/${name}
        - {name: !Ref Lambda}
      RetentionInDays: 1

  IamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - es:ESHttpHead
                  - es:DescribeElasticsearchDomain
                  - es:ESHttpGet
                  - es:DescribeElasticsearchDomainConfig
                Resource: !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/es-for-lambda
          PolicyName: policy
      RoleName: es-proxy-lambda-role

Python スクリプト

requirements.txt

AWS Lambda ランタイム環境には、デフォルトで boto3 がインストールされているため、 requirements.txt に含める必要はありません。

certifi==2019.11.28
chardet==3.0.4
elasticsearch==7.5.1
idna==2.9
requests==2.23.0
requests-aws4auth==0.9
urllib3==1.25.8

lambda_function.py

以下のスクリプトは、 requests_aws4auth を使用して AWS の認証情報を含む Auth ヘッダーを生成します(10-11行目および14行目)。

import os

import boto3
from elasticsearch import Elasticsearch, RequestsHttpConnection
from requests_aws4auth import AWS4Auth


es_domain = os.environ.get('ES_DOMAIN')
credentials = boto3.Session().get_credentials()
awsauth = AWS4Auth(
    credentials.access_key, credentials.secret_key, 'ap-northeast-1', 'es', session_token=credentials.token)
es = Elasticsearch(
    hosts=[{'host': es_domain, 'port': 443}],
    http_auth=awsauth,
    use_ssl=True,
    verify_certs=True,
    connection_class=RequestsHttpConnection
)


def lambda_handler(event, context):
    response = es.info()
    print(response)

samconfig.toml

<YOUR_S3_BUCKET> を実際の値に置き換えてください。

version = 0.1
[default]
[default.deploy]
[default.deploy.parameters]
stack_name = "es-proxy-lambda"
s3_bucket = "<YOUR_S3_BUCKET>"
s3_prefix = "es-proxy-lambda"
region = "ap-northeast-1"
capabilities = "CAPABILITY_IAM CAPABILITY_NAMED_IAM"

ビルドとデプロイ

次のコマンドでビルドおよびデプロイしてください。 Elasticsearch ドメインの作成には、10分から20分かかることがあります。

sam build
sam deploy

テスト

Lambda でのテスト

Lambda 関数を実行してください。 Elasticsearch ドメインへのアクセスが成功するはずです。

Terminal でのテスト

以下のコマンドを使用して Elasticsearch ドメインにアクセスを試してください。 Elasticsearch ドメインによってアクセス拒否されるはずです。

$ curl https://search-es-for-lambda-rase3snu6yozl6xhjcuq34cu5m.ap-northeast-1.es.amazonaws.com/
{"Message":"User: anonymous is not authorized to perform: es:ESHttpGet"}

クリーンアップ

以下のコマンドを使用して、プロビジョニングされた AWS リソースを削除してください。

sam delete --stack-name es-proxy-lambda

まとめ

Lambda 関数にアタッチする IAM ロールを利用して、 Elasticsearch クラスターをパブリックに起動しつつ、 Lambda 関数のみアクセスを許可できます。

なお、セキュリティを優先する場合、 Elasticsearch クラスターは、基本的に VPC 内に配置する必要があります。

この投稿が、お役に立てば幸いです。

Takahiro Iwasa

Takahiro Iwasa

Software Developer at KAKEHASHI Inc.
Involved in the requirements definition, design, and development of cloud-native applications using AWS. Now, building a new prescription data collection platform at KAKEHASHI Inc. Japan AWS Top Engineers 2020-2023.