test_aws.py 87 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125
  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
  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. IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE = (
  27. "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/imp"
  28. )
  29. LANG_LIBRARY_METRICS_HEADER_VALUE = "gl-python/3.7 auth/1.1"
  30. CLIENT_ID = "username"
  31. CLIENT_SECRET = "password"
  32. # Base64 encoding of "username:password".
  33. BASIC_AUTH_ENCODING = "dXNlcm5hbWU6cGFzc3dvcmQ="
  34. SERVICE_ACCOUNT_EMAIL = "service-1234@service-name.iam.gserviceaccount.com"
  35. SERVICE_ACCOUNT_IMPERSONATION_URL_BASE = (
  36. "https://us-east1-iamcredentials.googleapis.com"
  37. )
  38. SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE = "/v1/projects/-/serviceAccounts/{}:generateAccessToken".format(
  39. SERVICE_ACCOUNT_EMAIL
  40. )
  41. SERVICE_ACCOUNT_IMPERSONATION_URL = (
  42. SERVICE_ACCOUNT_IMPERSONATION_URL_BASE + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE
  43. )
  44. QUOTA_PROJECT_ID = "QUOTA_PROJECT_ID"
  45. SCOPES = ["scope1", "scope2"]
  46. TOKEN_URL = "https://sts.googleapis.com/v1/token"
  47. TOKEN_INFO_URL = "https://sts.googleapis.com/v1/introspect"
  48. SUBJECT_TOKEN_TYPE = "urn:ietf:params:aws:token-type:aws4_request"
  49. AUDIENCE = "//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID"
  50. REGION_URL = "http://169.254.169.254/latest/meta-data/placement/availability-zone"
  51. IMDSV2_SESSION_TOKEN_URL = "http://169.254.169.254/latest/api/token"
  52. SECURITY_CREDS_URL = "http://169.254.169.254/latest/meta-data/iam/security-credentials"
  53. REGION_URL_IPV6 = "http://[fd00:ec2::254]/latest/meta-data/placement/availability-zone"
  54. IMDSV2_SESSION_TOKEN_URL_IPV6 = "http://[fd00:ec2::254]/latest/api/token"
  55. SECURITY_CREDS_URL_IPV6 = (
  56. "http://[fd00:ec2::254]/latest/meta-data/iam/security-credentials"
  57. )
  58. CRED_VERIFICATION_URL = (
  59. "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
  60. )
  61. # Sample fictitious AWS security credentials to be used with tests that require a session token.
  62. ACCESS_KEY_ID = "AKIAIOSFODNN7EXAMPLE"
  63. SECRET_ACCESS_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
  64. TOKEN = "AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKwRcOIfrRh3c/LTo6UDdyJwOOvEVPvLXCrrrUtdnniCEXAMPLE/IvU1dYUg2RVAJBanLiHb4IgRmpRV3zrkuWJOgQs8IZZaIv2BXIa2R4OlgkBN9bkUDNCJiBeb/AXlzBBko7b15fjrBs2+cTQtpZ3CYWFXG8C5zqx37wnOE49mRl/+OtkIKGO7fAE"
  65. # To avoid json.dumps() differing behavior from one version to other,
  66. # the JSON payload is hardcoded.
  67. REQUEST_PARAMS = '{"KeySchema":[{"KeyType":"HASH","AttributeName":"Id"}],"TableName":"TestTable","AttributeDefinitions":[{"AttributeName":"Id","AttributeType":"S"}],"ProvisionedThroughput":{"WriteCapacityUnits":5,"ReadCapacityUnits":5}}'
  68. # Each tuple contains the following entries:
  69. # region, time, credentials, original_request, signed_request
  70. DEFAULT_UNIVERSE_DOMAIN = "googleapis.com"
  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. actual_signed_request = request_signer.get_request_options(
  611. credentials,
  612. original_request.get("url"),
  613. original_request.get("method"),
  614. original_request.get("data"),
  615. original_request.get("headers"),
  616. )
  617. assert actual_signed_request == signed_request
  618. def test_get_request_options_with_missing_scheme_url(self):
  619. request_signer = aws.RequestSigner("us-east-2")
  620. with pytest.raises(ValueError) as excinfo:
  621. request_signer.get_request_options(
  622. {
  623. "access_key_id": ACCESS_KEY_ID,
  624. "secret_access_key": SECRET_ACCESS_KEY,
  625. },
  626. "invalid",
  627. "POST",
  628. )
  629. assert excinfo.match(r"Invalid AWS service URL")
  630. def test_get_request_options_with_invalid_scheme_url(self):
  631. request_signer = aws.RequestSigner("us-east-2")
  632. with pytest.raises(ValueError) as excinfo:
  633. request_signer.get_request_options(
  634. {
  635. "access_key_id": ACCESS_KEY_ID,
  636. "secret_access_key": SECRET_ACCESS_KEY,
  637. },
  638. "http://invalid",
  639. "POST",
  640. )
  641. assert excinfo.match(r"Invalid AWS service URL")
  642. def test_get_request_options_with_missing_hostname_url(self):
  643. request_signer = aws.RequestSigner("us-east-2")
  644. with pytest.raises(ValueError) as excinfo:
  645. request_signer.get_request_options(
  646. {
  647. "access_key_id": ACCESS_KEY_ID,
  648. "secret_access_key": SECRET_ACCESS_KEY,
  649. },
  650. "https://",
  651. "POST",
  652. )
  653. assert excinfo.match(r"Invalid AWS service URL")
  654. class TestCredentials(object):
  655. AWS_REGION = "us-east-2"
  656. AWS_ROLE = "gcp-aws-role"
  657. AWS_SECURITY_CREDENTIALS_RESPONSE = {
  658. "AccessKeyId": ACCESS_KEY_ID,
  659. "SecretAccessKey": SECRET_ACCESS_KEY,
  660. "Token": TOKEN,
  661. }
  662. AWS_IMDSV2_SESSION_TOKEN = "awsimdsv2sessiontoken"
  663. AWS_SIGNATURE_TIME = "2020-08-11T06:55:22Z"
  664. CREDENTIAL_SOURCE = {
  665. "environment_id": "aws1",
  666. "region_url": REGION_URL,
  667. "url": SECURITY_CREDS_URL,
  668. "regional_cred_verification_url": CRED_VERIFICATION_URL,
  669. }
  670. CREDENTIAL_SOURCE_IPV6 = {
  671. "environment_id": "aws1",
  672. "region_url": REGION_URL_IPV6,
  673. "url": SECURITY_CREDS_URL_IPV6,
  674. "regional_cred_verification_url": CRED_VERIFICATION_URL,
  675. "imdsv2_session_token_url": IMDSV2_SESSION_TOKEN_URL_IPV6,
  676. }
  677. SUCCESS_RESPONSE = {
  678. "access_token": "ACCESS_TOKEN",
  679. "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
  680. "token_type": "Bearer",
  681. "expires_in": 3600,
  682. "scope": " ".join(SCOPES),
  683. }
  684. @classmethod
  685. def make_serialized_aws_signed_request(
  686. cls,
  687. aws_security_credentials,
  688. region_name="us-east-2",
  689. url="https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15",
  690. ):
  691. """Utility to generate serialize AWS signed requests.
  692. This makes it easy to assert generated subject tokens based on the
  693. provided AWS security credentials, regions and AWS STS endpoint.
  694. """
  695. request_signer = aws.RequestSigner(region_name)
  696. signed_request = request_signer.get_request_options(
  697. aws_security_credentials, url, "POST"
  698. )
  699. reformatted_signed_request = {
  700. "url": signed_request.get("url"),
  701. "method": signed_request.get("method"),
  702. "headers": [
  703. {
  704. "key": "Authorization",
  705. "value": signed_request.get("headers").get("Authorization"),
  706. },
  707. {"key": "host", "value": signed_request.get("headers").get("host")},
  708. {
  709. "key": "x-amz-date",
  710. "value": signed_request.get("headers").get("x-amz-date"),
  711. },
  712. ],
  713. }
  714. # Include security token if available.
  715. if "security_token" in aws_security_credentials:
  716. reformatted_signed_request.get("headers").append(
  717. {
  718. "key": "x-amz-security-token",
  719. "value": signed_request.get("headers").get("x-amz-security-token"),
  720. }
  721. )
  722. # Append x-goog-cloud-target-resource header.
  723. reformatted_signed_request.get("headers").append(
  724. {"key": "x-goog-cloud-target-resource", "value": AUDIENCE}
  725. ),
  726. return urllib.parse.quote(
  727. json.dumps(
  728. reformatted_signed_request, separators=(",", ":"), sort_keys=True
  729. )
  730. )
  731. @classmethod
  732. def make_mock_request(
  733. cls,
  734. region_status=None,
  735. region_name=None,
  736. role_status=None,
  737. role_name=None,
  738. security_credentials_status=None,
  739. security_credentials_data=None,
  740. token_status=None,
  741. token_data=None,
  742. impersonation_status=None,
  743. impersonation_data=None,
  744. imdsv2_session_token_status=None,
  745. imdsv2_session_token_data=None,
  746. ):
  747. """Utility function to generate a mock HTTP request object.
  748. This will facilitate testing various edge cases by specify how the
  749. various endpoints will respond while generating a Google Access token
  750. in an AWS environment.
  751. """
  752. responses = []
  753. if imdsv2_session_token_status:
  754. # AWS session token request
  755. imdsv2_session_response = mock.create_autospec(
  756. transport.Response, instance=True
  757. )
  758. imdsv2_session_response.status = imdsv2_session_token_status
  759. imdsv2_session_response.data = imdsv2_session_token_data
  760. responses.append(imdsv2_session_response)
  761. if region_status:
  762. # AWS region request.
  763. region_response = mock.create_autospec(transport.Response, instance=True)
  764. region_response.status = region_status
  765. if region_name:
  766. region_response.data = "{}b".format(region_name).encode("utf-8")
  767. responses.append(region_response)
  768. if role_status:
  769. # AWS role name request.
  770. role_response = mock.create_autospec(transport.Response, instance=True)
  771. role_response.status = role_status
  772. if role_name:
  773. role_response.data = role_name.encode("utf-8")
  774. responses.append(role_response)
  775. if security_credentials_status:
  776. # AWS security credentials request.
  777. security_credentials_response = mock.create_autospec(
  778. transport.Response, instance=True
  779. )
  780. security_credentials_response.status = security_credentials_status
  781. if security_credentials_data:
  782. security_credentials_response.data = json.dumps(
  783. security_credentials_data
  784. ).encode("utf-8")
  785. responses.append(security_credentials_response)
  786. if token_status:
  787. # GCP token exchange request.
  788. token_response = mock.create_autospec(transport.Response, instance=True)
  789. token_response.status = token_status
  790. token_response.data = json.dumps(token_data).encode("utf-8")
  791. responses.append(token_response)
  792. if impersonation_status:
  793. # Service account impersonation request.
  794. impersonation_response = mock.create_autospec(
  795. transport.Response, instance=True
  796. )
  797. impersonation_response.status = impersonation_status
  798. impersonation_response.data = json.dumps(impersonation_data).encode("utf-8")
  799. responses.append(impersonation_response)
  800. request = mock.create_autospec(transport.Request)
  801. request.side_effect = responses
  802. return request
  803. @classmethod
  804. def make_credentials(
  805. cls,
  806. credential_source,
  807. token_url=TOKEN_URL,
  808. token_info_url=TOKEN_INFO_URL,
  809. client_id=None,
  810. client_secret=None,
  811. quota_project_id=None,
  812. scopes=None,
  813. default_scopes=None,
  814. service_account_impersonation_url=None,
  815. ):
  816. return aws.Credentials(
  817. audience=AUDIENCE,
  818. subject_token_type=SUBJECT_TOKEN_TYPE,
  819. token_url=token_url,
  820. token_info_url=token_info_url,
  821. service_account_impersonation_url=service_account_impersonation_url,
  822. credential_source=credential_source,
  823. client_id=client_id,
  824. client_secret=client_secret,
  825. quota_project_id=quota_project_id,
  826. scopes=scopes,
  827. default_scopes=default_scopes,
  828. )
  829. @classmethod
  830. def assert_aws_metadata_request_kwargs(
  831. cls, request_kwargs, url, headers=None, method="GET"
  832. ):
  833. assert request_kwargs["url"] == url
  834. # All used AWS metadata server endpoints use GET HTTP method.
  835. assert request_kwargs["method"] == method
  836. if headers:
  837. assert request_kwargs["headers"] == headers
  838. else:
  839. assert "headers" not in request_kwargs or request_kwargs["headers"] is None
  840. # None of the endpoints used require any data in request.
  841. assert "body" not in request_kwargs
  842. @classmethod
  843. def assert_token_request_kwargs(
  844. cls, request_kwargs, headers, request_data, token_url=TOKEN_URL
  845. ):
  846. assert request_kwargs["url"] == token_url
  847. assert request_kwargs["method"] == "POST"
  848. assert request_kwargs["headers"] == headers
  849. assert request_kwargs["body"] is not None
  850. body_tuples = urllib.parse.parse_qsl(request_kwargs["body"])
  851. assert len(body_tuples) == len(request_data.keys())
  852. for (k, v) in body_tuples:
  853. assert v.decode("utf-8") == request_data[k.decode("utf-8")]
  854. @classmethod
  855. def assert_impersonation_request_kwargs(
  856. cls,
  857. request_kwargs,
  858. headers,
  859. request_data,
  860. service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL,
  861. ):
  862. assert request_kwargs["url"] == service_account_impersonation_url
  863. assert request_kwargs["method"] == "POST"
  864. assert request_kwargs["headers"] == headers
  865. assert request_kwargs["body"] is not None
  866. body_json = json.loads(request_kwargs["body"].decode("utf-8"))
  867. assert body_json == request_data
  868. @mock.patch.object(aws.Credentials, "__init__", return_value=None)
  869. def test_from_info_full_options(self, mock_init):
  870. credentials = aws.Credentials.from_info(
  871. {
  872. "audience": AUDIENCE,
  873. "subject_token_type": SUBJECT_TOKEN_TYPE,
  874. "token_url": TOKEN_URL,
  875. "token_info_url": TOKEN_INFO_URL,
  876. "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL,
  877. "service_account_impersonation": {"token_lifetime_seconds": 2800},
  878. "client_id": CLIENT_ID,
  879. "client_secret": CLIENT_SECRET,
  880. "quota_project_id": QUOTA_PROJECT_ID,
  881. "credential_source": self.CREDENTIAL_SOURCE,
  882. }
  883. )
  884. # Confirm aws.Credentials instance initialized with the expected parameters.
  885. assert isinstance(credentials, aws.Credentials)
  886. mock_init.assert_called_once_with(
  887. audience=AUDIENCE,
  888. subject_token_type=SUBJECT_TOKEN_TYPE,
  889. token_url=TOKEN_URL,
  890. token_info_url=TOKEN_INFO_URL,
  891. service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL,
  892. service_account_impersonation_options={"token_lifetime_seconds": 2800},
  893. client_id=CLIENT_ID,
  894. client_secret=CLIENT_SECRET,
  895. credential_source=self.CREDENTIAL_SOURCE,
  896. quota_project_id=QUOTA_PROJECT_ID,
  897. workforce_pool_user_project=None,
  898. universe_domain=DEFAULT_UNIVERSE_DOMAIN,
  899. )
  900. @mock.patch.object(aws.Credentials, "__init__", return_value=None)
  901. def test_from_info_required_options_only(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. "credential_source": self.CREDENTIAL_SOURCE,
  908. }
  909. )
  910. # Confirm aws.Credentials instance initialized with the expected parameters.
  911. assert isinstance(credentials, aws.Credentials)
  912. mock_init.assert_called_once_with(
  913. audience=AUDIENCE,
  914. subject_token_type=SUBJECT_TOKEN_TYPE,
  915. token_url=TOKEN_URL,
  916. token_info_url=None,
  917. service_account_impersonation_url=None,
  918. service_account_impersonation_options={},
  919. client_id=None,
  920. client_secret=None,
  921. credential_source=self.CREDENTIAL_SOURCE,
  922. quota_project_id=None,
  923. workforce_pool_user_project=None,
  924. universe_domain=DEFAULT_UNIVERSE_DOMAIN,
  925. )
  926. @mock.patch.object(aws.Credentials, "__init__", return_value=None)
  927. def test_from_file_full_options(self, mock_init, tmpdir):
  928. info = {
  929. "audience": AUDIENCE,
  930. "subject_token_type": SUBJECT_TOKEN_TYPE,
  931. "token_url": TOKEN_URL,
  932. "token_info_url": TOKEN_INFO_URL,
  933. "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL,
  934. "service_account_impersonation": {"token_lifetime_seconds": 2800},
  935. "client_id": CLIENT_ID,
  936. "client_secret": CLIENT_SECRET,
  937. "quota_project_id": QUOTA_PROJECT_ID,
  938. "credential_source": self.CREDENTIAL_SOURCE,
  939. "universe_domain": DEFAULT_UNIVERSE_DOMAIN,
  940. }
  941. config_file = tmpdir.join("config.json")
  942. config_file.write(json.dumps(info))
  943. credentials = aws.Credentials.from_file(str(config_file))
  944. # Confirm aws.Credentials instance initialized with the expected parameters.
  945. assert isinstance(credentials, aws.Credentials)
  946. mock_init.assert_called_once_with(
  947. audience=AUDIENCE,
  948. subject_token_type=SUBJECT_TOKEN_TYPE,
  949. token_url=TOKEN_URL,
  950. token_info_url=TOKEN_INFO_URL,
  951. service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL,
  952. service_account_impersonation_options={"token_lifetime_seconds": 2800},
  953. client_id=CLIENT_ID,
  954. client_secret=CLIENT_SECRET,
  955. credential_source=self.CREDENTIAL_SOURCE,
  956. quota_project_id=QUOTA_PROJECT_ID,
  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_file_required_options_only(self, mock_init, tmpdir):
  962. info = {
  963. "audience": AUDIENCE,
  964. "subject_token_type": SUBJECT_TOKEN_TYPE,
  965. "token_url": TOKEN_URL,
  966. "credential_source": self.CREDENTIAL_SOURCE,
  967. }
  968. config_file = tmpdir.join("config.json")
  969. config_file.write(json.dumps(info))
  970. credentials = aws.Credentials.from_file(str(config_file))
  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=self.CREDENTIAL_SOURCE,
  983. quota_project_id=None,
  984. workforce_pool_user_project=None,
  985. universe_domain=DEFAULT_UNIVERSE_DOMAIN,
  986. )
  987. def test_constructor_invalid_credential_source(self):
  988. # Provide invalid credential source.
  989. credential_source = {"unsupported": "value"}
  990. with pytest.raises(ValueError) as excinfo:
  991. self.make_credentials(credential_source=credential_source)
  992. assert excinfo.match(r"No valid AWS 'credential_source' provided")
  993. def test_constructor_invalid_environment_id(self):
  994. # Provide invalid environment_id.
  995. credential_source = self.CREDENTIAL_SOURCE.copy()
  996. credential_source["environment_id"] = "azure1"
  997. with pytest.raises(ValueError) as excinfo:
  998. self.make_credentials(credential_source=credential_source)
  999. assert excinfo.match(r"No valid AWS 'credential_source' provided")
  1000. def test_constructor_missing_cred_verification_url(self):
  1001. # regional_cred_verification_url is a required field.
  1002. credential_source = self.CREDENTIAL_SOURCE.copy()
  1003. credential_source.pop("regional_cred_verification_url")
  1004. with pytest.raises(ValueError) as excinfo:
  1005. self.make_credentials(credential_source=credential_source)
  1006. assert excinfo.match(r"No valid AWS 'credential_source' provided")
  1007. def test_constructor_invalid_environment_id_version(self):
  1008. # Provide an unsupported version.
  1009. credential_source = self.CREDENTIAL_SOURCE.copy()
  1010. credential_source["environment_id"] = "aws3"
  1011. with pytest.raises(ValueError) as excinfo:
  1012. self.make_credentials(credential_source=credential_source)
  1013. assert excinfo.match(r"aws version '3' is not supported in the current build.")
  1014. def test_info(self):
  1015. credentials = self.make_credentials(
  1016. credential_source=self.CREDENTIAL_SOURCE.copy()
  1017. )
  1018. assert credentials.info == {
  1019. "type": "external_account",
  1020. "audience": AUDIENCE,
  1021. "subject_token_type": SUBJECT_TOKEN_TYPE,
  1022. "token_url": TOKEN_URL,
  1023. "token_info_url": TOKEN_INFO_URL,
  1024. "credential_source": self.CREDENTIAL_SOURCE,
  1025. "universe_domain": DEFAULT_UNIVERSE_DOMAIN,
  1026. }
  1027. def test_token_info_url(self):
  1028. credentials = self.make_credentials(
  1029. credential_source=self.CREDENTIAL_SOURCE.copy()
  1030. )
  1031. assert credentials.token_info_url == TOKEN_INFO_URL
  1032. def test_token_info_url_custom(self):
  1033. for url in VALID_TOKEN_URLS:
  1034. credentials = self.make_credentials(
  1035. credential_source=self.CREDENTIAL_SOURCE.copy(),
  1036. token_info_url=(url + "/introspect"),
  1037. )
  1038. assert credentials.token_info_url == (url + "/introspect")
  1039. def test_token_info_url_negative(self):
  1040. credentials = self.make_credentials(
  1041. credential_source=self.CREDENTIAL_SOURCE.copy(), token_info_url=None
  1042. )
  1043. assert not credentials.token_info_url
  1044. def test_token_url_custom(self):
  1045. for url in VALID_TOKEN_URLS:
  1046. credentials = self.make_credentials(
  1047. credential_source=self.CREDENTIAL_SOURCE.copy(),
  1048. token_url=(url + "/token"),
  1049. )
  1050. assert credentials._token_url == (url + "/token")
  1051. def test_service_account_impersonation_url_custom(self):
  1052. for url in VALID_SERVICE_ACCOUNT_IMPERSONATION_URLS:
  1053. credentials = self.make_credentials(
  1054. credential_source=self.CREDENTIAL_SOURCE.copy(),
  1055. service_account_impersonation_url=(
  1056. url + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE
  1057. ),
  1058. )
  1059. assert credentials._service_account_impersonation_url == (
  1060. url + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE
  1061. )
  1062. def test_retrieve_subject_token_missing_region_url(self):
  1063. # When AWS_REGION envvar is not available, region_url is required for
  1064. # determining the current AWS region.
  1065. credential_source = self.CREDENTIAL_SOURCE.copy()
  1066. credential_source.pop("region_url")
  1067. credentials = self.make_credentials(credential_source=credential_source)
  1068. with pytest.raises(exceptions.RefreshError) as excinfo:
  1069. credentials.retrieve_subject_token(None)
  1070. assert excinfo.match(r"Unable to determine AWS region")
  1071. @mock.patch("google.auth._helpers.utcnow")
  1072. def test_retrieve_subject_token_success_temp_creds_no_environment_vars(
  1073. self, utcnow
  1074. ):
  1075. utcnow.return_value = datetime.datetime.strptime(
  1076. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1077. )
  1078. request = self.make_mock_request(
  1079. region_status=http_client.OK,
  1080. region_name=self.AWS_REGION,
  1081. role_status=http_client.OK,
  1082. role_name=self.AWS_ROLE,
  1083. security_credentials_status=http_client.OK,
  1084. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1085. )
  1086. credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
  1087. subject_token = credentials.retrieve_subject_token(request)
  1088. assert subject_token == self.make_serialized_aws_signed_request(
  1089. {
  1090. "access_key_id": ACCESS_KEY_ID,
  1091. "secret_access_key": SECRET_ACCESS_KEY,
  1092. "security_token": TOKEN,
  1093. }
  1094. )
  1095. # Assert region request.
  1096. self.assert_aws_metadata_request_kwargs(
  1097. request.call_args_list[0][1], REGION_URL
  1098. )
  1099. # Assert role request.
  1100. self.assert_aws_metadata_request_kwargs(
  1101. request.call_args_list[1][1], SECURITY_CREDS_URL
  1102. )
  1103. # Assert security credentials request.
  1104. self.assert_aws_metadata_request_kwargs(
  1105. request.call_args_list[2][1],
  1106. "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE),
  1107. {"Content-Type": "application/json"},
  1108. )
  1109. # Retrieve subject_token again. Region should not be queried again.
  1110. new_request = self.make_mock_request(
  1111. role_status=http_client.OK,
  1112. role_name=self.AWS_ROLE,
  1113. security_credentials_status=http_client.OK,
  1114. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1115. )
  1116. credentials.retrieve_subject_token(new_request)
  1117. # Only 3 requests should be sent as the region is cached.
  1118. assert len(new_request.call_args_list) == 2
  1119. # Assert role request.
  1120. self.assert_aws_metadata_request_kwargs(
  1121. new_request.call_args_list[0][1], SECURITY_CREDS_URL
  1122. )
  1123. # Assert security credentials request.
  1124. self.assert_aws_metadata_request_kwargs(
  1125. new_request.call_args_list[1][1],
  1126. "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE),
  1127. {"Content-Type": "application/json"},
  1128. )
  1129. @mock.patch("google.auth._helpers.utcnow")
  1130. @mock.patch.dict(os.environ, {})
  1131. def test_retrieve_subject_token_success_temp_creds_no_environment_vars_idmsv2(
  1132. self, utcnow
  1133. ):
  1134. utcnow.return_value = datetime.datetime.strptime(
  1135. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1136. )
  1137. request = self.make_mock_request(
  1138. region_status=http_client.OK,
  1139. region_name=self.AWS_REGION,
  1140. role_status=http_client.OK,
  1141. role_name=self.AWS_ROLE,
  1142. security_credentials_status=http_client.OK,
  1143. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1144. imdsv2_session_token_status=http_client.OK,
  1145. imdsv2_session_token_data=self.AWS_IMDSV2_SESSION_TOKEN,
  1146. )
  1147. credential_source_token_url = self.CREDENTIAL_SOURCE.copy()
  1148. credential_source_token_url[
  1149. "imdsv2_session_token_url"
  1150. ] = IMDSV2_SESSION_TOKEN_URL
  1151. credentials = self.make_credentials(
  1152. credential_source=credential_source_token_url
  1153. )
  1154. subject_token = credentials.retrieve_subject_token(request)
  1155. assert subject_token == self.make_serialized_aws_signed_request(
  1156. {
  1157. "access_key_id": ACCESS_KEY_ID,
  1158. "secret_access_key": SECRET_ACCESS_KEY,
  1159. "security_token": TOKEN,
  1160. }
  1161. )
  1162. # Assert session token request
  1163. self.assert_aws_metadata_request_kwargs(
  1164. request.call_args_list[0][1],
  1165. IMDSV2_SESSION_TOKEN_URL,
  1166. {"X-aws-ec2-metadata-token-ttl-seconds": "300"},
  1167. "PUT",
  1168. )
  1169. # Assert region request.
  1170. self.assert_aws_metadata_request_kwargs(
  1171. request.call_args_list[1][1],
  1172. REGION_URL,
  1173. {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN},
  1174. )
  1175. # Assert role request.
  1176. self.assert_aws_metadata_request_kwargs(
  1177. request.call_args_list[2][1],
  1178. SECURITY_CREDS_URL,
  1179. {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN},
  1180. )
  1181. # Assert security credentials request.
  1182. self.assert_aws_metadata_request_kwargs(
  1183. request.call_args_list[3][1],
  1184. "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE),
  1185. {
  1186. "Content-Type": "application/json",
  1187. "X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN,
  1188. },
  1189. )
  1190. # Retrieve subject_token again. Region should not be queried again.
  1191. new_request = self.make_mock_request(
  1192. role_status=http_client.OK,
  1193. role_name=self.AWS_ROLE,
  1194. security_credentials_status=http_client.OK,
  1195. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1196. imdsv2_session_token_status=http_client.OK,
  1197. imdsv2_session_token_data=self.AWS_IMDSV2_SESSION_TOKEN,
  1198. )
  1199. credentials.retrieve_subject_token(new_request)
  1200. # Only 3 requests should be sent as the region is cached.
  1201. assert len(new_request.call_args_list) == 3
  1202. # Assert session token request.
  1203. self.assert_aws_metadata_request_kwargs(
  1204. request.call_args_list[0][1],
  1205. IMDSV2_SESSION_TOKEN_URL,
  1206. {"X-aws-ec2-metadata-token-ttl-seconds": "300"},
  1207. "PUT",
  1208. )
  1209. # Assert role request.
  1210. self.assert_aws_metadata_request_kwargs(
  1211. new_request.call_args_list[1][1],
  1212. SECURITY_CREDS_URL,
  1213. {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN},
  1214. )
  1215. # Assert security credentials request.
  1216. self.assert_aws_metadata_request_kwargs(
  1217. new_request.call_args_list[2][1],
  1218. "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE),
  1219. {
  1220. "Content-Type": "application/json",
  1221. "X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN,
  1222. },
  1223. )
  1224. @mock.patch("google.auth._helpers.utcnow")
  1225. @mock.patch.dict(
  1226. os.environ,
  1227. {
  1228. environment_vars.AWS_REGION: AWS_REGION,
  1229. environment_vars.AWS_ACCESS_KEY_ID: ACCESS_KEY_ID,
  1230. },
  1231. )
  1232. def test_retrieve_subject_token_success_temp_creds_environment_vars_missing_secret_access_key_idmsv2(
  1233. self, utcnow
  1234. ):
  1235. utcnow.return_value = datetime.datetime.strptime(
  1236. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1237. )
  1238. request = self.make_mock_request(
  1239. role_status=http_client.OK,
  1240. role_name=self.AWS_ROLE,
  1241. security_credentials_status=http_client.OK,
  1242. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1243. imdsv2_session_token_status=http_client.OK,
  1244. imdsv2_session_token_data=self.AWS_IMDSV2_SESSION_TOKEN,
  1245. )
  1246. credential_source_token_url = self.CREDENTIAL_SOURCE.copy()
  1247. credential_source_token_url[
  1248. "imdsv2_session_token_url"
  1249. ] = IMDSV2_SESSION_TOKEN_URL
  1250. credentials = self.make_credentials(
  1251. credential_source=credential_source_token_url
  1252. )
  1253. subject_token = credentials.retrieve_subject_token(request)
  1254. assert subject_token == self.make_serialized_aws_signed_request(
  1255. {
  1256. "access_key_id": ACCESS_KEY_ID,
  1257. "secret_access_key": SECRET_ACCESS_KEY,
  1258. "security_token": TOKEN,
  1259. }
  1260. )
  1261. # Assert session token request.
  1262. self.assert_aws_metadata_request_kwargs(
  1263. request.call_args_list[0][1],
  1264. IMDSV2_SESSION_TOKEN_URL,
  1265. {"X-aws-ec2-metadata-token-ttl-seconds": "300"},
  1266. "PUT",
  1267. )
  1268. # Assert role request.
  1269. self.assert_aws_metadata_request_kwargs(
  1270. request.call_args_list[1][1],
  1271. SECURITY_CREDS_URL,
  1272. {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN},
  1273. )
  1274. # Assert security credentials request.
  1275. self.assert_aws_metadata_request_kwargs(
  1276. request.call_args_list[2][1],
  1277. "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE),
  1278. {
  1279. "Content-Type": "application/json",
  1280. "X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN,
  1281. },
  1282. )
  1283. @mock.patch("google.auth._helpers.utcnow")
  1284. @mock.patch.dict(
  1285. os.environ,
  1286. {
  1287. environment_vars.AWS_REGION: AWS_REGION,
  1288. environment_vars.AWS_SECRET_ACCESS_KEY: SECRET_ACCESS_KEY,
  1289. },
  1290. )
  1291. def test_retrieve_subject_token_success_temp_creds_environment_vars_missing_access_key_id_idmsv2(
  1292. self, utcnow
  1293. ):
  1294. utcnow.return_value = datetime.datetime.strptime(
  1295. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1296. )
  1297. request = self.make_mock_request(
  1298. role_status=http_client.OK,
  1299. role_name=self.AWS_ROLE,
  1300. security_credentials_status=http_client.OK,
  1301. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1302. imdsv2_session_token_status=http_client.OK,
  1303. imdsv2_session_token_data=self.AWS_IMDSV2_SESSION_TOKEN,
  1304. )
  1305. credential_source_token_url = self.CREDENTIAL_SOURCE.copy()
  1306. credential_source_token_url[
  1307. "imdsv2_session_token_url"
  1308. ] = IMDSV2_SESSION_TOKEN_URL
  1309. credentials = self.make_credentials(
  1310. credential_source=credential_source_token_url
  1311. )
  1312. subject_token = credentials.retrieve_subject_token(request)
  1313. assert subject_token == self.make_serialized_aws_signed_request(
  1314. {
  1315. "access_key_id": ACCESS_KEY_ID,
  1316. "secret_access_key": SECRET_ACCESS_KEY,
  1317. "security_token": TOKEN,
  1318. }
  1319. )
  1320. # Assert session token request.
  1321. self.assert_aws_metadata_request_kwargs(
  1322. request.call_args_list[0][1],
  1323. IMDSV2_SESSION_TOKEN_URL,
  1324. {"X-aws-ec2-metadata-token-ttl-seconds": "300"},
  1325. "PUT",
  1326. )
  1327. # Assert role request.
  1328. self.assert_aws_metadata_request_kwargs(
  1329. request.call_args_list[1][1],
  1330. SECURITY_CREDS_URL,
  1331. {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN},
  1332. )
  1333. # Assert security credentials request.
  1334. self.assert_aws_metadata_request_kwargs(
  1335. request.call_args_list[2][1],
  1336. "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE),
  1337. {
  1338. "Content-Type": "application/json",
  1339. "X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN,
  1340. },
  1341. )
  1342. @mock.patch("google.auth._helpers.utcnow")
  1343. @mock.patch.dict(os.environ, {environment_vars.AWS_REGION: AWS_REGION})
  1344. def test_retrieve_subject_token_success_temp_creds_environment_vars_missing_creds_idmsv2(
  1345. self, utcnow
  1346. ):
  1347. utcnow.return_value = datetime.datetime.strptime(
  1348. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1349. )
  1350. request = self.make_mock_request(
  1351. role_status=http_client.OK,
  1352. role_name=self.AWS_ROLE,
  1353. security_credentials_status=http_client.OK,
  1354. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1355. imdsv2_session_token_status=http_client.OK,
  1356. imdsv2_session_token_data=self.AWS_IMDSV2_SESSION_TOKEN,
  1357. )
  1358. credential_source_token_url = self.CREDENTIAL_SOURCE.copy()
  1359. credential_source_token_url[
  1360. "imdsv2_session_token_url"
  1361. ] = IMDSV2_SESSION_TOKEN_URL
  1362. credentials = self.make_credentials(
  1363. credential_source=credential_source_token_url
  1364. )
  1365. subject_token = credentials.retrieve_subject_token(request)
  1366. assert subject_token == self.make_serialized_aws_signed_request(
  1367. {
  1368. "access_key_id": ACCESS_KEY_ID,
  1369. "secret_access_key": SECRET_ACCESS_KEY,
  1370. "security_token": TOKEN,
  1371. }
  1372. )
  1373. # Assert session token request.
  1374. self.assert_aws_metadata_request_kwargs(
  1375. request.call_args_list[0][1],
  1376. IMDSV2_SESSION_TOKEN_URL,
  1377. {"X-aws-ec2-metadata-token-ttl-seconds": "300"},
  1378. "PUT",
  1379. )
  1380. # Assert role request.
  1381. self.assert_aws_metadata_request_kwargs(
  1382. request.call_args_list[1][1],
  1383. SECURITY_CREDS_URL,
  1384. {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN},
  1385. )
  1386. # Assert security credentials request.
  1387. self.assert_aws_metadata_request_kwargs(
  1388. request.call_args_list[2][1],
  1389. "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE),
  1390. {
  1391. "Content-Type": "application/json",
  1392. "X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN,
  1393. },
  1394. )
  1395. @mock.patch("google.auth._helpers.utcnow")
  1396. @mock.patch.dict(
  1397. os.environ,
  1398. {
  1399. environment_vars.AWS_REGION: AWS_REGION,
  1400. environment_vars.AWS_ACCESS_KEY_ID: ACCESS_KEY_ID,
  1401. environment_vars.AWS_SECRET_ACCESS_KEY: SECRET_ACCESS_KEY,
  1402. },
  1403. )
  1404. def test_retrieve_subject_token_success_temp_creds_idmsv2(self, utcnow):
  1405. utcnow.return_value = datetime.datetime.strptime(
  1406. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1407. )
  1408. request = self.make_mock_request(
  1409. role_status=http_client.OK, role_name=self.AWS_ROLE
  1410. )
  1411. credential_source_token_url = self.CREDENTIAL_SOURCE.copy()
  1412. credential_source_token_url[
  1413. "imdsv2_session_token_url"
  1414. ] = IMDSV2_SESSION_TOKEN_URL
  1415. credentials = self.make_credentials(
  1416. credential_source=credential_source_token_url
  1417. )
  1418. credentials.retrieve_subject_token(request)
  1419. assert not request.called
  1420. @mock.patch("google.auth._helpers.utcnow")
  1421. def test_retrieve_subject_token_success_ipv6(self, utcnow):
  1422. utcnow.return_value = datetime.datetime.strptime(
  1423. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1424. )
  1425. request = self.make_mock_request(
  1426. region_status=http_client.OK,
  1427. region_name=self.AWS_REGION,
  1428. role_status=http_client.OK,
  1429. role_name=self.AWS_ROLE,
  1430. security_credentials_status=http_client.OK,
  1431. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1432. imdsv2_session_token_status=http_client.OK,
  1433. imdsv2_session_token_data=self.AWS_IMDSV2_SESSION_TOKEN,
  1434. )
  1435. credential_source_token_url = self.CREDENTIAL_SOURCE_IPV6.copy()
  1436. credentials = self.make_credentials(
  1437. credential_source=credential_source_token_url
  1438. )
  1439. subject_token = credentials.retrieve_subject_token(request)
  1440. assert subject_token == self.make_serialized_aws_signed_request(
  1441. {
  1442. "access_key_id": ACCESS_KEY_ID,
  1443. "secret_access_key": SECRET_ACCESS_KEY,
  1444. "security_token": TOKEN,
  1445. }
  1446. )
  1447. # Assert session token request.
  1448. self.assert_aws_metadata_request_kwargs(
  1449. request.call_args_list[0][1],
  1450. IMDSV2_SESSION_TOKEN_URL_IPV6,
  1451. {"X-aws-ec2-metadata-token-ttl-seconds": "300"},
  1452. "PUT",
  1453. )
  1454. # Assert region request.
  1455. self.assert_aws_metadata_request_kwargs(
  1456. request.call_args_list[1][1],
  1457. REGION_URL_IPV6,
  1458. {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN},
  1459. )
  1460. # Assert role request.
  1461. self.assert_aws_metadata_request_kwargs(
  1462. request.call_args_list[2][1],
  1463. SECURITY_CREDS_URL_IPV6,
  1464. {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN},
  1465. )
  1466. # Assert security credentials request.
  1467. self.assert_aws_metadata_request_kwargs(
  1468. request.call_args_list[3][1],
  1469. "{}/{}".format(SECURITY_CREDS_URL_IPV6, self.AWS_ROLE),
  1470. {
  1471. "Content-Type": "application/json",
  1472. "X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN,
  1473. },
  1474. )
  1475. @mock.patch("google.auth._helpers.utcnow")
  1476. def test_retrieve_subject_token_session_error_idmsv2(self, utcnow):
  1477. utcnow.return_value = datetime.datetime.strptime(
  1478. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1479. )
  1480. request = self.make_mock_request(
  1481. imdsv2_session_token_status=http_client.UNAUTHORIZED,
  1482. imdsv2_session_token_data="unauthorized",
  1483. )
  1484. credential_source_token_url = self.CREDENTIAL_SOURCE.copy()
  1485. credential_source_token_url[
  1486. "imdsv2_session_token_url"
  1487. ] = IMDSV2_SESSION_TOKEN_URL
  1488. credentials = self.make_credentials(
  1489. credential_source=credential_source_token_url
  1490. )
  1491. with pytest.raises(exceptions.RefreshError) as excinfo:
  1492. credentials.retrieve_subject_token(request)
  1493. assert excinfo.match(r"Unable to retrieve AWS Session Token")
  1494. # Assert session token request
  1495. self.assert_aws_metadata_request_kwargs(
  1496. request.call_args_list[0][1],
  1497. IMDSV2_SESSION_TOKEN_URL,
  1498. {"X-aws-ec2-metadata-token-ttl-seconds": "300"},
  1499. "PUT",
  1500. )
  1501. @mock.patch("google.auth._helpers.utcnow")
  1502. def test_retrieve_subject_token_success_permanent_creds_no_environment_vars(
  1503. self, utcnow
  1504. ):
  1505. # Simualte a permanent credential without a session token is
  1506. # returned by the security-credentials endpoint.
  1507. security_creds_response = self.AWS_SECURITY_CREDENTIALS_RESPONSE.copy()
  1508. security_creds_response.pop("Token")
  1509. utcnow.return_value = datetime.datetime.strptime(
  1510. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1511. )
  1512. request = self.make_mock_request(
  1513. region_status=http_client.OK,
  1514. region_name=self.AWS_REGION,
  1515. role_status=http_client.OK,
  1516. role_name=self.AWS_ROLE,
  1517. security_credentials_status=http_client.OK,
  1518. security_credentials_data=security_creds_response,
  1519. )
  1520. credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
  1521. subject_token = credentials.retrieve_subject_token(request)
  1522. assert subject_token == self.make_serialized_aws_signed_request(
  1523. {"access_key_id": ACCESS_KEY_ID, "secret_access_key": SECRET_ACCESS_KEY}
  1524. )
  1525. @mock.patch("google.auth._helpers.utcnow")
  1526. def test_retrieve_subject_token_success_environment_vars(self, utcnow, monkeypatch):
  1527. monkeypatch.setenv(environment_vars.AWS_ACCESS_KEY_ID, ACCESS_KEY_ID)
  1528. monkeypatch.setenv(environment_vars.AWS_SECRET_ACCESS_KEY, SECRET_ACCESS_KEY)
  1529. monkeypatch.setenv(environment_vars.AWS_SESSION_TOKEN, TOKEN)
  1530. monkeypatch.setenv(environment_vars.AWS_REGION, self.AWS_REGION)
  1531. utcnow.return_value = datetime.datetime.strptime(
  1532. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1533. )
  1534. credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
  1535. subject_token = credentials.retrieve_subject_token(None)
  1536. assert subject_token == self.make_serialized_aws_signed_request(
  1537. {
  1538. "access_key_id": ACCESS_KEY_ID,
  1539. "secret_access_key": SECRET_ACCESS_KEY,
  1540. "security_token": TOKEN,
  1541. }
  1542. )
  1543. @mock.patch("google.auth._helpers.utcnow")
  1544. def test_retrieve_subject_token_success_environment_vars_with_default_region(
  1545. self, utcnow, monkeypatch
  1546. ):
  1547. monkeypatch.setenv(environment_vars.AWS_ACCESS_KEY_ID, ACCESS_KEY_ID)
  1548. monkeypatch.setenv(environment_vars.AWS_SECRET_ACCESS_KEY, SECRET_ACCESS_KEY)
  1549. monkeypatch.setenv(environment_vars.AWS_SESSION_TOKEN, TOKEN)
  1550. monkeypatch.setenv(environment_vars.AWS_DEFAULT_REGION, self.AWS_REGION)
  1551. utcnow.return_value = datetime.datetime.strptime(
  1552. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1553. )
  1554. credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
  1555. subject_token = credentials.retrieve_subject_token(None)
  1556. assert subject_token == self.make_serialized_aws_signed_request(
  1557. {
  1558. "access_key_id": ACCESS_KEY_ID,
  1559. "secret_access_key": SECRET_ACCESS_KEY,
  1560. "security_token": TOKEN,
  1561. }
  1562. )
  1563. @mock.patch("google.auth._helpers.utcnow")
  1564. def test_retrieve_subject_token_success_environment_vars_with_both_regions_set(
  1565. self, utcnow, monkeypatch
  1566. ):
  1567. monkeypatch.setenv(environment_vars.AWS_ACCESS_KEY_ID, ACCESS_KEY_ID)
  1568. monkeypatch.setenv(environment_vars.AWS_SECRET_ACCESS_KEY, SECRET_ACCESS_KEY)
  1569. monkeypatch.setenv(environment_vars.AWS_SESSION_TOKEN, TOKEN)
  1570. monkeypatch.setenv(environment_vars.AWS_DEFAULT_REGION, "Malformed AWS Region")
  1571. # This test makes sure that the AWS_REGION gets used over AWS_DEFAULT_REGION,
  1572. # So, AWS_DEFAULT_REGION is set to something that would cause the test to fail,
  1573. # And AWS_REGION is set to the a valid value, and it should succeed
  1574. monkeypatch.setenv(environment_vars.AWS_REGION, self.AWS_REGION)
  1575. utcnow.return_value = datetime.datetime.strptime(
  1576. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1577. )
  1578. credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
  1579. subject_token = credentials.retrieve_subject_token(None)
  1580. assert subject_token == self.make_serialized_aws_signed_request(
  1581. {
  1582. "access_key_id": ACCESS_KEY_ID,
  1583. "secret_access_key": SECRET_ACCESS_KEY,
  1584. "security_token": TOKEN,
  1585. }
  1586. )
  1587. @mock.patch("google.auth._helpers.utcnow")
  1588. def test_retrieve_subject_token_success_environment_vars_no_session_token(
  1589. self, utcnow, monkeypatch
  1590. ):
  1591. monkeypatch.setenv(environment_vars.AWS_ACCESS_KEY_ID, ACCESS_KEY_ID)
  1592. monkeypatch.setenv(environment_vars.AWS_SECRET_ACCESS_KEY, SECRET_ACCESS_KEY)
  1593. monkeypatch.setenv(environment_vars.AWS_REGION, self.AWS_REGION)
  1594. utcnow.return_value = datetime.datetime.strptime(
  1595. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1596. )
  1597. credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
  1598. subject_token = credentials.retrieve_subject_token(None)
  1599. assert subject_token == self.make_serialized_aws_signed_request(
  1600. {"access_key_id": ACCESS_KEY_ID, "secret_access_key": SECRET_ACCESS_KEY}
  1601. )
  1602. @mock.patch("google.auth._helpers.utcnow")
  1603. def test_retrieve_subject_token_success_environment_vars_except_region(
  1604. self, utcnow, monkeypatch
  1605. ):
  1606. monkeypatch.setenv(environment_vars.AWS_ACCESS_KEY_ID, ACCESS_KEY_ID)
  1607. monkeypatch.setenv(environment_vars.AWS_SECRET_ACCESS_KEY, SECRET_ACCESS_KEY)
  1608. monkeypatch.setenv(environment_vars.AWS_SESSION_TOKEN, TOKEN)
  1609. utcnow.return_value = datetime.datetime.strptime(
  1610. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1611. )
  1612. # Region will be queried since it is not found in envvars.
  1613. request = self.make_mock_request(
  1614. region_status=http_client.OK, region_name=self.AWS_REGION
  1615. )
  1616. credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
  1617. subject_token = credentials.retrieve_subject_token(request)
  1618. assert subject_token == self.make_serialized_aws_signed_request(
  1619. {
  1620. "access_key_id": ACCESS_KEY_ID,
  1621. "secret_access_key": SECRET_ACCESS_KEY,
  1622. "security_token": TOKEN,
  1623. }
  1624. )
  1625. def test_retrieve_subject_token_error_determining_aws_region(self):
  1626. # Simulate error in retrieving the AWS region.
  1627. request = self.make_mock_request(region_status=http_client.BAD_REQUEST)
  1628. credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
  1629. with pytest.raises(exceptions.RefreshError) as excinfo:
  1630. credentials.retrieve_subject_token(request)
  1631. assert excinfo.match(r"Unable to retrieve AWS region")
  1632. def test_retrieve_subject_token_error_determining_aws_role(self):
  1633. # Simulate error in retrieving the AWS role name.
  1634. request = self.make_mock_request(
  1635. region_status=http_client.OK,
  1636. region_name=self.AWS_REGION,
  1637. role_status=http_client.BAD_REQUEST,
  1638. )
  1639. credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
  1640. with pytest.raises(exceptions.RefreshError) as excinfo:
  1641. credentials.retrieve_subject_token(request)
  1642. assert excinfo.match(r"Unable to retrieve AWS role name")
  1643. def test_retrieve_subject_token_error_determining_security_creds_url(self):
  1644. # Simulate the security-credentials url is missing. This is needed for
  1645. # determining the AWS security credentials when not found in envvars.
  1646. credential_source = self.CREDENTIAL_SOURCE.copy()
  1647. credential_source.pop("url")
  1648. request = self.make_mock_request(
  1649. region_status=http_client.OK, region_name=self.AWS_REGION
  1650. )
  1651. credentials = self.make_credentials(credential_source=credential_source)
  1652. with pytest.raises(exceptions.RefreshError) as excinfo:
  1653. credentials.retrieve_subject_token(request)
  1654. assert excinfo.match(
  1655. r"Unable to determine the AWS metadata server security credentials endpoint"
  1656. )
  1657. def test_retrieve_subject_token_error_determining_aws_security_creds(self):
  1658. # Simulate error in retrieving the AWS security credentials.
  1659. request = self.make_mock_request(
  1660. region_status=http_client.OK,
  1661. region_name=self.AWS_REGION,
  1662. role_status=http_client.OK,
  1663. role_name=self.AWS_ROLE,
  1664. security_credentials_status=http_client.BAD_REQUEST,
  1665. )
  1666. credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
  1667. with pytest.raises(exceptions.RefreshError) as excinfo:
  1668. credentials.retrieve_subject_token(request)
  1669. assert excinfo.match(r"Unable to retrieve AWS security credentials")
  1670. @mock.patch(
  1671. "google.auth.metrics.python_and_auth_lib_version",
  1672. return_value=LANG_LIBRARY_METRICS_HEADER_VALUE,
  1673. )
  1674. @mock.patch("google.auth._helpers.utcnow")
  1675. def test_refresh_success_without_impersonation_ignore_default_scopes(
  1676. self, utcnow, mock_auth_lib_value
  1677. ):
  1678. utcnow.return_value = datetime.datetime.strptime(
  1679. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1680. )
  1681. expected_subject_token = self.make_serialized_aws_signed_request(
  1682. {
  1683. "access_key_id": ACCESS_KEY_ID,
  1684. "secret_access_key": SECRET_ACCESS_KEY,
  1685. "security_token": TOKEN,
  1686. }
  1687. )
  1688. token_headers = {
  1689. "Content-Type": "application/x-www-form-urlencoded",
  1690. "Authorization": "Basic " + BASIC_AUTH_ENCODING,
  1691. "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/false config-lifetime/false source/aws",
  1692. }
  1693. token_request_data = {
  1694. "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
  1695. "audience": AUDIENCE,
  1696. "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
  1697. "scope": " ".join(SCOPES),
  1698. "subject_token": expected_subject_token,
  1699. "subject_token_type": SUBJECT_TOKEN_TYPE,
  1700. }
  1701. request = self.make_mock_request(
  1702. region_status=http_client.OK,
  1703. region_name=self.AWS_REGION,
  1704. role_status=http_client.OK,
  1705. role_name=self.AWS_ROLE,
  1706. security_credentials_status=http_client.OK,
  1707. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1708. token_status=http_client.OK,
  1709. token_data=self.SUCCESS_RESPONSE,
  1710. )
  1711. credentials = self.make_credentials(
  1712. client_id=CLIENT_ID,
  1713. client_secret=CLIENT_SECRET,
  1714. credential_source=self.CREDENTIAL_SOURCE,
  1715. quota_project_id=QUOTA_PROJECT_ID,
  1716. scopes=SCOPES,
  1717. # Default scopes should be ignored.
  1718. default_scopes=["ignored"],
  1719. )
  1720. credentials.refresh(request)
  1721. assert len(request.call_args_list) == 4
  1722. # Fourth request should be sent to GCP STS endpoint.
  1723. self.assert_token_request_kwargs(
  1724. request.call_args_list[3][1], token_headers, token_request_data
  1725. )
  1726. assert credentials.token == self.SUCCESS_RESPONSE["access_token"]
  1727. assert credentials.quota_project_id == QUOTA_PROJECT_ID
  1728. assert credentials.scopes == SCOPES
  1729. assert credentials.default_scopes == ["ignored"]
  1730. @mock.patch(
  1731. "google.auth.metrics.python_and_auth_lib_version",
  1732. return_value=LANG_LIBRARY_METRICS_HEADER_VALUE,
  1733. )
  1734. @mock.patch("google.auth._helpers.utcnow")
  1735. def test_refresh_success_without_impersonation_use_default_scopes(
  1736. self, utcnow, mock_auth_lib_value
  1737. ):
  1738. utcnow.return_value = datetime.datetime.strptime(
  1739. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1740. )
  1741. expected_subject_token = self.make_serialized_aws_signed_request(
  1742. {
  1743. "access_key_id": ACCESS_KEY_ID,
  1744. "secret_access_key": SECRET_ACCESS_KEY,
  1745. "security_token": TOKEN,
  1746. }
  1747. )
  1748. token_headers = {
  1749. "Content-Type": "application/x-www-form-urlencoded",
  1750. "Authorization": "Basic " + BASIC_AUTH_ENCODING,
  1751. "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/false config-lifetime/false source/aws",
  1752. }
  1753. token_request_data = {
  1754. "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
  1755. "audience": AUDIENCE,
  1756. "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
  1757. "scope": " ".join(SCOPES),
  1758. "subject_token": expected_subject_token,
  1759. "subject_token_type": SUBJECT_TOKEN_TYPE,
  1760. }
  1761. request = self.make_mock_request(
  1762. region_status=http_client.OK,
  1763. region_name=self.AWS_REGION,
  1764. role_status=http_client.OK,
  1765. role_name=self.AWS_ROLE,
  1766. security_credentials_status=http_client.OK,
  1767. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1768. token_status=http_client.OK,
  1769. token_data=self.SUCCESS_RESPONSE,
  1770. )
  1771. credentials = self.make_credentials(
  1772. client_id=CLIENT_ID,
  1773. client_secret=CLIENT_SECRET,
  1774. credential_source=self.CREDENTIAL_SOURCE,
  1775. quota_project_id=QUOTA_PROJECT_ID,
  1776. scopes=None,
  1777. # Default scopes should be used since user specified scopes are none.
  1778. default_scopes=SCOPES,
  1779. )
  1780. credentials.refresh(request)
  1781. assert len(request.call_args_list) == 4
  1782. # Fourth request should be sent to GCP STS endpoint.
  1783. self.assert_token_request_kwargs(
  1784. request.call_args_list[3][1], token_headers, token_request_data
  1785. )
  1786. assert credentials.token == self.SUCCESS_RESPONSE["access_token"]
  1787. assert credentials.quota_project_id == QUOTA_PROJECT_ID
  1788. assert credentials.scopes is None
  1789. assert credentials.default_scopes == SCOPES
  1790. @mock.patch(
  1791. "google.auth.metrics.token_request_access_token_impersonate",
  1792. return_value=IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE,
  1793. )
  1794. @mock.patch(
  1795. "google.auth.metrics.python_and_auth_lib_version",
  1796. return_value=LANG_LIBRARY_METRICS_HEADER_VALUE,
  1797. )
  1798. @mock.patch("google.auth._helpers.utcnow")
  1799. def test_refresh_success_with_impersonation_ignore_default_scopes(
  1800. self, utcnow, mock_metrics_header_value, mock_auth_lib_value
  1801. ):
  1802. utcnow.return_value = datetime.datetime.strptime(
  1803. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1804. )
  1805. expire_time = (
  1806. _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=3600)
  1807. ).isoformat("T") + "Z"
  1808. expected_subject_token = self.make_serialized_aws_signed_request(
  1809. {
  1810. "access_key_id": ACCESS_KEY_ID,
  1811. "secret_access_key": SECRET_ACCESS_KEY,
  1812. "security_token": TOKEN,
  1813. }
  1814. )
  1815. token_headers = {
  1816. "Content-Type": "application/x-www-form-urlencoded",
  1817. "Authorization": "Basic " + BASIC_AUTH_ENCODING,
  1818. "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/true config-lifetime/false source/aws",
  1819. }
  1820. token_request_data = {
  1821. "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
  1822. "audience": AUDIENCE,
  1823. "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
  1824. "scope": "https://www.googleapis.com/auth/iam",
  1825. "subject_token": expected_subject_token,
  1826. "subject_token_type": SUBJECT_TOKEN_TYPE,
  1827. }
  1828. # Service account impersonation request/response.
  1829. impersonation_response = {
  1830. "accessToken": "SA_ACCESS_TOKEN",
  1831. "expireTime": expire_time,
  1832. }
  1833. impersonation_headers = {
  1834. "Content-Type": "application/json",
  1835. "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]),
  1836. "x-goog-user-project": QUOTA_PROJECT_ID,
  1837. "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE,
  1838. "x-allowed-locations": "0x0",
  1839. }
  1840. impersonation_request_data = {
  1841. "delegates": None,
  1842. "scope": SCOPES,
  1843. "lifetime": "3600s",
  1844. }
  1845. request = self.make_mock_request(
  1846. region_status=http_client.OK,
  1847. region_name=self.AWS_REGION,
  1848. role_status=http_client.OK,
  1849. role_name=self.AWS_ROLE,
  1850. security_credentials_status=http_client.OK,
  1851. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1852. token_status=http_client.OK,
  1853. token_data=self.SUCCESS_RESPONSE,
  1854. impersonation_status=http_client.OK,
  1855. impersonation_data=impersonation_response,
  1856. )
  1857. credentials = self.make_credentials(
  1858. client_id=CLIENT_ID,
  1859. client_secret=CLIENT_SECRET,
  1860. credential_source=self.CREDENTIAL_SOURCE,
  1861. service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL,
  1862. quota_project_id=QUOTA_PROJECT_ID,
  1863. scopes=SCOPES,
  1864. # Default scopes should be ignored.
  1865. default_scopes=["ignored"],
  1866. )
  1867. credentials.refresh(request)
  1868. assert len(request.call_args_list) == 5
  1869. # Fourth request should be sent to GCP STS endpoint.
  1870. self.assert_token_request_kwargs(
  1871. request.call_args_list[3][1], token_headers, token_request_data
  1872. )
  1873. # Fifth request should be sent to iamcredentials endpoint for service
  1874. # account impersonation.
  1875. self.assert_impersonation_request_kwargs(
  1876. request.call_args_list[4][1],
  1877. impersonation_headers,
  1878. impersonation_request_data,
  1879. )
  1880. assert credentials.token == impersonation_response["accessToken"]
  1881. assert credentials.quota_project_id == QUOTA_PROJECT_ID
  1882. assert credentials.scopes == SCOPES
  1883. assert credentials.default_scopes == ["ignored"]
  1884. @mock.patch(
  1885. "google.auth.metrics.token_request_access_token_impersonate",
  1886. return_value=IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE,
  1887. )
  1888. @mock.patch(
  1889. "google.auth.metrics.python_and_auth_lib_version",
  1890. return_value=LANG_LIBRARY_METRICS_HEADER_VALUE,
  1891. )
  1892. @mock.patch("google.auth._helpers.utcnow")
  1893. def test_refresh_success_with_impersonation_use_default_scopes(
  1894. self, utcnow, mock_metrics_header_value, mock_auth_lib_value
  1895. ):
  1896. utcnow.return_value = datetime.datetime.strptime(
  1897. self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
  1898. )
  1899. expire_time = (
  1900. _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=3600)
  1901. ).isoformat("T") + "Z"
  1902. expected_subject_token = self.make_serialized_aws_signed_request(
  1903. {
  1904. "access_key_id": ACCESS_KEY_ID,
  1905. "secret_access_key": SECRET_ACCESS_KEY,
  1906. "security_token": TOKEN,
  1907. }
  1908. )
  1909. token_headers = {
  1910. "Content-Type": "application/x-www-form-urlencoded",
  1911. "Authorization": "Basic " + BASIC_AUTH_ENCODING,
  1912. "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/true config-lifetime/false source/aws",
  1913. }
  1914. token_request_data = {
  1915. "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
  1916. "audience": AUDIENCE,
  1917. "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
  1918. "scope": "https://www.googleapis.com/auth/iam",
  1919. "subject_token": expected_subject_token,
  1920. "subject_token_type": SUBJECT_TOKEN_TYPE,
  1921. }
  1922. # Service account impersonation request/response.
  1923. impersonation_response = {
  1924. "accessToken": "SA_ACCESS_TOKEN",
  1925. "expireTime": expire_time,
  1926. }
  1927. impersonation_headers = {
  1928. "Content-Type": "application/json",
  1929. "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]),
  1930. "x-goog-user-project": QUOTA_PROJECT_ID,
  1931. "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE,
  1932. "x-allowed-locations": "0x0",
  1933. }
  1934. impersonation_request_data = {
  1935. "delegates": None,
  1936. "scope": SCOPES,
  1937. "lifetime": "3600s",
  1938. }
  1939. request = self.make_mock_request(
  1940. region_status=http_client.OK,
  1941. region_name=self.AWS_REGION,
  1942. role_status=http_client.OK,
  1943. role_name=self.AWS_ROLE,
  1944. security_credentials_status=http_client.OK,
  1945. security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE,
  1946. token_status=http_client.OK,
  1947. token_data=self.SUCCESS_RESPONSE,
  1948. impersonation_status=http_client.OK,
  1949. impersonation_data=impersonation_response,
  1950. )
  1951. credentials = self.make_credentials(
  1952. client_id=CLIENT_ID,
  1953. client_secret=CLIENT_SECRET,
  1954. credential_source=self.CREDENTIAL_SOURCE,
  1955. service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL,
  1956. quota_project_id=QUOTA_PROJECT_ID,
  1957. scopes=None,
  1958. # Default scopes should be used since user specified scopes are none.
  1959. default_scopes=SCOPES,
  1960. )
  1961. credentials.refresh(request)
  1962. assert len(request.call_args_list) == 5
  1963. # Fourth request should be sent to GCP STS endpoint.
  1964. self.assert_token_request_kwargs(
  1965. request.call_args_list[3][1], token_headers, token_request_data
  1966. )
  1967. # Fifth request should be sent to iamcredentials endpoint for service
  1968. # account impersonation.
  1969. self.assert_impersonation_request_kwargs(
  1970. request.call_args_list[4][1],
  1971. impersonation_headers,
  1972. impersonation_request_data,
  1973. )
  1974. assert credentials.token == impersonation_response["accessToken"]
  1975. assert credentials.quota_project_id == QUOTA_PROJECT_ID
  1976. assert credentials.scopes is None
  1977. assert credentials.default_scopes == SCOPES
  1978. def test_refresh_with_retrieve_subject_token_error(self):
  1979. request = self.make_mock_request(region_status=http_client.BAD_REQUEST)
  1980. credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE)
  1981. with pytest.raises(exceptions.RefreshError) as excinfo:
  1982. credentials.refresh(request)
  1983. assert excinfo.match(r"Unable to retrieve AWS region")