Amazon OpenSearch Service への HTTP リクエストの署名 - Amazon OpenSearch Service

Amazon OpenSearch Service への HTTP リクエストの署名

このセクションでは、Elasticsearch と OpenSearch クライアントおよびその他の共通ライブラリを使用して、署名した HTTP リクエストを Amazon OpenSearch Service に送信する例を示します。これらのコード例は、_index_bulk_snapshot などの OpenSearch API を操作するためのものです。ドメインアクセスポリシーに IAM ユーザーまたは IAM ロールが含まれている場合 (または IAM マスターユーザーときめ細かなアクセスコントロールを使用する場合)、OpenSearch API へのリクエストには IAM 認証情報を使用して署名する必要があります。

OpenSearch Service ドメインの作成、更新、削除などのオペレーションを含む、設定 API を操作する方法の例については、「Amazon OpenSearch Service を操作するための AWS SDKの使用」を参照してください。

重要

最新バージョンの Elasticsearch クライアントには、人為的に互換性を損なうライセンスチェックやバージョンチェックが含まれる場合があります。使用する正しいクライアントバージョンについては、「Elasticech クライアントの互換性」を参照してください。

Java

Java を使用して署名付きリクエストを送信する最も簡単な方法は、opensearch-java バージョン 2.1.0 で導入された AwsSdk2Transport を使用することです。次のでは、インデックスを作成し、ドキュメントを作成し、インデックスを削除します。region および host の値を指定する必要があります。

package com.amazonaws.samples; import java.io.IOException; import java.util.HashMap; import java.util.Map; import org.opensearch.client.opensearch.OpenSearchClient; import org.opensearch.client.opensearch.core.IndexRequest; import org.opensearch.client.opensearch.indices.CreateIndexRequest; import org.opensearch.client.opensearch.indices.DeleteIndexRequest; import org.opensearch.client.transport.aws.AwsSdk2Transport; import org.opensearch.client.transport.aws.AwsSdk2TransportOptions; import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.apache.ApacheHttpClient; import software.amazon.awssdk.regions.Region; public class IndexDocument { private static final String host = "search-....us-west-2.es.amazonaws.com"; private static Region region = Region.US_WEST_2; public static void main(String[] args) throws IOException, InterruptedException { SdkHttpClient httpClient = ApacheHttpClient.builder().build(); try { OpenSearchClient client = new OpenSearchClient( new AwsSdk2Transport( httpClient, host, region, AwsSdk2TransportOptions.builder().build())); // create the index String index = "sample-index"; CreateIndexRequest createIndexRequest = new CreateIndexRequest.Builder().index(index).build(); client.indices().create(createIndexRequest); // index data Map<String, Object> document = new HashMap<>(); document.put("firstName", "Michael"); document.put("lastName", "Douglas"); IndexRequest documentIndexRequest = new IndexRequest.Builder() .index(index) .id("2") .document(document) .build(); client.index(documentIndexRequest); // delete the index DeleteIndexRequest deleteRequest = new DeleteIndexRequest.Builder().index(index).build(); client.indices().delete(deleteRequest); } finally { httpClient.close(); } } }

その他の代替手段には、AWS Request Signing Interceptor や高レベルの REST クライアントの使用が含まれます。このサンプルを表示

ヒント

このサンプルでは、デフォルトの認証情報チェーンが使用されます。AWS CLI を使用して aws configure を実行し、認証情報を設定します。

Python

この例では、Python の opensearch-py クライアントを使用します。これは、pip を使用してインストールできます。region および host の値を指定する必要があります。

from opensearchpy import OpenSearch, RequestsHttpConnection, AWSV4SignerAuth import boto3 host = '' # cluster endpoint, for example: my-test-domain.us-east-1.es.amazonaws.com region = '' # e.g. us-west-1 credentials = boto3.Session().get_credentials() auth = AWSV4SignerAuth(credentials, region) index_name = 'movies' client = OpenSearch( hosts = [{'host': host, 'port': 443}], http_auth = auth, use_ssl = True, verify_certs = True, connection_class = RequestsHttpConnection ) q = 'miller' query = { 'size': 5, 'query': { 'multi_match': { 'query': q, 'fields': ['title^2', 'director'] } } } response = client.search( body = query, index = index_name ) print('\nSearch results:') print(response)

クライアントの代わりに、リクエストをすることもできます。requests-aws4auth パッケージおよび SDK for Python (Boto3) パッケージを使用すると、認証プロセスを簡素化できますが、これらパッケージが絶対に必要というわけではありません。ターミナルから、次のコマンドを実行します。

pip install boto3 pip install opensearch-py pip install requests pip install requests-aws4auth

次のコード例では、指定した OpenSearch Service ドメインへのセキュアな接続を確立し、1 つのドキュメントのインデックスを作成します。region および host の値を指定する必要があります。

from opensearchpy import OpenSearch, RequestsHttpConnection from requests_aws4auth import AWS4Auth import boto3 host = '' # For example, my-test-domain.us-east-1.es.amazonaws.com region = '' # e.g. us-west-1 service = 'es' credentials = boto3.Session().get_credentials() awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token) search = OpenSearch( hosts = [{'host': host, 'port': 443}], http_auth = awsauth, use_ssl = True, verify_certs = True, connection_class = RequestsHttpConnection ) document = { "title": "Moneyball", "director": "Bennett Miller", "year": "2011" } search.index(index="movies", doc_type="_doc", id="5", body=document) print(search.get(index="movies", doc_type="_doc", id="5"))

opensearch-py を使用しない場合は、標準の HTTP リクエストだけを作成できます。この例では 7 つのシャードと 2 つのレプリカの新しいインデックスを作成します。

from requests_aws4auth import AWS4Auth import boto3 import requests host = '' # The domain with https:// and trailing slash. For example, https://my-test-domain.us-east-1.es.amazonaws.com/ path = 'my-index' # the OpenSearch API endpoint region = '' # For example, us-west-1 service = 'es' credentials = boto3.Session().get_credentials() awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token) url = host + path # The JSON body to accompany the request (if necessary) payload = { "settings" : { "number_of_shards" : 7, "number_of_replicas" : 2 } } r = requests.put(url, auth=awsauth, json=payload) # requests.get, post, and delete have similar syntax print(r.text)

静的認証情報ではなく、認証情報を自動的に更新して AWS4Auth インスタンスを構築できます。これは、AssumeRole を使用して長時間実行されるアプリケーションに適しています。更新可能な認証情報インスタンスは、リクエストごとに有効な静的認証情報を生成するために使用され、一時的な認証情報が期限切れになったときに AWS4Auth インスタンスを再作成する必要がなくなります。

from requests_aws4auth import AWS4Auth from botocore.session import Session credentials = Session().get_credentials() auth = AWS4Auth(region=us-west-1', service='es', refreshable_credentials=credentials)

次の例では、Beautiful Soup ライブラリを使用して、HTML ファイルのローカルディレクトリから大容量ファイルを構築します。最初の例と同じクライアントを使用し、ファイルを _bulk API に送信してインデックスを作成できます。このコードは、ウェブサイトに検索機能を追加するための基盤として使用できます。

from bs4 import BeautifulSoup from opensearchpy import OpenSearch, RequestsHttpConnection from requests_aws4auth import AWS4Auth import boto3 import glob import json bulk_file = '' id = 1 # This loop iterates through all HTML files in the current directory and # indexes two things: the contents of the first h1 tag and all other text. for html_file in glob.glob('*.htm'): with open(html_file) as f: soup = BeautifulSoup(f, 'html.parser') title = soup.h1.string body = soup.get_text(" ", strip=True) # If get_text() is too noisy, you can do further processing on the string. index = { 'title': title, 'body': body, 'link': html_file } # If running this script on a website, you probably need to prepend the URL and path to html_file. # The action_and_metadata portion of the bulk file bulk_file += '{ "index" : { "_index" : "site", "_type" : "_doc", "_id" : "' + str(id) + '" } }\n' # The optional_document portion of the bulk file bulk_file += json.dumps(index) + '\n' id += 1 host = '' # For example, my-test-domain.us-east-1.es.amazonaws.com region = '' # e.g. us-west-1 service = 'es' credentials = boto3.Session().get_credentials() awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service) search = OpenSearch( hosts = [{'host': host, 'port': 443}], http_auth = awsauth, use_ssl = True, verify_certs = True, connection_class = RequestsHttpConnection ) search.bulk(bulk_file) print(search.search(q='some test query'))

Ruby

この最初の例では、Elasticsearch Ruby クライアントと Faraday ミドルウェアを使用してリクエスト署名を実行します。最新バージョンのクライアントには、人為的に互換性を損なうライセンスまたはバージョンチェックが含まれている可能性があることに注意してください。使用する正しいクライアントバージョンについては、「Elasticech クライアントの互換性」を参照してください。このサンプルでは、推奨バージョン 7.13.3 を使用しています。

ターミナルから、次のコマンドを実行します。

gem install elasticsearch -v 7.13.3 gem install faraday_middleware-aws-sigv4

このコード例では、新しい クライアントを作成して、Faraday ミドルウェアを設定してリクエストに署名し、単一ドキュメントをインデックス化します。full_url_and_port および region の値を指定する必要があります。

require 'elasticsearch' require 'faraday_middleware/aws_sigv4' full_url_and_port = '' # e.g. https://my-domain.region.es.amazonaws.com:443 index = 'ruby-index' type = '_doc' id = '1' document = { year: 2007, title: '5 Centimeters per Second', info: { plot: 'Told in three interconnected segments, we follow a young man named Takaki through his life.', rating: 7.7 } } region = '' # e.g. us-west-1 service = 'es' client = Elasticsearch::Client.new(url: full_url_and_port) do |f| f.request :aws_sigv4, service: service, region: region, access_key_id: ENV['AWS_ACCESS_KEY_ID'], secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'], session_token: ENV['AWS_SESSION_TOKEN'] # optional end puts client.index index: index, type: type, id: id, body: document

認証情報が機能しない場合、次のコマンドを使用して端末にエクスポートすることができます。

export AWS_ACCESS_KEY_ID="your-access-key" export AWS_SECRET_ACCESS_KEY="your-secret-key" export AWS_SESSION_TOKEN="your-session-token"

次の例では、AWS SDK for Ruby と標準 Ruby ライブラリを使用して署名された HTTP リクエストを送信します。最初の例のように、単一のドキュメントをインデックス化します。ホストとリージョンの値を指定する必要があります。

require 'aws-sdk-opensearchservice' host = '' # e.g. https://my-domain.region.es.amazonaws.com index = 'ruby-index' type = '_doc' id = '2' document = { year: 2007, title: '5 Centimeters per Second', info: { plot: 'Told in three interconnected segments, we follow a young man named Takaki through his life.', rating: 7.7 } } service = 'es' region = '' # e.g. us-west-1 signer = Aws::Sigv4::Signer.new( service: service, region: region, access_key_id: ENV['AWS_ACCESS_KEY_ID'], secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'], session_token: ENV['AWS_SESSION_TOKEN'] ) signature = signer.sign_request( http_method: 'PUT', url: host + '/' + index + '/' + type + '/' + id, body: document.to_json ) uri = URI(host + '/' + index + '/' + type + '/' + id) Net::HTTP.start(uri.host, uri.port, :use_ssl => true) do |http| request = Net::HTTP::Put.new uri request.body = document.to_json request['Host'] = signature.headers['host'] request['X-Amz-Date'] = signature.headers['x-amz-date'] request['X-Amz-Security-Token'] = signature.headers['x-amz-security-token'] request['X-Amz-Content-Sha256']= signature.headers['x-amz-content-sha256'] request['Authorization'] = signature.headers['authorization'] request['Content-Type'] = 'application/json' response = http.request request puts response.body end

Node

この例では、JavaScript 用の opensearch-js クライアントを使用してインデックスを作成し、単一のドキュメントを追加します。リクエストに署名するために、credential-provider-node モジュールを使用する認証情報が Node.js の JavaScript のバージョン 3 の SDK から検出されます。次に aws4 が呼び出されて、署名バージョン 4 を使用してリクエストに署名されます。host の値を指定する必要があります。

const { Client, Connection } = require("@opensearch-project/opensearch"); const { defaultProvider } = require("@aws-sdk/credential-provider-node"); const aws4 = require("aws4"); var host = '' // e.g. https://my-domain.region.es.amazonaws.com const createAwsConnector = (credentials, region) => { class AmazonConnection extends Connection { buildRequestObject(params) { const request = super.buildRequestObject(params); request.service = 'es'; request.region = region; request.headers = request.headers || {}; request.headers['host'] = request.hostname; return aws4.sign(request, credentials); } } return { Connection: AmazonConnection }; }; const getClient = async () => { const credentials = await defaultProvider()(); return new Client({ ...createAwsConnector(credentials, 'us-east-1'), node: host, }); } async function search() { // Initialize the client. var client = await getClient(); // Create an index. var index_name = "test-index"; var response = await client.indices.create({ index: index_name, }); console.log("Creating index:"); console.log(response.body); // Add a document to the index. var document = { "title": "Moneyball", "director": "Bennett Miller", "year": "2011" }; var response = await client.index({ index: index_name, body: document }); console.log(response.body); } search().catch(console.log);

この同様の例では aws4 ではなく aws-opensearch-connector が使用されます。host の値を指定する必要があります。

const { Client } = require("@opensearch-project/opensearch"); const { defaultProvider } = require("@aws-sdk/credential-provider-node"); const createAwsOpensearchConnector = require("aws-opensearch-connector"); var host = '' // e.g. https://my-domain.region.es.amazonaws.com const getClient = async () => { const awsCredentials = await defaultProvider()(); const connector = createAwsOpensearchConnector({ credentials: awsCredentials, region: process.env.AWS_REGION ?? 'us-east-1', getCredentials: function(cb) { return cb(); } }); return new Client({ ...connector, node: host, }); } async function search() { // Initialize the client. var client = await getClient(); // Create an index. var index_name = "test-index"; var response = await client.indices.create({ index: index_name, }); console.log("Creating index:"); console.log(response.body); // Add a document to the index. var document = { "title": "Moneyball", "director": "Bennett Miller", "year": "2011" }; var response = await client.index({ index: index_name, body: document }); console.log(response.body); } search().catch(console.log);

opensearch-js を使用しない場合は、標準の HTTP リクエストだけを作成できます。このセクションには、SDK for JavaScript in Node.js のバージョン 2 および 3 の例を収録しています。バージョン 2 は 単独のパッケージとして公開されますが、バージョン 3 にはサービスごとに個別のパッケージを備えたモジュラーアーキテクチャがあります。

Version 3

この例では SDK for JavaScript in Node.js の バージョン 3 を使用します。ターミナルから、次のコマンドを実行します。

npm i @aws-sdk/protocol-http npm i @aws-sdk/credential-provider-node npm i @aws-sdk/signature-v4 npm i @aws-sdk/node-http-handler npm i @aws-crypto/sha256-browser

このコード例は、単一のドキュメントをインデックス化します。region および domain の値を指定する必要があります。

const { HttpRequest} = require("@aws-sdk/protocol-http"); const { defaultProvider } = require("@aws-sdk/credential-provider-node"); const { SignatureV4 } = require("@aws-sdk/signature-v4"); const { NodeHttpHandler } = require("@aws-sdk/node-http-handler"); const { Sha256 } = require("@aws-crypto/sha256-browser"); var region = ''; // e.g. us-west-1 var domain = ''; // e.g. search-domain.region.es.amazonaws.com var index = 'node-test'; var type = '_doc'; var id = '1'; var json = { "title": "Moneyball", "director": "Bennett Miller", "year": "2011" }; indexDocument(json).then(() => process.exit()) async function indexDocument(document) { // Create the HTTP request var request = new HttpRequest({ body: JSON.stringify(document), headers: { 'Content-Type': 'application/json', 'host': domain }, hostname: domain, method: 'PUT', path: index + '/' + type + '/' + id }); // Sign the request var signer = new SignatureV4({ credentials: defaultProvider(), region: region, service: 'es', sha256: Sha256 }); var signedRequest = await signer.sign(request); // Send the request var client = new NodeHttpHandler(); var { response } = await client.handle(signedRequest) console.log(response.statusCode + ' ' + response.body.statusMessage); var responseBody = ''; await new Promise(() => { response.body.on('data', (chunk) => { responseBody += chunk; }); response.body.on('end', () => { console.log('Response body: ' + responseBody); }); }).catch((error) => { console.log('Error: ' + error); }); };
Version 2

この例では SDK for JavaScript in Node.js の バージョン 2 を使用します。ターミナルから、次のコマンドを実行します。

npm install aws-sdk

このコード例は、単一のドキュメントをインデックス化します。region および domain の値を指定する必要があります。

var AWS = require('aws-sdk'); var region = ''; // e.g. us-west-1 var domain = ''; // e.g. search-domain.region.es.amazonaws.com var index = 'node-test'; var type = '_doc'; var id = '1'; var json = { "title": "Moneyball", "director": "Bennett Miller", "year": "2011" } indexDocument(json); function indexDocument(document) { var endpoint = new AWS.Endpoint(domain); var request = new AWS.HttpRequest(endpoint, region); request.method = 'PUT'; request.path += index + '/' + type + '/' + id; request.body = JSON.stringify(document); request.headers['host'] = domain; request.headers['Content-Type'] = 'application/json'; request.headers['Content-Length'] = Buffer.byteLength(request.body); var credentials = new AWS.EnvironmentCredentials('AWS'); var signer = new AWS.Signers.V4(request, 'es'); signer.addAuthorization(credentials, new Date()); var client = new AWS.HttpClient(); return new Promise((resolve, reject) => { client.handleRequest( request, null, (response) => { const {statusCode, statusMessage, headers} = response; let body = ''; response.on('data', (chunk) => { body += chunk; }); response.on('end', () => { const data = {statusCode, statusMessage, headers}; if (body) { data.body = body; } resolve(data); console.log("Response body:" + body); }); }, (error) => { reject(error); console.log("Error:" + error) } ); }) };

認証情報が機能しない場合、次のコマンドを使用して端末にエクスポートすることができます。

export AWS_ACCESS_KEY_ID="your-access-key" export AWS_SECRET_ACCESS_KEY="your-secret-key" export AWS_SESSION_TOKEN="your-session-token"

Go

この例では、AWS SDK for Go を使用し、1 つのドキュメントにインデックスを付けます。domain および region の値を指定する必要があります。

package main import ( "fmt" "net/http" "strings" "time" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/signer/v4" ) func main() { // Basic information for the Amazon OpenSearch Service domain domain := "" // e.g. https://my-domain.region.es.amazonaws.com index := "my-index" id := "1" endpoint := domain + "/" + index + "/" + "_doc" + "/" + id region := "" // e.g. us-east-1 service := "es" // Sample JSON document to be included as the request body json := `{ "title": "Thor: Ragnarok", "director": "Taika Waititi", "year": "2017" }` body := strings.NewReader(json) // Get credentials from environment variables and create the Signature Version 4 signer credentials := credentials.NewEnvCredentials() signer := v4.NewSigner(credentials) // An HTTP client for sending the request client := &http.Client{} // Form the HTTP request req, err := http.NewRequest(http.MethodPut, endpoint, body) if err != nil { fmt.Print(err) } // You can probably infer Content-Type programmatically, but here, we just say that it's JSON req.Header.Add("Content-Type", "application/json") // Sign the request, send it, and print the response signer.Sign(req, body, service, region, time.Now()) resp, err := client.Do(req) if err != nil { fmt.Print(err) } fmt.Print(resp.Status + "\n") }

認証情報が機能しない場合、次のコマンドを使用して端末にエクスポートすることができます。

export AWS_ACCESS_KEY_ID="your-access-key" export AWS_SECRET_ACCESS_KEY="your-secret-key" export AWS_SESSION_TOKEN="your-session-token"