1# Copyright 2017 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Tools for using the Google `Cloud Identity and Access Management (IAM) 16API`_'s auth-related functionality. 17 18.. _Cloud Identity and Access Management (IAM) API: 19 https://cloud.google.com/iam/docs/ 20""" 21 22import base64 23import json 24 25from six.moves import http_client 26 27from google.auth import _helpers 28from google.auth import crypt 29from google.auth import exceptions 30 31_IAM_API_ROOT_URI = "https://iamcredentials.googleapis.com/v1" 32_SIGN_BLOB_URI = _IAM_API_ROOT_URI + "/projects/-/serviceAccounts/{}:signBlob?alt=json" 33 34 35class Signer(crypt.Signer): 36 """Signs messages using the IAM `signBlob API`_. 37 38 This is useful when you need to sign bytes but do not have access to the 39 credential's private key file. 40 41 .. _signBlob API: 42 https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts 43 /signBlob 44 """ 45 46 def __init__(self, request, credentials, service_account_email): 47 """ 48 Args: 49 request (google.auth.transport.Request): The object used to make 50 HTTP requests. 51 credentials (google.auth.credentials.Credentials): The credentials 52 that will be used to authenticate the request to the IAM API. 53 The credentials must have of one the following scopes: 54 55 - https://www.googleapis.com/auth/iam 56 - https://www.googleapis.com/auth/cloud-platform 57 service_account_email (str): The service account email identifying 58 which service account to use to sign bytes. Often, this can 59 be the same as the service account email in the given 60 credentials. 61 """ 62 self._request = request 63 self._credentials = credentials 64 self._service_account_email = service_account_email 65 66 def _make_signing_request(self, message): 67 """Makes a request to the API signBlob API.""" 68 message = _helpers.to_bytes(message) 69 70 method = "POST" 71 url = _SIGN_BLOB_URI.format(self._service_account_email) 72 headers = {"Content-Type": "application/json"} 73 body = json.dumps( 74 {"payload": base64.b64encode(message).decode("utf-8")} 75 ).encode("utf-8") 76 77 self._credentials.before_request(self._request, method, url, headers) 78 response = self._request(url=url, method=method, body=body, headers=headers) 79 80 if response.status != http_client.OK: 81 raise exceptions.TransportError( 82 "Error calling the IAM signBlob API: {}".format(response.data) 83 ) 84 85 return json.loads(response.data.decode("utf-8")) 86 87 @property 88 def key_id(self): 89 """Optional[str]: The key ID used to identify this private key. 90 91 .. warning:: 92 This is always ``None``. The key ID used by IAM can not 93 be reliably determined ahead of time. 94 """ 95 return None 96 97 @_helpers.copy_docstring(crypt.Signer) 98 def sign(self, message): 99 response = self._make_signing_request(message) 100 return base64.b64decode(response["signedBlob"]) 101