diff --git a/storage/bucket.go b/storage/bucket.go index 5601357c9b2b..4183187213f9 100644 --- a/storage/bucket.go +++ b/storage/bucket.go @@ -200,11 +200,11 @@ func (b *BucketHandle) SignedURL(object string, opts *SignedURLOptions) (string, newopts.GoogleAccessID = id } if newopts.SignBytes == nil && len(newopts.PrivateKey) == 0 { - if b.c.creds != nil && len(b.c.creds.JSON) > 0 { + if j := b.c.credsJSON(); len(j) > 0 { var sa struct { PrivateKey string `json:"private_key"` } - err := json.Unmarshal(b.c.creds.JSON, &sa) + err := json.Unmarshal(j, &sa) if err == nil && sa.PrivateKey != "" { newopts.PrivateKey = []byte(sa.PrivateKey) } @@ -248,11 +248,11 @@ func (b *BucketHandle) GenerateSignedPostPolicyV4(object string, opts *PostPolic newopts.GoogleAccessID = id } if newopts.SignBytes == nil && newopts.SignRawBytes == nil && len(newopts.PrivateKey) == 0 { - if b.c.creds != nil && len(b.c.creds.JSON) > 0 { + if j := b.c.credsJSON(); len(j) > 0 { var sa struct { PrivateKey string `json:"private_key"` } - err := json.Unmarshal(b.c.creds.JSON, &sa) + err := json.Unmarshal(j, &sa) if err == nil && sa.PrivateKey != "" { newopts.PrivateKey = []byte(sa.PrivateKey) } @@ -270,14 +270,14 @@ func (b *BucketHandle) GenerateSignedPostPolicyV4(object string, opts *PostPolic func (b *BucketHandle) detectDefaultGoogleAccessID() (string, error) { returnErr := errors.New("no credentials found on client and not on GCE (Google Compute Engine)") - if b.c.creds != nil && len(b.c.creds.JSON) > 0 { + if j := b.c.credsJSON(); len(j) > 0 { var sa struct { ClientEmail string `json:"client_email"` SAImpersonationURL string `json:"service_account_impersonation_url"` CredType string `json:"type"` } - err := json.Unmarshal(b.c.creds.JSON, &sa) + err := json.Unmarshal(j, &sa) if err != nil { returnErr = err } else { diff --git a/storage/bucket_test.go b/storage/bucket_test.go index abd117d6e45a..e908792b6943 100644 --- a/storage/bucket_test.go +++ b/storage/bucket_test.go @@ -21,12 +21,12 @@ import ( "testing" "time" + "cloud.google.com/go/auth/credentials" "cloud.google.com/go/compute/metadata" "cloud.google.com/go/internal/testutil" "cloud.google.com/go/storage/internal/apiv2/storagepb" "github.com/google/go-cmp/cmp" gax "github.com/googleapis/gax-go/v2" - "golang.org/x/oauth2/google" "google.golang.org/api/googleapi" "google.golang.org/api/option" raw "google.golang.org/api/storage/v1" @@ -1291,11 +1291,16 @@ func TestDetectDefaultGoogleAccessID(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + jsonBytes := []byte(tc.creds(tc.serviceAccount)) + creds, err := credentials.DetectDefault(&credentials.DetectOptions{ + CredentialsJSON: jsonBytes, + }) + if tc.expectSuccess && err != nil { + t.Fatal(err) + } bucket := BucketHandle{ c: &Client{ - creds: &google.Credentials{ - JSON: []byte(tc.creds(tc.serviceAccount)), - }, + creds: creds, }, name: "my-bucket", } diff --git a/storage/http_client.go b/storage/http_client.go index 221078f3e262..4be0b4bd7569 100644 --- a/storage/http_client.go +++ b/storage/http_client.go @@ -31,24 +31,23 @@ import ( "strings" "time" + "cloud.google.com/go/auth" "cloud.google.com/go/iam/apiv1/iampb" "cloud.google.com/go/internal/optional" "cloud.google.com/go/internal/trace" "github.com/googleapis/gax-go/v2/callctx" - "golang.org/x/oauth2/google" "google.golang.org/api/googleapi" "google.golang.org/api/iterator" "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" ) // httpStorageClient is the HTTP-JSON API implementation of the transport-agnostic // storageClient interface. type httpStorageClient struct { - creds *google.Credentials + creds *auth.Credentials hc *http.Client xmlHost string raw *raw.Service @@ -65,7 +64,7 @@ func newHTTPStorageClient(ctx context.Context, opts ...storageOption) (storageCl o := s.clientOption config := newStorageConfig(o...) - var creds *google.Credentials + var creds *auth.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 // internal http client. However, in our case, "NewRangeReader" in reader.go needs to @@ -83,10 +82,10 @@ func newHTTPStorageClient(ctx context.Context, opts ...storageOption) (storageCl ) // Don't error out here. The user may have passed in their own HTTP // client which does not auth with ADC or other common conventions. - c, err := transport.Creds(ctx, o...) + c, err := internaloption.AuthCreds(ctx, o) if err == nil { creds = c - o = append(o, internaloption.WithCredentials(creds)) + o = append(o, option.WithAuthCredentials(creds)) } } else { var hostURL *url.URL diff --git a/storage/integration_test.go b/storage/integration_test.go index a9dc265c0ba5..8e2e4f4f9660 100644 --- a/storage/integration_test.go +++ b/storage/integration_test.go @@ -43,6 +43,8 @@ import ( "testing" "time" + "cloud.google.com/go/auth" + "cloud.google.com/go/auth/credentials" "cloud.google.com/go/httpreplay" "cloud.google.com/go/iam" "cloud.google.com/go/iam/apiv1/iampb" @@ -55,13 +57,11 @@ import ( "github.com/googleapis/gax-go/v2/apierror" "go.opentelemetry.io/contrib/detectors/gcp" "go.opentelemetry.io/otel/sdk/resource" - "golang.org/x/oauth2/google" "google.golang.org/api/googleapi" "google.golang.org/api/iterator" itesting "google.golang.org/api/iterator/testing" "google.golang.org/api/option" "google.golang.org/api/option/internaloption" - "google.golang.org/api/transport" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -94,6 +94,13 @@ var ( controlClient *control.StorageControlClient ) +var ( + testScopes = []string{ + ScopeFullControl, + "https://www.googleapis.com/auth/cloud-platform", + } +) + func TestMain(m *testing.M) { cleanup := initIntegrationTest() cleanupEmulatorClients := initEmulatorClients() @@ -5348,6 +5355,24 @@ func TestIntegration_NewReaderWithContentEncodingGzip(t *testing.T) { }) } +type credentialsFile struct { + Type string `json:"type"` + + // Service Account email + ClientEmail string `json:"client_email"` +} + +func jwtConfigFromJSON(jsonKey []byte) (*credentialsFile, error) { + var f credentialsFile + if err := json.Unmarshal(jsonKey, &f); err != nil { + return nil, err + } + if f.Type != "service_account" { + return nil, fmt.Errorf("read JWT from JSON credentials: 'type' field is %q (expected service_account)", f.Type) + } + return &f, nil +} + func TestIntegration_HMACKey(t *testing.T) { ctx := skipJSONReads(skipGRPC("hmac not implemented"), "no reads in test") multiTransportTest(ctx, t, func(t *testing.T, ctx context.Context, _, _ string, client *Client) { @@ -5367,13 +5392,12 @@ func TestIntegration_HMACKey(t *testing.T) { if credentials.JSON == nil { t.Fatal("could not read the JSON key file, is GCLOUD_TESTS_GOLANG_KEY set correctly?") } - conf, err := google.JWTConfigFromJSON(credentials.JSON) + conf, err := jwtConfigFromJSON(credentials.JSON) if err != nil { t.Fatal(err) } - serviceAccountEmail := conf.Email - hmacKey, err := client.CreateHMACKey(ctx, projectID, serviceAccountEmail) + hmacKey, err := client.CreateHMACKey(ctx, projectID, conf.ClientEmail) if err != nil { t.Fatalf("Failed to create HMACKey: %v", err) } @@ -5600,8 +5624,7 @@ func TestIntegration_SignedURL_WithCreds(t *testing.T) { } ctx := context.Background() - - creds, err := findTestCredentials(ctx, "GCLOUD_TESTS_GOLANG_KEY", ScopeFullControl, "https://www.googleapis.com/auth/cloud-platform") + creds, err := findTestCredentials(ctx, "GCLOUD_TESTS_GOLANG_KEY", testScopes) if err != nil { t.Fatalf("unable to find test credentials: %v", err) } @@ -5626,7 +5649,7 @@ func TestIntegration_SignedURL_WithCreds(t *testing.T) { if err := verifySignedURL(url, nil, contents); err != nil { t.Fatalf("problem with the signed URL: %v", err) } - }, option.WithCredentials(creds)) + }, option.WithAuthCredentials(creds)) } func TestIntegration_SignedURL_DefaultSignBytes(t *testing.T) { @@ -5683,7 +5706,7 @@ func TestIntegration_PostPolicyV4_WithCreds(t *testing.T) { // By default we are authed with a token source, so don't have the context to // read some of the fields from the keyfile. // Here we explictly send the key to the client. - creds, err := findTestCredentials(context.Background(), "GCLOUD_TESTS_GOLANG_KEY", ScopeFullControl, "https://www.googleapis.com/auth/cloud-platform") + creds, err := findTestCredentials(context.Background(), "GCLOUD_TESTS_GOLANG_KEY", testScopes) if err != nil { t.Fatalf("unable to find test credentials: %v", err) } @@ -5728,7 +5751,7 @@ func TestIntegration_PostPolicyV4_WithCreds(t *testing.T) { } }) } - }, option.WithCredentials(creds)) + }, option.WithAuthCredentials(creds)) } @@ -5969,16 +5992,11 @@ func verifyPostPolicy(pv4 *PostPolicyV4, obj *ObjectHandle, bytesToWrite []byte, }) } -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...) +func findTestCredentials(ctx context.Context, envVar string, scopes []string) (*auth.Credentials, error) { + return credentials.DetectDefault(&credentials.DetectOptions{ + CredentialsFile: os.Getenv(envVar), + Scopes: scopes, + }) } type testHelper struct { diff --git a/storage/storage.go b/storage/storage.go index 66fba72e6a49..1fb1bf7facc7 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -38,6 +38,7 @@ import ( "time" "unicode/utf8" + "cloud.google.com/go/auth" "cloud.google.com/go/internal/optional" "cloud.google.com/go/internal/trace" "cloud.google.com/go/storage/internal" @@ -46,12 +47,10 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" - "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" "google.golang.org/grpc/experimental/stats" "google.golang.org/grpc/stats/opentelemetry" @@ -117,13 +116,22 @@ type Client struct { // xmlHost is the default host used for XML requests. xmlHost string // May be nil. - creds *google.Credentials + creds *auth.Credentials retry *retryConfig // tc is the transport-agnostic client implemented with either gRPC or HTTP. tc storageClient } +// credsJSON returns the raw JSON of the Client's creds, or an empty slice +// if no credentials JSON is available. +func (c Client) credsJSON() []byte { + if c.creds != nil && len(c.creds.JSON()) > 0 { + return c.creds.JSON() + } + return []byte{} +} + // NewClient creates a new Google Cloud Storage client using the HTTP transport. // The default scope is ScopeFullControl. To use a different scope, like // ScopeReadOnly, use option.WithScopes. @@ -134,7 +142,7 @@ type Client struct { // You may configure the client by passing in options from the [google.golang.org/api/option] // package. You may also use options defined in this package, such as [WithJSONReads]. func NewClient(ctx context.Context, opts ...option.ClientOption) (*Client, error) { - var creds *google.Credentials + var creds *auth.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 @@ -154,10 +162,10 @@ func NewClient(ctx context.Context, opts ...option.ClientOption) (*Client, error // Don't error out here. The user may have passed in their own HTTP // client which does not auth with ADC or other common conventions. - c, err := transport.Creds(ctx, opts...) + c, err := internaloption.AuthCreds(ctx, opts) if err == nil { creds = c - opts = append(opts, internaloption.WithCredentials(creds)) + opts = append(opts, option.WithAuthCredentials(creds)) } } else { var hostURL *url.URL