Repository: opensearch-project/opensearch-go
Pull request #116 (2022-06-09 07:40:01)

Adding support for AWS SDK V2 request signer
### Description
This PR introduces support for a new package that allows to use the new AWS SDK V2 to sign the OpenSearch Go client's HTTP request messages according to the AWS Signature spec V4 .

I've also updated all project dependencies to latest versions for bonus maintenance.

### Issues Resolved
No issues found in tracker relating to this.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check [here](https://github.com/opensearch-project/OpenSearch/blob/main/CONTRIBUTING.md#developer-certificate-of-origin).


VijayanB: @matelang Thanks for creating PR. Will review your PR this week.
VachaShah: > For tracking, I've opened an Issue and PR on AWS SDK with the fix from the second commit: [aws/aws-sdk-go-v2#1728](https://github.com/aws/aws-sdk-go-v2/issues/1728) [aws/aws-sdk-go-v2#1729](https://github.com/aws/aws-sdk-go-v2/pull/1729)

Thank you @matelang! Retrying the CI
VijayanB: According to their documentation:
```
The v2 SDK requires a minimum version of Go 1.15.
```
I believe opensearch-go supports from go 1.11. Should we update this as well?
matelang: > According to their documentation:
> 
> ```
> The v2 SDK requires a minimum version of Go 1.15.
> ```
> 
> I believe opensearch-go supports from go 1.11. Should we update this as well?

I've added a new commit to address this:
https://github.com/opensearch-project/opensearch-go/pull/116/commits/6d4b420465990ccf0024ba5ff872110ce86beb9d
matelang: I've also fetched upstream and adapted the import path of the signer accordingly (appended v2 to import path).
VachaShah: @matelang Looks like one of the commits has a sign off missing. Can you please fix it?
matelang: > @matelang Looks like one of the commits has a sign off missing. Can you please fix it?

@VachaShah I think it was the merge commit, I've fixed it by squashing all the work into just 1 commit. Have not change anything else.
VijayanB: @matelang Thanks for adding v2 support. Unfortunately i don't know way of writing integration test for aws iam feature. Did you get chance to test v2 support manually from opensearch-go? 
matelang: > @matelang Thanks for adding v2 support. Unfortunately i don't know way of writing integration test for aws iam feature. Did you get chance to test v2 support manually from opensearch-go? 

We already use the vendored version of the signer in production, so far it is working great for us.

Patch:
diff --git a/README.md b/README.md
index de14ad7..f08c6ef 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-[![Go Reference](https://pkg.go.dev/badge/github.com/opensearch-project/opensearch-go.svg)](https://pkg.go.dev/github.com/opensearch-project/opensearch-go)
+[![Go Reference](https://pkg.go.dev/badge/github.com/opensearch-project/opensearch-go.svg)](https://pkg.go.dev/github.com/opensearch-project/opensearch-go/v2)
 [![Build](https://github.com/opensearch-project/opensearch-go/actions/workflows/lint.yml/badge.svg)](https://github.com/opensearch-project/opensearch-go/actions/workflows/lint.yml)
 [![Unit](https://github.com/opensearch-project/opensearch-go/actions/workflows/test-unit.yml/badge.svg)](https://github.com/opensearch-project/opensearch-go/actions/workflows/test-unit.yml)
 [![Integration](https://github.com/opensearch-project/opensearch-go/actions/workflows/test-integration.yml/badge.svg)](https://github.com/opensearch-project/opensearch-go/actions/workflows/test-integration.yml)
diff --git a/go.mod b/go.mod
index df2ad6f..0b81c7b 100644
--- a/go.mod
+++ b/go.mod
@@ -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
 )
diff --git a/go.sum b/go.sum
index 186d995..1579987 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,31 @@
-github.com/aws/aws-sdk-go v1.42.27 h1:kxsBXQg3ee6LLbqjp5/oUeDgG7TENFrWYDmEVnd7spU=
-github.com/aws/aws-sdk-go v1.42.27/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc=
+github.com/aws/aws-sdk-go v1.44.36 h1:zeHQau9fGEIE0CsBnQFCqqYWc98m2bk7BQKtkdEqzF4=
+github.com/aws/aws-sdk-go v1.44.36/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
+github.com/aws/aws-sdk-go-v2 v1.16.5 h1:Ah9h1TZD9E2S1LzHpViBO3Jz9FPL5+rmflmb8hXirtI=
+github.com/aws/aws-sdk-go-v2 v1.16.5/go.mod h1:Wh7MEsmEApyL5hrWzpDkba4gwAPc5/piwLVLFnCxp48=
+github.com/aws/aws-sdk-go-v2/config v1.15.11 h1:qfec8AtiCqVbwMcx51G1yO2PYVfWfhp2lWkDH65V9HA=
+github.com/aws/aws-sdk-go-v2/config v1.15.11/go.mod h1:mD5tNFciV7YHNjPpFYqJ6KGpoSfY107oZULvTHIxtbI=
+github.com/aws/aws-sdk-go-v2/credentials v1.12.6 h1:No1wZFW4bcM/uF6Tzzj6IbaeQJM+xxqXOYmoObm33ws=
+github.com/aws/aws-sdk-go-v2/credentials v1.12.6/go.mod h1:mQgnRmBPF2S/M01W4T4Obp3ZaZB6o1s/R8cOUda9vtI=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.6 h1:+NZzDh/RpcQTpo9xMFUgkseIam6PC+YJbdhbQp1NOXI=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.6/go.mod h1:ClLMcuQA/wcHPmOIfNzNI4Y1Q0oDbmEkbYhMFOzHDh8=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.12 h1:Zt7DDk5V7SyQULUUwIKzsROtVzp/kVvcz15uQx/Tkow=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.12/go.mod h1:Afj/U8svX6sJ77Q+FPWMzabJ9QjbwP32YlopgKALUpg=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.6 h1:eeXdGVtXEe+2Jc49+/vAzna3FAQnUD4AagAw8tzbmfc=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.6/go.mod h1:FwpAKI+FBPIELJIdmQzlLtRe8LQSOreMcM2wBsPMvvc=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.3.13 h1:L/l0WbIpIadRO7i44jZh1/XeXpNDX0sokFppb4ZnXUI=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.3.13/go.mod h1:hiM/y1XPp3DoEPhoVEYc/CZcS58dP6RKJRDFp99wdX0=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.6 h1:0ZxYAZ1cn7Swi/US55VKciCE6RhRHIwCKIWaMLdT6pg=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.6/go.mod h1:DxAPjquoEHf3rUHh1b9+47RAaXB8/7cB6jkzCt/GOEI=
+github.com/aws/aws-sdk-go-v2/service/sso v1.11.9 h1:Gju1UO3E8ceuoYc/AHcdXLuTZ0WGE1PT2BYDwcYhJg8=
+github.com/aws/aws-sdk-go-v2/service/sso v1.11.9/go.mod h1:UqRD9bBt15P0ofRyDZX6CfsIqPpzeHOhZKWzgSuAzpo=
+github.com/aws/aws-sdk-go-v2/service/sts v1.16.7 h1:HLzjwQM9975FQWSF3uENDGHT1gFQm/q3QXu2BYIcI08=
+github.com/aws/aws-sdk-go-v2/service/sts v1.16.7/go.mod h1:lVxTdiiSHY3jb1aeg+BBFtDzZGSUCv6qaNOyEGCJ1AY=
+github.com/aws/smithy-go v1.11.3 h1:DQixirEFM9IaKxX1olZ3ke3nvxRS2xMDteKIDWxozW8=
+github.com/aws/smithy-go v1.11.3/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
 github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
@@ -10,14 +34,13 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM=
-golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
+github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -25,5 +48,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/signer/awsv2/sdkv2signer.go b/signer/awsv2/sdkv2signer.go
new file mode 100644
index 0000000..3d44a17
--- /dev/null
+++ b/signer/awsv2/sdkv2signer.go
@@ -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
+}


Test patch:
diff --git a/signer/awsv2/sdkv2signer_test.go b/signer/awsv2/sdkv2signer_test.go
new file mode 100644
index 0000000..95d1f24
--- /dev/null
+++ b/signer/awsv2/sdkv2signer_test.go
@@ -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")
+	})
+}