diff --git a/.changelog/5702f383099a47a5860f5ab25cda67a1.json b/.changelog/5702f383099a47a5860f5ab25cda67a1.json new file mode 100644 index 00000000000..8b452bf4490 --- /dev/null +++ b/.changelog/5702f383099a47a5860f5ab25cda67a1.json @@ -0,0 +1,8 @@ +{ + "id": "5702f383-099a-47a5-860f-5ab25cda67a1", + "type": "bugfix", + "description": "Update presign post URL resolution to use the exact result from EndpointResolverV2", + "modules": [ + "service/s3" + ] +} \ No newline at end of file diff --git a/codegen/smithy-aws-go-codegen/src/main/java/software/amazon/smithy/aws/go/codegen/customization/s3/StoreResolvedUri.java b/codegen/smithy-aws-go-codegen/src/main/java/software/amazon/smithy/aws/go/codegen/customization/s3/StoreResolvedUri.java new file mode 100644 index 00000000000..341349a8aef --- /dev/null +++ b/codegen/smithy-aws-go-codegen/src/main/java/software/amazon/smithy/aws/go/codegen/customization/s3/StoreResolvedUri.java @@ -0,0 +1,42 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.aws.go.codegen.customization.s3; + +import software.amazon.smithy.aws.go.codegen.AwsGoDependency; +import software.amazon.smithy.aws.go.codegen.customization.S3ModelUtils; +import software.amazon.smithy.go.codegen.GoSettings; +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.integration.GoIntegration; +import software.amazon.smithy.model.Model; + +import java.util.Map; + +/** + * Stores the endpoint resolved by EndpointResolverV2 + */ +public class StoreResolvedUri implements GoIntegration { + @Override + public void renderPostEndpointResolutionHook(GoSettings settings, GoWriter writer, Model model) { + if (!S3ModelUtils.isServiceS3(model, settings.getService(model))) return; + writer.writeGoTemplate( + """ + ctx = $setFunc:L(ctx, endpt.URI.String()) + """, + Map.of( + "setFunc", "setS3ResolvedURI") + ); + } +} diff --git a/codegen/smithy-aws-go-codegen/src/main/resources/META-INF/services/software.amazon.smithy.go.codegen.integration.GoIntegration b/codegen/smithy-aws-go-codegen/src/main/resources/META-INF/services/software.amazon.smithy.go.codegen.integration.GoIntegration index 7c2810d24f8..193e594ce79 100644 --- a/codegen/smithy-aws-go-codegen/src/main/resources/META-INF/services/software.amazon.smithy.go.codegen.integration.GoIntegration +++ b/codegen/smithy-aws-go-codegen/src/main/resources/META-INF/services/software.amazon.smithy.go.codegen.integration.GoIntegration @@ -74,6 +74,7 @@ software.amazon.smithy.aws.go.codegen.customization.RemoveDefaults software.amazon.smithy.aws.go.codegen.customization.auth.S3ExpressAuthScheme software.amazon.smithy.aws.go.codegen.customization.S3BucketContext software.amazon.smithy.aws.go.codegen.customization.s3.ExpressDefaultChecksum +software.amazon.smithy.aws.go.codegen.customization.s3.StoreResolvedUri software.amazon.smithy.aws.go.codegen.customization.auth.GlobalAnonymousOption software.amazon.smithy.aws.go.codegen.customization.CloudFrontKVSSigV4a software.amazon.smithy.aws.go.codegen.customization.BackfillProtocolTestServiceTrait diff --git a/service/s3/endpoints.go b/service/s3/endpoints.go index 361e4201b94..6ff0cc6ed40 100644 --- a/service/s3/endpoints.go +++ b/service/s3/endpoints.go @@ -5849,6 +5849,8 @@ func (m *resolveEndpointV2Middleware) HandleFinalize(ctx context.Context, in mid rscheme.SignerProperties.SetAll(&o.SignerProperties) } + ctx = setS3ResolvedURI(ctx, endpt.URI.String()) + backend := s3cust.GetPropertiesBackend(&endpt.Properties) ctx = internalcontext.SetS3Backend(ctx, backend) diff --git a/service/s3/presign_post.go b/service/s3/presign_post.go index 6bdbcde6687..491ed2e5da5 100644 --- a/service/s3/presign_post.go +++ b/service/s3/presign_post.go @@ -8,7 +8,6 @@ import ( "encoding/hex" "encoding/json" "fmt" - "net/url" "strings" "time" @@ -211,10 +210,6 @@ func (s *presignPostRequestMiddleware) HandleFinalize( ) ( out middleware.FinalizeOutput, metadata middleware.Metadata, err error, ) { - req, ok := in.Request.(*smithyhttp.Request) - if !ok { - return out, metadata, fmt.Errorf("unexpected request middleware type %T", in.Request) - } input := getOperationInput(ctx) asS3Put, ok := input.(*PutObjectInput) @@ -230,8 +225,7 @@ func (s *presignPostRequestMiddleware) HandleFinalize( return out, metadata, fmt.Errorf("PutObject input does not have a key input") } - httpReq := req.Build(ctx) - u := httpReq.URL.String() + uri := getS3ResolvedURI(ctx) signingName := awsmiddleware.GetSigningName(ctx) signingRegion := awsmiddleware.GetSigningRegion(ctx) @@ -265,22 +259,14 @@ func (s *presignPostRequestMiddleware) HandleFinalize( } } - // Other middlewares may set default values on the URL on the path or as query params. Remove them - baseURL := toBaseURL(u) - out.Result = &PresignedPostRequest{ - URL: baseURL, + URL: uri, Values: fields, } return out, metadata, nil } -func toBaseURL(fullURL string) string { - a, _ := url.Parse(fullURL) - return a.Scheme + "://" + a.Host -} - // Adapted from existing PresignConverter middleware func (c presignPostConverter) ConvertToPresignMiddleware(stack *middleware.Stack, options Options) (err error) { stack.Build.Remove("UserAgent") diff --git a/service/s3/presign_post_test.go b/service/s3/presign_post_test.go index baf50a2aaf8..afafc684edb 100644 --- a/service/s3/presign_post_test.go +++ b/service/s3/presign_post_test.go @@ -19,11 +19,13 @@ func TestPresignPutObject(t *testing.T) { defer mockTime(fixedTime)() cases := map[string]struct { - input PutObjectInput - options []func(*PresignPostOptions) - expectedExpires time.Time - expectedURL string - region string + input PutObjectInput + options []func(*PresignPostOptions) + expectedExpires time.Time + expectedURL string + region string + pathStyleEnabled bool + BaseEndpoint string }{ "sample": { input: PutObjectInput{ @@ -31,6 +33,12 @@ func TestPresignPutObject(t *testing.T) { Key: aws.String("key"), }, }, + "bucket and key have the same value": { + input: PutObjectInput{ + Bucket: aws.String("bucket"), + Key: aws.String("bucket"), + }, + }, "expires override": { input: PutObjectInput{ Bucket: aws.String("bucket"), @@ -66,6 +74,40 @@ func TestPresignPutObject(t *testing.T) { }, expectedURL: "https://mfzwi23gnjvgw.mrap.accesspoint.s3-global.amazonaws.com", }, + "use path style bucket hosting pattern": { + input: PutObjectInput{ + Bucket: aws.String("bucket"), + Key: aws.String("key"), + }, + expectedURL: "https://s3.us-west-2.amazonaws.com/bucket", + pathStyleEnabled: true, + }, + "use path style bucket and key have the same value ": { + input: PutObjectInput{ + Bucket: aws.String("value"), + Key: aws.String("value"), + }, + expectedURL: "https://s3.us-west-2.amazonaws.com/value", + pathStyleEnabled: true, + }, + "use path style bucket with custom baseEndpoint": { + input: PutObjectInput{ + Bucket: aws.String("bucket"), + Key: aws.String("key"), + }, + expectedURL: "https://s3.custom-domain.com/bucket", + pathStyleEnabled: true, + BaseEndpoint: "https://s3.custom-domain.com", + }, + "use path style bucket with custom baseEndpoint with path": { + input: PutObjectInput{ + Bucket: aws.String("bucket"), + Key: aws.String("key"), + }, + BaseEndpoint: "https://my-custom-domain.com/path_my_path", + pathStyleEnabled: true, + expectedURL: "https://my-custom-domain.com/path_my_path/bucket", + }, } for name, tc := range cases { t.Run(name, func(t *testing.T) { @@ -81,8 +123,12 @@ func TestPresignPutObject(t *testing.T) { return aws.NopRetryer{} }, } - - presignClient := NewPresignClient(NewFromConfig(cfg)) + presignClient := NewPresignClient(NewFromConfig(cfg, func(options *Options) { + options.UsePathStyle = tc.pathStyleEnabled + if tc.BaseEndpoint != "" { + options.BaseEndpoint = aws.String(tc.BaseEndpoint) + } + })) postObject, err := presignClient.PresignPostObject(ctx, &tc.input, tc.options...) if err != nil { t.Error(err) diff --git a/service/s3/uri_context.go b/service/s3/uri_context.go new file mode 100644 index 00000000000..0e664c59ce0 --- /dev/null +++ b/service/s3/uri_context.go @@ -0,0 +1,23 @@ +package s3 + +// This contains helper methods to set resolver URI into the context object. If they are ever used for +// something other than S3, they should be moved to internal/context/context.go + +import ( + "context" + + "github.com/aws/smithy-go/middleware" +) + +type s3resolvedURI struct{} + +// setS3ResolvedURI sets the URI as resolved by the EndpointResolverV2 +func setS3ResolvedURI(ctx context.Context, value string) context.Context { + return middleware.WithStackValue(ctx, s3resolvedURI{}, value) +} + +// getS3ResolvedURI gets the URI as resolved by EndpointResolverV2 +func getS3ResolvedURI(ctx context.Context) string { + v, _ := middleware.GetStackValue(ctx, s3resolvedURI{}).(string) + return v +}