Skip to content

Commit f918f23

Browse files
authored
feat(storage): respect WithEndpoint for SignedURLs and PostPolicy (#8113)
Clients initiated with a non default endpoint will use that endpoint for SignedURLs and Post Policies.
1 parent 1a03107 commit f918f23

7 files changed

Lines changed: 370 additions & 18 deletions

File tree

storage/bucket.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,18 @@ func (b *BucketHandle) Update(ctx context.Context, uattrs BucketAttrsToUpdate) (
173173
// [Overview of access control]: https://cloud.google.com/storage/docs/accesscontrol#signed_urls_query_string_authentication
174174
// [automatic detection of credentials]: https://pkg.go.dev/cloud.google.com/go/storage#hdr-Credential_requirements_for_signing
175175
func (b *BucketHandle) SignedURL(object string, opts *SignedURLOptions) (string, error) {
176-
if opts.GoogleAccessID != "" && (opts.SignBytes != nil || len(opts.PrivateKey) > 0) {
177-
return SignedURL(b.name, object, opts)
178-
}
179176
// Make a copy of opts so we don't modify the pointer parameter.
180177
newopts := opts.clone()
181178

179+
if newopts.Hostname == "" {
180+
// Extract the correct host from the readhost set on the client
181+
newopts.Hostname = b.c.readHost
182+
}
183+
184+
if opts.GoogleAccessID != "" && (opts.SignBytes != nil || len(opts.PrivateKey) > 0) {
185+
return SignedURL(b.name, object, newopts)
186+
}
187+
182188
if newopts.GoogleAccessID == "" {
183189
id, err := b.detectDefaultGoogleAccessID()
184190
if err != nil {
@@ -215,12 +221,18 @@ func (b *BucketHandle) SignedURL(object string, opts *SignedURLOptions) (string,
215221
//
216222
// [automatic detection of credentials]: https://pkg.go.dev/cloud.google.com/go/storage#hdr-Credential_requirements_for_signing
217223
func (b *BucketHandle) GenerateSignedPostPolicyV4(object string, opts *PostPolicyV4Options) (*PostPolicyV4, error) {
218-
if opts.GoogleAccessID != "" && (opts.SignRawBytes != nil || opts.SignBytes != nil || len(opts.PrivateKey) > 0) {
219-
return GenerateSignedPostPolicyV4(b.name, object, opts)
220-
}
221224
// Make a copy of opts so we don't modify the pointer parameter.
222225
newopts := opts.clone()
223226

227+
if newopts.Hostname == "" {
228+
// Extract the correct host from the readhost set on the client
229+
newopts.Hostname = b.c.readHost
230+
}
231+
232+
if opts.GoogleAccessID != "" && (opts.SignRawBytes != nil || opts.SignBytes != nil || len(opts.PrivateKey) > 0) {
233+
return GenerateSignedPostPolicyV4(b.name, object, newopts)
234+
}
235+
224236
if newopts.GoogleAccessID == "" {
225237
id, err := b.detectDefaultGoogleAccessID()
226238
if err != nil {

storage/bucket_test.go

Lines changed: 319 additions & 0 deletions
Large diffs are not rendered by default.

storage/integration_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4810,11 +4810,9 @@ func TestIntegration_SignedURL_WithCreds(t *testing.T) {
48104810
t.Fatalf("problem with the signed URL: %v", err)
48114811
}
48124812
}, option.WithCredentials(creds))
4813-
48144813
}
48154814

48164815
func TestIntegration_SignedURL_DefaultSignBytes(t *testing.T) {
4817-
48184816
ctx := context.Background()
48194817

48204818
// Create another client to test the sign byte function as well

storage/post_policy_v4.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ type PostPolicyV4Options struct {
113113
// Optional.
114114
Conditions []PostPolicyV4Condition
115115

116+
// Hostname sets the host of the signed post policy. This field overrides
117+
// any endpoint set on a storage Client or through STORAGE_EMULATOR_HOST.
118+
// Only compatible with PathStyle URLStyle.
119+
// Optional.
120+
Hostname string
121+
116122
shouldHashSignBytes bool
117123
}
118124

@@ -128,6 +134,7 @@ func (opts *PostPolicyV4Options) clone() *PostPolicyV4Options {
128134
Fields: opts.Fields,
129135
Conditions: opts.Conditions,
130136
shouldHashSignBytes: opts.shouldHashSignBytes,
137+
Hostname: opts.Hostname,
131138
}
132139
}
133140

@@ -370,7 +377,7 @@ func GenerateSignedPostPolicyV4(bucket, object string, opts *PostPolicyV4Options
370377
u := &url.URL{
371378
Path: path,
372379
RawPath: pathEncodeV4(path),
373-
Host: opts.Style.host(bucket),
380+
Host: opts.Style.host(opts.Hostname, bucket),
374381
Scheme: scheme,
375382
}
376383

storage/post_policy_v4_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func TestPostPolicyV4OptionsClone(t *testing.T) {
4040
Fields: &PolicyV4Fields{ACL: "test-acl"},
4141
Conditions: []PostPolicyV4Condition{},
4242
shouldHashSignBytes: true,
43+
Hostname: "localhost:9000",
4344
}
4445

4546
// Check that all fields are set to a non-zero value, so we can check that

storage/storage.go

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -262,13 +262,13 @@ const (
262262
SigningSchemeV4
263263
)
264264

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

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

287-
func (s pathStyle) host(bucket string) string {
287+
func (s pathStyle) host(hostname, bucket string) string {
288+
if hostname != "" {
289+
return stripScheme(hostname)
290+
}
291+
288292
if host := os.Getenv("STORAGE_EMULATOR_HOST"); host != "" {
289293
return stripScheme(host)
290294
}
291295

292296
return "storage.googleapis.com"
293297
}
294298

295-
func (s virtualHostedStyle) host(bucket string) string {
299+
func (s virtualHostedStyle) host(_, bucket string) string {
296300
if host := os.Getenv("STORAGE_EMULATOR_HOST"); host != "" {
297301
return bucket + "." + stripScheme(host)
298302
}
299303

300304
return bucket + ".storage.googleapis.com"
301305
}
302306

303-
func (s bucketBoundHostname) host(bucket string) string {
307+
func (s bucketBoundHostname) host(_, bucket string) string {
304308
return s.hostname
305309
}
306310

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

323327
// PathStyle is the default style, and will generate a URL of the form
324-
// "storage.googleapis.com/<bucket-name>/<object-name>".
328+
// "<host-name>/<bucket-name>/<object-name>". By default, <host-name> is
329+
// storage.googleapis.com, but setting an endpoint on the storage Client or
330+
// through STORAGE_EMULATOR_HOST overrides this. Setting Hostname on
331+
// SignedURLOptions or PostPolicyV4Options overrides everything else.
325332
func PathStyle() URLStyle {
326333
return pathStyle{}
327334
}
@@ -442,6 +449,12 @@ type SignedURLOptions struct {
442449
// Scheme determines the version of URL signing to use. Default is
443450
// SigningSchemeV2.
444451
Scheme SigningScheme
452+
453+
// Hostname sets the host of the signed URL. This field overrides any
454+
// endpoint set on a storage Client or through STORAGE_EMULATOR_HOST.
455+
// Only compatible with PathStyle URLStyle.
456+
// Optional.
457+
Hostname string
445458
}
446459

447460
func (opts *SignedURLOptions) clone() *SignedURLOptions {
@@ -458,6 +471,7 @@ func (opts *SignedURLOptions) clone() *SignedURLOptions {
458471
Style: opts.Style,
459472
Insecure: opts.Insecure,
460473
Scheme: opts.Scheme,
474+
Hostname: opts.Hostname,
461475
}
462476
}
463477

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

718732
// Fill in the hostname based on the desired URL style.
719-
u.Host = opts.Style.host(bucket)
733+
u.Host = opts.Style.host(opts.Hostname, bucket)
720734

721735
// Fill in the URL scheme.
722736
if opts.Insecure {
@@ -850,7 +864,7 @@ func signedURLV2(bucket, name string, opts *SignedURLOptions) (string, error) {
850864
}
851865
encoded := base64.StdEncoding.EncodeToString(b)
852866
u.Scheme = "https"
853-
u.Host = PathStyle().host(bucket)
867+
u.Host = PathStyle().host(opts.Hostname, bucket)
854868
q := u.Query()
855869
q.Set("GoogleAccessId", opts.GoogleAccessID)
856870
q.Set("Expires", fmt.Sprintf("%d", opts.Expires.Unix()))

storage/storage_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2337,6 +2337,7 @@ func TestSignedURLOptionsClone(t *testing.T) {
23372337
Style: VirtualHostedStyle(),
23382338
Insecure: true,
23392339
Scheme: SigningSchemeV2,
2340+
Hostname: "localhost:8000",
23402341
}
23412342

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

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

0 commit comments

Comments
 (0)