Using ImpersonatedCredentials for Google Cloud APIs and IDTokens

2021-11-18

Samples which demonstrate getting and using impersonated credentials for Google Cloud Service Accounts.

Use the impersonated token to access GCP Services and IdTokens for Cloud Run, Cloud Functions, Endpoints

  • Start with Service Account SA1
  • Use SA1 to impersonate SA2
  • Use SA2 to get an id_token
  • Use SA2 to access a Google Cloud Storage object

See Creating Short-Lived Service Account Credentials

Note, if you use Workload Identity Federation, a type of first level impersonation is already done for you which means you can use WIF to directly access GCP resources. You can use the samples to chain impersonated credentials with WIF as well.

However, if you need to use the WIF identity to get an id_token, you still need to manually acquire that using one of the samples below

you can find the sourcecode here:

Also see:

Usage

# Create source identity:
export PROJECT_ID=$(gcloud config list --format="value(core.project)")

gcloud iam service-accounts create source-serviceaccount --display-name="Source Identity"

gcloud iam service-accounts keys  create svc-src.json \
 --iam-account=source-serviceaccount@$PROJECT_ID.iam.gserviceaccount.com

# Create target identity
gcloud iam service-accounts create target-serviceaccount --display-name="Target Identity"

# Allow source to impersonate target
gcloud iam service-accounts add-iam-policy-binding \
    target-serviceaccount@$PROJECT_ID.iam.gserviceaccount.com  \
    --member=serviceAccount:source-serviceaccount@$PROJECT_ID.iam.gserviceaccount.com \
    --role='roles/iam.serviceAccountTokenCreator'

The iam.serviceAccountTokenCreator role includes concentric set of permission (see Concentric IAMCredentials Permissions: The secret life of signBlob. If you just need to generate an access_token or an id_token, consider generating a custom role with that specific capability.

For this repo we will primarily use the Google Cloud Storage set of libraries (except in the node example set where we have to use Secret Manager as well)

# Add resource ACL to target resource

## resource here is a gcs bucket
gsutil mb gs://$PROJECT_ID-test
echo -n bar > foo.txt
gsutil cp foo.txt gs://$PROJECT_ID-test/
rm foo.txt

gsutil iam ch serviceAccount:"target-serviceaccount@$PROJECT_ID.iam.gserviceaccount.com":objectViewer  gs://$PROJECT_ID-test


## also secret manager (use for nodejs sample)
printf "s3cr3t" | gcloud secrets create my-secret --data-file=-    --replication-policy=user-managed --locations=us-central1,us-east1
export SECRET_NAME=`gcloud secrets describe my-secret --format="value(name)"`
echo $SECRET_NAME
gcloud secrets add-iam-policy-binding my-secret  \
    --member=serviceAccount:target-serviceaccount@$PROJECT_ID.iam.gserviceaccount.com \
    --role=roles/secretmanager.secretAccessor

gcloud

# activate the srouce account
gcloud auth activate-service-account --key-file=`pwd`/svc-src.json

### try source account (fail)
gcloud alpha storage ls gs://$PROJECT_ID-test

### try impersonated account (pass)
gcloud alpha storage ls gs://$PROJECT_ID-test --impersonate-service-account=target-serviceaccount@$PROJECT_ID.iam.gserviceaccount.com

gsutil

# activate the 'soruce'
gcloud auth activate-service-account --key-file=`pwd`/svc-src.json

# try with your service account
gsutil stat gs://$PROJECT_ID-test/foo.txt

# now use impersonation
gsutil -i target-serviceaccount@$PROJECT_ID.iam.gserviceaccount.com stat gs://$PROJECT_ID-test/foo.txt

bq

(not supported)

though you can use limited bq functions through gcloud right now and then use gcloud impersonation

 gcloud alpha bq jobs list

ADC

Tested with ADC variants:

  • Service Account Credentials JSON
export GOOGLE_APPLICATION_CREDENTIALS=`pwd`/svc-src.json
{
  "type": "service_account",
  "project_id": "$PROJECT_ID",
  "private_key_id": "da386aab2a4f793d14dc8c809c8f8180d86dfcb9",
  "private_key": "-----BEGIN PRIVATE KEY-----\nM",
  "client_email": "source-serviceaccount@$PROJECT_ID.iam.gserviceaccount.com",
  "client_id": "100973668658833123774",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/source-serviceaccount%40$PROJECT_ID.iam.gserviceaccount.com"
}
  • golang: pass

  • java: pass

  • python: pass

  • node: pass

  • dotnet: fail

Workload Identity Federation (OIDC)

Workload Identity Federation usually uses impersonation so including impersonated credentials here does this twice (and usually unnecessarily).

If what you just need is a plain id_token for use with Cloud Run, Cloud Functions, etc, see Using Federated Tokens to sign signJWT or get gcp idtokens

Compute Engine Credentials (metadata server)

curl -s -H 'Metadata-Flavor: Google' http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email
123456-compute@developer.gserviceaccount.com

in this case, 123456-compute@developer.gserviceaccount.com is allowed to impersonate target-serviceaccount@$PROJECT_ID.iam.gserviceaccount.com

gcloud iam service-accounts add-iam-policy-binding \
    target-serviceaccount@$PROJECT_ID.iam.gserviceaccount.com  \
    --member=serviceAccount:1071284184436-compute@developer.gserviceaccount.com \
    --role='roles/iam.serviceAccountTokenCreator'
  • golang: pass

  • java: pass

  • python: pass

  • node: pass

  • dotnet: fail

    $ dotnet run
    ERROR: Only ServiceAccountCredential and UserCredential support impersonation.
    

python

see:

golang

java

nodeJS

Requires atleast google-auth-library-nodejs@v8.3.0

dotnet

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.

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.

For more information on this, see:

Chaining Credential Types

The examples above chain credentials for other credentials or id_tokens:

  • source->impersonated->gcp_service
  • source->impersonated->id_token->cloud_run

The more rare way to chain credentials is to use a Downscoped Credential

in this, the flow is

  • source->impersonated->downscoped_token->gcp_service(gcs)

its not that common to do this type of chaining so i’ve left it out of this repo. see example in go for impersonate+downscope

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"
          }
        }
      ]
    },

Note: if chained delegation is used (i.,e user->sa1->sa2->resource), you will see multiple firstPartyPrincipal values. The first firstPartyPrincipal will be the most recent service account used in the chain. In other words, the order here matters:

    "authenticationInfo": {
      "principalEmail": "sa2@project.iam.gserviceaccount.com",
      "serviceAccountDelegationInfo": [
        {
          "firstPartyPrincipal": {
            "principalEmail": "sa1@project.iam.gserviceaccount.com"
          },
          "firstPartyPrincipal": {
            "principalEmail": "user"
          }
        }          
        }
      ]
    },

For more information, see Audit logs for service accounts

References

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