diff --git a/plugins/out_azure_kusto/azure_kusto.c b/plugins/out_azure_kusto/azure_kusto.c index 716768236c8..10fed8daa8a 100644 --- a/plugins/out_azure_kusto/azure_kusto.c +++ b/plugins/out_azure_kusto/azure_kusto.c @@ -59,7 +59,8 @@ static int azure_kusto_get_workload_identity_token(struct flb_azure_kusto *ctx) ret = flb_azure_workload_identity_token_get(ctx->o, ctx->workload_identity_token_file, ctx->client_id, - ctx->tenant_id); + ctx->tenant_id, + ctx->kusto_scope); if (ret == -1) { flb_plg_error(ctx->ins, "error retrieving workload identity token"); return -1; @@ -82,7 +83,9 @@ 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, + ctx->kusto_scope, + flb_sds_len(ctx->kusto_scope)); if (ret == -1) { flb_plg_error(ctx->ins, "error appending oauth2 params"); return -1; @@ -1532,6 +1535,27 @@ static struct flb_config_map config_map[] = { offsetof(struct flb_azure_kusto, auth_type_str), "Set the authentication type: 'service_principal', 'managed_identity', or 'workload_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, "cloud_name", "AzureCloud", 0, FLB_TRUE, + offsetof(struct flb_azure_kusto, cloud_name), + "Set the Azure cloud environment. Supported values: " + "'AzureCloud' (default), 'AzureChinaCloud', 'AzureUSGovernmentCloud'. " + "For private clouds (USSEC, USNAT, BLEU, etc.), set " + "cloud_login_host, cloud_kusto_scope, and cloud_kusto_resource instead"}, + {FLB_CONFIG_MAP_STR, "cloud_login_host", (char *)NULL, 0, FLB_TRUE, + offsetof(struct flb_azure_kusto, custom_login_host), + "Custom OAuth login host for private/sovereign clouds " + "(e.g. login.microsoftonline.eaglex.ic.gov). When set, cloud_kusto_scope " + "and cloud_kusto_resource must also be provided"}, + {FLB_CONFIG_MAP_STR, "cloud_kusto_scope", (char *)NULL, 0, FLB_TRUE, + offsetof(struct flb_azure_kusto, custom_kusto_scope), + "Custom Kusto OAuth scope for private/sovereign clouds " + "(e.g. https://help.kusto.core.eaglex.ic.gov/.default). When set, " + "cloud_login_host and cloud_kusto_resource must also be provided"}, + {FLB_CONFIG_MAP_STR, "cloud_kusto_resource", (char *)NULL, 0, FLB_TRUE, + offsetof(struct flb_azure_kusto, custom_kusto_resource), + "Custom Kusto IMDS resource URL for private/sovereign clouds " + "(e.g. https://api.kusto.core.eaglex.ic.gov/). When set, cloud_login_host " + "and cloud_kusto_scope must also be provided"}, {FLB_CONFIG_MAP_STR, "ingestion_endpoint", (char *)NULL, 0, FLB_TRUE, offsetof(struct flb_azure_kusto, ingestion_endpoint), "Set the Kusto cluster's ingestion endpoint URL (e.g. " diff --git a/plugins/out_azure_kusto/azure_kusto.h b/plugins/out_azure_kusto/azure_kusto.h index 5da2d9b5593..d5ffafbfa3a 100644 --- a/plugins/out_azure_kusto/azure_kusto.h +++ b/plugins/out_azure_kusto/azure_kusto.h @@ -43,12 +43,31 @@ typedef enum { 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" +/* Azure cloud environment types */ +typedef enum { + FLB_AZURE_CLOUD_PUBLIC = 0, /* AzureCloud (default) */ + FLB_AZURE_CLOUD_CHINA, /* AzureChinaCloud */ + FLB_AZURE_CLOUD_US_GOVERNMENT /* AzureUSGovernmentCloud */ +} flb_azure_cloud_type; -/* MSAL authorization URL */ +/* MSAL authorization URL template: %s = login host, %s = tenant_id */ #define FLB_MSAL_AUTH_URL_TEMPLATE \ - "https://login.microsoftonline.com/%s/oauth2/v2.0/token" + "https://%s/%s/oauth2/v2.0/token" + +/* Cloud-specific login hosts */ +#define FLB_AZURE_LOGIN_HOST_PUBLIC "login.microsoftonline.com" +#define FLB_AZURE_LOGIN_HOST_CHINA "login.chinacloudapi.cn" +#define FLB_AZURE_LOGIN_HOST_US_GOVERNMENT "login.microsoftonline.us" + +/* Cloud-specific Kusto scopes */ +#define FLB_AZURE_KUSTO_SCOPE_PUBLIC "https://help.kusto.windows.net/.default" +#define FLB_AZURE_KUSTO_SCOPE_CHINA "https://help.kusto.chinacloudapi.cn/.default" +#define FLB_AZURE_KUSTO_SCOPE_US_GOVERNMENT "https://help.kusto.usgovcloudapi.net/.default" + +/* Cloud-specific Kusto IMDS resources */ +#define FLB_AZURE_KUSTO_RESOURCE_PUBLIC "https://api.kusto.windows.net/" +#define FLB_AZURE_KUSTO_RESOURCE_CHINA "https://api.kusto.chinacloudapi.cn/" +#define FLB_AZURE_KUSTO_RESOURCE_US_GOVERNMENT "https://api.kusto.usgovcloudapi.net/" #define FLB_AZURE_KUSTO_MGMT_URI_PATH "/v1/rest/mgmt" #define FLB_AZURE_KUSTO_MGMT_BODY_TEMPLATE "{\"csl\":\"%s\", \"db\": \"NetDefaultDB\"}" @@ -74,7 +93,6 @@ typedef enum { #define FLB_AZURE_IMDS_ENDPOINT "/metadata/identity/oauth2/token" #define FLB_AZURE_IMDS_API_VERSION "2018-02-01" -#define FLB_AZURE_IMDS_RESOURCE "https://api.kusto.windows.net/" struct flb_azure_kusto_resources { @@ -105,6 +123,18 @@ struct flb_azure_kusto { char *auth_type_str; char *workload_identity_token_file; + /* Cloud environment */ + int cloud_type; + char *cloud_name; + flb_sds_t kusto_scope; + flb_sds_t kusto_resource; + flb_sds_t login_host; + + /* Custom cloud overrides (for private/sovereign clouds like USSEC, USNAT, BLEU) */ + flb_sds_t custom_login_host; + flb_sds_t custom_kusto_scope; + flb_sds_t custom_kusto_resource; + /* compress payload */ int compression_enabled; diff --git a/plugins/out_azure_kusto/azure_kusto_conf.c b/plugins/out_azure_kusto/azure_kusto_conf.c index f0945612923..21777d00482 100644 --- a/plugins/out_azure_kusto/azure_kusto_conf.c +++ b/plugins/out_azure_kusto/azure_kusto_conf.c @@ -700,6 +700,115 @@ static int flb_azure_kusto_resources_destroy(struct flb_azure_kusto_resources *r return 0; } +/** + * Resolves cloud-specific endpoints based on the cloud_name configuration. + * Sets kusto_scope, kusto_resource, and login_host in the context. + * + * @param ctx Pointer to the plugin's context + * @return int 0 on success, -1 on failure + */ +static int azure_kusto_resolve_cloud_endpoints(struct flb_azure_kusto *ctx) +{ + const char *scope = NULL; + const char *resource = NULL; + const char *login_host = NULL; + int has_custom_login_host; + int has_custom_scope; + int has_custom_resource; + + /* Check if custom overrides are provided */ + has_custom_login_host = (ctx->custom_login_host && flb_sds_len(ctx->custom_login_host) > 0); + has_custom_scope = (ctx->custom_kusto_scope && flb_sds_len(ctx->custom_kusto_scope) > 0); + has_custom_resource = (ctx->custom_kusto_resource && flb_sds_len(ctx->custom_kusto_resource) > 0); + + /* If all three custom properties are provided, use them directly */ + if (has_custom_login_host && has_custom_scope && has_custom_resource) { + ctx->login_host = flb_sds_create(ctx->custom_login_host); + if (!ctx->login_host) { + flb_errno(); + return -1; + } + + ctx->kusto_scope = flb_sds_create(ctx->custom_kusto_scope); + if (!ctx->kusto_scope) { + flb_errno(); + return -1; + } + + ctx->kusto_resource = flb_sds_create(ctx->custom_kusto_resource); + if (!ctx->kusto_resource) { + flb_errno(); + return -1; + } + + flb_plg_info(ctx->ins, + "using custom cloud endpoints: login_host='%s', scope='%s', resource='%s'", + ctx->login_host, ctx->kusto_scope, ctx->kusto_resource); + return 0; + } + + /* If some but not all custom properties are set, error out */ + if (has_custom_login_host || has_custom_scope || has_custom_resource) { + flb_plg_error(ctx->ins, + "When using custom cloud endpoints, all three properties must be set: " + "cloud_login_host, cloud_kusto_scope, cloud_kusto_resource"); + return -1; + } + + /* Resolve from well-known cloud names */ + if (!ctx->cloud_name || strcasecmp(ctx->cloud_name, "AzureCloud") == 0) { + ctx->cloud_type = FLB_AZURE_CLOUD_PUBLIC; + scope = FLB_AZURE_KUSTO_SCOPE_PUBLIC; + resource = FLB_AZURE_KUSTO_RESOURCE_PUBLIC; + login_host = FLB_AZURE_LOGIN_HOST_PUBLIC; + } + else if (strcasecmp(ctx->cloud_name, "AzureChinaCloud") == 0) { + ctx->cloud_type = FLB_AZURE_CLOUD_CHINA; + scope = FLB_AZURE_KUSTO_SCOPE_CHINA; + resource = FLB_AZURE_KUSTO_RESOURCE_CHINA; + login_host = FLB_AZURE_LOGIN_HOST_CHINA; + } + else if (strcasecmp(ctx->cloud_name, "AzureUSGovernmentCloud") == 0) { + ctx->cloud_type = FLB_AZURE_CLOUD_US_GOVERNMENT; + scope = FLB_AZURE_KUSTO_SCOPE_US_GOVERNMENT; + resource = FLB_AZURE_KUSTO_RESOURCE_US_GOVERNMENT; + login_host = FLB_AZURE_LOGIN_HOST_US_GOVERNMENT; + } + else { + flb_plg_error(ctx->ins, + "Unknown cloud_name '%s'. Use a well-known cloud name " + "('AzureCloud', 'AzureChinaCloud', 'AzureUSGovernmentCloud') " + "or specify custom endpoints via " + "cloud_login_host, cloud_kusto_scope, and cloud_kusto_resource", + ctx->cloud_name); + return -1; + } + + ctx->kusto_scope = flb_sds_create(scope); + if (!ctx->kusto_scope) { + flb_errno(); + return -1; + } + + ctx->kusto_resource = flb_sds_create(resource); + if (!ctx->kusto_resource) { + flb_errno(); + return -1; + } + + ctx->login_host = flb_sds_create(login_host); + if (!ctx->login_host) { + flb_errno(); + return -1; + } + + flb_plg_info(ctx->ins, "cloud environment='%s', login_host='%s', scope='%s'", + ctx->cloud_name ? ctx->cloud_name : "AzureCloud", + ctx->login_host, ctx->kusto_scope); + + return 0; +} + struct flb_azure_kusto *flb_azure_kusto_conf_create(struct flb_output_instance *ins, struct flb_config *config) { @@ -722,6 +831,14 @@ struct flb_azure_kusto *flb_azure_kusto_conf_create(struct flb_output_instance * return NULL; } + /* Resolve cloud-specific endpoints */ + ret = azure_kusto_resolve_cloud_endpoints(ctx); + if (ret == -1) { + flb_plg_error(ins, "failed to resolve cloud endpoints"); + flb_azure_kusto_conf_destroy(ctx); + return NULL; + } + /* Auth method validation and setup */ if (strcasecmp(ctx->auth_type_str, "service_principal") == 0) { ctx->auth_type = FLB_AZURE_KUSTO_AUTH_SERVICE_PRINCIPAL; @@ -802,30 +919,35 @@ struct flb_azure_kusto *flb_azure_kusto_conf_create(struct flb_output_instance * /* 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); + ctx->oauth_url = flb_sds_create_size(sizeof(FLB_AZURE_MSIAUTH_URL_TEMPLATE) - 1 + + flb_sds_len(ctx->kusto_resource)); 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, "", ""); + FLB_AZURE_MSIAUTH_URL_TEMPLATE, "", "", + ctx->kusto_resource); } 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)); + flb_sds_len(ctx->client_id) + + flb_sds_len(ctx->kusto_resource)); 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); + FLB_AZURE_MSIAUTH_URL_TEMPLATE, "&client_id=", + ctx->client_id, ctx->kusto_resource); } } 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->login_host) + flb_sds_len(ctx->tenant_id)); if (!ctx->oauth_url) { flb_errno(); @@ -833,7 +955,8 @@ struct flb_azure_kusto *flb_azure_kusto_conf_create(struct flb_output_instance * return NULL; } flb_sds_snprintf(&ctx->oauth_url, flb_sds_alloc(ctx->oauth_url), - FLB_MSAL_AUTH_URL_TEMPLATE, ctx->tenant_id); + FLB_MSAL_AUTH_URL_TEMPLATE, ctx->login_host, + ctx->tenant_id); } ctx->resources = flb_calloc(1, sizeof(struct flb_azure_kusto_resources)); @@ -857,6 +980,21 @@ int flb_azure_kusto_conf_destroy(struct flb_azure_kusto *ctx) flb_plg_info(ctx->ins, "before exiting the plugin kusto conf destroy called"); + if (ctx->kusto_scope) { + flb_sds_destroy(ctx->kusto_scope); + ctx->kusto_scope = NULL; + } + + if (ctx->kusto_resource) { + flb_sds_destroy(ctx->kusto_resource); + ctx->kusto_resource = NULL; + } + + if (ctx->login_host) { + flb_sds_destroy(ctx->login_host); + ctx->login_host = NULL; + } + if (ctx->oauth_url) { flb_sds_destroy(ctx->oauth_url); ctx->oauth_url = NULL; diff --git a/plugins/out_azure_kusto/azure_msiauth.c b/plugins/out_azure_kusto/azure_msiauth.c index 884f850dd58..08593f6ab78 100644 --- a/plugins/out_azure_kusto/azure_msiauth.c +++ b/plugins/out_azure_kusto/azure_msiauth.c @@ -135,7 +135,9 @@ static flb_sds_t read_token_from_file(const char *token_file) 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) +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 *scope) { int ret; size_t b_sent; @@ -158,7 +160,7 @@ int flb_azure_workload_identity_token_get(struct flb_oauth2 *ctx, const char *to return -1; } - flb_info("[azure workload identity] after read token from file %s", federated_token); + flb_debug("[azure workload identity] federated token read successfully from file"); /* Build the form data for token exchange *before* creating the client */ body = flb_sds_create_size(4096); @@ -169,23 +171,48 @@ int flb_azure_workload_identity_token_get(struct flb_oauth2 *ctx, const char *to } body = flb_sds_cat(body, "client_id=", 10); + if (!body) { + goto body_error; + } body = flb_sds_cat(body, client_id, strlen(client_id)); - /* Use the correct grant_type and length for workload identity */ + if (!body) { + goto body_error; + } body = flb_sds_cat(body, "&grant_type=client_credentials", 30); + if (!body) { + goto body_error; + } body = flb_sds_cat(body, "&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer", 77); + if (!body) { + goto body_error; + } body = flb_sds_cat(body, "&client_assertion=", 18); + if (!body) { + goto body_error; + } body = flb_sds_cat(body, federated_token, flb_sds_len(federated_token)); - /* Use the correct scope and length for Kusto */ - body = flb_sds_cat(body, "&scope=https://help.kusto.windows.net/.default", 46); - if (!body) { - /* This check might be redundant if flb_sds_cat handles errors, but safe */ - flb_error("[azure workload identity] failed to build request body"); - flb_sds_destroy(federated_token); - return -1; + goto body_error; + } + /* Use the cloud-specific scope for Kusto */ + body = flb_sds_cat(body, "&scope=", 7); + if (!body) { + goto body_error; + } + body = flb_sds_cat(body, scope, strlen(scope)); + if (!body) { + goto body_error; } /* Get upstream connection to Azure AD token endpoint */ + goto body_ok; + +body_error: + flb_error("[azure workload identity] failed to build request body (OOM)"); + flb_sds_destroy(federated_token); + return -1; + +body_ok: u_conn = flb_upstream_conn_get(ctx->u); if (!u_conn) { flb_error("[azure workload identity] could not get an upstream connection"); @@ -213,8 +240,8 @@ int flb_azure_workload_identity_token_get(struct flb_oauth2 *ctx, const char *to /* c->body_buf = body; */ /* c->body_len = flb_sds_len(body); */ - /* Add a debug log to verify the body content just before sending */ - flb_debug("[azure workload identity] Sending request body (len=%zu): %s", flb_sds_len(body), body); + /* Log only the body length, not the content (body contains sensitive credentials) */ + flb_debug("[azure workload identity] sending token exchange request (body_len=%zu)", flb_sds_len(body)); /* Issue request */ ret = flb_http_do(c, &b_sent); diff --git a/plugins/out_azure_kusto/azure_msiauth.h b/plugins/out_azure_kusto/azure_msiauth.h index b36e0700a7d..af5233ce167 100644 --- a/plugins/out_azure_kusto/azure_msiauth.h +++ b/plugins/out_azure_kusto/azure_msiauth.h @@ -19,10 +19,12 @@ #include -/* MSAL authorization URL */ +/* MSAL authorization URL template: %s%s = optional client_id param, %s = resource URL */ #define FLB_AZURE_MSIAUTH_URL_TEMPLATE \ - "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2021-02-01%s%s&resource=https://api.kusto.windows.net" + "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2021-02-01%s%s&resource=%s" char *flb_azure_msiauth_token_get(struct flb_oauth2 *ctx); -int flb_azure_workload_identity_token_get(struct flb_oauth2 *ctx, const char *token_file, const char *client_id, const char *tenant_id); +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 *scope);