Skip to content

Commit

Permalink
blob/gcsblob: add support for forcing an authenticated client (#3273)
Browse files Browse the repository at this point in the history
  • Loading branch information
vangent committed Jul 19, 2023
1 parent 0e5728d commit 8cab06e
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 18 deletions.
38 changes: 26 additions & 12 deletions blob/gcsblob/gcsblob.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
// The default URL opener will set up a connection using default credentials
// from the environment, as described in
// https://cloud.google.com/docs/authentication/production.
// You may force the use of an unauthenticated client by setting
// GoogleAccessID to "-" (via Options or via the URL parameter "access_id").
// Some environments, such as GCE, come without a private key. In such cases
// the IAM Credentials API will be configured for use in Options.MakeSignBytes,
// which will introduce latency to any and all calls to bucket.SignedURL
Expand Down Expand Up @@ -206,7 +208,8 @@ const Scheme = "gs"
// - access_id: sets Options.GoogleAccessID
// - private_key_path: path to read for Options.PrivateKey
//
// Currently their use is limited to SignedURL.
// Currently their use is limited to SignedURL, except that setting access_id
// to "-" forces the use of an unauthenticated client.
type URLOpener struct {
// Client must be set to a non-nil HTTP client authenticated with
// Cloud Storage scope or equivalent.
Expand All @@ -218,32 +221,34 @@ type URLOpener struct {

// OpenBucketURL opens the GCS bucket with the same name as the URL's host.
func (o *URLOpener) OpenBucketURL(ctx context.Context, u *url.URL) (*blob.Bucket, error) {
opts, err := o.forParams(ctx, u.Query())
opts, client, err := o.forParams(ctx, u.Query())
if err != nil {
return nil, fmt.Errorf("open bucket %v: %v", u, err)
}
return OpenBucket(ctx, o.Client, u.Host, opts)
return OpenBucket(ctx, client, u.Host, opts)
}

func (o *URLOpener) forParams(ctx context.Context, q url.Values) (*Options, error) {
func (o *URLOpener) forParams(ctx context.Context, q url.Values) (*Options, *gcp.HTTPClient, error) {
for k := range q {
if k != "access_id" && k != "private_key_path" {
return nil, fmt.Errorf("invalid query parameter %q", k)
return nil, nil, fmt.Errorf("invalid query parameter %q", k)
}
}
opts := new(Options)
*opts = o.Options
client := o.Client
if accessID := q.Get("access_id"); accessID != "" && accessID != opts.GoogleAccessID {
opts.GoogleAccessID = accessID
opts.PrivateKey = nil // Clear any previous key unrelated to the new accessID.

// Clear this as well to prevent calls with the old and mismatched accessID.
opts.MakeSignBytes = nil
opts.clear()
if accessID == "-" {
client = gcp.NewAnonymousHTTPClient(gcp.DefaultTransport())
} else {
opts.GoogleAccessID = accessID
}
}
if keyPath := q.Get("private_key_path"); keyPath != "" {
pk, err := ioutil.ReadFile(keyPath)
if err != nil {
return nil, err
return nil, nil, err
}
opts.PrivateKey = pk
} else if _, exists := q["private_key_path"]; exists {
Expand All @@ -252,12 +257,13 @@ func (o *URLOpener) forParams(ctx context.Context, q url.Values) (*Options, erro
// is intentional such as for tests or involving a key stored in a HSM/TPM.
opts.PrivateKey = nil
}
return opts, nil
return opts, client, nil
}

// Options sets options for constructing a *blob.Bucket backed by GCS.
type Options struct {
// GoogleAccessID represents the authorizer for SignedURL.
// If set to "-", an unauthenticated client will be used.
// Required to use SignedURL.
// See https://godoc.org/cloud.google.com/go/storage#SignedURLOptions.
GoogleAccessID string
Expand All @@ -279,6 +285,14 @@ type Options struct {
MakeSignBytes func(requestCtx context.Context) SignBytesFunc
}

// clear clears all the fields of o.
func (o *Options) clear() {
o.GoogleAccessID = ""
o.PrivateKey = nil
o.SignBytes = nil
o.MakeSignBytes = nil
}

// SignBytesFunc is shorthand for the signature of Options.SignBytes.
type SignBytesFunc func([]byte) ([]byte, error)

Expand Down
25 changes: 19 additions & 6 deletions blob/gcsblob/gcsblob_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,11 +532,12 @@ func TestURLOpenerForParams(t *testing.T) {
}

tests := []struct {
name string
currOpts Options
query url.Values
wantOpts Options
wantErr bool
name string
currOpts Options
query url.Values
wantOpts Options
wantClient bool
wantErr bool
}{
{
name: "InvalidParam",
Expand All @@ -560,6 +561,15 @@ func TestURLOpenerForParams(t *testing.T) {
},
wantOpts: Options{GoogleAccessID: "bar"},
},
{
name: "AccessID override to - makes new client",
currOpts: Options{GoogleAccessID: "foo"},
query: url.Values{
"access_id": {"-"},
},
wantOpts: Options{}, // cleared
wantClient: true,
},
{
name: "AccessID not overridden",
currOpts: Options{GoogleAccessID: "bar"},
Expand Down Expand Up @@ -608,7 +618,7 @@ func TestURLOpenerForParams(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
o := &URLOpener{Options: test.currOpts}
got, err := o.forParams(ctx, test.query)
got, gotClient, err := o.forParams(ctx, test.query)
if (err != nil) != test.wantErr {
t.Errorf("got err %v want error %v", err, test.wantErr)
}
Expand All @@ -618,6 +628,9 @@ func TestURLOpenerForParams(t *testing.T) {
if diff := cmp.Diff(got, &test.wantOpts); diff != "" {
t.Errorf("opener.forParams(...) diff (-want +got):\n%s", diff)
}
if test.wantClient != (gotClient != nil) {
t.Errorf("opener.forParams client return value was unexpected, got %v want %v", gotClient != nil, test.wantClient)
}
})
}
}
Expand Down

0 comments on commit 8cab06e

Please sign in to comment.