Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for Amazon OpenSearch Serverless. #96

Merged
merged 1 commit into from
Feb 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
### Added
- Adds Github workflow for changelog verification ([#89](https://github.com/opensearch-project/opensearch-rs/pull/89))
- Adds Github workflow for unit tests ([#112](https://github.com/opensearch-project/opensearch-rs/pull/112))
- Adds support for OpenSearch Serverless ([#96](https://github.com/opensearch-project/opensearch-rs/pull/96))

### Dependencies
- Bumps `simple_logger` from 2.3.0 to 4.0.0
Expand Down
8 changes: 5 additions & 3 deletions USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
- [Add a Document to the Index](#add-a-document-to-the-index)
- [Search for a Document](#search-for-a-document)
- [Delete the Index](#delete-the-index)
- [Amazon OpenSearch Service](#amazon-opensearch-service)
- [Amazon OpenSearch and OpenSearch Serverless](#amazon-opensearch-and-opensearch-serverless)
- [Create a Client](#create-a-client-1)

# User Guide
Expand Down Expand Up @@ -103,21 +103,23 @@ client
.await?;
```

## Amazon OpenSearch Service
## Amazon OpenSearch and OpenSearch Serverless

This library supports [Amazon OpenSearch Service](https://aws.amazon.com/opensearch-service/).
This library supports [Amazon OpenSearch Service](https://aws.amazon.com/opensearch-service/) and [OpenSearch Serverless](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/serverless.html).

### Create a Client

Create a client with AWS credentials as follows. Make sure to specify the correct service name and signing region.

```rust
let url = Url::parse("https://...");
let service_name = "es"; // use "aoss" for OpenSearch Serverless
let conn_pool = SingleNodeConnectionPool::new(url?);
let region_provider = RegionProviderChain::default_provider().or_else("us-east-1");
let aws_config = aws_config::from_env().region(region_provider).load().await.clone();
let transport = TransportBuilder::new(conn_pool)
.auth(aws_config.clone().try_into()?)
.service_name(service_name)
.build()?;
let client = OpenSearch::new(transport);
```
4 changes: 3 additions & 1 deletion opensearch/examples/cat_indices.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ fn create_client() -> Result<OpenSearch, Error> {

/// Determines if Fiddler.exe proxy process is running
fn running_proxy() -> bool {
let system = System::new_with_specifics(RefreshKind::new().with_processes(ProcessRefreshKind::default()));
let system = System::new_with_specifics(
RefreshKind::new().with_processes(ProcessRefreshKind::default()),
);
let has_fiddler = system.processes_by_name("Fiddler").next().is_some();
has_fiddler
}
Expand Down
21 changes: 13 additions & 8 deletions opensearch/src/http/aws_auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,33 @@
use std::time::SystemTime;

use aws_credential_types::{
Credentials,
provider::{ProvideCredentials, SharedCredentialsProvider}
provider::{ProvideCredentials, SharedCredentialsProvider},
Credentials,
};
use aws_sigv4::{
http_request::{sign, SignableBody, SignableRequest, SigningParams, SigningSettings},
http_request::{
sign, PayloadChecksumKind, SignableBody, SignableRequest, SigningParams, SigningSettings,
},
signing_params::BuildError,
};
use aws_types::region::Region;
use reqwest::Request;

const SERVICE_NAME: &str = "es";

fn get_signing_params<'a>(
credentials: &'a Credentials,
service_name: &'a str,
region: &'a Region,
) -> Result<SigningParams<'a>, BuildError> {
let mut signing_settings = SigningSettings::default();
signing_settings.payload_checksum_kind = PayloadChecksumKind::XAmzSha256; // required for OpenSearch Serverless

let mut builder = SigningParams::builder()
.access_key(credentials.access_key_id())
.secret_key(credentials.secret_access_key())
.service_name(SERVICE_NAME)
.service_name(service_name)
.region(region.as_ref())
.time(SystemTime::now())
.settings(SigningSettings::default());
.settings(signing_settings);

builder.set_security_token(credentials.session_token());

Expand All @@ -44,11 +48,12 @@ fn get_signing_params<'a>(
pub async fn sign_request(
request: &mut Request,
credentials_provider: &SharedCredentialsProvider,
service_name: &str,
region: &Region,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let credentials = credentials_provider.provide_credentials().await?;

let params = get_signing_params(&credentials, region)?;
let params = get_signing_params(&credentials, service_name, region)?;

let uri = request.url().as_str().parse()?;

Expand Down
28 changes: 25 additions & 3 deletions opensearch/src/http/transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ pub struct TransportBuilder {
disable_proxy: bool,
headers: HeaderMap,
timeout: Option<Duration>,
#[cfg(feature = "aws-auth")]
service_name: String,
}

impl TransportBuilder {
Expand All @@ -174,6 +176,8 @@ impl TransportBuilder {
disable_proxy: false,
headers: HeaderMap::new(),
timeout: None,
#[cfg(feature = "aws-auth")]
service_name: "es".to_string(),
}
}

Expand Down Expand Up @@ -241,6 +245,15 @@ impl TransportBuilder {
self
}

/// Sets a global AWS service name.
///
/// Default is "es". Other supported services are "aoss" for OpenSearch Serverless.
#[cfg(feature = "aws-auth")]
pub fn service_name(mut self, service_name: &str) -> Self {
self.service_name = service_name.to_string();
self
}

/// Builds a [Transport] to use to send API calls to Elasticsearch.
pub fn build(self) -> Result<Transport, BuildError> {
let mut client_builder = self.client_builder;
Expand Down Expand Up @@ -313,6 +326,8 @@ impl TransportBuilder {
client,
conn_pool: self.conn_pool,
credentials: self.credentials,
#[cfg(feature = "aws-auth")]
service_name: self.service_name,
})
}
}
Expand Down Expand Up @@ -352,6 +367,8 @@ pub struct Transport {
client: reqwest::Client,
credentials: Option<Credentials>,
conn_pool: Box<dyn ConnectionPool>,
#[cfg(feature = "aws-auth")]
service_name: String,
}

impl Transport {
Expand Down Expand Up @@ -460,9 +477,14 @@ impl Transport {

#[cfg(feature = "aws-auth")]
if let Some(Credentials::AwsSigV4(credentials_provider, region)) = &self.credentials {
super::aws_auth::sign_request(&mut request, credentials_provider, region)
.await
.map_err(|e| crate::error::lib(format!("AWSV4 Signing Failed: {}", e)))?;
super::aws_auth::sign_request(
&mut request,
credentials_provider,
&self.service_name,
region,
)
.await
.map_err(|e| crate::error::lib(format!("AWSV4 Signing Failed: {}", e)))?;
}

let response = self.client.execute(request).await;
Expand Down
11 changes: 6 additions & 5 deletions opensearch/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
//! - **experimental-apis**: Enables experimental APIs. Experimental APIs are just that - an experiment. An experimental
//! API might have breaking changes in any future version, or it might even be removed entirely. This feature also
//! enables `beta-apis`.
//! - **aws-auth**: Enables authentication with Amazon OpenSearch.
//! - **aws-auth**: Enables authentication with Amazon OpenSearch and OpenSearch Serverless.
//! Performs AWS SigV4 signing using credential types from `aws-types`.
//!
//! # Getting started
Expand Down Expand Up @@ -327,9 +327,9 @@
//! # }
//! ```
//!
//! ## Amazon OpenSearch
//! ## Amazon OpenSearch and OpenSearch Serverless
//!
//! For authenticating against an Amazon OpenSearch endpoint using AWS SigV4 request signing,
//! For authenticating against an Amazon OpenSearch or OpenSearch Serverless endpoint using AWS SigV4 request signing,
//! you must enable the `aws-auth` feature, then pass the AWS credentials to the [TransportBuilder](http::transport::TransportBuilder).
//! The easiest way to retrieve AWS credentials in the required format is to use [aws-config](https://docs.rs/aws-config/latest/aws_config/).
//!
Expand All @@ -349,14 +349,15 @@
//! # use std::convert::TryInto;
//! # #[tokio::main]
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! # #[cfg(feature = "aws-auth")] {
//! let creds = aws_config::load_from_env().await;
//! let url = Url::parse("https://example.com")?;
//! let url = Url::parse("https://...")?;
//! let region_provider = RegionProviderChain::default_provider().or_else("us-east-1");
//! let aws_config = aws_config::from_env().region(region_provider).load().await.clone();
//! let conn_pool = SingleNodeConnectionPool::new(url);
//! # #[cfg(feature = "aws-auth")] {
//! let transport = TransportBuilder::new(conn_pool)
//! .auth(aws_config.clone().try_into()?)
//! .service_name("es") // use "aoss" for OpenSearch Serverless
//! .build()?;
//! let client = OpenSearch::new(transport);
//! # }
Expand Down
2 changes: 0 additions & 2 deletions opensearch/tests/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ use common::*;
use opensearch::auth::Credentials;

use base64::{prelude::BASE64_STANDARD, write::EncoderWriter as Base64Encoder};
// use std::fs::File;
// use std::io::Read;
use std::io::Write;

#[tokio::test]
Expand Down
86 changes: 86 additions & 0 deletions opensearch/tests/aws_auth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

#![cfg(feature = "aws-auth")]

pub mod common;
use common::*;
use opensearch::OpenSearch;
use regex::Regex;

use aws_config::SdkConfig;
use aws_credential_types::provider::SharedCredentialsProvider;
use aws_credential_types::Credentials;
use aws_types::region::Region;
use std::convert::TryInto;

#[tokio::test]
async fn aws_auth_get() -> Result<(), failure::Error> {
let server = server::http(move |req| async move {
let authorization_header = req.headers()["authorization"].to_str().unwrap();
let re = Regex::new(r"^AWS4-HMAC-SHA256 Credential=id/\d*/us-west-1/custom/aws4_request, SignedHeaders=accept;content-type;host;x-amz-content-sha256;x-amz-date, Signature=[a-f,0-9].*$").unwrap();
assert!(
re.is_match(authorization_header),
"{}",
authorization_header
);
let amz_content_sha256_header = req.headers()["x-amz-content-sha256"].to_str().unwrap();
assert_eq!(
amz_content_sha256_header,
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
); // SHA of empty string
http::Response::default()
});

let client = create_aws_client(format!("http://{}", server.addr()).as_ref())?;
let _response = client.ping().send().await?;

Ok(())
}

#[tokio::test]
async fn aws_auth_post() -> Result<(), failure::Error> {
let server = server::http(move |req| async move {
let amz_content_sha256_header = req.headers()["x-amz-content-sha256"].to_str().unwrap();
assert_eq!(
amz_content_sha256_header,
"f3a842f988a653a734ebe4e57c45f19293a002241a72f0b3abbff71e4f5297b9"
); // SHA of the JSON
http::Response::default()
});

let client = create_aws_client(format!("http://{}", server.addr()).as_ref())?;
client
.index(opensearch::IndexParts::Index("movies"))
.body(serde_json::json!({
"title": "Moneyball",
"director": "Bennett Miller",
"year": 2011
}
))
.send()
.await?;

Ok(())
}

fn create_aws_client(addr: &str) -> Result<OpenSearch, failure::Error> {
let aws_creds = Credentials::new("id", "secret", None, None, "token");
let creds_provider = SharedCredentialsProvider::new(aws_creds);
let aws_config = SdkConfig::builder()
.credentials_provider(creds_provider)
.region(Region::new("us-west-1"))
.build();
let builder = client::create_builder(addr)
.auth(aws_config.clone().try_into()?)
.service_name("custom");
Ok(client::create(builder))
}
5 changes: 4 additions & 1 deletion opensearch/tests/cert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
//!
//!
//! DETACH=true .ci/run-opensearch.sh
#![cfg(all(feature = "_integration", any(feature = "native-tls", feature = "rustls-tls")))]
#![cfg(all(
feature = "_integration",
any(feature = "native-tls", feature = "rustls-tls")
))]

pub mod common;
use common::*;
Expand Down
4 changes: 3 additions & 1 deletion opensearch/tests/common/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ pub fn cluster_addr() -> String {

/// Checks if Fiddler proxy process is running
fn running_proxy() -> bool {
let system = System::new_with_specifics(RefreshKind::new().with_processes(ProcessRefreshKind::default()));
let system = System::new_with_specifics(
RefreshKind::new().with_processes(ProcessRefreshKind::default()),
);
let has_fiddler = system.processes_by_name("Fiddler").next().is_some();
has_fiddler
}
Expand Down
2 changes: 1 addition & 1 deletion opensearch/tests/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
* GitHub history for details.
*/

#![cfg(feature = "_integration")]
#![cfg(feature = "_integration")]

pub mod common;
use common::*;
Expand Down
2 changes: 1 addition & 1 deletion yaml_test_runner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ serde_yaml = "0.9"
serde_json = { version = "~1", features = ["arbitrary_precision"] }
simple_logger = "4.0.0"
syn = { version = "~1.0", features = ["full"] }
sysinfo = "0.26.4"
sysinfo = "0.26"
url = "2.1.1"
yaml-rust = "0.4.3"
tar = "~0.4"
Expand Down