blob: 9a8f98eecc4733b45f6db47b55ed6f82177009d3 [file] [log] [blame]
bojeil-googled4d7f382021-02-16 12:33:20 -08001# Copyright 2020 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
15import datetime
16import json
17
18import mock
19import pytest
20from six.moves import http_client
21from six.moves import urllib
22
23from google.auth import _helpers
24from google.auth import aws
25from google.auth import environment_vars
26from google.auth import exceptions
27from google.auth import transport
28
29
30CLIENT_ID = "username"
31CLIENT_SECRET = "password"
32# Base64 encoding of "username:password".
33BASIC_AUTH_ENCODING = "dXNlcm5hbWU6cGFzc3dvcmQ="
34SERVICE_ACCOUNT_EMAIL = "service-1234@service-name.iam.gserviceaccount.com"
35SERVICE_ACCOUNT_IMPERSONATION_URL = (
36 "https://us-east1-iamcredentials.googleapis.com/v1/projects/-"
37 + "/serviceAccounts/{}:generateAccessToken".format(SERVICE_ACCOUNT_EMAIL)
38)
39QUOTA_PROJECT_ID = "QUOTA_PROJECT_ID"
40SCOPES = ["scope1", "scope2"]
41TOKEN_URL = "https://sts.googleapis.com/v1/token"
42SUBJECT_TOKEN_TYPE = "urn:ietf:params:aws:token-type:aws4_request"
43AUDIENCE = "//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID"
44REGION_URL = "http://169.254.169.254/latest/meta-data/placement/availability-zone"
45SECURITY_CREDS_URL = "http://169.254.169.254/latest/meta-data/iam/security-credentials"
46CRED_VERIFICATION_URL = (
47 "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
48)
49# Sample AWS security credentials to be used with tests that require a session token.
50ACCESS_KEY_ID = "ASIARD4OQDT6A77FR3CL"
51SECRET_ACCESS_KEY = "Y8AfSaucF37G4PpvfguKZ3/l7Id4uocLXxX0+VTx"
52TOKEN = "IQoJb3JpZ2luX2VjEIz//////////wEaCXVzLWVhc3QtMiJGMEQCIH7MHX/Oy/OB8OlLQa9GrqU1B914+iMikqWQW7vPCKlgAiA/Lsv8Jcafn14owfxXn95FURZNKaaphj0ykpmS+Ki+CSq0AwhlEAAaDDA3NzA3MTM5MTk5NiIMx9sAeP1ovlMTMKLjKpEDwuJQg41/QUKx0laTZYjPlQvjwSqS3OB9P1KAXPWSLkliVMMqaHqelvMF/WO/glv3KwuTfQsavRNs3v5pcSEm4SPO3l7mCs7KrQUHwGP0neZhIKxEXy+Ls//1C/Bqt53NL+LSbaGv6RPHaX82laz2qElphg95aVLdYgIFY6JWV5fzyjgnhz0DQmy62/Vi8pNcM2/VnxeCQ8CC8dRDSt52ry2v+nc77vstuI9xV5k8mPtnaPoJDRANh0bjwY5Sdwkbp+mGRUJBAQRlNgHUJusefXQgVKBCiyJY4w3Csd8Bgj9IyDV+Azuy1jQqfFZWgP68LSz5bURyIjlWDQunO82stZ0BgplKKAa/KJHBPCp8Qi6i99uy7qh76FQAqgVTsnDuU6fGpHDcsDSGoCls2HgZjZFPeOj8mmRhFk1Xqvkbjuz8V1cJk54d3gIJvQt8gD2D6yJQZecnuGWd5K2e2HohvCc8Fc9kBl1300nUJPV+k4tr/A5R/0QfEKOZL1/k5lf1g9CREnrM8LVkGxCgdYMxLQow1uTL+QU67AHRRSp5PhhGX4Rek+01vdYSnJCMaPhSEgcLqDlQkhk6MPsyT91QMXcWmyO+cAZwUPwnRamFepuP4K8k2KVXs/LIJHLELwAZ0ekyaS7CptgOqS7uaSTFG3U+vzFZLEnGvWQ7y9IPNQZ+Dffgh4p3vF4J68y9049sI6Sr5d5wbKkcbm8hdCDHZcv4lnqohquPirLiFQ3q7B17V9krMPu3mz1cg4Ekgcrn/E09NTsxAqD8NcZ7C7ECom9r+X3zkDOxaajW6hu3Az8hGlyylDaMiFfRbBJpTIlxp7jfa7CxikNgNtEKLH9iCzvuSg2vhA=="
53# To avoid json.dumps() differing behavior from one version to other,
54# the JSON payload is hardcoded.
55REQUEST_PARAMS = '{"KeySchema":[{"KeyType":"HASH","AttributeName":"Id"}],"TableName":"TestTable","AttributeDefinitions":[{"AttributeName":"Id","AttributeType":"S"}],"ProvisionedThroughput":{"WriteCapacityUnits":5,"ReadCapacityUnits":5}}'
56# Each tuple contains the following entries:
57# region, time, credentials, original_request, signed_request
58TEST_FIXTURES = [
59 # GET request (AWS botocore tests).
60 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-vanilla.req
61 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-vanilla.sreq
62 (
63 "us-east-1",
64 "2011-09-09T23:36:00Z",
65 {
66 "access_key_id": "AKIDEXAMPLE",
67 "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
68 },
69 {
70 "method": "GET",
71 "url": "https://host.foo.com",
72 "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT"},
73 },
74 {
75 "url": "https://host.foo.com",
76 "method": "GET",
77 "headers": {
78 "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
79 "host": "host.foo.com",
80 "date": "Mon, 09 Sep 2011 23:36:00 GMT",
81 },
82 },
83 ),
84 # GET request with relative path (AWS botocore tests).
85 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-relative-relative.req
86 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-relative-relative.sreq
87 (
88 "us-east-1",
89 "2011-09-09T23:36:00Z",
90 {
91 "access_key_id": "AKIDEXAMPLE",
92 "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
93 },
94 {
95 "method": "GET",
96 "url": "https://host.foo.com/foo/bar/../..",
97 "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT"},
98 },
99 {
100 "url": "https://host.foo.com/foo/bar/../..",
101 "method": "GET",
102 "headers": {
103 "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
104 "host": "host.foo.com",
105 "date": "Mon, 09 Sep 2011 23:36:00 GMT",
106 },
107 },
108 ),
109 # GET request with /./ path (AWS botocore tests).
110 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-slash-dot-slash.req
111 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-slash-dot-slash.sreq
112 (
113 "us-east-1",
114 "2011-09-09T23:36:00Z",
115 {
116 "access_key_id": "AKIDEXAMPLE",
117 "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
118 },
119 {
120 "method": "GET",
121 "url": "https://host.foo.com/./",
122 "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT"},
123 },
124 {
125 "url": "https://host.foo.com/./",
126 "method": "GET",
127 "headers": {
128 "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
129 "host": "host.foo.com",
130 "date": "Mon, 09 Sep 2011 23:36:00 GMT",
131 },
132 },
133 ),
134 # GET request with pointless dot path (AWS botocore tests).
135 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-slash-pointless-dot.req
136 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-slash-pointless-dot.sreq
137 (
138 "us-east-1",
139 "2011-09-09T23:36:00Z",
140 {
141 "access_key_id": "AKIDEXAMPLE",
142 "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
143 },
144 {
145 "method": "GET",
146 "url": "https://host.foo.com/./foo",
147 "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT"},
148 },
149 {
150 "url": "https://host.foo.com/./foo",
151 "method": "GET",
152 "headers": {
153 "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=910e4d6c9abafaf87898e1eb4c929135782ea25bb0279703146455745391e63a",
154 "host": "host.foo.com",
155 "date": "Mon, 09 Sep 2011 23:36:00 GMT",
156 },
157 },
158 ),
159 # GET request with utf8 path (AWS botocore tests).
160 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-utf8.req
161 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-utf8.sreq
162 (
163 "us-east-1",
164 "2011-09-09T23:36:00Z",
165 {
166 "access_key_id": "AKIDEXAMPLE",
167 "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
168 },
169 {
170 "method": "GET",
171 "url": "https://host.foo.com/%E1%88%B4",
172 "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT"},
173 },
174 {
175 "url": "https://host.foo.com/%E1%88%B4",
176 "method": "GET",
177 "headers": {
178 "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=8d6634c189aa8c75c2e51e106b6b5121bed103fdb351f7d7d4381c738823af74",
179 "host": "host.foo.com",
180 "date": "Mon, 09 Sep 2011 23:36:00 GMT",
181 },
182 },
183 ),
184 # GET request with duplicate query key (AWS botocore tests).
185 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-vanilla-query-order-key-case.req
186 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-vanilla-query-order-key-case.sreq
187 (
188 "us-east-1",
189 "2011-09-09T23:36:00Z",
190 {
191 "access_key_id": "AKIDEXAMPLE",
192 "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
193 },
194 {
195 "method": "GET",
196 "url": "https://host.foo.com/?foo=Zoo&foo=aha",
197 "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT"},
198 },
199 {
200 "url": "https://host.foo.com/?foo=Zoo&foo=aha",
201 "method": "GET",
202 "headers": {
203 "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=be7148d34ebccdc6423b19085378aa0bee970bdc61d144bd1a8c48c33079ab09",
204 "host": "host.foo.com",
205 "date": "Mon, 09 Sep 2011 23:36:00 GMT",
206 },
207 },
208 ),
209 # GET request with duplicate out of order query key (AWS botocore tests).
210 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-vanilla-query-order-value.req
211 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-vanilla-query-order-value.sreq
212 (
213 "us-east-1",
214 "2011-09-09T23:36:00Z",
215 {
216 "access_key_id": "AKIDEXAMPLE",
217 "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
218 },
219 {
220 "method": "GET",
221 "url": "https://host.foo.com/?foo=b&foo=a",
222 "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT"},
223 },
224 {
225 "url": "https://host.foo.com/?foo=b&foo=a",
226 "method": "GET",
227 "headers": {
228 "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=feb926e49e382bec75c9d7dcb2a1b6dc8aa50ca43c25d2bc51143768c0875acc",
229 "host": "host.foo.com",
230 "date": "Mon, 09 Sep 2011 23:36:00 GMT",
231 },
232 },
233 ),
234 # GET request with utf8 query (AWS botocore tests).
235 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-vanilla-ut8-query.req
236 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-vanilla-ut8-query.sreq
237 (
238 "us-east-1",
239 "2011-09-09T23:36:00Z",
240 {
241 "access_key_id": "AKIDEXAMPLE",
242 "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
243 },
244 {
245 "method": "GET",
246 "url": "https://host.foo.com/?{}=bar".format(
247 urllib.parse.unquote("%E1%88%B4")
248 ),
249 "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT"},
250 },
251 {
252 "url": "https://host.foo.com/?{}=bar".format(
253 urllib.parse.unquote("%E1%88%B4")
254 ),
255 "method": "GET",
256 "headers": {
257 "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=6fb359e9a05394cc7074e0feb42573a2601abc0c869a953e8c5c12e4e01f1a8c",
258 "host": "host.foo.com",
259 "date": "Mon, 09 Sep 2011 23:36:00 GMT",
260 },
261 },
262 ),
263 # POST request with sorted headers (AWS botocore tests).
264 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/post-header-key-sort.req
265 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/post-header-key-sort.sreq
266 (
267 "us-east-1",
268 "2011-09-09T23:36:00Z",
269 {
270 "access_key_id": "AKIDEXAMPLE",
271 "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
272 },
273 {
274 "method": "POST",
275 "url": "https://host.foo.com/",
276 "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT", "ZOO": "zoobar"},
277 },
278 {
279 "url": "https://host.foo.com/",
280 "method": "POST",
281 "headers": {
282 "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host;zoo, Signature=b7a95a52518abbca0964a999a880429ab734f35ebbf1235bd79a5de87756dc4a",
283 "host": "host.foo.com",
284 "date": "Mon, 09 Sep 2011 23:36:00 GMT",
285 "ZOO": "zoobar",
286 },
287 },
288 ),
289 # POST request with upper case header value from AWS Python test harness.
290 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/post-header-value-case.req
291 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/post-header-value-case.sreq
292 (
293 "us-east-1",
294 "2011-09-09T23:36:00Z",
295 {
296 "access_key_id": "AKIDEXAMPLE",
297 "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
298 },
299 {
300 "method": "POST",
301 "url": "https://host.foo.com/",
302 "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT", "zoo": "ZOOBAR"},
303 },
304 {
305 "url": "https://host.foo.com/",
306 "method": "POST",
307 "headers": {
308 "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host;zoo, Signature=273313af9d0c265c531e11db70bbd653f3ba074c1009239e8559d3987039cad7",
309 "host": "host.foo.com",
310 "date": "Mon, 09 Sep 2011 23:36:00 GMT",
311 "zoo": "ZOOBAR",
312 },
313 },
314 ),
315 # POST request with header and no body (AWS botocore tests).
316 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-header-value-trim.req
317 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-header-value-trim.sreq
318 (
319 "us-east-1",
320 "2011-09-09T23:36:00Z",
321 {
322 "access_key_id": "AKIDEXAMPLE",
323 "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
324 },
325 {
326 "method": "POST",
327 "url": "https://host.foo.com/",
328 "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT", "p": "phfft"},
329 },
330 {
331 "url": "https://host.foo.com/",
332 "method": "POST",
333 "headers": {
334 "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host;p, Signature=debf546796015d6f6ded8626f5ce98597c33b47b9164cf6b17b4642036fcb592",
335 "host": "host.foo.com",
336 "date": "Mon, 09 Sep 2011 23:36:00 GMT",
337 "p": "phfft",
338 },
339 },
340 ),
341 # POST request with body and no header (AWS botocore tests).
342 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/post-x-www-form-urlencoded.req
343 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/post-x-www-form-urlencoded.sreq
344 (
345 "us-east-1",
346 "2011-09-09T23:36:00Z",
347 {
348 "access_key_id": "AKIDEXAMPLE",
349 "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
350 },
351 {
352 "method": "POST",
353 "url": "https://host.foo.com/",
354 "headers": {
355 "Content-Type": "application/x-www-form-urlencoded",
356 "date": "Mon, 09 Sep 2011 23:36:00 GMT",
357 },
358 "data": "foo=bar",
359 },
360 {
361 "url": "https://host.foo.com/",
362 "method": "POST",
363 "headers": {
364 "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=content-type;date;host, Signature=5a15b22cf462f047318703b92e6f4f38884e4a7ab7b1d6426ca46a8bd1c26cbc",
365 "host": "host.foo.com",
366 "Content-Type": "application/x-www-form-urlencoded",
367 "date": "Mon, 09 Sep 2011 23:36:00 GMT",
368 },
369 "data": "foo=bar",
370 },
371 ),
372 # POST request with querystring (AWS botocore tests).
373 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/post-vanilla-query.req
374 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/post-vanilla-query.sreq
375 (
376 "us-east-1",
377 "2011-09-09T23:36:00Z",
378 {
379 "access_key_id": "AKIDEXAMPLE",
380 "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
381 },
382 {
383 "method": "POST",
384 "url": "https://host.foo.com/?foo=bar",
385 "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT"},
386 },
387 {
388 "url": "https://host.foo.com/?foo=bar",
389 "method": "POST",
390 "headers": {
391 "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b6e3b79003ce0743a491606ba1035a804593b0efb1e20a11cba83f8c25a57a92",
392 "host": "host.foo.com",
393 "date": "Mon, 09 Sep 2011 23:36:00 GMT",
394 },
395 },
396 ),
397 # GET request with session token credentials.
398 (
399 "us-east-2",
400 "2020-08-11T06:55:22Z",
401 {
402 "access_key_id": ACCESS_KEY_ID,
403 "secret_access_key": SECRET_ACCESS_KEY,
404 "security_token": TOKEN,
405 },
406 {
407 "method": "GET",
408 "url": "https://ec2.us-east-2.amazonaws.com?Action=DescribeRegions&Version=2013-10-15",
409 },
410 {
411 "url": "https://ec2.us-east-2.amazonaws.com?Action=DescribeRegions&Version=2013-10-15",
412 "method": "GET",
413 "headers": {
414 "Authorization": "AWS4-HMAC-SHA256 Credential="
415 + ACCESS_KEY_ID
416 + "/20200811/us-east-2/ec2/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=631ea80cddfaa545fdadb120dc92c9f18166e38a5c47b50fab9fce476e022855",
417 "host": "ec2.us-east-2.amazonaws.com",
418 "x-amz-date": "20200811T065522Z",
419 "x-amz-security-token": TOKEN,
420 },
421 },
422 ),
423 # POST request with session token credentials.
424 (
425 "us-east-2",
426 "2020-08-11T06:55:22Z",
427 {
428 "access_key_id": ACCESS_KEY_ID,
429 "secret_access_key": SECRET_ACCESS_KEY,
430 "security_token": TOKEN,
431 },
432 {
433 "method": "POST",
434 "url": "https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15",
435 },
436 {
437 "url": "https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15",
438 "method": "POST",
439 "headers": {
440 "Authorization": "AWS4-HMAC-SHA256 Credential="
441 + ACCESS_KEY_ID
442 + "/20200811/us-east-2/sts/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=73452984e4a880ffdc5c392355733ec3f5ba310d5e0609a89244440cadfe7a7a",
443 "host": "sts.us-east-2.amazonaws.com",
444 "x-amz-date": "20200811T065522Z",
445 "x-amz-security-token": TOKEN,
446 },
447 },
448 ),
449 # POST request with computed x-amz-date and no data.
450 (
451 "us-east-2",
452 "2020-08-11T06:55:22Z",
453 {"access_key_id": ACCESS_KEY_ID, "secret_access_key": SECRET_ACCESS_KEY},
454 {
455 "method": "POST",
456 "url": "https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15",
457 },
458 {
459 "url": "https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15",
460 "method": "POST",
461 "headers": {
462 "Authorization": "AWS4-HMAC-SHA256 Credential="
463 + ACCESS_KEY_ID
464 + "/20200811/us-east-2/sts/aws4_request, SignedHeaders=host;x-amz-date, Signature=d095ba304919cd0d5570ba8a3787884ee78b860f268ed040ba23831d55536d56",
465 "host": "sts.us-east-2.amazonaws.com",
466 "x-amz-date": "20200811T065522Z",
467 },
468 },
469 ),
470 # POST request with session token and additional headers/data.
471 (
472 "us-east-2",
473 "2020-08-11T06:55:22Z",
474 {
475 "access_key_id": ACCESS_KEY_ID,
476 "secret_access_key": SECRET_ACCESS_KEY,
477 "security_token": TOKEN,
478 },
479 {
480 "method": "POST",
481 "url": "https://dynamodb.us-east-2.amazonaws.com/",
482 "headers": {
483 "Content-Type": "application/x-amz-json-1.0",
484 "x-amz-target": "DynamoDB_20120810.CreateTable",
485 },
486 "data": REQUEST_PARAMS,
487 },
488 {
489 "url": "https://dynamodb.us-east-2.amazonaws.com/",
490 "method": "POST",
491 "headers": {
492 "Authorization": "AWS4-HMAC-SHA256 Credential="
493 + ACCESS_KEY_ID
494 + "/20200811/us-east-2/dynamodb/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-security-token;x-amz-target, Signature=fdaa5b9cc9c86b80fe61eaf504141c0b3523780349120f2bd8145448456e0385",
495 "host": "dynamodb.us-east-2.amazonaws.com",
496 "x-amz-date": "20200811T065522Z",
497 "Content-Type": "application/x-amz-json-1.0",
498 "x-amz-target": "DynamoDB_20120810.CreateTable",
499 "x-amz-security-token": TOKEN,
500 },
501 "data": REQUEST_PARAMS,
502 },
503 ),
504]
505
506
507class TestRequestSigner(object):
508 @pytest.mark.parametrize(
509 "region, time, credentials, original_request, signed_request", TEST_FIXTURES
510 )
511 @mock.patch("google.auth._helpers.utcnow")
512 def test_get_request_options(
513 self, utcnow, region, time, credentials, original_request, signed_request
514 ):
515 utcnow.return_value = datetime.datetime.strptime(time, "%Y-%m-%dT%H:%M:%SZ")
516 request_signer = aws.RequestSigner(region)
517 actual_signed_request = request_signer.get_request_options(
518 credentials,
519 original_request.get("url"),
520 original_request.get("method"),
521 original_request.get("data"),
522 original_request.get("headers"),
523 )
524
525 assert actual_signed_request == signed_request
526
527 def test_get_request_options_with_missing_scheme_url(self):
528 request_signer = aws.RequestSigner("us-east-2")
529
530 with pytest.raises(ValueError) as excinfo:
531 request_signer.get_request_options(
532 {
533 "access_key_id": ACCESS_KEY_ID,
534 "secret_access_key": SECRET_ACCESS_KEY,
535 },
536 "invalid",
537 "POST",
538 )
539
540 assert excinfo.match(r"Invalid AWS service URL")
541
542 def test_get_request_options_with_invalid_scheme_url(self):
543 request_signer = aws.RequestSigner("us-east-2")
544
545 with pytest.raises(ValueError) as excinfo:
546 request_signer.get_request_options(
547 {
548 "access_key_id": ACCESS_KEY_ID,
549 "secret_access_key": SECRET_ACCESS_KEY,
550 },
551 "http://invalid",
552 "POST",
553 )
554
555 assert excinfo.match(r"Invalid AWS service URL")
556
557 def test_get_request_options_with_missing_hostname_url(self):
558 request_signer = aws.RequestSigner("us-east-2")
559
560 with pytest.raises(ValueError) as excinfo:
561 request_signer.get_request_options(
562 {
563 "access_key_id": ACCESS_KEY_ID,
564 "secret_access_key": SECRET_ACCESS_KEY,
565 },
566 "https://",
567 "POST",
568 )
569
570 assert excinfo.match(r"Invalid AWS service URL")
571
572
573class TestCredentials(object):
574 AWS_REGION = "us-east-2"
575 AWS_ROLE = "gcp-aws-role"
576 AWS_SECURITY_CREDENTIALS_RESPONSE = {
577 "AccessKeyId": ACCESS_KEY_ID,
578 "SecretAccessKey": SECRET_ACCESS_KEY,
579 "Token": TOKEN,
580 }
581 AWS_SIGNATURE_TIME = "2020-08-11T06:55:22Z"
582 CREDENTIAL_SOURCE = {
583 "environment_id": "aws1",
584 "region_url": REGION_URL,
585 "url": SECURITY_CREDS_URL,
586 "regional_cred_verification_url": CRED_VERIFICATION_URL,
587 }
588 SUCCESS_RESPONSE = {
589 "access_token": "ACCESS_TOKEN",
590 "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
591 "token_type": "Bearer",
592 "expires_in": 3600,
593 "scope": " ".join(SCOPES),
594 }
595
596 @classmethod
597 def make_serialized_aws_signed_request(
598 cls,
599 aws_security_credentials,
600 region_name="us-east-2",
601 url="https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15",
602 ):
603 """Utility to generate serialize AWS signed requests.
604 This makes it easy to assert generated subject tokens based on the
605 provided AWS security credentials, regions and AWS STS endpoint.
606 """
607 request_signer = aws.RequestSigner(region_name)
608 signed_request = request_signer.get_request_options(
609 aws_security_credentials, url, "POST"
610 )
611 reformatted_signed_request = {
612 "url": signed_request.get("url"),
613 "method": signed_request.get("method"),
614 "headers": [
615 {
616 "key": "Authorization",
617 "value": signed_request.get("headers").get("Authorization"),
618 },
619 {"key": "host", "value": signed_request.get("headers").get("host")},
620 {
621 "key": "x-amz-date",
622 "value": signed_request.get("headers").get("x-amz-date"),
623 },
624 ],
625 }
626 # Include security token if available.
627 if "security_token" in aws_security_credentials:
628 reformatted_signed_request.get("headers").append(
629 {
630 "key": "x-amz-security-token",
631 "value": signed_request.get("headers").get("x-amz-security-token"),
632 }
633 )
634 # Append x-goog-cloud-target-resource header.
635 reformatted_signed_request.get("headers").append(
636 {"key": "x-goog-cloud-target-resource", "value": AUDIENCE}
637 ),
638 return urllib.parse.quote(
639 json.dumps(
640 reformatted_signed_request, separators=(",", ":"), sort_keys=True
641 )
642 )
643
644 @classmethod
645 def make_mock_request(
646 cls,
647 region_status=None,
648 region_name=None,
649 role_status=None,
650 role_name=None,
651 security_credentials_status=None,
652 security_credentials_data=None,
653 token_status=None,
654 token_data=None,
655 impersonation_status=None,
656 impersonation_data=None,
657 ):
658 """Utility function to generate a mock HTTP request object.
659 This will facilitate testing various edge cases by specify how the
660 various endpoints will respond while generating a Google Access token
661 in an AWS environment.
662 """
663 responses = []
664 if region_status:
665 # AWS region request.
666 region_response = mock.create_autospec(transport.Response, instance=True)
667 region_response.status = region_status
668 if region_name:
669 region_response.data = "{}b".format(region_name).encode("utf-8")
670 responses.append(region_response)
671
672 if role_status:
673 # AWS role name request.
674 role_response = mock.create_autospec(transport.Response, instance=True)
675 role_response.status = role_status
676 if role_name:
677 role_response.data = role_name.encode("utf-8")
678 responses.append(role_response)
679
680 if security_credentials_status:
681 # AWS security credentials request.
682 security_credentials_response = mock.create_autospec(
683 transport.Response, instance=True
684 )
685 security_credentials_response.status = security_credentials_status
686 if security_credentials_data:
687 security_credentials_response.data = json.dumps(
688 security_credentials_data
689 ).encode("utf-8")
690 responses.append(security_credentials_response)
691
692 if token_status:
693 # GCP token exchange request.
694 token_response = mock.create_autospec(transport.Response, instance=True)
695 token_response.status = token_status
696 token_response.data = json.dumps(token_data).encode("utf-8")
697 responses.append(token_response)
698
699 if impersonation_status:
700 # Service account impersonation request.
701 impersonation_response = mock.create_autospec(
702 transport.Response, instance=True
703 )
704 impersonation_response.status = impersonation_status
705 impersonation_response.data = json.dumps(impersonation_data).encode("utf-8")
706 responses.append(impersonation_response)
707
708 request = mock.create_autospec(transport.Request)
709 request.side_effect = responses
710
711 return request
712
713 @classmethod
714 def make_credentials(
715 cls,
716 credential_source,
717 client_id=None,
718 client_secret=None,
719 quota_project_id=None,
720 scopes=None,
721 default_scopes=None,
722 service_account_impersonation_url=None,
723 ):
724 return aws.Credentials(
725 audience=AUDIENCE,
726 subject_token_type=SUBJECT_TOKEN_TYPE,
727 token_url=TOKEN_URL,
728 service_account_impersonation_url=service_account_impersonation_url,
729 credential_source=credential_source,
730 client_id=client_id,
731 client_secret=client_secret,
732 quota_project_id=quota_project_id,
733 scopes=scopes,
734 default_scopes=default_scopes,
735 )
736
737 @classmethod
738 def assert_aws_metadata_request_kwargs(cls, request_kwargs, url, headers=None):
739 assert request_kwargs["url"] == url
740 # All used AWS metadata server endpoints use GET HTTP method.
741 assert request_kwargs["method"] == "GET"
742 if headers:
743 assert request_kwargs["headers"] == headers
744 else:
745 assert "headers" not in request_kwargs
746 # None of the endpoints used require any data in request.
747 assert "body" not in request_kwargs
748
749 @classmethod
750 def assert_token_request_kwargs(
751 cls, request_kwargs, headers, request_data, token_url=TOKEN_URL
752 ):
753 assert request_kwargs["url"] == token_url
754 assert request_kwargs["method"] == "POST"
755 assert request_kwargs["headers"] == headers
756 assert request_kwargs["body"] is not None
757 body_tuples = urllib.parse.parse_qsl(request_kwargs["body"])
758 assert len(body_tuples) == len(request_data.keys())
759 for (k, v) in body_tuples:
760 assert v.decode("utf-8") == request_data[k.decode("utf-8")]
761
762 @classmethod
763 def assert_impersonation_request_kwargs(
764 cls,
765 request_kwargs,
766 headers,
767 request_data,
768 service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL,
769 ):
770 assert request_kwargs["url"] == service_account_impersonation_url
771 assert request_kwargs["method"] == "POST"
772 assert request_kwargs["headers"] == headers
773 assert request_kwargs["body"] is not None
774 body_json = json.loads(request_kwargs["body"].decode("utf-8"))
775 assert body_json == request_data
776
777 @mock.patch.object(aws.Credentials, "__init__", return_value=None)
778 def test_from_info_full_options(self, mock_init):
779 credentials = aws.Credentials.from_info(
780 {
781 "audience": AUDIENCE,
782 "subject_token_type": SUBJECT_TOKEN_TYPE,
783 "token_url": TOKEN_URL,
784 "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL,
785 "client_id": CLIENT_ID,
786 "client_secret": CLIENT_SECRET,
787 "quota_project_id": QUOTA_PROJECT_ID,
788 "credential_source": self.CREDENTIAL_SOURCE,
789 }
790 )
791
792 # Confirm aws.Credentials instance initialized with the expected parameters.
793 assert isinstance(credentials, aws.Credentials)
794 mock_init.assert_called_once_with(
795 audience=AUDIENCE,
796 subject_token_type=SUBJECT_TOKEN_TYPE,
797 token_url=TOKEN_URL,
798 service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL,
799 client_id=CLIENT_ID,
800 client_secret=CLIENT_SECRET,
801 credential_source=self.CREDENTIAL_SOURCE,
802 quota_project_id=QUOTA_PROJECT_ID,
803 )
804
805 @mock.patch.object(aws.Credentials, "__init__", return_value=None)
806 def test_from_info_required_options_only(self, mock_init):
807 credentials = aws.Credentials.from_info(
808 {
809 "audience": AUDIENCE,
810 "subject_token_type": SUBJECT_TOKEN_TYPE,
811 "token_url": TOKEN_URL,
812 "credential_source": self.CREDENTIAL_SOURCE,
813 }
814 )
815
816 # Confirm aws.Credentials instance initialized with the expected parameters.
817 assert isinstance(credentials, aws.Credentials)
818 mock_init.assert_called_once_with(
819 audience=AUDIENCE,
820 subject_token_type=SUBJECT_TOKEN_TYPE,
821 token_url=TOKEN_URL,
822 service_account_impersonation_url=None,
823 client_id=None,
824 client_secret=None,
825 credential_source=self.CREDENTIAL_SOURCE,
826 quota_project_id=None,
827 )
828
829 @mock.patch.object(aws.Credentials, "__init__", return_value=None)
830 def test_from_file_full_options(self, mock_init, tmpdir):
831 info = {
832 "audience": AUDIENCE,
833 "subject_token_type": SUBJECT_TOKEN_TYPE,
834 "token_url": TOKEN_URL,
835 "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL,
836 "client_id": CLIENT_ID,
837 "client_secret": CLIENT_SECRET,
838 "quota_project_id": QUOTA_PROJECT_ID,
839 "credential_source": self.CREDENTIAL_SOURCE,
840 }
841 config_file = tmpdir.join("config.json")
842 config_file.write(json.dumps(info))
843 credentials = aws.Credentials.from_file(str(config_file))
844
845 # Confirm aws.Credentials instance initialized with the expected parameters.
846 assert isinstance(credentials, aws.Credentials)
847 mock_init.assert_called_once_with(
848 audience=AUDIENCE,
849 subject_token_type=SUBJECT_TOKEN_TYPE,
850 token_url=TOKEN_URL,
851 service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL,
852 client_id=CLIENT_ID,
853 client_secret=CLIENT_SECRET,
854 credential_source=self.CREDENTIAL_SOURCE,
855 quota_project_id=QUOTA_PROJECT_ID,
856 )
857
858 @mock.patch.object(aws.Credentials, "__init__", return_value=None)
859 def test_from_file_required_options_only(self, mock_init, tmpdir):
860 info = {
861 "audience": AUDIENCE,
862 "subject_token_type": SUBJECT_TOKEN_TYPE,
863 "token_url": TOKEN_URL,
864 "credential_source": self.CREDENTIAL_SOURCE,
865 }
866 config_file = tmpdir.join("config.json")
867 config_file.write(json.dumps(info))
868 credentials = aws.Credentials.from_file(str(config_file))
869
870 # Confirm aws.Credentials instance initialized with the expected parameters.
871 assert isinstance(credentials, aws.Credentials)
872 mock_init.assert_called_once_with(
873 audience=AUDIENCE,
874 subject_token_type=SUBJECT_TOKEN_TYPE,
875 token_url=TOKEN_URL,
876 service_account_impersonation_url=None,
877 client_id=None,
878 client_secret=None,
879 credential_source=self.CREDENTIAL_SOURCE,
880 quota_project_id=None,
881 )
882
883 def test_constructor_invalid_credential_source(self):
884 # Provide invalid credential source.
885 credential_source = {"unsupported": "value"}
886
887 with pytest.raises(ValueError) as excinfo:
888 self.make_credentials(credential_source=credential_source)
889
890 assert excinfo.match(r"No valid AWS 'credential_source' provided")
891
892 def test_constructor_invalid_environment_id(self):
893 # Provide invalid environment_id.
894 credential_source = self.CREDENTIAL_SOURCE.copy()
895 credential_source["environment_id"] = "azure1"
896
897 with pytest.raises(ValueError) as excinfo:
898 self.make_credentials(credential_source=credential_source)
899
900 assert excinfo.match(r"No valid AWS 'credential_source' provided")
901
902 def test_constructor_missing_cred_verification_url(self):
903 # regional_cred_verification_url is a required field.
904 credential_source = self.CREDENTIAL_SOURCE.copy()
905 credential_source.pop("regional_cred_verification_url")
906
907 with pytest.raises(ValueError) as excinfo:
908 self.make_credentials(credential_source=credential_source)
909
910 assert excinfo.match(r"No valid AWS 'credential_source' provided")
911
912 def test_constructor_invalid_environment_id_version(self):
913 # Provide an unsupported version.
914 credential_source = self.CREDENTIAL_SOURCE.copy()
915 credential_source["environment_id"] = "aws3"
916
917 with pytest.raises(ValueError) as excinfo:
918 self.make_credentials(credential_source=credential_source)
919
920 assert excinfo.match(r"aws version '3' is not supported in the current build.")
921
922 def test_retrieve_subject_token_missing_region_url(self):
923 # When AWS_REGION envvar is not available, region_url is required for
924 # determining the current AWS region.
925 credential_source = self.CREDENTIAL_SOURCE.copy()
926 credential_source.pop("region_url")
927 credentials = self.make_credentials(credential_source=credential_source)
928
929 with pytest.raises(exceptions.RefreshError) as excinfo:
930 credentials.retrieve_subject_token(None)
931
932 assert excinfo.match(r"Unable to determine AWS region")
933
934 @mock.patch("google.auth._helpers.utcnow")
935 def test_retrieve_subject_token_success_temp_creds_no_environment_vars(
936 self, utcnow
937 ):
938 utcnow.return_value = datetime.datetime.strptime(
939 self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
940 )
941 request = self.make_mock_request(
942 region_status=http_client.OK,
943 region_name=self.AWS_REGION,
944 role_status=http_client.OK,
945 role_name=self.AWS_ROLE,
946 security_credentials_status=http_client.OK,
947 security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
948 )
949 credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
950
951 subject_token = credentials.retrieve_subject_token(request)
952
953 assert subject_token == self.make_serialized_aws_signed_request(
954 {
955 "access_key_id": ACCESS_KEY_ID,
956 "secret_access_key": SECRET_ACCESS_KEY,
957 "security_token": TOKEN,
958 }
959 )
960 # Assert region request.
961 self.assert_aws_metadata_request_kwargs(
962 request.call_args_list[0].kwargs, REGION_URL
963 )
964 # Assert role request.
965 self.assert_aws_metadata_request_kwargs(
966 request.call_args_list[1].kwargs, SECURITY_CREDS_URL
967 )
968 # Assert security credentials request.
969 self.assert_aws_metadata_request_kwargs(
970 request.call_args_list[2].kwargs,
971 "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE),
972 {"Content-Type": "application/json"},
973 )
974
975 # Retrieve subject_token again. Region should not be queried again.
976 new_request = self.make_mock_request(
977 role_status=http_client.OK,
978 role_name=self.AWS_ROLE,
979 security_credentials_status=http_client.OK,
980 security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
981 )
982
983 credentials.retrieve_subject_token(new_request)
984
985 # Only 2 requests should be sent as the region is cached.
986 assert len(new_request.call_args_list) == 2
987 # Assert role request.
988 self.assert_aws_metadata_request_kwargs(
989 new_request.call_args_list[0].kwargs, SECURITY_CREDS_URL
990 )
991 # Assert security credentials request.
992 self.assert_aws_metadata_request_kwargs(
993 new_request.call_args_list[1].kwargs,
994 "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE),
995 {"Content-Type": "application/json"},
996 )
997
998 @mock.patch("google.auth._helpers.utcnow")
999 def test_retrieve_subject_token_success_permanent_creds_no_environment_vars(
1000 self, utcnow
1001 ):
1002 # Simualte a permanent credential without a session token is
1003 # returned by the security-credentials endpoint.
1004 security_creds_response = self.AWS_SECURITY_CREDENTIALS_RESPONSE.copy()
1005 security_creds_response.pop("Token")
1006 utcnow.return_value = datetime.datetime.strptime(
1007 self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
1008 )
1009 request = self.make_mock_request(
1010 region_status=http_client.OK,
1011 region_name=self.AWS_REGION,
1012 role_status=http_client.OK,
1013 role_name=self.AWS_ROLE,
1014 security_credentials_status=http_client.OK,
1015 security_credentials_data=security_creds_response,
1016 )
1017 credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
1018
1019 subject_token = credentials.retrieve_subject_token(request)
1020
1021 assert subject_token == self.make_serialized_aws_signed_request(
1022 {"access_key_id": ACCESS_KEY_ID, "secret_access_key": SECRET_ACCESS_KEY}
1023 )
1024
1025 @mock.patch("google.auth._helpers.utcnow")
1026 def test_retrieve_subject_token_success_environment_vars(self, utcnow, monkeypatch):
1027 monkeypatch.setenv(environment_vars.AWS_ACCESS_KEY_ID, ACCESS_KEY_ID)
1028 monkeypatch.setenv(environment_vars.AWS_SECRET_ACCESS_KEY, SECRET_ACCESS_KEY)
1029 monkeypatch.setenv(environment_vars.AWS_SESSION_TOKEN, TOKEN)
1030 monkeypatch.setenv(environment_vars.AWS_REGION, self.AWS_REGION)
1031 utcnow.return_value = datetime.datetime.strptime(
1032 self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
1033 )
1034 credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
1035
1036 subject_token = credentials.retrieve_subject_token(None)
1037
1038 assert subject_token == self.make_serialized_aws_signed_request(
1039 {
1040 "access_key_id": ACCESS_KEY_ID,
1041 "secret_access_key": SECRET_ACCESS_KEY,
1042 "security_token": TOKEN,
1043 }
1044 )
1045
1046 @mock.patch("google.auth._helpers.utcnow")
1047 def test_retrieve_subject_token_success_environment_vars_no_session_token(
1048 self, utcnow, monkeypatch
1049 ):
1050 monkeypatch.setenv(environment_vars.AWS_ACCESS_KEY_ID, ACCESS_KEY_ID)
1051 monkeypatch.setenv(environment_vars.AWS_SECRET_ACCESS_KEY, SECRET_ACCESS_KEY)
1052 monkeypatch.setenv(environment_vars.AWS_REGION, self.AWS_REGION)
1053 utcnow.return_value = datetime.datetime.strptime(
1054 self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
1055 )
1056 credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
1057
1058 subject_token = credentials.retrieve_subject_token(None)
1059
1060 assert subject_token == self.make_serialized_aws_signed_request(
1061 {"access_key_id": ACCESS_KEY_ID, "secret_access_key": SECRET_ACCESS_KEY}
1062 )
1063
1064 @mock.patch("google.auth._helpers.utcnow")
1065 def test_retrieve_subject_token_success_environment_vars_except_region(
1066 self, utcnow, monkeypatch
1067 ):
1068 monkeypatch.setenv(environment_vars.AWS_ACCESS_KEY_ID, ACCESS_KEY_ID)
1069 monkeypatch.setenv(environment_vars.AWS_SECRET_ACCESS_KEY, SECRET_ACCESS_KEY)
1070 monkeypatch.setenv(environment_vars.AWS_SESSION_TOKEN, TOKEN)
1071 utcnow.return_value = datetime.datetime.strptime(
1072 self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
1073 )
1074 # Region will be queried since it is not found in envvars.
1075 request = self.make_mock_request(
1076 region_status=http_client.OK, region_name=self.AWS_REGION
1077 )
1078 credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
1079
1080 subject_token = credentials.retrieve_subject_token(request)
1081
1082 assert subject_token == self.make_serialized_aws_signed_request(
1083 {
1084 "access_key_id": ACCESS_KEY_ID,
1085 "secret_access_key": SECRET_ACCESS_KEY,
1086 "security_token": TOKEN,
1087 }
1088 )
1089
1090 def test_retrieve_subject_token_error_determining_aws_region(self):
1091 # Simulate error in retrieving the AWS region.
1092 request = self.make_mock_request(region_status=http_client.BAD_REQUEST)
1093 credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
1094
1095 with pytest.raises(exceptions.RefreshError) as excinfo:
1096 credentials.retrieve_subject_token(request)
1097
1098 assert excinfo.match(r"Unable to retrieve AWS region")
1099
1100 def test_retrieve_subject_token_error_determining_aws_role(self):
1101 # Simulate error in retrieving the AWS role name.
1102 request = self.make_mock_request(
1103 region_status=http_client.OK,
1104 region_name=self.AWS_REGION,
1105 role_status=http_client.BAD_REQUEST,
1106 )
1107 credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
1108
1109 with pytest.raises(exceptions.RefreshError) as excinfo:
1110 credentials.retrieve_subject_token(request)
1111
1112 assert excinfo.match(r"Unable to retrieve AWS role name")
1113
1114 def test_retrieve_subject_token_error_determining_security_creds_url(self):
1115 # Simulate the security-credentials url is missing. This is needed for
1116 # determining the AWS security credentials when not found in envvars.
1117 credential_source = self.CREDENTIAL_SOURCE.copy()
1118 credential_source.pop("url")
1119 request = self.make_mock_request(
1120 region_status=http_client.OK, region_name=self.AWS_REGION
1121 )
1122 credentials = self.make_credentials(credential_source=credential_source)
1123
1124 with pytest.raises(exceptions.RefreshError) as excinfo:
1125 credentials.retrieve_subject_token(request)
1126
1127 assert excinfo.match(
1128 r"Unable to determine the AWS metadata server security credentials endpoint"
1129 )
1130
1131 def test_retrieve_subject_token_error_determining_aws_security_creds(self):
1132 # Simulate error in retrieving the AWS security credentials.
1133 request = self.make_mock_request(
1134 region_status=http_client.OK,
1135 region_name=self.AWS_REGION,
1136 role_status=http_client.OK,
1137 role_name=self.AWS_ROLE,
1138 security_credentials_status=http_client.BAD_REQUEST,
1139 )
1140 credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
1141
1142 with pytest.raises(exceptions.RefreshError) as excinfo:
1143 credentials.retrieve_subject_token(request)
1144
1145 assert excinfo.match(r"Unable to retrieve AWS security credentials")
1146
1147 @mock.patch("google.auth._helpers.utcnow")
1148 def test_refresh_success_without_impersonation_ignore_default_scopes(self, utcnow):
1149 utcnow.return_value = datetime.datetime.strptime(
1150 self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
1151 )
1152 expected_subject_token = self.make_serialized_aws_signed_request(
1153 {
1154 "access_key_id": ACCESS_KEY_ID,
1155 "secret_access_key": SECRET_ACCESS_KEY,
1156 "security_token": TOKEN,
1157 }
1158 )
1159 token_headers = {
1160 "Content-Type": "application/x-www-form-urlencoded",
1161 "Authorization": "Basic " + BASIC_AUTH_ENCODING,
1162 }
1163 token_request_data = {
1164 "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
1165 "audience": AUDIENCE,
1166 "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
1167 "scope": " ".join(SCOPES),
1168 "subject_token": expected_subject_token,
1169 "subject_token_type": SUBJECT_TOKEN_TYPE,
1170 }
1171 request = self.make_mock_request(
1172 region_status=http_client.OK,
1173 region_name=self.AWS_REGION,
1174 role_status=http_client.OK,
1175 role_name=self.AWS_ROLE,
1176 security_credentials_status=http_client.OK,
1177 security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
1178 token_status=http_client.OK,
1179 token_data=self.SUCCESS_RESPONSE,
1180 )
1181 credentials = self.make_credentials(
1182 client_id=CLIENT_ID,
1183 client_secret=CLIENT_SECRET,
1184 credential_source=self.CREDENTIAL_SOURCE,
1185 quota_project_id=QUOTA_PROJECT_ID,
1186 scopes=SCOPES,
1187 # Default scopes should be ignored.
1188 default_scopes=["ignored"],
1189 )
1190
1191 credentials.refresh(request)
1192
1193 assert len(request.call_args_list) == 4
1194 # Fourth request should be sent to GCP STS endpoint.
1195 self.assert_token_request_kwargs(
1196 request.call_args_list[3].kwargs, token_headers, token_request_data
1197 )
1198 assert credentials.token == self.SUCCESS_RESPONSE["access_token"]
1199 assert credentials.quota_project_id == QUOTA_PROJECT_ID
1200 assert credentials.scopes == SCOPES
1201 assert credentials.default_scopes == ["ignored"]
1202
1203 @mock.patch("google.auth._helpers.utcnow")
1204 def test_refresh_success_without_impersonation_use_default_scopes(self, utcnow):
1205 utcnow.return_value = datetime.datetime.strptime(
1206 self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
1207 )
1208 expected_subject_token = self.make_serialized_aws_signed_request(
1209 {
1210 "access_key_id": ACCESS_KEY_ID,
1211 "secret_access_key": SECRET_ACCESS_KEY,
1212 "security_token": TOKEN,
1213 }
1214 )
1215 token_headers = {
1216 "Content-Type": "application/x-www-form-urlencoded",
1217 "Authorization": "Basic " + BASIC_AUTH_ENCODING,
1218 }
1219 token_request_data = {
1220 "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
1221 "audience": AUDIENCE,
1222 "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
1223 "scope": " ".join(SCOPES),
1224 "subject_token": expected_subject_token,
1225 "subject_token_type": SUBJECT_TOKEN_TYPE,
1226 }
1227 request = self.make_mock_request(
1228 region_status=http_client.OK,
1229 region_name=self.AWS_REGION,
1230 role_status=http_client.OK,
1231 role_name=self.AWS_ROLE,
1232 security_credentials_status=http_client.OK,
1233 security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
1234 token_status=http_client.OK,
1235 token_data=self.SUCCESS_RESPONSE,
1236 )
1237 credentials = self.make_credentials(
1238 client_id=CLIENT_ID,
1239 client_secret=CLIENT_SECRET,
1240 credential_source=self.CREDENTIAL_SOURCE,
1241 quota_project_id=QUOTA_PROJECT_ID,
1242 scopes=None,
1243 # Default scopes should be used since user specified scopes are none.
1244 default_scopes=SCOPES,
1245 )
1246
1247 credentials.refresh(request)
1248
1249 assert len(request.call_args_list) == 4
1250 # Fourth request should be sent to GCP STS endpoint.
1251 self.assert_token_request_kwargs(
1252 request.call_args_list[3].kwargs, token_headers, token_request_data
1253 )
1254 assert credentials.token == self.SUCCESS_RESPONSE["access_token"]
1255 assert credentials.quota_project_id == QUOTA_PROJECT_ID
1256 assert credentials.scopes is None
1257 assert credentials.default_scopes == SCOPES
1258
1259 @mock.patch("google.auth._helpers.utcnow")
1260 def test_refresh_success_with_impersonation_ignore_default_scopes(self, utcnow):
1261 utcnow.return_value = datetime.datetime.strptime(
1262 self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
1263 )
1264 expire_time = (
1265 _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=3600)
1266 ).isoformat("T") + "Z"
1267 expected_subject_token = self.make_serialized_aws_signed_request(
1268 {
1269 "access_key_id": ACCESS_KEY_ID,
1270 "secret_access_key": SECRET_ACCESS_KEY,
1271 "security_token": TOKEN,
1272 }
1273 )
1274 token_headers = {
1275 "Content-Type": "application/x-www-form-urlencoded",
1276 "Authorization": "Basic " + BASIC_AUTH_ENCODING,
1277 }
1278 token_request_data = {
1279 "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
1280 "audience": AUDIENCE,
1281 "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
1282 "scope": "https://www.googleapis.com/auth/iam",
1283 "subject_token": expected_subject_token,
1284 "subject_token_type": SUBJECT_TOKEN_TYPE,
1285 }
1286 # Service account impersonation request/response.
1287 impersonation_response = {
1288 "accessToken": "SA_ACCESS_TOKEN",
1289 "expireTime": expire_time,
1290 }
1291 impersonation_headers = {
1292 "Content-Type": "application/json",
1293 "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]),
1294 "x-goog-user-project": QUOTA_PROJECT_ID,
1295 }
1296 impersonation_request_data = {
1297 "delegates": None,
1298 "scope": SCOPES,
1299 "lifetime": "3600s",
1300 }
1301 request = self.make_mock_request(
1302 region_status=http_client.OK,
1303 region_name=self.AWS_REGION,
1304 role_status=http_client.OK,
1305 role_name=self.AWS_ROLE,
1306 security_credentials_status=http_client.OK,
1307 security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
1308 token_status=http_client.OK,
1309 token_data=self.SUCCESS_RESPONSE,
1310 impersonation_status=http_client.OK,
1311 impersonation_data=impersonation_response,
1312 )
1313 credentials = self.make_credentials(
1314 client_id=CLIENT_ID,
1315 client_secret=CLIENT_SECRET,
1316 credential_source=self.CREDENTIAL_SOURCE,
1317 service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL,
1318 quota_project_id=QUOTA_PROJECT_ID,
1319 scopes=SCOPES,
1320 # Default scopes should be ignored.
1321 default_scopes=["ignored"],
1322 )
1323
1324 credentials.refresh(request)
1325
1326 assert len(request.call_args_list) == 5
1327 # Fourth request should be sent to GCP STS endpoint.
1328 self.assert_token_request_kwargs(
1329 request.call_args_list[3].kwargs, token_headers, token_request_data
1330 )
1331 # Fifth request should be sent to iamcredentials endpoint for service
1332 # account impersonation.
1333 self.assert_impersonation_request_kwargs(
1334 request.call_args_list[4].kwargs,
1335 impersonation_headers,
1336 impersonation_request_data,
1337 )
1338 assert credentials.token == impersonation_response["accessToken"]
1339 assert credentials.quota_project_id == QUOTA_PROJECT_ID
1340 assert credentials.scopes == SCOPES
1341 assert credentials.default_scopes == ["ignored"]
1342
1343 @mock.patch("google.auth._helpers.utcnow")
1344 def test_refresh_success_with_impersonation_use_default_scopes(self, utcnow):
1345 utcnow.return_value = datetime.datetime.strptime(
1346 self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
1347 )
1348 expire_time = (
1349 _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=3600)
1350 ).isoformat("T") + "Z"
1351 expected_subject_token = self.make_serialized_aws_signed_request(
1352 {
1353 "access_key_id": ACCESS_KEY_ID,
1354 "secret_access_key": SECRET_ACCESS_KEY,
1355 "security_token": TOKEN,
1356 }
1357 )
1358 token_headers = {
1359 "Content-Type": "application/x-www-form-urlencoded",
1360 "Authorization": "Basic " + BASIC_AUTH_ENCODING,
1361 }
1362 token_request_data = {
1363 "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
1364 "audience": AUDIENCE,
1365 "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
1366 "scope": "https://www.googleapis.com/auth/iam",
1367 "subject_token": expected_subject_token,
1368 "subject_token_type": SUBJECT_TOKEN_TYPE,
1369 }
1370 # Service account impersonation request/response.
1371 impersonation_response = {
1372 "accessToken": "SA_ACCESS_TOKEN",
1373 "expireTime": expire_time,
1374 }
1375 impersonation_headers = {
1376 "Content-Type": "application/json",
1377 "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]),
1378 "x-goog-user-project": QUOTA_PROJECT_ID,
1379 }
1380 impersonation_request_data = {
1381 "delegates": None,
1382 "scope": SCOPES,
1383 "lifetime": "3600s",
1384 }
1385 request = self.make_mock_request(
1386 region_status=http_client.OK,
1387 region_name=self.AWS_REGION,
1388 role_status=http_client.OK,
1389 role_name=self.AWS_ROLE,
1390 security_credentials_status=http_client.OK,
1391 security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
1392 token_status=http_client.OK,
1393 token_data=self.SUCCESS_RESPONSE,
1394 impersonation_status=http_client.OK,
1395 impersonation_data=impersonation_response,
1396 )
1397 credentials = self.make_credentials(
1398 client_id=CLIENT_ID,
1399 client_secret=CLIENT_SECRET,
1400 credential_source=self.CREDENTIAL_SOURCE,
1401 service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL,
1402 quota_project_id=QUOTA_PROJECT_ID,
1403 scopes=None,
1404 # Default scopes should be used since user specified scopes are none.
1405 default_scopes=SCOPES,
1406 )
1407
1408 credentials.refresh(request)
1409
1410 assert len(request.call_args_list) == 5
1411 # Fourth request should be sent to GCP STS endpoint.
1412 self.assert_token_request_kwargs(
1413 request.call_args_list[3].kwargs, token_headers, token_request_data
1414 )
1415 # Fifth request should be sent to iamcredentials endpoint for service
1416 # account impersonation.
1417 self.assert_impersonation_request_kwargs(
1418 request.call_args_list[4].kwargs,
1419 impersonation_headers,
1420 impersonation_request_data,
1421 )
1422 assert credentials.token == impersonation_response["accessToken"]
1423 assert credentials.quota_project_id == QUOTA_PROJECT_ID
1424 assert credentials.scopes is None
1425 assert credentials.default_scopes == SCOPES
1426
1427 def test_refresh_with_retrieve_subject_token_error(self):
1428 request = self.make_mock_request(region_status=http_client.BAD_REQUEST)
1429 credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
1430
1431 with pytest.raises(exceptions.RefreshError) as excinfo:
1432 credentials.refresh(request)
1433
1434 assert excinfo.match(r"Unable to retrieve AWS region")