Amazon Elasticsearch を Lambda と IAM ロールで安全に制限する方法

Amazon Elasticsearch を Lambda と IAM ロールで安全に制限する方法

岩佐 孝浩
岩佐 孝浩
5 min read
Elasticsearch IAM Lambda

はじめに

インターネット経由でアクセス可能な Amazon Elasticsearch ドメインへのアクセスを管理することは難しい場合があります。ドメインを VPC 内に配置すると、NAT GatewayNAT Instance の追加コストが発生します。しかし、AWS Lambda 関数 をプロキシとして使用することで、コスト効率の良い安全なソリューションを実現できます。

この記事では、IAM ロールを設定した Lambda 関数からのみ Elasticsearch にアクセスを制限する方法を説明します。

前提条件

開始する前に、以下がインストールされていることを確認してください。

ディレクトリ構成

以下は SAM アプリケーションの構造です。

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

AWS SAM テンプレート

以下の AWS SAM テンプレート では、Elasticsearch ドメイン、Lambda 関数、および関連する IAM ロールなどのリソースを定義しています。

重要なポイント:

  1. AccessPolicies セクションは Elasticsearch ドメインへのアクセスを制限します。(8 行目から 16 行目)
  2. Lambda 関数はドメインに対して特定のアクションを実行する権限を持ちます。(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

Lambda 用 Python スクリプト

requirements.txt

以下の依存関係を追加します。なお、boto3 は Lambda ランタイム環境に事前にインストールされています。

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 を利用して Elasticsearch ドメインに安全に接続します。

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 ドメインにアクセスできることを示します。

Lambda 実行結果例

ターミナルからのテスト

Elasticsearch ドメインへの直接アクセスをテストします。許可されていない場合はエラーメッセージが表示されるはずです。

$ curl https://<elasticsearch-domain-endpoint>/
{"Message":"User: anonymous is not authorized to perform: es:ESHttpGet"}

クリーンアップ

以下のコマンドを使用してすべてのリソースを削除します。

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

まとめ

IAM ロール をアタッチした AWS Lambda 関数 を利用することで、Elasticsearch ドメインへのアクセスを安全に制限することができます。このアプローチにより、追加の NAT インフラストラクチャのコストを回避できます。ただし、可能であればドメインを VPC 内に配置することで、セキュリティをさらに強化することを検討してください。

Happy Coding! 🚀

岩佐 孝浩

岩佐 孝浩

Software Developer at KAKEHASHI Inc.
AWS を活用したクラウドネイティブ・アプリケーションの要件定義・設計・開発に従事。 株式会社カケハシで、処方箋データ収集の新たな基盤の構築に携わっています。 Japan AWS Top Engineers 2020-2023