From 3d3dd002117e44b0d152dc1cd11d47521e70d9c7 Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Tue, 28 Jan 2025 17:58:29 +0300 Subject: [PATCH] [#615] Use `UNSIGNED_PAYLOAD` to check sign Use `UNSIGNED_PAYLOAD` to check signature if x-amz-content-sha256 isn't provided as signed header https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html " You include the literal string UNSIGNED-PAYLOAD when constructing a canonical request, and set the same value as the x-amz-content-sha256 header value when sending the request to Amazon S3" Signed-off-by: Denis Kirillov --- api/auth/center.go | 5 +++ api/auth/center_test.go | 91 +++++++++++++++++++++++++++++++++++++++- api/auth/presign.go | 15 ++++++- api/auth/presign_test.go | 3 +- go.mod | 17 +++++--- go.sum | 34 +++++++++------ 6 files changed, 142 insertions(+), 23 deletions(-) diff --git a/api/auth/center.go b/api/auth/center.go index adb998a5..99570274 100644 --- a/api/auth/center.go +++ b/api/auth/center.go @@ -13,6 +13,7 @@ import ( "net/http" "net/url" "regexp" + "slices" "strings" "time" @@ -396,6 +397,10 @@ func cloneRequest(r *http.Request, authHeader *AuthHeader) *http.Request { func (c *Center) checkSign(ctx context.Context, authHeader *AuthHeader, box *accessbox.Box, request *http.Request, signatureDateTime time.Time) error { var signature string + if !slices.Contains(authHeader.SignedFields, "x-amz-content-sha256") && authHeader.PayloadHash == "" { + authHeader.PayloadHash = UnsignedPayload + } + switch authHeader.Preamble { case signaturePreambleSigV4: creds := aws.Credentials{ diff --git a/api/auth/center_test.go b/api/auth/center_test.go index c4756466..e12d1c8c 100644 --- a/api/auth/center_test.go +++ b/api/auth/center_test.go @@ -8,6 +8,7 @@ import ( "net/http" "net/http/httptest" "net/url" + "os" "strings" "testing" "time" @@ -26,6 +27,9 @@ import ( oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/s3" + smithyauth "github.com/aws/smithy-go/auth" + "github.com/aws/smithy-go/logging" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" @@ -298,6 +302,7 @@ func TestAuthenticate(t *testing.T) { for _, tc := range []struct { name string + region string prefixes []string request *http.Request err bool @@ -308,10 +313,23 @@ func TestAuthenticate(t *testing.T) { prefixes: []string{addr.Container().String()}, request: func() *http.Request { r := httptest.NewRequest(http.MethodPost, "/", nil) + err = defaultSigner.SignHTTP(ctx, awsCreds, r, UnsignedPayload, service, region, time.Now()) + require.NoError(t, err) + return r + }(), + region: region, + }, + { + name: "valid sign with hash", + prefixes: []string{addr.Container().String()}, + request: func() *http.Request { + r := httptest.NewRequest(http.MethodPost, "/", nil) + r.Header.Set(AmzContentSHA256, "") err = defaultSigner.SignHTTP(ctx, awsCreds, r, "", service, region, time.Now()) require.NoError(t, err) return r }(), + region: region, }, { name: "no authorization header", @@ -418,12 +436,27 @@ func TestAuthenticate(t *testing.T) { request: func() *http.Request { r := httptest.NewRequest(http.MethodPost, "/", nil) r.Header.Set(AmzExpires, "60") + signedURI, _, err := defaultSigner.PresignHTTP(ctx, awsCreds, r, UnsignedPayload, service, region, time.Now()) + require.NoError(t, err) + r.URL, err = url.ParseRequestURI(signedURI) + require.NoError(t, err) + return r + }(), + region: region, + }, + { + name: "valid presign with hash", + request: func() *http.Request { + r := httptest.NewRequest(http.MethodPost, "/", nil) + r.Header.Set(AmzExpires, "60") + r.Header.Set(AmzContentSHA256, "") 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) return r }(), + region: region, }, { name: "presign, bad X-Amz-Credential", @@ -480,6 +513,56 @@ func TestAuthenticate(t *testing.T) { err: true, errCode: errors.ErrBadRequest, }, + { + name: "presign using original aws sdk", + request: func() *http.Request { + cli := s3.NewPresignClient(s3.New(s3.Options{ + Credentials: credentials.NewStaticCredentialsProvider(awsCreds.AccessKeyID, awsCreds.SecretAccessKey, ""), + UsePathStyle: true, + BaseEndpoint: aws.String("http://localhost"), + Region: region, + Logger: logging.NewStandardLogger(os.Stdout), + ClientLogMode: aws.LogSigning, + })) + + res, err := cli.PresignGetObject(ctx, &s3.GetObjectInput{ + Bucket: aws.String("bucket"), + Key: aws.String("object"), + }) + require.NoError(t, err) + + r := httptest.NewRequest(http.MethodGet, "http://localhost", nil) + r.URL, err = url.ParseRequestURI(res.URL) + require.NoError(t, err) + return r + }(), + region: region, + }, + { + name: "presign sigv4a using original aws sdk", + request: func() *http.Request { + cli := s3.NewPresignClient(s3.New(s3.Options{ + Credentials: credentials.NewStaticCredentialsProvider(awsCreds.AccessKeyID, awsCreds.SecretAccessKey, ""), + UsePathStyle: true, + BaseEndpoint: aws.String("http://localhost"), + Region: region, + Logger: logging.NewStandardLogger(os.Stdout), + ClientLogMode: aws.LogSigning, + AuthSchemeResolver: resolver{}, + })) + + res, err := cli.PresignGetObject(ctx, &s3.GetObjectInput{ + Bucket: aws.String("bucket"), + Key: aws.String("object"), + }) + require.NoError(t, err) + + r := httptest.NewRequest(http.MethodGet, "http://localhost", nil) + r.URL, err = url.ParseRequestURI(res.URL) + require.NoError(t, err) + return r + }(), + }, } { t.Run(tc.name, func(t *testing.T) { creds := tokens.New(bigConfig) @@ -495,13 +578,19 @@ func TestAuthenticate(t *testing.T) { } else { require.NoError(t, err) require.Equal(t, accessKeyID, box.AuthHeaders.AccessKeyID) - require.Equal(t, region, box.AuthHeaders.Region) + require.Equal(t, tc.region, box.AuthHeaders.Region) require.Equal(t, secret.SecretKey, box.AccessBox.Gate.SecretKey) } }) } } +type resolver struct{} + +func (r resolver) ResolveAuthSchemes(context.Context, *s3.AuthResolverParameters) ([]*smithyauth.Option, error) { + return []*smithyauth.Option{{SchemeID: smithyauth.SchemeIDSigV4A}}, nil +} + func TestHTTPPostAuthenticate(t *testing.T) { const ( policyBase64 = "eyJleHBpcmF0aW9uIjogIjIwMjUtMTItMDFUMTI6MDA6MDAuMDAwWiIsImNvbmRpdGlvbnMiOiBbCiBbInN0YXJ0cy13aXRoIiwgIiR4LWFtei1jcmVkZW50aWFsIiwgIiJdLAogWyJzdGFydHMtd2l0aCIsICIkeC1hbXotZGF0ZSIsICIiXQpdfQ==" diff --git a/api/auth/presign.go b/api/auth/presign.go index f24b02f1..52bb65e4 100644 --- a/api/auth/presign.go +++ b/api/auth/presign.go @@ -52,7 +52,12 @@ func PresignRequest(ctx context.Context, creds aws.Credentials, reqData RequestD options.Logger = log }) - signedURI, _, err := signer.PresignHTTP(ctx, creds, req, presignData.Headers[AmzContentSHA256], presignData.Service, presignData.Region, presignData.SignTime) + payloadHash := presignData.Headers[AmzContentSHA256] + if payloadHash == "" { + payloadHash = UnsignedPayload + } + + signedURI, _, err := signer.PresignHTTP(ctx, creds, req, payloadHash, presignData.Service, presignData.Region, presignData.SignTime) if err != nil { return nil, fmt.Errorf("presign: %w", err) } @@ -93,7 +98,13 @@ func PresignRequestV4a(cred aws.Credentials, reqData RequestData, presignData Pr if err != nil { return nil, fmt.Errorf("failed to derive assymetric key from credentials: %w", err) } - presignedURL, _, err := signer.PresignHTTP(req.Context(), creds, req, presignData.Headers[AmzContentSHA256], presignData.Service, []string{presignData.Region}, presignData.SignTime) + + payloadHash := presignData.Headers[AmzContentSHA256] + if payloadHash == "" { + payloadHash = UnsignedPayload + } + + presignedURL, _, err := signer.PresignHTTP(req.Context(), creds, req, payloadHash, presignData.Service, []string{presignData.Region}, presignData.SignTime) if err != nil { return nil, fmt.Errorf("presign: %w", err) } diff --git a/api/auth/presign_test.go b/api/auth/presign_test.go index 7b70dbf4..361a7e45 100644 --- a/api/auth/presign_test.go +++ b/api/auth/presign_test.go @@ -77,8 +77,7 @@ func TestCheckSign(t *testing.T) { Lifetime: 10 * time.Minute, SignTime: time.Now().UTC(), Headers: map[string]string{ - ContentTypeHdr: "text/plain", - AmzContentSHA256: UnsignedPayload, + ContentTypeHdr: "text/plain", }, } diff --git a/go.mod b/go.mod index e44194fb..f5d16605 100644 --- a/go.mod +++ b/go.mod @@ -9,9 +9,11 @@ require ( git.frostfs.info/TrueCloudLab/multinet v0.0.0-20241015075604-6cb0d80e0972 git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240822104152-a3bc3099bd5b git.frostfs.info/TrueCloudLab/zapjournald v0.0.0-20240124114243-cb2e66427d02 - github.com/aws/aws-sdk-go-v2 v1.30.5 + github.com/aws/aws-sdk-go-v2 v1.34.0 github.com/aws/aws-sdk-go-v2/config v1.27.32 github.com/aws/aws-sdk-go-v2/credentials v1.17.31 + github.com/aws/aws-sdk-go-v2/service/s3 v1.74.1 + github.com/aws/smithy-go v1.22.2 github.com/bluele/gcache v0.0.2 github.com/go-chi/chi/v5 v5.0.8 github.com/google/uuid v1.6.0 @@ -48,16 +50,19 @@ require ( git.frostfs.info/TrueCloudLab/tzhash v1.8.0 // indirect github.com/VictoriaMetrics/easyproto v0.1.4 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8 // 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/configsources v1.3.29 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.29 // 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/internal/v4a v1.3.29 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.10 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.10 // 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/aws/smithy-go v1.20.4 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect diff --git a/go.sum b/go.sum index 7fb8068a..5d603254 100644 --- a/go.sum +++ b/go.sum @@ -62,32 +62,42 @@ github.com/VictoriaMetrics/easyproto v0.1.4 h1:r8cNvo8o6sR4QShBXQd1bKw/VVLSQma/V github.com/VictoriaMetrics/easyproto v0.1.4/go.mod h1:QlGlzaJnDfFd8Lk6Ci/fuLxfTo3/GThPs2KH23mv710= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= -github.com/aws/aws-sdk-go-v2 v1.30.5 h1:mWSRTwQAb0aLE17dSzztCVJWI9+cRMgqebndjwDyK0g= -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.34.0 h1:9iyL+cjifckRGEVpRKZP3eIxVlL06Qk1Tk13vreaVQU= +github.com/aws/aws-sdk-go-v2 v1.34.0/go.mod h1:JgstGg0JjWU1KpVJjD5H0y0yyAIpSdKEq556EI6yOOM= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8 h1:zAxi9p3wsZMIaVCdoiQp2uZ9k1LsZvmAnoTBeZPXom0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8/go.mod h1:3XkePX5dSaxveLAYY7nsbsZZrKxCyEuE5pM4ziFxyGg= github.com/aws/aws-sdk-go-v2/config v1.27.32 h1:jnAMVTJTpAQlePCUUlnXnllHEMGVWmvUJOiGjgtS9S0= 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.31 h1:jtyfcOfgoqWA2hW/E8sFbwdfgwD3APnF9CLCKE8dTyw= github.com/aws/aws-sdk-go-v2/credentials v1.17.31/go.mod h1:RSgY5lfCfw+FoyKWtOpLolPlfQVdDBQWTUniAaE+NKY= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 h1:pfQ2sqNpMVK6xz2RbqLEL0GH87JOwSxPV2rzm8Zsb74= 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/configsources v1.3.29 h1:Ej0Rf3GMv50Qh4G4852j2djtoDb7AzQ7MuQeFHa3D70= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.29/go.mod h1:oeNTC7PwJNoM5AznVr23wxhLnuJv0ZDe5v7w0wqIs9M= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.29 h1:6e8a71X+9GfghragVevC5bZqvATtc3mAMgxpSNbgzF0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.29/go.mod h1:c4jkZiQ+BWpNqq7VtrxjwISrLrt/VvPq3XiopkUIolI= 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/internal/v4a v1.3.29 h1:g9OUETuxA8i/Www5Cby0R3WSTe7ppFTZXHVLNskNS4w= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.29/go.mod h1:CQk+koLR1QeY1+vm7lqNfFii07DEderKq6T3F1L2pyc= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.3 h1:EP1ITDgYVPM2dL1bBBntJ7AW5yTjuWGz9XO+CZwpALU= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.3/go.mod h1:5lWNWeAgWenJ/BZ/CP9k9DjLbC0pjnM045WjXRPPi14= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.10 h1:hN4yJBGswmFTOVYqmbz1GBs9ZMtQe8SrYxPwrkrlRv8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.10/go.mod h1:TsxON4fEZXyrKY+D+3d2gSTyJkGORexIYab9PTf56DA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.10 h1:fXoWC2gi7tdJYNTPnnlSGzEVwewUchOi8xVq/dkg8Qs= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.10/go.mod h1:cvzBApD5dVazHU8C2rbBQzzzsKc8m5+wNJ9mCRZLKPc= +github.com/aws/aws-sdk-go-v2/service/s3 v1.74.1 h1:9LawY3cDJ3HE+v2GMd5SOkNLDwgN4K7TsCjyVBYu/L4= +github.com/aws/aws-sdk-go-v2/service/s3 v1.74.1/go.mod h1:hHnELVnIHltd8EOF3YzahVX6F6y2C6dNqpRj1IMkS5I= 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/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= +github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= 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/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c=