123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590 |
- /**
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0.
- */
- #include <aws/auth/credentials.h>
- #include <aws/auth/private/credentials_utils.h>
- #include <aws/common/clock.h>
- #include <aws/common/date_time.h>
- #include <aws/common/string.h>
- #include <aws/http/connection.h>
- #include <aws/http/connection_manager.h>
- #include <aws/http/request_response.h>
- #include <aws/http/status_code.h>
- #include <aws/io/logging.h>
- #include <aws/io/socket.h>
- #include <aws/io/tls_channel_handler.h>
- #include <aws/io/uri.h>
- #if defined(_MSC_VER)
- # pragma warning(disable : 4204)
- # pragma warning(disable : 4232)
- #endif /* _MSC_VER */
- /* ecs task role credentials body response is currently ~ 1300 characters + name length */
- #define ECS_RESPONSE_SIZE_INITIAL 2048
- #define ECS_RESPONSE_SIZE_LIMIT 10000
- #define ECS_CONNECT_TIMEOUT_DEFAULT_IN_SECONDS 2
- static void s_on_connection_manager_shutdown(void *user_data);
- struct aws_credentials_provider_ecs_impl {
- struct aws_http_connection_manager *connection_manager;
- const struct aws_auth_http_system_vtable *function_table;
- struct aws_string *host;
- struct aws_string *path_and_query;
- struct aws_string *auth_token;
- };
- /*
- * Tracking structure for each outstanding async query to an ecs provider
- */
- struct aws_credentials_provider_ecs_user_data {
- /* immutable post-creation */
- struct aws_allocator *allocator;
- struct aws_credentials_provider *ecs_provider;
- aws_on_get_credentials_callback_fn *original_callback;
- void *original_user_data;
- /* mutable */
- struct aws_http_connection *connection;
- struct aws_http_message *request;
- struct aws_byte_buf current_result;
- int status_code;
- int error_code;
- };
- static void s_aws_credentials_provider_ecs_user_data_destroy(struct aws_credentials_provider_ecs_user_data *user_data) {
- if (user_data == NULL) {
- return;
- }
- struct aws_credentials_provider_ecs_impl *impl = user_data->ecs_provider->impl;
- if (user_data->connection) {
- impl->function_table->aws_http_connection_manager_release_connection(
- impl->connection_manager, user_data->connection);
- }
- aws_byte_buf_clean_up(&user_data->current_result);
- if (user_data->request) {
- aws_http_message_destroy(user_data->request);
- }
- aws_credentials_provider_release(user_data->ecs_provider);
- aws_mem_release(user_data->allocator, user_data);
- }
- static struct aws_credentials_provider_ecs_user_data *s_aws_credentials_provider_ecs_user_data_new(
- struct aws_credentials_provider *ecs_provider,
- aws_on_get_credentials_callback_fn callback,
- void *user_data) {
- struct aws_credentials_provider_ecs_user_data *wrapped_user_data =
- aws_mem_calloc(ecs_provider->allocator, 1, sizeof(struct aws_credentials_provider_ecs_user_data));
- if (wrapped_user_data == NULL) {
- goto on_error;
- }
- wrapped_user_data->allocator = ecs_provider->allocator;
- wrapped_user_data->ecs_provider = ecs_provider;
- aws_credentials_provider_acquire(ecs_provider);
- wrapped_user_data->original_user_data = user_data;
- wrapped_user_data->original_callback = callback;
- if (aws_byte_buf_init(&wrapped_user_data->current_result, ecs_provider->allocator, ECS_RESPONSE_SIZE_INITIAL)) {
- goto on_error;
- }
- return wrapped_user_data;
- on_error:
- s_aws_credentials_provider_ecs_user_data_destroy(wrapped_user_data);
- return NULL;
- }
- static void s_aws_credentials_provider_ecs_user_data_reset_response(
- struct aws_credentials_provider_ecs_user_data *ecs_user_data) {
- ecs_user_data->current_result.len = 0;
- ecs_user_data->status_code = 0;
- if (ecs_user_data->request) {
- aws_http_message_destroy(ecs_user_data->request);
- ecs_user_data->request = NULL;
- }
- }
- /*
- * In general, the ECS document looks something like:
- {
- "Code" : "Success",
- "LastUpdated" : "2019-05-28T18:03:09Z",
- "Type" : "AWS-HMAC",
- "AccessKeyId" : "...",
- "SecretAccessKey" : "...",
- "Token" : "...",
- "Expiration" : "2019-05-29T00:21:43Z"
- }
- *
- * No matter the result, this always gets called assuming that esc_user_data is successfully allocated
- */
- static void s_ecs_finalize_get_credentials_query(struct aws_credentials_provider_ecs_user_data *ecs_user_data) {
- /* Try to build credentials from whatever, if anything, was in the result */
- struct aws_credentials *credentials = NULL;
- struct aws_parse_credentials_from_json_doc_options parse_options = {
- .access_key_id_name = "AccessKeyId",
- .secret_access_key_name = "SecretAccessKey",
- .token_name = "Token",
- .expiration_name = "Expiration",
- .token_required = true,
- .expiration_required = true,
- };
- if (aws_byte_buf_append_null_terminator(&ecs_user_data->current_result) == AWS_OP_SUCCESS) {
- credentials = aws_parse_credentials_from_json_document(
- ecs_user_data->allocator, (const char *)ecs_user_data->current_result.buffer, &parse_options);
- } else {
- AWS_LOGF_ERROR(
- AWS_LS_AUTH_CREDENTIALS_PROVIDER,
- "(id=%p) ECS credentials provider failed to add null terminating char to resulting buffer.",
- (void *)ecs_user_data->ecs_provider);
- }
- if (credentials != NULL) {
- AWS_LOGF_INFO(
- AWS_LS_AUTH_CREDENTIALS_PROVIDER,
- "(id=%p) ECS credentials provider successfully queried instance role credentials",
- (void *)ecs_user_data->ecs_provider);
- } else {
- /* no credentials, make sure we have a valid error to report */
- if (ecs_user_data->error_code == AWS_ERROR_SUCCESS) {
- ecs_user_data->error_code = aws_last_error();
- if (ecs_user_data->error_code == AWS_ERROR_SUCCESS) {
- ecs_user_data->error_code = AWS_AUTH_CREDENTIALS_PROVIDER_ECS_SOURCE_FAILURE;
- }
- }
- AWS_LOGF_WARN(
- AWS_LS_AUTH_CREDENTIALS_PROVIDER,
- "(id=%p) ECS credentials provider failed to query instance role credentials with error %d(%s)",
- (void *)ecs_user_data->ecs_provider,
- ecs_user_data->error_code,
- aws_error_str(ecs_user_data->error_code));
- }
- /* pass the credentials back */
- ecs_user_data->original_callback(credentials, ecs_user_data->error_code, ecs_user_data->original_user_data);
- /* clean up */
- s_aws_credentials_provider_ecs_user_data_destroy(ecs_user_data);
- aws_credentials_release(credentials);
- }
- static int s_ecs_on_incoming_body_fn(
- struct aws_http_stream *stream,
- const struct aws_byte_cursor *data,
- void *user_data) {
- (void)stream;
- struct aws_credentials_provider_ecs_user_data *ecs_user_data = user_data;
- struct aws_credentials_provider_ecs_impl *impl = ecs_user_data->ecs_provider->impl;
- AWS_LOGF_TRACE(
- AWS_LS_AUTH_CREDENTIALS_PROVIDER,
- "(id=%p) ECS credentials provider received %zu response bytes",
- (void *)ecs_user_data->ecs_provider,
- data->len);
- if (data->len + ecs_user_data->current_result.len > ECS_RESPONSE_SIZE_LIMIT) {
- impl->function_table->aws_http_connection_close(ecs_user_data->connection);
- AWS_LOGF_ERROR(
- AWS_LS_AUTH_CREDENTIALS_PROVIDER,
- "(id=%p) ECS credentials provider query response exceeded maximum allowed length",
- (void *)ecs_user_data->ecs_provider);
- return aws_raise_error(AWS_ERROR_SHORT_BUFFER);
- }
- if (aws_byte_buf_append_dynamic(&ecs_user_data->current_result, data)) {
- impl->function_table->aws_http_connection_close(ecs_user_data->connection);
- AWS_LOGF_ERROR(
- AWS_LS_AUTH_CREDENTIALS_PROVIDER,
- "(id=%p) ECS credentials provider query error appending response",
- (void *)ecs_user_data->ecs_provider);
- return AWS_OP_ERR;
- }
- return AWS_OP_SUCCESS;
- }
- static int s_ecs_on_incoming_headers_fn(
- struct aws_http_stream *stream,
- enum aws_http_header_block header_block,
- const struct aws_http_header *header_array,
- size_t num_headers,
- void *user_data) {
- (void)header_array;
- (void)num_headers;
- if (header_block != AWS_HTTP_HEADER_BLOCK_MAIN) {
- return AWS_OP_SUCCESS;
- }
- struct aws_credentials_provider_ecs_user_data *ecs_user_data = user_data;
- if (header_block == AWS_HTTP_HEADER_BLOCK_MAIN) {
- if (ecs_user_data->status_code == 0) {
- struct aws_credentials_provider_ecs_impl *impl = ecs_user_data->ecs_provider->impl;
- if (impl->function_table->aws_http_stream_get_incoming_response_status(
- stream, &ecs_user_data->status_code)) {
- AWS_LOGF_ERROR(
- AWS_LS_AUTH_CREDENTIALS_PROVIDER,
- "(id=%p) ECS credentials provider failed to get http status code",
- (void *)ecs_user_data->ecs_provider);
- return AWS_OP_ERR;
- }
- AWS_LOGF_DEBUG(
- AWS_LS_AUTH_CREDENTIALS_PROVIDER,
- "(id=%p) ECS credentials provider query received http status code %d",
- (void *)ecs_user_data->ecs_provider,
- ecs_user_data->status_code);
- }
- }
- return AWS_OP_SUCCESS;
- }
- static void s_ecs_query_task_role_credentials(struct aws_credentials_provider_ecs_user_data *ecs_user_data);
- static void s_ecs_on_stream_complete_fn(struct aws_http_stream *stream, int error_code, void *user_data) {
- struct aws_credentials_provider_ecs_user_data *ecs_user_data = user_data;
- aws_http_message_destroy(ecs_user_data->request);
- ecs_user_data->request = NULL;
- struct aws_credentials_provider_ecs_impl *impl = ecs_user_data->ecs_provider->impl;
- impl->function_table->aws_http_stream_release(stream);
- /*
- * On anything other than a 200, nullify the response and pretend there was
- * an error
- */
- if (ecs_user_data->status_code != AWS_HTTP_STATUS_CODE_200_OK || error_code != AWS_OP_SUCCESS) {
- ecs_user_data->current_result.len = 0;
- if (error_code != AWS_OP_SUCCESS) {
- ecs_user_data->error_code = error_code;
- } else {
- ecs_user_data->error_code = AWS_AUTH_CREDENTIALS_PROVIDER_HTTP_STATUS_FAILURE;
- }
- }
- s_ecs_finalize_get_credentials_query(ecs_user_data);
- }
- AWS_STATIC_STRING_FROM_LITERAL(s_ecs_accept_header, "Accept");
- AWS_STATIC_STRING_FROM_LITERAL(s_ecs_accept_header_value, "application/json");
- AWS_STATIC_STRING_FROM_LITERAL(s_ecs_user_agent_header, "User-Agent");
- AWS_STATIC_STRING_FROM_LITERAL(s_ecs_user_agent_header_value, "aws-sdk-crt/ecs-credentials-provider");
- AWS_STATIC_STRING_FROM_LITERAL(s_ecs_authorization_header, "Authorization");
- AWS_STATIC_STRING_FROM_LITERAL(s_ecs_accept_encoding_header, "Accept-Encoding");
- AWS_STATIC_STRING_FROM_LITERAL(s_ecs_accept_encoding_header_value, "identity");
- AWS_STATIC_STRING_FROM_LITERAL(s_ecs_host_header, "Host");
- static int s_make_ecs_http_query(
- struct aws_credentials_provider_ecs_user_data *ecs_user_data,
- struct aws_byte_cursor *uri) {
- AWS_FATAL_ASSERT(ecs_user_data->connection);
- struct aws_http_stream *stream = NULL;
- struct aws_http_message *request = aws_http_message_new_request(ecs_user_data->allocator);
- if (request == NULL) {
- return AWS_OP_ERR;
- }
- struct aws_credentials_provider_ecs_impl *impl = ecs_user_data->ecs_provider->impl;
- struct aws_http_header host_header = {
- .name = aws_byte_cursor_from_string(s_ecs_host_header),
- .value = aws_byte_cursor_from_string(impl->host),
- };
- if (aws_http_message_add_header(request, host_header)) {
- goto on_error;
- }
- if (impl->auth_token != NULL) {
- struct aws_http_header auth_header = {
- .name = aws_byte_cursor_from_string(s_ecs_authorization_header),
- .value = aws_byte_cursor_from_string(impl->auth_token),
- };
- if (aws_http_message_add_header(request, auth_header)) {
- goto on_error;
- }
- }
- struct aws_http_header accept_header = {
- .name = aws_byte_cursor_from_string(s_ecs_accept_header),
- .value = aws_byte_cursor_from_string(s_ecs_accept_header_value),
- };
- if (aws_http_message_add_header(request, accept_header)) {
- goto on_error;
- }
- struct aws_http_header accept_encoding_header = {
- .name = aws_byte_cursor_from_string(s_ecs_accept_encoding_header),
- .value = aws_byte_cursor_from_string(s_ecs_accept_encoding_header_value),
- };
- if (aws_http_message_add_header(request, accept_encoding_header)) {
- goto on_error;
- }
- struct aws_http_header user_agent_header = {
- .name = aws_byte_cursor_from_string(s_ecs_user_agent_header),
- .value = aws_byte_cursor_from_string(s_ecs_user_agent_header_value),
- };
- if (aws_http_message_add_header(request, user_agent_header)) {
- goto on_error;
- }
- if (aws_http_message_set_request_path(request, *uri)) {
- goto on_error;
- }
- if (aws_http_message_set_request_method(request, aws_byte_cursor_from_c_str("GET"))) {
- goto on_error;
- }
- ecs_user_data->request = request;
- struct aws_http_make_request_options request_options = {
- .self_size = sizeof(request_options),
- .on_response_headers = s_ecs_on_incoming_headers_fn,
- .on_response_header_block_done = NULL,
- .on_response_body = s_ecs_on_incoming_body_fn,
- .on_complete = s_ecs_on_stream_complete_fn,
- .user_data = ecs_user_data,
- .request = request,
- };
- stream = impl->function_table->aws_http_connection_make_request(ecs_user_data->connection, &request_options);
- if (!stream) {
- goto on_error;
- }
- if (impl->function_table->aws_http_stream_activate(stream)) {
- goto on_error;
- }
- return AWS_OP_SUCCESS;
- on_error:
- impl->function_table->aws_http_stream_release(stream);
- aws_http_message_destroy(request);
- ecs_user_data->request = NULL;
- return AWS_OP_ERR;
- }
- static void s_ecs_query_task_role_credentials(struct aws_credentials_provider_ecs_user_data *ecs_user_data) {
- AWS_FATAL_ASSERT(ecs_user_data->connection);
- struct aws_credentials_provider_ecs_impl *impl = ecs_user_data->ecs_provider->impl;
- /* "Clear" the result */
- s_aws_credentials_provider_ecs_user_data_reset_response(ecs_user_data);
- struct aws_byte_cursor uri_cursor = aws_byte_cursor_from_string(impl->path_and_query);
- if (s_make_ecs_http_query(ecs_user_data, &uri_cursor) == AWS_OP_ERR) {
- s_ecs_finalize_get_credentials_query(ecs_user_data);
- }
- }
- static void s_ecs_on_acquire_connection(struct aws_http_connection *connection, int error_code, void *user_data) {
- struct aws_credentials_provider_ecs_user_data *ecs_user_data = user_data;
- if (connection == NULL) {
- AWS_LOGF_WARN(
- AWS_LS_AUTH_CREDENTIALS_PROVIDER,
- "id=%p: ECS provider failed to acquire a connection, error code %d(%s)",
- (void *)ecs_user_data->ecs_provider,
- error_code,
- aws_error_str(error_code));
- ecs_user_data->error_code = error_code;
- s_ecs_finalize_get_credentials_query(ecs_user_data);
- return;
- }
- ecs_user_data->connection = connection;
- s_ecs_query_task_role_credentials(ecs_user_data);
- }
- static int s_credentials_provider_ecs_get_credentials_async(
- struct aws_credentials_provider *provider,
- aws_on_get_credentials_callback_fn callback,
- void *user_data) {
- struct aws_credentials_provider_ecs_impl *impl = provider->impl;
- struct aws_credentials_provider_ecs_user_data *wrapped_user_data =
- s_aws_credentials_provider_ecs_user_data_new(provider, callback, user_data);
- if (wrapped_user_data == NULL) {
- goto error;
- }
- impl->function_table->aws_http_connection_manager_acquire_connection(
- impl->connection_manager, s_ecs_on_acquire_connection, wrapped_user_data);
- return AWS_OP_SUCCESS;
- error:
- s_aws_credentials_provider_ecs_user_data_destroy(wrapped_user_data);
- return AWS_OP_ERR;
- }
- static void s_credentials_provider_ecs_destroy(struct aws_credentials_provider *provider) {
- struct aws_credentials_provider_ecs_impl *impl = provider->impl;
- if (impl == NULL) {
- return;
- }
- aws_string_destroy(impl->path_and_query);
- aws_string_destroy(impl->auth_token);
- aws_string_destroy(impl->host);
- /* aws_http_connection_manager_release will eventually leads to call of s_on_connection_manager_shutdown,
- * which will do memory release for provider and impl. So We should be freeing impl
- * related memory first, then call aws_http_connection_manager_release.
- */
- if (impl->connection_manager) {
- impl->function_table->aws_http_connection_manager_release(impl->connection_manager);
- } else {
- /* If provider setup failed halfway through, connection_manager might not exist.
- * In this case invoke shutdown completion callback directly to finish cleanup */
- s_on_connection_manager_shutdown(provider);
- }
- /* freeing the provider takes place in the shutdown callback below */
- }
- static struct aws_credentials_provider_vtable s_aws_credentials_provider_ecs_vtable = {
- .get_credentials = s_credentials_provider_ecs_get_credentials_async,
- .destroy = s_credentials_provider_ecs_destroy,
- };
- static void s_on_connection_manager_shutdown(void *user_data) {
- struct aws_credentials_provider *provider = user_data;
- aws_credentials_provider_invoke_shutdown_callback(provider);
- aws_mem_release(provider->allocator, provider);
- }
- struct aws_credentials_provider *aws_credentials_provider_new_ecs(
- struct aws_allocator *allocator,
- const struct aws_credentials_provider_ecs_options *options) {
- struct aws_credentials_provider *provider = NULL;
- struct aws_credentials_provider_ecs_impl *impl = NULL;
- aws_mem_acquire_many(
- allocator,
- 2,
- &provider,
- sizeof(struct aws_credentials_provider),
- &impl,
- sizeof(struct aws_credentials_provider_ecs_impl));
- if (!provider) {
- return NULL;
- }
- AWS_ZERO_STRUCT(*provider);
- AWS_ZERO_STRUCT(*impl);
- aws_credentials_provider_init_base(provider, allocator, &s_aws_credentials_provider_ecs_vtable, impl);
- struct aws_tls_connection_options tls_connection_options;
- AWS_ZERO_STRUCT(tls_connection_options);
- if (options->tls_ctx) {
- aws_tls_connection_options_init_from_ctx(&tls_connection_options, options->tls_ctx);
- struct aws_byte_cursor host = options->host;
- if (aws_tls_connection_options_set_server_name(&tls_connection_options, allocator, &host)) {
- AWS_LOGF_ERROR(
- AWS_LS_AUTH_CREDENTIALS_PROVIDER,
- "(id=%p): failed to create a tls connection options with error %s",
- (void *)provider,
- aws_error_debug_str(aws_last_error()));
- goto on_error;
- }
- }
- struct aws_socket_options socket_options;
- AWS_ZERO_STRUCT(socket_options);
- socket_options.type = AWS_SOCKET_STREAM;
- socket_options.domain = AWS_SOCKET_IPV4;
- socket_options.connect_timeout_ms = (uint32_t)aws_timestamp_convert(
- ECS_CONNECT_TIMEOUT_DEFAULT_IN_SECONDS, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_MILLIS, NULL);
- struct aws_http_connection_manager_options manager_options;
- AWS_ZERO_STRUCT(manager_options);
- manager_options.bootstrap = options->bootstrap;
- manager_options.initial_window_size = ECS_RESPONSE_SIZE_LIMIT;
- manager_options.socket_options = &socket_options;
- manager_options.host = options->host;
- if (options->port == 0) {
- manager_options.port = options->tls_ctx ? 443 : 80;
- } else {
- manager_options.port = options->port;
- }
- manager_options.max_connections = 2;
- manager_options.shutdown_complete_callback = s_on_connection_manager_shutdown;
- manager_options.shutdown_complete_user_data = provider;
- manager_options.tls_connection_options = options->tls_ctx ? &tls_connection_options : NULL;
- impl->function_table = options->function_table;
- if (impl->function_table == NULL) {
- impl->function_table = g_aws_credentials_provider_http_function_table;
- }
- impl->connection_manager = impl->function_table->aws_http_connection_manager_new(allocator, &manager_options);
- if (impl->connection_manager == NULL) {
- goto on_error;
- }
- if (options->auth_token.len != 0) {
- impl->auth_token = aws_string_new_from_cursor(allocator, &options->auth_token);
- if (impl->auth_token == NULL) {
- goto on_error;
- }
- }
- impl->path_and_query = aws_string_new_from_cursor(allocator, &options->path_and_query);
- if (impl->path_and_query == NULL) {
- goto on_error;
- }
- impl->host = aws_string_new_from_cursor(allocator, &options->host);
- if (impl->host == NULL) {
- goto on_error;
- }
- provider->shutdown_options = options->shutdown_options;
- aws_tls_connection_options_clean_up(&tls_connection_options);
- return provider;
- on_error:
- aws_tls_connection_options_clean_up(&tls_connection_options);
- aws_credentials_provider_destroy(provider);
- return NULL;
- }
|