Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support for AWS SDK v2 Signer (#116)
* Also raises minimum go version to 1.15 * Updates dependencies Signed-off-by: Máté Lang <langmatelaszlo@gmail.com> (cherry picked from commit f4a0b24)
- Loading branch information
1 parent
80646a4
commit b394bda
Showing
4 changed files
with
294 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,10 @@ | ||
module github.com/opensearch-project/opensearch-go/v2 | ||
|
||
go 1.11 | ||
go 1.15 | ||
|
||
require ( | ||
github.com/aws/aws-sdk-go v1.42.27 | ||
github.com/stretchr/testify v1.7.0 | ||
golang.org/x/text v0.3.7 // indirect | ||
github.com/aws/aws-sdk-go v1.44.36 | ||
github.com/aws/aws-sdk-go-v2 v1.16.5 | ||
github.com/aws/aws-sdk-go-v2/config v1.15.11 | ||
github.com/stretchr/testify v1.7.2 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// 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. | ||
|
||
package awsv2 | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"crypto/sha256" | ||
"encoding/hex" | ||
"errors" | ||
"fmt" | ||
"github.com/aws/aws-sdk-go-v2/aws" | ||
awsSignerV4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" | ||
"github.com/opensearch-project/opensearch-go/v2/signer" | ||
"io/ioutil" | ||
"net/http" | ||
"strings" | ||
"time" | ||
) | ||
|
||
const ( | ||
openSearchService = "es" | ||
emptyStringSHA256 = `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855` | ||
) | ||
|
||
type awsSdkV2Signer struct { | ||
service string | ||
signer *awsSignerV4.Signer | ||
awsCfg aws.Config | ||
} | ||
|
||
// NewSigner returns an instance of Signer for AWS OpenSearchService | ||
func NewSigner(cfg aws.Config) (signer.Signer, error) { | ||
return NewSignerWithService(cfg, openSearchService) | ||
} | ||
|
||
// NewSignerWithService returns an instance of Signer for given service | ||
func NewSignerWithService(cfg aws.Config, service string) (signer.Signer, error) { | ||
if len(strings.TrimSpace(service)) < 1 { | ||
return nil, errors.New("service cannot be empty") | ||
} | ||
|
||
return &awsSdkV2Signer{ | ||
service: service, | ||
signer: awsSignerV4.NewSigner(), | ||
awsCfg: cfg, | ||
}, nil | ||
} | ||
|
||
func (s *awsSdkV2Signer) SignRequest(r *http.Request) error { | ||
ctx := context.Background() | ||
t := time.Now() | ||
|
||
creds, err := s.awsCfg.Credentials.Retrieve(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if len(s.awsCfg.Region) == 0 { | ||
return fmt.Errorf("aws region cannot be empty") | ||
} | ||
|
||
hash, err := hexEncodedSha256OfRequest(r) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return s.signer.SignHTTP(ctx, creds, r, hash, s.service, s.awsCfg.Region, t) | ||
} | ||
|
||
func hexEncodedSha256OfRequest(r *http.Request) (string, error) { | ||
if r.Body == nil { | ||
return emptyStringSHA256, nil | ||
} | ||
|
||
hasher := sha256.New() | ||
reqBodyBytes, err := ioutil.ReadAll(r.Body) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
if err := r.Body.Close(); err != nil { | ||
return "", err | ||
} | ||
r.Body = ioutil.NopCloser(bytes.NewBuffer(reqBodyBytes)) | ||
|
||
hasher.Write(reqBodyBytes) | ||
digest := hasher.Sum(nil) | ||
|
||
return hex.EncodeToString(digest), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
// 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. | ||
|
||
package awsv2 | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"github.com/aws/aws-sdk-go-v2/aws" | ||
"github.com/aws/aws-sdk-go-v2/config" | ||
"github.com/stretchr/testify/assert" | ||
"net/http" | ||
"os" | ||
"testing" | ||
) | ||
|
||
func getCredentialProvider(accessKey, secretAccessKey, token string) aws.CredentialsProviderFunc { | ||
return func(ctx context.Context) (aws.Credentials, error) { | ||
c := &aws.Credentials{ | ||
AccessKeyID: accessKey, | ||
SecretAccessKey: secretAccessKey, | ||
SessionToken: token, | ||
} | ||
return *c, nil | ||
} | ||
} | ||
|
||
func TestV4SignerAwsSdkV2(t *testing.T) { | ||
t.Run("sign request failed due to no region found", func(t *testing.T) { | ||
req, err := http.NewRequest(http.MethodGet, "https://localhost:9200", nil) | ||
assert.NoError(t, err) | ||
region := os.Getenv("AWS_REGION") | ||
os.Setenv("AWS_REGION", "") | ||
defer func() { | ||
os.Setenv("AWS_REGION", region) | ||
}() | ||
awsCfg, err := config.LoadDefaultConfig(context.TODO(), | ||
config.WithCredentialsProvider( | ||
getCredentialProvider("AKID", "SECRET_KEY", "TOKEN"), | ||
), | ||
config.WithRegion(""), | ||
) | ||
assert.NoError(t, err) | ||
|
||
signer, err := NewSigner(awsCfg) | ||
assert.NoError(t, err) | ||
err = signer.SignRequest(req) | ||
|
||
assert.EqualErrorf( | ||
t, err, "aws region cannot be empty", "unexpected error") | ||
}) | ||
t.Run("sign request success", func(t *testing.T) { | ||
req, err := http.NewRequest(http.MethodGet, "https://localhost:9200", nil) | ||
assert.NoError(t, err) | ||
region := os.Getenv("AWS_REGION") | ||
os.Setenv("AWS_REGION", "us-west-2") | ||
defer func() { | ||
os.Setenv("AWS_REGION", region) | ||
}() | ||
|
||
awsCfg, err := config.LoadDefaultConfig(context.TODO(), | ||
config.WithRegion("us-west-2"), | ||
config.WithCredentialsProvider( | ||
getCredentialProvider("AKID", "SECRET_KEY", "TOKEN"), | ||
), | ||
) | ||
assert.NoError(t, err) | ||
|
||
signer, err := NewSigner(awsCfg) | ||
assert.NoError(t, err) | ||
|
||
err = signer.SignRequest(req) | ||
assert.NoError(t, err) | ||
|
||
q := req.Header | ||
assert.NotEmpty(t, q.Get("Authorization")) | ||
assert.NotEmpty(t, q.Get("X-Amz-Date")) | ||
}) | ||
t.Run("sign request success with body", func(t *testing.T) { | ||
req, err := http.NewRequest( | ||
http.MethodPost, "https://localhost:9200", | ||
bytes.NewBuffer([]byte(`some data`))) | ||
assert.NoError(t, err) | ||
region := os.Getenv("AWS_REGION") | ||
os.Setenv("AWS_REGION", "us-west-2") | ||
defer func() { | ||
os.Setenv("AWS_REGION", region) | ||
}() | ||
|
||
awsCfg, err := config.LoadDefaultConfig(context.TODO(), | ||
config.WithRegion("us-west-2"), | ||
config.WithCredentialsProvider( | ||
getCredentialProvider("AKID", "SECRET_KEY", "TOKEN"), | ||
), | ||
) | ||
assert.NoError(t, err) | ||
|
||
signer, err := NewSigner(awsCfg) | ||
assert.NoError(t, err) | ||
|
||
err = signer.SignRequest(req) | ||
assert.NoError(t, err) | ||
q := req.Header | ||
assert.NotEmpty(t, q.Get("Authorization")) | ||
assert.NotEmpty(t, q.Get("X-Amz-Date")) | ||
}) | ||
|
||
t.Run("sign request success with body for other AWS Services", func(t *testing.T) { | ||
req, err := http.NewRequest( | ||
http.MethodPost, "https://localhost:9200", | ||
bytes.NewBuffer([]byte(`some data`))) | ||
assert.NoError(t, err) | ||
region := os.Getenv("AWS_REGION") | ||
os.Setenv("AWS_REGION", "us-west-2") | ||
defer func() { | ||
os.Setenv("AWS_REGION", region) | ||
}() | ||
|
||
awsCfg, err := config.LoadDefaultConfig(context.TODO(), | ||
config.WithRegion("us-west-2"), | ||
config.WithCredentialsProvider( | ||
getCredentialProvider("AKID", "SECRET_KEY", "TOKEN"), | ||
), | ||
) | ||
assert.NoError(t, err) | ||
|
||
signer, err := NewSignerWithService(awsCfg, "ec") | ||
assert.NoError(t, err) | ||
|
||
assert.NoError(t, err) | ||
err = signer.SignRequest(req) | ||
assert.NoError(t, err) | ||
q := req.Header | ||
assert.NotEmpty(t, q.Get("Authorization")) | ||
assert.NotEmpty(t, q.Get("X-Amz-Date")) | ||
}) | ||
|
||
t.Run("sign request failed due to invalid service", func(t *testing.T) { | ||
awsCfg, err := config.LoadDefaultConfig(context.TODO(), | ||
config.WithRegion("us-west-2"), | ||
config.WithCredentialsProvider( | ||
getCredentialProvider("AKID", "SECRET_KEY", "TOKEN"), | ||
), | ||
) | ||
assert.NoError(t, err) | ||
|
||
_, err = NewSignerWithService(awsCfg, "") | ||
assert.EqualError(t, err, "service cannot be empty") | ||
}) | ||
} |