diff --git a/clients/cloud-setup/nmcs-provider-ec2.c b/clients/cloud-setup/nmcs-provider-ec2.c index 92e63a359e..3b776a0427 100644 --- a/clients/cloud-setup/nmcs-provider-ec2.c +++ b/clients/cloud-setup/nmcs-provider-ec2.c @@ -16,6 +16,11 @@ #define NM_EC2_METADATA_URL_BASE /* $NM_EC2_BASE/$NM_EC2_API_VERSION */ \ "/meta-data/network/interfaces/macs/" +/* Token TTL of 180 seconds is chosen abitrarily, in hope that it is + * surely more than enough to read all relevant metadata. */ +#define NM_EC2_TOKEN_TTL_HEADER "X-aws-ec2-metadata-token-ttl-seconds: 180" +#define NM_EC2_TOKEN_HEADER "X-aws-ec2-metadata-token: " + static const char * _ec2_base(void) { @@ -58,8 +63,15 @@ again: /*****************************************************************************/ +enum { + NM_EC2_HTTP_HEADER_TOKEN, + NM_EC2_HTTP_HEADER_SENTINEL, + _NM_EC2_HTTP_HEADER_NUM, +}; + struct _NMCSProviderEC2 { NMCSProvider parent; + char *token; }; struct _NMCSProviderEC2Class { @@ -70,23 +82,18 @@ G_DEFINE_TYPE(NMCSProviderEC2, nmcs_provider_ec2, NMCS_TYPE_PROVIDER); /*****************************************************************************/ -static gboolean -_detect_get_meta_data_check_cb(long response_code, - GBytes * response_data, - gpointer check_user_data, - GError **error) -{ - return response_code == 200 && nmcs_utils_parse_get_full_line(response_data, "ami-id"); -} - static void -_detect_get_meta_data_done_cb(GObject *source, GAsyncResult *result, gpointer user_data) +_detect_get_token_done_cb(GObject *source, GAsyncResult *result, gpointer user_data) { - gs_unref_object GTask *task = user_data; - gs_free_error GError *get_error = NULL; - gs_free_error GError *error = NULL; + gs_unref_object GTask *task = user_data; + NMCSProviderEC2 *self = NMCS_PROVIDER_EC2(g_task_get_source_object(task)); + gs_unref_bytes GBytes *response = NULL; + gs_free_error GError *get_error = NULL; + gs_free_error GError *error = NULL; - nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, NULL, &get_error); + nm_clear_g_free(&self->token); + + nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &get_error); if (nm_utils_error_is_cancelled(get_error)) { g_task_return_error(task, g_steal_pointer(&get_error)); @@ -102,6 +109,12 @@ _detect_get_meta_data_done_cb(GObject *source, GAsyncResult *result, gpointer us return; } + /* We use the token as-is. Special characters can cause confusion (e.g. + * response splitting), but we're not crossing a security boundary. + * None of the examples in AWS documentation does any sort of + * sanitization either. */ + self->token = g_strconcat(NM_EC2_TOKEN_HEADER, g_bytes_get_data(response, NULL), NULL); + g_task_return_boolean(task, TRUE); } @@ -114,17 +127,17 @@ detect(NMCSProvider *provider, GTask *task) http_client = nmcs_provider_get_http_client(provider); nm_http_client_poll_req(http_client, - (uri = _ec2_uri_concat("latest/meta-data/")), + (uri = _ec2_uri_concat("latest/api/token")), HTTP_TIMEOUT_MS, 256 * 1024, 7000, 1000, - NULL, - NULL, + NM_MAKE_STRV(NM_EC2_TOKEN_TTL_HEADER), + "PUT", g_task_get_cancellable(task), - _detect_get_meta_data_check_cb, NULL, - _detect_get_meta_data_done_cb, + NULL, + _detect_get_token_done_cb, task); } @@ -283,6 +296,8 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us GetConfigMetadataData * metadata_data = user_data; GetConfigIfaceData * iface_data; NMCSProviderGetConfigTaskData *get_config_data = metadata_data->get_config_data; + NMCSProviderEC2 * self = + NMCS_PROVIDER_EC2(g_task_get_source_object(get_config_data->task)); gs_unref_hashtable GHashTable *response_parsed = g_steal_pointer(&metadata_data->response_parsed); gs_free_error GError *error = NULL; @@ -377,7 +392,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us 512 * 1024, 10000, 1000, - NULL, + NM_MAKE_STRV(self->token), NULL, iface_data->cancellable, NULL, @@ -395,7 +410,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us 512 * 1024, 10000, 1000, - NULL, + NM_MAKE_STRV(self->token), NULL, iface_data->cancellable, NULL, @@ -477,9 +492,15 @@ _get_config_metadata_ready_check(long response_code, static void get_config(NMCSProvider *provider, NMCSProviderGetConfigTaskData *get_config_data) { + NMCSProviderEC2 * self = NMCS_PROVIDER_EC2(provider); gs_free char * uri = NULL; GetConfigMetadataData *metadata_data; + /* This can be called only if detect() succeeded, which implies + * there must be a token. + */ + nm_assert(self->token); + metadata_data = g_slice_new(GetConfigMetadataData); *metadata_data = (GetConfigMetadataData){ .get_config_data = get_config_data, @@ -495,7 +516,7 @@ get_config(NMCSProvider *provider, NMCSProviderGetConfigTaskData *get_config_dat 256 * 1024, 15000, 1000, - NULL, + NM_MAKE_STRV(self->token), NULL, g_task_get_cancellable(get_config_data->task), _get_config_metadata_ready_check, @@ -510,11 +531,24 @@ static void nmcs_provider_ec2_init(NMCSProviderEC2 *self) {} +static void +dispose(GObject *object) +{ + NMCSProviderEC2 *self = NMCS_PROVIDER_EC2(object); + + nm_clear_g_free(&self->token); + + G_OBJECT_CLASS(nmcs_provider_ec2_parent_class)->dispose(object); +} + static void nmcs_provider_ec2_class_init(NMCSProviderEC2Class *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); NMCSProviderClass *provider_class = NMCS_PROVIDER_CLASS(klass); + object_class->dispose = dispose; + provider_class->_name = "ec2"; provider_class->_env_provider_enabled = NMCS_ENV_VARIABLE("NM_CLOUD_SETUP_EC2"); provider_class->detect = detect;