Merge pull request #1679 from versity/sis/unsigned-streaming-upload-not-allowed

fix: rejects STREAMING-UNSIGNED-PAYLOAD-TRAILER for all actions, except for PutObject and UploadPart
This commit is contained in:
Ben McClelland
2025-12-08 18:08:46 -08:00
committed by GitHub
4 changed files with 54 additions and 0 deletions

View File

@@ -115,6 +115,11 @@ func VerifyV4Signature(root RootUserConfig, iam auth.IAMService, region string,
if !utils.IsValidSh256PayloadHeader(hashPayload) { if !utils.IsValidSh256PayloadHeader(hashPayload) {
return s3err.GetAPIError(s3err.ErrInvalidSHA256Paylod) return s3err.GetAPIError(s3err.ErrInvalidSHA256Paylod)
} }
// the streaming payload type is allowed only in PutObject and UploadPart
// e.g. STREAMING-UNSIGNED-PAYLOAD-TRAILER
if !streamBody && utils.IsStreamingPayload(hashPayload) {
return s3err.GetAPIError(s3err.ErrInvalidSHA256PayloadUsage)
}
if streamBody { if streamBody {
// for streaming PUT actions, authorization is deferred // for streaming PUT actions, authorization is deferred
// until end of stream due to need to get length and // until end of stream due to need to get length and

View File

@@ -119,6 +119,7 @@ const (
ErrSignatureDoesNotMatch ErrSignatureDoesNotMatch
ErrContentSHA256Mismatch ErrContentSHA256Mismatch
ErrInvalidSHA256Paylod ErrInvalidSHA256Paylod
ErrInvalidSHA256PayloadUsage
ErrUnsupportedAnonymousSignedStreaming ErrUnsupportedAnonymousSignedStreaming
ErrMissingContentLength ErrMissingContentLength
ErrContentLengthMismatch ErrContentLengthMismatch
@@ -511,6 +512,11 @@ var errorCodeResponse = map[ErrorCode]APIError{
Description: "x-amz-content-sha256 must be UNSIGNED-PAYLOAD, STREAMING-UNSIGNED-PAYLOAD-TRAILER, STREAMING-AWS4-HMAC-SHA256-PAYLOAD, STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER, STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD, STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER or a valid sha256 value.", Description: "x-amz-content-sha256 must be UNSIGNED-PAYLOAD, STREAMING-UNSIGNED-PAYLOAD-TRAILER, STREAMING-AWS4-HMAC-SHA256-PAYLOAD, STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER, STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD, STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER or a valid sha256 value.",
HTTPStatusCode: http.StatusBadRequest, HTTPStatusCode: http.StatusBadRequest,
}, },
ErrInvalidSHA256PayloadUsage: {
Code: "InvalidRequest",
Description: "The value of x-amz-content-sha256 header is invalid.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrUnsupportedAnonymousSignedStreaming: { ErrUnsupportedAnonymousSignedStreaming: {
Code: "InvalidRequest", Code: "InvalidRequest",
Description: "Anonymous requests don't support this x-amz-content-sha256 value. Please use UNSIGNED-PAYLOAD or STREAMING-UNSIGNED-PAYLOAD-TRAILER.", Description: "Anonymous requests don't support this x-amz-content-sha256 value. Please use UNSIGNED-PAYLOAD or STREAMING-UNSIGNED-PAYLOAD-TRAILER.",

View File

@@ -1110,6 +1110,7 @@ func TestUnsignedStreaminPayloadTrailer(ts *TestState) {
ts.Run(UnsignedStreamingPayloadTrailer_UploadPart_no_trailer_full_object) ts.Run(UnsignedStreamingPayloadTrailer_UploadPart_no_trailer_full_object)
ts.Run(UnsignedStreamingPayloadTrailer_UploadPart_trailer_and_mp_algo_mismatch) ts.Run(UnsignedStreamingPayloadTrailer_UploadPart_trailer_and_mp_algo_mismatch)
ts.Run(UnsignedStreamingPayloadTrailer_UploadPart_success_with_trailer) ts.Run(UnsignedStreamingPayloadTrailer_UploadPart_success_with_trailer)
ts.Run(UnsignedStreamingPayloadTrailer_not_allowed)
} }
} }
@@ -1765,5 +1766,6 @@ func GetIntTests() IntTests {
"UnsignedStreamingPayloadTrailer_UploadPart_no_trailer_full_object": UnsignedStreamingPayloadTrailer_UploadPart_no_trailer_full_object, "UnsignedStreamingPayloadTrailer_UploadPart_no_trailer_full_object": UnsignedStreamingPayloadTrailer_UploadPart_no_trailer_full_object,
"UnsignedStreamingPayloadTrailer_UploadPart_trailer_and_mp_algo_mismatch": UnsignedStreamingPayloadTrailer_UploadPart_trailer_and_mp_algo_mismatch, "UnsignedStreamingPayloadTrailer_UploadPart_trailer_and_mp_algo_mismatch": UnsignedStreamingPayloadTrailer_UploadPart_trailer_and_mp_algo_mismatch,
"UnsignedStreamingPayloadTrailer_UploadPart_success_with_trailer": UnsignedStreamingPayloadTrailer_UploadPart_success_with_trailer, "UnsignedStreamingPayloadTrailer_UploadPart_success_with_trailer": UnsignedStreamingPayloadTrailer_UploadPart_success_with_trailer,
"UnsignedStreamingPayloadTrailer_not_allowed": UnsignedStreamingPayloadTrailer_not_allowed,
} }
} }

View File

@@ -2,7 +2,9 @@ package integration
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"net/http"
"strings" "strings"
"github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3"
@@ -549,3 +551,42 @@ func UnsignedStreamingPayloadTrailer_UploadPart_success_with_trailer(s *S3Conf)
return nil return nil
}) })
} }
func UnsignedStreamingPayloadTrailer_not_allowed(s *S3Conf) error {
testName := "UnsignedStreamingPayloadTrailer_not_allowed"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
// doesn't matter what data is sent in the body
body := []byte("5\r\nabcde\r\n0\r\n\r\n")
// tests a couple of bucket PUT actions, where
// STREAMING-UNSIGNED-PAYLOAD-TRAILER is not allowed
for i, query := range []string{
"cors", // PutBucketCors
"tagging", // PutBucketTagging
"object-lock", // PutObjectLockConfiguration
"ownershipControls", // PutBucketOwnership
"versioning", // PutBucketVersioning
"policy", // PutBucketPolicy
} {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
req, err := http.NewRequestWithContext(ctx, http.MethodPut, fmt.Sprintf("%s/%s?%s", s.endpoint, bucket, query), bytes.NewReader(body))
if err != nil {
cancel()
return fmt.Errorf("test %v failed: %w", i+1, err)
}
req.Header.Set("x-amz-content-sha256", "STREAMING-UNSIGNED-PAYLOAD-TRAILER")
req.Header.Set("x-amz-decoded-content-length", "5")
_, apiErr, err := sendSignedRequest(s, req, cancel)
if err != nil {
return fmt.Errorf("test %v failed: %w", i+1, err)
}
if err := compareS3ApiError(s3err.GetAPIError(s3err.ErrInvalidSHA256PayloadUsage), apiErr); err != nil {
return fmt.Errorf("test %v failed: %w", i+1, err)
}
}
return nil
})
}