IAM ロールを使用して Lambda からのみ Internet-facing Elasticsearch へのアクセスを許可
Amazon Elasticsearch ユーザーは、 Elasticsearch クラスターを VPC 内に配置するべきです。 ただし、これにはNAT Gateway または NAT インスタンスが必要で、追加の費用が発生します。 クラスターをパブリックに配置する場合、 Lambda 関数を Elasticsearch ドメインへのプロキシとして使用できます。
前提条件
以下をインストールしてください。
- AWS SAM CLI
- Python 3.x
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 内に配置する必要があります。
この投稿が、お役に立てば幸いです。