diff --git a/clients/cloud-setup/nmcs-provider-ec2.c b/clients/cloud-setup/nmcs-provider-ec2.c index 4d92e1a778..30e9bcc586 100644 --- a/clients/cloud-setup/nmcs-provider-ec2.c +++ b/clients/cloud-setup/nmcs-provider-ec2.c @@ -15,6 +15,11 @@ #define NM_EC2_API_VERSION "2018-09-24" #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) { @@ -60,8 +65,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 { @@ -72,30 +84,24 @@ 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, +_detect_get_token_done_cb (GObject *source, GAsyncResult *result, gpointer user_data) { 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; gboolean success; + nm_clear_g_free(&self->token); + success = nm_http_client_poll_req_finish (NM_HTTP_CLIENT (source), result, NULL, - NULL, + &response, &get_error); if (nm_utils_error_is_cancelled (get_error, FALSE)) { @@ -120,6 +126,12 @@ _detect_get_meta_data_done_cb (GObject *source, 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); } @@ -133,17 +145,17 @@ detect (NMCSProvider *provider, 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); } @@ -307,6 +319,7 @@ _get_config_metadata_ready_cb (GObject *source, 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; GCancellable *cancellable; @@ -398,7 +411,7 @@ _get_config_metadata_ready_cb (GObject *source, 512*1024, 10000, 1000, - NULL, + NM_MAKE_STRV(self->token), NULL, iface_data->cancellable, NULL, @@ -417,7 +430,7 @@ _get_config_metadata_ready_cb (GObject *source, 512*1024, 10000, 1000, - NULL, + NM_MAKE_STRV(self->token), NULL, iface_data->cancellable, NULL, @@ -517,9 +530,15 @@ 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, @@ -535,7 +554,7 @@ get_config (NMCSProvider *provider, 256 * 1024, 15000, 1000, - NULL, + NM_MAKE_STRV(self->token), NULL, g_task_get_cancellable (get_config_data->task), _get_config_metadata_ready_check, @@ -551,11 +570,24 @@ 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;