From 8da71c3ae0f4b80e2f3648b98f4718d4e287e456 Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Tue, 23 Apr 2024 14:49:34 +0300 Subject: [PATCH] [#339] sigv4a: Support presign Signed-off-by: Denis Kirillov --- api/auth/center.go | 17 ++++++++--------- api/auth/center_test.go | 6 +++--- api/auth/presign.go | 10 ++++++---- api/auth/presign_test.go | 17 +++++++++++++---- api/auth/signer/v4asdk2/v4a.go | 15 ++++++++++----- .../modules/generate-presigned-url.go | 17 ++++++++++++++++- 6 files changed, 56 insertions(+), 26 deletions(-) diff --git a/api/auth/center.go b/api/auth/center.go index 2f2b8e14..cc60ad35 100644 --- a/api/auth/center.go +++ b/api/auth/center.go @@ -9,7 +9,6 @@ import ( "io" "mime/multipart" "net/http" - "os" "regexp" "strings" "time" @@ -24,7 +23,6 @@ import ( 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/aws/credentials" - "github.com/aws/smithy-go/logging" ) var ( @@ -426,8 +424,6 @@ func (c *Center) checkSign(authHeader *AuthHeader, box *accessbox.Box, request * case signaturePreambleSigV4A: signer := v4a.NewSigner(func(options *v4a.SignerOptions) { options.DisableURIPathEscaping = true - options.LogSigning = true - options.Logger = logging.NewStandardLogger(os.Stdout) }) credAdapter := v4a.SymmetricCredentialAdaptor{ @@ -439,13 +435,16 @@ func (c *Center) checkSign(authHeader *AuthHeader, box *accessbox.Box, request * return fmt.Errorf("failed to derive assymetric key from credentials: %w", err) } - if authHeader.IsPresigned { - if err = checkPresignedDate(authHeader, signatureDateTime); err != nil { - return err - } + if !authHeader.IsPresigned { + return signer.VerifySignature(creds, request, authHeader.PayloadHash, authHeader.Service, + strings.Split(authHeader.Region, ","), signatureDateTime, authHeader.Signature) } - return signer.VerifySignature(creds, request, authHeader.PayloadHash, authHeader.Service, + if err = checkPresignedDate(authHeader, signatureDateTime); err != nil { + return err + } + + return signer.VerifyPresigned(creds, request, authHeader.PayloadHash, authHeader.Service, strings.Split(authHeader.Region, ","), signatureDateTime, authHeader.Signature) default: return fmt.Errorf("invalid preamble: %s", authHeader.Preamble) diff --git a/api/auth/center_test.go b/api/auth/center_test.go index f4e51dd3..e217a57e 100644 --- a/api/auth/center_test.go +++ b/api/auth/center_test.go @@ -13,8 +13,8 @@ import ( "time" v4 "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4" - "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache" v4a "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4asdk2" + "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/creds/accessbox" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/tokens" @@ -24,10 +24,9 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" 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/aws/credentials" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" - credentialsv2 "github.com/aws/aws-sdk-go-v2/credentials" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) @@ -66,6 +65,7 @@ func TestAuthHeaderParse(t *testing.T) { Signature: "2811ccb9e242f41426738fb1f", SignedFields: []string{"host", "x-amz-content-sha256", "x-amz-date"}, Date: "20210809", + Preamble: signaturePreambleSigV4, }, }, { diff --git a/api/auth/presign.go b/api/auth/presign.go index 6d20b304..b7cbded5 100644 --- a/api/auth/presign.go +++ b/api/auth/presign.go @@ -39,11 +39,10 @@ func PresignRequest(creds *credentials.Credentials, reqData RequestData, presign return nil, fmt.Errorf("failed to create new request: %w", err) } - req.Header.Set(AmzDate, presignData.SignTime.Format("20060102T150405Z")) - for k, v := range presignData.Headers { - req.Header.Set(k, v) + req.Header.Set(k, v) // maybe we should filter system header (or keep responsibility on caller) } + req.Header.Set(AmzDate, presignData.SignTime.Format("20060102T150405Z")) signer := v4.NewSigner(creds) signer.DisableURIPathEscaping = true @@ -63,8 +62,11 @@ func PresignRequestV4a(credProvider credentialsv2.StaticCredentialsProvider, req return nil, fmt.Errorf("failed to create new request: %w", err) } + for k, v := range presignData.Headers { + 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(ContentTypeHdr, "text/plain") req.Header.Set(AmzExpires, strconv.Itoa(int(presignData.Lifetime.Seconds()))) signer := v4a.NewSigner(func(options *v4a.SignerOptions) { diff --git a/api/auth/presign_test.go b/api/auth/presign_test.go index 27f80842..6432cce0 100644 --- a/api/auth/presign_test.go +++ b/api/auth/presign_test.go @@ -75,6 +75,9 @@ func TestCheckSign(t *testing.T) { Region: "spb", Lifetime: 10 * time.Minute, SignTime: time.Now().UTC(), + Headers: map[string]string{ + ContentTypeHdr: "text/plain", + }, } req, err := PresignRequest(awsCreds, reqData, presignData) @@ -120,11 +123,16 @@ func TestCheckSignV4a(t *testing.T) { Region: "spb", Lifetime: 10 * time.Minute, SignTime: time.Now().UTC(), + Headers: map[string]string{ + ContentTypeHdr: "text/plain", + }, } req, err := PresignRequestV4a(awsCreds, reqData, presignData) require.NoError(t, err) + req.Header.Set(ContentTypeHdr, "text/plain") + expBox := &accessbox.Box{ Gate: &accessbox.GateData{ SecretKey: secretKey, @@ -171,9 +179,7 @@ func TestPresignRequestV4a(t *testing.T) { req, err := http.NewRequest("GET", "http://localhost:8084/bucket/object", nil) require.NoError(t, err) - //req.Header.Set(AmzRegionSet, strings.Join(regionSet, ",")) - //req.Header.Set(AmzDate, signingTime.Format("20060102T150405Z")) - //req.Header.Set(AmzAlgorithm, signaturePreambleSigV4A) + req.Header.Set(AmzExpires, "600") presignedURL, hdr, err := signer.PresignHTTP(req.Context(), creds, req, "", service, regionSet, signingTime) require.NoError(t, err) @@ -185,7 +191,10 @@ func TestPresignRequestV4a(t *testing.T) { r, err := http.NewRequest("GET", presignedURL, nil) require.NoError(t, err) + query := r.URL.Query() + query.Del(AmzSignature) + r.URL.RawQuery = query.Encode() - err = signer.VerifySignature(creds, r, "", service, regionSet, signingTime, signature) + err = signer.VerifyPresigned(creds, r, "", service, regionSet, signingTime, signature) require.NoError(t, err) } diff --git a/api/auth/signer/v4asdk2/v4a.go b/api/auth/signer/v4asdk2/v4a.go index 305fbd11..5f798594 100644 --- a/api/auth/signer/v4asdk2/v4a.go +++ b/api/auth/signer/v4asdk2/v4a.go @@ -202,6 +202,15 @@ func (s *Signer) SignHTTP(ctx context.Context, credentials Credentials, r *http. // VerifySignature checks sigv4a. func (s *Signer) VerifySignature(credentials Credentials, r *http.Request, payloadHash string, service string, regionSet []string, signingTime time.Time, signature string, optFns ...func(*SignerOptions)) error { + return s.verifySignature(credentials, r, payloadHash, service, regionSet, signingTime, signature, false, optFns...) +} + +// VerifyPresigned checks sigv4a. +func (s *Signer) VerifyPresigned(credentials Credentials, r *http.Request, payloadHash string, service string, regionSet []string, signingTime time.Time, signature string, optFns ...func(*SignerOptions)) error { + return s.verifySignature(credentials, r, payloadHash, service, regionSet, signingTime, signature, true, optFns...) +} + +func (s *Signer) verifySignature(credentials Credentials, r *http.Request, payloadHash string, service string, regionSet []string, signingTime time.Time, signature string, isPresigned bool, optFns ...func(*SignerOptions)) error { options := s.options for _, fn := range optFns { fn(&options) @@ -214,6 +223,7 @@ func (s *Signer) VerifySignature(credentials Credentials, r *http.Request, paylo RegionSet: regionSet, Credentials: credentials, Time: signingTime.UTC(), + IsPreSign: isPresigned, DisableHeaderHoisting: options.DisableHeaderHoisting, DisableURIPathEscaping: options.DisableURIPathEscaping, } @@ -465,11 +475,6 @@ func (s *httpSigner) buildCanonicalHeaders(host string, rule v4Internal.Rule, he continue // ignored header } - if strings.EqualFold(k, contentLengthHeader) { - // prevent signing already handled content-length header. - continue - } - lowerCaseKey := strings.ToLower(k) if _, ok := signed[lowerCaseKey]; ok { // include additional values diff --git a/cmd/s3-authmate/modules/generate-presigned-url.go b/cmd/s3-authmate/modules/generate-presigned-url.go index 764187cf..f29d6319 100644 --- a/cmd/s3-authmate/modules/generate-presigned-url.go +++ b/cmd/s3-authmate/modules/generate-presigned-url.go @@ -3,11 +3,13 @@ package modules import ( "encoding/json" "fmt" + "net/http" "os" "strings" "time" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth" + credentialsv2 "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" @@ -38,6 +40,7 @@ const ( awsAccessKeyIDFlag = "aws-access-key-id" awsSecretAccessKeyFlag = "aws-secret-access-key" headerFlag = "header" + sigV4AFlag = "sigv4a" ) func initGeneratePresignedURLCmd() { @@ -51,6 +54,7 @@ func initGeneratePresignedURLCmd() { generatePresignedURLCmd.Flags().String(awsAccessKeyIDFlag, "", "AWS access key id to sign the URL (default is taken from ~/.aws/credentials)") generatePresignedURLCmd.Flags().String(awsSecretAccessKeyFlag, "", "AWS secret access key to sign the URL (default is taken from ~/.aws/credentials)") generatePresignedURLCmd.Flags().StringSlice(headerFlag, nil, "Header in form of 'Key: value' to use in presigned URL (use flags repeatedly for multiple headers or separate them by comma)") + generatePresignedURLCmd.Flags().Bool(sigV4AFlag, false, "Use SigV4A for signing request") _ = generatePresignedURLCmd.MarkFlagRequired(endpointFlag) _ = generatePresignedURLCmd.MarkFlagRequired(bucketFlag) @@ -101,7 +105,18 @@ func runGeneratePresignedURLCmd(*cobra.Command, []string) error { } presignData.Headers = headers - req, err := auth.PresignRequest(sess.Config.Credentials, reqData, presignData) + var req *http.Request + if viper.GetBool(sigV4AFlag) { + val, err := sess.Config.Credentials.Get() + if err != nil { + return wrapPreparationError(err) + } + + awsCreds := credentialsv2.NewStaticCredentialsProvider(val.AccessKeyID, val.SecretAccessKey, "") + req, err = auth.PresignRequestV4a(awsCreds, reqData, presignData) + } else { + req, err = auth.PresignRequest(sess.Config.Credentials, reqData, presignData) + } if err != nil { return wrapBusinessLogicError(err) }