Getting error using Google cloud client libraries for Go: unknown credential type: "impersonated_service_account"?

Viewed 1217

I am working with Google Cloud in Go and following this article by John Hanley:

https://www.jhanley.com/google-cloud-improving-security-with-impersonation/

and mashed it with this SO answer:

How to authenticate Google APIs (Google Drive API) from Google Compute Engine and locally without downloading Service Account credentials?

The credentials are successfully saved to, "application_default_credentials.json":

Notice: "type": "impersonated_service_account"

    {
  "delegates": [],
  "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/[sa@example-2021.iam.gserviceaccount.com]:generateAccessToken",
  "source_credentials": {
    "client_id": "...apps.googleusercontent.com",
    "client_secret": "...",
    "refresh_token": "...",
    "type": "authorized_user"
  },
  "type": "impersonated_service_account"
}

My code which produces an unknown credential type: "impersonated_service_account" error:

package main

import (
...
    "cloud.google.com/go/storage"
    "golang.org/x/oauth2"
    "google.golang.org/api/docs/v1"
    "google.golang.org/api/drive/v3"
    "google.golang.org/api/impersonate"
    "google.golang.org/api/option"
...
)

var Config.GoogleServiceAccount string = "sa@example-2021.iam.gserviceaccount.com"




func main(){
  _ = getTokenAsImpersonator()
}

// From: https://pkg.go.dev/google.golang.org/api/impersonate#example-CredentialsTokenSource-ServiceAccount
func getTokenAsImpersonator() oauth2.TokenSource {
    ctx := context.Background()

    // Base credentials sourced from ADC or provided client options.
    ts, err := impersonate.CredentialsTokenSource(ctx, impersonate.CredentialsConfig{
        TargetPrincipal: Config.GoogleServiceAccount,
        Scopes:          []string{"https://www.googleapis.com/auth/cloud-platform"},
        // Delegates: []string{"bar@project-id.iam.gserviceaccount.com"},
    })
    if err != nil {
        log.Fatal(err)
    }

    return ts
}

The 'unknown credential type: "impersonated_service_account"' error:

google: error getting credentials using GOOGLE_APPLICATION_CREDENTIALS environment variable: unknown credential type: "impersonated_service_account"

Have I done something wrong or is this a bug?


UPDATE

Answering John's questions from the comments:

1.

a) What is the value of the environment variable GOOGLE_APPLICATION_CREDENTIALS?

GOOGLE_APPLICATION_CREDENTIALS=/Users/x/.config/gcloud/application_default_credentials.json

b) What command did you use to generate application_default_credentials.json?

gcloud auth application-default login --scopes=https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/accounts.reauth,openid --impersonate-service-account=[sa@example-2021.iam.gserviceaccount.com]


Response:

Credentials saved to file: [/Users/x/.config/gcloud/application_default_credentials.json]


c)Which OS and version?

MacOS 10.13.6

d)gcloud --version?

Google Cloud SDK 343.0.0
app-engine-go 
app-engine-python 1.9.91
bq 2.0.69
cloud-datastore-emulator 2.1.0
core 2021.05.27
gsutil 4.62
  1. If you can create a minimum example ...

I have updated the example code above.

2 Answers

At some point I had used the CLI to impersonate an account:

gcloud config set auth/impersonate_service_account <service account>

Then later on when trying to use the application default credentials command it wraps your credentials with the service account credentials.

gcloud auth application-default login

What you end up with is a file that looks like this:

{
  "delegates": [],
  "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/example@example-project.iam.gserviceaccount.com:generateAccessToken",
  "source_credentials": {
    "client_id": "123abc.apps.googleusercontent.com",
    "client_secret": "XXXXXXXXX",
    "refresh_token": "XXXXXXXXX",
    "type": "authorized_user"
  },
  "type": "impersonated_service_account"
}

This appears to cause a lot of problems with third party services such as terraform.

What is strange is that Terraform is just making API calls to Google using Google SDKs, so really its something to do with Google.

You need to remove the impersonation:

gcloud config unset auth/impersonate_service_account

And then run the application default credential command again:

gcloud auth application-default login

Now if you check your file it should look like this:

{
  "client_id": "XXXXXXXXX",
  "client_secret": "XXXXXXXXX",
  "quota_project_id": "example-project",
  "refresh_token": "XXXXXXXXXX",
  "type": "authorized_user"
}

I was hitting the same issue when I was trying to impersonate an account so I could run Terraform commands as a service account instead of my personal account but it doesn't like that.

EDIT: Rereading you question it sounds like you're in the same boat as me. We want to use service accounts without physically downloading the keys. This is even mentioned by Google as best practice. But doing so is causing issues with their own SDKs.

I had the same issue running GCP Terraform provider tests. You can specify the Service Account Terraform have to impersonate setting the env variable GOOGLE_IMPERSONATE_SERVICE_ACCOUNT (documentation).

Configuration steps:

  • export GOOGLE_IMPERSONATE_SERVICE_ACCOUNT=SERVICE_ACCOUNT@PROJECT_ID.iam.gserviceaccount.com
  • gcloud auth application-default login
Related