Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/pkg/cli/client/byoc/aws/byoc.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"
"io"
"iter"
"net/url"
"os"
"path/filepath"
"strconv"
Expand Down Expand Up @@ -202,6 +203,23 @@ func (b *ByocAws) Preview(ctx context.Context, req *client.DeployRequest) (*clie
return b.deploy(ctx, req, "preview")
}

// s3PayloadURI rewrites a virtual-hosted-style S3 URL (bucket.s3.region.amazonaws.com/key)
// into s3://bucket/key. cd's fetchPayload only authenticates s3:// URIs, via the deploy
// container's ambient AWS credentials; a bare https:// GET is unsigned, so a private bucket
// 403s it. Mirrors the gs:// rewrite in gcp/byoc.go for the same reason. Non-S3 URLs (or ones
// that fail to parse) pass through unchanged.
func s3PayloadURI(rawURL string) string {
u, err := url.Parse(rawURL)
if err != nil {
return rawURL
}
bucket, rest, ok := strings.Cut(u.Host, ".s3.")
if !ok || !strings.HasSuffix(rest, "amazonaws.com") {
return rawURL
}
return "s3://" + bucket + u.Path
}

func (b *ByocAws) deploy(ctx context.Context, req *client.DeployRequest, cmd string) (*client.DeployResponse, error) {
cfg, err := b.driver.LoadConfig(ctx)
if err != nil {
Expand Down Expand Up @@ -262,7 +280,7 @@ func (b *ByocAws) deploy(ctx context.Context, req *client.DeployRequest, cmd str
if resp.StatusCode != 200 {
return nil, fmt.Errorf("unexpected status code during upload: %s", resp.Status)
}
payloadString = http.RemoveQueryParam(payloadUrl)
payloadString = s3PayloadURI(http.RemoveQueryParam(payloadUrl))
}

cdCmd := cdCommand{
Expand Down
35 changes: 35 additions & 0 deletions src/pkg/cli/client/byoc/aws/byoc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -737,3 +737,38 @@ func TestDeriveBuildID(t *testing.T) {
})
}
}

func TestS3PayloadURI(t *testing.T) {
tests := []struct {
name string
in string
want string
}{
{
name: "regional virtual-hosted-style URL rewritten to s3 scheme",
in: "https://defang-cd-bucket-cybpbzz8hzm7.s3.us-west-2.amazonaws.com/uploads/t7jl0wwq4cz9",
want: "s3://defang-cd-bucket-cybpbzz8hzm7/uploads/t7jl0wwq4cz9",
},
{
name: "legacy virtual-hosted-style URL (no region) rewritten to s3 scheme",
in: "https://defang-cd-bucket-cybpbzz8hzm7.s3.amazonaws.com/uploads/t7jl0wwq4cz9",
want: "s3://defang-cd-bucket-cybpbzz8hzm7/uploads/t7jl0wwq4cz9",
},
{
name: "non-S3 URL passes through unchanged",
in: "https://example.com/uploads/t7jl0wwq4cz9",
want: "https://example.com/uploads/t7jl0wwq4cz9",
},
{
name: "base64 payload passes through unchanged",
in: "cGF5bG9hZA==",
want: "cGF5bG9hZA==",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, s3PayloadURI(tt.in))
})
}
}
Loading