From 60e632d891e02169db788f27ee262a235f314c8d Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Mon, 27 Feb 2023 00:15:11 +0100 Subject: [PATCH] cloud-setup/ec2: start with requesting a IMDSv2 token The present version of the EC2 metadata API (IMDSv2) requires a header with a token to be present in all requests. The token is essentially a cookie that's not actually a cookie that's obtained with a PUT call that doesn't put anything. Apparently it's too easy to trick someone into calling a GET method. EC2 now supports IMDSv2 everywhere with IMDSv1 being optional, so let's just use IMDSv2 unconditionally. Also, the presence of a token API can be used to detect the AWS EC2 cloud. Conflicts: - code format - missing 494819bbbf ("cloud-setup: move common code for get_config() to base class and improve cancellation"). From it we only needed the `get_config_data->self` part, but used g_task_get_source_object instead. - missing 5fb2f7e717 ("cloud-setup/trivial: rename "response_data" variable") https://bugzilla.redhat.com/show_bug.cgi?id=2151986 (cherry picked from commit 8b7e12c2d631c47292258c29429cd565715ea186) (cherry picked from commit 429f36cd81ddbe337f04c09a352fd78cd29e394d) (cherry picked from commit e3ac982b32361105708d489a73eaed2bc4dc5f9f) (cherry picked from commit c5a3e739b121b335f41d16e9098ac9e09037a79e) (cherry picked from commit ee157ad48bd213daefc0a0bb688c737ab337a26d) (cherry picked from commit ae3ec364629a739fb758b716a9542542ee98745b) (cherry picked from commit 865fe0732eb69bf8f0904b82d9335a44154f21ae) (cherry picked from commit d75e307ebc57fc8655742077b6a1ddc517b147ce) (cherry picked from commit ef3d4758d6e99c8e1978c50efc1a1506d83c7bc1) (cherry picked from commit 9be92ab29ed3b8e57a2c5cee530332b6302c1454) (cherry picked from commit f52ff08a65d5696bfc5fc05148a942eb1cad2c06) --- clients/cloud-setup/nmcs-provider-ec2.c | 72 ++++++++++++++++++------- 1 file changed, 52 insertions(+), 20 deletions(-) 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;