diff --git a/include/fluent-bit/flb_aws_credentials.h b/include/fluent-bit/flb_aws_credentials.h index 857468ec28b..60913955f62 100644 --- a/include/fluent-bit/flb_aws_credentials.h +++ b/include/fluent-bit/flb_aws_credentials.h @@ -131,6 +131,13 @@ struct flb_aws_provider { /* Standard credentials chain is a list of providers */ struct mk_list _head; + + /* Provider managed dependencies; to delete on destroy */ + struct flb_aws_provider *base_aws_provider; + struct flb_tls *cred_tls; /* tls instances can't be re-used; aws provider requires + a separate one */ + struct flb_tls *sts_tls; /* one for the standard chain provider, one for sts + assume role */ }; /* @@ -156,6 +163,43 @@ struct flb_aws_provider *flb_standard_chain_provider_create(struct flb_config flb_aws_client_generator *generator); +/* Provide base configuration options for managed chain */ +#define FLB_AWS_CREDENTIAL_BASE_CONFIG_MAP(prefix) \ + { \ + FLB_CONFIG_MAP_STR, prefix "region", NULL, \ + 0, FLB_FALSE, 0, \ + "AWS region of your service" \ + }, \ + { \ + FLB_CONFIG_MAP_STR, prefix "sts_endpoint", NULL, \ + 0, FLB_FALSE, 0, \ + "Custom endpoint for the AWS STS API, used with the `" prefix "role_arn` option" \ + }, \ + { \ + FLB_CONFIG_MAP_STR, prefix "role_arn", NULL, \ + 0, FLB_FALSE, 0, \ + "ARN of an IAM role to assume (ex. for cross account access)" \ + }, \ + { \ + FLB_CONFIG_MAP_STR, prefix "external_id", NULL, \ + 0, FLB_FALSE, 0, \ + "Specify an external ID for the STS API, can be used with the `" prefix \ + "role_arn` parameter if your role requires an external ID." \ + } + +/* + * Managed chain provider; Creates and manages removal of dependancies for an instance + */ +struct flb_aws_provider *flb_managed_chain_provider_create(struct flb_output_instance + *ins, + struct flb_config + *config, + char *config_key_prefix, + char *proxy, + struct + flb_aws_client_generator + *generator); + /* * A provider that uses OIDC tokens provided by kubernetes to obtain * AWS credentials. diff --git a/plugins/out_http/http.c b/plugins/out_http/http.c index 1b34d77051e..187e60fa641 100644 --- a/plugins/out_http/http.c +++ b/plugins/out_http/http.c @@ -29,6 +29,13 @@ #include #include +#ifdef FLB_HAVE_SIGNV4 +#ifdef FLB_HAVE_AWS +#include +#include +#endif +#endif + #include #include #include @@ -79,6 +86,7 @@ static int http_post(struct flb_out_http *ctx, struct flb_config_map_val *mv; struct flb_slist_entry *key = NULL; struct flb_slist_entry *val = NULL; + flb_sds_t signature = NULL; /* Get upstream context and connection */ u = ctx->u; @@ -174,6 +182,30 @@ static int http_post(struct flb_out_http *ctx, val->str, flb_sds_len(val->str)); } +#ifdef FLB_HAVE_SIGNV4 +#ifdef FLB_HAVE_AWS + /* AWS SigV4 headers */ + if (ctx->has_aws_auth == FLB_TRUE) { + flb_plg_debug(ctx->ins, "signing request with AWS Sigv4"); + signature = flb_signv4_do(c, + FLB_TRUE, /* normalize URI ? */ + FLB_TRUE, /* add x-amz-date header ? */ + time(NULL), + (char *) ctx->aws_region, + (char *) ctx->aws_service, + 0, + ctx->aws_provider); + + if (!signature) { + flb_plg_error(ctx->ins, "could not sign request with sigv4"); + out_ret = FLB_RETRY; + goto cleanup; + } + flb_sds_destroy(signature); + } +#endif +#endif + ret = flb_http_do(c, &b_sent); if (ret == 0) { /* @@ -220,6 +252,7 @@ static int http_post(struct flb_out_http *ctx, out_ret = FLB_RETRY; } +cleanup: /* * If the payload buffer is different than incoming records in body, means * we generated a different payload and must be freed. @@ -377,6 +410,21 @@ static struct flb_config_map config_map[] = { 0, FLB_TRUE, offsetof(struct flb_out_http, http_passwd), "Set HTTP auth password" }, +#ifdef FLB_HAVE_SIGNV4 +#ifdef FLB_HAVE_AWS + { + FLB_CONFIG_MAP_BOOL, "aws_auth", "false", + 0, FLB_TRUE, offsetof(struct flb_out_http, has_aws_auth), + "Enable AWS SigV4 authentication" + }, + { + FLB_CONFIG_MAP_STR, "aws_service", NULL, + 0, FLB_TRUE, offsetof(struct flb_out_http, aws_service), + "AWS destination service code, used by SigV4 authentication" + }, + FLB_AWS_CREDENTIAL_BASE_CONFIG_MAP(FLB_HTTP_AWS_CREDENTIAL_PREFIX), +#endif +#endif { FLB_CONFIG_MAP_STR, "header_tag", NULL, 0, FLB_TRUE, offsetof(struct flb_out_http, header_tag), diff --git a/plugins/out_http/http.h b/plugins/out_http/http.h index 58695f4d4e6..ed77c17545c 100644 --- a/plugins/out_http/http.h +++ b/plugins/out_http/http.h @@ -27,11 +27,27 @@ #define FLB_HTTP_MIME_MSGPACK "application/msgpack" #define FLB_HTTP_MIME_JSON "application/json" +#ifdef FLB_HAVE_SIGNV4 +#ifdef FLB_HAVE_AWS +#define FLB_HTTP_AWS_CREDENTIAL_PREFIX "aws_" +#endif +#endif + struct flb_out_http { /* HTTP Auth */ char *http_user; char *http_passwd; + /* AWS Auth */ +#ifdef FLB_HAVE_SIGNV4 +#ifdef FLB_HAVE_AWS + int has_aws_auth; + struct flb_aws_provider *aws_provider; + const char *aws_region; + const char *aws_service; +#endif +#endif + /* Proxy */ const char *proxy; char *proxy_host; diff --git a/plugins/out_http/http_conf.c b/plugins/out_http/http_conf.c index 6bfac5c65bd..7a0106384c3 100644 --- a/plugins/out_http/http_conf.c +++ b/plugins/out_http/http_conf.c @@ -22,7 +22,11 @@ #include #include #include - +#ifdef FLB_HAVE_SIGNV4 +#ifdef FLB_HAVE_AWS +#include +#endif +#endif #include "http.h" #include "http_conf.h" @@ -81,6 +85,39 @@ struct flb_out_http *flb_http_conf_create(struct flb_output_instance *ins, flb_output_net_default("127.0.0.1", 80, ins); } + /* Check if AWS SigV4 authentication is enabled */ +#ifdef FLB_HAVE_SIGNV4 +#ifdef FLB_HAVE_AWS + if (ctx->has_aws_auth) { + ctx->aws_service = flb_output_get_property(FLB_HTTP_AWS_CREDENTIAL_PREFIX + "service", ctx->ins); + if (!ctx->aws_service) { + flb_plg_error(ins, "aws_auth option requires " FLB_HTTP_AWS_CREDENTIAL_PREFIX + "service to be set"); + flb_free(ctx); + return NULL; + } + + ctx->aws_provider = flb_managed_chain_provider_create( + ins, + config, + FLB_HTTP_AWS_CREDENTIAL_PREFIX, + NULL, + flb_aws_client_generator() + ); + if (!ctx->aws_provider) { + flb_plg_error(ins, "failed to create aws credential provider for sigv4 auth"); + flb_free(ctx); + return NULL; + } + + /* If managed provider creation succeeds, then region key is present */ + ctx->aws_region = flb_output_get_property(FLB_HTTP_AWS_CREDENTIAL_PREFIX + "region", ctx->ins); + } +#endif /* !FLB_HAVE_AWS */ +#endif /* !FLB_HAVE_SIGNV4 */ + /* Check if SSL/TLS is enabled */ #ifdef FLB_HAVE_TLS if (ins->use_tls == FLB_TRUE) { @@ -213,6 +250,14 @@ void flb_http_conf_destroy(struct flb_out_http *ctx) flb_upstream_destroy(ctx->u); } +#ifdef FLB_HAVE_SIGNV4 +#ifdef FLB_HAVE_AWS + if (ctx->aws_provider) { + flb_aws_provider_destroy(ctx->aws_provider); + } +#endif +#endif + flb_free(ctx->proxy_host); flb_free(ctx->uri); flb_free(ctx); diff --git a/src/aws/flb_aws_credentials.c b/src/aws/flb_aws_credentials.c index e18bf57f621..6282456bf72 100644 --- a/src/aws/flb_aws_credentials.c +++ b/src/aws/flb_aws_credentials.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -323,6 +324,186 @@ struct flb_aws_provider *flb_standard_chain_provider_create(struct flb_config return provider; } +struct flb_aws_provider *flb_managed_chain_provider_create(struct flb_output_instance + *ins, + struct flb_config + *config, + char *config_key_prefix, + char *proxy, + struct + flb_aws_client_generator + *generator) +{ + flb_sds_t config_key_region; + flb_sds_t config_key_sts_endpoint; + flb_sds_t config_key_role_arn; + flb_sds_t config_key_external_id; + const char *region = NULL; + const char *sts_endpoint = NULL; + const char *role_arn = NULL; + const char *external_id = NULL; + char *session_name = NULL; + int key_prefix_len; + int key_max_len; + + /* Provider managed dependencies */ + struct flb_aws_provider *aws_provider = NULL; + struct flb_aws_provider *base_aws_provider = NULL; + struct flb_tls *cred_tls = NULL; + struct flb_tls *sts_tls = NULL; + + /* Config keys */ + key_prefix_len = strlen(config_key_prefix); + key_max_len = key_prefix_len + 12; /* max length of + "region", "sts_endpoint", "role_arn", + "external_id" */ + + /* Evaluate full config keys */ + config_key_region = flb_sds_create_len(config_key_prefix, key_max_len); + strcpy(config_key_region + key_prefix_len, "region"); + config_key_sts_endpoint = flb_sds_create_len(config_key_prefix, key_max_len); + strcpy(config_key_sts_endpoint + key_prefix_len, "sts_endpoint"); + config_key_role_arn = flb_sds_create_len(config_key_prefix, key_max_len); + strcpy(config_key_role_arn + key_prefix_len, "role_arn"); + config_key_external_id = flb_sds_create_len(config_key_prefix, key_max_len); + strcpy(config_key_external_id + key_prefix_len, "external_id"); + + /* AWS provider needs a separate TLS instance */ + cred_tls = flb_tls_create(FLB_TRUE, + ins->tls_debug, + ins->tls_vhost, + ins->tls_ca_path, + ins->tls_ca_file, + ins->tls_crt_file, + ins->tls_key_file, + ins->tls_key_passwd); + if (!cred_tls) { + flb_plg_error(ins, "Failed to create TLS instance for AWS Provider"); + flb_errno(); + goto error; + } + + region = flb_output_get_property(config_key_region, ins); + if (!region) { + flb_plg_error(ins, "aws_auth enabled but %s not set", config_key_region); + goto error; + } + + /* Use null sts_endpoint if none provided */ + sts_endpoint = flb_output_get_property(config_key_sts_endpoint, ins); + + aws_provider = flb_standard_chain_provider_create(config, + cred_tls, + (char *) region, + (char *) sts_endpoint, + NULL, + flb_aws_client_generator()); + if (!aws_provider) { + flb_plg_error(ins, "Failed to create AWS Credential Provider"); + goto error; + } + + role_arn = flb_output_get_property(config_key_role_arn, ins); + if (role_arn) { + /* Use the STS Provider */ + base_aws_provider = aws_provider; + external_id = flb_output_get_property(config_key_external_id, ins); + + session_name = flb_sts_session_name(); + if (!session_name) { + flb_plg_error(ins, "Failed to generate aws iam role " + "session name"); + goto error; + } + + /* STS provider needs yet another separate TLS instance */ + sts_tls = flb_tls_create(FLB_TRUE, + ins->tls_debug, + ins->tls_vhost, + ins->tls_ca_path, + ins->tls_ca_file, + ins->tls_crt_file, + ins->tls_key_file, + ins->tls_key_passwd); + if (!sts_tls) { + flb_plg_error(ins, "Failed to create TLS instance for AWS STS Credential " + "Provider"); + flb_errno(); + goto error; + } + + aws_provider = flb_sts_provider_create(config, + sts_tls, + base_aws_provider, + (char *) external_id, + (char *) role_arn, + session_name, + (char *) region, + (char *) sts_endpoint, + NULL, + flb_aws_client_generator()); + if (!aws_provider) { + flb_plg_error(ins, "Failed to create AWS STS Credential " + "Provider"); + goto error; + } + } + + /* initialize credentials in sync mode */ + aws_provider->provider_vtable->sync(aws_provider); + aws_provider->provider_vtable->init(aws_provider); + + /* set back to async */ + aws_provider->provider_vtable->async(aws_provider); + + /* store dependencies in aws_provider for managed cleanup */ + aws_provider->base_aws_provider = base_aws_provider; + aws_provider->cred_tls = cred_tls; + aws_provider->sts_tls = sts_tls; + + goto cleanup; + +error: + if (aws_provider) { + /* disconnect dependencies */ + aws_provider->base_aws_provider = NULL; + aws_provider->cred_tls = NULL; + aws_provider->sts_tls = NULL; + /* destroy */ + flb_aws_provider_destroy(aws_provider); + } + /* free dependencies */ + if (base_aws_provider) { + flb_aws_provider_destroy(base_aws_provider); + } + if (cred_tls) { + flb_tls_destroy(cred_tls); + } + if (sts_tls) { + flb_tls_destroy(sts_tls); + } + aws_provider = NULL; + +cleanup: + if (config_key_region) { + flb_sds_destroy(config_key_region); + } + if (config_key_sts_endpoint) { + flb_sds_destroy(config_key_sts_endpoint); + } + if (config_key_role_arn) { + flb_sds_destroy(config_key_role_arn); + } + if (config_key_external_id) { + flb_sds_destroy(config_key_external_id); + } + if (session_name) { + flb_free(session_name); + } + + return aws_provider; +} + static struct flb_aws_provider *standard_chain_create(struct flb_config *config, struct flb_tls *tls, @@ -575,6 +756,17 @@ void flb_aws_provider_destroy(struct flb_aws_provider *provider) provider->provider_vtable->destroy(provider); } + /* free managed dependencies */ + if (provider->base_aws_provider) { + flb_aws_provider_destroy(provider->base_aws_provider); + } + if (provider->cred_tls) { + flb_tls_destroy(provider->cred_tls); + } + if (provider->sts_tls) { + flb_tls_destroy(provider->sts_tls); + } + flb_free(provider); } }