Skip to content

Commit

Permalink
feat(storage): respect WithEndpoint for SignedURLs and PostPolicy (#8113
Browse files Browse the repository at this point in the history
)

Clients initiated with a non default endpoint will use that endpoint for SignedURLs and Post Policies.
  • Loading branch information
BrennaEpp authored Jun 27, 2023
1 parent 1a03107 commit f918f23
Show file tree
Hide file tree
Showing 7 changed files with 370 additions and 18 deletions.
24 changes: 18 additions & 6 deletions storage/bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,18 @@ func (b *BucketHandle) Update(ctx context.Context, uattrs BucketAttrsToUpdate) (
// [Overview of access control]: https://cloud.google.com/storage/docs/accesscontrol#signed_urls_query_string_authentication
// [automatic detection of credentials]: https://pkg.go.dev/cloud.google.com/go/storage#hdr-Credential_requirements_for_signing
func (b *BucketHandle) SignedURL(object string, opts *SignedURLOptions) (string, error) {
if opts.GoogleAccessID != "" && (opts.SignBytes != nil || len(opts.PrivateKey) > 0) {
return SignedURL(b.name, object, opts)
}
// Make a copy of opts so we don't modify the pointer parameter.
newopts := opts.clone()

if newopts.Hostname == "" {
// Extract the correct host from the readhost set on the client
newopts.Hostname = b.c.readHost
}

if opts.GoogleAccessID != "" && (opts.SignBytes != nil || len(opts.PrivateKey) > 0) {
return SignedURL(b.name, object, newopts)
}

if newopts.GoogleAccessID == "" {
id, err := b.detectDefaultGoogleAccessID()
if err != nil {
Expand Down Expand Up @@ -215,12 +221,18 @@ func (b *BucketHandle) SignedURL(object string, opts *SignedURLOptions) (string,
//
// [automatic detection of credentials]: https://pkg.go.dev/cloud.google.com/go/storage#hdr-Credential_requirements_for_signing
func (b *BucketHandle) GenerateSignedPostPolicyV4(object string, opts *PostPolicyV4Options) (*PostPolicyV4, error) {
if opts.GoogleAccessID != "" && (opts.SignRawBytes != nil || opts.SignBytes != nil || len(opts.PrivateKey) > 0) {
return GenerateSignedPostPolicyV4(b.name, object, opts)
}
// Make a copy of opts so we don't modify the pointer parameter.
newopts := opts.clone()

if newopts.Hostname == "" {
// Extract the correct host from the readhost set on the client
newopts.Hostname = b.c.readHost
}

if opts.GoogleAccessID != "" && (opts.SignRawBytes != nil || opts.SignBytes != nil || len(opts.PrivateKey) > 0) {
return GenerateSignedPostPolicyV4(b.name, object, newopts)
}

if newopts.GoogleAccessID == "" {
id, err := b.detectDefaultGoogleAccessID()
if err != nil {
Expand Down
319 changes: 319 additions & 0 deletions storage/bucket_test.go

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions storage/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4810,11 +4810,9 @@ func TestIntegration_SignedURL_WithCreds(t *testing.T) {
t.Fatalf("problem with the signed URL: %v", err)
}
}, option.WithCredentials(creds))

}

func TestIntegration_SignedURL_DefaultSignBytes(t *testing.T) {

ctx := context.Background()

// Create another client to test the sign byte function as well
Expand Down
9 changes: 8 additions & 1 deletion storage/post_policy_v4.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ type PostPolicyV4Options struct {
// Optional.
Conditions []PostPolicyV4Condition

// Hostname sets the host of the signed post policy. This field overrides
// any endpoint set on a storage Client or through STORAGE_EMULATOR_HOST.
// Only compatible with PathStyle URLStyle.
// Optional.
Hostname string

shouldHashSignBytes bool
}

Expand All @@ -128,6 +134,7 @@ func (opts *PostPolicyV4Options) clone() *PostPolicyV4Options {
Fields: opts.Fields,
Conditions: opts.Conditions,
shouldHashSignBytes: opts.shouldHashSignBytes,
Hostname: opts.Hostname,
}
}

Expand Down Expand Up @@ -370,7 +377,7 @@ func GenerateSignedPostPolicyV4(bucket, object string, opts *PostPolicyV4Options
u := &url.URL{
Path: path,
RawPath: pathEncodeV4(path),
Host: opts.Style.host(bucket),
Host: opts.Style.host(opts.Hostname, bucket),
Scheme: scheme,
}

Expand Down
1 change: 1 addition & 0 deletions storage/post_policy_v4_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func TestPostPolicyV4OptionsClone(t *testing.T) {
Fields: &PolicyV4Fields{ACL: "test-acl"},
Conditions: []PostPolicyV4Condition{},
shouldHashSignBytes: true,
Hostname: "localhost:9000",
}

// Check that all fields are set to a non-zero value, so we can check that
Expand Down
30 changes: 22 additions & 8 deletions storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,13 +262,13 @@ const (
SigningSchemeV4
)

// URLStyle determines the style to use for the signed URL. pathStyle is the
// URLStyle determines the style to use for the signed URL. PathStyle is the
// default. All non-default options work with V4 scheme only. See
// https://cloud.google.com/storage/docs/request-endpoints for details.
type URLStyle interface {
// host should return the host portion of the signed URL, not including
// the scheme (e.g. storage.googleapis.com).
host(bucket string) string
host(hostname, bucket string) string

// path should return the path portion of the signed URL, which may include
// both the bucket and object name or only the object name depending on the
Expand All @@ -284,23 +284,27 @@ type bucketBoundHostname struct {
hostname string
}

func (s pathStyle) host(bucket string) string {
func (s pathStyle) host(hostname, bucket string) string {
if hostname != "" {
return stripScheme(hostname)
}

if host := os.Getenv("STORAGE_EMULATOR_HOST"); host != "" {
return stripScheme(host)
}

return "storage.googleapis.com"
}

func (s virtualHostedStyle) host(bucket string) string {
func (s virtualHostedStyle) host(_, bucket string) string {
if host := os.Getenv("STORAGE_EMULATOR_HOST"); host != "" {
return bucket + "." + stripScheme(host)
}

return bucket + ".storage.googleapis.com"
}

func (s bucketBoundHostname) host(bucket string) string {
func (s bucketBoundHostname) host(_, bucket string) string {
return s.hostname
}

Expand All @@ -321,7 +325,10 @@ func (s bucketBoundHostname) path(bucket, object string) string {
}

// PathStyle is the default style, and will generate a URL of the form
// "storage.googleapis.com/<bucket-name>/<object-name>".
// "<host-name>/<bucket-name>/<object-name>". By default, <host-name> is
// storage.googleapis.com, but setting an endpoint on the storage Client or
// through STORAGE_EMULATOR_HOST overrides this. Setting Hostname on
// SignedURLOptions or PostPolicyV4Options overrides everything else.
func PathStyle() URLStyle {
return pathStyle{}
}
Expand Down Expand Up @@ -442,6 +449,12 @@ type SignedURLOptions struct {
// Scheme determines the version of URL signing to use. Default is
// SigningSchemeV2.
Scheme SigningScheme

// Hostname sets the host of the signed URL. This field overrides any
// endpoint set on a storage Client or through STORAGE_EMULATOR_HOST.
// Only compatible with PathStyle URLStyle.
// Optional.
Hostname string
}

func (opts *SignedURLOptions) clone() *SignedURLOptions {
Expand All @@ -458,6 +471,7 @@ func (opts *SignedURLOptions) clone() *SignedURLOptions {
Style: opts.Style,
Insecure: opts.Insecure,
Scheme: opts.Scheme,
Hostname: opts.Hostname,
}
}

Expand Down Expand Up @@ -716,7 +730,7 @@ func signedURLV4(bucket, name string, opts *SignedURLOptions, now time.Time) (st
fmt.Fprintf(buf, "%s\n", escapedQuery)

// Fill in the hostname based on the desired URL style.
u.Host = opts.Style.host(bucket)
u.Host = opts.Style.host(opts.Hostname, bucket)

// Fill in the URL scheme.
if opts.Insecure {
Expand Down Expand Up @@ -850,7 +864,7 @@ func signedURLV2(bucket, name string, opts *SignedURLOptions) (string, error) {
}
encoded := base64.StdEncoding.EncodeToString(b)
u.Scheme = "https"
u.Host = PathStyle().host(bucket)
u.Host = PathStyle().host(opts.Hostname, bucket)
q := u.Query()
q.Set("GoogleAccessId", opts.GoogleAccessID)
q.Set("Expires", fmt.Sprintf("%d", opts.Expires.Unix()))
Expand Down
3 changes: 2 additions & 1 deletion storage/storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2337,6 +2337,7 @@ func TestSignedURLOptionsClone(t *testing.T) {
Style: VirtualHostedStyle(),
Insecure: true,
Scheme: SigningSchemeV2,
Hostname: "localhost:8000",
}

// Check that all fields are set to a non-zero value, so we can check that
Expand All @@ -2360,7 +2361,7 @@ func TestSignedURLOptionsClone(t *testing.T) {
return reflect.ValueOf(a) == reflect.ValueOf(b)
}

if diff := cmp.Diff(opts, optsClone, cmp.Comparer(signBytesComp)); diff != "" {
if diff := cmp.Diff(opts, optsClone, cmp.Comparer(signBytesComp), cmp.AllowUnexported(SignedURLOptions{})); diff != "" {
t.Errorf("clone does not match (original: -, cloned: +):\n%s", diff)
}
}
Expand Down

0 comments on commit f918f23

Please sign in to comment.