# Copyright 2018 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import logging import google.auth import google.auth.app_engine import google.auth.compute_engine.credentials import google.auth.iam from google.auth.transport.requests import Request import google.oauth2.credentials import google.oauth2.service_account import requests_toolbelt.adapters.appengine IAM_SCOPE = 'https://www.googleapis.com/auth/iam' OAUTH_TOKEN_URI = 'https://www.googleapis.com/oauth2/v4/token' def get_auth_token(client_id): """Gets auth token from default service account. If no service account credential is found, returns None. """ service_account_credentials = get_service_account_credentials(client_id) if service_account_credentials: return get_google_open_id_connect_token(service_account_credentials) return None def get_service_account_credentials(client_id): # Figure out what environment we're running in and get some preliminary # information about the service account. bootstrap_credentials, _ = google.auth.default( scopes=[IAM_SCOPE]) if isinstance(bootstrap_credentials, google.oauth2.credentials.Credentials): logging.info('Found OAuth2 credentials and skip SA auth.') return None elif isinstance(bootstrap_credentials, google.auth.app_engine.Credentials): requests_toolbelt.adapters.appengine.monkeypatch() # For service account's using the Compute Engine metadata service, # service_account_email isn't available until refresh is called. bootstrap_credentials.refresh(Request()) signer_email = bootstrap_credentials.service_account_email if isinstance(bootstrap_credentials, google.auth.compute_engine.credentials.Credentials): # Since the Compute Engine metadata service doesn't expose the service # account key, we use the IAM signBlob API to sign instead. # In order for this to work: # # 1. Your VM needs the https://www.googleapis.com/auth/iam scope. # You can specify this specific scope when creating a VM # through the API or gcloud. When using Cloud Console, # you'll need to specify the "full access to all Cloud APIs" # scope. A VM's scopes can only be specified at creation time. # # 2. The VM's default service account needs the "Service Account Actor" # role. This can be found under the "Project" category in Cloud # Console, or roles/iam.serviceAccountActor in gcloud. signer = google.auth.iam.Signer( Request(), bootstrap_credentials, signer_email) else: # A Signer object can sign a JWT using the service account's key. signer = bootstrap_credentials.signer # Construct OAuth 2.0 service account credentials using the signer # and email acquired from the bootstrap credentials. return google.oauth2.service_account.Credentials( signer, signer_email, token_uri=OAUTH_TOKEN_URI, additional_claims={ 'target_audience': client_id }) def get_google_open_id_connect_token(service_account_credentials): """Get an OpenID Connect token issued by Google for the service account. This function: 1. Generates a JWT signed with the service account's private key containing a special "target_audience" claim. 2. Sends it to the OAUTH_TOKEN_URI endpoint. Because the JWT in #1 has a target_audience claim, that endpoint will respond with an OpenID Connect token for the service account -- in other words, a JWT signed by *Google*. The aud claim in this JWT will be set to the value from the target_audience claim in #1. For more information, see https://developers.google.com/identity/protocols/OAuth2ServiceAccount . The HTTP/REST example on that page describes the JWT structure and demonstrates how to call the token endpoint. (The example on that page shows how to get an OAuth2 access token; this code is using a modified version of it to get an OpenID Connect token.) """ service_account_jwt = ( service_account_credentials._make_authorization_grant_assertion()) request = google.auth.transport.requests.Request() body = { 'assertion': service_account_jwt, 'grant_type': google.oauth2._client._JWT_GRANT_TYPE, } token_response = google.oauth2._client._token_endpoint_request( request, OAUTH_TOKEN_URI, body) return token_response['id_token']