Firma di richieste HTTP in Amazon OpenSearch Service - Amazon OpenSearch Service

Firma di richieste HTTP in Amazon OpenSearch Service

Questa sezione contiene esempi di come inviare richieste HTTP firmate ad Amazon OpenSearch Service utilizzando i client Elasticsearch ed OpenSearch e altre librerie comuni. Questi codici di esempio consentono di interagire con le API OpenSearch, quali _index, _bulk e _snapshot. Se le policy di accesso al dominio includono utenti o ruoli IAM (oppure si utilizza un utente principali IAM con controllo granulare degli accessi), è necessario firmare le richieste alle API OpenSearch con le credenziali IAM.

Per esempi su come interagire con l'API di configurazione, incluse le operazioni quali creazione, aggiornamento ed eliminazione di domini OpenSearch Service, consultare Uso degli SDK AWS per interagire con Amazon OpenSearch Service.

Importante

Le versioni più recenti dei client Elasticsearch potrebbero includere controlli di licenza o versione che violano artificialmente la compatibilità. Per la versione client corretta da utilizzare, consultare Compatibilità con i client Elasticsearch.

Java

Il modo più semplice per inviare una richiesta firmata è utilizzare Amazon Web Services Request Signing Interceptor. Il repository contiene alcuni esempi per iniziare, oppure è possibile scaricare un progetto di esempio per OpenSearch Service su GitHub.

L'esempio seguente usa il client REST Java di basso livello opensearch-java per eseguire due operazioni non correlate: la registrazione di una repository delle snapshot e l'indicizzazione di un documento. È necessario fornire valori per region e host.

import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpRequestInterceptor; import org.apache.http.entity.ContentType; import org.apache.http.nio.entity.NStringEntity; import org.opensearch.client.Request; import org.opensearch.client.Response; import org.opensearch.client.RestClient; import com.amazonaws.auth.AWS4Signer; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import com.amazonaws.http.AWSRequestSigningApacheInterceptor; import java.io.IOException; public class AmazonOpenSearchServiceSample { private static String serviceName = "es"; private static String region = ""; private static String host = ""; // e.g. https://search-mydomain.us-west-1.es.amazonaws.com private static String payload = "{ \"type\": \"s3\", \"settings\": { \"bucket\": \"your-bucket\", \"region\": \"us-west-1\", \"role_arn\": \"arn:aws:iam::123456789012:role/TheServiceRole\" } }"; private static String snapshotPath = "/_snapshot/my-snapshot-repo"; private static String sampleDocument = "{" + "\"title\":\"Walk the Line\"," + "\"director\":\"James Mangold\"," + "\"year\":\"2005\"}"; private static String indexingPath = "/my-index/_doc"; static final AWSCredentialsProvider credentialsProvider = new DefaultAWSCredentialsProviderChain(); public static void main(String[] args) throws IOException { RestClient searchClient = searchClient(serviceName, region); // Register a snapshot repository HttpEntity entity = new NStringEntity(payload, ContentType.APPLICATION_JSON); Request request = new Request("PUT", snapshotPath); request.setEntity(entity); // request.addParameter(name, value); // optional parameters Response response = searchClient.performRequest(request); System.out.println(response.toString()); // Index a document entity = new NStringEntity(sampleDocument, ContentType.APPLICATION_JSON); String id = "1"; request = new Request("PUT", indexingPath + "/" + id); request.setEntity(entity); // Using a String instead of an HttpEntity sets Content-Type to application/json automatically. // request.setJsonEntity(sampleDocument); response = searchClient.performRequest(request); System.out.println(response.toString()); } // Adds the interceptor to the OpenSearch REST client public static RestClient searchClient(String serviceName, String region) { AWS4Signer signer = new AWS4Signer(); signer.setServiceName(serviceName); signer.setRegionName(region); HttpRequestInterceptor interceptor = new AWSRequestSigningApacheInterceptor(serviceName, signer, credentialsProvider); return RestClient.builder(HttpHost.create(host)).setHttpClientConfigCallback(hacb -> hacb.addInterceptorLast(interceptor)).build(); } }

Se si preferisce il client REST di livello superiore, che offre quasi tutte le stesse caratteristiche e un codice più semplice, provare il seguente esempio, che usa anche Amazon Web Services Request Signing Interceptor:

import org.apache.http.HttpHost; import org.apache.http.HttpRequestInterceptor; import org.opensearch.action.index.IndexRequest; import org.opensearch.action.index.IndexResponse; import org.opensearch.client.RequestOptions; import org.opensearch.client.RestClient; import org.opensearch.client.RestHighLevelClient; import com.amazonaws.auth.AWS4Signer; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import com.amazonaws.http.AWSRequestSigningApacheInterceptor; import java.io.IOException; import java.util.HashMap; import java.util.Map; public class AmazonOpenSearchServiceSample { private static String serviceName = "es"; private static String region = ""; // e.g. us-east-1 private static String host = ""; // e.g. https://search-mydomain.us-west-1.es.amazonaws.com private static String index = "my-index"; private static String type = "_doc"; private static String id = "1"; static final AWSCredentialsProvider credentialsProvider = new DefaultAWSCredentialsProviderChain(); public static void main(String[] args) throws IOException { RestHighLevelClient searchClient = searchClient(serviceName, region); // Create the document as a hash map Map<String, Object> document = new HashMap<>(); document.put("title", "Walk the Line"); document.put("director", "James Mangold"); document.put("year", "2005"); // Form the indexing request, send it, and print the response IndexRequest request = new IndexRequest(index, type, id).source(document); IndexResponse response = searchClient.index(request, RequestOptions.DEFAULT); System.out.println(response.toString()); } // Adds the interceptor to the OpenSearch REST client public static RestHighLevelClient searchClient(String serviceName, String region) { AWS4Signer signer = new AWS4Signer(); signer.setServiceName(serviceName); signer.setRegionName(region); HttpRequestInterceptor interceptor = new AWSRequestSigningApacheInterceptor(serviceName, signer, credentialsProvider); return new RestHighLevelClient(RestClient.builder(HttpHost.create(host)).setHttpClientConfigCallback(hacb -> hacb.addInterceptorLast(interceptor))); } }
Suggerimento

Entrambi gli esempi firmati utilizzano la catena di credenziali predefinita. Esegui aws configure utilizzando la AWS CLI per impostare le credenziali.

Python

Questo esempio utilizza il client opensearch-py legacy per Python, che può essere installato tramite pip. È necessario fornire valori per region e 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)

Invece del client, potresti preferire le richieste. I pacchetti requests-aws4auth e SDK for Python (Boto3) semplificano il processo di autenticazione ma non sono obbligatori. Dal terminale, eseguire i comandi seguenti:

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

Il seguente codice di esempio stabilisce una connessione sicura al dominio OpenSearch Service specificato e indicizza un singolo documento. È necessario fornire valori per region e 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"))

Se non desideri utilizzare opensearch-py, è sufficiente effettuare richieste HTTP standard. In questo esempio viene creato un nuovo indice con sette partizioni e due repliche:

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)

Invece di credenziali statiche, è possibile creare un'istanza AWS4Auth con credenziali di aggiornamento automatico, che è adatta per applicazioni di lunga durata che utilizzano AssumeRole. L'istanza delle credenziali aggiornabili viene utilizzata per generare credenziali statiche valide per ogni richiesta, eliminando la necessità di ricreare l'istanza AWS4Auth alla scadenza delle credenziali temporanee:

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)

L'esempio successivo utilizza la libreria Beautiful Soup per creare un file bulk da una directory locale di file HTML. Utilizzando lo stesso client del primo esempio, è possibile inviare il file all'API _bulk per l'indicizzazione. È possibile utilizzare questo codice come base per l'aggiunta di funzionalità di ricerca a un sito Web:

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

Questo primo esempio utilizza il client Elasticsearch Ruby e il middleware Faraday per eseguire la firma delle richieste. Si noti che le versioni più recenti del client potrebbero includere controlli di licenza o versione che violano artificialmente la compatibilità. Per la versione client corretta da utilizzare, consultare Compatibilità con i client Elasticsearch. In questo esempio viene utilizzata la versione consigliata 7.13.3.

Dal terminale, eseguire i comandi seguenti:

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

Questo codice di esempio crea un nuovo client , configura Faraday middleware per firmare le richieste e indicizza un singolo documento. È necessario fornire valori per full_url_and_port e 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

Se le credenziali non funzionano, esportarle sul terminale utilizzando i comandi seguenti:

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

Il prossimo esempio usa le librerie AWS SDK for Ruby e Ruby standard per inviare una richiesta HTTP firmata. Come il primo esempio, indicizza un singolo documento. È necessario fornire valori per host e region.

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

In questo esempio viene utilizzata il client opensearch-js per JavaScript per creare un indice e aggiungere un singolo documento. Per firmare la richiesta, individua prima le credenziali utilizzando il modulo credential-provider-node dalla versione 3 di SDK for JavaScript in Node.js. Quindi chiama aws4 per firmare la richiesta utilizzando Signature Version 4. È necessario fornire un valore per 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);

Questo esempio simile utilizza aws-opensearch-connector piuttosto che aws4. È necessario fornire un valore per 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);

Se non si desidera utilizzare opensearch-js, è sufficiente effettuare le richieste HTTP standard. Questa sezione include esempi per le versioni 2 e 3 dell'SDK per JavaScript in Node.js. Mentre la versione 2 viene pubblicata come un singolo pacchetto, la versione 3 ha un'architettura modulare con un pacchetto separato per ogni servizio.

Version 3

In questo esempio viene utilizzata la versione 3 di SDK per JavaScript in Node.js. Dal terminale, eseguire i comandi seguenti:

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

Questo codice di esempio indicizza un singolo documento. È necessario fornire valori per region e 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

In questo esempio viene utilizzata la versione 2 di SDK per JavaScript in Node.js. Dal terminale, esegui il comando seguente:

npm install aws-sdk

Questo codice di esempio indicizza un singolo documento. È necessario fornire valori per region e 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) } ); }) };

Se le credenziali non funzionano, esportarle sul terminale utilizzando i comandi seguenti:

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

Go

Questo esempio utilizza AWS SDK for Goe indicizza un singolo documento. È necessario fornire valori per domain e 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") }

Se le credenziali non funzionano, esportarle sul terminale utilizzando i comandi seguenti:

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