r-lib / gargle

Infrastructure for calling Google APIs from R, including auth
https://gargle.r-lib.org/
Other
113 stars 33 forks source link

application_default_credentials() doesn't support "impersonated_service_account" credentials (and gives confusing messages) #266

Open egnor opened 1 year ago

egnor commented 1 year ago

REPRO CASE

  1. Create a Service account in GCP (if you don't have one)
  2. Create a user account in GCP (you probably have one)
  3. Allow the user account to run as (impersonate) the service account
  4. Run gcloud auth application-default login --impersonate-service-account=SERVICE-ACCOUNT-NAME
  5. Login as your user account
  6. Start R and run gargle::credentials_app_default() (or something that calls it, like gargle::token_fetch or bigrquery::bq_auth())

Expected behavior: Successfully authenticates and acquires a token that authenticates as the service account, OR, fails in some explicit way (at least when debugging is enabled via options(gargle_quiet = FALSE))

Actual behavior: Prints that it found an ADC file, but then returns NULL instead of a token:

> print(gargle::credentials_app_default())
trying `credentials_app_default()`
file exists at ADC path:
/root/credentials.json
NULL

EXPLANATION

Impersonation credentials are a relatively new and relatively obscure but very useful and increasingly supported type of credentials for GCP, see:

Here is what these "impersonation credentials" look like:

{
  "delegates": [],
  "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/<SERVICE ACCOUNT NAME>",
  "source_credentials": {
    "client_id": "<USER CLIENT ID>",
    "client_secret": "<USER CLIENT SECRET>",
    "refresh_token": "<USER REFRESH TOKEN>",
    "type": "authorized_user"
  },
  "type": "impersonated_service_account"
}

As you can see, they include a nested set of user credentials, plus instructions for how to use an API to get a token that impersonates the service account (without directly having the service account's credentials). The idea is that when fetching a token for these credentials, first you fetch a token for the nested user credentials, then you use that token to fetch a token to impersonate the service account, then you use that token.

At the very least, credentials_app_default.R should issue some sort of message if it falls of the end of the recognized info$type conditions, so that we'd see something like unknown ADC cred type: "impersonated_service_account" and have a clue about what's going wrong. Better yet, of course, would be to in fact support this type of credential properly...!