/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Fluent Bit * ========== * Copyright (C) 2015-2022 The Fluent Bit Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #define FLB_AWS_IMDS_ROOT "/" #define FLB_AWS_IMDS_V2_TOKEN_PATH "/latest/api/token" /* Request headers */ static struct flb_aws_header imds_v2_token_ttl_header = { .key = "X-aws-ec2-metadata-token-ttl-seconds", .key_len = 36, .val = "21600", /* 6 hours (ie maximum ttl) */ .val_len = 5, }; /* Request header templates */ const static struct flb_aws_header imds_v2_token_token_header_template = { .key = "X-aws-ec2-metadata-token", .key_len = 24, .val = "", /* Replace with token value */ .val_len = 0, /* Replace with token length */ }; /* Declarations */ static int get_imds_version(struct flb_aws_imds *ctx); static int refresh_imds_v2_token(struct flb_aws_imds *ctx); /* Default config values */ const struct flb_aws_imds_config flb_aws_imds_config_default = { FLB_AWS_IMDS_VERSION_EVALUATE}; /* Create IMDS context */ struct flb_aws_imds *flb_aws_imds_create(const struct flb_aws_imds_config *imds_config, struct flb_aws_client *ec2_imds_client) { struct flb_aws_imds *ctx = NULL; /* Create context */ ctx = flb_calloc(1, sizeof(struct flb_aws_imds)); if (!ctx) { flb_errno(); return NULL; } /* * Set IMDS version to whatever is specified in config * Version may be evaluated later if set to FLB_AWS_IMDS_VERSION_EVALUATE */ ctx->imds_version = imds_config->use_imds_version; ctx->imds_v2_token = flb_sds_create_len("INVALID_TOKEN", 13); ctx->imds_v2_token_len = 13; /* Detect IMDS support */ if (!ec2_imds_client->upstream) { flb_debug( "[imds] unable to connect to EC2 IMDS. ec2_imds_client upstream is null"); flb_aws_imds_destroy(ctx); return NULL; } if (0 != strncmp(ec2_imds_client->upstream->tcp_host, FLB_AWS_IMDS_HOST, FLB_AWS_IMDS_HOST_LEN)) { flb_debug("[imds] ec2_imds_client tcp host must be set to %s", FLB_AWS_IMDS_HOST); flb_aws_imds_destroy(ctx); return NULL; } if (ec2_imds_client->upstream->tcp_port != FLB_AWS_IMDS_PORT) { flb_debug("[imds] ec2_imds_client tcp port must be set to %i", FLB_AWS_IMDS_PORT); flb_aws_imds_destroy(ctx); return NULL; } /* Connect client */ ctx->ec2_imds_client = ec2_imds_client; return ctx; } /* Destroy IMDS context */ void flb_aws_imds_destroy(struct flb_aws_imds *ctx) { if (ctx->imds_v2_token) { flb_sds_destroy(ctx->imds_v2_token); } flb_free(ctx); } /* Get IMDS metadata */ int flb_aws_imds_request(struct flb_aws_imds *ctx, const char *metadata_path, flb_sds_t *metadata, size_t *metadata_len) { return flb_aws_imds_request_by_key(ctx, metadata_path, metadata, metadata_len, NULL); } /* Get IMDS metadata by key */ int flb_aws_imds_request_by_key(struct flb_aws_imds *ctx, const char *metadata_path, flb_sds_t *metadata, size_t *metadata_len, char *key) { int ret; flb_sds_t tmp; struct flb_http_client *c = NULL; struct flb_aws_client *ec2_imds_client = ctx->ec2_imds_client; struct flb_aws_header token_header = imds_v2_token_token_header_template; /* Get IMDS version */ int imds_version = get_imds_version(ctx); /* Abort on version detection failure */ if (imds_version == FLB_AWS_IMDS_VERSION_EVALUATE) { /* Exit gracefully allowing for retrys */ flb_warn("[imds] unable to evaluate IMDS version"); return -1; } if (imds_version == FLB_AWS_IMDS_VERSION_2) { token_header.val = ctx->imds_v2_token; token_header.val_len = ctx->imds_v2_token_len; flb_debug("[imds] using IMDSv2"); } else { flb_debug("[imds] using IMDSv1"); } c = ec2_imds_client->client_vtable->request( ec2_imds_client, FLB_HTTP_GET, metadata_path, NULL, 0, &token_header, (imds_version == FLB_AWS_IMDS_VERSION_1) ? 0 : 1); if (!c) { /* Exit gracefully allowing for retrys */ flb_warn("[imds] failed to retrieve metadata"); return -1; } /* Detect invalid token */ if (imds_version == FLB_AWS_IMDS_VERSION_2 && c->resp.status == 401) { /* Refresh token and retry request */ flb_http_client_destroy(c); ret = refresh_imds_v2_token(ctx); if (ret < 0) { flb_debug("[imds] failed to refresh IMDSv2 token"); return -1; } token_header.val = ctx->imds_v2_token; token_header.val_len = ctx->imds_v2_token_len; flb_debug("[imds] refreshed IMDSv2 token"); c = ec2_imds_client->client_vtable->request( ec2_imds_client, FLB_HTTP_GET, metadata_path, NULL, 0, &token_header, 1); if (!c) { /* Exit gracefully allowing for retries */ flb_warn("[imds] failed to retrieve metadata"); return -1; } } if (c->resp.status != 200) { ret = -1; if (c->resp.status == 404) { ret = -2; } if (c->resp.payload_size > 0) { flb_debug("[imds] metadata request failure response\n%s", c->resp.payload); } flb_http_client_destroy(c); return ret; } if (key != NULL) { /* get the value of the key from payload json string */ tmp = flb_json_get_val(c->resp.payload, c->resp.payload_size, key); if (!tmp) { tmp = flb_sds_create_len("NULL", 4); flb_error("[imds] %s is undefined in EC2 instance", key); } } else { tmp = flb_sds_create_len(c->resp.payload, c->resp.payload_size); } if (!tmp) { flb_errno(); flb_http_client_destroy(c); return -1; } *metadata = tmp; *metadata_len = key == NULL ? c->resp.payload_size : strlen(tmp); flb_http_client_destroy(c); return 0; } /* Get VPC Id */ flb_sds_t flb_aws_imds_get_vpc_id(struct flb_aws_imds *ctx) { int ret; flb_sds_t mac_id = NULL; size_t mac_len = 0; flb_sds_t vpc_id = NULL; size_t vpc_id_len = 0; /* get EC2 instance Mac id first before getting VPC id */ ret = flb_aws_imds_request(ctx, FLB_AWS_IMDS_MAC_PATH, &mac_id, &mac_len); if (ret < 0) { flb_sds_destroy(mac_id); return NULL; } /* * the VPC full path should be like: * latest/meta-data/network/interfaces/macs/{mac_id}/vpc-id/" */ flb_sds_t vpc_path = flb_sds_create_size(70); vpc_path = flb_sds_printf(&vpc_path, "%s/%s/%s/", "/latest/meta-data/network/interfaces/macs", mac_id, "vpc-id"); ret = flb_aws_imds_request(ctx, vpc_path, &vpc_id, &vpc_id_len); flb_sds_destroy(mac_id); flb_sds_destroy(vpc_path); return vpc_id; } /* Obtain the IMDS version */ static int get_imds_version(struct flb_aws_imds *ctx) { int ret; struct flb_aws_client *client = ctx->ec2_imds_client; struct flb_aws_header invalid_token_header; struct flb_http_client *c = NULL; if (ctx->imds_version != FLB_AWS_IMDS_VERSION_EVALUATE) { return ctx->imds_version; } /* * Evaluate version * To evaluate wether IMDSv2 is available, send an invalid token * in IMDS request. If response status is 'Unauthorized', then IMDSv2 * is available. */ invalid_token_header = imds_v2_token_token_header_template; invalid_token_header.val = "INVALID"; invalid_token_header.val_len = 7; c = client->client_vtable->request(client, FLB_HTTP_GET, FLB_AWS_IMDS_ROOT, NULL, 0, &invalid_token_header, 1); if (!c) { flb_debug("[imds] imds endpoint unavailable"); return FLB_AWS_IMDS_VERSION_EVALUATE; } /* Unauthorized response means that IMDS version 2 is in use */ if (c->resp.status == 401) { ctx->imds_version = FLB_AWS_IMDS_VERSION_2; ret = refresh_imds_v2_token(ctx); if (ret == -1) { /* * Token cannot be refreshed, test IMDSv1 * If IMDSv1 cannot be used, response will be status 401 */ flb_http_client_destroy(c); ctx->imds_version = FLB_AWS_IMDS_VERSION_EVALUATE; c = client->client_vtable->request(client, FLB_HTTP_GET, FLB_AWS_IMDS_ROOT, NULL, 0, NULL, 0); if (!c) { flb_debug("[imds] imds v1 attempt, endpoint unavailable"); return FLB_AWS_IMDS_VERSION_EVALUATE; } if (c->resp.status == 200) { flb_info("[imds] to use IMDSv2, set --http-put-response-hop-limit to 2"); } else { /* IMDSv1 unavailable. IMDSv2 beyond network hop count */ flb_warn("[imds] failed to retrieve IMDSv2 token and IMDSv1 unavailable. " "This is likely due to instance-metadata-options " "--http-put-response-hop-limit being set to 1 and --http-tokens " "set to required. " "To use IMDSv2, please set --http-put-response-hop-limit to 2 as " "described https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/" "configuring-instance-metadata-options.html"); } } } /* * Success means that IMDS version 1 is in use */ if (c->resp.status == 200) { flb_warn("[imds] falling back on IMDSv1"); ctx->imds_version = FLB_AWS_IMDS_VERSION_1; } flb_http_client_destroy(c); return ctx->imds_version; } /* * Get an IMDSv2 token * Token preserved in imds context */ static int refresh_imds_v2_token(struct flb_aws_imds *ctx) { struct flb_http_client *c = NULL; struct flb_aws_client *ec2_imds_client = ctx->ec2_imds_client; c = ec2_imds_client->client_vtable->request(ec2_imds_client, FLB_HTTP_PUT, FLB_AWS_IMDS_V2_TOKEN_PATH, NULL, 0, &imds_v2_token_ttl_header, 1); if (!c) { return -1; } if (c->resp.status != 200) { if (c->resp.payload_size > 0) { flb_error("[imds] IMDSv2 token retrieval failure response\n%s", c->resp.payload); } flb_http_client_destroy(c); return -1; } /* Preserve token information in ctx */ if (c->resp.payload_size > 0) { if (ctx->imds_v2_token) { flb_sds_destroy(ctx->imds_v2_token); } ctx->imds_v2_token = flb_sds_create_len(c->resp.payload, c->resp.payload_size); if (!ctx->imds_v2_token) { flb_errno(); flb_http_client_destroy(c); return -1; } ctx->imds_v2_token_len = c->resp.payload_size; flb_http_client_destroy(c); return 0; } flb_debug("[imds] IMDS metadata response was empty"); flb_http_client_destroy(c); return -1; }