blob: 7c7ee36bee2108cbeed85d8befe66a080041bd56 [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(
arithmetic1728d80c85f2021-03-08 13:35:44 -0800962 request.call_args_list[0][1], REGION_URL
bojeil-googled4d7f382021-02-16 12:33:20 -0800963 )
964 # Assert role request.
965 self.assert_aws_metadata_request_kwargs(
arithmetic1728d80c85f2021-03-08 13:35:44 -0800966 request.call_args_list[1][1], SECURITY_CREDS_URL
bojeil-googled4d7f382021-02-16 12:33:20 -0800967 )
968 # Assert security credentials request.
969 self.assert_aws_metadata_request_kwargs(
arithmetic1728d80c85f2021-03-08 13:35:44 -0800970 request.call_args_list[2][1],
bojeil-googled4d7f382021-02-16 12:33:20 -0800971 "{}/{}".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(
arithmetic1728d80c85f2021-03-08 13:35:44 -0800989 new_request.call_args_list[0][1], SECURITY_CREDS_URL
bojeil-googled4d7f382021-02-16 12:33:20 -0800990 )
991 # Assert security credentials request.
992 self.assert_aws_metadata_request_kwargs(
arithmetic1728d80c85f2021-03-08 13:35:44 -0800993 new_request.call_args_list[1][1],
bojeil-googled4d7f382021-02-16 12:33:20 -0800994 "{}/{}".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")
Ryan Kohler199da472021-03-12 16:04:02 -08001047 def test_retrieve_subject_token_success_environment_vars_with_default_region(
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_SESSION_TOKEN, TOKEN)
1053 monkeypatch.setenv(environment_vars.AWS_DEFAULT_REGION, self.AWS_REGION)
1054 utcnow.return_value = datetime.datetime.strptime(
1055 self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
1056 )
1057 credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
1058
1059 subject_token = credentials.retrieve_subject_token(None)
1060
1061 assert subject_token == self.make_serialized_aws_signed_request(
1062 {
1063 "access_key_id": ACCESS_KEY_ID,
1064 "secret_access_key": SECRET_ACCESS_KEY,
1065 "security_token": TOKEN,
1066 }
1067 )
1068
1069 @mock.patch("google.auth._helpers.utcnow")
1070 def test_retrieve_subject_token_success_environment_vars_with_both_regions_set(
1071 self, utcnow, monkeypatch
1072 ):
1073 monkeypatch.setenv(environment_vars.AWS_ACCESS_KEY_ID, ACCESS_KEY_ID)
1074 monkeypatch.setenv(environment_vars.AWS_SECRET_ACCESS_KEY, SECRET_ACCESS_KEY)
1075 monkeypatch.setenv(environment_vars.AWS_SESSION_TOKEN, TOKEN)
1076 monkeypatch.setenv(environment_vars.AWS_DEFAULT_REGION, "Malformed AWS Region")
1077 # This test makes sure that the AWS_REGION gets used over AWS_DEFAULT_REGION,
1078 # So, AWS_DEFAULT_REGION is set to something that would cause the test to fail,
1079 # And AWS_REGION is set to the a valid value, and it should succeed
1080 monkeypatch.setenv(environment_vars.AWS_REGION, self.AWS_REGION)
1081 utcnow.return_value = datetime.datetime.strptime(
1082 self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
1083 )
1084 credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
1085
1086 subject_token = credentials.retrieve_subject_token(None)
1087
1088 assert subject_token == self.make_serialized_aws_signed_request(
1089 {
1090 "access_key_id": ACCESS_KEY_ID,
1091 "secret_access_key": SECRET_ACCESS_KEY,
1092 "security_token": TOKEN,
1093 }
1094 )
1095
1096 @mock.patch("google.auth._helpers.utcnow")
bojeil-googled4d7f382021-02-16 12:33:20 -08001097 def test_retrieve_subject_token_success_environment_vars_no_session_token(
1098 self, utcnow, monkeypatch
1099 ):
1100 monkeypatch.setenv(environment_vars.AWS_ACCESS_KEY_ID, ACCESS_KEY_ID)
1101 monkeypatch.setenv(environment_vars.AWS_SECRET_ACCESS_KEY, SECRET_ACCESS_KEY)
1102 monkeypatch.setenv(environment_vars.AWS_REGION, self.AWS_REGION)
1103 utcnow.return_value = datetime.datetime.strptime(
1104 self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
1105 )
1106 credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
1107
1108 subject_token = credentials.retrieve_subject_token(None)
1109
1110 assert subject_token == self.make_serialized_aws_signed_request(
1111 {"access_key_id": ACCESS_KEY_ID, "secret_access_key": SECRET_ACCESS_KEY}
1112 )
1113
1114 @mock.patch("google.auth._helpers.utcnow")
1115 def test_retrieve_subject_token_success_environment_vars_except_region(
1116 self, utcnow, monkeypatch
1117 ):
1118 monkeypatch.setenv(environment_vars.AWS_ACCESS_KEY_ID, ACCESS_KEY_ID)
1119 monkeypatch.setenv(environment_vars.AWS_SECRET_ACCESS_KEY, SECRET_ACCESS_KEY)
1120 monkeypatch.setenv(environment_vars.AWS_SESSION_TOKEN, TOKEN)
1121 utcnow.return_value = datetime.datetime.strptime(
1122 self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
1123 )
1124 # Region will be queried since it is not found in envvars.
1125 request = self.make_mock_request(
1126 region_status=http_client.OK, region_name=self.AWS_REGION
1127 )
1128 credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
1129
1130 subject_token = credentials.retrieve_subject_token(request)
1131
1132 assert subject_token == self.make_serialized_aws_signed_request(
1133 {
1134 "access_key_id": ACCESS_KEY_ID,
1135 "secret_access_key": SECRET_ACCESS_KEY,
1136 "security_token": TOKEN,
1137 }
1138 )
1139
1140 def test_retrieve_subject_token_error_determining_aws_region(self):
1141 # Simulate error in retrieving the AWS region.
1142 request = self.make_mock_request(region_status=http_client.BAD_REQUEST)
1143 credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
1144
1145 with pytest.raises(exceptions.RefreshError) as excinfo:
1146 credentials.retrieve_subject_token(request)
1147
1148 assert excinfo.match(r"Unable to retrieve AWS region")
1149
1150 def test_retrieve_subject_token_error_determining_aws_role(self):
1151 # Simulate error in retrieving the AWS role name.
1152 request = self.make_mock_request(
1153 region_status=http_client.OK,
1154 region_name=self.AWS_REGION,
1155 role_status=http_client.BAD_REQUEST,
1156 )
1157 credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
1158
1159 with pytest.raises(exceptions.RefreshError) as excinfo:
1160 credentials.retrieve_subject_token(request)
1161
1162 assert excinfo.match(r"Unable to retrieve AWS role name")
1163
1164 def test_retrieve_subject_token_error_determining_security_creds_url(self):
1165 # Simulate the security-credentials url is missing. This is needed for
1166 # determining the AWS security credentials when not found in envvars.
1167 credential_source = self.CREDENTIAL_SOURCE.copy()
1168 credential_source.pop("url")
1169 request = self.make_mock_request(
1170 region_status=http_client.OK, region_name=self.AWS_REGION
1171 )
1172 credentials = self.make_credentials(credential_source=credential_source)
1173
1174 with pytest.raises(exceptions.RefreshError) as excinfo:
1175 credentials.retrieve_subject_token(request)
1176
1177 assert excinfo.match(
1178 r"Unable to determine the AWS metadata server security credentials endpoint"
1179 )
1180
1181 def test_retrieve_subject_token_error_determining_aws_security_creds(self):
1182 # Simulate error in retrieving the AWS security credentials.
1183 request = self.make_mock_request(
1184 region_status=http_client.OK,
1185 region_name=self.AWS_REGION,
1186 role_status=http_client.OK,
1187 role_name=self.AWS_ROLE,
1188 security_credentials_status=http_client.BAD_REQUEST,
1189 )
1190 credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
1191
1192 with pytest.raises(exceptions.RefreshError) as excinfo:
1193 credentials.retrieve_subject_token(request)
1194
1195 assert excinfo.match(r"Unable to retrieve AWS security credentials")
1196
1197 @mock.patch("google.auth._helpers.utcnow")
1198 def test_refresh_success_without_impersonation_ignore_default_scopes(self, utcnow):
1199 utcnow.return_value = datetime.datetime.strptime(
1200 self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
1201 )
1202 expected_subject_token = self.make_serialized_aws_signed_request(
1203 {
1204 "access_key_id": ACCESS_KEY_ID,
1205 "secret_access_key": SECRET_ACCESS_KEY,
1206 "security_token": TOKEN,
1207 }
1208 )
1209 token_headers = {
1210 "Content-Type": "application/x-www-form-urlencoded",
1211 "Authorization": "Basic " + BASIC_AUTH_ENCODING,
1212 }
1213 token_request_data = {
1214 "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
1215 "audience": AUDIENCE,
1216 "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
1217 "scope": " ".join(SCOPES),
1218 "subject_token": expected_subject_token,
1219 "subject_token_type": SUBJECT_TOKEN_TYPE,
1220 }
1221 request = self.make_mock_request(
1222 region_status=http_client.OK,
1223 region_name=self.AWS_REGION,
1224 role_status=http_client.OK,
1225 role_name=self.AWS_ROLE,
1226 security_credentials_status=http_client.OK,
1227 security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
1228 token_status=http_client.OK,
1229 token_data=self.SUCCESS_RESPONSE,
1230 )
1231 credentials = self.make_credentials(
1232 client_id=CLIENT_ID,
1233 client_secret=CLIENT_SECRET,
1234 credential_source=self.CREDENTIAL_SOURCE,
1235 quota_project_id=QUOTA_PROJECT_ID,
1236 scopes=SCOPES,
1237 # Default scopes should be ignored.
1238 default_scopes=["ignored"],
1239 )
1240
1241 credentials.refresh(request)
1242
1243 assert len(request.call_args_list) == 4
1244 # Fourth request should be sent to GCP STS endpoint.
1245 self.assert_token_request_kwargs(
arithmetic1728d80c85f2021-03-08 13:35:44 -08001246 request.call_args_list[3][1], token_headers, token_request_data
bojeil-googled4d7f382021-02-16 12:33:20 -08001247 )
1248 assert credentials.token == self.SUCCESS_RESPONSE["access_token"]
1249 assert credentials.quota_project_id == QUOTA_PROJECT_ID
1250 assert credentials.scopes == SCOPES
1251 assert credentials.default_scopes == ["ignored"]
1252
1253 @mock.patch("google.auth._helpers.utcnow")
1254 def test_refresh_success_without_impersonation_use_default_scopes(self, utcnow):
1255 utcnow.return_value = datetime.datetime.strptime(
1256 self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
1257 )
1258 expected_subject_token = self.make_serialized_aws_signed_request(
1259 {
1260 "access_key_id": ACCESS_KEY_ID,
1261 "secret_access_key": SECRET_ACCESS_KEY,
1262 "security_token": TOKEN,
1263 }
1264 )
1265 token_headers = {
1266 "Content-Type": "application/x-www-form-urlencoded",
1267 "Authorization": "Basic " + BASIC_AUTH_ENCODING,
1268 }
1269 token_request_data = {
1270 "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
1271 "audience": AUDIENCE,
1272 "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
1273 "scope": " ".join(SCOPES),
1274 "subject_token": expected_subject_token,
1275 "subject_token_type": SUBJECT_TOKEN_TYPE,
1276 }
1277 request = self.make_mock_request(
1278 region_status=http_client.OK,
1279 region_name=self.AWS_REGION,
1280 role_status=http_client.OK,
1281 role_name=self.AWS_ROLE,
1282 security_credentials_status=http_client.OK,
1283 security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
1284 token_status=http_client.OK,
1285 token_data=self.SUCCESS_RESPONSE,
1286 )
1287 credentials = self.make_credentials(
1288 client_id=CLIENT_ID,
1289 client_secret=CLIENT_SECRET,
1290 credential_source=self.CREDENTIAL_SOURCE,
1291 quota_project_id=QUOTA_PROJECT_ID,
1292 scopes=None,
1293 # Default scopes should be used since user specified scopes are none.
1294 default_scopes=SCOPES,
1295 )
1296
1297 credentials.refresh(request)
1298
1299 assert len(request.call_args_list) == 4
1300 # Fourth request should be sent to GCP STS endpoint.
1301 self.assert_token_request_kwargs(
arithmetic1728d80c85f2021-03-08 13:35:44 -08001302 request.call_args_list[3][1], token_headers, token_request_data
bojeil-googled4d7f382021-02-16 12:33:20 -08001303 )
1304 assert credentials.token == self.SUCCESS_RESPONSE["access_token"]
1305 assert credentials.quota_project_id == QUOTA_PROJECT_ID
1306 assert credentials.scopes is None
1307 assert credentials.default_scopes == SCOPES
1308
1309 @mock.patch("google.auth._helpers.utcnow")
1310 def test_refresh_success_with_impersonation_ignore_default_scopes(self, utcnow):
1311 utcnow.return_value = datetime.datetime.strptime(
1312 self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
1313 )
1314 expire_time = (
1315 _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=3600)
1316 ).isoformat("T") + "Z"
1317 expected_subject_token = self.make_serialized_aws_signed_request(
1318 {
1319 "access_key_id": ACCESS_KEY_ID,
1320 "secret_access_key": SECRET_ACCESS_KEY,
1321 "security_token": TOKEN,
1322 }
1323 )
1324 token_headers = {
1325 "Content-Type": "application/x-www-form-urlencoded",
1326 "Authorization": "Basic " + BASIC_AUTH_ENCODING,
1327 }
1328 token_request_data = {
1329 "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
1330 "audience": AUDIENCE,
1331 "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
1332 "scope": "https://www.googleapis.com/auth/iam",
1333 "subject_token": expected_subject_token,
1334 "subject_token_type": SUBJECT_TOKEN_TYPE,
1335 }
1336 # Service account impersonation request/response.
1337 impersonation_response = {
1338 "accessToken": "SA_ACCESS_TOKEN",
1339 "expireTime": expire_time,
1340 }
1341 impersonation_headers = {
1342 "Content-Type": "application/json",
1343 "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]),
1344 "x-goog-user-project": QUOTA_PROJECT_ID,
1345 }
1346 impersonation_request_data = {
1347 "delegates": None,
1348 "scope": SCOPES,
1349 "lifetime": "3600s",
1350 }
1351 request = self.make_mock_request(
1352 region_status=http_client.OK,
1353 region_name=self.AWS_REGION,
1354 role_status=http_client.OK,
1355 role_name=self.AWS_ROLE,
1356 security_credentials_status=http_client.OK,
1357 security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
1358 token_status=http_client.OK,
1359 token_data=self.SUCCESS_RESPONSE,
1360 impersonation_status=http_client.OK,
1361 impersonation_data=impersonation_response,
1362 )
1363 credentials = self.make_credentials(
1364 client_id=CLIENT_ID,
1365 client_secret=CLIENT_SECRET,
1366 credential_source=self.CREDENTIAL_SOURCE,
1367 service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL,
1368 quota_project_id=QUOTA_PROJECT_ID,
1369 scopes=SCOPES,
1370 # Default scopes should be ignored.
1371 default_scopes=["ignored"],
1372 )
1373
1374 credentials.refresh(request)
1375
1376 assert len(request.call_args_list) == 5
1377 # Fourth request should be sent to GCP STS endpoint.
1378 self.assert_token_request_kwargs(
arithmetic1728d80c85f2021-03-08 13:35:44 -08001379 request.call_args_list[3][1], token_headers, token_request_data
bojeil-googled4d7f382021-02-16 12:33:20 -08001380 )
1381 # Fifth request should be sent to iamcredentials endpoint for service
1382 # account impersonation.
1383 self.assert_impersonation_request_kwargs(
arithmetic1728d80c85f2021-03-08 13:35:44 -08001384 request.call_args_list[4][1],
bojeil-googled4d7f382021-02-16 12:33:20 -08001385 impersonation_headers,
1386 impersonation_request_data,
1387 )
1388 assert credentials.token == impersonation_response["accessToken"]
1389 assert credentials.quota_project_id == QUOTA_PROJECT_ID
1390 assert credentials.scopes == SCOPES
1391 assert credentials.default_scopes == ["ignored"]
1392
1393 @mock.patch("google.auth._helpers.utcnow")
1394 def test_refresh_success_with_impersonation_use_default_scopes(self, utcnow):
1395 utcnow.return_value = datetime.datetime.strptime(
1396 self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
1397 )
1398 expire_time = (
1399 _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=3600)
1400 ).isoformat("T") + "Z"
1401 expected_subject_token = self.make_serialized_aws_signed_request(
1402 {
1403 "access_key_id": ACCESS_KEY_ID,
1404 "secret_access_key": SECRET_ACCESS_KEY,
1405 "security_token": TOKEN,
1406 }
1407 )
1408 token_headers = {
1409 "Content-Type": "application/x-www-form-urlencoded",
1410 "Authorization": "Basic " + BASIC_AUTH_ENCODING,
1411 }
1412 token_request_data = {
1413 "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
1414 "audience": AUDIENCE,
1415 "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
1416 "scope": "https://www.googleapis.com/auth/iam",
1417 "subject_token": expected_subject_token,
1418 "subject_token_type": SUBJECT_TOKEN_TYPE,
1419 }
1420 # Service account impersonation request/response.
1421 impersonation_response = {
1422 "accessToken": "SA_ACCESS_TOKEN",
1423 "expireTime": expire_time,
1424 }
1425 impersonation_headers = {
1426 "Content-Type": "application/json",
1427 "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]),
1428 "x-goog-user-project": QUOTA_PROJECT_ID,
1429 }
1430 impersonation_request_data = {
1431 "delegates": None,
1432 "scope": SCOPES,
1433 "lifetime": "3600s",
1434 }
1435 request = self.make_mock_request(
1436 region_status=http_client.OK,
1437 region_name=self.AWS_REGION,
1438 role_status=http_client.OK,
1439 role_name=self.AWS_ROLE,
1440 security_credentials_status=http_client.OK,
1441 security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
1442 token_status=http_client.OK,
1443 token_data=self.SUCCESS_RESPONSE,
1444 impersonation_status=http_client.OK,
1445 impersonation_data=impersonation_response,
1446 )
1447 credentials = self.make_credentials(
1448 client_id=CLIENT_ID,
1449 client_secret=CLIENT_SECRET,
1450 credential_source=self.CREDENTIAL_SOURCE,
1451 service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL,
1452 quota_project_id=QUOTA_PROJECT_ID,
1453 scopes=None,
1454 # Default scopes should be used since user specified scopes are none.
1455 default_scopes=SCOPES,
1456 )
1457
1458 credentials.refresh(request)
1459
1460 assert len(request.call_args_list) == 5
1461 # Fourth request should be sent to GCP STS endpoint.
1462 self.assert_token_request_kwargs(
arithmetic1728d80c85f2021-03-08 13:35:44 -08001463 request.call_args_list[3][1], token_headers, token_request_data
bojeil-googled4d7f382021-02-16 12:33:20 -08001464 )
1465 # Fifth request should be sent to iamcredentials endpoint for service
1466 # account impersonation.
1467 self.assert_impersonation_request_kwargs(
arithmetic1728d80c85f2021-03-08 13:35:44 -08001468 request.call_args_list[4][1],
bojeil-googled4d7f382021-02-16 12:33:20 -08001469 impersonation_headers,
1470 impersonation_request_data,
1471 )
1472 assert credentials.token == impersonation_response["accessToken"]
1473 assert credentials.quota_project_id == QUOTA_PROJECT_ID
1474 assert credentials.scopes is None
1475 assert credentials.default_scopes == SCOPES
1476
1477 def test_refresh_with_retrieve_subject_token_error(self):
1478 request = self.make_mock_request(region_status=http_client.BAD_REQUEST)
1479 credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
1480
1481 with pytest.raises(exceptions.RefreshError) as excinfo:
1482 credentials.refresh(request)
1483
1484 assert excinfo.match(r"Unable to retrieve AWS region")