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: variable alignments only

https://bugzilla.redhat.com/show_bug.cgi?id=2151986
(cherry picked from commit 8b7e12c2d6)
(cherry picked from commit 429f36cd81)
(cherry picked from commit e3ac982b32)
(cherry picked from commit c5a3e739b1)
(cherry picked from commit ee157ad48b)
(cherry picked from commit ae3ec36462)
(cherry picked from commit 865fe0732e)
This commit is contained in:
Lubomir Rintel 2023-02-27 00:15:11 +01:00 committed by Íñigo Huguet
parent 59b5a8fdcb
commit d75e307ebc

View file

@ -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)
{
@ -58,8 +63,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 {
@ -70,23 +82,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;
gs_free_error GError *get_error = NULL;
gs_free_error GError *error = NULL;
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));
@ -102,6 +109,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);
}
@ -114,17 +127,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);
}
@ -215,6 +228,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;
@ -228,6 +242,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;
@ -281,7 +296,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,
@ -299,7 +314,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,
@ -385,7 +400,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
@ -397,7 +418,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,
@ -412,11 +433,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;