Using ImpersonatedCredentials for Google Cloud APIs

2018-11-21

note, i just have this here as archive, please see


Almost two years ago I wrote an article about using the Service Account Actor IAM role for account impersonation. Since then, there have been some API surface changes and enchanced capabilities that makes that flow a lot easier…and as noted in the article, a bit obsolete. The ideas contained in that article are still valid though the mechanism is dated. In the new flow, you can now ‘just get’ an access_token, id_token, jwt or sign arbitrary blobs for a target service account by directly calling the iamcredentials API.

Lets use that API and go one step further: what if we bake in that exchange directly into the various google-auth-* libraries? That is, perform the exchange directly within any cloud library and use the assumed identity within any of target service set. For example:

  1. You start with a credential A initialized by any means (Service Account JSON, GCE metadata, userflow-oauth, etc)

  2. You use GCP auth libraries to exchange A for credentials representing serviceAccountB

  3. You use any google-cloud* library as account B

In code, it would look like this:

Note we start off with serviceAccount Credentials and then use google.auth.impersonated_credentials to exchange the provided credentials for another and the proceed to ‘just use’ any google API library

Usage

Using this is pretty straightforward:

then just run the the main.py python sample above (ofcourse replace PROJECT_ID variable in code above)

You can also ‘just run’ the sample above in a GCE compute engine instance:

gcloud compute instances create impersonate-test \
   --service-account=source-serviceaccount@$PROJECT.iam.gserviceaccount.com \
   --scopes=https://www.googleapis.com/auth/iam

Token Lifetime

Note that the API surface for iamcredentials.* describes a lifetime variable. What that does is pretty self-explanatory in that it just limits the lifetime of the derived access_token. By default its one hour 3600s but will automatically refresh even if the lifetime is set. Effectively, the lifetime is there for the transient token since its auto-refreshed on expiration.

If on the otherhand what you want is a true static access token for its duration, you will need to extract the impersonated credentials access token and apply it to a generic Credential object as such:

import google.auth.transport.requests
from google.oauth2.credentials import Credentials

target_credentials = impersonated_credentials.Credentials(
    source_credentials = source_credentials,
    target_principal='impersonated-account@your_project.iam.gserviceaccount.com',
    target_scopes = target_scopes,
    delegates=[],
    lifetime=500)


request = google.auth.transport.requests.Request()
target_credentials.refresh(request)
derived_access_token = target_credentials.token
print derived_access_token

static_credentials = Credentials(token=derived_access_token)
client = storage.Client(credentials=static_credentials)
buckets = client.list_buckets(project='your_project')
for bucket in buckets:
    print bucket.name

In the example above, tthe final client will only work for 500 seconds

Chained Delegation

Another feature with iamcredentials.* api suite is the delegates parameter. What that signifies is the list of service accounts that must already have authorization to mint tokens on behalf of the successive account. The chained list of delegates required to grant the final access_token. If set, the sequence of identities must have Service Account Token Creator capability granted to the prceeding identity. For example, if set to [serviceAccountB, serviceAccountC], the source_credential must have the Token Creator role on serviceAccountB. serviceAccountB must have the Token Creator on serviceAccountC. Finally, C must have Token Creator on target_principal. If left unset, source_credential must have that role on target_principal.

You can think of this as getting a form of a simple ‘workflow’ where the source account can’t unilaterally acquire permissions on the target but one where a sequence of ‘approvals` in a workflow is needed.

For more information on this, see: Creating Short-Lived Service Account Credentials

Language bindings

update 12/13/21: the following is incorrect!!, see (https://github.com/salrashid123/gcp_impersonated_credentials)

At the time of writing (11/25/18), the ‘first class’ support for impersonated credentials is not available in all languages Available:

  • python: google.auth.impersonated_credentials
  • java: com.google.auth.oauth2.ImpersonatedCredentials Pending
  • golang: google.auth.ImpersonatedTokenSource
  • nodejs: {ImpersonatedClient} = require(‘google-auth-library’);
  • dotnet (Google.Apis.Auth.OAuth2.ImpersonatedCredentials)

If you are interested in contributing a binding in any of the languages above, please feel free :)

AuditLogs

The following shows the audit logs that would get generated as a result of the Impersonated and Resource (GCS) access above:

  • images/audit_log.png

Note that the logs span two different resource.types in Cloud logging (IAM and the resource in context). That means to see them together, just run a query with a filter like:

resource.type="service_account" OR resource.type="gcs_bucket"

Here is a detailed logging payload for the IAM request:

  • images/get_credentials_request.png

And the actual resource request.

  • images/api_request.png

Notice that the api request contains the ORIGINAL user that is impersonating the service account

    "authenticationInfo": {
      "principalEmail": "target-serviceaccount@fabled-ray-104117.iam.gserviceaccount.com",
      "serviceAccountDelegationInfo": [
        {
          "firstPartyPrincipal": {
            "principalEmail": "admin@esodemoapp2.com"
          }
        }
      ]
    },

For more information, see Audit logs for service accounts

This site supports webmentions. Send me a mention via this form.