Edited at

Amazon Elasticsearch Service の IAM User/Role アクセス制御に対応する(Ruby 版)


はじめに: Amazon ES のアクセス制御

Amazon Elasticsearch Service (以降 Amazon ES) は VPC に対応していないため、アクセス制御を設定しないと誰からでもアクセスが可能な状態になってしまいます。Amazon ES のアクセス制御は AWS ユーザ・IAM User/Role・IP アドレス等に対して設定できます。Auto Scaling させている EC2 のアプリケーションサーバから Amazon ES にアクセスするようなケースでは都度 IP を設定するのも困難なため、各 EC2 インスタンスに設定した IAM Role による制御を用いることになります。


課題: AWS 署名付きリクエスト

AWS でアクセス制御している API (今回の場合、Amazon ES の API)にリクエストを投げるには、アクセスを許可されたリソースの credential を使った署名を付与する必要があります。

参考: 署名バージョン 4 を使用して AWS リクエストに署名する

aws-sdk を使って AWS の各種サービスにリクエストを投げるような場合には aws-sdk がこの署名プロセスを隠蔽してくれているのですが、残念ながら aws-sdk は Amazon ES の AWS 側の API (Amazon ES インスタンスを作成したり、アクセス制御を設定したりといった AWS 側が提供している機能)には対応してるものの Elasticsearch 本体の API (インデックスの作成・参照など)のクライアントとしての機能は提供していません。

そのため Elasticsearch API に対するリクエストの署名は自前で付与する必要があります。Ruby には Elasticsearch を開発する Elastic 社本家が提供する elasticsearch-ruby というクライアントがあるので、今回はこの elasticsearch-ruby のリクエストに AWS の IAM Role による署名を付与してみます。


elasticsearch-ruby のリクエストに AWS の署名を付与する

faraday_middleware-aws-signers-v4 という faraday を AWS 署名対応させるミドルウェアがあるのでこれを利用します。幸いなことに elasticsearch-ruby は内部で 利用する HTTP クライアントとして faraday を使っているので、この faraday に faraday_middleware-aws-signers-v4 の設定を渡します。

現状、elasticsearch-ruby は外から faraday_middleware の設定を渡すインタフェースは提供していないので、ミドルウェアを設定したクライアントのインスタンスを直接渡すアプローチを取ります。

require 'faraday_middleware/aws_signers_v4'

# Elasticsearch::Transport::Transport::HTTP::Faraday クラスは渡されたブロックをそのまま faraday に渡す
transport = Elasticsearch::Transport::Transport::HTTP::Faraday.new(
hosts: [{host: 'xxx.ap-northeast-1.es.amazonaws.com', port: 80}]
) do |faraday|
faraday.request :aws_signers_v4,
credentials: Aws::InstanceProfileCredentials.new,
service_name: 'es',
region: 'ap-northeast-1'
faraday.adapter Faraday.default_adapter
end

# elasticsearch-model と併用して Rails の model から利用する場合
# transport オプションに渡されたインスタンスは HTTP クライアントとして利用される
Elasticsearch::Model.client = Elasticsearch::Client.new(transport: transport)

faraday_middleware-aws-signers-v4 に渡す credentials オプションは利用するリソースによって変更します。

# EC2 インスタンスに紐付いた IAM Role を利用する場合

Aws::InstanceProfileCredentials.new

# 環境変数で IAM User の access_key_id, secret_access_key を渡す場合
Aws::Credentials.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY'])

# ~/.aws/credentials を設定している場合
Aws::SharedCredentials.new.credentials

上記の対応によって elasticsearch-ruby から AWS の署名付きリクエストを投げられるようになり、Amazon ES の IAM User/Role アクセス制御に対応できました。


おまけ

上記の Elasticsearch::Transport::Transport::HTTP::Faraday インスタンスに faraday のオプションを渡すアプローチはアプリケーション側から elasticsearch-ruby の内部実装を意識したコードを書かないといけないのが少し複雑です。

elasticsearch-ruby に Elasticsearch::Client の初期化時に直接 faraday のブロックを渡せるようにする Pull Request を送って既に master にマージされているので、次回の Gem アップデート後は次のように書くことができます。(2015/11/8現在)

require 'faraday_middleware/aws_signers_v4'

Elasticsearch::Model.client = Elasticsearch::Client.new(
host: 'xxx.ap-northeast-1.es.amazonaws.com',
port: 80
) do |faraday|
faraday.request :aws_signers_v4,
credentials: Aws::SharedCredentials.new,
service_name: 'es',
region: 'ap-northeast-1'
faraday.adapter Faraday.default_adapter
end