test_aws.py 63 KB

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