@@ -3,6 +3,7 @@ package gcp
3
3
import (
4
4
"context"
5
5
"encoding/base64"
6
+ "encoding/json"
6
7
"errors"
7
8
"fmt"
8
9
"log"
@@ -289,6 +290,14 @@ func ResourceMachineDelete(ctx context.Context, d *schema.ResourceData, m interf
289
290
return nil
290
291
}
291
292
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
+
292
301
func getServiceAccountData (saString string ) (string , []string ) {
293
302
// ["SA email", "scopes=s1", "s2", ...]
294
303
splitStr := strings .Split (saString , "," )
@@ -301,19 +310,11 @@ func getServiceAccountData(saString string) (string, []string) {
301
310
splitStr [1 ] = strings .Split (splitStr [1 ], "=" )[1 ]
302
311
// ["s1", "s2", ...]
303
312
serviceAccountScopes := splitStr [1 :]
304
- return serviceAccountEmail , utils . CanonicalizeServiceScopes (serviceAccountScopes )
313
+ return serviceAccountEmail , getCanonicalizedServiceScopes (serviceAccountScopes )
305
314
}
306
315
307
316
func 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 ()
317
318
if err != nil {
318
319
return "" , nil , err
319
320
}
@@ -324,13 +325,50 @@ func getProjectService() (string, *gcp_compute.Service, error) {
324
325
}
325
326
326
327
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
328
349
}
329
350
330
351
os .Setenv ("GOOGLE_APPLICATION_CREDENTIALS_DATA" , string (credentials .JSON ))
331
352
return credentials .ProjectID , service , nil
332
353
}
333
354
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
+
334
372
func waitForOperation (ctx context.Context , timeout time.Duration , function func (... googleapi.CallOption ) (* gcp_compute.Operation , error ), arguments ... googleapi.CallOption ) (* gcp_compute.Operation , error ) {
335
373
var result * gcp_compute.Operation
336
374
@@ -514,3 +552,46 @@ func getInstanceType(instanceType string, instanceGPU string) (map[string]map[st
514
552
},
515
553
}, nil
516
554
}
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