207 lines
7.9 KiB
Diff
207 lines
7.9 KiB
Diff
From e3ac982b32361105708d489a73eaed2bc4dc5f9f Mon Sep 17 00:00:00 2001
|
|
From: Lubomir Rintel <lkundrak@v3.sk>
|
|
Date: Mon, 27 Feb 2023 00:15:11 +0100
|
|
Subject: [PATCH 6/6] 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.
|
|
|
|
https://bugzilla.redhat.com/show_bug.cgi?id=2151986
|
|
(cherry picked from commit 8b7e12c2d631c47292258c29429cd565715ea186)
|
|
(cherry picked from commit 429f36cd81ddbe337f04c09a352fd78cd29e394d)
|
|
---
|
|
src/nm-cloud-setup/nmcs-provider-ec2.c | 74 +++++++++++++++++++-------
|
|
1 file 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 c3c527cfd4..3a27a30e68 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,
|
|
@@ -395,11 +416,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;
|
|
--
|
|
2.39.2
|
|
|