@@ -3,6 +3,7 @@ package gcp
33import (
44 "context"
55 "encoding/base64"
6+ "encoding/json"
67 "errors"
78 "fmt"
89 "log"
@@ -289,6 +290,14 @@ func ResourceMachineDelete(ctx context.Context, d *schema.ResourceData, m interf
289290 return nil
290291}
291292
293+ func LoadGCPCredentials () (* google.Credentials , error ) {
294+ if credentialsData := os .Getenv ("GOOGLE_APPLICATION_CREDENTIALS_DATA" ); credentialsData != "" {
295+ return google .CredentialsFromJSON (oauth2 .NoContext , []byte (credentialsData ), gcp_compute .ComputeScope )
296+ }
297+
298+ return google .FindDefaultCredentials (oauth2 .NoContext , gcp_compute .ComputeScope )
299+ }
300+
292301func getServiceAccountData (saString string ) (string , []string ) {
293302 // ["SA email", "scopes=s1", "s2", ...]
294303 splitStr := strings .Split (saString , "," )
@@ -301,19 +310,11 @@ func getServiceAccountData(saString string) (string, []string) {
301310 splitStr [1 ] = strings .Split (splitStr [1 ], "=" )[1 ]
302311 // ["s1", "s2", ...]
303312 serviceAccountScopes := splitStr [1 :]
304- return serviceAccountEmail , utils . CanonicalizeServiceScopes (serviceAccountScopes )
313+ return serviceAccountEmail , getCanonicalizedServiceScopes (serviceAccountScopes )
305314}
306315
307316func getProjectService () (string , * gcp_compute.Service , error ) {
308- var credentials * google.Credentials
309- var err error
310-
311- if credentialsData := []byte (utils .LoadGCPCredentials ()); len (credentialsData ) > 0 {
312- credentials , err = google .CredentialsFromJSON (oauth2 .NoContext , credentialsData , gcp_compute .ComputeScope )
313- } else {
314- credentials , err = google .FindDefaultCredentials (oauth2 .NoContext , gcp_compute .ComputeScope )
315- }
316-
317+ credentials , err := LoadGCPCredentials ()
317318 if err != nil {
318319 return "" , nil , err
319320 }
@@ -324,13 +325,50 @@ func getProjectService() (string, *gcp_compute.Service, error) {
324325 }
325326
326327 if credentials .ProjectID == "" {
327- return "" , nil , errors .New ("Couldn't extract the project identifier from the given credentials!" )
328+ // Coerce Credentials to handle GCP OIDC auth
329+ // Common ProjectID ENVs:
330+ // https://github.com/google-github-actions/auth/blob/b05f71482f54380997bcc43a29ef5007de7789b1/src/main.ts#L187-L191
331+ // https://github.com/hashicorp/terraform-provider-google/blob/d6734812e2c6a679334dcb46932f4b92729fa98c/google/provider.go#L64-L73
332+ coercedProjectID := utils .MultiEnvLoadFirst ([]string {
333+ "CLOUDSDK_CORE_PROJECT" ,
334+ "CLOUDSDK_PROJECT" ,
335+ "GCLOUD_PROJECT" ,
336+ "GCP_PROJECT" ,
337+ "GOOGLE_CLOUD_PROJECT" ,
338+ "GOOGLE_PROJECT" ,
339+ })
340+ if coercedProjectID == "" {
341+ // last effort to load
342+ fromCredentialsID , err := coerceOIDCCredentials (credentials .JSON )
343+ if err != nil {
344+ return "" , nil , fmt .Errorf ("Couldn't extract the project identifier from the given credentials!: [%w]" , err )
345+ }
346+ coercedProjectID = fromCredentialsID
347+ }
348+ credentials .ProjectID = coercedProjectID
328349 }
329350
330351 os .Setenv ("GOOGLE_APPLICATION_CREDENTIALS_DATA" , string (credentials .JSON ))
331352 return credentials .ProjectID , service , nil
332353}
333354
355+ func coerceOIDCCredentials (credentialsJSON []byte ) (string , error ) {
356+ var credentials map [string ]interface {}
357+ if err := json .Unmarshal (credentialsJSON , & credentials ); err != nil {
358+ return "" , err
359+ }
360+
361+ if url , ok := credentials ["service_account_impersonation_url" ].(string ); ok {
362+ re := regexp .MustCompile ("^https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/.+?@(?P<project>.+).iam.gserviceaccount.com:generateAccessToken$" )
363+ if match := re .FindStringSubmatch (url ); match != nil {
364+ return match [1 ], nil
365+ }
366+ return "" , errors .New ("failed to get project identifier from service_account_impersonation_url" )
367+ }
368+
369+ return "" , errors .New ("unable to load service_account_impersonation_url" )
370+ }
371+
334372func waitForOperation (ctx context.Context , timeout time.Duration , function func (... googleapi.CallOption ) (* gcp_compute.Operation , error ), arguments ... googleapi.CallOption ) (* gcp_compute.Operation , error ) {
335373 var result * gcp_compute.Operation
336374
@@ -514,3 +552,46 @@ func getInstanceType(instanceType string, instanceGPU string) (map[string]map[st
514552 },
515553 }, nil
516554}
555+
556+ // https://github.com/hashicorp/terraform-provider-google/blob/8a362008bd4d36b6a882eb53455f87305e6dff52/google/service_scope.go#L5-L48
557+ func shorthandServiceScopeLookup (scope string ) string {
558+ // This is a convenience map of short names used by the gcloud tool
559+ // to the GCE auth endpoints they alias to.
560+ scopeMap := map [string ]string {
561+ "bigquery" : "https://www.googleapis.com/auth/bigquery" ,
562+ "cloud-platform" : "https://www.googleapis.com/auth/cloud-platform" ,
563+ "cloud-source-repos" : "https://www.googleapis.com/auth/source.full_control" ,
564+ "cloud-source-repos-ro" : "https://www.googleapis.com/auth/source.read_only" ,
565+ "compute-ro" : "https://www.googleapis.com/auth/compute.readonly" ,
566+ "compute-rw" : "https://www.googleapis.com/auth/compute" ,
567+ "datastore" : "https://www.googleapis.com/auth/datastore" ,
568+ "logging-write" : "https://www.googleapis.com/auth/logging.write" ,
569+ "monitoring" : "https://www.googleapis.com/auth/monitoring" ,
570+ "monitoring-read" : "https://www.googleapis.com/auth/monitoring.read" ,
571+ "monitoring-write" : "https://www.googleapis.com/auth/monitoring.write" ,
572+ "pubsub" : "https://www.googleapis.com/auth/pubsub" ,
573+ "service-control" : "https://www.googleapis.com/auth/servicecontrol" ,
574+ "service-management" : "https://www.googleapis.com/auth/service.management.readonly" ,
575+ "sql" : "https://www.googleapis.com/auth/sqlservice" ,
576+ "sql-admin" : "https://www.googleapis.com/auth/sqlservice.admin" ,
577+ "storage-full" : "https://www.googleapis.com/auth/devstorage.full_control" ,
578+ "storage-ro" : "https://www.googleapis.com/auth/devstorage.read_only" ,
579+ "storage-rw" : "https://www.googleapis.com/auth/devstorage.read_write" ,
580+ "taskqueue" : "https://www.googleapis.com/auth/taskqueue" ,
581+ "trace" : "https://www.googleapis.com/auth/trace.append" ,
582+ "useraccounts-ro" : "https://www.googleapis.com/auth/cloud.useraccounts.readonly" ,
583+ "useraccounts-rw" : "https://www.googleapis.com/auth/cloud.useraccounts" ,
584+ "userinfo-email" : "https://www.googleapis.com/auth/userinfo.email" ,
585+ }
586+ if matchedURL , ok := scopeMap [scope ]; ok {
587+ return matchedURL
588+ }
589+ return scope
590+ }
591+ func getCanonicalizedServiceScopes (scopes []string ) []string {
592+ cs := make ([]string , len (scopes ))
593+ for i , scope := range scopes {
594+ cs [i ] = shorthandServiceScopeLookup (scope )
595+ }
596+ return cs
597+ }
0 commit comments