From 2842b367cbb223cfb0e172ebf074b41390bb64d2 Mon Sep 17 00:00:00 2001 From: Andrew Titmuss Date: Wed, 26 Jun 2024 22:01:43 +1000 Subject: [PATCH 1/4] aws_credentials_http: add support for EKS pod identities This patch rewrites how the HTTP credentials provider works to allow both ECS and EKS identities to work. It is based on the aws-sdk-go-v2 implementation. It validates that the endpoint is correct if the transport is HTTP, but does not support DNS resolution, however based on how the pod identity agent works today, DNS should not be needed. If the transport is HTTPS, which will not be the case when using EKS Pod Identities, any endpoint is allowed. This is in line with how the AWS SDK works. Similarly to the SDK, it also reads the authentication token environment variables, with the file taking precedence over the raw token variable. This has been tested against an EKS 1.30 cluster with AL2023 nodes. Signed-off-by: Andrew Titmuss --- include/fluent-bit/flb_aws_credentials.h | 20 +- src/aws/flb_aws_credentials.c | 6 +- src/aws/flb_aws_credentials_http.c | 275 ++++++++++++++++-- src/aws/flb_aws_util.c | 6 + tests/internal/aws_credentials_http.c | 240 ++++++++++++++- .../data/aws_credentials/http_token_file.txt | 1 + 6 files changed, 496 insertions(+), 52 deletions(-) create mode 100644 tests/internal/data/aws_credentials/http_token_file.txt diff --git a/include/fluent-bit/flb_aws_credentials.h b/include/fluent-bit/flb_aws_credentials.h index 5bcda676b5b..adde77bf039 100644 --- a/include/fluent-bit/flb_aws_credentials.h +++ b/include/fluent-bit/flb_aws_credentials.h @@ -257,18 +257,28 @@ struct flb_aws_provider *flb_aws_env_provider_create(); * used by host and path. */ struct flb_aws_provider *flb_http_provider_create(struct flb_config *config, - flb_sds_t host, + flb_sds_t endpoint, flb_sds_t path, + flb_sds_t auth_token, struct flb_aws_client_generator *generator); +struct flb_aws_provider *flb_local_http_provider_create(struct flb_config *config, + flb_sds_t endpoint, + flb_sds_t auth_token, + struct + flb_aws_client_generator + *generator); + + + /* - * ECS Provider - * The ECS Provider is just a wrapper around the HTTP Provider - * with the ECS credentials endpoint. + * Container Provider + * The Container Provider is just a wrapper around the HTTP Provider + * with the ECS/EKS credentials endpoint. */ -struct flb_aws_provider *flb_ecs_provider_create(struct flb_config *config, +struct flb_aws_provider *flb_container_provider_create(struct flb_config *config, struct flb_aws_client_generator *generator); diff --git a/src/aws/flb_aws_credentials.c b/src/aws/flb_aws_credentials.c index 824a535d021..d3cb3d1c9e7 100644 --- a/src/aws/flb_aws_credentials.c +++ b/src/aws/flb_aws_credentials.c @@ -581,11 +581,11 @@ static struct flb_aws_provider *standard_chain_create(struct flb_config } } - sub_provider = flb_ecs_provider_create(config, generator); + sub_provider = flb_container_provider_create(config, generator); if (sub_provider) { - /* ECS Provider will fail creation if we are not running in ECS */ + /* HTTP Provider will fail creation if we are not running in ECS/EKS */ mk_list_add(&sub_provider->_head, &implementation->sub_providers); - flb_debug("[aws_credentials] Initialized ECS Provider in standard chain"); + flb_debug("[aws_credentials] Initialized HTTP Provider in standard chain"); } sub_provider = flb_ec2_provider_create(config, generator); diff --git a/src/aws/flb_aws_credentials_http.c b/src/aws/flb_aws_credentials_http.c index d9095935ce6..1c3512a5a1c 100644 --- a/src/aws/flb_aws_credentials_http.c +++ b/src/aws/flb_aws_credentials_http.c @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include #include @@ -34,9 +36,19 @@ #define AWS_HTTP_RESPONSE_TOKEN "Token" #define AWS_CREDENTIAL_RESPONSE_EXPIRATION "Expiration" -#define ECS_CREDENTIALS_HOST "169.254.170.2" -#define ECS_CREDENTIALS_HOST_LEN 13 -#define ECS_CREDENTIALS_PATH_ENV_VAR "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" +#define ECS_CONTAINER_ENDPOINT "http://169.254.170.2" +#define ECS_CONTAINER_ENDPOINT_LEN 20 +#define ECS_CREDENTIALS_HOST "169.254.170.2" +#define ECS_CREDENTIALS_HOST_LEN 13 +#define EKS_CREDENTIALS_HOST "169.254.170.23" +#define EKS_CREDENTIALS_HOST_LEN 14 +#define EKS_CREDENTIALS_HOST_IPV6 "fd00:ec2::23" +#define EKS_CREDENTIALS_HOST_IPV6_LEN 12 + +#define AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE_ENV_VAR "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE" +#define AWS_CONTAINER_AUTHORIZATION_TOKEN_ENV_VAR "AWS_CONTAINER_AUTHORIZATION_TOKEN" +#define AWS_CONTAINER_CREDENTIALS_RELATIVE_URI_ENV_VAR "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" +#define AWS_CONTAINER_CREDENTIALS_FULL_URI_ENV_VAR "AWS_CONTAINER_CREDENTIALS_FULL_URI" /* Declarations */ @@ -44,10 +56,16 @@ struct flb_aws_provider_http; static int http_credentials_request(struct flb_aws_provider_http *implementation); +static struct flb_aws_header authorization_header = { + .key = "Authorization", + .key_len = 13, + .val = "", + .val_len = 0, +}; /* * HTTP Credentials Provider - retrieve credentials from a local http server - * Used to implement the ECS Credentials provider. + * Used to implement the Container Credentials provider. * Equivalent to: * https://github.com/aws/aws-sdk-go/tree/master/aws/credentials/endpointcreds */ @@ -58,9 +76,13 @@ struct flb_aws_provider_http { struct flb_aws_client *client; - /* Host and Path to request credentials */ - flb_sds_t host; + /* Endpoint to request credentials */ + flb_sds_t endpoint; flb_sds_t path; + + /* Auth token */ + flb_sds_t auth_token; + flb_sds_t auth_token_file; }; @@ -136,6 +158,35 @@ struct flb_aws_credentials *get_credentials_fn_http(struct flb_aws_provider return NULL; } +static int flb_aws_allowed_ip(char *ip_address) { + if (strcmp(ip_address, ECS_CREDENTIALS_HOST) == 0) { + return FLB_TRUE; // ECS container host + } else if (strcmp(ip_address, EKS_CREDENTIALS_HOST) == 0 || + strcmp(ip_address, EKS_CREDENTIALS_HOST_IPV6) == 0) { + return FLB_TRUE; // EKS container host + } + + // Loopback + // IPv4 + if (strncmp(ip_address, "127.0.0.", 8) == 0 && + strlen(ip_address) > 8 && + strlen(ip_address) <= 11) { + ip_address += 8; + if (atoi(ip_address) > 255) { + return FLB_FALSE; + } + return FLB_TRUE; + } + + // IPv6 + if (strcmp(ip_address, "::1") == 0 || + strcmp(ip_address, "0:0:0:0:0:0:0:1") == 0) { + return FLB_TRUE; + } + + return FLB_FALSE; +} + int refresh_fn_http(struct flb_aws_provider *provider) { struct flb_aws_provider_http *implementation = provider->implementation; int ret = -1; @@ -204,14 +255,22 @@ void destroy_fn_http(struct flb_aws_provider *provider) { flb_aws_client_destroy(implementation->client); } - if (implementation->host) { - flb_sds_destroy(implementation->host); + if (implementation->endpoint) { + flb_sds_destroy(implementation->endpoint); } if (implementation->path) { flb_sds_destroy(implementation->path); } + if (implementation->auth_token) { + flb_sds_destroy(implementation->auth_token); + } + + if (implementation->auth_token_file) { + flb_sds_destroy(implementation->auth_token_file); + } + flb_free(implementation); provider->implementation = NULL; } @@ -230,18 +289,20 @@ static struct flb_aws_provider_vtable http_provider_vtable = { }; struct flb_aws_provider *flb_http_provider_create(struct flb_config *config, - flb_sds_t host, + flb_sds_t endpoint, flb_sds_t path, + flb_sds_t auth_token, struct flb_aws_client_generator *generator) { + char *auth_token_file = NULL; struct flb_aws_provider_http *implementation = NULL; struct flb_aws_provider *provider = NULL; struct flb_upstream *upstream = NULL; - flb_debug("[aws_credentials] Configuring HTTP provider with %s:80%s", - host, path); + flb_debug("[aws_credentials] Configuring HTTP provider with %s", + endpoint); provider = flb_calloc(1, sizeof(struct flb_aws_provider)); @@ -263,10 +324,21 @@ struct flb_aws_provider *flb_http_provider_create(struct flb_config *config, provider->provider_vtable = &http_provider_vtable; provider->implementation = implementation; - implementation->host = host; + auth_token_file = getenv(AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE_ENV_VAR); + if (auth_token_file) { + implementation->auth_token_file = flb_sds_create(auth_token_file); + if (!implementation->auth_token_file) { + flb_aws_provider_destroy(provider); + flb_errno(); + return NULL; + } + } + + implementation->auth_token = auth_token; + implementation->endpoint = endpoint; implementation->path = path; - upstream = flb_upstream_create(config, host, 80, FLB_IO_TCP, NULL); + upstream = flb_upstream_create_url(config, endpoint, FLB_IO_TCP, NULL); if (!upstream) { flb_aws_provider_destroy(provider); @@ -289,7 +361,6 @@ struct flb_aws_provider *flb_http_provider_create(struct flb_config *config, implementation->client->provider = NULL; implementation->client->region = NULL; implementation->client->service = NULL; - implementation->client->port = 80; implementation->client->flags = 0; implementation->client->proxy = NULL; implementation->client->upstream = upstream; @@ -297,41 +368,139 @@ struct flb_aws_provider *flb_http_provider_create(struct flb_config *config, return provider; } +struct flb_aws_provider *flb_local_http_provider_create(struct flb_config *config, + flb_sds_t endpoint, + flb_sds_t auth_token, + struct + flb_aws_client_generator + *generator) +{ + int ret; + char *prot = NULL; + char *host = NULL; + char *port = NULL; + char *uri = NULL; + flb_sds_t path = NULL; + struct flb_aws_provider *p = NULL; + + ret = flb_utils_url_split(endpoint, &prot, &host, &port, &uri); + if (ret == -1) { + flb_error("[aws_credentials] HTTP Provider: invalid URL: %s", endpoint); + return NULL; + } + + if (strlen(host) == 0) { + flb_error("[aws_credentials] HTTP Provider: " + "unable to parse host from local HTTP cred provider URL"); + goto out; + } + + if (strncmp(prot, "http", 4) == 0) { + if (!flb_aws_allowed_ip(host)) { + flb_error("[aws_credentials] HTTP Provider: " + "invalid endpoint host, %s, only loopback/ecs/eks hosts are allowed", + host); + goto out; + } + } + + if (strlen(uri) > 0) { + path = flb_sds_create(uri); + if (!path) { + flb_error("[aws_credentials] HTTP Provider: " + "unable to get path for provider"); + goto out; + } + } + + p = flb_http_provider_create(config, endpoint, path, auth_token, generator); + + out: + if (prot) { + flb_free(prot); + } + if (host) { + flb_free(host); + } + if (port) { + flb_free(port); + } + if (uri) { + flb_free(uri); + } + + return p; +} + /* - * ECS Provider - * The ECS Provider is just a wrapper around the HTTP Provider - * with the ECS credentials endpoint. + * Container Provider + * The Container Provider is just a wrapper around the HTTP Provider + * with the ECS/EKS credentials endpoint. */ - struct flb_aws_provider *flb_ecs_provider_create(struct flb_config *config, + struct flb_aws_provider *flb_container_provider_create(struct flb_config *config, struct flb_aws_client_generator *generator) { - flb_sds_t host = NULL; + flb_sds_t endpoint = NULL; flb_sds_t path = NULL; + flb_sds_t token = NULL; + char *endpoint_var = NULL; char *path_var = NULL; + char *token_var = NULL; - host = flb_sds_create_len(ECS_CREDENTIALS_HOST, ECS_CREDENTIALS_HOST_LEN); - if (!host) { - flb_errno(); - return NULL; + token_var = getenv(AWS_CONTAINER_AUTHORIZATION_TOKEN_ENV_VAR); + if (token_var && strlen(token_var) > 0) { + token = flb_sds_create(token_var); + if (!token) { + flb_errno(); + return NULL; + } } - path_var = getenv(ECS_CREDENTIALS_PATH_ENV_VAR); + endpoint_var = getenv(AWS_CONTAINER_CREDENTIALS_FULL_URI_ENV_VAR); + if (endpoint_var && strlen(endpoint_var) > 0) { + endpoint = flb_sds_create(endpoint_var); + if (!endpoint) { + flb_errno(); + if (token) { + flb_sds_destroy(token); + } + return NULL; + } + + return flb_local_http_provider_create(config, endpoint, token, generator); + } + + path_var = getenv(AWS_CONTAINER_CREDENTIALS_RELATIVE_URI_ENV_VAR); if (path_var && strlen(path_var) > 0) { path = flb_sds_create(path_var); if (!path) { flb_errno(); - flb_free(host); + if (token) { + flb_sds_destroy(token); + } + return NULL; + } + + endpoint = flb_sds_create_len(ECS_CONTAINER_ENDPOINT, ECS_CONTAINER_ENDPOINT_LEN); + if (!endpoint) { + flb_errno(); + flb_sds_destroy(path); + if (token) { + flb_sds_destroy(token); + } return NULL; } - return flb_http_provider_create(config, host, path, generator); + return flb_http_provider_create(config, endpoint, path, token, generator); } else { - flb_debug("[aws_credentials] Not initializing ECS Provider because" - " %s is not set", ECS_CREDENTIALS_PATH_ENV_VAR); - flb_sds_destroy(host); + flb_debug("[aws_credentials] Not initializing HTTP Provider because" + " %s is not set", AWS_CONTAINER_CREDENTIALS_RELATIVE_URI_ENV_VAR); + if (token) { + flb_sds_destroy(token); + } return NULL; } @@ -340,22 +509,60 @@ struct flb_aws_provider *flb_http_provider_create(struct flb_config *config, static int http_credentials_request(struct flb_aws_provider_http *implementation) { + int ret; + int headers_len = 0; char *response = NULL; + char *auth_token = NULL; + size_t auth_token_size; size_t response_len; time_t expiration; struct flb_aws_credentials *creds = NULL; struct flb_aws_client *client = implementation->client; + struct flb_aws_header *headers = NULL; struct flb_http_client *c = NULL; + if (implementation->auth_token_file) { + flb_debug("[aws_credentials] loading auth token file"); + ret = flb_read_file(implementation->auth_token_file, &auth_token, + &auth_token_size); + if (ret < 0) { + flb_debug("[aws_credentials] failed to retrieve token file"); + flb_errno(); + return -1; + } + } else if (implementation->auth_token) { + auth_token_size = flb_sds_len(implementation->auth_token); + auth_token = flb_strndup(implementation->auth_token, auth_token_size); + } + + if (auth_token) { + headers = flb_calloc(1, sizeof(struct flb_aws_header)); + if (headers == NULL) { + flb_errno(); + flb_free(auth_token); + return -1; + } + + headers[0] = authorization_header; + headers[0].val = auth_token; + headers[0].val_len = auth_token_size; + headers_len = 1; + } + c = client->client_vtable->request(client, FLB_HTTP_GET, implementation->path, NULL, 0, - NULL, 0); + headers, headers_len); + + flb_free(headers); if (!c || c->resp.status != 200) { flb_debug("[aws_credentials] http credentials request failed"); if (c) { flb_http_client_destroy(c); } + if (auth_token) { + flb_free(auth_token); + } return -1; } @@ -365,6 +572,9 @@ static int http_credentials_request(struct flb_aws_provider_http creds = flb_parse_http_credentials(response, response_len, &expiration); if (!creds) { flb_http_client_destroy(c); + if (auth_token) { + flb_free(auth_token); + } return -1; } @@ -375,6 +585,11 @@ static int http_credentials_request(struct flb_aws_provider_http implementation->creds = creds; implementation->next_refresh = expiration - FLB_AWS_REFRESH_WINDOW; flb_http_client_destroy(c); + + if (auth_token) { + flb_free(auth_token); + } + return 0; } diff --git a/src/aws/flb_aws_util.c b/src/aws/flb_aws_util.c index 48cf8b3d89e..0c4f6ffcb87 100644 --- a/src/aws/flb_aws_util.c +++ b/src/aws/flb_aws_util.c @@ -343,6 +343,12 @@ struct flb_http_client *request_do(struct flb_aws_client *aws_client, goto error; } + /* Remove port from host header, EKS Pod Identities breaks without this */ + ret = flb_http_strip_port_from_host(c); + if (ret != 0) { + flb_warn("[aws_http_client] failed to remove port from Host header"); + } + /* Increase the maximum HTTP response buffer size to fit large responses from AWS services */ ret = flb_http_buffer_size(c, FLB_MAX_AWS_RESP_BUFFER_SIZE); if (ret != 0) { diff --git a/tests/internal/aws_credentials_http.c b/tests/internal/aws_credentials_http.c index 55912da3a60..971dc691d7f 100644 --- a/tests/internal/aws_credentials_http.c +++ b/tests/internal/aws_credentials_http.c @@ -16,6 +16,10 @@ #define SECRET_KEY_HTTP "http_skid" #define TOKEN_HTTP "http_token" +#define TOKEN_FILE_ENV_VAR "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE" +#define HTTP_TOKEN_FILE FLB_TESTS_DATA_PATH "/data/aws_credentials/\ +http_token_file.txt" + #define HTTP_CREDENTIALS_RESPONSE "{\n\ \"AccessKeyId\": \"http_akid\",\n\ \"Expiration\": \"2025-10-24T23:00:23Z\",\n\ @@ -68,6 +72,38 @@ struct flb_http_client *request_happy_case(struct flb_aws_client *aws_client, return c; } +struct flb_http_client *request_auth_token_case(struct flb_aws_client *aws_client, + int method, const char *uri, + struct flb_aws_header *dynamic_headers, + size_t dynamic_headers_len) +{ + struct flb_http_client *c = NULL; + + TEST_CHECK(method == FLB_HTTP_GET); + + TEST_CHECK(strstr(uri, "auth-token") != NULL); + + TEST_CHECK(dynamic_headers_len == 1); + + TEST_CHECK(strstr(dynamic_headers[0].key, "Authorization") != NULL); + + TEST_CHECK(strstr(dynamic_headers[0].val, "this-is-a-fake-http-jwt") != NULL); + + /* create an http client so that we can set the response */ + c = flb_calloc(1, sizeof(struct flb_http_client)); + if (!c) { + flb_errno(); + return NULL; + } + mk_list_init(&c->headers); + + c->resp.status = 200; + c->resp.payload = HTTP_CREDENTIALS_RESPONSE; + c->resp.payload_size = strlen(HTTP_CREDENTIALS_RESPONSE); + + return c; +} + /* unexpected output test- see description for HTTP_RESPONSE_MALFORMED */ struct flb_http_client *request_malformed(struct flb_aws_client *aws_client, int method, const char *uri) @@ -130,6 +166,10 @@ struct flb_http_client *test_http_client_request(struct flb_aws_client *aws_clie */ if (strstr(uri, "happy-case") != NULL) { return request_happy_case(aws_client, method, uri); + } else if (strstr(uri, "auth-token") != NULL) { + return request_auth_token_case(aws_client, method, uri, + dynamic_headers, + dynamic_headers_len); } else if (strstr(uri, "error-case") != NULL) { return request_error_case(aws_client, method, uri); } else if (strstr(uri, "malformed") != NULL) { @@ -169,14 +209,14 @@ struct flb_aws_client_generator *generator_in_test() return &test_generator; } -/* http and ecs providers */ +/* http and container providers */ static void test_http_provider() { struct flb_aws_provider *provider; struct flb_aws_credentials *creds; int ret; struct flb_config *config; - flb_sds_t host; + flb_sds_t endpoint; flb_sds_t path; g_request_count = 0; @@ -187,8 +227,8 @@ static void test_http_provider() return; } - host = flb_sds_create("127.0.0.1"); - if (!host) { + endpoint = flb_sds_create("http://127.0.0.1"); + if (!endpoint) { flb_errno(); flb_config_exit(config); return; @@ -200,7 +240,7 @@ static void test_http_provider() return; } - provider = flb_http_provider_create(config, host, path, + provider = flb_http_provider_create(config, endpoint, path, NULL, generator_in_test()); if (!provider) { @@ -249,13 +289,181 @@ static void test_http_provider() flb_config_exit(config); } +static void test_http_provider_auth_token() +{ + struct flb_aws_provider *provider; + struct flb_aws_credentials *creds; + int ret; + struct flb_config *config; + flb_sds_t endpoint; + flb_sds_t path; + flb_sds_t auth_token; + + g_request_count = 0; + + config = flb_config_init(); + + if (config == NULL) { + return; + } + + endpoint = flb_sds_create("http://127.0.0.1"); + if (!endpoint) { + flb_errno(); + flb_config_exit(config); + return; + } + path = flb_sds_create("/auth-token"); + if (!path) { + flb_errno(); + flb_config_exit(config); + return; + } + auth_token = flb_sds_create("this-is-a-fake-http-jwt"); + if (!auth_token) { + flb_errno(); + flb_config_exit(config); + return; + } + + provider = flb_http_provider_create(config, endpoint, path, auth_token, + generator_in_test()); + + if (!provider) { + flb_errno(); + flb_config_exit(config); + return; + } + + /* repeated calls to get credentials should return the same set */ + creds = provider->provider_vtable->get_credentials(provider); + if (!creds) { + flb_errno(); + flb_config_exit(config); + return; + } + TEST_CHECK(strcmp(ACCESS_KEY_HTTP, creds->access_key_id) == 0); + TEST_CHECK(strcmp(SECRET_KEY_HTTP, creds->secret_access_key) == 0); + TEST_CHECK(strcmp(TOKEN_HTTP, creds->session_token) == 0); + + flb_aws_credentials_destroy(creds); + + creds = provider->provider_vtable->get_credentials(provider); + if (!creds) { + flb_errno(); + flb_config_exit(config); + return; + } + TEST_CHECK(strcmp(ACCESS_KEY_HTTP, creds->access_key_id) == 0); + TEST_CHECK(strcmp(SECRET_KEY_HTTP, creds->secret_access_key) == 0); + TEST_CHECK(strcmp(TOKEN_HTTP, creds->session_token) == 0); + + flb_aws_credentials_destroy(creds); + + /* refresh should return 0 (success) */ + ret = provider->provider_vtable->refresh(provider); + TEST_CHECK(ret == 0); + + /* + * Request count should be 2: + * - One for the first call to get_credentials (2nd should hit cred cache) + * - One for the call to refresh + */ + TEST_CHECK(g_request_count == 2); + + flb_aws_provider_destroy(provider); + flb_config_exit(config); +} + +static void test_local_http_provider() +{ + struct flb_aws_provider *provider; + struct flb_aws_credentials *creds; + int ret; + struct flb_config *config; + flb_sds_t endpoint; + flb_sds_t auth_token; + + g_request_count = 0; + + config = flb_config_init(); + + if (config == NULL) { + return; + } + + endpoint = flb_sds_create("http://127.0.0.1/auth-token"); + if (!endpoint) { + flb_errno(); + flb_config_exit(config); + return; + } + /* tests that the token file takes precedence */ + auth_token = flb_sds_create("this-is-the-wrong-jwt"); + if (!auth_token) { + flb_errno(); + flb_config_exit(config); + return; + } + setenv(TOKEN_FILE_ENV_VAR, HTTP_TOKEN_FILE, 1); + + provider = flb_local_http_provider_create(config, endpoint, auth_token, + generator_in_test()); + + if (!provider) { + flb_errno(); + flb_config_exit(config); + return; + } + + /* repeated calls to get credentials should return the same set */ + creds = provider->provider_vtable->get_credentials(provider); + if (!creds) { + flb_errno(); + flb_config_exit(config); + return; + } + TEST_CHECK(strcmp(ACCESS_KEY_HTTP, creds->access_key_id) == 0); + TEST_CHECK(strcmp(SECRET_KEY_HTTP, creds->secret_access_key) == 0); + TEST_CHECK(strcmp(TOKEN_HTTP, creds->session_token) == 0); + + flb_aws_credentials_destroy(creds); + + creds = provider->provider_vtable->get_credentials(provider); + if (!creds) { + flb_errno(); + flb_config_exit(config); + return; + } + TEST_CHECK(strcmp(ACCESS_KEY_HTTP, creds->access_key_id) == 0); + TEST_CHECK(strcmp(SECRET_KEY_HTTP, creds->secret_access_key) == 0); + TEST_CHECK(strcmp(TOKEN_HTTP, creds->session_token) == 0); + + flb_aws_credentials_destroy(creds); + + /* refresh should return 0 (success) */ + ret = provider->provider_vtable->refresh(provider); + TEST_CHECK(ret == 0); + + /* + * Request count should be 2: + * - One for the first call to get_credentials (2nd should hit cred cache) + * - One for the call to refresh + */ + TEST_CHECK(g_request_count == 2); + + unsetenv(TOKEN_FILE_ENV_VAR); + flb_aws_provider_destroy(provider); + flb_config_exit(config); +} + static void test_http_provider_error_case() { struct flb_aws_provider *provider; struct flb_aws_credentials *creds; int ret; struct flb_config *config; - flb_sds_t host; + flb_sds_t endpoint; flb_sds_t path; g_request_count = 0; @@ -266,8 +474,8 @@ static void test_http_provider_error_case() return; } - host = flb_sds_create("127.0.0.1"); - if (!host) { + endpoint = flb_sds_create("http://127.0.0.1"); + if (!endpoint) { flb_errno(); flb_config_exit(config); return; @@ -276,10 +484,12 @@ static void test_http_provider_error_case() if (!path) { flb_errno(); flb_config_exit(config); - return; + return; if (path) { + flb_sds_destroy(path); + } } - provider = flb_http_provider_create(config, host, path, + provider = flb_http_provider_create(config, endpoint, path, NULL, generator_in_test()); if (!provider) { @@ -316,7 +526,7 @@ static void test_http_provider_malformed_response() struct flb_aws_credentials *creds; int ret; struct flb_config *config; - flb_sds_t host; + flb_sds_t endpoint; flb_sds_t path; g_request_count = 0; @@ -329,8 +539,8 @@ static void test_http_provider_malformed_response() mk_list_init(&config->upstreams); - host = flb_sds_create("127.0.0.1"); - if (!host) { + endpoint = flb_sds_create("http://127.0.0.1"); + if (!endpoint) { flb_errno(); flb_config_exit(config); return; @@ -342,7 +552,7 @@ static void test_http_provider_malformed_response() return; } - provider = flb_http_provider_create(config, host, path, + provider = flb_http_provider_create(config, endpoint, path, NULL, generator_in_test()); if (!provider) { @@ -375,6 +585,8 @@ static void test_http_provider_malformed_response() TEST_LIST = { { "test_http_provider" , test_http_provider}, + { "test_http_provider_auth_token" , test_http_provider_auth_token}, + { "test_local_http_provider" , test_local_http_provider}, { "test_http_provider_error_case" , test_http_provider_error_case}, { "test_http_provider_malformed_response" , test_http_provider_malformed_response}, diff --git a/tests/internal/data/aws_credentials/http_token_file.txt b/tests/internal/data/aws_credentials/http_token_file.txt new file mode 100644 index 00000000000..a6884e3eacf --- /dev/null +++ b/tests/internal/data/aws_credentials/http_token_file.txt @@ -0,0 +1 @@ +this-is-a-fake-http-jwt \ No newline at end of file From 1b809e25b0f32e9a8a4d0f11983f9989b6dd9088 Mon Sep 17 00:00:00 2001 From: Andrew Titmuss Date: Wed, 26 Jun 2024 22:10:40 +1000 Subject: [PATCH 2/4] utils: fix parsing of urls with IPv6 literals This patch fixes the handling of IPv6 literals, which would fail to parse after not finding balanced `[]` characters. This happened because it searched for the first `:` before copying the host portion, so (assuming it didn't return NULL), the endpoint `[fd00:ec2::23]` would become `fd00`. I've added some tests around this case, but I wouldn't describe the tests as in-depth. This patch is needed for EKS Pod Identities to work on IPv6 clusters. Signed-off-by: Andrew Titmuss --- src/flb_utils.c | 23 +++++++++++++++++++++-- tests/internal/utils.c | 4 ++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/flb_utils.c b/src/flb_utils.c index 2f445980d84..6cc341160be 100644 --- a/src/flb_utils.c +++ b/src/flb_utils.c @@ -1036,6 +1036,8 @@ int flb_utils_url_split(const char *in_url, char **out_protocol, char *p; char *tmp; char *sep; + char *v6_start; + char *v6_end; /* Protocol */ p = strstr(in_url, "://"); @@ -1057,9 +1059,26 @@ int flb_utils_url_split(const char *in_url, char **out_protocol, /* Check for first '/' */ sep = strchr(p, '/'); - tmp = strchr(p, ':'); + v6_start = strchr(p, '['); + v6_end = strchr(p, ']'); + + /* + * Validate port separator is found before the first slash, + * If IPv6, ensure it is after the ']', + * but only if before the first slash + */ + if (v6_start && v6_end) { + if (sep && v6_end > sep) { + tmp = strchr(p, ':'); + } + else { + tmp = strchr(v6_end, ':'); + } + } + else { + tmp = strchr(p, ':'); + } - /* Validate port separator is found before the first slash */ if (sep && tmp) { if (tmp > sep) { tmp = NULL; diff --git a/tests/internal/utils.c b/tests/internal/utils.c index bcb9053d86f..73591f634ef 100644 --- a/tests/internal/utils.c +++ b/tests/internal/utils.c @@ -35,6 +35,10 @@ struct url_check url_checks[] = { {0, "https://fluentbit.io:1234", "https", "fluentbit.io", "1234", "/"}, {0, "https://fluentbit.io:1234/", "https", "fluentbit.io", "1234", "/"}, {0, "https://fluentbit.io:1234/v", "https", "fluentbit.io", "1234", "/v"}, + {0, "http://[fd00:ec2::23]", "http", "fd00:ec2::23", "80", "/"}, + {0, "http://[fd00:ec2::23]:81", "http", "fd00:ec2::23", "81", "/"}, + {0, "http://[fd00:ec2::23]:81/something", "http", "fd00:ec2::23", "81", "/something"}, + {0, "http://[fd00:ec2::23]/something", "http", "fd00:ec2::23", "80", "/something"}, {-1, "://", NULL, NULL, NULL, NULL}, }; From 2e36b055c622496b02e42dbe438524e37271b7a3 Mon Sep 17 00:00:00 2001 From: Andrew Titmuss Date: Sat, 29 Jun 2024 11:51:33 +1000 Subject: [PATCH 3/4] aws_util: add error handler for code and message This error handler is for JSON APIs that respond with a Code and Message field in their error responses. It is originally from PettitWesley/fluent-bit@7a2c1a8 Signed-off-by: Andrew Titmuss --- include/fluent-bit/flb_aws_util.h | 12 +++++++++-- src/aws/flb_aws_util.c | 35 +++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/include/fluent-bit/flb_aws_util.h b/include/fluent-bit/flb_aws_util.h index ac978e6e23c..560f3a54208 100644 --- a/include/fluent-bit/flb_aws_util.h +++ b/include/fluent-bit/flb_aws_util.h @@ -144,13 +144,21 @@ flb_sds_t flb_aws_xml_error(char *response, size_t response_len); flb_sds_t flb_aws_error(char *response, size_t response_len); /* - * Similar to 'flb_aws_error', except it prints the JSON error type and message - * to the user in a error log. + * Similar to 'flb_aws_error', except it prints the JSON error __type and message + * field values to the user in a error log. * 'api' is the name of the API that was called; this is used in the error log. */ void flb_aws_print_error(char *response, size_t response_len, char *api, struct flb_output_instance *ins); +/* + * Similar to 'flb_aws_error', except it prints the JSON error Code and Message + * field values to the user in a error log. + * 'api' is the name of the API that was called; this is used in the error log. + */ +void flb_aws_print_error_code(char *response, size_t response_len, + char *api); + /* Similar to 'flb_aws_print_error', but for APIs that return XML */ void flb_aws_print_xml_error(char *response, size_t response_len, char *api, struct flb_output_instance *ins); diff --git a/src/aws/flb_aws_util.c b/src/aws/flb_aws_util.c index 0c4f6ffcb87..d1454bf51d7 100644 --- a/src/aws/flb_aws_util.c +++ b/src/aws/flb_aws_util.c @@ -579,6 +579,10 @@ flb_sds_t flb_aws_xml_get_val(char *response, size_t response_len, char *tag, ch return val; } +/* + * Error parsing for json APIs that respond with an + * __type and message fields for error responses. + */ void flb_aws_print_error(char *response, size_t response_len, char *api, struct flb_output_instance *ins) { @@ -606,6 +610,37 @@ void flb_aws_print_error(char *response, size_t response_len, flb_sds_destroy(error); } +/* + * Error parsing for json APIs that respond with a + * Code and Message fields for error responses. + */ +void flb_aws_print_error_code(char *response, size_t response_len, + char *api) +{ + flb_sds_t error; + flb_sds_t message; + + error = flb_json_get_val(response, response_len, "Code"); + if (!error) { + /* error can not be parsed, print raw response */ + flb_warn("%s: Raw response: %s", api, response); + return; + } + + message = flb_json_get_val(response, response_len, "Message"); + if (!message) { + /* just print the error */ + flb_error("%s API responded with code='%s'", api, error); + } + else { + flb_error("%s API responded with code='%s', message='%s'", + api, error, message); + flb_sds_destroy(message); + } + + flb_sds_destroy(error); +} + /* parses AWS JSON API error responses and returns the value of the __type field */ flb_sds_t flb_aws_error(char *response, size_t response_len) { From 041c314b73a19348e4c187e9fbcf25a4bfec7e45 Mon Sep 17 00:00:00 2001 From: Andrew Titmuss Date: Sat, 29 Jun 2024 15:01:56 +1000 Subject: [PATCH 4/4] aws_credentials_http: improved error cases, rewrite tests This patch fixes the following: 1. ensures malformed tokens (containing `\r\n`) are treated as invalid 2. fixes allowing any https host, which previously went through the same checks as http hosts 3. fixes a memory leak when provider initialisation fails 4. logs errors from the HTTP agent 5. rewrites the tests using the cases from PettitWesley/fluent-bit@195db30 Signed-off-by: Andrew Titmuss --- include/fluent-bit/flb_aws_credentials.h | 21 + src/aws/flb_aws_credentials_http.c | 132 ++-- tests/internal/aws_credentials_http.c | 842 +++++++++++------------ 3 files changed, 500 insertions(+), 495 deletions(-) diff --git a/include/fluent-bit/flb_aws_credentials.h b/include/fluent-bit/flb_aws_credentials.h index adde77bf039..05ad0f6c743 100644 --- a/include/fluent-bit/flb_aws_credentials.h +++ b/include/fluent-bit/flb_aws_credentials.h @@ -359,6 +359,27 @@ int try_lock_provider(struct flb_aws_provider *provider); void unlock_provider(struct flb_aws_provider *provider); +/* + * HTTP Credentials Provider - retrieve credentials from a local http server + * Used to implement the Container Credentials provider. + * Equivalent to: + * https://github.com/aws/aws-sdk-go/tree/master/aws/credentials/endpointcreds + */ + +struct flb_aws_provider_http { + struct flb_aws_credentials *creds; + time_t next_refresh; + + struct flb_aws_client *client; + + /* Endpoint to request credentials */ + flb_sds_t endpoint; + flb_sds_t path; + + /* Auth token */ + flb_sds_t auth_token; + flb_sds_t auth_token_file; +}; #endif #endif /* FLB_HAVE_AWS */ diff --git a/src/aws/flb_aws_credentials_http.c b/src/aws/flb_aws_credentials_http.c index 1c3512a5a1c..d3c647b5bcd 100644 --- a/src/aws/flb_aws_credentials_http.c +++ b/src/aws/flb_aws_credentials_http.c @@ -63,29 +63,6 @@ static struct flb_aws_header authorization_header = { .val_len = 0, }; -/* - * HTTP Credentials Provider - retrieve credentials from a local http server - * Used to implement the Container Credentials provider. - * Equivalent to: - * https://github.com/aws/aws-sdk-go/tree/master/aws/credentials/endpointcreds - */ - -struct flb_aws_provider_http { - struct flb_aws_credentials *creds; - time_t next_refresh; - - struct flb_aws_client *client; - - /* Endpoint to request credentials */ - flb_sds_t endpoint; - flb_sds_t path; - - /* Auth token */ - flb_sds_t auth_token; - flb_sds_t auth_token_file; -}; - - struct flb_aws_credentials *get_credentials_fn_http(struct flb_aws_provider *provider) { @@ -158,7 +135,19 @@ struct flb_aws_credentials *get_credentials_fn_http(struct flb_aws_provider return NULL; } -static int flb_aws_allowed_ip(char *ip_address) { +/* + * If the resolved URI’s scheme is HTTPS, its hostname may be used in the request. + * Otherwise, implementations MUST fail to resolve when the URI hostname + * does not satisfy any of the following conditions: + * is within the loopback CIDR (IPv4 127.0.0.0/8, IPv6 ::1/128) + * is the ECS container host 169.254.170.2 + * is the EKS Pod Identity Agent (IPv4 169.254.170.23, IPv6 fd00:ec2::23) + */ +static int flb_aws_allowed_ip(char *prot, char *ip_address) { + if (strncmp(prot, "https", 5) == 0) { + return FLB_TRUE; + } + if (strcmp(ip_address, ECS_CREDENTIALS_HOST) == 0) { return FLB_TRUE; // ECS container host } else if (strcmp(ip_address, EKS_CREDENTIALS_HOST) == 0 || @@ -301,6 +290,15 @@ struct flb_aws_provider *flb_http_provider_create(struct flb_config *config, struct flb_aws_provider *provider = NULL; struct flb_upstream *upstream = NULL; + if (auth_token) { + if (strstr(auth_token, "\\r\\n")) { + flb_error("[aws_credentials] unable to configure HTTP provider: " + "auth token contains invalid characters (\\r\\n)"); + flb_errno(); + return NULL; + } + } + flb_debug("[aws_credentials] Configuring HTTP provider with %s", endpoint); @@ -395,13 +393,18 @@ struct flb_aws_provider *flb_local_http_provider_create(struct flb_config *confi goto out; } - if (strncmp(prot, "http", 4) == 0) { - if (!flb_aws_allowed_ip(host)) { - flb_error("[aws_credentials] HTTP Provider: " - "invalid endpoint host, %s, only loopback/ecs/eks hosts are allowed", - host); - goto out; - } + if (!flb_aws_allowed_ip(prot, host)) { + flb_error("[aws_credentials] HTTP Provider: " + "invalid endpoint %s, only https or loopback/ecs/eks hosts are allowed", + endpoint); + goto out; + } + + ret = atoi(port); + if (ret == 0) { + flb_error("[aws_credentials] HTTP Provider: " + "invalid endpoint port %s", port); + goto out; } if (strlen(uri) > 0) { @@ -449,6 +452,7 @@ struct flb_aws_provider *flb_local_http_provider_create(struct flb_config *confi char *endpoint_var = NULL; char *path_var = NULL; char *token_var = NULL; + struct flb_aws_provider *provider; token_var = getenv(AWS_CONTAINER_AUTHORIZATION_TOKEN_ENV_VAR); if (token_var && strlen(token_var) > 0) { @@ -464,13 +468,16 @@ struct flb_aws_provider *flb_local_http_provider_create(struct flb_config *confi endpoint = flb_sds_create(endpoint_var); if (!endpoint) { flb_errno(); - if (token) { - flb_sds_destroy(token); - } - return NULL; + goto err; + } + + provider = flb_local_http_provider_create(config, endpoint, token, generator); + if (!provider) { + flb_errno(); + goto err; } - return flb_local_http_provider_create(config, endpoint, token, generator); + return provider; } path_var = getenv(AWS_CONTAINER_CREDENTIALS_RELATIVE_URI_ENV_VAR); @@ -478,32 +485,42 @@ struct flb_aws_provider *flb_local_http_provider_create(struct flb_config *confi path = flb_sds_create(path_var); if (!path) { flb_errno(); - if (token) { - flb_sds_destroy(token); - } - return NULL; + goto err; } endpoint = flb_sds_create_len(ECS_CONTAINER_ENDPOINT, ECS_CONTAINER_ENDPOINT_LEN); if (!endpoint) { flb_errno(); - flb_sds_destroy(path); - if (token) { - flb_sds_destroy(token); - } - return NULL; + goto err; } - return flb_http_provider_create(config, endpoint, path, token, generator); + provider = flb_http_provider_create(config, endpoint, path, token, generator); + if (!provider) { + flb_errno(); + goto err; + } } else { flb_debug("[aws_credentials] Not initializing HTTP Provider because" " %s is not set", AWS_CONTAINER_CREDENTIALS_RELATIVE_URI_ENV_VAR); - if (token) { - flb_sds_destroy(token); - } - return NULL; + goto err; + } + + return provider; + + err: + if (token) { + flb_sds_destroy(token); + } + + if (path) { + flb_sds_destroy(path); } + if (endpoint) { + flb_sds_destroy(endpoint); + } + + return NULL; } static int http_credentials_request(struct flb_aws_provider_http @@ -530,6 +547,13 @@ static int http_credentials_request(struct flb_aws_provider_http flb_errno(); return -1; } + if (strstr(auth_token, "\\r\\n")) { + flb_error("[aws_credentials] unable to retreive credentials: " + "auth token file contains invalid characters (\\r\\n)"); + flb_errno(); + flb_free(auth_token); + return -1; + } } else if (implementation->auth_token) { auth_token_size = flb_sds_len(implementation->auth_token); auth_token = flb_strndup(implementation->auth_token, auth_token_size); @@ -558,6 +582,10 @@ static int http_credentials_request(struct flb_aws_provider_http if (!c || c->resp.status != 200) { flb_debug("[aws_credentials] http credentials request failed"); if (c) { + if (c->resp.payload_size > 0) { + flb_aws_print_error_code(c->resp.payload, c->resp.payload_size, + "ContainerCredentialsLocalServer"); + } flb_http_client_destroy(c); } if (auth_token) { @@ -747,12 +775,12 @@ struct flb_aws_credentials *flb_parse_json_credentials(char *response, goto error; } *expiration = flb_aws_cred_expiration(tmp); - flb_sds_destroy(tmp); if (*expiration < 0) { flb_warn("[aws_credentials] '%s' was invalid or " "could not be parsed. Disabling auto-refresh of " - "credentials.", AWS_CREDENTIAL_RESPONSE_EXPIRATION); + "credentials.", tmp); } + flb_sds_destroy(tmp); } } diff --git a/tests/internal/aws_credentials_http.c b/tests/internal/aws_credentials_http.c index 971dc691d7f..e07eb19048e 100644 --- a/tests/internal/aws_credentials_http.c +++ b/tests/internal/aws_credentials_http.c @@ -12,28 +12,13 @@ #include "flb_tests_internal.h" -#define ACCESS_KEY_HTTP "http_akid" -#define SECRET_KEY_HTTP "http_skid" -#define TOKEN_HTTP "http_token" +#include "../include/aws_client_mock.h" +#include "../include/aws_client_mock.c" -#define TOKEN_FILE_ENV_VAR "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE" -#define HTTP_TOKEN_FILE FLB_TESTS_DATA_PATH "/data/aws_credentials/\ -http_token_file.txt" +#include "aws_credentials_test_internal.h" -#define HTTP_CREDENTIALS_RESPONSE "{\n\ - \"AccessKeyId\": \"http_akid\",\n\ - \"Expiration\": \"2025-10-24T23:00:23Z\",\n\ - \"RoleArn\": \"TASK_ROLE_ARN\",\n\ - \"SecretAccessKey\": \"http_skid\",\n\ - \"Token\": \"http_token\"\n\ -}" +#define HTTP_TOKEN_FILE AWS_TEST_DATA_PATH("http_token_file.txt") -/* - * Unexpected/invalid HTTP response. The goal of this is not to test anything - * that might happen in production, but rather to test the error handling - * code for the providers. This helps ensure all code paths are tested and - * the error handling code does not introduce memory leaks. - */ #define HTTP_RESPONSE_MALFORMED "{\n\ \"AccessKeyId\": \"http_akid\",\n\ \"partially-correct\": \"json\",\n\ @@ -41,236 +26,230 @@ http_token_file.txt" \"but incomplete\": \"and not terminated with a closing brace\",\n\ \"Token\": \"http_token\"" - /* - * Global Variable that allows us to check the number of calls - * made in each test + * Setup test & Initialize test environment */ -int g_request_count; - -struct flb_http_client *request_happy_case(struct flb_aws_client *aws_client, - int method, const char *uri) -{ - struct flb_http_client *c = NULL; +void setup_test(struct flb_aws_client_mock_request_chain *request_chain, + struct flb_aws_provider **out_provider, struct flb_config **out_config) { + struct flb_aws_provider *provider; + struct flb_config *config; - TEST_CHECK(method == FLB_HTTP_GET); + /* Initialize test environment */ + config = flb_config_init(); + TEST_ASSERT(config != NULL); - TEST_CHECK(strstr(uri, "happy-case") != NULL); + flb_aws_client_mock_configure_generator(request_chain); - /* create an http client so that we can set the response */ - c = flb_calloc(1, sizeof(struct flb_http_client)); - if (!c) { - flb_errno(); - return NULL; - } - mk_list_init(&c->headers); + /* Init provider */ + provider = flb_container_provider_create(config, flb_aws_client_get_mock_generator()); + TEST_ASSERT(provider != NULL); - c->resp.status = 200; - c->resp.payload = HTTP_CREDENTIALS_RESPONSE; - c->resp.payload_size = strlen(HTTP_CREDENTIALS_RESPONSE); - - return c; + *out_config = config; + *out_provider = provider; } -struct flb_http_client *request_auth_token_case(struct flb_aws_client *aws_client, - int method, const char *uri, - struct flb_aws_header *dynamic_headers, - size_t dynamic_headers_len) -{ - struct flb_http_client *c = NULL; - - TEST_CHECK(method == FLB_HTTP_GET); - - TEST_CHECK(strstr(uri, "auth-token") != NULL); - - TEST_CHECK(dynamic_headers_len == 1); - - TEST_CHECK(strstr(dynamic_headers[0].key, "Authorization") != NULL); - - TEST_CHECK(strstr(dynamic_headers[0].val, "this-is-a-fake-http-jwt") != NULL); - - /* create an http client so that we can set the response */ - c = flb_calloc(1, sizeof(struct flb_http_client)); - if (!c) { - flb_errno(); - return NULL; +/* Test clean up */ +void cleanup_test(struct flb_aws_provider *provider, struct flb_config *config) { + flb_aws_client_mock_destroy_generator(); + if (provider != NULL) { + ((struct flb_aws_provider_http *) (provider->implementation))->client = NULL; + flb_aws_provider_destroy(provider); + provider = NULL; + } + if (config != NULL) { + flb_config_exit(config); + config = NULL; } - mk_list_init(&c->headers); - - c->resp.status = 200; - c->resp.payload = HTTP_CREDENTIALS_RESPONSE; - c->resp.payload_size = strlen(HTTP_CREDENTIALS_RESPONSE); - - return c; } -/* unexpected output test- see description for HTTP_RESPONSE_MALFORMED */ -struct flb_http_client *request_malformed(struct flb_aws_client *aws_client, - int method, const char *uri) +/* + * Unexpected/invalid HTTP response. The goal of this is not to test anything + * that might happen in production, but rather to test the error handling + * code for the providers. This helps ensure all code paths are tested and + * the error handling code does not introduce memory leaks. + */ +static void test_http_provider_malformed_response() { - struct flb_http_client *c = NULL; + struct flb_aws_provider *provider; + struct flb_aws_credentials *creds; + struct flb_config *config; + int ret; - TEST_CHECK(method == FLB_HTTP_GET); + setenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/iam_credentials/pod1", 1); + + setup_test(FLB_AWS_CLIENT_MOCK( + response( + expect(URI, "/iam_credentials/pod1"), + expect(METHOD, FLB_HTTP_GET), + expect(HEADER_COUNT, 0), + set(STATUS, 200), + set(PAYLOAD, HTTP_RESPONSE_MALFORMED), + set(PAYLOAD_SIZE, strlen(HTTP_RESPONSE_MALFORMED)) + ), + response( + expect(URI, "/iam_credentials/pod1"), + expect(METHOD, FLB_HTTP_GET), + expect(HEADER_COUNT, 0), + set(STATUS, 200), + set(PAYLOAD, HTTP_RESPONSE_MALFORMED), + set(PAYLOAD_SIZE, strlen(HTTP_RESPONSE_MALFORMED)) + ), + response( + expect(URI, "/iam_credentials/pod1"), + expect(METHOD, FLB_HTTP_GET), + set(STATUS, 200), + set(PAYLOAD, HTTP_RESPONSE_MALFORMED), + set(PAYLOAD_SIZE, strlen(HTTP_RESPONSE_MALFORMED)) + ) + ), &provider, &config); + + flb_time_msleep(1000); - TEST_CHECK(strstr(uri, "malformed") != NULL); + /* get_credentials will fail */ + creds = provider->provider_vtable->get_credentials(provider); + TEST_CHECK(creds == NULL); - /* create an http client so that we can set the response */ - c = flb_calloc(1, sizeof(struct flb_http_client)); - if (!c) { - flb_errno(); - return NULL; - } - mk_list_init(&c->headers); + creds = provider->provider_vtable->get_credentials(provider); + TEST_CHECK(creds == NULL); - c->resp.status = 200; - c->resp.payload = HTTP_RESPONSE_MALFORMED; - c->resp.payload_size = strlen(HTTP_RESPONSE_MALFORMED); + /* refresh should return -1 (failure) */ + ret = provider->provider_vtable->refresh(provider); + TEST_CHECK(ret < 0); + + /* + * Request count should be 3: + * - Each call to get_credentials and refresh invokes the client's + * request method and returns a request failure. + */ + TEST_CHECK(flb_aws_client_mock_generator_count_unused_requests() == 0); - return c; + cleanup_test(provider, config); } -struct flb_http_client *request_error_case(struct flb_aws_client *aws_client, - int method, const char *uri) +static void test_http_provider_ecs_case() { - struct flb_http_client *c = NULL; - - TEST_CHECK(method == FLB_HTTP_GET); - - TEST_CHECK(strstr(uri, "error-case") != NULL); - - /* create an http client so that we can set the response */ - c = flb_calloc(1, sizeof(struct flb_http_client)); - if (!c) { - flb_errno(); - return NULL; - } - mk_list_init(&c->headers); + struct flb_aws_provider *provider; + struct flb_aws_credentials *creds; + struct flb_config *config; + int ret; - c->resp.status = 400; - c->resp.payload = NULL; - c->resp.payload_size = 0; + setenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/iam_credentials/pod1", 1); + + setup_test(FLB_AWS_CLIENT_MOCK( + response( + expect(URI, "/iam_credentials/pod1"), + expect(METHOD, FLB_HTTP_GET), + expect(HEADER_COUNT, 0), + set(STATUS, 200), + set(PAYLOAD, "{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-16T18:29:09Z\",\n" + " \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"XACCESSEKSXXX\",\n \"SecretAccessKey\"" + " : \"XSECRETEKSXXXXXXXXXXXXXX\",\n \"Token\" : \"XTOKENEKSXXXXXXXXXXXXXXX==\",\n" + " \"Expiration\" : \"3021-09-17T00:41:00Z\"\n}"), + set(PAYLOAD_SIZE, 257) + ), + response( + expect(URI, "/iam_credentials/pod1"), + expect(METHOD, FLB_HTTP_GET), + set(STATUS, 200), + set(PAYLOAD, "{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-16T18:29:09Z\",\n" + " \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"YACCESSEKSXXX\",\n \"SecretAccessKey\"" + " : \"YSECRETEKSXXXXXXXXXXXXXX\",\n \"Token\" : \"YTOKENEKSXXXXXXXXXXXXXXX==\",\n" + " \"Expiration\" : \"3021-09-17T00:41:00Z\"\n}"), // Expires Year 3021 + set(PAYLOAD_SIZE, 257) + ) + ), &provider, &config); + + flb_time_msleep(1000); + + /* Repeated calls to get credentials should return the same set */ + creds = provider->provider_vtable->get_credentials(provider); + TEST_ASSERT(creds != NULL); + TEST_CHECK(strcmp("XACCESSEKSXXX", creds->access_key_id) == 0); + TEST_CHECK(strcmp("XSECRETEKSXXXXXXXXXXXXXX", creds->secret_access_key) == 0); + TEST_CHECK(strcmp("XTOKENEKSXXXXXXXXXXXXXXX==", creds->session_token) == 0); - return c; -} + flb_aws_credentials_destroy(creds); -/* test/mock version of the flb_aws_client request function */ -struct flb_http_client *test_http_client_request(struct flb_aws_client *aws_client, - int method, const char *uri, - const char *body, size_t body_len, - struct flb_aws_header *dynamic_headers, - size_t dynamic_headers_len) -{ - g_request_count++; - /* - * route to the correct test case fn using the uri - */ - if (strstr(uri, "happy-case") != NULL) { - return request_happy_case(aws_client, method, uri); - } else if (strstr(uri, "auth-token") != NULL) { - return request_auth_token_case(aws_client, method, uri, - dynamic_headers, - dynamic_headers_len); - } else if (strstr(uri, "error-case") != NULL) { - return request_error_case(aws_client, method, uri); - } else if (strstr(uri, "malformed") != NULL) { - return request_malformed(aws_client, method, uri); - } + /* Retrieve from cache */ + creds = provider->provider_vtable->get_credentials(provider); + TEST_ASSERT(creds != NULL); + TEST_CHECK(strcmp("XACCESSEKSXXX", creds->access_key_id) == 0); + TEST_CHECK(strcmp("XSECRETEKSXXXXXXXXXXXXXX", creds->secret_access_key) == 0); + TEST_CHECK(strcmp("XTOKENEKSXXXXXXXXXXXXXXX==", creds->session_token) == 0); - /* uri should match one of the above conditions */ - flb_errno(); - return NULL; + flb_aws_credentials_destroy(creds); -} + /* refresh should return 0 (success) */ + ret = provider->provider_vtable->refresh(provider); + TEST_CHECK(ret == 0); -/* Test/mock flb_aws_client */ -static struct flb_aws_client_vtable test_vtable = { - .request = test_http_client_request, -}; + /* Retrieve refreshed credentials from cache */ + creds = provider->provider_vtable->get_credentials(provider); + TEST_ASSERT(creds != NULL); + TEST_CHECK(strcmp("YACCESSEKSXXX", creds->access_key_id) == 0); + TEST_CHECK(strcmp("YSECRETEKSXXXXXXXXXXXXXX", creds->secret_access_key) == 0); + TEST_CHECK(strcmp("YTOKENEKSXXXXXXXXXXXXXXX==", creds->session_token) == 0); -struct flb_aws_client *test_http_client_create() -{ - struct flb_aws_client *client = flb_calloc(1, - sizeof(struct flb_aws_client)); - if (!client) { - flb_errno(); - return NULL; - } - client->client_vtable = &test_vtable; - return client; -} + flb_aws_credentials_destroy(creds); -/* Generator that returns clients with the test vtable */ -static struct flb_aws_client_generator test_generator = { - .create = test_http_client_create, -}; + /* Check we have exhausted our response list */ + TEST_CHECK(flb_aws_client_mock_generator_count_unused_requests() == 0); -struct flb_aws_client_generator *generator_in_test() -{ - return &test_generator; + cleanup_test(provider, config); } -/* http and container providers */ -static void test_http_provider() +static void test_http_provider_eks_with_token() { struct flb_aws_provider *provider; struct flb_aws_credentials *creds; - int ret; struct flb_config *config; - flb_sds_t endpoint; - flb_sds_t path; - - g_request_count = 0; - - config = flb_config_init(); - - if (config == NULL) { - return; - } - - endpoint = flb_sds_create("http://127.0.0.1"); - if (!endpoint) { - flb_errno(); - flb_config_exit(config); - return; - } - path = flb_sds_create("/happy-case"); - if (!path) { - flb_errno(); - flb_config_exit(config); - return; - } - - provider = flb_http_provider_create(config, endpoint, path, NULL, - generator_in_test()); - - if (!provider) { - flb_errno(); - flb_config_exit(config); - return; - } + int ret; - /* repeated calls to get credentials should return the same set */ + setenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/iam_credentials/pod1", 1); + setenv("AWS_CONTAINER_AUTHORIZATION_TOKEN", "password", 1); + + setup_test(FLB_AWS_CLIENT_MOCK( + response( + expect(URI, "/iam_credentials/pod1"), + expect(METHOD, FLB_HTTP_GET), + expect(HEADER, "Authorization", "password"), + set(STATUS, 200), + set(PAYLOAD, "{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-16T18:29:09Z\",\n" + " \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"XACCESSEKSXXX\",\n \"SecretAccessKey\"" + " : \"XSECRETEKSXXXXXXXXXXXXXX\",\n \"Token\" : \"XTOKENEKSXXXXXXXXXXXXXXX==\",\n" + " \"Expiration\" : \"3021-09-17T00:41:00Z\"\n}"), + set(PAYLOAD_SIZE, 257) + ), + response( + expect(URI, "/iam_credentials/pod1"), + expect(METHOD, FLB_HTTP_GET), + expect(HEADER, "Authorization", "password"), + set(STATUS, 200), + set(PAYLOAD, "{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-16T18:29:09Z\",\n" + " \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"YACCESSEKSXXX\",\n \"SecretAccessKey\"" + " : \"YSECRETEKSXXXXXXXXXXXXXX\",\n \"Token\" : \"YTOKENEKSXXXXXXXXXXXXXXX==\",\n" + " \"Expiration\" : \"3021-09-17T00:41:00Z\"\n}"), + set(PAYLOAD_SIZE, 257) + ) + ), &provider, &config); + + flb_time_msleep(1000); + + /* Repeated calls to get credentials should return the same set */ creds = provider->provider_vtable->get_credentials(provider); - if (!creds) { - flb_errno(); - flb_config_exit(config); - return; - } - TEST_CHECK(strcmp(ACCESS_KEY_HTTP, creds->access_key_id) == 0); - TEST_CHECK(strcmp(SECRET_KEY_HTTP, creds->secret_access_key) == 0); - TEST_CHECK(strcmp(TOKEN_HTTP, creds->session_token) == 0); + TEST_ASSERT(creds != NULL); + TEST_CHECK(strcmp("XACCESSEKSXXX", creds->access_key_id) == 0); + TEST_CHECK(strcmp("XSECRETEKSXXXXXXXXXXXXXX", creds->secret_access_key) == 0); + TEST_CHECK(strcmp("XTOKENEKSXXXXXXXXXXXXXXX==", creds->session_token) == 0); flb_aws_credentials_destroy(creds); + /* Retrieve from cache */ creds = provider->provider_vtable->get_credentials(provider); - if (!creds) { - flb_errno(); - flb_config_exit(config); - return; - } - TEST_CHECK(strcmp(ACCESS_KEY_HTTP, creds->access_key_id) == 0); - TEST_CHECK(strcmp(SECRET_KEY_HTTP, creds->secret_access_key) == 0); - TEST_CHECK(strcmp(TOKEN_HTTP, creds->session_token) == 0); + TEST_ASSERT(creds != NULL); + TEST_CHECK(strcmp("XACCESSEKSXXX", creds->access_key_id) == 0); + TEST_CHECK(strcmp("XSECRETEKSXXXXXXXXXXXXXX", creds->secret_access_key) == 0); + TEST_CHECK(strcmp("XTOKENEKSXXXXXXXXXXXXXXX==", creds->session_token) == 0); flb_aws_credentials_destroy(creds); @@ -278,85 +257,78 @@ static void test_http_provider() ret = provider->provider_vtable->refresh(provider); TEST_CHECK(ret == 0); - /* - * Request count should be 2: - * - One for the first call to get_credentials (2nd should hit cred cache) - * - One for the call to refresh - */ - TEST_CHECK(g_request_count == 2); + /* Retrieve refreshed credentials from cache */ + creds = provider->provider_vtable->get_credentials(provider); + TEST_ASSERT(creds != NULL); + TEST_CHECK(strcmp("YACCESSEKSXXX", creds->access_key_id) == 0); + TEST_CHECK(strcmp("YSECRETEKSXXXXXXXXXXXXXX", creds->secret_access_key) == 0); + TEST_CHECK(strcmp("YTOKENEKSXXXXXXXXXXXXXXX==", creds->session_token) == 0); + + flb_aws_credentials_destroy(creds); + + /* Check we have exhausted our response list */ + TEST_CHECK(flb_aws_client_mock_generator_count_unused_requests() == 0); - flb_aws_provider_destroy(provider); - flb_config_exit(config); + cleanup_test(provider, config); } -static void test_http_provider_auth_token() +static void test_http_provider_eks_with_token_file() { struct flb_aws_provider *provider; struct flb_aws_credentials *creds; - int ret; struct flb_config *config; - flb_sds_t endpoint; - flb_sds_t path; - flb_sds_t auth_token; - - g_request_count = 0; - - config = flb_config_init(); - - if (config == NULL) { - return; - } - - endpoint = flb_sds_create("http://127.0.0.1"); - if (!endpoint) { - flb_errno(); - flb_config_exit(config); - return; - } - path = flb_sds_create("/auth-token"); - if (!path) { - flb_errno(); - flb_config_exit(config); - return; - } - auth_token = flb_sds_create("this-is-a-fake-http-jwt"); - if (!auth_token) { - flb_errno(); - flb_config_exit(config); - return; - } - - provider = flb_http_provider_create(config, endpoint, path, auth_token, - generator_in_test()); - - if (!provider) { - flb_errno(); - flb_config_exit(config); - return; - } + int ret; - /* repeated calls to get credentials should return the same set */ + /* + * tests validation of valid non-default local loopback IP + * tests token file takes precedence over token variable + */ + setenv("AWS_CONTAINER_CREDENTIALS_FULL_URI", "http://127.0.0.7:80/iam_credentials/pod1", 1); + setenv("AWS_CONTAINER_AUTHORIZATION_TOKEN", "password", 1); + setenv("AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE", HTTP_TOKEN_FILE, 1); + + setup_test(FLB_AWS_CLIENT_MOCK( + response( + expect(URI, "/iam_credentials/pod1"), + expect(METHOD, FLB_HTTP_GET), + expect(HEADER, "Authorization", "this-is-a-fake-http-jwt"), + set(STATUS, 200), + set(PAYLOAD, "{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-16T18:29:09Z\",\n" + " \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"XACCESSEKSXXX\",\n \"SecretAccessKey\"" + " : \"XSECRETEKSXXXXXXXXXXXXXX\",\n \"Token\" : \"XTOKENEKSXXXXXXXXXXXXXXX==\",\n" + " \"Expiration\" : \"3021-09-17T00:41:00Z\"\n}"), + set(PAYLOAD_SIZE, 257) + ), + response( + expect(URI, "/iam_credentials/pod1"), + expect(METHOD, FLB_HTTP_GET), + expect(HEADER, "Authorization", "this-is-a-fake-http-jwt"), + set(STATUS, 200), + set(PAYLOAD, "{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-16T18:29:09Z\",\n" + " \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"YACCESSEKSXXX\",\n \"SecretAccessKey\"" + " : \"YSECRETEKSXXXXXXXXXXXXXX\",\n \"Token\" : \"YTOKENEKSXXXXXXXXXXXXXXX==\",\n" + " \"Expiration\" : \"3021-09-17T00:41:00Z\"\n}"), + set(PAYLOAD_SIZE, 257) + ) + ), &provider, &config); + + flb_time_msleep(1000); + + /* Repeated calls to get credentials should return the same set */ creds = provider->provider_vtable->get_credentials(provider); - if (!creds) { - flb_errno(); - flb_config_exit(config); - return; - } - TEST_CHECK(strcmp(ACCESS_KEY_HTTP, creds->access_key_id) == 0); - TEST_CHECK(strcmp(SECRET_KEY_HTTP, creds->secret_access_key) == 0); - TEST_CHECK(strcmp(TOKEN_HTTP, creds->session_token) == 0); + TEST_ASSERT(creds != NULL); + TEST_CHECK(strcmp("XACCESSEKSXXX", creds->access_key_id) == 0); + TEST_CHECK(strcmp("XSECRETEKSXXXXXXXXXXXXXX", creds->secret_access_key) == 0); + TEST_CHECK(strcmp("XTOKENEKSXXXXXXXXXXXXXXX==", creds->session_token) == 0); flb_aws_credentials_destroy(creds); + /* Retrieve from cache */ creds = provider->provider_vtable->get_credentials(provider); - if (!creds) { - flb_errno(); - flb_config_exit(config); - return; - } - TEST_CHECK(strcmp(ACCESS_KEY_HTTP, creds->access_key_id) == 0); - TEST_CHECK(strcmp(SECRET_KEY_HTTP, creds->secret_access_key) == 0); - TEST_CHECK(strcmp(TOKEN_HTTP, creds->session_token) == 0); + TEST_ASSERT(creds != NULL); + TEST_CHECK(strcmp("XACCESSEKSXXX", creds->access_key_id) == 0); + TEST_CHECK(strcmp("XSECRETEKSXXXXXXXXXXXXXX", creds->secret_access_key) == 0); + TEST_CHECK(strcmp("XTOKENEKSXXXXXXXXXXXXXXX==", creds->session_token) == 0); flb_aws_credentials_destroy(creds); @@ -364,80 +336,73 @@ static void test_http_provider_auth_token() ret = provider->provider_vtable->refresh(provider); TEST_CHECK(ret == 0); - /* - * Request count should be 2: - * - One for the first call to get_credentials (2nd should hit cred cache) - * - One for the call to refresh - */ - TEST_CHECK(g_request_count == 2); + /* Retrieve refreshed credentials from cache */ + creds = provider->provider_vtable->get_credentials(provider); + TEST_ASSERT(creds != NULL); + TEST_CHECK(strcmp("YACCESSEKSXXX", creds->access_key_id) == 0); + TEST_CHECK(strcmp("YSECRETEKSXXXXXXXXXXXXXX", creds->secret_access_key) == 0); + TEST_CHECK(strcmp("YTOKENEKSXXXXXXXXXXXXXXX==", creds->session_token) == 0); + + flb_aws_credentials_destroy(creds); + + /* Check we have exhausted our response list */ + TEST_CHECK(flb_aws_client_mock_generator_count_unused_requests() == 0); - flb_aws_provider_destroy(provider); - flb_config_exit(config); + cleanup_test(provider, config); } -static void test_local_http_provider() +static void test_http_provider_https_endpoint() { struct flb_aws_provider *provider; struct flb_aws_credentials *creds; - int ret; struct flb_config *config; - flb_sds_t endpoint; - flb_sds_t auth_token; - - g_request_count = 0; - - config = flb_config_init(); - - if (config == NULL) { - return; - } - - endpoint = flb_sds_create("http://127.0.0.1/auth-token"); - if (!endpoint) { - flb_errno(); - flb_config_exit(config); - return; - } - /* tests that the token file takes precedence */ - auth_token = flb_sds_create("this-is-the-wrong-jwt"); - if (!auth_token) { - flb_errno(); - flb_config_exit(config); - return; - } - setenv(TOKEN_FILE_ENV_VAR, HTTP_TOKEN_FILE, 1); - - provider = flb_local_http_provider_create(config, endpoint, auth_token, - generator_in_test()); - - if (!provider) { - flb_errno(); - flb_config_exit(config); - return; - } + int ret; - /* repeated calls to get credentials should return the same set */ + setenv("AWS_CONTAINER_CREDENTIALS_FULL_URI", "https://customers-vpc-credential-vending-server/iam_credentials/pod1", 1); + setenv("AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE", HTTP_TOKEN_FILE, 1); + + setup_test(FLB_AWS_CLIENT_MOCK( + response( + expect(URI, "/iam_credentials/pod1"), + expect(METHOD, FLB_HTTP_GET), + expect(HEADER, "Authorization", "this-is-a-fake-http-jwt"), + set(STATUS, 200), + set(PAYLOAD, "{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-16T18:29:09Z\",\n" + " \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"XACCESSEKSXXX\",\n \"SecretAccessKey\"" + " : \"XSECRETEKSXXXXXXXXXXXXXX\",\n \"Token\" : \"XTOKENEKSXXXXXXXXXXXXXXX==\",\n" + " \"Expiration\" : \"3021-09-17T00:41:00Z\"\n}"), + set(PAYLOAD_SIZE, 257) + ), + response( + expect(URI, "/iam_credentials/pod1"), + expect(METHOD, FLB_HTTP_GET), + expect(HEADER, "Authorization", "this-is-a-fake-http-jwt"), + set(STATUS, 200), + set(PAYLOAD, "{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-16T18:29:09Z\",\n" + " \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"YACCESSEKSXXX\",\n \"SecretAccessKey\"" + " : \"YSECRETEKSXXXXXXXXXXXXXX\",\n \"Token\" : \"YTOKENEKSXXXXXXXXXXXXXXX==\",\n" + " \"Expiration\" : \"3021-09-17T00:41:00Z\"\n}"), + set(PAYLOAD_SIZE, 257) + ) + ), &provider, &config); + + flb_time_msleep(1000); + + /* Repeated calls to get credentials should return the same set */ creds = provider->provider_vtable->get_credentials(provider); - if (!creds) { - flb_errno(); - flb_config_exit(config); - return; - } - TEST_CHECK(strcmp(ACCESS_KEY_HTTP, creds->access_key_id) == 0); - TEST_CHECK(strcmp(SECRET_KEY_HTTP, creds->secret_access_key) == 0); - TEST_CHECK(strcmp(TOKEN_HTTP, creds->session_token) == 0); + TEST_ASSERT(creds != NULL); + TEST_CHECK(strcmp("XACCESSEKSXXX", creds->access_key_id) == 0); + TEST_CHECK(strcmp("XSECRETEKSXXXXXXXXXXXXXX", creds->secret_access_key) == 0); + TEST_CHECK(strcmp("XTOKENEKSXXXXXXXXXXXXXXX==", creds->session_token) == 0); flb_aws_credentials_destroy(creds); + /* Retrieve from cache */ creds = provider->provider_vtable->get_credentials(provider); - if (!creds) { - flb_errno(); - flb_config_exit(config); - return; - } - TEST_CHECK(strcmp(ACCESS_KEY_HTTP, creds->access_key_id) == 0); - TEST_CHECK(strcmp(SECRET_KEY_HTTP, creds->secret_access_key) == 0); - TEST_CHECK(strcmp(TOKEN_HTTP, creds->session_token) == 0); + TEST_ASSERT(creds != NULL); + TEST_CHECK(strcmp("XACCESSEKSXXX", creds->access_key_id) == 0); + TEST_CHECK(strcmp("XSECRETEKSXXXXXXXXXXXXXX", creds->secret_access_key) == 0); + TEST_CHECK(strcmp("XTOKENEKSXXXXXXXXXXXXXXX==", creds->session_token) == 0); flb_aws_credentials_destroy(creds); @@ -445,150 +410,141 @@ static void test_local_http_provider() ret = provider->provider_vtable->refresh(provider); TEST_CHECK(ret == 0); - /* - * Request count should be 2: - * - One for the first call to get_credentials (2nd should hit cred cache) - * - One for the call to refresh - */ - TEST_CHECK(g_request_count == 2); + /* Retrieve refreshed credentials from cache */ + creds = provider->provider_vtable->get_credentials(provider); + TEST_ASSERT(creds != NULL); + TEST_CHECK(strcmp("YACCESSEKSXXX", creds->access_key_id) == 0); + TEST_CHECK(strcmp("YSECRETEKSXXXXXXXXXXXXXX", creds->secret_access_key) == 0); + TEST_CHECK(strcmp("YTOKENEKSXXXXXXXXXXXXXXX==", creds->session_token) == 0); + + flb_aws_credentials_destroy(creds); + + /* Check we have exhausted our response list */ + TEST_CHECK(flb_aws_client_mock_generator_count_unused_requests() == 0); - unsetenv(TOKEN_FILE_ENV_VAR); - flb_aws_provider_destroy(provider); - flb_config_exit(config); + cleanup_test(provider, config); } -static void test_http_provider_error_case() +static void test_http_provider_server_failure() { struct flb_aws_provider *provider; struct flb_aws_credentials *creds; - int ret; struct flb_config *config; - flb_sds_t endpoint; - flb_sds_t path; - - g_request_count = 0; + int ret; - config = flb_config_init(); + setenv("AWS_CONTAINER_CREDENTIALS_FULL_URI", "https://customers-vpc-credential-vending-server/iam_credentials/pod1", 1); + setenv("AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE", HTTP_TOKEN_FILE, 1); + + setup_test(FLB_AWS_CLIENT_MOCK( + response( + expect(URI, "/iam_credentials/pod1"), + expect(METHOD, FLB_HTTP_GET), + expect(HEADER, "Authorization", "this-is-a-fake-http-jwt"), + set(STATUS, 400), + set(PAYLOAD, "{\"Message\": \"Invalid Authorization token\",\"Code\": \"ClientError\"}"), + set(PAYLOAD_SIZE, 64) + ), + response( + expect(URI, "/iam_credentials/pod1"), + expect(METHOD, FLB_HTTP_GET), + expect(HEADER, "Authorization", "this-is-a-fake-http-jwt"), + set(STATUS, 500), + set(PAYLOAD, "{\"Message\": \"Internal Server Error\",\"Code\": \"ServerError\"}"), + set(PAYLOAD_SIZE, 58) + ) + ), &provider, &config); + + flb_time_msleep(1000); + + /* Endpoint failure, no creds returnd */ + creds = provider->provider_vtable->get_credentials(provider); + TEST_ASSERT(creds == NULL); - if (config == NULL) { - return; - } + /* refresh should return 0 (success) */ + ret = provider->provider_vtable->refresh(provider); + TEST_CHECK(ret != 0); - endpoint = flb_sds_create("http://127.0.0.1"); - if (!endpoint) { - flb_errno(); - flb_config_exit(config); - return; - } - path = flb_sds_create("/error-case"); - if (!path) { - flb_errno(); - flb_config_exit(config); - return; if (path) { - flb_sds_destroy(path); - } - } + /* Check we have exhausted our response list */ + TEST_CHECK(flb_aws_client_mock_generator_count_unused_requests() == 0); - provider = flb_http_provider_create(config, endpoint, path, NULL, - generator_in_test()); + cleanup_test(provider, config); +} - if (!provider) { - flb_errno(); - flb_config_exit(config); - return; - } +static void test_http_validator_invalid_auth_token() +{ + struct flb_aws_provider *provider; + struct flb_config *config; - /* get_credentials will fail */ - creds = provider->provider_vtable->get_credentials(provider); - TEST_CHECK(creds == NULL); + setenv("AWS_CONTAINER_CREDENTIALS_FULL_URI", "http://169.254.70.2:80/iam_credentials/pod1", 1); + setenv("AWS_CONTAINER_AUTHORIZATION_TOKEN", "password\\r\\n", 1); - creds = provider->provider_vtable->get_credentials(provider); - TEST_CHECK(creds == NULL); + flb_aws_client_mock_configure_generator(NULL); - /* refresh should return -1 (failure) */ - ret = provider->provider_vtable->refresh(provider); - TEST_CHECK(ret < 0); + config = flb_calloc(1, sizeof(struct flb_config)); + TEST_ASSERT(config != NULL); + mk_list_init(&config->upstreams); - /* - * Request count should be 3: - * - Each call to get_credentials and refresh invokes the client's - * request method and returns a request failure. - */ - TEST_CHECK(g_request_count == 3); + /* provider creation will fail with error message indicating port was invalid */ + provider = flb_container_provider_create(config, flb_aws_client_get_mock_generator()); + TEST_ASSERT(provider == NULL); - flb_aws_provider_destroy(provider); - flb_config_exit(config); + flb_aws_client_mock_destroy_generator(); + flb_free(config); } -static void test_http_provider_malformed_response() +static void test_http_validator_invalid_host() { struct flb_aws_provider *provider; - struct flb_aws_credentials *creds; - int ret; struct flb_config *config; - flb_sds_t endpoint; - flb_sds_t path; - - g_request_count = 0; - config = flb_config_init(); + setenv("AWS_CONTAINER_CREDENTIALS_FULL_URI", "http://104.156.107.142:80/iam_credentials/pod1", 1); + setenv("AWS_CONTAINER_AUTHORIZATION_TOKEN", "password", 1); - if (config == NULL) { - return; - } + flb_aws_client_mock_configure_generator(NULL); + config = flb_calloc(1, sizeof(struct flb_config)); + TEST_ASSERT(config != NULL); mk_list_init(&config->upstreams); - endpoint = flb_sds_create("http://127.0.0.1"); - if (!endpoint) { - flb_errno(); - flb_config_exit(config); - return; - } - path = flb_sds_create("/malformed"); - if (!path) { - flb_errno(); - flb_config_exit(config); - return; - } + /* provider creation will fail with error message indicating host was invalid */ + provider = flb_container_provider_create(config, flb_aws_client_get_mock_generator()); + TEST_ASSERT(provider == NULL); - provider = flb_http_provider_create(config, endpoint, path, NULL, - generator_in_test()); + flb_aws_client_mock_destroy_generator(); + flb_free(config); +} - if (!provider) { - flb_errno(); - flb_config_exit(config); - return; - } +static void test_http_validator_invalid_port() +{ + struct flb_aws_provider *provider; + struct flb_config *config; - /* get_credentials will fail */ - creds = provider->provider_vtable->get_credentials(provider); - TEST_CHECK(creds == NULL); + setenv("AWS_CONTAINER_CREDENTIALS_FULL_URI", "http://169.254.70.2:AA/iam_credentials/pod1", 1); + setenv("AWS_CONTAINER_AUTHORIZATION_TOKEN", "password", 1); - creds = provider->provider_vtable->get_credentials(provider); - TEST_CHECK(creds == NULL); + flb_aws_client_mock_configure_generator(NULL); - /* refresh should return -1 (failure) */ - ret = provider->provider_vtable->refresh(provider); - TEST_CHECK(ret < 0); + config = flb_calloc(1, sizeof(struct flb_config)); + TEST_ASSERT(config != NULL); + mk_list_init(&config->upstreams); - /* - * Request count should be 3: - * - Each call to get_credentials and refresh invokes the client's - * request method and returns a request failure. - */ - TEST_CHECK(g_request_count == 3); + /* provider creation will fail with error message indicating port was invalid */ + provider = flb_container_provider_create(config, flb_aws_client_get_mock_generator()); + TEST_ASSERT(provider == NULL); - flb_aws_provider_destroy(provider); - flb_config_exit(config); + flb_aws_client_mock_destroy_generator(); + flb_free(config); } TEST_LIST = { - { "test_http_provider" , test_http_provider}, - { "test_http_provider_auth_token" , test_http_provider_auth_token}, - { "test_local_http_provider" , test_local_http_provider}, - { "test_http_provider_error_case" , test_http_provider_error_case}, - { "test_http_provider_malformed_response" , - test_http_provider_malformed_response}, + { "test_http_provider_malformed_response" , test_http_provider_malformed_response}, + { "test_http_provider_ecs_case" , test_http_provider_ecs_case}, + { "test_http_provider_eks_with_token" , test_http_provider_eks_with_token}, + { "test_http_provider_eks_with_token_file" , test_http_provider_eks_with_token_file}, + { "test_http_provider_https_endpoint" , test_http_provider_https_endpoint}, + { "test_http_provider_server_failure" , test_http_provider_server_failure}, + { "test_http_validator_invalid_auth_token" , test_http_validator_invalid_auth_token}, + { "test_http_validator_invalid_host" , test_http_validator_invalid_host}, + { "test_http_validator_invalid_port" , test_http_validator_invalid_port}, { 0 } };