diff --git a/iterative/gcp/provider.go b/iterative/gcp/provider.go index 1527438f..394cbd16 100644 --- a/iterative/gcp/provider.go +++ b/iterative/gcp/provider.go @@ -324,7 +324,27 @@ func getProjectService() (string, *gcp_compute.Service, error) { } if credentials.ProjectID == "" { - return "", nil, errors.New("Couldn't extract the project identifier from the given credentials!") + // Coerce Credentials to handle GCP OIDC auth + // Common ProjectID ENVs: + // https://github.com/google-github-actions/auth/blob/b05f71482f54380997bcc43a29ef5007de7789b1/src/main.ts#L187-L191 + // https://github.com/hashicorp/terraform-provider-google/blob/d6734812e2c6a679334dcb46932f4b92729fa98c/google/provider.go#L64-L73 + coercedProjectID := utils.MultiEnvLoadFirst([]string{ + "CLOUDSDK_CORE_PROJECT", + "CLOUDSDK_PROJECT", + "GCLOUD_PROJECT", + "GCP_PROJECT", + "GOOGLE_CLOUD_PROJECT", + "GOOGLE_PROJECT", + }) + if coercedProjectID == "" { + // last effort to load + fromCredentialsID, err := utils.GCPCoerceOIDCCredentials(credentials.JSON) + if err != nil { + return "", nil, fmt.Errorf("Couldn't extract the project identifier from the given credentials!: [%w]", err) + } + coercedProjectID = fromCredentialsID + } + credentials.ProjectID = coercedProjectID } os.Setenv("GOOGLE_APPLICATION_CREDENTIALS_DATA", string(credentials.JSON)) diff --git a/iterative/utils/helpers.go b/iterative/utils/helpers.go index 28ba71fb..8797f25c 100644 --- a/iterative/utils/helpers.go +++ b/iterative/utils/helpers.go @@ -2,6 +2,8 @@ package utils import ( "context" + "encoding/json" + "errors" "fmt" "os" "strings" @@ -89,6 +91,38 @@ func SetId(d *schema.ResourceData) { } } +func MultiEnvLoadFirst(envs []string) string { + for _, val := range envs { + if env_value := os.Getenv(val); env_value != "" { + return env_value + } + } + return "" +} + +func GCPCoerceOIDCCredentials(rawCreds []byte) (string, error) { + hack := make(map[string]interface{}) + err := json.Unmarshal(rawCreds, &hack) + if err != nil { + return "", err + } + saString := fmt.Sprint(hack["service_account_impersonation_url"]) + // saString example: "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/cml@cml-pulse.iam.gserviceaccount.com:generateAccessToken" + if saString == "" { + return "", errors.New("[GCP OIDC] Unable to load service_account_impersonation_url") + } + // from saString, yank this + // -----------------------v.........v---------------------- + // (...serviceAccounts/cml@cml-pulse.iam.gserviceaccount.com:...) + atIndex := strings.Index(saString, "@") + 1 + iamIndex := strings.Index(saString, ".iam.") + if atIndex == -1 || iamIndex == -1 { + return "", errors.New("[GCP OIDC] Failed to get Project ID from service_account_impersonation_url") + } + projectID := saString[atIndex:iamIndex] + return projectID, nil +} + func LoadGCPCredentials() string { credentialsData := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS_DATA") if len(credentialsData) == 0 {