Programmatically get current Service Account on GCP

9.5k views Asked by At

Is there a way to programmatically access the email of the currently used Service Account on a GCP instance when no GOOGLE_APPLICATION_CREDENTIALS is set? (ie. when using the default Service Account)

I've looked through the GCP documentation, but the only resource I found can't be used with the default Service Account when no GOOGLE_APPLICATION_CREDENTIALS is set. I know that it is possible to do so using gcloud (see this SO question or documentation), however these solutions aren't applicable when running on a ContainerOptimisedOS. I've spent a couple of weeks back and forth with the GCP support team, but they concluded with not being able to help me and redirected me to Stack Overflow for help.

5

There are 5 answers

13
guillaume blaquiere On BEST ANSWER

The solution of John works great, on any language without any external library. However, it works only on Google Cloud environment, when a metadata server is deployed. You can't perform this test on your computer.

I propose just bellow a piece of Python code (with Google OAuth library, but it works in other languages that have this library) to ask the library the current credential. If the credential is a service account (from GOOGLE_APPLICATION_CREDENTIALS on your computer, the ADC (Application Default Credential) or from the metadata server), you have the email printed, else, you have warning message because you use your user account credential

    import google.auth

    credentials, project_id = google.auth.default()

    if hasattr(credentials, "service_account_email"):
      print(credentials.service_account_email)
    else:
        print("WARNING: no service account credential. User account credential?")

Note that if the default service account is used this method will print default instead of the entire email address.


EDIT 1

    ctx := context.Background()
    credential,err := google.FindDefaultCredentials(ctx)
    content := map[string]interface{}{}

    json.Unmarshal(credential.JSON,&content)
    if content["client_email"] != nil {
      fmt.Println(content["client_email"])
    } else {
      fmt.Println("WARNING: no service account credential. User account credential?")
    }

0
red888 On

Just adding to the accepted answer. As stated in the answer this seems to return "default":

import google.auth

credentials, project_id = google.auth.default()
# returns "default"
print(credentials.service_account_email) 

I found to get the email name of the GSA currently active (via the metadata api) I had to manually refresh first:

import google.auth
import google.auth.transport.requests

credentials, project_id = google.auth.default()
request = google.auth.transport.requests.Request()
credentials.refresh(request=request)
# returns "[email protected]"
print(credentials.service_account_email) 

I'm using workload ID. I think maybe there is a race condition and I'm trying to read the service_account_email property before the creds get initialized for the first time when the pod starts.

The _retrieve_info() function is called when refresh() is called and it appears this is the function that grabs the email name.

If I had my script sleep for a few seconds on start up, I wonder if service_account_email would eventually be populated with the email name of the GSA.

0
Greg Dubicki On

Querying the GCP metadata server should work on any GCP compute instance, including, ContainerOptimisedOS and without using gcloud SDK, as the OP requested.

Example:

curl --header "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email"

0
no steppin' on snekin' On

If you are interested in getting the exact email and not just the "default" alias (when you are using the compute engine), you can fetch it using the credentials metadata. This was particularly helpful in determining which service account is being used by AI Platform jobs.

    import google.auth
    from google.auth.transport.requests import Request
    from google.auth.compute_engine import _metadata
    
    if hasattr(credentials, "service_account_email"):
        print(credentials.service_account_email)

        # edits made on top of the top answer's code
        info = _metadata.get_service_account_info(Request(),
                service_account=credentials.service_account_email)

        print(f"Service account email: {info.email}")
        print(f"Service account aliases: {info.aliases}")
        print(f"Service account scopes: {info.scopes}")

    else:
        print("WARNING: no service account credentials available.")
2
Vincent Yin On

Further exploring the Python code snippet posted by Guillaume Blaquiere...

I believe the code will throw an Exception instead of going to the else: branch if we supply an individual user account. Here's the demo setup:

$ python -V
Python 3.8.10


$ pip list | grep google
google-auth    2.14.0


$ gcloud config get-value account
...
[email protected]  <==== My individual (personal) email address


$ env | grep GOOGLE_APPLICATION_CREDENTIALS
    <==== Nothing, meaning this env var isn't set.


  ## I don't have the following file which means
  ## that I did NOT do `gcloud auth application-default login`
$ ls -l ~/.config/gcloud/application_default_credentials.json
ls: /Users/vyin/.config/gcloud/application_default_credentials.json: No such file or directory


$ cat test.py
import google.auth

credentials, project_id = google.auth.default()

if hasattr(credentials, "service_account_email"):
    print(credentials.service_account_email)
else:
    print("WARNING: no service account credential. User account credential?")

Now test it:

  ## Throws an Exception as I believe it should.
$ python test.py

Traceback (most recent call last):
  File "test.py", line 3, in <module>
    credentials, project_id = google.auth.default()
  File "/....../.venv/lib/python3.8/site-packages/google/auth/_default.py", line 643, in default
    raise exceptions.DefaultCredentialsError(_HELP_MESSAGE)
google.auth.exceptions.DefaultCredentialsError: 
Could not automatically determine credentials. 
Please set GOOGLE_APPLICATION_CREDENTIALS or explicitly create credentials and re-run the application. 
For more information, please see https://cloud.google.com/docs/authentication/getting-started


  ## Now create an ADC:
$ gcloud auth application-default login


  ## The above command created this file:
$ ls -l ~/.config/gcloud/application_default_credentials.json
-rw-------  ... Apr 12 17:48 /Users/vyin/.config/gcloud/application_default_credentials.json


  ## The Python code now enters the `else:` branch.

$ python test.py
WARNING: no service account credential. User account credential?

Conclusion:

  1. The else: branch isn't for user account at all -- a user account results in an exception.
  2. The else: branch is for the Application Default Credential (ADC).