credentials_provider_sso.c 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851
  1. /**
  2. * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
  3. * SPDX-License-Identifier: Apache-2.0.
  4. */
  5. #include <aws/auth/credentials.h>
  6. #include <aws/auth/private/aws_profile.h>
  7. #include <aws/auth/private/credentials_utils.h>
  8. #include <aws/auth/private/sso_token_providers.h>
  9. #include <aws/auth/private/sso_token_utils.h>
  10. #include <aws/common/clock.h>
  11. #include <aws/http/connection_manager.h>
  12. #include <aws/http/request_response.h>
  13. #include <aws/http/status_code.h>
  14. #include <aws/io/channel_bootstrap.h>
  15. #include <aws/io/socket.h>
  16. #include <aws/io/tls_channel_handler.h>
  17. #include <aws/io/uri.h>
  18. #if defined(_MSC_VER)
  19. # pragma warning(disable : 4204)
  20. #endif /* _MSC_VER */
  21. #define SSO_RESPONSE_SIZE_INITIAL 2048
  22. #define SSO_RESPONSE_SIZE_LIMIT 10000
  23. #define SSO_CONNECT_TIMEOUT_DEFAULT_IN_SECONDS 2
  24. #define SSO_MAX_ATTEMPTS 3
  25. #define SSO_RETRY_TIMEOUT_MS 100
  26. struct aws_credentials_provider_sso_impl {
  27. struct aws_http_connection_manager *connection_manager;
  28. const struct aws_auth_http_system_vtable *function_table;
  29. struct aws_string *endpoint;
  30. struct aws_string *sso_account_id;
  31. struct aws_string *sso_role_name;
  32. struct aws_credentials_provider *token_provider;
  33. struct aws_retry_strategy *retry_strategy;
  34. };
  35. /**
  36. * aws_sso_query_context - context for each outstanding SSO query.
  37. */
  38. struct aws_sso_query_context {
  39. /* immutable post-creation */
  40. struct aws_allocator *allocator;
  41. struct aws_credentials_provider *provider;
  42. aws_on_get_credentials_callback_fn *original_callback;
  43. void *original_user_data;
  44. /* mutable */
  45. struct aws_http_connection *connection;
  46. struct aws_http_message *request;
  47. struct aws_byte_buf payload;
  48. struct aws_retry_token *retry_token;
  49. struct aws_byte_buf path_and_query;
  50. struct aws_string *token;
  51. int status_code;
  52. int error_code;
  53. };
  54. /* called in between retries. */
  55. static void s_sso_query_context_reset_request_specific_data(struct aws_sso_query_context *sso_query_context) {
  56. if (sso_query_context->request) {
  57. aws_http_message_release(sso_query_context->request);
  58. sso_query_context->request = NULL;
  59. }
  60. if (sso_query_context->connection) {
  61. struct aws_credentials_provider_sso_impl *provider_impl = sso_query_context->provider->impl;
  62. int result = provider_impl->function_table->aws_http_connection_manager_release_connection(
  63. provider_impl->connection_manager, sso_query_context->connection);
  64. (void)result;
  65. AWS_ASSERT(result == AWS_OP_SUCCESS);
  66. sso_query_context->connection = NULL;
  67. }
  68. if (sso_query_context->token) {
  69. aws_string_destroy_secure(sso_query_context->token);
  70. sso_query_context->token = NULL;
  71. }
  72. sso_query_context->status_code = 0;
  73. sso_query_context->error_code = 0;
  74. }
  75. static void s_sso_query_context_destroy(struct aws_sso_query_context *sso_query_context) {
  76. if (sso_query_context == NULL) {
  77. return;
  78. }
  79. s_sso_query_context_reset_request_specific_data(sso_query_context);
  80. aws_byte_buf_clean_up(&sso_query_context->payload);
  81. aws_byte_buf_clean_up(&sso_query_context->path_and_query);
  82. aws_credentials_provider_release(sso_query_context->provider);
  83. aws_retry_token_release(sso_query_context->retry_token);
  84. aws_mem_release(sso_query_context->allocator, sso_query_context);
  85. }
  86. static struct aws_sso_query_context *s_sso_query_context_new(
  87. struct aws_credentials_provider *provider,
  88. aws_on_get_credentials_callback_fn callback,
  89. void *user_data) {
  90. struct aws_credentials_provider_sso_impl *impl = provider->impl;
  91. struct aws_sso_query_context *sso_query_context =
  92. aws_mem_calloc(provider->allocator, 1, sizeof(struct aws_sso_query_context));
  93. sso_query_context->allocator = provider->allocator;
  94. sso_query_context->provider = aws_credentials_provider_acquire(provider);
  95. sso_query_context->original_user_data = user_data;
  96. sso_query_context->original_callback = callback;
  97. /* construct path and query */
  98. struct aws_byte_cursor account_id_cursor = aws_byte_cursor_from_string(impl->sso_account_id);
  99. struct aws_byte_cursor role_name_cursor = aws_byte_cursor_from_string(impl->sso_role_name);
  100. struct aws_byte_cursor path_cursor = aws_byte_cursor_from_c_str("/federation/credentials?account_id=");
  101. struct aws_byte_cursor role_name_param_cursor = aws_byte_cursor_from_c_str("&role_name=");
  102. if (aws_byte_buf_init_copy_from_cursor(&sso_query_context->path_and_query, provider->allocator, path_cursor) ||
  103. aws_byte_buf_append_encoding_uri_param(&sso_query_context->path_and_query, &account_id_cursor) ||
  104. aws_byte_buf_append_dynamic(&sso_query_context->path_and_query, &role_name_param_cursor) ||
  105. aws_byte_buf_append_encoding_uri_param(&sso_query_context->path_and_query, &role_name_cursor)) {
  106. goto on_error;
  107. }
  108. if (aws_byte_buf_init(&sso_query_context->payload, provider->allocator, SSO_RESPONSE_SIZE_INITIAL)) {
  109. goto on_error;
  110. }
  111. return sso_query_context;
  112. on_error:
  113. s_sso_query_context_destroy(sso_query_context);
  114. return NULL;
  115. }
  116. /*
  117. * No matter the result, this always gets called assuming that sso_query_context is successfully allocated
  118. */
  119. static void s_finalize_get_credentials_query(struct aws_sso_query_context *sso_query_context) {
  120. struct aws_credentials *credentials = NULL;
  121. if (sso_query_context->error_code == AWS_ERROR_SUCCESS) {
  122. /* parse credentials */
  123. struct aws_parse_credentials_from_json_doc_options parse_options = {
  124. .access_key_id_name = "accessKeyId",
  125. .secret_access_key_name = "secretAccessKey",
  126. .token_name = "sessionToken",
  127. .expiration_name = "expiration",
  128. .top_level_object_name = "roleCredentials",
  129. .token_required = true,
  130. .expiration_required = true,
  131. .expiration_format = AWS_PCEF_NUMBER_UNIX_EPOCH_MS,
  132. };
  133. credentials = aws_parse_credentials_from_json_document(
  134. sso_query_context->allocator, aws_byte_cursor_from_buf(&sso_query_context->payload), &parse_options);
  135. }
  136. if (credentials) {
  137. AWS_LOGF_INFO(
  138. AWS_LS_AUTH_CREDENTIALS_PROVIDER,
  139. "(id=%p) successfully queried credentials",
  140. (void *)sso_query_context->provider);
  141. } else {
  142. AWS_LOGF_ERROR(
  143. AWS_LS_AUTH_CREDENTIALS_PROVIDER,
  144. "(id=%p) failed to query credentials",
  145. (void *)sso_query_context->provider);
  146. if (sso_query_context->error_code == AWS_ERROR_SUCCESS) {
  147. sso_query_context->error_code = AWS_AUTH_CREDENTIALS_PROVIDER_SSO_SOURCE_FAILURE;
  148. }
  149. }
  150. /* pass the credentials back */
  151. sso_query_context->original_callback(
  152. credentials, sso_query_context->error_code, sso_query_context->original_user_data);
  153. /* clean up */
  154. s_sso_query_context_destroy(sso_query_context);
  155. aws_credentials_release(credentials);
  156. }
  157. static void s_on_retry_ready(struct aws_retry_token *token, int error_code, void *user_data);
  158. static void s_on_stream_complete_fn(struct aws_http_stream *stream, int error_code, void *user_data) {
  159. struct aws_sso_query_context *sso_query_context = user_data;
  160. struct aws_credentials_provider_sso_impl *impl = sso_query_context->provider->impl;
  161. impl->function_table->aws_http_stream_release(stream);
  162. /* set error code */
  163. sso_query_context->error_code = error_code;
  164. impl->function_table->aws_http_stream_get_incoming_response_status(stream, &sso_query_context->status_code);
  165. if (error_code == AWS_OP_SUCCESS && sso_query_context->status_code != AWS_HTTP_STATUS_CODE_200_OK) {
  166. sso_query_context->error_code = AWS_AUTH_CREDENTIALS_PROVIDER_HTTP_STATUS_FAILURE;
  167. }
  168. /*
  169. * If we can retry the request based on error response or http status code failure, retry it, otherwise, call the
  170. * finalize function.
  171. */
  172. if (error_code || sso_query_context->status_code != AWS_HTTP_STATUS_CODE_200_OK) {
  173. enum aws_retry_error_type error_type =
  174. aws_credentials_provider_compute_retry_error_type(sso_query_context->status_code, error_code);
  175. /* don't retry client errors at all. */
  176. if (error_type != AWS_RETRY_ERROR_TYPE_CLIENT_ERROR) {
  177. if (aws_retry_strategy_schedule_retry(
  178. sso_query_context->retry_token, error_type, s_on_retry_ready, sso_query_context) ==
  179. AWS_OP_SUCCESS) {
  180. AWS_LOGF_INFO(
  181. AWS_LS_AUTH_CREDENTIALS_PROVIDER,
  182. "(id=%p): successfully scheduled a retry",
  183. (void *)sso_query_context->provider);
  184. return;
  185. }
  186. AWS_LOGF_ERROR(
  187. AWS_LS_AUTH_CREDENTIALS_PROVIDER,
  188. "(id=%p): failed to schedule retry: %s",
  189. (void *)sso_query_context->provider,
  190. aws_error_str(aws_last_error()));
  191. sso_query_context->error_code = aws_last_error();
  192. }
  193. } else {
  194. int result = aws_retry_token_record_success(sso_query_context->retry_token);
  195. (void)result;
  196. AWS_ASSERT(result == AWS_ERROR_SUCCESS);
  197. }
  198. s_finalize_get_credentials_query(sso_query_context);
  199. }
  200. static int s_on_incoming_body_fn(struct aws_http_stream *stream, const struct aws_byte_cursor *body, void *user_data) {
  201. (void)stream;
  202. struct aws_sso_query_context *sso_query_context = user_data;
  203. AWS_LOGF_TRACE(
  204. AWS_LS_AUTH_CREDENTIALS_PROVIDER,
  205. "(id=%p) received %zu response bytes",
  206. (void *)sso_query_context->provider,
  207. body->len);
  208. if (body->len + sso_query_context->payload.len > SSO_RESPONSE_SIZE_LIMIT) {
  209. AWS_LOGF_ERROR(
  210. AWS_LS_AUTH_CREDENTIALS_PROVIDER,
  211. "(id=%p) response exceeded maximum allowed length",
  212. (void *)sso_query_context->provider);
  213. return aws_raise_error(AWS_ERROR_SHORT_BUFFER);
  214. }
  215. if (aws_byte_buf_append_dynamic(&sso_query_context->payload, body)) {
  216. AWS_LOGF_ERROR(
  217. AWS_LS_AUTH_CREDENTIALS_PROVIDER,
  218. "(id=%p) error appending response payload: %s",
  219. (void *)sso_query_context->provider,
  220. aws_error_str(aws_last_error()));
  221. return AWS_OP_ERR;
  222. }
  223. return AWS_OP_SUCCESS;
  224. }
  225. /* Request headers. */
  226. AWS_STATIC_STRING_FROM_LITERAL(s_sso_token_header, "x-amz-sso_bearer_token");
  227. AWS_STATIC_STRING_FROM_LITERAL(s_sso_user_agent_header, "User-Agent");
  228. AWS_STATIC_STRING_FROM_LITERAL(s_sso_user_agent_header_value, "aws-sdk-crt/sso-credentials-provider");
  229. static void s_query_credentials(struct aws_sso_query_context *sso_query_context) {
  230. AWS_FATAL_ASSERT(sso_query_context->connection);
  231. struct aws_http_stream *stream = NULL;
  232. struct aws_credentials_provider_sso_impl *impl = sso_query_context->provider->impl;
  233. sso_query_context->request = aws_http_message_new_request(sso_query_context->allocator);
  234. if (sso_query_context->request == NULL) {
  235. goto on_error;
  236. }
  237. struct aws_http_header auth_header = {
  238. .name = aws_byte_cursor_from_string(s_sso_token_header),
  239. .value = aws_byte_cursor_from_string(sso_query_context->token),
  240. };
  241. struct aws_http_header host_header = {
  242. .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Host"),
  243. .value = aws_byte_cursor_from_string(impl->endpoint),
  244. };
  245. struct aws_http_header user_agent_header = {
  246. .name = aws_byte_cursor_from_string(s_sso_user_agent_header),
  247. .value = aws_byte_cursor_from_string(s_sso_user_agent_header_value),
  248. };
  249. if (aws_http_message_add_header(sso_query_context->request, auth_header) ||
  250. aws_http_message_add_header(sso_query_context->request, host_header) ||
  251. aws_http_message_add_header(sso_query_context->request, user_agent_header)) {
  252. AWS_LOGF_ERROR(
  253. AWS_LS_AUTH_CREDENTIALS_PROVIDER,
  254. "(id=%p) failed to add http header with error: %s",
  255. (void *)sso_query_context->provider,
  256. aws_error_debug_str(aws_last_error()));
  257. goto on_error;
  258. }
  259. if (aws_http_message_set_request_method(sso_query_context->request, aws_http_method_get)) {
  260. AWS_LOGF_ERROR(
  261. AWS_LS_AUTH_CREDENTIALS_PROVIDER,
  262. "(id=%p) failed to set request method with error: %s",
  263. (void *)sso_query_context->provider,
  264. aws_error_debug_str(aws_last_error()));
  265. goto on_error;
  266. }
  267. if (aws_http_message_set_request_path(
  268. sso_query_context->request, aws_byte_cursor_from_buf(&sso_query_context->path_and_query))) {
  269. AWS_LOGF_ERROR(
  270. AWS_LS_AUTH_CREDENTIALS_PROVIDER,
  271. "(id=%p) failed to set request path with error: %s",
  272. (void *)sso_query_context->provider,
  273. aws_error_debug_str(aws_last_error()));
  274. goto on_error;
  275. }
  276. struct aws_http_make_request_options request_options = {
  277. .self_size = sizeof(request_options),
  278. .on_response_headers = NULL,
  279. .on_response_header_block_done = NULL,
  280. .on_response_body = s_on_incoming_body_fn,
  281. .on_complete = s_on_stream_complete_fn,
  282. .user_data = sso_query_context,
  283. .request = sso_query_context->request,
  284. };
  285. stream = impl->function_table->aws_http_connection_make_request(sso_query_context->connection, &request_options);
  286. if (!stream) {
  287. AWS_LOGF_ERROR(
  288. AWS_LS_AUTH_CREDENTIALS_PROVIDER,
  289. "(id=%p) failed to make request with error: %s",
  290. (void *)sso_query_context->provider,
  291. aws_error_debug_str(aws_last_error()));
  292. goto on_error;
  293. }
  294. if (impl->function_table->aws_http_stream_activate(stream)) {
  295. AWS_LOGF_ERROR(
  296. AWS_LS_AUTH_CREDENTIALS_PROVIDER,
  297. "(id=%p) failed to activate the stream with error: %s",
  298. (void *)sso_query_context->provider,
  299. aws_error_debug_str(aws_last_error()));
  300. goto on_error;
  301. }
  302. return;
  303. on_error:
  304. sso_query_context->error_code = aws_last_error();
  305. impl->function_table->aws_http_stream_release(stream);
  306. s_finalize_get_credentials_query(sso_query_context);
  307. }
  308. static void s_on_get_token_callback(struct aws_credentials *credentials, int error_code, void *user_data) {
  309. struct aws_sso_query_context *sso_query_context = user_data;
  310. if (error_code) {
  311. AWS_LOGF_ERROR(
  312. AWS_LS_AUTH_CREDENTIALS_PROVIDER,
  313. "id=%p: failed to acquire a token, error code %d(%s)",
  314. (void *)sso_query_context->provider,
  315. error_code,
  316. aws_error_str(error_code));
  317. sso_query_context->error_code = error_code;
  318. s_finalize_get_credentials_query(sso_query_context);
  319. return;
  320. }
  321. struct aws_byte_cursor token = aws_credentials_get_token(credentials);
  322. AWS_LOGF_INFO(
  323. AWS_LS_AUTH_CREDENTIALS_PROVIDER,
  324. "(id=%p): successfully accquired a token",
  325. (void *)sso_query_context->provider);
  326. sso_query_context->token = aws_string_new_from_cursor(sso_query_context->allocator, &token);
  327. s_query_credentials(sso_query_context);
  328. }
  329. static void s_on_acquire_connection(struct aws_http_connection *connection, int error_code, void *user_data) {
  330. struct aws_sso_query_context *sso_query_context = user_data;
  331. if (error_code) {
  332. AWS_LOGF_ERROR(
  333. AWS_LS_AUTH_CREDENTIALS_PROVIDER,
  334. "id=%p: failed to acquire a connection, error code %d(%s)",
  335. (void *)sso_query_context->provider,
  336. error_code,
  337. aws_error_str(error_code));
  338. sso_query_context->error_code = error_code;
  339. s_finalize_get_credentials_query(sso_query_context);
  340. return;
  341. }
  342. AWS_LOGF_INFO(
  343. AWS_LS_AUTH_CREDENTIALS_PROVIDER,
  344. "(id=%p): successfully accquired a connection",
  345. (void *)sso_query_context->provider);
  346. sso_query_context->connection = connection;
  347. struct aws_credentials_provider_sso_impl *impl = sso_query_context->provider->impl;
  348. if (aws_credentials_provider_get_credentials(impl->token_provider, s_on_get_token_callback, user_data)) {
  349. int last_error_code = aws_last_error();
  350. AWS_LOGF_ERROR(
  351. AWS_LS_AUTH_CREDENTIALS_PROVIDER,
  352. "id=%p: failed to get a token, error code %d(%s)",
  353. (void *)sso_query_context->provider,
  354. last_error_code,
  355. aws_error_str(last_error_code));
  356. sso_query_context->error_code = last_error_code;
  357. s_finalize_get_credentials_query(sso_query_context);
  358. }
  359. }
  360. /* called for each retry. */
  361. static void s_on_retry_ready(struct aws_retry_token *token, int error_code, void *user_data) {
  362. (void)token;
  363. struct aws_sso_query_context *sso_query_context = user_data;
  364. struct aws_credentials_provider_sso_impl *impl = sso_query_context->provider->impl;
  365. if (error_code) {
  366. AWS_LOGF_ERROR(
  367. AWS_LS_AUTH_CREDENTIALS_PROVIDER,
  368. "(id=%p): failed to schedule retry with error: %s",
  369. (void *)sso_query_context->provider,
  370. aws_error_debug_str(error_code));
  371. sso_query_context->error_code = error_code;
  372. s_finalize_get_credentials_query(sso_query_context);
  373. return;
  374. }
  375. /* clear the result from previous attempt */
  376. s_sso_query_context_reset_request_specific_data(sso_query_context);
  377. impl->function_table->aws_http_connection_manager_acquire_connection(
  378. impl->connection_manager, s_on_acquire_connection, sso_query_context);
  379. }
  380. static void s_on_retry_token_acquired(
  381. struct aws_retry_strategy *strategy,
  382. int error_code,
  383. struct aws_retry_token *token,
  384. void *user_data) {
  385. struct aws_sso_query_context *sso_query_context = user_data;
  386. (void)strategy;
  387. if (error_code) {
  388. AWS_LOGF_ERROR(
  389. AWS_LS_AUTH_CREDENTIALS_PROVIDER,
  390. "(id=%p): failed to acquire retry token: %s",
  391. (void *)sso_query_context->provider,
  392. aws_error_debug_str(error_code));
  393. sso_query_context->error_code = error_code;
  394. s_finalize_get_credentials_query(sso_query_context);
  395. return;
  396. }
  397. sso_query_context->retry_token = token;
  398. struct aws_credentials_provider_sso_impl *impl = sso_query_context->provider->impl;
  399. impl->function_table->aws_http_connection_manager_acquire_connection(
  400. impl->connection_manager, s_on_acquire_connection, user_data);
  401. }
  402. static int s_credentials_provider_sso_get_credentials(
  403. struct aws_credentials_provider *provider,
  404. aws_on_get_credentials_callback_fn callback,
  405. void *user_data) {
  406. struct aws_credentials_provider_sso_impl *impl = provider->impl;
  407. struct aws_sso_query_context *sso_query_context = s_sso_query_context_new(provider, callback, user_data);
  408. if (sso_query_context == NULL) {
  409. return AWS_OP_ERR;
  410. }
  411. if (aws_retry_strategy_acquire_retry_token(
  412. impl->retry_strategy, NULL, s_on_retry_token_acquired, sso_query_context, SSO_RETRY_TIMEOUT_MS)) {
  413. AWS_LOGF_ERROR(
  414. AWS_LS_AUTH_CREDENTIALS_PROVIDER,
  415. "(id=%p): failed to acquire retry token: %s",
  416. (void *)provider,
  417. aws_error_debug_str(aws_last_error()));
  418. goto on_error;
  419. }
  420. return AWS_OP_SUCCESS;
  421. on_error:
  422. s_sso_query_context_destroy(sso_query_context);
  423. return AWS_OP_ERR;
  424. }
  425. static void s_on_connection_manager_shutdown(void *user_data) {
  426. struct aws_credentials_provider *provider = user_data;
  427. aws_credentials_provider_invoke_shutdown_callback(provider);
  428. aws_mem_release(provider->allocator, provider);
  429. }
  430. static void s_credentials_provider_sso_destroy(struct aws_credentials_provider *provider) {
  431. struct aws_credentials_provider_sso_impl *impl = provider->impl;
  432. if (impl == NULL) {
  433. return;
  434. }
  435. aws_string_destroy(impl->endpoint);
  436. aws_string_destroy(impl->sso_account_id);
  437. aws_string_destroy(impl->sso_role_name);
  438. aws_retry_strategy_release(impl->retry_strategy);
  439. aws_credentials_provider_release(impl->token_provider);
  440. /* aws_http_connection_manager_release will eventually leads to call of s_on_connection_manager_shutdown,
  441. * which will do memory release for provider and impl. So We should be freeing impl
  442. * related memory first, then call aws_http_connection_manager_release.
  443. */
  444. if (impl->connection_manager) {
  445. impl->function_table->aws_http_connection_manager_release(impl->connection_manager);
  446. } else {
  447. /* If provider setup failed halfway through, connection_manager might not exist.
  448. * In this case invoke shutdown completion callback directly to finish cleanup */
  449. s_on_connection_manager_shutdown(provider);
  450. }
  451. }
  452. static struct aws_credentials_provider_vtable s_aws_credentials_provider_sso_vtable = {
  453. .get_credentials = s_credentials_provider_sso_get_credentials,
  454. .destroy = s_credentials_provider_sso_destroy,
  455. };
  456. static int s_construct_sso_portal_endpoint(
  457. struct aws_allocator *allocator,
  458. struct aws_byte_buf *out_endpoint,
  459. const struct aws_string *region) {
  460. AWS_PRECONDITION(allocator);
  461. AWS_PRECONDITION(out_endpoint);
  462. if (!region) {
  463. return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
  464. }
  465. aws_byte_buf_clean_up(out_endpoint);
  466. struct aws_byte_cursor sso_prefix = aws_byte_cursor_from_c_str("portal.sso.");
  467. struct aws_byte_cursor region_cursor = aws_byte_cursor_from_string(region);
  468. struct aws_byte_cursor amazonaws_cursor = aws_byte_cursor_from_c_str(".amazonaws.com");
  469. struct aws_byte_cursor cn_cursor = aws_byte_cursor_from_c_str(".cn");
  470. if (aws_byte_buf_init_copy_from_cursor(out_endpoint, allocator, sso_prefix) ||
  471. aws_byte_buf_append_dynamic(out_endpoint, &region_cursor) ||
  472. aws_byte_buf_append_dynamic(out_endpoint, &amazonaws_cursor)) {
  473. goto on_error;
  474. }
  475. if (aws_string_eq_c_str_ignore_case(region, "cn-north-1") ||
  476. aws_string_eq_c_str_ignore_case(region, "cn-northwest-1")) {
  477. if (aws_byte_buf_append_dynamic(out_endpoint, &cn_cursor)) {
  478. goto on_error;
  479. }
  480. }
  481. return AWS_OP_SUCCESS;
  482. on_error:
  483. aws_byte_buf_clean_up(out_endpoint);
  484. return AWS_OP_ERR;
  485. }
  486. AWS_STATIC_STRING_FROM_LITERAL(s_sso_account_id, "sso_account_id");
  487. AWS_STATIC_STRING_FROM_LITERAL(s_sso_region, "sso_region");
  488. AWS_STATIC_STRING_FROM_LITERAL(s_sso_role_name, "sso_role_name");
  489. AWS_STATIC_STRING_FROM_LITERAL(s_sso_session, "sso_session");
  490. struct sso_parameters {
  491. struct aws_allocator *allocator;
  492. struct aws_byte_buf endpoint;
  493. struct aws_string *sso_account_id;
  494. struct aws_string *sso_role_name;
  495. struct aws_credentials_provider *token_provider;
  496. };
  497. static void s_parameters_destroy(struct sso_parameters *parameters) {
  498. if (!parameters) {
  499. return;
  500. }
  501. aws_byte_buf_clean_up(&parameters->endpoint);
  502. aws_string_destroy(parameters->sso_account_id);
  503. aws_string_destroy(parameters->sso_role_name);
  504. aws_credentials_provider_release(parameters->token_provider);
  505. aws_mem_release(parameters->allocator, parameters);
  506. }
  507. /**
  508. * Read the config file and construct profile or sso_session token provider based on sso_session property.
  509. *
  510. * If the profile contains sso_session property, a valid config example is as follow.
  511. * [profile sso-profile]
  512. * sso_session = dev
  513. * sso_account_id = 012345678901
  514. * sso_role_name = SampleRole
  515. *
  516. * [sso-session dev]
  517. * sso_region = us-east-1
  518. * sso_start_url = https://d-abc123.awsapps.com/start
  519. *
  520. * If the profile does't contains sso_session, the legacy valid config example is as follow.
  521. * [profile sso-profile]
  522. * sso_account_id = 012345678901
  523. * sso_region = us-east-1
  524. * sso_role_name = SampleRole
  525. * sso_start_url = https://d-abc123.awsapps.com/start-beta
  526. */
  527. static struct sso_parameters *s_parameters_new(
  528. struct aws_allocator *allocator,
  529. const struct aws_credentials_provider_sso_options *options) {
  530. struct sso_parameters *parameters = aws_mem_calloc(allocator, 1, sizeof(struct sso_parameters));
  531. parameters->allocator = allocator;
  532. struct aws_profile_collection *config_profile_collection = NULL;
  533. struct aws_string *profile_name = NULL;
  534. bool success = false;
  535. profile_name = aws_get_profile_name(allocator, &options->profile_name_override);
  536. if (!profile_name) {
  537. AWS_LOGF_ERROR(AWS_LS_AUTH_CREDENTIALS_PROVIDER, "sso: failed to resolve profile name");
  538. goto on_finish;
  539. }
  540. if (options->config_file_cached) {
  541. /* Use cached config file */
  542. config_profile_collection = aws_profile_collection_acquire(options->config_file_cached);
  543. } else {
  544. /* load config file */
  545. config_profile_collection =
  546. aws_load_profile_collection_from_config_file(allocator, options->config_file_name_override);
  547. }
  548. if (!config_profile_collection) {
  549. goto on_finish;
  550. }
  551. const struct aws_profile *profile = aws_profile_collection_get_profile(config_profile_collection, profile_name);
  552. if (!profile) {
  553. AWS_LOGF_ERROR(
  554. AWS_LS_AUTH_CREDENTIALS_PROVIDER, "sso: failed to load \"%s\" profile", aws_string_c_str(profile_name));
  555. goto on_finish;
  556. }
  557. const struct aws_profile_property *sso_account_id = aws_profile_get_property(profile, s_sso_account_id);
  558. const struct aws_profile_property *sso_role_name = aws_profile_get_property(profile, s_sso_role_name);
  559. const struct aws_profile_property *sso_region = NULL;
  560. if (!sso_account_id) {
  561. AWS_LOGF_ERROR(AWS_LS_AUTH_CREDENTIALS_PROVIDER, "sso: sso_account_id is missing");
  562. aws_raise_error(AWS_AUTH_CREDENTIALS_PROVIDER_SSO_SOURCE_FAILURE);
  563. goto on_finish;
  564. }
  565. if (!sso_role_name) {
  566. AWS_LOGF_ERROR(AWS_LS_AUTH_CREDENTIALS_PROVIDER, "sso: sso_role_name is missing");
  567. aws_raise_error(AWS_AUTH_CREDENTIALS_PROVIDER_SSO_SOURCE_FAILURE);
  568. goto on_finish;
  569. }
  570. const struct aws_profile_property *sso_session_property = aws_profile_get_property(profile, s_sso_session);
  571. /* create the appropriate token provider based on sso_session property is available or not */
  572. if (sso_session_property) {
  573. /* construct sso_session token provider */
  574. struct aws_token_provider_sso_session_options token_provider_options = {
  575. .config_file_name_override = options->config_file_name_override,
  576. .config_file_cached = config_profile_collection,
  577. .profile_name_override = options->profile_name_override,
  578. .bootstrap = options->bootstrap,
  579. .tls_ctx = options->tls_ctx,
  580. .system_clock_fn = options->system_clock_fn,
  581. };
  582. parameters->token_provider = aws_token_provider_new_sso_session(allocator, &token_provider_options);
  583. if (!parameters->token_provider) {
  584. AWS_LOGF_ERROR(AWS_LS_AUTH_CREDENTIALS_PROVIDER, "sso: unable to create a sso token provider");
  585. aws_raise_error(AWS_AUTH_CREDENTIALS_PROVIDER_SSO_SOURCE_FAILURE);
  586. goto on_finish;
  587. }
  588. sso_region = aws_profile_get_property(
  589. aws_profile_collection_get_section(
  590. config_profile_collection,
  591. AWS_PROFILE_SECTION_TYPE_SSO_SESSION,
  592. aws_profile_property_get_value(sso_session_property)),
  593. s_sso_region);
  594. } else {
  595. /* construct profile token provider */
  596. struct aws_token_provider_sso_profile_options token_provider_options = {
  597. .config_file_name_override = options->config_file_name_override,
  598. .config_file_cached = config_profile_collection,
  599. .profile_name_override = options->profile_name_override,
  600. .system_clock_fn = options->system_clock_fn,
  601. };
  602. parameters->token_provider = aws_token_provider_new_sso_profile(allocator, &token_provider_options);
  603. if (!parameters->token_provider) {
  604. AWS_LOGF_ERROR(AWS_LS_AUTH_CREDENTIALS_PROVIDER, "sso: unable to create a profile token provider");
  605. aws_raise_error(AWS_AUTH_CREDENTIALS_PROVIDER_SSO_SOURCE_FAILURE);
  606. goto on_finish;
  607. }
  608. sso_region = aws_profile_get_property(profile, s_sso_region);
  609. }
  610. if (!sso_region) {
  611. AWS_LOGF_ERROR(AWS_LS_AUTH_CREDENTIALS_PROVIDER, "sso: sso_region is missing");
  612. aws_raise_error(AWS_AUTH_CREDENTIALS_PROVIDER_SSO_SOURCE_FAILURE);
  613. goto on_finish;
  614. }
  615. parameters->sso_account_id = aws_string_new_from_string(allocator, aws_profile_property_get_value(sso_account_id));
  616. parameters->sso_role_name = aws_string_new_from_string(allocator, aws_profile_property_get_value(sso_role_name));
  617. /* determine endpoint */
  618. if (s_construct_sso_portal_endpoint(allocator, &parameters->endpoint, aws_profile_property_get_value(sso_region))) {
  619. AWS_LOGF_ERROR(AWS_LS_AUTH_CREDENTIALS_PROVIDER, "Failed to construct sso endpoint");
  620. goto on_finish;
  621. }
  622. AWS_LOGF_DEBUG(
  623. AWS_LS_AUTH_CREDENTIALS_PROVIDER, "Successfully loaded all required parameters for sso credentials provider.");
  624. success = true;
  625. on_finish:
  626. if (!success) {
  627. s_parameters_destroy(parameters);
  628. parameters = NULL;
  629. }
  630. aws_string_destroy(profile_name);
  631. aws_profile_collection_release(config_profile_collection);
  632. return parameters;
  633. }
  634. struct aws_credentials_provider *aws_credentials_provider_new_sso(
  635. struct aws_allocator *allocator,
  636. const struct aws_credentials_provider_sso_options *options) {
  637. struct sso_parameters *parameters = s_parameters_new(allocator, options);
  638. if (!parameters) {
  639. return NULL;
  640. }
  641. struct aws_credentials_provider *provider = NULL;
  642. struct aws_credentials_provider_sso_impl *impl = NULL;
  643. struct aws_tls_connection_options tls_connection_options;
  644. aws_mem_acquire_many(
  645. allocator,
  646. 2,
  647. &provider,
  648. sizeof(struct aws_credentials_provider),
  649. &impl,
  650. sizeof(struct aws_credentials_provider_sso_impl));
  651. AWS_ZERO_STRUCT(*provider);
  652. AWS_ZERO_STRUCT(*impl);
  653. AWS_ZERO_STRUCT(tls_connection_options);
  654. aws_credentials_provider_init_base(provider, allocator, &s_aws_credentials_provider_sso_vtable, impl);
  655. if (!options->tls_ctx) {
  656. AWS_LOGF_ERROR(AWS_LS_AUTH_CREDENTIALS_PROVIDER, "(id=%p): a TLS context must be provided", (void *)provider);
  657. aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
  658. goto on_error;
  659. }
  660. if (!options->bootstrap) {
  661. AWS_LOGF_ERROR(
  662. AWS_LS_AUTH_CREDENTIALS_PROVIDER, "(id=%p): a bootstrap instance must be provided", (void *)provider);
  663. aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
  664. goto on_error;
  665. }
  666. aws_tls_connection_options_init_from_ctx(&tls_connection_options, options->tls_ctx);
  667. struct aws_byte_cursor host = aws_byte_cursor_from_buf(&parameters->endpoint);
  668. if (aws_tls_connection_options_set_server_name(&tls_connection_options, allocator, &host)) {
  669. AWS_LOGF_ERROR(
  670. AWS_LS_AUTH_CREDENTIALS_PROVIDER,
  671. "(id=%p): failed to create a tls connection options with error %s",
  672. (void *)provider,
  673. aws_error_str(aws_last_error()));
  674. goto on_error;
  675. }
  676. struct aws_socket_options socket_options;
  677. AWS_ZERO_STRUCT(socket_options);
  678. socket_options.type = AWS_SOCKET_STREAM;
  679. socket_options.domain = AWS_SOCKET_IPV4;
  680. socket_options.connect_timeout_ms = (uint32_t)aws_timestamp_convert(
  681. SSO_CONNECT_TIMEOUT_DEFAULT_IN_SECONDS, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_MILLIS, NULL);
  682. struct aws_http_connection_manager_options manager_options;
  683. AWS_ZERO_STRUCT(manager_options);
  684. manager_options.bootstrap = options->bootstrap;
  685. manager_options.initial_window_size = SSO_RESPONSE_SIZE_LIMIT;
  686. manager_options.socket_options = &socket_options;
  687. manager_options.host = host;
  688. manager_options.port = 443;
  689. manager_options.max_connections = 2;
  690. manager_options.shutdown_complete_callback = s_on_connection_manager_shutdown;
  691. manager_options.shutdown_complete_user_data = provider;
  692. manager_options.tls_connection_options = &tls_connection_options;
  693. impl->function_table = options->function_table;
  694. if (impl->function_table == NULL) {
  695. impl->function_table = g_aws_credentials_provider_http_function_table;
  696. }
  697. impl->connection_manager = impl->function_table->aws_http_connection_manager_new(allocator, &manager_options);
  698. if (impl->connection_manager == NULL) {
  699. goto on_error;
  700. }
  701. impl->token_provider = aws_credentials_provider_acquire(parameters->token_provider);
  702. impl->endpoint = aws_string_new_from_buf(allocator, &parameters->endpoint);
  703. impl->sso_account_id = aws_string_new_from_string(allocator, parameters->sso_account_id);
  704. impl->sso_role_name = aws_string_new_from_string(allocator, parameters->sso_role_name);
  705. provider->shutdown_options = options->shutdown_options;
  706. struct aws_standard_retry_options retry_options = {
  707. .backoff_retry_options =
  708. {
  709. .el_group = options->bootstrap->event_loop_group,
  710. .max_retries = SSO_MAX_ATTEMPTS,
  711. },
  712. };
  713. impl->retry_strategy = aws_retry_strategy_new_standard(allocator, &retry_options);
  714. if (!impl->retry_strategy) {
  715. AWS_LOGF_ERROR(
  716. AWS_LS_AUTH_CREDENTIALS_PROVIDER,
  717. "(id=%p): failed to create a retry strategy with error %s",
  718. (void *)provider,
  719. aws_error_debug_str(aws_last_error()));
  720. goto on_error;
  721. }
  722. s_parameters_destroy(parameters);
  723. aws_tls_connection_options_clean_up(&tls_connection_options);
  724. return provider;
  725. on_error:
  726. aws_credentials_provider_destroy(provider);
  727. s_parameters_destroy(parameters);
  728. aws_tls_connection_options_clean_up(&tls_connection_options);
  729. return NULL;
  730. }