diff options
author | Lubomir Rintel <lkundrak@v3.sk> | 2023-02-27 00:15:11 +0100 |
---|---|---|
committer | Lubomir Rintel <lkundrak@v3.sk> | 2023-03-07 13:54:08 +0100 |
commit | 8b7e12c2d631c47292258c29429cd565715ea186 (patch) | |
tree | ec8bb34f98a26992396af2771937605f01be9779 | |
parent | 088bfd817ab5eb8aa0fb9cffe52fa3f456030ecc (diff) | |
download | NetworkManager-lr/aws-ec2-idms2.tar.gz |
cloud-setup/ec2: start with requesting a IMDSv2 tokenlr/aws-ec2-idms2
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.
https://bugzilla.redhat.com/show_bug.cgi?id=2151986
-rw-r--r-- | src/nm-cloud-setup/nmcs-provider-ec2.c | 74 |
1 files changed, 54 insertions, 20 deletions
diff --git a/src/nm-cloud-setup/nmcs-provider-ec2.c b/src/nm-cloud-setup/nmcs-provider-ec2.c index e16e844b00..65a8f6298f 100644 --- a/src/nm-cloud-setup/nmcs-provider-ec2.c +++ b/src/nm-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) { @@ -44,8 +49,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 { @@ -56,23 +68,18 @@ G_DEFINE_TYPE(NMCSProviderEC2, nmcs_provider_ec2, NMCS_TYPE_PROVIDER); /*****************************************************************************/ -static gboolean -_detect_get_meta_data_check_cb(long response_code, - GBytes *response, - gpointer check_user_data, - GError **error) -{ - return response_code == 200 && nmcs_utils_parse_get_full_line(response, "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; + 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)); @@ -88,6 +95,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); } @@ -100,17 +113,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); } @@ -198,6 +211,7 @@ static void _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer user_data) { NMCSProviderGetConfigTaskData *get_config_data; + NMCSProviderEC2 *self; gs_unref_hashtable GHashTable *response_parsed = NULL; gs_free_error GError *error = NULL; GetConfigMetadataMac *v_mac_data; @@ -211,6 +225,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us return; get_config_data = user_data; + self = NMCS_PROVIDER_EC2(get_config_data->self); response_parsed = g_steal_pointer(&get_config_data->extra_data); get_config_data->extra_data_destroy = NULL; @@ -264,7 +279,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us 512 * 1024, 10000, 1000, - NULL, + NM_MAKE_STRV(self->token), NULL, get_config_data->intern_cancellable, NULL, @@ -282,7 +297,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us 512 * 1024, 10000, 1000, - NULL, + NM_MAKE_STRV(self->token), NULL, get_config_data->intern_cancellable, NULL, @@ -368,7 +383,13 @@ _get_config_metadata_ready_check(long response_code, static void get_config(NMCSProvider *provider, NMCSProviderGetConfigTaskData *get_config_data) { - gs_free char *uri = NULL; + NMCSProviderEC2 *self = NMCS_PROVIDER_EC2(provider); + gs_free char *uri = NULL; + + /* This can be called only if detect() succeeded, which implies + * there must be a token. + */ + nm_assert(self->token); /* First we fetch the "macs/". If the caller requested some particular * MAC addresses, then we poll until we see them. They might not yet be @@ -380,7 +401,7 @@ get_config(NMCSProvider *provider, NMCSProviderGetConfigTaskData *get_config_dat 256 * 1024, 15000, 1000, - NULL, + NM_MAKE_STRV(self->token), NULL, get_config_data->intern_cancellable, _get_config_metadata_ready_check, @@ -396,10 +417,23 @@ 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; |