Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(storage): SignedUrl can use existing creds to authenticate #4604

Merged
merged 33 commits into from Oct 8, 2021
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2e0a907
feat(storage): SignedUrl can use existing creds to authenticate
BrennaEpp Aug 12, 2021
d606c02
Merge branch 'master' into signedurl
BrennaEpp Aug 12, 2021
4034078
Add comment to BucketHandle.SignedURL func
BrennaEpp Aug 12, 2021
5637a00
Fix test
BrennaEpp Aug 13, 2021
1571612
Merge branch 'master' into signedurl
BrennaEpp Aug 17, 2021
589df75
Merge branch 'googleapis:master' into signedurl
BrennaEpp Aug 19, 2021
834eac2
Merge branch 'master' into signedurl
BrennaEpp Sep 8, 2021
9b0e43f
Update comment link
BrennaEpp Sep 8, 2021
cac8974
Update other comment link
BrennaEpp Sep 8, 2021
9f4d347
Remove comment
BrennaEpp Sep 8, 2021
2f081dd
Merge branch 'master' into signedurl
BrennaEpp Sep 13, 2021
6200600
add clone method to SignedURLOptions
BrennaEpp Sep 13, 2021
7a97fed
Change comment location
BrennaEpp Sep 13, 2021
3fc9d6e
updated comments
BrennaEpp Sep 13, 2021
6fe68ee
add info to error from detectDefaultGoogleAccessID()
BrennaEpp Sep 13, 2021
80d75ff
add comment on reason for not needing to maintain service between calls
BrennaEpp Sep 13, 2021
f8f064b
add todo for GenerateSignedPostPolicyV4 wrapper
BrennaEpp Sep 13, 2021
29c6c75
Add details to signedurl comment
BrennaEpp Sep 17, 2021
35fbfe6
Merge branch 'master' into signedurl
BrennaEpp Sep 17, 2021
ea4412e
Add iam comment
BrennaEpp Sep 17, 2021
af48c46
fix lint
BrennaEpp Sep 27, 2021
0d5283a
Merge branch 'master' into signedurl
BrennaEpp Sep 27, 2021
b093d12
switch signedurl integrations tests to bucket wrapper
BrennaEpp Sep 27, 2021
99e3e5e
fix comment
BrennaEpp Sep 27, 2021
cc4d2b4
improve flow
BrennaEpp Sep 27, 2021
d55146a
Merge branch 'master' into signedurl
BrennaEpp Sep 27, 2021
66f6859
add test case:
BrennaEpp Sep 28, 2021
e86c240
Merge branch 'master' into signedurl
BrennaEpp Sep 28, 2021
1a1e7e4
Merge branch 'master' into signedurl
BrennaEpp Sep 29, 2021
761168b
Merge branch 'master' into signedurl
BrennaEpp Oct 6, 2021
f1e2d26
Merge branch 'master' into signedurl
BrennaEpp Oct 7, 2021
105f8e6
Merge branch 'master' into signedurl
codyoss Oct 8, 2021
a067318
Merge branch 'master' into signedurl
BrennaEpp Oct 8, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
95 changes: 95 additions & 0 deletions storage/bucket.go
Expand Up @@ -16,15 +16,20 @@ package storage

import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"reflect"
"time"

"cloud.google.com/go/compute/metadata"
"cloud.google.com/go/internal/optional"
"cloud.google.com/go/internal/trace"
"google.golang.org/api/googleapi"
"google.golang.org/api/iamcredentials/v1"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
raw "google.golang.org/api/storage/v1"
)

Expand Down Expand Up @@ -223,6 +228,96 @@ func (b *BucketHandle) newPatchCall(uattrs *BucketAttrsToUpdate) (*raw.BucketsPa
return req, nil
}

// SignedURL returns a URL for the specified object. Signed URLs allow anyone
// access to a restricted resource for a limited time without needing a
// Google account or signing in. For more information about signed URLs, see
// https://cloud.google.com/storage/docs/accesscontrol#signed_urls_query_string_authentication
BrennaEpp marked this conversation as resolved.
Show resolved Hide resolved
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.
BrennaEpp marked this conversation as resolved.
Show resolved Hide resolved
newopts := &SignedURLOptions{
GoogleAccessID: opts.GoogleAccessID,
SignBytes: opts.SignBytes,
PrivateKey: opts.PrivateKey,
Method: opts.Method,
Expires: opts.Expires,
ContentType: opts.ContentType,
Headers: opts.Headers,
QueryParameters: opts.QueryParameters,
MD5: opts.MD5,
Style: opts.Style,
Insecure: opts.Insecure,
Scheme: opts.Scheme,
}

if newopts.GoogleAccessID == "" {
id, err := b.detectDefaultGoogleAccessID()
if err != nil {
return "", err
}
newopts.GoogleAccessID = id
}
if newopts.SignBytes == nil && len(newopts.PrivateKey) == 0 {
if len(b.c.creds.JSON) > 0 {
var sa struct {
PrivateKey string `json:"private_key"`
}
// Don't error out if we can't marshal, fallback to GCE check.
BrennaEpp marked this conversation as resolved.
Show resolved Hide resolved
err := json.Unmarshal(b.c.creds.JSON, &sa)
if err == nil && sa.PrivateKey != "" {
newopts.PrivateKey = []byte(sa.PrivateKey)
}
}
if len(newopts.PrivateKey) == 0 {
newopts.SignBytes = b.defaultSignBytesFunc(newopts.GoogleAccessID)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably want an error here if for whatever reason PrivateKey or SignBytes hasn't been populated at this point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SignBytes should definitely be populated by b.defaultSignBytesFunc(newopts.GoogleAccessID), but I can guess an extra check wouldn't hurt?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm yeah I guess that's correct-- so then if there is an issue with signBytes, it will occur when it is called to do the signing. That seems fine. Can you verify that the error looks sensible if, for example, we don't have correct permissions and so signBytes fails?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error if the service account email is not found by the IAM client (for example can occur if you use your email instead of a service account email or there's a typo in your email):
unable to sign bytes: googleapi: Error 404: Requested entity was not found.
This error isn't the best but it's what we receive from the IAM api: https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/signBlob

Error if the service account does not have permissions:
unable to sign bytes: googleapi: Error 403: The caller does not have permission

Error if the Service Account Credentials API is not enabled:

unable to sign bytes: googleapi: Error 403: IAM Service Account Credentials API has not been used in project 1074109021461 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/iamcredentials.googleapis.com/overview?project=1074109021461 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.
Details:
[
  {
    "@type": "type.googleapis.com/google.rpc.Help",
    "links": [
      {
        "description": "Google developers console API activation",
        "url": "https://console.developers.google.com/apis/api/iamcredentials.googleapis.com/overview?project=1074109021461"
      }
    ]
  },
  {
    "@type": "type.googleapis.com/google.rpc.ErrorInfo",
    "domain": "googleapis.com",
    "metadata": {
      "consumer": "projects/1074109021461",
      "service": "iamcredentials.googleapis.com"
    },
    "reason": "SERVICE_DISABLED"
  }
]

return SignedURL(b.name, object, newopts)
}

func (b *BucketHandle) detectDefaultGoogleAccessID() (string, error) {
if len(b.c.creds.JSON) > 0 {
var sa struct {
ClientEmail string `json:"client_email"`
}
// Don't error out if we can't marshal, fallback to GCE check.
err := json.Unmarshal(b.c.creds.JSON, &sa)
if err == nil && sa.ClientEmail != "" {
return sa.ClientEmail, nil
}
}
if metadata.OnGCE() {
email, err := metadata.Email("default")
if err == nil && email != "" {
return email, nil
}
}
return "", fmt.Errorf("unable to detect default GoogleAccessID")
BrennaEpp marked this conversation as resolved.
Show resolved Hide resolved
}

func (b *BucketHandle) defaultSignBytesFunc(email string) func([]byte) ([]byte, error) {
return func(in []byte) ([]byte, error) {
ctx := context.Background()
svc, err := iamcredentials.NewService(ctx, option.WithHTTPClient(b.c.hc))
BrennaEpp marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, fmt.Errorf("unable to create iamcredentials client: %v", err)
}
resp, err := svc.Projects.ServiceAccounts.SignBlob(fmt.Sprintf("projects/-/serviceAccounts/%s", email), &iamcredentials.SignBlobRequest{
Payload: base64.StdEncoding.EncodeToString(in),
}).Do()
if err != nil {
return nil, fmt.Errorf("unable to sign bytes: %v", err)
}
out, err := base64.StdEncoding.DecodeString(resp.SignedBlob)
if err != nil {
return nil, fmt.Errorf("unable to base64 decode response: %v", err)
}
return out, nil
}
}
BrennaEpp marked this conversation as resolved.
Show resolved Hide resolved

// BucketAttrs represents the metadata for a Google Cloud Storage bucket.
// Read-only fields are ignored by BucketHandle.Create.
type BucketAttrs struct {
Expand Down
8 changes: 4 additions & 4 deletions storage/go.mod
Expand Up @@ -6,11 +6,11 @@ require (
cloud.google.com/go v0.93.3
github.com/golang/protobuf v1.5.2
github.com/google/go-cmp v0.5.6
github.com/googleapis/gax-go/v2 v2.0.5
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a
github.com/googleapis/gax-go/v2 v2.1.0
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
google.golang.org/api v0.54.0
google.golang.org/genproto v0.0.0-20210825212027-de86158e7fda
google.golang.org/api v0.56.0
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71
google.golang.org/grpc v1.40.0
google.golang.org/protobuf v1.27.1
)
17 changes: 11 additions & 6 deletions storage/go.sum
Expand Up @@ -141,8 +141,9 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.1.0 h1:6DWmvNpomjL1+3liNSZbVns3zsYzzCjm6pRBO1tLeso=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
Expand Down Expand Up @@ -266,8 +267,9 @@ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a h1:4Kd8OPUx1xgUwrHDaviWZO8MsgoZTZYC3g+8m16RBww=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down Expand Up @@ -321,8 +323,9 @@ golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down Expand Up @@ -417,8 +420,9 @@ google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59t
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
google.golang.org/api v0.54.0 h1:ECJUVngj71QI6XEm7b1sAf8BljU5inEhMbKPR8Lxhhk=
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
google.golang.org/api v0.56.0 h1:08F9XVYTLOGeSQb3xI9C0gXMuQanhdGed0cWFhDozbI=
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
Expand Down Expand Up @@ -477,8 +481,9 @@ google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
google.golang.org/genproto v0.0.0-20210825212027-de86158e7fda h1:iT5uhT54PtbqUsWddv/nnEWdE5e/MTr+Nv3vjxlBP1A=
google.golang.org/genproto v0.0.0-20210825212027-de86158e7fda/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71 h1:z+ErRPu0+KS02Td3fOAgdX+lnPDh/VyaABEJPD4JRQs=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
Expand Down
64 changes: 64 additions & 0 deletions storage/integration_test.go
Expand Up @@ -53,6 +53,7 @@ import (
"google.golang.org/api/iterator"
itesting "google.golang.org/api/iterator/testing"
"google.golang.org/api/option"
"google.golang.org/api/transport"
iampb "google.golang.org/genproto/googleapis/iam/v1"
)

Expand Down Expand Up @@ -3994,6 +3995,69 @@ func TestIntegration_Scopes(t *testing.T) {

}

func TestBucketSignURL(t *testing.T) {
BrennaEpp marked this conversation as resolved.
Show resolved Hide resolved
ctx := context.Background()

if testing.Short() && !replaying {
t.Skip("Integration tests skipped in short mode")
}

creds, err := findTestCredentials(ctx, "GCLOUD_TESTS_GOLANG_KEY", ScopeFullControl, "https://www.googleapis.com/auth/cloud-platform")
if err != nil {
t.Fatalf("unable to find test credentials: %v", err)
}
client, err := newTestClient(ctx, option.WithCredentials(creds))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this creds magic necessary? What happens if you run the tests with a test client as-is?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it's fine without it as long as env variables are set correctly. @codyoss is there a reason we can't call newTestClient without passing the creds?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So here we are explicitly testing the SA use-case and signing with it IIRC. By default we are authed with a TS so don't have the context to read some of the fields from the keyfile. For completeness we could also have a test that does the iamcredentials flow where we auth like normal but set the email address field. Maybe this bit of code deserves a comment 😛

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a couple comments and the extra test case

if err != nil {
log.Fatalf("NewClient: %v", err)
}
if client == nil {
t.Skip("Integration tests skipped. See CONTRIBUTING.md for details")
}
defer client.Close()
bkt := client.Bucket(bucketName)

obj := "testBucketSignedURL"
contents := []byte("test")
if err := writeObject(ctx, bkt.Object(obj), "text/plain", contents); err != nil {
t.Fatalf("writing: %v", err)
}

url, err := bkt.SignedURL(obj, &SignedURLOptions{
Method: "GET",
Expires: time.Now().Add(30 * time.Second),
})
if err != nil {
t.Fatalf("unable to create signed URL: %v", err)
}
resp, err := http.Get(url)
if err != nil {
t.Fatalf("http.Get(%q) errored: %q", url, err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
t.Fatalf("resp.StatusCode = %v, want 200: %v", resp.StatusCode, err)
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("unable to read resp.Body: %v", err)
}
if !bytes.Equal(b, contents) {
t.Fatalf("got %q, want %q", b, contents)
}
}

func findTestCredentials(ctx context.Context, envVar string, scopes ...string) (*google.Credentials, error) {
key := os.Getenv(envVar)
var opts []option.ClientOption
if len(scopes) > 0 {
opts = append(opts, option.WithScopes(scopes...))
}
if key != "" {
opts = append(opts, option.WithCredentialsFile(key))
}
return transport.Creds(ctx, opts...)
}

type testHelper struct {
t *testing.T
}
Expand Down
33 changes: 24 additions & 9 deletions storage/storage.go
Expand Up @@ -41,10 +41,12 @@ import (
"cloud.google.com/go/internal/trace"
"cloud.google.com/go/internal/version"
gapic "cloud.google.com/go/storage/internal/apiv2"
"golang.org/x/oauth2/google"
"google.golang.org/api/googleapi"
"google.golang.org/api/option"
"google.golang.org/api/option/internaloption"
raw "google.golang.org/api/storage/v1"
"google.golang.org/api/transport"
htransport "google.golang.org/api/transport/http"
storagepb "google.golang.org/genproto/googleapis/storage/v2"
"google.golang.org/protobuf/proto"
Expand Down Expand Up @@ -96,6 +98,7 @@ type Client struct {
scheme string
// ReadHost is the default host used on the reader.
readHost string
creds *google.Credentials

// gc is an optional gRPC-based, GAPIC client.
//
Expand All @@ -110,6 +113,7 @@ type Client struct {
// Clients should be reused instead of created as needed. The methods of Client
// are safe for concurrent use by multiple goroutines.
func NewClient(ctx context.Context, opts ...option.ClientOption) (*Client, error) {
var creds *google.Credentials

// In general, it is recommended to use raw.NewService instead of htransport.NewClient
// since raw.NewService configures the correct default endpoints when initializing the
Expand All @@ -120,10 +124,19 @@ func NewClient(ctx context.Context, opts ...option.ClientOption) (*Client, error
// need to account for STORAGE_EMULATOR_HOST override when setting the default endpoints.
if host := os.Getenv("STORAGE_EMULATOR_HOST"); host == "" {
// Prepend default options to avoid overriding options passed by the user.
opts = append([]option.ClientOption{option.WithScopes(ScopeFullControl), option.WithUserAgent(userAgent)}, opts...)
opts = append([]option.ClientOption{option.WithScopes(ScopeFullControl, "https://www.googleapis.com/auth/cloud-platform"), option.WithUserAgent(userAgent)}, opts...)
BrennaEpp marked this conversation as resolved.
Show resolved Hide resolved

opts = append(opts, internaloption.WithDefaultEndpoint("https://storage.googleapis.com/storage/v1/"))
opts = append(opts, internaloption.WithDefaultMTLSEndpoint("https://storage.mtls.googleapis.com/storage/v1/"))

c, err := transport.Creds(ctx, opts...)
if err != nil {
return nil, err
}
creds = c

opts = append(opts, internaloption.WithCredentials(creds))

} else {
var hostURL *url.URL

Expand Down Expand Up @@ -170,6 +183,7 @@ func NewClient(ctx context.Context, opts ...option.ClientOption) (*Client, error
raw: rawService,
scheme: u.Scheme,
readHost: u.Host,
creds: creds,
}, nil
}

Expand Down Expand Up @@ -208,6 +222,7 @@ func (c *Client) Close() error {
// Set fields to nil so that subsequent uses will panic.
c.hc = nil
c.raw = nil
c.creds = nil
if c.gc != nil {
return c.gc.Close()
}
Expand Down Expand Up @@ -507,11 +522,11 @@ func v4SanitizeHeaders(hdrs []string) []string {
return sanitizedHeaders
}

// SignedURL returns a URL for the specified object. Signed URLs allow
// the users access to a restricted resource for a limited time without having a
// Google account or signing in. For more information about the signed
// URLs, see https://cloud.google.com/storage/docs/accesscontrol#Signed-URLs.
func SignedURL(bucket, name string, opts *SignedURLOptions) (string, error) {
// SignedURL returns a URL for the specified object. Signed URLs allow anyone
// access to a restricted resource for a limited time without needing a
// Google account or signing in. For more information about signed URLs, see
// https://cloud.google.com/storage/docs/accesscontrol#signed_urls_query_string_authentication
func SignedURL(bucket, object string, opts *SignedURLOptions) (string, error) {
now := utcNow()
if err := validateOptions(opts, now); err != nil {
return "", err
Expand All @@ -520,13 +535,13 @@ func SignedURL(bucket, name string, opts *SignedURLOptions) (string, error) {
switch opts.Scheme {
case SigningSchemeV2:
opts.Headers = v2SanitizeHeaders(opts.Headers)
return signedURLV2(bucket, name, opts)
return signedURLV2(bucket, object, opts)
case SigningSchemeV4:
opts.Headers = v4SanitizeHeaders(opts.Headers)
return signedURLV4(bucket, name, opts, now)
return signedURLV4(bucket, object, opts, now)
default: // SigningSchemeDefault
opts.Headers = v2SanitizeHeaders(opts.Headers)
return signedURLV2(bucket, name, opts)
return signedURLV2(bucket, object, opts)
}
}

Expand Down