diff --git a/exporter/azuredataexplorerexporter/README.md b/exporter/azuredataexplorerexporter/README.md index b0024e90d34a..52d99ddaed44 100644 --- a/exporter/azuredataexplorerexporter/README.md +++ b/exporter/azuredataexplorerexporter/README.md @@ -63,6 +63,9 @@ exporters: # Set to "system" for system-assigned managed identity. # Set the MI client Id (GUID) for user-assigned managed identity. managed_identity_id: "z80da32c-108c-415c-a19e-643f461a677a" + # Workload identity authentication is enabled + # Set to true to use workload identity + use_workload_identity: true # Database for the logs db_name: "oteldb" # Metric table name diff --git a/exporter/azuredataexplorerexporter/adx_exporter.go b/exporter/azuredataexplorerexporter/adx_exporter.go index b9b8ada24dae..17370a50c99a 100644 --- a/exporter/azuredataexplorerexporter/adx_exporter.go +++ b/exporter/azuredataexplorerexporter/adx_exporter.go @@ -214,6 +214,7 @@ func createKcsb(config *Config, version string) *kusto.ConnectionStringBuilder { var kcsb *kusto.ConnectionStringBuilder isManagedIdentity := len(strings.TrimSpace(config.ManagedIdentityID)) > 0 isSystemManagedIdentity := strings.EqualFold(strings.TrimSpace(config.ManagedIdentityID), "SYSTEM") + isWorkloadIdentity := config.UseWorkloadIdentity // If the user has managed identity done, use it. For System managed identity use the MI as system switch { case !isManagedIdentity: @@ -222,6 +223,8 @@ func createKcsb(config *Config, version string) *kusto.ConnectionStringBuilder { kcsb = kusto.NewConnectionStringBuilder(config.ClusterURI).WithSystemManagedIdentity() case isManagedIdentity && !isSystemManagedIdentity: kcsb = kusto.NewConnectionStringBuilder(config.ClusterURI).WithUserManagedIdentity(config.ManagedIdentityID) + case isWorkloadIdentity: + kcsb = kusto.NewConnectionStringBuilder(config.ClusterURI).WithDefaultAzureCredential() } kcsb.SetConnectorDetails("OpenTelemetry", version, "", "", false, "", kusto.StringPair{Key: "isManagedIdentity", Value: strconv.FormatBool(isManagedIdentity)}) return kcsb diff --git a/exporter/azuredataexplorerexporter/config.go b/exporter/azuredataexplorerexporter/config.go index e6d92cbbaf5d..bc97e2b8fca1 100644 --- a/exporter/azuredataexplorerexporter/config.go +++ b/exporter/azuredataexplorerexporter/config.go @@ -24,6 +24,7 @@ type Config struct { ApplicationKey configopaque.String `mapstructure:"application_key"` TenantID string `mapstructure:"tenant_id"` ManagedIdentityID string `mapstructure:"managed_identity_id"` + UseWorkloadIdentity bool `mapstructure:"use_workload_identity"` Database string `mapstructure:"db_name"` MetricTable string `mapstructure:"metrics_table_name"` LogTable string `mapstructure:"logs_table_name"` @@ -41,14 +42,26 @@ func (adxCfg *Config) Validate() error { } isAppAuthEmpty := isEmpty(adxCfg.ApplicationID) || isEmpty(string(adxCfg.ApplicationKey)) || isEmpty(adxCfg.TenantID) isManagedAuthEmpty := isEmpty(adxCfg.ManagedIdentityID) + isWorkloadIdentityEmpty := !adxCfg.UseWorkloadIdentity isClusterURIEmpty := isEmpty(adxCfg.ClusterURI) // Cluster URI is the target ADX cluster if isClusterURIEmpty { return errors.New(`clusterURI config is mandatory`) } - // Parameters for AD App Auth or Managed Identity Auth are mandatory - if isAppAuthEmpty && isManagedAuthEmpty { - return errors.New(`either ["application_id" , "application_key" , "tenant_id"] or ["managed_identity_id"] are needed for auth`) + + // Parameters for AD App Auth, Managed Identity or Workload Identity Auth are mandatory + authMethods := 0 + if !isAppAuthEmpty { + authMethods++ + } + if !isManagedAuthEmpty { + authMethods++ + } + if !isWorkloadIdentityEmpty { + authMethods++ + } + if authMethods != 1 { + return errors.New(`only one of ["application_id" , "application_key" , "tenant_id"], ["managed_identity_id"], or ["use_workload_identity"] should be provided for auth`) } if !(adxCfg.IngestionType == managedIngestType || adxCfg.IngestionType == queuedIngestTest || isEmpty(adxCfg.IngestionType)) { diff --git a/exporter/azuredataexplorerexporter/config_test.go b/exporter/azuredataexplorerexporter/config_test.go index 2c6667c3db61..451ac835085b 100644 --- a/exporter/azuredataexplorerexporter/config_test.go +++ b/exporter/azuredataexplorerexporter/config_test.go @@ -45,7 +45,7 @@ func TestLoadConfig(t *testing.T) { }, { id: component.NewIDWithName(metadata.Type, "2"), - errorMessage: `either ["application_id" , "application_key" , "tenant_id"] or ["managed_identity_id"] are needed for auth`, + errorMessage: `only one of ["application_id" , "application_key" , "tenant_id"], ["managed_identity_id"], or ["use_workload_identity"] should be provided for auth`, }, { id: component.NewIDWithName(metadata.Type, "3"), @@ -111,6 +111,18 @@ func TestLoadConfig(t *testing.T) { }, }, }, + { + id: component.NewIDWithName(metadata.Type, "9"), + expected: &Config{ + ClusterURI: "https://CLUSTER.kusto.windows.net", + Database: "oteldb", + MetricTable: "OTELMetrics", + LogTable: "OTELLogs", + TraceTable: "OTELTraces", + UseWorkloadIdentity: true, + IngestionType: queuedIngestTest, + }, + }, } for _, tt := range tests { diff --git a/exporter/azuredataexplorerexporter/testdata/config.yaml b/exporter/azuredataexplorerexporter/testdata/config.yaml index 0fdf9fbe0a13..908b1bcdadba 100644 --- a/exporter/azuredataexplorerexporter/testdata/config.yaml +++ b/exporter/azuredataexplorerexporter/testdata/config.yaml @@ -145,4 +145,9 @@ azuredataexplorer/8: enabled: true initial_interval: 10s max_interval: 60s - max_elapsed_time: 10m \ No newline at end of file + max_elapsed_time: 10m +azuredataexplorer/9: + # Kusto cluster uri + cluster_uri: "https://CLUSTER.kusto.windows.net" + # weather to use workload identity + use_workload_identity: true