blob: efa3127100b18a1b95f8c290a991e346db6f391a [file] [log] [blame]
Jon Wayne Parrott924191c2017-02-15 16:43:23 -08001# Copyright 2017 Google Inc.
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 exceptions
29
30_IAM_API_ROOT_URI = 'https://iam.googleapis.com/v1'
31_SIGN_BLOB_URI = (
32 _IAM_API_ROOT_URI + '/projects/-/serviceAccounts/{}:signBlob?alt=json')
33
34
35class Signer(object):
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 .. warning::
42 The IAM API signs bytes using Google-managed keys. Because of this
43 it's possible that the key used to sign bytes will change. In some
44 cases this change can occur between successive calls to :attr:`key_id`
45 and :meth:`sign`. This could result in a signature that was signed
46 with a different key than the one indicated by :attr:`key_id`. It's
47 recommended that if you use this in your code that you account for
48 this behavior by building in retry logic.
49
50 .. _signBlob API:
51 https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts
52 /signBlob
53 """
54
55 def __init__(self, request, credentials, service_account_email):
56 """
57 Args:
58 request (google.auth.transport.Request): The object used to make
59 HTTP requests.
60 credentials (google.auth.credentials.Credentials): The credentials
61 that will be used to authenticate the request to the IAM API.
62 The credentials must have of one the following scopes:
63
64 - https://www.googleapis.com/auth/iam
65 - https://www.googleapis.com/auth/cloud-platform
66 service_account_email (str): The service account email identifying
67 which service account to use to sign bytes. Often, this can
68 be the same as the service account email in the given
69 credentials.
70 """
71 self._request = request
72 self._credentials = credentials
73 self._service_account_email = service_account_email
74
75 def _make_signing_request(self, message):
76 """Makes a request to the API signBlob API."""
77 message = _helpers.to_bytes(message)
78
79 method = 'POST'
80 url = _SIGN_BLOB_URI.format(self._service_account_email)
81 headers = {}
82 body = json.dumps({
83 'bytesToSign': base64.b64encode(message).decode('utf-8'),
84 })
85
86 self._credentials.before_request(self._request, method, url, headers)
87 response = self._request(
88 url=url, method=method, body=body, headers=headers)
89
90 if response.status != http_client.OK:
91 raise exceptions.TransportError(
92 'Error calling the IAM signBytes API: {}'.format(
93 response.data))
94
95 return json.loads(response.data.decode('utf-8'))
96
97 @property
98 def key_id(self):
99 """Optional[str]: The key ID used to identify this private key.
100
101 .. note::
102 This makes an API request to the IAM API.
103 """
104 response = self._make_signing_request('')
105 return response['keyId']
106
107 def sign(self, message):
108 """Signs a message.
109
110 Args:
111 message (Union[str, bytes]): The message to be signed.
112
113 Returns:
114 bytes: The signature of the message.
115 """
116 response = self._make_signing_request(message)
117 return base64.b64decode(response['signature'])