diff --git a/service/internal/integrationtest/s3/presign_test.go b/service/internal/integrationtest/s3/presign_test.go index 76c90b42d1d..9aa8e4483d2 100644 --- a/service/internal/integrationtest/s3/presign_test.go +++ b/service/internal/integrationtest/s3/presign_test.go @@ -5,49 +5,128 @@ package s3 import ( "bytes" "context" + "fmt" + "io" "io/ioutil" "net/http" "strconv" "testing" "time" - "github.com/aws/aws-sdk-go-v2/aws" + v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" "github.com/aws/aws-sdk-go-v2/service/internal/integrationtest" "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/google/go-cmp/cmp" ) -func TestInteg_PresignURL_PutObject(t *testing.T) { - key := integrationtest.UniqueID() - - ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) - defer cancelFn() - - cfg, err := integrationtest.LoadConfigWithDefaultRegion("us-west-2") - if err != nil { - t.Fatalf("failed to load config, %v", err) +func TestInteg_PresignURL(t *testing.T) { + cases := map[string]struct { + body io.Reader + expires time.Duration + sha256Header string + expectedSignedHeader http.Header + }{ + "standard": { + body: bytes.NewReader([]byte("Hello-world")), + expectedSignedHeader: http.Header{ + "content-type": {"application/octet-stream"}, + "content-length": {"11"}, + }, + }, + "nil-body": { + expectedSignedHeader: http.Header{}, + }, + // TODO: fix PutObject with an empty body returning 501 + // "empty-body": { + // body: bytes.NewReader([]byte("")), + // expectedSignedHeader: http.Header{ + // "content-type": {"application/octet-stream"}, + // }, + // }, } - client := s3.NewFromConfig(cfg) - - params := &s3.PutObjectInput{ - Bucket: &setupMetadata.Buckets.Source.Name, - Key: aws.String(key), - Body: bytes.NewReader([]byte(`Hello-world`)), - } - - presignerClient := s3.NewPresignClient(client, func(options *s3.PresignOptions) { - options.Expires = 600 * time.Second - }) - - presignRequest, err := presignerClient.PresignPutObject(ctx, params) - if err != nil { - t.Errorf("expect no error, got %v", err) + for name, c := range cases { + t.Run(name, func(t *testing.T) { + key := integrationtest.UniqueID() + + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + cfg, err := integrationtest.LoadConfigWithDefaultRegion("us-west-2") + if err != nil { + t.Fatalf("failed to load config, %v", err) + } + + client := s3.NewFromConfig(cfg) + + // construct a put object + putObjectInput := &s3.PutObjectInput{ + Bucket: &setupMetadata.Buckets.Source.Name, + Key: &key, + Body: c.body, + } + + presignerClient := s3.NewPresignClient(client, func(options *s3.PresignOptions) { + options.Expires = 600 * time.Second + }) + + presignRequest, err := presignerClient.PresignPutObject(ctx, putObjectInput) + if err != nil { + t.Errorf("expect no error, got %v", err) + } + + for k, v := range c.expectedSignedHeader { + value := presignRequest.SignedHeader[k] + if len(value) == 0 { + t.Fatalf("expected %v header to be present in presigned url, got %v", k, presignRequest.SignedHeader) + } + + if diff := cmp.Diff(v, value); len(diff) != 0 { + t.Fatalf("expected %v header value to be %v got %v", k, v, value) + } + } + + resp, err := sendHTTPRequest(presignRequest, putObjectInput.Body) + if err != nil { + t.Errorf("expect no error while sending HTTP request using presigned url, got %v", err) + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Fatalf("failed to put S3 object, %d:%s", resp.StatusCode, resp.Status) + } + + // construct a get object + getObjectInput := &s3.GetObjectInput{ + Bucket: &setupMetadata.Buckets.Source.Name, + Key: &key, + } + + presignRequest, err = presignerClient.PresignGetObject(ctx, getObjectInput) + if err != nil { + t.Errorf("expect no error, got %v", err) + } + + resp, err = sendHTTPRequest(presignRequest, nil) + if err != nil { + t.Errorf("expect no error while sending HTTP request using presigned url, got %v", err) + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Fatalf("failed to get S3 object, %d:%s", resp.StatusCode, resp.Status) + } + }) } +} +func sendHTTPRequest(presignRequest *v4.PresignedHTTPRequest, body io.Reader) (*http.Response, error) { // create a http request req, err := http.NewRequest(presignRequest.Method, presignRequest.URL, nil) if err != nil { - t.Fatalf("failed to build presigned request, %v", err) + return nil, fmt.Errorf("failed to build presigned request, %v", err) } // assign the signed headers onto the http request @@ -65,19 +144,11 @@ func TestInteg_PresignURL_PutObject(t *testing.T) { } // assign the request body if not nil - if params.Body != nil { - req.Body = ioutil.NopCloser(params.Body) + if body != nil { + req.Body = ioutil.NopCloser(body) } // Upload the object to S3. resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("failed to do PUT request, %v", err) - } - - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - t.Fatalf("failed to put S3 object, %d:%s", resp.StatusCode, resp.Status) - } + return resp, err } diff --git a/service/s3/internal/customizations/presign_test.go b/service/s3/internal/customizations/presign_test.go index d64175c87ce..f67df2f5d5e 100644 --- a/service/s3/internal/customizations/presign_test.go +++ b/service/s3/internal/customizations/presign_test.go @@ -16,13 +16,13 @@ import ( func TestPutObject_PresignURL(t *testing.T) { cases := map[string]struct { - input s3.PutObjectInput - options s3.PresignOptions - expectPresignedURLHost string - expectedRequestURILabel []string - expectSignedHeader http.Header - expectMethod string - expectError string + input s3.PutObjectInput + options s3.PresignOptions + expectPresignedURLHost string + expectRequestURIQuery []string + expectSignedHeader http.Header + expectMethod string + expectError string }{ "standard case": { input: s3.PutObjectInput{ @@ -31,7 +31,7 @@ func TestPutObject_PresignURL(t *testing.T) { Body: strings.NewReader("hello-world"), }, expectPresignedURLHost: "https://mock-bucket.s3.us-west-2.amazonaws.com/mockkey?", - expectedRequestURILabel: []string{ + expectRequestURIQuery: []string{ "X-Amz-Expires=900", "X-Amz-Credential", "X-Amz-Date", @@ -52,7 +52,7 @@ func TestPutObject_PresignURL(t *testing.T) { Body: bytes.NewReader([]byte("hello-world")), }, expectPresignedURLHost: "https://mock-bucket.s3.us-west-2.amazonaws.com/mockkey?", - expectedRequestURILabel: []string{ + expectRequestURIQuery: []string{ "X-Amz-Expires=900", "X-Amz-Credential", "X-Amz-Date", @@ -74,7 +74,7 @@ func TestPutObject_PresignURL(t *testing.T) { Body: bytes.NewBuffer([]byte(`hello-world`)), }, expectPresignedURLHost: "https://mock-bucket.s3.us-west-2.amazonaws.com/mockkey?", - expectedRequestURILabel: []string{ + expectRequestURIQuery: []string{ "X-Amz-Expires=900", "X-Amz-Credential", "X-Amz-Date", @@ -95,7 +95,7 @@ func TestPutObject_PresignURL(t *testing.T) { Body: bytes.NewReader([]byte(``)), }, expectPresignedURLHost: "https://mock-bucket.s3.us-west-2.amazonaws.com/mockkey?", - expectedRequestURILabel: []string{ + expectRequestURIQuery: []string{ "X-Amz-Expires=900", "X-Amz-Credential", "X-Amz-Date", @@ -114,7 +114,7 @@ func TestPutObject_PresignURL(t *testing.T) { Key: aws.String("mockkey"), }, expectPresignedURLHost: "https://mock-bucket.s3.us-west-2.amazonaws.com/mockkey?", - expectedRequestURILabel: []string{ + expectRequestURIQuery: []string{ "X-Amz-Expires=900", "X-Amz-Credential", "X-Amz-Date", @@ -159,8 +159,8 @@ func TestPutObject_PresignURL(t *testing.T) { t.Fatalf("expected presigned url to contain host %s, got %s", e, a) } - if len(c.expectedRequestURILabel) != 0 { - for _, label := range c.expectedRequestURILabel { + if len(c.expectRequestURIQuery) != 0 { + for _, label := range c.expectRequestURIQuery { if e, a := label, req.URL; !strings.Contains(a, e) { t.Fatalf("expected presigned url to contain %v label in url: %v", label, req.URL) }