test_aws.py 102 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455
  1. # 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. import datetime
  15. import http.client as http_client
  16. import json
  17. import os
  18. import urllib.parse
  19. import mock
  20. import pytest # type: ignore
  21. from google.auth import _helpers, external_account
  22. from google.auth import aws
  23. from google.auth import environment_vars
  24. from google.auth import exceptions
  25. from google.auth import transport
  26. from google.auth.credentials import DEFAULT_UNIVERSE_DOMAIN
  27. IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE = (
  28. "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/imp"
  29. )
  30. LANG_LIBRARY_METRICS_HEADER_VALUE = "gl-python/3.7 auth/1.1"
  31. CLIENT_ID = "username"
  32. CLIENT_SECRET = "password"
  33. # Base64 encoding of "username:password".
  34. BASIC_AUTH_ENCODING = "dXNlcm5hbWU6cGFzc3dvcmQ="
  35. SERVICE_ACCOUNT_EMAIL = "service-1234@service-name.iam.gserviceaccount.com"
  36. SERVICE_ACCOUNT_IMPERSONATION_URL_BASE = (
  37. "https://us-east1-iamcredentials.googleapis.com"
  38. )
  39. SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE = "/v1/projects/-/serviceAccounts/{}:generateAccessToken".format(
  40. SERVICE_ACCOUNT_EMAIL
  41. )
  42. SERVICE_ACCOUNT_IMPERSONATION_URL = (
  43. SERVICE_ACCOUNT_IMPERSONATION_URL_BASE + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE
  44. )
  45. QUOTA_PROJECT_ID = "QUOTA_PROJECT_ID"
  46. SCOPES = ["scope1", "scope2"]
  47. TOKEN_URL = "https://sts.googleapis.com/v1/token"
  48. TOKEN_INFO_URL = "https://sts.googleapis.com/v1/introspect"
  49. SUBJECT_TOKEN_TYPE = "urn:ietf:params:aws:token-type:aws4_request"
  50. AUDIENCE = "//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID"
  51. REGION_URL = "http://169.254.169.254/latest/meta-data/placement/availability-zone"
  52. IMDSV2_SESSION_TOKEN_URL = "http://169.254.169.254/latest/api/token"
  53. SECURITY_CREDS_URL = "http://169.254.169.254/latest/meta-data/iam/security-credentials"
  54. REGION_URL_IPV6 = "http://[fd00:ec2::254]/latest/meta-data/placement/availability-zone"
  55. IMDSV2_SESSION_TOKEN_URL_IPV6 = "http://[fd00:ec2::254]/latest/api/token"
  56. SECURITY_CREDS_URL_IPV6 = (
  57. "http://[fd00:ec2::254]/latest/meta-data/iam/security-credentials"
  58. )
  59. CRED_VERIFICATION_URL = (
  60. "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
  61. )
  62. # Sample fictitious AWS security credentials to be used with tests that require a session token.
  63. ACCESS_KEY_ID = "AKIAIOSFODNN7EXAMPLE"
  64. SECRET_ACCESS_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
  65. TOKEN = "AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKwRcOIfrRh3c/LTo6UDdyJwOOvEVPvLXCrrrUtdnniCEXAMPLE/IvU1dYUg2RVAJBanLiHb4IgRmpRV3zrkuWJOgQs8IZZaIv2BXIa2R4OlgkBN9bkUDNCJiBeb/AXlzBBko7b15fjrBs2+cTQtpZ3CYWFXG8C5zqx37wnOE49mRl/+OtkIKGO7fAE"
  66. # To avoid json.dumps() differing behavior from one version to other,
  67. # the JSON payload is hardcoded.
  68. REQUEST_PARAMS = '{"KeySchema":[{"KeyType":"HASH","AttributeName":"Id"}],"TableName":"TestTable","AttributeDefinitions":[{"AttributeName":"Id","AttributeType":"S"}],"ProvisionedThroughput":{"WriteCapacityUnits":5,"ReadCapacityUnits":5}}'
  69. # Each tuple contains the following entries:
  70. # region, time, credentials, original_request, signed_request
  71. VALID_TOKEN_URLS = [
  72. "https://sts.googleapis.com",
  73. "https://us-east-1.sts.googleapis.com",
  74. "https://US-EAST-1.sts.googleapis.com",
  75. "https://sts.us-east-1.googleapis.com",
  76. "https://sts.US-WEST-1.googleapis.com",
  77. "https://us-east-1-sts.googleapis.com",
  78. "https://US-WEST-1-sts.googleapis.com",
  79. "https://us-west-1-sts.googleapis.com/path?query",
  80. "https://sts-us-east-1.p.googleapis.com",
  81. ]
  82. INVALID_TOKEN_URLS = [
  83. "https://iamcredentials.googleapis.com",
  84. "sts.googleapis.com",
  85. "https://",
  86. "http://sts.googleapis.com",
  87. "https://st.s.googleapis.com",
  88. "https://us-eas\t-1.sts.googleapis.com",
  89. "https:/us-east-1.sts.googleapis.com",
  90. "https://US-WE/ST-1-sts.googleapis.com",
  91. "https://sts-us-east-1.googleapis.com",
  92. "https://sts-US-WEST-1.googleapis.com",
  93. "testhttps://us-east-1.sts.googleapis.com",
  94. "https://us-east-1.sts.googleapis.comevil.com",
  95. "https://us-east-1.us-east-1.sts.googleapis.com",
  96. "https://us-ea.s.t.sts.googleapis.com",
  97. "https://sts.googleapis.comevil.com",
  98. "hhttps://us-east-1.sts.googleapis.com",
  99. "https://us- -1.sts.googleapis.com",
  100. "https://-sts.googleapis.com",
  101. "https://us-east-1.sts.googleapis.com.evil.com",
  102. "https://sts.pgoogleapis.com",
  103. "https://p.googleapis.com",
  104. "https://sts.p.com",
  105. "http://sts.p.googleapis.com",
  106. "https://xyz-sts.p.googleapis.com",
  107. "https://sts-xyz.123.p.googleapis.com",
  108. "https://sts-xyz.p1.googleapis.com",
  109. "https://sts-xyz.p.foo.com",
  110. "https://sts-xyz.p.foo.googleapis.com",
  111. ]
  112. VALID_SERVICE_ACCOUNT_IMPERSONATION_URLS = [
  113. "https://iamcredentials.googleapis.com",
  114. "https://us-east-1.iamcredentials.googleapis.com",
  115. "https://US-EAST-1.iamcredentials.googleapis.com",
  116. "https://iamcredentials.us-east-1.googleapis.com",
  117. "https://iamcredentials.US-WEST-1.googleapis.com",
  118. "https://us-east-1-iamcredentials.googleapis.com",
  119. "https://US-WEST-1-iamcredentials.googleapis.com",
  120. "https://us-west-1-iamcredentials.googleapis.com/path?query",
  121. "https://iamcredentials-us-east-1.p.googleapis.com",
  122. ]
  123. INVALID_SERVICE_ACCOUNT_IMPERSONATION_URLS = [
  124. "https://sts.googleapis.com",
  125. "iamcredentials.googleapis.com",
  126. "https://",
  127. "http://iamcredentials.googleapis.com",
  128. "https://iamcre.dentials.googleapis.com",
  129. "https://us-eas\t-1.iamcredentials.googleapis.com",
  130. "https:/us-east-1.iamcredentials.googleapis.com",
  131. "https://US-WE/ST-1-iamcredentials.googleapis.com",
  132. "https://iamcredentials-us-east-1.googleapis.com",
  133. "https://iamcredentials-US-WEST-1.googleapis.com",
  134. "testhttps://us-east-1.iamcredentials.googleapis.com",
  135. "https://us-east-1.iamcredentials.googleapis.comevil.com",
  136. "https://us-east-1.us-east-1.iamcredentials.googleapis.com",
  137. "https://us-ea.s.t.iamcredentials.googleapis.com",
  138. "https://iamcredentials.googleapis.comevil.com",
  139. "hhttps://us-east-1.iamcredentials.googleapis.com",
  140. "https://us- -1.iamcredentials.googleapis.com",
  141. "https://-iamcredentials.googleapis.com",
  142. "https://us-east-1.iamcredentials.googleapis.com.evil.com",
  143. "https://iamcredentials.pgoogleapis.com",
  144. "https://p.googleapis.com",
  145. "https://iamcredentials.p.com",
  146. "http://iamcredentials.p.googleapis.com",
  147. "https://xyz-iamcredentials.p.googleapis.com",
  148. "https://iamcredentials-xyz.123.p.googleapis.com",
  149. "https://iamcredentials-xyz.p1.googleapis.com",
  150. "https://iamcredentials-xyz.p.foo.com",
  151. "https://iamcredentials-xyz.p.foo.googleapis.com",
  152. ]
  153. TEST_FIXTURES = [
  154. # GET request (AWS botocore tests).
  155. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-vanilla.req
  156. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-vanilla.sreq
  157. (
  158. "us-east-1",
  159. "2011-09-09T23:36:00Z",
  160. {
  161. "access_key_id": "AKIDEXAMPLE",
  162. "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
  163. },
  164. {
  165. "method": "GET",
  166. "url": "https://host.foo.com",
  167. "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT"},
  168. },
  169. {
  170. "url": "https://host.foo.com",
  171. "method": "GET",
  172. "headers": {
  173. "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
  174. "host": "host.foo.com",
  175. "date": "Mon, 09 Sep 2011 23:36:00 GMT",
  176. },
  177. },
  178. ),
  179. # GET request with relative path (AWS botocore tests).
  180. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-relative-relative.req
  181. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-relative-relative.sreq
  182. (
  183. "us-east-1",
  184. "2011-09-09T23:36:00Z",
  185. {
  186. "access_key_id": "AKIDEXAMPLE",
  187. "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
  188. },
  189. {
  190. "method": "GET",
  191. "url": "https://host.foo.com/foo/bar/../..",
  192. "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT"},
  193. },
  194. {
  195. "url": "https://host.foo.com/foo/bar/../..",
  196. "method": "GET",
  197. "headers": {
  198. "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
  199. "host": "host.foo.com",
  200. "date": "Mon, 09 Sep 2011 23:36:00 GMT",
  201. },
  202. },
  203. ),
  204. # GET request with /./ path (AWS botocore tests).
  205. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-slash-dot-slash.req
  206. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-slash-dot-slash.sreq
  207. (
  208. "us-east-1",
  209. "2011-09-09T23:36:00Z",
  210. {
  211. "access_key_id": "AKIDEXAMPLE",
  212. "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
  213. },
  214. {
  215. "method": "GET",
  216. "url": "https://host.foo.com/./",
  217. "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT"},
  218. },
  219. {
  220. "url": "https://host.foo.com/./",
  221. "method": "GET",
  222. "headers": {
  223. "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
  224. "host": "host.foo.com",
  225. "date": "Mon, 09 Sep 2011 23:36:00 GMT",
  226. },
  227. },
  228. ),
  229. # GET request with pointless dot path (AWS botocore tests).
  230. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-slash-pointless-dot.req
  231. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-slash-pointless-dot.sreq
  232. (
  233. "us-east-1",
  234. "2011-09-09T23:36:00Z",
  235. {
  236. "access_key_id": "AKIDEXAMPLE",
  237. "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
  238. },
  239. {
  240. "method": "GET",
  241. "url": "https://host.foo.com/./foo",
  242. "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT"},
  243. },
  244. {
  245. "url": "https://host.foo.com/./foo",
  246. "method": "GET",
  247. "headers": {
  248. "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=910e4d6c9abafaf87898e1eb4c929135782ea25bb0279703146455745391e63a",
  249. "host": "host.foo.com",
  250. "date": "Mon, 09 Sep 2011 23:36:00 GMT",
  251. },
  252. },
  253. ),
  254. # GET request with utf8 path (AWS botocore tests).
  255. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-utf8.req
  256. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-utf8.sreq
  257. (
  258. "us-east-1",
  259. "2011-09-09T23:36:00Z",
  260. {
  261. "access_key_id": "AKIDEXAMPLE",
  262. "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
  263. },
  264. {
  265. "method": "GET",
  266. "url": "https://host.foo.com/%E1%88%B4",
  267. "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT"},
  268. },
  269. {
  270. "url": "https://host.foo.com/%E1%88%B4",
  271. "method": "GET",
  272. "headers": {
  273. "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=8d6634c189aa8c75c2e51e106b6b5121bed103fdb351f7d7d4381c738823af74",
  274. "host": "host.foo.com",
  275. "date": "Mon, 09 Sep 2011 23:36:00 GMT",
  276. },
  277. },
  278. ),
  279. # GET request with duplicate query key (AWS botocore tests).
  280. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-vanilla-query-order-key-case.req
  281. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-vanilla-query-order-key-case.sreq
  282. (
  283. "us-east-1",
  284. "2011-09-09T23:36:00Z",
  285. {
  286. "access_key_id": "AKIDEXAMPLE",
  287. "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
  288. },
  289. {
  290. "method": "GET",
  291. "url": "https://host.foo.com/?foo=Zoo&foo=aha",
  292. "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT"},
  293. },
  294. {
  295. "url": "https://host.foo.com/?foo=Zoo&foo=aha",
  296. "method": "GET",
  297. "headers": {
  298. "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=be7148d34ebccdc6423b19085378aa0bee970bdc61d144bd1a8c48c33079ab09",
  299. "host": "host.foo.com",
  300. "date": "Mon, 09 Sep 2011 23:36:00 GMT",
  301. },
  302. },
  303. ),
  304. # GET request with duplicate out of order query key (AWS botocore tests).
  305. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-vanilla-query-order-value.req
  306. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-vanilla-query-order-value.sreq
  307. (
  308. "us-east-1",
  309. "2011-09-09T23:36:00Z",
  310. {
  311. "access_key_id": "AKIDEXAMPLE",
  312. "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
  313. },
  314. {
  315. "method": "GET",
  316. "url": "https://host.foo.com/?foo=b&foo=a",
  317. "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT"},
  318. },
  319. {
  320. "url": "https://host.foo.com/?foo=b&foo=a",
  321. "method": "GET",
  322. "headers": {
  323. "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=feb926e49e382bec75c9d7dcb2a1b6dc8aa50ca43c25d2bc51143768c0875acc",
  324. "host": "host.foo.com",
  325. "date": "Mon, 09 Sep 2011 23:36:00 GMT",
  326. },
  327. },
  328. ),
  329. # GET request with utf8 query (AWS botocore tests).
  330. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-vanilla-ut8-query.req
  331. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-vanilla-ut8-query.sreq
  332. (
  333. "us-east-1",
  334. "2011-09-09T23:36:00Z",
  335. {
  336. "access_key_id": "AKIDEXAMPLE",
  337. "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
  338. },
  339. {
  340. "method": "GET",
  341. "url": "https://host.foo.com/?{}=bar".format(
  342. urllib.parse.unquote("%E1%88%B4")
  343. ),
  344. "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT"},
  345. },
  346. {
  347. "url": "https://host.foo.com/?{}=bar".format(
  348. urllib.parse.unquote("%E1%88%B4")
  349. ),
  350. "method": "GET",
  351. "headers": {
  352. "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=6fb359e9a05394cc7074e0feb42573a2601abc0c869a953e8c5c12e4e01f1a8c",
  353. "host": "host.foo.com",
  354. "date": "Mon, 09 Sep 2011 23:36:00 GMT",
  355. },
  356. },
  357. ),
  358. # POST request with sorted headers (AWS botocore tests).
  359. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/post-header-key-sort.req
  360. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/post-header-key-sort.sreq
  361. (
  362. "us-east-1",
  363. "2011-09-09T23:36:00Z",
  364. {
  365. "access_key_id": "AKIDEXAMPLE",
  366. "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
  367. },
  368. {
  369. "method": "POST",
  370. "url": "https://host.foo.com/",
  371. "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT", "ZOO": "zoobar"},
  372. },
  373. {
  374. "url": "https://host.foo.com/",
  375. "method": "POST",
  376. "headers": {
  377. "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host;zoo, Signature=b7a95a52518abbca0964a999a880429ab734f35ebbf1235bd79a5de87756dc4a",
  378. "host": "host.foo.com",
  379. "date": "Mon, 09 Sep 2011 23:36:00 GMT",
  380. "ZOO": "zoobar",
  381. },
  382. },
  383. ),
  384. # POST request with upper case header value from AWS Python test harness.
  385. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/post-header-value-case.req
  386. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/post-header-value-case.sreq
  387. (
  388. "us-east-1",
  389. "2011-09-09T23:36:00Z",
  390. {
  391. "access_key_id": "AKIDEXAMPLE",
  392. "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
  393. },
  394. {
  395. "method": "POST",
  396. "url": "https://host.foo.com/",
  397. "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT", "zoo": "ZOOBAR"},
  398. },
  399. {
  400. "url": "https://host.foo.com/",
  401. "method": "POST",
  402. "headers": {
  403. "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host;zoo, Signature=273313af9d0c265c531e11db70bbd653f3ba074c1009239e8559d3987039cad7",
  404. "host": "host.foo.com",
  405. "date": "Mon, 09 Sep 2011 23:36:00 GMT",
  406. "zoo": "ZOOBAR",
  407. },
  408. },
  409. ),
  410. # POST request with header and no body (AWS botocore tests).
  411. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-header-value-trim.req
  412. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-header-value-trim.sreq
  413. (
  414. "us-east-1",
  415. "2011-09-09T23:36:00Z",
  416. {
  417. "access_key_id": "AKIDEXAMPLE",
  418. "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
  419. },
  420. {
  421. "method": "POST",
  422. "url": "https://host.foo.com/",
  423. "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT", "p": "phfft"},
  424. },
  425. {
  426. "url": "https://host.foo.com/",
  427. "method": "POST",
  428. "headers": {
  429. "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host;p, Signature=debf546796015d6f6ded8626f5ce98597c33b47b9164cf6b17b4642036fcb592",
  430. "host": "host.foo.com",
  431. "date": "Mon, 09 Sep 2011 23:36:00 GMT",
  432. "p": "phfft",
  433. },
  434. },
  435. ),
  436. # POST request with body and no header (AWS botocore tests).
  437. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/post-x-www-form-urlencoded.req
  438. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/post-x-www-form-urlencoded.sreq
  439. (
  440. "us-east-1",
  441. "2011-09-09T23:36:00Z",
  442. {
  443. "access_key_id": "AKIDEXAMPLE",
  444. "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
  445. },
  446. {
  447. "method": "POST",
  448. "url": "https://host.foo.com/",
  449. "headers": {
  450. "Content-Type": "application/x-www-form-urlencoded",
  451. "date": "Mon, 09 Sep 2011 23:36:00 GMT",
  452. },
  453. "data": "foo=bar",
  454. },
  455. {
  456. "url": "https://host.foo.com/",
  457. "method": "POST",
  458. "headers": {
  459. "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=content-type;date;host, Signature=5a15b22cf462f047318703b92e6f4f38884e4a7ab7b1d6426ca46a8bd1c26cbc",
  460. "host": "host.foo.com",
  461. "Content-Type": "application/x-www-form-urlencoded",
  462. "date": "Mon, 09 Sep 2011 23:36:00 GMT",
  463. },
  464. "data": "foo=bar",
  465. },
  466. ),
  467. # POST request with querystring (AWS botocore tests).
  468. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/post-vanilla-query.req
  469. # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/post-vanilla-query.sreq
  470. (
  471. "us-east-1",
  472. "2011-09-09T23:36:00Z",
  473. {
  474. "access_key_id": "AKIDEXAMPLE",
  475. "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
  476. },
  477. {
  478. "method": "POST",
  479. "url": "https://host.foo.com/?foo=bar",
  480. "headers": {"date": "Mon, 09 Sep 2011 23:36:00 GMT"},
  481. },
  482. {
  483. "url": "https://host.foo.com/?foo=bar",
  484. "method": "POST",
  485. "headers": {
  486. "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b6e3b79003ce0743a491606ba1035a804593b0efb1e20a11cba83f8c25a57a92",
  487. "host": "host.foo.com",
  488. "date": "Mon, 09 Sep 2011 23:36:00 GMT",
  489. },
  490. },
  491. ),
  492. # GET request with session token credentials.
  493. (
  494. "us-east-2",
  495. "2020-08-11T06:55:22Z",
  496. {
  497. "access_key_id": ACCESS_KEY_ID,
  498. "secret_access_key": SECRET_ACCESS_KEY,
  499. "security_token": TOKEN,
  500. },
  501. {
  502. "method": "GET",
  503. "url": "https://ec2.us-east-2.amazonaws.com?Action=DescribeRegions&Version=2013-10-15",
  504. },
  505. {
  506. "url": "https://ec2.us-east-2.amazonaws.com?Action=DescribeRegions&Version=2013-10-15",
  507. "method": "GET",
  508. "headers": {
  509. "Authorization": "AWS4-HMAC-SHA256 Credential="
  510. + ACCESS_KEY_ID
  511. + "/20200811/us-east-2/ec2/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=41e226f997bf917ec6c9b2b14218df0874225f13bb153236c247881e614fafc9",
  512. "host": "ec2.us-east-2.amazonaws.com",
  513. "x-amz-date": "20200811T065522Z",
  514. "x-amz-security-token": TOKEN,
  515. },
  516. },
  517. ),
  518. # POST request with session token credentials.
  519. (
  520. "us-east-2",
  521. "2020-08-11T06:55:22Z",
  522. {
  523. "access_key_id": ACCESS_KEY_ID,
  524. "secret_access_key": SECRET_ACCESS_KEY,
  525. "security_token": TOKEN,
  526. },
  527. {
  528. "method": "POST",
  529. "url": "https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15",
  530. },
  531. {
  532. "url": "https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15",
  533. "method": "POST",
  534. "headers": {
  535. "Authorization": "AWS4-HMAC-SHA256 Credential="
  536. + ACCESS_KEY_ID
  537. + "/20200811/us-east-2/sts/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=596aa990b792d763465d73703e684ca273c45536c6d322c31be01a41d02e5b60",
  538. "host": "sts.us-east-2.amazonaws.com",
  539. "x-amz-date": "20200811T065522Z",
  540. "x-amz-security-token": TOKEN,
  541. },
  542. },
  543. ),
  544. # POST request with computed x-amz-date and no data.
  545. (
  546. "us-east-2",
  547. "2020-08-11T06:55:22Z",
  548. {"access_key_id": ACCESS_KEY_ID, "secret_access_key": SECRET_ACCESS_KEY},
  549. {
  550. "method": "POST",
  551. "url": "https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15",
  552. },
  553. {
  554. "url": "https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15",
  555. "method": "POST",
  556. "headers": {
  557. "Authorization": "AWS4-HMAC-SHA256 Credential="
  558. + ACCESS_KEY_ID
  559. + "/20200811/us-east-2/sts/aws4_request, SignedHeaders=host;x-amz-date, Signature=9e722e5b7bfa163447e2a14df118b45ebd283c5aea72019bdf921d6e7dc01a9a",
  560. "host": "sts.us-east-2.amazonaws.com",
  561. "x-amz-date": "20200811T065522Z",
  562. },
  563. },
  564. ),
  565. # POST request with session token and additional headers/data.
  566. (
  567. "us-east-2",
  568. "2020-08-11T06:55:22Z",
  569. {
  570. "access_key_id": ACCESS_KEY_ID,
  571. "secret_access_key": SECRET_ACCESS_KEY,
  572. "security_token": TOKEN,
  573. },
  574. {
  575. "method": "POST",
  576. "url": "https://dynamodb.us-east-2.amazonaws.com/",
  577. "headers": {
  578. "Content-Type": "application/x-amz-json-1.0",
  579. "x-amz-target": "DynamoDB_20120810.CreateTable",
  580. },
  581. "data": REQUEST_PARAMS,
  582. },
  583. {
  584. "url": "https://dynamodb.us-east-2.amazonaws.com/",
  585. "method": "POST",
  586. "headers": {
  587. "Authorization": "AWS4-HMAC-SHA256 Credential="
  588. + ACCESS_KEY_ID
  589. + "/20200811/us-east-2/dynamodb/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-security-token;x-amz-target, Signature=eb8bce0e63654bba672d4a8acb07e72d69210c1797d56ce024dbbc31beb2a2c7",
  590. "host": "dynamodb.us-east-2.amazonaws.com",
  591. "x-amz-date": "20200811T065522Z",
  592. "Content-Type": "application/x-amz-json-1.0",
  593. "x-amz-target": "DynamoDB_20120810.CreateTable",
  594. "x-amz-security-token": TOKEN,
  595. },
  596. "data": REQUEST_PARAMS,
  597. },
  598. ),
  599. ]
  600. class TestRequestSigner(object):
  601. @pytest.mark.parametrize(
  602. "region, time, credentials, original_request, signed_request", TEST_FIXTURES
  603. )
  604. @mock.patch("google.auth._helpers.utcnow")
  605. def test_get_request_options(
  606. self, utcnow, region, time, credentials, original_request, signed_request
  607. ):
  608. utcnow.return_value = datetime.datetime.strptime(time, "%Y-%m-%dT%H:%M:%SZ")
  609. request_signer = aws.RequestSigner(region)
  610. credentials_object = aws.AwsSecurityCredentials(
  611. credentials.get("access_key_id"),
  612. credentials.get("secret_access_key"),
  613. credentials.get("security_token"),
  614. )
  615. actual_signed_request = request_signer.get_request_options(
  616. credentials_object,
  617. original_request.get("url"),
  618. original_request.get("method"),
  619. original_request.get("data"),
  620. original_request.get("headers"),
  621. )
  622. assert actual_signed_request == signed_request
  623. def test_get_request_options_with_missing_scheme_url(self):
  624. request_signer = aws.RequestSigner("us-east-2")
  625. with pytest.raises(ValueError) as excinfo:
  626. request_signer.get_request_options(
  627. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY),
  628. "invalid",
  629. "POST",
  630. )
  631. assert excinfo.match(r"Invalid AWS service URL")
  632. def test_get_request_options_with_invalid_scheme_url(self):
  633. request_signer = aws.RequestSigner("us-east-2")
  634. with pytest.raises(ValueError) as excinfo:
  635. request_signer.get_request_options(
  636. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY),
  637. "http://invalid",
  638. "POST",
  639. )
  640. assert excinfo.match(r"Invalid AWS service URL")
  641. def test_get_request_options_with_missing_hostname_url(self):
  642. request_signer = aws.RequestSigner("us-east-2")
  643. with pytest.raises(ValueError) as excinfo:
  644. request_signer.get_request_options(
  645. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY),
  646. "https://",
  647. "POST",
  648. )
  649. assert excinfo.match(r"Invalid AWS service URL")
  650. class TestAwsSecurityCredentialsSupplier(aws.AwsSecurityCredentialsSupplier):
  651. def __init__(
  652. self,
  653. security_credentials=None,
  654. region=None,
  655. credentials_exception=None,
  656. region_exception=None,
  657. expected_context=None,
  658. ):
  659. self._security_credentials = security_credentials
  660. self._region = region
  661. self._credentials_exception = credentials_exception
  662. self._region_exception = region_exception
  663. self._expected_context = expected_context
  664. def get_aws_security_credentials(self, context, request):
  665. if self._expected_context is not None:
  666. assert self._expected_context == context
  667. if self._credentials_exception is not None:
  668. raise self._credentials_exception
  669. return self._security_credentials
  670. def get_aws_region(self, context, request):
  671. if self._expected_context is not None:
  672. assert self._expected_context == context
  673. if self._region_exception is not None:
  674. raise self._region_exception
  675. return self._region
  676. class TestCredentials(object):
  677. AWS_REGION = "us-east-2"
  678. AWS_ROLE = "gcp-aws-role"
  679. AWS_SECURITY_CREDENTIALS_RESPONSE = {
  680. "AccessKeyId": ACCESS_KEY_ID,
  681. "SecretAccessKey": SECRET_ACCESS_KEY,
  682. "Token": TOKEN,
  683. }
  684. AWS_IMDSV2_SESSION_TOKEN = "awsimdsv2sessiontoken"
  685. AWS_SIGNATURE_TIME = "2020-08-11T06:55:22Z"
  686. CREDENTIAL_SOURCE = {
  687. "environment_id": "aws1",
  688. "region_url": REGION_URL,
  689. "url": SECURITY_CREDS_URL,
  690. "regional_cred_verification_url": CRED_VERIFICATION_URL,
  691. }
  692. CREDENTIAL_SOURCE_IPV6 = {
  693. "environment_id": "aws1",
  694. "region_url": REGION_URL_IPV6,
  695. "url": SECURITY_CREDS_URL_IPV6,
  696. "regional_cred_verification_url": CRED_VERIFICATION_URL,
  697. "imdsv2_session_token_url": IMDSV2_SESSION_TOKEN_URL_IPV6,
  698. }
  699. SUCCESS_RESPONSE = {
  700. "access_token": "ACCESS_TOKEN",
  701. "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
  702. "token_type": "Bearer",
  703. "expires_in": 3600,
  704. "scope": " ".join(SCOPES),
  705. }
  706. @classmethod
  707. def make_serialized_aws_signed_request(
  708. cls,
  709. aws_security_credentials,
  710. region_name="us-east-2",
  711. url="https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15",
  712. ):
  713. """Utility to generate serialize AWS signed requests.
  714. This makes it easy to assert generated subject tokens based on the
  715. provided AWS security credentials, regions and AWS STS endpoint.
  716. """
  717. request_signer = aws.RequestSigner(region_name)
  718. signed_request = request_signer.get_request_options(
  719. aws_security_credentials, url, "POST"
  720. )
  721. reformatted_signed_request = {
  722. "url": signed_request.get("url"),
  723. "method": signed_request.get("method"),
  724. "headers": [
  725. {
  726. "key": "Authorization",
  727. "value": signed_request.get("headers").get("Authorization"),
  728. },
  729. {"key": "host", "value": signed_request.get("headers").get("host")},
  730. {
  731. "key": "x-amz-date",
  732. "value": signed_request.get("headers").get("x-amz-date"),
  733. },
  734. ],
  735. }
  736. # Include security token if available.
  737. if aws_security_credentials.session_token is not None:
  738. reformatted_signed_request.get("headers").append(
  739. {
  740. "key": "x-amz-security-token",
  741. "value": signed_request.get("headers").get("x-amz-security-token"),
  742. }
  743. )
  744. # Append x-goog-cloud-target-resource header.
  745. reformatted_signed_request.get("headers").append(
  746. {"key": "x-goog-cloud-target-resource", "value": AUDIENCE}
  747. ),
  748. return urllib.parse.quote(
  749. json.dumps(
  750. reformatted_signed_request, separators=(",", ":"), sort_keys=True
  751. )
  752. )
  753. @classmethod
  754. def make_mock_request(
  755. cls,
  756. region_status=None,
  757. region_name=None,
  758. role_status=None,
  759. role_name=None,
  760. security_credentials_status=None,
  761. security_credentials_data=None,
  762. token_status=None,
  763. token_data=None,
  764. impersonation_status=None,
  765. impersonation_data=None,
  766. imdsv2_session_token_status=None,
  767. imdsv2_session_token_data=None,
  768. ):
  769. """Utility function to generate a mock HTTP request object.
  770. This will facilitate testing various edge cases by specify how the
  771. various endpoints will respond while generating a Google Access token
  772. in an AWS environment.
  773. """
  774. responses = []
  775. if region_status:
  776. if imdsv2_session_token_status:
  777. # AWS session token request
  778. imdsv2_session_response = mock.create_autospec(
  779. transport.Response, instance=True
  780. )
  781. imdsv2_session_response.status = imdsv2_session_token_status
  782. imdsv2_session_response.data = imdsv2_session_token_data
  783. responses.append(imdsv2_session_response)
  784. # AWS region request.
  785. region_response = mock.create_autospec(transport.Response, instance=True)
  786. region_response.status = region_status
  787. if region_name:
  788. region_response.data = "{}b".format(region_name).encode("utf-8")
  789. responses.append(region_response)
  790. if imdsv2_session_token_status:
  791. # AWS session token request
  792. imdsv2_session_response = mock.create_autospec(
  793. transport.Response, instance=True
  794. )
  795. imdsv2_session_response.status = imdsv2_session_token_status
  796. imdsv2_session_response.data = imdsv2_session_token_data
  797. responses.append(imdsv2_session_response)
  798. if role_status:
  799. # AWS role name request.
  800. role_response = mock.create_autospec(transport.Response, instance=True)
  801. role_response.status = role_status
  802. if role_name:
  803. role_response.data = role_name.encode("utf-8")
  804. responses.append(role_response)
  805. if security_credentials_status:
  806. # AWS security credentials request.
  807. security_credentials_response = mock.create_autospec(
  808. transport.Response, instance=True
  809. )
  810. security_credentials_response.status = security_credentials_status
  811. if security_credentials_data:
  812. security_credentials_response.data = json.dumps(
  813. security_credentials_data
  814. ).encode("utf-8")
  815. responses.append(security_credentials_response)
  816. if token_status:
  817. # GCP token exchange request.
  818. token_response = mock.create_autospec(transport.Response, instance=True)
  819. token_response.status = token_status
  820. token_response.data = json.dumps(token_data).encode("utf-8")
  821. responses.append(token_response)
  822. if impersonation_status:
  823. # Service account impersonation request.
  824. impersonation_response = mock.create_autospec(
  825. transport.Response, instance=True
  826. )
  827. impersonation_response.status = impersonation_status
  828. impersonation_response.data = json.dumps(impersonation_data).encode("utf-8")
  829. responses.append(impersonation_response)
  830. request = mock.create_autospec(transport.Request)
  831. request.side_effect = responses
  832. return request
  833. @classmethod
  834. def make_credentials(
  835. cls,
  836. credential_source=None,
  837. aws_security_credentials_supplier=None,
  838. token_url=TOKEN_URL,
  839. token_info_url=TOKEN_INFO_URL,
  840. client_id=None,
  841. client_secret=None,
  842. quota_project_id=None,
  843. scopes=None,
  844. default_scopes=None,
  845. service_account_impersonation_url=None,
  846. ):
  847. return aws.Credentials(
  848. audience=AUDIENCE,
  849. subject_token_type=SUBJECT_TOKEN_TYPE,
  850. token_url=token_url,
  851. token_info_url=token_info_url,
  852. service_account_impersonation_url=service_account_impersonation_url,
  853. credential_source=credential_source,
  854. aws_security_credentials_supplier=aws_security_credentials_supplier,
  855. client_id=client_id,
  856. client_secret=client_secret,
  857. quota_project_id=quota_project_id,
  858. scopes=scopes,
  859. default_scopes=default_scopes,
  860. )
  861. @classmethod
  862. def assert_aws_metadata_request_kwargs(
  863. cls, request_kwargs, url, headers=None, method="GET"
  864. ):
  865. assert request_kwargs["url"] == url
  866. # All used AWS metadata server endpoints use GET HTTP method.
  867. assert request_kwargs["method"] == method
  868. if headers:
  869. assert request_kwargs["headers"] == headers
  870. else:
  871. assert "headers" not in request_kwargs or request_kwargs["headers"] is None
  872. # None of the endpoints used require any data in request.
  873. assert "body" not in request_kwargs
  874. @classmethod
  875. def assert_token_request_kwargs(
  876. cls, request_kwargs, headers, request_data, token_url=TOKEN_URL
  877. ):
  878. assert request_kwargs["url"] == token_url
  879. assert request_kwargs["method"] == "POST"
  880. assert request_kwargs["headers"] == headers
  881. assert request_kwargs["body"] is not None
  882. body_tuples = urllib.parse.parse_qsl(request_kwargs["body"])
  883. assert len(body_tuples) == len(request_data.keys())
  884. for (k, v) in body_tuples:
  885. assert v.decode("utf-8") == request_data[k.decode("utf-8")]
  886. @classmethod
  887. def assert_impersonation_request_kwargs(
  888. cls,
  889. request_kwargs,
  890. headers,
  891. request_data,
  892. service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL,
  893. ):
  894. assert request_kwargs["url"] == service_account_impersonation_url
  895. assert request_kwargs["method"] == "POST"
  896. assert request_kwargs["headers"] == headers
  897. assert request_kwargs["body"] is not None
  898. body_json = json.loads(request_kwargs["body"].decode("utf-8"))
  899. assert body_json == request_data
  900. @mock.patch.object(aws.Credentials, "__init__", return_value=None)
  901. def test_from_info_full_options(self, mock_init):
  902. credentials = aws.Credentials.from_info(
  903. {
  904. "audience": AUDIENCE,
  905. "subject_token_type": SUBJECT_TOKEN_TYPE,
  906. "token_url": TOKEN_URL,
  907. "token_info_url": TOKEN_INFO_URL,
  908. "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL,
  909. "service_account_impersonation": {"token_lifetime_seconds": 2800},
  910. "client_id": CLIENT_ID,
  911. "client_secret": CLIENT_SECRET,
  912. "quota_project_id": QUOTA_PROJECT_ID,
  913. "credential_source": self.CREDENTIAL_SOURCE,
  914. }
  915. )
  916. # Confirm aws.Credentials instance initialized with the expected parameters.
  917. assert isinstance(credentials, aws.Credentials)
  918. mock_init.assert_called_once_with(
  919. audience=AUDIENCE,
  920. subject_token_type=SUBJECT_TOKEN_TYPE,
  921. token_url=TOKEN_URL,
  922. token_info_url=TOKEN_INFO_URL,
  923. service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL,
  924. service_account_impersonation_options={"token_lifetime_seconds": 2800},
  925. client_id=CLIENT_ID,
  926. client_secret=CLIENT_SECRET,
  927. credential_source=self.CREDENTIAL_SOURCE,
  928. aws_security_credentials_supplier=None,
  929. quota_project_id=QUOTA_PROJECT_ID,
  930. workforce_pool_user_project=None,
  931. universe_domain=DEFAULT_UNIVERSE_DOMAIN,
  932. )
  933. @mock.patch.object(aws.Credentials, "__init__", return_value=None)
  934. def test_from_info_required_options_only(self, mock_init):
  935. credentials = aws.Credentials.from_info(
  936. {
  937. "audience": AUDIENCE,
  938. "subject_token_type": SUBJECT_TOKEN_TYPE,
  939. "token_url": TOKEN_URL,
  940. "credential_source": self.CREDENTIAL_SOURCE,
  941. }
  942. )
  943. # Confirm aws.Credentials instance initialized with the expected parameters.
  944. assert isinstance(credentials, aws.Credentials)
  945. mock_init.assert_called_once_with(
  946. audience=AUDIENCE,
  947. subject_token_type=SUBJECT_TOKEN_TYPE,
  948. token_url=TOKEN_URL,
  949. token_info_url=None,
  950. service_account_impersonation_url=None,
  951. service_account_impersonation_options={},
  952. client_id=None,
  953. client_secret=None,
  954. credential_source=self.CREDENTIAL_SOURCE,
  955. aws_security_credentials_supplier=None,
  956. quota_project_id=None,
  957. workforce_pool_user_project=None,
  958. universe_domain=DEFAULT_UNIVERSE_DOMAIN,
  959. )
  960. @mock.patch.object(aws.Credentials, "__init__", return_value=None)
  961. def test_from_info_supplier(self, mock_init):
  962. supplier = TestAwsSecurityCredentialsSupplier()
  963. credentials = aws.Credentials.from_info(
  964. {
  965. "audience": AUDIENCE,
  966. "subject_token_type": SUBJECT_TOKEN_TYPE,
  967. "token_url": TOKEN_URL,
  968. "aws_security_credentials_supplier": supplier,
  969. }
  970. )
  971. # Confirm aws.Credentials instance initialized with the expected parameters.
  972. assert isinstance(credentials, aws.Credentials)
  973. mock_init.assert_called_once_with(
  974. audience=AUDIENCE,
  975. subject_token_type=SUBJECT_TOKEN_TYPE,
  976. token_url=TOKEN_URL,
  977. token_info_url=None,
  978. service_account_impersonation_url=None,
  979. service_account_impersonation_options={},
  980. client_id=None,
  981. client_secret=None,
  982. credential_source=None,
  983. aws_security_credentials_supplier=supplier,
  984. quota_project_id=None,
  985. workforce_pool_user_project=None,
  986. universe_domain=DEFAULT_UNIVERSE_DOMAIN,
  987. )
  988. @mock.patch.object(aws.Credentials, "__init__", return_value=None)
  989. def test_from_file_full_options(self, mock_init, tmpdir):
  990. info = {
  991. "audience": AUDIENCE,
  992. "subject_token_type": SUBJECT_TOKEN_TYPE,
  993. "token_url": TOKEN_URL,
  994. "token_info_url": TOKEN_INFO_URL,
  995. "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL,
  996. "service_account_impersonation": {"token_lifetime_seconds": 2800},
  997. "client_id": CLIENT_ID,
  998. "client_secret": CLIENT_SECRET,
  999. "quota_project_id": QUOTA_PROJECT_ID,
  1000. "credential_source": self.CREDENTIAL_SOURCE,
  1001. "universe_domain": DEFAULT_UNIVERSE_DOMAIN,
  1002. }
  1003. config_file = tmpdir.join("config.json")
  1004. config_file.write(json.dumps(info))
  1005. credentials = aws.Credentials.from_file(str(config_file))
  1006. # Confirm aws.Credentials instance initialized with the expected parameters.
  1007. assert isinstance(credentials, aws.Credentials)
  1008. mock_init.assert_called_once_with(
  1009. audience=AUDIENCE,
  1010. subject_token_type=SUBJECT_TOKEN_TYPE,
  1011. token_url=TOKEN_URL,
  1012. token_info_url=TOKEN_INFO_URL,
  1013. service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL,
  1014. service_account_impersonation_options={"token_lifetime_seconds": 2800},
  1015. client_id=CLIENT_ID,
  1016. client_secret=CLIENT_SECRET,
  1017. credential_source=self.CREDENTIAL_SOURCE,
  1018. aws_security_credentials_supplier=None,
  1019. quota_project_id=QUOTA_PROJECT_ID,
  1020. workforce_pool_user_project=None,
  1021. universe_domain=DEFAULT_UNIVERSE_DOMAIN,
  1022. )
  1023. @mock.patch.object(aws.Credentials, "__init__", return_value=None)
  1024. def test_from_file_required_options_only(self, mock_init, tmpdir):
  1025. info = {
  1026. "audience": AUDIENCE,
  1027. "subject_token_type": SUBJECT_TOKEN_TYPE,
  1028. "token_url": TOKEN_URL,
  1029. "credential_source": self.CREDENTIAL_SOURCE,
  1030. }
  1031. config_file = tmpdir.join("config.json")
  1032. config_file.write(json.dumps(info))
  1033. credentials = aws.Credentials.from_file(str(config_file))
  1034. # Confirm aws.Credentials instance initialized with the expected parameters.
  1035. assert isinstance(credentials, aws.Credentials)
  1036. mock_init.assert_called_once_with(
  1037. audience=AUDIENCE,
  1038. subject_token_type=SUBJECT_TOKEN_TYPE,
  1039. token_url=TOKEN_URL,
  1040. token_info_url=None,
  1041. service_account_impersonation_url=None,
  1042. service_account_impersonation_options={},
  1043. client_id=None,
  1044. client_secret=None,
  1045. credential_source=self.CREDENTIAL_SOURCE,
  1046. aws_security_credentials_supplier=None,
  1047. quota_project_id=None,
  1048. workforce_pool_user_project=None,
  1049. universe_domain=DEFAULT_UNIVERSE_DOMAIN,
  1050. )
  1051. def test_constructor_invalid_credential_source(self):
  1052. # Provide invalid credential source.
  1053. credential_source = {"unsupported": "value"}
  1054. with pytest.raises(ValueError) as excinfo:
  1055. self.make_credentials(credential_source=credential_source)
  1056. assert excinfo.match(r"No valid AWS 'credential_source' provided")
  1057. def test_constructor_invalid_credential_source_and_supplier(self):
  1058. # Provide both a credential source and supplier.
  1059. with pytest.raises(ValueError) as excinfo:
  1060. self.make_credentials(
  1061. credential_source=self.CREDENTIAL_SOURCE,
  1062. aws_security_credentials_supplier="test",
  1063. )
  1064. assert excinfo.match(
  1065. r"AWS credential cannot have both a credential source and an AWS security credentials supplier."
  1066. )
  1067. def test_constructor_invalid_no_credential_source_or_supplier(self):
  1068. # Provide no credential source or supplier.
  1069. with pytest.raises(ValueError) as excinfo:
  1070. self.make_credentials()
  1071. assert excinfo.match(
  1072. r"A valid credential source or AWS security credentials supplier must be provided."
  1073. )
  1074. def test_constructor_invalid_environment_id(self):
  1075. # Provide invalid environment_id.
  1076. credential_source = self.CREDENTIAL_SOURCE.copy()
  1077. credential_source["environment_id"] = "azure1"
  1078. with pytest.raises(ValueError) as excinfo:
  1079. self.make_credentials(credential_source=credential_source)
  1080. assert excinfo.match(r"No valid AWS 'credential_source' provided")
  1081. def test_constructor_missing_cred_verification_url(self):
  1082. # regional_cred_verification_url is a required field.
  1083. credential_source = self.CREDENTIAL_SOURCE.copy()
  1084. credential_source.pop("regional_cred_verification_url")
  1085. with pytest.raises(ValueError) as excinfo:
  1086. self.make_credentials(credential_source=credential_source)
  1087. assert excinfo.match(r"No valid AWS 'credential_source' provided")
  1088. def test_constructor_invalid_environment_id_version(self):
  1089. # Provide an unsupported version.
  1090. credential_source = self.CREDENTIAL_SOURCE.copy()
  1091. credential_source["environment_id"] = "aws3"
  1092. with pytest.raises(ValueError) as excinfo:
  1093. self.make_credentials(credential_source=credential_source)
  1094. assert excinfo.match(r"aws version '3' is not supported in the current build.")
  1095. def test_info(self):
  1096. credentials = self.make_credentials(
  1097. credential_source=self.CREDENTIAL_SOURCE.copy()
  1098. )
  1099. assert credentials.info == {
  1100. "type": "external_account",
  1101. "audience": AUDIENCE,
  1102. "subject_token_type": SUBJECT_TOKEN_TYPE,
  1103. "token_url": TOKEN_URL,
  1104. "token_info_url": TOKEN_INFO_URL,
  1105. "credential_source": self.CREDENTIAL_SOURCE,
  1106. "universe_domain": DEFAULT_UNIVERSE_DOMAIN,
  1107. }
  1108. def test_token_info_url(self):
  1109. credentials = self.make_credentials(
  1110. credential_source=self.CREDENTIAL_SOURCE.copy()
  1111. )
  1112. assert credentials.token_info_url == TOKEN_INFO_URL
  1113. def test_token_info_url_custom(self):
  1114. for url in VALID_TOKEN_URLS:
  1115. credentials = self.make_credentials(
  1116. credential_source=self.CREDENTIAL_SOURCE.copy(),
  1117. token_info_url=(url + "/introspect"),
  1118. )
  1119. assert credentials.token_info_url == (url + "/introspect")
  1120. def test_token_info_url_negative(self):
  1121. credentials = self.make_credentials(
  1122. credential_source=self.CREDENTIAL_SOURCE.copy(), token_info_url=None
  1123. )
  1124. assert not credentials.token_info_url
  1125. def test_token_url_custom(self):
  1126. for url in VALID_TOKEN_URLS:
  1127. credentials = self.make_credentials(
  1128. credential_source=self.CREDENTIAL_SOURCE.copy(),
  1129. token_url=(url + "/token"),
  1130. )
  1131. assert credentials._token_url == (url + "/token")
  1132. def test_service_account_impersonation_url_custom(self):
  1133. for url in VALID_SERVICE_ACCOUNT_IMPERSONATION_URLS:
  1134. credentials = self.make_credentials(
  1135. credential_source=self.CREDENTIAL_SOURCE.copy(),
  1136. service_account_impersonation_url=(
  1137. url + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE
  1138. ),
  1139. )
  1140. assert credentials._service_account_impersonation_url == (
  1141. url + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE
  1142. )
  1143. def test_info_with_default_token_url(self):
  1144. credentials = aws.Credentials(
  1145. audience=AUDIENCE,
  1146. subject_token_type=SUBJECT_TOKEN_TYPE,
  1147. credential_source=self.CREDENTIAL_SOURCE.copy(),
  1148. )
  1149. assert credentials.info == {
  1150. "type": "external_account",
  1151. "audience": AUDIENCE,
  1152. "subject_token_type": SUBJECT_TOKEN_TYPE,
  1153. "token_url": TOKEN_URL,
  1154. "credential_source": self.CREDENTIAL_SOURCE.copy(),
  1155. "universe_domain": DEFAULT_UNIVERSE_DOMAIN,
  1156. }
  1157. def test_info_with_default_token_url_with_universe_domain(self):
  1158. credentials = aws.Credentials(
  1159. audience=AUDIENCE,
  1160. subject_token_type=SUBJECT_TOKEN_TYPE,
  1161. credential_source=self.CREDENTIAL_SOURCE.copy(),
  1162. universe_domain="testdomain.org",
  1163. )
  1164. assert credentials.info == {
  1165. "type": "external_account",
  1166. "audience": AUDIENCE,
  1167. "subject_token_type": SUBJECT_TOKEN_TYPE,
  1168. "token_url": "https://sts.testdomain.org/v1/token",
  1169. "credential_source": self.CREDENTIAL_SOURCE.copy(),
  1170. "universe_domain": "testdomain.org",
  1171. }
  1172. def test_retrieve_subject_token_missing_region_url(self):
  1173. # When AWS_REGION envvar is not available, region_url is required for
  1174. # determining the current AWS region.
  1175. credential_source = self.CREDENTIAL_SOURCE.copy()
  1176. credential_source.pop("region_url")
  1177. credentials = self.make_credentials(credential_source=credential_source)
  1178. with pytest.raises(exceptions.RefreshError) as excinfo:
  1179. credentials.retrieve_subject_token(None)
  1180. assert excinfo.match(r"Unable to determine AWS region")
  1181. @mock.patch("google.auth._helpers.utcnow")
  1182. def test_retrieve_subject_token_success_temp_creds_no_environment_vars(
  1183. self, utcnow
  1184. ):
  1185. utcnow.return_value = datetime.datetime.strptime(
  1186. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1187. )
  1188. request = self.make_mock_request(
  1189. region_status=http_client.OK,
  1190. region_name=self.AWS_REGION,
  1191. role_status=http_client.OK,
  1192. role_name=self.AWS_ROLE,
  1193. security_credentials_status=http_client.OK,
  1194. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1195. )
  1196. credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
  1197. subject_token = credentials.retrieve_subject_token(request)
  1198. assert subject_token == self.make_serialized_aws_signed_request(
  1199. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
  1200. )
  1201. # Assert region request.
  1202. self.assert_aws_metadata_request_kwargs(
  1203. request.call_args_list[0][1], REGION_URL
  1204. )
  1205. # Assert role request.
  1206. self.assert_aws_metadata_request_kwargs(
  1207. request.call_args_list[1][1], SECURITY_CREDS_URL
  1208. )
  1209. # Assert security credentials request.
  1210. self.assert_aws_metadata_request_kwargs(
  1211. request.call_args_list[2][1],
  1212. "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE),
  1213. {"Content-Type": "application/json"},
  1214. )
  1215. # Retrieve subject_token again. Region should not be queried again.
  1216. new_request = self.make_mock_request(
  1217. role_status=http_client.OK,
  1218. role_name=self.AWS_ROLE,
  1219. security_credentials_status=http_client.OK,
  1220. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1221. )
  1222. credentials.retrieve_subject_token(new_request)
  1223. # Only 3 requests should be sent as the region is cached.
  1224. assert len(new_request.call_args_list) == 2
  1225. # Assert role request.
  1226. self.assert_aws_metadata_request_kwargs(
  1227. new_request.call_args_list[0][1], SECURITY_CREDS_URL
  1228. )
  1229. # Assert security credentials request.
  1230. self.assert_aws_metadata_request_kwargs(
  1231. new_request.call_args_list[1][1],
  1232. "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE),
  1233. {"Content-Type": "application/json"},
  1234. )
  1235. @mock.patch("google.auth._helpers.utcnow")
  1236. @mock.patch.dict(os.environ, {})
  1237. def test_retrieve_subject_token_success_temp_creds_no_environment_vars_idmsv2(
  1238. self, utcnow
  1239. ):
  1240. utcnow.return_value = datetime.datetime.strptime(
  1241. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1242. )
  1243. request = self.make_mock_request(
  1244. region_status=http_client.OK,
  1245. region_name=self.AWS_REGION,
  1246. role_status=http_client.OK,
  1247. role_name=self.AWS_ROLE,
  1248. security_credentials_status=http_client.OK,
  1249. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1250. imdsv2_session_token_status=http_client.OK,
  1251. imdsv2_session_token_data=self.AWS_IMDSV2_SESSION_TOKEN,
  1252. )
  1253. credential_source_token_url = self.CREDENTIAL_SOURCE.copy()
  1254. credential_source_token_url[
  1255. "imdsv2_session_token_url"
  1256. ] = IMDSV2_SESSION_TOKEN_URL
  1257. credentials = self.make_credentials(
  1258. credential_source=credential_source_token_url
  1259. )
  1260. subject_token = credentials.retrieve_subject_token(request)
  1261. assert subject_token == self.make_serialized_aws_signed_request(
  1262. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
  1263. )
  1264. # Assert session token request
  1265. self.assert_aws_metadata_request_kwargs(
  1266. request.call_args_list[0][1],
  1267. IMDSV2_SESSION_TOKEN_URL,
  1268. {"X-aws-ec2-metadata-token-ttl-seconds": "300"},
  1269. "PUT",
  1270. )
  1271. # Assert region request.
  1272. self.assert_aws_metadata_request_kwargs(
  1273. request.call_args_list[1][1],
  1274. REGION_URL,
  1275. {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN},
  1276. )
  1277. # Assert session token request
  1278. self.assert_aws_metadata_request_kwargs(
  1279. request.call_args_list[2][1],
  1280. IMDSV2_SESSION_TOKEN_URL,
  1281. {"X-aws-ec2-metadata-token-ttl-seconds": "300"},
  1282. "PUT",
  1283. )
  1284. # Assert role request.
  1285. self.assert_aws_metadata_request_kwargs(
  1286. request.call_args_list[3][1],
  1287. SECURITY_CREDS_URL,
  1288. {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN},
  1289. )
  1290. # Assert security credentials request.
  1291. self.assert_aws_metadata_request_kwargs(
  1292. request.call_args_list[4][1],
  1293. "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE),
  1294. {
  1295. "Content-Type": "application/json",
  1296. "X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN,
  1297. },
  1298. )
  1299. # Retrieve subject_token again. Region should not be queried again.
  1300. new_request = self.make_mock_request(
  1301. role_status=http_client.OK,
  1302. role_name=self.AWS_ROLE,
  1303. security_credentials_status=http_client.OK,
  1304. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1305. imdsv2_session_token_status=http_client.OK,
  1306. imdsv2_session_token_data=self.AWS_IMDSV2_SESSION_TOKEN,
  1307. )
  1308. credentials.retrieve_subject_token(new_request)
  1309. # Only 3 requests should be sent as the region is cached.
  1310. assert len(new_request.call_args_list) == 3
  1311. # Assert session token request.
  1312. self.assert_aws_metadata_request_kwargs(
  1313. request.call_args_list[0][1],
  1314. IMDSV2_SESSION_TOKEN_URL,
  1315. {"X-aws-ec2-metadata-token-ttl-seconds": "300"},
  1316. "PUT",
  1317. )
  1318. # Assert role request.
  1319. self.assert_aws_metadata_request_kwargs(
  1320. new_request.call_args_list[1][1],
  1321. SECURITY_CREDS_URL,
  1322. {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN},
  1323. )
  1324. # Assert security credentials request.
  1325. self.assert_aws_metadata_request_kwargs(
  1326. new_request.call_args_list[2][1],
  1327. "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE),
  1328. {
  1329. "Content-Type": "application/json",
  1330. "X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN,
  1331. },
  1332. )
  1333. @mock.patch("google.auth._helpers.utcnow")
  1334. @mock.patch.dict(
  1335. os.environ,
  1336. {
  1337. environment_vars.AWS_REGION: AWS_REGION,
  1338. environment_vars.AWS_ACCESS_KEY_ID: ACCESS_KEY_ID,
  1339. },
  1340. )
  1341. def test_retrieve_subject_token_success_temp_creds_environment_vars_missing_secret_access_key_idmsv2(
  1342. self, utcnow
  1343. ):
  1344. utcnow.return_value = datetime.datetime.strptime(
  1345. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1346. )
  1347. request = self.make_mock_request(
  1348. role_status=http_client.OK,
  1349. role_name=self.AWS_ROLE,
  1350. security_credentials_status=http_client.OK,
  1351. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1352. imdsv2_session_token_status=http_client.OK,
  1353. imdsv2_session_token_data=self.AWS_IMDSV2_SESSION_TOKEN,
  1354. )
  1355. credential_source_token_url = self.CREDENTIAL_SOURCE.copy()
  1356. credential_source_token_url[
  1357. "imdsv2_session_token_url"
  1358. ] = IMDSV2_SESSION_TOKEN_URL
  1359. credentials = self.make_credentials(
  1360. credential_source=credential_source_token_url
  1361. )
  1362. subject_token = credentials.retrieve_subject_token(request)
  1363. assert subject_token == self.make_serialized_aws_signed_request(
  1364. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
  1365. )
  1366. # Assert session token request.
  1367. self.assert_aws_metadata_request_kwargs(
  1368. request.call_args_list[0][1],
  1369. IMDSV2_SESSION_TOKEN_URL,
  1370. {"X-aws-ec2-metadata-token-ttl-seconds": "300"},
  1371. "PUT",
  1372. )
  1373. # Assert role request.
  1374. self.assert_aws_metadata_request_kwargs(
  1375. request.call_args_list[1][1],
  1376. SECURITY_CREDS_URL,
  1377. {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN},
  1378. )
  1379. # Assert security credentials request.
  1380. self.assert_aws_metadata_request_kwargs(
  1381. request.call_args_list[2][1],
  1382. "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE),
  1383. {
  1384. "Content-Type": "application/json",
  1385. "X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN,
  1386. },
  1387. )
  1388. @mock.patch("google.auth._helpers.utcnow")
  1389. @mock.patch.dict(
  1390. os.environ,
  1391. {
  1392. environment_vars.AWS_REGION: AWS_REGION,
  1393. environment_vars.AWS_SECRET_ACCESS_KEY: SECRET_ACCESS_KEY,
  1394. },
  1395. )
  1396. def test_retrieve_subject_token_success_temp_creds_environment_vars_missing_access_key_id_idmsv2(
  1397. self, utcnow
  1398. ):
  1399. utcnow.return_value = datetime.datetime.strptime(
  1400. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1401. )
  1402. request = self.make_mock_request(
  1403. role_status=http_client.OK,
  1404. role_name=self.AWS_ROLE,
  1405. security_credentials_status=http_client.OK,
  1406. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1407. imdsv2_session_token_status=http_client.OK,
  1408. imdsv2_session_token_data=self.AWS_IMDSV2_SESSION_TOKEN,
  1409. )
  1410. credential_source_token_url = self.CREDENTIAL_SOURCE.copy()
  1411. credential_source_token_url[
  1412. "imdsv2_session_token_url"
  1413. ] = IMDSV2_SESSION_TOKEN_URL
  1414. credentials = self.make_credentials(
  1415. credential_source=credential_source_token_url
  1416. )
  1417. subject_token = credentials.retrieve_subject_token(request)
  1418. assert subject_token == self.make_serialized_aws_signed_request(
  1419. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
  1420. )
  1421. # Assert session token request.
  1422. self.assert_aws_metadata_request_kwargs(
  1423. request.call_args_list[0][1],
  1424. IMDSV2_SESSION_TOKEN_URL,
  1425. {"X-aws-ec2-metadata-token-ttl-seconds": "300"},
  1426. "PUT",
  1427. )
  1428. # Assert role request.
  1429. self.assert_aws_metadata_request_kwargs(
  1430. request.call_args_list[1][1],
  1431. SECURITY_CREDS_URL,
  1432. {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN},
  1433. )
  1434. # Assert security credentials request.
  1435. self.assert_aws_metadata_request_kwargs(
  1436. request.call_args_list[2][1],
  1437. "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE),
  1438. {
  1439. "Content-Type": "application/json",
  1440. "X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN,
  1441. },
  1442. )
  1443. @mock.patch("google.auth._helpers.utcnow")
  1444. @mock.patch.dict(os.environ, {environment_vars.AWS_REGION: AWS_REGION})
  1445. def test_retrieve_subject_token_success_temp_creds_environment_vars_missing_creds_idmsv2(
  1446. self, utcnow
  1447. ):
  1448. utcnow.return_value = datetime.datetime.strptime(
  1449. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1450. )
  1451. request = self.make_mock_request(
  1452. role_status=http_client.OK,
  1453. role_name=self.AWS_ROLE,
  1454. security_credentials_status=http_client.OK,
  1455. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1456. imdsv2_session_token_status=http_client.OK,
  1457. imdsv2_session_token_data=self.AWS_IMDSV2_SESSION_TOKEN,
  1458. )
  1459. credential_source_token_url = self.CREDENTIAL_SOURCE.copy()
  1460. credential_source_token_url[
  1461. "imdsv2_session_token_url"
  1462. ] = IMDSV2_SESSION_TOKEN_URL
  1463. credentials = self.make_credentials(
  1464. credential_source=credential_source_token_url
  1465. )
  1466. subject_token = credentials.retrieve_subject_token(request)
  1467. assert subject_token == self.make_serialized_aws_signed_request(
  1468. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
  1469. )
  1470. # Assert session token request.
  1471. self.assert_aws_metadata_request_kwargs(
  1472. request.call_args_list[0][1],
  1473. IMDSV2_SESSION_TOKEN_URL,
  1474. {"X-aws-ec2-metadata-token-ttl-seconds": "300"},
  1475. "PUT",
  1476. )
  1477. # Assert role request.
  1478. self.assert_aws_metadata_request_kwargs(
  1479. request.call_args_list[1][1],
  1480. SECURITY_CREDS_URL,
  1481. {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN},
  1482. )
  1483. # Assert security credentials request.
  1484. self.assert_aws_metadata_request_kwargs(
  1485. request.call_args_list[2][1],
  1486. "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE),
  1487. {
  1488. "Content-Type": "application/json",
  1489. "X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN,
  1490. },
  1491. )
  1492. @mock.patch("google.auth._helpers.utcnow")
  1493. @mock.patch.dict(
  1494. os.environ,
  1495. {
  1496. environment_vars.AWS_REGION: AWS_REGION,
  1497. environment_vars.AWS_ACCESS_KEY_ID: ACCESS_KEY_ID,
  1498. environment_vars.AWS_SECRET_ACCESS_KEY: SECRET_ACCESS_KEY,
  1499. },
  1500. )
  1501. def test_retrieve_subject_token_success_temp_creds_idmsv2(self, utcnow):
  1502. utcnow.return_value = datetime.datetime.strptime(
  1503. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1504. )
  1505. request = self.make_mock_request(
  1506. role_status=http_client.OK, role_name=self.AWS_ROLE
  1507. )
  1508. credential_source_token_url = self.CREDENTIAL_SOURCE.copy()
  1509. credential_source_token_url[
  1510. "imdsv2_session_token_url"
  1511. ] = IMDSV2_SESSION_TOKEN_URL
  1512. credentials = self.make_credentials(
  1513. credential_source=credential_source_token_url
  1514. )
  1515. credentials.retrieve_subject_token(request)
  1516. assert not request.called
  1517. @mock.patch("google.auth._helpers.utcnow")
  1518. def test_retrieve_subject_token_success_ipv6(self, utcnow):
  1519. utcnow.return_value = datetime.datetime.strptime(
  1520. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1521. )
  1522. request = self.make_mock_request(
  1523. region_status=http_client.OK,
  1524. region_name=self.AWS_REGION,
  1525. role_status=http_client.OK,
  1526. role_name=self.AWS_ROLE,
  1527. security_credentials_status=http_client.OK,
  1528. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1529. imdsv2_session_token_status=http_client.OK,
  1530. imdsv2_session_token_data=self.AWS_IMDSV2_SESSION_TOKEN,
  1531. )
  1532. credential_source_token_url = self.CREDENTIAL_SOURCE_IPV6.copy()
  1533. credentials = self.make_credentials(
  1534. credential_source=credential_source_token_url
  1535. )
  1536. subject_token = credentials.retrieve_subject_token(request)
  1537. assert subject_token == self.make_serialized_aws_signed_request(
  1538. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
  1539. )
  1540. # Assert session token request.
  1541. self.assert_aws_metadata_request_kwargs(
  1542. request.call_args_list[0][1],
  1543. IMDSV2_SESSION_TOKEN_URL_IPV6,
  1544. {"X-aws-ec2-metadata-token-ttl-seconds": "300"},
  1545. "PUT",
  1546. )
  1547. # Assert region request.
  1548. self.assert_aws_metadata_request_kwargs(
  1549. request.call_args_list[1][1],
  1550. REGION_URL_IPV6,
  1551. {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN},
  1552. )
  1553. # Assert session token request.
  1554. self.assert_aws_metadata_request_kwargs(
  1555. request.call_args_list[2][1],
  1556. IMDSV2_SESSION_TOKEN_URL_IPV6,
  1557. {"X-aws-ec2-metadata-token-ttl-seconds": "300"},
  1558. "PUT",
  1559. )
  1560. # Assert role request.
  1561. self.assert_aws_metadata_request_kwargs(
  1562. request.call_args_list[3][1],
  1563. SECURITY_CREDS_URL_IPV6,
  1564. {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN},
  1565. )
  1566. # Assert security credentials request.
  1567. self.assert_aws_metadata_request_kwargs(
  1568. request.call_args_list[4][1],
  1569. "{}/{}".format(SECURITY_CREDS_URL_IPV6, self.AWS_ROLE),
  1570. {
  1571. "Content-Type": "application/json",
  1572. "X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN,
  1573. },
  1574. )
  1575. @mock.patch("google.auth._helpers.utcnow")
  1576. def test_retrieve_subject_token_session_error_idmsv2(self, utcnow):
  1577. utcnow.return_value = datetime.datetime.strptime(
  1578. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1579. )
  1580. request = self.make_mock_request(
  1581. imdsv2_session_token_status=http_client.UNAUTHORIZED,
  1582. imdsv2_session_token_data="unauthorized",
  1583. )
  1584. credential_source_token_url = self.CREDENTIAL_SOURCE.copy()
  1585. credential_source_token_url[
  1586. "imdsv2_session_token_url"
  1587. ] = IMDSV2_SESSION_TOKEN_URL
  1588. credentials = self.make_credentials(
  1589. credential_source=credential_source_token_url
  1590. )
  1591. with pytest.raises(exceptions.RefreshError) as excinfo:
  1592. credentials.retrieve_subject_token(request)
  1593. assert excinfo.match(r"Unable to retrieve AWS Session Token")
  1594. # Assert session token request
  1595. self.assert_aws_metadata_request_kwargs(
  1596. request.call_args_list[0][1],
  1597. IMDSV2_SESSION_TOKEN_URL,
  1598. {"X-aws-ec2-metadata-token-ttl-seconds": "300"},
  1599. "PUT",
  1600. )
  1601. @mock.patch("google.auth._helpers.utcnow")
  1602. def test_retrieve_subject_token_success_permanent_creds_no_environment_vars(
  1603. self, utcnow
  1604. ):
  1605. # Simualte a permanent credential without a session token is
  1606. # returned by the security-credentials endpoint.
  1607. security_creds_response = self.AWS_SECURITY_CREDENTIALS_RESPONSE.copy()
  1608. security_creds_response.pop("Token")
  1609. utcnow.return_value = datetime.datetime.strptime(
  1610. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1611. )
  1612. request = self.make_mock_request(
  1613. region_status=http_client.OK,
  1614. region_name=self.AWS_REGION,
  1615. role_status=http_client.OK,
  1616. role_name=self.AWS_ROLE,
  1617. security_credentials_status=http_client.OK,
  1618. security_credentials_data=security_creds_response,
  1619. )
  1620. credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
  1621. subject_token = credentials.retrieve_subject_token(request)
  1622. assert subject_token == self.make_serialized_aws_signed_request(
  1623. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY)
  1624. )
  1625. @mock.patch("google.auth._helpers.utcnow")
  1626. def test_retrieve_subject_token_success_environment_vars(self, utcnow, monkeypatch):
  1627. monkeypatch.setenv(environment_vars.AWS_ACCESS_KEY_ID, ACCESS_KEY_ID)
  1628. monkeypatch.setenv(environment_vars.AWS_SECRET_ACCESS_KEY, SECRET_ACCESS_KEY)
  1629. monkeypatch.setenv(environment_vars.AWS_SESSION_TOKEN, TOKEN)
  1630. monkeypatch.setenv(environment_vars.AWS_REGION, self.AWS_REGION)
  1631. utcnow.return_value = datetime.datetime.strptime(
  1632. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1633. )
  1634. credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
  1635. subject_token = credentials.retrieve_subject_token(None)
  1636. assert subject_token == self.make_serialized_aws_signed_request(
  1637. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
  1638. )
  1639. @mock.patch("google.auth._helpers.utcnow")
  1640. def test_retrieve_subject_token_success_environment_vars_with_default_region(
  1641. self, utcnow, monkeypatch
  1642. ):
  1643. monkeypatch.setenv(environment_vars.AWS_ACCESS_KEY_ID, ACCESS_KEY_ID)
  1644. monkeypatch.setenv(environment_vars.AWS_SECRET_ACCESS_KEY, SECRET_ACCESS_KEY)
  1645. monkeypatch.setenv(environment_vars.AWS_SESSION_TOKEN, TOKEN)
  1646. monkeypatch.setenv(environment_vars.AWS_DEFAULT_REGION, self.AWS_REGION)
  1647. utcnow.return_value = datetime.datetime.strptime(
  1648. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1649. )
  1650. credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
  1651. subject_token = credentials.retrieve_subject_token(None)
  1652. assert subject_token == self.make_serialized_aws_signed_request(
  1653. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
  1654. )
  1655. @mock.patch("google.auth._helpers.utcnow")
  1656. def test_retrieve_subject_token_success_environment_vars_with_both_regions_set(
  1657. self, utcnow, monkeypatch
  1658. ):
  1659. monkeypatch.setenv(environment_vars.AWS_ACCESS_KEY_ID, ACCESS_KEY_ID)
  1660. monkeypatch.setenv(environment_vars.AWS_SECRET_ACCESS_KEY, SECRET_ACCESS_KEY)
  1661. monkeypatch.setenv(environment_vars.AWS_SESSION_TOKEN, TOKEN)
  1662. monkeypatch.setenv(environment_vars.AWS_DEFAULT_REGION, "Malformed AWS Region")
  1663. # This test makes sure that the AWS_REGION gets used over AWS_DEFAULT_REGION,
  1664. # So, AWS_DEFAULT_REGION is set to something that would cause the test to fail,
  1665. # And AWS_REGION is set to the a valid value, and it should succeed
  1666. monkeypatch.setenv(environment_vars.AWS_REGION, self.AWS_REGION)
  1667. utcnow.return_value = datetime.datetime.strptime(
  1668. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1669. )
  1670. credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
  1671. subject_token = credentials.retrieve_subject_token(None)
  1672. assert subject_token == self.make_serialized_aws_signed_request(
  1673. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
  1674. )
  1675. @mock.patch("google.auth._helpers.utcnow")
  1676. def test_retrieve_subject_token_success_environment_vars_no_session_token(
  1677. self, utcnow, monkeypatch
  1678. ):
  1679. monkeypatch.setenv(environment_vars.AWS_ACCESS_KEY_ID, ACCESS_KEY_ID)
  1680. monkeypatch.setenv(environment_vars.AWS_SECRET_ACCESS_KEY, SECRET_ACCESS_KEY)
  1681. monkeypatch.setenv(environment_vars.AWS_REGION, self.AWS_REGION)
  1682. utcnow.return_value = datetime.datetime.strptime(
  1683. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1684. )
  1685. credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
  1686. subject_token = credentials.retrieve_subject_token(None)
  1687. assert subject_token == self.make_serialized_aws_signed_request(
  1688. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY)
  1689. )
  1690. @mock.patch("google.auth._helpers.utcnow")
  1691. def test_retrieve_subject_token_success_environment_vars_except_region(
  1692. self, utcnow, monkeypatch
  1693. ):
  1694. monkeypatch.setenv(environment_vars.AWS_ACCESS_KEY_ID, ACCESS_KEY_ID)
  1695. monkeypatch.setenv(environment_vars.AWS_SECRET_ACCESS_KEY, SECRET_ACCESS_KEY)
  1696. monkeypatch.setenv(environment_vars.AWS_SESSION_TOKEN, TOKEN)
  1697. utcnow.return_value = datetime.datetime.strptime(
  1698. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1699. )
  1700. # Region will be queried since it is not found in envvars.
  1701. request = self.make_mock_request(
  1702. region_status=http_client.OK, region_name=self.AWS_REGION
  1703. )
  1704. credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
  1705. subject_token = credentials.retrieve_subject_token(request)
  1706. assert subject_token == self.make_serialized_aws_signed_request(
  1707. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
  1708. )
  1709. def test_retrieve_subject_token_error_determining_aws_region(self):
  1710. # Simulate error in retrieving the AWS region.
  1711. request = self.make_mock_request(region_status=http_client.BAD_REQUEST)
  1712. credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
  1713. with pytest.raises(exceptions.RefreshError) as excinfo:
  1714. credentials.retrieve_subject_token(request)
  1715. assert excinfo.match(r"Unable to retrieve AWS region")
  1716. def test_retrieve_subject_token_error_determining_aws_role(self):
  1717. # Simulate error in retrieving the AWS role name.
  1718. request = self.make_mock_request(
  1719. region_status=http_client.OK,
  1720. region_name=self.AWS_REGION,
  1721. role_status=http_client.BAD_REQUEST,
  1722. )
  1723. credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
  1724. with pytest.raises(exceptions.RefreshError) as excinfo:
  1725. credentials.retrieve_subject_token(request)
  1726. assert excinfo.match(r"Unable to retrieve AWS role name")
  1727. def test_retrieve_subject_token_error_determining_security_creds_url(self):
  1728. # Simulate the security-credentials url is missing. This is needed for
  1729. # determining the AWS security credentials when not found in envvars.
  1730. credential_source = self.CREDENTIAL_SOURCE.copy()
  1731. credential_source.pop("url")
  1732. request = self.make_mock_request(
  1733. region_status=http_client.OK, region_name=self.AWS_REGION
  1734. )
  1735. credentials = self.make_credentials(credential_source=credential_source)
  1736. with pytest.raises(exceptions.RefreshError) as excinfo:
  1737. credentials.retrieve_subject_token(request)
  1738. assert excinfo.match(
  1739. r"Unable to determine the AWS metadata server security credentials endpoint"
  1740. )
  1741. def test_retrieve_subject_token_error_determining_aws_security_creds(self):
  1742. # Simulate error in retrieving the AWS security credentials.
  1743. request = self.make_mock_request(
  1744. region_status=http_client.OK,
  1745. region_name=self.AWS_REGION,
  1746. role_status=http_client.OK,
  1747. role_name=self.AWS_ROLE,
  1748. security_credentials_status=http_client.BAD_REQUEST,
  1749. )
  1750. credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
  1751. with pytest.raises(exceptions.RefreshError) as excinfo:
  1752. credentials.retrieve_subject_token(request)
  1753. assert excinfo.match(r"Unable to retrieve AWS security credentials")
  1754. @mock.patch(
  1755. "google.auth.metrics.python_and_auth_lib_version",
  1756. return_value=LANG_LIBRARY_METRICS_HEADER_VALUE,
  1757. )
  1758. @mock.patch("google.auth._helpers.utcnow")
  1759. def test_refresh_success_without_impersonation_ignore_default_scopes(
  1760. self, utcnow, mock_auth_lib_value
  1761. ):
  1762. utcnow.return_value = datetime.datetime.strptime(
  1763. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1764. )
  1765. expected_subject_token = self.make_serialized_aws_signed_request(
  1766. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
  1767. )
  1768. token_headers = {
  1769. "Content-Type": "application/x-www-form-urlencoded",
  1770. "Authorization": "Basic " + BASIC_AUTH_ENCODING,
  1771. "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/false config-lifetime/false source/aws",
  1772. }
  1773. token_request_data = {
  1774. "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
  1775. "audience": AUDIENCE,
  1776. "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
  1777. "scope": " ".join(SCOPES),
  1778. "subject_token": expected_subject_token,
  1779. "subject_token_type": SUBJECT_TOKEN_TYPE,
  1780. }
  1781. request = self.make_mock_request(
  1782. region_status=http_client.OK,
  1783. region_name=self.AWS_REGION,
  1784. role_status=http_client.OK,
  1785. role_name=self.AWS_ROLE,
  1786. security_credentials_status=http_client.OK,
  1787. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1788. token_status=http_client.OK,
  1789. token_data=self.SUCCESS_RESPONSE,
  1790. )
  1791. credentials = self.make_credentials(
  1792. client_id=CLIENT_ID,
  1793. client_secret=CLIENT_SECRET,
  1794. credential_source=self.CREDENTIAL_SOURCE,
  1795. quota_project_id=QUOTA_PROJECT_ID,
  1796. scopes=SCOPES,
  1797. # Default scopes should be ignored.
  1798. default_scopes=["ignored"],
  1799. )
  1800. credentials.refresh(request)
  1801. assert len(request.call_args_list) == 4
  1802. # Fourth request should be sent to GCP STS endpoint.
  1803. self.assert_token_request_kwargs(
  1804. request.call_args_list[3][1], token_headers, token_request_data
  1805. )
  1806. assert credentials.token == self.SUCCESS_RESPONSE["access_token"]
  1807. assert credentials.quota_project_id == QUOTA_PROJECT_ID
  1808. assert credentials.scopes == SCOPES
  1809. assert credentials.default_scopes == ["ignored"]
  1810. @mock.patch(
  1811. "google.auth.metrics.python_and_auth_lib_version",
  1812. return_value=LANG_LIBRARY_METRICS_HEADER_VALUE,
  1813. )
  1814. @mock.patch("google.auth._helpers.utcnow")
  1815. def test_refresh_success_without_impersonation_use_default_scopes(
  1816. self, utcnow, mock_auth_lib_value
  1817. ):
  1818. utcnow.return_value = datetime.datetime.strptime(
  1819. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1820. )
  1821. expected_subject_token = self.make_serialized_aws_signed_request(
  1822. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
  1823. )
  1824. token_headers = {
  1825. "Content-Type": "application/x-www-form-urlencoded",
  1826. "Authorization": "Basic " + BASIC_AUTH_ENCODING,
  1827. "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/false config-lifetime/false source/aws",
  1828. }
  1829. token_request_data = {
  1830. "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
  1831. "audience": AUDIENCE,
  1832. "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
  1833. "scope": " ".join(SCOPES),
  1834. "subject_token": expected_subject_token,
  1835. "subject_token_type": SUBJECT_TOKEN_TYPE,
  1836. }
  1837. request = self.make_mock_request(
  1838. region_status=http_client.OK,
  1839. region_name=self.AWS_REGION,
  1840. role_status=http_client.OK,
  1841. role_name=self.AWS_ROLE,
  1842. security_credentials_status=http_client.OK,
  1843. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1844. token_status=http_client.OK,
  1845. token_data=self.SUCCESS_RESPONSE,
  1846. )
  1847. credentials = self.make_credentials(
  1848. client_id=CLIENT_ID,
  1849. client_secret=CLIENT_SECRET,
  1850. credential_source=self.CREDENTIAL_SOURCE,
  1851. quota_project_id=QUOTA_PROJECT_ID,
  1852. scopes=None,
  1853. # Default scopes should be used since user specified scopes are none.
  1854. default_scopes=SCOPES,
  1855. )
  1856. credentials.refresh(request)
  1857. assert len(request.call_args_list) == 4
  1858. # Fourth request should be sent to GCP STS endpoint.
  1859. self.assert_token_request_kwargs(
  1860. request.call_args_list[3][1], token_headers, token_request_data
  1861. )
  1862. assert credentials.token == self.SUCCESS_RESPONSE["access_token"]
  1863. assert credentials.quota_project_id == QUOTA_PROJECT_ID
  1864. assert credentials.scopes is None
  1865. assert credentials.default_scopes == SCOPES
  1866. @mock.patch(
  1867. "google.auth.metrics.token_request_access_token_impersonate",
  1868. return_value=IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE,
  1869. )
  1870. @mock.patch(
  1871. "google.auth.metrics.python_and_auth_lib_version",
  1872. return_value=LANG_LIBRARY_METRICS_HEADER_VALUE,
  1873. )
  1874. @mock.patch("google.auth._helpers.utcnow")
  1875. def test_refresh_success_with_impersonation_ignore_default_scopes(
  1876. self, utcnow, mock_metrics_header_value, mock_auth_lib_value
  1877. ):
  1878. utcnow.return_value = datetime.datetime.strptime(
  1879. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1880. )
  1881. expire_time = (
  1882. _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=3600)
  1883. ).isoformat("T") + "Z"
  1884. expected_subject_token = self.make_serialized_aws_signed_request(
  1885. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
  1886. )
  1887. token_headers = {
  1888. "Content-Type": "application/x-www-form-urlencoded",
  1889. "Authorization": "Basic " + BASIC_AUTH_ENCODING,
  1890. "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/true config-lifetime/false source/aws",
  1891. }
  1892. token_request_data = {
  1893. "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
  1894. "audience": AUDIENCE,
  1895. "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
  1896. "scope": "https://www.googleapis.com/auth/iam",
  1897. "subject_token": expected_subject_token,
  1898. "subject_token_type": SUBJECT_TOKEN_TYPE,
  1899. }
  1900. # Service account impersonation request/response.
  1901. impersonation_response = {
  1902. "accessToken": "SA_ACCESS_TOKEN",
  1903. "expireTime": expire_time,
  1904. }
  1905. impersonation_headers = {
  1906. "Content-Type": "application/json",
  1907. "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]),
  1908. "x-goog-user-project": QUOTA_PROJECT_ID,
  1909. "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE,
  1910. "x-allowed-locations": "0x0",
  1911. }
  1912. impersonation_request_data = {
  1913. "delegates": None,
  1914. "scope": SCOPES,
  1915. "lifetime": "3600s",
  1916. }
  1917. request = self.make_mock_request(
  1918. region_status=http_client.OK,
  1919. region_name=self.AWS_REGION,
  1920. role_status=http_client.OK,
  1921. role_name=self.AWS_ROLE,
  1922. security_credentials_status=http_client.OK,
  1923. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1924. token_status=http_client.OK,
  1925. token_data=self.SUCCESS_RESPONSE,
  1926. impersonation_status=http_client.OK,
  1927. impersonation_data=impersonation_response,
  1928. )
  1929. credentials = self.make_credentials(
  1930. client_id=CLIENT_ID,
  1931. client_secret=CLIENT_SECRET,
  1932. credential_source=self.CREDENTIAL_SOURCE,
  1933. service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL,
  1934. quota_project_id=QUOTA_PROJECT_ID,
  1935. scopes=SCOPES,
  1936. # Default scopes should be ignored.
  1937. default_scopes=["ignored"],
  1938. )
  1939. credentials.refresh(request)
  1940. assert len(request.call_args_list) == 5
  1941. # Fourth request should be sent to GCP STS endpoint.
  1942. self.assert_token_request_kwargs(
  1943. request.call_args_list[3][1], token_headers, token_request_data
  1944. )
  1945. # Fifth request should be sent to iamcredentials endpoint for service
  1946. # account impersonation.
  1947. self.assert_impersonation_request_kwargs(
  1948. request.call_args_list[4][1],
  1949. impersonation_headers,
  1950. impersonation_request_data,
  1951. )
  1952. assert credentials.token == impersonation_response["accessToken"]
  1953. assert credentials.quota_project_id == QUOTA_PROJECT_ID
  1954. assert credentials.scopes == SCOPES
  1955. assert credentials.default_scopes == ["ignored"]
  1956. @mock.patch(
  1957. "google.auth.metrics.token_request_access_token_impersonate",
  1958. return_value=IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE,
  1959. )
  1960. @mock.patch(
  1961. "google.auth.metrics.python_and_auth_lib_version",
  1962. return_value=LANG_LIBRARY_METRICS_HEADER_VALUE,
  1963. )
  1964. @mock.patch("google.auth._helpers.utcnow")
  1965. def test_refresh_success_with_impersonation_use_default_scopes(
  1966. self, utcnow, mock_metrics_header_value, mock_auth_lib_value
  1967. ):
  1968. utcnow.return_value = datetime.datetime.strptime(
  1969. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1970. )
  1971. expire_time = (
  1972. _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=3600)
  1973. ).isoformat("T") + "Z"
  1974. expected_subject_token = self.make_serialized_aws_signed_request(
  1975. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
  1976. )
  1977. token_headers = {
  1978. "Content-Type": "application/x-www-form-urlencoded",
  1979. "Authorization": "Basic " + BASIC_AUTH_ENCODING,
  1980. "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/true config-lifetime/false source/aws",
  1981. }
  1982. token_request_data = {
  1983. "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
  1984. "audience": AUDIENCE,
  1985. "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
  1986. "scope": "https://www.googleapis.com/auth/iam",
  1987. "subject_token": expected_subject_token,
  1988. "subject_token_type": SUBJECT_TOKEN_TYPE,
  1989. }
  1990. # Service account impersonation request/response.
  1991. impersonation_response = {
  1992. "accessToken": "SA_ACCESS_TOKEN",
  1993. "expireTime": expire_time,
  1994. }
  1995. impersonation_headers = {
  1996. "Content-Type": "application/json",
  1997. "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]),
  1998. "x-goog-user-project": QUOTA_PROJECT_ID,
  1999. "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE,
  2000. "x-allowed-locations": "0x0",
  2001. }
  2002. impersonation_request_data = {
  2003. "delegates": None,
  2004. "scope": SCOPES,
  2005. "lifetime": "3600s",
  2006. }
  2007. request = self.make_mock_request(
  2008. region_status=http_client.OK,
  2009. region_name=self.AWS_REGION,
  2010. role_status=http_client.OK,
  2011. role_name=self.AWS_ROLE,
  2012. security_credentials_status=http_client.OK,
  2013. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  2014. token_status=http_client.OK,
  2015. token_data=self.SUCCESS_RESPONSE,
  2016. impersonation_status=http_client.OK,
  2017. impersonation_data=impersonation_response,
  2018. )
  2019. credentials = self.make_credentials(
  2020. client_id=CLIENT_ID,
  2021. client_secret=CLIENT_SECRET,
  2022. credential_source=self.CREDENTIAL_SOURCE,
  2023. service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL,
  2024. quota_project_id=QUOTA_PROJECT_ID,
  2025. scopes=None,
  2026. # Default scopes should be used since user specified scopes are none.
  2027. default_scopes=SCOPES,
  2028. )
  2029. credentials.refresh(request)
  2030. assert len(request.call_args_list) == 5
  2031. # Fourth request should be sent to GCP STS endpoint.
  2032. self.assert_token_request_kwargs(
  2033. request.call_args_list[3][1], token_headers, token_request_data
  2034. )
  2035. # Fifth request should be sent to iamcredentials endpoint for service
  2036. # account impersonation.
  2037. self.assert_impersonation_request_kwargs(
  2038. request.call_args_list[4][1],
  2039. impersonation_headers,
  2040. impersonation_request_data,
  2041. )
  2042. assert credentials.token == impersonation_response["accessToken"]
  2043. assert credentials.quota_project_id == QUOTA_PROJECT_ID
  2044. assert credentials.scopes is None
  2045. assert credentials.default_scopes == SCOPES
  2046. def test_refresh_with_retrieve_subject_token_error(self):
  2047. request = self.make_mock_request(region_status=http_client.BAD_REQUEST)
  2048. credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
  2049. with pytest.raises(exceptions.RefreshError) as excinfo:
  2050. credentials.refresh(request)
  2051. assert excinfo.match(r"Unable to retrieve AWS region")
  2052. @mock.patch("google.auth._helpers.utcnow")
  2053. def test_retrieve_subject_token_success_with_supplier(self, utcnow):
  2054. utcnow.return_value = datetime.datetime.strptime(
  2055. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  2056. )
  2057. request = self.make_mock_request()
  2058. security_credentials = aws.AwsSecurityCredentials(
  2059. ACCESS_KEY_ID, SECRET_ACCESS_KEY
  2060. )
  2061. supplier = TestAwsSecurityCredentialsSupplier(
  2062. security_credentials=security_credentials, region=self.AWS_REGION
  2063. )
  2064. credentials = self.make_credentials(aws_security_credentials_supplier=supplier)
  2065. subject_token = credentials.retrieve_subject_token(request)
  2066. assert subject_token == self.make_serialized_aws_signed_request(
  2067. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY)
  2068. )
  2069. @mock.patch("google.auth._helpers.utcnow")
  2070. def test_retrieve_subject_token_success_with_supplier_session_token(self, utcnow):
  2071. utcnow.return_value = datetime.datetime.strptime(
  2072. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  2073. )
  2074. request = self.make_mock_request()
  2075. security_credentials = aws.AwsSecurityCredentials(
  2076. ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN
  2077. )
  2078. supplier = TestAwsSecurityCredentialsSupplier(
  2079. security_credentials=security_credentials, region=self.AWS_REGION
  2080. )
  2081. credentials = self.make_credentials(aws_security_credentials_supplier=supplier)
  2082. subject_token = credentials.retrieve_subject_token(request)
  2083. assert subject_token == self.make_serialized_aws_signed_request(
  2084. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
  2085. )
  2086. @mock.patch("google.auth._helpers.utcnow")
  2087. def test_retrieve_subject_token_success_with_supplier_correct_context(self, utcnow):
  2088. utcnow.return_value = datetime.datetime.strptime(
  2089. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  2090. )
  2091. request = self.make_mock_request()
  2092. expected_context = external_account.SupplierContext(
  2093. SUBJECT_TOKEN_TYPE, AUDIENCE
  2094. )
  2095. security_credentials = aws.AwsSecurityCredentials(
  2096. ACCESS_KEY_ID, SECRET_ACCESS_KEY
  2097. )
  2098. supplier = TestAwsSecurityCredentialsSupplier(
  2099. security_credentials=security_credentials,
  2100. region=self.AWS_REGION,
  2101. expected_context=expected_context,
  2102. )
  2103. credentials = self.make_credentials(aws_security_credentials_supplier=supplier)
  2104. credentials.retrieve_subject_token(request)
  2105. def test_retrieve_subject_token_error_with_supplier(self):
  2106. request = self.make_mock_request()
  2107. expected_exception = exceptions.RefreshError("Test error")
  2108. supplier = TestAwsSecurityCredentialsSupplier(
  2109. region=self.AWS_REGION, credentials_exception=expected_exception
  2110. )
  2111. credentials = self.make_credentials(aws_security_credentials_supplier=supplier)
  2112. with pytest.raises(exceptions.RefreshError) as excinfo:
  2113. credentials.refresh(request)
  2114. assert excinfo.match(r"Test error")
  2115. def test_retrieve_subject_token_error_with_supplier_region(self):
  2116. request = self.make_mock_request()
  2117. expected_exception = exceptions.RefreshError("Test error")
  2118. security_credentials = aws.AwsSecurityCredentials(
  2119. ACCESS_KEY_ID, SECRET_ACCESS_KEY
  2120. )
  2121. supplier = TestAwsSecurityCredentialsSupplier(
  2122. security_credentials=security_credentials,
  2123. region_exception=expected_exception,
  2124. )
  2125. credentials = self.make_credentials(aws_security_credentials_supplier=supplier)
  2126. with pytest.raises(exceptions.RefreshError) as excinfo:
  2127. credentials.refresh(request)
  2128. assert excinfo.match(r"Test error")
  2129. @mock.patch(
  2130. "google.auth.metrics.python_and_auth_lib_version",
  2131. return_value=LANG_LIBRARY_METRICS_HEADER_VALUE,
  2132. )
  2133. @mock.patch("google.auth._helpers.utcnow")
  2134. def test_refresh_success_with_supplier_with_impersonation(
  2135. self, utcnow, mock_auth_lib_value
  2136. ):
  2137. utcnow.return_value = datetime.datetime.strptime(
  2138. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  2139. )
  2140. expire_time = (
  2141. _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=3600)
  2142. ).isoformat("T") + "Z"
  2143. expected_subject_token = self.make_serialized_aws_signed_request(
  2144. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
  2145. )
  2146. token_headers = {
  2147. "Content-Type": "application/x-www-form-urlencoded",
  2148. "Authorization": "Basic " + BASIC_AUTH_ENCODING,
  2149. "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/true config-lifetime/false source/programmatic",
  2150. }
  2151. token_request_data = {
  2152. "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
  2153. "audience": AUDIENCE,
  2154. "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
  2155. "scope": "https://www.googleapis.com/auth/iam",
  2156. "subject_token": expected_subject_token,
  2157. "subject_token_type": SUBJECT_TOKEN_TYPE,
  2158. }
  2159. # Service account impersonation request/response.
  2160. impersonation_response = {
  2161. "accessToken": "SA_ACCESS_TOKEN",
  2162. "expireTime": expire_time,
  2163. }
  2164. impersonation_headers = {
  2165. "Content-Type": "application/json",
  2166. "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]),
  2167. "x-goog-user-project": QUOTA_PROJECT_ID,
  2168. "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE,
  2169. "x-allowed-locations": "0x0",
  2170. }
  2171. impersonation_request_data = {
  2172. "delegates": None,
  2173. "scope": SCOPES,
  2174. "lifetime": "3600s",
  2175. }
  2176. request = self.make_mock_request(
  2177. token_status=http_client.OK,
  2178. token_data=self.SUCCESS_RESPONSE,
  2179. impersonation_status=http_client.OK,
  2180. impersonation_data=impersonation_response,
  2181. )
  2182. supplier = TestAwsSecurityCredentialsSupplier(
  2183. security_credentials=aws.AwsSecurityCredentials(
  2184. ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN
  2185. ),
  2186. region=self.AWS_REGION,
  2187. )
  2188. credentials = self.make_credentials(
  2189. client_id=CLIENT_ID,
  2190. client_secret=CLIENT_SECRET,
  2191. aws_security_credentials_supplier=supplier,
  2192. service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL,
  2193. quota_project_id=QUOTA_PROJECT_ID,
  2194. scopes=SCOPES,
  2195. # Default scopes should be ignored.
  2196. default_scopes=["ignored"],
  2197. )
  2198. credentials.refresh(request)
  2199. assert len(request.call_args_list) == 2
  2200. # First request should be sent to GCP STS endpoint.
  2201. self.assert_token_request_kwargs(
  2202. request.call_args_list[0][1], token_headers, token_request_data
  2203. )
  2204. # Second request should be sent to iamcredentials endpoint for service
  2205. # account impersonation.
  2206. self.assert_impersonation_request_kwargs(
  2207. request.call_args_list[1][1],
  2208. impersonation_headers,
  2209. impersonation_request_data,
  2210. )
  2211. assert credentials.token == impersonation_response["accessToken"]
  2212. assert credentials.quota_project_id == QUOTA_PROJECT_ID
  2213. assert credentials.scopes == SCOPES
  2214. assert credentials.default_scopes == ["ignored"]
  2215. @mock.patch(
  2216. "google.auth.metrics.python_and_auth_lib_version",
  2217. return_value=LANG_LIBRARY_METRICS_HEADER_VALUE,
  2218. )
  2219. @mock.patch("google.auth._helpers.utcnow")
  2220. def test_refresh_success_with_supplier(self, utcnow, mock_auth_lib_value):
  2221. utcnow.return_value = datetime.datetime.strptime(
  2222. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  2223. )
  2224. expected_subject_token = self.make_serialized_aws_signed_request(
  2225. aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
  2226. )
  2227. token_headers = {
  2228. "Content-Type": "application/x-www-form-urlencoded",
  2229. "Authorization": "Basic " + BASIC_AUTH_ENCODING,
  2230. "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/false config-lifetime/false source/programmatic",
  2231. }
  2232. token_request_data = {
  2233. "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
  2234. "audience": AUDIENCE,
  2235. "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
  2236. "scope": " ".join(SCOPES),
  2237. "subject_token": expected_subject_token,
  2238. "subject_token_type": SUBJECT_TOKEN_TYPE,
  2239. }
  2240. request = self.make_mock_request(
  2241. token_status=http_client.OK, token_data=self.SUCCESS_RESPONSE
  2242. )
  2243. supplier = TestAwsSecurityCredentialsSupplier(
  2244. security_credentials=aws.AwsSecurityCredentials(
  2245. ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN
  2246. ),
  2247. region=self.AWS_REGION,
  2248. )
  2249. credentials = self.make_credentials(
  2250. client_id=CLIENT_ID,
  2251. client_secret=CLIENT_SECRET,
  2252. aws_security_credentials_supplier=supplier,
  2253. quota_project_id=QUOTA_PROJECT_ID,
  2254. scopes=SCOPES,
  2255. # Default scopes should be ignored.
  2256. default_scopes=["ignored"],
  2257. )
  2258. credentials.refresh(request)
  2259. assert len(request.call_args_list) == 1
  2260. # First request should be sent to GCP STS endpoint.
  2261. self.assert_token_request_kwargs(
  2262. request.call_args_list[0][1], token_headers, token_request_data
  2263. )
  2264. assert credentials.token == self.SUCCESS_RESPONSE["access_token"]
  2265. assert credentials.quota_project_id == QUOTA_PROJECT_ID
  2266. assert credentials.scopes == SCOPES
  2267. assert credentials.default_scopes == ["ignored"]