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
Takahiro Iwasa
(岩佐 孝浩)

Takahiro Iwasa (岩佐 孝浩)

Software Developer at KAKEHASHI Inc.
処方箋データ収集基盤の設計・開発・運用に携わっています。