summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLubomir Rintel <lkundrak@v3.sk>2023-02-27 00:15:11 +0100
committerLubomir Rintel <lkundrak@v3.sk>2023-03-07 13:54:08 +0100
commit8b7e12c2d631c47292258c29429cd565715ea186 (patch)
treeec8bb34f98a26992396af2771937605f01be9779
parent088bfd817ab5eb8aa0fb9cffe52fa3f456030ecc (diff)
downloadNetworkManager-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.c74
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;