From e9418b1eb8428e6cf2a410e77d007cf956359533 Mon Sep 17 00:00:00 2001 From: Stefano Boriero Date: Tue, 9 Sep 2025 15:26:00 +0200 Subject: [PATCH 1/4] out_azure_logs_ingestion: implement Managed Identity support This change is based on the existing approach to Managed Identity authentication used for the out_azure_kusto plugin. Signed-off-by: Stefano Boriero --- .../out_azure_logs_ingestion/CMakeLists.txt | 1 + .../azure_logs_ingestion.c | 86 ++-- .../azure_logs_ingestion.h | 11 + .../azure_logs_ingestion_conf.c | 89 +++-- .../azure_logs_ingestion_msiauth.c | 99 +++++ .../azure_logs_ingestion_msiauth.h | 27 ++ tests/runtime/CMakeLists.txt | 1 + tests/runtime/out_azure_logs_ingestion.c | 377 ++++++++++++++++++ 8 files changed, 635 insertions(+), 56 deletions(-) create mode 100644 plugins/out_azure_logs_ingestion/azure_logs_ingestion_msiauth.c create mode 100644 plugins/out_azure_logs_ingestion/azure_logs_ingestion_msiauth.h create mode 100644 tests/runtime/out_azure_logs_ingestion.c diff --git a/plugins/out_azure_logs_ingestion/CMakeLists.txt b/plugins/out_azure_logs_ingestion/CMakeLists.txt index b51308c70d0..4a7ead7ad5f 100644 --- a/plugins/out_azure_logs_ingestion/CMakeLists.txt +++ b/plugins/out_azure_logs_ingestion/CMakeLists.txt @@ -1,6 +1,7 @@ set(src azure_logs_ingestion.c azure_logs_ingestion_conf.c + azure_logs_ingestion_msiauth.c ) FLB_PLUGIN(out_azure_logs_ingestion "${src}" "") diff --git a/plugins/out_azure_logs_ingestion/azure_logs_ingestion.c b/plugins/out_azure_logs_ingestion/azure_logs_ingestion.c index e816488cd8a..5ebcfebaad1 100644 --- a/plugins/out_azure_logs_ingestion/azure_logs_ingestion.c +++ b/plugins/out_azure_logs_ingestion/azure_logs_ingestion.c @@ -31,6 +31,7 @@ #include "azure_logs_ingestion.h" #include "azure_logs_ingestion_conf.h" +#include "azure_logs_ingestion_msiauth.h" static int cb_azure_logs_ingestion_init(struct flb_output_instance *ins, struct flb_config *config, void *data) @@ -170,48 +171,63 @@ flb_sds_t get_az_li_token(struct flb_az_li *ctx) flb_plg_error(ctx->ins, "error locking mutex"); return NULL; } + /* Retrieve access token only if expired */ if (flb_oauth2_token_expired(ctx->u_auth) == FLB_TRUE) { flb_plg_debug(ctx->ins, "token expired. getting new token"); - /* Clear any previous oauth2 payload content */ - flb_oauth2_payload_clear(ctx->u_auth); - - ret = flb_oauth2_payload_append(ctx->u_auth, "grant_type", 10, - "client_credentials", 18); - if (ret == -1) { - flb_plg_error(ctx->ins, "error appending oauth2 params"); - goto token_cleanup; + + if (ctx->auth_type == FLB_AZ_LI_AUTH_MANAGED_IDENTITY_SYSTEM || + ctx->auth_type == FLB_AZ_LI_AUTH_MANAGED_IDENTITY_USER) { + /* Use MSI authentication */ + token = flb_azure_li_msiauth_token_get(ctx->u_auth); + if (!token) { + flb_plg_error(ctx->ins, "error retrieving MSI access token"); + goto token_cleanup; + } + flb_plg_debug(ctx->ins, "got azure MSI token"); } + else { + /* Use service principal authentication */ + /* Clear any previous oauth2 payload content */ + flb_oauth2_payload_clear(ctx->u_auth); + + ret = flb_oauth2_payload_append(ctx->u_auth, "grant_type", 10, + "client_credentials", 18); + if (ret == -1) { + flb_plg_error(ctx->ins, "error appending oauth2 params"); + goto token_cleanup; + } - ret = flb_oauth2_payload_append(ctx->u_auth, "scope", 5, FLB_AZ_LI_AUTH_SCOPE, - sizeof(FLB_AZ_LI_AUTH_SCOPE) - 1); - if (ret == -1) { - flb_plg_error(ctx->ins, "error appending oauth2 params"); - goto token_cleanup; - } + ret = flb_oauth2_payload_append(ctx->u_auth, "scope", 5, FLB_AZ_LI_AUTH_SCOPE, + sizeof(FLB_AZ_LI_AUTH_SCOPE) - 1); + if (ret == -1) { + flb_plg_error(ctx->ins, "error appending oauth2 params"); + goto token_cleanup; + } - ret = flb_oauth2_payload_append(ctx->u_auth, "client_id", 9, - ctx->client_id, -1); - if (ret == -1) { - flb_plg_error(ctx->ins, "error appending oauth2 params"); - goto token_cleanup; - } + ret = flb_oauth2_payload_append(ctx->u_auth, "client_id", 9, + ctx->client_id, -1); + if (ret == -1) { + flb_plg_error(ctx->ins, "error appending oauth2 params"); + goto token_cleanup; + } - ret = flb_oauth2_payload_append(ctx->u_auth, "client_secret", 13, - ctx->client_secret, -1); - if (ret == -1) { - flb_plg_error(ctx->ins, "error appending oauth2 params"); - goto token_cleanup; - } + ret = flb_oauth2_payload_append(ctx->u_auth, "client_secret", 13, + ctx->client_secret, -1); + if (ret == -1) { + flb_plg_error(ctx->ins, "error appending oauth2 params"); + goto token_cleanup; + } - token = flb_oauth2_token_get(ctx->u_auth); + token = flb_oauth2_token_get(ctx->u_auth); - /* Copy string to prevent race conditions */ - if (!token) { - flb_plg_error(ctx->ins, "error retrieving oauth2 access token"); - goto token_cleanup; + /* Copy string to prevent race conditions */ + if (!token) { + flb_plg_error(ctx->ins, "error retrieving oauth2 access token"); + goto token_cleanup; + } + flb_plg_debug(ctx->ins, "got azure token"); } - flb_plg_debug(ctx->ins, "got azure token"); } /* Reached this code-block means, got new token or token not expired */ @@ -393,6 +409,12 @@ static struct flb_config_map config_map[] = { 0, FLB_TRUE, offsetof(struct flb_az_li, client_secret), "Set the client secret of the AAD application" }, + { + FLB_CONFIG_MAP_STR, "auth_type", "service_principal", + 0, FLB_TRUE, offsetof(struct flb_az_li, auth_type_str), + "Set the authentication type: 'service_principal' or 'managed_identity'. " + "For managed_identity, use 'system' as client_id for system-assigned identity, or specify the managed identity's client ID" + }, { FLB_CONFIG_MAP_STR, "dce_url", (char *)NULL, 0, FLB_TRUE, offsetof(struct flb_az_li, dce_url), diff --git a/plugins/out_azure_logs_ingestion/azure_logs_ingestion.h b/plugins/out_azure_logs_ingestion/azure_logs_ingestion.h index 2d420806c27..e044cd6adaf 100644 --- a/plugins/out_azure_logs_ingestion/azure_logs_ingestion.h +++ b/plugins/out_azure_logs_ingestion/azure_logs_ingestion.h @@ -34,6 +34,13 @@ /* refresh token every 60 minutes */ #define FLB_AZ_LI_TOKEN_TIMEOUT 3600 +/* Authentication types */ +typedef enum { + FLB_AZ_LI_AUTH_SERVICE_PRINCIPAL = 0, /* Client ID + Client Secret */ + FLB_AZ_LI_AUTH_MANAGED_IDENTITY_SYSTEM, /* System-assigned managed identity */ + FLB_AZ_LI_AUTH_MANAGED_IDENTITY_USER /* User-assigned managed identity */ +} flb_az_li_auth_type; + #include #include #include @@ -48,6 +55,10 @@ struct flb_az_li { flb_sds_t dcr_id; flb_sds_t table_name; + /* Authentication */ + int auth_type; + char *auth_type_str; + /* time_generated: on/off */ int time_generated; /* time key name */ diff --git a/plugins/out_azure_logs_ingestion/azure_logs_ingestion_conf.c b/plugins/out_azure_logs_ingestion/azure_logs_ingestion_conf.c index 27034d1b89a..2488a6ba0ae 100644 --- a/plugins/out_azure_logs_ingestion/azure_logs_ingestion_conf.c +++ b/plugins/out_azure_logs_ingestion/azure_logs_ingestion_conf.c @@ -25,6 +25,7 @@ #include "azure_logs_ingestion.h" #include "azure_logs_ingestion_conf.h" +#include "azure_logs_ingestion_msiauth.h" struct flb_az_li* flb_az_li_ctx_create(struct flb_output_instance *ins, struct flb_config *config) @@ -54,21 +55,34 @@ struct flb_az_li* flb_az_li_ctx_create(struct flb_output_instance *ins, return NULL; } - /* config: 'client_id' */ - if (!ctx->client_id) { - flb_plg_error(ins, "property 'client_id' is not defined"); - flb_az_li_ctx_destroy(ctx); - return NULL; - } - /* config: 'tenant_id' */ - if (!ctx->tenant_id) { - flb_plg_error(ins, "property 'tenant_id' is not defined"); - flb_az_li_ctx_destroy(ctx); - return NULL; - } - /* config: 'client_secret' */ - if (!ctx->client_secret) { - flb_plg_error(ins, "property 'client_secret' is not defined"); + /* Auth method validation and setup */ + if (strcasecmp(ctx->auth_type_str, "service_principal") == 0) { + ctx->auth_type = FLB_AZ_LI_AUTH_SERVICE_PRINCIPAL; + + /* Verify required parameters for Service Principal auth */ + if (!ctx->tenant_id || !ctx->client_id || !ctx->client_secret) { + flb_plg_error(ins, "When using service_principal auth, tenant_id, client_id, and client_secret are required"); + flb_az_li_ctx_destroy(ctx); + return NULL; + } + } + else if (strcasecmp(ctx->auth_type_str, "managed_identity") == 0) { + /* Check if client_id indicates system-assigned or user-assigned managed identity */ + if (!ctx->client_id) { + flb_plg_error(ins, "When using managed_identity auth, client_id must be set to 'system' for system-assigned or the managed identity client ID"); + flb_az_li_ctx_destroy(ctx); + return NULL; + } + + if (strcasecmp(ctx->client_id, "system") == 0) { + ctx->auth_type = FLB_AZ_LI_AUTH_MANAGED_IDENTITY_SYSTEM; + } else { + ctx->auth_type = FLB_AZ_LI_AUTH_MANAGED_IDENTITY_USER; + } + } + else { + flb_plg_error(ins, "Invalid auth_type '%s'. Valid options are: 'service_principal', 'managed_identity', or 'workload_identity'", + ctx->auth_type_str); flb_az_li_ctx_destroy(ctx); return NULL; } @@ -91,16 +105,43 @@ struct flb_az_li* flb_az_li_ctx_create(struct flb_output_instance *ins, return NULL; } - /* Allocate and set auth url */ - ctx->auth_url = flb_sds_create_size(sizeof(FLB_AZ_LI_AUTH_URL_TMPLT) - 1 + - flb_sds_len(ctx->tenant_id)); - if (!ctx->auth_url) { - flb_errno(); - flb_az_li_ctx_destroy(ctx); - return NULL; + /* Allocate and set auth url based on authentication method */ + if (ctx->auth_type == FLB_AZ_LI_AUTH_MANAGED_IDENTITY_SYSTEM) { + /* System-assigned managed identity */ + ctx->auth_url = flb_sds_create_size(sizeof(FLB_AZ_LI_MSIAUTH_URL_TEMPLATE) - 1); + if (!ctx->auth_url) { + flb_errno(); + flb_az_li_ctx_destroy(ctx); + return NULL; + } + flb_sds_snprintf(&ctx->auth_url, flb_sds_alloc(ctx->auth_url), + FLB_AZ_LI_MSIAUTH_URL_TEMPLATE, "", ""); + } + else if (ctx->auth_type == FLB_AZ_LI_AUTH_MANAGED_IDENTITY_USER) { + /* User-assigned managed identity */ + ctx->auth_url = flb_sds_create_size(sizeof(FLB_AZ_LI_MSIAUTH_URL_TEMPLATE) - 1 + + sizeof("&client_id=") - 1 + + flb_sds_len(ctx->client_id)); + if (!ctx->auth_url) { + flb_errno(); + flb_az_li_ctx_destroy(ctx); + return NULL; + } + flb_sds_snprintf(&ctx->auth_url, flb_sds_alloc(ctx->auth_url), + FLB_AZ_LI_MSIAUTH_URL_TEMPLATE, "&client_id=", ctx->client_id); + } + else { + /* Service principal authentication */ + ctx->auth_url = flb_sds_create_size(sizeof(FLB_AZ_LI_AUTH_URL_TMPLT) - 1 + + flb_sds_len(ctx->tenant_id)); + if (!ctx->auth_url) { + flb_errno(); + flb_az_li_ctx_destroy(ctx); + return NULL; + } + flb_sds_snprintf(&ctx->auth_url, flb_sds_alloc(ctx->auth_url), + FLB_AZ_LI_AUTH_URL_TMPLT, ctx->tenant_id); } - flb_sds_snprintf(&ctx->auth_url, flb_sds_alloc(ctx->auth_url), - FLB_AZ_LI_AUTH_URL_TMPLT, ctx->tenant_id); /* Allocate and set dce full url */ ctx->dce_u_url = flb_sds_create_size(sizeof(FLB_AZ_LI_DCE_URL_TMPLT) - 1 + diff --git a/plugins/out_azure_logs_ingestion/azure_logs_ingestion_msiauth.c b/plugins/out_azure_logs_ingestion/azure_logs_ingestion_msiauth.c new file mode 100644 index 00000000000..edaca8db1dc --- /dev/null +++ b/plugins/out_azure_logs_ingestion/azure_logs_ingestion_msiauth.c @@ -0,0 +1,99 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2024 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 +#include + +#include "azure_logs_ingestion_msiauth.h" + +char *flb_azure_li_msiauth_token_get(struct flb_oauth2 *ctx) +{ + int ret; + size_t b_sent; + time_t now; + struct flb_connection *u_conn; + struct flb_http_client *c; + + now = time(NULL); + if (ctx->access_token) { + /* validate unexpired token */ + if (ctx->expires > now && flb_sds_len(ctx->access_token) > 0) { + return ctx->access_token; + } + } + + /* Get Token and store it in the context */ + u_conn = flb_upstream_conn_get(ctx->u); + if (!u_conn) { + flb_error("[azure li msi auth] could not get an upstream connection to %s:%i", + ctx->u->tcp_host, ctx->u->tcp_port); + return NULL; + } + + /* Create HTTP client context */ + c = flb_http_client(u_conn, FLB_HTTP_GET, ctx->uri, + NULL, 0, + ctx->host, atoi(ctx->port), + NULL, 0); + if (!c) { + flb_error("[azure li msi auth] error creating HTTP client context"); + flb_upstream_conn_release(u_conn); + return NULL; + } + + /* Append HTTP Header */ + flb_http_add_header(c, "Metadata", 8, "true", 4); + + /* Issue request */ + ret = flb_http_do(c, &b_sent); + if (ret != 0) { + flb_warn("[azure li msi auth] cannot issue request, http_do=%i", ret); + } + else { + flb_info("[azure li msi auth] HTTP Status=%i", c->resp.status); + if (c->resp.payload_size > 0 && c->resp.status != 200) { + flb_info("[azure li msi auth] payload:\n%s", c->resp.payload); + } + } + + /* Extract token */ + if (c->resp.payload_size > 0 && c->resp.status == 200) { + ret = flb_oauth2_parse_json_response(c->resp.payload, + c->resp.payload_size, ctx); + if (ret == 0) { + flb_info("[azure li msi auth] access token from '%s:%s' retrieved", + ctx->host, ctx->port); + flb_http_client_destroy(c); + flb_upstream_conn_release(u_conn); + ctx->issued = time(NULL); + ctx->expires = ctx->issued + ctx->expires_in; + return ctx->access_token; + } + } + + flb_http_client_destroy(c); + flb_upstream_conn_release(u_conn); + + return NULL; +} diff --git a/plugins/out_azure_logs_ingestion/azure_logs_ingestion_msiauth.h b/plugins/out_azure_logs_ingestion/azure_logs_ingestion_msiauth.h new file mode 100644 index 00000000000..4950c42065a --- /dev/null +++ b/plugins/out_azure_logs_ingestion/azure_logs_ingestion_msiauth.h @@ -0,0 +1,27 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2024 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 + +/* MSI authorization URL template */ +#define FLB_AZ_LI_MSIAUTH_URL_TEMPLATE \ + "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2021-02-01%s%s&resource=https://monitor.azure.com" + +char *flb_azure_li_msiauth_token_get(struct flb_oauth2 *ctx); +int flb_azure_kusto_conf_destroy(struct flb_az_li *ctx); diff --git a/tests/runtime/CMakeLists.txt b/tests/runtime/CMakeLists.txt index 01838c68d27..9d938fb18aa 100644 --- a/tests/runtime/CMakeLists.txt +++ b/tests/runtime/CMakeLists.txt @@ -204,6 +204,7 @@ if(FLB_IN_LIB) FLB_RT_TEST(FLB_OUT_LIB "config_map_opts.c") FLB_RT_TEST(FLB_OUT_COUNTER "out_counter.c") FLB_RT_TEST(FLB_OUT_AZURE_KUSTO "out_azure_kusto.c") + FLB_RT_TEST(FLB_OUT_AZURE_LOGS_INGESTION "out_azure_logs_ingestion.c") FLB_RT_TEST(FLB_OUT_DATADOG "out_datadog.c") FLB_RT_TEST(FLB_OUT_SKYWALKING "out_skywalking.c") FLB_RT_TEST(FLB_OUT_ES "out_elasticsearch.c") diff --git a/tests/runtime/out_azure_logs_ingestion.c b/tests/runtime/out_azure_logs_ingestion.c new file mode 100644 index 00000000000..8461a78eda7 --- /dev/null +++ b/tests/runtime/out_azure_logs_ingestion.c @@ -0,0 +1,377 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2024 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 "flb_tests_runtime.h" + +/* Test data */ +#include "data/common/json_invalid.h" /* JSON_INVALID */ + +/* Test functions */ +void flb_test_azure_logs_ingestion_json_invalid(void); +void flb_test_azure_logs_ingestion_managed_identity_system(void); +void flb_test_azure_logs_ingestion_managed_identity_user(void); +void flb_test_azure_logs_ingestion_managed_identity_missing_client_id(void); +void flb_test_azure_logs_ingestion_service_principal_explicit(void); +void flb_test_azure_logs_ingestion_service_principal_default(void); +void flb_test_azure_logs_ingestion_service_principal_missing_client_id(void); +void flb_test_azure_logs_ingestion_service_principal_missing_client_secret(void); +void flb_test_azure_logs_ingestion_service_principal_missing_tenant_id(void); +void flb_test_azure_logs_ingestion_invalid_auth_type(void); + +/* Test list */ +TEST_LIST = { + {"json_invalid", flb_test_azure_logs_ingestion_json_invalid}, + {"managed_identity_system", flb_test_azure_logs_ingestion_managed_identity_system}, + {"managed_identity_user", flb_test_azure_logs_ingestion_managed_identity_user}, + {"managed_identity_missing_client_id", flb_test_azure_logs_ingestion_managed_identity_missing_client_id}, + {"service_principal_explicit", flb_test_azure_logs_ingestion_service_principal_explicit}, + {"service_principal_default", flb_test_azure_logs_ingestion_service_principal_default}, + {"service_principal_missing_client_id", flb_test_azure_logs_ingestion_service_principal_missing_client_id}, + {"service_principal_missing_client_secret", flb_test_azure_logs_ingestion_service_principal_missing_client_secret}, + {"service_principal_missing_tenant_id", flb_test_azure_logs_ingestion_service_principal_missing_tenant_id}, + {"auth_type_invalid", flb_test_azure_logs_ingestion_invalid_auth_type}, + {NULL, NULL} +}; + +void flb_test_azure_logs_ingestion_json_invalid(void) +{ + int i; + int ret; + int total; + int bytes; + char *p = (char *) JSON_INVALID; + flb_ctx_t *ctx; + int in_ffd; + int out_ffd; + + ctx = flb_create(); + flb_service_set(ctx, "Flush", "1", "Grace", "1", "Log_Level", "error", NULL); + + in_ffd = flb_input(ctx, (char *) "lib", NULL); + TEST_CHECK(in_ffd >= 0); + flb_input_set(ctx, in_ffd, "tag", "test", NULL); + + out_ffd = flb_output(ctx, (char *) "azure_logs_ingestion", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "test", NULL); + flb_output_set(ctx, out_ffd, "auth_type", "managed_identity", NULL); + flb_output_set(ctx, out_ffd, "client_id", "system", NULL); + flb_output_set(ctx, out_ffd, "dce_url", "https://test-dce.eastus-1.ingest.monitor.azure.com", NULL); + flb_output_set(ctx, out_ffd, "dcr_id", "dcr-00000000000000000000000000000000", NULL); + flb_output_set(ctx, out_ffd, "table_name", "TestTable_CL", NULL); + + ret = flb_start(ctx); + TEST_CHECK(ret == 0); + + total = 0; + for (i = 0; i < (int) sizeof(JSON_INVALID) - 1; i++) { + bytes = flb_lib_push(ctx, in_ffd, p + i, 1); + TEST_CHECK(bytes == 1); + total++; + } + + sleep(1); /* waiting flush */ + + flb_stop(ctx); + flb_destroy(ctx); +} + +/* Test for system-assigned managed identity */ +void flb_test_azure_logs_ingestion_managed_identity_system(void) +{ + int ret; + flb_ctx_t *ctx; + int in_ffd; + int out_ffd; + + ctx = flb_create(); + flb_service_set(ctx, "Flush", "1", "Grace", "1", "Log_Level", "error", NULL); + + in_ffd = flb_input(ctx, (char *) "lib", NULL); + TEST_CHECK(in_ffd >= 0); + flb_input_set(ctx, in_ffd, "tag", "test", NULL); + + out_ffd = flb_output(ctx, (char *) "azure_logs_ingestion", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "test", NULL); + flb_output_set(ctx, out_ffd, "auth_type", "managed_identity", NULL); + flb_output_set(ctx, out_ffd, "client_id", "system", NULL); + flb_output_set(ctx, out_ffd, "dce_url", "https://test-dce.eastus-1.ingest.monitor.azure.com", NULL); + flb_output_set(ctx, out_ffd, "dcr_id", "dcr-00000000000000000000000000000000", NULL); + flb_output_set(ctx, out_ffd, "table_name", "TestTable_CL", NULL); + + ret = flb_start(ctx); + TEST_CHECK(ret == 0); + + flb_stop(ctx); + flb_destroy(ctx); +} + +/* Test for user-assigned managed identity */ +void flb_test_azure_logs_ingestion_managed_identity_user(void) +{ + int ret; + flb_ctx_t *ctx; + int in_ffd; + int out_ffd; + + ctx = flb_create(); + flb_service_set(ctx, "Flush", "1", "Grace", "1", "Log_Level", "error", NULL); + + in_ffd = flb_input(ctx, (char *) "lib", NULL); + TEST_CHECK(in_ffd >= 0); + flb_input_set(ctx, in_ffd, "tag", "test", NULL); + + out_ffd = flb_output(ctx, (char *) "azure_logs_ingestion", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "test", NULL); + flb_output_set(ctx, out_ffd, "auth_type", "managed_identity", NULL); + flb_output_set(ctx, out_ffd, "client_id", "00000000-0000-0000-0000-000000000000", NULL); /* Example UUID */ + flb_output_set(ctx, out_ffd, "dce_url", "https://test-dce.eastus-1.ingest.monitor.azure.com", NULL); + flb_output_set(ctx, out_ffd, "dcr_id", "dcr-00000000000000000000000000000000", NULL); + flb_output_set(ctx, out_ffd, "table_name", "TestTable_CL", NULL); + + ret = flb_start(ctx); + TEST_CHECK(ret == 0); + + flb_stop(ctx); + flb_destroy(ctx); +} + +/* Test for failure if user-assigned managed identity is used without client_id*/ +void flb_test_azure_logs_ingestion_managed_identity_missing_client_id(void) +{ + int ret; + flb_ctx_t *ctx; + int in_ffd; + int out_ffd; + + ctx = flb_create(); + flb_service_set(ctx, "Flush", "1", "Grace", "1", "Log_Level", "error", NULL); + + in_ffd = flb_input(ctx, (char *) "lib", NULL); + TEST_CHECK(in_ffd >= 0); + flb_input_set(ctx, in_ffd, "tag", "test", NULL); + + out_ffd = flb_output(ctx, (char *) "azure_logs_ingestion", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "test", NULL); + flb_output_set(ctx, out_ffd, "auth_type", "managed_identity", NULL); + flb_output_set(ctx, out_ffd, "dce_url", "https://test-dce.eastus-1.ingest.monitor.azure.com", NULL); + flb_output_set(ctx, out_ffd, "dcr_id", "dcr-00000000000000000000000000000000", NULL); + flb_output_set(ctx, out_ffd, "table_name", "TestTable_CL", NULL); + + ret = flb_start(ctx); + TEST_CHECK(ret != 0); + + flb_stop(ctx); + flb_destroy(ctx); +} + +/* Test for service principal authentication with explicit auth_type*/ +void flb_test_azure_logs_ingestion_service_principal_explicit(void) +{ + int ret; + flb_ctx_t *ctx; + int in_ffd; + int out_ffd; + + ctx = flb_create(); + flb_service_set(ctx, "Flush", "1", "Grace", "1", "Log_Level", "error", NULL); + + in_ffd = flb_input(ctx, (char *) "lib", NULL); + TEST_CHECK(in_ffd >= 0); + flb_input_set(ctx, in_ffd, "tag", "test", NULL); + + out_ffd = flb_output(ctx, (char *) "azure_logs_ingestion", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "test", NULL); + flb_output_set(ctx, out_ffd, "auth_type", "service_principal", NULL); + flb_output_set(ctx, out_ffd, "tenant_id", "your-tenant-id", NULL); + flb_output_set(ctx, out_ffd, "client_id", "your-client-id", NULL); + flb_output_set(ctx, out_ffd, "client_secret", "your-client-secret", NULL); + flb_output_set(ctx, out_ffd, "dce_url", "https://test-dce.eastus-1.ingest.monitor.azure.com", NULL); + flb_output_set(ctx, out_ffd, "dcr_id", "dcr-00000000000000000000000000000000", NULL); + flb_output_set(ctx, out_ffd, "table_name", "TestTable_CL", NULL); + + ret = flb_start(ctx); + TEST_CHECK(ret == 0); + + flb_stop(ctx); + flb_destroy(ctx); +} + +/* Test for default service principal authentication with empty auth_type*/ +void flb_test_azure_logs_ingestion_service_principal_default(void) +{ + int ret; + flb_ctx_t *ctx; + int in_ffd; + int out_ffd; + + ctx = flb_create(); + flb_service_set(ctx, "Flush", "1", "Grace", "1", "Log_Level", "error", NULL); + + in_ffd = flb_input(ctx, (char *) "lib", NULL); + TEST_CHECK(in_ffd >= 0); + flb_input_set(ctx, in_ffd, "tag", "test", NULL); + + out_ffd = flb_output(ctx, (char *) "azure_logs_ingestion", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "test", NULL); + flb_output_set(ctx, out_ffd, "tenant_id", "your-tenant-id", NULL); + flb_output_set(ctx, out_ffd, "client_id", "your-client-id", NULL); + flb_output_set(ctx, out_ffd, "client_secret", "your-client-secret", NULL); + flb_output_set(ctx, out_ffd, "dce_url", "https://test-dce.eastus-1.ingest.monitor.azure.com", NULL); + flb_output_set(ctx, out_ffd, "dcr_id", "dcr-00000000000000000000000000000000", NULL); + flb_output_set(ctx, out_ffd, "table_name", "TestTable_CL", NULL); + + ret = flb_start(ctx); + TEST_CHECK(ret == 0); + + flb_stop(ctx); + flb_destroy(ctx); +} + +/* Test for failure in case of missing client_id for auth_type service_principal*/ +void flb_test_azure_logs_ingestion_service_principal_missing_client_id(void) +{ + int ret; + flb_ctx_t *ctx; + int in_ffd; + int out_ffd; + + ctx = flb_create(); + flb_service_set(ctx, "Flush", "1", "Grace", "1", "Log_Level", "error", NULL); + + in_ffd = flb_input(ctx, (char *) "lib", NULL); + TEST_CHECK(in_ffd >= 0); + flb_input_set(ctx, in_ffd, "tag", "test", NULL); + + out_ffd = flb_output(ctx, (char *) "azure_logs_ingestion", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "test", NULL); + flb_output_set(ctx, out_ffd, "tenant_id", "your-tenant-id", NULL); + flb_output_set(ctx, out_ffd, "client_secret", "your-client-secret", NULL); + flb_output_set(ctx, out_ffd, "dce_url", "https://test-dce.eastus-1.ingest.monitor.azure.com", NULL); + flb_output_set(ctx, out_ffd, "dcr_id", "dcr-00000000000000000000000000000000", NULL); + flb_output_set(ctx, out_ffd, "table_name", "TestTable_CL", NULL); + + ret = flb_start(ctx); + TEST_CHECK(ret != 0); + + flb_stop(ctx); + flb_destroy(ctx); +} + +/* Test for failure in case of missing client_secret for auth_type service_principal*/ +void flb_test_azure_logs_ingestion_service_principal_missing_client_secret(void) +{ + int ret; + flb_ctx_t *ctx; + int in_ffd; + int out_ffd; + + ctx = flb_create(); + flb_service_set(ctx, "Flush", "1", "Grace", "1", "Log_Level", "error", NULL); + + in_ffd = flb_input(ctx, (char *) "lib", NULL); + TEST_CHECK(in_ffd >= 0); + flb_input_set(ctx, in_ffd, "tag", "test", NULL); + + out_ffd = flb_output(ctx, (char *) "azure_logs_ingestion", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "test", NULL); + flb_output_set(ctx, out_ffd, "client_id", "your-client-id", NULL); + flb_output_set(ctx, out_ffd, "tenant_id", "your-tenant-id", NULL); + flb_output_set(ctx, out_ffd, "dce_url", "https://test-dce.eastus-1.ingest.monitor.azure.com", NULL); + flb_output_set(ctx, out_ffd, "dcr_id", "dcr-00000000000000000000000000000000", NULL); + flb_output_set(ctx, out_ffd, "table_name", "TestTable_CL", NULL); + + ret = flb_start(ctx); + TEST_CHECK(ret != 0); + + flb_stop(ctx); + flb_destroy(ctx); +} + +/* Test for failure in case of missing tenant_id for auth_type service_principal*/ +void flb_test_azure_logs_ingestion_service_principal_missing_tenant_id(void) +{ + int ret; + flb_ctx_t *ctx; + int in_ffd; + int out_ffd; + + ctx = flb_create(); + flb_service_set(ctx, "Flush", "1", "Grace", "1", "Log_Level", "error", NULL); + + in_ffd = flb_input(ctx, (char *) "lib", NULL); + TEST_CHECK(in_ffd >= 0); + flb_input_set(ctx, in_ffd, "tag", "test", NULL); + + out_ffd = flb_output(ctx, (char *) "azure_logs_ingestion", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "test", NULL); + flb_output_set(ctx, out_ffd, "client_id", "your-client-id", NULL); + flb_output_set(ctx, out_ffd, "client_secret", "your-client-secret", NULL); + flb_output_set(ctx, out_ffd, "dce_url", "https://test-dce.eastus-1.ingest.monitor.azure.com", NULL); + flb_output_set(ctx, out_ffd, "dcr_id", "dcr-00000000000000000000000000000000", NULL); + flb_output_set(ctx, out_ffd, "table_name", "TestTable_CL", NULL); + + ret = flb_start(ctx); + TEST_CHECK(ret != 0); + + flb_stop(ctx); + flb_destroy(ctx); +} + +/* Test for failure in case of invalid auth_type*/ +void flb_test_azure_logs_ingestion_invalid_auth_type(void) +{ + int ret; + flb_ctx_t *ctx; + int in_ffd; + int out_ffd; + + ctx = flb_create(); + flb_service_set(ctx, "Flush", "1", "Grace", "1", "Log_Level", "error", NULL); + + in_ffd = flb_input(ctx, (char *) "lib", NULL); + TEST_CHECK(in_ffd >= 0); + flb_input_set(ctx, in_ffd, "tag", "test", NULL); + + out_ffd = flb_output(ctx, (char *) "azure_logs_ingestion", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "test", NULL); + flb_output_set(ctx, out_ffd, "auth_type", "INVALID", NULL); + flb_output_set(ctx, out_ffd, "client_id", "your-client-id", NULL); + flb_output_set(ctx, out_ffd, "client_secret", "your-client-secret", NULL); + flb_output_set(ctx, out_ffd, "tenant_id", "your-tenant-id", NULL); + flb_output_set(ctx, out_ffd, "dce_url", "https://test-dce.eastus-1.ingest.monitor.azure.com", NULL); + flb_output_set(ctx, out_ffd, "dcr_id", "dcr-00000000000000000000000000000000", NULL); + flb_output_set(ctx, out_ffd, "table_name", "TestTable_CL", NULL); + + ret = flb_start(ctx); + TEST_CHECK(ret != 0); + + flb_stop(ctx); + flb_destroy(ctx); +} From a3ab6298297e2fd2827ff9ff71ecbbb517bec023 Mon Sep 17 00:00:00 2001 From: Stefano Boriero Date: Wed, 10 Sep 2025 14:46:00 +0200 Subject: [PATCH 2/4] out_azure_logs_ingestion: remove unsupported option from log line Signed-off-by: Stefano Boriero --- plugins/out_azure_logs_ingestion/azure_logs_ingestion_conf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/out_azure_logs_ingestion/azure_logs_ingestion_conf.c b/plugins/out_azure_logs_ingestion/azure_logs_ingestion_conf.c index 2488a6ba0ae..c8e773f47fe 100644 --- a/plugins/out_azure_logs_ingestion/azure_logs_ingestion_conf.c +++ b/plugins/out_azure_logs_ingestion/azure_logs_ingestion_conf.c @@ -81,7 +81,7 @@ struct flb_az_li* flb_az_li_ctx_create(struct flb_output_instance *ins, } } else { - flb_plg_error(ins, "Invalid auth_type '%s'. Valid options are: 'service_principal', 'managed_identity', or 'workload_identity'", + flb_plg_error(ins, "Invalid auth_type '%s'. Valid options are: 'service_principal' or 'managed_identity'", ctx->auth_type_str); flb_az_li_ctx_destroy(ctx); return NULL; From c2caad62938c06579a0caba66cf0b04e746787ba Mon Sep 17 00:00:00 2001 From: Stefano Boriero Date: Mon, 15 Sep 2025 10:51:18 +0200 Subject: [PATCH 3/4] out_azure_logs_ingestion: safeguard against null auth_type Fail fast in case auth_type is null, and default to "service_principal" if empty. Defer the assignment of the context after all validation of configuration has taken place. Signed-off-by: Stefano Boriero --- .../azure_logs_ingestion_conf.c | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/plugins/out_azure_logs_ingestion/azure_logs_ingestion_conf.c b/plugins/out_azure_logs_ingestion/azure_logs_ingestion_conf.c index c8e773f47fe..e5835d9b9ee 100644 --- a/plugins/out_azure_logs_ingestion/azure_logs_ingestion_conf.c +++ b/plugins/out_azure_logs_ingestion/azure_logs_ingestion_conf.c @@ -42,11 +42,9 @@ struct flb_az_li* flb_az_li_ctx_create(struct flb_output_instance *ins, return NULL; } - /* Set the conext in output_instance so that we can retrieve it later */ + /* Set the context in output_instance so that we can retrieve it later */ ctx->ins = ins; ctx->config = config; - /* Set context */ - flb_output_set_context(ins, ctx); /* Load config map */ ret = flb_output_config_map_set(ins, (void *) ctx); @@ -56,7 +54,18 @@ struct flb_az_li* flb_az_li_ctx_create(struct flb_output_instance *ins, } /* Auth method validation and setup */ - if (strcasecmp(ctx->auth_type_str, "service_principal") == 0) { + if (!ctx->auth_type_str || strlen(ctx->auth_type_str) == 0) { + /* Default to service_principal if auth_type_str is NULL or empty */ + ctx->auth_type = FLB_AZ_LI_AUTH_SERVICE_PRINCIPAL; + + /* Verify required parameters for Service Principal auth */ + if (!ctx->tenant_id || !ctx->client_id || !ctx->client_secret) { + flb_plg_error(ins, "When using service_principal auth, tenant_id, client_id, and client_secret are required"); + flb_az_li_ctx_destroy(ctx); + return NULL; + } + } + else if (strcasecmp(ctx->auth_type_str, "service_principal") == 0) { ctx->auth_type = FLB_AZ_LI_AUTH_SERVICE_PRINCIPAL; /* Verify required parameters for Service Principal auth */ @@ -182,6 +191,9 @@ struct flb_az_li* flb_az_li_ctx_create(struct flb_output_instance *ins, flb_plg_info(ins, "dce_url='%s', dcr='%s', table='%s', stream='Custom-%s'", ctx->dce_url, ctx->dcr_id, ctx->table_name, ctx->table_name); + /* Set context only after all validation and initialization is complete */ + flb_output_set_context(ins, ctx); + return ctx; } From 11e70e92634172d6c57a8f66a2668020b60bd23c Mon Sep 17 00:00:00 2001 From: Stefano Boriero Date: Mon, 15 Sep 2025 11:19:29 +0200 Subject: [PATCH 4/4] out_azure_logs_ingestion: check snprintf return code In case of snprintf failures, report an error and fail early. Signed-off-by: Stefano Boriero --- .../azure_logs_ingestion_conf.c | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/plugins/out_azure_logs_ingestion/azure_logs_ingestion_conf.c b/plugins/out_azure_logs_ingestion/azure_logs_ingestion_conf.c index e5835d9b9ee..225be3cb884 100644 --- a/plugins/out_azure_logs_ingestion/azure_logs_ingestion_conf.c +++ b/plugins/out_azure_logs_ingestion/azure_logs_ingestion_conf.c @@ -123,8 +123,12 @@ struct flb_az_li* flb_az_li_ctx_create(struct flb_output_instance *ins, flb_az_li_ctx_destroy(ctx); return NULL; } - flb_sds_snprintf(&ctx->auth_url, flb_sds_alloc(ctx->auth_url), - FLB_AZ_LI_MSIAUTH_URL_TEMPLATE, "", ""); + if (flb_sds_snprintf(&ctx->auth_url, flb_sds_alloc(ctx->auth_url), + FLB_AZ_LI_MSIAUTH_URL_TEMPLATE, "", "") < 0) { + flb_plg_error(ins, "failed to build auth URL for system-assigned managed identity"); + flb_az_li_ctx_destroy(ctx); + return NULL; + } } else if (ctx->auth_type == FLB_AZ_LI_AUTH_MANAGED_IDENTITY_USER) { /* User-assigned managed identity */ @@ -136,8 +140,12 @@ struct flb_az_li* flb_az_li_ctx_create(struct flb_output_instance *ins, flb_az_li_ctx_destroy(ctx); return NULL; } - flb_sds_snprintf(&ctx->auth_url, flb_sds_alloc(ctx->auth_url), - FLB_AZ_LI_MSIAUTH_URL_TEMPLATE, "&client_id=", ctx->client_id); + if (flb_sds_snprintf(&ctx->auth_url, flb_sds_alloc(ctx->auth_url), + FLB_AZ_LI_MSIAUTH_URL_TEMPLATE, "&client_id=", ctx->client_id) < 0) { + flb_plg_error(ins, "failed to build auth URL for user-assigned managed identity"); + flb_az_li_ctx_destroy(ctx); + return NULL; + } } else { /* Service principal authentication */ @@ -148,8 +156,12 @@ struct flb_az_li* flb_az_li_ctx_create(struct flb_output_instance *ins, flb_az_li_ctx_destroy(ctx); return NULL; } - flb_sds_snprintf(&ctx->auth_url, flb_sds_alloc(ctx->auth_url), - FLB_AZ_LI_AUTH_URL_TMPLT, ctx->tenant_id); + if (flb_sds_snprintf(&ctx->auth_url, flb_sds_alloc(ctx->auth_url), + FLB_AZ_LI_AUTH_URL_TMPLT, ctx->tenant_id) < 0) { + flb_plg_error(ins, "failed to build auth URL for service principal"); + flb_az_li_ctx_destroy(ctx); + return NULL; + } } /* Allocate and set dce full url */ @@ -180,8 +192,7 @@ struct flb_az_li* flb_az_li_ctx_create(struct flb_output_instance *ins, /* Create upstream context for Log Ingsetion endpoint */ ctx->u_dce = flb_upstream_create_url(config, ctx->dce_url, - FLB_AZ_LI_TLS_MODE, ins->tls); - if (!ctx->u_dce) { + FLB_AZ_LI_TLS_MODE, ins->tls); if (!ctx->u_dce) { flb_plg_error(ins, "upstream creation failed"); flb_az_li_ctx_destroy(ctx); return NULL;