11

I would like to allow users to impersonate a service account to do operations on a long running process. However, all the code examples illustrate a service account impersonating another service account.

Can users directly impersonate a service account? If so, how?

I'm following this example code.

Initialize a source credential which does not have access to list bucket:

from google.oauth2 import service_acccount

target_scopes = [
    'https://www.googleapis.com/auth/devstorage.read_only']

source_credentials = (
    service_account.Credentials.from_service_account_file(
        '/path/to/svc_account.json',
        scopes=target_scopes))

Now use the source credentials to acquire credentials to impersonate another service account:

from google.auth import impersonated_credentials

target_credentials = impersonated_credentials.Credentials(
  source_credentials=source_credentials,
  target_principal='impersonated-account@_project_.iam.gserviceaccount.com',
  target_scopes = target_scopes,
  lifetime=500)
John Hanley
  • 74,467
  • 6
  • 95
  • 159
A Clockwork Orange
  • 23,913
  • 7
  • 25
  • 28
  • One point for your question. You mention a long-running process. The maximum time for a user or service account access token is 3,600 seconds (one hour). What type of process do you need tokens for? Both account types have the same expiration limits. – John Hanley Mar 05 '20 at 23:46
  • The process is long running but is handled by another google product, the service account would only be responsible for starting, stopping or modifying that process. – A Clockwork Orange Mar 06 '20 at 02:06

2 Answers2

11

Yes, you can impersonate from user to service account. You only need to ensure that your user has Service Account Token Creator role for the target service account. You need to explicitly grant it by:

  1. Selecting the service account in IAM & Admin
  2. Select IAM
  3. Select your account and yourself as the above role (Service Account Token Creator).

Even if you are Project Owner, it doesn't cut it.

Please note it can take 1-2 minutes for the permission to apply, so if your code errors on:

Unable to acquire impersonated credentials...

Make sure you have the above permission and if you just added it recently have a coffee break and try again :)

The code remains practically the same, here is an adapted example from the docs:

import google.auth
import google.auth.impersonated_credentials
from google.cloud import storage


target_scopes = [
    "https://www.googleapis.com/auth/devstorage.read_only"
]

creds, pid = google.auth.default()
print(f"Obtained default credentials for the project {pid}")
tcreds = google.auth.impersonated_credentials.Credentials(
    source_credentials=creds,
    target_principal="<target service account email>",
    target_scopes=target_scopes,
)

client = storage.Client(credentials=tcreds)
buckets = client.list_buckets(project=pid)
for bucket in buckets:
    print(bucket.name)
gogasca
  • 9,283
  • 6
  • 80
  • 125
Zaar Hai
  • 9,152
  • 8
  • 37
  • 45
  • thanks for the sample code! Are there target_scopes other than the `devstorage.read_only` ? – Nikhil VJ Jul 23 '21 at 02:01
  • 1
    Is this what you are after? https://developers.google.com/identity/protocols/oauth2/scopes – Zaar Hai Jul 23 '21 at 10:43
  • Is there any suggestion for using impersonation locally for testing but have it use its inherited default credentials once deployed? e.g. App Engine or Cloud Run rather than it trying to impersonate itself essentially. – Ari Sep 07 '21 at 06:49
  • 1
    I wrote quite a sophisticated boilerplate some time ago: https://gist.github.com/haizaar/fcf8ee4b98b2452c618582bca632a338 Covers both dev, CI, and prod scenarios – Zaar Hai Sep 08 '21 at 06:15
  • I modified the code to run in cloud function; The code also works in Cloud Shell. This avoids using Service account keys to impersonate the service account. Check the last section of my blog article at https://hilliao.medium.com/google-cloud-identity-impersonates-as-service-account-vs-using-google-groups-3bdf668cdc9f – Hil Liao Feb 10 '23 at 19:23
9

Instead of trying to impersonate a service account from a user account, grant the user permission to create a service account OAuth access token.

Grant the user the role roles/iam.serviceAccountTokenCreator on the service account. This role is called "Service Account Token Creator" in the web console.

Call the API generateAccessToken to create an access token from the service account.

projects.serviceAccounts.generateAccessToken

A simple HTTP POST request will return an access token. Modify the following request with the service account email address.

POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/SERVICE-ACCOUNT-NAME@PROJECTID.iam.gserviceaccount.com:generateAccessToken

Request Body:

{
  "delegates": [],
  "scope": [
      "https://www.googleapis.com/auth/cloud-platform"
  ],
  "lifetime": "3600s"
}

This API requires authorization. Include the user's OAuth access token in the HTTP Authorization header.

Authorization: Bearer ACCESS_TOKEN

Response Body:

{
   "accessToken": "eyJ0eXAifeA...NiK8i",
   "expireTime": "2020-03-05T15:01:00.12345678Z"
}
Josiah Yoder
  • 3,321
  • 4
  • 40
  • 58
John Hanley
  • 74,467
  • 6
  • 95
  • 159
  • This answer didn't save me hours, but it was the fulfillment of hours of searching before I found it! – Josiah Yoder Sep 05 '22 at 14:45
  • I think this is the correct way to use gcloud with a service account from a local development machine. – Josiah Yoder Sep 05 '22 at 14:46
  • But following these instructions was not trivial to me. I had to first perform two system calls running these two commands: `gcloud auth login` and `gcloud auth print-access-token`. This gave my gmail user account a local access token to use in the HTTP request above. – Josiah Yoder Sep 05 '22 at 14:47
  • Then I had to go into the IAM console. I couldn't give my gmail account the permissions directly on the service account because "Role cannot be edited as it is inherited from another resource," so I went to the IAM console and edited my gmail role there. I couldn't search for the internal role name `roles/iam.serviceAccountTokenCreator`, so I had to search for "Service Account Token Creator" instead. Then I had to wait a bit. My first attempt to use the HTTP request failed, but the second one succeeded. – Josiah Yoder Sep 05 '22 at 14:51
  • Thanks! How do I use this accessToken to create a google storage client object ? – Abhishek Poojary Jun 19 '23 at 12:05
  • @AbhishekPoojary - Create a new question. – John Hanley Jun 19 '23 at 15:46