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
This commit is contained in:
@@ -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;
|
||||
|
Reference in New Issue
Block a user