forked from TrueCloudLab/frostfs-s3-gw
[#339] Drop aws-sdk-go v1
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
parent
81a1f9a959
commit
0397bf83e7
18 changed files with 201 additions and 1759 deletions
|
@ -9,19 +9,20 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
v4 "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4"
|
|
||||||
v4a "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4asdk2"
|
v4a "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4asdk2"
|
||||||
|
v4 "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4sdk2/signer/v4"
|
||||||
apiErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apiErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/tokens"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/tokens"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
credentialsv2 "github.com/aws/aws-sdk-go-v2/credentials"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
"github.com/aws/aws-sdk-go-v2/credentials"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -192,10 +193,11 @@ func (c *Center) Authenticate(r *http.Request) (*middleware.Box, error) {
|
||||||
Date: creds[1],
|
Date: creds[1],
|
||||||
IsPresigned: true,
|
IsPresigned: true,
|
||||||
Preamble: signaturePreambleSigV4,
|
Preamble: signaturePreambleSigV4,
|
||||||
|
PayloadHash: r.Header.Get(AmzContentSHA256),
|
||||||
}
|
}
|
||||||
authHdr.Expiration, err = time.ParseDuration(queryValues.Get(AmzExpires) + "s")
|
authHdr.Expiration, err = time.ParseDuration(queryValues.Get(AmzExpires) + "s")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("couldn't parse X-Amz-Expires: %w", err)
|
return nil, fmt.Errorf("%w: couldn't parse X-Amz-Expires %v", apiErrors.GetAPIError(apiErrors.ErrMalformedExpires), err)
|
||||||
}
|
}
|
||||||
signatureDateTimeStr = queryValues.Get(AmzDate)
|
signatureDateTimeStr = queryValues.Get(AmzDate)
|
||||||
} else if queryValues.Get(AmzAlgorithm) == signaturePreambleSigV4A {
|
} else if queryValues.Get(AmzAlgorithm) == signaturePreambleSigV4A {
|
||||||
|
@ -212,10 +214,11 @@ func (c *Center) Authenticate(r *http.Request) (*middleware.Box, error) {
|
||||||
Date: creds[1],
|
Date: creds[1],
|
||||||
IsPresigned: true,
|
IsPresigned: true,
|
||||||
Preamble: signaturePreambleSigV4A,
|
Preamble: signaturePreambleSigV4A,
|
||||||
|
PayloadHash: r.Header.Get(AmzContentSHA256),
|
||||||
}
|
}
|
||||||
authHdr.Expiration, err = time.ParseDuration(queryValues.Get(AmzExpires) + "s")
|
authHdr.Expiration, err = time.ParseDuration(queryValues.Get(AmzExpires) + "s")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("couldn't parse X-Amz-Expires: %w", err)
|
return nil, fmt.Errorf("%w: couldn't parse X-Amz-Expires %v", apiErrors.GetAPIError(apiErrors.ErrMalformedExpires), err)
|
||||||
}
|
}
|
||||||
signatureDateTimeStr = queryValues.Get(AmzDate)
|
signatureDateTimeStr = queryValues.Get(AmzDate)
|
||||||
} else {
|
} else {
|
||||||
|
@ -258,7 +261,7 @@ func (c *Center) Authenticate(r *http.Request) (*middleware.Box, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
clonedRequest := cloneRequest(r, authHdr)
|
clonedRequest := cloneRequest(r, authHdr)
|
||||||
if err = c.checkSign(authHdr, box, clonedRequest, signatureDateTime); err != nil {
|
if err = c.checkSign(r.Context(), authHdr, box, clonedRequest, signatureDateTime); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,26 +389,36 @@ func cloneRequest(r *http.Request, authHeader *AuthHeader) *http.Request {
|
||||||
return otherRequest
|
return otherRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Center) checkSign(authHeader *AuthHeader, box *accessbox.Box, request *http.Request, signatureDateTime time.Time) error {
|
func (c *Center) checkSign(ctx context.Context, authHeader *AuthHeader, box *accessbox.Box, request *http.Request, signatureDateTime time.Time) error {
|
||||||
var signature string
|
var signature string
|
||||||
|
|
||||||
switch authHeader.Preamble {
|
switch authHeader.Preamble {
|
||||||
case signaturePreambleSigV4:
|
case signaturePreambleSigV4:
|
||||||
awsCreds := credentials.NewStaticCredentials(authHeader.AccessKeyID, box.Gate.SecretKey, "")
|
creds := aws.Credentials{
|
||||||
signer := v4.NewSigner(awsCreds)
|
AccessKeyID: authHeader.AccessKeyID,
|
||||||
signer.DisableURIPathEscaping = true
|
SecretAccessKey: box.Gate.SecretKey,
|
||||||
|
}
|
||||||
|
signer := v4.NewSigner(func(options *v4.SignerOptions) {
|
||||||
|
options.DisableURIPathEscaping = true
|
||||||
|
})
|
||||||
|
|
||||||
if authHeader.IsPresigned {
|
if authHeader.IsPresigned {
|
||||||
if err := checkPresignedDate(authHeader, signatureDateTime); err != nil {
|
if err := checkPresignedDate(authHeader, signatureDateTime); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := signer.Presign(request, nil, authHeader.Service, authHeader.Region, authHeader.Expiration, signatureDateTime); err != nil {
|
signedURI, _, err := signer.PresignHTTP(ctx, creds, request, authHeader.PayloadHash, authHeader.Service, authHeader.Region, signatureDateTime)
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("failed to pre-sign temporary HTTP request: %w", err)
|
return fmt.Errorf("failed to pre-sign temporary HTTP request: %w", err)
|
||||||
}
|
}
|
||||||
signature = request.URL.Query().Get(AmzSignature)
|
|
||||||
|
u, err := url.ParseRequestURI(signedURI)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
signature = u.Query().Get(AmzSignature)
|
||||||
} else {
|
} else {
|
||||||
if _, err := signer.Sign(request, nil, authHeader.Service, authHeader.Region, signatureDateTime); err != nil {
|
if err := signer.SignHTTP(ctx, creds, request, authHeader.PayloadHash, authHeader.Service, authHeader.Region, signatureDateTime); err != nil {
|
||||||
return fmt.Errorf("failed to sign temporary HTTP request: %w", err)
|
return fmt.Errorf("failed to sign temporary HTTP request: %w", err)
|
||||||
}
|
}
|
||||||
signature = c.reg.GetSubmatches(request.Header.Get(AuthorizationHdr))["v4_signature"]
|
signature = c.reg.GetSubmatches(request.Header.Get(AuthorizationHdr))["v4_signature"]
|
||||||
|
@ -421,7 +434,7 @@ func (c *Center) checkSign(authHeader *AuthHeader, box *accessbox.Box, request *
|
||||||
})
|
})
|
||||||
|
|
||||||
credAdapter := v4a.SymmetricCredentialAdaptor{
|
credAdapter := v4a.SymmetricCredentialAdaptor{
|
||||||
SymmetricProvider: credentialsv2.NewStaticCredentialsProvider(authHeader.AccessKeyID, box.Gate.SecretKey, ""),
|
SymmetricProvider: credentials.NewStaticCredentialsProvider(authHeader.AccessKeyID, box.Gate.SecretKey, ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
creds, err := credAdapter.RetrievePrivateKey(request.Context())
|
creds, err := credAdapter.RetrievePrivateKey(request.Context())
|
||||||
|
|
|
@ -13,8 +13,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
v4 "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4"
|
|
||||||
v4a "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4asdk2"
|
v4a "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4asdk2"
|
||||||
|
v4 "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4sdk2/signer/v4"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
||||||
|
@ -24,8 +24,8 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
||||||
credentialsv2 "github.com/aws/aws-sdk-go-v2/credentials"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
"github.com/aws/aws-sdk-go-v2/credentials"
|
||||||
"github.com/aws/smithy-go/logging"
|
"github.com/aws/smithy-go/logging"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -135,7 +135,7 @@ func TestSignatureV4A(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
credAdapter := v4a.SymmetricCredentialAdaptor{
|
credAdapter := v4a.SymmetricCredentialAdaptor{
|
||||||
SymmetricProvider: credentialsv2.NewStaticCredentialsProvider(accessKeyID, secretKey, ""),
|
SymmetricProvider: credentials.NewStaticCredentialsProvider(accessKeyID, secretKey, ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
bodyStr := `
|
bodyStr := `
|
||||||
|
@ -245,6 +245,7 @@ func (f *frostFSMock) CreateObject(context.Context, tokens.PrmObjectCreate) (oid
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthenticate(t *testing.T) {
|
func TestAuthenticate(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
key, err := keys.NewPrivateKey()
|
key, err := keys.NewPrivateKey()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -275,8 +276,8 @@ func TestAuthenticate(t *testing.T) {
|
||||||
|
|
||||||
accessKeyID := addr.Container().String() + "0" + addr.Object().String()
|
accessKeyID := addr.Container().String() + "0" + addr.Object().String()
|
||||||
|
|
||||||
awsCreds := credentials.NewStaticCredentials(accessKeyID, secret.SecretKey, "")
|
awsCreds := aws.Credentials{AccessKeyID: accessKeyID, SecretAccessKey: secret.SecretKey}
|
||||||
defaultSigner := v4.NewSigner(awsCreds)
|
defaultSigner := v4.NewSigner()
|
||||||
|
|
||||||
service, region := "s3", "default"
|
service, region := "s3", "default"
|
||||||
invalidValue := "invalid-value"
|
invalidValue := "invalid-value"
|
||||||
|
@ -299,7 +300,7 @@ func TestAuthenticate(t *testing.T) {
|
||||||
prefixes: []string{addr.Container().String()},
|
prefixes: []string{addr.Container().String()},
|
||||||
request: func() *http.Request {
|
request: func() *http.Request {
|
||||||
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
_, err = defaultSigner.Sign(r, nil, service, region, time.Now())
|
err = defaultSigner.SignHTTP(ctx, awsCreds, r, "", service, region, time.Now())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return r
|
return r
|
||||||
}(),
|
}(),
|
||||||
|
@ -325,8 +326,8 @@ func TestAuthenticate(t *testing.T) {
|
||||||
name: "invalid access key id format",
|
name: "invalid access key id format",
|
||||||
request: func() *http.Request {
|
request: func() *http.Request {
|
||||||
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
signer := v4.NewSigner(credentials.NewStaticCredentials(addr.Object().String(), secret.SecretKey, ""))
|
cred := aws.Credentials{AccessKeyID: addr.Object().String(), SecretAccessKey: secret.SecretKey}
|
||||||
_, err = signer.Sign(r, nil, service, region, time.Now())
|
err = v4.NewSigner().SignHTTP(ctx, cred, r, "", service, region, time.Now())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return r
|
return r
|
||||||
}(),
|
}(),
|
||||||
|
@ -338,7 +339,7 @@ func TestAuthenticate(t *testing.T) {
|
||||||
prefixes: []string{addr.Object().String()},
|
prefixes: []string{addr.Object().String()},
|
||||||
request: func() *http.Request {
|
request: func() *http.Request {
|
||||||
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
_, err = defaultSigner.Sign(r, nil, service, region, time.Now())
|
err = defaultSigner.SignHTTP(ctx, awsCreds, r, "", service, region, time.Now())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return r
|
return r
|
||||||
}(),
|
}(),
|
||||||
|
@ -349,8 +350,8 @@ func TestAuthenticate(t *testing.T) {
|
||||||
name: "invalid access key id value",
|
name: "invalid access key id value",
|
||||||
request: func() *http.Request {
|
request: func() *http.Request {
|
||||||
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
signer := v4.NewSigner(credentials.NewStaticCredentials(accessKeyID[:len(accessKeyID)-4], secret.SecretKey, ""))
|
cred := aws.Credentials{AccessKeyID: accessKeyID[:len(accessKeyID)-4], SecretAccessKey: secret.SecretKey}
|
||||||
_, err = signer.Sign(r, nil, service, region, time.Now())
|
err = v4.NewSigner().SignHTTP(ctx, cred, r, "", service, region, time.Now())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return r
|
return r
|
||||||
}(),
|
}(),
|
||||||
|
@ -361,8 +362,8 @@ func TestAuthenticate(t *testing.T) {
|
||||||
name: "unknown access key id",
|
name: "unknown access key id",
|
||||||
request: func() *http.Request {
|
request: func() *http.Request {
|
||||||
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
signer := v4.NewSigner(credentials.NewStaticCredentials(addr.Object().String()+"0"+addr.Container().String(), secret.SecretKey, ""))
|
cred := aws.Credentials{AccessKeyID: addr.Object().String() + "0" + addr.Container().String(), SecretAccessKey: secret.SecretKey}
|
||||||
_, err = signer.Sign(r, nil, service, region, time.Now())
|
err = v4.NewSigner().SignHTTP(ctx, cred, r, "", service, region, time.Now())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return r
|
return r
|
||||||
}(),
|
}(),
|
||||||
|
@ -372,8 +373,8 @@ func TestAuthenticate(t *testing.T) {
|
||||||
name: "invalid signature",
|
name: "invalid signature",
|
||||||
request: func() *http.Request {
|
request: func() *http.Request {
|
||||||
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
signer := v4.NewSigner(credentials.NewStaticCredentials(accessKeyID, "secret", ""))
|
cred := aws.Credentials{AccessKeyID: accessKeyID, SecretAccessKey: "secret"}
|
||||||
_, err = signer.Sign(r, nil, service, region, time.Now())
|
err = v4.NewSigner().SignHTTP(ctx, cred, r, "", service, region, time.Now())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return r
|
return r
|
||||||
}(),
|
}(),
|
||||||
|
@ -385,7 +386,7 @@ func TestAuthenticate(t *testing.T) {
|
||||||
prefixes: []string{addr.Container().String()},
|
prefixes: []string{addr.Container().String()},
|
||||||
request: func() *http.Request {
|
request: func() *http.Request {
|
||||||
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
_, err = defaultSigner.Sign(r, nil, service, region, time.Now())
|
err = defaultSigner.SignHTTP(ctx, awsCreds, r, "", service, region, time.Now())
|
||||||
r.Header.Set(AmzDate, invalidValue)
|
r.Header.Set(AmzDate, invalidValue)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return r
|
return r
|
||||||
|
@ -397,7 +398,7 @@ func TestAuthenticate(t *testing.T) {
|
||||||
prefixes: []string{addr.Container().String()},
|
prefixes: []string{addr.Container().String()},
|
||||||
request: func() *http.Request {
|
request: func() *http.Request {
|
||||||
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
_, err = defaultSigner.Sign(r, nil, service, region, time.Now())
|
err = defaultSigner.SignHTTP(ctx, awsCreds, r, "", service, region, time.Now())
|
||||||
r.Header.Set(AmzContentSHA256, invalidValue)
|
r.Header.Set(AmzContentSHA256, invalidValue)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return r
|
return r
|
||||||
|
@ -408,7 +409,10 @@ func TestAuthenticate(t *testing.T) {
|
||||||
name: "valid presign",
|
name: "valid presign",
|
||||||
request: func() *http.Request {
|
request: func() *http.Request {
|
||||||
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
_, err = defaultSigner.Presign(r, nil, service, region, time.Minute, time.Now())
|
r.Header.Set(AmzExpires, "60")
|
||||||
|
signedURI, _, err := defaultSigner.PresignHTTP(ctx, awsCreds, r, "", service, region, time.Now())
|
||||||
|
require.NoError(t, err)
|
||||||
|
r.URL, err = url.ParseRequestURI(signedURI)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return r
|
return r
|
||||||
}(),
|
}(),
|
||||||
|
@ -430,20 +434,24 @@ func TestAuthenticate(t *testing.T) {
|
||||||
name: "presign, bad X-Amz-Expires",
|
name: "presign, bad X-Amz-Expires",
|
||||||
request: func() *http.Request {
|
request: func() *http.Request {
|
||||||
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
_, err = defaultSigner.Presign(r, nil, service, region, time.Minute, time.Now())
|
r.Header.Set(AmzExpires, invalidValue)
|
||||||
queryParams := r.URL.Query()
|
signedURI, _, err := defaultSigner.PresignHTTP(ctx, awsCreds, r, UnsignedPayload, service, region, time.Now())
|
||||||
queryParams.Set("X-Amz-Expires", invalidValue)
|
require.NoError(t, err)
|
||||||
r.URL.RawQuery = queryParams.Encode()
|
r.URL, err = url.ParseRequestURI(signedURI)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return r
|
return r
|
||||||
}(),
|
}(),
|
||||||
err: true,
|
err: true,
|
||||||
|
errCode: errors.ErrMalformedExpires,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "presign, expired",
|
name: "presign, expired",
|
||||||
request: func() *http.Request {
|
request: func() *http.Request {
|
||||||
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
_, err = defaultSigner.Presign(r, nil, service, region, time.Minute, time.Now().Add(-time.Minute))
|
r.Header.Set(AmzExpires, "60")
|
||||||
|
signedURI, _, err := defaultSigner.PresignHTTP(ctx, awsCreds, r, UnsignedPayload, service, region, time.Now().Add(-time.Minute))
|
||||||
|
require.NoError(t, err)
|
||||||
|
r.URL, err = url.ParseRequestURI(signedURI)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return r
|
return r
|
||||||
}(),
|
}(),
|
||||||
|
@ -454,7 +462,10 @@ func TestAuthenticate(t *testing.T) {
|
||||||
name: "presign, signature from future",
|
name: "presign, signature from future",
|
||||||
request: func() *http.Request {
|
request: func() *http.Request {
|
||||||
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
_, err = defaultSigner.Presign(r, nil, service, region, time.Minute, time.Now().Add(time.Minute))
|
r.Header.Set(AmzExpires, "60")
|
||||||
|
signedURI, _, err := defaultSigner.PresignHTTP(ctx, awsCreds, r, UnsignedPayload, service, region, time.Now().Add(time.Minute))
|
||||||
|
require.NoError(t, err)
|
||||||
|
r.URL, err = url.ParseRequestURI(signedURI)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return r
|
return r
|
||||||
}(),
|
}(),
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
v4 "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4"
|
|
||||||
v4a "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4asdk2"
|
v4a "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4asdk2"
|
||||||
credentialsv2 "github.com/aws/aws-sdk-go-v2/credentials"
|
v4 "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4sdk2/signer/v4"
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
"github.com/aws/aws-sdk-go/private/protocol/rest"
|
"github.com/aws/aws-sdk-go-v2/credentials"
|
||||||
|
"github.com/aws/smithy-go/encoding/httpbinding"
|
||||||
"github.com/aws/smithy-go/logging"
|
"github.com/aws/smithy-go/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,8 +34,8 @@ type PresignData struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// PresignRequest forms pre-signed request to access objects without aws credentials.
|
// PresignRequest forms pre-signed request to access objects without aws credentials.
|
||||||
func PresignRequest(creds *credentials.Credentials, reqData RequestData, presignData PresignData) (*http.Request, error) {
|
func PresignRequest(ctx context.Context, creds aws.Credentials, reqData RequestData, presignData PresignData) (*http.Request, error) {
|
||||||
urlStr := fmt.Sprintf("%s/%s/%s", reqData.Endpoint, rest.EscapePath(reqData.Bucket, false), rest.EscapePath(reqData.Object, false))
|
urlStr := fmt.Sprintf("%s/%s/%s", reqData.Endpoint, httpbinding.EscapePath(reqData.Bucket, false), httpbinding.EscapePath(reqData.Object, false))
|
||||||
req, err := http.NewRequest(strings.ToUpper(reqData.Method), urlStr, nil)
|
req, err := http.NewRequest(strings.ToUpper(reqData.Method), urlStr, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create new request: %w", err)
|
return nil, fmt.Errorf("failed to create new request: %w", err)
|
||||||
|
@ -43,20 +45,27 @@ func PresignRequest(creds *credentials.Credentials, reqData RequestData, presign
|
||||||
req.Header.Set(k, v) // maybe we should filter system header (or keep responsibility on caller)
|
req.Header.Set(k, v) // maybe we should filter system header (or keep responsibility on caller)
|
||||||
}
|
}
|
||||||
req.Header.Set(AmzDate, presignData.SignTime.Format("20060102T150405Z"))
|
req.Header.Set(AmzDate, presignData.SignTime.Format("20060102T150405Z"))
|
||||||
|
req.Header.Set(AmzExpires, strconv.FormatFloat(presignData.Lifetime.Round(time.Second).Seconds(), 'f', 0, 64))
|
||||||
|
|
||||||
signer := v4.NewSigner(creds)
|
signer := v4.NewSigner(func(options *v4.SignerOptions) {
|
||||||
signer.DisableURIPathEscaping = true
|
options.DisableURIPathEscaping = true
|
||||||
|
})
|
||||||
|
|
||||||
if _, err = signer.Presign(req, nil, presignData.Service, presignData.Region, presignData.Lifetime, presignData.SignTime); err != nil {
|
signedURI, _, err := signer.PresignHTTP(ctx, creds, req, presignData.Headers[AmzContentSHA256], presignData.Service, presignData.Region, presignData.SignTime)
|
||||||
|
if err != nil {
|
||||||
return nil, fmt.Errorf("presign: %w", err)
|
return nil, fmt.Errorf("presign: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.URL, err = url.ParseRequestURI(signedURI); err != nil {
|
||||||
|
return nil, fmt.Errorf("parse signed URI: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PresignRequestV4a forms pre-signed request to access objects without aws credentials.
|
// PresignRequestV4a forms pre-signed request to access objects without aws credentials.
|
||||||
func PresignRequestV4a(credProvider credentialsv2.StaticCredentialsProvider, reqData RequestData, presignData PresignData) (*http.Request, error) {
|
func PresignRequestV4a(cred aws.Credentials, reqData RequestData, presignData PresignData) (*http.Request, error) {
|
||||||
urlStr := fmt.Sprintf("%s/%s/%s", reqData.Endpoint, rest.EscapePath(reqData.Bucket, false), rest.EscapePath(reqData.Object, false))
|
urlStr := fmt.Sprintf("%s/%s/%s", reqData.Endpoint, httpbinding.EscapePath(reqData.Bucket, false), httpbinding.EscapePath(reqData.Object, false))
|
||||||
req, err := http.NewRequest(strings.ToUpper(reqData.Method), urlStr, nil)
|
req, err := http.NewRequest(strings.ToUpper(reqData.Method), urlStr, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create new request: %w", err)
|
return nil, fmt.Errorf("failed to create new request: %w", err)
|
||||||
|
@ -76,14 +85,14 @@ func PresignRequestV4a(credProvider credentialsv2.StaticCredentialsProvider, req
|
||||||
})
|
})
|
||||||
|
|
||||||
credAdapter := v4a.SymmetricCredentialAdaptor{
|
credAdapter := v4a.SymmetricCredentialAdaptor{
|
||||||
SymmetricProvider: credProvider,
|
SymmetricProvider: credentials.NewStaticCredentialsProvider(cred.AccessKeyID, cred.SecretAccessKey, ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
creds, err := credAdapter.RetrievePrivateKey(req.Context())
|
creds, err := credAdapter.RetrievePrivateKey(req.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to derive assymetric key from credentials: %w", err)
|
return nil, fmt.Errorf("failed to derive assymetric key from credentials: %w", err)
|
||||||
}
|
}
|
||||||
presignedURL, _, err := signer.PresignHTTP(req.Context(), creds, req, "", presignData.Service, []string{presignData.Region}, presignData.SignTime)
|
presignedURL, _, err := signer.PresignHTTP(req.Context(), creds, req, presignData.Headers[AmzContentSHA256], presignData.Service, []string{presignData.Region}, presignData.SignTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("presign: %w", err)
|
return nil, fmt.Errorf("presign: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@ import (
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
credentialsv2 "github.com/aws/aws-sdk-go-v2/credentials"
|
credentialsv2 "github.com/aws/aws-sdk-go-v2/credentials"
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
||||||
"github.com/aws/smithy-go/logging"
|
"github.com/aws/smithy-go/logging"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -56,13 +56,15 @@ func (m credentialsMock) Update(context.Context, oid.Address, tokens.Credentials
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckSign(t *testing.T) {
|
func TestCheckSign(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
var accessKeyAddr oid.Address
|
var accessKeyAddr oid.Address
|
||||||
err := accessKeyAddr.DecodeString("8N7CYBY74kxZXoyvA5UNdmovaXqFpwNfvEPsqaN81es2/3tDwq5tR8fByrJcyJwyiuYX7Dae8tyDT7pd8oaL1MBto")
|
err := accessKeyAddr.DecodeString("8N7CYBY74kxZXoyvA5UNdmovaXqFpwNfvEPsqaN81es2/3tDwq5tR8fByrJcyJwyiuYX7Dae8tyDT7pd8oaL1MBto")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
accessKeyID := strings.ReplaceAll(accessKeyAddr.String(), "/", "0")
|
accessKeyID := strings.ReplaceAll(accessKeyAddr.String(), "/", "0")
|
||||||
secretKey := "713d0a0b9efc7d22923e17b0402a6a89b4273bc711c8bacb2da1b643d0006aeb"
|
secretKey := "713d0a0b9efc7d22923e17b0402a6a89b4273bc711c8bacb2da1b643d0006aeb"
|
||||||
awsCreds := credentials.NewStaticCredentials(accessKeyID, secretKey, "")
|
awsCreds := aws.Credentials{AccessKeyID: accessKeyID, SecretAccessKey: secretKey}
|
||||||
|
|
||||||
reqData := RequestData{
|
reqData := RequestData{
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
|
@ -77,10 +79,11 @@ func TestCheckSign(t *testing.T) {
|
||||||
SignTime: time.Now().UTC(),
|
SignTime: time.Now().UTC(),
|
||||||
Headers: map[string]string{
|
Headers: map[string]string{
|
||||||
ContentTypeHdr: "text/plain",
|
ContentTypeHdr: "text/plain",
|
||||||
|
AmzContentSHA256: UnsignedPayload,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := PresignRequest(awsCreds, reqData, presignData)
|
req, err := PresignRequest(ctx, awsCreds, reqData, presignData)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
expBox := &accessbox.Box{
|
expBox := &accessbox.Box{
|
||||||
|
@ -109,7 +112,7 @@ func TestCheckSignV4a(t *testing.T) {
|
||||||
|
|
||||||
accessKeyID := strings.ReplaceAll(accessKeyAddr.String(), "/", "0")
|
accessKeyID := strings.ReplaceAll(accessKeyAddr.String(), "/", "0")
|
||||||
secretKey := "713d0a0b9efc7d22923e17b0402a6a89b4273bc711c8bacb2da1b643d0006aeb"
|
secretKey := "713d0a0b9efc7d22923e17b0402a6a89b4273bc711c8bacb2da1b643d0006aeb"
|
||||||
awsCreds := credentialsv2.NewStaticCredentialsProvider(accessKeyID, secretKey, "")
|
awsCreds := aws.Credentials{AccessKeyID: accessKeyID, SecretAccessKey: secretKey}
|
||||||
|
|
||||||
reqData := RequestData{
|
reqData := RequestData{
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
|
|
|
@ -1,87 +0,0 @@
|
||||||
package v4
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// validator houses a set of rule needed for validation of a
|
|
||||||
// string value.
|
|
||||||
type rules []rule
|
|
||||||
|
|
||||||
// rule interface allows for more flexible rules and just simply
|
|
||||||
// checks whether or not a value adheres to that rule.
|
|
||||||
type rule interface {
|
|
||||||
IsValid(value string) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsValid will iterate through all rules and see if any rules
|
|
||||||
// apply to the value and supports nested rules.
|
|
||||||
func (r rules) IsValid(value string) bool {
|
|
||||||
for _, rule := range r {
|
|
||||||
if rule.IsValid(value) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// mapRule generic rule for maps.
|
|
||||||
type mapRule map[string]struct{}
|
|
||||||
|
|
||||||
// IsValid for the map rule satisfies whether it exists in the map.
|
|
||||||
func (m mapRule) IsValid(value string) bool {
|
|
||||||
_, ok := m[value]
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// whitelist is a generic rule for whitelisting.
|
|
||||||
type whitelist struct {
|
|
||||||
rule
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsValid for whitelist checks if the value is within the whitelist.
|
|
||||||
func (w whitelist) IsValid(value string) bool {
|
|
||||||
return w.rule.IsValid(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// blacklist is a generic rule for blacklisting.
|
|
||||||
type blacklist struct {
|
|
||||||
rule
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsValid for whitelist checks if the value is within the whitelist.
|
|
||||||
func (b blacklist) IsValid(value string) bool {
|
|
||||||
return !b.rule.IsValid(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
type patterns []string
|
|
||||||
|
|
||||||
// IsValid for patterns checks each pattern and returns if a match has
|
|
||||||
// been found.
|
|
||||||
func (p patterns) IsValid(value string) bool {
|
|
||||||
for _, pattern := range p {
|
|
||||||
if HasPrefixFold(value, pattern) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasPrefixFold tests whether the string s begins with prefix, interpreted as UTF-8 strings,
|
|
||||||
// under Unicode case-folding.
|
|
||||||
func HasPrefixFold(s, prefix string) bool {
|
|
||||||
return len(s) >= len(prefix) && strings.EqualFold(s[0:len(prefix)], prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
// inclusiveRules rules allow for rules to depend on one another.
|
|
||||||
type inclusiveRules []rule
|
|
||||||
|
|
||||||
// IsValid will return true if all rules are true.
|
|
||||||
func (r inclusiveRules) IsValid(value string) bool {
|
|
||||||
for _, rule := range r {
|
|
||||||
if !rule.IsValid(value) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package v4
|
|
||||||
|
|
||||||
// WithUnsignedPayload will enable and set the UnsignedPayload field to
|
|
||||||
// true of the signer.
|
|
||||||
func WithUnsignedPayload(v4 *Signer) {
|
|
||||||
v4.UnsignedPayload = true
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
//go:build go1.7
|
|
||||||
// +build go1.7
|
|
||||||
|
|
||||||
package v4
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
|
||||||
)
|
|
||||||
|
|
||||||
func requestContext(r *http.Request) aws.Context {
|
|
||||||
return r.Context()
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
package v4
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
||||||
)
|
|
||||||
|
|
||||||
type credentialValueProvider interface {
|
|
||||||
Get() (credentials.Value, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StreamSigner implements signing of event stream encoded payloads.
|
|
||||||
type StreamSigner struct {
|
|
||||||
region string
|
|
||||||
service string
|
|
||||||
|
|
||||||
credentials credentialValueProvider
|
|
||||||
|
|
||||||
prevSig []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewStreamSigner creates a SigV4 signer used to sign Event Stream encoded messages.
|
|
||||||
func NewStreamSigner(region, service string, seedSignature []byte, credentials *credentials.Credentials) *StreamSigner {
|
|
||||||
return &StreamSigner{
|
|
||||||
region: region,
|
|
||||||
service: service,
|
|
||||||
credentials: credentials,
|
|
||||||
prevSig: seedSignature,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSignature takes an event stream encoded headers and payload and returns a signature.
|
|
||||||
func (s *StreamSigner) GetSignature(headers, payload []byte, date time.Time) ([]byte, error) {
|
|
||||||
credValue, err := s.credentials.Get()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sigKey := deriveSigningKey(s.region, s.service, credValue.SecretAccessKey, date)
|
|
||||||
|
|
||||||
keyPath := buildSigningScope(s.region, s.service, date)
|
|
||||||
|
|
||||||
stringToSign := buildEventStreamStringToSign(headers, payload, s.prevSig, keyPath, date)
|
|
||||||
|
|
||||||
signature := hmacSHA256(sigKey, []byte(stringToSign))
|
|
||||||
s.prevSig = signature
|
|
||||||
|
|
||||||
return signature, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildEventStreamStringToSign(headers, payload, prevSig []byte, scope string, date time.Time) string {
|
|
||||||
return strings.Join([]string{
|
|
||||||
"AWS4-HMAC-SHA256-PAYLOAD",
|
|
||||||
formatTime(date),
|
|
||||||
scope,
|
|
||||||
hex.EncodeToString(prevSig),
|
|
||||||
hex.EncodeToString(hashSHA256(headers)),
|
|
||||||
hex.EncodeToString(hashSHA256(payload)),
|
|
||||||
}, "\n")
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
//go:build go1.5
|
|
||||||
// +build go1.5
|
|
||||||
|
|
||||||
package v4
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getURIPath(u *url.URL) string {
|
|
||||||
var uri string
|
|
||||||
|
|
||||||
if len(u.Opaque) > 0 {
|
|
||||||
uri = "/" + strings.Join(strings.Split(u.Opaque, "/")[3:], "/")
|
|
||||||
} else {
|
|
||||||
uri = u.EscapedPath()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(uri) == 0 {
|
|
||||||
uri = "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
return uri
|
|
||||||
}
|
|
|
@ -1,858 +0,0 @@
|
||||||
// Package v4 implements signing for AWS V4 signer
|
|
||||||
//
|
|
||||||
// Provides request signing for request that need to be signed with
|
|
||||||
// AWS V4 Signatures.
|
|
||||||
//
|
|
||||||
// # Standalone Signer
|
|
||||||
//
|
|
||||||
// Generally using the signer outside of the SDK should not require any additional
|
|
||||||
// logic when using Go v1.5 or higher. The signer does this by taking advantage
|
|
||||||
// of the URL.EscapedPath method. If your request URI requires additional escaping
|
|
||||||
// you many need to use the URL.Opaque to define what the raw URI should be sent
|
|
||||||
// to the service as.
|
|
||||||
//
|
|
||||||
// The signer will first check the URL.Opaque field, and use its value if set.
|
|
||||||
// The signer does require the URL.Opaque field to be set in the form of:
|
|
||||||
//
|
|
||||||
// "//<hostname>/<path>"
|
|
||||||
//
|
|
||||||
// // e.g.
|
|
||||||
// "//example.com/some/path"
|
|
||||||
//
|
|
||||||
// The leading "//" and hostname are required or the URL.Opaque escaping will
|
|
||||||
// not work correctly.
|
|
||||||
//
|
|
||||||
// If URL.Opaque is not set the signer will fallback to the URL.EscapedPath()
|
|
||||||
// method and using the returned value. If you're using Go v1.4 you must set
|
|
||||||
// URL.Opaque if the URI path needs escaping. If URL.Opaque is not set with
|
|
||||||
// Go v1.5 the signer will fallback to URL.Path.
|
|
||||||
//
|
|
||||||
// AWS v4 signature validation requires that the canonical string's URI path
|
|
||||||
// element must be the URI escaped form of the HTTP request's path.
|
|
||||||
// http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
|
|
||||||
//
|
|
||||||
// The Go HTTP client will perform escaping automatically on the request. Some
|
|
||||||
// of these escaping may cause signature validation errors because the HTTP
|
|
||||||
// request differs from the URI path or query that the signature was generated.
|
|
||||||
// https://golang.org/pkg/net/url/#URL.EscapedPath
|
|
||||||
//
|
|
||||||
// Because of this, it is recommended that when using the signer outside of the
|
|
||||||
// SDK that explicitly escaping the request prior to being signed is preferable,
|
|
||||||
// and will help prevent signature validation errors. This can be done by setting
|
|
||||||
// the URL.Opaque or URL.RawPath. The SDK will use URL.Opaque first and then
|
|
||||||
// call URL.EscapedPath() if Opaque is not set.
|
|
||||||
//
|
|
||||||
// If signing a request intended for HTTP2 server, and you're using Go 1.6.2
|
|
||||||
// through 1.7.4 you should use the URL.RawPath as the pre-escaped form of the
|
|
||||||
// request URL. https://github.com/golang/go/issues/16847 points to a bug in
|
|
||||||
// Go pre 1.8 that fails to make HTTP2 requests using absolute URL in the HTTP
|
|
||||||
// message. URL.Opaque generally will force Go to make requests with absolute URL.
|
|
||||||
// URL.RawPath does not do this, but RawPath must be a valid escaping of Path
|
|
||||||
// or url.EscapedPath will ignore the RawPath escaping.
|
|
||||||
//
|
|
||||||
// Test `TestStandaloneSign` provides a complete example of using the signer
|
|
||||||
// outside of the SDK and pre-escaping the URI path.
|
|
||||||
package v4
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/hmac"
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/request"
|
|
||||||
"github.com/aws/aws-sdk-go/private/protocol/rest"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
authorizationHeader = "Authorization"
|
|
||||||
authHeaderSignatureElem = "Signature="
|
|
||||||
signatureQueryKey = "X-Amz-Signature"
|
|
||||||
|
|
||||||
authHeaderPrefix = "AWS4-HMAC-SHA256"
|
|
||||||
timeFormat = "20060102T150405Z"
|
|
||||||
shortTimeFormat = "20060102"
|
|
||||||
awsV4Request = "aws4_request"
|
|
||||||
|
|
||||||
// emptyStringSHA256 is a SHA256 of an empty string.
|
|
||||||
emptyStringSHA256 = `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`
|
|
||||||
)
|
|
||||||
|
|
||||||
var ignoredPresignHeaders = rules{
|
|
||||||
blacklist{
|
|
||||||
mapRule{
|
|
||||||
authorizationHeader: struct{}{},
|
|
||||||
"User-Agent": struct{}{},
|
|
||||||
"X-Amzn-Trace-Id": struct{}{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// drop User-Agent header to be compatible with aws sdk java v1.
|
|
||||||
var ignoredHeaders = rules{
|
|
||||||
blacklist{
|
|
||||||
mapRule{
|
|
||||||
authorizationHeader: struct{}{},
|
|
||||||
"X-Amzn-Trace-Id": struct{}{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// requiredSignedHeaders is a whitelist for build canonical headers.
|
|
||||||
var requiredSignedHeaders = rules{
|
|
||||||
whitelist{
|
|
||||||
mapRule{
|
|
||||||
"Cache-Control": struct{}{},
|
|
||||||
"Content-Disposition": struct{}{},
|
|
||||||
"Content-Encoding": struct{}{},
|
|
||||||
"Content-Language": struct{}{},
|
|
||||||
"Content-Md5": struct{}{},
|
|
||||||
"Content-Type": struct{}{},
|
|
||||||
"Expires": struct{}{},
|
|
||||||
"If-Match": struct{}{},
|
|
||||||
"If-Modified-Since": struct{}{},
|
|
||||||
"If-None-Match": struct{}{},
|
|
||||||
"If-Unmodified-Since": struct{}{},
|
|
||||||
"Range": struct{}{},
|
|
||||||
"X-Amz-Acl": struct{}{},
|
|
||||||
"X-Amz-Copy-Source": struct{}{},
|
|
||||||
"X-Amz-Copy-Source-If-Match": struct{}{},
|
|
||||||
"X-Amz-Copy-Source-If-Modified-Since": struct{}{},
|
|
||||||
"X-Amz-Copy-Source-If-None-Match": struct{}{},
|
|
||||||
"X-Amz-Copy-Source-If-Unmodified-Since": struct{}{},
|
|
||||||
"X-Amz-Copy-Source-Range": struct{}{},
|
|
||||||
"X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm": struct{}{},
|
|
||||||
"X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key": struct{}{},
|
|
||||||
"X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5": struct{}{},
|
|
||||||
"X-Amz-Grant-Full-control": struct{}{},
|
|
||||||
"X-Amz-Grant-Read": struct{}{},
|
|
||||||
"X-Amz-Grant-Read-Acp": struct{}{},
|
|
||||||
"X-Amz-Grant-Write": struct{}{},
|
|
||||||
"X-Amz-Grant-Write-Acp": struct{}{},
|
|
||||||
"X-Amz-Metadata-Directive": struct{}{},
|
|
||||||
"X-Amz-Mfa": struct{}{},
|
|
||||||
"X-Amz-Request-Payer": struct{}{},
|
|
||||||
"X-Amz-Server-Side-Encryption": struct{}{},
|
|
||||||
"X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": struct{}{},
|
|
||||||
"X-Amz-Server-Side-Encryption-Customer-Algorithm": struct{}{},
|
|
||||||
"X-Amz-Server-Side-Encryption-Customer-Key": struct{}{},
|
|
||||||
"X-Amz-Server-Side-Encryption-Customer-Key-Md5": struct{}{},
|
|
||||||
"X-Amz-Storage-Class": struct{}{},
|
|
||||||
"X-Amz-Tagging": struct{}{},
|
|
||||||
"X-Amz-Website-Redirect-Location": struct{}{},
|
|
||||||
"X-Amz-Content-Sha256": struct{}{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
patterns{"X-Amz-Meta-"},
|
|
||||||
}
|
|
||||||
|
|
||||||
// allowedHoisting is a whitelist for build query headers. The boolean value
|
|
||||||
// represents whether or not it is a pattern.
|
|
||||||
var allowedQueryHoisting = inclusiveRules{
|
|
||||||
blacklist{requiredSignedHeaders},
|
|
||||||
patterns{"X-Amz-"},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signer applies AWS v4 signing to given request. Use this to sign requests
|
|
||||||
// that need to be signed with AWS V4 Signatures.
|
|
||||||
type Signer struct {
|
|
||||||
// The authentication credentials the request will be signed against.
|
|
||||||
// This value must be set to sign requests.
|
|
||||||
Credentials *credentials.Credentials
|
|
||||||
|
|
||||||
// Sets the log level the signer should use when reporting information to
|
|
||||||
// the logger. If the logger is nil nothing will be logged. See
|
|
||||||
// aws.LogLevelType for more information on available logging levels
|
|
||||||
//
|
|
||||||
// By default nothing will be logged.
|
|
||||||
Debug aws.LogLevelType
|
|
||||||
|
|
||||||
// The logger loging information will be written to. If there the logger
|
|
||||||
// is nil, nothing will be logged.
|
|
||||||
Logger aws.Logger
|
|
||||||
|
|
||||||
// Disables the Signer's moving HTTP header key/value pairs from the HTTP
|
|
||||||
// request header to the request's query string. This is most commonly used
|
|
||||||
// with pre-signed requests preventing headers from being added to the
|
|
||||||
// request's query string.
|
|
||||||
DisableHeaderHoisting bool
|
|
||||||
|
|
||||||
// Disables the automatic escaping of the URI path of the request for the
|
|
||||||
// siganture's canonical string's path. For services that do not need additional
|
|
||||||
// escaping then use this to disable the signer escaping the path.
|
|
||||||
//
|
|
||||||
// S3 is an example of a service that does not need additional escaping.
|
|
||||||
//
|
|
||||||
// http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
|
|
||||||
DisableURIPathEscaping bool
|
|
||||||
|
|
||||||
// Disables the automatical setting of the HTTP request's Body field with the
|
|
||||||
// io.ReadSeeker passed in to the signer. This is useful if you're using a
|
|
||||||
// custom wrapper around the body for the io.ReadSeeker and want to preserve
|
|
||||||
// the Body value on the Request.Body.
|
|
||||||
//
|
|
||||||
// This does run the risk of signing a request with a body that will not be
|
|
||||||
// sent in the request. Need to ensure that the underlying data of the Body
|
|
||||||
// values are the same.
|
|
||||||
DisableRequestBodyOverwrite bool
|
|
||||||
|
|
||||||
// currentTimeFn returns the time value which represents the current time.
|
|
||||||
// This value should only be used for testing. If it is nil the default
|
|
||||||
// time.Now will be used.
|
|
||||||
currentTimeFn func() time.Time
|
|
||||||
|
|
||||||
// UnsignedPayload will prevent signing of the payload. This will only
|
|
||||||
// work for services that have support for this.
|
|
||||||
UnsignedPayload bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSigner returns a Signer pointer configured with the credentials and optional
|
|
||||||
// option values provided. If not options are provided the Signer will use its
|
|
||||||
// default configuration.
|
|
||||||
func NewSigner(credentials *credentials.Credentials, options ...func(*Signer)) *Signer {
|
|
||||||
v4 := &Signer{
|
|
||||||
Credentials: credentials,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, option := range options {
|
|
||||||
option(v4)
|
|
||||||
}
|
|
||||||
|
|
||||||
return v4
|
|
||||||
}
|
|
||||||
|
|
||||||
type signingCtx struct {
|
|
||||||
ServiceName string
|
|
||||||
Region string
|
|
||||||
Request *http.Request
|
|
||||||
Body io.ReadSeeker
|
|
||||||
Query url.Values
|
|
||||||
Time time.Time
|
|
||||||
ExpireTime time.Duration
|
|
||||||
SignedHeaderVals http.Header
|
|
||||||
|
|
||||||
DisableURIPathEscaping bool
|
|
||||||
|
|
||||||
credValues credentials.Value
|
|
||||||
isPresign bool
|
|
||||||
unsignedPayload bool
|
|
||||||
|
|
||||||
bodyDigest string
|
|
||||||
signedHeaders string
|
|
||||||
canonicalHeaders string
|
|
||||||
canonicalString string
|
|
||||||
credentialString string
|
|
||||||
stringToSign string
|
|
||||||
signature string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign signs AWS v4 requests with the provided body, service name, region the
|
|
||||||
// request is made to, and time the request is signed at. The signTime allows
|
|
||||||
// you to specify that a request is signed for the future, and cannot be
|
|
||||||
// used until then.
|
|
||||||
//
|
|
||||||
// Returns a list of HTTP headers that were included in the signature or an
|
|
||||||
// error if signing the request failed. Generally for signed requests this value
|
|
||||||
// is not needed as the full request context will be captured by the http.Request
|
|
||||||
// value. It is included for reference though.
|
|
||||||
//
|
|
||||||
// Sign will set the request's Body to be the `body` parameter passed in. If
|
|
||||||
// the body is not already an io.ReadCloser, it will be wrapped within one. If
|
|
||||||
// a `nil` body parameter passed to Sign, the request's Body field will be
|
|
||||||
// also set to nil. Its important to note that this functionality will not
|
|
||||||
// change the request's ContentLength of the request.
|
|
||||||
//
|
|
||||||
// Sign differs from Presign in that it will sign the request using HTTP
|
|
||||||
// header values. This type of signing is intended for http.Request values that
|
|
||||||
// will not be shared, or are shared in a way the header values on the request
|
|
||||||
// will not be lost.
|
|
||||||
//
|
|
||||||
// The requests body is an io.ReadSeeker so the SHA256 of the body can be
|
|
||||||
// generated. To bypass the signer computing the hash you can set the
|
|
||||||
// "X-Amz-Content-Sha256" header with a precomputed value. The signer will
|
|
||||||
// only compute the hash if the request header value is empty.
|
|
||||||
func (v4 Signer) Sign(r *http.Request, body io.ReadSeeker, service, region string, signTime time.Time) (http.Header, error) {
|
|
||||||
return v4.signWithBody(r, body, service, region, 0, false, signTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Presign signs AWS v4 requests with the provided body, service name, region
|
|
||||||
// the request is made to, and time the request is signed at. The signTime
|
|
||||||
// allows you to specify that a request is signed for the future, and cannot
|
|
||||||
// be used until then.
|
|
||||||
//
|
|
||||||
// Returns a list of HTTP headers that were included in the signature or an
|
|
||||||
// error if signing the request failed. For presigned requests these headers
|
|
||||||
// and their values must be included on the HTTP request when it is made. This
|
|
||||||
// is helpful to know what header values need to be shared with the party the
|
|
||||||
// presigned request will be distributed to.
|
|
||||||
//
|
|
||||||
// Presign differs from Sign in that it will sign the request using query string
|
|
||||||
// instead of header values. This allows you to share the Presigned Request's
|
|
||||||
// URL with third parties, or distribute it throughout your system with minimal
|
|
||||||
// dependencies.
|
|
||||||
//
|
|
||||||
// Presign also takes an exp value which is the duration the
|
|
||||||
// signed request will be valid after the signing time. This is allows you to
|
|
||||||
// set when the request will expire.
|
|
||||||
//
|
|
||||||
// The requests body is an io.ReadSeeker so the SHA256 of the body can be
|
|
||||||
// generated. To bypass the signer computing the hash you can set the
|
|
||||||
// "X-Amz-Content-Sha256" header with a precomputed value. The signer will
|
|
||||||
// only compute the hash if the request header value is empty.
|
|
||||||
//
|
|
||||||
// Presigning a S3 request will not compute the body's SHA256 hash by default.
|
|
||||||
// This is done due to the general use case for S3 presigned URLs is to share
|
|
||||||
// PUT/GET capabilities. If you would like to include the body's SHA256 in the
|
|
||||||
// presigned request's signature you can set the "X-Amz-Content-Sha256"
|
|
||||||
// HTTP header and that will be included in the request's signature.
|
|
||||||
func (v4 Signer) Presign(r *http.Request, body io.ReadSeeker, service, region string, exp time.Duration, signTime time.Time) (http.Header, error) {
|
|
||||||
return v4.signWithBody(r, body, service, region, exp, true, signTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v4 Signer) signWithBody(r *http.Request, body io.ReadSeeker, service, region string, exp time.Duration, isPresign bool, signTime time.Time) (http.Header, error) {
|
|
||||||
currentTimeFn := v4.currentTimeFn
|
|
||||||
if currentTimeFn == nil {
|
|
||||||
currentTimeFn = time.Now
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := &signingCtx{
|
|
||||||
Request: r,
|
|
||||||
Body: body,
|
|
||||||
Query: r.URL.Query(),
|
|
||||||
Time: signTime,
|
|
||||||
ExpireTime: exp,
|
|
||||||
isPresign: isPresign,
|
|
||||||
ServiceName: service,
|
|
||||||
Region: region,
|
|
||||||
DisableURIPathEscaping: v4.DisableURIPathEscaping,
|
|
||||||
unsignedPayload: v4.UnsignedPayload,
|
|
||||||
}
|
|
||||||
|
|
||||||
for key := range ctx.Query {
|
|
||||||
sort.Strings(ctx.Query[key])
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.isRequestSigned() {
|
|
||||||
ctx.Time = currentTimeFn()
|
|
||||||
ctx.handlePresignRemoval()
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
ctx.credValues, err = v4.Credentials.GetWithContext(requestContext(r))
|
|
||||||
if err != nil {
|
|
||||||
return http.Header{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.sanitizeHostForHeader()
|
|
||||||
ctx.assignAmzQueryValues()
|
|
||||||
if err := ctx.build(v4.DisableHeaderHoisting); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the request is not presigned the body should be attached to it. This
|
|
||||||
// prevents the confusion of wanting to send a signed request without
|
|
||||||
// the body the request was signed for attached.
|
|
||||||
if !(v4.DisableRequestBodyOverwrite || ctx.isPresign) {
|
|
||||||
var reader io.ReadCloser
|
|
||||||
if body != nil {
|
|
||||||
var ok bool
|
|
||||||
if reader, ok = body.(io.ReadCloser); !ok {
|
|
||||||
reader = io.NopCloser(body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r.Body = reader
|
|
||||||
}
|
|
||||||
|
|
||||||
if v4.Debug.Matches(aws.LogDebugWithSigning) {
|
|
||||||
v4.logSigningInfo(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctx.SignedHeaderVals, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *signingCtx) sanitizeHostForHeader() {
|
|
||||||
request.SanitizeHostForHeader(ctx.Request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *signingCtx) handlePresignRemoval() {
|
|
||||||
if !ctx.isPresign {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// The credentials have expired for this request. The current signing
|
|
||||||
// is invalid, and needs to be request because the request will fail.
|
|
||||||
ctx.removePresign()
|
|
||||||
|
|
||||||
// Update the request's query string to ensure the values stays in
|
|
||||||
// sync in the case retrieving the new credentials fails.
|
|
||||||
ctx.Request.URL.RawQuery = ctx.Query.Encode()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *signingCtx) assignAmzQueryValues() {
|
|
||||||
if ctx.isPresign {
|
|
||||||
ctx.Query.Set("X-Amz-Algorithm", authHeaderPrefix)
|
|
||||||
if ctx.credValues.SessionToken != "" {
|
|
||||||
ctx.Query.Set("X-Amz-Security-Token", ctx.credValues.SessionToken)
|
|
||||||
} else {
|
|
||||||
ctx.Query.Del("X-Amz-Security-Token")
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.credValues.SessionToken != "" {
|
|
||||||
ctx.Request.Header.Set("X-Amz-Security-Token", ctx.credValues.SessionToken)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignRequestHandler is a named request handler the SDK will use to sign
|
|
||||||
// service client request with using the V4 signature.
|
|
||||||
var SignRequestHandler = request.NamedHandler{
|
|
||||||
Name: "v4.SignRequestHandler", Fn: SignSDKRequest,
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignSDKRequest signs an AWS request with the V4 signature. This
|
|
||||||
// request handler should only be used with the SDK's built in service client's
|
|
||||||
// API operation requests.
|
|
||||||
//
|
|
||||||
// This function should not be used on its on its own, but in conjunction with
|
|
||||||
// an AWS service client's API operation call. To sign a standalone request
|
|
||||||
// not created by a service client's API operation method use the "Sign" or
|
|
||||||
// "Presign" functions of the "Signer" type.
|
|
||||||
//
|
|
||||||
// If the credentials of the request's config are set to
|
|
||||||
// credentials.AnonymousCredentials the request will not be signed.
|
|
||||||
func SignSDKRequest(req *request.Request) {
|
|
||||||
SignSDKRequestWithCurrentTime(req, time.Now)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildNamedHandler will build a generic handler for signing.
|
|
||||||
func BuildNamedHandler(name string, opts ...func(*Signer)) request.NamedHandler {
|
|
||||||
return request.NamedHandler{
|
|
||||||
Name: name,
|
|
||||||
Fn: func(req *request.Request) {
|
|
||||||
SignSDKRequestWithCurrentTime(req, time.Now, opts...)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignSDKRequestWithCurrentTime will sign the SDK's request using the time
|
|
||||||
// function passed in. Behaves the same as SignSDKRequest with the exception
|
|
||||||
// the request is signed with the value returned by the current time function.
|
|
||||||
func SignSDKRequestWithCurrentTime(req *request.Request, curTimeFn func() time.Time, opts ...func(*Signer)) {
|
|
||||||
// If the request does not need to be signed ignore the signing of the
|
|
||||||
// request if the AnonymousCredentials object is used.
|
|
||||||
if req.Config.Credentials == credentials.AnonymousCredentials {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
region := req.ClientInfo.SigningRegion
|
|
||||||
if region == "" {
|
|
||||||
region = aws.StringValue(req.Config.Region)
|
|
||||||
}
|
|
||||||
|
|
||||||
name := req.ClientInfo.SigningName
|
|
||||||
if name == "" {
|
|
||||||
name = req.ClientInfo.ServiceName
|
|
||||||
}
|
|
||||||
|
|
||||||
v4 := NewSigner(req.Config.Credentials, func(v4 *Signer) {
|
|
||||||
v4.Debug = req.Config.LogLevel.Value()
|
|
||||||
v4.Logger = req.Config.Logger
|
|
||||||
v4.DisableHeaderHoisting = req.NotHoist
|
|
||||||
v4.currentTimeFn = curTimeFn
|
|
||||||
if name == "s3" {
|
|
||||||
// S3 service should not have any escaping applied
|
|
||||||
v4.DisableURIPathEscaping = true
|
|
||||||
}
|
|
||||||
// Prevents setting the HTTPRequest's Body. Since the Body could be
|
|
||||||
// wrapped in a custom io.Closer that we do not want to be stompped
|
|
||||||
// on top of by the signer.
|
|
||||||
v4.DisableRequestBodyOverwrite = true
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(v4)
|
|
||||||
}
|
|
||||||
|
|
||||||
curTime := curTimeFn()
|
|
||||||
signedHeaders, err := v4.signWithBody(req.HTTPRequest, req.GetBody(),
|
|
||||||
name, region, req.ExpireTime, req.ExpireTime > 0, curTime,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
req.Error = err
|
|
||||||
req.SignedHeaderVals = nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.SignedHeaderVals = signedHeaders
|
|
||||||
req.LastSignedAt = curTime
|
|
||||||
}
|
|
||||||
|
|
||||||
const logSignInfoMsg = `DEBUG: Request Signature:
|
|
||||||
---[ CANONICAL STRING ]-----------------------------
|
|
||||||
%s
|
|
||||||
---[ STRING TO SIGN ]--------------------------------
|
|
||||||
%s%s
|
|
||||||
-----------------------------------------------------`
|
|
||||||
const logSignedURLMsg = `
|
|
||||||
---[ SIGNED URL ]------------------------------------
|
|
||||||
%s`
|
|
||||||
|
|
||||||
func (v4 *Signer) logSigningInfo(ctx *signingCtx) {
|
|
||||||
signedURLMsg := ""
|
|
||||||
if ctx.isPresign {
|
|
||||||
signedURLMsg = fmt.Sprintf(logSignedURLMsg, ctx.Request.URL.String())
|
|
||||||
}
|
|
||||||
msg := fmt.Sprintf(logSignInfoMsg, ctx.canonicalString, ctx.stringToSign, signedURLMsg)
|
|
||||||
v4.Logger.Log(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *signingCtx) build(disableHeaderHoisting bool) error {
|
|
||||||
ctx.buildTime() // no depends
|
|
||||||
ctx.buildCredentialString() // no depends
|
|
||||||
|
|
||||||
if err := ctx.buildBodyDigest(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
unsignedHeaders := ctx.Request.Header
|
|
||||||
if ctx.isPresign {
|
|
||||||
if !disableHeaderHoisting {
|
|
||||||
var urlValues url.Values
|
|
||||||
urlValues, unsignedHeaders = buildQuery(allowedQueryHoisting, unsignedHeaders) // no depends
|
|
||||||
for k := range urlValues {
|
|
||||||
ctx.Query[k] = urlValues[k]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.isPresign {
|
|
||||||
ctx.buildCanonicalHeaders(ignoredPresignHeaders, unsignedHeaders)
|
|
||||||
} else {
|
|
||||||
ctx.buildCanonicalHeaders(ignoredHeaders, unsignedHeaders)
|
|
||||||
}
|
|
||||||
ctx.buildCanonicalString() // depends on canon headers / signed headers
|
|
||||||
ctx.buildStringToSign() // depends on canon string
|
|
||||||
ctx.buildSignature() // depends on string to sign
|
|
||||||
|
|
||||||
if ctx.isPresign {
|
|
||||||
ctx.Request.URL.RawQuery += "&" + signatureQueryKey + "=" + ctx.signature
|
|
||||||
} else {
|
|
||||||
parts := []string{
|
|
||||||
authHeaderPrefix + " Credential=" + ctx.credValues.AccessKeyID + "/" + ctx.credentialString,
|
|
||||||
"SignedHeaders=" + ctx.signedHeaders,
|
|
||||||
authHeaderSignatureElem + ctx.signature,
|
|
||||||
}
|
|
||||||
ctx.Request.Header.Set(authorizationHeader, strings.Join(parts, ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSignedRequestSignature attempts to extract the signature of the request.
|
|
||||||
// Returning an error if the request is unsigned, or unable to extract the
|
|
||||||
// signature.
|
|
||||||
func GetSignedRequestSignature(r *http.Request) ([]byte, error) {
|
|
||||||
if auth := r.Header.Get(authorizationHeader); len(auth) != 0 {
|
|
||||||
ps := strings.Split(auth, ", ")
|
|
||||||
for _, p := range ps {
|
|
||||||
if idx := strings.Index(p, authHeaderSignatureElem); idx >= 0 {
|
|
||||||
sig := p[len(authHeaderSignatureElem):]
|
|
||||||
if len(sig) == 0 {
|
|
||||||
return nil, fmt.Errorf("invalid request signature authorization header")
|
|
||||||
}
|
|
||||||
return hex.DecodeString(sig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sig := r.URL.Query().Get("X-Amz-Signature"); len(sig) != 0 {
|
|
||||||
return hex.DecodeString(sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("request not signed")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *signingCtx) buildTime() {
|
|
||||||
if ctx.isPresign {
|
|
||||||
duration := int64(ctx.ExpireTime / time.Second)
|
|
||||||
ctx.Query.Set("X-Amz-Date", formatTime(ctx.Time))
|
|
||||||
ctx.Query.Set("X-Amz-Expires", strconv.FormatInt(duration, 10))
|
|
||||||
} else {
|
|
||||||
ctx.Request.Header.Set("X-Amz-Date", formatTime(ctx.Time))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *signingCtx) buildCredentialString() {
|
|
||||||
ctx.credentialString = buildSigningScope(ctx.Region, ctx.ServiceName, ctx.Time)
|
|
||||||
|
|
||||||
if ctx.isPresign {
|
|
||||||
ctx.Query.Set("X-Amz-Credential", ctx.credValues.AccessKeyID+"/"+ctx.credentialString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildQuery(r rule, header http.Header) (url.Values, http.Header) {
|
|
||||||
query := url.Values{}
|
|
||||||
unsignedHeaders := http.Header{}
|
|
||||||
for k, h := range header {
|
|
||||||
if r.IsValid(k) {
|
|
||||||
query[k] = h
|
|
||||||
} else {
|
|
||||||
unsignedHeaders[k] = h
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return query, unsignedHeaders
|
|
||||||
}
|
|
||||||
func (ctx *signingCtx) buildCanonicalHeaders(r rule, header http.Header) {
|
|
||||||
var headers []string
|
|
||||||
headers = append(headers, "host")
|
|
||||||
for k, v := range header {
|
|
||||||
if !r.IsValid(k) {
|
|
||||||
continue // ignored header
|
|
||||||
}
|
|
||||||
if ctx.SignedHeaderVals == nil {
|
|
||||||
ctx.SignedHeaderVals = make(http.Header)
|
|
||||||
}
|
|
||||||
|
|
||||||
lowerCaseKey := strings.ToLower(k)
|
|
||||||
if _, ok := ctx.SignedHeaderVals[lowerCaseKey]; ok {
|
|
||||||
// include additional values
|
|
||||||
ctx.SignedHeaderVals[lowerCaseKey] = append(ctx.SignedHeaderVals[lowerCaseKey], v...)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
headers = append(headers, lowerCaseKey)
|
|
||||||
ctx.SignedHeaderVals[lowerCaseKey] = v
|
|
||||||
}
|
|
||||||
sort.Strings(headers)
|
|
||||||
|
|
||||||
ctx.signedHeaders = strings.Join(headers, ";")
|
|
||||||
|
|
||||||
if ctx.isPresign {
|
|
||||||
ctx.Query.Set("X-Amz-SignedHeaders", ctx.signedHeaders)
|
|
||||||
}
|
|
||||||
|
|
||||||
headerValues := make([]string, len(headers))
|
|
||||||
for i, k := range headers {
|
|
||||||
if k == "host" {
|
|
||||||
if ctx.Request.Host != "" {
|
|
||||||
headerValues[i] = "host:" + ctx.Request.Host
|
|
||||||
} else {
|
|
||||||
headerValues[i] = "host:" + ctx.Request.URL.Host
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
headerValues[i] = k + ":" +
|
|
||||||
strings.Join(ctx.SignedHeaderVals[k], ",")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stripExcessSpaces(headerValues)
|
|
||||||
ctx.canonicalHeaders = strings.Join(headerValues, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *signingCtx) buildCanonicalString() {
|
|
||||||
ctx.Request.URL.RawQuery = strings.Replace(ctx.Query.Encode(), "+", "%20", -1)
|
|
||||||
|
|
||||||
uri := getURIPath(ctx.Request.URL)
|
|
||||||
|
|
||||||
if !ctx.DisableURIPathEscaping {
|
|
||||||
uri = rest.EscapePath(uri, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.canonicalString = strings.Join([]string{
|
|
||||||
ctx.Request.Method,
|
|
||||||
uri,
|
|
||||||
ctx.Request.URL.RawQuery,
|
|
||||||
ctx.canonicalHeaders + "\n",
|
|
||||||
ctx.signedHeaders,
|
|
||||||
ctx.bodyDigest,
|
|
||||||
}, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *signingCtx) buildStringToSign() {
|
|
||||||
ctx.stringToSign = strings.Join([]string{
|
|
||||||
authHeaderPrefix,
|
|
||||||
formatTime(ctx.Time),
|
|
||||||
ctx.credentialString,
|
|
||||||
hex.EncodeToString(hashSHA256([]byte(ctx.canonicalString))),
|
|
||||||
}, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *signingCtx) buildSignature() {
|
|
||||||
creds := deriveSigningKey(ctx.Region, ctx.ServiceName, ctx.credValues.SecretAccessKey, ctx.Time)
|
|
||||||
signature := hmacSHA256(creds, []byte(ctx.stringToSign))
|
|
||||||
ctx.signature = hex.EncodeToString(signature)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *signingCtx) buildBodyDigest() error {
|
|
||||||
hash := ctx.Request.Header.Get("X-Amz-Content-Sha256")
|
|
||||||
if hash == "" {
|
|
||||||
includeSHA256Header := ctx.unsignedPayload ||
|
|
||||||
ctx.ServiceName == "s3" ||
|
|
||||||
ctx.ServiceName == "glacier"
|
|
||||||
|
|
||||||
s3Presign := ctx.isPresign && ctx.ServiceName == "s3"
|
|
||||||
|
|
||||||
if ctx.unsignedPayload || s3Presign {
|
|
||||||
hash = "UNSIGNED-PAYLOAD"
|
|
||||||
includeSHA256Header = !s3Presign
|
|
||||||
} else if ctx.Body == nil {
|
|
||||||
hash = emptyStringSHA256
|
|
||||||
} else {
|
|
||||||
if !aws.IsReaderSeekable(ctx.Body) {
|
|
||||||
return fmt.Errorf("cannot use unseekable request body %T, for signed request with body", ctx.Body)
|
|
||||||
}
|
|
||||||
hashBytes, err := makeSha256Reader(ctx.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
hash = hex.EncodeToString(hashBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
if includeSHA256Header {
|
|
||||||
ctx.Request.Header.Set("X-Amz-Content-Sha256", hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx.bodyDigest = hash
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// isRequestSigned returns if the request is currently signed or presigned.
|
|
||||||
func (ctx *signingCtx) isRequestSigned() bool {
|
|
||||||
if ctx.isPresign && ctx.Query.Get("X-Amz-Signature") != "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ctx.Request.Header.Get("Authorization") != "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// unsign removes signing flags for both signed and presigned requests.
|
|
||||||
func (ctx *signingCtx) removePresign() {
|
|
||||||
ctx.Query.Del("X-Amz-Algorithm")
|
|
||||||
ctx.Query.Del("X-Amz-Signature")
|
|
||||||
ctx.Query.Del("X-Amz-Security-Token")
|
|
||||||
ctx.Query.Del("X-Amz-Date")
|
|
||||||
ctx.Query.Del("X-Amz-Expires")
|
|
||||||
ctx.Query.Del("X-Amz-Credential")
|
|
||||||
ctx.Query.Del("X-Amz-SignedHeaders")
|
|
||||||
}
|
|
||||||
|
|
||||||
func hmacSHA256(key []byte, data []byte) []byte {
|
|
||||||
hash := hmac.New(sha256.New, key)
|
|
||||||
hash.Write(data)
|
|
||||||
return hash.Sum(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hashSHA256(data []byte) []byte {
|
|
||||||
hash := sha256.New()
|
|
||||||
hash.Write(data)
|
|
||||||
return hash.Sum(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeSha256Reader(reader io.ReadSeeker) (hashBytes []byte, err error) {
|
|
||||||
hash := sha256.New()
|
|
||||||
start, err := reader.Seek(0, io.SeekCurrent)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
// ensure error is return if unable to seek back to start of payload.
|
|
||||||
_, err = reader.Seek(start, io.SeekStart)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Use CopyN to avoid allocating the 32KB buffer in io.Copy for bodies
|
|
||||||
// smaller than 32KB. Fall back to io.Copy if we fail to determine the size.
|
|
||||||
size, err := aws.SeekerLen(reader)
|
|
||||||
if err != nil {
|
|
||||||
_, _ = io.Copy(hash, reader)
|
|
||||||
} else {
|
|
||||||
_, _ = io.CopyN(hash, reader, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash.Sum(nil), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const doubleSpace = " "
|
|
||||||
|
|
||||||
// stripExcessSpaces will rewrite the passed in slice's string values to not
|
|
||||||
// contain multiple side-by-side spaces.
|
|
||||||
//
|
|
||||||
//nolint:revive
|
|
||||||
func stripExcessSpaces(vals []string) {
|
|
||||||
var j, k, l, m, spaces int
|
|
||||||
for i, str := range vals {
|
|
||||||
// Trim trailing spaces
|
|
||||||
for j = len(str) - 1; j >= 0 && str[j] == ' '; j-- {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trim leading spaces
|
|
||||||
for k = 0; k < j && str[k] == ' '; k++ {
|
|
||||||
}
|
|
||||||
str = str[k : j+1]
|
|
||||||
|
|
||||||
// Strip multiple spaces.
|
|
||||||
j = strings.Index(str, doubleSpace)
|
|
||||||
if j < 0 {
|
|
||||||
vals[i] = str
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := []byte(str)
|
|
||||||
for k, m, l = j, j, len(buf); k < l; k++ {
|
|
||||||
if buf[k] == ' ' {
|
|
||||||
if spaces == 0 {
|
|
||||||
// First space.
|
|
||||||
buf[m] = buf[k]
|
|
||||||
m++
|
|
||||||
}
|
|
||||||
spaces++
|
|
||||||
} else {
|
|
||||||
// End of multiple spaces.
|
|
||||||
spaces = 0
|
|
||||||
buf[m] = buf[k]
|
|
||||||
m++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vals[i] = string(buf[:m])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildSigningScope(region, service string, dt time.Time) string {
|
|
||||||
return strings.Join([]string{
|
|
||||||
formatShortTime(dt),
|
|
||||||
region,
|
|
||||||
service,
|
|
||||||
awsV4Request,
|
|
||||||
}, "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
func deriveSigningKey(region, service, secretKey string, dt time.Time) []byte {
|
|
||||||
hmacDate := hmacSHA256([]byte("AWS4"+secretKey), []byte(formatShortTime(dt)))
|
|
||||||
hmacRegion := hmacSHA256(hmacDate, []byte(region))
|
|
||||||
hmacService := hmacSHA256(hmacRegion, []byte(service))
|
|
||||||
signingKey := hmacSHA256(hmacService, []byte(awsV4Request))
|
|
||||||
return signingKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatShortTime(dt time.Time) string {
|
|
||||||
return dt.UTC().Format(shortTimeFormat)
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatTime(dt time.Time) string {
|
|
||||||
return dt.UTC().Format(timeFormat)
|
|
||||||
}
|
|
|
@ -1,442 +0,0 @@
|
||||||
package v4
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
v4Internal "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4sdk2/signer/internal/v4"
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
|
||||||
awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws/middleware/private/metrics"
|
|
||||||
"github.com/aws/smithy-go/middleware"
|
|
||||||
smithyhttp "github.com/aws/smithy-go/transport/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
const computePayloadHashMiddlewareID = "ComputePayloadHash"
|
|
||||||
|
|
||||||
// HashComputationError indicates an error occurred while computing the signing hash
|
|
||||||
type HashComputationError struct {
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error is the error message
|
|
||||||
func (e *HashComputationError) Error() string {
|
|
||||||
return fmt.Sprintf("failed to compute payload hash: %v", e.Err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unwrap returns the underlying error if one is set
|
|
||||||
func (e *HashComputationError) Unwrap() error {
|
|
||||||
return e.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SigningError indicates an error condition occurred while performing SigV4 signing
|
|
||||||
type SigningError struct {
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *SigningError) Error() string {
|
|
||||||
return fmt.Sprintf("failed to sign request: %v", e.Err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unwrap returns the underlying error cause
|
|
||||||
func (e *SigningError) Unwrap() error {
|
|
||||||
return e.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UseDynamicPayloadSigningMiddleware swaps the compute payload sha256 middleware with a resolver middleware that
|
|
||||||
// switches between unsigned and signed payload based on TLS state for request.
|
|
||||||
// This middleware should not be used for AWS APIs that do not support unsigned payload signing auth.
|
|
||||||
// By default, SDK uses this middleware for known AWS APIs that support such TLS based auth selection .
|
|
||||||
//
|
|
||||||
// Usage example -
|
|
||||||
// S3 PutObject API allows unsigned payload signing auth usage when TLS is enabled, and uses this middleware to
|
|
||||||
// dynamically switch between unsigned and signed payload based on TLS state for request.
|
|
||||||
func UseDynamicPayloadSigningMiddleware(stack *middleware.Stack) error {
|
|
||||||
_, err := stack.Finalize.Swap(computePayloadHashMiddlewareID, &dynamicPayloadSigningMiddleware{})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// dynamicPayloadSigningMiddleware dynamically resolves the middleware that computes and set payload sha256 middleware.
|
|
||||||
type dynamicPayloadSigningMiddleware struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID returns the resolver identifier
|
|
||||||
func (m *dynamicPayloadSigningMiddleware) ID() string {
|
|
||||||
return computePayloadHashMiddlewareID
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleFinalize delegates SHA256 computation according to whether the request
|
|
||||||
// is TLS-enabled.
|
|
||||||
func (m *dynamicPayloadSigningMiddleware) HandleFinalize(
|
|
||||||
ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler,
|
|
||||||
) (
|
|
||||||
out middleware.FinalizeOutput, metadata middleware.Metadata, err error,
|
|
||||||
) {
|
|
||||||
req, ok := in.Request.(*smithyhttp.Request)
|
|
||||||
if !ok {
|
|
||||||
return out, metadata, fmt.Errorf("unknown transport type %T", in.Request)
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.IsHTTPS() {
|
|
||||||
return (&UnsignedPayload{}).HandleFinalize(ctx, in, next)
|
|
||||||
}
|
|
||||||
return (&ComputePayloadSHA256{}).HandleFinalize(ctx, in, next)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnsignedPayload sets the SigV4 request payload hash to unsigned.
|
|
||||||
//
|
|
||||||
// Will not set the Unsigned Payload magic SHA value, if a SHA has already been
|
|
||||||
// stored in the context. (e.g. application pre-computed SHA256 before making
|
|
||||||
// API call).
|
|
||||||
//
|
|
||||||
// This middleware does not check the X-Amz-Content-Sha256 header, if that
|
|
||||||
// header is serialized a middleware must translate it into the context.
|
|
||||||
type UnsignedPayload struct{}
|
|
||||||
|
|
||||||
// AddUnsignedPayloadMiddleware adds unsignedPayload to the operation
|
|
||||||
// middleware stack
|
|
||||||
func AddUnsignedPayloadMiddleware(stack *middleware.Stack) error {
|
|
||||||
return stack.Finalize.Insert(&UnsignedPayload{}, "ResolveEndpointV2", middleware.After)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID returns the unsignedPayload identifier
|
|
||||||
func (m *UnsignedPayload) ID() string {
|
|
||||||
return computePayloadHashMiddlewareID
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleFinalize sets the payload hash magic value to the unsigned sentinel.
|
|
||||||
func (m *UnsignedPayload) HandleFinalize(
|
|
||||||
ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler,
|
|
||||||
) (
|
|
||||||
out middleware.FinalizeOutput, metadata middleware.Metadata, err error,
|
|
||||||
) {
|
|
||||||
if GetPayloadHash(ctx) == "" {
|
|
||||||
ctx = SetPayloadHash(ctx, v4Internal.UnsignedPayload)
|
|
||||||
}
|
|
||||||
return next.HandleFinalize(ctx, in)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ComputePayloadSHA256 computes SHA256 payload hash to sign.
|
|
||||||
//
|
|
||||||
// Will not set the Unsigned Payload magic SHA value, if a SHA has already been
|
|
||||||
// stored in the context. (e.g. application pre-computed SHA256 before making
|
|
||||||
// API call).
|
|
||||||
//
|
|
||||||
// This middleware does not check the X-Amz-Content-Sha256 header, if that
|
|
||||||
// header is serialized a middleware must translate it into the context.
|
|
||||||
type ComputePayloadSHA256 struct{}
|
|
||||||
|
|
||||||
// AddComputePayloadSHA256Middleware adds computePayloadSHA256 to the
|
|
||||||
// operation middleware stack
|
|
||||||
func AddComputePayloadSHA256Middleware(stack *middleware.Stack) error {
|
|
||||||
return stack.Finalize.Insert(&ComputePayloadSHA256{}, "ResolveEndpointV2", middleware.After)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveComputePayloadSHA256Middleware removes computePayloadSHA256 from the
|
|
||||||
// operation middleware stack
|
|
||||||
func RemoveComputePayloadSHA256Middleware(stack *middleware.Stack) error {
|
|
||||||
_, err := stack.Finalize.Remove(computePayloadHashMiddlewareID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID is the middleware name
|
|
||||||
func (m *ComputePayloadSHA256) ID() string {
|
|
||||||
return computePayloadHashMiddlewareID
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleFinalize computes the payload hash for the request, storing it to the
|
|
||||||
// context. This is a no-op if a caller has previously set that value.
|
|
||||||
func (m *ComputePayloadSHA256) HandleFinalize(
|
|
||||||
ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler,
|
|
||||||
) (
|
|
||||||
out middleware.FinalizeOutput, metadata middleware.Metadata, err error,
|
|
||||||
) {
|
|
||||||
if GetPayloadHash(ctx) != "" {
|
|
||||||
return next.HandleFinalize(ctx, in)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, ok := in.Request.(*smithyhttp.Request)
|
|
||||||
if !ok {
|
|
||||||
return out, metadata, &HashComputationError{
|
|
||||||
Err: fmt.Errorf("unexpected request middleware type %T", in.Request),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hash := sha256.New()
|
|
||||||
if stream := req.GetStream(); stream != nil {
|
|
||||||
_, err = io.Copy(hash, stream)
|
|
||||||
if err != nil {
|
|
||||||
return out, metadata, &HashComputationError{
|
|
||||||
Err: fmt.Errorf("failed to compute payload hash, %w", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := req.RewindStream(); err != nil {
|
|
||||||
return out, metadata, &HashComputationError{
|
|
||||||
Err: fmt.Errorf("failed to seek body to start, %w", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx = SetPayloadHash(ctx, hex.EncodeToString(hash.Sum(nil)))
|
|
||||||
|
|
||||||
return next.HandleFinalize(ctx, in)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SwapComputePayloadSHA256ForUnsignedPayloadMiddleware replaces the
|
|
||||||
// ComputePayloadSHA256 middleware with the UnsignedPayload middleware.
|
|
||||||
//
|
|
||||||
// Use this to disable computing the Payload SHA256 checksum and instead use
|
|
||||||
// UNSIGNED-PAYLOAD for the SHA256 value.
|
|
||||||
func SwapComputePayloadSHA256ForUnsignedPayloadMiddleware(stack *middleware.Stack) error {
|
|
||||||
_, err := stack.Finalize.Swap(computePayloadHashMiddlewareID, &UnsignedPayload{})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContentSHA256Header sets the X-Amz-Content-Sha256 header value to
|
|
||||||
// the Payload hash stored in the context.
|
|
||||||
type ContentSHA256Header struct{}
|
|
||||||
|
|
||||||
// AddContentSHA256HeaderMiddleware adds ContentSHA256Header to the
|
|
||||||
// operation middleware stack
|
|
||||||
func AddContentSHA256HeaderMiddleware(stack *middleware.Stack) error {
|
|
||||||
return stack.Finalize.Insert(&ContentSHA256Header{}, computePayloadHashMiddlewareID, middleware.After)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveContentSHA256HeaderMiddleware removes contentSHA256Header middleware
|
|
||||||
// from the operation middleware stack
|
|
||||||
func RemoveContentSHA256HeaderMiddleware(stack *middleware.Stack) error {
|
|
||||||
_, err := stack.Finalize.Remove((*ContentSHA256Header)(nil).ID())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID returns the ContentSHA256HeaderMiddleware identifier
|
|
||||||
func (m *ContentSHA256Header) ID() string {
|
|
||||||
return "SigV4ContentSHA256Header"
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleFinalize sets the X-Amz-Content-Sha256 header value to the Payload hash
|
|
||||||
// stored in the context.
|
|
||||||
func (m *ContentSHA256Header) HandleFinalize(
|
|
||||||
ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler,
|
|
||||||
) (
|
|
||||||
out middleware.FinalizeOutput, metadata middleware.Metadata, err error,
|
|
||||||
) {
|
|
||||||
req, ok := in.Request.(*smithyhttp.Request)
|
|
||||||
if !ok {
|
|
||||||
return out, metadata, &HashComputationError{Err: fmt.Errorf("unexpected request middleware type %T", in.Request)}
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set(v4Internal.ContentSHAKey, GetPayloadHash(ctx))
|
|
||||||
return next.HandleFinalize(ctx, in)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignHTTPRequestMiddlewareOptions is the configuration options for
|
|
||||||
// [SignHTTPRequestMiddleware].
|
|
||||||
//
|
|
||||||
// Deprecated: [SignHTTPRequestMiddleware] is deprecated.
|
|
||||||
type SignHTTPRequestMiddlewareOptions struct {
|
|
||||||
CredentialsProvider aws.CredentialsProvider
|
|
||||||
Signer HTTPSigner
|
|
||||||
LogSigning bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignHTTPRequestMiddleware is a `FinalizeMiddleware` implementation for SigV4
|
|
||||||
// HTTP Signing.
|
|
||||||
//
|
|
||||||
// Deprecated: AWS service clients no longer use this middleware. Signing as an
|
|
||||||
// SDK operation is now performed through an internal per-service middleware
|
|
||||||
// which opaquely selects and uses the signer from the resolved auth scheme.
|
|
||||||
type SignHTTPRequestMiddleware struct {
|
|
||||||
credentialsProvider aws.CredentialsProvider
|
|
||||||
signer HTTPSigner
|
|
||||||
logSigning bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSignHTTPRequestMiddleware constructs a [SignHTTPRequestMiddleware] using
|
|
||||||
// the given [Signer] for signing requests.
|
|
||||||
//
|
|
||||||
// Deprecated: SignHTTPRequestMiddleware is deprecated.
|
|
||||||
func NewSignHTTPRequestMiddleware(options SignHTTPRequestMiddlewareOptions) *SignHTTPRequestMiddleware {
|
|
||||||
return &SignHTTPRequestMiddleware{
|
|
||||||
credentialsProvider: options.CredentialsProvider,
|
|
||||||
signer: options.Signer,
|
|
||||||
logSigning: options.LogSigning,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID is the SignHTTPRequestMiddleware identifier.
|
|
||||||
//
|
|
||||||
// Deprecated: SignHTTPRequestMiddleware is deprecated.
|
|
||||||
func (s *SignHTTPRequestMiddleware) ID() string {
|
|
||||||
return "Signing"
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleFinalize will take the provided input and sign the request using the
|
|
||||||
// SigV4 authentication scheme.
|
|
||||||
//
|
|
||||||
// Deprecated: SignHTTPRequestMiddleware is deprecated.
|
|
||||||
func (s *SignHTTPRequestMiddleware) HandleFinalize(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (
|
|
||||||
out middleware.FinalizeOutput, metadata middleware.Metadata, err error,
|
|
||||||
) {
|
|
||||||
if !haveCredentialProvider(s.credentialsProvider) {
|
|
||||||
return next.HandleFinalize(ctx, in)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, ok := in.Request.(*smithyhttp.Request)
|
|
||||||
if !ok {
|
|
||||||
return out, metadata, &SigningError{Err: fmt.Errorf("unexpected request middleware type %T", in.Request)}
|
|
||||||
}
|
|
||||||
|
|
||||||
signingName, signingRegion := awsmiddleware.GetSigningName(ctx), awsmiddleware.GetSigningRegion(ctx)
|
|
||||||
payloadHash := GetPayloadHash(ctx)
|
|
||||||
if len(payloadHash) == 0 {
|
|
||||||
return out, metadata, &SigningError{Err: fmt.Errorf("computed payload hash missing from context")}
|
|
||||||
}
|
|
||||||
|
|
||||||
mctx := metrics.Context(ctx)
|
|
||||||
|
|
||||||
if mctx != nil {
|
|
||||||
if attempt, err := mctx.Data().LatestAttempt(); err == nil {
|
|
||||||
attempt.CredentialFetchStartTime = time.Now()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
credentials, err := s.credentialsProvider.Retrieve(ctx)
|
|
||||||
|
|
||||||
if mctx != nil {
|
|
||||||
if attempt, err := mctx.Data().LatestAttempt(); err == nil {
|
|
||||||
attempt.CredentialFetchEndTime = time.Now()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return out, metadata, &SigningError{Err: fmt.Errorf("failed to retrieve credentials: %w", err)}
|
|
||||||
}
|
|
||||||
|
|
||||||
signerOptions := []func(o *SignerOptions){
|
|
||||||
func(o *SignerOptions) {
|
|
||||||
o.Logger = middleware.GetLogger(ctx)
|
|
||||||
o.LogSigning = s.logSigning
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// existing DisableURIPathEscaping is equivalent in purpose
|
|
||||||
// to authentication scheme property DisableDoubleEncoding
|
|
||||||
//disableDoubleEncoding, overridden := internalauth.GetDisableDoubleEncoding(ctx) // internalauth "github.com/aws/aws-sdk-go-v2/internal/auth"
|
|
||||||
//if overridden {
|
|
||||||
// signerOptions = append(signerOptions, func(o *SignerOptions) {
|
|
||||||
// o.DisableURIPathEscaping = disableDoubleEncoding
|
|
||||||
// })
|
|
||||||
//}
|
|
||||||
|
|
||||||
if mctx != nil {
|
|
||||||
if attempt, err := mctx.Data().LatestAttempt(); err == nil {
|
|
||||||
attempt.SignStartTime = time.Now()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.signer.SignHTTP(ctx, credentials, req.Request, payloadHash, signingName, signingRegion, time.Now(), signerOptions...)
|
|
||||||
|
|
||||||
if mctx != nil {
|
|
||||||
if attempt, err := mctx.Data().LatestAttempt(); err == nil {
|
|
||||||
attempt.SignEndTime = time.Now()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return out, metadata, &SigningError{Err: fmt.Errorf("failed to sign http request, %w", err)}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx = awsmiddleware.SetSigningCredentials(ctx, credentials)
|
|
||||||
|
|
||||||
return next.HandleFinalize(ctx, in)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StreamingEventsPayload signs input event stream messages.
|
|
||||||
type StreamingEventsPayload struct{}
|
|
||||||
|
|
||||||
// AddStreamingEventsPayload adds the streamingEventsPayload middleware to the stack.
|
|
||||||
func AddStreamingEventsPayload(stack *middleware.Stack) error {
|
|
||||||
return stack.Finalize.Add(&StreamingEventsPayload{}, middleware.Before)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID identifies the middleware.
|
|
||||||
func (s *StreamingEventsPayload) ID() string {
|
|
||||||
return computePayloadHashMiddlewareID
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleFinalize marks the input stream to be signed with SigV4.
|
|
||||||
func (s *StreamingEventsPayload) HandleFinalize(
|
|
||||||
ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler,
|
|
||||||
) (
|
|
||||||
out middleware.FinalizeOutput, metadata middleware.Metadata, err error,
|
|
||||||
) {
|
|
||||||
contentSHA := GetPayloadHash(ctx)
|
|
||||||
if len(contentSHA) == 0 {
|
|
||||||
contentSHA = v4Internal.StreamingEventsPayload
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx = SetPayloadHash(ctx, contentSHA)
|
|
||||||
|
|
||||||
return next.HandleFinalize(ctx, in)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSignedRequestSignature attempts to extract the signature of the request.
|
|
||||||
// Returning an error if the request is unsigned, or unable to extract the
|
|
||||||
// signature.
|
|
||||||
func GetSignedRequestSignature(r *http.Request) ([]byte, error) {
|
|
||||||
const authHeaderSignatureElem = "Signature="
|
|
||||||
|
|
||||||
if auth := r.Header.Get(authorizationHeader); len(auth) != 0 {
|
|
||||||
ps := strings.Split(auth, ", ")
|
|
||||||
for _, p := range ps {
|
|
||||||
if idx := strings.Index(p, authHeaderSignatureElem); idx >= 0 {
|
|
||||||
sig := p[len(authHeaderSignatureElem):]
|
|
||||||
if len(sig) == 0 {
|
|
||||||
return nil, fmt.Errorf("invalid request signature authorization header")
|
|
||||||
}
|
|
||||||
return hex.DecodeString(sig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sig := r.URL.Query().Get("X-Amz-Signature"); len(sig) != 0 {
|
|
||||||
return hex.DecodeString(sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("request not signed")
|
|
||||||
}
|
|
||||||
|
|
||||||
func haveCredentialProvider(p aws.CredentialsProvider) bool {
|
|
||||||
if p == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return !aws.IsCredentialsProvider(p, (*aws.AnonymousCredentials)(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
type payloadHashKey struct{}
|
|
||||||
|
|
||||||
// GetPayloadHash retrieves the payload hash to use for signing
|
|
||||||
//
|
|
||||||
// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues
|
|
||||||
// to clear all stack values.
|
|
||||||
func GetPayloadHash(ctx context.Context) (v string) {
|
|
||||||
v, _ = middleware.GetStackValue(ctx, payloadHashKey{}).(string)
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPayloadHash sets the payload hash to be used for signing the request
|
|
||||||
//
|
|
||||||
// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues
|
|
||||||
// to clear all stack values.
|
|
||||||
func SetPayloadHash(ctx context.Context, hash string) context.Context {
|
|
||||||
return middleware.WithStackValue(ctx, payloadHashKey{}, hash)
|
|
||||||
}
|
|
|
@ -1,126 +0,0 @@
|
||||||
package v4
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
|
||||||
awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
|
|
||||||
"github.com/aws/smithy-go/middleware"
|
|
||||||
smithyHTTP "github.com/aws/smithy-go/transport/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HTTPPresigner is an interface to a SigV4 signer that can sign create a
|
|
||||||
// presigned URL for a HTTP requests.
|
|
||||||
type HTTPPresigner interface {
|
|
||||||
PresignHTTP(
|
|
||||||
ctx context.Context, credentials aws.Credentials, r *http.Request,
|
|
||||||
payloadHash string, service string, region string, signingTime time.Time,
|
|
||||||
optFns ...func(*SignerOptions),
|
|
||||||
) (url string, signedHeader http.Header, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PresignedHTTPRequest provides the URL and signed headers that are included
|
|
||||||
// in the presigned URL.
|
|
||||||
type PresignedHTTPRequest struct {
|
|
||||||
URL string
|
|
||||||
Method string
|
|
||||||
SignedHeader http.Header
|
|
||||||
}
|
|
||||||
|
|
||||||
// PresignHTTPRequestMiddlewareOptions is the options for the PresignHTTPRequestMiddleware middleware.
|
|
||||||
type PresignHTTPRequestMiddlewareOptions struct {
|
|
||||||
CredentialsProvider aws.CredentialsProvider
|
|
||||||
Presigner HTTPPresigner
|
|
||||||
LogSigning bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// PresignHTTPRequestMiddleware provides the Finalize middleware for creating a
|
|
||||||
// presigned URL for an HTTP request.
|
|
||||||
//
|
|
||||||
// Will short circuit the middleware stack and not forward onto the next
|
|
||||||
// Finalize handler.
|
|
||||||
type PresignHTTPRequestMiddleware struct {
|
|
||||||
credentialsProvider aws.CredentialsProvider
|
|
||||||
presigner HTTPPresigner
|
|
||||||
logSigning bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPresignHTTPRequestMiddleware returns a new PresignHTTPRequestMiddleware
|
|
||||||
// initialized with the presigner.
|
|
||||||
func NewPresignHTTPRequestMiddleware(options PresignHTTPRequestMiddlewareOptions) *PresignHTTPRequestMiddleware {
|
|
||||||
return &PresignHTTPRequestMiddleware{
|
|
||||||
credentialsProvider: options.CredentialsProvider,
|
|
||||||
presigner: options.Presigner,
|
|
||||||
logSigning: options.LogSigning,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID provides the middleware ID.
|
|
||||||
func (*PresignHTTPRequestMiddleware) ID() string { return "PresignHTTPRequest" }
|
|
||||||
|
|
||||||
// HandleFinalize will take the provided input and create a presigned url for
|
|
||||||
// the http request using the SigV4 presign authentication scheme.
|
|
||||||
//
|
|
||||||
// Since the signed request is not a valid HTTP request
|
|
||||||
func (s *PresignHTTPRequestMiddleware) HandleFinalize(
|
|
||||||
ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler,
|
|
||||||
) (
|
|
||||||
out middleware.FinalizeOutput, metadata middleware.Metadata, err error,
|
|
||||||
) {
|
|
||||||
req, ok := in.Request.(*smithyHTTP.Request)
|
|
||||||
if !ok {
|
|
||||||
return out, metadata, &SigningError{
|
|
||||||
Err: fmt.Errorf("unexpected request middleware type %T", in.Request),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
httpReq := req.Build(ctx)
|
|
||||||
if !haveCredentialProvider(s.credentialsProvider) {
|
|
||||||
out.Result = &PresignedHTTPRequest{
|
|
||||||
URL: httpReq.URL.String(),
|
|
||||||
Method: httpReq.Method,
|
|
||||||
SignedHeader: http.Header{},
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, metadata, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
signingName := awsmiddleware.GetSigningName(ctx)
|
|
||||||
signingRegion := awsmiddleware.GetSigningRegion(ctx)
|
|
||||||
payloadHash := GetPayloadHash(ctx)
|
|
||||||
if len(payloadHash) == 0 {
|
|
||||||
return out, metadata, &SigningError{
|
|
||||||
Err: fmt.Errorf("computed payload hash missing from context"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
credentials, err := s.credentialsProvider.Retrieve(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return out, metadata, &SigningError{
|
|
||||||
Err: fmt.Errorf("failed to retrieve credentials: %w", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
u, h, err := s.presigner.PresignHTTP(ctx, credentials,
|
|
||||||
httpReq, payloadHash, signingName, signingRegion, time.Now(),
|
|
||||||
func(o *SignerOptions) {
|
|
||||||
o.Logger = middleware.GetLogger(ctx)
|
|
||||||
o.LogSigning = s.logSigning
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return out, metadata, &SigningError{
|
|
||||||
Err: fmt.Errorf("failed to sign http request, %w", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out.Result = &PresignedHTTPRequest{
|
|
||||||
URL: u,
|
|
||||||
Method: httpReq.Method,
|
|
||||||
SignedHeader: h,
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, metadata, nil
|
|
||||||
}
|
|
|
@ -3,6 +3,7 @@ package handler
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -14,9 +15,6 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/encryption"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/encryption"
|
||||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
|
||||||
"github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil"
|
|
||||||
"github.com/aws/aws-sdk-go/service/s3"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -135,16 +133,17 @@ func TestDeleteObjectsError(t *testing.T) {
|
||||||
hc.tp.SetObjectError(addr, expectedError)
|
hc.tp.SetObjectError(addr, expectedError)
|
||||||
|
|
||||||
w := deleteObjectsBase(hc, bktName, [][2]string{{objName, nodeVersion.OID.EncodeToString()}})
|
w := deleteObjectsBase(hc, bktName, [][2]string{{objName, nodeVersion.OID.EncodeToString()}})
|
||||||
|
var buf bytes.Buffer
|
||||||
res := &s3.DeleteObjectsOutput{}
|
res := &DeleteObjectsResponse{}
|
||||||
err = xmlutil.UnmarshalXML(res, xml.NewDecoder(w.Result().Body), "")
|
err = xml.NewDecoder(io.TeeReader(w.Result().Body, &buf)).Decode(res)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.ElementsMatch(t, []*s3.Error{{
|
require.Contains(t, buf.String(), "VersionId")
|
||||||
Code: aws.String(expectedError.Code),
|
require.ElementsMatch(t, []DeleteError{{
|
||||||
Key: aws.String(objName),
|
Code: expectedError.Code,
|
||||||
Message: aws.String(expectedError.Error()),
|
Key: objName,
|
||||||
VersionId: aws.String(nodeVersion.OID.EncodeToString()),
|
Message: expectedError.Error(),
|
||||||
|
VersionID: nodeVersion.OID.EncodeToString(),
|
||||||
}}, res.Errors)
|
}}, res.Errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,14 +18,14 @@ import (
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
|
||||||
v4 "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4"
|
v4 "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4sdk2/signer/v4"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -408,8 +408,8 @@ func getChunkedRequest(ctx context.Context, t *testing.T, bktName, objName strin
|
||||||
AWSAccessKeyID := "AKIAIOSFODNN7EXAMPLE"
|
AWSAccessKeyID := "AKIAIOSFODNN7EXAMPLE"
|
||||||
AWSSecretAccessKey := "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
|
AWSSecretAccessKey := "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
|
||||||
|
|
||||||
awsCreds := credentials.NewStaticCredentials(AWSAccessKeyID, AWSSecretAccessKey, "")
|
awsCreds := aws.Credentials{AccessKeyID: AWSAccessKeyID, SecretAccessKey: AWSSecretAccessKey}
|
||||||
signer := v4.NewSigner(awsCreds)
|
signer := v4.NewSigner()
|
||||||
|
|
||||||
reqBody := bytes.NewBufferString("10000;chunk-signature=ad80c730a21e5b8d04586a2213dd63b9a0e99e0e2307b0ade35a65485a288648\r\n")
|
reqBody := bytes.NewBufferString("10000;chunk-signature=ad80c730a21e5b8d04586a2213dd63b9a0e99e0e2307b0ade35a65485a288648\r\n")
|
||||||
_, err := reqBody.Write(chunk1)
|
_, err := reqBody.Write(chunk1)
|
||||||
|
@ -432,7 +432,7 @@ func getChunkedRequest(ctx context.Context, t *testing.T, bktName, objName strin
|
||||||
signTime, err := time.Parse("20060102T150405Z", "20130524T000000Z")
|
signTime, err := time.Parse("20060102T150405Z", "20130524T000000Z")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = signer.Sign(req, nil, "s3", "us-east-1", signTime)
|
err = signer.SignHTTP(ctx, awsCreds, req, auth.UnsignedPayload, "s3", "us-east-1", signTime)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
req.Body = io.NopCloser(reqBody)
|
req.Body = io.NopCloser(reqBody)
|
||||||
|
|
|
@ -3,16 +3,17 @@ package handler
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
v4 "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4"
|
v4 "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4sdk2/signer/v4"
|
||||||
errs "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
errs "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -22,6 +23,7 @@ const (
|
||||||
|
|
||||||
type (
|
type (
|
||||||
s3ChunkReader struct {
|
s3ChunkReader struct {
|
||||||
|
ctx context.Context
|
||||||
reader *bufio.Reader
|
reader *bufio.Reader
|
||||||
streamSigner *v4.StreamSigner
|
streamSigner *v4.StreamSigner
|
||||||
|
|
||||||
|
@ -166,7 +168,7 @@ func (c *s3ChunkReader) Read(buf []byte) (num int, err error) {
|
||||||
// Once we have read the entire chunk successfully, we verify
|
// Once we have read the entire chunk successfully, we verify
|
||||||
// that the received signature matches our computed signature.
|
// that the received signature matches our computed signature.
|
||||||
|
|
||||||
calculatedSignature, err := c.streamSigner.GetSignature(nil, c.buffer, c.requestTime)
|
calculatedSignature, err := c.streamSigner.GetSignature(c.ctx, nil, c.buffer, c.requestTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.err = err
|
c.err = err
|
||||||
return num, c.err
|
return num, c.err
|
||||||
|
@ -189,29 +191,31 @@ func (c *s3ChunkReader) Read(buf []byte) (num int, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSignV4ChunkedReader(req *http.Request) (io.ReadCloser, error) {
|
func newSignV4ChunkedReader(req *http.Request) (io.ReadCloser, error) {
|
||||||
box, err := middleware.GetBoxData(req.Context())
|
ctx := req.Context()
|
||||||
|
box, err := middleware.GetBoxData(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errs.GetAPIError(errs.ErrAuthorizationHeaderMalformed)
|
return nil, errs.GetAPIError(errs.ErrAuthorizationHeaderMalformed)
|
||||||
}
|
}
|
||||||
|
|
||||||
authHeaders, err := middleware.GetAuthHeaders(req.Context())
|
authHeaders, err := middleware.GetAuthHeaders(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errs.GetAPIError(errs.ErrAuthorizationHeaderMalformed)
|
return nil, errs.GetAPIError(errs.ErrAuthorizationHeaderMalformed)
|
||||||
}
|
}
|
||||||
|
|
||||||
currentCredentials := credentials.NewStaticCredentials(authHeaders.AccessKeyID, box.Gate.SecretKey, "")
|
currentCredentials := aws.Credentials{AccessKeyID: authHeaders.AccessKeyID, SecretAccessKey: box.Gate.SecretKey}
|
||||||
seed, err := hex.DecodeString(authHeaders.SignatureV4)
|
seed, err := hex.DecodeString(authHeaders.SignatureV4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errs.GetAPIError(errs.ErrSignatureDoesNotMatch)
|
return nil, errs.GetAPIError(errs.ErrSignatureDoesNotMatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
reqTime, err := middleware.GetClientTime(req.Context())
|
reqTime, err := middleware.GetClientTime(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errs.GetAPIError(errs.ErrMalformedDate)
|
return nil, errs.GetAPIError(errs.ErrMalformedDate)
|
||||||
}
|
}
|
||||||
newStreamSigner := v4.NewStreamSigner(authHeaders.Region, "s3", seed, currentCredentials)
|
newStreamSigner := v4.NewStreamSigner(currentCredentials, "s3", authHeaders.Region, seed)
|
||||||
|
|
||||||
return &s3ChunkReader{
|
return &s3ChunkReader{
|
||||||
|
ctx: ctx,
|
||||||
reader: bufio.NewReader(req.Body),
|
reader: bufio.NewReader(req.Body),
|
||||||
streamSigner: newStreamSigner,
|
streamSigner: newStreamSigner,
|
||||||
requestTime: reqTime,
|
requestTime: reqTime,
|
||||||
|
|
|
@ -8,10 +8,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
|
||||||
credentialsv2 "github.com/aws/aws-sdk-go-v2/credentials"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go-v2/config"
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/session"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
@ -58,29 +56,41 @@ func initGeneratePresignedURLCmd() {
|
||||||
_ = generatePresignedURLCmd.MarkFlagRequired(objectFlag)
|
_ = generatePresignedURLCmd.MarkFlagRequired(objectFlag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runGeneratePresignedURLCmd(*cobra.Command, []string) error {
|
func runGeneratePresignedURLCmd(cmd *cobra.Command, _ []string) error {
|
||||||
var cfg aws.Config
|
var (
|
||||||
|
region string
|
||||||
|
creds aws.Credentials
|
||||||
|
)
|
||||||
|
|
||||||
if region := viper.GetString(regionFlag); region != "" {
|
profile := viper.GetString(profileFlag)
|
||||||
cfg.Region = ®ion
|
|
||||||
}
|
|
||||||
accessKeyID := viper.GetString(awsAccessKeyIDFlag)
|
|
||||||
secretAccessKey := viper.GetString(awsSecretAccessKeyFlag)
|
|
||||||
|
|
||||||
if accessKeyID != "" && secretAccessKey != "" {
|
if profile == "" {
|
||||||
cfg.Credentials = credentials.NewStaticCredentialsFromCreds(credentials.Value{
|
cfg, err := config.LoadDefaultConfig(cmd.Context())
|
||||||
AccessKeyID: accessKeyID,
|
|
||||||
SecretAccessKey: secretAccessKey,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
sess, err := session.NewSessionWithOptions(session.Options{
|
|
||||||
Config: cfg,
|
|
||||||
Profile: viper.GetString(profileFlag),
|
|
||||||
SharedConfigState: session.SharedConfigEnable,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapPreparationError(fmt.Errorf("couldn't get aws credentials: %w", err))
|
return wrapPreparationError(err)
|
||||||
|
}
|
||||||
|
region = cfg.Region
|
||||||
|
if creds, err = cfg.Credentials.Retrieve(cmd.Context()); err != nil {
|
||||||
|
return wrapPreparationError(fmt.Errorf("couldn't get default aws credentials: %w", err))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cfg, err := config.LoadSharedConfigProfile(cmd.Context(), viper.GetString(profileFlag))
|
||||||
|
if err != nil {
|
||||||
|
return wrapPreparationError(fmt.Errorf("couldn't get '%s' aws credentials: %w", viper.GetString(profileFlag), err))
|
||||||
|
}
|
||||||
|
region = cfg.Region
|
||||||
|
creds = cfg.Credentials
|
||||||
|
}
|
||||||
|
|
||||||
|
accessKeyIDArg := viper.GetString(awsAccessKeyIDFlag)
|
||||||
|
secretAccessKeyArg := viper.GetString(awsSecretAccessKeyFlag)
|
||||||
|
if accessKeyIDArg != "" && secretAccessKeyArg != "" {
|
||||||
|
creds.AccessKeyID = accessKeyIDArg
|
||||||
|
creds.SecretAccessKey = secretAccessKeyArg
|
||||||
|
}
|
||||||
|
|
||||||
|
if regionArg := viper.GetString(regionFlag); regionArg != "" {
|
||||||
|
region = regionArg
|
||||||
}
|
}
|
||||||
|
|
||||||
reqData := auth.RequestData{
|
reqData := auth.RequestData{
|
||||||
|
@ -91,22 +101,20 @@ func runGeneratePresignedURLCmd(*cobra.Command, []string) error {
|
||||||
}
|
}
|
||||||
presignData := auth.PresignData{
|
presignData := auth.PresignData{
|
||||||
Service: "s3",
|
Service: "s3",
|
||||||
Region: *sess.Config.Region,
|
Region: region,
|
||||||
Lifetime: viper.GetDuration(lifetimeFlag),
|
Lifetime: viper.GetDuration(lifetimeFlag),
|
||||||
SignTime: time.Now().UTC(),
|
SignTime: time.Now().UTC(),
|
||||||
}
|
}
|
||||||
|
|
||||||
var req *http.Request
|
var (
|
||||||
|
err error
|
||||||
|
req *http.Request
|
||||||
|
)
|
||||||
|
fmt.Println(region, creds)
|
||||||
if viper.GetBool(sigV4AFlag) {
|
if viper.GetBool(sigV4AFlag) {
|
||||||
val, err := sess.Config.Credentials.Get()
|
req, err = auth.PresignRequestV4a(creds, reqData, presignData)
|
||||||
if err != nil {
|
|
||||||
return wrapPreparationError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
awsCreds := credentialsv2.NewStaticCredentialsProvider(val.AccessKeyID, val.SecretAccessKey, "")
|
|
||||||
req, err = auth.PresignRequestV4a(awsCreds, reqData, presignData)
|
|
||||||
} else {
|
} else {
|
||||||
req, err = auth.PresignRequest(sess.Config.Credentials, reqData, presignData)
|
req, err = auth.PresignRequest(cmd.Context(), creds, reqData, presignData)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapBusinessLogicError(err)
|
return wrapBusinessLogicError(err)
|
||||||
|
|
18
go.mod
18
go.mod
|
@ -9,10 +9,10 @@ require (
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20240822080251-28f140bf06c1
|
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20240822080251-28f140bf06c1
|
||||||
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240822104152-a3bc3099bd5b
|
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240822104152-a3bc3099bd5b
|
||||||
git.frostfs.info/TrueCloudLab/zapjournald v0.0.0-20240124114243-cb2e66427d02
|
git.frostfs.info/TrueCloudLab/zapjournald v0.0.0-20240124114243-cb2e66427d02
|
||||||
github.com/aws/aws-sdk-go v1.44.6
|
github.com/aws/aws-sdk-go-v2 v1.30.5
|
||||||
github.com/aws/aws-sdk-go-v2 v1.26.0
|
github.com/aws/aws-sdk-go-v2/config v1.27.32
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.9
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.31
|
||||||
github.com/aws/smithy-go v1.20.1
|
github.com/aws/smithy-go v1.20.4
|
||||||
github.com/bluele/gcache v0.0.2
|
github.com/bluele/gcache v0.0.2
|
||||||
github.com/go-chi/chi/v5 v5.0.8
|
github.com/go-chi/chi/v5 v5.0.8
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
@ -47,6 +47,15 @@ require (
|
||||||
git.frostfs.info/TrueCloudLab/tzhash v1.8.0 // indirect
|
git.frostfs.info/TrueCloudLab/tzhash v1.8.0 // indirect
|
||||||
github.com/VictoriaMetrics/easyproto v0.1.4 // indirect
|
github.com/VictoriaMetrics/easyproto v0.1.4 // indirect
|
||||||
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.22.6 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.6 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.30.6 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
@ -62,7 +71,6 @@ require (
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
|
|
43
go.sum
43
go.sum
|
@ -64,14 +64,32 @@ github.com/VictoriaMetrics/easyproto v0.1.4/go.mod h1:QlGlzaJnDfFd8Lk6Ci/fuLxfTo
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
|
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
|
||||||
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
|
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
|
||||||
github.com/aws/aws-sdk-go v1.44.6 h1:Y+uHxmZfhRTLX2X3khkdxCoTZAyGEX21aOUHe1U6geg=
|
github.com/aws/aws-sdk-go-v2 v1.30.5 h1:mWSRTwQAb0aLE17dSzztCVJWI9+cRMgqebndjwDyK0g=
|
||||||
github.com/aws/aws-sdk-go v1.44.6/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
github.com/aws/aws-sdk-go-v2 v1.30.5/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.26.0 h1:/Ce4OCiM3EkpW7Y+xUnfAFpchU78K7/Ug01sZni9PgA=
|
github.com/aws/aws-sdk-go-v2/config v1.27.32 h1:jnAMVTJTpAQlePCUUlnXnllHEMGVWmvUJOiGjgtS9S0=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.26.0/go.mod h1:35hUlJVYd+M++iLI3ALmVwMOyRYMmRqUXpTtRGW+K9I=
|
github.com/aws/aws-sdk-go-v2/config v1.27.32/go.mod h1:JibtzKJoXT0M/MhoYL6qfCk7nm/MppwukDFZtdgVRoY=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.9 h1:N8s0/7yW+h8qR8WaRlPQeJ6czVMNQVNtNdUqf6cItao=
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.31 h1:jtyfcOfgoqWA2hW/E8sFbwdfgwD3APnF9CLCKE8dTyw=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.9/go.mod h1:446YhIdmSV0Jf/SLafGZalQo+xr2iw7/fzXGDPTU1yQ=
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.31/go.mod h1:RSgY5lfCfw+FoyKWtOpLolPlfQVdDBQWTUniAaE+NKY=
|
||||||
github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 h1:pfQ2sqNpMVK6xz2RbqLEL0GH87JOwSxPV2rzm8Zsb74=
|
||||||
github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13/go.mod h1:NG7RXPUlqfsCLLFfi0+IpKN4sCB9D9fw/qTaSB+xRoU=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 h1:pI7Bzt0BJtYA0N/JEC6B8fJ4RBrEMi1LBrkMdFYNSnQ=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17/go.mod h1:Dh5zzJYMtxfIjYW+/evjQ8uj2OyR/ve2KROHGHlSFqE=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 h1:Mqr/V5gvrhA2gvgnF42Zh5iMiQNcOYthFYwCyrnuWlc=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17/go.mod h1:aLJpZlCmjE+V+KtN1q1uyZkfnUWpQGpbsn89XPKyzfU=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 h1:rfprUlsdzgl7ZL2KlXiUAoJnI/VxfHCvDFr2QDFj6u4=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19/go.mod h1:SCWkEdRq8/7EK60NcvvQ6NXKuTcchAD4ROAsC37VEZE=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.22.6 h1:o++HUDXlbrTl4PSal3YHtdErQxB8mDGAtkKNXBWPfIU=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.22.6/go.mod h1:eEygMHnTKH/3kNp9Jr1n3PdejuSNcgwLe1dWgQtO0VQ=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.6 h1:yCHcQCOwTfIsc8DoEhM3qXPxD+j8CbI6t1K3dNzsWV0=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.6/go.mod h1:bCbAxKDqNvkHxRaIMnyVPXPo+OaPRwvmgzMxbz1VKSA=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.30.6 h1:TrQadF7GcqvQ63kgwEcjlrVc2Fa0wpgLT0xtc73uAd8=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.30.6/go.mod h1:NXi1dIAGteSaRLqYgarlhP/Ij0cFT+qmCwiJqWh/U5o=
|
||||||
|
github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4=
|
||||||
|
github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c=
|
github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c=
|
||||||
|
@ -220,10 +238,6 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
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=
|
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
|
@ -449,7 +463,6 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
|
||||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
@ -516,13 +529,11 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
@ -532,7 +543,6 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
@ -713,7 +723,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
|
Loading…
Reference in a new issue