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

Update signer v1 example for AOSS #259

Merged
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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
### Added
- Github workflow for changelog verification ([#172](https://github.com/opensearch-project/opensearch-go/pull/172))
- Add Go Documentation link for the client ([#182](https://github.com/opensearch-project/opensearch-go/pull/182))
- Support for Amazon OpenSearch Serverless ([#216](https://github.com/opensearch-project/opensearch-go/pull/216))
- Support for Amazon OpenSearch Serverless ([#216](https://github.com/opensearch-project/opensearch-go/pull/216), [#259](https://github.com/opensearch-project/opensearch-go/pull/259))

### Dependencies
- Bumps `github.com/stretchr/testify` from 1.8.0 to 1.8.1
Expand Down
43 changes: 22 additions & 21 deletions USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,37 +184,40 @@ func main() {

// Create an AWS request Signer and load AWS configuration using default config folder or env vars.
// See https://docs.aws.amazon.com/opensearch-service/latest/developerguide/request-signing.html#request-signing-go
signer, err := requestsigner.NewSigner(session.Options{SharedConfigState: session.SharedConfigEnable})
signer, err := requestsigner.NewSignerWithService(
session.Options{SharedConfigState: session.SharedConfigEnable},
requestsigner.OpenSearchService, // Use requestsigner.OpenSearchServerless for Amazon OpenSearch Serverless.
)
if err != nil {
log.Fatal(err) // Do not log.fatal in a production ready app.
log.Fatalf("failed to create signer: %v", err) // Do not log.fatal in a production ready app.
}

// Create an opensearch client and use the request-signer
// Create an opensearch client and use the request-signer.
client, err := opensearch.NewClient(opensearch.Config{
Addresses: []string{endpoint},
Signer: signer,
})
if err != nil {
log.Fatal("client creation err", err)
log.Fatalf("failed to create new opensearch client: %v", err)
}

ping := opensearchapi.PingRequest{}

resp, err := ping.Do(ctx, client)
if err != nil {
log.Fatal(err)
log.Fatalf("failed to ping: %v", err)
}
defer resp.Body.Close()

if resp.IsError() {
log.Println("ping response status ", resp.Status())
log.Printf("ping response status: %q", resp.Status())

respBody, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal("response body read err", err)
log.Fatalf("failed to read response body body: %v", err)
}

log.Fatal("ping resp body", respBody)
log.Fatalf("ping resp body: %s", respBody)
}

log.Println("PING OK")
Expand All @@ -231,6 +234,7 @@ package main
import (
"context"
"log"
"strings"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
Expand All @@ -251,25 +255,25 @@ func main() {
),
)
if err != nil {
log.Fatal(err) // Do not log.fatal in a production ready app.
log.Fatalf("failed to load aws configuraiton: %v", err) // Do not log.fatal in a production ready app.
}

// Create an AWS request Signer and load AWS configuration using default config folder or env vars.
signer, err := requestsigner.NewSignerWithService(awsCfg, "es") // "aoss" for Amazon OpenSearch Serverless
signer, err := requestsigner.NewSignerWithService(awsCfg, "es") // Use "aoss" for Amazon OpenSearch Serverless
if err != nil {
log.Fatal(err) // Do not log.fatal in a production ready app.
log.Fatalf("failed to create signer: %v", err)
}

// Create an opensearch client and use the request-signer
// Create an opensearch client and use the request-signer.
client, err := opensearch.NewClient(opensearch.Config{
Addresses: []string{endpoint},
Signer: signer,
})
if err != nil {
log.Fatal("client creation err", err)
log.Fatalf("failed to create new opensearch client: %v", err)
}

indexName = "go-test-index"
indexName := "go-test-index"

// Define index mapping.
mapping := strings.NewReader(`{
Expand All @@ -287,11 +291,9 @@ func main() {
}
createIndexResponse, err := createIndex.Do(context.Background(), client)
if err != nil {
log.Println("Error ", err.Error())
log.Println("failed to create index ", err)
log.Fatal("create response body read err", err)
log.Fatalf("failed to create index: %v", err)
}
log.Println(createIndexResponse)
log.Printf("created index: %#v", createIndexResponse)

// Delete previously created index.
deleteIndex := opensearchapi.IndicesDeleteRequest{
Expand All @@ -300,10 +302,9 @@ func main() {

deleteIndexResponse, err := deleteIndex.Do(context.Background(), client)
if err != nil {
log.Println("failed to delete index ", err)
log.Fatal("delete index response body read err", err)
log.Fatalf("failed to delete index: %v", err)
}
log.Println("deleting index", deleteIndexResponse)
log.Printf("deleted index: %#v", deleteIndexResponse)
}

func getCredentialProvider(accessKey, secretAccessKey, token string) aws.CredentialsProviderFunc {
Expand Down
67 changes: 50 additions & 17 deletions signer/aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ package aws

import (
"bytes"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io"
Expand All @@ -22,54 +24,85 @@ import (
v4 "github.com/aws/aws-sdk-go/aws/signer/v4"
)

// OpenSearchService AWS OpenSearchService Name
// OpenSearchService Amazon OpenSearch Service Name
const OpenSearchService = "es"

// Signer is a interface that will implement opensearchtransport.Signer
// OpenSearchServerless Amazon OpenSearch Serverless Name
const OpenSearchServerless = "aoss"

const emptyBodySHA256 = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"

// Signer is an interface that will implement opensearchtransport.Signer
type Signer struct {
session session.Session
service string
}

// NewSigner returns an instance of Signer for AWS OpenSearchService
// NewSigner returns an instance of Signer for configured for Amazon OpenSearch Service.
// Use NewSignerWithService to configure it for another service such as Amazon OpenSearch Serverless.
func NewSigner(opts session.Options) (*Signer, error) {
return NewSignerWithService(opts, OpenSearchService)
}

// NewSignerWithService returns an instance of Signer for given service
// NewSignerWithService returns an instance of Signer for a given service.
func NewSignerWithService(opts session.Options, service string) (*Signer, error) {
if len(strings.TrimSpace(service)) < 1 {
return nil, errors.New("service cannot be empty")
}
awsSession, err := session.NewSessionWithOptions(opts)
if err != nil {
return nil, fmt.Errorf("failed to get session from given option %v due to %s", opts, err)
return nil, fmt.Errorf("failed to get session from given options %v: %v", opts, err)
}
return &Signer{
session: *awsSession,
service: service,
}, nil
}

// SignRequest signs the request using SigV4
// SignRequest signs the request using SigV4.
func (s Signer) SignRequest(req *http.Request) error {
signer := v4.NewSigner(s.session.Config.Credentials)
return sign(req, s.session.Config.Region, s.service, signer)

return sign(req, s.session.Config.Region, s.service, v4.NewSigner(s.session.Config.Credentials))
}

func sign(req *http.Request, region *string, serviceName string, signer *v4.Signer) (err error) {
func sign(req *http.Request, region *string, serviceName string, signer *v4.Signer) error {
if region == nil || len(*region) == 0 {
return fmt.Errorf("aws region cannot be empty")
}
if req.Body == nil {
_, err = signer.Sign(req, nil, serviceName, *region, time.Now().UTC())
return

var body io.ReadSeeker
var contentSha256Hash = emptyBodySHA256

if req.Body != nil {
b, err := io.ReadAll(req.Body)
if err != nil {
return fmt.Errorf("failed to read request body: %v", err)
}
body = bytes.NewReader(b)
hash, err := hexEncodedSha256(b)
if err != nil {
return fmt.Errorf("failed to calculate hash of request body: %v", err)
}
contentSha256Hash = hash
}
buf, err := io.ReadAll(req.Body)
if err != nil {
// Add the "X-Amz-Content-Sha256" header as required by Amazon OpenSearch Serverless.
req.Header.Set("X-Amz-Content-Sha256", contentSha256Hash)

if _, err := signer.Sign(req, body, serviceName, *region, time.Now().UTC()); err != nil {
return err
}
_, err = signer.Sign(req, bytes.NewReader(buf), serviceName, *region, time.Now().UTC())
return

return nil
}

func hexEncodedSha256(b []byte) (string, error) {
hasher := sha256.New()

_, err := hasher.Write(b)
if err != nil {
return "", fmt.Errorf("failed to write: %v", err)
}

digest := hasher.Sum(nil)

return hex.EncodeToString(digest), nil
}
Loading