diff --git a/include/fluent-bit/flb_azure_auth.h b/include/fluent-bit/flb_azure_auth.h new file mode 100644 index 00000000000..f365194832a --- /dev/null +++ b/include/fluent-bit/flb_azure_auth.h @@ -0,0 +1,107 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2026 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. + */ + +#ifndef FLB_AZURE_AUTH_H +#define FLB_AZURE_AUTH_H + +#include +#include +#include + +/* Authentication types for Azure services */ +typedef enum { + FLB_AZURE_AUTH_KEY = 0, /* Shared Access Key (blob-specific) */ + FLB_AZURE_AUTH_SAS, /* Shared Access Signature (blob-specific) */ + FLB_AZURE_AUTH_SERVICE_PRINCIPAL, /* Service Principal (Client ID + Secret) */ + FLB_AZURE_AUTH_MANAGED_IDENTITY_SYSTEM, /* System-assigned Managed Identity */ + FLB_AZURE_AUTH_MANAGED_IDENTITY_USER, /* User-assigned Managed Identity */ + FLB_AZURE_AUTH_WORKLOAD_IDENTITY /* Workload Identity (Federated Token) */ +} flb_azure_auth_type; + +/* Azure Instance Metadata Service (IMDS) endpoint for Managed Identity */ +#define FLB_AZURE_IMDS_HOST "169.254.169.254" +#define FLB_AZURE_IMDS_PORT "80" + +/* Managed Identity authentication URL template */ +#define FLB_AZURE_MSI_AUTH_URL_TEMPLATE \ + "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2021-02-01%s%s&resource=%s" + +/* Microsoft Authentication Library (MSAL) authorization URL template */ +#define FLB_AZURE_MSAL_AUTH_URL_TEMPLATE \ + "https://login.microsoftonline.com/%s/oauth2/v2.0/token" + +/* Azure Blob Storage resource identifier */ +#define FLB_AZURE_BLOB_RESOURCE "https://storage.azure.com" + +/* Azure Kusto resource identifier */ +#define FLB_AZURE_KUSTO_RESOURCE "https://help.kusto.windows.net" + +/* Default Workload Identity token file path */ +#define FLB_AZURE_WORKLOAD_IDENTITY_TOKEN_FILE \ + "/var/run/secrets/azure/tokens/azure-identity-token" + +/** + * Get an OAuth2 access token using Azure Managed Identity (MSI) + * + * This function retrieves an access token from the Azure Instance Metadata Service (IMDS) + * for use with Azure services. Supports both system-assigned and user-assigned managed identities. + * + * @param ctx OAuth2 context containing connection and token information + * @return Access token string on success, NULL on failure + */ +char *flb_azure_msi_token_get(struct flb_oauth2 *ctx); + +/** + * Get an OAuth2 access token using Azure Workload Identity + * + * This function exchanges a federated token (JWT) for an Azure AD access token + * using the OAuth2 client credentials flow with client assertion. + * + * @param ctx OAuth2 context for token management + * @param token_file Path to the file containing the federated token + * @param client_id Client ID of the Azure AD application + * @param tenant_id Tenant ID of the Azure AD directory + * @param resource Resource scope for the token (e.g., "https://storage.azure.com/") + * @return 0 on success, -1 on failure + */ +int flb_azure_workload_identity_token_get(struct flb_oauth2 *ctx, + const char *token_file, + const char *client_id, + const char *tenant_id, + const char *resource); + +/** + * Build OAuth URL for Azure authentication + * + * Creates the appropriate OAuth2 endpoint URL based on the authentication type. + * For Managed Identity, uses IMDS endpoint. For Service Principal and Workload Identity, + * uses Azure AD OAuth2 endpoint. + * + * @param auth_type Type of authentication to use + * @param tenant_id Azure AD tenant ID (required for Service Principal and Workload Identity) + * @param client_id Client ID (optional, used for user-assigned managed identity) + * @param resource Resource scope for the token (e.g., storage.azure.com) + * @return Allocated SDS string with OAuth URL, or NULL on failure + */ +flb_sds_t flb_azure_auth_build_oauth_url(flb_azure_auth_type auth_type, + const char *tenant_id, + const char *client_id, + const char *resource); + +#endif /* FLB_AZURE_AUTH_H */ diff --git a/plugins/out_azure_blob/azure_blob.c b/plugins/out_azure_blob/azure_blob.c index beae29d8880..a84e645c3c3 100644 --- a/plugins/out_azure_blob/azure_blob.c +++ b/plugins/out_azure_blob/azure_blob.c @@ -1826,7 +1826,7 @@ static struct flb_config_map config_map[] = { { FLB_CONFIG_MAP_STR, "auth_type", "key", 0, FLB_TRUE, offsetof(struct flb_azure_blob, auth_type), - "Set the auth type: key or sas" + "Set the auth type: key, sas, service_principal, managed_identity, or workload_identity" }, { @@ -1835,6 +1835,31 @@ static struct flb_config_map config_map[] = { "Azure Blob SAS token" }, + /* OAuth authentication parameters */ + { + FLB_CONFIG_MAP_STR, "tenant_id", NULL, + 0, FLB_TRUE, offsetof(struct flb_azure_blob, tenant_id), + "Azure AD tenant ID (required for service_principal and workload_identity auth)" + }, + + { + FLB_CONFIG_MAP_STR, "client_id", NULL, + 0, FLB_TRUE, offsetof(struct flb_azure_blob, client_id), + "Azure AD client ID / Application ID (required for OAuth-based authentication)" + }, + + { + FLB_CONFIG_MAP_STR, "client_secret", NULL, + 0, FLB_TRUE, offsetof(struct flb_azure_blob, client_secret), + "Azure AD client secret (required for service_principal auth)" + }, + + { + FLB_CONFIG_MAP_STR, "workload_identity_token_file", NULL, + 0, FLB_TRUE, offsetof(struct flb_azure_blob, workload_identity_token_file), + "Path to workload identity token file (for workload_identity auth)" + }, + { FLB_CONFIG_MAP_STR, "database_file", NULL, 0, FLB_TRUE, offsetof(struct flb_azure_blob, database_file), diff --git a/plugins/out_azure_blob/azure_blob.h b/plugins/out_azure_blob/azure_blob.h index 421a5027a17..af0bb71bfa4 100644 --- a/plugins/out_azure_blob/azure_blob.h +++ b/plugins/out_azure_blob/azure_blob.h @@ -24,6 +24,10 @@ #include #include #include +#include +#ifdef FLB_HAVE_TLS +#include +#endif /* Content-Type */ #define AZURE_BLOB_CT "Content-Type" @@ -48,9 +52,6 @@ #define AZURE_BLOB_APPENDBLOB 0 #define AZURE_BLOB_BLOCKBLOB 1 -#define AZURE_BLOB_AUTH_KEY 0 -#define AZURE_BLOB_AUTH_SAS 1 - struct flb_azure_blob { int auto_create_container; int emulator_mode; @@ -65,6 +66,13 @@ struct flb_azure_blob { flb_sds_t date_key; flb_sds_t auth_type; flb_sds_t sas_token; + + /* OAuth authentication fields */ + flb_sds_t tenant_id; + flb_sds_t client_id; + flb_sds_t client_secret; + flb_sds_t workload_identity_token_file; + flb_sds_t database_file; size_t part_size; time_t upload_parts_timeout; @@ -112,7 +120,7 @@ struct flb_azure_blob { * Internal use */ int btype; /* blob type */ - int atype; /* auth type */ + flb_azure_auth_type atype; /* auth type (uses flb_azure_auth_type enum from flb_azure_auth.h) */ flb_sds_t real_endpoint; flb_sds_t base_uri; flb_sds_t shared_key_prefix; @@ -121,6 +129,13 @@ struct flb_azure_blob { unsigned char *decoded_sk; /* decoded shared key */ size_t decoded_sk_size; /* size of decoded shared key */ +#ifdef FLB_HAVE_TLS + /* OAuth2 authentication */ + flb_sds_t oauth_url; + struct flb_oauth2 *o; + pthread_mutex_t token_mutex; +#endif + #ifdef FLB_HAVE_SQLDB /* * SQLite by default is not built with multi-threading enabled, and diff --git a/plugins/out_azure_blob/azure_blob_appendblob.c b/plugins/out_azure_blob/azure_blob_appendblob.c index 966e4dc99a9..5b030814b1a 100644 --- a/plugins/out_azure_blob/azure_blob_appendblob.c +++ b/plugins/out_azure_blob/azure_blob_appendblob.c @@ -40,7 +40,7 @@ flb_sds_t azb_append_blob_uri(struct flb_azure_blob *ctx, char *tag) flb_sds_printf(&uri, "/%s?comp=appendblock", tag); } - if (ctx->atype == AZURE_BLOB_AUTH_SAS && ctx->sas_token) { + if (ctx->atype == FLB_AZURE_AUTH_SAS && ctx->sas_token) { flb_sds_printf(&uri, "&%s", ctx->sas_token); } diff --git a/plugins/out_azure_blob/azure_blob_blockblob.c b/plugins/out_azure_blob/azure_blob_blockblob.c index dd4ae4ec8a9..4d125777b39 100644 --- a/plugins/out_azure_blob/azure_blob_blockblob.c +++ b/plugins/out_azure_blob/azure_blob_blockblob.c @@ -48,7 +48,7 @@ flb_sds_t azb_block_blob_blocklist_uri(struct flb_azure_blob *ctx, char *name) flb_sds_printf(&uri, "/%s?comp=blocklist", name); } - if (ctx->atype == AZURE_BLOB_AUTH_SAS && ctx->sas_token) { + if (ctx->atype == FLB_AZURE_AUTH_SAS && ctx->sas_token) { flb_sds_printf(&uri, "&%s", ctx->sas_token); } @@ -103,7 +103,7 @@ flb_sds_t azb_block_blob_uri(struct flb_azure_blob *ctx, char *name, } } - if (ctx->atype == AZURE_BLOB_AUTH_SAS && ctx->sas_token) { + if (ctx->atype == FLB_AZURE_AUTH_SAS && ctx->sas_token) { flb_sds_printf(&uri, "&%s", ctx->sas_token); } @@ -137,7 +137,7 @@ flb_sds_t azb_block_blob_uri_commit(struct flb_azure_blob *ctx, flb_sds_printf(&uri, "/%s.%s.%" PRIu64 "%s?comp=blocklist", tag, str, ms, ext); } - if (ctx->atype == AZURE_BLOB_AUTH_SAS && ctx->sas_token) { + if (ctx->atype == FLB_AZURE_AUTH_SAS && ctx->sas_token) { flb_sds_printf(&uri, "&%s", ctx->sas_token); } diff --git a/plugins/out_azure_blob/azure_blob_conf.c b/plugins/out_azure_blob/azure_blob_conf.c index fa806ae69a5..6921f294fd4 100644 --- a/plugins/out_azure_blob/azure_blob_conf.c +++ b/plugins/out_azure_blob/azure_blob_conf.c @@ -213,7 +213,7 @@ static int flb_azure_blob_process_remote_configuration_payload( context->endpoint_overriden_flag = FLB_TRUE; - if (context->atype == AZURE_BLOB_AUTH_KEY) { + if (context->atype == FLB_AZURE_AUTH_KEY) { value_backup = context->shared_key; context->shared_key = NULL; @@ -233,7 +233,7 @@ static int flb_azure_blob_process_remote_configuration_payload( context->shared_key_overriden_flag = FLB_TRUE; } - else if (context->atype == AZURE_BLOB_AUTH_SAS) { + else if (context->atype == FLB_AZURE_AUTH_SAS) { value_backup = context->sas_token; context->sas_token = NULL; @@ -590,27 +590,80 @@ struct flb_azure_blob *flb_azure_blob_conf_create(struct flb_output_instance *in /* Set Auth type */ tmp = (char *) flb_output_get_property("auth_type", ins); if (!tmp) { - ctx->atype = AZURE_BLOB_AUTH_KEY; + ctx->atype = FLB_AZURE_AUTH_KEY; /* Default to legacy key auth */ } else { if (strcasecmp(tmp, "key") == 0) { - ctx->atype = AZURE_BLOB_AUTH_KEY; + ctx->atype = FLB_AZURE_AUTH_KEY; } else if (strcasecmp(tmp, "sas") == 0) { - ctx->atype = AZURE_BLOB_AUTH_SAS; + ctx->atype = FLB_AZURE_AUTH_SAS; + } +#ifdef FLB_HAVE_TLS + else if (strcasecmp(tmp, "service_principal") == 0) { + ctx->atype = FLB_AZURE_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"); + return NULL; + } + } + else if (strcasecmp(tmp, "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"); + return NULL; + } + + if (strcasecmp(ctx->client_id, "system") == 0) { + ctx->atype = FLB_AZURE_AUTH_MANAGED_IDENTITY_SYSTEM; + } else { + ctx->atype = FLB_AZURE_AUTH_MANAGED_IDENTITY_USER; + } } + else if (strcasecmp(tmp, "workload_identity") == 0) { + ctx->atype = FLB_AZURE_AUTH_WORKLOAD_IDENTITY; + + /* Verify required parameters for Workload Identity auth */ + if (!ctx->tenant_id || !ctx->client_id) { + flb_plg_error(ins, "When using workload_identity auth, tenant_id and client_id are required"); + return NULL; + } + + /* Set default token file path if not specified */ + if (!ctx->workload_identity_token_file) { + ctx->workload_identity_token_file = flb_sds_create(FLB_AZURE_WORKLOAD_IDENTITY_TOKEN_FILE); + if (!ctx->workload_identity_token_file) { + flb_errno(); + flb_plg_error(ins, "Could not allocate default workload identity token path"); + return NULL; + } + } + } +#else + else if (strcasecmp(tmp, "service_principal") == 0 || + strcasecmp(tmp, "managed_identity") == 0 || + strcasecmp(tmp, "workload_identity") == 0) { + flb_plg_error(ctx->ins, "OAuth authentication requires TLS support. "\ + "Rebuild with -DFLB_TLS=ON"); + return NULL; + } +#endif else { - flb_plg_error(ctx->ins, "invalid auth_type value '%s'", tmp); + flb_plg_error(ctx->ins, "invalid auth_type value '%s'. Valid options are: 'key', 'sas', 'service_principal', 'managed_identity', or 'workload_identity'", tmp); return NULL; } } - if (ctx->atype == AZURE_BLOB_AUTH_KEY && + + /* Validate auth-specific requirements */ + if (ctx->atype == FLB_AZURE_AUTH_KEY && ctx->shared_key == NULL) { flb_plg_error(ctx->ins, "'shared_key' has not been set"); return NULL; } - if (ctx->atype == AZURE_BLOB_AUTH_SAS) { + if (ctx->atype == FLB_AZURE_AUTH_SAS) { if (ctx->sas_token == NULL) { flb_plg_error(ctx->ins, "'sas_token' has not been set"); return NULL; @@ -621,7 +674,7 @@ struct flb_azure_blob *flb_azure_blob_conf_create(struct flb_output_instance *in } /* If the shared key is set decode it */ - if (ctx->atype == AZURE_BLOB_AUTH_KEY && + if (ctx->atype == FLB_AZURE_AUTH_KEY && ctx->shared_key != NULL) { ret = set_shared_key(ctx); if (ret == -1) { @@ -730,6 +783,37 @@ struct flb_azure_blob *flb_azure_blob_conf_create(struct flb_output_instance *in } flb_output_upstream_set(ctx->u, ins); +#ifdef FLB_HAVE_TLS + /* Initialize OAuth2 context for OAuth-based authentication methods */ + if (ctx->atype == FLB_AZURE_AUTH_SERVICE_PRINCIPAL || + ctx->atype == FLB_AZURE_AUTH_MANAGED_IDENTITY_SYSTEM || + ctx->atype == FLB_AZURE_AUTH_MANAGED_IDENTITY_USER || + ctx->atype == FLB_AZURE_AUTH_WORKLOAD_IDENTITY) { + + /* Build OAuth URL based on auth type */ + ctx->oauth_url = flb_azure_auth_build_oauth_url(ctx->atype, + ctx->tenant_id, + ctx->client_id, + FLB_AZURE_BLOB_RESOURCE); + if (!ctx->oauth_url) { + flb_plg_error(ctx->ins, "failed to create OAuth URL"); + return NULL; + } + + /* Create OAuth2 context */ + ctx->o = flb_oauth2_create(ctx->config, ctx->oauth_url, 3000); + if (!ctx->o) { + flb_plg_error(ctx->ins, "cannot create oauth2 context"); + return NULL; + } + + /* Initialize token mutex */ + pthread_mutex_init(&ctx->token_mutex, NULL); + + flb_plg_info(ctx->ins, "oauth2 context initialized for auth type"); + } +#endif + /* Compose base uri */ ctx->base_uri = flb_sds_create_size(256); if (!ctx->base_uri) { @@ -746,7 +830,7 @@ struct flb_azure_blob *flb_azure_blob_conf_create(struct flb_output_instance *in } /* Prepare shared key buffer */ - if (ctx->atype == AZURE_BLOB_AUTH_KEY) { + if (ctx->atype == FLB_AZURE_AUTH_KEY) { ctx->shared_key_prefix = flb_sds_create_size(256); if (!ctx->shared_key_prefix) { flb_plg_error(ctx->ins, "cannot create shared key prefix"); @@ -772,13 +856,40 @@ struct flb_azure_blob *flb_azure_blob_conf_create(struct flb_output_instance *in pthread_mutex_init(&ctx->file_upload_commit_file_parts, NULL); - flb_plg_info(ctx->ins, - "account_name=%s, container_name=%s, blob_type=%s, emulator_mode=%s, endpoint=%s, auth_type=%s", - ctx->account_name, ctx->container_name, - ctx->btype == AZURE_BLOB_APPENDBLOB ? "appendblob" : "blockblob", - ctx->emulator_mode ? "yes" : "no", - ctx->real_endpoint ? ctx->real_endpoint : "no", - ctx->atype == AZURE_BLOB_AUTH_KEY ? "key" : "sas"); + /* Log configuration summary */ + { + const char *auth_type_str; + switch (ctx->atype) { + case FLB_AZURE_AUTH_KEY: + auth_type_str = "key"; + break; + case FLB_AZURE_AUTH_SAS: + auth_type_str = "sas"; + break; + case FLB_AZURE_AUTH_SERVICE_PRINCIPAL: + auth_type_str = "service_principal"; + break; + case FLB_AZURE_AUTH_MANAGED_IDENTITY_SYSTEM: + auth_type_str = "managed_identity (system)"; + break; + case FLB_AZURE_AUTH_MANAGED_IDENTITY_USER: + auth_type_str = "managed_identity (user)"; + break; + case FLB_AZURE_AUTH_WORKLOAD_IDENTITY: + auth_type_str = "workload_identity"; + break; + default: + auth_type_str = "unknown"; + } + + flb_plg_info(ctx->ins, + "account_name=%s, container_name=%s, blob_type=%s, emulator_mode=%s, endpoint=%s, auth_type=%s", + ctx->account_name, ctx->container_name, + ctx->btype == AZURE_BLOB_APPENDBLOB ? "appendblob" : "blockblob", + ctx->emulator_mode ? "yes" : "no", + ctx->real_endpoint ? ctx->real_endpoint : "no", + auth_type_str); + } return ctx; } @@ -826,6 +937,23 @@ void flb_azure_blob_conf_destroy(struct flb_azure_blob *ctx) flb_upstream_destroy(ctx->u); } +#ifdef FLB_HAVE_TLS + /* Cleanup OAuth2 resources */ + if (ctx->oauth_url) { + flb_sds_destroy(ctx->oauth_url); + } + + if (ctx->o) { + flb_oauth2_destroy(ctx->o); + } + + if (ctx->atype == FLB_AZURE_AUTH_SERVICE_PRINCIPAL || + ctx->atype == FLB_AZURE_AUTH_MANAGED_IDENTITY_SYSTEM || + ctx->atype == FLB_AZURE_AUTH_MANAGED_IDENTITY_USER || + ctx->atype == FLB_AZURE_AUTH_WORKLOAD_IDENTITY) { + pthread_mutex_destroy(&ctx->token_mutex); + } +#endif azb_db_close(ctx); flb_free(ctx); diff --git a/plugins/out_azure_blob/azure_blob_http.c b/plugins/out_azure_blob/azure_blob_http.c index 0d6ee0b2f81..a8b1bebd301 100644 --- a/plugins/out_azure_blob/azure_blob_http.c +++ b/plugins/out_azure_blob/azure_blob_http.c @@ -24,10 +24,105 @@ #include #include #include +#include #include "azure_blob.h" #include "azure_blob_uri.h" +#ifdef FLB_HAVE_TLS +/* Helper function to get OAuth token for blob storage access */ +static flb_sds_t get_azure_blob_token(struct flb_azure_blob *ctx) +{ + int ret = 0; + flb_sds_t output = NULL; + char *token = NULL; + + if (pthread_mutex_lock(&ctx->token_mutex)) { + flb_plg_error(ctx->ins, "error locking token mutex"); + return NULL; + } + + /* Check if token needs refresh */ + if (flb_oauth2_token_expired(ctx->o) == FLB_TRUE) { + switch (ctx->atype) { + case FLB_AZURE_AUTH_WORKLOAD_IDENTITY: + ret = flb_azure_workload_identity_token_get(ctx->o, + ctx->workload_identity_token_file, + ctx->client_id, + ctx->tenant_id, + FLB_AZURE_BLOB_RESOURCE "/.default"); + break; + + case FLB_AZURE_AUTH_MANAGED_IDENTITY_SYSTEM: + case FLB_AZURE_AUTH_MANAGED_IDENTITY_USER: + token = flb_azure_msi_token_get(ctx->o); + if (!token) { + ret = -1; + } + break; + + case FLB_AZURE_AUTH_SERVICE_PRINCIPAL: + /* Clear any previous oauth2 payload content */ + flb_oauth2_payload_clear(ctx->o); + + ret = flb_oauth2_payload_append(ctx->o, "grant_type", 10, "client_credentials", 18); + if (ret == -1) { + flb_plg_error(ctx->ins, "error appending oauth2 params"); + pthread_mutex_unlock(&ctx->token_mutex); + return NULL; + } + + ret = flb_oauth2_payload_append(ctx->o, "scope", 5, + FLB_AZURE_BLOB_RESOURCE "/.default", + sizeof(FLB_AZURE_BLOB_RESOURCE "/.default") - 1); + if (ret == -1) { + flb_plg_error(ctx->ins, "error appending oauth2 params"); + pthread_mutex_unlock(&ctx->token_mutex); + return NULL; + } + + ret = flb_oauth2_payload_append(ctx->o, "client_id", 9, ctx->client_id, -1); + if (ret == -1) { + flb_plg_error(ctx->ins, "error appending oauth2 params"); + pthread_mutex_unlock(&ctx->token_mutex); + return NULL; + } + + ret = flb_oauth2_payload_append(ctx->o, "client_secret", 13, ctx->client_secret, -1); + if (ret == -1) { + flb_plg_error(ctx->ins, "error appending oauth2 params"); + pthread_mutex_unlock(&ctx->token_mutex); + return NULL; + } + + /* Retrieve access token */ + token = flb_oauth2_token_get(ctx->o); + if (!token) { + ret = -1; + } + break; + + default: + ret = -1; + break; + } + } + + /* Create output string with token type and access token */ + if (ret == 0 && ctx->o->access_token) { + output = flb_sds_create_size(flb_sds_len(ctx->o->token_type) + + flb_sds_len(ctx->o->access_token) + 2); + if (output) { + flb_sds_snprintf(&output, flb_sds_alloc(output), "%s %s", + ctx->o->token_type, ctx->o->access_token); + } + } + + pthread_mutex_unlock(&ctx->token_mutex); + return output; +} +#endif + static int hmac_sha256_sign(unsigned char out[32], unsigned char *key, size_t key_len, unsigned char *msg, size_t msg_len) @@ -342,7 +437,9 @@ int azb_http_client_setup(struct flb_azure_blob *ctx, struct flb_http_client *c, /* Azure header: x-ms-version */ flb_http_add_header(c, "x-ms-version", 12, "2019-12-12", 10); - if (ctx->atype == AZURE_BLOB_AUTH_KEY) { + /* Authorization header based on auth type */ + if (ctx->atype == FLB_AZURE_AUTH_KEY) { + /* Use shared key authentication */ can_req = azb_http_canonical_request(ctx, c, content_length, content_type, content_encoding); @@ -358,6 +455,27 @@ int azb_http_client_setup(struct flb_azure_blob *ctx, struct flb_http_client *c, flb_sds_destroy(can_req); flb_sds_destroy(auth); } +#ifdef FLB_HAVE_TLS + else if (ctx->atype == FLB_AZURE_AUTH_SERVICE_PRINCIPAL || + ctx->atype == FLB_AZURE_AUTH_MANAGED_IDENTITY_SYSTEM || + ctx->atype == FLB_AZURE_AUTH_MANAGED_IDENTITY_USER || + ctx->atype == FLB_AZURE_AUTH_WORKLOAD_IDENTITY) { + /* Use OAuth2 bearer token authentication */ + auth = get_azure_blob_token(ctx); + if (!auth) { + flb_plg_error(ctx->ins, "failed to get OAuth token"); + return -1; + } + + /* Azure header: authorization (Bearer token) */ + flb_http_add_header(c, "Authorization", 13, auth, flb_sds_len(auth)); + + /* Release buffer */ + flb_sds_destroy(auth); + } +#endif + /* Note: FLB_AZURE_AUTH_SAS does not use Authorization header, + * SAS token is appended to URI instead */ /* Set callback context to the HTTP client context */ flb_http_set_callback_context(c, ctx->ins->callback); diff --git a/plugins/out_azure_blob/azure_blob_uri.c b/plugins/out_azure_blob/azure_blob_uri.c index 954b72d62a0..05ab6a6e6cb 100644 --- a/plugins/out_azure_blob/azure_blob_uri.c +++ b/plugins/out_azure_blob/azure_blob_uri.c @@ -127,7 +127,7 @@ flb_sds_t azb_uri_ensure_or_create_container(struct flb_azure_blob *ctx) } flb_sds_printf(&uri, "?restype=container"); - if (ctx->atype == AZURE_BLOB_AUTH_SAS && ctx->sas_token) { + if (ctx->atype == FLB_AZURE_AUTH_SAS && ctx->sas_token) { flb_sds_printf(&uri, "&%s", ctx->sas_token); } @@ -150,7 +150,7 @@ flb_sds_t azb_uri_create_blob(struct flb_azure_blob *ctx, char *tag) flb_sds_printf(&uri, "/%s", tag); } - if (ctx->atype == AZURE_BLOB_AUTH_SAS && ctx->sas_token) { + if (ctx->atype == FLB_AZURE_AUTH_SAS && ctx->sas_token) { flb_sds_printf(&uri, "?%s", ctx->sas_token); } diff --git a/plugins/out_azure_kusto/CMakeLists.txt b/plugins/out_azure_kusto/CMakeLists.txt index eb34d54dc63..c87483c573f 100644 --- a/plugins/out_azure_kusto/CMakeLists.txt +++ b/plugins/out_azure_kusto/CMakeLists.txt @@ -2,7 +2,6 @@ set(src azure_kusto.c azure_kusto_conf.c azure_kusto_ingest.c - azure_msiauth.c azure_kusto_store.c ) diff --git a/plugins/out_azure_kusto/azure_kusto.c b/plugins/out_azure_kusto/azure_kusto.c index cd3f1c3966b..b67b81ba315 100644 --- a/plugins/out_azure_kusto/azure_kusto.c +++ b/plugins/out_azure_kusto/azure_kusto.c @@ -36,15 +36,14 @@ #include "azure_kusto.h" #include "azure_kusto_conf.h" #include "azure_kusto_ingest.h" -#include "azure_msiauth.h" #include "azure_kusto_store.h" static int azure_kusto_get_msi_token(struct flb_azure_kusto *ctx) { char *token; - /* Retrieve access token */ - token = flb_azure_msiauth_token_get(ctx->o); + /* Retrieve access token using common auth function */ + token = flb_azure_msi_token_get(ctx->o); if (!token) { flb_plg_error(ctx->ins, "error retrieving oauth2 access token"); return -1; @@ -57,10 +56,12 @@ static int azure_kusto_get_workload_identity_token(struct flb_azure_kusto *ctx) { int ret; + /* Use common auth function for workload identity */ ret = flb_azure_workload_identity_token_get(ctx->o, ctx->workload_identity_token_file, ctx->client_id, - ctx->tenant_id); + ctx->tenant_id, + FLB_AZURE_KUSTO_RESOURCE "/.default"); if (ret == -1) { flb_plg_error(ctx->ins, "error retrieving workload identity token"); return -1; @@ -83,7 +84,8 @@ static int azure_kusto_get_service_principal_token(struct flb_azure_kusto *ctx) return -1; } - ret = flb_oauth2_payload_append(ctx->o, "scope", 5, FLB_AZURE_KUSTO_SCOPE, 39); + ret = flb_oauth2_payload_append(ctx->o, "scope", 5, FLB_AZURE_KUSTO_RESOURCE "/.default", + sizeof(FLB_AZURE_KUSTO_RESOURCE "/.default") - 1); if (ret == -1) { flb_plg_error(ctx->ins, "error appending oauth2 params"); return -1; @@ -124,14 +126,14 @@ flb_sds_t get_azure_kusto_token(struct flb_azure_kusto *ctx) if (flb_oauth2_token_expired(ctx->o) == FLB_TRUE) { switch (ctx->auth_type) { - case FLB_AZURE_KUSTO_AUTH_WORKLOAD_IDENTITY: + case FLB_AZURE_AUTH_WORKLOAD_IDENTITY: ret = azure_kusto_get_workload_identity_token(ctx); break; - case FLB_AZURE_KUSTO_AUTH_MANAGED_IDENTITY_SYSTEM: - case FLB_AZURE_KUSTO_AUTH_MANAGED_IDENTITY_USER: + case FLB_AZURE_AUTH_MANAGED_IDENTITY_SYSTEM: + case FLB_AZURE_AUTH_MANAGED_IDENTITY_USER: ret = azure_kusto_get_msi_token(ctx); break; - case FLB_AZURE_KUSTO_AUTH_SERVICE_PRINCIPAL: + case FLB_AZURE_AUTH_SERVICE_PRINCIPAL: default: ret = azure_kusto_get_service_principal_token(ctx); break; diff --git a/plugins/out_azure_kusto/azure_kusto.h b/plugins/out_azure_kusto/azure_kusto.h index 5da2d9b5593..e75635649ca 100644 --- a/plugins/out_azure_kusto/azure_kusto.h +++ b/plugins/out_azure_kusto/azure_kusto.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -35,17 +36,6 @@ /* refresh token every 50 minutes */ #define FLB_AZURE_KUSTO_TOKEN_REFRESH 3000 -/* Authentication types */ -typedef enum { - FLB_AZURE_KUSTO_AUTH_SERVICE_PRINCIPAL = 0, /* Client ID + Client Secret */ - FLB_AZURE_KUSTO_AUTH_MANAGED_IDENTITY_SYSTEM, /* System-assigned managed identity */ - FLB_AZURE_KUSTO_AUTH_MANAGED_IDENTITY_USER, /* User-assigned managed identity */ - FLB_AZURE_KUSTO_AUTH_WORKLOAD_IDENTITY /* Workload Identity */ -} flb_azure_kusto_auth_type; - -/* Kusto streaming inserts oauth scope */ -#define FLB_AZURE_KUSTO_SCOPE "https://help.kusto.windows.net/.default" - /* MSAL authorization URL */ #define FLB_MSAL_AUTH_URL_TEMPLATE \ "https://login.microsoftonline.com/%s/oauth2/v2.0/token" diff --git a/plugins/out_azure_kusto/azure_kusto_conf.c b/plugins/out_azure_kusto/azure_kusto_conf.c index 23db8fac647..3db3d5bf896 100644 --- a/plugins/out_azure_kusto/azure_kusto_conf.c +++ b/plugins/out_azure_kusto/azure_kusto_conf.c @@ -31,7 +31,6 @@ #include "azure_kusto.h" #include "azure_kusto_conf.h" -#include "azure_msiauth.h" /* Constants for PCG random number generator */ #define PCG_DEFAULT_MULTIPLIER_64 6364136223846793005ULL @@ -724,7 +723,7 @@ struct flb_azure_kusto *flb_azure_kusto_conf_create(struct flb_output_instance * /* Auth method validation and setup */ if (strcasecmp(ctx->auth_type_str, "service_principal") == 0) { - ctx->auth_type = FLB_AZURE_KUSTO_AUTH_SERVICE_PRINCIPAL; + ctx->auth_type = FLB_AZURE_AUTH_SERVICE_PRINCIPAL; /* Verify required parameters for Service Principal auth */ if (!ctx->tenant_id || !ctx->client_id || !ctx->client_secret) { @@ -742,13 +741,13 @@ struct flb_azure_kusto *flb_azure_kusto_conf_create(struct flb_output_instance * } if (strcasecmp(ctx->client_id, "system") == 0) { - ctx->auth_type = FLB_AZURE_KUSTO_AUTH_MANAGED_IDENTITY_SYSTEM; + ctx->auth_type = FLB_AZURE_AUTH_MANAGED_IDENTITY_SYSTEM; } else { - ctx->auth_type = FLB_AZURE_KUSTO_AUTH_MANAGED_IDENTITY_USER; + ctx->auth_type = FLB_AZURE_AUTH_MANAGED_IDENTITY_USER; } } else if (strcasecmp(ctx->auth_type_str, "workload_identity") == 0) { - ctx->auth_type = FLB_AZURE_KUSTO_AUTH_WORKLOAD_IDENTITY; + ctx->auth_type = FLB_AZURE_AUTH_WORKLOAD_IDENTITY; /* Verify required parameters for Workload Identity auth */ if (!ctx->tenant_id || !ctx->client_id) { @@ -759,7 +758,7 @@ struct flb_azure_kusto *flb_azure_kusto_conf_create(struct flb_output_instance * /* Set default token file path if not specified */ if (!ctx->workload_identity_token_file) { - ctx->workload_identity_token_file = flb_strdup("/var/run/secrets/azure/tokens/azure-identity-token"); + ctx->workload_identity_token_file = flb_strdup(FLB_AZURE_WORKLOAD_IDENTITY_TOKEN_FILE); if (!ctx->workload_identity_token_file) { flb_errno(); flb_plg_error(ins, "Could not allocate default workload identity token path"); @@ -796,44 +795,15 @@ struct flb_azure_kusto *flb_azure_kusto_conf_create(struct flb_output_instance * return NULL; } - /* Create oauth2 context */ - if (ctx->auth_type == FLB_AZURE_KUSTO_AUTH_MANAGED_IDENTITY_SYSTEM || - ctx->auth_type == FLB_AZURE_KUSTO_AUTH_MANAGED_IDENTITY_USER) { - /* MSI auth */ - /* Construct the URL template with or without client_id for managed identity */ - if (ctx->auth_type == FLB_AZURE_KUSTO_AUTH_MANAGED_IDENTITY_SYSTEM) { - ctx->oauth_url = flb_sds_create_size(sizeof(FLB_AZURE_MSIAUTH_URL_TEMPLATE) - 1); - if (!ctx->oauth_url) { - flb_errno(); - flb_azure_kusto_conf_destroy(ctx); - return NULL; - } - flb_sds_snprintf(&ctx->oauth_url, flb_sds_alloc(ctx->oauth_url), - FLB_AZURE_MSIAUTH_URL_TEMPLATE, "", ""); - } else { - /* User-assigned managed identity */ - ctx->oauth_url = flb_sds_create_size(sizeof(FLB_AZURE_MSIAUTH_URL_TEMPLATE) - 1 + - sizeof("&client_id=") - 1 + - flb_sds_len(ctx->client_id)); - if (!ctx->oauth_url) { - flb_errno(); - flb_azure_kusto_conf_destroy(ctx); - return NULL; - } - flb_sds_snprintf(&ctx->oauth_url, flb_sds_alloc(ctx->oauth_url), - FLB_AZURE_MSIAUTH_URL_TEMPLATE, "&client_id=", ctx->client_id); - } - } else { - /* Standard OAuth2 for service principal or workload identity */ - ctx->oauth_url = flb_sds_create_size(sizeof(FLB_MSAL_AUTH_URL_TEMPLATE) - 1 + - flb_sds_len(ctx->tenant_id)); - if (!ctx->oauth_url) { - flb_errno(); - flb_azure_kusto_conf_destroy(ctx); - return NULL; - } - flb_sds_snprintf(&ctx->oauth_url, flb_sds_alloc(ctx->oauth_url), - FLB_MSAL_AUTH_URL_TEMPLATE, ctx->tenant_id); + /* Create oauth2 context using common auth URL builder */ + ctx->oauth_url = flb_azure_auth_build_oauth_url(ctx->auth_type, + ctx->tenant_id, + ctx->client_id, + FLB_AZURE_KUSTO_RESOURCE); + if (!ctx->oauth_url) { + flb_plg_error(ctx->ins, "failed to create OAuth URL"); + flb_azure_kusto_conf_destroy(ctx); + return NULL; } ctx->resources = flb_calloc(1, sizeof(struct flb_azure_kusto_resources)); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f179a76e52a..7fc9b298bdc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -164,6 +164,7 @@ if(FLB_TLS) "tls/flb_tls.c" "flb_oauth2.c" "flb_oauth2_jwt.c" + "flb_azure_auth.c" ) # Make sure our output targets links to the TLS library diff --git a/src/flb_azure_auth.c b/src/flb_azure_auth.c new file mode 100644 index 00000000000..a30f52b295e --- /dev/null +++ b/src/flb_azure_auth.c @@ -0,0 +1,360 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2026 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 +#include + +#include +#include +#include + +char *flb_azure_msi_token_get(struct flb_oauth2 *ctx) +{ + int ret; + size_t b_sent; + time_t now; + char *token = NULL; + struct flb_connection *u_conn; + struct flb_http_client *c; + + /* Acquire lock to prevent token races */ + ret = flb_lock_acquire(&ctx->lock, + FLB_LOCK_DEFAULT_RETRY_LIMIT, + FLB_LOCK_DEFAULT_RETRY_DELAY); + if (ret != 0) { + flb_error("[azure msi auth] failed to acquire lock"); + return NULL; + } + + now = time(NULL); + if (ctx->access_token) { + /* validate unexpired token */ + if (ctx->expires_at > now && flb_sds_len(ctx->access_token) > 0) { + token = ctx->access_token; + goto unlock; + } + } + + /* Get Token and store it in the context */ + u_conn = flb_upstream_conn_get(ctx->u); + if (!u_conn) { + flb_error("[azure msi auth] could not get an upstream connection to %s:%d", + ctx->u->tcp_host, ctx->u->tcp_port); + goto unlock; + } + + /* 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 msi auth] error creating HTTP client context"); + flb_upstream_conn_release(u_conn); + goto unlock; + } + + /* Append HTTP Header - IMDS requires Metadata:true 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 msi auth] cannot issue request, http_do=%i", ret); + } + else { + flb_debug("[azure msi auth] HTTP Status=%i", c->resp.status); + } + + /* 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 msi auth] access token from '%s:%s' retrieved", + ctx->host, ctx->port); + ctx->expires_at = time(NULL) + ctx->expires_in; + token = ctx->access_token; + } + } + + flb_http_client_destroy(c); + flb_upstream_conn_release(u_conn); + +unlock: + flb_lock_release(&ctx->lock, + FLB_LOCK_DEFAULT_RETRY_LIMIT, + FLB_LOCK_DEFAULT_RETRY_DELAY); + + return token; +} + +/** Read token from file */ +static flb_sds_t read_token_from_file(const char *token_file) +{ + FILE *fp; + flb_sds_t token = NULL; + char buf[4096]; /* Assuming token won't be larger than 4KB */ + size_t bytes_read; + + if (!token_file) { + flb_error("[azure workload identity] token file path is NULL"); + return NULL; + } + + fp = fopen(token_file, "r"); + if (!fp) { + flb_error("[azure workload identity] could not open token file: %s", token_file); + return NULL; + } + + bytes_read = fread(buf, 1, sizeof(buf) - 1, fp); + fclose(fp); + + if (bytes_read <= 0) { + flb_error("[azure workload identity] could not read token from file: %s", token_file); + return NULL; + } + + buf[bytes_read] = '\0'; + token = flb_sds_create(buf); + + return token; +} + +int flb_azure_workload_identity_token_get(struct flb_oauth2 *ctx, + const char *token_file, + const char *client_id, + const char *tenant_id, + const char *resource) +{ + int ret; + size_t b_sent; + struct flb_connection *u_conn; + struct flb_http_client *c; + flb_sds_t federated_token; + flb_sds_t body = NULL; + + flb_info("[azure workload identity] initiating token exchange"); + + /* Default token file location if not specified */ + if (!token_file) { + token_file = FLB_AZURE_WORKLOAD_IDENTITY_TOKEN_FILE; + } + + /* Read the federated token from file */ + federated_token = read_token_from_file(token_file); + if (!federated_token) { + flb_error("[azure workload identity] failed to read federated token"); + return -1; + } + + flb_debug("[azure workload identity] federated token read from file: %s", token_file); + + /* Build the form data for token exchange */ + body = flb_sds_create_size(4096); + if (!body) { + flb_error("[azure workload identity] failed to allocate memory for request body"); + flb_sds_destroy(federated_token); + return -1; + } + + flb_sds_cat_safe(&body, "client_id=", 10); + flb_sds_cat_safe(&body, client_id, strlen(client_id)); + flb_sds_cat_safe(&body, "&grant_type=client_credentials", 30); + flb_sds_cat_safe(&body, "&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer", 77); + flb_sds_cat_safe(&body, "&client_assertion=", 18); + flb_sds_cat_safe(&body, federated_token, flb_sds_len(federated_token)); + flb_sds_cat_safe(&body, "&scope=", 7); + flb_sds_cat_safe(&body, resource, strlen(resource)); + + if (!body) { + flb_error("[azure workload identity] failed to build request body"); + flb_sds_destroy(federated_token); + return -1; + } + + /* Get upstream connection to Azure AD token endpoint */ + u_conn = flb_upstream_conn_get(ctx->u); + if (!u_conn) { + flb_error("[azure workload identity] could not get an upstream connection"); + flb_sds_destroy(federated_token); + flb_sds_destroy(body); + return -1; + } + + /* Create HTTP client context */ + c = flb_http_client(u_conn, FLB_HTTP_POST, ctx->uri, + body, flb_sds_len(body), + ctx->host, atoi(ctx->port), NULL, 0); + if (!c) { + flb_error("[azure workload identity] error creating HTTP client context"); + flb_upstream_conn_release(u_conn); + flb_sds_destroy(federated_token); + flb_sds_destroy(body); + return -1; + } + + /* Prepare token exchange request headers */ + flb_http_add_header(c, "Content-Type", 12, "application/x-www-form-urlencoded", 33); + + flb_debug("[azure workload identity] sending request body (len=%zu)", flb_sds_len(body)); + + /* Issue request */ + ret = flb_http_do(c, &b_sent); + + /* Clean up the body sds now that the request is done */ + flb_sds_destroy(body); + body = NULL; + + if (ret != 0) { + flb_warn("[azure workload identity] error in HTTP request, http_do=%i", ret); + flb_http_client_destroy(c); + flb_upstream_conn_release(u_conn); + flb_sds_destroy(federated_token); + return -1; + } + + flb_debug("[azure workload identity] HTTP Status=%i", c->resp.status); + if (c->resp.payload_size > 0) { + if (c->resp.status == 200) { + flb_debug("[azure workload identity] token exchange successful"); + } + else { + flb_warn("[azure workload identity] token exchange failed with status %i", c->resp.status); + flb_http_client_destroy(c); + flb_upstream_conn_release(u_conn); + flb_sds_destroy(federated_token); + return -1; + } + } + + /* Parse the response and extract the token */ + if (c->resp.payload_size > 0 && c->resp.status == 200) { + /* Acquire lock before updating token */ + ret = flb_lock_acquire(&ctx->lock, + FLB_LOCK_DEFAULT_RETRY_LIMIT, + FLB_LOCK_DEFAULT_RETRY_DELAY); + if (ret != 0) { + flb_error("[azure workload identity] failed to acquire lock"); + flb_http_client_destroy(c); + flb_upstream_conn_release(u_conn); + flb_sds_destroy(federated_token); + return -1; + } + + ret = flb_oauth2_parse_json_response(c->resp.payload, + c->resp.payload_size, ctx); + if (ret == 0) { + flb_info("[azure workload identity] access token retrieved successfully"); + ctx->expires_at = time(NULL) + ctx->expires_in; + } + + flb_lock_release(&ctx->lock, + FLB_LOCK_DEFAULT_RETRY_LIMIT, + FLB_LOCK_DEFAULT_RETRY_DELAY); + + flb_http_client_destroy(c); + flb_upstream_conn_release(u_conn); + flb_sds_destroy(federated_token); + + if (ret == 0) { + return 0; + } + } + + flb_error("[azure workload identity] failed to parse token response"); + flb_http_client_destroy(c); + flb_upstream_conn_release(u_conn); + flb_sds_destroy(federated_token); + + return -1; +} + +flb_sds_t flb_azure_auth_build_oauth_url(flb_azure_auth_type auth_type, + const char *tenant_id, + const char *client_id, + const char *resource) +{ + flb_sds_t url = NULL; + size_t url_size; + + switch (auth_type) { + case FLB_AZURE_AUTH_MANAGED_IDENTITY_SYSTEM: + /* System-assigned managed identity - no client_id parameter, no .default suffix */ + url_size = sizeof(FLB_AZURE_MSI_AUTH_URL_TEMPLATE) + strlen(resource); + url = flb_sds_create_size(url_size); + if (!url) { + flb_errno(); + return NULL; + } + flb_sds_snprintf(&url, flb_sds_alloc(url), + FLB_AZURE_MSI_AUTH_URL_TEMPLATE, + "", "", resource); + break; + + case FLB_AZURE_AUTH_MANAGED_IDENTITY_USER: + /* User-assigned managed identity - include client_id parameter, no .default suffix */ + if (!client_id) { + flb_error("[azure auth] client_id required for user-assigned managed identity"); + return NULL; + } + url_size = sizeof(FLB_AZURE_MSI_AUTH_URL_TEMPLATE) + + sizeof("&client_id=") + strlen(client_id) + strlen(resource); + url = flb_sds_create_size(url_size); + if (!url) { + flb_errno(); + return NULL; + } + flb_sds_snprintf(&url, flb_sds_alloc(url), + FLB_AZURE_MSI_AUTH_URL_TEMPLATE, + "&client_id=", client_id, resource); + break; + + case FLB_AZURE_AUTH_SERVICE_PRINCIPAL: + case FLB_AZURE_AUTH_WORKLOAD_IDENTITY: + /* Standard OAuth2 endpoint for Azure AD */ + if (!tenant_id) { + flb_error("[azure auth] tenant_id required for service principal or workload identity"); + return NULL; + } + url_size = sizeof(FLB_AZURE_MSAL_AUTH_URL_TEMPLATE) + strlen(tenant_id); + url = flb_sds_create_size(url_size); + if (!url) { + flb_errno(); + return NULL; + } + flb_sds_snprintf(&url, flb_sds_alloc(url), + FLB_AZURE_MSAL_AUTH_URL_TEMPLATE, tenant_id); + break; + + default: + flb_error("[azure auth] unsupported auth type: %d", auth_type); + return NULL; + } + + return url; +} diff --git a/tests/runtime/CMakeLists.txt b/tests/runtime/CMakeLists.txt index dd76c16faee..b96c3df639d 100644 --- a/tests/runtime/CMakeLists.txt +++ b/tests/runtime/CMakeLists.txt @@ -205,6 +205,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_BLOB "out_azure_blob.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_blob.c b/tests/runtime/out_azure_blob.c new file mode 100644 index 00000000000..c5b66727e9b --- /dev/null +++ b/tests/runtime/out_azure_blob.c @@ -0,0 +1,236 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2026 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 functions */ +#ifdef FLB_HAVE_TLS +void flb_test_azure_blob_managed_identity_system(void); +void flb_test_azure_blob_managed_identity_user(void); +void flb_test_azure_blob_service_principal(void); +void flb_test_azure_blob_workload_identity(void); +#endif +void flb_test_azure_blob_shared_key(void); +void flb_test_azure_blob_sas_token(void); + +/* Test list */ +TEST_LIST = { +#ifdef FLB_HAVE_TLS + {"managed_identity_system", flb_test_azure_blob_managed_identity_system}, + {"managed_identity_user", flb_test_azure_blob_managed_identity_user}, + {"service_principal", flb_test_azure_blob_service_principal}, + {"workload_identity", flb_test_azure_blob_workload_identity}, +#endif + {"shared_key", flb_test_azure_blob_shared_key}, + {"sas_token", flb_test_azure_blob_sas_token}, + {NULL, NULL} +}; + +#ifdef FLB_HAVE_TLS +/* Test for system-assigned managed identity */ +void flb_test_azure_blob_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_blob", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "test", NULL); + flb_output_set(ctx, out_ffd, "account_name", "testaccount", NULL); + flb_output_set(ctx, out_ffd, "container_name", "testcontainer", 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, "auto_create_container", "off", 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_blob_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_blob", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "test", NULL); + flb_output_set(ctx, out_ffd, "account_name", "testaccount", NULL); + flb_output_set(ctx, out_ffd, "container_name", "testcontainer", 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); + flb_output_set(ctx, out_ffd, "auto_create_container", "off", NULL); + + ret = flb_start(ctx); + TEST_CHECK(ret == 0); + + flb_stop(ctx); + flb_destroy(ctx); +} + +/* Test for service principal authentication */ +void flb_test_azure_blob_service_principal(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_blob", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "test", NULL); + flb_output_set(ctx, out_ffd, "account_name", "testaccount", NULL); + flb_output_set(ctx, out_ffd, "container_name", "testcontainer", NULL); + flb_output_set(ctx, out_ffd, "auth_type", "service_principal", NULL); + flb_output_set(ctx, out_ffd, "tenant_id", "test-tenant-id", NULL); + flb_output_set(ctx, out_ffd, "client_id", "test-client-id", NULL); + flb_output_set(ctx, out_ffd, "client_secret", "test-client-secret", NULL); + flb_output_set(ctx, out_ffd, "auto_create_container", "off", NULL); + + ret = flb_start(ctx); + TEST_CHECK(ret == 0); + + flb_stop(ctx); + flb_destroy(ctx); +} + +/* Test for workload identity authentication */ +void flb_test_azure_blob_workload_identity(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_blob", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "test", NULL); + flb_output_set(ctx, out_ffd, "account_name", "testaccount", NULL); + flb_output_set(ctx, out_ffd, "container_name", "testcontainer", NULL); + flb_output_set(ctx, out_ffd, "auth_type", "workload_identity", NULL); + flb_output_set(ctx, out_ffd, "tenant_id", "test-tenant-id", NULL); + flb_output_set(ctx, out_ffd, "client_id", "test-client-id", NULL); + flb_output_set(ctx, out_ffd, "workload_identity_token_file", "/tmp/test-token", NULL); + flb_output_set(ctx, out_ffd, "auto_create_container", "off", NULL); + + ret = flb_start(ctx); + TEST_CHECK(ret == 0); + + flb_stop(ctx); + flb_destroy(ctx); +} +#endif /* FLB_HAVE_TLS */ + +/* Test for shared key authentication (existing method) */ +void flb_test_azure_blob_shared_key(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_blob", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "test", NULL); + flb_output_set(ctx, out_ffd, "account_name", "testaccount", NULL); + flb_output_set(ctx, out_ffd, "container_name", "testcontainer", NULL); + flb_output_set(ctx, out_ffd, "auth_type", "key", NULL); + flb_output_set(ctx, out_ffd, "shared_key", "dGVzdGtleQ==", NULL); /* base64 "testkey" */ + flb_output_set(ctx, out_ffd, "auto_create_container", "off", NULL); + + ret = flb_start(ctx); + TEST_CHECK(ret == 0); + + flb_stop(ctx); + flb_destroy(ctx); +} + +/* Test for SAS token authentication (existing method) */ +void flb_test_azure_blob_sas_token(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_blob", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "test", NULL); + flb_output_set(ctx, out_ffd, "account_name", "testaccount", NULL); + flb_output_set(ctx, out_ffd, "container_name", "testcontainer", NULL); + flb_output_set(ctx, out_ffd, "auth_type", "sas", NULL); + flb_output_set(ctx, out_ffd, "sas_token", "sv=2021-01-01&ss=b&srt=sco&sp=rwdlacx&se=2026-01-01T00:00:00Z&st=2025-01-01T00:00:00Z&spr=https&sig=test", NULL); + flb_output_set(ctx, out_ffd, "auto_create_container", "off", NULL); + + ret = flb_start(ctx); + TEST_CHECK(ret == 0); + + flb_stop(ctx); + flb_destroy(ctx); +}